Răsfoiți Sursa

Added notation for absolute value to the parser.

Taddeus Kroes 14 ani în urmă
părinte
comite
196a602c5b
4 a modificat fișierele cu 59 adăugiri și 27 ștergeri
  1. 26 21
      src/node.py
  2. 10 5
      src/parser.py
  3. 8 1
      src/rules/utils.py
  4. 15 0
      tests/test_parser.py

+ 26 - 21
src/node.py

@@ -21,35 +21,36 @@ TYPE_FLOAT = 8
 
 # Unary
 OP_NEG = 1
+OP_ABS = 2
 
 # Binary
-OP_ADD = 2
-OP_SUB = 3
-OP_MUL = 4
-OP_DIV = 5
-OP_POW = 6
-OP_SUBSCRIPT = 7
+OP_ADD = 3
+OP_SUB = 4
+OP_MUL = 5
+OP_DIV = 6
+OP_POW = 7
+OP_SUBSCRIPT = 8
 
 # N-ary (functions)
-OP_INT = 8
-OP_INT_INDEF = 9
-OP_COMMA = 10
-OP_SQRT = 11
-OP_DER = 12
-OP_LOG = 13
+OP_INT = 9
+OP_INT_INDEF = 10
+OP_COMMA = 11
+OP_SQRT = 12
+OP_DER = 13
+OP_LOG = 14
 
 # Goniometry
-OP_SIN = 14
-OP_COS = 15
-OP_TAN = 16
+OP_SIN = 15
+OP_COS = 16
+OP_TAN = 17
 
-OP_SOLVE = 17
-OP_EQ = 18
+OP_SOLVE = 18
+OP_EQ = 19
 
-OP_POSSIBILITIES = 19
-OP_HINT = 20
-OP_REWRITE_ALL = 21
-OP_REWRITE = 22
+OP_POSSIBILITIES = 20
+OP_HINT = 21
+OP_REWRITE_ALL = 22
+OP_REWRITE = 23
 
 # Special identifiers
 PI = 'pi'
@@ -94,6 +95,7 @@ OP_MAP = {
 OP_VALUE_MAP = dict([(v, k) for k, v in OP_MAP.iteritems()])
 OP_MAP['ln'] = OP_LOG
 OP_VALUE_MAP[OP_INT_INDEF] = 'indef'
+OP_VALUE_MAP[OP_ABS] = 'abs'
 
 TOKEN_MAP = {
         OP_COMMA: 'COMMA',
@@ -319,6 +321,9 @@ class ExpressionNode(Node, ExpressionBase):
 
             return '[%s]%s%s' % (F, lbnd, ubnd)
 
+        if self.op == OP_ABS:
+            return '|%s|' % children[0]
+
     def __str__(self):  # pragma: nocover
         return generate_line(self)
 

+ 10 - 5
src/parser.py

@@ -18,7 +18,7 @@ from node import ExpressionBase, ExpressionNode as Node, \
         ExpressionLeaf as Leaf, OP_MAP, OP_DER, TOKEN_MAP, TYPE_OPERATOR, \
         OP_COMMA, OP_NEG, OP_MUL, OP_DIV, OP_POW, OP_LOG, OP_ADD, Scope, E, \
         DEFAULT_LOGARITHM_BASE, OP_VALUE_MAP, SPECIAL_TOKENS, OP_INT, \
-        OP_INT_INDEF
+        OP_INT_INDEF, OP_ABS
 from rules import RULES
 from rules.utils import find_variable
 from strategy import pick_suggestion
@@ -78,7 +78,7 @@ class Parser(BisonParser):
     # of tokens of the lex script.
     tokens = ['NUMBER', 'IDENTIFIER', 'NEWLINE', 'QUIT', 'RAISE', 'GRAPH',
               'LPAREN', 'RPAREN', 'FUNCTION', 'FUNCTION_LPAREN', 'LBRACKET',
-              'RBRACKET', 'PRIME', 'DERIVATIVE'] \
+              'RBRACKET', 'PIPE', 'PRIME', 'DERIVATIVE'] \
              + filter(lambda t: t != 'FUNCTION', TOKEN_MAP.values())
 
     # ------------------------------
@@ -180,16 +180,16 @@ class Parser(BisonParser):
         #   - "4a" with "4*a".
         #   - "a4" with "a^4".
 
-        pattern = ('(?:(\))\s*(\(|\[)'                        # )(  -> ) * (
+        pattern = ('(?:(\))\s*([([])'                         # )(  -> ) * (
                                                               # )[  -> ) * [
-                + '|([\x00-\x09\x0b-\x19a-z0-9])\s*(\(|\[)'   # a(  -> a * (
+                + '|([\x00-\x09\x0b-\x19a-z0-9])\s*([([])'    # a(  -> a * (
                                                               # a[  -> a * [
                 + '|(\))\s*([\x00-\x09\x0b-\x19a-z0-9])'      # )a  -> ) * a
                 + '|([\x00-\x09\x0b-\x19a-z])\s*'
                   + '([\x00-\x09\x0b-\x19a-z])'               # ab  -> a * b
                 + '|([0-9])\s*([\x00-\x09\x0b-\x19a-z])'      # 4a  -> 4 * a
                 + '|([\x00-\x09\x0b-\x19a-z])([0-9])'         # a4  -> a ^ 4
-                + '|([\x00-\x09\x0b-\x19a-z0-9])(\s+[0-9]))'  # a 4 -> a * 4,
+                + '|([\x00-\x09\x0b-\x19a-z0-9])(\s+[0-9]))'  # a 4 -> a * 4
                                                               # 4 4 -> 4 * 4
                 )
 
@@ -399,6 +399,7 @@ class Parser(BisonParser):
               | INTEGRAL exp
               | integral_bounds TIMES exp %prec INTEGRAL
               | LBRACKET exp RBRACKET lbnd ubnd
+              | PIPE exp PIPE
         """
 
         if option == 0:  # rule: NEG exp
@@ -464,6 +465,9 @@ class Parser(BisonParser):
         if option == 7:  # rule: LBRACKET exp RBRACKET lbnd ubnd
             return Node(OP_INT_INDEF, values[1], values[3], values[4])
 
+        if option == 8:  # rule: PIPE exp PIPE
+            return Node(OP_ABS, values[1])
+
         raise BisonSyntaxError('Unsupported option %d in target "%s".'
                                % (option, target))  # pragma: nocover
 
@@ -633,6 +637,7 @@ class Parser(BisonParser):
     "["       { returntoken(LBRACKET); }
     "]"       { returntoken(RBRACKET); }
     "'"       { returntoken(PRIME); }
+    "|"       { returntoken(PIPE); }
     log_([0-9]+|[a-zA-Z])"*(" { returntoken(FUNCTION_LPAREN); }
     log_([0-9]+|[a-zA-Z])"*" { returntoken(FUNCTION); }
     """ + operators + r"""

+ 8 - 1
src/rules/utils.py

@@ -1,5 +1,5 @@
 from ..node import ExpressionNode as N, ExpressionLeaf as L, OP_MUL, OP_DIV, \
-        INFINITY
+        INFINITY, OP_ABS
 
 
 def greatest_common_divisor(a, b):
@@ -133,3 +133,10 @@ def infinity():
     Return an infinity leaf node.
     """
     return L(INFINITY)
+
+
+def absolute(exp):
+    """
+    Put an 'absolute value' operator on top of the given expression.
+    """
+    return N(OP_ABS, exp)

+ 15 - 0
tests/test_parser.py

@@ -10,6 +10,7 @@ from src.rules.goniometry import sin, cos
 from src.rules.derivatives import der
 from src.rules.logarithmic import log, ln
 from src.rules.integrals import integral, indef
+from src.rules.utils import absolute
 
 
 class TestParser(unittest.TestCase):
@@ -51,6 +52,14 @@ class TestParser(unittest.TestCase):
         self.assertEqual(tree('2(a + b)'), tree('2 * (a + b)'))
         self.assertEqual(tree('(a + b)2'), tree('(a + b) * 2'))
 
+        self.assertEqual(tree('(a)(b)'), tree('(a) * (b)'))
+        self.assertEqual(tree('(a)[b]\''), tree('(a) * [b]\''))
+
+        # FIXME: self.assertEqual(tree('(a)|b|'), tree('(a) * |b|'))
+        # FIXME: self.assertEqual(tree('|a|(b)'), tree('|a| * (b)'))
+        # FIXME: self.assertEqual(tree('a|b|'), tree('a * |b|'))
+        # FIXME: self.assertEqual(tree('|a|b'), tree('|a| * b'))
+
     def test_moved_negation(self):
         a, b = tree('a,b')
 
@@ -128,3 +137,9 @@ class TestParser(unittest.TestCase):
         x2, a, b = tree('x ^ 2, a, b')
 
         self.assertEqual(tree('[x ^ 2]_a^b'), indef(x2, a, b))
+
+    def test_absolute_value(self):
+        x = tree('x')
+
+        self.assertEqual(tree('|x|'), absolute(x))
+        self.assertEqual(tree('|x2|'), absolute(x ** 2))