From ae20570f2ad55b2fa8e71cf450457cb9c4b21b1b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 18 Apr 2016 10:17:37 +0200 Subject: Add support for using value attributes in eval context For example: if ($x == [null]) Or: if ([uint64] 01 == [uint64] 1) --- build2/parser.cxx | 445 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 271 insertions(+), 174 deletions(-) (limited to 'build2/parser.cxx') diff --git a/build2/parser.cxx b/build2/parser.cxx index 2c21de3..80e744e 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -167,12 +167,12 @@ namespace build2 type tt; token t (type::eos, false, 0, 0); - pair p (variable_value (t, tt)); + value rhs (variable_value (t, tt)); - value v; - value_attributes (var, v, move (p), type::assign); + value lhs; + value_attributes (&var, lhs, move (rhs), type::assign); - return make_pair (move (v), move (t)); + return make_pair (move (lhs), move (t)); } void parser:: @@ -190,7 +190,8 @@ namespace build2 { // Extract attributes if any. // - attributes (t, tt); + assert (attributes_.empty ()); + attributes& a (attributes_push (t, tt)); // We always start with one or more names. // @@ -199,7 +200,16 @@ namespace build2 tt != type::dollar && // Variable expansion: '$foo ...' tt != type::lparen && // Eval context: '(foo) ...' tt != type::colon) // Empty name: ': ...' - break; // Something else. Let our caller handle that. + { + // Something else. Let our caller handle that. + // + if (a) + fail (a.loc) << "attributes before " << t; + else + attributes_pop (); + + break; + } // See if this is one of the directives. // @@ -256,8 +266,10 @@ namespace build2 if (f != nullptr) { - if (ha_) - fail (al_) << "attributes before " << n; + if (a) + fail (a.loc) << "attributes before " << n; + else + attributes_pop (); (this->*f) (t, tt); continue; @@ -321,8 +333,10 @@ namespace build2 { // Directory scope. // - if (ha_) - fail (al_) << "attributes before directory scope"; + if (a) + fail (a.loc) << "attributes before directory scope"; + else + attributes_pop (); // Can contain anything that a top level can. // @@ -331,8 +345,10 @@ namespace build2 } else { - if (ha_) - fail (al_) << "attributes before target scope"; + if (a) + fail (a.loc) << "attributes before target scope"; + else + attributes_pop (); // @@ TODO: target scope. } @@ -359,13 +375,12 @@ namespace build2 // assignment. // - // Will have to stash them if later support attributes on - // target/scope. - // - if (ha_) - fail (al_) << "attributes before target/scope"; + if (a) + fail (a.loc) << "attributes before target/scope"; + else + attributes_pop (); - attributes (t, tt); + attributes& a (attributes_push (t, tt)); if (tt == type::name || tt == type::lcbrace || @@ -392,8 +407,7 @@ namespace build2 // Handle variable attributes. // - if (ha_) - variable_attributes (var); + variable_attributes (var); // If we have multiple targets/scopes, then we save the value // tokens when parsing the first one and then replay them for @@ -462,10 +476,10 @@ namespace build2 // Note: expanding the value in the context of the scope. // - pair p (variable_value (t, tt)); - value& v (scope_->target_vars[*ti][move (n.value)].assign ( - var).first); - value_attributes (var, v, move (p), type::assign); + value rhs (variable_value (t, tt)); + value& lhs (scope_->target_vars[*ti][move (n.value)].assign ( + var).first); + value_attributes (&var, lhs, move (rhs), type::assign); } } @@ -476,8 +490,10 @@ namespace build2 // else { - if (ha_) - fail (al_) << "attributes before prerequisites"; + if (a) + fail (a.loc) << "attributes before prerequisites"; + else + attributes_pop (); // Prepare the prerequisite list. // @@ -549,8 +565,7 @@ namespace build2 // Handle variable attributes. // - if (ha_) - variable_attributes (var); + variable_attributes (var); variable (t, tt, var, tt); @@ -566,8 +581,10 @@ namespace build2 // if (tt == type::newline && ns.empty ()) { - if (ha_) - fail (al_) << "standalone attributes"; + if (a) + fail (a.loc) << "standalone attributes"; + else + attributes_pop (); next (t, tt); continue; @@ -829,7 +846,7 @@ namespace build2 // Get variable attributes, if any (note that here we will go into a // nested pairs mode). // - attributes (t, tt); + attributes& a (attributes_push (t, tt)); if (tt == type::name) { @@ -900,15 +917,19 @@ namespace build2 { // Handle variable attributes. // - if (ha_) - variable_attributes (*var); + variable_attributes (*var); val = at == type::assign ? &scope_->assign (*var) : &scope_->append (*var); } - else if (ha_) - fail (al_) << "attributes without variable"; + else + { + if (a) + fail (a.loc) << "attributes without variable"; + else + attributes_pop (); + } // The rest should be a list of projects and/or targets. Parse // them as names to get variable expansion and directory prefixes. @@ -930,11 +951,11 @@ namespace build2 if (val != nullptr) { if (at == type::assign) - val->assign (move (r), *var); + val->assign (move (r), var); else if (at == type::prepend) - val->prepend (move (r), *var); + val->prepend (move (r), var); else - val->append (move (r), *var); + val->append (move (r), var); } } @@ -959,24 +980,26 @@ namespace build2 // The rest is a value. Parse it as a variable value to get expansion, // attributes, etc. build2::import() will check the names, if required. // - pair p (variable_value (t, tt)); + value rhs (variable_value (t, tt)); // While it may seem like supporting attributes is a good idea here, // there is actually little benefit in being able to type them or to // return NULL. // // export_value_ = value (); // Reset to untyped NULL value. - // value_attributes (var_pool.find ("__export"), // Well, this is a hack. + // value_attributes (nullptr, // export_value_, - // move (p), + // move (rhs), // type::assign); - if (ha_) - fail (al_) << "attributes in export"; + if (attributes& a = attributes_top ()) + fail (a.loc) << "attributes in export"; + else + attributes_pop (); - if (!p.second) + if (rhs.null ()) fail (t) << "null value in export"; - export_value_ = move (p.first); + export_value_ = move (rhs).as (); if (tt == type::newline) next (t, tt); @@ -1253,32 +1276,31 @@ namespace build2 { // Parse the rest as a variable value to get expansion, attributes, etc. // - pair p (variable_value (t, tt)); + value v (variable_value (t, tt)); - if (ha_) + if (attributes_top ()) { - // Round-trip it through value. + // Round-trip it through (a potentially typed) value. // - value v; - value_attributes (var_pool.find ("__print"), // Well, this is a hack. - v, - move (p), - type::assign); + value tv; + value_attributes (nullptr, tv, move (v), type::assign); - if (v.null ()) + if (tv.null ()) cout << "[null]" << endl; else { - p.first.clear (); - cout << reverse (v, p.first) << endl; + names_type storage; + cout << reverse (tv, storage) << endl; } } else { - if (!p.second) + attributes_pop (); + + if (v.null ()) cout << "[null]" << endl; else - cout << p.first << endl; + cout << v.as () << endl; } if (tt != type::eos) @@ -1305,17 +1327,17 @@ namespace build2 void parser:: variable (token& t, type& tt, const variable_type& var, type kind) { - pair p (variable_value (t, tt)); + value rhs (variable_value (t, tt)); - value& v ( + value& lhs ( kind == type::assign ? target_ != nullptr ? target_->assign (var) : scope_->assign (var) : target_ != nullptr ? target_->append (var) : scope_->append (var)); - value_attributes (var, v, move (p), kind); + value_attributes (&var, lhs, move (rhs), kind); } - pair parser:: + value parser:: variable_value (token& t, type& tt) { mode (lexer_mode::pairs, '@'); @@ -1324,11 +1346,11 @@ namespace build2 // Parse value attributes if any. Note that it's ok not to have anything // after the attributes (e.g., foo=[null]). // - attributes (t, tt, true); + attributes_push (t, tt, true); return tt != type::newline && tt != type::eos - ? names_null (t, tt) - : make_pair (names_type (), true); + ? names_value (t, tt) + : value (names_type ()); } static const value_type* @@ -1352,9 +1374,15 @@ namespace build2 void parser:: variable_attributes (const variable_type& var) { + attributes a (attributes_pop ()); + + if (!a) + return; + + const location& l (a.loc); const value_type* type (nullptr); - for (auto& p: as_) + for (auto& p: a.ats) { string& k (p.first); string& v (p.second); @@ -1362,16 +1390,16 @@ namespace build2 if (const value_type* t = map_type (k)) { if (type != nullptr && t != type) - fail (al_) << "multiple variable types: " << k << ", " << type->name; + fail (l) << "multiple variable types: " << k << ", " << type->name; type = t; // Fall through. } else - fail (al_) << "unknown variable attribute " << k; + fail (l) << "unknown variable attribute " << k; if (!v.empty ()) - fail (al_) << "unexpected value for attribute " << k << ": " << v; + fail (l) << "unexpected value for attribute " << k << ": " << v; } if (type != nullptr) @@ -1379,31 +1407,34 @@ namespace build2 if (var.type == nullptr) var.type = type; else if (var.type != type) - fail (al_) << "changing variable " << var.name << " type from " - << var.type->name << " to " << type->name; + fail (l) << "changing variable " << var.name << " type from " + << var.type->name << " to " << type->name; } } void parser:: - value_attributes (const variable_type& var, + value_attributes (const variable_type* var, value& v, - pair&& rhs, + value&& rhs, token_type kind) { + attributes a (attributes_pop ()); + const location& l (a.loc); + // Essentially this is an attribute-augmented assign/append/prepend. // bool null (false); const value_type* type (nullptr); - for (auto& p: as_) + for (auto& p: a.ats) { string& k (p.first); string& v (p.second); if (k == "null") { - if (!rhs.first.empty () || !rhs.second) - fail (al_) << "value with null attribute"; + if (!rhs.empty ()) // Note: null means we had an expansion. + fail (l) << "value with null attribute"; null = true; // Fall through. @@ -1411,16 +1442,16 @@ namespace build2 else if (const value_type* t = map_type (k)) { if (type != nullptr && t != type) - fail (al_) << "multiple value types: " << k << ", " << type->name; + fail (l) << "multiple value types: " << k << ", " << type->name; type = t; // Fall through. } else - fail (al_) << "unknown value attribute " << k; + fail (l) << "unknown value attribute " << k; if (!v.empty ()) - fail (al_) << "unexpected value for attribute " << k << ": " << v; + fail (l) << "unexpected value for attribute " << k << ": " << v; } // When do we set the type and when do we keep the original? This gets @@ -1435,9 +1466,9 @@ namespace build2 // if (type != nullptr) { - if (var.type != nullptr && var.type != type) - fail (al_) << "confliction variable " << var.name << " type " - << var.type->name << " and value type " << type->name; + if (var != nullptr && var->type != nullptr && var->type != type) + fail (l) << "confliction variable " << var->name << " type " + << var->type->name << " and value type " << type->name; if (kind == token_type::assign) { @@ -1454,8 +1485,8 @@ namespace build2 else if (v.type == nullptr) typify (v, *type, var); else if (v.type != type) - fail (al_) << "confliction original value type " << v.type->name - << " and append/prepend value type " << type->name; + fail (l) << "confliction original value type " << v.type->name + << " and append/prepend value type " << type->name; } } @@ -1465,107 +1496,152 @@ namespace build2 { if (kind == token_type::assign) { - if (rhs.second) - v.assign (move (rhs.first), var); + if (rhs) + v.assign (move (rhs).as (), var); else v = nullptr; } - else if (rhs.second) // Don't append/prepent NULL. + else if (rhs) // Don't append/prepent NULL. { if (kind == token_type::prepend) - v.prepend (move (rhs.first), var); + v.prepend (move (rhs).as (), var); else - v.append (move (rhs.first), var); + v.append (move (rhs).as (), var); } } } - pair parser:: + value parser:: eval (token& t, type& tt) { mode (lexer_mode::eval); next (t, tt); - - names_type ns; - bool n (eval_trailer (t, tt, ns)); - return make_pair (move (ns), n); + return eval_trailer (t, tt); } - bool parser:: - eval_trailer (token& t, type& tt, names_type& ns) + value parser:: + eval_trailer (token& t, type& tt) { - bool null (false); - - // Note that names() will handle cases like ( == foo) or (:foo) since if - // it gets called, it expects to see a name. + // Parse the next value (if any) handling its attributes. // - if (tt != type::rparen) + auto parse_value = [&t, &tt, this] () -> value { - if (!names (t, tt, ns) && tt == type::rparen) - null = true; - } + // Parse value attributes if any. Note that it's ok not to have anything + // after the attributes, as in, ($foo == [null]), or even ([null]) + // + attributes& a (attributes_push (t, tt, true)); - switch (tt) - { - case type::colon: + // Note that if names() gets called, it expects to see a name. + // + value r (tt != type::rparen && + tt != type::colon && + tt != type::equal && + tt != type::not_equal + ? names_value (t, tt) + : value (names_type ())); + + // Process attributes if any. + // + if (!a) { - // Later, when we support '?:', we will need to decide which case this - // is. But for now ':' is always a scope/target qualified name which - // we represent as a special ':'-style pair. - // - size_t n (ns.size ()); - ns.back ().pair = ':'; + attributes_pop (); + return r; + } - next (t, tt); - eval_trailer (t, tt, ns); + value v; + value_attributes (nullptr, v, move (r), type::assign); + return v; + }; - if (ns.size () == n) - fail (t) << "scope/target expected after ':'"; + value lhs (parse_value ()); - break; - } - case type::equal: - case type::not_equal: + // We continue evaluating from left to right until we reach ')', storing + // the result in lhs (think 'a == b == false'). + // + while (tt != type::rparen) + { + const location l (get_location (t, &path_)); + + // Remember to update parse_value above if adding any new name + // separators here. + // + switch (tt) { - type op (tt); + case type::colon: + { + // Later, when we support '?:', we will need to decide which case + // this is. But for now ':' is always a scope/target qualified name + // which we represent as a special ':'-style pair. + // + if (lhs.type != nullptr || lhs.null () || lhs.empty ()) + fail (l) << "scope/target expected before ':'"; - // ==, != are left-associative, so get the rhs name and evaluate. - // - next (t, tt); - names_type rhs (names (t, tt)); + names_type& ns (lhs.as ()); + ns.back ().pair = ':'; - bool r; - switch (op) - { - case type::equal: r = ns == rhs; break; - case type::not_equal: r = ns != rhs; break; - default: r = false; assert (false); + next (t, tt); + value rhs (eval_trailer (t, tt)); + + if (tt != type::rparen || + rhs.type != nullptr || rhs.null () || rhs.empty ()) + fail (l) << "variable name expected after ':'"; + + ns.insert (ns.end (), + make_move_iterator (rhs.as ().begin ()), + make_move_iterator (rhs.as ().end ())); + + break; } + case type::equal: + case type::not_equal: + { + type op (tt); - ns.resize (1); - ns[0] = name (r ? "true" : "false"); + // ==, != are left-associative, so get the rhs value and evaluate. + // + next (t, tt); + value rhs (parse_value ()); - eval_trailer (t, tt, ns); - break; + // Use (potentially typed) comparison via value. + // + // @@ Should we detect type mismatch here? + // + bool r; + switch (op) + { + case type::equal: r = lhs == rhs; break; + case type::not_equal: r = lhs != rhs; break; + default: r = false; assert (false); + } + + // While storing the result as a bool value might seem like a good + // idea, this would mean the user won't be able to compare it to + // (untyped) true/false names. + // + names_type ns; + ns.push_back (name (r ? "true" : "false")); + lhs = value (move (ns)); + break; + } + default: + fail (l) << "expected ')' instead of " << t; } - case type::rparen: - break; - default: - fail (t) << "expected ')' instead of " << t; } - return !null; + return lhs; } - bool parser:: - attributes (token& t, token_type& tt, bool standalone) + parser::attributes& parser:: + attributes_push (token& t, token_type& tt, bool standalone) { - as_.clear (); + attributes_.push ( + attributes {tt == type::lsbrace, get_location (t, &path_), {}}); - if (tt != type::lsbrace) - return (ha_ = false); + attributes& a (attributes_.top ()); + const location& l (a.loc); - al_ = get_location (t, &path_); + if (!a.has) + return a; // Using '@' for key-value pairs would be just too ugly. Seeing that we // control what goes into keys/values, let's use a much nicer '='. @@ -1587,13 +1663,13 @@ namespace build2 } catch (const invalid_argument&) { - fail (al_) << "invalid attribute key '" << *i << "'"; + fail (l) << "invalid attribute key '" << *i << "'"; } if (i->pair) { if (i->pair != '=') - fail << "unexpected pair style in attributes"; + fail (l) << "unexpected pair style in attributes"; try { @@ -1601,11 +1677,11 @@ namespace build2 } catch (const invalid_argument&) { - fail (al_) << "invalid attribute value '" << *i << "'"; + fail (l) << "invalid attribute value '" << *i << "'"; } } - as_.emplace_back (move (k), move (v)); + a.ats.emplace_back (move (k), move (v)); } } @@ -1623,7 +1699,7 @@ namespace build2 if (!standalone && (tt == type::newline || tt == type::eos)) fail (t) << "standalone attributes"; - return (ha_ = true); + return a; } // Parse names inside {} and handle the following "crosses" (i.e., @@ -1960,9 +2036,9 @@ namespace build2 names_type lv_storage; names_view lv; - // Check if we should set/propagate NULL. We only do this is if this - // is the only expansion, that is, it is the first and the text token - // is not part of the name. + // Check if we should set/propagate NULL. We only do this if this is + // the only expansion, that is, it is the first and the text token is + // not part of the name. // auto set_null = [first, &tt] () { @@ -1976,6 +2052,7 @@ namespace build2 location loc; const char* what; // Variable or evaluation context. + value result; // Holds function call/eval context result. if (tt == type::dollar) { @@ -1996,7 +2073,13 @@ namespace build2 else if (tt == type::lparen) { expire_mode (); - names_type ns (eval (t, tt).first); + value v (eval (t, tt)); + + if (v.null ()) + fail (loc) << "null variable/function name"; + + names_type storage; + vector_view ns (reverse (v, storage)); // Movable. size_t n (ns.size ()); // Make sure the result of evaluation is a potentially-qualified @@ -2035,26 +2118,37 @@ namespace build2 // Just a stub for now. // - // Should we use qualification as the context in which to call - // the function? + // Should we use (target/scope) qualification (of name) as the + // context in which to call the function? // - std::pair a (eval (t, tt)); - cout << name << "(" << a.first << ")" << endl; + value a (eval (t, tt)); + cout << name << "("; + + if (a.null ()) + cout << "[null]"; + else if (!a.empty ()) + cout << reverse (a, lv_storage); + + cout << ")" << endl; - std::pair r (names_type (), false); // NULL. - lv_storage.swap (r.first); + result = value (); // NULL for now. tt = peek (); // See if we should propagate the NULL indicator. // - if (set_null ()) - null = !r.second; + if (result.null ()) + { + if (set_null ()) + null = true; + + continue; + } - if (lv_storage.empty ()) + if (result.empty ()) continue; - lv = lv_storage; + lv = reverse (result, lv_storage); what = "function call"; } else @@ -2108,25 +2202,28 @@ namespace build2 else { loc = get_location (t, &path_); - - std::pair r (eval (t, tt)); - lv_storage.swap (r.first); + result = eval (t, tt); tt = peek (); // See if we should propagate the NULL indicator. // - if (set_null ()) - null = !r.second; + if (result.null ()) + { + if (set_null ()) + null = true; + + continue; + } - if (lv_storage.empty ()) + if (result.empty ()) continue; - lv = lv_storage; + lv = reverse (result, lv_storage); what = "context evaluation"; } - // @@ Could move if lv is lv_storage. + // @@ Could move if lv is lv_storage (or even result). // // Should we accumulate? If the buffer is not empty, then -- cgit v1.1