479 lines
17 KiB
Python
479 lines
17 KiB
Python
from centvrion.errors import CentvrionError
|
|
from centvrion.ast_nodes import (
|
|
String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID,
|
|
BinOp, UnaryMinus, UnaryNot,
|
|
ArrayIndex, ArraySlice, DataArray, DataRangeArray, DataDict,
|
|
BuiltIn, Invoca, Fvnctio,
|
|
num_to_int, frac_to_fraction,
|
|
)
|
|
|
|
_BINOP_FN = {
|
|
"SYMBOL_PLUS": "cent_add",
|
|
"SYMBOL_MINUS": "cent_sub",
|
|
"SYMBOL_TIMES": "cent_mul",
|
|
"SYMBOL_DIVIDE": "cent_div",
|
|
"SYMBOL_AMPERSAND": "cent_concat",
|
|
"SYMBOL_AT": "cent_array_concat",
|
|
"KEYWORD_RELIQVVM": "cent_mod",
|
|
"KEYWORD_EST": "cent_eq",
|
|
"KEYWORD_DISPAR": "cent_neq",
|
|
"KEYWORD_MINVS": "cent_lt",
|
|
"KEYWORD_PLVS": "cent_gt",
|
|
"KEYWORD_HAVD_PLVS": "cent_lte",
|
|
"KEYWORD_HAVD_MINVS": "cent_gte",
|
|
"KEYWORD_ET": "cent_and",
|
|
"KEYWORD_AVT": "cent_or",
|
|
}
|
|
|
|
|
|
def _escape(s):
|
|
return (s
|
|
.replace("\\", "\\\\")
|
|
.replace('"', '\\"')
|
|
.replace("\n", "\\n")
|
|
.replace("\r", "\\r")
|
|
.replace("\t", "\\t"))
|
|
|
|
|
|
def emit_expr(node, ctx):
|
|
"""
|
|
Emit C code for a CENTVRION expression node.
|
|
|
|
Returns (lines, result_var):
|
|
lines — list of C statements that compute the expression
|
|
result_var — name of the CentValue variable holding the result
|
|
"""
|
|
if isinstance(node, Numeral):
|
|
tmp = ctx.fresh_tmp()
|
|
magnvm = "MAGNVM" in ctx.modules
|
|
svbnvlla = "SVBNVLLA" in ctx.modules
|
|
n = num_to_int(node.value, magnvm, svbnvlla)
|
|
return [f"CentValue {tmp} = cent_int({n}L);"], tmp
|
|
|
|
if isinstance(node, String):
|
|
tmp = ctx.fresh_tmp()
|
|
return [f'CentValue {tmp} = cent_str("{_escape(node.value)}");'], tmp
|
|
|
|
if isinstance(node, InterpolatedString):
|
|
if len(node.parts) == 0:
|
|
tmp = ctx.fresh_tmp()
|
|
return [f'CentValue {tmp} = cent_str("");'], tmp
|
|
if len(node.parts) == 1:
|
|
return emit_expr(node.parts[0], ctx)
|
|
l_lines, l_var = emit_expr(node.parts[0], ctx)
|
|
r_lines, r_var = emit_expr(node.parts[1], ctx)
|
|
lines = l_lines + r_lines
|
|
acc = ctx.fresh_tmp()
|
|
lines.append(f"CentValue {acc} = cent_concat({l_var}, {r_var});")
|
|
for part in node.parts[2:]:
|
|
p_lines, p_var = emit_expr(part, ctx)
|
|
lines.extend(p_lines)
|
|
new_acc = ctx.fresh_tmp()
|
|
lines.append(f"CentValue {new_acc} = cent_concat({acc}, {p_var});")
|
|
acc = new_acc
|
|
return lines, acc
|
|
|
|
if isinstance(node, Bool):
|
|
tmp = ctx.fresh_tmp()
|
|
v = "1" if node.value else "0"
|
|
return [f"CentValue {tmp} = cent_bool({v});"], tmp
|
|
|
|
if isinstance(node, Nullus):
|
|
tmp = ctx.fresh_tmp()
|
|
return [f"CentValue {tmp} = cent_null();"], tmp
|
|
|
|
if isinstance(node, Fractio):
|
|
if not ctx.has_module("FRACTIO"):
|
|
raise CentvrionError("Cannot use fraction literals without 'FRACTIO' module")
|
|
tmp = ctx.fresh_tmp()
|
|
magnvm = "MAGNVM" in ctx.modules
|
|
svbnvlla = "SVBNVLLA" in ctx.modules
|
|
frac = frac_to_fraction(node.value, magnvm, svbnvlla)
|
|
return [f"CentValue {tmp} = cent_frac({frac.numerator}L, {frac.denominator}L);"], tmp
|
|
|
|
if isinstance(node, ID):
|
|
tmp = ctx.fresh_tmp()
|
|
return [f'CentValue {tmp} = cent_scope_get(&_scope, "{node.name}");'], tmp
|
|
|
|
if isinstance(node, BinOp):
|
|
# Short-circuit for logical operators
|
|
if node.op in ("KEYWORD_AVT", "KEYWORD_ET"):
|
|
l_lines, l_var = emit_expr(node.left, ctx)
|
|
r_lines, r_var = emit_expr(node.right, ctx)
|
|
tmp = ctx.fresh_tmp()
|
|
if node.op == "KEYWORD_AVT":
|
|
lines = l_lines + [f"CentValue {tmp};"]
|
|
lines += [f"if (cent_truthy({l_var})) {{ {tmp} = cent_bool(1); }} else {{"]
|
|
lines += [f" {l}" for l in r_lines]
|
|
lines += [f" {tmp} = cent_bool(cent_truthy({r_var}));"]
|
|
lines += ["}"]
|
|
else:
|
|
lines = l_lines + [f"CentValue {tmp};"]
|
|
lines += [f"if (!cent_truthy({l_var})) {{ {tmp} = cent_bool(0); }} else {{"]
|
|
lines += [f" {l}" for l in r_lines]
|
|
lines += [f" {tmp} = cent_bool(cent_truthy({r_var}));"]
|
|
lines += ["}"]
|
|
return lines, tmp
|
|
|
|
l_lines, l_var = emit_expr(node.left, ctx)
|
|
r_lines, r_var = emit_expr(node.right, ctx)
|
|
tmp = ctx.fresh_tmp()
|
|
if node.op == "SYMBOL_DIVIDE" and ctx.has_module("FRACTIO"):
|
|
fn = "cent_div_frac"
|
|
elif node.op == "KEYWORD_RELIQVVM" and ctx.has_module("FRACTIO"):
|
|
fn = "cent_mod_frac"
|
|
else:
|
|
fn = _BINOP_FN[node.op]
|
|
return l_lines + r_lines + [f"CentValue {tmp} = {fn}({l_var}, {r_var});"], tmp
|
|
|
|
if isinstance(node, UnaryMinus):
|
|
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
|
tmp = ctx.fresh_tmp()
|
|
return inner_lines + [f"CentValue {tmp} = cent_neg({inner_var});"], tmp
|
|
|
|
if isinstance(node, UnaryNot):
|
|
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
|
tmp = ctx.fresh_tmp()
|
|
return inner_lines + [f"CentValue {tmp} = cent_bool(!cent_truthy({inner_var}));"], tmp
|
|
|
|
if isinstance(node, ArrayIndex):
|
|
arr_lines, arr_var = emit_expr(node.array, ctx)
|
|
idx_lines, idx_var = emit_expr(node.index, ctx)
|
|
tmp = ctx.fresh_tmp()
|
|
return arr_lines + idx_lines + [f"CentValue {tmp} = cent_list_index({arr_var}, {idx_var});"], tmp
|
|
|
|
if isinstance(node, ArraySlice):
|
|
arr_lines, arr_var = emit_expr(node.array, ctx)
|
|
lo_lines, lo_var = emit_expr(node.from_index, ctx)
|
|
hi_lines, hi_var = emit_expr(node.to_index, ctx)
|
|
tmp = ctx.fresh_tmp()
|
|
return arr_lines + lo_lines + hi_lines + [
|
|
f"CentValue {tmp} = cent_list_slice({arr_var}, {lo_var}, {hi_var});"
|
|
], tmp
|
|
|
|
if isinstance(node, DataArray):
|
|
lines = []
|
|
tmp = ctx.fresh_tmp()
|
|
lines.append(f"CentValue {tmp} = cent_list_new({len(node.content)});")
|
|
for item in node.content:
|
|
item_lines, item_var = emit_expr(item, ctx)
|
|
lines.extend(item_lines)
|
|
lines.append(f"cent_list_push(&{tmp}, {item_var});")
|
|
return lines, tmp
|
|
|
|
if isinstance(node, DataRangeArray):
|
|
lo_lines, lo_var = emit_expr(node.from_value, ctx)
|
|
hi_lines, hi_var = emit_expr(node.to_value, ctx)
|
|
tmp = ctx.fresh_tmp()
|
|
i_var = ctx.fresh_tmp()
|
|
if node.step is None:
|
|
cap = f"({hi_var}.ival >= {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival + 1) : 0)"
|
|
lines = lo_lines + hi_lines + [
|
|
f"CentValue {tmp} = cent_list_new({cap});",
|
|
f"for (long {i_var} = {lo_var}.ival; {i_var} <= {hi_var}.ival; {i_var}++) {{",
|
|
f" cent_list_push(&{tmp}, cent_int({i_var}));",
|
|
"}",
|
|
]
|
|
return lines, tmp
|
|
step_lines, step_var = emit_expr(node.step, ctx)
|
|
lines = lo_lines + hi_lines + step_lines + [
|
|
f'if ({step_var}.type != CENT_INT) cent_type_error("Range step must be a number");',
|
|
f'if ({step_var}.ival == 0) cent_runtime_error("Range step cannot be zero");',
|
|
f"CentValue {tmp} = cent_list_new(0);",
|
|
f"for (long {i_var} = {lo_var}.ival; ({step_var}.ival > 0 ? {i_var} <= {hi_var}.ival : {i_var} >= {hi_var}.ival); {i_var} += {step_var}.ival) {{",
|
|
f" cent_list_push(&{tmp}, cent_int({i_var}));",
|
|
"}",
|
|
]
|
|
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)
|
|
|
|
if isinstance(node, Invoca):
|
|
return _emit_invoca(node, ctx)
|
|
|
|
if isinstance(node, Fvnctio):
|
|
return _emit_fvnctio(node, ctx)
|
|
|
|
raise NotImplementedError(type(node).__name__)
|
|
|
|
|
|
def _emit_builtin(node, ctx):
|
|
lines = []
|
|
param_vars = []
|
|
for p in node.parameters:
|
|
p_lines, p_var = emit_expr(p, ctx)
|
|
lines.extend(p_lines)
|
|
param_vars.append(p_var)
|
|
|
|
tmp = ctx.fresh_tmp()
|
|
|
|
match node.builtin:
|
|
case "DIC":
|
|
if not param_vars:
|
|
lines.append('cent_dic(cent_str(""));')
|
|
lines.append(f'CentValue {tmp} = cent_str("");')
|
|
elif len(param_vars) == 1:
|
|
lines.append(f"cent_dic({param_vars[0]});")
|
|
lines.append(f"CentValue {tmp} = {param_vars[0]};")
|
|
else:
|
|
acc = param_vars[0]
|
|
for pv in param_vars[1:]:
|
|
space_tmp = ctx.fresh_tmp()
|
|
joined_tmp = ctx.fresh_tmp()
|
|
lines.append(f'CentValue {space_tmp} = cent_concat({acc}, cent_str(" "));')
|
|
lines.append(f"CentValue {joined_tmp} = cent_concat({space_tmp}, {pv});")
|
|
acc = joined_tmp
|
|
lines.append(f"cent_dic({acc});")
|
|
lines.append(f"CentValue {tmp} = {acc};")
|
|
|
|
case "AVDI":
|
|
lines.append(f"CentValue {tmp} = cent_avdi();")
|
|
|
|
case "AVDI_NVMERVS":
|
|
lines.append(f"CentValue {tmp} = cent_avdi_numerus();")
|
|
|
|
case "LONGITVDO":
|
|
lines.append(f"CentValue {tmp} = cent_longitudo({param_vars[0]});")
|
|
|
|
case "LITTERA":
|
|
lines.append(f"CentValue {tmp} = cent_littera({param_vars[0]});")
|
|
|
|
case "MAIVSCVLA":
|
|
if len(param_vars) != 1:
|
|
lines.append(f'cent_runtime_error("MAIVSCVLA takes exactly I argument");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_maivscvla({param_vars[0]});")
|
|
|
|
case "MINVSCVLA":
|
|
if len(param_vars) != 1:
|
|
lines.append(f'cent_runtime_error("MINVSCVLA takes exactly I argument");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_minvscvla({param_vars[0]});")
|
|
|
|
case "FORTVITVS_NVMERVS":
|
|
if not ctx.has_module("FORS"):
|
|
lines.append('cent_runtime_error("FORS module required for FORTVITVS_NVMERVS");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_fortuitus_numerus({param_vars[0]}, {param_vars[1]});")
|
|
|
|
case "FORTVITA_ELECTIO":
|
|
if not ctx.has_module("FORS"):
|
|
lines.append('cent_runtime_error("FORS module required for FORTVITA_ELECTIO");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_fortuita_electionis({param_vars[0]});")
|
|
|
|
case "DECIMATIO":
|
|
if not ctx.has_module("FORS"):
|
|
lines.append('cent_runtime_error("FORS module required for DECIMATIO");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_decimatio({param_vars[0]});")
|
|
|
|
case "SEMEN":
|
|
if not ctx.has_module("FORS"):
|
|
lines.append('cent_runtime_error("FORS module required for SEMEN");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"cent_semen({param_vars[0]});")
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
|
|
case "SENATVS":
|
|
if param_vars:
|
|
arr_tmp = ctx.fresh_tmp() + "_arr"
|
|
lines.append(f"CentValue {arr_tmp}[] = {{{', '.join(param_vars)}}};")
|
|
lines.append(f"CentValue {tmp} = cent_senatus({arr_tmp}, {len(param_vars)});")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_senatus(NULL, 0);")
|
|
|
|
case "ERVMPE":
|
|
# break as expression (side-effecting; result is unused)
|
|
lines.append("break;")
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
|
|
case "CLAVES":
|
|
lines.append(f"CentValue {tmp} = cent_dict_keys({param_vars[0]});")
|
|
|
|
case "ORDINA":
|
|
lines.append(f"CentValue {tmp} = cent_ordina({param_vars[0]});")
|
|
|
|
case "ADDE":
|
|
lines.append(f"CentValue {tmp} = cent_adde({param_vars[0]}, {param_vars[1]});")
|
|
|
|
case "TOLLE":
|
|
lines.append(f"CentValue {tmp} = cent_tolle({param_vars[0]}, {param_vars[1]});")
|
|
|
|
case "INSERE":
|
|
lines.append(f"CentValue {tmp} = cent_insere({param_vars[0]}, {param_vars[1]}, {param_vars[2]});")
|
|
|
|
case "NECTE":
|
|
lines.append(f"CentValue {tmp} = cent_necte({param_vars[0]}, {param_vars[1]});")
|
|
|
|
case "IVNGE":
|
|
lines.append(f"CentValue {tmp} = cent_ivnge({param_vars[0]}, {param_vars[1]});")
|
|
|
|
case "EVERRE":
|
|
lines.append("cent_everre();")
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
|
|
case "TYPVS":
|
|
lines.append(f"CentValue {tmp} = cent_typvs({param_vars[0]});")
|
|
|
|
case "DORMI":
|
|
lines.append(f"cent_dormi({param_vars[0]});")
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
|
|
case "LEGE":
|
|
if not ctx.has_module("SCRIPTA"):
|
|
lines.append('cent_runtime_error("SCRIPTA module required for LEGE");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_lege({param_vars[0]});")
|
|
|
|
case "SCRIBE":
|
|
if not ctx.has_module("SCRIPTA"):
|
|
lines.append('cent_runtime_error("SCRIPTA module required for SCRIBE");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"cent_scribe({param_vars[0]}, {param_vars[1]});")
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
|
|
case "ADIVNGE":
|
|
if not ctx.has_module("SCRIPTA"):
|
|
lines.append('cent_runtime_error("SCRIPTA module required for ADIVNGE");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"cent_adivnge({param_vars[0]}, {param_vars[1]});")
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
|
|
case "NVMERVS":
|
|
if len(param_vars) != 1:
|
|
lines.append(f'cent_runtime_error("NVMERVS takes exactly I argument");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_numerus({param_vars[0]});")
|
|
|
|
case "QVAERE":
|
|
lines.append(f"CentValue {tmp} = cent_qvaere({param_vars[0]}, {param_vars[1]});")
|
|
|
|
case "SVBSTITVE":
|
|
lines.append(f"CentValue {tmp} = cent_svbstitve({param_vars[0]}, {param_vars[1]}, {param_vars[2]});")
|
|
|
|
case "SCINDE":
|
|
lines.append(f"CentValue {tmp} = cent_scinde({param_vars[0]}, {param_vars[1]});")
|
|
|
|
case "PETE":
|
|
if not ctx.has_module("RETE"):
|
|
lines.append('cent_runtime_error("RETE module required for PETE");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"CentValue {tmp} = cent_pete({param_vars[0]});")
|
|
|
|
case "PETITVR":
|
|
if not ctx.has_module("RETE"):
|
|
lines.append('cent_runtime_error("RETE module required for PETITVR");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"cent_petitvr({param_vars[0]}, {param_vars[1]}, _scope);")
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
|
|
case "AVSCVLTA":
|
|
if not ctx.has_module("RETE"):
|
|
lines.append('cent_runtime_error("RETE module required for AVSCVLTA");')
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
else:
|
|
lines.append(f"cent_avscvlta({param_vars[0]});")
|
|
lines.append(f"CentValue {tmp} = cent_null();")
|
|
|
|
case _:
|
|
raise NotImplementedError(node.builtin)
|
|
|
|
return lines, tmp
|
|
|
|
|
|
def _emit_invoca(node, ctx):
|
|
"""
|
|
Emits a user-defined function call.
|
|
Supports both static resolution (ID callee with known function) and
|
|
dynamic dispatch (arbitrary expression callee via CENT_FUNC values).
|
|
"""
|
|
lines = []
|
|
param_vars = []
|
|
for p in node.parameters:
|
|
p_lines, p_var = emit_expr(p, ctx)
|
|
lines.extend(p_lines)
|
|
param_vars.append(p_var)
|
|
|
|
# Try static resolution for simple ID callees
|
|
if isinstance(node.callee, ID):
|
|
c_func_name = ctx.func_resolve.get(node.callee.name)
|
|
if c_func_name is not None:
|
|
call_scope_var = ctx.fresh_tmp() + "_sc"
|
|
lines.append(f"CentScope {call_scope_var} = cent_scope_copy(&_scope);")
|
|
param_names = ctx.functions[c_func_name]
|
|
if len(param_vars) != len(param_names):
|
|
raise CentvrionError(
|
|
f"Function '{node.callee.name}' expects {len(param_names)} argument(s), "
|
|
f"got {len(param_vars)}"
|
|
)
|
|
for i, pname in enumerate(param_names):
|
|
lines.append(f'cent_scope_set(&{call_scope_var}, "{pname}", {param_vars[i]});')
|
|
tmp = ctx.fresh_tmp()
|
|
lines.append(f"CentValue {tmp} = {c_func_name}({call_scope_var});")
|
|
return lines, tmp
|
|
|
|
# Dynamic dispatch: evaluate callee, call via function pointer
|
|
callee_lines, callee_var = emit_expr(node.callee, ctx)
|
|
lines.extend(callee_lines)
|
|
lines.append(f'if ({callee_var}.type != CENT_FUNC) cent_type_error("cannot call non-function");')
|
|
call_scope_var = ctx.fresh_tmp() + "_sc"
|
|
lines.append(f"CentScope {call_scope_var} = cent_scope_copy(&_scope);")
|
|
nargs = len(param_vars)
|
|
lines.append(
|
|
f"if ({callee_var}.fnval.param_count != {nargs}) "
|
|
f'cent_runtime_error("wrong number of arguments");'
|
|
)
|
|
for i, pv in enumerate(param_vars):
|
|
lines.append(
|
|
f'cent_scope_set(&{call_scope_var}, '
|
|
f'{callee_var}.fnval.param_names[{i}], {pv});'
|
|
)
|
|
tmp = ctx.fresh_tmp()
|
|
lines.append(f"CentValue {tmp} = {callee_var}.fnval.fn({call_scope_var});")
|
|
return lines, tmp
|
|
|
|
|
|
def _emit_fvnctio(node, ctx):
|
|
"""Emit a FVNCTIO lambda expression as a CENT_FUNC value."""
|
|
c_name = ctx.lambda_names[id(node)]
|
|
param_names = ctx.functions[c_name]
|
|
tmp = ctx.fresh_tmp()
|
|
lines = []
|
|
# Build static param name array
|
|
params_arr = ctx.fresh_tmp() + "_pn"
|
|
lines.append(
|
|
f"static const char *{params_arr}[] = {{"
|
|
+ ", ".join(f'"{p}"' for p in param_names)
|
|
+ "};"
|
|
)
|
|
lines.append(
|
|
f"CentValue {tmp} = cent_func_val({c_name}, {params_arr}, {len(param_names)});"
|
|
)
|
|
return lines, tmp
|