diff --git a/README.md b/README.md index cf40948..0d94854 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,11 @@ Reads one line from stdin and returns it as a string. Reads one line from stdin, parses it as a Roman numeral, and returns it as an integer. Raises an error if the input is not a valid numeral. +### CONTINVA +`CONTINVA` + +Skips the rest of the current loop body and continues to the next iteration (`DVM` or `PER`). Has no meaningful return value. + ### ERVMPE `ERVMPE` diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index dcc6211..d750cfc 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -494,6 +494,21 @@ class Erumpe(Node): return vtable, ValNul() +class Continva(Node): + def __eq__(self, other): + return type(self) == type(other) + + def __repr__(self) -> str: + return "Continva()" + + def print(self): + return "CONTINVA" + + def _eval(self, vtable): + vtable["#continue"] = True + return vtable, ValNul() + + class Nullus(Node): def __eq__(self, other): return type(self) == type(other) @@ -723,12 +738,16 @@ class DumStatement(Node): while not cond: for statement in self.statements: vtable, val = statement.eval(vtable) - if vtable["#break"] or vtable["#return"] is not None: + if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None: break last_val = val if vtable["#break"]: vtable["#break"] = False break + if vtable["#continue"]: + vtable["#continue"] = False + vtable, cond = self.test.eval(vtable) + continue if vtable["#return"] is not None: break vtable, cond = self.test.eval(vtable) @@ -765,12 +784,15 @@ class PerStatement(Node): vtable[variable_name] = item for statement in self.statements: vtable, val = statement.eval(vtable) - if vtable["#break"] or vtable["#return"] is not None: + if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None: break last_val = val if vtable["#break"]: vtable["#break"] = False break + if vtable["#continue"]: + vtable["#continue"] = False + continue if vtable["#return"] is not None: break return vtable, last_val @@ -904,6 +926,7 @@ class Program(BaseBox): def eval(self, *_): vtable = { "#break": False, + "#continue": False, "#return": None, "#modules": [m.module_name for m in self.modules], } diff --git a/centvrion/compiler/emit_stmt.py b/centvrion/compiler/emit_stmt.py index 60ad23e..2f856a4 100644 --- a/centvrion/compiler/emit_stmt.py +++ b/centvrion/compiler/emit_stmt.py @@ -1,6 +1,6 @@ from centvrion.ast_nodes import ( Designa, DesignaIndex, SiStatement, DumStatement, PerStatement, - Defini, Redi, Erumpe, ExpressionStatement, ID, + Defini, Redi, Erumpe, Continva, ExpressionStatement, ID, ) from centvrion.compiler.emit_expr import emit_expr @@ -92,6 +92,9 @@ def emit_stmt(node, ctx): if isinstance(node, Erumpe): return ["break;"] + if isinstance(node, Continva): + return ["continue;"] + if isinstance(node, ExpressionStatement): lines, _ = emit_expr(node.expression, ctx) return lines diff --git a/centvrion/lexer.py b/centvrion/lexer.py index b024783..d8773c8 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -9,6 +9,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [ "DESIGNA", "DONICVM", "DVM", + "CONTINVA", "ERVMPE", "EST", "ET", diff --git a/centvrion/parser.py b/centvrion/parser.py index 68cb883..7970288 100644 --- a/centvrion/parser.py +++ b/centvrion/parser.py @@ -97,6 +97,10 @@ class Parser(): def erumpe(_): return ast_nodes.Erumpe() + @self.pg.production('statement : KEYWORD_CONTINVA') + def continva(_): + return ast_nodes.Continva() + @self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL') @self.pg.production('si_statement : KEYWORD_SI expression KEYWORD_TVNC SYMBOL_LCURL statements SYMBOL_RCURL aluid_statement') def si_statement(tokens): diff --git a/language/main.tex b/language/main.tex index 11a02d6..6312f74 100644 --- a/language/main.tex +++ b/language/main.tex @@ -36,7 +36,8 @@ \languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FACE} \textit{scope}} \\ \languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FACE} \textit{scope}} \\ \languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\ - \languageline{statement}{\texttt{ERVMPE}} \\ \hline + \languageline{statement}{\texttt{ERVMPE}} \\ + \languageline{statement}{\texttt{CONTINVA}} \\ \hline \languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope}} \\ \languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope} \textit{optional-newline} \textit{else-statement}} \\ \hline diff --git a/tests.py b/tests.py index 6cc4d98..da05bdc 100644 --- a/tests.py +++ b/tests.py @@ -11,7 +11,7 @@ from fractions import Fraction from centvrion.ast_nodes import ( ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini, - Designa, DesignaIndex, DumStatement, Erumpe, ExpressionStatement, ID, + Continva, Designa, DesignaIndex, DumStatement, Erumpe, ExpressionStatement, ID, Invoca, ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, String, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, fraction_to_frac, @@ -868,6 +868,62 @@ loop_edge_tests = [ ExpressionStatement(ID("cnt")), ]), ValInt(3), ""), + # PER with CONTINVA: skip odd numbers, sum evens + # [I,II,III,IV] → skip I and III; cnt increments on II and IV → cnt = III + ("DESIGNA cnt VT I\nPER i IN [I, II, III, IV] FACE {\nSI i EST I AVT i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement( + DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]), + ID("i"), + [SiStatement(BinOp(BinOp(ID("i"), Numeral("I"), "KEYWORD_EST"), BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), "KEYWORD_AVT"), [Continva()], None), + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("cnt")), + ]), + ValInt(3), ""), + # DVM with CONTINVA: skip body when x is II, increment regardless + # x goes 1→2→3; on x=2 we continue (no DICE); DICE fires for x=1 and x=3 + ("DESIGNA x VT I\nDVM x EST IV FACE {\nSI x EST II TVNC { DESIGNA x VT x + I\nCONTINVA }\nDICE(x)\nDESIGNA x VT x + I\n}\nx", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement( + BinOp(ID("x"), Numeral("IV"), "KEYWORD_EST"), + [SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"), + [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), Continva()], None), + ExpressionStatement(BuiltIn("DICE", [ID("x")])), + Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("x")), + ]), + ValInt(4), "I\nIII\n"), + # nested PER: CONTINVA in inner only skips rest of inner body; outer still increments + ("DESIGNA cnt VT I\nPER i IN [I, II] FACE {\nPER k IN [I, II] FACE {\nCONTINVA\nDESIGNA cnt VT cnt + I\n}\nDESIGNA cnt VT cnt + I\n}\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement( + DataArray([Numeral("I"), Numeral("II")]), + ID("i"), + [PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"), + [Continva(), Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))]), + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("cnt")), + ]), + ValInt(3), ""), + # DONICVM with CONTINVA: skip value III, count remaining + ("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FACE {\nSI i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt", + Program([], [ + Designa(ID("cnt"), Numeral("I")), + PerStatement( + DataRangeArray(Numeral("I"), Numeral("IV")), + ID("i"), + [SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Continva()], None), + Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))], + ), + ExpressionStatement(ID("cnt")), + ]), + ValInt(3)), # DVM condition true from start — body never runs ("DESIGNA x VT I\nDVM VERITAS FACE {\nDESIGNA x VT x + I\n}\nx", Program([], [