aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin McCorkell <rmccorkell@karoshi.org.uk>2014-11-08 01:41:05 +0000
committerRobin McCorkell <rmccorkell@karoshi.org.uk>2014-11-16 16:45:10 +0000
commit702148aea515b5c7b2d6004615a7f4a635a7ca1e (patch)
tree7594171a735bea6ac32cdd5da337dfbeee789e92
parent9ce01c16f409208ca17235efee602a71da9712de (diff)
downloadmeson-702148aea515b5c7b2d6004615a7f4a635a7ca1e.zip
meson-702148aea515b5c7b2d6004615a7f4a635a7ca1e.tar.gz
meson-702148aea515b5c7b2d6004615a7f4a635a7ca1e.tar.bz2
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
-rw-r--r--authors.txt1
-rw-r--r--interpreter.py56
-rw-r--r--mparser.py68
-rw-r--r--test cases/common/68 number arithmetic/meson.build22
-rw-r--r--test cases/common/69 string arithmetic/meson.build20
-rw-r--r--test cases/common/70 array arithmetic/meson.build15
-rw-r--r--test cases/common/71 arithmetic bidmas/meson.build15
-rw-r--r--test cases/failing/11 object arithmetic/meson.build3
-rw-r--r--test cases/failing/12 string arithmetic/meson.build3
-rw-r--r--test cases/failing/13 array arithmetic/meson.build3
10 files changed, 164 insertions, 42 deletions
diff --git a/authors.txt b/authors.txt
index 1859942..e3652c8 100644
--- a/authors.txt
+++ b/authors.txt
@@ -8,3 +8,4 @@ The following people have submitted patches for the project
Peter Koval
Masashi Fujita
Juhani Simola
+Robin McCorkell
diff --git a/interpreter.py b/interpreter.py
index e2a7b16..2b75fa8 100644
--- a/interpreter.py
+++ b/interpreter.py
@@ -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:
diff --git a/mparser.py b/mparser.py
index a0be378..1cdc953 100644
--- a/mparser.py
+++ b/mparser.py
@@ -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)
diff --git a/test cases/common/68 number arithmetic/meson.build b/test cases/common/68 number arithmetic/meson.build
new file mode 100644
index 0000000..894c065
--- /dev/null
+++ b/test cases/common/68 number arithmetic/meson.build
@@ -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
diff --git a/test cases/common/69 string arithmetic/meson.build b/test cases/common/69 string arithmetic/meson.build
new file mode 100644
index 0000000..f0e46e9
--- /dev/null
+++ b/test cases/common/69 string arithmetic/meson.build
@@ -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
diff --git a/test cases/common/70 array arithmetic/meson.build b/test cases/common/70 array arithmetic/meson.build
new file mode 100644
index 0000000..8b8785a
--- /dev/null
+++ b/test cases/common/70 array arithmetic/meson.build
@@ -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
diff --git a/test cases/common/71 arithmetic bidmas/meson.build b/test cases/common/71 arithmetic bidmas/meson.build
new file mode 100644
index 0000000..2a530c8
--- /dev/null
+++ b/test cases/common/71 arithmetic bidmas/meson.build
@@ -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
diff --git a/test cases/failing/11 object arithmetic/meson.build b/test cases/failing/11 object arithmetic/meson.build
new file mode 100644
index 0000000..34e3a7a
--- /dev/null
+++ b/test cases/failing/11 object arithmetic/meson.build
@@ -0,0 +1,3 @@
+project('object arithmetic')
+
+foo = '5' + meson
diff --git a/test cases/failing/12 string arithmetic/meson.build b/test cases/failing/12 string arithmetic/meson.build
new file mode 100644
index 0000000..753f62b
--- /dev/null
+++ b/test cases/failing/12 string arithmetic/meson.build
@@ -0,0 +1,3 @@
+project('string arithmetic')
+
+foo = 'a' / 'b'
diff --git a/test cases/failing/13 array arithmetic/meson.build b/test cases/failing/13 array arithmetic/meson.build
new file mode 100644
index 0000000..ceaa8bc
--- /dev/null
+++ b/test cases/failing/13 array arithmetic/meson.build
@@ -0,0 +1,3 @@
+project('array arithmetic')
+
+foo = ['a', 'b'] * ['6', '4']