diff --git a/expressions/__init__.py b/expressions/__init__.py index 0f27f12..813186a 100644 --- a/expressions/__init__.py +++ b/expressions/__init__.py @@ -1,4 +1,4 @@ from .compiler import * -__version__ = '0.2.2' +__version__ = '0.2.3' diff --git a/expressions/compiler.py b/expressions/compiler.py index 7d7e511..070fb9c 100644 --- a/expressions/compiler.py +++ b/expressions/compiler.py @@ -149,8 +149,7 @@ def _default(self, ast, node_type=None, *args): def variable(self, ast): # Note: ast is expected to be a _Result() from the `reference` rule value = ast.value - if not isinstance(ast, _Result): - import pdb; pdb.set_trace() + assert isinstance(ast, _Result) result = self.compiler.compile_variable(self.context, value) return _Result(result) diff --git a/expressions/grammar.ebnf b/expressions/grammar.ebnf index 7600635..cd6e990 100644 --- a/expressions/grammar.ebnf +++ b/expressions/grammar.ebnf @@ -14,8 +14,8 @@ term(binary) = factor {('*' | '/' | '%') factor} ; factor(unary) = ('+' | '-' | '~') factor | power ; power = atom ['^' factor] ; -atom = NUMBER - | STRING +atom = NUMBER + | STRING | function | variable | ( '(' @:test ')' ); @@ -30,7 +30,7 @@ reference = { @+:NAME '.'} @+:NAME; NUMBER = ?/[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/? ; STRING = ?/'[^'\\\r\n]*(?:\\.[^'\\\r\n]*)*'/? ; -comparison_operator = ('=' | '!=' | '<' | '<=' | '>' | '>=' +comparison_operator = ('=' | '!=' | '<=' | '<' | '>=' | '>' | 'in' | 'is' ); (* Allow any unicode character to be an identifier *) diff --git a/expressions/grammar.py b/expressions/grammar.py index 5cef0bb..65e99b9 100644 --- a/expressions/grammar.py +++ b/expressions/grammar.py @@ -15,7 +15,7 @@ from grako.parsing import graken, Parser -__version__ = (2014, 9, 19, 16, 34, 11, 4) +__version__ = (2015, 6, 29, 20, 16, 29, 0) __all__ = [ 'ExpressionParser', @@ -25,10 +25,13 @@ class ExpressionParser(Parser): - def __init__(self, whitespace=None, nameguard=True, **kwargs): + def __init__(self, whitespace=None, nameguard=None, **kwargs): super(ExpressionParser, self).__init__( whitespace=whitespace, nameguard=nameguard, + comments_re=None, + eol_comments_re=None, + ignorecase=None, **kwargs ) @@ -139,9 +142,7 @@ def block0(): self._token('/') with self._option(): self._token('%') - with self._option(): - self._token('//') - self._error('expecting one of: % * / //') + self._error('expecting one of: % * /') self._factor_() self._closure(block0) @@ -247,22 +248,22 @@ def _comparison_operator_(self): with self._group(): with self._choice(): with self._option(): - self._token('==') + self._token('=') with self._option(): self._token('!=') - with self._option(): - self._token('<') with self._option(): self._token('<=') with self._option(): - self._token('>') + self._token('<') with self._option(): self._token('>=') + with self._option(): + self._token('>') with self._option(): self._token('in') with self._option(): self._token('is') - self._error('expecting one of: != < <= == > >= in is') + self._error('expecting one of: != < <= = > >= in is') @graken() def _NAME_(self): @@ -340,7 +341,7 @@ def NAME(self, ast): return ast -def main(filename, startrule, trace=False, whitespace=None): +def main(filename, startrule, trace=False, whitespace=None, nameguard=None): import json with open(filename) as f: text = f.read() @@ -350,7 +351,8 @@ def main(filename, startrule, trace=False, whitespace=None): startrule, filename=filename, trace=trace, - whitespace=whitespace) + whitespace=whitespace, + nameguard=nameguard) print('AST:') print(ast) print() @@ -374,6 +376,9 @@ def __call__(self, parser, namespace, values, option_string): parser = argparse.ArgumentParser(description="Simple parser for Expression.") parser.add_argument('-l', '--list', action=ListRules, nargs=0, help="list all rules and exit") + parser.add_argument('-n', '--no-nameguard', action='store_true', + dest='no_nameguard', + help="disable the 'nameguard' feature") parser.add_argument('-t', '--trace', action='store_true', help="output trace information") parser.add_argument('-w', '--whitespace', type=str, default=string.whitespace, @@ -387,5 +392,6 @@ def __call__(self, parser, namespace, values, option_string): args.file, args.startrule, trace=args.trace, - whitespace=args.whitespace + whitespace=args.whitespace, + nameguard=not args.no_nameguard ) diff --git a/setup.py b/setup.py index 4257353..428aa20 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name = "expressions", - version = "0.2.2", + version = "0.2.3", packages = find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), package_data = { @@ -21,6 +21,8 @@ 'Topic :: Utilities' ], + install_requires='grako==3.6.1', + test_suite = "tests", # metadata for upload to PyPI diff --git a/tests/test_expressions.py b/tests/test_expressions.py index 6db6715..c9ba771 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -1,6 +1,6 @@ # -*- encoding: utf8 -*- import unittest -from expressions import Compiler, IdentifierPreprocessor +from expressions import Compiler from expressions import Variable, Function, UnaryOperator, BinaryOperator class ValidatingCompiler(Compiler): @@ -90,6 +90,30 @@ def test_operator(self): self.assertEqual(result.left, 1) self.assertEqual(result.right, 2) + result = compiler.compile("1 = 2") + self.assertIsInstance(result, BinaryOperator) + self.assertEqual(result.operator, "=") + self.assertEqual(result.left, 1) + self.assertEqual(result.right, 2) + + result = compiler.compile("1 != 2") + self.assertIsInstance(result, BinaryOperator) + self.assertEqual(result.operator, "!=") + self.assertEqual(result.left, 1) + self.assertEqual(result.right, 2) + + result = compiler.compile("1 >= 2") + self.assertIsInstance(result, BinaryOperator) + self.assertEqual(result.operator, ">=") + self.assertEqual(result.left, 1) + self.assertEqual(result.right, 2) + + result = compiler.compile("1 <= 2") + self.assertIsInstance(result, BinaryOperator) + self.assertEqual(result.operator, "<=") + self.assertEqual(result.left, 1) + self.assertEqual(result.right, 2) + @unittest.skip("later") def test_validating_compiler(self): compiler = ValidatingCompiler() @@ -108,7 +132,26 @@ def test_function_call_compile(self): result = compiler.compile("f(x, y)") self.assertEqual("CALL f(x, y)", result) +class IdentifierPreprocessor(Compiler): + """ + CustomCompiler, just for testing + """ + def __init__(self): + super(IdentifierPreprocessor, self).__init__() + + self.variables = set() + self.functions = set() + + def compile_variable(self, context, variable): + self.variables.add(variable) + return variable + + def compile_function(self, context, function, args): + self.functions.add(function) + return function + class CustomCompilersTestCase(unittest.TestCase): + def test_preprocessor(self): pp = IdentifierPreprocessor() pp.compile("foo(a + b) * bar(b + c)") @@ -128,3 +171,6 @@ def test_preprocessor_unique(self): self.assertEqual(functions, ["foo"]) self.assertEqual(variables, ["a", "b", "c"]) + +if __name__ == '__main__': + unittest.main()