Compare commits

...

4 Commits

Author SHA1 Message Date
27c5f7bf56 🐐 Index assignment 2026-04-22 16:10:11 +02:00
5418dfa577 🐐 PER deconstructing 2026-04-22 15:43:32 +02:00
60fe691731 🐐 Array concat 2026-04-22 15:35:51 +02:00
634c5a2f93 🐐 Arrays with newlines 2026-04-22 15:29:38 +02:00
12 changed files with 464 additions and 68 deletions

View File

@@ -136,6 +136,19 @@ Individual elements can be accessed by index using square brackets. Indexing is
> I > I
``` ```
Arrays are concatenated with `@`:
```
DESIGNA x VT [I, II, III] @ [IV, V]
DIC x
```
```
> [I, II, III, IV, V]
```
Both operands must be arrays — using `@` on non-arrays raises an error.
A sub-array can be extracted with `VSQVE` inside the index brackets. Both bounds are inclusive and 1-based: A sub-array can be extracted with `VSQVE` inside the index brackets. Both bounds are inclusive and 1-based:
![Array slicing](snippets/array_slice.png) ![Array slicing](snippets/array_slice.png)
@@ -240,6 +253,19 @@ condition. Exit the loop with `ERVMPE` (or `REDI` from inside a function).
> V > V
``` ```
Variables can be unpacked in `PER` loops, similar to `DESIGNA` destructuring:
```
PER a, b IN [[I, II], [III, IV]] FAC {
DIC(a + b)
}
```
```
> III
> VII
```
## Error handling ## Error handling
Errors can be caught using `TEMPTA` (temptare = to try) and `CAPE` (capere = to catch). The `CAPE` block binds the error message to a variable as a string. Errors can be caught using `TEMPTA` (temptare = to try) and `CAPE` (capere = to catch). The `CAPE` block binds the error message to a variable as a string.

View File

@@ -59,7 +59,7 @@ def rep_join(l):
OP_STR = { OP_STR = {
"SYMBOL_PLUS": "+", "SYMBOL_MINUS": "-", "SYMBOL_PLUS": "+", "SYMBOL_MINUS": "-",
"SYMBOL_TIMES": "*", "SYMBOL_DIVIDE": "/", "SYMBOL_TIMES": "*", "SYMBOL_DIVIDE": "/",
"SYMBOL_AMPERSAND": "&", "SYMBOL_AMPERSAND": "&", "SYMBOL_AT": "@",
"KEYWORD_RELIQVVM": "RELIQVVM", "KEYWORD_RELIQVVM": "RELIQVVM",
"KEYWORD_EST": "EST", "KEYWORD_DISPAR": "DISPAR", "KEYWORD_EST": "EST", "KEYWORD_DISPAR": "DISPAR",
"KEYWORD_MINVS": "MINVS", "KEYWORD_MINVS": "MINVS",
@@ -598,42 +598,97 @@ class Designa(Node):
return vtable, ValNul() return vtable, ValNul()
def _index_get(container, index):
if isinstance(container, ValDict):
if not isinstance(index, (ValStr, ValInt)):
raise CentvrionError("Dict key must be a string or integer")
d = container.value()
k = index.value()
if k not in d:
raise CentvrionError("Key not found in dict")
return d[k]
if isinstance(container, ValList):
i = index.value()
lst = container.value()
if i < 1 or i > len(lst):
raise CentvrionError(f"Index {i} out of range for array of length {len(lst)}")
return lst[i - 1]
if isinstance(container, ValStr):
if isinstance(index, ValInt):
i = index.value()
elif isinstance(index, ValFrac) and index.value().denominator == 1:
i = index.value().numerator
else:
raise CentvrionError("String index must be a number")
s = container.value()
if i < 1 or i > len(s):
raise CentvrionError(f"Index {i} out of range for string of length {len(s)}")
return ValStr(s[i - 1])
raise CentvrionError("Cannot index into a non-array, non-dict value")
def _index_set(container, index, value):
if isinstance(container, ValDict):
if not isinstance(index, (ValStr, ValInt)):
raise CentvrionError("Dict key must be a string or integer")
d = dict(container.value())
d[index.value()] = value
return ValDict(d)
if isinstance(container, ValList):
i = index.value()
lst = list(container.value())
if i < 1 or i > len(lst):
raise CentvrionError(f"Index {i} out of range for array of length {len(lst)}")
lst[i - 1] = value
return ValList(lst)
if isinstance(container, ValStr):
if isinstance(index, ValInt):
i = index.value()
elif isinstance(index, ValFrac) and index.value().denominator == 1:
i = index.value().numerator
else:
raise CentvrionError("String index must be a number")
if not isinstance(value, ValStr) or len(value.value()) != 1:
raise CentvrionError("String index assignment requires a single character")
s = container.value()
if i < 1 or i > len(s):
raise CentvrionError(f"Index {i} out of range for string of length {len(s)}")
return ValStr(s[:i - 1] + value.value() + s[i:])
raise CentvrionError("Cannot assign to index of a non-array, non-dict value")
class DesignaIndex(Node): class DesignaIndex(Node):
def __init__(self, variable: ID, index, value) -> None: def __init__(self, variable: ID, indices, value) -> None:
self.id = variable self.id = variable
self.index = index self.indices = indices if isinstance(indices, list) else [indices]
self.value = value self.value = value
def __eq__(self, other): def __eq__(self, other):
return type(self) == type(other) and self.id == other.id and self.index == other.index and self.value == other.value return type(self) == type(other) and self.id == other.id and self.indices == other.indices and self.value == other.value
def __repr__(self) -> str: def __repr__(self) -> str:
return f"DesignaIndex({self.id!r}, {self.index!r}, {self.value!r})" return f"DesignaIndex({self.id!r}, {self.indices!r}, {self.value!r})"
def print(self): def print(self):
return f"DESIGNA {self.id.print()}[{self.index.print()}] VT {self.value.print()}" idx_str = ''.join(f'[{idx.print()}]' for idx in self.indices)
return f"DESIGNA {self.id.print()}{idx_str} VT {self.value.print()}"
def _eval(self, vtable): def _eval(self, vtable):
vtable, index = self.index.eval(vtable) evaluated_indices = []
for idx_expr in self.indices:
vtable, idx_val = idx_expr.eval(vtable)
evaluated_indices.append(idx_val)
vtable, val = self.value.eval(vtable) vtable, val = self.value.eval(vtable)
if self.id.name not in vtable: if self.id.name not in vtable:
raise CentvrionError(f"Undefined variable: {self.id.name}") raise CentvrionError(f"Undefined variable: {self.id.name}")
target = vtable[self.id.name] root = vtable[self.id.name]
if isinstance(target, ValDict): containers = [root]
if not isinstance(index, (ValStr, ValInt)): for idx in evaluated_indices[:-1]:
raise CentvrionError("Dict key must be a string or integer") containers.append(_index_get(containers[-1], idx))
d = dict(target.value()) new_val = _index_set(containers[-1], evaluated_indices[-1], val)
d[index.value()] = val for i in range(len(containers) - 2, -1, -1):
vtable[self.id.name] = ValDict(d) new_val = _index_set(containers[i], evaluated_indices[i], new_val)
return vtable, ValNul() vtable[self.id.name] = new_val
if not isinstance(target, ValList):
raise CentvrionError(f"{self.id.name} is not an array or dict")
i = index.value()
lst = list(target.value())
if i < 1 or i > len(lst):
raise CentvrionError(f"Index {i} out of range for array of length {len(lst)}")
lst[i - 1] = val
vtable[self.id.name] = ValList(lst)
return vtable, ValNul() return vtable, ValNul()
@@ -833,6 +888,10 @@ class BinOp(Node):
return vtable, ValNul() return vtable, ValNul()
result = (lv or 0) + (rv or 0) result = (lv or 0) + (rv or 0)
return vtable, ValFrac(result) if isinstance(result, Fraction) else ValInt(result) return vtable, ValFrac(result) if isinstance(result, Fraction) else ValInt(result)
case "SYMBOL_AT":
if not isinstance(left, ValList) or not isinstance(right, ValList):
raise CentvrionError("@ requires two arrays")
return vtable, ValList(list(lv) + list(rv))
case "SYMBOL_AMPERSAND": case "SYMBOL_AMPERSAND":
magnvm = "MAGNVM" in vtable["#modules"] magnvm = "MAGNVM" in vtable["#modules"]
svbnvlla = "SVBNVLLA" in vtable["#modules"] svbnvlla = "SVBNVLLA" in vtable["#modules"]
@@ -1136,6 +1195,10 @@ class PerStatement(Node):
def __eq__(self, other): def __eq__(self, other):
return type(self) == type(other) and self.data_list == other.data_list and self.variable_name == other.variable_name and self.statements == other.statements return type(self) == type(other) and self.data_list == other.data_list and self.variable_name == other.variable_name and self.statements == other.statements
@property
def destructure(self):
return isinstance(self.variable_name, list)
def __repr__(self) -> str: def __repr__(self) -> str:
test = repr(self.data_list) test = repr(self.data_list)
variable_name = repr(self.variable_name) variable_name = repr(self.variable_name)
@@ -1145,7 +1208,23 @@ class PerStatement(Node):
def print(self): def print(self):
body = "\n".join(s.print() for s in self.statements) body = "\n".join(s.print() for s in self.statements)
return f"PER {self.variable_name.print()} IN {self.data_list.print()} FAC {{\n{body}\n}}" if self.destructure:
var_str = ", ".join(v.print() for v in self.variable_name)
else:
var_str = self.variable_name.print()
return f"PER {var_str} IN {self.data_list.print()} FAC {{\n{body}\n}}"
def _assign_loop_var(self, vtable, item):
if self.destructure:
if not isinstance(item, ValList):
raise CentvrionError("Cannot destructure non-array value in PER loop")
if len(item.value()) != len(self.variable_name):
raise CentvrionError(
f"Destructuring mismatch: {len(self.variable_name)} targets, {len(item.value())} values")
for id_node, val in zip(self.variable_name, item.value()):
vtable[id_node.name] = val
else:
vtable[self.variable_name.name] = item
def _eval(self, vtable): def _eval(self, vtable):
vtable, array = self.data_list.eval(vtable) vtable, array = self.data_list.eval(vtable)
@@ -1154,10 +1233,9 @@ class PerStatement(Node):
array = ValList(keys) array = ValList(keys)
if not isinstance(array, ValList): if not isinstance(array, ValList):
raise CentvrionError("PER requires an array or dict") raise CentvrionError("PER requires an array or dict")
variable_name = self.variable_name.name
last_val = ValNul() last_val = ValNul()
for item in array: for item in array:
vtable[variable_name] = item self._assign_loop_var(vtable, item)
for statement in self.statements: for statement in self.statements:
vtable, val = statement.eval(vtable) vtable, val = statement.eval(vtable)
if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None: if vtable["#break"] or vtable["#continue"] or vtable["#return"] is not None:

View File

@@ -13,6 +13,7 @@ _BINOP_FN = {
"SYMBOL_TIMES": "cent_mul", "SYMBOL_TIMES": "cent_mul",
"SYMBOL_DIVIDE": "cent_div", "SYMBOL_DIVIDE": "cent_div",
"SYMBOL_AMPERSAND": "cent_concat", "SYMBOL_AMPERSAND": "cent_concat",
"SYMBOL_AT": "cent_array_concat",
"KEYWORD_RELIQVVM": "cent_mod", "KEYWORD_RELIQVVM": "cent_mod",
"KEYWORD_EST": "cent_eq", "KEYWORD_EST": "cent_eq",
"KEYWORD_DISPAR": "cent_neq", "KEYWORD_DISPAR": "cent_neq",

View File

@@ -16,16 +16,32 @@ def emit_stmt(node, ctx):
return val_lines + [f'cent_scope_set(&_scope, "{node.id.name}", {val_var});'] return val_lines + [f'cent_scope_set(&_scope, "{node.id.name}", {val_var});']
if isinstance(node, DesignaIndex): if isinstance(node, DesignaIndex):
idx_lines, idx_var = emit_expr(node.index, ctx) lines = []
idx_vars = []
for idx_expr in node.indices:
idx_lines, idx_var = emit_expr(idx_expr, ctx)
lines += idx_lines
idx_vars.append(idx_var)
val_lines, val_var = emit_expr(node.value, ctx) val_lines, val_var = emit_expr(node.value, ctx)
arr_tmp = ctx.fresh_tmp() lines += val_lines
return ( root_tmp = ctx.fresh_tmp()
idx_lines + val_lines + [ lines.append(f'CentValue {root_tmp} = cent_scope_get(&_scope, "{node.id.name}");')
f'CentValue {arr_tmp} = cent_scope_get(&_scope, "{node.id.name}");', if len(idx_vars) == 1:
f"cent_list_index_set(&{arr_tmp}, {idx_var}, {val_var});", lines.append(f"cent_list_index_set(&{root_tmp}, {idx_vars[0]}, {val_var});")
f'cent_scope_set(&_scope, "{node.id.name}", {arr_tmp});', else:
] # Walk down to collect intermediate containers
) container_tmps = [root_tmp]
for idx_var in idx_vars[:-1]:
tmp = ctx.fresh_tmp()
lines.append(f"CentValue {tmp} = cent_list_index({container_tmps[-1]}, {idx_var});")
container_tmps.append(tmp)
# Set at deepest level
lines.append(f"cent_list_index_set(&{container_tmps[-1]}, {idx_vars[-1]}, {val_var});")
# Rebuild up the chain
for i in range(len(container_tmps) - 2, -1, -1):
lines.append(f"cent_list_index_set(&{container_tmps[i]}, {idx_vars[i]}, {container_tmps[i + 1]});")
lines.append(f'cent_scope_set(&_scope, "{node.id.name}", {root_tmp});')
return lines
if isinstance(node, DesignaDestructure): if isinstance(node, DesignaDestructure):
n = len(node.ids) n = len(node.ids)
@@ -65,23 +81,44 @@ def emit_stmt(node, ctx):
if isinstance(node, PerStatement): if isinstance(node, PerStatement):
arr_lines, arr_var = emit_expr(node.data_list, ctx) arr_lines, arr_var = emit_expr(node.data_list, ctx)
i_var = ctx.fresh_tmp() i_var = ctx.fresh_tmp()
var_name = node.variable_name.name
body_lines = _emit_body(node.statements, ctx) body_lines = _emit_body(node.statements, ctx)
lines = arr_lines + [
f"if ({arr_var}.type == CENT_DICT) {{", if node.destructure:
f" for (int {i_var} = 0; {i_var} < {arr_var}.dval.len; {i_var}++) {{", # Destructuring PER — each element must be a list
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.dval.keys[{i_var}]);', elem_var = ctx.fresh_tmp()
] assign_lines = [
lines += [f" {l}" for l in body_lines] f"CentValue {elem_var} = {arr_var}.lval.items[{i_var}];",
lines += [ f'if ({elem_var}.type != CENT_LIST) cent_type_error("Cannot destructure non-array value in PER loop");',
" }", f'if ({elem_var}.lval.len != {len(node.variable_name)}) cent_runtime_error("Destructuring mismatch");',
"} else {", ]
f' if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array or dict");', for j, id_node in enumerate(node.variable_name):
f" for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{", tmp = ctx.fresh_tmp()
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.lval.items[{i_var}]);', assign_lines.append(f"CentValue {tmp} = cent_list_index({elem_var}, cent_int({j + 1}));")
] assign_lines.append(f'cent_scope_set(&_scope, "{id_node.name}", {tmp});')
lines += [f" {l}" for l in body_lines] lines = arr_lines + [
lines += [" }", "}"] f'if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array");',
f"for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
]
lines += [f" {l}" for l in assign_lines]
lines += [f" {l}" for l in body_lines]
lines += ["}"]
else:
var_name = node.variable_name.name
lines = arr_lines + [
f"if ({arr_var}.type == CENT_DICT) {{",
f" for (int {i_var} = 0; {i_var} < {arr_var}.dval.len; {i_var}++) {{",
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.dval.keys[{i_var}]);',
]
lines += [f" {l}" for l in body_lines]
lines += [
" }",
"} else {",
f' if ({arr_var}.type != CENT_LIST) cent_type_error("PER requires an array or dict");',
f" for (int {i_var} = 0; {i_var} < {arr_var}.lval.len; {i_var}++) {{",
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.lval.items[{i_var}]);',
]
lines += [f" {l}" for l in body_lines]
lines += [" }", "}"]
return lines return lines
if isinstance(node, Defini): if isinstance(node, Defini):

View File

@@ -409,6 +409,18 @@ CentValue cent_add(CentValue a, CentValue b) {
return cent_null(); return cent_null();
} }
CentValue cent_array_concat(CentValue a, CentValue b) {
if (a.type != CENT_LIST || b.type != CENT_LIST)
cent_type_error("'@' requires two arrays");
int total = a.lval.len + b.lval.len;
CentValue result = cent_list_new(total);
for (int i = 0; i < a.lval.len; i++)
cent_list_push(&result, a.lval.items[i]);
for (int i = 0; i < b.lval.len; i++)
cent_list_push(&result, b.lval.items[i]);
return result;
}
CentValue cent_concat(CentValue a, CentValue b) { CentValue cent_concat(CentValue a, CentValue b) {
const char *sa = (a.type == CENT_NULL) ? "" : cent_make_string(a); const char *sa = (a.type == CENT_NULL) ? "" : cent_make_string(a);
const char *sb = (b.type == CENT_NULL) ? "" : cent_make_string(b); const char *sb = (b.type == CENT_NULL) ? "" : cent_make_string(b);
@@ -852,8 +864,23 @@ void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v) {
cent_dict_set(lst, idx, v); cent_dict_set(lst, idx, v);
return; return;
} }
if (lst->type == CENT_STR) {
if (idx.type != CENT_INT)
cent_type_error("string index must be an integer");
if (v.type != CENT_STR || strlen(v.sval) != 1)
cent_type_error("string index assignment requires a single character");
long slen = (long)strlen(lst->sval);
long i = idx.ival;
if (i < 1 || i > slen)
cent_runtime_error("string index out of range");
char *buf = cent_arena_alloc(cent_arena, slen + 1);
memcpy(buf, lst->sval, slen + 1);
buf[i - 1] = v.sval[0];
lst->sval = buf;
return;
}
if (lst->type != CENT_LIST) if (lst->type != CENT_LIST)
cent_type_error("index-assign requires a list or dict"); cent_type_error("index-assign requires a list, dict, or string");
if (idx.type != CENT_INT) if (idx.type != CENT_INT)
cent_type_error("list index must be an integer"); cent_type_error("list index must be an integer");
long i = idx.ival; long i = idx.ival;

View File

@@ -198,6 +198,7 @@ char *cent_make_string(CentValue v);
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
CentValue cent_add(CentValue a, CentValue b); /* INT+INT or FRAC+FRAC/INT */ CentValue cent_add(CentValue a, CentValue b); /* INT+INT or FRAC+FRAC/INT */
CentValue cent_array_concat(CentValue a, CentValue b); /* @ operator: concatenate two arrays */
CentValue cent_concat(CentValue a, CentValue b); /* & operator: coerce all types to str */ CentValue cent_concat(CentValue a, CentValue b); /* & operator: coerce all types to str */
CentValue cent_sub(CentValue a, CentValue b); /* INT-INT or FRAC-FRAC/INT */ CentValue cent_sub(CentValue a, CentValue b); /* INT-INT or FRAC-FRAC/INT */
CentValue cent_mul(CentValue a, CentValue b); /* INT*INT or FRAC*FRAC/INT */ CentValue cent_mul(CentValue a, CentValue b); /* INT*INT or FRAC*FRAC/INT */

View File

@@ -94,6 +94,7 @@ symbol_tokens = [
("SYMBOL_TIMES", r"\*"), ("SYMBOL_TIMES", r"\*"),
("SYMBOL_DIVIDE", r"\/"), ("SYMBOL_DIVIDE", r"\/"),
("SYMBOL_AMPERSAND", r"&"), ("SYMBOL_AMPERSAND", r"&"),
("SYMBOL_AT", r"@"),
("SYMBOL_COMMA", r",") ("SYMBOL_COMMA", r",")
] ]

View File

@@ -116,7 +116,7 @@ class Parser():
('left', ["KEYWORD_AVT"]), ('left', ["KEYWORD_AVT"]),
('left', ["KEYWORD_ET"]), ('left', ["KEYWORD_ET"]),
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST", "KEYWORD_DISPAR"]), ('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST", "KEYWORD_DISPAR"]),
('left', ["SYMBOL_AMPERSAND", "SYMBOL_PLUS", "SYMBOL_MINUS"]), ('left', ["SYMBOL_AMPERSAND", "SYMBOL_AT", "SYMBOL_PLUS", "SYMBOL_MINUS"]),
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE", "KEYWORD_RELIQVVM"]), ('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE", "KEYWORD_RELIQVVM"]),
('right', ["UMINUS", "UNOT"]), ('right', ["UMINUS", "UNOT"]),
('left', ["SYMBOL_LBRACKET", "INDEX"]), ('left', ["SYMBOL_LBRACKET", "INDEX"]),
@@ -173,9 +173,17 @@ class Parser():
def statement_designa(tokens): def statement_designa(tokens):
return ast_nodes.Designa(tokens[1], tokens[3]) return ast_nodes.Designa(tokens[1], tokens[3])
@self.pg.production('statement : KEYWORD_DESIGNA id SYMBOL_LBRACKET expression SYMBOL_RBRACKET KEYWORD_VT expression') @self.pg.production('index_chain : SYMBOL_LBRACKET expression SYMBOL_RBRACKET')
def index_chain_single(tokens):
return [tokens[1]]
@self.pg.production('index_chain : SYMBOL_LBRACKET expression SYMBOL_RBRACKET index_chain')
def index_chain_multi(tokens):
return [tokens[1]] + tokens[3]
@self.pg.production('statement : KEYWORD_DESIGNA id index_chain KEYWORD_VT expression')
def statement_designa_index(tokens): def statement_designa_index(tokens):
return ast_nodes.DesignaIndex(tokens[1], tokens[3], tokens[6]) return ast_nodes.DesignaIndex(tokens[1], tokens[2], tokens[4])
@self.pg.production('statement : KEYWORD_DESIGNA id SYMBOL_COMMA id_list_rest KEYWORD_VT expression') @self.pg.production('statement : KEYWORD_DESIGNA id SYMBOL_COMMA id_list_rest KEYWORD_VT expression')
def statement_designa_destructure(tokens): def statement_designa_destructure(tokens):
@@ -242,6 +250,10 @@ class Parser():
def aeternvm(tokens): def aeternvm(tokens):
return ast_nodes.DumStatement(ast_nodes.Bool(False), tokens[3]) return ast_nodes.DumStatement(ast_nodes.Bool(False), tokens[3])
@self.pg.production('per_statement : KEYWORD_PER id SYMBOL_COMMA id_list_rest KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
def per_destructure(tokens):
return ast_nodes.PerStatement(tokens[5], [tokens[1]] + tokens[3], tokens[8])
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL') @self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
def per(tokens): def per(tokens):
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6]) return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
@@ -273,14 +285,14 @@ class Parser():
@self.pg.production('array_items : ') @self.pg.production('array_items : ')
@self.pg.production('array_items : expression') @self.pg.production('array_items : expression')
@self.pg.production('array_items : expression SYMBOL_COMMA array_items') @self.pg.production('array_items : expression SYMBOL_COMMA opt_newline array_items')
def array_items(calls): def array_items(calls):
if len(calls) == 0: if len(calls) == 0:
return [] return []
elif len(calls) == 1: elif len(calls) == 1:
return [calls[0]] return [calls[0]]
else: else:
return [calls[0]] + calls[2] return [calls[0]] + calls[3]
@self.pg.production('expression : id') @self.pg.production('expression : id')
def expression_id(tokens): def expression_id(tokens):
@@ -311,6 +323,7 @@ class Parser():
def expression_nullus(_): def expression_nullus(_):
return ast_nodes.Nullus() return ast_nodes.Nullus()
@self.pg.production('expression : expression SYMBOL_AT expression')
@self.pg.production('expression : expression SYMBOL_AMPERSAND expression') @self.pg.production('expression : expression SYMBOL_AMPERSAND expression')
@self.pg.production('expression : expression SYMBOL_MINUS expression') @self.pg.production('expression : expression SYMBOL_MINUS expression')
@self.pg.production('expression : expression SYMBOL_PLUS expression') @self.pg.production('expression : expression SYMBOL_PLUS expression')

View File

@@ -38,6 +38,7 @@
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FAC} \textit{scope}} \\ \languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FAC} \textit{scope}} \\
\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{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} \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}} \\
@@ -105,7 +106,7 @@
\item \textbf{interpolated-string}: \\ A double-quoted string containing \texttt{\{}\textit{expression}\texttt{\}} segments. Each expression is evaluated and coerced to a string. Use \texttt{\{\{} and \texttt{\}\}} for literal braces. \item \textbf{interpolated-string}: \\ A double-quoted string containing \texttt{\{}\textit{expression}\texttt{\}} segments. Each expression is evaluated and coerced to a string. Use \texttt{\{\{} and \texttt{\}\}} for literal braces.
\item \textbf{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM. \item \textbf{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM.
\item \textbf{bool}: \\ VERITAS or FALSITAS. \item \textbf{bool}: \\ VERITAS or FALSITAS.
\item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{RELIQVVM} (modulo), \texttt{EST} (equality), \texttt{DISPAR} (not-equal), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string concatenation). \item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{RELIQVVM} (modulo), \texttt{EST} (equality), \texttt{DISPAR} (not-equal), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string concatenation), \texttt{@} (array concatenation).
\item \textbf{unop}: \\ Unary operators: \texttt{-} (negation), \texttt{NON} (boolean not). \item \textbf{unop}: \\ Unary operators: \texttt{-} (negation), \texttt{NON} (boolean not).
\end{itemize} \end{itemize}

View File

@@ -82,7 +82,7 @@ contexts:
scope: keyword.control.centvrion scope: keyword.control.centvrion
operators: operators:
- match: '[+\-*/&]' - match: '[+\-*/&@]'
scope: keyword.operator.centvrion scope: keyword.operator.centvrion
identifiers: identifiers:

227
tests.py
View File

@@ -473,6 +473,20 @@ control_tests = [
("DONICVM i VT I VSQVE V FAC { DIC(i) }", ("DONICVM i VT I VSQVE V FAC { DIC(i) }",
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]), Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
ValStr("V"), "I\nII\nIII\nIV\nV\n"), ValStr("V"), "I\nII\nIII\nIV\nV\n"),
# PER destructuring
("PER a, b IN [[I, II], [III, IV]] FAC { DIC(a + b) }",
Program([], [PerStatement(
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]),
[ID("a"), ID("b")],
[ExpressionStatement(BuiltIn("DIC", [BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")]))])]),
ValStr("VII"), "III\nVII\n"),
# PER destructuring: three variables
("PER a, b, c IN [[I, II, III]] FAC { DIC(a + b + c) }",
Program([], [PerStatement(
DataArray([DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]),
[ID("a"), ID("b"), ID("c")],
[ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")]))])]),
ValStr("VI"), "VI\n"),
] ]
class TestControl(unittest.TestCase): class TestControl(unittest.TestCase):
@@ -701,6 +715,9 @@ error_tests = [
("FALSITAS AVT NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean ("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 ("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 ("NVLLVS ET VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in ET
('I @ [II]', CentvrionError), # @ requires two arrays (int @ array)
('[I] @ "hello"', CentvrionError), # @ requires two arrays (array @ string)
('"a" @ "b"', CentvrionError), # @ requires two arrays (string @ string)
('"hello" + " world"', CentvrionError), # use & for string concatenation, not + ('"hello" + " world"', CentvrionError), # use & for string concatenation, not +
("[I, II][III]", CentvrionError), # index too high ("[I, II][III]", CentvrionError), # index too high
("CVM SVBNVLLA\n[I, II][-I]", CentvrionError), # negative index ("CVM SVBNVLLA\n[I, II][-I]", CentvrionError), # negative index
@@ -751,6 +768,8 @@ error_tests = [
("DESIGNA a, b VT III", CentvrionError), # destructure non-array ("DESIGNA a, b VT III", CentvrionError), # destructure non-array
("DESIGNA a, b VT [I]", CentvrionError), # destructure length mismatch: too many targets ("DESIGNA a, b VT [I]", CentvrionError), # destructure length mismatch: too many targets
("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets ("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets
("PER a, b IN [I, II, III] FAC { DIC(a) }", CentvrionError), # PER destructure: element is not an array
("PER a, b IN [[I], [II]] FAC { DIC(a) }", CentvrionError), # PER destructure: wrong number of elements
("[I, II, III][II VSQVE IV]", CentvrionError), # slice upper bound out of range ("[I, II, III][II VSQVE IV]", CentvrionError), # slice upper bound out of range
("[I, II, III][NVLLVS VSQVE II]", CentvrionError), # slice with non-integer bound ("[I, II, III][NVLLVS VSQVE II]", CentvrionError), # slice with non-integer bound
("I[I VSQVE II]", CentvrionError), # slice on non-array ("I[I VSQVE II]", CentvrionError), # slice on non-array
@@ -1031,6 +1050,40 @@ class TestArithmeticEdge(unittest.TestCase):
run_test(self, source, nodes, value) run_test(self, source, nodes, value)
# --- Array concatenation ---
array_concat_tests = [
("[I, II] @ [III, IV]",
Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), "SYMBOL_AT"))]),
ValList([ValInt(1), ValInt(2), ValInt(3), ValInt(4)])),
("[] @ [I]",
Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([Numeral("I")]), "SYMBOL_AT"))]),
ValList([ValInt(1)])),
("[I] @ []",
Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I")]), DataArray([]), "SYMBOL_AT"))]),
ValList([ValInt(1)])),
("[] @ []",
Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([]), "SYMBOL_AT"))]),
ValList([])),
('["a"] @ [I]',
Program([], [ExpressionStatement(BinOp(DataArray([String("a")]), DataArray([Numeral("I")]), "SYMBOL_AT"))]),
ValList([ValStr("a"), ValInt(1)])),
# left-associative chaining
("[I] @ [II] @ [III]",
Program([], [ExpressionStatement(BinOp(BinOp(DataArray([Numeral("I")]), DataArray([Numeral("II")]), "SYMBOL_AT"), DataArray([Numeral("III")]), "SYMBOL_AT"))]),
ValList([ValInt(1), ValInt(2), ValInt(3)])),
# concat with variable
("DESIGNA a VT [I, II]\nDESIGNA b VT [III]\na @ b",
Program([], [Designa(ID("a"), DataArray([Numeral("I"), Numeral("II")])), Designa(ID("b"), DataArray([Numeral("III")])), ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_AT"))]),
ValList([ValInt(1), ValInt(2), ValInt(3)])),
]
class TestArrayConcat(unittest.TestCase):
@parameterized.expand(array_concat_tests)
def test_array_concat(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- String concatenation --- # --- String concatenation ---
string_concat_tests = [ string_concat_tests = [
@@ -1512,6 +1565,31 @@ loop_edge_tests = [
[Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]), [Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]),
ExpressionStatement(ID("x"))]), ExpressionStatement(ID("x"))]),
ValNul(), ""), ValNul(), ""),
# PER destructuring with ERVMPE
("DESIGNA r VT I\nPER a, b IN [[I, II], [III, IV], [V, VI]] FAC {\nSI a EST III TVNC { ERVMPE }\nDESIGNA r VT r + a + b\n}\nr",
Program([], [
Designa(ID("r"), Numeral("I")),
PerStatement(
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), DataArray([Numeral("V"), Numeral("VI")])]),
[ID("a"), ID("b")],
[SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None),
Designa(ID("r"), BinOp(BinOp(ID("r"), ID("a"), "SYMBOL_PLUS"), ID("b"), "SYMBOL_PLUS"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(4)), # 1 + 1 + 2 = 4, breaks before [III, IV]
# PER destructuring with REDI
("DEFINI f () VT {\nPER a, b IN [[I, II], [III, IV]] FAC {\nSI a EST III TVNC { REDI (b) }\n}\n}\nINVOCA f ()",
Program([], [
Defini(ID("f"), [],
[PerStatement(
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]),
[ID("a"), ID("b")],
[SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Redi([ID("b")])], None)],
)]),
ExpressionStatement(Invoca(ID("f"), [])),
]),
ValInt(4)), # returns b=IV when a=III
] ]
class TestLoopEdge(unittest.TestCase): class TestLoopEdge(unittest.TestCase):
@@ -1696,7 +1774,7 @@ array_index_assign_tests = [
("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[II]", ("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[II]",
Program([], [ Program([], [
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
DesignaIndex(ID("a"), Numeral("II"), Numeral("X")), DesignaIndex(ID("a"), [Numeral("II")], Numeral("X")),
ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))), ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))),
]), ]),
ValInt(10)), ValInt(10)),
@@ -1704,7 +1782,7 @@ array_index_assign_tests = [
("DESIGNA a VT [I, II, III]\nDESIGNA a[I] VT V\na[I]", ("DESIGNA a VT [I, II, III]\nDESIGNA a[I] VT V\na[I]",
Program([], [ Program([], [
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
DesignaIndex(ID("a"), Numeral("I"), Numeral("V")), DesignaIndex(ID("a"), [Numeral("I")], Numeral("V")),
ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))), ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))),
]), ]),
ValInt(5)), ValInt(5)),
@@ -1712,7 +1790,7 @@ array_index_assign_tests = [
("DESIGNA a VT [I, II, III]\nDESIGNA a[III] VT L\na[III]", ("DESIGNA a VT [I, II, III]\nDESIGNA a[III] VT L\na[III]",
Program([], [ Program([], [
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
DesignaIndex(ID("a"), Numeral("III"), Numeral("L")), DesignaIndex(ID("a"), [Numeral("III")], Numeral("L")),
ExpressionStatement(ArrayIndex(ID("a"), Numeral("III"))), ExpressionStatement(ArrayIndex(ID("a"), Numeral("III"))),
]), ]),
ValInt(50)), ValInt(50)),
@@ -1720,7 +1798,7 @@ array_index_assign_tests = [
("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[I]", ("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[I]",
Program([], [ Program([], [
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
DesignaIndex(ID("a"), Numeral("II"), Numeral("X")), DesignaIndex(ID("a"), [Numeral("II")], Numeral("X")),
ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))), ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))),
]), ]),
ValInt(1)), ValInt(1)),
@@ -1729,7 +1807,7 @@ array_index_assign_tests = [
Program([], [ Program([], [
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
Designa(ID("i"), Numeral("II")), Designa(ID("i"), Numeral("II")),
DesignaIndex(ID("a"), ID("i"), Numeral("X")), DesignaIndex(ID("a"), [ID("i")], Numeral("X")),
ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))), ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))),
]), ]),
ValInt(10)), ValInt(10)),
@@ -1741,6 +1819,109 @@ class TestArrayIndexAssign(unittest.TestCase):
run_test(self, source, nodes, value) run_test(self, source, nodes, value)
# --- Multi-dimensional array index assignment ---
multidim_assign_tests = [
# 2D array assignment
("DESIGNA a VT [[I, II], [III, IV]]\nDESIGNA a[I][II] VT X\na[I][II]",
Program([], [
Designa(ID("a"), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])),
DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], Numeral("X")),
ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), Numeral("II"))),
]),
ValInt(10)),
# other elements unaffected
("DESIGNA a VT [[I, II], [III, IV]]\nDESIGNA a[I][II] VT X\na[II][I]",
Program([], [
Designa(ID("a"), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])),
DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], Numeral("X")),
ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("II")), Numeral("I"))),
]),
ValInt(3)),
# dict inside array
('DESIGNA a VT [TABVLA {"x" VT I}]\nDESIGNA a[I]["x"] VT X\na[I]["x"]',
Program([], [
Designa(ID("a"), DataArray([DataDict([(String("x"), Numeral("I"))])])),
DesignaIndex(ID("a"), [Numeral("I"), String("x")], Numeral("X")),
ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), String("x"))),
]),
ValInt(10)),
# array inside dict
('DESIGNA d VT TABVLA {"a" VT [I, II]}\nDESIGNA d["a"][I] VT X\nd["a"][I]',
Program([], [
Designa(ID("d"), DataDict([(String("a"), DataArray([Numeral("I"), Numeral("II")]))])),
DesignaIndex(ID("d"), [String("a"), Numeral("I")], Numeral("X")),
ExpressionStatement(ArrayIndex(ArrayIndex(ID("d"), String("a")), Numeral("I"))),
]),
ValInt(10)),
# 3 levels deep
("DESIGNA a VT [[[I]]]\nDESIGNA a[I][I][I] VT X\na[I][I][I]",
Program([], [
Designa(ID("a"), DataArray([DataArray([DataArray([Numeral("I")])])])),
DesignaIndex(ID("a"), [Numeral("I"), Numeral("I"), Numeral("I")], Numeral("X")),
ExpressionStatement(ArrayIndex(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), Numeral("I")), Numeral("I"))),
]),
ValInt(10)),
]
class TestMultidimAssign(unittest.TestCase):
@parameterized.expand(multidim_assign_tests)
def test_multidim_assign(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- String index assignment ---
string_index_assign_tests = [
# assign to middle character
('DESIGNA s VT "ABCDE"\nDESIGNA s[III] VT "X"\ns',
Program([], [
Designa(ID("s"), String("ABCDE")),
DesignaIndex(ID("s"), [Numeral("III")], String("X")),
ExpressionStatement(ID("s")),
]),
ValStr("ABXDE")),
# assign to first character
('DESIGNA s VT "ABCDE"\nDESIGNA s[I] VT "Z"\ns',
Program([], [
Designa(ID("s"), String("ABCDE")),
DesignaIndex(ID("s"), [Numeral("I")], String("Z")),
ExpressionStatement(ID("s")),
]),
ValStr("ZBCDE")),
# assign to last character
('DESIGNA s VT "ABCDE"\nDESIGNA s[V] VT "Z"\ns',
Program([], [
Designa(ID("s"), String("ABCDE")),
DesignaIndex(ID("s"), [Numeral("V")], String("Z")),
ExpressionStatement(ID("s")),
]),
ValStr("ABCDZ")),
# variable as index
('DESIGNA s VT "ABCDE"\nDESIGNA i VT II\nDESIGNA s[i] VT "X"\ns',
Program([], [
Designa(ID("s"), String("ABCDE")),
Designa(ID("i"), Numeral("II")),
DesignaIndex(ID("s"), [ID("i")], String("X")),
ExpressionStatement(ID("s")),
]),
ValStr("AXCDE")),
# string inside array
('DESIGNA a VT ["ABC", "DEF"]\nDESIGNA a[I][II] VT "X"\na[I]',
Program([], [
Designa(ID("a"), DataArray([String("ABC"), String("DEF")])),
DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], String("X")),
ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))),
]),
ValStr("AXC")),
]
class TestStringIndexAssign(unittest.TestCase):
@parameterized.expand(string_index_assign_tests)
def test_string_index_assign(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Array slicing --- # --- Array slicing ---
array_slice_tests = [ array_slice_tests = [
@@ -1798,6 +1979,36 @@ class TestArraySlice(unittest.TestCase):
run_test(self, source, nodes, value) run_test(self, source, nodes, value)
# --- Multiline arrays ---
multiline_array_tests = [
# newlines after commas
("[I,\nII,\nIII]",
Program([], [ExpressionStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]))]),
ValList([ValInt(1), ValInt(2), ValInt(3)])),
# single newline after comma
("[I, II,\nIII]",
Program([], [ExpressionStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]))]),
ValList([ValInt(1), ValInt(2), ValInt(3)])),
# empty array still works
("[]",
Program([], [ExpressionStatement(DataArray([]))]),
ValList([])),
# nested arrays with newlines
("[I,\n[II, III],\nIV]",
Program([], [ExpressionStatement(DataArray([
Numeral("I"),
DataArray([Numeral("II"), Numeral("III")]),
Numeral("IV")]))]),
ValList([ValInt(1), ValList([ValInt(2), ValInt(3)]), ValInt(4)])),
]
class TestMultilineArray(unittest.TestCase):
@parameterized.expand(multiline_array_tests)
def test_multiline_array(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- String indexing --- # --- String indexing ---
string_index_tests = [ string_index_tests = [
@@ -2387,7 +2598,7 @@ dict_assign_tests = [
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["a"] VT X\nd["a"]', ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["a"] VT X\nd["a"]',
Program([], [ Program([], [
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
DesignaIndex(ID("d"), String("a"), Numeral("X")), DesignaIndex(ID("d"), [String("a")], Numeral("X")),
ExpressionStatement(ArrayIndex(ID("d"), String("a"))), ExpressionStatement(ArrayIndex(ID("d"), String("a"))),
]), ]),
ValInt(10)), ValInt(10)),
@@ -2395,7 +2606,7 @@ dict_assign_tests = [
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["b"]', ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["b"]',
Program([], [ Program([], [
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
DesignaIndex(ID("d"), String("b"), Numeral("II")), DesignaIndex(ID("d"), [String("b")], Numeral("II")),
ExpressionStatement(ArrayIndex(ID("d"), String("b"))), ExpressionStatement(ArrayIndex(ID("d"), String("b"))),
]), ]),
ValInt(2)), ValInt(2)),
@@ -2403,7 +2614,7 @@ dict_assign_tests = [
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["a"]', ('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["a"]',
Program([], [ Program([], [
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])), Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
DesignaIndex(ID("d"), String("b"), Numeral("II")), DesignaIndex(ID("d"), [String("b")], Numeral("II")),
ExpressionStatement(ArrayIndex(ID("d"), String("a"))), ExpressionStatement(ArrayIndex(ID("d"), String("a"))),
]), ]),
ValInt(1)), ValInt(1)),

View File

@@ -57,7 +57,7 @@
}, },
{ {
"name": "keyword.operator.arithmetic.cent", "name": "keyword.operator.arithmetic.cent",
"match": "(\\*|\\+|-|/|&)" "match": "(\\*|\\+|-|/|&|@)"
} }
] ]
}, },