From be14801929cf2a6caced87df034ae12a85f42aa6 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 30 Nov 2016 17:32:43 +0200 Subject: Add support for typed/untyped concatenated expansion --- build/root.build | 2 +- build2/algorithm.cxx | 2 +- build2/buildfile | 1 + build2/cc/pkgconfig.cxx | 2 +- build2/config/operation.cxx | 2 +- build2/function | 21 +- build2/function.cxx | 20 +- build2/functions-builtin.cxx | 6 + build2/functions-path.cxx | 115 ++++- build2/functions-string.cxx | 39 ++ build2/name | 34 +- build2/name.cxx | 54 ++- build2/parser.cxx | 662 +++++++++++++++++++---------- build2/test/script/parser.cxx | 2 +- build2/variable | 69 +-- build2/variable.cxx | 68 ++- build2/variable.ixx | 23 +- build2/variable.txx | 40 +- tests/function/path/testscript | 4 +- tests/test/script/runner/cleanup.test | 2 +- tests/test/script/runner/status.test | 2 +- unit-tests/function/buildfile | 4 +- unit-tests/test/script/parser/buildfile | 4 +- unit-tests/test/script/parser/include.test | 4 +- unit-tests/test/script/parser/scope.test | 4 +- 25 files changed, 818 insertions(+), 368 deletions(-) create mode 100644 build2/functions-string.cxx diff --git a/build/root.build b/build/root.build index 7fb4a2c..621eaf0 100644 --- a/build/root.build +++ b/build/root.build @@ -11,7 +11,7 @@ ixx{*}: extension = ixx txx{*}: extension = txx cxx{*}: extension = cxx -cxx.poptions =+ -I$out_root -I$src_root +cxx.poptions =+ "-I$out_root" "-I$src_root" # Load the cli module but only if it's available. This way a distribution # that includes pre-generated files can be built without installing cli. diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index 245e7ad..910f073 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -44,7 +44,7 @@ namespace build2 fail << "unknown target type " << n.type << " in name " << n; if (!n.dir.empty ()) - n.dir.normalize (); + n.dir.normalize (); //@@ NORM (empty) // @@ OUT: for now we assume the prerequisite's out is undetermined. // Would need to pass a pair of names. diff --git a/build2/buildfile b/build2/buildfile index 98494d8..351c16f 100644 --- a/build2/buildfile +++ b/build2/buildfile @@ -18,6 +18,7 @@ exe{b}: \ { cxx}{ functions-builtin } \ { cxx}{ functions-path } \ { cxx}{ functions-process-path } \ + { cxx}{ functions-string } \ {hxx cxx}{ lexer } \ {hxx cxx}{ module } \ {hxx ixx cxx}{ name } \ diff --git a/build2/cc/pkgconfig.cxx b/build2/cc/pkgconfig.cxx index 580812c..a20014a 100644 --- a/build2/cc/pkgconfig.cxx +++ b/build2/cc/pkgconfig.cxx @@ -314,7 +314,7 @@ namespace build2 continue; } - libs.push_back (name (move (o), false)); + libs.push_back (name (move (o))); continue; } diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx index 0aa554f..8e288da 100644 --- a/build2/config/operation.cxx +++ b/build2/config/operation.cxx @@ -50,7 +50,7 @@ namespace build2 ofs << "# Created automatically by the config module." << endl << "#" << endl << "src_root = "; - to_stream (ofs, name (src_root, false), true, '@'); // Quote. + to_stream (ofs, name (src_root), true, '@'); // Quote. ofs << endl; ofs.close (); diff --git a/build2/function b/build2/function index 6397720..f824f9c 100644 --- a/build2/function +++ b/build2/function @@ -145,7 +145,23 @@ namespace build2 erase (iterator i) {map_.erase (i);} value - call (const string& name, vector_view args, const location&) const; + call (const string& name, vector_view args, const location& l) const + { + return call (name, args, l, true).first; + } + + // As above but do not fail if no match was found (but still do if the + // match is ambiguous). Instead return an indication of whether the call + // was made. Used to issue custom diagnostics when calling internal + // functions. + // + pair + try_call (const string& name, + vector_view args, + const location& l) const + { + return call (name, args, l, false); + } iterator begin () {return map_.begin ();} @@ -160,6 +176,9 @@ namespace build2 end () const {return map_.end ();} private: + pair + call (const string&, vector_view, const location&, bool fail) const; + map_type map_; }; diff --git a/build2/function.cxx b/build2/function.cxx index bede978..28d4638 100644 --- a/build2/function.cxx +++ b/build2/function.cxx @@ -73,8 +73,11 @@ namespace build2 return i; } - value function_map:: - call (const string& name, vector_view args, const location& loc) const + pair function_map:: + call (const string& name, + vector_view args, + const location& loc, + bool fa) const { auto print_call = [&name, &args] (ostream& os) { @@ -157,13 +160,13 @@ namespace build2 { case 1: { - // Print the call location if the function fails. + // Print the call location in case the function fails. // auto g ( make_exception_guard ( - [&loc, &print_call] () + [fa, &loc, &print_call] () { - if (verb != 0) + if (fa && verb != 0) { diag_record dr (info (loc)); dr << "while calling "; print_call (dr.os); @@ -171,10 +174,13 @@ namespace build2 })); auto f (r.back ()); - return f->impl (move (args), *f); + return make_pair (f->impl (move (args), *f), true); } case 0: { + if (!fa) + return make_pair (value (nullptr), false); + // No match. // diag_record dr; @@ -298,6 +304,7 @@ namespace build2 void builtin_functions (); // functions-builtin.cxx void path_functions (); // functions-path.cxx void process_path_functions (); // functions-process-path.cxx + void string_functions (); // functions-string.cxx struct functions_init { @@ -306,6 +313,7 @@ namespace build2 builtin_functions (); path_functions (); process_path_functions (); + string_functions (); } }; diff --git a/build2/functions-builtin.cxx b/build2/functions-builtin.cxx index 448bb53..0fe6135 100644 --- a/build2/functions-builtin.cxx +++ b/build2/functions-builtin.cxx @@ -20,5 +20,11 @@ namespace build2 f["empty"] = [](value v) {return v.empty ();}; f["identity"] = [](value* v) {return move (*v);}; + + // string + // + f["string"] = [](bool b) {return b ? "true" : "false";}; + f["string"] = [](uint64_t i) {return to_string (i);}; + f["string"] = [](name n) {return to_string (n);}; } } diff --git a/build2/functions-path.cxx b/build2/functions-path.cxx index c97518c..e21609c 100644 --- a/build2/functions-path.cxx +++ b/build2/functions-path.cxx @@ -20,33 +20,118 @@ namespace build2 fail << "invalid path: '" << e.path << "'" << endf; } + static value + concat_path_string (path l, string sr) + { + if (path::traits::is_separator (sr[0])) // '\0' if empty. + { + sr.erase (0, 1); + path pr (move (sr)); + pr.canonicalize (); // Convert to canonical directory separators. + + // If RHS is syntactically a directory (ends with a trailing slash), + // then return it as dir_path, not path. + // + if (pr.to_directory () || pr.empty ()) + return value ( + path_cast (move (l)) /= path_cast (move (pr))); + else + l /= pr; + } + else + l += sr; + + return value (move (l)); + } + + static value + concat_dir_path_string (dir_path l, string sr) + { + if (path::traits::is_separator (sr[0])) // '\0' if empty. + { + sr.erase (0, 1); + path pr (move (sr)); + pr.canonicalize (); // Convert to canonical directory separators. + + // If RHS is syntactically a directory (ends with a trailing slash), + // then return it as dir_path, not path. + // + if (pr.to_directory () || pr.empty ()) + l /= path_cast (move (pr)); + else + return value (path_cast (move (l)) /= pr); + } + else + l += sr; + + return value (move (l)); + } + void path_functions () { function_family f ("path", &path_thunk); + // string + // + f["string"] = [](path p) {return move (p).string ();}; + f["string"] = [](dir_path p) {return move (p).string ();}; + + f["string"] = [](paths v) + { + strings r; + for (auto& p: v) + r.push_back (move (p).string ()); + return r; + }; + + f["string"] = [](dir_paths v) + { + strings r; + for (auto& p: v) + r.push_back (move (p).string ()); + return r; + }; + // normalize // f["normalize"] = [](path p) {p.normalize (); return p;}; - f["normalize"] = [](paths v) {for (auto& p: v) p.normalize (); return v;}; - f["normalize"] = [](dir_path p) {p.normalize (); return p;}; + + f["normalize"] = [](paths v) {for (auto& p: v) p.normalize (); return v;}; f["normalize"] = [](dir_paths v) {for (auto& p: v) p.normalize (); return v;}; f[".normalize"] = [](names ns) + { + // For each path decide based on the presence of a trailing slash + // whether it is a directory. Return as untyped list of (potentially + // mixed) paths. + // + for (name& n: ns) { - // For each path decide based on the presence of a trailing slash - // whether it is a directory. Return as untyped list of (potentially - // mixed) paths. - // - for (name& n: ns) - { - if (n.directory ()) - n.dir.normalize (); - else - n.value = convert (move (n)).normalize ().string (); - } - return ns; - }; + if (n.directory ()) + n.dir.normalize (); + else + n.value = convert (move (n)).normalize ().string (); + } + return ns; + }; + + // Path-specific overloads from builtins. + // + function_family b ("builtin", &path_thunk); + + b[".concat"] = &concat_path_string; + b[".concat"] = &concat_dir_path_string; + + b[".concat"] = [](path l, names ur) + { + return concat_path_string (move (l), convert (move (ur))); + }; + + b[".concat"] = [](dir_path l, names ur) + { + return concat_dir_path_string (move (l), convert (move (ur))); + }; } } diff --git a/build2/functions-string.cxx b/build2/functions-string.cxx new file mode 100644 index 0000000..1bf7aa8 --- /dev/null +++ b/build2/functions-string.cxx @@ -0,0 +1,39 @@ +// file : build2/functions-string.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include + +using namespace std; + +namespace build2 +{ + void + string_functions () + { + function_family f ("string"); + + f["string"] = [](string s) {return s;}; + f["string"] = [](strings v) {return v;}; + + // String-specific overloads from builtins. + // + function_family b ("builtin"); + + b[".concat"] = [](string l, string r) {l += r; return l;}; + + b[".concat"] = [](string l, names ur) + { + l += convert (move (ur)); + return l; + }; + + b[".concat"] = [](names ul, string r) + { + string l (convert (move (ul))); + l += r; + return l; + }; + } +} diff --git a/build2/name b/build2/name index e079c9f..ffddf00 100644 --- a/build2/name +++ b/build2/name @@ -23,15 +23,12 @@ namespace build2 // a project. If the project name is empty, then it means the name is in a // project other than our own (e.g., it is installed). // + // A type or project can only be specified if either directory or value are + // not empty. + // // If pair is not '\0', then this name and the next in the list form a // pair. Can be used as a bool flag. // - // The original flag indicates whether this is the original name (e.g., came - // from the buildfile) or if it is the result of reversing a typed value. - // Original names have stricter representation requirements since we don't - // know what they actually mean (e.g., is s/foo/bar/ really a directory or - // a sed script). - // struct name { const string* proj = nullptr; // Points to project_name_pool. @@ -39,11 +36,10 @@ namespace build2 string type; string value; char pair = '\0'; - bool original = true; name () = default; - name (string v, bool o): value (move (v)), original (o) {} - name (dir_path d, bool o): dir (move (d)), original (o) {} + name (string v): value (move (v)) {} + name (dir_path d): dir (move (d)) {} name (string t, string v): type (move (t)), value (move (v)) {} name (dir_path d, string t, string v) @@ -66,6 +62,8 @@ namespace build2 bool untyped () const {return type.empty ();} + // Note: if dir and value are empty then so should be proj and type. + // bool empty () const {return dir.empty () && value.empty ();} @@ -101,6 +99,11 @@ namespace build2 inline bool operator< (const name& x, const name& y) {return x.compare (y) < 0;} + // Return string representation of a name. + // + string + to_string (const name&); + // Serialize the name to the stream. If requested, the name components // containing special characters are quoted. The special characters are: // @@ -122,17 +125,14 @@ namespace build2 inline ostream& operator<< (ostream& os, const name& n) {return to_stream (os, n, false);} - // Vector of names. // - // We make it a separate type rather than an alias for vector in order - // to distinguish between untyped variable values (names) and typed ones - // (vector). + // Quote often it will contain just one element so we use small_vector<1>. + // Note also that it must be a separate type rather than an alias for + // vector in order to distinguish between untyped variable values + // (names) and typed ones (vector). // - struct names: vector - { - using vector::vector; - }; + using names = small_vector; using names_view = vector_view; diff --git a/build2/name.cxx b/build2/name.cxx index 5f6762d..1e5f5e4 100644 --- a/build2/name.cxx +++ b/build2/name.cxx @@ -12,6 +12,49 @@ namespace build2 { + string + to_string (const name& n) + { + string r; + + // Note: similar to to_stream() below. + // + if (n.empty ()) + return r; + + if (n.proj != nullptr) + { + r += *n.proj; + r += '%'; + } + + // If the value is empty, then we want to put the directory inside {}, + // e.g., dir{bar/}, not bar/dir{}. + // + bool d (!n.dir.empty ()); + bool v (!n.value.empty ()); + bool t (!n.type.empty ()); + + if (v && d) + r += n.dir.representation (); + + if (t) + { + r += n.type; + r += '{'; + } + + if (v) + r += n.value; + else + r += n.dir.representation (); + + if (t) + r += '}'; + + return r; + } + ostream& to_stream (ostream& os, const name& n, bool quote, char pair) { @@ -62,9 +105,12 @@ namespace build2 os << d; }; + // Note: similar to to_string() below. + // + // If quoted then print empty name as '' rather than {}. // - if (quote && n.empty () && n.proj == nullptr) + if (quote && n.empty ()) return os << "''"; if (n.proj != nullptr) @@ -73,9 +119,9 @@ namespace build2 os << '%'; } - // If the value is empty, then we want to print the directory - // inside {}, e.g., dir{bar/}, not bar/dir{}. We also want to - // print {} for an empty name (unless quoted). + // If the value is empty, then we want to print the directory inside {}, + // e.g., dir{bar/}, not bar/dir{}. We also want to print {} for an empty + // name (unless quoted). // bool d (!n.dir.empty ()); bool v (!n.value.empty ()); diff --git a/build2/parser.cxx b/build2/parser.cxx index c2737cb..456e3f7 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -658,7 +658,7 @@ namespace build2 fail (ploc) << "unknown target type " << pn.type; if (!pn.dir.empty ()) - pn.dir.normalize (); + pn.dir.normalize (); //@@ NORM (empty) // Find or insert. // @@ -781,7 +781,7 @@ namespace build2 for (name& n: ns) { - if (n.pair || n.qualified () || n.empty () || n.value.empty ()) + if (n.pair || n.qualified () || n.typed () || n.value.empty ()) fail (l) << "expected buildfile instead of " << n; // Construct the buildfile path. @@ -857,7 +857,7 @@ namespace build2 for (name& n: ns) { - if (n.pair || n.qualified () || n.empty ()) + if (n.pair || n.qualified () || n.typed () || n.empty ()) fail (l) << "expected buildfile instead of " << n; // Construct the buildfile path. If it is a directory, then append @@ -1983,7 +1983,7 @@ namespace build2 names& ns, const char* what, const string* separators, - size_t pair, + size_t pairn, const string* pp, const dir_path* dp, const string* tp) @@ -1998,8 +1998,8 @@ namespace build2 false, what, separators, - (pair != 0 - ? pair + (pairn != 0 + ? pairn : (ns.empty () || ns.back ().pair ? ns.size () : 0)), pp, dp, tp); count = ns.size () - count; @@ -2105,7 +2105,7 @@ namespace build2 bool chunk, const char* what, const string* separators, - size_t pair, + size_t pairn, const string* pp, const dir_path* dp, const string* tp) @@ -2130,35 +2130,180 @@ namespace build2 // The idea is to concatenate all the individual parts in this buffer and // then re-inject it into the loop as a single token. // + // If the concatenation is untyped (see below), then the name should be + // simple (i.e., just a string). + // bool concat (false); - string concat_str; + name concat_data; + + auto concat_typed = [&vnull, &vtype, &concat, &concat_data, this] + (value&& rhs, const location& loc) + { + // If we have no LHS yet, then simply copy value/type. + // + if (concat) + { + small_vector a; + + // Convert LHS to value. + // + a.push_back (value (vtype)); // Potentially typed NULL value. + + if (!vnull) + a.back ().assign (move (concat_data), nullptr); + + // RHS. + // + a.push_back (move (rhs)); + + const char* l ((a[0].type != nullptr ? a[0].type->name : "")); + const char* r ((a[1].type != nullptr ? a[1].type->name : "")); + + pair p; + { + // Print the location information in case the function fails. + // + auto g ( + make_exception_guard ( + [&loc, l, r] () + { + if (verb != 0) + info (loc) << "while concatenating " << l << " to " << r << + info << "use quoting to force untyped concatenation"; + })); + + p = functions.try_call ( + "builtin.concat", vector_view (a), loc); + } + + if (!p.second) + fail (loc) << "no typed concatenation of " << l << " to " << r << + info << "use quoting to force untyped concatenation"; + + rhs = move (p.first); + + // It seems natural to expect that a typed concatenation result + // is also typed. + // + assert (rhs.type != nullptr); + } + + vnull = rhs.null; + vtype = rhs.type; + + if (!vnull) + { + untypify (rhs); + names& d (rhs.as ()); + assert (d.size () == 1); // Must be single value. + concat_data = move (d[0]); + } + }; + // Number of names in the last group. This is used to detect when // we need to add an empty first pair element (e.g., @y) or when // we have a (for now unsupported) multi-name LHS (e.g., {x y}@z). // size_t count (0); + size_t start (ns.size ()); for (bool first (true);; first = false) { // Note that here we assume that, except for the first iterartion, // tt contains the type of the peeked token. - // If the accumulating buffer is not empty, then we have two options: - // continue accumulating or inject. We inject if the next token is - // not a word, var expansion, or eval context or if it is separated. + // Return true if the next token which should be peeked at won't be part + // of the name. + // + auto last_token = [chunk, this] () + { + const token& t (peeked ()); + type tt (t.type); + + return ((chunk && t.separated) || + (tt != type::word && + tt != type::dollar && + tt != type::lparen && + tt != type::lcbrace && + tt != type::pair_separator)); + }; + + // If we have accumulated some concatenations, then we have two options: + // continue accumulating or inject. We inject if the next token is not a + // word, var expansion, or eval context or if it is separated. // if (concat && ((tt != type::word && tt != type::dollar && tt != type::lparen) || peeked ().separated)) { + // Concatenation does not affect the tokens we get, only what we do + // with them. As a result, we never set the concat flag during pre- + // parsing. + // + assert (!pre_parse_); + concat = false; + + // If this is a result of typed concatenation, then don't inject. For + // one we don't want any of the "interpretations" performed in the + // word parsing code below. + // + // And if this is the only name, then we also want to preserve the + // type in the result. + // + // There is one exception, however: if the type is path, dir_path, or + // string and what follows is an unseparated '{', then we need to + // de-type it and inject in order to support our directory/target-type + // syntax, for example: + // + // $out_root/foo/lib{bar} + // $out_root/$libtype{bar} + // + // This means that a target type must be a valid path component. + // + vnull = false; // A concatenation cannot produce NULL. + + if (vtype != nullptr) + { + if (tt == type::lcbrace && !peeked ().separated) + { + if (vtype == &value_traits::value_type || + vtype == &value_traits::value_type) + ; // Representation is already in concat_data.value. + else if (vtype == &value_traits::value_type) + concat_data.value = move (concat_data.dir).representation (); + else + fail (t) << "expected directory and/or target type " + << "instead of " << vtype->name; + + vtype = nullptr; + // Fall through to injection. + } + else + { + ns.push_back (move (concat_data)); + + // Clear the type information if that's not the only name. + // + if (start != ns.size () || !last_token ()) + vtype = nullptr; + + // Restart the loop (but now with concat mode off) to handle + // chunking, etc. + // + continue; + } + } + + // Replace the current token with our injection (after handling it + // we will peek at the current token again). + // tt = type::word; - t = token (move (concat_str), + t = token (move (concat_data.value), true, - quote_type::unquoted, false, + quote_type::unquoted, false, // @@ Not quite true. t.line, t.column); - concat = false; } else if (!first) { @@ -2174,9 +2319,13 @@ namespace build2 // if (tt == type::word) { - string name (move (t.value)); tt = peek (); + if (pre_parse_) + continue; + + string val (move (t.value)); + // Should we accumulate? If the buffer is not empty, then // we continue accumulating (the case where we are separated // should have been handled by the injection code above). If @@ -2187,180 +2336,173 @@ namespace build2 ((tt == type::dollar || tt == type::lparen) && !peeked ().separated)) // Start. { - if (!pre_parse_) + // If LHS is typed then do typed concatenation. + // + if (concat && vtype != nullptr) + { + // Create untyped RHS. + // + names ns; + ns.push_back (name (move (val))); + concat_typed (value (move (ns)), get_location (t)); + } + else { - if (concat_str.empty ()) - concat_str = move (name); + auto& v (concat_data.value); + + if (v.empty ()) + v = move (val); else - concat_str += name; + v += val; } concat = true; continue; } - if (!pre_parse_) - { - // Find a separator (slash or %). - // - string::size_type p (separators != nullptr - ? name.find_last_of (*separators) - : string::npos); + // Find a separator (slash or %). + // + string::size_type p (separators != nullptr + ? val.find_last_of (*separators) + : string::npos); - // First take care of project. A project-qualified name is - // not very common, so we can afford some copying for the - // sake of simplicity. - // - const string* pp1 (pp); + // First take care of project. A project-qualified name is not very + // common, so we can afford some copying for the sake of simplicity. + // + const string* pp1 (pp); - if (p != string::npos) - { - bool last (name[p] == '%'); - string::size_type p1 (last ? p : name.rfind ('%', p - 1)); + if (p != string::npos) + { + bool last (val[p] == '%'); + string::size_type p1 (last ? p : val.rfind ('%', p - 1)); - if (p1 != string::npos) - { - string proj; - proj.swap (name); + if (p1 != string::npos) + { + string proj; + proj.swap (val); - // First fix the rest of the name. - // - name.assign (proj, p1 + 1, string::npos); - p = last ? string::npos : p - (p1 + 1); + // First fix the rest of the name. + // + val.assign (proj, p1 + 1, string::npos); + p = last ? string::npos : p - (p1 + 1); - // Now process the project name. - // - proj.resize (p1); + // Now process the project name. + // + proj.resize (p1); - if (pp != nullptr) - fail (t) << "nested project name " << proj; + if (pp != nullptr) + fail (t) << "nested project name " << proj; - pp1 = &project_name_pool.find (proj); - } + pp1 = &project_name_pool.find (proj); } + } - string::size_type n (p != string::npos ? name.size () - 1 : 0); - - // See if this is a type name, directory prefix, or both. That - // is, it is followed by an un-separated '{'. - // - if (tt == type::lcbrace && !peeked ().separated) - { - next (t, tt); + string::size_type n (p != string::npos ? val.size () - 1 : 0); - if (p != n && tp != nullptr) - fail (t) << "nested type name " << name; + // See if this is a type name, directory prefix, or both. That + // is, it is followed by an un-separated '{'. + // + if (tt == type::lcbrace && !peeked ().separated) + { + next (t, tt); - dir_path d1; - const dir_path* dp1 (dp); + if (p != n && tp != nullptr) + fail (t) << "nested type name " << val; - string t1; - const string* tp1 (tp); + dir_path d1; + const dir_path* dp1 (dp); - if (p == string::npos) // type - tp1 = &name; - else if (p == n) // directory - { - if (dp == nullptr) - d1 = dir_path (name); - else - d1 = *dp / dir_path (name); + string t1; + const string* tp1 (tp); - dp1 = &d1; - } - else // both - { - t1.assign (name, p + 1, n - p); + if (p == string::npos) // type + tp1 = &val; + else if (p == n) // directory + { + if (dp == nullptr) + d1 = dir_path (val); + else + d1 = *dp / dir_path (val); - if (dp == nullptr) - d1 = dir_path (name, 0, p + 1); - else - d1 = *dp / dir_path (name, 0, p + 1); + dp1 = &d1; + } + else // both + { + t1.assign (val, p + 1, n - p); - dp1 = &d1; - tp1 = &t1; - } + if (dp == nullptr) + d1 = dir_path (val, 0, p + 1); + else + d1 = *dp / dir_path (val, 0, p + 1); - count = parse_names_trailer ( - t, tt, ns, what, separators, pair, pp1, dp1, tp1); - tt = peek (); - continue; + dp1 = &d1; + tp1 = &t1; } - // If we are a second half of a pair, add another first half - // unless this is the first instance. - // - if (pair != 0 && pair != ns.size ()) - ns.push_back (ns[pair - 1]); - - count = 1; - - // If it ends with a directory separator, then it is a directory. - // Note that at this stage we don't treat '.' and '..' as special - // (unless they are specified with a directory separator) because - // then we would have ended up treating '.: ...' as a directory - // scope. Instead, this is handled higher up the processing chain, - // in scope::find_target_type(). This would also mess up - // reversibility to simple name. - // - // @@ TODO: and not quoted + count = parse_names_trailer ( + t, tt, ns, what, separators, pairn, pp1, dp1, tp1); + tt = peek (); + continue; + } + + // If we are a second half of a pair, add another first half + // unless this is the first instance. + // + if (pairn != 0 && pairn != ns.size ()) + ns.push_back (ns[pairn - 1]); + + count = 1; + + // If it ends with a directory separator, then it is a directory. + // Note that at this stage we don't treat '.' and '..' as special + // (unless they are specified with a directory separator) because + // then we would have ended up treating '.: ...' as a directory + // scope. Instead, this is handled higher up the processing chain, + // in scope::find_target_type(). This would also mess up + // reversibility to simple name. + // + // @@ TODO: and not quoted (but what about partially quoted, e.g., + // "foo bar"/ or concatenated, e.g., $dir/foo/). + // + if (p == n) + { + // For reversibility to simple name, only treat it as a directory + // if the string is an exact representation. // - if (p == n) - { - // For reversibility to simple name, only treat it as a directory - // if the string is an exact representation. - // - dir_path dir (move (name), dir_path::exact); + dir_path dir (move (val), dir_path::exact); - if (!dir.empty ()) - { - if (dp != nullptr) - dir = *dp / dir; + if (!dir.empty ()) + { + if (dp != nullptr) + dir = *dp / dir; - ns.emplace_back (pp1, - move (dir), - (tp != nullptr ? *tp : string ()), - string ()); - continue; - } + ns.emplace_back (pp1, + move (dir), + (tp != nullptr ? *tp : string ()), + string ()); + continue; } - - ns.emplace_back (pp1, - (dp != nullptr ? *dp : dir_path ()), - (tp != nullptr ? *tp : string ()), - move (name)); } + ns.emplace_back (pp1, + (dp != nullptr ? *dp : dir_path ()), + (tp != nullptr ? *tp : string ()), + move (val)); continue; } - // Variable expansion/function call or eval context. + // Variable expansion, function call, or eval context. // if (tt == type::dollar || tt == type::lparen) { - // These two cases are pretty similar in that in both we quickly end - // up with a list of names that we need to splice into the result. - // - names lv_storage; - names_view lv; - - // Check if we should set/propagate value NULL/type. We only do this - // if this is the only expansion, that is, it is the first and the - // text token is not part of the name. + // These cases are pretty similar in that in both we quickly end up + // with a list of names that we need to splice into the result. // - auto set_value = [first, &tt] () - { - return first && - tt != type::word && - tt != type::dollar && - tt != type::lparen && - tt != type::lcbrace && - tt != type::pair_separator; - }; - location loc; + value result_data; + const value* result (&result_data); const char* what; // Variable, function, or evaluation context. - value result; // Holds function call/eval context result. + bool quoted (t.qtype != quote_type::unquoted); if (tt == type::dollar) { @@ -2446,82 +2588,51 @@ namespace build2 // Note that we move args to call(). // - result = functions.call (name, - vector_view ( - args.second ? &args.first : nullptr, - args.second ? 1 : 0), - loc); - - // See if we should propagate the value NULL/type. - // - if (set_value ()) - { - vnull = result.null; - vtype = result.type; - } - - if (!result || result.empty ()) - continue; + result_data = functions.call ( + name, + vector_view ( + args.second ? &args.first : nullptr, + args.second ? 1 : 0), + loc); - lv = reverse (result, lv_storage); what = "function call"; } else { + // Variable expansion. + // + if (pre_parse_) continue; // As if empty value. - // Variable expansion. - // lookup l (lookup_variable (move (qual), move (name), loc)); - // See if we should propagate the value NULL/type. - // - if (set_value ()) - { - vnull = !l; - vtype = l.defined () ? l->type : nullptr; - } + if (l.defined ()) + result = l.value; // Otherwise leave as NULL result_data. - if (!l || l->empty ()) - continue; - - lv = reverse (*l, lv_storage); what = "variable expansion"; } } else { + // Context evaluation. + // + loc = get_location (t); - result = parse_eval (t, tt).first; + result_data = parse_eval (t, tt).first; tt = peek (); if (pre_parse_) continue; // As if empty result. - // See if we should propagate the value NULL/type. - // - if (set_value ()) - { - vnull = result.null; - vtype = result.type; - } - - if (!result || result.empty ()) - continue; - - lv = reverse (result, lv_storage); what = "context evaluation"; } - // Note that we never end up here during pre-parsing. + // We never end up here during pre-parsing. // assert (!pre_parse_); - // @@ Could move if lv is lv_storage (or even result). - // - // Should we accumulate? If the buffer is not empty, then // we continue accumulating (the case where we are separated // should have been handled by the injection code above). If @@ -2533,41 +2644,145 @@ namespace build2 tt == type::dollar || tt == type::lparen) && !peeked ().separated)) { - // This should be a simple value or a simple directory. The token - // still points to the name (or closing paren). + // This can be a typed or untyped concatenation. The rules that + // determine which one it is are as follows: + // + // 1. Determine if to preserver the type of RHS: if its first + // token is quoted, then we do not. + // + // 2. Given LHS (if any) and RHS we do typed concatenation if + // either is typed. + // + // Here are some interesting corner cases to meditate on: + // + // $dir/"foo bar" + // $dir"/foo bar" + // "foo"$dir + // "foo""$dir" + // ""$dir + // + + // First if RHS is typed but quoted then convert it to an untyped + // string. + // + // Conversion to an untyped string happens differently, depending + // on whether we are in a quoted or unquoted context. In an + // unquoted context we use $representation() which must return a + // "round-trippable representation" (and if that it not possible, + // then it should not be overloaded for a type). In a quoted + // context we use $string() which returns a "canonical + // representation" (e.g., a directory path without a trailing + // slash). // - if (lv.size () > 1) - fail (loc) << "concatenating " << what << " contains multiple " - << "values"; + if (result->type != nullptr && quoted) + { + // RHS is already a value but it could be a const reference (to + // the variable value) while we need to move things around. So in + // this case we make a copy. + // + if (result != &result_data) + result = &(result_data = *result); - const name& n (lv[0]); + const char* t (result_data.type->name); - if (n.qualified ()) - fail (loc) << "concatenating " << what << " contains project name"; + pair p; + { + // Print the location information in case the function fails. + // + auto g ( + make_exception_guard ( + [&loc, t] () + { + if (verb != 0) + info (loc) << "while converting " << t << " to string"; + })); - if (n.typed ()) - fail (loc) << "concatenating " << what << " contains type"; + p = functions.try_call ( + "string", vector_view (&result_data, 1), loc); + } + + if (!p.second) + fail (loc) << "no string conversion for " << t; + + result_data = move (p.first); + untypify (result_data); // Convert to untyped simple name. + } - if (!n.dir.empty ()) + if ((concat && vtype != nullptr) || // LHS typed. + (result->type != nullptr)) // RHS typed. { - if (!n.value.empty ()) - fail (loc) << "concatenating " << what << " contains directory"; + if (result != &result_data) // Same reason as above. + result = &(result_data = *result); - // If this is an original name, then we cannot assume it is - // actually a directory path (think s/foo/bar/) so we have to - // reverse it exactly. + concat_typed (move (result_data), loc); + } + // + // Untyped concatenation. Note that if RHS is NULL/empty, we still + // set the concat flag. + // + else if (!result->null && !result->empty ()) + { + // This can only an untyped value. // - concat_str += n.original - ? n.dir.representation () - : n.dir.string (); + // @@ Could move if result == &result_data. + // + const names& lv (cast (*result)); + + // This should be a simple value or a simple directory. + // + if (lv.size () > 1) + fail (loc) << "concatenating " << what << " contains multiple " + << "values"; + + const name& n (lv[0]); + + if (n.qualified ()) + fail (loc) << "concatenating " << what << " contains project " + << "name"; + + if (n.typed ()) + fail (loc) << "concatenating " << what << " contains type"; + + if (!n.dir.empty ()) + { + if (!n.value.empty ()) + fail (loc) << "concatenating " << what << " contains " + << "directory"; + + // Note that here we cannot assume what's in dir is really a + // path (think s/foo/bar/) so we have to reverse it exactly. + // + concat_data.value += n.dir.representation (); + } + else + concat_data.value += n.value; } - else - concat_str += n.value; concat = true; } else { + // See if we should propagate the value NULL/type. We only do this + // if this is the only expansion, that is, it is the first and the + // text token is not part of the name. + // + if (first && last_token ()) + { + vnull = result->null; + vtype = result->type; + } + + // Nothing else to do here if the result is NULL or empty. + // + if (result->null || result->empty ()) + continue; + + // @@ Could move if lv is lv_storage (or even result_data; see + // untypify()). + // + names lv_storage; + names_view lv (reverse (*result, lv_storage)); + // Copy the names from the variable into the resulting name list // while doing sensible things with the types and directories. // @@ -2612,7 +2827,7 @@ namespace build2 // If we are a second half of a pair. // - if (pair != 0) + if (pairn != 0) { // Check that there are no nested pairs. // @@ -2621,8 +2836,8 @@ namespace build2 // And add another first half unless this is the first instance. // - if (pair != ns.size ()) - ns.push_back (ns[pair - 1]); + if (pairn != ns.size ()) + ns.push_back (ns[pairn - 1]); } ns.emplace_back (pp1, @@ -2631,7 +2846,6 @@ namespace build2 n.value); ns.back ().pair = n.pair; - ns.back ().original = n.original; } count = lv.size (); @@ -2645,7 +2859,7 @@ namespace build2 if (tt == type::lcbrace) { count = parse_names_trailer ( - t, tt, ns, what, separators, pair, pp, dp, tp); + t, tt, ns, what, separators, pairn, pp, dp, tp); tt = peek (); continue; } @@ -2654,7 +2868,7 @@ namespace build2 // if (tt == type::pair_separator) { - if (pair != 0) + if (pairn != 0) fail (t) << "nested pair on the right hand side of a pair"; tt = peek (); @@ -2701,7 +2915,7 @@ namespace build2 continue; } - // Note: remember to update set_value() test if adding new recognized + // Note: remember to update last_token() test if adding new recognized // tokens. if (!first) @@ -2712,8 +2926,8 @@ namespace build2 // If we are a second half of a pair, add another first half // unless this is the first instance. // - if (pair != 0 && pair != ns.size ()) - ns.push_back (ns[pair - 1]); + if (pairn != 0 && pairn != ns.size ()) + ns.push_back (ns[pairn - 1]); ns.emplace_back (pp, (dp != nullptr ? *dp : dir_path ()), diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx index fae138b..6ee1429 100644 --- a/build2/test/script/parser.cxx +++ b/build2/test/script/parser.cxx @@ -1296,7 +1296,7 @@ namespace build2 if (!p.empty ()) { - p.normalize (); + p.normalize (); //@@ NORM return p; } diff --git a/build2/variable b/build2/variable index 9eb173a..b773047 100644 --- a/build2/variable +++ b/build2/variable @@ -208,14 +208,11 @@ namespace build2 // Assign/append/prepend raw data. Variable is optional and is only used // for diagnostics. // - void - assign (names&&, const variable*); + void assign (names&&, const variable*); + void assign (name&&, const variable*); // Shortcut for single name. + void append (names&&, const variable*); + void prepend (names&&, const variable*); - void - append (names&&, const variable*); - - void - prepend (names&&, const variable*); // Implementation details, don't use directly except in representation // type implementations. @@ -229,13 +226,14 @@ namespace build2 return reinterpret_cast (data_);} public: - // The maximum size we can store directly in the value is that of a name, - // which is sufficient for the most commonly used types (string, vector, - // map) on all the platforms that we support (each type should static - // assert this in its value_traits specialization below). Types that don't - // fit will have to be handled with an extra dynamic allocation. + // The maximum size we can store directly in the value is that of names + // (which is a small_vector), which is sufficient for the most + // commonly used types (string, vector, map) on all the platforms that we + // support (each type should static assert this in its value_traits + // specialization below). Types that don't fit will have to be handled + // with an extra dynamic allocation. // - std::aligned_storage::type data_; + std::aligned_storage::type data_; // VC14 needs decltype. // @@ -413,6 +411,10 @@ namespace build2 // // // static bool empty (const T&); // + // // True if can be constructed from empty names as T(). + // // + // static const bool empty_value = true; + // // // For simple types (those that can be used as elements of containers), // // type_name must be constexpr in order to sidestep the static init // // order issue (in fact, that's the only reason we have it both here @@ -431,10 +433,11 @@ namespace build2 template T convert (name&&); template T convert (name&&, name&&); - // As above but for container types. Note that in case of invalid_argument - // the names are not guaranteed to be unchanged. + // As above but can also be called for container types. Note that in this + // case (container) if invalid_argument is thrown, the names are not + // guaranteed to be unchanged. // - template T convert (names&&); + //template T convert (names&&); (declaration causes ambiguity) // Default implementations of the dtor/copy_ctor/copy_assing callbacks for // types that are stored directly in value::data_ and the provide all the @@ -462,19 +465,17 @@ namespace build2 // Default implementations of the assign/append/prepend callbacks for simple // types. They call value_traits::convert() and then pass the result to // value_traits::assign()/append()/prepend(). As a result, it may not be - // the most efficient way to do it. If the empty template parameter is true, - // then an empty names sequence is converted to a default-constructed T. And - // if false, then an empty value is not allowed. + // the most efficient way to do it. // - template + template static void simple_assign (value&, names&&, const variable*); - template + template static void simple_append (value&, names&&, const variable*); - template + template static void simple_prepend (value&, names&&, const variable*); @@ -503,10 +504,11 @@ namespace build2 static bool convert (name&&, name*); static void assign (value&, bool); static void append (value&, bool); // OR. - static name reverse (bool x) {return name (x ? "true" : "false", false);} + static name reverse (bool x) {return name (x ? "true" : "false");} static int compare (bool, bool); static bool empty (bool) {return false;} + static const bool empty_value = false; static const char* const type_name; static const build2::value_type value_type; }; @@ -519,10 +521,11 @@ namespace build2 static uint64_t convert (name&&, name*); static void assign (value&, uint64_t); static void append (value&, uint64_t); // ADD. - static name reverse (uint64_t x) {return name (to_string (x), false);} + static name reverse (uint64_t x) {return name (to_string (x));} static int compare (uint64_t, uint64_t); static bool empty (bool) {return false;} + static const bool empty_value = false; static const char* const type_name; static const build2::value_type value_type; }; @@ -548,10 +551,11 @@ namespace build2 static void assign (value&, string&&); static void append (value&, string&&); static void prepend (value&, string&&); - static name reverse (const string& x) {return name (x, false);} + static name reverse (const string& x) {return name (x);} static int compare (const string&, const string&); static bool empty (const string& x) {return x.empty ();} + static const bool empty_value = true; static const char* const type_name; static const build2::value_type value_type; }; @@ -572,10 +576,15 @@ namespace build2 static void assign (value&, path&&); static void append (value&, path&&); // operator/ static void prepend (value&, path&&); // operator/ - static name reverse (const path& x) {return name (x.string (), false);} + static name reverse (const path& x) { + return x.to_directory () + ? name (path_cast (x)) + : name (x.string ()); + } static int compare (const path&, const path&); static bool empty (const path& x) {return x.empty ();} + static const bool empty_value = true; static const char* const type_name; static const build2::value_type value_type; }; @@ -591,10 +600,11 @@ namespace build2 static void assign (value&, dir_path&&); static void append (value&, dir_path&&); // operator/ static void prepend (value&, dir_path&&); // operator/ - static name reverse (const dir_path& x) {return name (x, false);} + static name reverse (const dir_path& x) {return name (x);} static int compare (const dir_path&, const dir_path&); static bool empty (const dir_path& x) {return x.empty ();} + static const bool empty_value = true; static const char* const type_name; static const build2::value_type value_type; }; @@ -610,10 +620,11 @@ namespace build2 static abs_dir_path convert (name&&, name*); static void assign (value&, abs_dir_path&&); static void append (value&, abs_dir_path&&); // operator/ - static name reverse (const abs_dir_path& x) {return name (x, false);} + static name reverse (const abs_dir_path& x) {return name (x);} static int compare (const abs_dir_path&, const abs_dir_path&); static bool empty (const abs_dir_path& x) {return x.empty ();} + static const bool empty_value = true; static const char* const type_name; static const build2::value_type value_type; }; @@ -631,6 +642,7 @@ namespace build2 static int compare (const name&, const name&); static bool empty (const name& x) {return x.empty ();} + static const bool empty_value = true; static const char* const type_name; static const build2::value_type value_type; }; @@ -653,6 +665,7 @@ namespace build2 static int compare (const process_path&, const process_path&); static bool empty (const process_path& x) {return x.empty ();} + static const bool empty_value = true; static const char* const type_name; static const build2::value_type value_type; }; diff --git a/build2/variable.cxx b/build2/variable.cxx index 5a52557..41dbd5f 100644 --- a/build2/variable.cxx +++ b/build2/variable.cxx @@ -58,13 +58,11 @@ namespace build2 value& value:: operator= (value&& v) { - assert (type == nullptr || type == v.type); - if (this != &v) { // Prepare the receiving value. // - if (type == nullptr && v.type != nullptr) + if (type != v.type) { *this = nullptr; type = v.type; @@ -99,13 +97,11 @@ namespace build2 value& value:: operator= (const value& v) { - assert (type == nullptr || type == v.type); - if (this != &v) { // Prepare the receiving value. // - if (type == nullptr && v.type != nullptr) + if (type != v.type) { *this = nullptr; type = v.type; @@ -212,7 +208,7 @@ namespace build2 ns.insert (ns.end (), make_move_iterator (p.begin ()), make_move_iterator (p.end ())); - p.swap (ns); + p = move (ns); } } } @@ -388,8 +384,7 @@ namespace build2 if (n.simple ()) m += "'" + n.value + "'"; else if (n.directory ()) - m += "'" + - (n.original ? n.dir.representation () : n.dir.string ()) + "'"; + m += "'" + n.dir.representation () + "'"; else m += "complex name"; } @@ -428,9 +423,9 @@ namespace build2 nullptr, // No dtor (POD). nullptr, // No copy_ctor (POD). nullptr, // No copy_assign (POD). - &simple_assign, // No empty value. - &simple_append, - &simple_append, // Prepend same as append. + &simple_assign, + &simple_append, + &simple_append, // Prepend same as append. &simple_reverse, nullptr, // No cast (cast data_ directly). nullptr, // No compare (compare as POD). @@ -469,9 +464,9 @@ namespace build2 nullptr, // No dtor (POD). nullptr, // No copy_ctor (POD). nullptr, // No copy_assign (POD). - &simple_assign, // No empty value. - &simple_append, - &simple_append, // Prepend same as append. + &simple_assign, + &simple_append, + &simple_append, // Prepend same as append. &simple_reverse, nullptr, // No cast (cast data_ directly). nullptr, // No compare (compare as POD). @@ -497,13 +492,10 @@ namespace build2 string s; if (n.directory (true)) - // Use either the precise or traditional representation depending on - // whether this is the original name (if it is, then this might not be - // a path after all; think s/foo/bar/). + // Note that here we cannot assume what's in dir is really a + // path (think s/foo/bar/) so we have to reverse it exactly. // - s = n.original - ? move (n.dir).representation () // Move out of path. - : move (n.dir).string (); + s = move (n.dir).representation (); // Move out of path. else s.swap (n.value); @@ -530,12 +522,7 @@ namespace build2 } if (r->directory (true)) - { - if (r->original) - s += move (r->dir).representation (); - else - s += r->dir.string (); - } + s += move (r->dir).representation (); else s += r->value; } @@ -553,9 +540,9 @@ namespace build2 &default_dtor, &default_copy_ctor, &default_copy_assign, - &simple_assign, // Allow empty strings. - &simple_append, - &simple_prepend, + &simple_assign, + &simple_append, + &simple_prepend, &simple_reverse, nullptr, // No cast (cast data_ directly). &simple_compare, @@ -599,9 +586,9 @@ namespace build2 &default_dtor, &default_copy_ctor, &default_copy_assign, - &simple_assign, // Allow empty paths. - &simple_append, - &simple_prepend, + &simple_assign, + &simple_append, + &simple_prepend, &simple_reverse, nullptr, // No cast (cast data_ directly). &simple_compare, @@ -643,9 +630,9 @@ namespace build2 &default_dtor, &default_copy_ctor, &default_copy_assign, - &simple_assign, // Allow empty paths. - &simple_append, - &simple_prepend, + &simple_assign, + &simple_append, + &simple_prepend, &simple_reverse, nullptr, // No cast (cast data_ directly). &simple_compare, @@ -689,8 +676,8 @@ namespace build2 &default_dtor, &default_copy_ctor, &default_copy_assign, - &simple_assign, // Allow empty paths. - &simple_append, + &simple_assign, + &simple_append, nullptr, // No prepend. &simple_reverse, nullptr, // No cast (cast data_ directly). @@ -704,10 +691,7 @@ namespace build2 convert (name&& n, name* r) { if (r == nullptr) - { - n.original = false; return move (n); - } throw_invalid_argument (n, r, "name"); } @@ -729,7 +713,7 @@ namespace build2 &default_dtor, &default_copy_ctor, &default_copy_assign, - &simple_assign, // Allow empty names. + &simple_assign, nullptr, // Append not supported. nullptr, // Prepend not supported. &name_reverse, diff --git a/build2/variable.ixx b/build2/variable.ixx index 4d70685..e5fe794 100644 --- a/build2/variable.ixx +++ b/build2/variable.ixx @@ -80,6 +80,14 @@ namespace build2 return *this; } + inline void value:: + assign (name&& n, const variable* var) + { + names ns; + ns.push_back (move (n)); + assign (move (ns), var); + } + inline bool operator!= (const value& x, const value& y) { @@ -226,9 +234,11 @@ namespace build2 return value_traits::convert (move (l), &r); } + // This one will be SFINAE'd out unless T is a container. + // template - inline T - convert (names&& ns) + inline auto + convert (names&& ns) -> decltype (value_traits::convert (move (ns))) { return value_traits::convert (move (ns)); } @@ -474,11 +484,10 @@ namespace build2 inline void value_traits:: assign (value& v, name&& x) { - name* p (v - ? &(v.as () = move (x)) - : new (&v.data_) name (move (x))); - - p->original = false; + if (v) + v.as () = move (x); + else + new (&v.data_) name (move (x)); } inline int value_traits:: diff --git a/build2/variable.txx b/build2/variable.txx index 0678a6a..ab31a45 100644 --- a/build2/variable.txx +++ b/build2/variable.txx @@ -6,9 +6,35 @@ namespace build2 { - // value + // This one will be SFINAE'd out unless T is a simple value. // template + auto + convert (names&& ns) -> + decltype (value_traits::convert (move (ns[0]), nullptr)) + { + size_t n (ns.size ()); + + if (n == 0) + { + if (value_traits::empty_value) + return T (); + } + else if (n == 1) + { + return convert (move (ns[0])); + } + else if (n == 2 && ns[0].pair != '\0') + { + return convert (move (ns[0]), move (ns[1])); + } + + throw invalid_argument ( + string ("invalid ") + value_traits::type_name + + (n == 0 ? " value: empty" : " value: multiple names")); + } + + template void default_dtor (value& v) { @@ -42,13 +68,13 @@ namespace build2 return value_traits::empty (v.as ()); } - template + template void simple_assign (value& v, names&& ns, const variable* var) { size_t n (ns.size ()); - if (empty ? n <= 1 : n == 1) + if (value_traits::empty_value ? n <= 1 : n == 1) { try { @@ -72,13 +98,13 @@ namespace build2 dr << " in variable " << var->name; } - template + template void simple_append (value& v, names&& ns, const variable* var) { size_t n (ns.size ()); - if (empty ? n <= 1 : n == 1) + if (value_traits::empty_value ? n <= 1 : n == 1) { try { @@ -102,13 +128,13 @@ namespace build2 dr << " in variable " << var->name; } - template + template void simple_prepend (value& v, names&& ns, const variable* var) { size_t n (ns.size ()); - if (empty ? n <= 1 : n == 1) + if (value_traits::empty_value ? n <= 1 : n == 1) { try { diff --git a/tests/function/path/testscript b/tests/function/path/testscript index 0f83ad6..fc1cb31 100644 --- a/tests/function/path/testscript +++ b/tests/function/path/testscript @@ -19,8 +19,8 @@ end : normalize : { - $* <'print $normalize([path] a/../b/)' >"b" : path - $* <'print $normalize([paths] a/../b/ a/../c)' >"b c" : paths + $* <'print $normalize([path] a/../b)' >"b" : path + $* <'print $normalize([paths] a/../b a/../c)' >"b c" : paths $* <'print $normalize([dir_path] a/../b)' >"b$s" : dir-path $* <'print $normalize([dir_paths] a/../b a/../c/)' >"b$s c$s" : dir-paths $* <'print $path.normalize(a/../b)' >"b" : untyped diff --git a/tests/test/script/runner/cleanup.test b/tests/test/script/runner/cleanup.test index e6cefd5..e5d917d 100644 --- a/tests/test/script/runner/cleanup.test +++ b/tests/test/script/runner/cleanup.test @@ -13,7 +13,7 @@ EOI b = $effect($build.path) -q --no-column --buildfile - <"./: test{testscript}" \ &?test/*** test c = cat >>>testscript -test = \'$test\' +test = "'$test'" # Valid cleanups. # diff --git a/tests/test/script/runner/status.test b/tests/test/script/runner/status.test index 716f2d4..4998311 100644 --- a/tests/test/script/runner/status.test +++ b/tests/test/script/runner/status.test @@ -13,7 +13,7 @@ EOI b = $effect($build.path) -q --no-column --buildfile - <"./: test{testscript}" \ &?test/*** test c = cat >>>testscript -test = \'$test\' +test = "'$test'" +if ($cxx.target.class == "windows") ext = ".exe" diff --git a/unit-tests/function/buildfile b/unit-tests/function/buildfile index 756fbcd..c297ed8 100644 --- a/unit-tests/function/buildfile +++ b/unit-tests/function/buildfile @@ -7,8 +7,8 @@ import libs = libbutl%lib{butl} src = token lexer diagnostics utility variable name b-options types-parsers \ context scope parser target operation rule prerequisite file module function \ -functions-builtin functions-path functions-process-path algorithm search dump \ -filesystem config/{utility init operation} +functions-builtin functions-path functions-process-path functions-string \ +algorithm search dump filesystem config/{utility init operation} exe{driver}: cxx{driver} ../../build2/cxx{$src} $libs test{call syntax} diff --git a/unit-tests/test/script/parser/buildfile b/unit-tests/test/script/parser/buildfile index 6cc319a..b570901 100644 --- a/unit-tests/test/script/parser/buildfile +++ b/unit-tests/test/script/parser/buildfile @@ -8,8 +8,8 @@ import libs = libbutl%lib{butl} src = token lexer parser diagnostics utility variable name context target \ scope prerequisite file module operation rule b-options algorithm search \ filesystem function functions-builtin functions-path functions-process-path \ -config/{utility init operation} dump types-parsers test/{target \ -script/{token lexer parser script}} +functions-string config/{utility init operation} dump types-parsers \ +test/{target script/{token lexer parser script}} exe{driver}: cxx{driver} ../../../../build2/cxx{$src} $libs \ test{cleanup command-if command-re-parse description exit expansion \ diff --git a/unit-tests/test/script/parser/include.test b/unit-tests/test/script/parser/include.test index 7a3b517..1639d37 100644 --- a/unit-tests/test/script/parser/include.test +++ b/unit-tests/test/script/parser/include.test @@ -104,11 +104,11 @@ EOO : var-expansion : -cat <>>foo-$(build.version).test; +cat <>>"foo-$(build.version).test"; cmd EOI $* <>EOO -.include foo-$(build.version).test +.include "foo-$(build.version).test" EOI cmd EOO diff --git a/unit-tests/test/script/parser/scope.test b/unit-tests/test/script/parser/scope.test index 6ddb265..44aebee 100644 --- a/unit-tests/test/script/parser/scope.test +++ b/unit-tests/test/script/parser/scope.test @@ -4,13 +4,13 @@ $* foo.test <'cmd $@' >"cmd foo/1" # id wd = [dir_path] $~; wd += test-driver; wd += 1; -$* testscript <'cmd $~' >"cmd $wd" # wd-testscript +$* testscript <'cmd "$~"' >"cmd $wd" # wd-testscript wd = [dir_path] $~; wd += test-driver; wd += foo; wd += 1; -$* foo.test <'cmd $~' >"cmd $wd" # wd +$* foo.test <'cmd "$~"' >"cmd $wd" # wd $* -s <