🐐 Dict
This commit is contained in:
@@ -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"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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user