aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/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/script
parente59b4fc15eef3b3d0af5b81190b1e54f270ee2d2 (diff)
Add support for 'while' loop in script
Diffstat (limited to 'libbuild2/script')
-rw-r--r--libbuild2/script/parser.cxx201
-rw-r--r--libbuild2/script/parser.hxx12
-rw-r--r--libbuild2/script/run.cxx57
-rw-r--r--libbuild2/script/run.hxx12
-rw-r--r--libbuild2/script/script.cxx21
-rw-r--r--libbuild2/script/script.hxx10
6 files changed, 199 insertions, 114 deletions
diff --git a/libbuild2/script/parser.cxx b/libbuild2/script/parser.cxx
index c199c0e..c371e5b 100644
--- a/libbuild2/script/parser.cxx
+++ b/libbuild2/script/parser.cxx
@@ -2061,6 +2061,7 @@ namespace build2
else if (n == "elif") r = line_type::cmd_elif;
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 == "end") r = line_type::cmd_end;
else
{
@@ -2091,8 +2092,8 @@ namespace build2
exec_lines (lines::const_iterator i, lines::const_iterator e,
const function<exec_set_function>& exec_set,
const function<exec_cmd_function>& exec_cmd,
- const function<exec_if_function>& exec_if,
- size_t& li,
+ const function<exec_cond_function>& exec_cond,
+ const iteration_index* ii, size_t& li,
variable_pool* var_pool)
{
try
@@ -2116,6 +2117,69 @@ namespace build2
next (t, tt);
const location ll (get_location (t));
+ // If end is true, then find the flow control construct's end ('end'
+ // line). Otherwise, find the flow control construct's block end
+ // ('end', 'else', etc). If skip is true then increment the command
+ // line index.
+ //
+ auto fcend = [e, &li] (lines::const_iterator j,
+ bool end,
+ bool skip) -> lines::const_iterator
+ {
+ // We need to be aware of nested flow control constructs.
+ //
+ size_t n (0);
+
+ for (++j; j != e; ++j)
+ {
+ line_type lt (j->type);
+
+ if (lt == line_type::cmd_if ||
+ lt == line_type::cmd_ifn ||
+ lt == line_type::cmd_while)
+ ++n;
+
+ // If we are nested then we just wait until we get back
+ // to the surface.
+ //
+ if (n == 0)
+ {
+ switch (lt)
+ {
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_else:
+ if (end) break;
+ // Fall through.
+ case line_type::cmd_end: return j;
+ default: break;
+ }
+ }
+
+ if (lt == line_type::cmd_end)
+ --n;
+
+ if (skip)
+ {
+ // Note that we don't count else and end as commands.
+ //
+ switch (lt)
+ {
+ case line_type::cmd:
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_while: ++li; break;
+ default: break;
+ }
+ }
+ }
+
+ assert (false); // Missing end.
+ return e;
+ };
+
switch (lt)
{
case line_type::var:
@@ -2151,7 +2215,7 @@ namespace build2
single = true;
}
- exec_cmd (t, tt, li++, single, ll);
+ exec_cmd (t, tt, ii, li++, single, ll);
replay_stop ();
break;
@@ -2167,7 +2231,7 @@ namespace build2
bool take;
if (lt != line_type::cmd_else)
{
- take = exec_if (t, tt, li++, ll);
+ take = exec_cond (t, tt, ii, li++, ll);
if (lt == line_type::cmd_ifn || lt == line_type::cmd_elifn)
take = !take;
@@ -2180,94 +2244,89 @@ namespace build2
replay_stop ();
- // If end is true, then find the 'end' line. Otherwise, find
- // the next if-else line. If skip is true then increment the
- // command line index.
- //
- auto next = [e, &li] (lines::const_iterator j,
- bool end,
- bool skip) -> lines::const_iterator
- {
- // We need to be aware of nested if-else chains.
- //
- size_t n (0);
-
- for (++j; j != e; ++j)
- {
- line_type lt (j->type);
-
- if (lt == line_type::cmd_if || lt == line_type::cmd_ifn)
- ++n;
-
- // If we are nested then we just wait until we get back
- // to the surface.
- //
- if (n == 0)
- {
- switch (lt)
- {
- case line_type::cmd_elif:
- case line_type::cmd_elifn:
- case line_type::cmd_else:
- if (end) break;
- // Fall through.
- case line_type::cmd_end: return j;
- default: break;
- }
- }
-
- if (lt == line_type::cmd_end)
- --n;
-
- if (skip)
- {
- // Note that we don't count else and end as commands.
- //
- switch (lt)
- {
- case line_type::cmd:
- case line_type::cmd_if:
- case line_type::cmd_ifn:
- case line_type::cmd_elif:
- case line_type::cmd_elifn: ++li; break;
- default: break;
- }
- }
- }
-
- assert (false); // Missing end.
- return e;
- };
-
// If we are taking this branch then we need to parse all the
- // lines until the next if-else line and then skip all the
- // lines until the end (unless next is already end).
+ // lines until the next if-else line and then skip all the lines
+ // until the end (unless we are already at the end).
//
// Otherwise, we need to skip all the lines until the next
// if-else line and then continue parsing.
//
if (take)
{
- // Next if-else.
+ // Find block end.
//
- lines::const_iterator j (next (i, false, false));
+ lines::const_iterator j (fcend (i, false, false));
+
if (!exec_lines (i + 1, j,
- exec_set, exec_cmd, exec_if,
- li,
+ exec_set, exec_cmd, exec_cond,
+ ii, li,
var_pool))
return false;
- i = j->type == line_type::cmd_end ? j : next (j, true, true);
+ // Find construct end.
+ //
+ i = j->type == line_type::cmd_end ? j : fcend (j, true, true);
}
else
{
- i = next (i, false, true);
+ // Find block end.
+ //
+ i = fcend (i, false, true);
+
if (i->type != line_type::cmd_end)
--i; // Continue with this line (e.g., elif or else).
}
break;
}
+ case line_type::cmd_while:
+ {
+ size_t wli (li);
+
+ for (iteration_index wi {1, ii};; wi.index++)
+ {
+ next (t, tt); // Skip to start of command.
+
+ bool exec (exec_cond (t, tt, &wi, li++, ll));
+
+ replay_stop ();
+
+ // If the condition evaluates to true, then we need to parse
+ // all the lines until the end line, prepare for the condition
+ // reevaluation, and re-iterate.
+ //
+ // Otherwise, we need to skip all the lines until the end
+ // line, bail out from the loop, and continue parsing.
+ //
+ if (exec)
+ {
+ // Find construct end.
+ //
+ lines::const_iterator j (fcend (i, true, false));
+
+ if (!exec_lines (i + 1, j,
+ exec_set, exec_cmd, exec_cond,
+ &wi, li,
+ var_pool))
+ return false;
+
+ // Prepare for the condition reevaluation.
+ //
+ replay_data (replay_tokens (ln.tokens));
+ next (t, tt);
+ li = wli;
+ }
+ else
+ {
+ // Find construct end.
+ //
+ i = fcend (i, true, true);
+ break; // Bail out from the while-loop.
+ }
+ }
+
+ break;
+ }
case line_type::cmd_end:
{
assert (false);
diff --git a/libbuild2/script/parser.hxx b/libbuild2/script/parser.hxx
index d8e5dbf..9edb6ca 100644
--- a/libbuild2/script/parser.hxx
+++ b/libbuild2/script/parser.hxx
@@ -166,13 +166,13 @@ namespace build2
const location&);
using exec_cmd_function = void (token&, token_type&,
- size_t li,
+ const iteration_index*, size_t li,
bool single,
const location&);
- using exec_if_function = bool (token&, token_type&,
- size_t li,
- const location&);
+ using exec_cond_function = bool (token&, token_type&,
+ const iteration_index*, size_t li,
+ const location&);
// If a parser implementation doesn't pre-enter variables into a pool
// during the pre-parsing phase, then they are entered during the
@@ -183,8 +183,8 @@ namespace build2
exec_lines (lines::const_iterator b, lines::const_iterator e,
const function<exec_set_function>&,
const function<exec_cmd_function>&,
- const function<exec_if_function>&,
- size_t& li,
+ const function<exec_cond_function>&,
+ const iteration_index*, size_t& li,
variable_pool* = nullptr);
// Customization hooks.
diff --git a/libbuild2/script/run.cxx b/libbuild2/script/run.cxx
index 5b45afd..51a1f92 100644
--- a/libbuild2/script/run.cxx
+++ b/libbuild2/script/run.cxx
@@ -809,7 +809,7 @@ namespace build2
// regex to file for troubleshooting regardless of whether we print
// the diagnostics or not. We, however, register it for cleanup in the
// later case (the expression may still succeed, we can be evaluating
- // the if condition, etc).
+ // the flow control construct condition, etc).
//
optional<path> rp;
if (env.temp_dir_keep)
@@ -1239,7 +1239,8 @@ namespace build2
command_pipe::const_iterator bc,
command_pipe::const_iterator ec,
auto_fd ifd,
- size_t ci, size_t li, const location& ll,
+ const iteration_index* ii, size_t li, size_t ci,
+ const location& ll,
bool diag,
string* output,
optional<deadline> dl = nullopt,
@@ -1444,19 +1445,28 @@ namespace build2
// Create a unique path for a command standard stream cache file.
//
- auto std_path = [&env, &ci, &li, &ll] (const char* n) -> path
+ auto std_path = [&env, ii, &li, &ci, &ll] (const char* nm) -> path
{
using std::to_string;
- path p (n);
+ string s (nm);
+ size_t n (s.size ());
+
+ if (ii != nullptr)
+ {
+ // Note: reverse order (outermost to innermost).
+ //
+ for (const iteration_index* i (ii); i != nullptr; i = i->prev)
+ s.insert (n, "-i" + to_string (i->index));
+ }
// 0 if belongs to a single-line script, otherwise is the command line
// number (start from one) in the script.
//
- if (li > 0)
+ if (li != 0)
{
- p += '-';
- p += to_string (li);
+ s += "-n";
+ s += to_string (li);
}
// 0 if belongs to a single-command expression, otherwise is the
@@ -1466,13 +1476,13 @@ namespace build2
// single-line script or to N-th single-command line of multi-line
// script. These cases are mutually exclusive and so are unambiguous.
//
- if (ci > 0)
+ if (ci != 0)
{
- p += '-';
- p += to_string (ci);
+ s += "-c";
+ s += to_string (ci);
}
- return normalize (move (p), temp_dir (env), ll);
+ return normalize (path (move (s)), temp_dir (env), ll);
};
// If this is the first pipeline command, then open stdin descriptor
@@ -2206,7 +2216,7 @@ namespace build2
success = run_pipe (env,
nc, ec,
move (ofd.in),
- ci + 1, li, ll, diag,
+ ii, li, ci + 1, ll, diag,
output,
dl, dl_cmd,
&pc);
@@ -2333,7 +2343,7 @@ namespace build2
success = run_pipe (env,
nc, ec,
move (ofd.in),
- ci + 1, li, ll, diag,
+ ii, li, ci + 1, ll, diag,
output,
dl, dl_cmd,
&pc);
@@ -2471,7 +2481,8 @@ namespace build2
static bool
run_expr (environment& env,
const command_expr& expr,
- size_t li, const location& ll,
+ const iteration_index* ii, size_t li,
+ const location& ll,
bool diag,
string* output)
{
@@ -2517,7 +2528,7 @@ namespace build2
r = run_pipe (env,
p.begin (), p.end (),
auto_fd (),
- ci, li, ll, print,
+ ii, li, ci, ll, print,
output);
}
@@ -2530,26 +2541,28 @@ namespace build2
void
run (environment& env,
const command_expr& expr,
- size_t li, const location& ll,
+ const iteration_index* ii, size_t li,
+ const location& ll,
string* output)
{
// Note that we don't print the expression at any verbosity level
// assuming that the caller does this, potentially providing some
// additional information (command type, etc).
//
- if (!run_expr (env, expr, li, ll, true /* diag */, output))
+ if (!run_expr (env, expr, ii, li, ll, true /* diag */, output))
throw failed (); // Assume diagnostics is already printed.
}
bool
- run_if (environment& env,
- const command_expr& expr,
- size_t li, const location& ll,
- string* output)
+ run_cond (environment& env,
+ const command_expr& expr,
+ const iteration_index* ii, size_t li,
+ const location& ll,
+ string* output)
{
// Note that we don't print the expression here (see above).
//
- return run_expr (env, expr, li, ll, false /* diag */, output);
+ return run_expr (env, expr, ii, li, ll, false /* diag */, output);
}
void
diff --git a/libbuild2/script/run.hxx b/libbuild2/script/run.hxx
index 8bc246c..01b010c 100644
--- a/libbuild2/script/run.hxx
+++ b/libbuild2/script/run.hxx
@@ -44,16 +44,16 @@ namespace build2
void
run (environment&,
const command_expr&,
- size_t index,
+ const iteration_index*, size_t index,
const location&,
string* output = nullptr);
bool
- run_if (environment&,
- const command_expr&,
- size_t index,
- const location&,
- string* output = nullptr);
+ run_cond (environment&,
+ const command_expr&,
+ const iteration_index*, size_t index,
+ const location&,
+ string* output = nullptr);
// Perform the registered special file cleanups in the direct order and
// then the regular cleanups in the reverse order.
diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx
index 9e6eeed..d4096cf 100644
--- a/libbuild2/script/script.cxx
+++ b/libbuild2/script/script.cxx
@@ -27,6 +27,7 @@ namespace build2
case line_type::cmd_elif: s = "'elif'"; break;
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_end: s = "'end'"; break;
}
@@ -186,14 +187,14 @@ namespace build2
void
dump (ostream& os, const string& ind, const lines& ls)
{
- // Additionally indent the if-branch lines.
+ // Additionally indent the flow control construct block lines.
//
- string if_ind;
+ string fc_ind;
for (const line& l: ls)
{
- // Before printing indentation, decrease it if the else or end line is
- // reached.
+ // Before printing indentation, decrease it if the else, end, etc line
+ // is reached.
//
switch (l.type)
{
@@ -202,9 +203,9 @@ namespace build2
case line_type::cmd_else:
case line_type::cmd_end:
{
- size_t n (if_ind.size ());
+ size_t n (fc_ind.size ());
assert (n >= 2);
- if_ind.resize (n - 2);
+ fc_ind.resize (n - 2);
break;
}
default: break;
@@ -212,9 +213,10 @@ namespace build2
// Print indentations.
//
- os << ind << if_ind;
+ os << ind << fc_ind;
- // After printing indentation, increase it for if/else branch.
+ // After printing indentation, increase it for the flow control
+ // construct block lines.
//
switch (l.type)
{
@@ -222,7 +224,8 @@ namespace build2
case line_type::cmd_ifn:
case line_type::cmd_elif:
case line_type::cmd_elifn:
- case line_type::cmd_else: if_ind += " "; break;
+ case line_type::cmd_else:
+ case line_type::cmd_while: fc_ind += " "; break;
default: break;
}
diff --git a/libbuild2/script/script.hxx b/libbuild2/script/script.hxx
index 5a39659..d6018f0 100644
--- a/libbuild2/script/script.hxx
+++ b/libbuild2/script/script.hxx
@@ -27,6 +27,7 @@ namespace build2
cmd_elif,
cmd_elifn,
cmd_else,
+ cmd_while,
cmd_end
};
@@ -380,6 +381,15 @@ namespace build2
ostream&
operator<< (ostream&, const command_expr&);
+ // Stack-allocated linked list of iteration indexes of the nested loops.
+ //
+ struct iteration_index
+ {
+ size_t index; // 1-based.
+
+ const iteration_index* prev; // NULL for the top-most loop.
+ };
+
struct timeout
{
duration value;