🐐 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

@@ -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)