diff --git a/README.md b/README.md index fd812f9..5f75c82 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,18 @@ The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used a > XI ``` +### AETERNVM loops + +`AETERNVM FACE { ... }` is shorthand for an infinite loop — equivalent +to `DVM FALSITAS FACE { ... }` but without relying on `DVM`'s inverted +condition. Exit the loop with `ERVMPE` (or `REDI` from inside a function). + +![AETERNVM loop](snippets/aeternvm.png) + +``` +> X +``` + ### PER loops ![PER loop](snippets/per.png) diff --git a/centvrion/lexer.py b/centvrion/lexer.py index 554dd18..6745a48 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -3,6 +3,7 @@ from rply import LexerGenerator valid_characters = '|'.join(list("abcdefghiklmnopqrstvxyz_")) keyword_tokens = [("KEYWORD_"+i, i) for i in [ + "AETERNVM", "ALVID", "AVT", "DEFINI", diff --git a/centvrion/parser.py b/centvrion/parser.py index e95ab0c..993b236 100644 --- a/centvrion/parser.py +++ b/centvrion/parser.py @@ -121,6 +121,11 @@ class Parser(): def dum(tokens): return ast_nodes.DumStatement(tokens[1], tokens[4]) + # AETERNVM is sugar for `DVM FALSITAS` — same AST, no observable difference. + @self.pg.production('dum_statement : KEYWORD_AETERNVM KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL') + def aeternvm(tokens): + return ast_nodes.DumStatement(ast_nodes.Bool(False), tokens[3]) + @self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL') def per(tokens): return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6]) diff --git a/language/main.tex b/language/main.tex index 15dd90b..e2f8866 100644 --- a/language/main.tex +++ b/language/main.tex @@ -33,6 +33,7 @@ \languageline{statement}{\texttt{DEFINI} \textbf{id} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\ \languageline{statement}{\textit{if-statement}} \\ \languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FACE} \textit{scope}} \\ + \languageline{statement}{\texttt{AETERNVM} \texttt{FACE} \textit{scope}} \\ \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{)}} \\ diff --git a/snippets/aeternvm.cent b/snippets/aeternvm.cent new file mode 100644 index 0000000..472dce8 --- /dev/null +++ b/snippets/aeternvm.cent @@ -0,0 +1,8 @@ +DESIGNA x VT NVLLVS +AETERNVM FACE { + DESIGNA x VT x+I + SI x EST X TVNC { + ERVMPE + } +} +DICE(x) diff --git a/snippets/syntaxes/centvrion.sublime-syntax b/snippets/syntaxes/centvrion.sublime-syntax index f98a1f6..566e83d 100644 --- a/snippets/syntaxes/centvrion.sublime-syntax +++ b/snippets/syntaxes/centvrion.sublime-syntax @@ -65,7 +65,7 @@ contexts: scope: support.class.module.centvrion keywords: - - match: '\b(ALVID|AVT|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TVNC|VSQVE|VT|CVM)\b' + - match: '\b(AETERNVM|ALVID|AVT|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TVNC|VSQVE|VT|CVM)\b' scope: keyword.control.centvrion operators: diff --git a/tests.py b/tests.py index 089533d..ed7322e 100644 --- a/tests.py +++ b/tests.py @@ -342,6 +342,50 @@ control_tests = [ DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DICE", [ID("x")])), Erumpe()]), ]), ValStr("I"), "I\n"), + # AETERNVM is sugar for DVM FALSITAS — must produce the same AST. + ("DESIGNA x VT I\nAETERNVM FACE {\nDICE(x)\nERVMPE\n}", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DICE", [ID("x")])), Erumpe()]), + ]), + ValStr("I"), "I\n"), + # AETERNVM with counter + ERVMPE on condition + ("DESIGNA x VT I\nAETERNVM FACE {\nSI x EST III TVNC { ERVMPE }\nDESIGNA x VT x + I\n}\nx", + Program([], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [ + SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None), + Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), + ]), + ExpressionStatement(ID("x")), + ]), + ValInt(3)), + # AETERNVM with CONTINVA — skip printing III; ERVMPE after V. + # Return value is ValNul because the iteration that triggers ERVMPE runs + # Designa first (resetting last_val); we test on output, which is the point. + ("DESIGNA x VT NVLLVS\nAETERNVM FACE {\nDESIGNA x VT x + I\nSI x PLVS V TVNC { ERVMPE }\nSI x EST III TVNC { CONTINVA }\nDICE(x)\n}", + Program([], [ + Designa(ID("x"), Nullus()), + DumStatement(Bool(False), [ + Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), + SiStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_PLVS"), [Erumpe()], None), + SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Continva()], None), + ExpressionStatement(BuiltIn("DICE", [ID("x")])), + ]), + ]), + ValNul(), "I\nII\nIV\nV\n"), + # REDI inside AETERNVM (inside DEFINI) — exits both loop and function + ( + "DEFINI f () VT {\nDESIGNA x VT I\nAETERNVM FACE {\nREDI (x)\n}\n}\nINVOCA f ()", + Program([], [ + Defini(ID("f"), [], [ + Designa(ID("x"), Numeral("I")), + DumStatement(Bool(False), [Redi([ID("x")])]), + ]), + ExpressionStatement(Invoca(ID("f"), [])), + ]), + ValInt(1), + ), # PER foreach ("PER i IN [I, II, III] FACE { DICE(i) }", Program([], [PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),