diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2014-12-10 10:20:26 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2014-12-10 10:20:26 +0200 |
commit | 5e9eb843f6ccadfb47fa603260783425da9e7805 (patch) | |
tree | 3778f76de37f5258a07a8fae0e58a843b8a49f1d | |
parent | 20e3aedeb7df742c38276fb41cae8f3eb027b6dd (diff) |
Add rules
g++-4.9 -std=c++11 -g -I.. -o bd bd.cxx target.cxx native.cxx rule.cxx cxx/rule.cxx cxx/target.cxx process.cxx timestamp.cxx path.cxx
-rw-r--r-- | build/bd.cxx | 167 | ||||
-rw-r--r-- | build/cxx/rule | 40 | ||||
-rw-r--r-- | build/cxx/rule.cxx | 260 | ||||
-rw-r--r-- | build/cxx/target | 52 | ||||
-rw-r--r-- | build/cxx/target.cxx | 20 | ||||
-rw-r--r-- | build/native | 31 | ||||
-rw-r--r-- | build/native.cxx | 15 | ||||
-rw-r--r-- | build/rule | 39 | ||||
-rw-r--r-- | build/rule.cxx | 72 | ||||
-rw-r--r-- | build/target | 110 | ||||
-rw-r--r-- | build/target.cxx | 38 | ||||
-rw-r--r-- | build/timestamp | 4 | ||||
-rw-r--r-- | build/timestamp.cxx | 37 |
13 files changed, 792 insertions, 93 deletions
diff --git a/build/bd.cxx b/build/bd.cxx index 03d0aa4..e48de21 100644 --- a/build/bd.cxx +++ b/build/bd.cxx @@ -8,48 +8,82 @@ #include <cstdlib> // exit #include <cassert> #include <iostream> +#include <typeinfo> #include <system_error> -#include <build/process> -#include <build/timestamp> #include <build/target> +#include <build/rule> +#include <build/process> using namespace std; namespace build { bool - update (target& t) + match (target& t) { - auto tts (path_timestamp (t.name ())); - cout << t.name () << ": " << tts << endl; + if (!t.recipe ()) + { + for (auto ti (&t.type_id ()); + ti != nullptr && !t.recipe (); + ti = ti->base) + { + for (auto rs (rules.equal_range (ti->id)); + rs.first != rs.second; + ++rs.first) + { + const rule& ru (rs.first->second); + + if (recipe re = ru.match (t)) + { + t.recipe (re); + break; + } + } + } + + if (!t.recipe ()) + { + cerr << "error: no rule to build target " << t << endl; + return false; + } + } - bool u (tts == timestamp_nonexistent); for (target& p: t.prerequisites ()) { - if (!update (p)) + if (!match (p)) + { + cerr << "info: required by " << t << endl; return false; + } + } + + return true; + } + + target_state + update (target& t) + { + assert (t.state () == target_state::unknown); - if (!u) + target_state ts; + + for (target& p: t.prerequisites ()) + { + if (p.state () == target_state::unknown) { - auto tps (path_timestamp (p.name ())); + p.state ((ts = update (p))); - if (tts <= tps) // Note: not just less. - { - cout << t.name () << " vs " << p.name () << ": " << (tps - tts) - << " ahead" << endl; - u = true; - } + if (ts == target_state::failed) + return ts; } } - if (!u) // Nothing to do. - return true; - try { - auto r (t.rule ()); - return r != 0 ? r (t) : true; + t.state ((ts = t.recipe () (t))); + assert (ts != target_state::unknown); + return ts; } catch (const process_error& e) { @@ -63,52 +97,13 @@ namespace build } } -using namespace build; +#include <build/native> -bool -cxx_compile_rule (target& t) -{ - const targets& ps (t.prerequisites ()); - - //@@ TODO: assuming .cxx is first. - // - const target& p0 (ps[0]); - const char* args[] { - "g++-4.9", - "-std=c++11", - "-I..", - "-c", - "-o", t.name ().c_str (), - p0.name ().c_str (), - nullptr}; - - cerr << "c++ " << t.name () << endl; - - try - { - process pr (args); - return pr.wait (); - } - catch (const process_error& e) - { - cerr << "error: unable to execute '" << args[0] << "': " << - e.what () << endl; - - if (e.child ()) - throw; // Let our caller terminate us quickly without causing a scene. +#include <build/cxx/target> +#include <build/cxx/rule> - return false; - } -} - -bool -cxx_link_rule (target& t) -{ - const targets& ps (t.prerequisites ()); - cerr << "ld " << t.name () << endl; - return true; -} +using namespace build; int main (int argc, char* argv[]) @@ -117,20 +112,52 @@ main (int argc, char* argv[]) // tzset (); + cxx::link cxx_link; + rules.emplace (typeid (exe), cxx_link); + + cxx::compile cxx_compile; + rules.emplace (typeid (obj), cxx_compile); + + default_path_rule path_exists; + rules.emplace (typeid (path_target), path_exists); + + // + // + using namespace build::cxx; + exe bd ("bd"); - obj bd_o ("bd.o"); + obj bd_o ("bd"); bd.prerequisite (bd_o); - bd.rule (&cxx_link_rule); - cxx bd_cxx ("bd.cxx"); + cxx::cxx bd_cxx ("bd"); + bd_cxx.path (path ("bd.cxx")); + hxx target ("target"); + target.path (path ("target")); + bd_o.prerequisite (bd_cxx); bd_o.prerequisite (target); - bd_o.rule (&cxx_compile_rule); - if (!update (bd)) + // + // + if (!match (bd)) + return 1; // Diagnostics has already been issued. + + switch (update (bd)) { - cerr << "unable to update '" << bd.name () << "'" << endl; - return 1; + case target_state::uptodate: + { + cerr << "info: target " << bd << " is up to date" << endl; + break; + } + case target_state::updated: + break; + case target_state::failed: + { + cerr << "error: failed to update target " << bd << endl; + return 1; + } + case target_state::unknown: + assert (false); } } diff --git a/build/cxx/rule b/build/cxx/rule new file mode 100644 index 0000000..d4412e4 --- /dev/null +++ b/build/cxx/rule @@ -0,0 +1,40 @@ +// file : build/cxx/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_CXX_RULE +#define BUILD_CXX_RULE + +#include <build/rule> + +namespace build +{ + namespace cxx + { + // @@ Can't we do match(obj&) and then registration code extracts + // that. And no virtuals. + // + + class compile: public rule + { + public: + virtual recipe + match (target&) const; + + static target_state + update (target&); + }; + + class link: public rule + { + public: + virtual recipe + match (target&) const; + + static target_state + update (target&); + }; + } +} + +#endif // BUILD_CXX_RULE diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx new file mode 100644 index 0000000..9f8f1ae --- /dev/null +++ b/build/cxx/rule.cxx @@ -0,0 +1,260 @@ +// file : build/cxx/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include <build/cxx/rule> + +#include <vector> +#include <iostream> + +#include <build/native> +#include <build/process> +#include <build/timestamp> + +#include <build/cxx/target> + +using namespace std; + +namespace build +{ + namespace cxx + { + // compile + // + recipe compile:: + match (target& t) const + { + // @@ TODO: + // + // - check prerequisites: single source file + // - check prerequisites: the rest are headers (issue warning at v=1?) + // - if path already assigned, verify extension + // + // @@ Q: + // + // - if there is no .cxx, are we going to check if the one derived + // from target exist or can be built? If we do that, then it + // probably makes sense to try other rules first (two passes). + // + // - Wouldn't it make sense to cache source file? Careful: unloading + // of dependency info. + // + + // See if we have a source file. + // + const cxx* s (nullptr); + for (const target& p: t.prerequisites ()) + { + if ((s = dynamic_cast<const cxx*> (&p)) != nullptr) + break; + } + + if (s == nullptr) + return recipe (); + + // Derive object file name from target name. + // + obj& o (dynamic_cast<obj&> (t)); + + if (o.path ().empty ()) + o.path (path (o.name () + ".o")); + + return recipe (&update); + } + + target_state compile:: + update (target& t) + { + obj& o (dynamic_cast<obj&> (t)); + timestamp mt (o.mtime ()); + + bool u (mt == timestamp_nonexistent); + const cxx* s (nullptr); + + for (const target& p: t.prerequisites ()) + { + // Assume all our prerequisites are mtime-based (checked in + // match()). + // + if (!u) + { + const auto& mtp (dynamic_cast<const mtime_target&> (p)); + timestamp mp (mtp.mtime ()); + + // What do we do if timestamps are equal? This can happen, for + // example, on filesystems that don't have subsecond resolution. + // There is not much we can do here except detect the case where + // the prerequisite was updated in this run which means the + // target must be out of date. + // + if (mt < mp || mt == mp && mtp.state () == target_state::updated) + u = true; + } + + if (s == nullptr) + s = dynamic_cast<const cxx*> (&p); + + if (u && s != nullptr) + break; + } + + if (!u) + return target_state::uptodate; + + const char* args[] = { + "g++-4.9", + "-std=c++11", + "-I..", + "-c", + "-o", o.path ().string ().c_str (), + s->path ().string ().c_str (), + nullptr}; + + cerr << "c++ " << *s << endl; + + try + { + process pr (args); + + if (!pr.wait ()) + return target_state::failed; + + // Should we go to the filesystem and get the new mtime? We + // know the file has been modified, so instead just use the + // current clock time. It has the advantage of having the + // subseconds precision. + // + o.mtime (system_clock::now ()); + return target_state::updated; + } + catch (const process_error& e) + { + cerr << "error: unable to execute '" << args[0] << "': " << + e.what () << endl; + + if (e.child ()) + throw; // Let caller terminate us quickly without causing a scene. + + return target_state::failed; + } + } + + // link + // + recipe link:: + match (target& t) const + { + // @@ TODO: + // + // - check prerequisites: object files, libraries + // - if path already assigned, verify extension + // + // @@ Q: + // + // - if there is no .o, are we going to check if the one derived + // from target exist or can be built? If we do that, then it + // probably makes sense to try other rules first (two passes). + // What if there is a library. Probably ok if .a, not is .so. + // + + // See if we have at least one object file. + // + const obj* o (nullptr); + for (const target& p: t.prerequisites ()) + { + if ((o = dynamic_cast<const obj*> (&p)) != nullptr) + break; + } + + if (o == nullptr) + return recipe (); + + // Derive executable file name from target name. + // + exe& e (dynamic_cast<exe&> (t)); + + if (e.path ().empty ()) + e.path (path (e.name ())); + + return recipe (&update); + } + + target_state link:: + update (target& t) + { + // @@ Q: + // + // - what are we doing with libraries? + // + + exe& e (dynamic_cast<exe&> (t)); + timestamp mt (e.mtime ()); + + bool u (mt == timestamp_nonexistent); + + for (const target& p: t.prerequisites ()) + { + // Assume all our prerequisites are mtime-based (checked in + // match()). + // + const auto& mtp (dynamic_cast<const mtime_target&> (p)); + timestamp mp (mtp.mtime ()); + + // What do we do if timestamps are equal? This can happen, for + // example, on filesystems that don't have subsecond resolution. + // There is not much we can do here except detect the case where + // the prerequisite was updated in this run which means the + // target must be out of date. + // + if (mt < mp || mt == mp && mtp.state () == target_state::updated) + { + u = true; + break; + } + } + + if (!u) + return target_state::uptodate; + + vector<const char*> args {"g++-4.9", "-std=c++11", "-o"}; + + args.push_back (e.path ().string ().c_str ()); + + for (const target& p: t.prerequisites ()) + { + const obj& o (dynamic_cast<const obj&> (p)); + args.push_back (o.path ().string ().c_str ()); + } + + args.push_back (nullptr); + + cerr << "ld " << e << endl; + + try + { + process pr (args.data ()); + + if (!pr.wait ()) + return target_state::failed; + + // Should we go to the filesystem and get the new mtime? We + // know the file has been modified, so instead just use the + // current clock time. It has the advantage of having the + // subseconds precision. + // + e.mtime (system_clock::now ()); + return target_state::updated; + } + catch (const process_error& e) + { + cerr << "error: unable to execute '" << args[0] << "': " << + e.what () << endl; + + if (e.child ()) + throw; // Let caller terminate us quickly without causing a scene. + + return target_state::failed; + } + } + } +} diff --git a/build/cxx/target b/build/cxx/target new file mode 100644 index 0000000..fe17c36 --- /dev/null +++ b/build/cxx/target @@ -0,0 +1,52 @@ +// file : build/cxx/target -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_CXX_TARGET +#define BUILD_CXX_TARGET + +#include <build/target> + +namespace build +{ + namespace cxx + { + class hxx: public file + { + public: + using file::file; + + public: virtual const type_info& type_id () const {return ti_;} + protected: static const type_info ti_; + }; + + class ixx: public file + { + public: + using file::file; + + public: virtual const type_info& type_id () const {return ti_;} + protected: static const type_info ti_; + }; + + class txx: public file + { + public: + using file::file; + + public: virtual const type_info& type_id () const {return ti_;} + protected: static const type_info ti_; + }; + + class cxx: public file + { + public: + using file::file; + + public: virtual const type_info& type_id () const {return ti_;} + protected: static const type_info ti_; + }; + } +} + +#endif // BUILD_CXX_TARGET diff --git a/build/cxx/target.cxx b/build/cxx/target.cxx new file mode 100644 index 0000000..7171e30 --- /dev/null +++ b/build/cxx/target.cxx @@ -0,0 +1,20 @@ +// file : build/cxx/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include <build/cxx/target> + +using namespace std; + +namespace build +{ + namespace cxx + { + using type_info = target::type_info; + + const type_info hxx::ti_ {typeid (hxx), "hxx", &file::ti_}; + const type_info ixx::ti_ {typeid (ixx), "ixx", &file::ti_}; + const type_info txx::ti_ {typeid (txx), "txx", &file::ti_}; + const type_info cxx::ti_ {typeid (cxx), "cxx", &file::ti_}; + } +} diff --git a/build/native b/build/native new file mode 100644 index 0000000..29ad653 --- /dev/null +++ b/build/native @@ -0,0 +1,31 @@ +// file : build/native -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_NATIVE +#define BUILD_NATIVE + +#include <build/target> + +namespace build +{ + class exe: public file + { + public: + using file::file; + + public: virtual const type_info& type_id () const {return ti_;} + protected: static const type_info ti_; + }; + + class obj: public file + { + public: + using file::file; + + public: virtual const type_info& type_id () const {return ti_;} + protected: static const type_info ti_; + }; +} + +#endif // BUILD_NATIVE diff --git a/build/native.cxx b/build/native.cxx new file mode 100644 index 0000000..6130578 --- /dev/null +++ b/build/native.cxx @@ -0,0 +1,15 @@ +// file : build/native.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include <build/native> + +using namespace std; + +namespace build +{ + using type_info = target::type_info; + + const type_info exe::ti_ {typeid (exe), "exe", &file::ti_}; + const type_info obj::ti_ {typeid (obj), "obj", &file::ti_}; +} diff --git a/build/rule b/build/rule new file mode 100644 index 0000000..6bb58fe --- /dev/null +++ b/build/rule @@ -0,0 +1,39 @@ +// file : build/rule -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#ifndef BUILD_RULE +#define BUILD_RULE + +#include <typeindex> +#include <functional> // reference_wrapper +#include <unordered_map> + +#include <build/target> + +namespace build +{ + class rule + { + public: + virtual recipe + match (target&) const = 0; + }; + + typedef std::unordered_multimap<std::type_index, + std::reference_wrapper<rule>> rule_map; + + extern rule_map rules; + + class default_path_rule: public rule + { + public: + virtual recipe + match (target&) const; + + static target_state + update (target&); + }; +} + +#endif // BUILD_RULE diff --git a/build/rule.cxx b/build/rule.cxx new file mode 100644 index 0000000..fe4c4b2 --- /dev/null +++ b/build/rule.cxx @@ -0,0 +1,72 @@ +// file : build/rule.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include <build/rule> + +#include <iostream> + +using namespace std; + +namespace build +{ + rule_map rules; + + // default_path_rule + // + recipe default_path_rule:: + match (target& t) const + { + // @@ TODO: + // + // - need to assign path somehow. Get (potentially several) + // extensions from target type? Maybe target type should + // generate a list of potential paths that we can try here. + // + + path_target& pt (dynamic_cast<path_target&> (t)); + + return pt.mtime () != timestamp_nonexistent ? &update : nullptr; + } + + target_state default_path_rule:: + update (target& t) + { + // Make sure the target is not older than any of its prerequisites. + // + path_target& pt (dynamic_cast<path_target&> (t)); + timestamp mt (pt.mtime ()); + + for (const target& p: t.prerequisites ()) + { + // If this is an mtime-based target, then simply compare timestamps. + // + if (auto mtp = dynamic_cast<const mtime_target*> (&p)) + { + if (mt < mtp->mtime ()) + { + cerr << "error: no rule to update target " << t << endl + << "info: prerequisite " << p << " is ahead of " << t << + " by " << (mtp->mtime () - mt) << endl; + + return target_state::failed; + } + } + else + { + // Otherwise we assume the prerequisite is newer if it was updated. + // + if (p.state () == target_state::updated) + { + cerr << "error: no rule to update target " << t << endl + << "info: prerequisite " << p << " is ahead of " << t << + " because it was updated" << endl; + + return target_state::failed; + } + } + } + + return target_state::uptodate; + } +} diff --git a/build/target b/build/target index 4319ccb..3ef3192 100644 --- a/build/target +++ b/build/target @@ -7,11 +7,21 @@ #include <string> #include <vector> -#include <functional> // std::reference_wrapper +#include <functional> // function, reference_wrapper +#include <typeindex> +#include <iosfwd> +#include <cassert> + +#include <build/path> +#include <build/timestamp> namespace build { class target; + + enum class target_state {unknown, uptodate, updated, failed}; + typedef std::function<target_state (target&)> recipe; + typedef std::vector<std::reference_wrapper<target>> targets; class target @@ -32,42 +42,112 @@ namespace build prerequisite (target& t) {prerequisites_.push_back (t);} public: - typedef bool (*rule_type) (target&); + typedef build::recipe recipe_type; - rule_type - rule () const {return rule_;} + const recipe_type& + recipe () const {return recipe_;} void - rule (rule_type r) {rule_ = r;} + recipe (recipe_type r) {assert (!recipe_); recipe_ = r;} + + public: + target_state + state () const {return state_;} + + void + state (target_state s) {state_ = s;} private: target (const target&) = delete; target& operator= (const target&) = delete; + public: + struct type_info + { + std::type_index id; + const char* name; + const type_info* base; + }; + + virtual const type_info& + type_id () const = 0; + + protected: + static const type_info ti_; + private: std::string name_; targets prerequisites_; - rule_type rule_ {0}; + recipe_type recipe_; + target_state state_ {target_state::unknown}; }; - class exe: public target - { - using target::target; - }; + std::ostream& + operator<< (std::ostream&, const target&); - class obj: public target + // Modification time-based target. + // + class mtime_target: public target { + public: using target::target; + + timestamp + mtime () const + { + if (mtime_ == timestamp_unknown) + mtime_ = load_mtime (); + + return mtime_; + } + + void + mtime (timestamp mt) {mtime_ = mt;} + + protected: + virtual timestamp + load_mtime () const = 0; + + protected: static const type_info ti_; + + private: + mutable timestamp mtime_ {timestamp_unknown}; }; - class hxx: public target + // Filesystem path-bases target. + // + class path_target: public mtime_target { - using target::target; + public: + using mtime_target::mtime_target; + + typedef build::path path_type; + + const path_type& + path () const {return path_;} + + void + path (path_type p) {assert (path_.empty ()); path_ = p;} + + protected: + virtual timestamp + load_mtime () const; + + protected: static const type_info ti_; + + private: + path_type path_; }; - class cxx: public target + // File target. + // + class file: public path_target { - using target::target; + public: + using path_target::path_target; + + public: virtual const type_info& type_id () const {return ti_;} + protected: static const type_info ti_; }; } diff --git a/build/target.cxx b/build/target.cxx new file mode 100644 index 0000000..e3e3d4d --- /dev/null +++ b/build/target.cxx @@ -0,0 +1,38 @@ +// file : build/target.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC +// license : MIT; see accompanying LICENSE file + +#include <build/target> + +#include <ostream> + +using namespace std; + +namespace build +{ + // target + // + ostream& + operator<< (ostream& os, const target& t) + { + return os << t.type_id ().name << '{' << t.name () << '}'; + } + + // path_target + // + timestamp path_target:: + load_mtime () const + { + assert (!path_.empty ()); + return path_mtime (path_); + } + + using type_info = target::type_info; + + const type_info target::ti_ {typeid (target), "target", nullptr}; + const type_info mtime_target::ti_ { + typeid (mtime_target), "mtime_target", &target::ti_}; + const type_info path_target::ti_ { + typeid (path_target), "path_target", &mtime_target::ti_}; + const type_info file::ti_ {typeid (file), "file", &path_target::ti_}; +} diff --git a/build/timestamp b/build/timestamp index 6ed2f2c..2386125 100644 --- a/build/timestamp +++ b/build/timestamp @@ -9,6 +9,8 @@ #include <string> #include <iosfwd> +#include <build/path> + namespace build { // On all three main platforms that we target (GNU/Linux, Windows (both @@ -46,7 +48,7 @@ namespace build // std::system_error. // timestamp - path_timestamp (const std::string&); + path_mtime (const path&); }; #endif // BUILD_TIMESTAMP diff --git a/build/timestamp.cxx b/build/timestamp.cxx index db8efb2..0ad3f3f 100644 --- a/build/timestamp.cxx +++ b/build/timestamp.cxx @@ -41,10 +41,10 @@ namespace build constexpr int nsec (...) {return 0;} timestamp - path_timestamp (const std::string& p) + path_mtime (const path& p) { struct stat s; - if (stat (p.c_str (), &s) != 0) + if (stat (p.string ().c_str (), &s) != 0) { if (errno == ENOENT || errno == ENOTDIR) return timestamp_nonexistent; @@ -108,18 +108,37 @@ namespace build time_t t (system_clock::to_time_t (ts)); const char* fmt (nullptr); + const char* unt ("nanoseconds"); if (t >= 365 * 12 * 24 * 60 * 60) + { fmt = "%Y-%m-%d %H:%M:%S"; + unt = "years"; + } else if (t >= 12 * 24 * 60* 60) + { fmt = "%m-%d %H:%M:%S"; + unt = "months"; + } else if (t >= 24 * 60* 60) + { fmt = "%d %H:%M:%S"; + unt = "days"; + } else if (t >= 60 * 60) + { fmt = "%H:%M:%S"; + unt = "hours"; + } else if (t >= 60) + { fmt = "%M:%S"; + unt = "minutes"; + } else if (t >= 1) + { fmt = "%S"; + unt = "seconds"; + } if (fmt != nullptr) { @@ -129,7 +148,7 @@ namespace build char buf[20]; // YYYY-MM-DD HH:MM:SS\0 if (strftime (buf, sizeof (buf), fmt, &tm) == 0) - return os << "<beyond 9999 years>"; + return os << "<beyond 9999>"; os << buf; } @@ -141,10 +160,14 @@ namespace build if (ns != nanoseconds::zero ()) { - os << '.'; - os.width (9); - os.fill ('0'); - os << ns.count (); + if (fmt != nullptr) + { + os << '.'; + os.width (9); + os.fill ('0'); + } + + os << ns.count () << ' ' << unt; } else if (fmt == 0) os << '0'; |