From 744e8215261fbf81b9348d115d4916a9c88b52cc Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 20 Sep 2022 23:00:27 +0300 Subject: Add support for 'while' loop in script --- libbuild2/script/parser.cxx | 201 ++++++++++++++++++++++++++++---------------- libbuild2/script/parser.hxx | 12 +-- libbuild2/script/run.cxx | 57 ++++++++----- libbuild2/script/run.hxx | 12 +-- libbuild2/script/script.cxx | 21 +++-- libbuild2/script/script.hxx | 10 +++ 6 files changed, 199 insertions(+), 114 deletions(-) (limited to 'libbuild2/script') 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, const function& exec_cmd, - const function& exec_if, - size_t& li, + const 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&, const function&, - const function&, - size_t& li, + const 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 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 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; -- cgit v1.1