Taddeus Kroes 14 лет назад
Родитель
Сommit
17c6fae8aa
3 измененных файлов с 50 добавлено и 22 удалено
  1. 28 16
      src/node.py
  2. 14 6
      src/parser.py
  3. 8 0
      tests/test_parser.py

+ 28 - 16
src/node.py

@@ -28,34 +28,36 @@ OP_MUL = 5
 OP_DIV = 6
 OP_POW = 7
 OP_SUBSCRIPT = 8
+OP_AND = 9
+OP_OR = 10
 
 # N-ary (functions)
-OP_INT = 9
-OP_INT_INDEF = 10
-OP_COMMA = 11
-OP_SQRT = 12
-OP_DER = 13
-OP_LOG = 14
+OP_INT = 11
+OP_INT_INDEF = 12
+OP_COMMA = 13
+OP_SQRT = 14
+OP_DER = 15
+OP_LOG = 16
 
 # Goniometry
-OP_SIN = 15
-OP_COS = 16
-OP_TAN = 17
+OP_SIN = 17
+OP_COS = 18
+OP_TAN = 19
 
-OP_SOLVE = 18
-OP_EQ = 19
+OP_SOLVE = 20
+OP_EQ = 21
 
-OP_POSSIBILITIES = 20
-OP_HINT = 21
-OP_REWRITE_ALL = 22
-OP_REWRITE = 23
+OP_POSSIBILITIES = 22
+OP_HINT = 23
+OP_REWRITE_ALL = 24
+OP_REWRITE = 25
 
 # Special identifiers
 PI = 'pi'
 E = 'e'
 INFINITY = 'oo'
 
-SPECIAL_TOKENS = [PI, INFINITY]
+SPECIAL_TOKENS = [PI, E, INFINITY]
 
 # Default base to use in parsing 'log(...)'
 DEFAULT_LOGARITHM_BASE = 10
@@ -75,6 +77,8 @@ OP_MAP = {
         '/': OP_DIV,
         '^': OP_POW,
         '_': OP_SUBSCRIPT,
+        '^^': OP_AND,
+        'vv': OP_OR,
         'sin': OP_SIN,
         'cos': OP_COS,
         'tan': OP_TAN,
@@ -103,6 +107,8 @@ TOKEN_MAP = {
         OP_DIV: 'DIVIDE',
         OP_POW: 'POW',
         OP_SUBSCRIPT: 'SUB',
+        OP_AND: 'AND',
+        OP_OR: 'OR',
         OP_SQRT: 'FUNCTION',
         OP_SIN: 'FUNCTION',
         OP_COS: 'FUNCTION',
@@ -226,6 +232,12 @@ class ExpressionBase(object):
     def __pos__(self):
         return self.reduce_negation()
 
+    def __and__(self, other):
+        return ExpressionNode(OP_AND, self, to_expression(other))
+
+    def __or__(self, other):
+        return ExpressionNode(OP_OR, self, to_expression(other))
+
     def reduce_negation(self, n=1):
         """Remove n negation flags from the node."""
         assert self.negated

+ 14 - 6
src/parser.py

@@ -87,10 +87,12 @@ class Parser(BisonParser):
     precedences = (
         ('left', ('COMMA', )),
         ('right', ('INTEGRAL', 'DERIVATIVE')),
+        ('left', ('OR', )),
+        ('left', ('AND', )),
+        ('left', ('EQ', )),
         ('left', ('MINUS', 'PLUS')),
         ('left', ('TIMES', 'DIVIDE')),
         ('right', ('FUNCTION', )),
-        ('left', ('EQ', )),
         ('left', ('NEG', )),
         ('right', ('POW', )),
         ('left', ('SUB', )),
@@ -225,6 +227,9 @@ class Parser(BisonParser):
         for i, keyword in enumerate(words):
             data = data.replace(chr(i), keyword)
 
+        # Fix TIMES operator next to OR
+        data = re.sub(r'\*?vv\*?', 'vv', data)
+
         if self.verbose and data_before != data:  # pragma: nocover
             print 'hook_read_after() modified the input data:'
             print 'before:', repr(data_before)
@@ -527,14 +532,16 @@ class Parser(BisonParser):
                | exp TIMES exp
                | exp DIVIDE exp
                | exp EQ exp
+               | exp AND exp
+               | exp OR exp
                | exp MINUS exp
                | power
         """
 
-        if 0 <= option < 4:  # rule: exp {PLUS,TIMES,DIVIDE,EQ} exp
+        if 0 <= option <= 5:  # rule: exp {PLUS,TIMES,DIVIDE,EQ,AND,OR} exp
             return Node(values[1], values[0], values[2])
 
-        if option == 4:  # rule: exp MINUS exp
+        if option == 6:  # rule: exp MINUS exp
             node = values[2]
 
             # Add negation to the left-most child
@@ -549,7 +556,7 @@ class Parser(BisonParser):
 
             return Node(OP_ADD, values[0], values[2])
 
-        if option == 5:  # rule: power
+        if option == 7:  # rule: power
             return Node(OP_POW, *values[0])
 
         raise BisonSyntaxError('Unsupported option %d in target "%s".'
@@ -573,8 +580,9 @@ class Parser(BisonParser):
     functions = []
 
     for token in SPECIAL_TOKENS:
-        operators += '"%s"%s{ returntoken(IDENTIFIER); }\n' \
-                     % (token, ' ' * (8 - len(token)))
+        if len(token) > 1:
+            operators += '"%s"%s{ returntoken(IDENTIFIER); }\n' \
+                         % (token, ' ' * (8 - len(token)))
 
     for op_str, op in OP_MAP.iteritems():
         if TOKEN_MAP[op] == 'FUNCTION':

+ 8 - 0
tests/test_parser.py

@@ -38,6 +38,14 @@ class TestParser(unittest.TestCase):
 
         self.assertNotEqual(possibilities1, possibilities2)
 
+    def test_binary(self):
+        a, b, c = tree('a, b, c')
+
+        self.assertEqual(tree('a ^^ b'), a & b)
+        self.assertEqual(tree('a vv b'), a | b)
+        self.assertEqual(tree('a vv b vv c'), (a | b) | c)
+        self.assertEqual(tree('a vv b ^^ c'), a | (b & c))
+
     def test_preprocessor(self):
         self.assertEqual(tree('ab'), tree('a * b'))
         self.assertEqual(tree('abc'), tree('a * b * c'))