diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2015-12-03 11:04:48 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2015-12-03 11:08:08 +0200 |
commit | 772b1e013bb0068d7347d0bbe2ff73c67358ee1b (patch) | |
tree | 8d7bdd8999ed62db77015e60d6115eb6ff235912 | |
parent | becea217436a79b7ef37a023da6cb4c560225a71 (diff) |
Implement if-else conditions
if
if!
elif
elif!
else
The expression should evaluate to true of false. The if! and elif!
versions are provided as shortcuts to writing if (!...).
See tests/if-else for examples.
-rw-r--r-- | build/parser | 13 | ||||
-rw-r--r-- | build/parser.cxx | 192 | ||||
-rw-r--r-- | tests/if-else/buildfile | 114 | ||||
-rw-r--r-- | tests/if-else/test.out | 9 | ||||
-rwxr-xr-x | tests/if-else/test.sh | 3 |
5 files changed, 311 insertions, 20 deletions
diff --git a/build/parser b/build/parser index 4390c67..ca27890 100644 --- a/build/parser +++ b/build/parser @@ -75,6 +75,9 @@ namespace build define (token&, token_type&); void + if_else (token&, token_type&); + + void variable (token&, token_type&, std::string name, token_type kind); std::string @@ -115,6 +118,16 @@ namespace build const dir_path* dir, const std::string* type); + // Skip until newline or eos. + // + void + skip_line (token&, token_type&); + + // Skip until block-closing } or eos, taking into account nested blocks. + // + void + skip_block (token&, token_type&); + // Buildspec. // buildspec diff --git a/build/parser.cxx b/build/parser.cxx index ee22254..de4cfdc 100644 --- a/build/parser.cxx +++ b/build/parser.cxx @@ -64,7 +64,7 @@ namespace build } token parser:: - parse_variable (lexer& l, scope& s, string name, token_type kind) + parse_variable (lexer& l, scope& s, string name, type kind) { path_ = &l.name (); lexer_ = &l; @@ -78,10 +78,16 @@ namespace build } void parser:: - clause (token& t, token_type& tt) + clause (token& t, type& tt) { tracer trace ("parser::clause", &path_); + // clause() should always stop at a token that is at the beginning of + // the line (except for eof). That is, if something is called to parse + // a line, it should parse it until newline (or fail). This is important + // for if-else blocks, directory scopes, etc., that assume the } token + // they see is on the new line. + // while (tt != type::eos) { // We always start with one or more names. @@ -139,6 +145,20 @@ namespace build define (t, tt); continue; } + else if (n == "if" || + n == "if!") + { + if_else (t, tt); + continue; + } + else if (n == "else" || + n == "elif" || + n == "elif!") + { + // Valid ones are handled in if_else(). + // + fail (t) << n << " without if"; + } } // ': foo' is equvalent to '{}: foo' and to 'dir{}: foo'. @@ -466,7 +486,7 @@ namespace build } void parser:: - source (token& t, token_type& tt) + source (token& t, type& tt) { tracer trace ("parser::source", &path_); @@ -544,7 +564,7 @@ namespace build } void parser:: - include (token& t, token_type& tt) + include (token& t, type& tt) { tracer trace ("parser::include", &path_); @@ -689,7 +709,7 @@ namespace build } void parser:: - import (token& t, token_type& tt) + import (token& t, type& tt) { tracer trace ("parser::import", &path_); @@ -705,15 +725,15 @@ namespace build value* val (nullptr); const build::variable* var (nullptr); - token_type at; // Assignment type. + type at; // Assignment type. if (tt == type::name) { at = peek (); - if (at == token_type::equal || at == token_type::plus_equal) + if (at == type::equal || at == type::plus_equal) { var = &var_pool.find (t.value); - val = at == token_type::equal + val = at == type::equal ? &scope_->assign (*var) : &scope_->append (*var); next (t, tt); // Consume =/+=. @@ -738,7 +758,7 @@ namespace build if (val != nullptr) { - if (at == token_type::equal) + if (at == type::equal) val->assign (move (r), *var); else val->append (move (r), *var); @@ -752,7 +772,7 @@ namespace build } void parser:: - export_ (token& t, token_type& tt) + export_ (token& t, type& tt) { tracer trace ("parser::export", &path_); @@ -779,7 +799,7 @@ namespace build } void parser:: - using_ (token& t, token_type& tt) + using_ (token& t, type& tt) { tracer trace ("parser::using", &path_); @@ -821,7 +841,7 @@ namespace build constexpr const char derived_ext_var[] = "extension"; void parser:: - define (token& t, token_type& tt) + define (token& t, type& tt) { // define <derived>: <base> // @@ -883,7 +903,102 @@ namespace build } void parser:: - print (token& t, token_type& tt) + if_else (token& t, type& tt) + { + // Handle the whole if-else chain. See tests/if-else. + // + bool taken (false); // One of the branches has been taken. + + for (;;) + { + string k (move (t.value)); + next (t, tt); + + bool take (false); // Take this branch? + + if (k != "else") + { + // Should we evaluate the expression if one of the branches has + // already been taken? On the one hand, evaluating it is a waste + // of time. On the other, it can be invalid and the only way for + // the user to know their buildfile is valid is to test every + // branch. There could also be side effects. We also have the same + // problem with ignored branch blocks except there evaluating it + // is not an option. So let's skip it. + // + if (taken) + skip_line (t, tt); + else + { + if (tt == type::newline || tt == type::eos) + fail (t) << "expected " << k << "-expression instead of " << t; + + // Parse as names to get variable expansion, evaluation, etc. + // + const location nsl (get_location (t, &path_)); + names_type ns (names (t, tt)); + + // Should evaluate to true or false. + // + if (ns.size () != 1 || !value_traits<bool>::assign (ns[0])) + fail (nsl) << "expected " << k << "-expression to evaluate to " + << "'true' or 'false' instead of '" << ns << "'"; + + bool e (ns[0].value == "true"); + take = (k.back () == '!' ? !e : e); + } + } + else + take = !taken; + + if (tt != type::newline) + fail (t) << "expected newline instead of " << t << " after " << k + << (k != "else" ? "-expression" : ""); + + if (next (t, tt) != type::lcbrace) + fail (t) << "expected { instead of " << t << " at the beginning of " + << k << "-block"; + + if (next (t, tt) != type::newline) + fail (t) << "expected newline after {"; + + next (t, tt); + + if (take) + { + clause (t, tt); + taken = true; + } + else + skip_block (t, tt); + + if (tt != type::rcbrace) + fail (t) << "expected } instead of " << t << " at the end of " << k + << "-block"; + + next (t, tt); + + if (tt == type::newline) + next (t, tt); + else if (tt != type::eos) + fail (t) << "expected newline after }"; + + // See if we have another el* keyword. + // + if (k != "else" && tt == type::name) + { + const string& n (t.value); + + if (!t.quoted && (n == "else" || n == "elif" || n == "elif!")) + continue; + } + + break; + } + } + + void parser:: + print (token& t, type& tt) { // Parse the rest as names to get variable expansion, etc. Switch // to the variable value lexing mode so that we don't treat special @@ -920,7 +1035,7 @@ namespace build } void parser:: - variable (token& t, token_type& tt, string name, token_type kind) + variable (token& t, type& tt, string name, type kind) { const auto& var (var_pool.find (move (name))); names_type vns (variable_value (t, tt, var)); @@ -942,7 +1057,7 @@ namespace build } names parser:: - variable_value (token& t, token_type& tt, const variable_type& var) + variable_value (token& t, type& tt, const variable_type& var) { if (var.pairs != '\0') lexer_->mode (lexer_mode::pairs, var.pairs); @@ -956,7 +1071,7 @@ namespace build } parser::names_type parser:: - eval (token& t, token_type& tt) + eval (token& t, type& tt) { lexer_->mode (lexer_mode::eval); next (t, tt); @@ -1587,6 +1702,43 @@ namespace build } } + void parser:: + skip_line (token& t, type& tt) + { + for (; tt != type::newline && tt != type::eos; next (t, tt)) ; + } + + void parser:: + skip_block (token& t, type& tt) + { + // Skip until } or eos, keeping track of the {}-balance. + // + for (size_t b (0); tt != type::eos; ) + { + if (tt == type::lcbrace || tt == type::rcbrace) + { + type ptt (peek ()); + if (ptt == type::newline || ptt == type::eos) // Block { or }. + { + if (tt == type::lcbrace) + ++b; + else + { + if (b == 0) + break; + + --b; + } + } + } + + skip_line (t, tt); + + if (tt != type::eos) + next (t, tt); + } + } + // Buildspec parsing. // @@ -1659,7 +1811,7 @@ namespace build } buildspec parser:: - buildspec_clause (token& t, token_type& tt, token_type tt_end) + buildspec_clause (token& t, type& tt, type tt_end) { buildspec bs; @@ -1917,8 +2069,8 @@ namespace build trace); } - token_type parser:: - next (token& t, token_type& tt) + type parser:: + next (token& t, type& tt) { if (!peeked_) t = lexer_->next (); @@ -1932,7 +2084,7 @@ namespace build return tt; } - token_type parser:: + type parser:: peek () { if (!peeked_) diff --git a/tests/if-else/buildfile b/tests/if-else/buildfile new file mode 100644 index 0000000..30da40f --- /dev/null +++ b/tests/if-else/buildfile @@ -0,0 +1,114 @@ +#else # else without if +#elif # elif without if +#elif! # elif! without if +#if # expected if-expression +#if test # expected true or false +#if true # expected { +#x +#if true # expected newline after { +#{x +#if true # expected newline after } +#{ +#}x + +if true +{ + print 1 +} +else +{ + print 0 +} + +if! true +{ + print 0 +} +else +{ + print 1 +} + +if false +{ + print 0 +} +else +{ + print 1 +} + +if false +{ + print 0 +} +elif true +{ + print 1 +} +else +{ + print 0 +} + +if true +{ + print 1 +} +elif true +{ + print 0 +} +else +{ + print 0 +} + +# Empty block +# +if true +{ +} +else +{ + print 0 +} + +# Nested if-else +# +if true +{ + if true + { + print 1 + } +} +else +{ + if true + { + print 0 + } +} + +t = true +f = false + +if $t +{ + print 1 +} + +if! $f +{ + print 1 +} + +./: + +# EOF test. +# +if true +{ + print 1 +} diff --git a/tests/if-else/test.out b/tests/if-else/test.out new file mode 100644 index 0000000..bb08505 --- /dev/null +++ b/tests/if-else/test.out @@ -0,0 +1,9 @@ +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/tests/if-else/test.sh b/tests/if-else/test.sh new file mode 100755 index 0000000..b898b3c --- /dev/null +++ b/tests/if-else/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +valgrind -q b -q | diff -u test.out - |