aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2022-09-23 20:08:47 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2022-09-28 19:07:50 +0300
commit1c60c97b6b05cbee7e106fae6d8582382cbe4b7c (patch)
treeee389ea12fbe0dcecd39c32fd39fcb6c754af45a
parent744e8215261fbf81b9348d115d4916a9c88b52cc (diff)
Add support for 'for' loop first form (for x:...) in script
-rw-r--r--libbuild2/build/script/parser+for.test.testscript184
-rw-r--r--libbuild2/build/script/parser.cxx119
-rw-r--r--libbuild2/build/script/parser.hxx2
-rw-r--r--libbuild2/parser.hxx10
-rw-r--r--libbuild2/script/parser.cxx190
-rw-r--r--libbuild2/script/parser.hxx17
-rw-r--r--libbuild2/script/script.cxx4
-rw-r--r--libbuild2/script/script.hxx1
-rw-r--r--libbuild2/test/script/parser+for.test.testscript315
-rw-r--r--libbuild2/test/script/parser.cxx157
-rw-r--r--libbuild2/test/script/parser.hxx7
-rw-r--r--tests/recipe/buildscript/testscript122
-rw-r--r--tests/test/script/runner/for.testscript42
13 files changed, 1067 insertions, 103 deletions
diff --git a/libbuild2/build/script/parser+for.test.testscript b/libbuild2/build/script/parser+for.test.testscript
new file mode 100644
index 0000000..f4ebf3c
--- /dev/null
+++ b/libbuild2/build/script/parser+for.test.testscript
@@ -0,0 +1,184 @@
+# file : libbuild2/build/script/parser+for.test.testscript
+# license : MIT; see accompanying LICENSE file
+
+: form-1
+:
+: for x: ...
+:
+{
+ : for
+ :
+ {
+ : no-var
+ :
+ $* <<EOI 2>>EOE != 0
+ for
+ cmd
+ end
+ EOI
+ buildfile:11:4: error: expected variable name instead of <newline>
+ EOE
+
+ : untyped
+ :
+ $* <<EOI >>EOO
+ for x: a b
+ cmd $x
+ end
+ EOI
+ cmd a
+ cmd b
+ EOO
+
+ : null
+ :
+ $* <<EOI >:''
+ for x: [null]
+ cmd $x
+ end
+ EOI
+
+ : empty
+ :
+ $* <<EOI >:''
+ for x:
+ cmd $x
+ end
+ EOI
+
+ : expansion
+ :
+ $* <<EOI >>EOO
+ vs = a b
+ for x: $vs
+ cmd $x
+ end
+ EOI
+ cmd a
+ cmd b
+ EOO
+
+ : typed-value
+ :
+ $* <<EOI >>~%EOO%
+ for x: [dir_paths] a b
+ cmd $x
+ end
+ EOI
+ %cmd (a/|'a\\')%
+ %cmd (b/|'b\\')%
+ EOO
+
+ : typed-var
+ :
+ $* <<EOI >>~%EOO%
+ for [dir_path] x: a b
+ cmd $x
+ end
+ EOI
+ %cmd (a/|'a\\')%
+ %cmd (b/|'b\\')%
+ EOO
+
+ : type-mismatch
+ :
+ $* <<EOI 2>>EOE != 0
+ for [string] x: [dir_paths] a b
+ cmd $x
+ end
+ EOI
+ error: type mismatch in variable x
+ info: value type is dir_path
+ info: variable type is string
+ EOE
+
+ : defined-var
+ :
+ $* <<EOI >>EOO
+ x = x
+
+ for x: a b
+ cmd $x
+ end
+
+ cmd $x
+ EOI
+ cmd a
+ cmd b
+ cmd b
+ EOO
+ }
+
+ : end
+ :
+ {
+ : without-end
+ :
+ $* <<EOI 2>>EOE != 0
+ for x: a b
+ cmd
+ EOI
+ buildfile:13:1: error: expected closing 'end'
+ EOE
+ }
+
+ : elif
+ :
+ {
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ for x: a b
+ elif true
+ cmd
+ end
+ end
+ EOI
+ buildfile:12:3: error: 'elif' without preceding 'if'
+ EOE
+ }
+
+ : nested
+ :
+ {
+ $* -l -r <<EOI >>EOO
+ for x: a b
+ cmd1 $x # 1
+ if ($x == "a") # 2
+ cmd2 # 3
+ for y: x y
+ cmd3 # 4
+ end
+ else
+ cmd4 # 5
+ end
+ cmd5 # 6
+ end
+ cmd6 # 7
+ EOI
+ cmd1 a # 1 i1
+ ? true # 2 i1
+ cmd2 # 3 i1
+ cmd3 # 4 i1 i1
+ cmd3 # 4 i1 i2
+ cmd5 # 6 i1
+ cmd1 b # 1 i2
+ ? false # 2 i2
+ cmd4 # 5 i2
+ cmd5 # 6 i2
+ cmd6 # 7
+ EOO
+ }
+
+ : contained
+ :
+ {
+ : eos
+ :
+ $* <<EOI 2>>EOE != 0
+ for x:
+ EOI
+ buildfile:12:1: error: expected closing 'end'
+ EOE
+ }
+}
diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx
index 1a7f4e1..cb0dbbb 100644
--- a/libbuild2/build/script/parser.cxx
+++ b/libbuild2/build/script/parser.cxx
@@ -205,9 +205,10 @@ namespace build2
// enter: next token is peeked at (type in tt)
// leave: newline
- assert (!fct ||
- *fct == line_type::cmd_if ||
- *fct == line_type::cmd_while);
+ assert (!fct ||
+ *fct == line_type::cmd_if ||
+ *fct == line_type::cmd_while ||
+ *fct == line_type::cmd_for);
// Determine the line type/start token.
//
@@ -245,6 +246,52 @@ namespace build2
break;
}
+ case line_type::cmd_for:
+ {
+ // First take care of the variable name.
+ //
+ mode (lexer_mode::normal);
+
+ next_with_attributes (t, tt);
+ attributes_push (t, tt);
+
+ if (tt != type::word || t.qtype != quote_type::unquoted)
+ fail (t) << "expected variable name instead of " << t;
+
+ const string& n (t.value);
+
+ if (special_variable (n))
+ fail (t) << "attempt to set '" << n << "' variable directly";
+
+ // We don't pre-enter variables.
+ //
+ ln.var = nullptr;
+
+ next (t, tt);
+
+ if (tt != type::colon)
+ {
+ // @@ TMP We will need to fallback to parsing the 'for x <...'
+ // form instead.
+ //
+ fail (t) << "expected ':' instead of " << t
+ << " after variable name";
+ }
+
+ expire_mode (); // Expire the normal lexer mode.
+
+ // Parse the value similar to the var line type (see above).
+ //
+ mode (lexer_mode::variable_line);
+ parse_variable_line (t, tt);
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t << " after for";
+
+ ++level_;
+
+ break;
+ }
case line_type::cmd_elif:
case line_type::cmd_elifn:
case line_type::cmd_else:
@@ -300,17 +347,25 @@ namespace build2
*save_line_ = move (ln);
}
- if (lt == line_type::cmd_if || lt == line_type::cmd_ifn)
+ switch (lt)
{
- tt = peek (lexer_mode::first_token);
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
+ {
+ tt = peek (lexer_mode::first_token);
- pre_parse_if_else (t, tt);
- }
- else if (lt == line_type::cmd_while)
- {
- tt = peek (lexer_mode::first_token);
+ pre_parse_if_else (t, tt);
+ break;
+ }
+ case line_type::cmd_while:
+ case line_type::cmd_for:
+ {
+ tt = peek (lexer_mode::first_token);
- pre_parse_while (t, tt);
+ pre_parse_loop (t, tt, lt);
+ break;
+ }
+ default: break;
}
}
@@ -341,8 +396,9 @@ namespace build2
break;
}
case line_type::cmd_while:
+ case line_type::cmd_for:
{
- fct = line_type::cmd_while;
+ fct = bt;
break;
}
default: assert(false);
@@ -405,18 +461,20 @@ namespace build2
}
void parser::
- pre_parse_while (token& t, type& tt)
+ pre_parse_loop (token& t, type& tt, line_type lt)
{
// enter: peeked first token of next line (type in tt)
// leave: newline
+ assert (lt == line_type::cmd_while || lt == line_type::cmd_for);
+
// Parse lines until we see closing 'end'.
//
for (;; tt = peek (lexer_mode::first_token))
{
size_t i (script_->body.size ());
- pre_parse_block_line (t, tt, line_type::cmd_while);
+ pre_parse_block_line (t, tt, lt);
if (script_->body[i].type == line_type::cmd_end)
break;
@@ -1247,25 +1305,19 @@ namespace build2
// Note that we rely on "small function object" optimization for the
// exec_*() lambdas.
//
- auto exec_set = [this] (const variable& var,
- token& t, build2::script::token_type& tt,
- const location&)
+ auto exec_assign = [this] (const variable& var,
+ value&& val,
+ type kind,
+ const location& l)
{
- next (t, tt);
- type kind (tt); // Assignment kind.
-
- mode (lexer_mode::variable_line);
- value rhs (parse_variable_line (t, tt));
-
- assert (tt == type::newline);
-
- // Assign.
- //
value& lhs (kind == type::assign
? environment_->assign (var)
: environment_->append (var));
- apply_value_attributes (&var, lhs, move (rhs), kind);
+ if (kind == type::assign)
+ lhs = move (val);
+ else
+ append_value (&var, lhs, move (val), l);
};
auto exec_cond = [this] (token& t, build2::script::token_type& tt,
@@ -1281,11 +1333,12 @@ namespace build2
return runner_->run_cond (*environment_, ce, ii, li, ll);
};
- build2::script::parser::exec_lines (begin, end,
- exec_set, exec_cmd, exec_cond,
- nullptr /* iteration_index */,
- environment_->exec_line,
- &environment_->var_pool);
+ build2::script::parser::exec_lines (
+ begin, end,
+ exec_assign, exec_cmd, exec_cond,
+ nullptr /* iteration_index */,
+ environment_->exec_line,
+ &environment_->var_pool);
}
names parser::
diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx
index 987ed1f..a81583c 100644
--- a/libbuild2/build/script/parser.hxx
+++ b/libbuild2/build/script/parser.hxx
@@ -75,7 +75,7 @@ namespace build2
pre_parse_if_else (token&, token_type&);
void
- pre_parse_while (token&, token_type&);
+ pre_parse_loop (token&, token_type&, line_type);
command_expr
parse_command_line (token&, token_type&);
diff --git a/libbuild2/parser.hxx b/libbuild2/parser.hxx
index 5c9d16a..0d1e9e2 100644
--- a/libbuild2/parser.hxx
+++ b/libbuild2/parser.hxx
@@ -759,6 +759,16 @@ namespace build2
}
void
+ replay_pop ()
+ {
+ assert (replay_ == replay::save);
+
+ assert (!peeked_ && !replay_data_.empty ());
+
+ replay_data_.pop_back ();
+ }
+
+ void
replay_play ()
{
assert ((replay_ == replay::save && !replay_data_.empty ()) ||
diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx
index c371e5b..aa186ff 100644
--- a/libbuild2/script/parser.cxx
+++ b/libbuild2/script/parser.cxx
@@ -2035,6 +2035,37 @@ namespace build2
build2::parser::apply_value_attributes (var, lhs, move (rhs), kind);
}
+ void parser::
+ append_value (const variable* var,
+ value& v,
+ value&& rhs,
+ const location& l)
+ {
+ if (rhs) // Don't append/prepent NULL.
+ {
+ // Perform the type conversion (see
+ // build2::parser::apply_value_attributes() for the approach
+ // reasoning).
+ //
+ if (rhs.type != nullptr)
+ {
+ if (!v)
+ v.type = rhs.type;
+ else if (v.type == nullptr)
+ typify (v, *rhs.type, var);
+ else if (v.type != rhs.type)
+ fail (l) << "conflicting original value type " << v.type->name
+ << " and append value type " << rhs.type->name;
+
+ // Reduce this to the untyped value case.
+ //
+ untypify (rhs);
+ }
+
+ v.append (move (rhs).as<names> (), var);
+ }
+ }
+
line_type parser::
pre_parse_line_start (token& t, token_type& tt, lexer_mode stm)
{
@@ -2062,6 +2093,7 @@ namespace build2
else if (n == "elif!") r = line_type::cmd_elifn;
else if (n == "else") r = line_type::cmd_else;
else if (n == "while") r = line_type::cmd_while;
+ else if (n == "for") r = line_type::cmd_for;
else if (n == "end") r = line_type::cmd_end;
else
{
@@ -2090,7 +2122,7 @@ namespace build2
bool parser::
exec_lines (lines::const_iterator i, lines::const_iterator e,
- const function<exec_set_function>& exec_set,
+ const function<exec_assign_function>& exec_assign,
const function<exec_cmd_function>& exec_cmd,
const function<exec_cond_function>& exec_cond,
const iteration_index* ii, size_t& li,
@@ -2134,9 +2166,10 @@ namespace build2
{
line_type lt (j->type);
- if (lt == line_type::cmd_if ||
- lt == line_type::cmd_ifn ||
- lt == line_type::cmd_while)
+ if (lt == line_type::cmd_if ||
+ lt == line_type::cmd_ifn ||
+ lt == line_type::cmd_while ||
+ lt == line_type::cmd_for)
++n;
// If we are nested then we just wait until we get back
@@ -2163,6 +2196,9 @@ namespace build2
{
// Note that we don't count else and end as commands.
//
+ // @@ Note that for the for-loop's second and third forms
+ // will probably need to increment li.
+ //
switch (lt)
{
case line_type::cmd:
@@ -2197,7 +2233,25 @@ namespace build2
var = &var_pool->insert (t.value);
}
- exec_set (*var, t, tt, ll);
+ next (t, tt);
+ type kind (tt); // Assignment kind.
+
+ assert (kind == type::assign || kind == type::append);
+
+ // Parse the value with the potential attributes.
+ //
+ // Note that we don't really need to change the mode since we
+ // are replaying the tokens.
+ //
+ value val;
+ apply_value_attributes (var,
+ val,
+ parse_variable_line (t, tt),
+ kind);
+
+ assert (tt == type::newline);
+
+ exec_assign (*var, move (val), kind, ll);
replay_stop ();
break;
@@ -2258,7 +2312,7 @@ namespace build2
lines::const_iterator j (fcend (i, false, false));
if (!exec_lines (i + 1, j,
- exec_set, exec_cmd, exec_cond,
+ exec_assign, exec_cmd, exec_cond,
ii, li,
var_pool))
return false;
@@ -2281,6 +2335,10 @@ namespace build2
}
case line_type::cmd_while:
{
+ // The while-loop construct end. Set on the first iteration.
+ //
+ lines::const_iterator we (e);
+
size_t wli (li);
for (iteration_index wi {1, ii};; wi.index++)
@@ -2300,12 +2358,13 @@ namespace build2
//
if (exec)
{
- // Find construct end.
+ // Find the construct end, if it is not found yet.
//
- lines::const_iterator j (fcend (i, true, false));
+ if (we == e)
+ we = fcend (i, true, false);
- if (!exec_lines (i + 1, j,
- exec_set, exec_cmd, exec_cond,
+ if (!exec_lines (i + 1, we,
+ exec_assign, exec_cmd, exec_cond,
&wi, li,
var_pool))
return false;
@@ -2318,7 +2377,8 @@ namespace build2
}
else
{
- // Find construct end.
+ // Position to the construct end, always incrementing the
+ // line index (skip is true).
//
i = fcend (i, true, true);
break; // Bail out from the while-loop.
@@ -2327,9 +2387,117 @@ namespace build2
break;
}
+ case line_type::cmd_for:
+ {
+ // Parse the variable name with the potential attributes.
+ //
+ next_with_attributes (t, tt);
+ attributes_push (t, tt);
+
+ assert (tt == type::word && t.qtype == quote_type::unquoted);
+
+ string vn (move (t.value));
+
+ // Enter the variable into the pool if this is not done during
+ // the script parsing (see the var line type handling for
+ // details).
+ //
+ const variable* var (ln.var);
+
+ if (var == nullptr)
+ {
+ assert (var_pool != nullptr);
+
+ var = &var_pool->insert (move (vn));
+ }
+
+ apply_variable_attributes (*var);
+
+ next (t, tt); // Skip the colon.
+ assert (tt == type::colon);
+
+ // Parse the value with the potential attributes.
+ //
+ // Note that we don't really need to change the mode since we
+ // are replaying the tokens.
+ //
+ value val;
+ apply_value_attributes (nullptr /* variable */,
+ val,
+ parse_variable_line (t, tt),
+ type::assign);
+
+ replay_stop ();
+
+ // If the value is not NULL then iterate over its elements,
+ // assigning them to the for-loop variable, and parsing all the
+ // construct lines afterwards. Then position to the end line of
+ // the construct and continue parsing.
+
+ // The for-loop construct end. Set on the first iteration.
+ //
+ lines::const_iterator fe (e);
+
+ if (val)
+ {
+ // If this value is a vector, then save its element type so
+ // that we can typify each element below.
+ //
+ const value_type* etype (nullptr);
+
+ if (val.type != nullptr)
+ {
+ etype = val.type->element_type;
+ untypify (val);
+ }
+
+ size_t fli (li);
+ iteration_index fi {1, ii};
+
+ // @@ Handle pairs.
+ //
+ // Do we need to always lex the variable values (for-loop
+ // and var lines) pair-character aware?
+ //
+ // Can there be any harm if a value with pairs is
+ // substituted into the command line?
+ //
+ for (name& n: val.as<names> ())
+ {
+ li = fli;
+
+ value v (names {move (n)}); // Untyped.
+
+ if (etype != nullptr)
+ typify (v, *etype, var);
+
+ exec_assign (*var, move (v), type::assign, ll);
+
+ // Find the construct end, if it is not found yet.
+ //
+ if (fe == e)
+ fe = fcend (i, true, false);
+
+ if (!exec_lines (i + 1, fe,
+ exec_assign, exec_cmd, exec_cond,
+ &fi, li,
+ var_pool))
+ return false;
+
+ fi.index++;
+ }
+ }
+
+ // Position to construct end.
+ //
+ i = (fe != e ? fe : fcend (i, true, true));
+
+ break;
+ }
case line_type::cmd_end:
{
assert (false);
+ break;
}
}
}
diff --git a/libbuild2/script/parser.hxx b/libbuild2/script/parser.hxx
index 9edb6ca..0f276e1 100644
--- a/libbuild2/script/parser.hxx
+++ b/libbuild2/script/parser.hxx
@@ -42,6 +42,11 @@ namespace build2
using build2::parser::apply_value_attributes;
+ // The variable is optional and is only used for diagnostics.
+ //
+ void
+ append_value (const variable*, value& lhs, value&& rhs, const location&);
+
// Return true if a command line element needs to be re-lexed.
//
// Specifically, it needs to be re-lexed if it contains any of the
@@ -159,11 +164,13 @@ namespace build2
protected:
// Return false if the execution of the script should be terminated with
// the success status (e.g., as a result of encountering the exit
- // builtin). For unsuccessful termination the failed exception is thrown.
+ // builtin). For unsuccessful termination the failed exception is
+ // thrown.
//
- using exec_set_function = void (const variable&,
- token&, token_type&,
- const location&);
+ using exec_assign_function = void (const variable&,
+ value&&,
+ token_type kind,
+ const location&);
using exec_cmd_function = void (token&, token_type&,
const iteration_index*, size_t li,
@@ -181,7 +188,7 @@ namespace build2
//
bool
exec_lines (lines::const_iterator b, lines::const_iterator e,
- const function<exec_set_function>&,
+ const function<exec_assign_function>&,
const function<exec_cmd_function>&,
const function<exec_cond_function>&,
const iteration_index*, size_t& li,
diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx
index d4096cf..33c4c30 100644
--- a/libbuild2/script/script.cxx
+++ b/libbuild2/script/script.cxx
@@ -28,6 +28,7 @@ namespace build2
case line_type::cmd_elifn: s = "'elif!'"; break;
case line_type::cmd_else: s = "'else'"; break;
case line_type::cmd_while: s = "'while'"; break;
+ case line_type::cmd_for: s = "'for'"; break;
case line_type::cmd_end: s = "'end'"; break;
}
@@ -225,7 +226,8 @@ namespace build2
case line_type::cmd_elif:
case line_type::cmd_elifn:
case line_type::cmd_else:
- case line_type::cmd_while: fc_ind += " "; break;
+ case line_type::cmd_while:
+ case line_type::cmd_for: fc_ind += " "; break;
default: break;
}
diff --git a/libbuild2/script/script.hxx b/libbuild2/script/script.hxx
index d6018f0..5eb4ee9 100644
--- a/libbuild2/script/script.hxx
+++ b/libbuild2/script/script.hxx
@@ -28,6 +28,7 @@ namespace build2
cmd_elifn,
cmd_else,
cmd_while,
+ cmd_for,
cmd_end
};
diff --git a/libbuild2/test/script/parser+for.test.testscript b/libbuild2/test/script/parser+for.test.testscript
new file mode 100644
index 0000000..92fc714
--- /dev/null
+++ b/libbuild2/test/script/parser+for.test.testscript
@@ -0,0 +1,315 @@
+# file : libbuild2/test/script/parser+for.test.testscript
+# license : MIT; see accompanying LICENSE file
+
+: form-1
+:
+: for x: ...
+:
+{
+ : for
+ :
+ {
+ : no-var
+ :
+ $* <<EOI 2>>EOE != 0
+ for
+ cmd
+ end
+ EOI
+ testscript:1:4: error: expected variable name instead of <newline>
+ EOE
+
+ : untyped
+ :
+ $* <<EOI >>EOO
+ for x: a b
+ cmd $x
+ end
+ EOI
+ cmd a
+ cmd b
+ EOO
+
+ : null
+ :
+ $* <<EOI >:''
+ for x: [null]
+ cmd $x
+ end
+ EOI
+
+ : empty
+ :
+ $* <<EOI >:''
+ for x:
+ cmd $x
+ end
+ EOI
+
+ : expansion
+ :
+ $* <<EOI >>EOO
+ vs = a b
+ for x: $vs
+ cmd $x
+ end
+ EOI
+ cmd a
+ cmd b
+ EOO
+
+ : typed-value
+ :
+ $* <<EOI >>~%EOO%
+ for x: [dir_paths] a b
+ cmd $x
+ end
+ EOI
+ %cmd (a/|'a\\')%
+ %cmd (b/|'b\\')%
+ EOO
+
+ : typed-var
+ :
+ $* <<EOI >>~%EOO%
+ for [dir_path] x: a b
+ cmd $x
+ end
+ EOI
+ %cmd (a/|'a\\')%
+ %cmd (b/|'b\\')%
+ EOO
+
+ : type-mismatch
+ :
+ $* <<EOI 2>>EOE != 0
+ for [string] x: [dir_paths] a b
+ cmd $x
+ end
+ EOI
+ error: type mismatch in variable x
+ info: value type is dir_path
+ info: variable type is string
+ EOE
+
+ : scope-var
+ :
+ $* <<EOI >>EOO
+ x = x
+
+ for x: a b
+ cmd $x
+ end
+
+ -cmd $x
+ EOI
+ cmd a
+ cmd b
+ -cmd x
+ EOO
+ }
+
+ : after-semi
+ :
+ $* -s <<EOI >>EOO
+ cmd1;
+ for x: a b
+ cmd2 $x
+ end
+ EOI
+ {
+ {
+ cmd1
+ cmd2 a
+ cmd2 b
+ }
+ }
+ EOO
+
+ : setup
+ :
+ $* -s <<EOI >>EOO
+ +for x: a b
+ cmd $x
+ end
+ EOI
+ {
+ +cmd a
+ +cmd b
+ }
+ EOO
+
+ : tdown
+ :
+ $* -s <<EOI >>EOO
+ -for x: a b
+ cmd $x
+ end
+ EOI
+ {
+ -cmd a
+ -cmd b
+ }
+ EOO
+
+ : end
+ :
+ {
+ : without-end
+ :
+ $* <<EOI 2>>EOE != 0
+ for x: a b
+ cmd
+ EOI
+ testscript:3:1: error: expected closing 'end'
+ EOE
+ }
+
+ : elif
+ :
+ {
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ for x: a b
+ elif true
+ cmd
+ end
+ end
+ EOI
+ testscript:2:3: error: 'elif' without preceding 'if'
+ EOE
+ }
+
+ : nested
+ :
+ {
+ $* -l -r <<EOI >>EOO
+ for x: a b
+ cmd1 $x # 1
+ if ($x == "a") # 2
+ cmd2 # 3
+ for y: x y
+ cmd3 # 4
+ end
+ else
+ cmd4 # 5
+ end
+ cmd5 # 6
+ end;
+ cmd6 # 7
+ EOI
+ cmd1 a # 1 i1
+ ? true # 2 i1
+ cmd2 # 3 i1
+ cmd3 # 4 i1 i1
+ cmd3 # 4 i1 i2
+ cmd5 # 6 i1
+ cmd1 b # 1 i2
+ ? false # 2 i2
+ cmd4 # 5 i2
+ cmd5 # 6 i2
+ cmd6 # 7
+ EOO
+ }
+
+ : contained
+ :
+ {
+ : semi
+ :
+ $* <<EOI 2>>EOE != 0
+ for x:
+ cmd;
+ cmd
+ end
+ EOI
+ testscript:2:3: error: ';' inside 'for'
+ EOE
+
+ : colon-leading
+ :
+ $* <<EOI 2>>EOE != 0
+ for x:
+ : foo
+ cmd
+ end
+ EOI
+ testscript:2:3: error: description inside 'for'
+ EOE
+
+ : colon-trailing
+ :
+ $* <<EOI 2>>EOE != 0
+ for x:
+ cmd : foo
+ end
+ EOI
+ testscript:2:3: error: description inside 'for'
+ EOE
+
+ : eos
+ :
+ $* <<EOI 2>>EOE != 0
+ for x:
+ EOI
+ testscript:2:1: error: expected closing 'end'
+ EOE
+
+ : scope
+ :
+ $* <<EOI 2>>EOE != 0
+ for x:
+ cmd
+ {
+ }
+ end
+ EOI
+ testscript:3:3: error: expected closing 'end'
+ EOE
+
+ : setup
+ :
+ $* <<EOI 2>>EOE != 0
+ for x:
+ +cmd
+ end
+ EOI
+ testscript:2:3: error: setup command inside 'for'
+ EOE
+
+ : tdown
+ :
+ $* <<EOI 2>>EOE != 0
+ for x:
+ -cmd
+ end
+ EOI
+ testscript:2:3: error: teardown command inside 'for'
+ EOE
+ }
+
+ : var
+ :
+ $* <<EOI >>EOO
+ for x: a b
+ cmd1 $x
+ end;
+ cmd2 $x
+ EOI
+ cmd1 a
+ cmd1 b
+ cmd2 b
+ EOO
+
+ : leading-and-trailing-description
+ :
+ $* <<EOI 2>>EOE != 0
+ : foo
+ for x: a b
+ cmd
+ end : bar
+ EOI
+ testscript:4:1: error: both leading and trailing descriptions
+ EOE
+}
diff --git a/libbuild2/test/script/parser.cxx b/libbuild2/test/script/parser.cxx
index a99f80a..0ba5a79 100644
--- a/libbuild2/test/script/parser.cxx
+++ b/libbuild2/test/script/parser.cxx
@@ -311,9 +311,10 @@ namespace build2
// enter: next token is peeked at (type in tt)
// leave: newline
- assert (!fct ||
- *fct == line_type::cmd_if ||
- *fct == line_type::cmd_while);
+ assert (!fct ||
+ *fct == line_type::cmd_if ||
+ *fct == line_type::cmd_while ||
+ *fct == line_type::cmd_for);
// Note: token is only peeked at.
//
@@ -373,6 +374,7 @@ namespace build2
if (n == "if") lt = line_type::cmd_if;
else if (n == "if!") lt = line_type::cmd_ifn;
else if (n == "while") lt = line_type::cmd_while;
+ else if (n == "for") lt = line_type::cmd_for;
}
break;
@@ -414,16 +416,70 @@ namespace build2
mode (lexer_mode::variable_line);
parse_variable_line (t, tt);
+ // Note that the semicolon token is only required during
+ // pre-parsing to decide which line list the current line should
+ // go to and provides no additional semantics during the
+ // execution. Moreover, build2::script::parser::exec_lines()
+ // doesn't expect this token to be present. Thus, we just drop
+ // this token from the saved tokens.
+ //
semi = (tt == type::semi);
- if (tt == type::semi)
+ if (semi)
+ {
+ replay_pop ();
next (t, tt);
+ }
if (tt != type::newline)
fail (t) << "expected newline instead of " << t;
break;
}
+ case line_type::cmd_for:
+ {
+ // First take care of the variable name. There is no reason not to
+ // support variable attributes.
+ //
+ mode (lexer_mode::normal);
+
+ next_with_attributes (t, tt);
+ attributes_push (t, tt);
+
+ if (tt != type::word || t.qtype != quote_type::unquoted)
+ fail (t) << "expected variable name instead of " << t;
+
+ string& n (t.value);
+
+ if (special_variable (n))
+ fail (t) << "attempt to set '" << n << "' variable directly";
+
+ ln.var = &script_->var_pool.insert (move (n));
+
+ next (t, tt);
+
+ if (tt != type::colon)
+ {
+ // @@ TMP We will need to fallback to parsing the 'for x <...'
+ // form instead.
+ //
+ fail (t) << "expected ':' instead of " << t
+ << " after variable name";
+ }
+
+ expire_mode (); // Expire the normal lexer mode.
+
+ // Parse the value similar to the var line type (see above),
+ // except for the fact that we don't expect a trailing semicolon.
+ //
+ mode (lexer_mode::variable_line);
+ parse_variable_line (t, tt);
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t << " after for";
+
+ break;
+ }
case line_type::cmd_elif:
case line_type::cmd_elifn:
case line_type::cmd_else:
@@ -480,7 +536,8 @@ namespace build2
case type::semi:
{
semi = true;
- next (t, tt); // Get newline.
+ replay_pop (); // See above for the reasoning.
+ next (t, tt); // Get newline.
break;
}
}
@@ -506,18 +563,29 @@ namespace build2
ln.tokens = replay_data ();
ls->push_back (move (ln));
- if (lt == line_type::cmd_if || lt == line_type::cmd_ifn)
+ switch (lt)
{
- semi = pre_parse_if_else (t, tt, d, *ls);
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
+ {
+ semi = pre_parse_if_else (t, tt, d, *ls);
- // If this turned out to be scope-if, then ls is empty, semi is
- // false, and none of the below logic applies.
- //
- if (ls->empty ())
- return semi;
+ // If this turned out to be scope-if, then ls is empty, semi is
+ // false, and none of the below logic applies.
+ //
+ if (ls->empty ())
+ return semi;
+
+ break;
+ }
+ case line_type::cmd_while:
+ case line_type::cmd_for:
+ {
+ semi = pre_parse_loop (t, tt, lt, d, *ls);
+ break;
+ }
+ default: break;
}
- else if (lt == line_type::cmd_while)
- semi = pre_parse_while (t, tt, d, *ls);
// Unless we were told where to put it, decide where it actually goes.
//
@@ -540,6 +608,7 @@ namespace build2
case line_type::cmd_if:
case line_type::cmd_ifn:
case line_type::cmd_while:
+ case line_type::cmd_for:
{
// See if this is a variable-only flow control construct.
//
@@ -896,8 +965,9 @@ namespace build2
break;
}
case line_type::cmd_while:
+ case line_type::cmd_for:
{
- fct = line_type::cmd_while;
+ fct = bt;
break;
}
default: assert(false);
@@ -990,13 +1060,16 @@ namespace build2
}
bool parser::
- pre_parse_while (token& t, type& tt,
- optional<description>& d,
- lines& ls)
+ pre_parse_loop (token& t, type& tt,
+ line_type lt,
+ optional<description>& d,
+ lines& ls)
{
// enter: <newline> (previous line)
// leave: <newline>
+ assert (lt == line_type::cmd_while || lt == line_type::cmd_for);
+
tt = peek (lexer_mode::first_token);
// Parse lines until we see closing 'end'.
@@ -1005,8 +1078,7 @@ namespace build2
{
size_t i (ls.size ());
- bool semi (
- pre_parse_block_line (t, tt, line_type::cmd_while, d, ls));
+ bool semi (pre_parse_block_line (t, tt, lt, d, ls));
if (ls[i].type == line_type::cmd_end)
return semi;
@@ -1359,11 +1431,8 @@ namespace build2
pair<command_expr, here_docs> p (
parse_command_expr (t, tt, lexer::redirect_aliases));
- switch (tt)
- {
- case type::colon: parse_trailing_description (t, tt); break;
- case type::semi: next (t, tt); break; // Get newline.
- }
+ if (tt == type::colon)
+ parse_trailing_description (t, tt);
assert (tt == type::newline);
@@ -1480,30 +1549,19 @@ namespace build2
// Note that we rely on "small function object" optimization for the
// exec_*() lambdas.
//
- auto exec_set = [this] (const variable& var,
- token& t, build2::script::token_type& tt,
- const location&)
+ auto exec_assign = [this] (const variable& var,
+ value&& val,
+ type kind,
+ const location& l)
{
- next (t, tt);
- type kind (tt); // Assignment kind.
-
- // We cannot reuse the value mode (see above for details).
- //
- mode (lexer_mode::variable_line);
- value rhs (parse_variable_line (t, tt));
-
- if (tt == type::semi)
- next (t, tt);
-
- assert (tt == type::newline);
-
- // Assign.
- //
value& lhs (kind == type::assign
? scope_->assign (var)
: scope_->append (var));
- apply_value_attributes (&var, lhs, move (rhs), kind);
+ if (kind == type::assign)
+ lhs = move (val);
+ else
+ append_value (&var, lhs, move (val), l);
if (script_->test_command_var (var.name))
scope_->reset_special ();
@@ -1550,16 +1608,17 @@ namespace build2
ct = command_type::test;
exec_lines (t->tests_.begin (), t->tests_.end (),
- exec_set, exec_cmd, exec_cond,
+ exec_assign, exec_cmd, exec_cond,
nullptr /* iteration_index */, li);
}
else if (group* g = dynamic_cast<group*> (scope_))
{
ct = command_type::setup;
- bool exec_scope (exec_lines (g->setup_.begin (), g->setup_.end (),
- exec_set, exec_cmd, exec_cond,
- nullptr /* iteration_index */, li));
+ bool exec_scope (
+ exec_lines (g->setup_.begin (), g->setup_.end (),
+ exec_assign, exec_cmd, exec_cond,
+ nullptr /* iteration_index */, li));
if (exec_scope)
{
@@ -1729,7 +1788,7 @@ namespace build2
ct = command_type::teardown;
exec_lines (g->tdown_.begin (), g->tdown_.end (),
- exec_set, exec_cmd, exec_cond,
+ exec_assign, exec_cmd, exec_cond,
nullptr /* iteration_index */, li);
}
else
diff --git a/libbuild2/test/script/parser.hxx b/libbuild2/test/script/parser.hxx
index 31dd41d..6fe46e2 100644
--- a/libbuild2/test/script/parser.hxx
+++ b/libbuild2/test/script/parser.hxx
@@ -86,9 +86,10 @@ namespace build2
lines&);
bool
- pre_parse_while (token&, token_type&,
- optional<description>&,
- lines&);
+ pre_parse_loop (token&, token_type&,
+ line_type,
+ optional<description>&,
+ lines&);
void
pre_parse_directive (token&, token_type&);
diff --git a/tests/recipe/buildscript/testscript b/tests/recipe/buildscript/testscript
index 54c3bbe..0ac5d5a 100644
--- a/tests/recipe/buildscript/testscript
+++ b/tests/recipe/buildscript/testscript
@@ -910,4 +910,126 @@ if $posix
$* clean 2>-
}
+
+ : for
+ :
+ {
+ : form-1
+ :
+ : for x: ...
+ :
+ {
+ : basics
+ :
+ {
+ echo 'bar' >=bar;
+ echo 'baz' >=baz;
+
+ cat <<EOI >=buildfile;
+ foo: bar baz
+ {{
+ p = $path($>)
+ rm -f $p
+
+ for f: $<
+ cat $path($f) >>$p
+ end
+ }}
+ EOI
+
+ $* 2>'cat file{foo}';
+
+ cat <<<foo >>EOO;
+ bar
+ baz
+ EOO
+
+ $* clean 2>-
+ }
+
+ : depdb
+ :
+ {
+ : inside
+ :
+ {
+ echo 'bar' >=bar;
+
+ cat <<EOI >=buildfile;
+ foo: bar
+ {{
+ for f: $<
+ depdb hash $f
+ end
+
+ p = $path($>)
+ rm -f $p
+
+ for f: $<
+ cat $path($f) >>$p
+ end
+ }}
+ EOI
+
+ $* 2>>EOE != 0
+ buildfile:4:5: error: 'depdb' call inside flow control construct
+ EOE
+ }
+
+ : after-commands
+ :
+ {
+ echo 'bar' >=bar;
+
+ cat <<EOI >=buildfile;
+ foo: bar
+ {{
+ for f: $<
+ echo $path($f) >-
+ end
+
+ depdb hash a
+ }}
+ EOI
+
+ $* 2>>~%EOE% != 0;
+ buildfile:4:5: error: disallowed command in depdb preamble
+ info: only variable assignments are allowed in depdb preamble
+ buildfile:7:3: info: depdb preamble ends here
+ %.{3}
+ EOE
+
+ $* clean 2>-
+ }
+
+ : after-vars
+ :
+ {
+ echo 'bar' >=bar;
+
+ cat <<EOI >=buildfile;
+ foo: bar
+ {{
+ h =
+ for f: $<
+ h += $path($f)
+ end
+
+ depdb hash $h
+
+ p = $path($>)
+ rm -f $p
+
+ for f: $<
+ cat $path($f) >>$p
+ end
+ }}
+ EOI
+
+ $* 2>'cat file{foo}';
+ $* clean 2>-
+ }
+ }
+ }
+ }
}
diff --git a/tests/test/script/runner/for.testscript b/tests/test/script/runner/for.testscript
new file mode 100644
index 0000000..21042e5
--- /dev/null
+++ b/tests/test/script/runner/for.testscript
@@ -0,0 +1,42 @@
+# File : tests/test/script/runner/for.testscript
+# license : MIT; see accompanying LICENSE file
+
+.include ../common.testscript
+
+: form-1
+:
+: for x: ...
+:
+{
+ : basics
+ :
+ $c <<EOI && $b >>EOO
+ for x: a b
+ echo "$x" >|
+ end
+ EOI
+ a
+ b
+ EOO
+
+ : test-options
+ :
+ $c <<EOI && $b >>~%EOO%
+ for test.options: -a -b
+ echo $* >|
+ end
+ EOI
+ %.+ -a%
+ %.+ -b%
+ EOO
+
+ : special-var
+ :
+ $c <<EOI && $b 2>>EOE != 0
+ for *: -a -b
+ echo $* >|
+ end
+ EOI
+ testscript:1:5: error: attempt to set '*' variable directly
+ EOE
+}