diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-07-20 14:14:52 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-07-20 14:14:52 +0200 |
commit | df43058115b389f1375690812ad92301288f976f (patch) | |
tree | bb4196fef2bfddd8fe778b92e7d2d3ce58e68c73 | |
parent | 5c5a60a02ba1ddcb6782a938f3c892cda979d8fe (diff) |
Implement support for <, >, <=, >= in eval context
Now can write:
if ($build.version > 30000)
-rw-r--r-- | build2/lexer.cxx | 15 | ||||
-rw-r--r-- | build2/parser.cxx | 76 | ||||
-rw-r--r-- | build2/token | 22 | ||||
-rw-r--r-- | build2/token.cxx | 4 | ||||
-rw-r--r-- | build2/variable | 9 | ||||
-rw-r--r-- | build2/variable.cxx | 62 | ||||
-rw-r--r-- | build2/variable.ixx | 13 | ||||
-rw-r--r-- | tests/eval/buildfile | 12 | ||||
-rw-r--r-- | tests/eval/test.out | 7 | ||||
-rw-r--r-- | tests/if-else/buildfile | 5 | ||||
-rw-r--r-- | tests/if-else/test.out | 1 |
11 files changed, 199 insertions, 27 deletions
diff --git a/build2/lexer.cxx b/build2/lexer.cxx index 2319ecb..773cd88 100644 --- a/build2/lexer.cxx +++ b/build2/lexer.cxx @@ -160,6 +160,19 @@ namespace build2 get (); return token (c == '=' ? type::equal : type::not_equal, sep, ln, cn); } + break; + } + case '<': + case '>': + { + bool e (peek () == '='); + if (e) + get (); + + return token (c == '<' + ? e ? type::less_equal : type::less + : e ? type::greater_equal : type::greater, + sep, ln, cn); } } @@ -271,6 +284,8 @@ namespace build2 switch (c) { case ':': + case '<': + case '>': { done = true; break; diff --git a/build2/parser.cxx b/build2/parser.cxx index e463f50..6b6c000 100644 --- a/build2/parser.cxx +++ b/build2/parser.cxx @@ -1593,10 +1593,14 @@ namespace build2 // 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 + value r (tt != type::rparen && + tt != type::colon && + tt != type::equal && + tt != type::not_equal && + tt != type::less && + tt != type::less_equal && + tt != type::greater && + tt != type::greater_equal ? names_value (t, tt) : value (names_type ())); @@ -1654,33 +1658,73 @@ namespace build2 } case type::equal: case type::not_equal: + case type::less: + case type::less_equal: + case type::greater: + case type::greater_equal: { type op (tt); - // ==, != are left-associative, so get the rhs value and evaluate. + // These are left-associative, so get the rhs value and evaluate. + // + // @@ In C++ == and != have lower precedence than <, etc. // next (t, tt); value rhs (parse_value ()); - // Use (potentially typed) comparison via value. + // Use (potentially typed) comparison via value. If one of the + // values is typed while the other is not, then try to convert the + // untyped one to the other's type instead of complaining. This + // seems like a reasonable thing to do and will allow us to write: + // + // if (build.version > 30000) // - // @@ Should we detect type mismatch here? + // Rather than having to write: // + // if (build.version > [uint64] 30000) + // + if (lhs.type != rhs.type) + { + // @@ Would be nice to pass location for diagnostics. + // + if (lhs.type == nullptr) + { + if (lhs) + typify (lhs, *rhs.type, nullptr); + } + else if (rhs.type == nullptr) + { + if (rhs) + typify (rhs, *lhs.type, nullptr); + } + else + fail (l) << "comparison between " << lhs.type->name << " and " + << rhs.type->name; + } + bool r; switch (op) { - case type::equal: r = lhs == rhs; break; - case type::not_equal: r = lhs != rhs; break; - default: r = false; assert (false); + case type::equal: r = lhs == rhs; break; + case type::not_equal: r = lhs != rhs; break; + case type::less: r = lhs < rhs; break; + case type::less_equal: r = lhs <= rhs; break; + case type::greater: r = lhs > rhs; break; + case type::greater_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. + // Store the result as a bool value. // - names_type ns; - ns.push_back (name (r ? "true" : "false")); - lhs = value (move (ns)); + if (lhs.type != nullptr) + { + // Reset to NULL and untype. + // + lhs = nullptr; + lhs.type = nullptr; + } + + lhs = r; break; } default: diff --git a/build2/token b/build2/token index 6695010..c896ba1 100644 --- a/build2/token +++ b/build2/token @@ -17,15 +17,19 @@ namespace build2 newline, pair_separator, colon, - lcbrace, // { - rcbrace, // } - lsbrace, // [ - rsbrace, // ] - assign, // = - prepend, // =+ - append, // += - equal, // == - not_equal, // != + lcbrace, // { + rcbrace, // } + lsbrace, // [ + rsbrace, // ] + assign, // = + prepend, // =+ + append, // += + equal, // == + not_equal, // != + less, // < + greater, // > + less_equal, // <= + greater_equal, // >= dollar, lparen, rparen diff --git a/build2/token.cxx b/build2/token.cxx index 330af1f..5a47eb7 100644 --- a/build2/token.cxx +++ b/build2/token.cxx @@ -26,6 +26,10 @@ namespace build2 case token_type::append: os << "'+='"; break; case token_type::equal: os << "'=='"; break; case token_type::not_equal: os << "'!='"; break; + case token_type::less: os << "'<'"; break; + case token_type::greater: os << "'>'"; break; + case token_type::less_equal: os << "'<='"; break; + case token_type::greater_equal: os << "'>='"; break; case token_type::dollar: os << "'$'"; break; case token_type::lparen: os << "'('"; break; case token_type::rparen: os << "')'"; break; diff --git a/build2/variable b/build2/variable index cc99dc6..9473674 100644 --- a/build2/variable +++ b/build2/variable @@ -119,7 +119,7 @@ namespace build2 // value // - enum class value_state: uint8_t {null, empty, filled}; + enum class value_state: uint8_t {null, empty, filled}; // Order is important. class value { @@ -231,10 +231,15 @@ namespace build2 }; // The values should be of the same type (or both be untyped) except NULL - // values can also be untyped. NULL values compare equal. + // values can also be untyped. NULL values compare equal and a NULL value + // is always less than a non-NULL. // bool operator== (const value&, const value&); bool operator!= (const value&, const value&); + bool operator< (const value&, const value&); + bool operator<= (const value&, const value&); + bool operator> (const value&, const value&); + bool operator>= (const value&, const value&); // Value cast. The first three expect the value to be not NULL. The cast // from lookup expects the value to aslo be defined. diff --git a/build2/variable.cxx b/build2/variable.cxx index 58076d4..78f9df8 100644 --- a/build2/variable.cxx +++ b/build2/variable.cxx @@ -280,6 +280,68 @@ namespace build2 return x.type->compare (x, y) == 0; } + bool + operator< (const value& x, const value& y) + { + bool xn (x.null ()); + bool yn (y.null ()); + + assert (x.type == y.type || + (xn && x.type == nullptr) || + (yn && y.type == nullptr)); + + // NULL value is always less than non-NULL and we assume that empty + // value is always less than non-empty. + // + if (x.state < y.state) + return true; + else if (x.state > y.state) + return false; + else if (x.state != value_state::filled) // Both are NULL or empty. + return false; + + // Both are filled. + // + if (x.type == nullptr) + return x.as<names> () < y.as<names> (); + + if (x.type->compare == nullptr) + return memcmp (&x.data_, &y.data_, x.type->size) < 0; + + return x.type->compare (x, y) < 0; + } + + bool + operator> (const value& x, const value& y) + { + bool xn (x.null ()); + bool yn (y.null ()); + + assert (x.type == y.type || + (xn && x.type == nullptr) || + (yn && y.type == nullptr)); + + // NULL value is always less than non-NULL and we assume that empty + // value is always less than non-empty. + // + if (x.state > y.state) + return true; + else if (x.state < y.state) + return false; + else if (x.state != value_state::filled) // Both are NULL or empty. + return false; + + // Both are filled. + // + if (x.type == nullptr) + return x.as<names> () > y.as<names> (); + + if (x.type->compare == nullptr) + return memcmp (&x.data_, &y.data_, x.type->size) > 0; + + return x.type->compare (x, y) > 0; + } + void typify (value& v, const value_type& t, const variable* var) { diff --git a/build2/variable.ixx b/build2/variable.ixx index da86c39..e7d5654 100644 --- a/build2/variable.ixx +++ b/build2/variable.ixx @@ -75,6 +75,19 @@ namespace build2 return !(x == y); } + inline bool + operator<= (const value& x, const value& y) + { + return !(x > y); + } + + inline bool + operator>= (const value& x, const value& y) + { + return !(x < y); + } + + template <> inline const names& cast (const value& v) diff --git a/tests/eval/buildfile b/tests/eval/buildfile index b1e5350..f26a9a0 100644 --- a/tests/eval/buildfile +++ b/tests/eval/buildfile @@ -58,3 +58,15 @@ n = print ($n == ) n = {} print ($n == "") + +#print ([uint64] 01 == [string] 01) + +# <, <=, >, >= evaluation +# +print (a < b) +print (a b > a a) +print (123 <= 123) +print ([uint64] 02 > [uint64] 01) +print (a > [null]) +print ([uint64] 02 > [null]) +print ($build.version > 30000) diff --git a/tests/eval/test.out b/tests/eval/test.out index bad003e..555853f 100644 --- a/tests/eval/test.out +++ b/tests/eval/test.out @@ -27,3 +27,10 @@ true true true true +true +true +true +true +true +true +true diff --git a/tests/if-else/buildfile b/tests/if-else/buildfile index 2b87b8c..a54092d 100644 --- a/tests/if-else/buildfile +++ b/tests/if-else/buildfile @@ -123,6 +123,11 @@ if!(foo == bar) print 1 } +if ([uint64] 01 == [uint64] 1) +{ + print 1 +} + # EOF test. # if true diff --git a/tests/if-else/test.out b/tests/if-else/test.out index 4dff9ef..71c9a23 100644 --- a/tests/if-else/test.out +++ b/tests/if-else/test.out @@ -10,3 +10,4 @@ 1 1 1 +1 |