From 38a67608e1b9ca173b84b95314de41a8e2ff5f34 Mon Sep 17 00:00:00 2001
From: Nora Abi Akar <nora.abiakar@gmail.com>
Date: Thu, 28 Nov 2019 10:01:17 +0100
Subject: [PATCH] Modcc: Add support for `and` and `or` operators (#905)

Also renumber operator precedence.

Fixes #25
---
 modcc/expression.cpp           |  2 ++
 modcc/lexer.cpp                | 48 +++++++++++++++++++++++++++++-----
 modcc/printer/cexpr_emit.cpp   |  2 ++
 modcc/symdiff.cpp              |  6 ++++-
 modcc/token.cpp                |  2 ++
 modcc/token.hpp                |  2 +-
 test/unit-modcc/test_lexer.cpp | 26 +++++++++++++++---
 7 files changed, 77 insertions(+), 11 deletions(-)

diff --git a/modcc/expression.cpp b/modcc/expression.cpp
index a33e7a76..18e5a531 100644
--- a/modcc/expression.cpp
+++ b/modcc/expression.cpp
@@ -1138,6 +1138,8 @@ expression_ptr binary_expression(Location loc,
         case tok::lte      :
         case tok::gt       :
         case tok::gte      :
+        case tok::land     :
+        case tok::lor      :
         case tok::equality :
             return make_expression<ConditionalExpression>(loc, op, std::move(lhs), std::move(rhs));
         default         :
diff --git a/modcc/lexer.cpp b/modcc/lexer.cpp
index 1eb684ff..dc5851ec 100644
--- a/modcc/lexer.cpp
+++ b/modcc/lexer.cpp
@@ -195,6 +195,40 @@ Token Lexer::parse() {
                 }
                 return t;
             }
+            case '&': {
+                bool valid = false;
+                t.spelling += character();
+                if (*current_ == '&') {
+                    t.spelling += character();
+                    if (*current_ != '&') {
+                        t.type = tok::land;
+                        valid = true;
+                    }
+                }
+                if (!valid) {
+                    error_string_ = pprintf("& must be in pairs");
+                    status_ = lexerStatus::error;
+                    t.type = tok::reserved;
+                }
+                return t;
+            }
+            case '|': {
+                bool valid = false;
+                t.spelling += character();
+                if (*current_ == '|') {
+                    t.spelling += character();
+                    if (*current_ != '|') {
+                        t.type = tok::lor;
+                        valid = true;
+                    }
+                }
+                if (!valid) {
+                    error_string_ = pprintf("| must be in pairs");
+                    status_ = lexerStatus::error;
+                    t.type = tok::reserved;
+                }
+                return t;
+            }
             case '\'':
                 t.type = tok::prime;
                 t.spelling += character();
@@ -379,18 +413,20 @@ void Lexer::binop_prec_init() {
 
     // I have taken the operator precedence from C++
     // Note that only infix operators require precidence.
-    binop_prec_[tok::eq]       = 2;
+    binop_prec_[tok::eq]       = 1;
+    binop_prec_[tok::land]     = 2;
+    binop_prec_[tok::lor]      = 3;
     binop_prec_[tok::equality] = 4;
     binop_prec_[tok::ne]       = 4;
     binop_prec_[tok::lt]       = 5;
     binop_prec_[tok::lte]      = 5;
     binop_prec_[tok::gt]       = 5;
     binop_prec_[tok::gte]      = 5;
-    binop_prec_[tok::plus]     = 10;
-    binop_prec_[tok::minus]    = 10;
-    binop_prec_[tok::times]    = 20;
-    binop_prec_[tok::divide]   = 20;
-    binop_prec_[tok::pow]      = 30;
+    binop_prec_[tok::plus]     = 6;
+    binop_prec_[tok::minus]    = 6;
+    binop_prec_[tok::times]    = 7;
+    binop_prec_[tok::divide]   = 7;
+    binop_prec_[tok::pow]      = 8;
 }
 
 int Lexer::binop_precedence(tok tok) {
diff --git a/modcc/printer/cexpr_emit.cpp b/modcc/printer/cexpr_emit.cpp
index 62aecf2e..a3be0274 100644
--- a/modcc/printer/cexpr_emit.cpp
+++ b/modcc/printer/cexpr_emit.cpp
@@ -96,6 +96,8 @@ void CExprEmitter::visit(BinaryExpression* e) {
         {tok::gt,       ">"},
         {tok::gte,      ">="},
         {tok::equality, "=="},
+        {tok::land,     "&&"},
+        {tok::lor,      "||"},
         {tok::ne,       "!="},
         {tok::min,      "min"},
         {tok::max,      "max"},
diff --git a/modcc/symdiff.cpp b/modcc/symdiff.cpp
index f8573b6a..27c2ff2b 100644
--- a/modcc/symdiff.cpp
+++ b/modcc/symdiff.cpp
@@ -529,7 +529,11 @@ public:
                 return;
             case tok::gte:
                 as_number(loc, lval>=rval);
-                return;
+            case tok::land:
+                as_number(loc, lval&&rval);
+            case tok::lor:
+                as_number(loc, lval||rval);
+                    return;
             default: ;
                 // unrecognized, fall through to non-numeric case below
             }
diff --git a/modcc/token.cpp b/modcc/token.cpp
index bd9a2957..6900d91c 100644
--- a/modcc/token.cpp
+++ b/modcc/token.cpp
@@ -84,6 +84,8 @@ static TokenString token_strings[] = {
     {">=",          tok::gte},
     {"==",          tok::equality},
     {"!=",          tok::ne},
+    {"&&",          tok::land},
+    {"||",          tok::lor},
     {"<->",         tok::arrow},
     {"~",           tok::tilde},
     {",",           tok::comma},
diff --git a/modcc/token.hpp b/modcc/token.hpp
index 03d06410..21467063 100644
--- a/modcc/token.hpp
+++ b/modcc/token.hpp
@@ -16,7 +16,7 @@ enum class tok {
     // infix binary ops
 
     // = + - * / ^
-    eq, plus, minus, times, divide, pow,
+    eq, plus, minus, times, divide, pow, land, lor,
     // comparison
     lnot,    // !   named logical not, to avoid clash with C++ not keyword
     lt,      // <
diff --git a/test/unit-modcc/test_lexer.cpp b/test/unit-modcc/test_lexer.cpp
index 4641ae3d..c6fb38b0 100644
--- a/test/unit-modcc/test_lexer.cpp
+++ b/test/unit-modcc/test_lexer.cpp
@@ -212,7 +212,7 @@ TEST(Lexer, symbols) {
 }
 
 TEST(Lexer, comparison_operators) {
-    char string[] = "< <= > >= == != !";
+    char string[] = "< <= > >= == != ! && ||";
     VerboseLexer lexer(string, string+sizeof(string));
 
     auto t1 = lexer.parse();
@@ -229,9 +229,12 @@ TEST(Lexer, comparison_operators) {
     EXPECT_EQ(t6.type, tok::ne);
     auto t7 = lexer.parse();
     EXPECT_EQ(t7.type, tok::lnot);
-
     auto t8 = lexer.parse();
-    EXPECT_EQ(t8.type, tok::eof);
+    EXPECT_EQ(t8.type, tok::land);
+    auto t9 = lexer.parse();
+    EXPECT_EQ(t9.type, tok::lor);
+    auto t10 = lexer.parse();
+    EXPECT_EQ(t10.type, tok::eof);
 }
 
 // test braces
@@ -340,4 +343,21 @@ TEST(Lexer, numbers) {
     lexer = VerboseLexer("1.2E4.3");
     lexer.parse();
     EXPECT_EQ(lexerStatus::error, lexer.status());
+
+    // single or triple & or | should give errors
+    lexer = VerboseLexer("&");
+    lexer.parse();
+    EXPECT_EQ(lexerStatus::error, lexer.status());
+
+    lexer = VerboseLexer("&&&");
+    lexer.parse();
+    EXPECT_EQ(lexerStatus::error, lexer.status());
+
+    lexer = VerboseLexer("|");
+    lexer.parse();
+    EXPECT_EQ(lexerStatus::error, lexer.status());
+
+    lexer = VerboseLexer("|||");
+    lexer.parse();
+    EXPECT_EQ(lexerStatus::error, lexer.status());
 }
-- 
GitLab