🐐 Dict

This commit is contained in:
2026-04-21 17:37:12 +02:00
parent 264ea84dfc
commit db5b7bf144
12 changed files with 456 additions and 25 deletions

View File

@@ -127,6 +127,32 @@ Individual elements can be accessed by index using square brackets. Indexing is
> I
```
### Dicts (TABVLA)
Dicts are key-value maps created with the `TABVLA` keyword and curly braces:
```
DESIGNA d VT TABVLA {"nomen" VT "Marcus", "aetas" VT XXV}
```
Keys must be strings or integers. Values are accessed and assigned with square brackets:
```
DICE(d["nomen"]) // → Marcus
DESIGNA d["aetas"] VT XXVI // update existing key
DESIGNA d["novus"] VT I // insert new key
```
Iterating over a dict with `PER` loops over its keys:
```
PER k IN d FACE {
DICE(k)
}
```
`LONGITVDO(dict)` returns the number of entries. `CLAVES(dict)` returns the keys as an array.
## Conditionals
### SI/TVNC
If-then statements are denoted with the keywords `SI` (if) and `TVNC` (then). Thus, the code
@@ -247,9 +273,14 @@ Skips the rest of the current loop body and continues to the next iteration (`DV
Breaks out of the current loop (`DVM` or `PER`). Has no meaningful return value.
### LONGITVDO
`LONGITVDO(array)` or `LONGITVDO(string)`
`LONGITVDO(array)`, `LONGITVDO(string)`, or `LONGITVDO(dict)`
Returns the length of `array` (element count) or `string` (character count) as an integer.
Returns the length of `array` (element count), `string` (character count), or `dict` (entry count) as an integer.
### CLAVES
`CLAVES(dict)`
Returns the keys of `dict` as an array.
### SENATVS
`SENATVS(bool, ...)` or `SENATVS([bool])`

View File

@@ -5,7 +5,7 @@ from fractions import Fraction
from rply.token import BaseBox
from centvrion.errors import CentvrionError
from centvrion.values import Val, ValInt, ValStr, ValBool, ValList, ValNul, ValFunc, ValFrac
from centvrion.values import Val, ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac
NUMERALS = {
"I": 1,
@@ -136,6 +136,14 @@ def make_string(val, magnvm=False, svbnvlla=False) -> str:
elif isinstance(val, ValList):
inner = ' '.join(make_string(i, magnvm, svbnvlla) for i in val.value())
return f"[{inner}]"
elif isinstance(val, ValDict):
def _key_val(k):
return ValStr(k) if isinstance(k, str) else ValInt(k)
inner = ', '.join(
f"{make_string(_key_val(k), magnvm, svbnvlla)} VT {make_string(v, magnvm, svbnvlla)}"
for k, v in val.value().items()
)
return "{" + inner + "}"
else:
raise CentvrionError(f"Cannot display {val!r}")
@@ -272,6 +280,32 @@ class DataRangeArray(Node):
return vtable, ValList([ValInt(i) for i in range(from_int, to_int)])
class DataDict(Node):
def __init__(self, pairs) -> None:
self.pairs = pairs
def __eq__(self, other):
return type(self) == type(other) and self.pairs == other.pairs
def __repr__(self) -> str:
pair_strs = ', '.join(f"({k!r}, {v!r})" for k, v in self.pairs)
return f"Dict([{pair_strs}])"
def print(self):
items = ", ".join(f"{k.print()} VT {v.print()}" for k, v in self.pairs)
return "TABVLA {" + items + "}"
def _eval(self, vtable):
d = {}
for key_node, val_node in self.pairs:
vtable, key = key_node.eval(vtable)
vtable, val = val_node.eval(vtable)
if not isinstance(key, (ValStr, ValInt)):
raise CentvrionError("Dict keys must be strings or integers")
d[key.value()] = val
return vtable, ValDict(d)
class String(Node):
def __init__(self, value) -> None:
self.value = value
@@ -469,8 +503,15 @@ class DesignaIndex(Node):
if self.id.name not in vtable:
raise CentvrionError(f"Undefined variable: {self.id.name}")
target = vtable[self.id.name]
if isinstance(target, ValDict):
if not isinstance(index, (ValStr, ValInt)):
raise CentvrionError("Dict key must be a string or integer")
d = dict(target.value())
d[index.value()] = val
vtable[self.id.name] = ValDict(d)
return vtable, ValNul()
if not isinstance(target, ValList):
raise CentvrionError(f"{self.id.name} is not an array")
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):
@@ -756,6 +797,14 @@ class ArrayIndex(Node):
def _eval(self, vtable):
vtable, array = self.array.eval(vtable)
vtable, index = self.index.eval(vtable)
if isinstance(array, ValDict):
if not isinstance(index, (ValStr, ValInt)):
raise CentvrionError("Dict key must be a string or integer")
k = index.value()
d = array.value()
if k not in d:
raise CentvrionError(f"Key not found in dict")
return vtable, d[k]
if not isinstance(array, ValList):
raise CentvrionError("Cannot index a non-array value")
if isinstance(index, ValInt):
@@ -879,8 +928,11 @@ class PerStatement(Node):
def _eval(self, vtable):
vtable, array = self.data_list.eval(vtable)
if isinstance(array, ValDict):
keys = [ValStr(k) if isinstance(k, str) else ValInt(k) for k in array.value().keys()]
array = ValList(keys)
if not isinstance(array, ValList):
raise CentvrionError("PER requires an array")
raise CentvrionError("PER requires an array or dict")
variable_name = self.variable_name.name
last_val = ValNul()
for item in array:
@@ -1027,9 +1079,14 @@ class BuiltIn(Node):
true_count = sum(1 for p in items if p.value())
return vtable, ValBool(true_count > len(items) / 2)
case "LONGITVDO":
if isinstance(params[0], (ValList, ValStr)):
if isinstance(params[0], (ValList, ValStr, ValDict)):
return vtable, ValInt(len(params[0].value()))
raise CentvrionError("LONGITVDO requires an array or string")
raise CentvrionError("LONGITVDO requires an array, string, or dict")
case "CLAVES":
if not isinstance(params[0], ValDict):
raise CentvrionError("CLAVES requires a dict")
keys = [ValStr(k) if isinstance(k, str) else ValInt(k) for k in params[0].value().keys()]
return vtable, ValList(keys)
case "EVERRO":
print("\033[2J\033[H", end="", flush=True)
return vtable, ValNul()

View File

@@ -2,7 +2,7 @@ from centvrion.errors import CentvrionError
from centvrion.ast_nodes import (
String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID,
BinOp, UnaryMinus, UnaryNot,
ArrayIndex, DataArray, DataRangeArray,
ArrayIndex, DataArray, DataRangeArray, DataDict,
BuiltIn, Invoca,
num_to_int, frac_to_fraction,
)
@@ -144,6 +144,18 @@ def emit_expr(node, ctx):
]
return lines, tmp
if isinstance(node, DataDict):
lines = []
tmp = ctx.fresh_tmp()
lines.append(f"CentValue {tmp} = cent_dict_new({len(node.pairs)});")
for key_node, val_node in node.pairs:
k_lines, k_var = emit_expr(key_node, ctx)
v_lines, v_var = emit_expr(val_node, ctx)
lines.extend(k_lines)
lines.extend(v_lines)
lines.append(f"cent_dict_set(&{tmp}, {k_var}, {v_var});")
return lines, tmp
if isinstance(node, BuiltIn):
return _emit_builtin(node, ctx)
@@ -233,6 +245,9 @@ def _emit_builtin(node, ctx):
lines.append("break;")
lines.append(f"CentValue {tmp} = cent_null();")
case "CLAVES":
lines.append(f"CentValue {tmp} = cent_dict_keys({param_vars[0]});")
case "EVERRO":
lines.append("cent_everro();")
lines.append(f"CentValue {tmp} = cent_null();")

View File

@@ -70,12 +70,20 @@ def emit_stmt(node, ctx):
var_name = node.variable_name.name
body_lines = _emit_body(node.statements, ctx)
lines = arr_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}++) {{",
f' cent_scope_set(&_scope, "{var_name}", {arr_var}.lval.items[{i_var}]);',
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 += ["}"]
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
if isinstance(node, Defini):

View File

@@ -290,6 +290,29 @@ static int write_val(CentValue v, char *buf, int bufsz) {
return total;
}
case CENT_DICT: {
/* "{key VT val, key VT val}" */
int total = 2; /* '{' + '}' */
for (int i = 0; i < v.dval.len; i++) {
if (i > 0) total += 2; /* ", " */
total += write_val(v.dval.keys[i], NULL, 0);
total += 4; /* " VT " */
total += write_val(v.dval.vals[i], NULL, 0);
}
if (!buf) return total;
int pos = 0;
buf[pos++] = '{';
for (int i = 0; i < v.dval.len; i++) {
if (i > 0) { buf[pos++] = ','; buf[pos++] = ' '; }
pos += write_val(v.dval.keys[i], buf + pos, bufsz - pos);
memcpy(buf + pos, " VT ", 4); pos += 4;
pos += write_val(v.dval.vals[i], buf + pos, bufsz - pos);
}
buf[pos++] = '}';
if (pos < bufsz) buf[pos] = '\0';
return total;
}
default:
cent_runtime_error("cannot display value");
return 0;
@@ -512,7 +535,8 @@ CentValue cent_avdi_numerus(void) {
CentValue cent_longitudo(CentValue v) {
if (v.type == CENT_LIST) return cent_int(v.lval.len);
if (v.type == CENT_STR) return cent_int((long)strlen(v.sval));
cent_type_error("'LONGITVDO' requires a list or string");
if (v.type == CENT_DICT) return cent_int(v.dval.len);
cent_type_error("'LONGITVDO' requires a list, string, or dict");
return cent_null(); /* unreachable; silences warning */
}
@@ -595,8 +619,10 @@ void cent_list_push(CentValue *lst, CentValue v) {
}
CentValue cent_list_index(CentValue lst, CentValue idx) {
if (lst.type == CENT_DICT)
return cent_dict_get(lst, idx);
if (lst.type != CENT_LIST)
cent_type_error("index requires a list");
cent_type_error("index requires a list or dict");
long i;
if (idx.type == CENT_INT)
i = idx.ival;
@@ -610,8 +636,12 @@ CentValue cent_list_index(CentValue lst, CentValue idx) {
}
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v) {
if (lst->type == CENT_DICT) {
cent_dict_set(lst, idx, v);
return;
}
if (lst->type != CENT_LIST)
cent_type_error("index-assign requires a list");
cent_type_error("index-assign requires a list or dict");
if (idx.type != CENT_INT)
cent_type_error("list index must be an integer");
long i = idx.ival;
@@ -620,6 +650,68 @@ void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v) {
lst->lval.items[i - 1] = v;
}
/* ------------------------------------------------------------------ */
/* Dict helpers */
/* ------------------------------------------------------------------ */
static int _cent_key_eq(CentValue a, CentValue b) {
if (a.type != b.type) return 0;
if (a.type == CENT_INT) return a.ival == b.ival;
if (a.type == CENT_STR) return strcmp(a.sval, b.sval) == 0;
return 0;
}
CentValue cent_dict_new(int cap) {
if (cap < 4) cap = 4;
CentValue *keys = cent_arena_alloc(cent_arena, cap * sizeof(CentValue));
CentValue *vals = cent_arena_alloc(cent_arena, cap * sizeof(CentValue));
return cent_dict_val(keys, vals, 0, cap);
}
void cent_dict_set(CentValue *dict, CentValue key, CentValue val) {
if (dict->type != CENT_DICT)
cent_type_error("dict-set requires a dict");
for (int i = 0; i < dict->dval.len; i++) {
if (_cent_key_eq(dict->dval.keys[i], key)) {
dict->dval.vals[i] = val;
return;
}
}
if (dict->dval.len >= dict->dval.cap) {
int new_cap = dict->dval.cap * 2;
CentValue *new_keys = cent_arena_alloc(cent_arena, new_cap * sizeof(CentValue));
CentValue *new_vals = cent_arena_alloc(cent_arena, new_cap * sizeof(CentValue));
memcpy(new_keys, dict->dval.keys, dict->dval.len * sizeof(CentValue));
memcpy(new_vals, dict->dval.vals, dict->dval.len * sizeof(CentValue));
dict->dval.keys = new_keys;
dict->dval.vals = new_vals;
dict->dval.cap = new_cap;
}
dict->dval.keys[dict->dval.len] = key;
dict->dval.vals[dict->dval.len] = val;
dict->dval.len++;
}
CentValue cent_dict_get(CentValue dict, CentValue key) {
if (dict.type != CENT_DICT)
cent_type_error("dict-get requires a dict");
for (int i = 0; i < dict.dval.len; i++) {
if (_cent_key_eq(dict.dval.keys[i], key))
return dict.dval.vals[i];
}
cent_runtime_error("Key not found in dict");
return cent_null();
}
CentValue cent_dict_keys(CentValue dict) {
if (dict.type != CENT_DICT)
cent_type_error("CLAVES requires a dict");
CentValue result = cent_list_new(dict.dval.len);
for (int i = 0; i < dict.dval.len; i++)
cent_list_push(&result, dict.dval.keys[i]);
return result;
}
/* ------------------------------------------------------------------ */
/* Initialisation */
/* ------------------------------------------------------------------ */

View File

@@ -14,11 +14,13 @@ typedef enum {
CENT_BOOL,
CENT_LIST,
CENT_FRAC,
CENT_DICT,
CENT_NULL
} CentType;
typedef struct CentValue CentValue;
typedef struct CentList CentList;
typedef struct CentDict CentDict;
/* Duodecimal fraction: num/den stored as exact integers */
typedef struct {
@@ -32,6 +34,13 @@ struct CentList {
int cap;
};
struct CentDict {
CentValue *keys;
CentValue *vals;
int len;
int cap;
};
struct CentValue {
CentType type;
union {
@@ -40,6 +49,7 @@ struct CentValue {
int bval; /* CENT_BOOL */
CentList lval; /* CENT_LIST */
CentFrac fval; /* CENT_FRAC */
CentDict dval; /* CENT_DICT */
};
};
@@ -101,6 +111,15 @@ static inline CentValue cent_list(CentValue *items, int len, int cap) {
r.lval.cap = cap;
return r;
}
static inline CentValue cent_dict_val(CentValue *keys, CentValue *vals, int len, int cap) {
CentValue r;
r.type = CENT_DICT;
r.dval.keys = keys;
r.dval.vals = vals;
r.dval.len = len;
r.dval.cap = cap;
return r;
}
/* ------------------------------------------------------------------ */
/* Error handling */
@@ -191,6 +210,15 @@ void cent_list_push(CentValue *lst, CentValue v);
CentValue cent_list_index(CentValue lst, CentValue idx); /* 1-based */
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v);
/* ------------------------------------------------------------------ */
/* Dict helpers */
/* ------------------------------------------------------------------ */
CentValue cent_dict_new(int cap);
void cent_dict_set(CentValue *dict, CentValue key, CentValue val);
CentValue cent_dict_get(CentValue dict, CentValue key);
CentValue cent_dict_keys(CentValue dict);
/* ------------------------------------------------------------------ */
/* Initialisation */
/* ------------------------------------------------------------------ */

View File

@@ -30,6 +30,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
"RELIQVVM",
"SI",
"TVNC",
"TABVLA",
"VSQVE",
"VT",
"VERITAS",
@@ -39,6 +40,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
builtin_tokens = [("BUILTIN", i) for i in [
"AVDI_NVMERVS",
"AVDI",
"CLAVES",
"DECIMATIO",
"DICE",
"EVERRO",

View File

@@ -295,6 +295,21 @@ class Parser():
def parens(tokens):
return tokens[1]
@self.pg.production('dict_items : ')
@self.pg.production('dict_items : expression KEYWORD_VT expression')
@self.pg.production('dict_items : expression KEYWORD_VT expression SYMBOL_COMMA dict_items')
def dict_items(calls):
if len(calls) == 0:
return []
elif len(calls) == 3:
return [(calls[0], calls[2])]
else:
return [(calls[0], calls[2])] + calls[4]
@self.pg.production('expression : KEYWORD_TABVLA SYMBOL_LCURL dict_items SYMBOL_RCURL')
def dict_literal(tokens):
return ast_nodes.DataDict(tokens[2])
@self.pg.production('expression : SYMBOL_LBRACKET array_items SYMBOL_RBRACKET')
def array(tokens):
return ast_nodes.DataArray(tokens[1])

View File

@@ -66,6 +66,21 @@ class ValList(Val):
def __iter__(self):
return iter(self._v)
class ValDict(Val):
def __init__(self, v: dict):
assert isinstance(v, dict)
self._v = v
def value(self):
return self._v
def __bool__(self):
return len(self._v) > 0
def __iter__(self):
return iter(self._v.keys())
class ValFrac(Val):
def __init__(self, v: Fraction):
assert isinstance(v, Fraction)

View File

@@ -65,7 +65,14 @@
\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{]}} \\ \hline \hline
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]}} \\
\languageline{literal}{\texttt{TABVLA} \texttt{\{} \textit{optional-dict-items} \texttt{\}}} \\ \hline
\languageline{optional-dict-items}{\textit{dict-items}} \\
\languageline{optional-dict-items}{} \\ \hline
\languageline{dict-items}{\textit{expression} \texttt{VT} \textit{expression} \texttt{,} \textit{dict-items}} \\
\languageline{dict-items}{\textit{expression} \texttt{VT} \textit{expression}} \\ \hline \hline
\multicolumn{3}{|c|}{\textbf{Lists}} \\ \hline
\languageline{optional-ids}{ids} \\

View File

@@ -70,7 +70,7 @@ contexts:
scope: constant.language.centvrion
builtins:
- match: '\b(AVDI_NVMERVS|AVDI|DECIMATIO|DICE|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN)\b'
- match: '\b(AVDI_NVMERVS|AVDI|CLAVES|DECIMATIO|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN|SENATVS)\b'
scope: support.function.builtin.centvrion
modules:
@@ -78,7 +78,7 @@ contexts:
scope: support.class.module.centvrion
keywords:
- match: '\b(AETERNVM|ALVID|AVGE|AVT|CONTINVA|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVE|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TVNC|VSQVE|VT|CVM)\b'
- match: '\b(AETERNVM|ALVID|AVGE|AVT|CONTINVA|DEFINI|DESIGNA|DISPAR|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVE|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TABVLA|TVNC|VSQVE|VT|CVM)\b'
scope: keyword.control.centvrion
operators:

173
tests.py
View File

@@ -10,18 +10,18 @@ from parameterized import parameterized
from fractions import Fraction
from centvrion.ast_nodes import (
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini,
Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, Erumpe,
ExpressionStatement, ID, InterpolatedString, Invoca, ModuleCall, Nullus,
Numeral, PerStatement, Program, Redi, SiStatement, String, UnaryMinus,
UnaryNot, Fractio, frac_to_fraction, fraction_to_frac,
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray,
Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement,
Erumpe, ExpressionStatement, ID, InterpolatedString, Invoca, ModuleCall,
Nullus, Numeral, PerStatement, Program, Redi, SiStatement, String,
UnaryMinus, UnaryNot, Fractio, frac_to_fraction, fraction_to_frac,
num_to_int, int_to_num, make_string,
)
from centvrion.compiler.emitter import compile_program
from centvrion.errors import CentvrionError
from centvrion.lexer import Lexer
from centvrion.parser import Parser
from centvrion.values import ValInt, ValStr, ValBool, ValList, ValNul, ValFunc, ValFrac
from centvrion.values import ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac
_RUNTIME_C = os.path.join(
os.path.dirname(__file__),
@@ -1886,5 +1886,166 @@ class TestFractioHelpers(unittest.TestCase):
self.assertEqual(fraction_to_frac(frac_to_fraction(s)), s)
# --- Dict (TABVLA) ---
dict_tests = [
# empty dict
("TABVLA {}",
Program([], [ExpressionStatement(DataDict([]))]),
ValDict({})),
# single string key
('TABVLA {"a" VT I}',
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I"))]))]),
ValDict({"a": ValInt(1)})),
# multiple entries
('TABVLA {"a" VT I, "b" VT II}',
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]))]),
ValDict({"a": ValInt(1), "b": ValInt(2)})),
# integer keys
('TABVLA {I VT "one", II VT "two"}',
Program([], [ExpressionStatement(DataDict([(Numeral("I"), String("one")), (Numeral("II"), String("two"))]))]),
ValDict({1: ValStr("one"), 2: ValStr("two")})),
# expression values
('TABVLA {"x" VT I + II}',
Program([], [ExpressionStatement(DataDict([(String("x"), BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"))]))]),
ValDict({"x": ValInt(3)})),
]
class TestDict(unittest.TestCase):
@parameterized.expand(dict_tests)
def test_dict(self, source, nodes, value):
run_test(self, source, nodes, value)
dict_index_tests = [
# string key access
('TABVLA {"a" VT X}["a"]',
Program([], [ExpressionStatement(ArrayIndex(DataDict([(String("a"), Numeral("X"))]), String("a")))]),
ValInt(10)),
# integer key access
('TABVLA {I VT "one"}[I]',
Program([], [ExpressionStatement(ArrayIndex(DataDict([(Numeral("I"), String("one"))]), Numeral("I")))]),
ValStr("one")),
# access via variable
('DESIGNA d VT TABVLA {"x" VT V}\nd["x"]',
Program([], [
Designa(ID("d"), DataDict([(String("x"), Numeral("V"))])),
ExpressionStatement(ArrayIndex(ID("d"), String("x"))),
]),
ValInt(5)),
# nested dict access
('TABVLA {"a" VT TABVLA {"b" VT X}}["a"]["b"]',
Program([], [ExpressionStatement(
ArrayIndex(ArrayIndex(DataDict([(String("a"), DataDict([(String("b"), Numeral("X"))]))]), String("a")), String("b"))
)]),
ValInt(10)),
]
class TestDictIndex(unittest.TestCase):
@parameterized.expand(dict_index_tests)
def test_dict_index(self, source, nodes, value):
run_test(self, source, nodes, value)
dict_assign_tests = [
# update existing key
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["a"] VT X\nd["a"]',
Program([], [
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
DesignaIndex(ID("d"), String("a"), Numeral("X")),
ExpressionStatement(ArrayIndex(ID("d"), String("a"))),
]),
ValInt(10)),
# insert new key
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["b"]',
Program([], [
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
DesignaIndex(ID("d"), String("b"), Numeral("II")),
ExpressionStatement(ArrayIndex(ID("d"), String("b"))),
]),
ValInt(2)),
# original key unaffected after insert
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["a"]',
Program([], [
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
DesignaIndex(ID("d"), String("b"), Numeral("II")),
ExpressionStatement(ArrayIndex(ID("d"), String("a"))),
]),
ValInt(1)),
]
class TestDictAssign(unittest.TestCase):
@parameterized.expand(dict_assign_tests)
def test_dict_assign(self, source, nodes, value):
run_test(self, source, nodes, value)
dict_builtin_tests = [
# LONGITVDO on dict
('LONGITVDO(TABVLA {"a" VT I, "b" VT II})',
Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
ValInt(2)),
# LONGITVDO on empty dict
('LONGITVDO(TABVLA {})',
Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([])]))]),
ValInt(0)),
# CLAVES
('CLAVES(TABVLA {"a" VT I, "b" VT II})',
Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
ValList([ValStr("a"), ValStr("b")])),
# CLAVES with int keys
('CLAVES(TABVLA {I VT "x", II VT "y"})',
Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(Numeral("I"), String("x")), (Numeral("II"), String("y"))])]))]),
ValList([ValInt(1), ValInt(2)])),
]
class TestDictBuiltins(unittest.TestCase):
@parameterized.expand(dict_builtin_tests)
def test_dict_builtin(self, source, nodes, value):
run_test(self, source, nodes, value)
dict_iteration_tests = [
# PER iterates over keys
('DESIGNA r VT ""\nPER k IN TABVLA {"a" VT I, "b" VT II} FACE {\nDESIGNA r VT r & k\n}\nr',
Program([], [
Designa(ID("r"), String("")),
PerStatement(
DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]),
ID("k"),
[Designa(ID("r"), BinOp(ID("r"), ID("k"), "SYMBOL_AMPERSAND"))],
),
ExpressionStatement(ID("r")),
]),
ValStr("ab")),
]
class TestDictIteration(unittest.TestCase):
@parameterized.expand(dict_iteration_tests)
def test_dict_iteration(self, source, nodes, value):
run_test(self, source, nodes, value)
dict_display_tests = [
# DICE on dict
('DICE(TABVLA {"a" VT I})',
Program([], [ExpressionStatement(BuiltIn("DICE", [DataDict([(String("a"), Numeral("I"))])]))]),
ValStr("{a VT I}"), "{a VT I}\n"),
# DICE on multi-entry dict
('DICE(TABVLA {"a" VT I, "b" VT II})',
Program([], [ExpressionStatement(BuiltIn("DICE", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
ValStr("{a VT I, b VT II}"), "{a VT I, b VT II}\n"),
# DICE on empty dict
('DICE(TABVLA {})',
Program([], [ExpressionStatement(BuiltIn("DICE", [DataDict([])]))]),
ValStr("{}"), "{}\n"),
]
class TestDictDisplay(unittest.TestCase):
@parameterized.expand(dict_display_tests)
def test_dict_display(self, source, nodes, value, output):
run_test(self, source, nodes, value, output)
if __name__ == "__main__":
unittest.main()