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 --- .../build/script/parser+command-if.test.testscript | 2 +- .../build/script/parser+while.test.testscript | 133 +++++++++++++++++++ libbuild2/build/script/parser.cxx | 141 ++++++++++++++++----- libbuild2/build/script/parser.hxx | 21 ++- libbuild2/build/script/parser.test.cxx | 57 +++++++-- libbuild2/build/script/runner.cxx | 13 +- libbuild2/build/script/runner.hxx | 26 ++-- libbuild2/build/script/script.hxx | 3 +- 8 files changed, 324 insertions(+), 72 deletions(-) create mode 100644 libbuild2/build/script/parser+while.test.testscript (limited to 'libbuild2/build') 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 + : + $* <>EOO + while ($v != "aa") + cmd "$v" + v = "$(v)a" + end + EOI + ? true + cmd '' + ? true + cmd a + ? false + EOO + + : false + : + $* <>EOO + while ($v == "aa") + cmd "$v" + v = "$(v)a" + end + EOI + ? false + EOO + + : without-command + : + $* <>EOE != 0 + while + cmd + end + EOI + buildfile:11:6: error: missing program + EOE +} + +: end +: +{ + : without-end + : + $* <>EOE != 0 + while true + cmd + EOI + buildfile:13:1: error: expected closing 'end' + EOE +} + +: elif +: +{ + : without-if + : + $* <>EOE != 0 + while false + elif true + cmd + end + end + EOI + buildfile:12:3: error: 'elif' without preceding 'if' + EOE +} + +: nested +: +{ + $* -l -r <>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 + : + $* <>EOE != 0 + while + EOI + buildfile:12:1: error: expected closing 'end' + EOE +} + +: var +: +$* <>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 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 (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 (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 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 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; -- cgit v1.1