diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index 723596c..2439fe0 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -462,6 +462,24 @@ class UnaryMinus(Node): return vtable, ValInt(-val.value()) +class UnaryNot(Node): + def __init__(self, expr): + self.expr = expr + + def __eq__(self, other): + return type(self) == type(other) and self.expr == other.expr + + def __repr__(self): + return f"UnaryNot({self.expr!r})" + + def print(self): + return f"(NON {self.expr.print()})" + + def _eval(self, vtable): + vtable, val = self.expr.eval(vtable) + return vtable, ValBool(not bool(val)) + + class ArrayIndex(Node): def __init__(self, array, index) -> None: self.array = array diff --git a/centvrion/lexer.py b/centvrion/lexer.py index b6258c9..21b3ff7 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -17,6 +17,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [ "INVOCA", "IN", "MINVS", + "NON", "NVLLVS", "PER", "PLVS", diff --git a/centvrion/parser.py b/centvrion/parser.py index 63bac9b..3ead9e5 100644 --- a/centvrion/parser.py +++ b/centvrion/parser.py @@ -15,7 +15,7 @@ class Parser(): ('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]), ('left', ["SYMBOL_COLON", "SYMBOL_PLUS", "SYMBOL_MINUS"]), ('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]), - ('right', ["UMINUS"]), + ('right', ["UMINUS", "UNOT"]), ('left', ["INDEX"]), ] ) @@ -191,6 +191,10 @@ class Parser(): def unary_minus(tokens): return ast_nodes.UnaryMinus(tokens[1]) + @self.pg.production('expression : KEYWORD_NON expression', precedence='UNOT') + def unary_not(tokens): + return ast_nodes.UnaryNot(tokens[1]) + @self.pg.production('expression : KEYWORD_INVOCA id expressions') def invoca(tokens): return ast_nodes.Invoca(tokens[1], tokens[2]) diff --git a/tests.py b/tests.py index b76dde3..6605d6c 100644 --- a/tests.py +++ b/tests.py @@ -8,7 +8,7 @@ from centvrion.ast_nodes import ( ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini, Designa, DumStatement, Erumpe, ExpressionStatement, ID, Invoca, ModuleCall, Nullus, Numeral, PerStatement, - Program, Redi, SiStatement, String, UnaryMinus, + Program, Redi, SiStatement, String, UnaryMinus, UnaryNot, num_to_int, int_to_num, make_string, ) from centvrion.lexer import Lexer @@ -160,6 +160,10 @@ precedence_tests = [ ("CVM SVBNVLLA\n- [I, II, III][I]", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I"))))]), ValInt(-1)), + # INDEX binds tighter than NON: NON (arr[I]) = NON VERITAS = False + ("NON [VERITAS, FALSITAS][I]", + Program([], [ExpressionStatement(UnaryNot(ArrayIndex(DataArray([Bool(True), Bool(False)]), Numeral("I"))))]), + ValBool(False)), # INDEX binds tighter than +: (arr[II]) + X = 2 + 10 = 12 ("[I, II, III][II] + X", Program([], [ExpressionStatement(BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")), Numeral("X"), "SYMBOL_PLUS"))]), @@ -1080,5 +1084,40 @@ class TestScope(unittest.TestCase): run_test(self, source, nodes, value) +# --- NON (boolean not) --- + +non_tests = [ + ("NON VERITAS", + Program([], [ExpressionStatement(UnaryNot(Bool(True)))]), + ValBool(False)), + ("NON FALSITAS", + Program([], [ExpressionStatement(UnaryNot(Bool(False)))]), + ValBool(True)), + ("NON NON VERITAS", + Program([], [ExpressionStatement(UnaryNot(UnaryNot(Bool(True))))]), + ValBool(True)), + ("NON I", + Program([], [ExpressionStatement(UnaryNot(Numeral("I")))]), + ValBool(False)), + # zero int is falsy, so NON gives True + ("DESIGNA z VT I - I\nNON z", + Program([], [Designa(ID("z"), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")), ExpressionStatement(UnaryNot(ID("z")))]), + ValBool(True)), + # NON binds tighter than AVT: (NON VERITAS) AVT FALSITAS → FALSITAS AVT FALSITAS → False + ("NON VERITAS AVT FALSITAS", + Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_AVT"))]), + ValBool(False)), + # NON binds tighter than EST: (NON I) EST I → FALSITAS EST I → False + ("NON I EST I", + Program([], [ExpressionStatement(BinOp(UnaryNot(Numeral("I")), Numeral("I"), "KEYWORD_EST"))]), + ValBool(False)), +] + +class TestNon(unittest.TestCase): + @parameterized.expand(non_tests) + def test_non(self, source, nodes, value): + run_test(self, source, nodes, value) + + if __name__ == "__main__": unittest.main()