diff options
author | Robin McCorkell <rmccorkell@karoshi.org.uk> | 2014-11-08 01:41:05 +0000 |
---|---|---|
committer | Robin McCorkell <rmccorkell@karoshi.org.uk> | 2014-11-16 16:45:10 +0000 |
commit | 702148aea515b5c7b2d6004615a7f4a635a7ca1e (patch) | |
tree | 7594171a735bea6ac32cdd5da337dfbeee789e92 | |
parent | 9ce01c16f409208ca17235efee602a71da9712de (diff) | |
download | meson-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.txt | 1 | ||||
-rw-r--r-- | interpreter.py | 56 | ||||
-rw-r--r-- | mparser.py | 68 | ||||
-rw-r--r-- | test cases/common/68 number arithmetic/meson.build | 22 | ||||
-rw-r--r-- | test cases/common/69 string arithmetic/meson.build | 20 | ||||
-rw-r--r-- | test cases/common/70 array arithmetic/meson.build | 15 | ||||
-rw-r--r-- | test cases/common/71 arithmetic bidmas/meson.build | 15 | ||||
-rw-r--r-- | test cases/failing/11 object arithmetic/meson.build | 3 | ||||
-rw-r--r-- | test cases/failing/12 string arithmetic/meson.build | 3 | ||||
-rw-r--r-- | test cases/failing/13 array arithmetic/meson.build | 3 |
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: @@ -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'] |