🐐 Step in for loop
This commit is contained in:
10
README.md
10
README.md
@@ -222,6 +222,16 @@ The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used a
|
||||
> 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.
|
||||
|
||||

|
||||
|
||||
```
|
||||
> XXV
|
||||
```
|
||||
|
||||
### DVM loops
|
||||
|
||||

|
||||
|
||||
@@ -369,19 +369,28 @@ class DataArray(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.to_value = to_value
|
||||
self.step = step
|
||||
|
||||
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:
|
||||
content_string = rep_join([self.from_value, self.to_value])
|
||||
return f"RangeArray([{content_string}])"
|
||||
parts = [self.from_value, self.to_value]
|
||||
if self.step is not None:
|
||||
parts.append(self.step)
|
||||
return f"RangeArray([{rep_join(parts)}])"
|
||||
|
||||
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):
|
||||
vtable, from_val = self.from_value.eval(vtable)
|
||||
@@ -390,7 +399,25 @@ class DataRangeArray(Node):
|
||||
raise CentvrionError("Range bounds must be numbers")
|
||||
from_int = from_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)])
|
||||
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):
|
||||
|
||||
@@ -166,6 +166,7 @@ def emit_expr(node, ctx):
|
||||
hi_lines, hi_var = emit_expr(node.to_value, ctx)
|
||||
tmp = 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)"
|
||||
lines = lo_lines + hi_lines + [
|
||||
f"CentValue {tmp} = cent_list_new({cap});",
|
||||
@@ -174,6 +175,16 @@ def emit_expr(node, ctx):
|
||||
"}",
|
||||
]
|
||||
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):
|
||||
lines = []
|
||||
|
||||
@@ -21,6 +21,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
||||
"FAC",
|
||||
"FALSITAS",
|
||||
"FVNCTIO",
|
||||
"GRADV",
|
||||
"HAVD_MINVS",
|
||||
"HAVD_PLVS",
|
||||
"INVOCA",
|
||||
|
||||
@@ -276,6 +276,11 @@ class Parser():
|
||||
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
|
||||
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
|
||||
@self.pg.production('expressions : SYMBOL_LPARENS expression_list')
|
||||
def expressions(tokens):
|
||||
@@ -394,6 +399,10 @@ class Parser():
|
||||
def range_array(tokens):
|
||||
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')
|
||||
def array_index(tokens):
|
||||
return ast_nodes.ArrayIndex(tokens[0], tokens[2])
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
\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{,} \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{ERVMPE}} \\
|
||||
\languageline{statement}{\texttt{CONTINVA}} \\
|
||||
@@ -73,7 +73,7 @@
|
||||
\languageline{literal}{\textbf{numeral}} \\
|
||||
\languageline{literal}{\textbf{bool}} \\
|
||||
\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{optional-dict-items}{\textit{dict-items}} \\
|
||||
@@ -94,6 +94,9 @@
|
||||
|
||||
\languageline{expressions}{\textit{expression}, \textit{expressions}} \\
|
||||
\languageline{expressions}{\textit{expression}} \\ \hline
|
||||
|
||||
\languageline{optional-step}{\texttt{GRADV} \textit{expression}} \\
|
||||
\languageline{optional-step}{} \\ \hline
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\end{table}
|
||||
|
||||
5
snippets/donicvm_gradv.cent
Normal file
5
snippets/donicvm_gradv.cent
Normal 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
BIN
snippets/donicvm_gradv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -78,7 +78,7 @@ contexts:
|
||||
scope: support.class.module.centvrion
|
||||
|
||||
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
|
||||
|
||||
operators:
|
||||
|
||||
86
tests.py
86
tests.py
@@ -894,6 +894,8 @@ error_tests = [
|
||||
('CVM RETE\nAVSCVLTA(LXXX)', CentvrionError), # AVSCVLTA: no routes registered
|
||||
('AVSCVLTA(LXXX)', CentvrionError), # RETE required for AVSCVLTA
|
||||
('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):
|
||||
@@ -1705,6 +1707,90 @@ loop_edge_tests = [
|
||||
ExpressionStatement(Invoca(ID("f"), [])),
|
||||
]),
|
||||
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):
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"FAC": { "prefix": "FAC", "body": "FAC", "description": "block opener for loops and functions" },
|
||||
"FALSITAS": { "prefix": "FALSITAS", "body": "FALSITAS", "description": "boolean false" },
|
||||
"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_PLVS": { "prefix": "HAVD_PLVS", "body": "HAVD_PLVS", "description": "less-or-equal (<=)" },
|
||||
"IN": { "prefix": "IN", "body": "IN", "description": "PER-loop iterator separator" },
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"patterns": [
|
||||
{
|
||||
"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",
|
||||
|
||||
Reference in New Issue
Block a user