🐐 Step in for loop

This commit is contained in:
2026-04-24 18:33:48 +02:00
parent dbaf01b6a3
commit f197c2c3d5
12 changed files with 167 additions and 14 deletions

View File

@@ -222,6 +222,16 @@ The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used a
> LV > LV
``` ```
An optional `GRADV` clause sets the stride. The step must be a nonzero
integer expression; positive values ascend, negative values descend, and
the endpoint is included only when the stride lands on it exactly.
![DONICVM with GRADV](snippets/donicvm_gradv.png)
```
> XXV
```
### DVM loops ### DVM loops
![DVM loop](snippets/dvm.png) ![DVM loop](snippets/dvm.png)

View File

@@ -369,19 +369,28 @@ class DataArray(Node):
class DataRangeArray(Node): class DataRangeArray(Node):
def __init__(self, from_value, to_value) -> None: def __init__(self, from_value, to_value, step=None) -> None:
self.from_value = from_value self.from_value = from_value
self.to_value = to_value self.to_value = to_value
self.step = step
def __eq__(self, other): def __eq__(self, other):
return type(self) == type(other) and self.from_value == other.from_value and self.to_value == other.to_value return (type(self) == type(other)
and self.from_value == other.from_value
and self.to_value == other.to_value
and self.step == other.step)
def __repr__(self) -> str: def __repr__(self) -> str:
content_string = rep_join([self.from_value, self.to_value]) parts = [self.from_value, self.to_value]
return f"RangeArray([{content_string}])" if self.step is not None:
parts.append(self.step)
return f"RangeArray([{rep_join(parts)}])"
def print(self): def print(self):
return f"[{self.from_value.print()} VSQVE {self.to_value.print()}]" base = f"[{self.from_value.print()} VSQVE {self.to_value.print()}"
if self.step is not None:
base += f" GRADV {self.step.print()}"
return base + "]"
def _eval(self, vtable): def _eval(self, vtable):
vtable, from_val = self.from_value.eval(vtable) vtable, from_val = self.from_value.eval(vtable)
@@ -390,7 +399,25 @@ class DataRangeArray(Node):
raise CentvrionError("Range bounds must be numbers") raise CentvrionError("Range bounds must be numbers")
from_int = from_val.value() or 0 from_int = from_val.value() or 0
to_int = to_val.value() or 0 to_int = to_val.value() or 0
if self.step is None:
return vtable, ValList([ValInt(i) for i in range(from_int, to_int + 1)]) return vtable, ValList([ValInt(i) for i in range(from_int, to_int + 1)])
vtable, step_val = self.step.eval(vtable)
if not isinstance(step_val, ValInt):
raise CentvrionError("Range step must be a number")
step_int = step_val.value()
if step_int == 0:
raise CentvrionError("Range step cannot be zero")
items = []
i = from_int
if step_int > 0:
while i <= to_int:
items.append(ValInt(i))
i += step_int
else:
while i >= to_int:
items.append(ValInt(i))
i += step_int
return vtable, ValList(items)
class DataDict(Node): class DataDict(Node):

View File

@@ -166,6 +166,7 @@ def emit_expr(node, ctx):
hi_lines, hi_var = emit_expr(node.to_value, ctx) hi_lines, hi_var = emit_expr(node.to_value, ctx)
tmp = ctx.fresh_tmp() tmp = ctx.fresh_tmp()
i_var = ctx.fresh_tmp() i_var = ctx.fresh_tmp()
if node.step is None:
cap = f"({hi_var}.ival >= {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival + 1) : 0)" cap = f"({hi_var}.ival >= {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival + 1) : 0)"
lines = lo_lines + hi_lines + [ lines = lo_lines + hi_lines + [
f"CentValue {tmp} = cent_list_new({cap});", f"CentValue {tmp} = cent_list_new({cap});",
@@ -174,6 +175,16 @@ def emit_expr(node, ctx):
"}", "}",
] ]
return lines, tmp return lines, tmp
step_lines, step_var = emit_expr(node.step, ctx)
lines = lo_lines + hi_lines + step_lines + [
f'if ({step_var}.type != CENT_INT) cent_type_error("Range step must be a number");',
f'if ({step_var}.ival == 0) cent_runtime_error("Range step cannot be zero");',
f"CentValue {tmp} = cent_list_new(0);",
f"for (long {i_var} = {lo_var}.ival; ({step_var}.ival > 0 ? {i_var} <= {hi_var}.ival : {i_var} >= {hi_var}.ival); {i_var} += {step_var}.ival) {{",
f" cent_list_push(&{tmp}, cent_int({i_var}));",
"}",
]
return lines, tmp
if isinstance(node, DataDict): if isinstance(node, DataDict):
lines = [] lines = []

View File

@@ -21,6 +21,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
"FAC", "FAC",
"FALSITAS", "FALSITAS",
"FVNCTIO", "FVNCTIO",
"GRADV",
"HAVD_MINVS", "HAVD_MINVS",
"HAVD_PLVS", "HAVD_PLVS",
"INVOCA", "INVOCA",

View File

@@ -276,6 +276,11 @@ class Parser():
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5]) range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
return ast_nodes.PerStatement(range_array, tokens[1], tokens[8]) return ast_nodes.PerStatement(range_array, tokens[1], tokens[8])
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_GRADV expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
def donicum_step(tokens):
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5], tokens[7])
return ast_nodes.PerStatement(range_array, tokens[1], tokens[10])
# expressions # expressions
@self.pg.production('expressions : SYMBOL_LPARENS expression_list') @self.pg.production('expressions : SYMBOL_LPARENS expression_list')
def expressions(tokens): def expressions(tokens):
@@ -394,6 +399,10 @@ class Parser():
def range_array(tokens): def range_array(tokens):
return ast_nodes.DataRangeArray(tokens[1], tokens[3]) return ast_nodes.DataRangeArray(tokens[1], tokens[3])
@self.pg.production('expression : SYMBOL_LBRACKET expression KEYWORD_VSQVE expression KEYWORD_GRADV expression SYMBOL_RBRACKET')
def range_array_step(tokens):
return ast_nodes.DataRangeArray(tokens[1], tokens[3], tokens[5])
@self.pg.production('expression : expression SYMBOL_LBRACKET expression SYMBOL_RBRACKET', precedence='INDEX') @self.pg.production('expression : expression SYMBOL_LBRACKET expression SYMBOL_RBRACKET', precedence='INDEX')
def array_index(tokens): def array_index(tokens):
return ast_nodes.ArrayIndex(tokens[0], tokens[2]) return ast_nodes.ArrayIndex(tokens[0], tokens[2])

View File

@@ -41,7 +41,7 @@
\languageline{statement}{\texttt{AETERNVM} \texttt{FAC} \textit{scope}} \\ \languageline{statement}{\texttt{AETERNVM} \texttt{FAC} \textit{scope}} \\
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\ \languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
\languageline{statement}{\texttt{PER} \textbf{id}\texttt{,} \textbf{id-list} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\ \languageline{statement}{\texttt{PER} \textbf{id}\texttt{,} \textbf{id-list} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FAC} \textit{scope}} \\ \languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \textit{optional-step} \texttt{FAC} \textit{scope}} \\
\languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\ \languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\
\languageline{statement}{\texttt{ERVMPE}} \\ \languageline{statement}{\texttt{ERVMPE}} \\
\languageline{statement}{\texttt{CONTINVA}} \\ \languageline{statement}{\texttt{CONTINVA}} \\
@@ -73,7 +73,7 @@
\languageline{literal}{\textbf{numeral}} \\ \languageline{literal}{\textbf{numeral}} \\
\languageline{literal}{\textbf{bool}} \\ \languageline{literal}{\textbf{bool}} \\
\languageline{literal}{\texttt{[} \textit{optional-expressions} \texttt{]}} \\ \languageline{literal}{\texttt{[} \textit{optional-expressions} \texttt{]}} \\
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]} \textnormal{\small\ (inclusive on both ends)}} \\ \languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \textit{optional-step} \texttt{]} \textnormal{\small\ (inclusive on both ends)}} \\
\languageline{literal}{\texttt{TABVLA} \texttt{\{} \textit{optional-dict-items} \texttt{\}}} \\ \hline \languageline{literal}{\texttt{TABVLA} \texttt{\{} \textit{optional-dict-items} \texttt{\}}} \\ \hline
\languageline{optional-dict-items}{\textit{dict-items}} \\ \languageline{optional-dict-items}{\textit{dict-items}} \\
@@ -94,6 +94,9 @@
\languageline{expressions}{\textit{expression}, \textit{expressions}} \\ \languageline{expressions}{\textit{expression}, \textit{expressions}} \\
\languageline{expressions}{\textit{expression}} \\ \hline \languageline{expressions}{\textit{expression}} \\ \hline
\languageline{optional-step}{\texttt{GRADV} \textit{expression}} \\
\languageline{optional-step}{} \\ \hline
\end{tabular} \end{tabular}
\end{center} \end{center}
\end{table} \end{table}

View File

@@ -0,0 +1,5 @@
DESIGNA s VT NVLLVS
DONICVM i VT I VSQVE X GRADV II FAC {
s AVGE i
}
DIC(s)

BIN
snippets/donicvm_gradv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -78,7 +78,7 @@ contexts:
scope: support.class.module.centvrion scope: support.class.module.centvrion
keywords: keywords:
- match: '\b(HAVD_PLVS|HAVD_MINVS|AETERNVM|ALIVD|AVGE|AVT|CAPE|CONTINVA|DEFINI|DESIGNA|DISPAR|DIVIDE|DONICVM|DVM|ERVMPE|EST|ET|FAC|FVNCTIO|INVOCA|IN|MINVE|MINVS|MVLTIPLICA|NON|PER|PLVS|REDI|RELIQVVM|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT|CVM)\b' - match: '\b(HAVD_PLVS|HAVD_MINVS|AETERNVM|ALIVD|AVGE|AVT|CAPE|CONTINVA|DEFINI|DESIGNA|DISPAR|DIVIDE|DONICVM|DVM|ERVMPE|EST|ET|FAC|FVNCTIO|GRADV|INVOCA|IN|MINVE|MINVS|MVLTIPLICA|NON|PER|PLVS|REDI|RELIQVVM|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT|CVM)\b'
scope: keyword.control.centvrion scope: keyword.control.centvrion
operators: operators:

View File

@@ -894,6 +894,8 @@ error_tests = [
('CVM RETE\nAVSCVLTA(LXXX)', CentvrionError), # AVSCVLTA: no routes registered ('CVM RETE\nAVSCVLTA(LXXX)', CentvrionError), # AVSCVLTA: no routes registered
('AVSCVLTA(LXXX)', CentvrionError), # RETE required for AVSCVLTA ('AVSCVLTA(LXXX)', CentvrionError), # RETE required for AVSCVLTA
('CVM RETE\nPETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})\nAVSCVLTA("text")', CentvrionError), # AVSCVLTA port must be integer ('CVM RETE\nPETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})\nAVSCVLTA("text")', CentvrionError), # AVSCVLTA port must be integer
("DONICVM i VT I VSQVE X GRADV I - I FAC { DIC(i) }", CentvrionError), # GRADV zero step
('DONICVM i VT I VSQVE X GRADV "foo" FAC { DIC(i) }', CentvrionError), # GRADV non-integer step
] ]
class TestErrors(unittest.TestCase): class TestErrors(unittest.TestCase):
@@ -1705,6 +1707,90 @@ loop_edge_tests = [
ExpressionStatement(Invoca(ID("f"), [])), ExpressionStatement(Invoca(ID("f"), [])),
]), ]),
ValInt(4)), # returns b=IV when a=III ValInt(4)), # returns b=IV when a=III
# DONICVM GRADV II, endpoint hit exactly: [I, III, V]
("DONICVM i VT I VSQVE V GRADV II FAC { DIC(i) }",
Program([], [PerStatement(
DataRangeArray(Numeral("I"), Numeral("V"), Numeral("II")),
ID("i"),
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("V"), "I\nIII\nV\n"),
# DONICVM GRADV II, endpoint overshot: VI excluded, stops at V
("DONICVM i VT I VSQVE VI GRADV II FAC { DIC(i) }",
Program([], [PerStatement(
DataRangeArray(Numeral("I"), Numeral("VI"), Numeral("II")),
ID("i"),
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("V"), "I\nIII\nV\n"),
# DONICVM GRADV I is equivalent to default step
("DONICVM i VT I VSQVE III GRADV I FAC { DIC(i) }",
Program([], [PerStatement(
DataRangeArray(Numeral("I"), Numeral("III"), Numeral("I")),
ID("i"),
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("III"), "I\nII\nIII\n"),
# DONICVM descending by I
("CVM SVBNVLLA\nDONICVM i VT V VSQVE I GRADV - I FAC { DIC(i) }",
Program([ModuleCall("SVBNVLLA")], [PerStatement(
DataRangeArray(Numeral("V"), Numeral("I"), UnaryMinus(Numeral("I"))),
ID("i"),
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("I"), "V\nIV\nIII\nII\nI\n"),
# DONICVM descending by II, endpoint overshot
("CVM SVBNVLLA\nDONICVM i VT X VSQVE I GRADV - II FAC { DIC(i) }",
Program([ModuleCall("SVBNVLLA")], [PerStatement(
DataRangeArray(Numeral("X"), Numeral("I"), UnaryMinus(Numeral("II"))),
ID("i"),
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("II"), "X\nVIII\nVI\nIV\nII\n"),
# DONICVM with step bound to a variable
("DESIGNA s VT II\nDONICVM i VT I VSQVE V GRADV s FAC { DIC(i) }",
Program([], [
Designa(ID("s"), Numeral("II")),
PerStatement(
DataRangeArray(Numeral("I"), Numeral("V"), ID("s")),
ID("i"),
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("V"), "I\nIII\nV\n"),
# DONICVM from == to with nonzero step: single iteration
("DONICVM i VT III VSQVE III GRADV II FAC { DIC(i) }",
Program([], [PerStatement(
DataRangeArray(Numeral("III"), Numeral("III"), Numeral("II")),
ID("i"),
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("III"), "III\n"),
# DONICVM direction mismatch — negative step with ascending bounds: body never runs
("CVM SVBNVLLA\nDESIGNA x VT NVLLVS\nDONICVM i VT I VSQVE X GRADV - I FAC { DESIGNA x VT x + i }\nx",
Program([ModuleCall("SVBNVLLA")], [
Designa(ID("x"), Nullus()),
PerStatement(
DataRangeArray(Numeral("I"), Numeral("X"), UnaryMinus(Numeral("I"))),
ID("i"),
[Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]),
ExpressionStatement(ID("x"))]),
ValNul(), ""),
# Range-array literal with GRADV used via PER
("PER i IN [I VSQVE V GRADV II] FAC { DIC(i) }",
Program([], [PerStatement(
DataRangeArray(Numeral("I"), Numeral("V"), Numeral("II")),
ID("i"),
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("V"), "I\nIII\nV\n"),
# DONICVM GRADV II with CONTINVA: skip value V, print the others
("DONICVM i VT I VSQVE IX GRADV II FAC {\nSI i EST V TVNC { CONTINVA }\nDIC(i)\n}",
Program([], [PerStatement(
DataRangeArray(Numeral("I"), Numeral("IX"), Numeral("II")),
ID("i"),
[SiStatement(BinOp(ID("i"), Numeral("V"), "KEYWORD_EST"), [Continva()], None),
ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("IX"), "I\nIII\nVII\nIX\n"),
# DONICVM GRADV II with ERVMPE: stop at V (last successful DIC was III)
("DONICVM i VT I VSQVE IX GRADV II FAC {\nSI i EST V TVNC { ERVMPE }\nDIC(i)\n}",
Program([], [PerStatement(
DataRangeArray(Numeral("I"), Numeral("IX"), Numeral("II")),
ID("i"),
[SiStatement(BinOp(ID("i"), Numeral("V"), "KEYWORD_EST"), [Erumpe()], None),
ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("III"), "I\nIII\n"),
] ]
class TestLoopEdge(unittest.TestCase): class TestLoopEdge(unittest.TestCase):

View File

@@ -41,6 +41,7 @@
"FAC": { "prefix": "FAC", "body": "FAC", "description": "block opener for loops and functions" }, "FAC": { "prefix": "FAC", "body": "FAC", "description": "block opener for loops and functions" },
"FALSITAS": { "prefix": "FALSITAS", "body": "FALSITAS", "description": "boolean false" }, "FALSITAS": { "prefix": "FALSITAS", "body": "FALSITAS", "description": "boolean false" },
"FVNCTIO": { "prefix": "FVNCTIO", "body": "FVNCTIO", "description": "anonymous function expression" }, "FVNCTIO": { "prefix": "FVNCTIO", "body": "FVNCTIO", "description": "anonymous function expression" },
"GRADV": { "prefix": "GRADV", "body": "GRADV", "description": "DONICVM range step (by a step of)" },
"HAVD_MINVS": { "prefix": "HAVD_MINVS", "body": "HAVD_MINVS", "description": "greater-or-equal (>=)" }, "HAVD_MINVS": { "prefix": "HAVD_MINVS", "body": "HAVD_MINVS", "description": "greater-or-equal (>=)" },
"HAVD_PLVS": { "prefix": "HAVD_PLVS", "body": "HAVD_PLVS", "description": "less-or-equal (<=)" }, "HAVD_PLVS": { "prefix": "HAVD_PLVS", "body": "HAVD_PLVS", "description": "less-or-equal (<=)" },
"IN": { "prefix": "IN", "body": "IN", "description": "PER-loop iterator separator" }, "IN": { "prefix": "IN", "body": "IN", "description": "PER-loop iterator separator" },

View File

@@ -45,7 +45,7 @@
"patterns": [ "patterns": [
{ {
"name": "keyword.control.cent", "name": "keyword.control.cent",
"match": "\\b(AETERNVM|ALIVD|AVGE|AVT|CAPE|CONTINVA|CVM|DEFINI|DESIGNA|DIVIDE|DONICVM|DVM|ERVMPE|ET|FAC|FVNCTIO|IN|INVOCA|MINVE|MVLTIPLICA|NON|PER|REDI|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT)\\b" "match": "\\b(AETERNVM|ALIVD|AVGE|AVT|CAPE|CONTINVA|CVM|DEFINI|DESIGNA|DIVIDE|DONICVM|DVM|ERVMPE|ET|FAC|FVNCTIO|GRADV|IN|INVOCA|MINVE|MVLTIPLICA|NON|PER|REDI|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT)\\b"
}, },
{ {
"name": "keyword.operator.comparison.cent", "name": "keyword.operator.comparison.cent",