Add number, string and array arithmetic

Addition (+), subtraction (-), multiplication (*) and division (/) for numbers
follows the BIDMAS rules.

Strings and arrays can be concatenated with the addition operator

Strings can be concatenated with numbers with the addition operator
pull/18/head
Robin McCorkell 10 years ago
parent 9ce01c16f4
commit 702148aea5
  1. 1
      authors.txt
  2. 56
      interpreter.py
  3. 68
      mparser.py
  4. 22
      test cases/common/68 number arithmetic/meson.build
  5. 20
      test cases/common/69 string arithmetic/meson.build
  6. 15
      test cases/common/70 array arithmetic/meson.build
  7. 15
      test cases/common/71 arithmetic bidmas/meson.build
  8. 3
      test cases/failing/11 object arithmetic/meson.build
  9. 3
      test cases/failing/12 string arithmetic/meson.build
  10. 3
      test cases/failing/13 array arithmetic/meson.build

@ -8,3 +8,4 @@ The following people have submitted patches for the project
Peter Koval
Masashi Fujita
Juhani Simola
Robin McCorkell

@ -1,4 +1,5 @@
# Copyright 2012-2014 Jussi Pakkanen
# Copyright 2014 Robin McCorkell
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -800,15 +801,19 @@ class Interpreter():
elif isinstance(cur, mparser.ArrayNode):
return self.evaluate_arraystatement(cur)
elif isinstance(cur, mparser.NumberNode):
return cur
return cur.value
elif isinstance(cur, mparser.AndNode):
return self.evaluate_andstatement(cur)
elif isinstance(cur, mparser.OrNode):
return self.evaluate_orstatement(cur)
elif isinstance(cur, mparser.NotNode):
return self.evaluate_notstatement(cur)
elif isinstance(cur, mparser.ArithmeticNode):
return self.evaluate_arithmeticstatement(cur)
elif isinstance(cur, mparser.ForeachClauseNode):
return self.evaluate_foreach(cur)
elif self.is_elementary_type(cur):
return cur
else:
raise InvalidCode("Unknown statement.")
@ -1393,37 +1398,17 @@ class Interpreter():
self.set_variable(var_name, value)
return value
def reduce_single(self, arg):
if isinstance(arg, mparser.IdNode):
return self.get_variable(arg.value)
elif isinstance(arg, str):
return arg
elif isinstance(arg, mparser.StringNode):
return arg.value
elif isinstance(arg, mparser.FunctionNode):
return self.function_call(arg)
elif isinstance(arg, mparser.MethodNode):
return self.method_call(arg)
elif isinstance(arg, mparser.BooleanNode):
return arg.value
elif isinstance(arg, mparser.ArrayNode):
return [self.reduce_single(curarg) for curarg in arg.args.arguments]
elif isinstance(arg, mparser.NumberNode):
return arg.value
else:
raise InvalidCode('Irreducible argument.')
def reduce_arguments(self, args):
assert(isinstance(args, mparser.ArgumentNode))
if args.incorrect_order():
raise InvalidArguments('All keyword arguments must be after positional arguments.')
reduced_pos = [self.reduce_single(arg) for arg in args.arguments]
reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments]
reduced_kw = {}
for key in args.kwargs.keys():
if not isinstance(key, str):
raise InvalidArguments('Keyword argument name is not a string.')
a = args.kwargs[key]
reduced_kw[key] = self.reduce_single(a)
reduced_kw[key] = self.evaluate_statement(a)
return (reduced_pos, reduced_kw)
def string_method_call(self, obj, method_name, args):
@ -1457,7 +1442,7 @@ class Interpreter():
if isinstance(args, mparser.ArgumentNode):
args = args.arguments
for (i, arg) in enumerate(args):
arg = self.to_native(self.reduce_single(arg))
arg = self.to_native(self.evaluate_statement(arg))
if isinstance(arg, bool): # Python boolean is upper case.
arg = str(arg).lower()
templ = templ.replace('@{}@'.format(i), str(arg))
@ -1537,7 +1522,7 @@ class Interpreter():
self.evaluate_codeblock(node.block)
def is_elementary_type(self, v):
if isinstance(v, int) or isinstance(v, str) or isinstance(v, bool):
if isinstance(v, (int, float, str, bool, list)):
return True
return False
@ -1552,9 +1537,6 @@ class Interpreter():
val2 = v2
else:
val2 = v2.value
if type(val1) != type(val2):
raise InterpreterException('Comparison of different types %s and %s.' %
(str(type(val1)), str(type(val2))))
if node.ctype == '==':
return val1 == val2
elif node.ctype == '!=':
@ -1600,6 +1582,24 @@ class Interpreter():
raise InterpreterException('Argument to "not" is not a boolean.')
return not v
def evaluate_arithmeticstatement(self, cur):
l = self.to_native(self.evaluate_statement(cur.left))
r = self.to_native(self.evaluate_statement(cur.right))
if isinstance(l, str) or isinstance(r, str):
l = str(l)
r = str(r)
if cur.operation == 'add':
return l + r
elif cur.operation == 'sub':
return l - r
elif cur.operation == 'mul':
return l * r
elif cur.operation == 'div':
return l // r
else:
raise InvalidCode('You broke me.')
def evaluate_arraystatement(self, cur):
(arguments, kwargs) = self.reduce_arguments(cur.args)
if len(kwargs) > 0:

@ -1,6 +1,7 @@
#!/usr/bin/python3
# Copyright 2014 Jussi Pakkanen
# Copyright 2014 Robin McCorkell
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -56,6 +57,10 @@ class Lexer:
('string', re.compile("'[^']*?'")),
('comma', re.compile(r',')),
('dot', re.compile(r'\.')),
('plus', re.compile(r'\+')),
('dash', re.compile(r'-')),
('star', re.compile(r'\*')),
('fslash', re.compile(r'/')),
('colon', re.compile(r':')),
('equal', re.compile(r'==')),
('nequal', re.compile(r'\!=')),
@ -177,6 +182,14 @@ class ComparisonNode:
self.right = right
self.ctype = ctype
class ArithmeticNode:
def __init__(self, lineno, colno, operation, left, right):
self.lineno = lineno
self.colno = colno
self.left = left
self.right = right
self.operation = operation
class NotNode:
def __init__(self, lineno, colno, value):
self.lineno = lineno
@ -277,12 +290,12 @@ class ArgumentNode():
# 1 assignment
# 2 or
# 3 and
# 4 equality
# comparison, plus and multiplication would go here
# 5 negation
# 6 funcall, method call
# 7 parentheses
# 8 plain token
# 4 comparison
# 5 arithmetic
# 6 negation
# 7 funcall, method call
# 8 parentheses
# 9 plain token
class Parser:
def __init__(self, code):
@ -345,12 +358,39 @@ class Parser:
return left
def e5(self):
if self.accept('not'):
return NotNode(self.current.lineno, self.current.colno, self.e6())
return self.e6()
return self.e5add()
def e5add(self):
left = self.e5sub()
if self.accept('plus'):
return ArithmeticNode(left.lineno, left.colno, 'add', left, self.e5add())
return left
def e5sub(self):
left = self.e5mul()
if self.accept('dash'):
return ArithmeticNode(left.lineno, left.colno, 'sub', left, self.e5sub())
return left
def e5mul(self):
left = self.e5div()
if self.accept('star'):
return ArithmeticNode(left.lineno, left.colno, 'mul', left, self.e5mul())
return left
def e5div(self):
left = self.e6()
if self.accept('fslash'):
return ArithmeticNode(left.lineno, left.colno, 'div', left, self.e5div())
return left
def e6(self):
left = self.e7()
if self.accept('not'):
return NotNode(self.current.lineno, self.current.colno, self.e7())
return self.e7()
def e7(self):
left = self.e8()
if self.accept('lparen'):
args = self.args()
self.expect('rparen')
@ -362,7 +402,7 @@ class Parser:
left = self.method_call(left)
return left
def e7(self):
def e8(self):
if self.accept('lparen'):
e = self.statement()
self.expect('rparen')
@ -372,9 +412,9 @@ class Parser:
self.expect('rbracket')
return ArrayNode(args)
else:
return self.e8()
return self.e9()
def e8(self):
def e9(self):
t = self.current
if self.accept('true'):
return BooleanNode(t, True);
@ -413,7 +453,7 @@ class Parser:
return a
def method_call(self, source_object):
methodname = self.e8()
methodname = self.e9()
if not(isinstance(methodname, IdNode)):
raise ParseException('Method name must be plain id',
self.current.lineno, self.current.colno)

@ -0,0 +1,22 @@
project('number arithmetic', 'c')
if 6 + 4 != 10
error('Number addition is broken')
endif
if 6 - 4 != 2
error('Number subtraction is broken')
endif
if 6 * 4 != 24
error('Number multiplication is broken')
endif
if 16 / 4 != 4
error('Number division is broken')
endif
#if (1 / 3) * 3 != 1
# error('Float interconversion broken')
#endif
if (5 / 3) * 3 != 3
error('Integer division is broken')
endif

@ -0,0 +1,20 @@
project('string arithmetic', 'c')
if 'foo' + 'bar' != 'foobar'
error('String concatenation is broken')
endif
if 'foo' + 'bar' + 'baz' != 'foobarbaz'
error('Many-string concatenation is broken')
endif
if 'foobar' + 5 != 'foobar5' or 5 + 'foobar' != '5foobar'
error('String-number concatenation is broken')
endif
if (5 + 3) + 'foobar' != '8foobar'
error('String-number addition then concatenation broken')
endif
if 5 + (3 + 'foobar') != '53foobar'
error('String-number concatenation then addition broken')
endif

@ -0,0 +1,15 @@
project('array arithmetic', 'c')
array1 = ['foo', 'bar']
array2 = ['qux', 'baz']
if array1 + array2 != ['foo', 'bar', 'qux', 'baz']
error('Array concatenation is broken')
endif
if array2 + array1 != ['qux', 'baz', 'foo', 'bar']
error('Array concatenation is broken')
endif
if array1 + array1 + array1 != ['foo', 'bar', 'foo', 'bar', 'foo', 'bar']
error('Many-array concatenation is broken')
endif

@ -0,0 +1,15 @@
project('arithmetic bidmas', 'c')
if 5 * 3 - 6 / 2 + 1 != 13
error('Arithemtic bidmas broken')
endif
if 5 * (3 - 6 / 2) + 1 != 1
error('Arithmetic bidmas with brackets broken')
endif
if 5 * 12 / 2 * 3 != 90
error('Sequential multiplication and division broken')
endif
if 5 * (12 / (2 * 3)) != 10
error('Sequential multiplication and division with brackets broken')
endif

@ -0,0 +1,3 @@
project('object arithmetic')
foo = '5' + meson

@ -0,0 +1,3 @@
project('string arithmetic')
foo = 'a' / 'b'

@ -0,0 +1,3 @@
project('array arithmetic')
foo = ['a', 'b'] * ['6', '4']
Loading…
Cancel
Save