aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/build/script
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2022-09-20 23:00:27 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2022-09-28 17:59:43 +0300
commit744e8215261fbf81b9348d115d4916a9c88b52cc (patch)
tree9b78941d4ea67fefdccca98215b1340dd2dd6c99 /libbuild2/build/script
parente59b4fc15eef3b3d0af5b81190b1e54f270ee2d2 (diff)
Add support for 'while' loop in script
Diffstat (limited to 'libbuild2/build/script')
-rw-r--r--libbuild2/build/script/parser+command-if.test.testscript2
-rw-r--r--libbuild2/build/script/parser+while.test.testscript133
-rw-r--r--libbuild2/build/script/parser.cxx141
-rw-r--r--libbuild2/build/script/parser.hxx21
-rw-r--r--libbuild2/build/script/parser.test.cxx57
-rw-r--r--libbuild2/build/script/runner.cxx13
-rw-r--r--libbuild2/build/script/runner.hxx26
-rw-r--r--libbuild2/build/script/script.hxx3
8 files changed, 324 insertions, 72 deletions
diff --git a/libbuild2/build/script/parser+command-if.test.testscript b/libbuild2/build/script/parser+command-if.test.testscript
index a18a885..8b19186 100644
--- a/libbuild2/build/script/parser+command-if.test.testscript
+++ b/libbuild2/build/script/parser+command-if.test.testscript
@@ -279,7 +279,7 @@
cmd
end
EOI
- buildfile:12:1: error: 'end' without preceding 'if'
+ buildfile:12:1: error: 'end' without preceding 'if', 'for', or 'while'
EOE
: before
diff --git a/libbuild2/build/script/parser+while.test.testscript b/libbuild2/build/script/parser+while.test.testscript
new file mode 100644
index 0000000..5587291
--- /dev/null
+++ b/libbuild2/build/script/parser+while.test.testscript
@@ -0,0 +1,133 @@
+# file : libbuild2/build/script/parser+while.test.testscript
+# license : MIT; see accompanying LICENSE file
+
+: while
+:
+{
+ : true
+ :
+ $* <<EOI >>EOO
+ while ($v != "aa")
+ cmd "$v"
+ v = "$(v)a"
+ end
+ EOI
+ ? true
+ cmd ''
+ ? true
+ cmd a
+ ? false
+ EOO
+
+ : false
+ :
+ $* <<EOI >>EOO
+ while ($v == "aa")
+ cmd "$v"
+ v = "$(v)a"
+ end
+ EOI
+ ? false
+ EOO
+
+ : without-command
+ :
+ $* <<EOI 2>>EOE != 0
+ while
+ cmd
+ end
+ EOI
+ buildfile:11:6: error: missing program
+ EOE
+}
+
+: end
+:
+{
+ : without-end
+ :
+ $* <<EOI 2>>EOE != 0
+ while true
+ cmd
+ EOI
+ buildfile:13:1: error: expected closing 'end'
+ EOE
+}
+
+: elif
+:
+{
+ : without-if
+ :
+ $* <<EOI 2>>EOE != 0
+ while false
+ elif true
+ cmd
+ end
+ end
+ EOI
+ buildfile:12:3: error: 'elif' without preceding 'if'
+ EOE
+}
+
+: nested
+:
+{
+ $* -l -r <<EOI >>EOO
+ while ($v != "aa") # 1
+ cmd1 "$v" # 2
+ if ($v == "a") # 3
+ cmd2 # 4
+ while ($v2 != "$v") # 5
+ cmd3 # 6
+ v2=$v
+ end
+ else
+ cmd4 # 7
+ end
+ cmd5 # 8
+ v = "$(v)a"
+ end
+ EOI
+ ? true # 1 i1
+ cmd1 '' # 2 i1
+ ? false # 3 i1
+ cmd4 # 7 i1
+ cmd5 # 8 i1
+ ? true # 1 i2
+ cmd1 a # 2 i2
+ ? true # 3 i2
+ cmd2 # 4 i2
+ ? true # 5 i2 i1
+ cmd3 # 6 i2 i1
+ ? false # 5 i2 i2
+ cmd5 # 8 i2
+ ? false # 1 i3
+ EOO
+}
+
+: contained
+:
+{
+ : eos
+ :
+ $* <<EOI 2>>EOE != 0
+ while
+ EOI
+ buildfile:12:1: error: expected closing 'end'
+ EOE
+}
+
+: var
+:
+$* <<EOI >>EOO
+while ($v1 != "a")
+ v1 = "$(v1)a"
+ v2 = "$v1"
+end
+cmd $v1
+EOI
+? true
+? false
+cmd a
+EOO
diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx
index 0614f20..1a7f4e1 100644
--- a/libbuild2/build/script/parser.cxx
+++ b/libbuild2/build/script/parser.cxx
@@ -193,9 +193,22 @@ namespace build2
}
}
+ // Parse a logical line, handling the flow control constructs
+ // recursively.
+ //
+ // If the flow control construct type is specified, then this line is
+ // assumed to belong to such a construct.
+ //
void parser::
- pre_parse_line (token& t, type& tt, bool if_line)
+ pre_parse_line (token& t, type& tt, optional<line_type> fct)
{
+ // enter: next token is peeked at (type in tt)
+ // leave: newline
+
+ assert (!fct ||
+ *fct == line_type::cmd_if ||
+ *fct == line_type::cmd_while);
+
// Determine the line type/start token.
//
line_type lt (
@@ -235,19 +248,25 @@ namespace build2
case line_type::cmd_elif:
case line_type::cmd_elifn:
case line_type::cmd_else:
- case line_type::cmd_end:
{
- if (!if_line)
- {
+ if (!fct || *fct != line_type::cmd_if)
fail (t) << lt << " without preceding 'if'";
- }
+ }
+ // Fall through.
+ case line_type::cmd_end:
+ {
+ if (!fct)
+ fail (t) << lt << " without preceding 'if', 'for', or 'while'";
}
// Fall through.
case line_type::cmd_if:
case line_type::cmd_ifn:
+ case line_type::cmd_while:
next (t, tt); // Skip to start of command.
- if (lt == line_type::cmd_if || lt == line_type::cmd_ifn)
+ if (lt == line_type::cmd_if ||
+ lt == line_type::cmd_ifn ||
+ lt == line_type::cmd_while)
++level_;
else if (lt == line_type::cmd_end)
--level_;
@@ -287,6 +306,50 @@ namespace build2
pre_parse_if_else (t, tt);
}
+ else if (lt == line_type::cmd_while)
+ {
+ tt = peek (lexer_mode::first_token);
+
+ pre_parse_while (t, tt);
+ }
+ }
+
+ // Pre-parse the flow control construct block line.
+ //
+ void parser::
+ pre_parse_block_line (token& t, type& tt, line_type bt)
+ {
+ // enter: peeked first token of the line (type in tt)
+ // leave: newline
+
+ const location ll (get_location (peeked ()));
+
+ if (tt == type::eos)
+ fail (ll) << "expected closing 'end'";
+
+ line_type fct; // Flow control type the block type relates to.
+
+ switch (bt)
+ {
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_else:
+ {
+ fct = line_type::cmd_if;
+ break;
+ }
+ case line_type::cmd_while:
+ {
+ fct = line_type::cmd_while;
+ break;
+ }
+ default: assert(false);
+ }
+
+ pre_parse_line (t, tt, fct);
+ assert (tt == type::newline);
}
void parser::
@@ -295,8 +358,7 @@ namespace build2
// enter: peeked first token of next line (type in tt)
// leave: newline
- // Parse lines until we see closing 'end'. Nested if-else blocks are
- // handled recursively.
+ // Parse lines until we see closing 'end'.
//
for (line_type bt (line_type::cmd_if); // Current block.
;
@@ -304,25 +366,21 @@ namespace build2
{
const location ll (get_location (peeked ()));
- if (tt == type::eos)
- fail (ll) << "expected closing 'end'";
-
// Parse one line. Note that this one line can still be multiple
- // lines in case of if-else. In this case we want to view it as
- // cmd_if, not cmd_end. Thus remember the start position of the
- // next logical line.
+ // lines in case of a flow control construct. In this case we want
+ // to view it as cmd_if, not cmd_end. Thus remember the start
+ // position of the next logical line.
//
size_t i (script_->body.size ());
- pre_parse_line (t, tt, true /* if_line */);
- assert (tt == type::newline);
+ pre_parse_block_line (t, tt, bt);
line_type lt (script_->body[i].type);
// First take care of 'end'.
//
if (lt == line_type::cmd_end)
- return;
+ break;
// Check if-else block sequencing.
//
@@ -346,6 +404,25 @@ namespace build2
}
}
+ void parser::
+ pre_parse_while (token& t, type& tt)
+ {
+ // enter: peeked first token of next line (type in tt)
+ // leave: newline
+
+ // 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);
+
+ if (script_->body[i].type == line_type::cmd_end)
+ break;
+ }
+ }
+
command_expr parser::
parse_command_line (token& t, type& tt)
{
@@ -946,7 +1023,7 @@ namespace build2
// Note that we rely on "small function object" optimization here.
//
auto exec_cmd = [this] (token& t, build2::script::token_type& tt,
- size_t li,
+ const iteration_index* ii, size_t li,
bool single,
const location& ll)
{
@@ -958,7 +1035,7 @@ namespace build2
command_expr ce (
parse_command_line (t, static_cast<token_type&> (tt)));
- runner_->run (*environment_, ce, li, ll);
+ runner_->run (*environment_, ce, ii, li, ll);
};
exec_lines (s.body, exec_cmd);
@@ -1009,13 +1086,13 @@ namespace build2
auto exec_cmd = [this, &data] (token& t,
build2::script::token_type& tt,
- size_t li,
+ const iteration_index* ii, size_t li,
bool /* single */,
const location& ll)
{
// Note that we never reset the line index to zero (as we do in
- // execute_body()) assuming that there are some script body
- // commands to follow.
+ // execute_body()) assuming that there are some script body commands
+ // to follow.
//
if (tt == type::word && t.value == "depdb")
{
@@ -1128,7 +1205,7 @@ namespace build2
info (rt[0].location ()) << "depdb preamble ends here";
}
- runner_->run (*environment_, ce, li, ll);
+ runner_->run (*environment_, ce, ii, li, ll);
}
};
@@ -1191,20 +1268,22 @@ namespace build2
apply_value_attributes (&var, lhs, move (rhs), kind);
};
- auto exec_if = [this] (token& t, build2::script::token_type& tt,
- size_t li,
- const location& ll)
+ auto exec_cond = [this] (token& t, build2::script::token_type& tt,
+ const iteration_index* ii, size_t li,
+ const location& ll)
{
command_expr ce (
parse_command_line (t, static_cast<token_type&> (tt)));
- // Assume if-else always involves multiple commands.
+ // Assume a flow control construct always involves multiple
+ // commands.
//
- return runner_->run_if (*environment_, ce, li, ll);
+ return runner_->run_cond (*environment_, ce, ii, li, ll);
};
build2::script::parser::exec_lines (begin, end,
- exec_set, exec_cmd, exec_if,
+ exec_set, exec_cmd, exec_cond,
+ nullptr /* iteration_index */,
environment_->exec_line,
&environment_->var_pool);
}
@@ -2145,10 +2224,12 @@ namespace build2
istringstream iss;
if (prog)
{
+ // Note: depdb is disallowed inside flow control constructs.
+ //
string s;
build2::script::run (*environment_,
cmd,
- li,
+ nullptr /* iteration_index */, li,
ll,
!file ? &s : nullptr);
diff --git a/libbuild2/build/script/parser.hxx b/libbuild2/build/script/parser.hxx
index 932cbad..987ed1f 100644
--- a/libbuild2/build/script/parser.hxx
+++ b/libbuild2/build/script/parser.hxx
@@ -65,11 +65,18 @@ namespace build2
pre_parse_script ();
void
- pre_parse_line (token&, token_type&, bool if_line = false);
+ pre_parse_line (token&, token_type&,
+ optional<line_type> flow_control_type = nullopt);
+
+ void
+ pre_parse_block_line (token&, token_type&, line_type block_type);
void
pre_parse_if_else (token&, token_type&);
+ void
+ pre_parse_while (token&, token_type&);
+
command_expr
parse_command_line (token&, token_type&);
@@ -367,13 +374,13 @@ namespace build2
//
line* save_line_;
- // The if-else nesting level (and in the future for other flow
- // control constructs).
+ // The flow control constructs nesting level.
//
- // Maintained during pre-parsing and is incremented when the cmd_if or
- // cmd_ifn lines are encountered, which in particular means that it is
- // already incremented by the time the if-condition expression is
- // pre-parsed. Decremented when the cmd_end line is encountered.
+ // Maintained during pre-parsing and is incremented when flow control
+ // construct condition lines are encountered, which in particular
+ // means that it is already incremented by the time the condition
+ // expression is pre-parsed. Decremented when the cmd_end line is
+ // encountered.
//
size_t level_ = 0;
diff --git a/libbuild2/build/script/parser.test.cxx b/libbuild2/build/script/parser.test.cxx
index 5808015..f8c2696 100644
--- a/libbuild2/build/script/parser.test.cxx
+++ b/libbuild2/build/script/parser.test.cxx
@@ -29,7 +29,9 @@ namespace build2
class print_runner: public runner
{
public:
- print_runner (bool line): line_ (line) {}
+ print_runner (bool line, bool iterations):
+ line_ (line),
+ iterations_ (iterations) {}
virtual void
enter (environment&, const location&) override {}
@@ -37,27 +39,27 @@ namespace build2
virtual void
run (environment&,
const command_expr& e,
- size_t i,
+ const iteration_index* ii, size_t i,
const location&) override
{
cout << e;
- if (line_)
- cout << " # " << i;
+ if (line_ || iterations_)
+ print_line_info (ii, i);
cout << endl;
}
virtual bool
- run_if (environment&,
- const command_expr& e,
- size_t i,
- const location&) override
+ run_cond (environment&,
+ const command_expr& e,
+ const iteration_index* ii, size_t i,
+ const location&) override
{
cout << "? " << e;
- if (line_)
- cout << " # " << i;
+ if (line_ || iterations_)
+ print_line_info (ii, i);
cout << endl;
@@ -68,12 +70,32 @@ namespace build2
leave (environment&, const location&) override {}
private:
+ void
+ print_line_info (const iteration_index* ii, size_t i) const
+ {
+ cout << " #";
+
+ if (line_)
+ cout << ' ' << i;
+
+ if (iterations_ && ii != nullptr)
+ {
+ string s;
+ for (const iteration_index* i (ii); i != nullptr; i = i->prev)
+ s.insert (0, " i" + to_string (i->index));
+
+ cout << s;
+ }
+ }
+
+ private:
bool line_;
+ bool iterations_;
};
// Usages:
//
- // argv[0] [-l]
+ // argv[0] [-l] [-r]
// argv[0] -b [-t]
// argv[0] -d [-t]
// argv[0] -q
@@ -99,6 +121,9 @@ namespace build2
// -l
// Print the script line number for each executed expression.
//
+ // -r
+ // Print the loop iteration numbers for each executed expression.
+ //
// -b
// Dump the parsed script body to stdout.
//
@@ -136,6 +161,7 @@ namespace build2
} m (mode::run);
bool print_line (false);
+ bool print_iterations (false);
optional<string> diag_name;
bool temp_dir (false);
@@ -145,6 +171,8 @@ namespace build2
if (a == "-l")
print_line = true;
+ else if (a == "-r")
+ print_iterations = true;
else if (a == "-b")
m = mode::body;
else if (a == "-d")
@@ -170,8 +198,9 @@ namespace build2
}
}
- assert (!print_line || m == mode::run);
- assert (!diag_name || m == mode::diag);
+ assert (!print_line || m == mode::run);
+ assert (!print_iterations || m == mode::run);
+ assert (!diag_name || m == mode::diag);
// Fake build system driver, default verbosity.
//
@@ -223,7 +252,7 @@ namespace build2
case mode::run:
{
environment e (perform_update_id, tt, s.body_temp_dir);
- print_runner r (print_line);
+ print_runner r (print_line, print_iterations);
p.execute_body (ctx.global_scope, ctx.global_scope, e, s, r);
break;
}
diff --git a/libbuild2/build/script/runner.cxx b/libbuild2/build/script/runner.cxx
index 51139d4..157fc60 100644
--- a/libbuild2/build/script/runner.cxx
+++ b/libbuild2/build/script/runner.cxx
@@ -96,7 +96,7 @@ namespace build2
void default_runner::
run (environment& env,
const command_expr& expr,
- size_t li,
+ const iteration_index* ii, size_t li,
const location& ll)
{
if (verb >= 3)
@@ -115,20 +115,21 @@ namespace build2
(p.recall.string () == "set" ||
p.recall.string () == "exit");
}) != expr.end ())
- build2::script::run (env, expr, li, ll);
+ build2::script::run (env, expr, ii, li, ll);
else if (verb >= 2)
text << expr;
}
bool default_runner::
- run_if (environment& env,
- const command_expr& expr,
- size_t li, const location& ll)
+ run_cond (environment& env,
+ const command_expr& expr,
+ const iteration_index* ii, size_t li,
+ const location& ll)
{
if (verb >= 3)
text << ": ?" << expr;
- return build2::script::run_if (env, expr, li, ll);
+ return build2::script::run_cond (env, expr, ii, li, ll);
}
}
}
diff --git a/libbuild2/build/script/runner.hxx b/libbuild2/build/script/runner.hxx
index 558de9b..0652396 100644
--- a/libbuild2/build/script/runner.hxx
+++ b/libbuild2/build/script/runner.hxx
@@ -35,14 +35,14 @@ namespace build2
virtual void
run (environment&,
const command_expr&,
- size_t index,
+ const iteration_index*, size_t index,
const location&) = 0;
virtual bool
- run_if (environment&,
- const command_expr&,
- size_t,
- const location&) = 0;
+ run_cond (environment&,
+ const command_expr&,
+ const iteration_index*, size_t,
+ const location&) = 0;
// Location is the script end location (for diagnostics, etc).
//
@@ -52,9 +52,9 @@ namespace build2
// Run command expressions.
//
- // In dry-run mode don't run the expressions unless they are if-
- // conditions or execute the set or exit builtins, but print them at
- // verbosity level 2 and up.
+ // In dry-run mode don't run the expressions unless they are flow
+ // control construct conditions or execute the set or exit builtins, but
+ // print them at verbosity level 2 and up.
//
class default_runner: public runner
{
@@ -65,14 +65,14 @@ namespace build2
virtual void
run (environment&,
const command_expr&,
- size_t,
+ const iteration_index*, size_t,
const location&) override;
virtual bool
- run_if (environment&,
- const command_expr&,
- size_t,
- const location&) override;
+ run_cond (environment&,
+ const command_expr&,
+ const iteration_index*, size_t,
+ const location&) override;
virtual void
leave (environment&, const location&) override;
diff --git a/libbuild2/build/script/script.hxx b/libbuild2/build/script/script.hxx
index d0ab139..f8df204 100644
--- a/libbuild2/build/script/script.hxx
+++ b/libbuild2/build/script/script.hxx
@@ -20,12 +20,13 @@ namespace build2
namespace script
{
using build2::script::line;
- using build2::script::lines;
using build2::script::line_type;
+ using build2::script::lines;
using build2::script::redirect;
using build2::script::redirect_type;
using build2::script::expr_term;
using build2::script::command_expr;
+ using build2::script::iteration_index;
using build2::script::deadline;
using build2::script::timeout;