diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index 571389a..edfdd2e 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -806,6 +806,20 @@ class BinOp(Node): return f"({self.left.print()} {OP_STR[self.op]} {self.right.print()})" def _eval(self, vtable): + # Short-circuit for logical operators + if self.op == "KEYWORD_AVT": + vtable, left = self.left.eval(vtable) + if bool(left): + return vtable, ValBool(True) + vtable, right = self.right.eval(vtable) + return vtable, ValBool(bool(right)) + if self.op == "KEYWORD_ET": + vtable, left = self.left.eval(vtable) + if not bool(left): + return vtable, ValBool(False) + vtable, right = self.right.eval(vtable) + return vtable, ValBool(bool(right)) + vtable, left = self.left.eval(vtable) vtable, right = self.right.eval(vtable) lv, rv = left.value(), right.value() @@ -874,10 +888,6 @@ class BinOp(Node): (isinstance(left, ValNul) and isinstance(right, ValInt) and rv == 0)): return vtable, ValBool(False) return vtable, ValBool(lv != rv) - case "KEYWORD_ET": - return vtable, ValBool(bool(left) and bool(right)) - case "KEYWORD_AVT": - return vtable, ValBool(bool(left) or bool(right)) case _: raise Exception(self.op) diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index a754593..5ef2ef2 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -93,6 +93,25 @@ def emit_expr(node, ctx): return [f'CentValue {tmp} = cent_scope_get(&_scope, "{node.name}");'], tmp if isinstance(node, BinOp): + # Short-circuit for logical operators + if node.op in ("KEYWORD_AVT", "KEYWORD_ET"): + l_lines, l_var = emit_expr(node.left, ctx) + r_lines, r_var = emit_expr(node.right, ctx) + tmp = ctx.fresh_tmp() + if node.op == "KEYWORD_AVT": + lines = l_lines + [f"CentValue {tmp};"] + lines += [f"if (cent_truthy({l_var})) {{ {tmp} = cent_bool(1); }} else {{"] + lines += [f" {l}" for l in r_lines] + lines += [f" {tmp} = cent_bool(cent_truthy({r_var}));"] + lines += ["}"] + else: + lines = l_lines + [f"CentValue {tmp};"] + lines += [f"if (!cent_truthy({l_var})) {{ {tmp} = cent_bool(0); }} else {{"] + lines += [f" {l}" for l in r_lines] + lines += [f" {tmp} = cent_bool(cent_truthy({r_var}));"] + lines += ["}"] + return lines, tmp + l_lines, l_var = emit_expr(node.left, ctx) r_lines, r_var = emit_expr(node.right, ctx) tmp = ctx.fresh_tmp() diff --git a/tests.py b/tests.py index 897a44d..6d46ed0 100644 --- a/tests.py +++ b/tests.py @@ -698,6 +698,9 @@ error_tests = [ ("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", CentvrionError), # args to zero-param function ("SI NVLLVS TVNC { DESIGNA r VT I }", CentvrionError), # NVLLVS cannot be used as boolean ("NVLLVS AVT VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in AVT + ("FALSITAS AVT NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean + ("VERITAS ET NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean + ("NVLLVS ET VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in ET ('"hello" + " world"', CentvrionError), # use & for string concatenation, not + ("[I, II][III]", CentvrionError), # index too high ("CVM SVBNVLLA\n[I, II][-I]", CentvrionError), # negative index @@ -1612,6 +1615,32 @@ et_avt_tests = [ ("SI FALSITAS AVT FALSITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", Program([], [SiStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]), ValInt(2)), + # short-circuit: right side not evaluated when result is determined + ("VERITAS AVT NVLLVS", + Program([], [ExpressionStatement(BinOp(Bool(True), Nullus(), "KEYWORD_AVT"))]), + ValBool(True)), + ("FALSITAS ET NVLLVS", + Program([], [ExpressionStatement(BinOp(Bool(False), Nullus(), "KEYWORD_ET"))]), + ValBool(False)), + # short-circuit with side-effect-prone expressions + ("DESIGNA x VT NVLLVS\nSI x EST NVLLVS AVT [I, II][x] EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [ + Designa(ID("x"), Nullus()), + SiStatement( + BinOp(BinOp(ID("x"), Nullus(), "KEYWORD_EST"), BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II")]), ID("x")), Numeral("I"), "KEYWORD_EST"), "KEYWORD_AVT"), + [Designa(ID("r"), Numeral("I"))], + [Designa(ID("r"), Numeral("II"))]), + ExpressionStatement(ID("r"))]), + ValInt(1)), + ("DESIGNA x VT NVLLVS\nSI x DISPAR NVLLVS ET [I, II][x] EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr", + Program([], [ + Designa(ID("x"), Nullus()), + SiStatement( + BinOp(BinOp(ID("x"), Nullus(), "KEYWORD_DISPAR"), BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II")]), ID("x")), Numeral("I"), "KEYWORD_EST"), "KEYWORD_ET"), + [Designa(ID("r"), Numeral("I"))], + [Designa(ID("r"), Numeral("II"))]), + ExpressionStatement(ID("r"))]), + ValInt(2)), ] class TestEtAvt(unittest.TestCase):