🐐 Compiler
This commit is contained in:
@@ -622,7 +622,9 @@ class UnaryNot(Node):
|
||||
|
||||
def _eval(self, vtable):
|
||||
vtable, val = self.expr.eval(vtable)
|
||||
return vtable, ValBool(not bool(val))
|
||||
if not isinstance(val, ValBool):
|
||||
raise CentvrionError("NON requires a boolean")
|
||||
return vtable, ValBool(not val.value())
|
||||
|
||||
|
||||
class ArrayIndex(Node):
|
||||
@@ -730,8 +732,6 @@ class DumStatement(Node):
|
||||
if vtable["#return"] is not None:
|
||||
break
|
||||
vtable, cond = self.test.eval(vtable)
|
||||
if not isinstance(cond, ValBool):
|
||||
raise CentvrionError("DVM condition must be a boolean")
|
||||
return vtable, last_val
|
||||
|
||||
|
||||
|
||||
1
centvrion/compiler/__init__.py
Normal file
1
centvrion/compiler/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from centvrion.compiler.emitter import compile_program
|
||||
17
centvrion/compiler/context.py
Normal file
17
centvrion/compiler/context.py
Normal file
@@ -0,0 +1,17 @@
|
||||
class EmitContext:
|
||||
def __init__(self):
|
||||
self._tmp_counter = 0
|
||||
self.current_function = None
|
||||
self.modules = set()
|
||||
# c_func_name → [param_names]; populated by emitter pre-pass
|
||||
self.functions = {}
|
||||
# source-level name / alias → c_func_name; populated by emitter pre-pass
|
||||
self.func_resolve = {}
|
||||
|
||||
def fresh_tmp(self):
|
||||
name = f"_t{self._tmp_counter}"
|
||||
self._tmp_counter += 1
|
||||
return name
|
||||
|
||||
def has_module(self, name):
|
||||
return name in self.modules
|
||||
225
centvrion/compiler/emit_expr.py
Normal file
225
centvrion/compiler/emit_expr.py
Normal file
@@ -0,0 +1,225 @@
|
||||
from centvrion.errors import CentvrionError
|
||||
from centvrion.ast_nodes import (
|
||||
String, Numeral, Fractio, Bool, Nullus, ID,
|
||||
BinOp, UnaryMinus, UnaryNot,
|
||||
ArrayIndex, DataArray, DataRangeArray,
|
||||
BuiltIn, Invoca,
|
||||
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",
|
||||
"KEYWORD_EST": "cent_eq",
|
||||
"KEYWORD_MINVS": "cent_lt",
|
||||
"KEYWORD_PLVS": "cent_gt",
|
||||
"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, 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):
|
||||
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"
|
||||
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_int(-{inner_var}.ival);"], 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, 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()
|
||||
cap = f"({hi_var}.ival > {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival) : 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
|
||||
|
||||
if isinstance(node, BuiltIn):
|
||||
return _emit_builtin(node, ctx)
|
||||
|
||||
if isinstance(node, Invoca):
|
||||
return _emit_invoca(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 "DICE":
|
||||
if not param_vars:
|
||||
lines.append('cent_dice(cent_str(""));')
|
||||
lines.append(f'CentValue {tmp} = cent_str("");')
|
||||
elif len(param_vars) == 1:
|
||||
lines.append(f"cent_dice({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_dice({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 "FORTIS_NVMERVS":
|
||||
if not ctx.has_module("FORS"):
|
||||
lines.append('cent_runtime_error("FORS module required for FORTIS_NVMERVS");')
|
||||
lines.append(f"CentValue {tmp} = cent_null();")
|
||||
else:
|
||||
lines.append(f"CentValue {tmp} = cent_fortis_numerus({param_vars[0]}, {param_vars[1]});")
|
||||
|
||||
case "FORTIS_ELECTIONIS":
|
||||
if not ctx.has_module("FORS"):
|
||||
lines.append('cent_runtime_error("FORS module required for FORTIS_ELECTIONIS");')
|
||||
lines.append(f"CentValue {tmp} = cent_null();")
|
||||
else:
|
||||
lines.append(f"CentValue {tmp} = cent_fortis_electionis({param_vars[0]});")
|
||||
|
||||
case "ERVMPE":
|
||||
# break as expression (side-effecting; result is unused)
|
||||
lines.append("break;")
|
||||
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.
|
||||
Requires ctx.functions[name] = [param_names] populated by the emitter pre-pass.
|
||||
"""
|
||||
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)
|
||||
|
||||
func_name = node.name.name
|
||||
c_func_name = ctx.func_resolve.get(func_name)
|
||||
if c_func_name is None:
|
||||
raise CentvrionError(f"Undefined function: {func_name}")
|
||||
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 '{func_name}' expects {len(param_names)} argument(s), 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
|
||||
106
centvrion/compiler/emit_stmt.py
Normal file
106
centvrion/compiler/emit_stmt.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from centvrion.ast_nodes import (
|
||||
Designa, DesignaIndex, SiStatement, DumStatement, PerStatement,
|
||||
Defini, Redi, Erumpe, ExpressionStatement, ID,
|
||||
)
|
||||
from centvrion.compiler.emit_expr import emit_expr
|
||||
|
||||
|
||||
def emit_stmt(node, ctx):
|
||||
"""
|
||||
Emit C code for a CENTVRION statement node.
|
||||
Returns lines — list of C statements.
|
||||
"""
|
||||
if isinstance(node, Designa):
|
||||
# Function alias: resolved at compile time, no runtime code needed
|
||||
if isinstance(node.value, ID) and node.value.name in ctx.func_resolve:
|
||||
return []
|
||||
val_lines, val_var = emit_expr(node.value, ctx)
|
||||
return val_lines + [f'cent_scope_set(&_scope, "{node.id.name}", {val_var});']
|
||||
|
||||
if isinstance(node, DesignaIndex):
|
||||
idx_lines, idx_var = emit_expr(node.index, ctx)
|
||||
val_lines, val_var = emit_expr(node.value, ctx)
|
||||
arr_tmp = ctx.fresh_tmp()
|
||||
return (
|
||||
idx_lines + val_lines + [
|
||||
f'CentValue {arr_tmp} = cent_scope_get(&_scope, "{node.id.name}");',
|
||||
f"cent_list_index_set(&{arr_tmp}, {idx_var}, {val_var});",
|
||||
f'cent_scope_set(&_scope, "{node.id.name}", {arr_tmp});',
|
||||
]
|
||||
)
|
||||
|
||||
if isinstance(node, SiStatement):
|
||||
cond_lines, cond_var = emit_expr(node.test, ctx)
|
||||
then_lines = _emit_body(node.statements, ctx)
|
||||
lines = cond_lines + [f"if (cent_truthy({cond_var})) {{"]
|
||||
lines += [f" {l}" for l in then_lines]
|
||||
if node.else_part:
|
||||
else_lines = _emit_body(node.else_part, ctx)
|
||||
lines += ["} else {"]
|
||||
lines += [f" {l}" for l in else_lines]
|
||||
lines += ["}"]
|
||||
return lines
|
||||
|
||||
if isinstance(node, DumStatement):
|
||||
# DVM loops UNTIL condition is true (inverted while)
|
||||
lines = ["while (1) {"]
|
||||
cond_lines, cond_var = emit_expr(node.test, ctx)
|
||||
lines += [f" {l}" for l in cond_lines]
|
||||
lines += [f" if (cent_truthy({cond_var})) break;"]
|
||||
body_lines = _emit_body(node.statements, ctx)
|
||||
lines += [f" {l}" for l in body_lines]
|
||||
lines += ["}"]
|
||||
return lines
|
||||
|
||||
if isinstance(node, PerStatement):
|
||||
arr_lines, arr_var = emit_expr(node.data_list, ctx)
|
||||
i_var = ctx.fresh_tmp()
|
||||
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}]);',
|
||||
]
|
||||
lines += [f" {l}" for l in body_lines]
|
||||
lines += ["}"]
|
||||
return lines
|
||||
|
||||
if isinstance(node, Defini):
|
||||
# Function definitions are hoisted by emitter.py; no-op here.
|
||||
return []
|
||||
|
||||
if isinstance(node, Redi):
|
||||
lines = []
|
||||
val_vars = []
|
||||
for v in node.values:
|
||||
v_lines, v_var = emit_expr(v, ctx)
|
||||
lines.extend(v_lines)
|
||||
val_vars.append(v_var)
|
||||
if len(val_vars) == 1:
|
||||
lines.append(f"_return_val = {val_vars[0]};")
|
||||
else:
|
||||
# multiple return values → pack into a list
|
||||
lst_tmp = ctx.fresh_tmp()
|
||||
lines.append(f"CentValue {lst_tmp} = cent_list_new({len(val_vars)});")
|
||||
for vv in val_vars:
|
||||
lines.append(f"cent_list_push(&{lst_tmp}, {vv});")
|
||||
lines.append(f"_return_val = {lst_tmp};")
|
||||
lines.append("goto _func_return;")
|
||||
return lines
|
||||
|
||||
if isinstance(node, Erumpe):
|
||||
return ["break;"]
|
||||
|
||||
if isinstance(node, ExpressionStatement):
|
||||
lines, _ = emit_expr(node.expression, ctx)
|
||||
return lines
|
||||
|
||||
raise NotImplementedError(type(node).__name__)
|
||||
|
||||
|
||||
def _emit_body(stmts, ctx):
|
||||
lines = []
|
||||
for s in stmts:
|
||||
lines.extend(emit_stmt(s, ctx))
|
||||
return lines
|
||||
72
centvrion/compiler/emitter.py
Normal file
72
centvrion/compiler/emitter.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import os
|
||||
from centvrion.ast_nodes import Defini, Designa, ID
|
||||
from centvrion.compiler.context import EmitContext
|
||||
from centvrion.compiler.emit_stmt import emit_stmt, _emit_body
|
||||
|
||||
_RUNTIME_DIR = os.path.join(os.path.dirname(__file__), "runtime")
|
||||
|
||||
|
||||
def compile_program(program):
|
||||
"""Return a complete C source string for the given Program AST node."""
|
||||
ctx = EmitContext()
|
||||
|
||||
# Module pre-pass
|
||||
for mc in program.modules:
|
||||
ctx.modules.add(mc.module_name)
|
||||
|
||||
# Function pre-pass: assign unique C names, track aliases in order
|
||||
func_version = {}
|
||||
func_definitions = [] # [(c_name, Defini_stmt), ...]
|
||||
for stmt in program.statements:
|
||||
if isinstance(stmt, Defini):
|
||||
name = stmt.name.name
|
||||
ver = func_version.get(name, 0)
|
||||
c_name = f"_cent_{name}" if ver == 0 else f"_cent_{name}_{ver}"
|
||||
func_version[name] = ver + 1
|
||||
ctx.functions[c_name] = [p.name for p in stmt.parameters]
|
||||
ctx.func_resolve[name] = c_name
|
||||
func_definitions.append((c_name, stmt))
|
||||
elif isinstance(stmt, Designa) and isinstance(stmt.value, ID):
|
||||
rhs = stmt.value.name
|
||||
if rhs in ctx.func_resolve:
|
||||
ctx.func_resolve[stmt.id.name] = ctx.func_resolve[rhs]
|
||||
|
||||
lines = []
|
||||
|
||||
# Includes
|
||||
lines += [
|
||||
f'#include "{_RUNTIME_DIR}/cent_runtime.h"',
|
||||
"",
|
||||
]
|
||||
|
||||
# Forward declarations
|
||||
for c_name in ctx.functions:
|
||||
lines.append(f"CentValue {c_name}(CentScope _scope);")
|
||||
if ctx.functions:
|
||||
lines.append("")
|
||||
|
||||
# Hoisted function definitions
|
||||
for c_name, stmt in func_definitions:
|
||||
ctx.current_function = c_name
|
||||
lines.append(f"CentValue {c_name}(CentScope _scope) {{")
|
||||
lines.append(" CentValue _return_val = cent_null();")
|
||||
for l in _emit_body(stmt.statements, ctx):
|
||||
lines.append(f" {l}")
|
||||
lines += ["_func_return:", " return _return_val;", "}", ""]
|
||||
ctx.current_function = None
|
||||
|
||||
# main()
|
||||
lines.append("int main(void) {")
|
||||
lines.append(" cent_init();")
|
||||
if "MAGNVM" in ctx.modules:
|
||||
lines.append(" cent_magnvm = 1;")
|
||||
lines.append(" CentScope _scope = {0};")
|
||||
lines.append(" CentValue _return_val = cent_null();")
|
||||
for stmt in program.statements:
|
||||
if isinstance(stmt, Defini):
|
||||
continue
|
||||
for l in emit_stmt(stmt, ctx):
|
||||
lines.append(f" {l}")
|
||||
lines += ["_func_return:", " return 0;", "}"]
|
||||
|
||||
return "\n".join(lines) + "\n"
|
||||
551
centvrion/compiler/runtime/cent_runtime.c
Normal file
551
centvrion/compiler/runtime/cent_runtime.c
Normal file
@@ -0,0 +1,551 @@
|
||||
#include "cent_runtime.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Global arena */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
CentArena *cent_arena;
|
||||
int cent_magnvm = 0;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Arena allocator */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
CentArena *cent_arena_new(size_t cap) {
|
||||
CentArena *a = malloc(sizeof(CentArena));
|
||||
if (!a) { fputs("cent: out of memory\n", stderr); exit(1); }
|
||||
a->buf = malloc(cap);
|
||||
if (!a->buf) { fputs("cent: out of memory\n", stderr); exit(1); }
|
||||
a->used = 0;
|
||||
a->cap = cap;
|
||||
a->next = NULL;
|
||||
return a;
|
||||
}
|
||||
|
||||
static size_t align8(size_t n) { return (n + 7) & ~(size_t)7; }
|
||||
|
||||
void *cent_arena_alloc(CentArena *a, size_t n) {
|
||||
n = align8(n);
|
||||
CentArena *cur = a;
|
||||
for (;;) {
|
||||
if (cur->used + n <= cur->cap) {
|
||||
void *p = cur->buf + cur->used;
|
||||
cur->used += n;
|
||||
return p;
|
||||
}
|
||||
if (!cur->next) {
|
||||
size_t new_cap = cur->cap > n ? cur->cap : n;
|
||||
cur->next = cent_arena_new(new_cap);
|
||||
}
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Error handling */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void cent_type_error(const char *msg) {
|
||||
fprintf(stderr, "CENTVRION type error: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void cent_runtime_error(const char *msg) {
|
||||
fprintf(stderr, "CENTVRION error: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Scope operations */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
CentValue cent_scope_get(CentScope *s, const char *name) {
|
||||
for (int i = 0; i < s->len; i++) {
|
||||
if (strcmp(s->names[i], name) == 0)
|
||||
return s->vals[i];
|
||||
}
|
||||
fprintf(stderr, "CENTVRION error: undefined variable '%s'\n", name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void cent_scope_set(CentScope *s, const char *name, CentValue v) {
|
||||
for (int i = 0; i < s->len; i++) {
|
||||
if (strcmp(s->names[i], name) == 0) {
|
||||
s->vals[i] = v;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (s->len >= s->cap) {
|
||||
int new_cap = s->cap ? s->cap * 2 : 8;
|
||||
const char **new_names = cent_arena_alloc(cent_arena, new_cap * sizeof(char *));
|
||||
CentValue *new_vals = cent_arena_alloc(cent_arena, new_cap * sizeof(CentValue));
|
||||
if (s->len > 0) {
|
||||
memcpy(new_names, s->names, s->len * sizeof(char *));
|
||||
memcpy(new_vals, s->vals, s->len * sizeof(CentValue));
|
||||
}
|
||||
s->names = new_names;
|
||||
s->vals = new_vals;
|
||||
s->cap = new_cap;
|
||||
}
|
||||
s->names[s->len] = name;
|
||||
s->vals[s->len] = v;
|
||||
s->len++;
|
||||
}
|
||||
|
||||
CentScope cent_scope_copy(CentScope *s) {
|
||||
CentScope dst;
|
||||
dst.len = s->len;
|
||||
dst.cap = s->cap;
|
||||
if (s->cap > 0) {
|
||||
dst.names = cent_arena_alloc(cent_arena, s->cap * sizeof(char *));
|
||||
dst.vals = cent_arena_alloc(cent_arena, s->cap * sizeof(CentValue));
|
||||
if (s->len > 0) {
|
||||
memcpy(dst.names, s->names, s->len * sizeof(char *));
|
||||
memcpy(dst.vals, s->vals, s->len * sizeof(CentValue));
|
||||
}
|
||||
} else {
|
||||
dst.names = NULL;
|
||||
dst.vals = NULL;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Roman numeral conversion */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Descending table used for both int→roman and roman→int */
|
||||
static const struct { const char *str; long val; } ROMAN_TABLE[] = {
|
||||
{"M", 1000}, {"CM", 900}, {"D", 500}, {"CD", 400},
|
||||
{"C", 100}, {"XC", 90}, {"L", 50}, {"XL", 40},
|
||||
{"X", 10}, {"IX", 9}, {"V", 5}, {"IV", 4},
|
||||
{"I", 1}
|
||||
};
|
||||
#define ROMAN_TABLE_LEN 13
|
||||
|
||||
/* Transform a 1-3999 Roman string into its x1000 equivalent.
|
||||
Each char: 'I' -> "M" (since I*1000 = M), others -> char + "_". */
|
||||
static void transform_thousands(const char *src, char *dst, size_t dstsz) {
|
||||
size_t pos = 0;
|
||||
for (const char *p = src; *p; p++) {
|
||||
if (*p == 'I') {
|
||||
if (pos + 1 >= dstsz) cent_runtime_error("Roman numeral buffer overflow");
|
||||
dst[pos++] = 'M';
|
||||
} else {
|
||||
if (pos + 2 >= dstsz) cent_runtime_error("Roman numeral buffer overflow");
|
||||
dst[pos++] = *p;
|
||||
dst[pos++] = '_';
|
||||
}
|
||||
}
|
||||
dst[pos] = '\0';
|
||||
}
|
||||
|
||||
void cent_int_to_roman(long n, char *buf, size_t bufsz) {
|
||||
if (n <= 0 || (n > 3999 && !cent_magnvm))
|
||||
cent_runtime_error("number out of range for Roman numerals (1-3999)");
|
||||
size_t pos = 0;
|
||||
if (n > 3999) {
|
||||
char base[64];
|
||||
cent_int_to_roman(n / 1000, base, sizeof(base));
|
||||
char transformed[128];
|
||||
transform_thousands(base, transformed, sizeof(transformed));
|
||||
size_t tlen = strlen(transformed);
|
||||
if (tlen >= bufsz) cent_runtime_error("Roman numeral buffer overflow");
|
||||
memcpy(buf, transformed, tlen);
|
||||
pos = tlen;
|
||||
n = n % 1000;
|
||||
if (n == 0) { buf[pos] = '\0'; return; }
|
||||
}
|
||||
for (int i = 0; i < ROMAN_TABLE_LEN && n > 0; i++) {
|
||||
while (n >= ROMAN_TABLE[i].val) {
|
||||
size_t slen = strlen(ROMAN_TABLE[i].str);
|
||||
if (pos + slen >= bufsz)
|
||||
cent_runtime_error("Roman numeral buffer overflow");
|
||||
memcpy(buf + pos, ROMAN_TABLE[i].str, slen);
|
||||
pos += slen;
|
||||
n -= ROMAN_TABLE[i].val;
|
||||
}
|
||||
}
|
||||
buf[pos] = '\0';
|
||||
}
|
||||
|
||||
long cent_roman_to_int(const char *s) {
|
||||
long result = 0;
|
||||
int pos = 0;
|
||||
int slen = (int)strlen(s);
|
||||
if (slen == 0)
|
||||
cent_runtime_error("empty Roman numeral");
|
||||
while (pos < slen) {
|
||||
int matched = 0;
|
||||
for (int i = 0; i < ROMAN_TABLE_LEN; i++) {
|
||||
int tlen = (int)strlen(ROMAN_TABLE[i].str);
|
||||
if (strncmp(s + pos, ROMAN_TABLE[i].str, tlen) == 0) {
|
||||
result += ROMAN_TABLE[i].val;
|
||||
pos += tlen;
|
||||
matched = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
fprintf(stderr, "CENTVRION error: invalid Roman numeral: %s\n", s);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Display */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Write value as string into buf[0..bufsz-1]; return chars needed */
|
||||
/* (not counting null). buf may be NULL (just count mode). */
|
||||
static int write_val(CentValue v, char *buf, int bufsz) {
|
||||
char tmp[64];
|
||||
int n;
|
||||
switch (v.type) {
|
||||
case CENT_INT:
|
||||
cent_int_to_roman(v.ival, tmp, sizeof(tmp));
|
||||
n = (int)strlen(tmp);
|
||||
if (buf && n < bufsz) { memcpy(buf, tmp, n); buf[n] = '\0'; }
|
||||
return n;
|
||||
|
||||
case CENT_STR:
|
||||
n = (int)strlen(v.sval);
|
||||
if (buf && n < bufsz) { memcpy(buf, v.sval, n); buf[n] = '\0'; }
|
||||
return n;
|
||||
|
||||
case CENT_BOOL:
|
||||
if (v.bval) {
|
||||
if (buf && bufsz > 5) { memcpy(buf, "VERVS", 5); buf[5] = '\0'; }
|
||||
return 5;
|
||||
} else {
|
||||
if (buf && bufsz > 6) { memcpy(buf, "FALSVS", 6); buf[6] = '\0'; }
|
||||
return 6;
|
||||
}
|
||||
|
||||
case CENT_NULL:
|
||||
if (buf && bufsz > 6) { memcpy(buf, "NVLLVS", 6); buf[6] = '\0'; }
|
||||
return 6;
|
||||
|
||||
case CENT_FRAC: {
|
||||
long num = v.fval.num, den = v.fval.den;
|
||||
if (den < 0) { num = -num; den = -den; }
|
||||
if (num < 0)
|
||||
cent_runtime_error("cannot display negative fraction without SVBNVLLA");
|
||||
long int_part = num / den;
|
||||
long rem_num = num % den;
|
||||
|
||||
/* Integer part (omit leading zero for pure fractions) */
|
||||
char int_buf[64] = {0};
|
||||
int int_len = 0;
|
||||
if (int_part > 0 || rem_num == 0) {
|
||||
cent_int_to_roman(int_part, int_buf, sizeof(int_buf));
|
||||
int_len = (int)strlen(int_buf);
|
||||
}
|
||||
|
||||
/* Duodecimal fractional expansion — mirrors fraction_to_frac() */
|
||||
char frac_buf[64] = {0};
|
||||
int frac_pos = 0;
|
||||
long cur_num = rem_num, cur_den = den;
|
||||
for (int lvl = 0; lvl < 6 && cur_num != 0; lvl++) {
|
||||
if (lvl > 0) frac_buf[frac_pos++] = '|';
|
||||
long level_int = (cur_num * 12) / cur_den;
|
||||
cur_num = (cur_num * 12) % cur_den;
|
||||
for (int i = 0; i < (int)(level_int / 6); i++) frac_buf[frac_pos++] = 'S';
|
||||
for (int i = 0; i < (int)((level_int % 6) / 2); i++) frac_buf[frac_pos++] = ':';
|
||||
for (int i = 0; i < (int)((level_int % 6) % 2); i++) frac_buf[frac_pos++] = '.';
|
||||
}
|
||||
|
||||
n = int_len + frac_pos;
|
||||
if (buf && n < bufsz) {
|
||||
memcpy(buf, int_buf, int_len);
|
||||
memcpy(buf + int_len, frac_buf, frac_pos);
|
||||
buf[n] = '\0';
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
case CENT_LIST: {
|
||||
/* "[elem1 elem2 ...]" */
|
||||
int total = 2; /* '[' + ']' */
|
||||
for (int i = 0; i < v.lval.len; i++) {
|
||||
if (i > 0) total++; /* space separator */
|
||||
total += write_val(v.lval.items[i], NULL, 0);
|
||||
}
|
||||
if (!buf) return total;
|
||||
int pos = 0;
|
||||
buf[pos++] = '[';
|
||||
for (int i = 0; i < v.lval.len; i++) {
|
||||
if (i > 0) buf[pos++] = ' ';
|
||||
int written = write_val(v.lval.items[i], buf + pos, bufsz - pos);
|
||||
pos += written;
|
||||
}
|
||||
buf[pos++] = ']';
|
||||
if (pos < bufsz) buf[pos] = '\0';
|
||||
return total;
|
||||
}
|
||||
|
||||
default:
|
||||
cent_runtime_error("cannot display value");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
char *cent_make_string(CentValue v) {
|
||||
int len = write_val(v, NULL, 0);
|
||||
char *buf = cent_arena_alloc(cent_arena, len + 1);
|
||||
write_val(v, buf, len + 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Arithmetic and comparison operators */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static long gcd(long a, long b) {
|
||||
a = a < 0 ? -a : a;
|
||||
b = b < 0 ? -b : b;
|
||||
while (b) { long t = b; b = a % b; a = t; }
|
||||
return a ? a : 1;
|
||||
}
|
||||
|
||||
static CentValue frac_reduce(long num, long den) {
|
||||
if (den < 0) { num = -num; den = -den; }
|
||||
long g = gcd(num < 0 ? -num : num, den);
|
||||
return cent_frac(num / g, den / g);
|
||||
}
|
||||
|
||||
static void to_frac(CentValue v, long *num, long *den) {
|
||||
if (v.type == CENT_INT) { *num = v.ival; *den = 1; }
|
||||
else if (v.type == CENT_NULL) { *num = 0; *den = 1; }
|
||||
else { *num = v.fval.num; *den = v.fval.den; }
|
||||
}
|
||||
|
||||
CentValue cent_add(CentValue a, CentValue b) {
|
||||
if (a.type == CENT_INT && b.type == CENT_INT)
|
||||
return cent_int(a.ival + b.ival);
|
||||
if (a.type == CENT_NULL && b.type == CENT_NULL)
|
||||
return cent_null();
|
||||
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
||||
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
||||
long an, ad, bn, bd;
|
||||
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||
return frac_reduce(an * bd + bn * ad, ad * bd);
|
||||
}
|
||||
if (a.type == CENT_STR || b.type == CENT_STR)
|
||||
cent_type_error("'+' cannot be used with strings; use '&' for concatenation");
|
||||
else
|
||||
cent_type_error("'+' requires two integers");
|
||||
return cent_null();
|
||||
}
|
||||
|
||||
CentValue cent_concat(CentValue a, CentValue b) {
|
||||
const char *sa = (a.type == CENT_NULL) ? "" : cent_make_string(a);
|
||||
const char *sb = (b.type == CENT_NULL) ? "" : cent_make_string(b);
|
||||
int la = (int)strlen(sa), lb = (int)strlen(sb);
|
||||
char *s = cent_arena_alloc(cent_arena, la + lb + 1);
|
||||
memcpy(s, sa, la);
|
||||
memcpy(s + la, sb, lb);
|
||||
s[la + lb] = '\0';
|
||||
return cent_str(s);
|
||||
}
|
||||
|
||||
CentValue cent_sub(CentValue a, CentValue b) {
|
||||
if (a.type == CENT_INT && b.type == CENT_INT)
|
||||
return cent_int(a.ival - b.ival);
|
||||
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
||||
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
||||
long an, ad, bn, bd;
|
||||
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||
return frac_reduce(an * bd - bn * ad, ad * bd);
|
||||
}
|
||||
cent_type_error("'-' requires two integers");
|
||||
return cent_null();
|
||||
}
|
||||
|
||||
CentValue cent_mul(CentValue a, CentValue b) {
|
||||
if (a.type == CENT_INT && b.type == CENT_INT)
|
||||
return cent_int(a.ival * b.ival);
|
||||
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
||||
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
||||
long an, ad, bn, bd;
|
||||
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||
return frac_reduce(an * bn, ad * bd);
|
||||
}
|
||||
cent_type_error("'*' requires two integers");
|
||||
return cent_null();
|
||||
}
|
||||
|
||||
CentValue cent_div(CentValue a, CentValue b) {
|
||||
if (a.type != CENT_INT || b.type != CENT_INT)
|
||||
cent_type_error("'/' requires two integers");
|
||||
if (b.ival == 0)
|
||||
cent_runtime_error("division by zero");
|
||||
return cent_int(a.ival / b.ival);
|
||||
}
|
||||
|
||||
CentValue cent_div_frac(CentValue a, CentValue b) {
|
||||
long an, ad, bn, bd;
|
||||
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||
if (bn == 0) cent_runtime_error("division by zero");
|
||||
return frac_reduce(an * bd, ad * bn);
|
||||
}
|
||||
|
||||
CentValue cent_eq(CentValue a, CentValue b) {
|
||||
if ((a.type == CENT_INT || a.type == CENT_FRAC) &&
|
||||
(b.type == CENT_INT || b.type == CENT_FRAC)) {
|
||||
long an, ad, bn, bd;
|
||||
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||
return cent_bool(an * bd == bn * ad);
|
||||
}
|
||||
if (a.type != b.type) return cent_bool(0);
|
||||
switch (a.type) {
|
||||
case CENT_STR: return cent_bool(strcmp(a.sval, b.sval) == 0);
|
||||
case CENT_BOOL: return cent_bool(a.bval == b.bval);
|
||||
case CENT_NULL: return cent_bool(1);
|
||||
default:
|
||||
cent_type_error("'EST' not supported for this type");
|
||||
return cent_null();
|
||||
}
|
||||
}
|
||||
|
||||
CentValue cent_lt(CentValue a, CentValue b) {
|
||||
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
||||
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
||||
long an, ad, bn, bd;
|
||||
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||
return cent_bool(an * bd < bn * ad);
|
||||
}
|
||||
cent_type_error("'MINVS' requires two integers");
|
||||
return cent_null();
|
||||
}
|
||||
|
||||
CentValue cent_gt(CentValue a, CentValue b) {
|
||||
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
||||
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
||||
long an, ad, bn, bd;
|
||||
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||
return cent_bool(an * bd > bn * ad);
|
||||
}
|
||||
cent_type_error("'PLVS' requires two integers");
|
||||
return cent_null();
|
||||
}
|
||||
|
||||
CentValue cent_and(CentValue a, CentValue b) {
|
||||
if (a.type != CENT_BOOL || b.type != CENT_BOOL)
|
||||
cent_type_error("'ET' requires two booleans");
|
||||
return cent_bool(a.bval && b.bval);
|
||||
}
|
||||
|
||||
CentValue cent_or(CentValue a, CentValue b) {
|
||||
if (a.type != CENT_BOOL || b.type != CENT_BOOL)
|
||||
cent_type_error("'AVT' requires two booleans");
|
||||
return cent_bool(a.bval || b.bval);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Builtin functions */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void cent_dice(CentValue v) {
|
||||
char *s = cent_make_string(v);
|
||||
fputs(s, stdout);
|
||||
fputc('\n', stdout);
|
||||
}
|
||||
|
||||
CentValue cent_avdi(void) {
|
||||
char *buf = cent_arena_alloc(cent_arena, 1024);
|
||||
if (!fgets(buf, 1024, stdin)) {
|
||||
buf[0] = '\0';
|
||||
return cent_str(buf);
|
||||
}
|
||||
int len = (int)strlen(buf);
|
||||
if (len > 0 && buf[len - 1] == '\n')
|
||||
buf[len - 1] = '\0';
|
||||
return cent_str(buf);
|
||||
}
|
||||
|
||||
CentValue cent_avdi_numerus(void) {
|
||||
CentValue s = cent_avdi();
|
||||
return cent_int(cent_roman_to_int(s.sval));
|
||||
}
|
||||
|
||||
CentValue cent_longitudo(CentValue v) {
|
||||
if (v.type != CENT_LIST)
|
||||
cent_type_error("'LONGITVDO' requires a list");
|
||||
return cent_int(v.lval.len);
|
||||
}
|
||||
|
||||
CentValue cent_fortis_numerus(CentValue lo, CentValue hi) {
|
||||
if (lo.type != CENT_INT || hi.type != CENT_INT)
|
||||
cent_type_error("'FORTIS_NVMERVS' requires two integers");
|
||||
long range = hi.ival - lo.ival + 1;
|
||||
if (range <= 0)
|
||||
cent_runtime_error("'FORTIS_NVMERVS' requires lo <= hi");
|
||||
return cent_int(lo.ival + rand() % range);
|
||||
}
|
||||
|
||||
CentValue cent_fortis_electionis(CentValue lst) {
|
||||
if (lst.type != CENT_LIST)
|
||||
cent_type_error("'FORTIS_ELECTIONIS' requires a list");
|
||||
if (lst.lval.len == 0)
|
||||
cent_runtime_error("'FORTIS_ELECTIONIS' requires a non-empty list");
|
||||
return lst.lval.items[rand() % lst.lval.len];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Array helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
CentValue cent_list_new(int cap) {
|
||||
CentValue *items = cent_arena_alloc(cent_arena, cap * sizeof(CentValue));
|
||||
return cent_list(items, 0, cap);
|
||||
}
|
||||
|
||||
void cent_list_push(CentValue *lst, CentValue v) {
|
||||
if (lst->lval.len >= lst->lval.cap) {
|
||||
int new_cap = lst->lval.cap ? lst->lval.cap * 2 : 8;
|
||||
CentValue *new_items = cent_arena_alloc(cent_arena, new_cap * sizeof(CentValue));
|
||||
if (lst->lval.len > 0)
|
||||
memcpy(new_items, lst->lval.items, lst->lval.len * sizeof(CentValue));
|
||||
lst->lval.items = new_items;
|
||||
lst->lval.cap = new_cap;
|
||||
}
|
||||
lst->lval.items[lst->lval.len++] = v;
|
||||
}
|
||||
|
||||
CentValue cent_list_index(CentValue lst, CentValue idx) {
|
||||
if (lst.type != CENT_LIST)
|
||||
cent_type_error("index requires a list");
|
||||
if (idx.type != CENT_INT)
|
||||
cent_type_error("list index must be an integer");
|
||||
long i = idx.ival;
|
||||
if (i < 1 || i > lst.lval.len)
|
||||
cent_runtime_error("list index out of range");
|
||||
return lst.lval.items[i - 1];
|
||||
}
|
||||
|
||||
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v) {
|
||||
if (lst->type != CENT_LIST)
|
||||
cent_type_error("index-assign requires a list");
|
||||
if (idx.type != CENT_INT)
|
||||
cent_type_error("list index must be an integer");
|
||||
long i = idx.ival;
|
||||
if (i < 1 || i > lst->lval.len)
|
||||
cent_runtime_error("list index out of range");
|
||||
lst->lval.items[i - 1] = v;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Initialisation */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void cent_init(void) {
|
||||
cent_arena = cent_arena_new(1024 * 1024); /* 1 MiB initial arena */
|
||||
srand((unsigned)time(NULL));
|
||||
}
|
||||
194
centvrion/compiler/runtime/cent_runtime.h
Normal file
194
centvrion/compiler/runtime/cent_runtime.h
Normal file
@@ -0,0 +1,194 @@
|
||||
#ifndef CENT_RUNTIME_H
|
||||
#define CENT_RUNTIME_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Types */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef enum {
|
||||
CENT_INT,
|
||||
CENT_STR,
|
||||
CENT_BOOL,
|
||||
CENT_LIST,
|
||||
CENT_FRAC,
|
||||
CENT_NULL
|
||||
} CentType;
|
||||
|
||||
typedef struct CentValue CentValue;
|
||||
typedef struct CentList CentList;
|
||||
|
||||
/* Duodecimal fraction: num/den stored as exact integers */
|
||||
typedef struct {
|
||||
long num;
|
||||
long den;
|
||||
} CentFrac;
|
||||
|
||||
struct CentList {
|
||||
CentValue *items;
|
||||
int len;
|
||||
int cap;
|
||||
};
|
||||
|
||||
struct CentValue {
|
||||
CentType type;
|
||||
union {
|
||||
long ival; /* CENT_INT */
|
||||
char *sval; /* CENT_STR */
|
||||
int bval; /* CENT_BOOL */
|
||||
CentList lval; /* CENT_LIST */
|
||||
CentFrac fval; /* CENT_FRAC */
|
||||
};
|
||||
};
|
||||
|
||||
/* Scope: flat name→value array. Stack-allocated by the caller;
|
||||
cent_scope_set uses cent_arena when it needs to grow. */
|
||||
typedef struct {
|
||||
const char **names;
|
||||
CentValue *vals;
|
||||
int len;
|
||||
int cap;
|
||||
} CentScope;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Arena allocator — no free() during a program run */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
typedef struct CentArena CentArena;
|
||||
|
||||
struct CentArena {
|
||||
char *buf;
|
||||
size_t used;
|
||||
size_t cap;
|
||||
CentArena *next; /* overflow chain */
|
||||
};
|
||||
|
||||
CentArena *cent_arena_new(size_t cap);
|
||||
void *cent_arena_alloc(CentArena *a, size_t n);
|
||||
|
||||
/* Global arena; initialised in cent_runtime.c main() setup */
|
||||
extern CentArena *cent_arena;
|
||||
|
||||
/* Set to 1 when CVM MAGNVM is active; enables extended numeral display */
|
||||
extern int cent_magnvm;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Value constructors */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static inline CentValue cent_int(long v) {
|
||||
CentValue r; r.type = CENT_INT; r.ival = v; return r;
|
||||
}
|
||||
static inline CentValue cent_str(const char *s) {
|
||||
CentValue r; r.type = CENT_STR; r.sval = (char *)s; return r;
|
||||
}
|
||||
static inline CentValue cent_bool(int v) {
|
||||
CentValue r; r.type = CENT_BOOL; r.bval = !!v; return r;
|
||||
}
|
||||
static inline CentValue cent_null(void) {
|
||||
CentValue r; r.type = CENT_NULL; r.ival = 0; return r;
|
||||
}
|
||||
static inline CentValue cent_frac(long num, long den) {
|
||||
CentValue r; r.type = CENT_FRAC; r.fval.num = num; r.fval.den = den; return r;
|
||||
}
|
||||
static inline CentValue cent_list(CentValue *items, int len, int cap) {
|
||||
CentValue r;
|
||||
r.type = CENT_LIST;
|
||||
r.lval.items = items;
|
||||
r.lval.len = len;
|
||||
r.lval.cap = cap;
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Error handling */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void cent_type_error(const char *msg); /* type mismatch → exit(1) */
|
||||
void cent_runtime_error(const char *msg); /* runtime fault → exit(1) */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Truthiness — conditions must be booleans; anything else is a fault */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static inline int cent_truthy(CentValue v) {
|
||||
if (v.type != CENT_BOOL)
|
||||
cent_type_error("condition must be a boolean (VERITAS/FALSITAS)");
|
||||
return v.bval;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Scope operations */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Look up name; returns cent_null() if not found */
|
||||
CentValue cent_scope_get(CentScope *s, const char *name);
|
||||
|
||||
/* Insert or update name→v; grows via cent_arena if needed */
|
||||
void cent_scope_set(CentScope *s, const char *name, CentValue v);
|
||||
|
||||
/* Shallow copy of a scope (matches vtable.copy() semantics) */
|
||||
CentScope cent_scope_copy(CentScope *s);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Roman numeral conversion */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* int → Roman numeral string (1–3999, or any positive int if cent_magnvm set) */
|
||||
void cent_int_to_roman(long n, char *buf, size_t bufsz);
|
||||
/* Roman numeral string → int; exits on invalid input */
|
||||
long cent_roman_to_int(const char *s);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Display */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Returns arena-allocated string mirroring Python make_string() */
|
||||
char *cent_make_string(CentValue v);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Arithmetic and comparison operators */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
CentValue cent_add(CentValue a, CentValue b); /* INT+INT or FRAC+FRAC/INT */
|
||||
CentValue cent_concat(CentValue a, CentValue b); /* & operator: coerce all types to str */
|
||||
CentValue cent_sub(CentValue a, CentValue b); /* INT-INT or FRAC-FRAC/INT */
|
||||
CentValue cent_mul(CentValue a, CentValue b); /* INT*INT or FRAC*FRAC/INT */
|
||||
CentValue cent_div(CentValue a, CentValue b); /* INT/INT integer div */
|
||||
CentValue cent_div_frac(CentValue a, CentValue b); /* FRACTIO: exact div → FRAC */
|
||||
CentValue cent_eq (CentValue a, CentValue b); /* EST → BOOL */
|
||||
CentValue cent_lt (CentValue a, CentValue b); /* MINVS → BOOL */
|
||||
CentValue cent_gt (CentValue a, CentValue b); /* PLVS → BOOL */
|
||||
CentValue cent_and(CentValue a, CentValue b); /* ET → BOOL */
|
||||
CentValue cent_or (CentValue a, CentValue b); /* AVT → BOOL */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Builtin functions */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void cent_dice(CentValue v); /* DICE */
|
||||
CentValue cent_avdi(void); /* AVDI */
|
||||
CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */
|
||||
CentValue cent_longitudo(CentValue v); /* LONGITVDO */
|
||||
CentValue cent_fortis_numerus(CentValue lo, CentValue hi); /* FORTIS_NVMERVS */
|
||||
CentValue cent_fortis_electionis(CentValue lst); /* FORTIS_ELECTIONIS */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Array helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
CentValue cent_list_new(int cap);
|
||||
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);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Initialisation */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Call once at program start: sets up arena + seeds RNG */
|
||||
void cent_init(void);
|
||||
|
||||
#endif /* CENT_RUNTIME_H */
|
||||
Reference in New Issue
Block a user