aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build2/buildfile147
-rw-r--r--build2/test/script/lexer74
-rw-r--r--build2/test/script/lexer.cxx249
-rw-r--r--build2/test/script/parser67
-rw-r--r--build2/test/script/parser.cxx283
-rw-r--r--build2/test/script/script66
-rw-r--r--build2/test/script/script.cxx83
-rw-r--r--build2/test/script/token45
8 files changed, 942 insertions, 72 deletions
diff --git a/build2/buildfile b/build2/buildfile
index 9c15468..6e71856 100644
--- a/build2/buildfile
+++ b/build2/buildfile
@@ -4,78 +4,81 @@
import libs = libbutl%lib{butl}
-exe{b}: \
- {hxx ixx cxx}{ algorithm } \
- { cxx}{ b } \
- {hxx ixx cxx}{ b-options } \
- {hxx cxx}{ context } \
- {hxx cxx}{ depdb } \
- {hxx cxx}{ diagnostics } \
- {hxx cxx}{ dump } \
- {hxx ixx cxx}{ file } \
- {hxx txx cxx}{ filesystem } \
- {hxx cxx}{ lexer } \
- {hxx cxx}{ module } \
- {hxx ixx cxx}{ name } \
- {hxx cxx}{ operation } \
- {hxx cxx}{ parser } \
- {hxx cxx}{ prerequisite } \
- {hxx cxx}{ rule } \
- {hxx }{ rule-map } \
- {hxx cxx}{ scope } \
- {hxx cxx}{ search } \
- {hxx cxx}{ spec } \
- {hxx ixx txx cxx}{ target } \
- {hxx }{ target-key } \
- {hxx }{ target-type } \
- {hxx cxx}{ token } \
- {hxx }{ types } \
- {hxx cxx}{ types-parsers } \
- {hxx ixx txx cxx}{ utility } \
- {hxx ixx txx cxx}{ variable } \
- {hxx }{ version } \
- bin/{hxx cxx}{ guess } \
- bin/{hxx cxx}{ init } \
- bin/{hxx cxx}{ rule } \
- bin/{hxx cxx}{ target } \
- c/{hxx cxx}{ init } \
- c/{hxx }{ target } \
- cc/{hxx cxx}{ common } \
- cc/{hxx cxx}{ compile } \
- cc/{ cxx}{ gcc } \
- cc/{hxx cxx}{ guess } \
- cc/{hxx cxx}{ init } \
- cc/{hxx cxx}{ install } \
- cc/{hxx cxx}{ link } \
- cc/{hxx cxx}{ module } \
- cc/{ cxx}{ msvc } \
- cc/{ cxx}{ pkgconfig } \
- cc/{hxx cxx}{ target } \
- cc/{hxx }{ types } \
- cc/{hxx ixx cxx}{ utility } \
- cc/{ cxx}{ windows-manifest } \
- cc/{ cxx}{ windows-rpath } \
- cli/{hxx cxx}{ init } \
- cli/{hxx cxx}{ rule } \
- cli/{hxx cxx}{ target } \
- config/{hxx cxx}{ init } \
- config/{hxx }{ module } \
- config/{hxx cxx}{ operation } \
- config/{hxx txx cxx}{ utility } \
- cxx/{hxx cxx}{ init } \
- cxx/{hxx cxx}{ target } \
- dist/{hxx cxx}{ init } \
- dist/{hxx cxx}{ operation } \
- dist/{hxx cxx}{ rule } \
-pkgconfig/{hxx cxx}{ init } \
- install/{hxx cxx}{ init } \
- install/{hxx cxx}{ operation } \
- install/{hxx cxx}{ rule } \
- install/{hxx }{ utility } \
- test/{hxx cxx}{ init } \
- test/{hxx cxx}{ operation } \
- test/{hxx cxx}{ rule } \
- $libs
+exe{b}: \
+ {hxx ixx cxx}{ algorithm } \
+ { cxx}{ b } \
+ {hxx ixx cxx}{ b-options } \
+ {hxx cxx}{ context } \
+ {hxx cxx}{ depdb } \
+ {hxx cxx}{ diagnostics } \
+ {hxx cxx}{ dump } \
+ {hxx ixx cxx}{ file } \
+ {hxx txx cxx}{ filesystem } \
+ {hxx cxx}{ lexer } \
+ {hxx cxx}{ module } \
+ {hxx ixx cxx}{ name } \
+ {hxx cxx}{ operation } \
+ {hxx cxx}{ parser } \
+ {hxx cxx}{ prerequisite } \
+ {hxx cxx}{ rule } \
+ {hxx }{ rule-map } \
+ {hxx cxx}{ scope } \
+ {hxx cxx}{ search } \
+ {hxx cxx}{ spec } \
+ {hxx ixx txx cxx}{ target } \
+ {hxx }{ target-key } \
+ {hxx }{ target-type } \
+ {hxx cxx}{ token } \
+ {hxx }{ types } \
+ {hxx cxx}{ types-parsers } \
+ {hxx ixx txx cxx}{ utility } \
+ {hxx ixx txx cxx}{ variable } \
+ {hxx }{ version } \
+ bin/{hxx cxx}{ guess } \
+ bin/{hxx cxx}{ init } \
+ bin/{hxx cxx}{ rule } \
+ bin/{hxx cxx}{ target } \
+ c/{hxx cxx}{ init } \
+ c/{hxx }{ target } \
+ cc/{hxx cxx}{ common } \
+ cc/{hxx cxx}{ compile } \
+ cc/{ cxx}{ gcc } \
+ cc/{hxx cxx}{ guess } \
+ cc/{hxx cxx}{ init } \
+ cc/{hxx cxx}{ install } \
+ cc/{hxx cxx}{ link } \
+ cc/{hxx cxx}{ module } \
+ cc/{ cxx}{ msvc } \
+ cc/{ cxx}{ pkgconfig } \
+ cc/{hxx cxx}{ target } \
+ cc/{hxx }{ types } \
+ cc/{hxx ixx cxx}{ utility } \
+ cc/{ cxx}{ windows-manifest } \
+ cc/{ cxx}{ windows-rpath } \
+ cli/{hxx cxx}{ init } \
+ cli/{hxx cxx}{ rule } \
+ cli/{hxx cxx}{ target } \
+ config/{hxx cxx}{ init } \
+ config/{hxx }{ module } \
+ config/{hxx cxx}{ operation } \
+ config/{hxx txx cxx}{ utility } \
+ cxx/{hxx cxx}{ init } \
+ cxx/{hxx cxx}{ target } \
+ dist/{hxx cxx}{ init } \
+ dist/{hxx cxx}{ operation } \
+ dist/{hxx cxx}{ rule } \
+ pkgconfig/{hxx cxx}{ init } \
+ install/{hxx cxx}{ init } \
+ install/{hxx cxx}{ operation } \
+ install/{hxx cxx}{ rule } \
+ install/{hxx }{ utility } \
+ test/{hxx cxx}{ init } \
+ test/{hxx cxx}{ operation } \
+ test/{hxx cxx}{ rule } \
+test/script/{hxx cxx}{ lexer } \
+test/script/{hxx cxx}{ parser } \
+test/script/{hxx cxx}{ script } \
+ $libs
# Pass our compiler target to be used as build2 host.
#
diff --git a/build2/test/script/lexer b/build2/test/script/lexer
new file mode 100644
index 0000000..de4c84e
--- /dev/null
+++ b/build2/test/script/lexer
@@ -0,0 +1,74 @@
+// file : build2/test/script/lexer -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TEST_SCRIPT_LEXER
+#define BUILD2_TEST_SCRIPT_LEXER
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/lexer>
+
+#include <build2/test/script/token>
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ struct lexer_mode: build2::lexer_mode
+ {
+ using base_type = build2::lexer_mode;
+
+ enum
+ {
+ script_line = base_type::value_next,
+ variable_line,
+ test_line,
+ command_line
+ };
+
+ using base_type::base_type;
+ };
+
+ class lexer: public build2::lexer
+ {
+ public:
+ using base_lexer = build2::lexer;
+ using base_mode = build2::lexer_mode;
+
+ lexer (istream& is, const path& name, lexer_mode m)
+ : base_lexer (is, name, nullptr, nullptr, false) {mode (m);}
+
+ virtual void
+ mode (base_mode, char = '\0') override;
+
+ // Return true if we entered the quoted (double or single) mode since
+ // last reset.
+ //
+ bool
+ quoted () const {return quoted_;}
+
+ void
+ reset_quoted (bool q) {quoted_ = q;}
+
+ protected:
+ virtual token
+ next_impl () override;
+
+ token
+ next_line ();
+
+ token
+ name_line (bool separated);
+
+ protected:
+ bool quoted_ = false;
+ };
+ }
+ }
+}
+
+#endif // BUILD2_TEST_SCRIPT_LEXER
diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx
new file mode 100644
index 0000000..84be7c1
--- /dev/null
+++ b/build2/test/script/lexer.cxx
@@ -0,0 +1,249 @@
+// file : build2/test/script/lexer.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/test/script/lexer>
+
+using namespace std;
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ using type = token_type;
+
+ void lexer::
+ mode (base_mode m, char)
+ {
+ const char* s1 (nullptr);
+ const char* s2 (nullptr);
+ bool s (true);
+
+ switch (m)
+ {
+ case lexer_mode::script_line:
+ {
+ s1 = "=+!|&<> $()#\t\n";
+ s2 = " == ";
+ break;
+ }
+ case lexer_mode::variable_line:
+ {
+ // Like value except we don't recognize {.
+ //
+ s1 = " $()[]#\t\n";
+ s2 = " ";
+ break;
+ }
+ case lexer_mode::test_line:
+ {
+ // As script_line but without variable assignments.
+ //
+ s1 = "=!|&<> $()#\t\n";
+ s2 = "== ";
+ break;
+ }
+ case lexer_mode::command_line:
+ {
+ // Note that whitespaces are not word separators in this mode.
+ //
+ s1 = "|&<>";
+ s2 = " ";
+ s = false;
+ break;
+ }
+ case lexer_mode::single_quoted:
+ case lexer_mode::double_quoted:
+ quoted_ = true;
+ // Fall through.
+ default:
+ {
+ // Disable pair separator.
+ //
+ base_lexer::mode (m, '\0');
+ }
+ }
+
+ state_.push (state {m, '\0', s, s1, s2});
+ }
+
+ token lexer::
+ next_impl ()
+ {
+ switch (state_.top ().mode)
+ {
+ case lexer_mode::script_line:
+ case lexer_mode::variable_line:
+ case lexer_mode::test_line:
+ case lexer_mode::command_line: return next_line ();
+ default: return base_lexer::next_impl ();
+ }
+ }
+
+ token lexer::
+ next_line ()
+ {
+ bool sep (skip_spaces ());
+
+ xchar c (get ());
+ uint64_t ln (c.line), cn (c.column);
+
+ if (eos (c))
+ return token (type::eos, sep, ln, cn);
+
+ lexer_mode m (state_.top ().mode);
+
+ // NOTE: remember to update mode() if adding new special characters.
+
+ if (m != lexer_mode::command_line)
+ {
+ switch (c)
+ {
+ case '\n':
+ {
+ return token (type::newline, sep, ln, cn);
+ }
+
+ // Variable expansion, function call, and evaluation context.
+ //
+ case '$': return token (type::dollar, sep, ln, cn);
+ case '(': return token (type::lparen, sep, ln, cn);
+ case ')': return token (type::rparen, sep, ln, cn);
+ }
+ }
+
+ if (m == lexer_mode::variable_line)
+ {
+ switch (c)
+ {
+ // Attributes.
+ //
+ case '[': return token (type::lsbrace, sep, ln, cn);
+ case ']': return token (type::rsbrace, sep, ln, cn);
+ }
+ }
+
+ // Command line operator/separators.
+ //
+ if (m == lexer_mode::script_line || m == lexer_mode::test_line)
+ {
+ switch (c)
+ {
+ // Comparison (==, !=).
+ //
+ case '=':
+ case '!':
+ {
+ if (peek () == '=')
+ {
+ get ();
+ return token (
+ c == '=' ? type::equal : type::not_equal, sep, ln, cn);
+ }
+ }
+ }
+ }
+
+ // Command operators/separators.
+ //
+ if (m == lexer_mode::script_line ||
+ m == lexer_mode::test_line ||
+ m == lexer_mode::command_line)
+ {
+ switch (c)
+ {
+ // |, ||
+ //
+ case '|':
+ {
+ if (peek () == '|')
+ {
+ get ();
+ return token (type::log_or, sep, ln, cn);
+ }
+ else
+ return token (type::pipe, sep, ln, cn);
+ }
+ // &, &&
+ //
+ case '&':
+ {
+ if (peek () == '&')
+ {
+ get ();
+ return token (type::log_and, sep, ln, cn);
+ }
+ else
+ return token (type::clean, sep, ln, cn);
+ }
+ // <
+ //
+ case '<':
+ {
+ xchar p (peek ());
+
+ if (p == '!' || p == '<')
+ {
+ get ();
+ return token (
+ p == '!' ? type::in_null : type::in_document, sep, ln, cn);
+ }
+ else
+ return token (type::in_string, sep, ln, cn);
+
+ }
+ // >
+ //
+ case '>':
+ {
+ xchar p (peek ());
+
+ if (p == '!' || p == '>')
+ {
+ get ();
+ return token (
+ p == '!' ? type::out_null : type::out_document, sep, ln, cn);
+ }
+ else
+ return token (type::out_string, sep, ln, cn);
+ }
+ }
+ }
+
+ // Variable assignment (=, +=, =+).
+ //
+ if (m == lexer_mode::script_line)
+ {
+ switch (c)
+ {
+ case '=':
+ {
+ if (peek () == '+')
+ {
+ get ();
+ return token (type::prepend, sep, ln, cn);
+ }
+ else
+ return token (type::assign, sep, ln, cn);
+ }
+ case '+':
+ {
+ if (peek () == '=')
+ {
+ get ();
+ return token (type::append, sep, ln, cn);
+ }
+ }
+ }
+ }
+
+ // Otherwise it is a name.
+ //
+ unget (c);
+ return name (sep);
+ }
+ }
+ }
+}
diff --git a/build2/test/script/parser b/build2/test/script/parser
new file mode 100644
index 0000000..720a077
--- /dev/null
+++ b/build2/test/script/parser
@@ -0,0 +1,67 @@
+// file : build2/test/script/parser -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TEST_SCRIPT_PARSER
+#define BUILD2_TEST_SCRIPT_PARSER
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/parser>
+#include <build2/diagnostics>
+
+#include <build2/test/script/token>
+#include <build2/test/script/script>
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ class lexer;
+
+ class parser: protected build2::parser
+ {
+ public:
+ using script_type = test::script::script;
+
+ // Issue diagnostics and throw failed in case of an error.
+ //
+ script_type
+ parse (istream&, const path& name, target& test, target& script);
+
+ // Recursive descent parser.
+ //
+ // Each parse function receives the token/type from which it should
+ // start consuming. On return the token/type should contain the first
+ // token that has not been consumed.
+ //
+ protected:
+ void
+ script (token&, token_type&);
+
+ void
+ script_line (token&, token_type&);
+
+ void
+ variable_line (token&, token_type&, string);
+
+ void
+ test_line (token&, token_type&, names_type, location);
+
+ void
+ command_exit (token&, token_type&);
+
+ protected:
+ using base_parser = build2::parser;
+
+ lexer* lexer_;
+ script_type* script_;
+ };
+ }
+ }
+}
+
+#endif // BUILD2_TEST_SCRIPT_PARSER
diff --git a/build2/test/script/parser.cxx b/build2/test/script/parser.cxx
new file mode 100644
index 0000000..aba9f9a
--- /dev/null
+++ b/build2/test/script/parser.cxx
@@ -0,0 +1,283 @@
+// file : build2/test/script/parser.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/test/script/parser>
+
+#include <build2/test/script/lexer>
+
+using namespace std;
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ using type = token_type;
+
+ script parser::
+ parse (istream& is, const path& p, target& test_t, target& script_t)
+ {
+ path_ = &p;
+
+ lexer l (is, *path_, lexer_mode::script_line);
+ lexer_ = &l;
+ base_parser::lexer_ = &l;
+
+ script_type r (test_t, script_t);
+ script_ = &r;
+
+ token t (type::eos, false, 0, 0);
+ type tt;
+ next (t, tt);
+
+ script (t, tt);
+
+ if (tt != type::eos)
+ fail (t) << "unexpected " << t;
+
+ return r;
+ }
+
+ void parser::
+ script (token& t, token_type& tt)
+ {
+ while (tt != type::eos)
+ {
+ script_line (t, tt);
+ }
+ }
+
+ void parser::
+ script_line (token& t, token_type& tt)
+ {
+ // Parse first chunk. Keep track of whether anything in it was quoted.
+ //
+ names_type ns;
+ location nl (get_location (t));
+ lexer_->reset_quoted (t.quoted);
+ names (t, tt, ns, true);
+
+ // See if this is a variable assignment or a test command.
+ //
+ if (tt == type::assign ||
+ tt == type::prepend ||
+ tt == type::append)
+ {
+ // We need to strike a balance between recognizing command lines
+ // that contain the assignment operator and variable assignments.
+ //
+ // If we choose to treat these tokens literally (for example, if we
+ // have several names on the LHS), then we have the reversibility
+ // problem: we need to restore original whitespaces before and after
+ // the assignment operator (e.g., foo=bar vs foo = bar).
+ //
+ // To keep things simple we will start with the following rule: if
+ // the token after the first chunk of input is assignment, then it
+ // must be a variable assignment. After all, command lines like this
+ // are not expected to be common:
+ //
+ // $* =x
+ //
+ // It will also be easy to get the desired behavior with quoting:
+ //
+ // $* "=x"
+ //
+ // The only issue here is if $* above expands to a single, simple
+ // name (e.g., an executable name) in which case it will be treated
+ // as a variable name. One way to resolve it would be to detect
+ // "funny" variable names and require that they be quoted (this
+ // won't help with built-in commands; maybe we could warn if it's
+ // the same as built-in). Note that currently we have no way of
+ // knowing it's quoted.
+ //
+ // Or perhaps we should just let people learn that first assignment
+ // needs to be quoted?
+ //
+ if (ns.size () != 1 || !ns[0].simple () || ns[0].empty ())
+ fail (nl) << "variable name expected instead of '" << ns << "'";
+
+ variable_line (t, tt, move (ns[0].value));
+ }
+ else
+ test_line (t, tt, move (ns), move (nl));
+ }
+
+ void parser::
+ variable_line (token& t, token_type& tt, string name)
+ {
+ type kind (tt); // Assignment kind.
+ const variable_type& var (script_->var_pool.insert (move (name)));
+
+ // We cannot reuse the value mode since it will recognize { which
+ // we want to treat as a literal.
+ //
+ value rhs (variable_value (t, tt, lexer_mode::variable_line));
+
+ value& lhs (kind == type::assign
+ ? script_->assign (var)
+ : script_->append (var));
+
+ // @@ Need to adjust to make strings the default type.
+ //
+ value_attributes (&var, lhs, move (rhs), kind);
+ }
+
+ void parser::
+ test_line (token& t, token_type& tt, names_type ns, location nl)
+ {
+ // Stop recognizing variable assignments.
+ //
+ mode (lexer_mode::test_line);
+
+ // Keep parsing chunks of the command line until we see the newline or
+ // the exit status comparison.
+ //
+ strings cmd;
+
+ do
+ {
+ // Process words that we already have.
+ //
+ bool q (lexer_->quoted ());
+
+ for (name& n: ns)
+ {
+ string s;
+
+ try
+ {
+ s = value_traits<string>::convert (move (n), nullptr);
+ }
+ catch (const invalid_argument&)
+ {
+ fail (nl) << "invalid string value '" << n << "'";
+ }
+
+ // If it is a quoted chunk, then we add the word as is. Otherwise
+ // we re-lex it. But if the word doesn't contain any interesting
+ // characters (operators plus quotes/escapes), then no need to
+ // re-lex.
+ //
+ if (q || s.find_first_of ("|&<>\'\"\\") == string::npos)
+ cmd.push_back (move (s));
+ else
+ {
+ // Come up with a "path" that contains both the original
+ // location as well as the expanded string. The resulting
+ // diagnostics will look like this:
+ //
+ // testscript:10:1 ('abc): unterminated single quote
+ //
+ path name;
+ {
+ string n (nl.file->string ());
+ n += ':';
+ n += to_string (nl.line);
+ n += ':';
+ n += to_string (nl.column);
+ n += ": (";
+ n += s;
+ n += ')';
+ name = path (move (n));
+ }
+
+ istringstream is (s);
+ lexer lex (is, name, lexer_mode::command_line);
+
+ string w;
+ bool f (true); // In case the whole thing is empty.
+ for (token t (lex.next ()); t.type != type::eos; t = lex.next ())
+ {
+ // Note that this is not "our" token so we cannot do fail(t).
+ // Rather we should do fail(l).
+ //
+ location l (build2::get_location (t, lex.name ()));
+
+ // Re-lexing double-quotes will recognize $, ( inside as
+ // tokens so we have to reverse them back. Since we don't
+ // treat spaces as separators we can be sure we will get it
+ // right.
+ //
+ switch (t.type)
+ {
+ case type::dollar: w += '$'; continue;
+ case type::lparen: w += '('; continue;
+ }
+
+ // Retire the current word. We need to distinguish between
+ // empty and non-existent (e.g., > vs >"").
+ //
+ if (!w.empty () || f)
+ {
+ cmd.push_back (move (w));
+ f = false;
+ }
+
+ switch (t.type)
+ {
+ case type::name: w = move (t.value); f = true; break;
+
+ // @@ TODO
+ //
+ case type::pipe:
+ case type::clean:
+ case type::log_and:
+ case type::log_or:
+
+ case type::in_null:
+ case type::in_string:
+ case type::in_document:
+
+ case type::out_null:
+ case type::out_string:
+ case type::out_document:
+ break;
+ }
+ }
+
+ // Don't forget the last word.
+ //
+ if (!w.empty () || f)
+ cmd.push_back (move (w));
+ }
+ }
+
+ if (tt == type::newline ||
+ tt == type::equal ||
+ tt == type::not_equal)
+ break;
+
+ // Parse the next chunk.
+ //
+ ns.clear ();
+ lexer_->reset_quoted (t.quoted);
+ names (t, tt, ns, true);
+
+ } while (true);
+
+ //@@ switch mode (we no longer want to recognize command operators)?
+
+ if (tt == type::equal || tt == type::not_equal)
+ {
+ command_exit (t, tt);
+ }
+
+ // here-document
+ }
+
+ void parser::
+ command_exit (token& t, token_type& tt)
+ {
+ // The next chunk should be the exit status.
+ //
+ next (t, tt);
+ names_type ns (names (t, tt, true));
+
+ //@@ TODO: validate to be single, simple, non-empty name that
+ // converts to integer (is exit status always non-negative).
+ }
+ }
+ }
+}
diff --git a/build2/test/script/script b/build2/test/script/script
new file mode 100644
index 0000000..de81fa6
--- /dev/null
+++ b/build2/test/script/script
@@ -0,0 +1,66 @@
+// file : build2/test/script/script -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TEST_SCRIPT_SCRIPT
+#define BUILD2_TEST_SCRIPT_SCRIPT
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/variable>
+
+namespace build2
+{
+ class target;
+
+ namespace test
+ {
+ namespace script
+ {
+ class script
+ {
+ public:
+ script (target& tt, target& st)
+ : test_target (tt), script_target (st) {}
+
+ public:
+ target& test_target; // Target we are testing.
+ target& script_target; // Target of the testscript file.
+
+ public:
+ // Note that if we pass the variable name as a string, then it will
+ // be looked up in the wrong pool.
+ //
+ variable_pool var_pool;
+ variable_map vars;
+
+ // Lookup the variable starting from this scope, continuing with outer
+ // scopes, then the target being tested, then the testscript target,
+ // and then outer buildfile scopes (including testscript-type/pattern
+ // specific).
+ //
+ lookup
+ find (const variable&) const;
+
+ // Return a value suitable for assignment. If the variable does not
+ // exist in this scope's map, then a new one with the NULL value is
+ // added and returned. Otherwise the existing value is returned.
+ //
+ value&
+ assign (const variable& var) {return vars.assign (var);}
+
+ // Return a value suitable for append/prepend. If the variable does
+ // not exist in this scope's map, then outer scopes are searched for
+ // the same variable. If found then a new variable with the found
+ // value is added to this scope and returned. Otherwise this function
+ // proceeds as assign() above.
+ //
+ value&
+ append (const variable&);
+ };
+ }
+ }
+}
+
+#endif // BUILD2_TEST_SCRIPT_SCRIPT
diff --git a/build2/test/script/script.cxx b/build2/test/script/script.cxx
new file mode 100644
index 0000000..706c87d
--- /dev/null
+++ b/build2/test/script/script.cxx
@@ -0,0 +1,83 @@
+// file : build2/test/script/script.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build2/test/script/script>
+
+#include <build2/target>
+
+using namespace std;
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ lookup script::
+ find (const variable& var) const
+ {
+ if (const value* v = vars.find (var))
+ return lookup (v, &vars);
+
+ // Switch to the corresponding buildfile variable. Note that we don't
+ // want to insert a new variable into the pool (we might be running
+ // concurrently). Plus, if there is no such variable, then we cannot
+ // possibly find any value.
+ //
+ const variable* pvar (build2::var_pool.find (var.name));
+
+ if (pvar == nullptr)
+ return lookup ();
+
+ {
+ const variable& var (*pvar);
+
+ // First check the target we are testing.
+ //
+ {
+ // Note that we skip applying the override if we did not find any
+ // value. In this case, presumably the override also affects the
+ // script target and we will pick it up there. A bit fuzzy.
+ //
+ auto p (test_target.find_original (var, true));
+
+ if (p.first)
+ {
+ if (var.override != nullptr)
+ p = test_target.base_scope ().find_override (
+ var, move (p), true);
+
+ return p.first;
+ }
+ }
+
+ // Then the script target followed by the scopes it is in. Note that
+ // while unlikely it is possible the test and script targets will be
+ // in different scopes which brings the question of which scopes we
+ // should search.
+ //
+ return script_target[var];
+ }
+ }
+
+ value& script::
+ append (const variable& var)
+ {
+ lookup l (find (var));
+
+ if (l.defined () && l.belongs (*this)) // Existing var in this scope.
+ return const_cast<value&> (*l);
+
+ value& r (assign (var)); // NULL.
+
+ //@@ I guess this is where we convert untyped value to strings?
+ //
+ if (l.defined ())
+ r = *l; // Copy value (and type) from the outer scope.
+
+ return r;
+ }
+ }
+ }
+}
diff --git a/build2/test/script/token b/build2/test/script/token
new file mode 100644
index 0000000..51bf282
--- /dev/null
+++ b/build2/test/script/token
@@ -0,0 +1,45 @@
+// file : build2/test/script/token -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD2_TEST_SCRIPT_TOKEN
+#define BUILD2_TEST_SCRIPT_TOKEN
+
+#include <build2/types>
+#include <build2/utility>
+
+#include <build2/token>
+
+namespace build2
+{
+ namespace test
+ {
+ namespace script
+ {
+ struct token_type: build2::token_type
+ {
+ using base_type = build2::token_type;
+
+ enum
+ {
+ pipe = base_type::value_next, // |
+ clean, // &
+ log_and, // &&
+ log_or, // ||
+
+ in_null, // <!
+ in_string, // <
+ in_document, // <<
+
+ out_null, // <!
+ out_string, // <
+ out_document, // <<
+ };
+
+ using base_type::base_type;
+ };
+ }
+ }
+}
+
+#endif // BUILD2_TEST_SCRIPT_TOKEN