diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-04-02 16:18:43 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-04-02 16:18:43 +0200 |
commit | 0e486cd3642da8a442629ffce9a3daf16745c35e (patch) | |
tree | 37dd2b40366598a68685168759543bc8abb4de80 | |
parent | 4bf42322fdd5dd7e01a3f61272bccc4a66a5585f (diff) |
Implement variable typing (via attributes)
Now we can do:
[string] str = foo
-rw-r--r-- | build2/parser | 21 | ||||
-rw-r--r-- | build2/parser.cxx | 96 | ||||
-rw-r--r-- | build2/variable | 2 | ||||
-rw-r--r-- | tests/variable/type/buildfile | 21 | ||||
-rw-r--r-- | tests/variable/type/test.out | 2 | ||||
-rwxr-xr-x | tests/variable/type/test.sh | 3 |
6 files changed, 118 insertions, 27 deletions
diff --git a/build2/parser b/build2/parser index a4b5dc5..23f90cc 100644 --- a/build2/parser +++ b/build2/parser @@ -22,8 +22,9 @@ namespace build2 class parser { public: - typedef build2::names names_type; - typedef build2::variable variable_type; + using names_type = build2::names; + using variable_type = build2::variable; + using attributes_type = vector<pair<string, string>>; // If boot is true, then we are parsing bootstrap.build and modules // should only be bootstrapped. @@ -90,17 +91,23 @@ namespace build2 names_type variable_value (token&, token_type&); + void + variable_attribute (const variable_type&, + attributes_type&, + const location&); + names_type eval (token&, token_type&); void eval_trailer (token&, token_type&, names_type&); - // If the next token is [, parse the attribute sequence until ] storing - // it in attrs_, get the next token, verify it is not a newline or eos, - // and return true. Otherwise return false. + // If the next token is '[' parse the attribute sequence until ']', get + // the next token, verify it is not newline/eos, and return the pointer to + // the extracted attributes (which is only valid until the next call). + // Otherwise return NULL. // - bool + attributes_type* attributes (token&, token_type&); // If chunk is true, then parse the smallest but complete, name-wise, @@ -298,7 +305,7 @@ namespace build2 scope* scope_; // Current base scope (out_base). scope* root_; // Current root scope (out_root). - vector<pair<string, string>> attrs_; // Current attributes, if any. + attributes_type attrs_; // Current attributes, if any. target* default_target_; names_type export_value_; diff --git a/build2/parser.cxx b/build2/parser.cxx index 663ca0c..c516cef 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -98,7 +98,7 @@ namespace build2 // Extract attributes if any. // location al (get_location (t, &path_)); - bool ha (attributes (t, tt)); + attributes_type* as (attributes (t, tt)); // We always start with one or more names. // @@ -164,7 +164,7 @@ namespace build2 if (f != nullptr) { - if (ha) + if (as != nullptr) fail (al) << "attributes before " << n; (this->*f) (t, tt); @@ -260,7 +260,7 @@ namespace build2 { // Directory scope. // - if (ha) + if (as != nullptr) fail (al) << "attributes before directory scope"; // Can contain anything that a top level can. @@ -271,7 +271,7 @@ namespace build2 } else { - if (ha) + if (as != nullptr) fail (al) << "attributes before target scope"; // @@ TODO: target scope. @@ -302,11 +302,11 @@ namespace build2 // Will have to stash them if later support attributes on // target/scope. // - if (ha) + if (as != nullptr) fail (al) << "attributes before target/scope"; al = get_location (t, &path_); - ha = attributes (t, tt); + as = attributes (t, tt); if (tt == type::name || tt == type::lcbrace || @@ -359,7 +359,10 @@ namespace build2 var_pool.find ( variable_name (move (pns), ploc))); - //@@ TODO: handle attrs. + // Handle variable attributes. + // + if (as != nullptr) + variable_attribute (var, *as, al); // If we have multiple targets/scopes, then we save the value // tokens when parsing the first one and then replay them for @@ -447,7 +450,7 @@ namespace build2 // else { - if (ha) + if (as != nullptr) fail (al) << "attributes before prerequisites"; // Prepare the prerequisite list. @@ -516,9 +519,15 @@ namespace build2 // if (tt == type::assign || tt == type::prepend || tt == type::append) { - //@@ TODO handle attrs. + const variable_type& var ( + var_pool.find (variable_name (move (ns), nloc))); + + // Handle variable attributes. + // + if (as != nullptr) + variable_attribute (var, *as, al); - variable (t, tt, var_pool.find (variable_name (move (ns), nloc)), tt); + variable (t, tt, var, tt); if (tt == type::newline) next (t, tt); @@ -532,7 +541,7 @@ namespace build2 // if (tt == type::newline && ns.empty ()) { - if (ha) + if (as != nullptr) fail (al) << "standalone attributes"; next (t, tt); @@ -796,7 +805,7 @@ namespace build2 // mode). // location al (get_location (t, &path_)); - bool ha (attributes (t, tt)); + attributes_type* as (attributes (t, tt)); if (tt == type::name) { @@ -865,13 +874,16 @@ namespace build2 if (var != nullptr) { - // @@ TODO handle attrs. + // Handle variable attributes. + // + if (as != nullptr) + variable_attribute (*var, *as, al); val = at == type::assign ? &scope_->assign (*var) : &scope_->append (*var); } - else if (ha) + else if (as != nullptr) fail (al) << "attributes without variable"; // The rest should be a list of projects and/or targets. Parse @@ -1267,6 +1279,54 @@ namespace build2 : names_type ()); } + void parser:: + variable_attribute (const variable_type& var, + attributes_type& as, + const location& al) + { + const value_type* type (nullptr); + + for (auto& p: as) + { + string& k (p.first); + string& v (p.second); + + if (const value_type* t = + k == "bool" ? &value_traits<bool>::value_type : + k == "uint64" ? &value_traits<uint64_t>::value_type : + k == "string" ? &value_traits<string>::value_type : + k == "path" ? &value_traits<path>::value_type : + k == "dir_path" ? &value_traits<dir_path>::value_type : + k == "name" ? &value_traits<name>::value_type : + k == "strings" ? &value_traits<strings>::value_type : + k == "paths" ? &value_traits<paths>::value_type : + k == "dir_paths" ? &value_traits<dir_paths>::value_type : + k == "names" ? &value_traits<names_type>::value_type : + nullptr) + { + if (!v.empty ()) + fail (al) << "value in variable type " << k << ": " << v; + + if (type != nullptr) + fail (al) << "multiple variable types: " << k << ", " << type->name; + + type = t; + continue; + } + + fail (al) << "unknown variable attribute " << k; + } + + if (type != nullptr) + { + 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; + } + } + parser::names_type parser:: eval (token& t, type& tt) { @@ -1320,13 +1380,13 @@ namespace build2 } } - bool parser:: + parser::attributes_type* parser:: attributes (token& t, token_type& tt) { attrs_.clear (); if (tt != type::lsbrace) - return false; + return nullptr; // 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 '='. @@ -1339,8 +1399,6 @@ namespace build2 const location l (get_location (t, &path_)); names_type ns (names (t, tt)); - text << '[' << ns << ']'; - for (auto i (ns.begin ()); i != ns.end (); ++i) { string k, v; @@ -1384,7 +1442,7 @@ namespace build2 if (tt == type::newline || tt == type::eos) fail (t) << "standalone attributes"; - return true; + return &attrs_; } // Parse names inside {} and handle the following "crosses" (i.e., diff --git a/build2/variable b/build2/variable index d22d6da..48acfea 100644 --- a/build2/variable +++ b/build2/variable @@ -86,7 +86,7 @@ namespace build2 struct variable { string name; - const value_type* type; // If NULL, then not (yet) typed. + mutable const value_type* type; // If NULL, then not (yet) typed. mutable unique_ptr<variable> override; variable_visibility visibility; }; diff --git a/tests/variable/type/buildfile b/tests/variable/type/buildfile new file mode 100644 index 0000000..372b0ad --- /dev/null +++ b/tests/variable/type/buildfile @@ -0,0 +1,21 @@ +# Variable typing. +# +[string] str1 = bar +str1 =+ foo +str1 += baz +print $str1 + +str2 = bar +[string] str2 =+ foo +str2 += baz +print $str2 + +#[string] str3 = foo +#[bool] str3 = false # error: changing str3 type from string to bool + +#[bool string] str3 = foo # error: multiple variable types: bool, string + +#[junk] jnk = foo # error: unknown variable attribute junk + + +./: diff --git a/tests/variable/type/test.out b/tests/variable/type/test.out new file mode 100644 index 0000000..f5dfb84 --- /dev/null +++ b/tests/variable/type/test.out @@ -0,0 +1,2 @@ +foobarbaz +foobarbaz diff --git a/tests/variable/type/test.sh b/tests/variable/type/test.sh new file mode 100755 index 0000000..afcb3bd --- /dev/null +++ b/tests/variable/type/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +b -q | diff -u test.out - |