aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/script/parser.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/script/parser.cxx')
-rw-r--r--libbuild2/script/parser.cxx162
1 files changed, 157 insertions, 5 deletions
diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx
index a00651c..d5cff1a 100644
--- a/libbuild2/script/parser.cxx
+++ b/libbuild2/script/parser.cxx
@@ -97,7 +97,7 @@ namespace build2
}
optional<process_path> parser::
- parse_program (token& t, type& tt, bool, names& ns)
+ parse_program (token& t, type& tt, bool, bool, names& ns)
{
parse_names (t, tt,
ns,
@@ -1019,6 +1019,18 @@ namespace build2
}
}
+ bool prog (p == pending::program_first ||
+ p == pending::program_next);
+
+ // Check if this is the env pseudo-builtin.
+ //
+ bool env (false);
+ if (prog && tt == type::word && t.value == "env")
+ {
+ c.variables = parse_env_builtin (t, tt);
+ env = true;
+ }
+
// Parse the next chunk as names to get expansion, etc. Note that
// we do it in the chunking mode to detect whether anything in
// each chunk is quoted. If we are waiting for the command
@@ -1033,10 +1045,10 @@ namespace build2
//
reset_quoted (t);
- if (p == pending::program_first || p == pending::program_next)
+ if (prog)
{
optional<process_path> pp (
- parse_program (t, tt, p == pending::program_first, ns));
+ parse_program (t, tt, p == pending::program_first, env, ns));
// During pre-parsing we are not interested in the
// parse_program() call result, so just discard the potentially
@@ -1088,7 +1100,7 @@ namespace build2
{
diag_record dr (fail (l));
dr << "invalid string value ";
- to_stream (dr.os, n, true); // Quote.
+ to_stream (dr.os, n, true /* quote */);
}
// If it is a quoted chunk, then we add the word as is.
@@ -1275,6 +1287,146 @@ namespace build2
return make_pair (move (expr), move (hd));
}
+ environment_vars parser::
+ parse_env_builtin (token& t, token_type& tt)
+ {
+ // enter: 'env' word token
+ // leave: first token of the program name
+
+ next (t, tt); // Skip 'env'.
+
+ // Note that the -u option and its value can belong to the different
+ // name chunks. That's why we parse the env builtin arguments in the
+ // chunking mode into the argument/location pair list up to the '--'
+ // separator and parse this list into the variable sets/unsets
+ // afterwords.
+ //
+ // Align the size with environment_vars (double because of -u <var>
+ // which is two arguments).
+ //
+ using args = small_vector<pair<string, location>, 4>;
+
+ args as;
+ names ns; // Reuse to reduce allocations.
+ while (tt != type::word || t.value != "--")
+ {
+ location l (get_location (t));
+
+ if (!start_names (tt))
+ fail (l) << "env: expected option, variable, or '--' separator "
+ << "instead of " << t;
+
+ parse_names (t, tt,
+ ns,
+ pattern_mode::ignore,
+ true /* chunk */,
+ "env builtin argument",
+ nullptr);
+
+ if (pre_parse_)
+ continue;
+
+ for (name& n: ns)
+ {
+ try
+ {
+ as.emplace_back (
+ value_traits<string>::convert (move (n), nullptr), l);
+ }
+ catch (const invalid_argument&)
+ {
+ diag_record dr (fail (l));
+ dr << "invalid string value ";
+ to_stream (dr.os, n, true /* quote */);
+ }
+ }
+
+ ns.clear ();
+ }
+
+ location l (get_location (t)); // '--' location.
+ next (t, tt); // Skip '--'.
+
+ if (tt == type::newline || tt == type::eos)
+ fail (t) << "env: expected program name instead of " << t;
+
+ // Parse the env builtin options and arguments.
+ //
+ environment_vars r;
+
+ // Note: args is empty in the pre-parse mode.
+ //
+ auto i (as.begin ()), e (as.end ());
+
+ // Parse the variable unsets (from options).
+ //
+ for (; i != e; ++i)
+ {
+ string& o (i->first);
+
+ // Bail out if the options and arguments separator is encountered.
+ //
+ if (o == "-")
+ {
+ ++i;
+ break;
+ }
+
+ // Unset the variable, adding its name to the resulting variable list.
+ //
+ auto unset = [&r, &i, this] (string&& v, const char* o)
+ {
+ if (v.empty ())
+ fail (i->second) << "env: empty value for option '" << o << "'";
+
+ if (v.find ('=') != string::npos)
+ fail (i->second) << "env: invalid value '" << v << "' for "
+ << "option '" << o << "': contains '='";
+
+ r.push_back (move (v));
+ };
+
+ // If this is the --unset|-u option then add the variable unset and
+ // bail out to parsing the variable sets otherwise.
+ //
+ if (o == "--unset" || o == "-u")
+ {
+ if (++i == e)
+ fail (l) << "env: missing value for option '" << o << "'";
+
+ unset (move (i->first), o.c_str ());
+ }
+ else if (o.compare (0, 8, "--unset=") == 0)
+ unset (string (o, 8), "--unset");
+ else
+ break;
+ }
+
+ // Parse the variable sets (from arguments).
+ //
+ for (; i != e; ++i)
+ {
+ string& a (i->first);
+
+ // Validate the variable assignment.
+ //
+ size_t p (a.find ('='));
+
+ if (p == string::npos)
+ fail (i->second)
+ << "env: expected variable assignment instead of '" << a << "'";
+
+ if (p == 0)
+ fail (i->second) << "env: empty variable name";
+
+ // Add the variable set to the resulting list.
+ //
+ r.push_back (move (a));
+ }
+
+ return r;
+ }
+
command_exit parser::
parse_command_exit (token& t, type& tt)
{
@@ -1310,7 +1462,7 @@ namespace build2
diag_record dr;
dr << fail (l) << "expected exit status instead of ";
- to_stream (dr.os, ns, true); // Quote.
+ to_stream (dr.os, ns, true /* quote */);
dr << info << "exit status is an unsigned integer less than 256";
}