aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/build/script/parser.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbuild2/build/script/parser.cxx')
-rw-r--r--libbuild2/build/script/parser.cxx391
1 files changed, 391 insertions, 0 deletions
diff --git a/libbuild2/build/script/parser.cxx b/libbuild2/build/script/parser.cxx
new file mode 100644
index 0000000..e64db91
--- /dev/null
+++ b/libbuild2/build/script/parser.cxx
@@ -0,0 +1,391 @@
+// file : libbuild2/build/script/parser.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/build/script/parser.hxx>
+
+#include <libbuild2/build/script/lexer.hxx>
+#include <libbuild2/build/script/runner.hxx>
+
+using namespace std;
+
+namespace build2
+{
+ namespace build
+ {
+ namespace script
+ {
+ using type = token_type;
+
+ //
+ // Pre-parse.
+ //
+
+ script parser::
+ pre_parse (istream& is, const path_name& pn, uint64_t line)
+ {
+ path_ = &pn;
+
+ pre_parse_ = true;
+
+ lexer l (is, *path_, line, lexer_mode::command_line);
+ set_lexer (&l);
+
+ script s;
+ script_ = &s;
+ runner_ = nullptr;
+ environment_ = nullptr;
+
+ s.start_loc = location (*path_, line, 1);
+
+ token t (pre_parse_script ());
+
+ assert (t.type == type::eos);
+
+ s.end_loc = get_location (t);
+
+ return s;
+ }
+
+ token parser::
+ pre_parse_script ()
+ {
+ // enter: next token is first token of the script
+ // leave: eos (returned)
+
+ token t;
+ type tt;
+
+ // Parse lines until we see eos.
+ //
+ for (;;)
+ {
+ // Start lexing each line.
+ //
+ tt = peek (lexer_mode::first_token);
+
+ // Determine the line type by peeking at the first token.
+ //
+ switch (tt)
+ {
+ case type::eos:
+ {
+ next (t, tt);
+ return t;
+ }
+ default:
+ {
+ pre_parse_line (t, tt);
+ assert (tt == type::newline);
+ break;
+ }
+ }
+ }
+ }
+
+ void parser::
+ pre_parse_line (token& t, type& tt, bool if_line)
+ {
+ // Determine the line type/start token.
+ //
+ line_type lt (
+ pre_parse_line_start (t, tt, lexer_mode::second_token));
+
+ line ln;
+ switch (lt)
+ {
+ case line_type::var:
+ {
+ // Check if we are trying to modify any of the special variables.
+ //
+ if (special_variable (t.value))
+ fail (t) << "attempt to set '" << t.value << "' special "
+ << "variable";
+
+ // We don't pre-enter variables.
+ //
+ ln.var = nullptr;
+
+ next (t, tt); // Assignment kind.
+
+ mode (lexer_mode::variable_line);
+ parse_variable_line (t, tt);
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t;
+
+ break;
+ }
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_else:
+ case line_type::cmd_end:
+ {
+ if (!if_line)
+ {
+ fail (t) << lt << " without preceding 'if'";
+ }
+ }
+ // Fall through.
+ case line_type::cmd_if:
+ case line_type::cmd_ifn:
+ next (t, tt); // Skip to start of command.
+ // Fall through.
+ case line_type::cmd:
+ {
+ pair<command_expr, here_docs> p;
+
+ if (lt != line_type::cmd_else && lt != line_type::cmd_end)
+ p = parse_command_expr (t, tt, lexer::redirect_aliases);
+
+ if (tt != type::newline)
+ fail (t) << "expected newline instead of " << t;
+
+ parse_here_documents (t, tt, p);
+ break;
+ }
+ }
+
+ assert (tt == type::newline);
+
+ ln.type = lt;
+ ln.tokens = replay_data ();
+ script_->lines.push_back (move (ln));
+
+ if (lt == line_type::cmd_if || lt == line_type::cmd_ifn)
+ {
+ tt = peek (lexer_mode::first_token);
+
+ pre_parse_if_else (t, tt);
+ }
+ }
+
+ void parser::
+ pre_parse_if_else (token& t, type& tt)
+ {
+ // 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.
+ //
+ for (line_type bt (line_type::cmd_if); // Current block.
+ ;
+ tt = peek (lexer_mode::first_token))
+ {
+ 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.
+ //
+ size_t i (script_->lines.size ());
+
+ pre_parse_line (t, tt, true /* if_line */);
+ assert (tt == type::newline);
+
+ line_type lt (script_->lines[i].type);
+
+ // First take care of 'end'.
+ //
+ if (lt == line_type::cmd_end)
+ return;
+
+ // Check if-else block sequencing.
+ //
+ if (bt == line_type::cmd_else)
+ {
+ if (lt == line_type::cmd_else ||
+ lt == line_type::cmd_elif ||
+ lt == line_type::cmd_elifn)
+ fail (ll) << lt << " after " << bt;
+ }
+
+ // Update current if-else block.
+ //
+ switch (lt)
+ {
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn: bt = line_type::cmd_elif; break;
+ case line_type::cmd_else: bt = line_type::cmd_else; break;
+ default: break;
+ }
+ }
+ }
+
+ command_expr parser::
+ parse_command_line (token& t, type& tt)
+ {
+ // enter: first token of the command line
+ // leave: <newline>
+
+ // Note: this one is only used during execution.
+ //
+ assert (!pre_parse_);
+
+ pair<command_expr, here_docs> p (
+ parse_command_expr (t, tt, lexer::redirect_aliases));
+
+ assert (tt == type::newline);
+
+ parse_here_documents (t, tt, p);
+ assert (tt == type::newline);
+
+ return move (p.first);
+ }
+
+ //
+ // Execute.
+ //
+
+ void parser::
+ execute (const scope& rs, const scope& bs,
+ environment& e, const script& s, runner& r)
+ {
+ path_ = nullptr; // Set by replays.
+
+ pre_parse_ = false;
+
+ set_lexer (nullptr);
+
+ // The script shouldn't be able to modify the scopes.
+ //
+ root_ = const_cast<scope*> (&rs);
+ scope_ = const_cast<scope*> (&bs);
+ pbase_ = scope_->src_path_;
+
+ script_ = const_cast<script*> (&s);
+ runner_ = &r;
+ environment_ = &e;
+
+ exec_script ();
+ }
+
+ void parser::
+ exec_script ()
+ {
+ const script& s (*script_);
+
+ runner_->enter (*environment_, s.start_loc);
+
+ // 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&)
+ {
+ 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);
+ };
+
+ auto exec_cmd = [this] (token& t, build2::script::token_type& tt,
+ size_t li,
+ bool single,
+ const location& ll)
+ {
+ // We use the 0 index to signal that this is the only command.
+ //
+ if (single)
+ li = 0;
+
+ command_expr ce (
+ parse_command_line (t, static_cast<token_type&> (tt)));
+
+ runner_->run (*environment_, ce, li, ll);
+ };
+
+ auto exec_if = [this] (token& t, build2::script::token_type& tt,
+ 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.
+ //
+ return runner_->run_if (*environment_, ce, li, ll);
+ };
+
+ size_t li (1);
+
+ exec_lines (s.lines.begin (), s.lines.end (),
+ exec_set, exec_cmd, exec_if,
+ li,
+ &environment_->var_pool);
+
+ runner_->leave (*environment_, s.end_loc);
+ }
+
+ // When add a special variable don't forget to update lexer::word().
+ //
+ bool parser::
+ special_variable (const string& n) noexcept
+ {
+ return n == ">" || n == "<" || n == "~";
+ }
+
+ lookup parser::
+ lookup_variable (name&& qual, string&& name, const location& loc)
+ {
+ // In the pre-parse mode collect the referenced variable names for the
+ // script semantics change tracking.
+ //
+ if (pre_parse_)
+ {
+ // Add the variable name skipping special variables and suppressing
+ // duplicates. While at it, check if the script temporary directory
+ // is referenced and set the flag, if that's the case.
+ //
+ if (special_variable (name))
+ {
+ if (name == "~")
+ script_->temp_dir = true;
+ }
+ else if (!name.empty ())
+ {
+ auto& vars (script_->vars);
+
+ if (find (vars.begin (), vars.end (), name) == vars.end ())
+ vars.push_back (move (name));
+ }
+
+ return lookup ();
+ }
+
+ if (!qual.empty ())
+ fail (loc) << "qualified variable name";
+
+ lookup r (environment_->lookup (name));
+
+ // Fail if non-script-local variable with an untracked name.
+ //
+ if (r.defined () && !r.belongs (*environment_))
+ {
+ const auto& vars (script_->vars);
+
+ if (find (vars.begin (), vars.end (), name) == vars.end ())
+ fail (loc) << "use of untracked variable '" << name << "'";
+ }
+
+ return r;
+ }
+ }
+ }
+}