Ver código fonte

Added some new syntaxes for logarithms in the parser and printer.

Taddeus Kroes 14 anos atrás
pai
commit
f1e51800eb
4 arquivos alterados com 72 adições e 25 exclusões
  1. 39 19
      src/node.py
  2. 18 6
      src/parser.py
  3. 6 0
      tests/test_node.py
  4. 9 0
      tests/test_parser.py

+ 39 - 19
src/node.py

@@ -33,27 +33,29 @@ OP_MOD = 7
 OP_INT = 8
 OP_COMMA = 9
 OP_SQRT = 10
-OP_DERIV = 11
+OP_DER = 11
 OP_LOG = 12
-OP_LN = 13
 
 # Goniometry
-OP_SIN = 14
-OP_COS = 15
-OP_TAN = 16
+OP_SIN = 13
+OP_COS = 14
+OP_TAN = 15
 
-OP_SOLVE = 17
-OP_EQ = 18
+OP_SOLVE = 16
+OP_EQ = 17
 
-OP_POSSIBILITIES = 19
-OP_HINT = 20
-OP_REWRITE_ALL = 21
-OP_REWRITE = 22
+OP_POSSIBILITIES = 18
+OP_HINT = 19
+OP_REWRITE_ALL = 20
+OP_REWRITE = 21
 
 # Special identifiers
 PI = 'pi'
 E = 'e'
 
+# Default base to use in parsing 'log(...)'
+DEFAULT_LOGARITHM_BASE = 10
+
 
 TYPE_MAP = {
         int: TYPE_INTEGER,
@@ -73,10 +75,10 @@ OP_MAP = {
         'tan': OP_TAN,
         'sqrt': OP_SQRT,
         'int': OP_INT,
-        'der': OP_DERIV,
+        'der': OP_DER,
         'solve': OP_SOLVE,
         'log': OP_LOG,
-        'ln': OP_LN,
+        'ln': OP_LOG,
         '=': OP_EQ,
         '??': OP_POSSIBILITIES,
         '?': OP_HINT,
@@ -96,10 +98,9 @@ TOKEN_MAP = {
         OP_COS: 'FUNCTION',
         OP_TAN: 'FUNCTION',
         OP_INT: 'FUNCTION',
-        OP_DERIV: 'FUNCTION',
+        OP_DER: 'FUNCTION',
         OP_SOLVE: 'FUNCTION',
         OP_LOG: 'FUNCTION',
-        OP_LN: 'FUNCTION',
         OP_EQ: 'EQ',
         OP_POSSIBILITIES: 'POSSIBILITIES',
         OP_HINT: 'HINT',
@@ -162,8 +163,8 @@ class ExpressionBase(object):
 
         return s_root < o_root or s_exp < o_exp or s_coeff < o_coeff
 
-    def is_op(self, op):
-        return not self.is_leaf and self.op == op
+    def is_op(self, *ops):
+        return not self.is_leaf and self.op in ops
 
     def is_power(self, exponent=None):
         if self.is_leaf or self.op != OP_POW:
@@ -240,13 +241,32 @@ class ExpressionNode(Node, ExpressionBase):
         self.op = OP_MAP[args[0]]
 
     def construct_function(self, children):
-        if self.op == OP_DERIV:
+        if self.op == OP_DER:
             f = children[0]
 
             if len(children) < 2:
+                # der(der(x ^ 2))  ->  [x ^ 2]''
+                if self[0].is_op(OP_DER) and len(self[0]) < 2:
+                    return f + '\''
+
+                # der(x ^ 2)  ->  [x ^ 2]'
                 return '[' + f + ']\''
 
-            return 'd/d' + children[1] + ' (' + f + ')'
+            # der(x ^ 2, x)  ->  d/dx (x ^ 2)
+            return 'd/d%s (%s)' % (children[1], f)
+
+        if self.op == OP_LOG:
+            # log(a, e)  ->  ln(a)
+            if self[1].is_identifier(E):
+                return 'ln(%s)' % children[0]
+
+            # log(a, 10)  ->  log(a)
+            if self[1] == 10:
+                return 'log(%s)' % children[0]
+
+            # log(a, 2)  ->  log_2(a)
+            if children[1].isdigit():
+                return 'log_%s(%s)' % (children[1], children[0])
 
     def __str__(self):  # pragma: nocover
         return generate_line(self)

+ 18 - 6
src/parser.py

@@ -15,13 +15,14 @@ from pybison import BisonParser, BisonSyntaxError
 from graph_drawing.graph import generate_graph
 
 from node import ExpressionNode as Node, ExpressionLeaf as Leaf, OP_MAP, \
-        OP_DERIV, TOKEN_MAP, TYPE_OPERATOR, OP_COMMA, OP_NEG, OP_MUL, OP_DIV, \
-        Scope, PI
+        OP_DER, TOKEN_MAP, TYPE_OPERATOR, OP_COMMA, OP_NEG, OP_MUL, OP_DIV, \
+        OP_LOG, Scope, PI, E, DEFAULT_LOGARITHM_BASE
 from rules import RULES
 from strategy import pick_suggestion
 from possibilities import filter_duplicates, apply_suggestion
 
 import Queue
+import re
 
 
 # Check for n-ary operator in child nodes
@@ -141,8 +142,6 @@ class Parser(BisonParser):
 
         self.possibilities = []
 
-        import re
-
         # Replace known keywords with escape sequences.
         words = list(Parser.words)
         words.insert(10, '\n')
@@ -389,13 +388,24 @@ class Parser(BisonParser):
         if option in (1, 2):  # rule: FUNCTION_LPAREN exp RPAREN | FUNCTION exp
             op = values[0].split(' ', 1)[0]
 
+            if op == 'ln':
+                return Node('log', values[1], Leaf(E))
+
             if values[1].is_op(OP_COMMA):
                 return Node(op, *values[1])
 
+            if op == 'log':
+                return Node('log', values[1], Leaf(DEFAULT_LOGARITHM_BASE))
+
+            m = re.match(r'^log_([0-9]+)', op)
+
+            if m:
+                return Node('log', values[1], Leaf(int(m.group(1))))
+
             return Node(op, values[1])
 
         if option == 3:  # rule: DERIVATIVE exp
-            op = [k for k, v in OP_MAP.iteritems() if v == OP_DERIV][0]
+            op = [k for k, v in OP_MAP.iteritems() if v == OP_DER][0]
 
             # DERIVATIVE looks like 'd/d*x*' -> extract the 'x'
             return Node(op, values[1], Leaf(values[0][-2]))
@@ -412,7 +422,7 @@ class Parser(BisonParser):
                            | bracket_derivative APOSTROPH
         """
 
-        op = [k for k, v in OP_MAP.iteritems() if v == OP_DERIV][0]
+        op = [k for k, v in OP_MAP.iteritems() if v == OP_DER][0]
 
         if option == 0:  # rule: LBRACKET exp RBRACKET APOSTROPH
             return Node(op, values[1])
@@ -527,6 +537,8 @@ class Parser(BisonParser):
     "["       { returntoken(LBRACKET); }
     "]"       { returntoken(RBRACKET); }
     "'"       { returntoken(APOSTROPH); }
+    log_[0-9]+"*(" { returntoken(FUNCTION_LPAREN); }
+    log_[0-9]+"*" { returntoken(FUNCTION); }
     """ + operators + r"""
     "raise"   { returntoken(RAISE); }
     "graph"   { returntoken(GRAPH); }

+ 6 - 0
tests/test_node.py

@@ -221,4 +221,10 @@ class TestNode(RulesTestCase):
 
     def test_construct_function(self):
         self.assertEqual(str(tree('der(x ^ 2)')), '[x ^ 2]\'')
+        self.assertEqual(str(tree('der(der(x ^ 2))')), '[x ^ 2]\'\'')
         self.assertEqual(str(tree('der(x ^ 2, x)')), 'd/dx (x ^ 2)')
+
+        self.assertEqual(str(tree('log(x, e)')), 'ln(x)')
+        self.assertEqual(str(tree('log(x, 10)')), 'log(x)')
+        self.assertEqual(str(tree('log(x, 2)')), 'log_2(x)')
+        self.assertEqual(str(tree('log(x, g)')), 'log(x, g)')

+ 9 - 0
tests/test_parser.py

@@ -7,6 +7,7 @@ from tests.parser import ParserWrapper, run_expressions, line, graph
 from tests.rulestestcase import tree
 from src.rules.goniometry import sin, cos
 from src.rules.derivatives import der
+from src.rules.logarithmic import log, ln
 
 
 class TestParser(unittest.TestCase):
@@ -75,3 +76,11 @@ class TestParser(unittest.TestCase):
         self.assertEqual(tree('d/dx (x ^ 2 + x)'), der(exp + x, x))
         self.assertEqual(tree('d/d'), d / d)
         # FIXME: self.assertEqual(tree('d(x ^ 2)/dx'), der(exp, x))
+
+    def test_logarithm(self):
+        x, g = tree('x, g')
+
+        self.assertEqual(tree('log(x, e)'), ln(x))
+        self.assertEqual(tree('log(x, 10)'), log(x))
+        self.assertEqual(tree('log(x, 2)'), log(x, 2))
+        self.assertEqual(tree('log(x, g)'), log(x, g))