aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-11-30 17:32:43 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-11-30 17:32:43 +0200
commitbe14801929cf2a6caced87df034ae12a85f42aa6 (patch)
tree74670e0a746961424e50c09449d526e143c1abfc
parent4b31ef06275ad423e48a75d15fb0ee21c3127e3c (diff)
Add support for typed/untyped concatenated expansion
-rw-r--r--build/root.build2
-rw-r--r--build2/algorithm.cxx2
-rw-r--r--build2/buildfile1
-rw-r--r--build2/cc/pkgconfig.cxx2
-rw-r--r--build2/config/operation.cxx2
-rw-r--r--build2/function21
-rw-r--r--build2/function.cxx20
-rw-r--r--build2/functions-builtin.cxx6
-rw-r--r--build2/functions-path.cxx115
-rw-r--r--build2/functions-string.cxx39
-rw-r--r--build2/name34
-rw-r--r--build2/name.cxx54
-rw-r--r--build2/parser.cxx662
-rw-r--r--build2/test/script/parser.cxx2
-rw-r--r--build2/variable69
-rw-r--r--build2/variable.cxx68
-rw-r--r--build2/variable.ixx23
-rw-r--r--build2/variable.txx40
-rw-r--r--tests/function/path/testscript4
-rw-r--r--tests/test/script/runner/cleanup.test2
-rw-r--r--tests/test/script/runner/status.test2
-rw-r--r--unit-tests/function/buildfile4
-rw-r--r--unit-tests/test/script/parser/buildfile4
-rw-r--r--unit-tests/test/script/parser/include.test4
-rw-r--r--unit-tests/test/script/parser/scope.test4
25 files changed, 818 insertions, 368 deletions
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<value> args, const location&) const;
+ call (const string& name, vector_view<value> 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<value, bool>
+ try_call (const string& name,
+ vector_view<value> 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<value, bool>
+ call (const string&, vector_view<value>, 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<value> args, const location& loc) const
+ pair<value, bool> function_map::
+ call (const string& name,
+ vector_view<value> 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<dir_path> (move (l)) /= path_cast<dir_path> (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<dir_path> (move (pr));
+ else
+ return value (path_cast<path> (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<path> (move (n)).normalize ().string ();
- }
- return ns;
- };
+ if (n.directory ())
+ n.dir.normalize ();
+ else
+ n.value = convert<path> (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<string> (move (ur)));
+ };
+
+ b[".concat"] = [](dir_path l, names ur)
+ {
+ return concat_dir_path_string (move (l), convert<string> (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 <build2/function>
+#include <build2/variable>
+
+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<string> (move (ur));
+ return l;
+ };
+
+ b[".concat"] = [](names ul, string r)
+ {
+ string l (convert<string> (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<name> in order
- // to distinguish between untyped variable values (names) and typed ones
- // (vector<name>).
+ // 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<name> in order to distinguish between untyped variable values
+ // (names) and typed ones (vector<name>).
//
- struct names: vector<name>
- {
- using vector::vector;
- };
+ using names = small_vector<name, 1>;
using names_view = vector_view<const name>;
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<value, 2> 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 : "<untyped>"));
+ const char* r ((a[1].type != nullptr ? a[1].type->name : "<untyped>"));
+
+ pair<value, bool> 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<value> (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<names> ());
+ 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<path>::value_type ||
+ vtype == &value_traits<string>::value_type)
+ ; // Representation is already in concat_data.value.
+ else if (vtype == &value_traits<dir_path>::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<value> (
- 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<value> (
+ 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<value, bool> 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<value> (&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<names> (*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<const T&> (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<name, 1>), 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<sizeof (name)>::type data_;
+ std::aligned_storage<sizeof (names)>::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 <typename T> T convert (name&&);
template <typename T> 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 <typename T> T convert (names&&);
+ //template <typename T> 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<T>::convert() and then pass the result to
// value_traits<T>::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 <typename T, bool empty>
+ template <typename T>
static void
simple_assign (value&, names&&, const variable*);
- template <typename T, bool empty>
+ template <typename T>
static void
simple_append (value&, names&&, const variable*);
- template <typename T, bool empty>
+ template <typename T>
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<dir_path> (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<bool, false>, // No empty value.
- &simple_append<bool, false>,
- &simple_append<bool, false>, // Prepend same as append.
+ &simple_assign<bool>,
+ &simple_append<bool>,
+ &simple_append<bool>, // Prepend same as append.
&simple_reverse<bool>,
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<uint64_t, false>, // No empty value.
- &simple_append<uint64_t, false>,
- &simple_append<uint64_t, false>, // Prepend same as append.
+ &simple_assign<uint64_t>,
+ &simple_append<uint64_t>,
+ &simple_append<uint64_t>, // Prepend same as append.
&simple_reverse<uint64_t>,
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<string>,
&default_copy_ctor<string>,
&default_copy_assign<string>,
- &simple_assign<string, true>, // Allow empty strings.
- &simple_append<string, true>,
- &simple_prepend<string, true>,
+ &simple_assign<string>,
+ &simple_append<string>,
+ &simple_prepend<string>,
&simple_reverse<string>,
nullptr, // No cast (cast data_ directly).
&simple_compare<string>,
@@ -599,9 +586,9 @@ namespace build2
&default_dtor<path>,
&default_copy_ctor<path>,
&default_copy_assign<path>,
- &simple_assign<path, true>, // Allow empty paths.
- &simple_append<path, true>,
- &simple_prepend<path, true>,
+ &simple_assign<path>,
+ &simple_append<path>,
+ &simple_prepend<path>,
&simple_reverse<path>,
nullptr, // No cast (cast data_ directly).
&simple_compare<path>,
@@ -643,9 +630,9 @@ namespace build2
&default_dtor<dir_path>,
&default_copy_ctor<dir_path>,
&default_copy_assign<dir_path>,
- &simple_assign<dir_path, true>, // Allow empty paths.
- &simple_append<dir_path, true>,
- &simple_prepend<dir_path, true>,
+ &simple_assign<dir_path>,
+ &simple_append<dir_path>,
+ &simple_prepend<dir_path>,
&simple_reverse<dir_path>,
nullptr, // No cast (cast data_ directly).
&simple_compare<dir_path>,
@@ -689,8 +676,8 @@ namespace build2
&default_dtor<abs_dir_path>,
&default_copy_ctor<abs_dir_path>,
&default_copy_assign<abs_dir_path>,
- &simple_assign<abs_dir_path, true>, // Allow empty paths.
- &simple_append<abs_dir_path, true>,
+ &simple_assign<abs_dir_path>,
+ &simple_append<abs_dir_path>,
nullptr, // No prepend.
&simple_reverse<abs_dir_path>,
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<name>,
&default_copy_ctor<name>,
&default_copy_assign<name>,
- &simple_assign<name, true>, // Allow empty names.
+ &simple_assign<name>,
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<T>::convert (move (l), &r);
}
+ // This one will be SFINAE'd out unless T is a container.
+ //
template <typename T>
- inline T
- convert (names&& ns)
+ inline auto
+ convert (names&& ns) -> decltype (value_traits<T>::convert (move (ns)))
{
return value_traits<T>::convert (move (ns));
}
@@ -474,11 +484,10 @@ namespace build2
inline void value_traits<name>::
assign (value& v, name&& x)
{
- name* p (v
- ? &(v.as<name> () = move (x))
- : new (&v.data_) name (move (x)));
-
- p->original = false;
+ if (v)
+ v.as<name> () = move (x);
+ else
+ new (&v.data_) name (move (x));
}
inline int value_traits<name>::
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 <typename T>
+ auto
+ convert (names&& ns) ->
+ decltype (value_traits<T>::convert (move (ns[0]), nullptr))
+ {
+ size_t n (ns.size ());
+
+ if (n == 0)
+ {
+ if (value_traits<T>::empty_value)
+ return T ();
+ }
+ else if (n == 1)
+ {
+ return convert<T> (move (ns[0]));
+ }
+ else if (n == 2 && ns[0].pair != '\0')
+ {
+ return convert<T> (move (ns[0]), move (ns[1]));
+ }
+
+ throw invalid_argument (
+ string ("invalid ") + value_traits<T>::type_name +
+ (n == 0 ? " value: empty" : " value: multiple names"));
+ }
+
+ template <typename T>
void
default_dtor (value& v)
{
@@ -42,13 +68,13 @@ namespace build2
return value_traits<T>::empty (v.as<T> ());
}
- template <typename T, bool empty>
+ template <typename T>
void
simple_assign (value& v, names&& ns, const variable* var)
{
size_t n (ns.size ());
- if (empty ? n <= 1 : n == 1)
+ if (value_traits<T>::empty_value ? n <= 1 : n == 1)
{
try
{
@@ -72,13 +98,13 @@ namespace build2
dr << " in variable " << var->name;
}
- template <typename T, bool empty>
+ template <typename T>
void
simple_append (value& v, names&& ns, const variable* var)
{
size_t n (ns.size ());
- if (empty ? n <= 1 : n == 1)
+ if (value_traits<T>::empty_value ? n <= 1 : n == 1)
{
try
{
@@ -102,13 +128,13 @@ namespace build2
dr << " in variable " << var->name;
}
- template <typename T, bool empty>
+ template <typename T>
void
simple_prepend (value& v, names&& ns, const variable* var)
{
size_t n (ns.size ());
- if (empty ? n <= 1 : n == 1)
+ if (value_traits<T>::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 <<EOI >>>foo-$(build.version).test;
+cat <<EOI >>>"foo-$(build.version).test";
cmd
EOI
$* <<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 <<EOI # group-empty
{