aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-10-16 13:29:57 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-11-04 09:26:21 +0200
commit4230333bc5b32d30e35264b1104240bb5e2247ff (patch)
tree0e3873c012dcf83281ecb43c4cfe2780a794a25c
parentd81ad6a0b20613ac77e115ca273cd48eaeeae1c8 (diff)
Implement testscript $*, $NN, $~ special variables
-rw-r--r--build2/test/script/lexer4
-rw-r--r--build2/test/script/lexer.cxx30
-rw-r--r--build2/test/script/parser2
-rw-r--r--build2/test/script/parser.cxx125
-rw-r--r--build2/test/script/runner.cxx2
-rw-r--r--build2/test/script/script15
-rw-r--r--build2/test/script/script.cxx49
-rw-r--r--unit-tests/test/script/lexer/driver.cxx21
-rw-r--r--unit-tests/test/script/lexer/script-line.test10
-rw-r--r--unit-tests/test/script/lexer/variable.test46
10 files changed, 266 insertions, 38 deletions
diff --git a/build2/test/script/lexer b/build2/test/script/lexer
index 99f87c0..be65018 100644
--- a/build2/test/script/lexer
+++ b/build2/test/script/lexer
@@ -62,8 +62,8 @@ namespace build2
token
next_line ();
- token
- name_line (bool separated);
+ virtual token
+ name (bool) override;
protected:
bool quoted_ = false;
diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx
index 324e577..4d57fe4 100644
--- a/build2/test/script/lexer.cxx
+++ b/build2/test/script/lexer.cxx
@@ -263,6 +263,36 @@ namespace build2
unget (c);
return name (sep);
}
+
+ token lexer::
+ name (bool sep)
+ {
+ // Customized implementation that handles special variable names ($*,
+ // $~, $NNN).
+ //
+ if (state_.top ().mode != lexer_mode::variable)
+ return base_lexer::name (sep);
+
+ xchar c (peek ());
+
+ if (c != '*' && c != '~' && !digit (c))
+ return base_lexer::name (sep);
+
+ uint64_t ln (c.line), cn (c.column);
+ string lexeme;
+
+ get ();
+ lexeme += c;
+
+ if (digit (c))
+ {
+ for (; digit (c = peek ()); get ())
+ lexeme += c;
+ }
+
+ state_.pop (); // Expire the variable mode.
+ return token (move (lexeme), sep, false, ln, cn);
+ }
}
}
}
diff --git a/build2/test/script/parser b/build2/test/script/parser
index 05b4d47..daaa953 100644
--- a/build2/test/script/parser
+++ b/build2/test/script/parser
@@ -46,7 +46,7 @@ namespace build2
parse_script_line (token&, token_type&);
void
- parse_variable_line (token&, token_type&, string);
+ parse_variable_line (token&, token_type&, string, location);
void
parse_test_line (token&, token_type&, names, location);
diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx
index 7d234eb..01dd010 100644
--- a/build2/test/script/parser.cxx
+++ b/build2/test/script/parser.cxx
@@ -98,15 +98,34 @@ namespace build2
if (ns.size () != 1 || !ns[0].simple () || ns[0].empty ())
fail (nl) << "variable name expected instead of '" << ns << "'";
- parse_variable_line (t, tt, move (ns[0].value));
+ parse_variable_line (t, tt, move (ns[0].value), move (nl));
}
else
parse_test_line (t, tt, move (ns), move (nl));
}
+ // Return true if the string contains only digit characters (used to
+ // detect the special $NN variables).
+ //
+ static inline bool
+ digits (const string& s)
+ {
+ for (char c: s)
+ if (!digit (c))
+ return false;
+
+ return !s.empty ();
+ }
+
void parser::
- parse_variable_line (token& t, token_type& tt, string name)
+ parse_variable_line (token& t, token_type& tt, string name, location nl)
{
+ // Check if we are trying to modify any of the special aliases ($*,
+ // $~, $N).
+ //
+ if (name == "*" || name == "~" || digits (name))
+ fail (nl) << "attempt to set '" << name << "' variable directly";
+
type kind (tt); // Assignment kind.
const variable& var (script_->var_pool.insert (move (name)));
@@ -125,6 +144,23 @@ namespace build2
// @@ Need to adjust to make strings the default type.
//
apply_value_attributes (&var, lhs, move (rhs), kind);
+
+ // Handle the $*, $NN special aliases.
+ //
+ // The plan is as follows: in this function we detect modification of
+ // the source variables (test*), and (re)set $* to NULL on this scope
+ // (this is important to both invalidate any old values but also to
+ // "stake" the lookup position). This signals to the variable lookup
+ // function below that the $* and $NN values need to be recalculated
+ // from their sources. Note that we don't need to invalidate $NN since
+ // their lookup always checks $* first.
+ //
+ if (var.name == script_->test_var.name ||
+ var.name == script_->opts_var.name ||
+ var.name == script_->args_var.name)
+ {
+ scope_->assign (script_->cmd_var) = nullptr;
+ }
}
void parser::
@@ -599,13 +635,90 @@ namespace build2
}
lookup parser::
- lookup_variable (name&& qual, string&& name, const location& l)
+ lookup_variable (name&& qual, string&& name, const location& loc)
{
if (!qual.empty ())
- fail (l) << "qualified variable name";
+ fail (loc) << "qualified variable name";
- const variable& var (script_->var_pool.insert (move (name)));
- return scope_->find (var);
+ if (name != "*" && !digits (name))
+ return scope_->find (script_->var_pool.insert (move (name)));
+
+ // Handle the $*, $NN special aliases.
+ //
+ // See the parse_variable_line() for the overall plan.
+ //
+
+ // In both cases first thing we do is lookup $*. It should always be
+ // defined since we set it on the script's root scope.
+ //
+ lookup l (scope_->find (script_->cmd_var));
+ assert (l.defined ());
+
+ // $* NULL value means it needs to be (re)calculated.
+ //
+ value& v (const_cast<value&> (*l));
+ bool recalc (v.null);
+
+ if (recalc)
+ {
+ strings s;
+
+ auto append = [&s] (const strings& v)
+ {
+ s.insert (s.end (), v.begin (), v.end ());
+ };
+
+ if (lookup l = scope_->find (script_->test_var))
+ s.push_back (cast<path> (l).string ());
+
+ if (lookup l = scope_->find (script_->opts_var))
+ append (cast<strings> (l));
+
+ if (lookup l = scope_->find (script_->args_var))
+ append (cast<strings> (l));
+
+ v = move (s);
+ }
+
+ if (name == "*")
+ return l;
+
+ // Use the string type for the $NN variables.
+ //
+ const variable& var (script_->var_pool.insert<string> (move (name)));
+
+ // We need to look for $NN in the same scope as where we found $*.
+ //
+ variable_map& vars (const_cast<variable_map&> (*l.vars));
+
+ // If there is already a value and no need to recalculate it, then we
+ // are done.
+ //
+ if (!recalc && (l = vars[var]).defined ())
+ return l;
+
+ // Convert the variable name to index we can use on $*.
+ //
+ unsigned long i;
+
+ try
+ {
+ i = stoul (var.name);
+ }
+ catch (const exception&)
+ {
+ fail (loc) << "invalid $* index " << var.name;
+ }
+
+ const strings& s (cast<strings> (v));
+ value& nv (vars.assign (var));
+
+ if (i < s.size ())
+ nv = s[i];
+ else
+ nv = nullptr;
+
+ return lookup (nv, vars);
}
}
}
diff --git a/build2/test/script/runner.cxx b/build2/test/script/runner.cxx
index 69b0213..a17b9bf 100644
--- a/build2/test/script/runner.cxx
+++ b/build2/test/script/runner.cxx
@@ -16,7 +16,7 @@ namespace build2
run (const test& t)
{
// @@ TODO
- text << "run " << t.program;
+ text << "run " << t.program.string ();
}
}
}
diff --git a/build2/test/script/script b/build2/test/script/script
index 005d6e5..b6a0f21 100644
--- a/build2/test/script/script
+++ b/build2/test/script/script
@@ -10,6 +10,8 @@
#include <build2/variable>
+#include <build2/test/target>
+
namespace build2
{
class target;
@@ -103,14 +105,21 @@ namespace build2
class script: public scope
{
public:
- script (target& test_target, target& script_target);
+ script (target& test_target, testscript& script_target);
public:
- target& test_target; // Target we are testing.
- target& script_target; // Target of the testscript file.
+ target& test_target; // Target we are testing.
+ testscript& script_target; // Target of the testscript file.
public:
variable_pool var_pool;
+
+ const variable& test_var; // test
+ const variable& opts_var; // test.options
+ const variable& args_var; // test.arguments
+
+ const variable& cmd_var; // $*
+ const variable& cwd_var; // $~
};
}
}
diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx
index b206e4f..2be023d 100644
--- a/build2/test/script/script.cxx
+++ b/build2/test/script/script.cxx
@@ -15,32 +15,45 @@ namespace build2
namespace script
{
script::
- script (target& tt, target& st)
- : test_target (tt), script_target (st)
+ script (target& tt, testscript& st)
+ : test_target (tt), script_target (st),
+
+ // Enter the test* variables with the same variable types as in
+ // buildfiles.
+ //
+ test_var (var_pool.insert<path> ("test")),
+ opts_var (var_pool.insert<strings> ("test.options")),
+ args_var (var_pool.insert<strings> ("test.arguments")),
+
+ cmd_var (var_pool.insert<strings> ("*")),
+ cwd_var (var_pool.insert<dir_path> ("~"))
{
// Unless we have the test variable set on the test or script target,
// set it at the script level to the test target's path.
//
+ if (!find (test_var))
{
- // Note: use the same variable type as in buildfile.
+ value& v (assign (test_var));
+
+ // If this is a path-based target, then we use the path. If this
+ // is a directory (alias) target, then we use the directory path.
+ // Otherwise, we leave it NULL expecting the testscript to set it
+ // to something appropriate, if used.
//
- const variable& var (var_pool.insert<path> ("test"));
+ if (auto* p = tt.is_a<path_target> ())
+ v = p->path ();
+ else if (tt.is_a<dir> ())
+ v = path (tt.dir.string ()); // Strip trailing slash.
+ }
- if (!find (var))
- {
- value& v (assign (var));
+ // Also add the NULL $* value that signals it needs to be recalculated
+ // on first access.
+ //
+ assign (cmd_var) = nullptr;
- // If this is a path-based target, then we use the path. If this
- // is a directory (alias) target, then we use the directory path.
- // Otherwise, we leave it NULL expecting the testscript to set it
- // to something appropriate, if used.
- //
- if (auto* p = tt.is_a<path_target> ())
- v = p->path ();
- else if (tt.is_a<dir> ())
- v = path (tt.dir.string ()); // Strip trailing slash.
- }
- }
+ // Set the script CWD ($~) which is the $out_base/<script-name>.
+ //
+ assign (cwd_var) = dir_path (tt.out_dir ()) /= st.name;
}
lookup scope::
diff --git a/unit-tests/test/script/lexer/driver.cxx b/unit-tests/test/script/lexer/driver.cxx
index 6e5167d..7e237ed 100644
--- a/unit-tests/test/script/lexer/driver.cxx
+++ b/unit-tests/test/script/lexer/driver.cxx
@@ -29,18 +29,25 @@ namespace build2
assert (argc == 2);
string s (argv[1]);
- if (s == "script") m = lexer_mode::script_line;
- else if (s == "variable") m = lexer_mode::variable_line;
- else if (s == "test") m = lexer_mode::test_line;
- else if (s == "command") m = lexer_mode::command_line;
- else if (s == "here") m = lexer_mode::here_line;
- else assert (false);
+ if (s == "script-line") m = lexer_mode::script_line;
+ else if (s == "variable-line") m = lexer_mode::variable_line;
+ else if (s == "test-line") m = lexer_mode::test_line;
+ else if (s == "command-line") m = lexer_mode::command_line;
+ else if (s == "here-line") m = lexer_mode::here_line;
+ else if (s == "variable") m = lexer_mode::variable;
+ else assert (false);
}
try
{
cin.exceptions (istream::failbit | istream::badbit);
- lexer l (cin, path ("stdin"), m);
+
+ // The variable mode auto-expires so we need something underneath.
+ //
+ bool u (m == lexer_mode::variable);
+ lexer l (cin, path ("stdin"), u ? lexer_mode::script_line : m);
+ if (u)
+ l.mode (m);
// No use printing eos since we will either get it or loop forever.
//
diff --git a/unit-tests/test/script/lexer/script-line.test b/unit-tests/test/script/lexer/script-line.test
index 8fd77ce..f03c3bb 100644
--- a/unit-tests/test/script/lexer/script-line.test
+++ b/unit-tests/test/script/lexer/script-line.test
@@ -6,3 +6,13 @@ $foo
fox = $foo-$cxx.target.class
$fox
$test
+$~
+$*
+$0
+test = xxx
+$0
+$*
+test.options += --foo
+$1
+test.arguments += bar
+$2
diff --git a/unit-tests/test/script/lexer/variable.test b/unit-tests/test/script/lexer/variable.test
new file mode 100644
index 0000000..6478fea
--- /dev/null
+++ b/unit-tests/test/script/lexer/variable.test
@@ -0,0 +1,46 @@
+# Test handling custom variable names ($*, $~, $NN).
+#
+test.arguments += variable
+
+$* <"*" >>EOO
+'*'
+<newline>
+EOO
+
+$* <"*abc" >>EOO
+'*'
+'abc'
+<newline>
+EOO
+
+$* <"~" >>EOO
+'~'
+<newline>
+EOO
+
+$* <"~123" >>EOO
+'~'
+'123'
+<newline>
+EOO
+
+$* <"0" >>EOO
+'0'
+<newline>
+EOO
+
+$* <"10" >>EOO
+'10'
+<newline>
+EOO
+
+$* <"101" >>EOO
+'101'
+<newline>
+EOO
+
+$* <"1abc" >>EOO
+'1'
+'abc'
+<newline>
+EOO