Commit f1e51800 authored by Taddeus Kroes's avatar Taddeus Kroes

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

parent 895c81d7
......@@ -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)
......
......@@ -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); }
......
......@@ -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)')
......@@ -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))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment