aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-07-20 14:14:52 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-07-20 14:14:52 +0200
commitdf43058115b389f1375690812ad92301288f976f (patch)
treebb4196fef2bfddd8fe778b92e7d2d3ce58e68c73
parent5c5a60a02ba1ddcb6782a938f3c892cda979d8fe (diff)
Implement support for <, >, <=, >= in eval context
Now can write: if ($build.version > 30000)
-rw-r--r--build2/lexer.cxx15
-rw-r--r--build2/parser.cxx76
-rw-r--r--build2/token22
-rw-r--r--build2/token.cxx4
-rw-r--r--build2/variable9
-rw-r--r--build2/variable.cxx62
-rw-r--r--build2/variable.ixx13
-rw-r--r--tests/eval/buildfile12
-rw-r--r--tests/eval/test.out7
-rw-r--r--tests/if-else/buildfile5
-rw-r--r--tests/if-else/test.out1
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