aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/script/script.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2020-04-28 08:48:53 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2020-05-27 15:47:28 +0200
commitb808c255b6a9ddba085bf5646e7d20ec344f2e2d (patch)
tree32730291f7e6de8ef0a227905520dd66fb4ec0f3 /libbuild2/script/script.cxx
parent3552356a87402727e663131994fa87f48b3cd4fb (diff)
Initial support for ad hoc recipes (still work in progress)
Diffstat (limited to 'libbuild2/script/script.cxx')
-rw-r--r--libbuild2/script/script.cxx659
1 files changed, 659 insertions, 0 deletions
diff --git a/libbuild2/script/script.cxx b/libbuild2/script/script.cxx
new file mode 100644
index 0000000..c85bfd3
--- /dev/null
+++ b/libbuild2/script/script.cxx
@@ -0,0 +1,659 @@
+// file : libbuild2/script/script.cxx -*- C++ -*-
+// license : MIT; see accompanying LICENSE file
+
+#include <libbuild2/script/script.hxx>
+
+#include <sstream>
+#include <cstring> // strchr()
+
+using namespace std;
+
+namespace build2
+{
+ namespace script
+ {
+ ostream&
+ operator<< (ostream& o, line_type lt)
+ {
+ const char* s (nullptr);
+
+ switch (lt)
+ {
+ case line_type::var: s = "variable"; break;
+ case line_type::cmd: s = "command"; break;
+ case line_type::cmd_if: s = "'if'"; break;
+ case line_type::cmd_ifn: s = "'if!'"; break;
+ 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_end: s = "'end'"; break;
+ }
+
+ return o << s;
+ }
+
+ void
+ dump (ostream& os, const string& ind, const lines& ls)
+ {
+ // For each line print its tokens literal representation trying to
+ // reproduce the quoting. Consider mixed quoting as double quoting
+ // since the information is lost.
+ //
+ // Also additionally indent the if-branch lines.
+ //
+ string if_ind;
+
+ for (const line& l: ls)
+ {
+ // Before printing indentation, decrease it if the else or end line is
+ // reached.
+ //
+ switch (l.type)
+ {
+ case line_type::cmd_elif:
+ case line_type::cmd_elifn:
+ case line_type::cmd_else:
+ case line_type::cmd_end:
+ {
+ size_t n (if_ind.size ());
+ assert (n >= 2);
+ if_ind.resize (n - 2);
+ break;
+ }
+ default: break;
+ }
+
+ // Print indentations.
+ //
+ os << ind << if_ind;
+
+ // After printing indentation, increase it for if/else branch.
+ //
+ switch (l.type)
+ {
+ 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: if_ind += " "; break;
+ default: break;
+ }
+
+ // '"' or '\'' if we are inside the quoted token sequence and '\0'
+ // otherwise. Thus, can be used as bool.
+ //
+ char qseq ('\0');
+
+ for (const replay_token& rt: l.tokens)
+ {
+ const token& t (rt.token);
+
+ // '"' or '\'' if the token is quoted and '\0' otherwise. Thus,
+ // can be used as bool.
+ //
+ char qtok ('\0');
+
+ switch (t.qtype)
+ {
+ case quote_type::unquoted: qtok = '\0'; break;
+ case quote_type::single: qtok = '\''; break;
+ case quote_type::mixed:
+ case quote_type::double_: qtok = '"'; break;
+ }
+
+ // If being inside a quoted token sequence we have reached a token
+ // quoted differently or the newline, then we probably made a
+ // mistake misinterpreting some previous partially quoted token, for
+ // example f"oo" as "foo. If that's the case, all we can do is to
+ // end the sequence adding the trailing quote.
+ //
+ // Note that a token inside the quoted sequence may well be
+ // unquoted, so for example "$foo" is lexed as:
+ //
+ // token quoting complete notes
+ // '' " no
+ // $ " yes
+ // 'foo' Unquoted since lexed in variable mode.
+ // '' " no
+ // \n
+ //
+ if (qseq &&
+ ((qtok && qtok != qseq) || t.type == token_type::newline))
+ {
+ os << qseq;
+ qseq = '\0';
+ }
+
+ // Left and right token quotes (can be used as bool).
+ //
+ char lq ('\0');
+ char rq ('\0');
+
+ // If the token is quoted, then determine if/which quotes should be
+ // present on its sides and track the quoted token sequence.
+ //
+ if (qtok)
+ {
+ if (t.qcomp) // Complete token quoting.
+ {
+ // If we are inside a quoted token sequence then do noting.
+ // Otherwise just quote the current token not starting a
+ // sequence.
+ //
+ if (!qseq)
+ {
+ lq = qtok;
+ rq = qtok;
+ }
+ }
+ else // Partial token quoting.
+ {
+ // Note that we can not always reproduce the original tokens
+ // representation for partial quoting. For example, the two
+ // following tokens are lexed into the identical token objects:
+ //
+ // "foo
+ // f"oo"
+ //
+ // We will always assume that the partially quoted token either
+ // starts or ends the quoted token sequence. Sometimes this ends
+ // up unexpectedly, but seems there is not much we can do:
+ //
+ // f"oo" "ba"r -> "foo bar"
+ //
+ if (!qseq) // Start quoted sequence.
+ {
+ lq = qtok;
+ qseq = qtok;
+ }
+ else // End quoted sequence.
+ {
+ rq = qtok;
+ qseq = '\0';
+ }
+ }
+ }
+
+ // Print the space character prior to the separated token, unless
+ // it is a first like token or the newline.
+ //
+ if (t.separated &&
+ t.type != token_type::newline &&
+ &rt != &l.tokens[0])
+ os << ' ';
+
+ if (lq) os << lq; // Print the left quote, if required.
+
+ // Escape the special characters, unless the token in not a word or
+ // is single-quoted. Note that the special character set depends on
+ // whether the word is double-quoted or unquoted.
+ //
+ if (t.type == token_type::word && qtok != '\'')
+ {
+ for (char c: t.value)
+ {
+ if (strchr (qtok ? "\\\"" : "|&<>=\\\"", c) != nullptr)
+ os << '\\';
+
+ os << c;
+ }
+ }
+ else
+ t.printer (os, t, print_mode::raw);
+
+ if (rq) os << rq; // Print the right quote, if required.
+ }
+ }
+ }
+
+ // Quote if empty or contains spaces or any of the special characters.
+ // Note that we use single quotes since double quotes still allow
+ // expansion.
+ //
+ // @@ What if it contains single quotes?
+ //
+ static void
+ to_stream_q (ostream& o, const string& s)
+ {
+ if (s.empty () || s.find_first_of (" |&<>=\\\"") != string::npos)
+ o << '\'' << s << '\'';
+ else
+ o << s;
+ };
+
+ void
+ to_stream (ostream& o, const command& c, command_to_stream m)
+ {
+ auto print_path = [&o] (const path& p)
+ {
+ using build2::operator<<;
+
+ ostringstream s;
+ stream_verb (s, stream_verb (o));
+ s << p;
+
+ to_stream_q (o, s.str ());
+ };
+
+ auto print_redirect = [&o, print_path] (const redirect& r, int fd)
+ {
+ const redirect& er (r.effective ());
+
+ // Print the none redirect (no data allowed) if/when the respective
+ // syntax is invented.
+ //
+ if (er.type == redirect_type::none)
+ return;
+
+ o << ' ';
+
+ // Print the redirect file descriptor.
+ //
+ if (fd == 2)
+ o << fd;
+
+ // Print the redirect original representation and the modifiers, if
+ // present.
+ //
+ r.token.printer (o, r.token, print_mode::raw);
+
+ // Print the rest of the redirect (file path, etc).
+ //
+ switch (er.type)
+ {
+ case redirect_type::none: assert (false); break;
+ case redirect_type::here_doc_ref: assert (false); break;
+
+ case redirect_type::pass:
+ case redirect_type::null:
+ case redirect_type::trace: break;
+ case redirect_type::merge: o << er.fd; break;
+
+ case redirect_type::file:
+ {
+ print_path (er.file.path);
+ break;
+ }
+
+ case redirect_type::here_str_literal:
+ case redirect_type::here_doc_literal:
+ {
+ if (er.type == redirect_type::here_doc_literal)
+ o << er.end;
+ else
+ {
+ const string& v (er.str);
+ to_stream_q (o,
+ er.modifiers ().find (':') == string::npos
+ ? string (v, 0, v.size () - 1) // Strip newline.
+ : v);
+ }
+
+ break;
+ }
+
+ case redirect_type::here_str_regex:
+ case redirect_type::here_doc_regex:
+ {
+ const regex_lines& re (er.regex);
+
+ if (er.type == redirect_type::here_doc_regex)
+ o << re.intro + er.end + re.intro + re.flags;
+ else
+ {
+ assert (!re.lines.empty ()); // Regex can't be empty.
+
+ regex_line l (re.lines[0]);
+ to_stream_q (o, re.intro + l.value + re.intro + l.flags);
+ }
+
+ break;
+ }
+ }
+ };
+
+ auto print_doc = [&o] (const redirect& r)
+ {
+ o << endl;
+
+ if (r.type == redirect_type::here_doc_literal)
+ o << r.str;
+ else
+ {
+ assert (r.type == redirect_type::here_doc_regex);
+
+ const regex_lines& rl (r.regex);
+
+ for (auto b (rl.lines.cbegin ()), i (b), e (rl.lines.cend ());
+ i != e; ++i)
+ {
+ if (i != b)
+ o << endl;
+
+ const regex_line& l (*i);
+
+ if (l.regex) // Regex (possibly empty),
+ o << rl.intro << l.value << rl.intro << l.flags;
+ else if (!l.special.empty ()) // Special literal.
+ o << rl.intro;
+ else // Textual literal.
+ o << l.value;
+
+ o << l.special;
+ }
+ }
+
+ o << (r.modifiers ().find (':') == string::npos ? "" : "\n") << r.end;
+ };
+
+ if ((m & command_to_stream::header) == command_to_stream::header)
+ {
+ // Program.
+ //
+ to_stream_q (o, c.program.string ());
+
+ // Arguments.
+ //
+ for (const string& a: c.arguments)
+ {
+ o << ' ';
+ to_stream_q (o, a);
+ }
+
+ // Redirects.
+ //
+ if (c.in)
+ print_redirect (*c.in, 0);
+
+ if (c.out)
+ print_redirect (*c.out, 1);
+
+ if (c.err)
+ print_redirect (*c.err, 2);
+
+ for (const auto& p: c.cleanups)
+ {
+ o << " &";
+
+ if (p.type != cleanup_type::always)
+ o << (p.type == cleanup_type::maybe ? '?' : '!');
+
+ print_path (p.path);
+ }
+
+ if (c.exit.comparison != exit_comparison::eq || c.exit.code != 0)
+ {
+ switch (c.exit.comparison)
+ {
+ case exit_comparison::eq: o << " == "; break;
+ case exit_comparison::ne: o << " != "; break;
+ }
+
+ o << static_cast<uint16_t> (c.exit.code);
+ }
+ }
+
+ if ((m & command_to_stream::here_doc) == command_to_stream::here_doc)
+ {
+ // Here-documents.
+ //
+ if (c.in &&
+ (c.in->type == redirect_type::here_doc_literal ||
+ c.in->type == redirect_type::here_doc_regex))
+ print_doc (*c.in);
+
+ if (c.out &&
+ (c.out->type == redirect_type::here_doc_literal ||
+ c.out->type == redirect_type::here_doc_regex))
+ print_doc (*c.out);
+
+ if (c.err &&
+ (c.err->type == redirect_type::here_doc_literal ||
+ c.err->type == redirect_type::here_doc_regex))
+ print_doc (*c.err);
+ }
+ }
+
+ void
+ to_stream (ostream& o, const command_pipe& p, command_to_stream m)
+ {
+ if ((m & command_to_stream::header) == command_to_stream::header)
+ {
+ for (auto b (p.begin ()), i (b); i != p.end (); ++i)
+ {
+ if (i != b)
+ o << " | ";
+
+ to_stream (o, *i, command_to_stream::header);
+ }
+ }
+
+ if ((m & command_to_stream::here_doc) == command_to_stream::here_doc)
+ {
+ for (const command& c: p)
+ to_stream (o, c, command_to_stream::here_doc);
+ }
+ }
+
+ void
+ to_stream (ostream& o, const command_expr& e, command_to_stream m)
+ {
+ if ((m & command_to_stream::header) == command_to_stream::header)
+ {
+ for (auto b (e.begin ()), i (b); i != e.end (); ++i)
+ {
+ if (i != b)
+ {
+ switch (i->op)
+ {
+ case expr_operator::log_or: o << " || "; break;
+ case expr_operator::log_and: o << " && "; break;
+ }
+ }
+
+ to_stream (o, i->pipe, command_to_stream::header);
+ }
+ }
+
+ if ((m & command_to_stream::here_doc) == command_to_stream::here_doc)
+ {
+ for (const expr_term& t: e)
+ to_stream (o, t.pipe, command_to_stream::here_doc);
+ }
+ }
+
+ // redirect
+ //
+ redirect::
+ redirect (redirect_type t)
+ : type (t)
+ {
+ switch (type)
+ {
+ case redirect_type::none:
+ case redirect_type::pass:
+ case redirect_type::null:
+ case redirect_type::trace:
+ case redirect_type::merge: break;
+
+ case redirect_type::here_str_literal:
+ case redirect_type::here_doc_literal: new (&str) string (); break;
+
+ case redirect_type::here_str_regex:
+ case redirect_type::here_doc_regex:
+ {
+ new (&regex) regex_lines ();
+ break;
+ }
+
+ case redirect_type::file: new (&file) file_type (); break;
+
+ case redirect_type::here_doc_ref: assert (false); break;
+ }
+ }
+
+ redirect::
+ redirect (redirect&& r) noexcept
+ : type (r.type),
+ token (move (r.token)),
+ end (move (r.end)),
+ end_line (r.end_line),
+ end_column (r.end_column)
+ {
+ switch (type)
+ {
+ case redirect_type::none:
+ case redirect_type::pass:
+ case redirect_type::null:
+ case redirect_type::trace: break;
+
+ case redirect_type::merge: fd = r.fd; break;
+
+ case redirect_type::here_str_literal:
+ case redirect_type::here_doc_literal:
+ {
+ new (&str) string (move (r.str));
+ break;
+ }
+ case redirect_type::here_str_regex:
+ case redirect_type::here_doc_regex:
+ {
+ new (&regex) regex_lines (move (r.regex));
+ break;
+ }
+ case redirect_type::file:
+ {
+ new (&file) file_type (move (r.file));
+ break;
+ }
+ case redirect_type::here_doc_ref:
+ {
+ new (&ref) reference_wrapper<const redirect> (r.ref);
+ break;
+ }
+ }
+ }
+
+ redirect& redirect::
+ operator= (redirect&& r) noexcept
+ {
+ if (this != &r)
+ {
+ this->~redirect ();
+ new (this) redirect (move (r)); // Assume noexcept move-constructor.
+ }
+ return *this;
+ }
+
+ redirect::
+ ~redirect ()
+ {
+ switch (type)
+ {
+ case redirect_type::none:
+ case redirect_type::pass:
+ case redirect_type::null:
+ case redirect_type::trace:
+ case redirect_type::merge: break;
+
+ case redirect_type::here_str_literal:
+ case redirect_type::here_doc_literal: str.~string (); break;
+
+ case redirect_type::here_str_regex:
+ case redirect_type::here_doc_regex: regex.~regex_lines (); break;
+
+ case redirect_type::file: file.~file_type (); break;
+
+ case redirect_type::here_doc_ref:
+ {
+ ref.~reference_wrapper<const redirect> ();
+ break;
+ }
+ }
+ }
+
+ redirect::
+ redirect (const redirect& r)
+ : type (r.type),
+ token (r.token),
+ end (r.end),
+ end_line (r.end_line),
+ end_column (r.end_column)
+ {
+ switch (type)
+ {
+ case redirect_type::none:
+ case redirect_type::pass:
+ case redirect_type::null:
+ case redirect_type::trace: break;
+
+ case redirect_type::merge: fd = r.fd; break;
+
+ case redirect_type::here_str_literal:
+ case redirect_type::here_doc_literal:
+ {
+ new (&str) string (r.str);
+ break;
+ }
+ case redirect_type::here_str_regex:
+ case redirect_type::here_doc_regex:
+ {
+ new (&regex) regex_lines (r.regex);
+ break;
+ }
+ case redirect_type::file:
+ {
+ new (&file) file_type (r.file);
+ break;
+ }
+ case redirect_type::here_doc_ref:
+ {
+ new (&ref) reference_wrapper<const redirect> (r.ref);
+ break;
+ }
+ }
+ }
+
+ redirect& redirect::
+ operator= (const redirect& r)
+ {
+ if (this != &r)
+ *this = redirect (r); // Reduce to move-assignment.
+ return *this;
+ }
+
+ // environment
+ //
+ void environment::
+ clean (script::cleanup c, bool implicit)
+ {
+ using script::cleanup;
+
+ assert (!implicit || c.type == cleanup_type::always);
+
+ const path& p (c.path);
+
+ if (sandbox_dir.path != nullptr && !p.sub (*sandbox_dir.path))
+ {
+ if (implicit)
+ return;
+ else
+ assert (false); // Error so should have been checked.
+ }
+
+ auto pr = [&p] (const cleanup& v) -> bool {return v.path == p;};
+ auto i (find_if (cleanups.begin (), cleanups.end (), pr));
+
+ if (i == cleanups.end ())
+ cleanups.emplace_back (move (c));
+ else if (!implicit)
+ i->type = c.type;
+ }
+
+ void environment::
+ clean_special (path p)
+ {
+ special_cleanups.emplace_back (move (p));
+ }
+ }
+}