aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-03-16 10:35:32 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-03-16 18:15:18 +0200
commitece4003beebd23082a5fd7a324de40c5572161d1 (patch)
tree44597a24266b31e55b27b7393d2d20c729603d95
parentd2b324a72fdf16fcd68e6ba7ca0280aa95de4b71 (diff)
Add support for passing parameters to (meta-) operations
-rw-r--r--build2/b.cxx105
-rw-r--r--build2/config/operation.cxx49
-rw-r--r--build2/dist/operation.cxx37
-rw-r--r--build2/install/operation.cxx5
-rw-r--r--build2/lexer33
-rw-r--r--build2/lexer.cxx53
-rw-r--r--build2/operation51
-rw-r--r--build2/operation.cxx10
-rw-r--r--build2/parser2
-rw-r--r--build2/parser.cxx127
-rw-r--r--build2/spec4
-rw-r--r--build2/spec.cxx28
-rw-r--r--build2/test/operation.cxx5
-rw-r--r--build2/test/script/lexer6
-rw-r--r--build2/test/script/lexer.cxx4
-rw-r--r--unit-tests/lexer/buildfile2
-rw-r--r--unit-tests/lexer/buildspec.test16
-rw-r--r--unit-tests/lexer/driver.cxx4
18 files changed, 349 insertions, 192 deletions
diff --git a/build2/b.cxx b/build2/b.cxx
index b13300e..d247958 100644
--- a/build2/b.cxx
+++ b/build2/b.cxx
@@ -359,7 +359,10 @@ main (int argc, char* argv[])
for (auto mit (bspec.begin ()); mit != bspec.end (); )
{
vector_view<opspec> opspecs;
+
const string& mname (lifted == nullptr ? mit->name : lifted->name);
+ const values& mparams (lifted == nullptr ? mit->params : lifted->params);
+
current_mname = &mname;
if (lifted == nullptr)
@@ -409,6 +412,8 @@ main (int argc, char* argv[])
// A lifted meta-operation will always have default operation.
//
const string& oname (lifted == nullptr ? os.name : string ());
+ const values& oparams (lifted == nullptr ? os.params : values ());
+
current_oname = &oname;
if (lifted != nullptr)
@@ -735,25 +740,6 @@ main (int argc, char* argv[])
skip = lifted - mit->data () + 1;
break; // Out of targetspec loop.
}
- else
- {
- o = operation_table.find (oname);
-
- if (o == 0)
- {
- diag_record dr;
- dr << fail (l) << "unknown operation " << oname;
-
- // If we guessed src_root and didn't load anything during
- // bootstrap, then this is probably a meta-operation that
- // would have been added by the module if src_root was
- // correct.
- //
- if (guessing && !bootstrapped)
- dr << info << "consider explicitly specifying src_base "
- << "for " << tn;
- }
- }
}
if (!mname.empty ())
@@ -773,6 +759,26 @@ main (int argc, char* argv[])
}
}
+ if (!oname.empty ())
+ {
+ o = operation_table.find (oname);
+
+ if (o == 0)
+ {
+ diag_record dr;
+ dr << fail (l) << "unknown operation " << oname;
+
+ // If we guessed src_root and didn't load anything during
+ // bootstrap, then this is probably a meta-operation that
+ // would have been added by the module if src_root was
+ // correct.
+ //
+ if (guessing && !bootstrapped)
+ dr << info << "consider explicitly specifying src_base "
+ << "for " << tn;
+ }
+ }
+
// The default meta-operation is perform. The default
// operation is assigned by the meta-operation below.
//
@@ -795,7 +801,10 @@ main (int argc, char* argv[])
<< ", id " << static_cast<uint16_t> (mid);});
if (mif->meta_operation_pre != nullptr)
- mif->meta_operation_pre ();
+ mif->meta_operation_pre (mparams, l);
+ else if (!mparams.empty ())
+ fail (l) << "unexpected parameters for meta-operation "
+ << mif->name;
set_current_mif (*mif);
dirty = true;
@@ -844,7 +853,7 @@ main (int argc, char* argv[])
// Allow the meta-operation to translate the operation.
//
if (mif->operation_pre != nullptr)
- oid = mif->operation_pre (o);
+ oid = mif->operation_pre (mparams, o);
else // Otherwise translate default to update.
oid = (o == default_id ? update_id : o);
@@ -857,16 +866,25 @@ main (int argc, char* argv[])
// Handle pre/post operations.
//
- if (oif->pre != nullptr && (pre_oid = oif->pre (mid)) != 0)
+ if (oif->pre != nullptr)
{
- assert (pre_oid != default_id);
- pre_oif = lookup (pre_oid);
+ if ((pre_oid = oif->pre (oparams, mid, l)) != 0)
+ {
+ assert (pre_oid != default_id);
+ pre_oif = lookup (pre_oid);
+ }
}
+ else if (!oparams.empty ())
+ fail (l) << "unexpected parameters for operation "
+ << oif->name;
- if (oif->post != nullptr && (post_oid = oif->post (mid)) != 0)
+ if (oif->post != nullptr)
{
- assert (post_oid != default_id);
- post_oif = lookup (post_oid);
+ if ((post_oid = oif->post (oparams, mid)) != 0)
+ {
+ assert (post_oid != default_id);
+ post_oif = lookup (post_oid);
+ }
}
}
//
@@ -1021,7 +1039,7 @@ main (int argc, char* argv[])
// Load the buildfile.
//
- mif->load (rs, ts.buildfile, ts.out_base, ts.src_base, l);
+ mif->load (mparams, rs, ts.buildfile, ts.out_base, ts.src_base, l);
// Next search and match the targets. We don't want to start
// building before we know how to for all the targets in this
@@ -1055,7 +1073,8 @@ main (int argc, char* argv[])
? out_src (d, rs)
: dir_path ());
- mif->search (rs, bs,
+ mif->search (mparams,
+ rs, bs,
target_key {ti, &d, &out, &tn.value, e},
l,
tgs);
@@ -1070,17 +1089,19 @@ main (int argc, char* argv[])
<< ", id " << static_cast<uint16_t> (pre_oid);});
if (mif->operation_pre != nullptr)
- mif->operation_pre (pre_oid); // Cannot be translated.
+ mif->operation_pre (mparams, pre_oid); // Cannot be translated.
set_current_oif (*pre_oif, oif);
action a (mid, pre_oid, oid);
- mif->match (a, tgs);
- mif->execute (a, tgs, true); // Run quiet.
+ // Run quiet.
+ //
+ if (mif->match != nullptr) mif->match (mparams, a, tgs);
+ if (mif->execute != nullptr) mif->execute (mparams, a, tgs, true);
if (mif->operation_post != nullptr)
- mif->operation_post (pre_oid);
+ mif->operation_post (mparams, pre_oid);
l5 ([&]{trace << "end pre-operation batch " << pre_oif->name
<< ", id " << static_cast<uint16_t> (pre_oid);});
@@ -1090,8 +1111,8 @@ main (int argc, char* argv[])
action a (mid, oid, 0);
- if (mif->match != nullptr) mif->match (a, tgs);
- if (mif->execute != nullptr) mif->execute (a, tgs, verb == 0);
+ if (mif->match != nullptr) mif->match (mparams, a, tgs);
+ if (mif->execute != nullptr) mif->execute (mparams, a, tgs, verb == 0);
if (post_oid != 0)
{
@@ -1099,24 +1120,26 @@ main (int argc, char* argv[])
<< ", id " << static_cast<uint16_t> (post_oid);});
if (mif->operation_pre != nullptr)
- mif->operation_pre (post_oid); // Cannot be translated.
+ mif->operation_pre (mparams, post_oid); // Cannot be translated.
set_current_oif (*post_oif, oif);
action a (mid, post_oid, oid);
- mif->match (a, tgs);
- mif->execute (a, tgs, true); // Run quiet.
+ // Run quiet.
+ //
+ if (mif->match != nullptr) mif->match (mparams, a, tgs);
+ if (mif->execute != nullptr) mif->execute (mparams, a, tgs, true);
if (mif->operation_post != nullptr)
- mif->operation_post (post_oid);
+ mif->operation_post (mparams, post_oid);
l5 ([&]{trace << "end post-operation batch " << post_oif->name
<< ", id " << static_cast<uint16_t> (post_oid);});
}
if (mif->operation_post != nullptr)
- mif->operation_post (oid);
+ mif->operation_post (mparams, oid);
l5 ([&]{trace << "end operation batch " << oif->name
<< ", id " << static_cast<uint16_t> (oid);});
@@ -1125,7 +1148,7 @@ main (int argc, char* argv[])
if (mid != 0)
{
if (mif->meta_operation_post != nullptr)
- mif->meta_operation_post ();
+ mif->meta_operation_post (mparams);
l5 ([&]{trace << "end meta-operation batch " << mif->name
<< ", id " << static_cast<uint16_t> (mid);});
diff --git a/build2/config/operation.cxx b/build2/config/operation.cxx
index a029b60..a8874fd 100644
--- a/build2/config/operation.cxx
+++ b/build2/config/operation.cxx
@@ -26,15 +26,6 @@ namespace build2
{
// configure
//
- static operation_id
- configure_operation_pre (operation_id o)
- {
- // Don't translate default to update. In our case unspecified
- // means configure everything.
- //
- return o;
- }
-
static void
save_src_root (const dir_path& out_root, const dir_path& src_root)
{
@@ -346,14 +337,23 @@ namespace build2
}
}
+ static operation_id
+ configure_operation_pre (const values&, operation_id o)
+ {
+ // Don't translate default to update. In our case unspecified
+ // means configure everything.
+ //
+ return o;
+ }
+
static void
- configure_match (action, action_targets&)
+ configure_match (const values&, action, action_targets&)
{
// Don't match anything -- see execute ().
}
static void
- configure_execute (action a, action_targets& ts, bool)
+ configure_execute (const values&, action a, action_targets& ts, bool)
{
// Match rules to configure every operation supported by each
// project. Note that we are not calling operation_pre/post()
@@ -413,14 +413,6 @@ namespace build2
// disfigure
//
- static operation_id
- disfigure_operation_pre (operation_id o)
- {
- // Don't translate default to update. In our case unspecified
- // means disfigure everything.
- //
- return o;
- }
static void
load_project (scope& root)
@@ -460,8 +452,18 @@ namespace build2
}
}
+ static operation_id
+ disfigure_operation_pre (const values&, operation_id o)
+ {
+ // Don't translate default to update. In our case unspecified
+ // means disfigure everything.
+ //
+ return o;
+ }
+
static void
- disfigure_load (scope& root,
+ disfigure_load (const values&,
+ scope& root,
const path& bf,
const dir_path&,
const dir_path&,
@@ -478,7 +480,8 @@ namespace build2
}
static void
- disfigure_search (const scope& root,
+ disfigure_search (const values&,
+ const scope& root,
const scope&,
const target_key&,
const location&,
@@ -490,7 +493,7 @@ namespace build2
}
static void
- disfigure_match (action, action_targets&)
+ disfigure_match (const values&, action, action_targets&)
{
}
@@ -588,7 +591,7 @@ namespace build2
}
static void
- disfigure_execute (action a, action_targets& ts, bool quiet)
+ disfigure_execute (const values&, action a, action_targets& ts, bool quiet)
{
tracer trace ("disfigure_execute");
diff --git a/build2/dist/operation.cxx b/build2/dist/operation.cxx
index 5c08c50..accf2d5 100644
--- a/build2/dist/operation.cxx
+++ b/build2/dist/operation.cxx
@@ -39,22 +39,22 @@ namespace build2
const string& ext);
static operation_id
- dist_operation_pre (operation_id o)
+ dist_operation_pre (const values&, operation_id o)
{
if (o != default_id)
- fail << "explicit operation specified for dist meta-operation";
+ fail << "explicit operation specified for meta-operation dist";
return o;
}
static void
- dist_match (action, action_targets&)
+ dist_match (const values&, action, action_targets&)
{
// Don't match anything -- see execute ().
}
static void
- dist_execute (action, action_targets& ts, bool)
+ dist_execute (const values&, action, action_targets& ts, bool)
{
tracer trace ("dist_execute");
@@ -115,6 +115,11 @@ namespace build2
// Note that we are not calling operation_pre/post() callbacks here
// since the meta operation is dist and we know what we are doing.
//
+
+ values params;
+ const path locf ("<dist>");
+ const location loc (&locf); // Dummy location.
+
for (operations::size_type id (default_id + 1);
id < rs->operations.size ();
++id)
@@ -125,19 +130,21 @@ namespace build2
//
if (oif->pre != nullptr)
{
- const operation_info* poif (rs->operations[oif->pre (dist_id)]);
+ const operation_info* poif (
+ rs->operations[oif->pre (params, dist_id, loc)]);
set_current_oif (*poif, oif);
- match (action (dist_id, poif->id, oif->id), ts);
+ match (params, action (dist_id, poif->id, oif->id), ts);
}
set_current_oif (*oif);
- match (action (dist_id, oif->id), ts);
+ match (params, action (dist_id, oif->id), ts);
if (oif->post != nullptr)
{
- const operation_info* poif (rs->operations[oif->post (dist_id)]);
+ const operation_info* poif (
+ rs->operations[oif->post (params, dist_id)]);
set_current_oif (*poif, oif);
- match (action (dist_id, poif->id, oif->id), ts);
+ match (params, action (dist_id, poif->id, oif->id), ts);
}
}
}
@@ -239,7 +246,7 @@ namespace build2
//
{
if (perform.meta_operation_pre != nullptr)
- perform.meta_operation_pre ();
+ perform.meta_operation_pre (params, loc);
// This is a hack since according to the rules we need to completely
// reset the state. We could have done that (i.e., saved target names
@@ -252,20 +259,20 @@ namespace build2
current_on = on + 1;
if (perform.operation_pre != nullptr)
- perform.operation_pre (update_id);
+ perform.operation_pre (params, update_id);
set_current_oif (update);
action a (perform_id, update_id);
- perform.match (a, files);
- perform.execute (a, files, true); // Run quiet.
+ perform.match (params, a, files);
+ perform.execute (params, a, files, true); // Run quiet.
if (perform.operation_post != nullptr)
- perform.operation_post (update_id);
+ perform.operation_post (params, update_id);
if (perform.meta_operation_post != nullptr)
- perform.meta_operation_post ();
+ perform.meta_operation_post (params);
}
dir_path td (dist_root / dir_path (dist_package));
diff --git a/build2/install/operation.cxx b/build2/install/operation.cxx
index 9873ac2..44d257f 100644
--- a/build2/install/operation.cxx
+++ b/build2/install/operation.cxx
@@ -12,8 +12,11 @@ namespace build2
namespace install
{
static operation_id
- install_pre (meta_operation_id mo)
+ install_pre (const values& params, meta_operation_id mo, const location& l)
{
+ if (!params.empty ())
+ fail (l) << "unexpected parameters for operation install";
+
// Run update as a pre-operation, unless we are disfiguring.
//
return mo != disfigure_id ? update_id : 0;
diff --git a/build2/lexer b/build2/lexer
index 8875892..aac1c40 100644
--- a/build2/lexer
+++ b/build2/lexer
@@ -56,6 +56,7 @@ namespace build2
eval,
single_quoted,
double_quoted,
+ buildspec,
value_next
};
@@ -72,11 +73,8 @@ namespace build2
// this string are considered "effective escapes" with all others passed
// through as is. Note that the escape string is not copied.
//
- lexer (istream& is,
- const path& name,
- const char* escapes = nullptr,
- void (*processor) (token&, const lexer&) = nullptr)
- : lexer (is, name, escapes, processor, true) {}
+ lexer (istream& is, const path& name, const char* escapes = nullptr)
+ : lexer (is, name, escapes, true) {}
const path&
name () const {return name_;}
@@ -104,7 +102,10 @@ namespace build2
// Scanner. Note that it is ok to call next() again after getting eos.
//
- token
+ // If you extend the lexer and add a custom lexer mode, then you must
+ // override next() and handle the custom mode there.
+ //
+ virtual token
next ();
// Peek at the first character of the next token. Return the character
@@ -135,12 +136,6 @@ namespace build2
const char* sep_second;
};
- // If you extend the lexer and add a custom lexer mode, then you must
- // override next_impl() and handle the custom mode there.
- //
- virtual token
- next_impl ();
-
token
next_eval ();
@@ -168,24 +163,14 @@ namespace build2
// Lexer state.
//
protected:
- lexer (istream& is,
- const path& n,
- const char* e,
- void (*p) (token&, const lexer&),
- bool sm)
- : char_scanner (is),
- fail ("error", &name_),
- name_ (n),
- processor_ (p),
- sep_ (false)
+ lexer (istream& is, const path& n, const char* e, bool sm)
+ : char_scanner (is), fail ("error", &name_), name_ (n), sep_ (false)
{
if (sm)
mode (lexer_mode::normal, '@', e);
}
const path name_;
- void (*processor_) (token&, const lexer&);
-
std::stack<state> state_;
bool sep_; // True if we skipped spaces in peek().
diff --git a/build2/lexer.cxx b/build2/lexer.cxx
index ab8d96a..5989548 100644
--- a/build2/lexer.cxx
+++ b/build2/lexer.cxx
@@ -12,15 +12,6 @@ namespace build2
{
using type = token_type;
- token lexer::
- next ()
- {
- token t (next_impl ());
- if (processor_ != nullptr)
- processor_ (t, *this);
- return t;
- }
-
pair<char, bool> lexer::
peek_char ()
{
@@ -69,6 +60,20 @@ namespace build2
s2 = " = &| ";
break;
}
+ case lexer_mode::buildspec:
+ {
+ // Like the value mode with these differences:
+ //
+ // 1. Returns '(' as a separated token provided the state stack depth
+ // is less than or equal to 3 (initial state plus two buildspec)
+ // (see parse_buildspec() for details).
+ //
+ // 2. Recognizes comma.
+ //
+ s1 = " $(){}[],\t\n";
+ s2 = " ";
+ break;
+ }
case lexer_mode::single_quoted:
case lexer_mode::double_quoted:
s = false;
@@ -86,7 +91,7 @@ namespace build2
}
token lexer::
- next_impl ()
+ next ()
{
const state& st (state_.top ());
lexer_mode m (st.mode);
@@ -98,7 +103,8 @@ namespace build2
case lexer_mode::normal:
case lexer_mode::value:
case lexer_mode::attribute:
- case lexer_mode::variable: break;
+ case lexer_mode::variable:
+ case lexer_mode::buildspec: break;
case lexer_mode::eval: return next_eval ();
case lexer_mode::double_quoted: return next_quoted ();
default: assert (false); // Unhandled custom mode.
@@ -150,14 +156,21 @@ namespace build2
return make_token (type::rsbrace);
}
case '$': return make_token (type::dollar);
- case '(': return make_token (type::lparen);
case ')': return make_token (type::rparen);
+ case '(':
+ {
+ // Left paren is always separated in the buildspec mode.
+ //
+ if (m == lexer_mode::buildspec && state_.size () <= 3)
+ sep = true;
+
+ return make_token (type::lparen);
+ }
}
- // The following characters are not treated as special in the value and
- // attribute modes.
+ // The following characters are special in the normal and variable modes.
//
- if (m != lexer_mode::value && m != lexer_mode::attribute)
+ if (m == lexer_mode::normal || m == lexer_mode::variable)
{
switch (c)
{
@@ -186,6 +199,16 @@ namespace build2
}
}
+ // The following characters are special in the buildspec mode.
+ //
+ if (m == lexer_mode::buildspec)
+ {
+ switch (c)
+ {
+ case ',': return make_token (type::comma);
+ }
+ }
+
// Otherwise it is a word.
//
unget (c);
diff --git a/build2/operation b/build2/operation
index 39bb799..ca51ee5 100644
--- a/build2/operation
+++ b/build2/operation
@@ -10,6 +10,8 @@
#include <build2/types>
#include <build2/utility>
+#include <build2/variable>
+
namespace build2
{
class location;
@@ -188,35 +190,48 @@ namespace build2
const string name_did; // E.g., 'configured'.
const string name_done; // E.g., 'is configured'.
+ // The first argument in all the callback is the meta-operation
+ // parameters.
+ //
+ // If the meta-operation expects parameters, then it should have a
+ // non-NULL meta_operation_pre(). Failed that, any parameters will be
+ // diagnosed as unexpected.
+
+ // Start of meta-operation and operation batches.
+ //
// If operation_pre() is not NULL, then it may translate default_id
// (and only default_id) to some other operation. If not translated,
// then default_id is used. If, however, operation_pre() is NULL,
// then default_id is translated to update_id.
//
- void (*meta_operation_pre) (); // Start of meta-operation batch.
- operation_id (*operation_pre) (operation_id); // Start of operation batch.
+ void (*meta_operation_pre) (const values&, const location&);
+ operation_id (*operation_pre) (const values&, operation_id);
// Meta-operation-specific logic to load the buildfile, search and match
// the targets, and execute the action on the targets.
//
- void (*load) (scope& root,
+ void (*load) (const values&,
+ scope& root,
const path& buildfile,
const dir_path& out_base,
const dir_path& src_base,
const location&);
- void (*search) (const scope& root,
+ void (*search) (const values&,
+ const scope& root,
const scope& base,
const target_key&,
const location&,
action_targets&);
- void (*match) (action, action_targets&);
+ void (*match) (const values&, action, action_targets&);
- void (*execute) (action, action_targets&, bool quiet);
+ void (*execute) (const values&, action, action_targets&, bool quiet);
- void (*operation_post) (operation_id); // End of operation batch.
- void (*meta_operation_post) (); // End of meta-operation batch.
+ // Start of operation and meta-operation batches.
+ //
+ void (*operation_post) (const values&, operation_id);
+ void (*meta_operation_post) (const values&);
};
// Built-in meta-operations.
@@ -231,7 +246,8 @@ namespace build2
// scope.
//
void
- load (scope&,
+ load (const values&,
+ scope&,
const path&,
const dir_path&,
const dir_path&,
@@ -241,21 +257,22 @@ namespace build2
// that does just that and adds a pointer to the target to the list.
//
void
- search (const scope&,
+ search (const values&,
+ const scope&,
const scope&,
const target_key&,
const location&,
action_targets&);
void
- match (action, action_targets&);
+ match (const values&, action, action_targets&);
// Execute the action on the list of targets. This is the default
// implementation that does just that while issuing appropriate
// diagnostics (unless quiet).
//
void
- execute (action, const action_targets&, bool quiet);
+ execute (const values&, action, const action_targets&, bool quiet);
extern const meta_operation_info noop;
extern const meta_operation_info perform;
@@ -285,12 +302,18 @@ namespace build2
//
const size_t concurrency;
+ // The first argument in all the callback is the operation parameters.
+ //
+ // If the meta-operation expects parameters, then it should have a
+ // non-NULL pre(). Failed that, any parameters will be diagnosed as
+ // unexpected.
+
// If the returned operation_id's are not 0, then they are injected
// as pre/post operations for this operation. Can be NULL if unused.
// The returned operation_id shall not be default_id.
//
- operation_id (*pre) (meta_operation_id);
- operation_id (*post) (meta_operation_id);
+ operation_id (*pre) (const values&, meta_operation_id, const location&);
+ operation_id (*post) (const values&, meta_operation_id);
};
// Built-in operations.
diff --git a/build2/operation.cxx b/build2/operation.cxx
index b821b1d..9656e2a 100644
--- a/build2/operation.cxx
+++ b/build2/operation.cxx
@@ -44,7 +44,8 @@ namespace build2
// perform
//
void
- load (scope& root,
+ load (const values&,
+ scope& root,
const path& bf,
const dir_path& out_base,
const dir_path& src_base,
@@ -67,7 +68,8 @@ namespace build2
}
void
- search (const scope&,
+ search (const values&,
+ const scope&,
const scope& bs,
const target_key& tk,
const location& l,
@@ -89,7 +91,7 @@ namespace build2
}
void
- match (action a, action_targets& ts)
+ match (const values&, action a, action_targets& ts)
{
tracer trace ("match");
@@ -191,7 +193,7 @@ namespace build2
}
void
- execute (action a, action_targets& ts, bool quiet)
+ execute (const values&, action a, action_targets& ts, bool quiet)
{
tracer trace ("execute");
diff --git a/build2/parser b/build2/parser
index 0855e89..7ca9366 100644
--- a/build2/parser
+++ b/build2/parser
@@ -339,7 +339,7 @@ namespace build2
// Buildspec.
//
buildspec
- parse_buildspec_clause (token&, token_type&, token_type end);
+ parse_buildspec_clause (token&, token_type&, size_t);
// Customization hooks.
//
diff --git a/build2/parser.cxx b/build2/parser.cxx
index b7f1930..27e1933 100644
--- a/build2/parser.cxx
+++ b/build2/parser.cxx
@@ -3712,27 +3712,27 @@ namespace build2
// parse_names() to parse names, get variable expansion/function calls,
// quoting, etc. We just need to disable the eval context. The way this is
// done has two parts: Firstly, we parse names in chunks and detect and
- // handle the opening paren. In other words, a buildspec like 'clean (./)'
- // is "chunked" as 'clean', '(', etc. While this is fairly straightforward,
- // there is one snag: concatenating eval contexts, as in
- // 'clean(./)'. Normally, this will be treated as a single chunk and we
- // don't want that. So here comes the trick (or hack, if you like): we will
- // make every opening paren token "separated" (i.e., as if it was proceeded
- // by a space). This will disable concatenating eval. In fact, we will even
- // go a step further and only do this if we are in the original value
- // mode. This will allow us to still use eval contexts in buildspec,
- // provided that we quote it: '"cle(an)"'. Note also that function calls
- // still work as usual: '$filter (clean test)'. To disable a function call
- // and make it instead a var that is expanded into operation name(s), we can
- // use quoting: '"$ops"(./)'.
+ // handle the opening paren ourselves. In other words, a buildspec like
+ // 'clean (./)' is "chunked" as 'clean', '(', etc. While this is fairly
+ // straightforward, there is one snag: concatenating eval contexts, as in
+ // 'clean(./)'. Normally, this will be treated as a single chunk and we
+ // don't want that. So here comes the trick (or hack, if you like): the
+ // buildspec lexer mode makes every opening paren token "separated" (i.e.,
+ // as if it was preceeded by a space). This will disable concatenating
+ // eval.
+ //
+ // In fact, because this is only done in the buildspec mode, we can still
+ // use eval contexts provided that we quote them: '"cle(an)"'. Note that
+ // function calls also need quoting (since a separated '(' is not treated as
+ // function call): '"$identity(update)"'.
+ //
+ // This poses a problem, though: if it's quoted then it is a concatenated
+ // expansion and therefore cannot contain multiple values, for example,
+ // $identity(foo/ bar/). So what we do is disable this chunking/separation
+ // after both meta-operation and operation were specified. So if we specify
+ // both explicitly, then we can use eval context, function calls, etc.,
+ // normally: perform(update($identity(foo/ bar/))).
//
- static void
- paren_processor (token& t, const lexer& l)
- {
- if (t.type == type::lparen && l.mode () == lexer_mode::value)
- t.separated = true;
- }
-
buildspec parser::
parse_buildspec (istream& is, const path& name)
{
@@ -3741,22 +3741,29 @@ namespace build2
// We do "effective escaping" and only for ['"\$(] (basically what's
// necessary inside a double-quoted literal plus the single quote).
//
- lexer l (is, *path_, "\'\"\\$(", &paren_processor);
+ lexer l (is, *path_, "\'\"\\$(");
lexer_ = &l;
target_ = nullptr;
scope_ = root_ = scope::global_;
pbase_ = &work; // Use current working directory.
- // Turn on the value mode/pairs recognition with '@' as the pair separator
- // (e.g., src_root/@out_root/exe{foo bar}).
+ // Turn on the buildspec mode/pairs recognition with '@' as the pair
+ // separator (e.g., src_root/@out_root/exe{foo bar}).
//
- mode (lexer_mode::value, '@');
+ mode (lexer_mode::buildspec, '@');
token t;
type tt;
next (t, tt);
- return parse_buildspec_clause (t, tt, type::eos);
+ buildspec r (tt != type::eos
+ ? parse_buildspec_clause (t, tt, 0)
+ : buildspec ());
+
+ if (tt != type::eos)
+ fail (t) << "operation or target expected instead of " << t;
+
+ return r;
}
static bool
@@ -3780,11 +3787,11 @@ namespace build2
}
buildspec parser::
- parse_buildspec_clause (token& t, type& tt, type tt_end)
+ parse_buildspec_clause (token& t, type& tt, size_t depth)
{
buildspec bs;
- while (tt != tt_end)
+ for (bool first (true);; first = false)
{
// We always start with one or more names. Eval context (lparen) only
// allowed if quoted.
@@ -3794,23 +3801,28 @@ namespace build2
tt != type::dollar && // Variable expansion: '$foo ...'
!(tt == type::lparen && mode () == lexer_mode::double_quoted) &&
tt != type::pair_separator) // Empty pair LHS: '@foo ...'
- fail (t) << "operation or target expected instead of " << t;
+ {
+ if (first)
+ fail (t) << "operation or target expected instead of " << t;
+
+ break;
+ }
const location l (get_location (t)); // Start of names.
// This call will parse the next chunk of output and produce zero or
// more names.
//
- names ns (parse_names (t, tt, pattern_mode::expand, true));
+ names ns (parse_names (t, tt, pattern_mode::expand, depth < 2));
if (ns.empty ()) // Can happen if pattern expansion.
fail (l) << "operation or target expected";
- // What these names mean depends on what's next. If it is an
- // opening paren, then they are operation/meta-operation names.
- // Otherwise they are targets.
+ // What these names mean depends on what's next. If it is an opening
+ // paren, then they are operation/meta-operation names. Otherwise they
+ // are targets.
//
- if (tt == type::lparen) // Peeked into by parse_names().
+ if (tt == type::lparen) // Got by parse_names().
{
if (ns.empty ())
fail (t) << "operation name expected before '('";
@@ -3819,15 +3831,40 @@ namespace build2
if (!opname (n))
fail (l) << "operation name expected instead of '" << n << "'";
- // Inside '(' and ')' we have another, nested, buildspec.
+ // Inside '(' and ')' we have another, nested, buildspec. Push another
+ // mode to keep track of the depth (used in the lexer implementation
+ // to decide when to stop separating '(').
//
- next (t, tt);
+ mode (lexer_mode::buildspec, '@');
+
+ next (t, tt); // Get what's after '('.
const location l (get_location (t)); // Start of nested names.
- buildspec nbs (parse_buildspec_clause (t, tt, type::rparen));
+ buildspec nbs (parse_buildspec_clause (t, tt, depth + 1));
+
+ // Parse additional operation/meta-operation parameters.
+ //
+ values params;
+ while (tt == type::comma)
+ {
+ next (t, tt);
+
+ // Note that for now we don't expand patterns. If it turns out we
+ // need this, then will probably have to be (meta-) operation-
+ // specific (via pre-parse or some such).
+ //
+ params.push_back (tt != type::rparen
+ ? parse_value (t, tt, pattern_mode::ignore)
+ : value (names ()));
+ }
- // Merge the nested buildspec into ours. But first determine
- // if we are an operation or meta-operation and do some sanity
- // checks.
+ if (tt != type::rparen)
+ fail (t) << "')' expected instead of " << t;
+
+ expire_mode ();
+ next (t, tt); // Get what's after ')'.
+
+ // Merge the nested buildspec into ours. But first determine if we are
+ // an operation or meta-operation and do some sanity checks.
//
bool meta (false);
for (const metaopspec& nms: nbs)
@@ -3839,8 +3876,8 @@ namespace build2
if (!meta)
{
- // If we have any operations in the nested spec, then this
- // mean that our names are meta-operation names.
+ // If we have any operations in the nested spec, then this mean
+ // that our names are meta-operation names.
//
for (const opspec& nos: nms)
{
@@ -3865,12 +3902,13 @@ namespace build2
{
bs.push_back (nmo);
bs.back ().name = move (n.value);
+ bs.back ().params = params;
}
}
else
{
- // Since we are not a meta-operation, the nested buildspec
- // should be just a bunch of targets.
+ // Since we are not a meta-operation, the nested buildspec should be
+ // just a bunch of targets.
//
assert (nmo.size () == 1);
const opspec& nos (nmo.back ());
@@ -3882,10 +3920,9 @@ namespace build2
{
bs.back ().push_back (nos);
bs.back ().back ().name = move (n.value);
+ bs.back ().back ().params = params;
}
}
-
- next (t, tt); // Done with '('.
}
else if (!ns.empty ())
{
diff --git a/build2/spec b/build2/spec
index 92b56ec..3ec4689 100644
--- a/build2/spec
+++ b/build2/spec
@@ -8,6 +8,8 @@
#include <build2/types>
#include <build2/utility>
+#include <build2/variable>
+
namespace build2
{
class scope;
@@ -37,6 +39,7 @@ namespace build2
opspec (string n): name (move (n)) {}
string name;
+ values params;
};
struct metaopspec: vector<opspec>
@@ -45,6 +48,7 @@ namespace build2
metaopspec (string n): name (move (n)) {}
string name;
+ values params;
};
typedef vector<metaopspec> buildspec;
diff --git a/build2/spec.cxx b/build2/spec.cxx
index c10d966..c6f386c 100644
--- a/build2/spec.cxx
+++ b/build2/spec.cxx
@@ -40,7 +40,6 @@ namespace build2
bool hn (!s.name.empty ());
bool ht (!s.empty ());
- //os << s.name;
os << (hn ? "\"" : "") << s.name << (hn ? "\"" : "");
if (hn && ht)
@@ -49,6 +48,19 @@ namespace build2
for (auto b (s.begin ()), i (b); i != s.end (); ++i)
os << (i != b ? " " : "") << *i;
+ for (const value& v: s.params)
+ {
+ os << ", ";
+
+ if (v)
+ {
+ names storage;
+ os << reverse (v, storage);
+ }
+ else
+ os << "[null]";
+ }
+
if (hn && ht)
os << ')';
@@ -61,7 +73,6 @@ namespace build2
bool hn (!s.name.empty ());
bool ho (!s.empty ());
- //os << s.name;
os << (hn ? "\'" : "") << s.name << (hn ? "\'" : "");
if (hn && ho)
@@ -70,6 +81,19 @@ namespace build2
for (auto b (s.begin ()), i (b); i != s.end (); ++i)
os << (i != b ? " " : "") << *i;
+ for (const value& v: s.params)
+ {
+ os << ", ";
+
+ if (v)
+ {
+ names storage;
+ os << reverse (v, storage);
+ }
+ else
+ os << "[null]";
+ }
+
if (hn && ho)
os << ')';
diff --git a/build2/test/operation.cxx b/build2/test/operation.cxx
index 87e3083..1f59fc1 100644
--- a/build2/test/operation.cxx
+++ b/build2/test/operation.cxx
@@ -12,8 +12,11 @@ namespace build2
namespace test
{
static operation_id
- test_pre (meta_operation_id mo)
+ test_pre (const values& params, meta_operation_id mo, const location& l)
{
+ if (!params.empty ())
+ fail (l) << "unexpected parameters for operation test";
+
// Run update as a pre-operation, unless we are disfiguring.
//
return mo != disfigure_id ? update_id : 0;
diff --git a/build2/test/script/lexer b/build2/test/script/lexer
index 207cfef..4851e13 100644
--- a/build2/test/script/lexer
+++ b/build2/test/script/lexer
@@ -49,7 +49,7 @@ namespace build2
const path& name,
lexer_mode m,
const char* escapes = nullptr)
- : base_lexer (is, name, nullptr, nullptr, false)
+ : base_lexer (is, name, nullptr, false)
{
mode (m, '\0', escapes);
}
@@ -67,10 +67,10 @@ namespace build2
void
reset_quoted (size_t q) {quoted_ = q;}
- protected:
virtual token
- next_impl () override;
+ next () override;
+ protected:
token
next_line ();
diff --git a/build2/test/script/lexer.cxx b/build2/test/script/lexer.cxx
index e060869..c7f9193 100644
--- a/build2/test/script/lexer.cxx
+++ b/build2/test/script/lexer.cxx
@@ -139,7 +139,7 @@ namespace build2
}
token lexer::
- next_impl ()
+ next ()
{
token r;
@@ -158,7 +158,7 @@ namespace build2
r = next_description ();
break;
default:
- r = base_lexer::next_impl ();
+ r = base_lexer::next ();
break;
}
diff --git a/unit-tests/lexer/buildfile b/unit-tests/lexer/buildfile
index 520d17c..47c9e45 100644
--- a/unit-tests/lexer/buildfile
+++ b/unit-tests/lexer/buildfile
@@ -12,6 +12,6 @@ functions-target-triplet algorithm search dump filesystem scheduler \
config/{utility init operation}
exe{driver}: cxx{driver} ../../build2/cxx{$src} ../../build2/liba{b} $libs \
-test{comment eval quoting}
+test{*}
include ../../build2/
diff --git a/unit-tests/lexer/buildspec.test b/unit-tests/lexer/buildspec.test
new file mode 100644
index 0000000..81eeb00
--- /dev/null
+++ b/unit-tests/lexer/buildspec.test
@@ -0,0 +1,16 @@
+# file : unit-tests/lexer/buildspec.test
+# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+test.arguments = buildspec
+
+: punctuation
+:
+$* <:'x,x(x)' >>EOO
+'x'
+,
+'x'
+ (
+'x'
+)
+EOO
diff --git a/unit-tests/lexer/driver.cxx b/unit-tests/lexer/driver.cxx
index e9ccd63..41366ec 100644
--- a/unit-tests/lexer/driver.cxx
+++ b/unit-tests/lexer/driver.cxx
@@ -36,6 +36,7 @@ namespace build2
else if (a == "value") m = lexer_mode::value;
else if (a == "attribute") m = lexer_mode::attribute;
else if (a == "eval") m = lexer_mode::eval;
+ else if (a == "buildspec") m = lexer_mode::buildspec;
else assert (false);
break;
}
@@ -56,6 +57,9 @@ namespace build2
//
for (token t (l.next ()); t.type != token_type::eos; t = l.next ())
{
+ if (t.separated && t.type != token_type::newline)
+ cout << ' ';
+
// Print each token on a separate line without quoting operators.
//
t.printer (cout, t, false);