🐐 Dict
This commit is contained in:
35
README.md
35
README.md
@@ -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])`
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();")
|
||||
|
||||
@@ -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"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 += ["}"]
|
||||
lines += [" }", "}"]
|
||||
return lines
|
||||
|
||||
if isinstance(node, Defini):
|
||||
|
||||
@@ -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 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
@@ -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 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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} \\
|
||||
|
||||
@@ -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
173
tests.py
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user