Ver Fonte

Started implementing integrals.

Taddeus Kroes há 14 anos atrás
pai
commit
b05101aeee
3 ficheiros alterados com 69 adições e 21 exclusões
  1. 15 14
      src/node.py
  2. 53 7
      src/parser.py
  3. 1 0
      src/rules/integrals.py

+ 15 - 14
src/node.py

@@ -31,23 +31,24 @@ OP_MOD = 7
 
 # N-ary (functions)
 OP_INT = 8
-OP_COMMA = 9
-OP_SQRT = 10
-OP_DER = 11
-OP_LOG = 12
+OP_INT_INDEF = 9
+OP_COMMA = 10
+OP_SQRT = 11
+OP_DER = 12
+OP_LOG = 13
 
 # Goniometry
-OP_SIN = 13
-OP_COS = 14
-OP_TAN = 15
+OP_SIN = 14
+OP_COS = 15
+OP_TAN = 16
 
-OP_SOLVE = 16
-OP_EQ = 17
+OP_SOLVE = 17
+OP_EQ = 18
 
-OP_POSSIBILITIES = 18
-OP_HINT = 19
-OP_REWRITE_ALL = 20
-OP_REWRITE = 21
+OP_POSSIBILITIES = 19
+OP_HINT = 20
+OP_REWRITE_ALL = 21
+OP_REWRITE = 22
 
 # Special identifiers
 PI = 'pi'
@@ -102,7 +103,7 @@ TOKEN_MAP = {
         OP_SIN: 'FUNCTION',
         OP_COS: 'FUNCTION',
         OP_TAN: 'FUNCTION',
-        OP_INT: 'FUNCTION',
+        OP_INT: 'INTEGRAL',
         OP_DER: 'FUNCTION',
         OP_SOLVE: 'FUNCTION',
         OP_LOG: 'FUNCTION',

+ 53 - 7
src/parser.py

@@ -17,7 +17,7 @@ from graph_drawing.graph import generate_graph
 from node import ExpressionNode as Node, ExpressionLeaf as Leaf, OP_MAP, \
         OP_DER, TOKEN_MAP, TYPE_OPERATOR, OP_COMMA, OP_NEG, OP_MUL, OP_DIV, \
         OP_LOG, OP_ADD, Scope, E, DEFAULT_LOGARITHM_BASE, OP_VALUE_MAP, \
-        SPECIAL_TOKENS
+        SPECIAL_TOKENS, OP_INT, OP_INT_INDEF
 from rules import RULES
 from strategy import pick_suggestion
 from possibilities import filter_duplicates, apply_suggestion
@@ -41,6 +41,20 @@ def combine(op, op_type, *nodes):
     return res
 
 
+def find_integration_variable(exp):
+    if not exp.is_op(OP_MUL):
+        return exp
+
+    scope = Scope(exp)
+
+    if len(scope) < 3 or scope[-2] != 'd' or not scope[-1].is_identifier():
+        return exp
+
+    scope.nodes = scope[:-2]
+
+    return scope.as_nary_node()
+
+
 class Parser(BisonParser):
     """
     Implements the calculator parser. Grammar rules are defined in the method
@@ -48,9 +62,8 @@ class Parser(BisonParser):
     """
 
     # Words to be ignored by preprocessor
-    words = zip(*filter(lambda (s, op): TOKEN_MAP[op] == 'FUNCTION', \
-                             OP_MAP.iteritems()))[0] \
-            + ('raise', 'graph') + tuple(SPECIAL_TOKENS)
+    words = tuple(filter(lambda w: len(w) > 1, OP_MAP.iterkeys())) \
+             + ('raise', 'graph')+ tuple(SPECIAL_TOKENS)
 
     # Output directory of generated pybison files, including a trailing slash.
     buildDirectory = PYBISON_BUILD + '/'
@@ -62,7 +75,7 @@ class Parser(BisonParser):
     # of tokens of the lex script.
     tokens = ['NUMBER', 'IDENTIFIER', 'NEWLINE', 'QUIT', 'RAISE', 'GRAPH',
               'LPAREN', 'RPAREN', 'FUNCTION', 'FUNCTION_LPAREN', 'LBRACKET',
-              'RBRACKET', 'APOSTROPH', 'DERIVATIVE'] \
+              'RBRACKET', 'APOSTROPH', 'DERIVATIVE', 'SUB'] \
              + filter(lambda t: t != 'FUNCTION', TOKEN_MAP.values())
 
     # ------------------------------
@@ -75,7 +88,7 @@ class Parser(BisonParser):
         ('right', ('FUNCTION', 'DERIVATIVE')),
         ('left', ('EQ', )),
         ('left', ('NEG', )),
-        ('right', ('POW', )),
+        ('right', ('POW', 'SUB')),
         ('right', ('FUNCTION_LPAREN', )),
         )
 
@@ -377,6 +390,7 @@ class Parser(BisonParser):
               | FUNCTION exp
               | DERIVATIVE exp
               | bracket_derivative
+              | integral
         """
 
         if option == 0:  # rule: NEG exp
@@ -418,7 +432,7 @@ class Parser(BisonParser):
             # DERIVATIVE looks like 'd/d*x*' -> extract the 'x'
             return Node(OP_DER, values[1], Leaf(values[0][-2]))
 
-        if option == 4:  # rule: bracket_derivative
+        if option in (4, 5):  # rule: bracket_derivative | integral
             return values[0]
 
         raise BisonSyntaxError('Unsupported option %d in target "%s".'
@@ -439,6 +453,37 @@ class Parser(BisonParser):
         raise BisonSyntaxError('Unsupported option %d in target "%s".'
                                % (option, target))  # pragma: nocover
 
+    def on_integral(self, target, option, names, values):
+        """
+        integral : INTEGRAL exp
+        """
+                 #| INTEGRAL SUB exp exp
+                 #| LBRACKET exp RBRACKET SUB exp exp
+
+        if option == 0:  # rule: INTEGRAL exp
+            fx, x = find_integration_variable(values[1])
+
+            return N(OP_INT, fx, x)
+
+        if option == 1:  # rule: INTEGRAL SUB exp exp
+            if not values[2].is_power():  # pragma: nocover
+                raise BisonSyntaxError('No upper bound specified in "%s".'
+                                       % values[2])
+
+            lbnd, ubnd = values[2]
+            fx, x = find_integration_variable(values[3])
+
+            return N(OP_INT, fx, x, lbnd, ubnd)
+
+        if option == 2:  # rule: LBRACKET exp RBRACKET SUB exp POWER exp
+            exp = values[1]
+            fx, x = find_integration_variable(values[1])
+
+            return N(OP_INT_INDEF, fx, x, values[4], values[6])
+
+        raise BisonSyntaxError('Unsupported option %d in target "%s".'
+                               % (option, target))  # pragma: nocover
+
     def on_binary(self, target, option, names, values):
         """
         binary : exp PLUS exp
@@ -541,6 +586,7 @@ class Parser(BisonParser):
     d[ ]*"/"[ ]*"d*"[a-z]"*" { returntoken(DERIVATIVE); }
     [0-9]+"."?[0-9]* { returntoken(NUMBER); }
     [a-zA-Z]  { returntoken(IDENTIFIER); }
+    "_"       { returntoken(SUB); }
     "("       { returntoken(LPAREN); }
     ")"       { returntoken(RPAREN); }
     "["       { returntoken(LBRACKET); }

+ 1 - 0
src/rules/integrals.py

@@ -37,6 +37,7 @@ def choose_constant(integral):
     """
     Choose a constant to be added to the antiderivative.
     """
+    # TODO: comments
     occupied = find_variables(integral)
     c = 'c'
     i = 96