aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-04-02 16:18:43 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-04-02 16:18:43 +0200
commit0e486cd3642da8a442629ffce9a3daf16745c35e (patch)
tree37dd2b40366598a68685168759543bc8abb4de80
parent4bf42322fdd5dd7e01a3f61272bccc4a66a5585f (diff)
Implement variable typing (via attributes)
Now we can do: [string] str = foo
-rw-r--r--build2/parser21
-rw-r--r--build2/parser.cxx96
-rw-r--r--build2/variable2
-rw-r--r--tests/variable/type/buildfile21
-rw-r--r--tests/variable/type/test.out2
-rwxr-xr-xtests/variable/type/test.sh3
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 -