Compare commits
4 Commits
6413d9fd1c
...
693054491f
| Author | SHA1 | Date | |
|---|---|---|---|
| 693054491f | |||
| db5b7bf144 | |||
| 264ea84dfc | |||
| 0b8b7c086e |
74
README.md
74
README.md
@@ -65,6 +65,21 @@ Strings are concatenated with `&`:
|
||||
|
||||
`NVLLVS` coerces to an empty string when used with `&`. Note: `+` is for arithmetic only — using it on strings raises an error.
|
||||
|
||||
#### String Interpolation
|
||||
|
||||
Double-quoted strings support interpolation with `{expression}`:
|
||||
|
||||
```
|
||||
DESIGNA nomen VT "Marcus"
|
||||
DICE("Salve, {nomen}!") // → Salve, Marcus!
|
||||
DICE("Sum: {III + IV}") // → Sum: VII
|
||||
DICE("{nomen} has {V} cats") // → Marcus has V cats
|
||||
```
|
||||
|
||||
Any expression can appear inside `{}`. Values are coerced to strings the same way as with `&` (integers become Roman numerals, booleans become `VERITAS`/`FALSITAS`, etc.).
|
||||
|
||||
Single-quoted strings do **not** interpolate — `'{nomen}'` is the literal text `{nomen}`. Use `{{` and `}}` for literal braces in double-quoted strings: `"use {{braces}}"` → `use {braces}`.
|
||||
|
||||
Integer modulo is `RELIQVVM`: `VII RELIQVVM III` evaluates to `I`. Under the `FRACTIO` module it returns a fraction, so `IIIS RELIQVVM IS` is `S` (i.e. 1/2).
|
||||
|
||||
### Integers
|
||||
@@ -112,6 +127,32 @@ Individual elements can be accessed by index using square brackets. Indexing is
|
||||
> I
|
||||
```
|
||||
|
||||
### Dicts (TABVLA)
|
||||
|
||||
Dicts are key-value maps created with the `TABVLA` keyword and curly braces:
|
||||
|
||||
```
|
||||
DESIGNA d VT TABVLA {"nomen" VT "Marcus", "aetas" VT XXV}
|
||||
```
|
||||
|
||||
Keys must be strings or integers. Values are accessed and assigned with square brackets:
|
||||
|
||||
```
|
||||
DICE(d["nomen"]) // → Marcus
|
||||
DESIGNA d["aetas"] VT XXVI // update existing key
|
||||
DESIGNA d["novus"] VT I // insert new key
|
||||
```
|
||||
|
||||
Iterating over a dict with `PER` loops over its keys:
|
||||
|
||||
```
|
||||
PER k IN d FACE {
|
||||
DICE(k)
|
||||
}
|
||||
```
|
||||
|
||||
`LONGITVDO(dict)` returns the number of entries. `CLAVES(dict)` returns the keys as an array.
|
||||
|
||||
## Conditionals
|
||||
### SI/TVNC
|
||||
If-then statements are denoted with the keywords `SI` (if) and `TVNC` (then). Thus, the code
|
||||
@@ -203,9 +244,31 @@ Calling a function is done with the `INVOCA` keyword.
|
||||
> CXXI
|
||||
```
|
||||
|
||||
## First-class functions
|
||||
Functions are first-class values in CENTVRION. They can be assigned to variables, passed as arguments, returned from functions, and stored in arrays or dicts.
|
||||
|
||||
Anonymous functions are created with the `FVNCTIO` keyword:
|
||||
|
||||

|
||||
|
||||
```
|
||||
> XIV
|
||||
```
|
||||
|
||||
`INVOCA` accepts any expression as the callee, not just a name:
|
||||
|
||||

|
||||
```
|
||||
> VI
|
||||
> VI
|
||||
> XVI
|
||||
```
|
||||
|
||||
Note: CENTVRION does **not** have closures. When a function is called, it receives a copy of the *caller's* scope, not the scope where it was defined. Variables from a function's definition site are only available if they also exist in the caller's scope at call time.
|
||||
|
||||
## Built-ins
|
||||
### DICE
|
||||
`DICE value ...`
|
||||
`DICE(value, ...)`
|
||||
|
||||
Prints one or more values to stdout, space-separated, with integers rendered as Roman numerals. Returns the printed string.
|
||||
|
||||
@@ -232,9 +295,14 @@ Skips the rest of the current loop body and continues to the next iteration (`DV
|
||||
Breaks out of the current loop (`DVM` or `PER`). Has no meaningful return value.
|
||||
|
||||
### LONGITVDO
|
||||
`LONGITVDO array` or `LONGITVDO string`
|
||||
`LONGITVDO(array)`, `LONGITVDO(string)`, or `LONGITVDO(dict)`
|
||||
|
||||
Returns the length of `array` (element count) or `string` (character count) as an integer.
|
||||
Returns the length of `array` (element count), `string` (character count), or `dict` (entry count) as an integer.
|
||||
|
||||
### CLAVES
|
||||
`CLAVES(dict)`
|
||||
|
||||
Returns the keys of `dict` as an array.
|
||||
|
||||
### SENATVS
|
||||
`SENATVS(bool, ...)` or `SENATVS([bool])`
|
||||
|
||||
@@ -5,7 +5,7 @@ from fractions import Fraction
|
||||
from rply.token import BaseBox
|
||||
|
||||
from centvrion.errors import CentvrionError
|
||||
from centvrion.values import Val, ValInt, ValStr, ValBool, ValList, ValNul, ValFunc, ValFrac
|
||||
from centvrion.values import Val, ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac
|
||||
|
||||
NUMERALS = {
|
||||
"I": 1,
|
||||
@@ -136,6 +136,16 @@ 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 + "}"
|
||||
elif isinstance(val, ValFunc):
|
||||
return "FVNCTIO"
|
||||
else:
|
||||
raise CentvrionError(f"Cannot display {val!r}")
|
||||
|
||||
@@ -272,9 +282,36 @@ 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
|
||||
self.quote = '"'
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) == type(other) and self.value == other.value
|
||||
@@ -283,12 +320,60 @@ class String(Node):
|
||||
return f"String({self.value})"
|
||||
|
||||
def print(self):
|
||||
return f'"{self.value}"'
|
||||
if self.quote == "'":
|
||||
return f"'{self.value}'"
|
||||
escaped = self.value.replace('{', '{{').replace('}', '}}')
|
||||
return f'"{escaped}"'
|
||||
|
||||
def _eval(self, vtable):
|
||||
return vtable, ValStr(self.value)
|
||||
|
||||
|
||||
def _flip_quotes(node, quote):
|
||||
"""Recursively set quote style on all String nodes in an expression tree."""
|
||||
if isinstance(node, String):
|
||||
node.quote = quote
|
||||
for attr in vars(node).values():
|
||||
if isinstance(attr, Node):
|
||||
_flip_quotes(attr, quote)
|
||||
elif isinstance(attr, list):
|
||||
for item in attr:
|
||||
if isinstance(item, Node):
|
||||
_flip_quotes(item, quote)
|
||||
|
||||
|
||||
class InterpolatedString(Node):
|
||||
def __init__(self, parts) -> None:
|
||||
self.parts = parts
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) == type(other) and self.parts == other.parts
|
||||
|
||||
def __repr__(self):
|
||||
return f"InterpolatedString([{rep_join(self.parts)}])"
|
||||
|
||||
def print(self):
|
||||
result = '"'
|
||||
for part in self.parts:
|
||||
if isinstance(part, String):
|
||||
result += part.value.replace('{', '{{').replace('}', '}}')
|
||||
else:
|
||||
_flip_quotes(part, "'")
|
||||
result += '{' + part.print() + '}'
|
||||
_flip_quotes(part, '"')
|
||||
result += '"'
|
||||
return result
|
||||
|
||||
def _eval(self, vtable):
|
||||
magnvm = "MAGNVM" in vtable["#modules"]
|
||||
svbnvlla = "SVBNVLLA" in vtable["#modules"]
|
||||
pieces = []
|
||||
for part in self.parts:
|
||||
vtable, val = part.eval(vtable)
|
||||
pieces.append(make_string(val, magnvm, svbnvlla))
|
||||
return vtable, ValStr(''.join(pieces))
|
||||
|
||||
|
||||
class Numeral(Node):
|
||||
def __init__(self, value: str) -> None:
|
||||
self.value = value
|
||||
@@ -420,8 +505,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):
|
||||
@@ -487,6 +579,31 @@ class Defini(Node):
|
||||
return vtable, ValNul()
|
||||
|
||||
|
||||
class Fvnctio(Node):
|
||||
def __init__(self, parameters: list[ID], statements: list[Node]) -> None:
|
||||
self.parameters = parameters
|
||||
self.statements = statements
|
||||
|
||||
def __eq__(self, other):
|
||||
return (type(self) == type(other)
|
||||
and self.parameters == other.parameters
|
||||
and self.statements == other.statements)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
parameter_string = f"parameters([{rep_join(self.parameters)}])"
|
||||
statements_string = f"statements([{rep_join(self.statements)}])"
|
||||
fvn_string = rep_join([parameter_string, statements_string])
|
||||
return f"Fvnctio({fvn_string})"
|
||||
|
||||
def print(self):
|
||||
params = ", ".join(p.print() for p in self.parameters)
|
||||
body = "\n".join(s.print() for s in self.statements)
|
||||
return f"FVNCTIO ({params}) VT {{\n{body}\n}}"
|
||||
|
||||
def _eval(self, vtable):
|
||||
return vtable, ValFunc(self.parameters, self.statements)
|
||||
|
||||
|
||||
class Redi(Node):
|
||||
def __init__(self, values: list[Node]) -> None:
|
||||
self.values = values
|
||||
@@ -707,6 +824,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):
|
||||
@@ -830,8 +955,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:
|
||||
@@ -853,32 +981,36 @@ class PerStatement(Node):
|
||||
|
||||
|
||||
class Invoca(Node):
|
||||
def __init__(self, name, parameters) -> None:
|
||||
self.name = name
|
||||
def __init__(self, callee, parameters) -> None:
|
||||
self.callee = callee
|
||||
self.parameters = parameters
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) == type(other) and self.name == other.name and self.parameters == other.parameters
|
||||
return (type(self) == type(other)
|
||||
and self.callee == other.callee
|
||||
and self.parameters == other.parameters)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
parameters_string = f"parameters([{rep_join(self.parameters)}])"
|
||||
invoca_string = rep_join([self.name, parameters_string])
|
||||
invoca_string = rep_join([self.callee, parameters_string])
|
||||
return f"Invoca({invoca_string})"
|
||||
|
||||
def print(self):
|
||||
args = ", ".join(p.print() for p in self.parameters)
|
||||
return f"INVOCA {self.name.print()} ({args})"
|
||||
return f"INVOCA {self.callee.print()} ({args})"
|
||||
|
||||
def _eval(self, vtable):
|
||||
params = [p.eval(vtable)[1] for p in self.parameters]
|
||||
if self.name.name not in vtable:
|
||||
raise CentvrionError(f"Undefined function: {self.name.name}")
|
||||
func = vtable[self.name.name]
|
||||
vtable, func = self.callee.eval(vtable)
|
||||
if not isinstance(func, ValFunc):
|
||||
raise CentvrionError(f"{self.name.name} is not a function")
|
||||
callee_desc = (self.callee.name
|
||||
if isinstance(self.callee, ID) else "expression")
|
||||
raise CentvrionError(f"{callee_desc} is not a function")
|
||||
if len(params) != len(func.params):
|
||||
callee_desc = (self.callee.name
|
||||
if isinstance(self.callee, ID) else "FVNCTIO")
|
||||
raise CentvrionError(
|
||||
f"{self.name.name} expects {len(func.params)} argument(s), got {len(params)}"
|
||||
f"{callee_desc} expects {len(func.params)} argument(s), got {len(params)}"
|
||||
)
|
||||
func_vtable = vtable.copy()
|
||||
for i, param in enumerate(func.params):
|
||||
@@ -978,9 +1110,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()
|
||||
|
||||
@@ -7,6 +7,10 @@ class EmitContext:
|
||||
self.functions = {}
|
||||
# source-level name / alias → c_func_name; populated by emitter pre-pass
|
||||
self.func_resolve = {}
|
||||
# id(Fvnctio_node) → c_func_name; populated by lambda lifting pass
|
||||
self.lambda_names = {}
|
||||
# [(c_name, Fvnctio_node), ...]; populated by lambda lifting pass
|
||||
self.lambdas = []
|
||||
|
||||
def fresh_tmp(self):
|
||||
name = f"_t{self._tmp_counter}"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from centvrion.errors import CentvrionError
|
||||
from centvrion.ast_nodes import (
|
||||
String, Numeral, Fractio, Bool, Nullus, ID,
|
||||
String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID,
|
||||
BinOp, UnaryMinus, UnaryNot,
|
||||
ArrayIndex, DataArray, DataRangeArray,
|
||||
BuiltIn, Invoca,
|
||||
ArrayIndex, DataArray, DataRangeArray, DataDict,
|
||||
BuiltIn, Invoca, Fvnctio,
|
||||
num_to_int, frac_to_fraction,
|
||||
)
|
||||
|
||||
@@ -51,6 +51,25 @@ def emit_expr(node, ctx):
|
||||
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"
|
||||
@@ -125,12 +144,27 @@ 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)
|
||||
|
||||
if isinstance(node, Invoca):
|
||||
return _emit_invoca(node, ctx)
|
||||
|
||||
if isinstance(node, Fvnctio):
|
||||
return _emit_fvnctio(node, ctx)
|
||||
|
||||
raise NotImplementedError(type(node).__name__)
|
||||
|
||||
|
||||
@@ -214,6 +248,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();")
|
||||
@@ -227,7 +264,8 @@ def _emit_builtin(node, ctx):
|
||||
def _emit_invoca(node, ctx):
|
||||
"""
|
||||
Emits a user-defined function call.
|
||||
Requires ctx.functions[name] = [param_names] populated by the emitter pre-pass.
|
||||
Supports both static resolution (ID callee with known function) and
|
||||
dynamic dispatch (arbitrary expression callee via CENT_FUNC values).
|
||||
"""
|
||||
lines = []
|
||||
param_vars = []
|
||||
@@ -236,21 +274,59 @@ def _emit_invoca(node, 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}")
|
||||
# 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);")
|
||||
|
||||
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)}"
|
||||
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});'
|
||||
)
|
||||
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});")
|
||||
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
|
||||
|
||||
@@ -11,9 +11,6 @@ def emit_stmt(node, ctx):
|
||||
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});']
|
||||
|
||||
@@ -70,16 +67,37 @@ 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):
|
||||
# Function definitions are hoisted by emitter.py; no-op here.
|
||||
# Top-level definitions are handled by emitter.py (hoisted + scope-set).
|
||||
# Nested definitions (inside another function) need runtime scope-set.
|
||||
if ctx.current_function is not None:
|
||||
name = node.name.name
|
||||
c_name = ctx.func_resolve[name]
|
||||
param_names = ctx.functions[c_name]
|
||||
pn_var = ctx.fresh_tmp() + "_pn"
|
||||
return [
|
||||
f"static const char *{pn_var}[] = {{"
|
||||
+ ", ".join(f'"{p}"' for p in param_names)
|
||||
+ "};",
|
||||
f'cent_scope_set(&_scope, "{name}", '
|
||||
f"cent_func_val({c_name}, {pn_var}, {len(param_names)}));",
|
||||
]
|
||||
return []
|
||||
|
||||
if isinstance(node, Redi):
|
||||
|
||||
@@ -1,11 +1,32 @@
|
||||
import os
|
||||
from centvrion.ast_nodes import Defini, Designa, ID
|
||||
from centvrion.ast_nodes import Defini, Designa, Fvnctio, ID, Node
|
||||
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 _collect_lambdas(node, ctx, counter):
|
||||
"""Walk AST recursively, find all Fvnctio nodes, assign C names."""
|
||||
if isinstance(node, Fvnctio):
|
||||
c_name = f"_cent_lambda_{counter[0]}"
|
||||
counter[0] += 1
|
||||
ctx.lambda_names[id(node)] = c_name
|
||||
ctx.functions[c_name] = [p.name for p in node.parameters]
|
||||
ctx.lambdas.append((c_name, node))
|
||||
for attr in vars(node).values():
|
||||
if isinstance(attr, Node):
|
||||
_collect_lambdas(attr, ctx, counter)
|
||||
elif isinstance(attr, list):
|
||||
for item in attr:
|
||||
if isinstance(item, Node):
|
||||
_collect_lambdas(item, ctx, counter)
|
||||
elif isinstance(item, tuple):
|
||||
for elem in item:
|
||||
if isinstance(elem, Node):
|
||||
_collect_lambdas(elem, ctx, counter)
|
||||
|
||||
|
||||
def compile_program(program):
|
||||
"""Return a complete C source string for the given Program AST node."""
|
||||
ctx = EmitContext()
|
||||
@@ -26,10 +47,11 @@ def compile_program(program):
|
||||
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]
|
||||
|
||||
# Lambda lifting: find all Fvnctio nodes in the entire AST
|
||||
counter = [0]
|
||||
for stmt in program.statements:
|
||||
_collect_lambdas(stmt, ctx, counter)
|
||||
|
||||
lines = []
|
||||
|
||||
@@ -39,13 +61,13 @@ def compile_program(program):
|
||||
"",
|
||||
]
|
||||
|
||||
# Forward declarations
|
||||
# Forward declarations (named functions + lambdas)
|
||||
for c_name in ctx.functions:
|
||||
lines.append(f"CentValue {c_name}(CentScope _scope);")
|
||||
if ctx.functions:
|
||||
lines.append("")
|
||||
|
||||
# Hoisted function definitions
|
||||
# Hoisted named function definitions
|
||||
for c_name, stmt in func_definitions:
|
||||
ctx.current_function = c_name
|
||||
lines.append(f"CentValue {c_name}(CentScope _scope) {{")
|
||||
@@ -55,6 +77,16 @@ def compile_program(program):
|
||||
lines += ["_func_return:", " return _return_val;", "}", ""]
|
||||
ctx.current_function = None
|
||||
|
||||
# Hoisted lambda definitions
|
||||
for c_name, fvnctio_node in ctx.lambdas:
|
||||
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(fvnctio_node.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();")
|
||||
@@ -62,8 +94,25 @@ def compile_program(program):
|
||||
lines.append(" cent_magnvm = 1;")
|
||||
lines.append(" CentScope _scope = {0};")
|
||||
lines.append(" CentValue _return_val = cent_null();")
|
||||
|
||||
# Build a map from id(Defini_node) → c_name for scope registration
|
||||
defini_c_names = {id(stmt): c_name for c_name, stmt in func_definitions}
|
||||
|
||||
for stmt in program.statements:
|
||||
if isinstance(stmt, Defini):
|
||||
name = stmt.name.name
|
||||
c_name = defini_c_names[id(stmt)]
|
||||
param_names = ctx.functions[c_name]
|
||||
pn_var = f"_pn_{c_name}"
|
||||
lines.append(
|
||||
f" static const char *{pn_var}[] = {{"
|
||||
+ ", ".join(f'"{p}"' for p in param_names)
|
||||
+ "};"
|
||||
)
|
||||
lines.append(
|
||||
f' cent_scope_set(&_scope, "{name}", '
|
||||
f"cent_func_val({c_name}, {pn_var}, {len(param_names)}));"
|
||||
)
|
||||
continue
|
||||
for l in emit_stmt(stmt, ctx):
|
||||
lines.append(f" {l}")
|
||||
|
||||
@@ -290,6 +290,33 @@ static int write_val(CentValue v, char *buf, int bufsz) {
|
||||
return total;
|
||||
}
|
||||
|
||||
case CENT_FUNC:
|
||||
if (buf && bufsz > 7) { memcpy(buf, "FVNCTIO", 7); buf[7] = '\0'; }
|
||||
return 7;
|
||||
|
||||
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;
|
||||
@@ -431,6 +458,7 @@ CentValue cent_eq(CentValue a, CentValue b) {
|
||||
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_FUNC: return cent_bool(a.fnval.fn == b.fnval.fn);
|
||||
case CENT_NULL: return cent_bool(1);
|
||||
default:
|
||||
cent_type_error("'EST' not supported for this type");
|
||||
@@ -512,7 +540,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 +624,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 +641,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 +655,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,24 @@ typedef enum {
|
||||
CENT_BOOL,
|
||||
CENT_LIST,
|
||||
CENT_FRAC,
|
||||
CENT_DICT,
|
||||
CENT_FUNC,
|
||||
CENT_NULL
|
||||
} CentType;
|
||||
|
||||
typedef struct CentValue CentValue;
|
||||
typedef struct CentList CentList;
|
||||
typedef struct CentDict CentDict;
|
||||
struct CentScope; /* forward declaration */
|
||||
|
||||
/* First-class function value */
|
||||
typedef CentValue (*CentFuncPtr)(struct CentScope);
|
||||
|
||||
typedef struct {
|
||||
CentFuncPtr fn;
|
||||
const char **param_names;
|
||||
int param_count;
|
||||
} CentFuncInfo;
|
||||
|
||||
/* Duodecimal fraction: num/den stored as exact integers */
|
||||
typedef struct {
|
||||
@@ -32,6 +45,13 @@ struct CentList {
|
||||
int cap;
|
||||
};
|
||||
|
||||
struct CentDict {
|
||||
CentValue *keys;
|
||||
CentValue *vals;
|
||||
int len;
|
||||
int cap;
|
||||
};
|
||||
|
||||
struct CentValue {
|
||||
CentType type;
|
||||
union {
|
||||
@@ -39,13 +59,15 @@ struct CentValue {
|
||||
char *sval; /* CENT_STR */
|
||||
int bval; /* CENT_BOOL */
|
||||
CentList lval; /* CENT_LIST */
|
||||
CentFrac fval; /* CENT_FRAC */
|
||||
CentFrac fval; /* CENT_FRAC */
|
||||
CentDict dval; /* CENT_DICT */
|
||||
CentFuncInfo fnval; /* CENT_FUNC */
|
||||
};
|
||||
};
|
||||
|
||||
/* Scope: flat name→value array. Stack-allocated by the caller;
|
||||
cent_scope_set uses cent_arena when it needs to grow. */
|
||||
typedef struct {
|
||||
typedef struct CentScope {
|
||||
const char **names;
|
||||
CentValue *vals;
|
||||
int len;
|
||||
@@ -101,6 +123,23 @@ static inline CentValue cent_list(CentValue *items, int len, int cap) {
|
||||
r.lval.cap = cap;
|
||||
return r;
|
||||
}
|
||||
static inline CentValue cent_func_val(CentFuncPtr fn, const char **param_names, int param_count) {
|
||||
CentValue r;
|
||||
r.type = CENT_FUNC;
|
||||
r.fnval.fn = fn;
|
||||
r.fnval.param_names = param_names;
|
||||
r.fnval.param_count = param_count;
|
||||
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 +230,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 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
@@ -18,6 +18,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
||||
"ET",
|
||||
"FACE",
|
||||
"FALSITAS",
|
||||
"FVNCTIO",
|
||||
"INVOCA",
|
||||
"IN",
|
||||
"MINVE",
|
||||
@@ -30,6 +31,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
||||
"RELIQVVM",
|
||||
"SI",
|
||||
"TVNC",
|
||||
"TABVLA",
|
||||
"VSQVE",
|
||||
"VT",
|
||||
"VERITAS",
|
||||
@@ -39,6 +41,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
||||
builtin_tokens = [("BUILTIN", i) for i in [
|
||||
"AVDI_NVMERVS",
|
||||
"AVDI",
|
||||
"CLAVES",
|
||||
"DECIMATIO",
|
||||
"DICE",
|
||||
"EVERRO",
|
||||
|
||||
@@ -1,10 +1,71 @@
|
||||
from rply import ParserGenerator
|
||||
|
||||
from centvrion.lexer import all_tokens
|
||||
from centvrion.errors import CentvrionError
|
||||
from centvrion.lexer import Lexer, all_tokens
|
||||
from . import ast_nodes
|
||||
|
||||
ALL_TOKENS = list(set([i[0] for i in all_tokens]))
|
||||
|
||||
|
||||
def _parse_interpolated(raw_value):
|
||||
quote_char = raw_value[0]
|
||||
inner = raw_value[1:-1]
|
||||
|
||||
if quote_char == "'" or len(inner) == 0:
|
||||
return ast_nodes.String(inner)
|
||||
|
||||
parts = []
|
||||
i = 0
|
||||
current = []
|
||||
|
||||
while i < len(inner):
|
||||
ch = inner[i]
|
||||
if ch == '{':
|
||||
if i + 1 < len(inner) and inner[i + 1] == '{':
|
||||
current.append('{')
|
||||
i += 2
|
||||
continue
|
||||
if current:
|
||||
parts.append(ast_nodes.String(''.join(current)))
|
||||
current = []
|
||||
j = i + 1
|
||||
depth = 1
|
||||
while j < len(inner) and depth > 0:
|
||||
if inner[j] == '{':
|
||||
depth += 1
|
||||
elif inner[j] == '}':
|
||||
depth -= 1
|
||||
j += 1
|
||||
if depth != 0:
|
||||
raise CentvrionError("Unclosed '{' in interpolated string")
|
||||
expr_src = inner[i + 1:j - 1]
|
||||
tokens = Lexer().get_lexer().lex(expr_src + "\n")
|
||||
program = Parser().parse(tokens)
|
||||
if len(program.statements) != 1:
|
||||
raise CentvrionError("Interpolation must contain exactly one expression")
|
||||
stmt = program.statements[0]
|
||||
if not isinstance(stmt, ast_nodes.ExpressionStatement):
|
||||
raise CentvrionError("Interpolation must contain an expression, not a statement")
|
||||
parts.append(stmt.expression)
|
||||
i = j
|
||||
elif ch == '}':
|
||||
if i + 1 < len(inner) and inner[i + 1] == '}':
|
||||
current.append('}')
|
||||
i += 2
|
||||
continue
|
||||
raise CentvrionError("Unmatched '}' in string (use '}}' for literal '}')")
|
||||
else:
|
||||
current.append(ch)
|
||||
i += 1
|
||||
|
||||
if current:
|
||||
parts.append(ast_nodes.String(''.join(current)))
|
||||
|
||||
if len(parts) == 1 and isinstance(parts[0], ast_nodes.String):
|
||||
return parts[0]
|
||||
|
||||
return ast_nodes.InterpolatedString(parts)
|
||||
|
||||
class Parser():
|
||||
def __init__(self):
|
||||
self.pg = ParserGenerator(
|
||||
@@ -184,7 +245,7 @@ class Parser():
|
||||
|
||||
@self.pg.production('expression : DATA_STRING')
|
||||
def expression_string(tokens):
|
||||
return ast_nodes.String(tokens[0].value[1:-1])
|
||||
return _parse_interpolated(tokens[0].value)
|
||||
|
||||
@self.pg.production('expression : DATA_NUMERAL')
|
||||
def expression_numeral(tokens):
|
||||
@@ -226,14 +287,33 @@ class Parser():
|
||||
def unary_not(tokens):
|
||||
return ast_nodes.UnaryNot(tokens[1])
|
||||
|
||||
@self.pg.production('expression : KEYWORD_INVOCA id expressions')
|
||||
@self.pg.production('expression : KEYWORD_INVOCA expression expressions')
|
||||
def invoca(tokens):
|
||||
return ast_nodes.Invoca(tokens[1], tokens[2])
|
||||
|
||||
@self.pg.production('expression : KEYWORD_FVNCTIO ids KEYWORD_VT SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||
def fvnctio(tokens):
|
||||
return ast_nodes.Fvnctio(tokens[1], tokens[4])
|
||||
|
||||
@self.pg.production('expression : SYMBOL_LPARENS expression SYMBOL_RPARENS')
|
||||
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)
|
||||
|
||||
@@ -55,16 +55,25 @@
|
||||
\languageline{expression}{\texttt{(} \textit{expression} \texttt{)}} \\
|
||||
\languageline{expression}{\textbf{id}} \\
|
||||
\languageline{expression}{\textbf{builtin} \texttt{(} \textit{optional-expressions} \texttt{)}} \\
|
||||
\languageline{expression}{\texttt{INVOCA} \textbf{id} \texttt{(} \textit{optional-expressions} \texttt{)}} \\
|
||||
\languageline{expression}{\texttt{INVOCA} \textit{expression} \texttt{(} \textit{optional-expressions} \texttt{)}} \\
|
||||
\languageline{expression}{\texttt{FVNCTIO} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
|
||||
\languageline{expression}{\textit{literal}} \\
|
||||
\languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{]}} \\
|
||||
\languageline{expression}{\textit{expression} \textbf{binop} \textit{expression}} \\
|
||||
\languageline{expression}{\textbf{unop} \textit{expression}} \\ \hline
|
||||
\languageline{literal}{\textbf{string}} \\
|
||||
\languageline{literal}{\textbf{interpolated-string}} \\
|
||||
\languageline{literal}{\textbf{numeral}} \\
|
||||
\languageline{literal}{\textbf{bool}} \\
|
||||
\languageline{literal}{\texttt{[} \textit{optional-expressions} \texttt{]}} \\
|
||||
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]}} \\ \hline \hline
|
||||
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]}} \\
|
||||
\languageline{literal}{\texttt{TABVLA} \texttt{\{} \textit{optional-dict-items} \texttt{\}}} \\ \hline
|
||||
|
||||
\languageline{optional-dict-items}{\textit{dict-items}} \\
|
||||
\languageline{optional-dict-items}{} \\ \hline
|
||||
|
||||
\languageline{dict-items}{\textit{expression} \texttt{VT} \textit{expression} \texttt{,} \textit{dict-items}} \\
|
||||
\languageline{dict-items}{\textit{expression} \texttt{VT} \textit{expression}} \\ \hline \hline
|
||||
|
||||
\multicolumn{3}{|c|}{\textbf{Lists}} \\ \hline
|
||||
\languageline{optional-ids}{ids} \\
|
||||
@@ -88,7 +97,8 @@
|
||||
\item \textbf{module-name}: \\ Modules are flags given to the interpreter/compiler, to let it know you want to be using certain rules, functions, or features.
|
||||
\item \textbf{id}: \\ Variable. Can only consist of lowercase characters and underscores, but not the letters j, u, or w.
|
||||
\item \textbf{builtin}: \\ Builtin functions are uppercase latin words.
|
||||
\item \textbf{string}: \\ Any text encased in " characters.
|
||||
\item \textbf{string}: \\ Any text encased in \texttt{"} or \texttt{'} characters. Single-quoted strings are always literal.
|
||||
\item \textbf{interpolated-string}: \\ A double-quoted string containing \texttt{\{}\textit{expression}\texttt{\}} segments. Each expression is evaluated and coerced to a string. Use \texttt{\{\{} and \texttt{\}\}} for literal braces.
|
||||
\item \textbf{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM.
|
||||
\item \textbf{bool}: \\ VERITAS or FALSITAS.
|
||||
\item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{RELIQVVM} (modulo), \texttt{EST} (equality), \texttt{DISPAR} (not-equal), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string concatenation).
|
||||
|
||||
9
snippets/fvnctio.cent
Normal file
9
snippets/fvnctio.cent
Normal file
@@ -0,0 +1,9 @@
|
||||
DEFINI apply (f, x) VT {
|
||||
REDI (INVOCA f (x))
|
||||
}
|
||||
|
||||
DESIGNA dbl VT FVNCTIO (n) VT {
|
||||
REDI (n * II)
|
||||
}
|
||||
|
||||
DICE(INVOCA apply (dbl, VII))
|
||||
BIN
snippets/fvnctio.png
Normal file
BIN
snippets/fvnctio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
11
snippets/invoca_expr.cent
Normal file
11
snippets/invoca_expr.cent
Normal file
@@ -0,0 +1,11 @@
|
||||
// Immediately invoked
|
||||
DICE(INVOCA FVNCTIO (x) VT { REDI (x + I) } (V))
|
||||
|
||||
// From an array
|
||||
DESIGNA fns VT [FVNCTIO (x) VT { REDI (x + I) }]
|
||||
DICE(INVOCA fns[I] (V))
|
||||
|
||||
// Passing a named function as an argument
|
||||
DEFINI apply (f, x) VT { REDI (INVOCA f (x)) }
|
||||
DEFINI sqr (x) VT { REDI (x * x) }
|
||||
DICE(INVOCA apply (sqr, IV))
|
||||
BIN
snippets/invoca_expr.png
Normal file
BIN
snippets/invoca_expr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -33,6 +33,19 @@ contexts:
|
||||
scope: string.quoted.double.centvrion
|
||||
push:
|
||||
- meta_scope: string.quoted.double.centvrion
|
||||
- match: '\{\{'
|
||||
scope: constant.character.escape.centvrion
|
||||
- match: '\}\}'
|
||||
scope: constant.character.escape.centvrion
|
||||
- match: '\{'
|
||||
scope: punctuation.section.interpolation.begin.centvrion
|
||||
push:
|
||||
- clear_scopes: 1
|
||||
- meta_scope: meta.interpolation.centvrion
|
||||
- match: '\}'
|
||||
scope: punctuation.section.interpolation.end.centvrion
|
||||
pop: true
|
||||
- include: main
|
||||
- match: '"'
|
||||
pop: true
|
||||
- match: "'"
|
||||
@@ -57,7 +70,7 @@ contexts:
|
||||
scope: constant.language.centvrion
|
||||
|
||||
builtins:
|
||||
- match: '\b(AVDI_NVMERVS|AVDI|DECIMATIO|DICE|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN)\b'
|
||||
- match: '\b(AVDI_NVMERVS|AVDI|CLAVES|DECIMATIO|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN|SENATVS)\b'
|
||||
scope: support.function.builtin.centvrion
|
||||
|
||||
modules:
|
||||
@@ -65,7 +78,7 @@ contexts:
|
||||
scope: support.class.module.centvrion
|
||||
|
||||
keywords:
|
||||
- match: '\b(AETERNVM|ALVID|AVGE|AVT|CONTINVA|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVE|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TVNC|VSQVE|VT|CVM)\b'
|
||||
- match: '\b(AETERNVM|ALVID|AVGE|AVT|CONTINVA|DEFINI|DESIGNA|DISPAR|DONICVM|DVM|ERVMPE|EST|ET|FACE|FVNCTIO|INVOCA|IN|MINVE|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TABVLA|TVNC|VSQVE|VT|CVM)\b'
|
||||
scope: keyword.control.centvrion
|
||||
|
||||
operators:
|
||||
|
||||
418
tests.py
418
tests.py
@@ -10,18 +10,18 @@ from parameterized import parameterized
|
||||
from fractions import Fraction
|
||||
|
||||
from centvrion.ast_nodes import (
|
||||
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataRangeArray, Defini,
|
||||
Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, Erumpe,
|
||||
ExpressionStatement, ID, Invoca, ModuleCall, Nullus, Numeral, PerStatement,
|
||||
Program, Redi, SiStatement, String, UnaryMinus, UnaryNot,
|
||||
Fractio, frac_to_fraction, fraction_to_frac,
|
||||
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray,
|
||||
Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement,
|
||||
Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca,
|
||||
ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement,
|
||||
String, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, fraction_to_frac,
|
||||
num_to_int, int_to_num, make_string,
|
||||
)
|
||||
from centvrion.compiler.emitter import compile_program
|
||||
from centvrion.errors import CentvrionError
|
||||
from centvrion.lexer import Lexer
|
||||
from centvrion.parser import Parser
|
||||
from centvrion.values import ValInt, ValStr, ValBool, ValList, ValNul, ValFunc, ValFrac
|
||||
from centvrion.values import ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac
|
||||
|
||||
_RUNTIME_C = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
@@ -881,6 +881,99 @@ class TestStringConcat(unittest.TestCase):
|
||||
run_test(self, source, nodes, value)
|
||||
|
||||
|
||||
# --- String interpolation ---
|
||||
|
||||
interpolation_tests = [
|
||||
# basic variable interpolation
|
||||
('DESIGNA nomen VT "Marcus"\n"Salve, {nomen}!"',
|
||||
Program([], [
|
||||
Designa(ID("nomen"), String("Marcus")),
|
||||
ExpressionStatement(InterpolatedString([String("Salve, "), ID("nomen"), String("!")]))
|
||||
]), ValStr("Salve, Marcus!")),
|
||||
# arithmetic expression inside interpolation
|
||||
('DESIGNA x VT III\n"Sum: {x + II}"',
|
||||
Program([], [
|
||||
Designa(ID("x"), Numeral("III")),
|
||||
ExpressionStatement(InterpolatedString([String("Sum: "), BinOp(ID("x"), Numeral("II"), "SYMBOL_PLUS")]))
|
||||
]), ValStr("Sum: V")),
|
||||
# multiple interpolations
|
||||
('DESIGNA a VT I\nDESIGNA b VT II\n"{a} + {b} = {a + b}"',
|
||||
Program([], [
|
||||
Designa(ID("a"), Numeral("I")),
|
||||
Designa(ID("b"), Numeral("II")),
|
||||
ExpressionStatement(InterpolatedString([
|
||||
ID("a"), String(" + "), ID("b"), String(" = "),
|
||||
BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"),
|
||||
]))
|
||||
]), ValStr("I + II = III")),
|
||||
# escaped braces become literal
|
||||
('"use {{braces}}"',
|
||||
Program([], [ExpressionStatement(String("use {braces}"))]),
|
||||
ValStr("use {braces}")),
|
||||
# single-quoted strings ignore braces
|
||||
("'hello {world}'",
|
||||
Program([], [ExpressionStatement(String("hello {world}"))]),
|
||||
ValStr("hello {world}")),
|
||||
# integer coercion
|
||||
('DESIGNA n VT V\n"n is {n}"',
|
||||
Program([], [
|
||||
Designa(ID("n"), Numeral("V")),
|
||||
ExpressionStatement(InterpolatedString([String("n is "), ID("n")]))
|
||||
]), ValStr("n is V")),
|
||||
# boolean coercion
|
||||
('DESIGNA b VT VERITAS\n"value: {b}"',
|
||||
Program([], [
|
||||
Designa(ID("b"), Bool(True)),
|
||||
ExpressionStatement(InterpolatedString([String("value: "), ID("b")]))
|
||||
]), ValStr("value: VERITAS")),
|
||||
# NVLLVS coercion
|
||||
('"value: {NVLLVS}"',
|
||||
Program([], [
|
||||
ExpressionStatement(InterpolatedString([String("value: "), Nullus()]))
|
||||
]), ValStr("value: NVLLVS")),
|
||||
# expression-only string (no literal parts around it)
|
||||
('DESIGNA x VT "hi"\n"{x}"',
|
||||
Program([], [
|
||||
Designa(ID("x"), String("hi")),
|
||||
ExpressionStatement(InterpolatedString([ID("x")]))
|
||||
]), ValStr("hi")),
|
||||
# adjacent interpolations
|
||||
('DESIGNA a VT "x"\nDESIGNA b VT "y"\n"{a}{b}"',
|
||||
Program([], [
|
||||
Designa(ID("a"), String("x")),
|
||||
Designa(ID("b"), String("y")),
|
||||
ExpressionStatement(InterpolatedString([ID("a"), ID("b")]))
|
||||
]), ValStr("xy")),
|
||||
# function call inside interpolation
|
||||
("DEFINI f () VT {\nREDI (V)\n}\n\"result: {INVOCA f()}\"",
|
||||
Program([], [
|
||||
Defini(ID("f"), [], [Redi([Numeral("V")])]),
|
||||
ExpressionStatement(InterpolatedString([String("result: "), Invoca(ID("f"), [])]))
|
||||
]), ValStr("result: V")),
|
||||
# single-quoted string inside interpolation
|
||||
("DESIGNA x VT 'hello'\n\"{x & '!'}\"",
|
||||
Program([], [
|
||||
Designa(ID("x"), String("hello")),
|
||||
ExpressionStatement(InterpolatedString([BinOp(ID("x"), String("!"), "SYMBOL_AMPERSAND")]))
|
||||
]), ValStr("hello!")),
|
||||
# plain double-quoted string (no braces) still works
|
||||
('"hello world"',
|
||||
Program([], [ExpressionStatement(String("hello world"))]),
|
||||
ValStr("hello world")),
|
||||
# interpolation in DICE output
|
||||
('DESIGNA name VT "Roma"\nDICE("Salve, {name}!")',
|
||||
Program([], [
|
||||
Designa(ID("name"), String("Roma")),
|
||||
ExpressionStatement(BuiltIn("DICE", [InterpolatedString([String("Salve, "), ID("name"), String("!")])]))
|
||||
]), ValStr("Salve, Roma!"), "Salve, Roma!\n"),
|
||||
]
|
||||
|
||||
class TestInterpolation(unittest.TestCase):
|
||||
@parameterized.expand(interpolation_tests)
|
||||
def test_interpolation(self, source, nodes, value, output=""):
|
||||
run_test(self, source, nodes, value, output)
|
||||
|
||||
|
||||
# --- Comparison operators ---
|
||||
|
||||
comparison_tests = [
|
||||
@@ -1793,5 +1886,318 @@ class TestFractioHelpers(unittest.TestCase):
|
||||
self.assertEqual(fraction_to_frac(frac_to_fraction(s)), s)
|
||||
|
||||
|
||||
# --- Dict (TABVLA) ---
|
||||
|
||||
dict_tests = [
|
||||
# empty dict
|
||||
("TABVLA {}",
|
||||
Program([], [ExpressionStatement(DataDict([]))]),
|
||||
ValDict({})),
|
||||
# single string key
|
||||
('TABVLA {"a" VT I}',
|
||||
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I"))]))]),
|
||||
ValDict({"a": ValInt(1)})),
|
||||
# multiple entries
|
||||
('TABVLA {"a" VT I, "b" VT II}',
|
||||
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]))]),
|
||||
ValDict({"a": ValInt(1), "b": ValInt(2)})),
|
||||
# integer keys
|
||||
('TABVLA {I VT "one", II VT "two"}',
|
||||
Program([], [ExpressionStatement(DataDict([(Numeral("I"), String("one")), (Numeral("II"), String("two"))]))]),
|
||||
ValDict({1: ValStr("one"), 2: ValStr("two")})),
|
||||
# expression values
|
||||
('TABVLA {"x" VT I + II}',
|
||||
Program([], [ExpressionStatement(DataDict([(String("x"), BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"))]))]),
|
||||
ValDict({"x": ValInt(3)})),
|
||||
]
|
||||
|
||||
class TestDict(unittest.TestCase):
|
||||
@parameterized.expand(dict_tests)
|
||||
def test_dict(self, source, nodes, value):
|
||||
run_test(self, source, nodes, value)
|
||||
|
||||
|
||||
dict_index_tests = [
|
||||
# string key access
|
||||
('TABVLA {"a" VT X}["a"]',
|
||||
Program([], [ExpressionStatement(ArrayIndex(DataDict([(String("a"), Numeral("X"))]), String("a")))]),
|
||||
ValInt(10)),
|
||||
# integer key access
|
||||
('TABVLA {I VT "one"}[I]',
|
||||
Program([], [ExpressionStatement(ArrayIndex(DataDict([(Numeral("I"), String("one"))]), Numeral("I")))]),
|
||||
ValStr("one")),
|
||||
# access via variable
|
||||
('DESIGNA d VT TABVLA {"x" VT V}\nd["x"]',
|
||||
Program([], [
|
||||
Designa(ID("d"), DataDict([(String("x"), Numeral("V"))])),
|
||||
ExpressionStatement(ArrayIndex(ID("d"), String("x"))),
|
||||
]),
|
||||
ValInt(5)),
|
||||
# nested dict access
|
||||
('TABVLA {"a" VT TABVLA {"b" VT X}}["a"]["b"]',
|
||||
Program([], [ExpressionStatement(
|
||||
ArrayIndex(ArrayIndex(DataDict([(String("a"), DataDict([(String("b"), Numeral("X"))]))]), String("a")), String("b"))
|
||||
)]),
|
||||
ValInt(10)),
|
||||
]
|
||||
|
||||
class TestDictIndex(unittest.TestCase):
|
||||
@parameterized.expand(dict_index_tests)
|
||||
def test_dict_index(self, source, nodes, value):
|
||||
run_test(self, source, nodes, value)
|
||||
|
||||
|
||||
dict_assign_tests = [
|
||||
# update existing key
|
||||
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["a"] VT X\nd["a"]',
|
||||
Program([], [
|
||||
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
|
||||
DesignaIndex(ID("d"), String("a"), Numeral("X")),
|
||||
ExpressionStatement(ArrayIndex(ID("d"), String("a"))),
|
||||
]),
|
||||
ValInt(10)),
|
||||
# insert new key
|
||||
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["b"]',
|
||||
Program([], [
|
||||
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
|
||||
DesignaIndex(ID("d"), String("b"), Numeral("II")),
|
||||
ExpressionStatement(ArrayIndex(ID("d"), String("b"))),
|
||||
]),
|
||||
ValInt(2)),
|
||||
# original key unaffected after insert
|
||||
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["a"]',
|
||||
Program([], [
|
||||
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
|
||||
DesignaIndex(ID("d"), String("b"), Numeral("II")),
|
||||
ExpressionStatement(ArrayIndex(ID("d"), String("a"))),
|
||||
]),
|
||||
ValInt(1)),
|
||||
]
|
||||
|
||||
class TestDictAssign(unittest.TestCase):
|
||||
@parameterized.expand(dict_assign_tests)
|
||||
def test_dict_assign(self, source, nodes, value):
|
||||
run_test(self, source, nodes, value)
|
||||
|
||||
|
||||
dict_builtin_tests = [
|
||||
# LONGITVDO on dict
|
||||
('LONGITVDO(TABVLA {"a" VT I, "b" VT II})',
|
||||
Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
|
||||
ValInt(2)),
|
||||
# LONGITVDO on empty dict
|
||||
('LONGITVDO(TABVLA {})',
|
||||
Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([])]))]),
|
||||
ValInt(0)),
|
||||
# CLAVES
|
||||
('CLAVES(TABVLA {"a" VT I, "b" VT II})',
|
||||
Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
|
||||
ValList([ValStr("a"), ValStr("b")])),
|
||||
# CLAVES with int keys
|
||||
('CLAVES(TABVLA {I VT "x", II VT "y"})',
|
||||
Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(Numeral("I"), String("x")), (Numeral("II"), String("y"))])]))]),
|
||||
ValList([ValInt(1), ValInt(2)])),
|
||||
]
|
||||
|
||||
class TestDictBuiltins(unittest.TestCase):
|
||||
@parameterized.expand(dict_builtin_tests)
|
||||
def test_dict_builtin(self, source, nodes, value):
|
||||
run_test(self, source, nodes, value)
|
||||
|
||||
|
||||
dict_iteration_tests = [
|
||||
# PER iterates over keys
|
||||
('DESIGNA r VT ""\nPER k IN TABVLA {"a" VT I, "b" VT II} FACE {\nDESIGNA r VT r & k\n}\nr',
|
||||
Program([], [
|
||||
Designa(ID("r"), String("")),
|
||||
PerStatement(
|
||||
DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]),
|
||||
ID("k"),
|
||||
[Designa(ID("r"), BinOp(ID("r"), ID("k"), "SYMBOL_AMPERSAND"))],
|
||||
),
|
||||
ExpressionStatement(ID("r")),
|
||||
]),
|
||||
ValStr("ab")),
|
||||
]
|
||||
|
||||
class TestDictIteration(unittest.TestCase):
|
||||
@parameterized.expand(dict_iteration_tests)
|
||||
def test_dict_iteration(self, source, nodes, value):
|
||||
run_test(self, source, nodes, value)
|
||||
|
||||
|
||||
dict_display_tests = [
|
||||
# DICE on dict
|
||||
('DICE(TABVLA {"a" VT I})',
|
||||
Program([], [ExpressionStatement(BuiltIn("DICE", [DataDict([(String("a"), Numeral("I"))])]))]),
|
||||
ValStr("{a VT I}"), "{a VT I}\n"),
|
||||
# DICE on multi-entry dict
|
||||
('DICE(TABVLA {"a" VT I, "b" VT II})',
|
||||
Program([], [ExpressionStatement(BuiltIn("DICE", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
|
||||
ValStr("{a VT I, b VT II}"), "{a VT I, b VT II}\n"),
|
||||
# DICE on empty dict
|
||||
('DICE(TABVLA {})',
|
||||
Program([], [ExpressionStatement(BuiltIn("DICE", [DataDict([])]))]),
|
||||
ValStr("{}"), "{}\n"),
|
||||
]
|
||||
|
||||
class TestDictDisplay(unittest.TestCase):
|
||||
@parameterized.expand(dict_display_tests)
|
||||
def test_dict_display(self, source, nodes, value, output):
|
||||
run_test(self, source, nodes, value, output)
|
||||
|
||||
|
||||
# --- First-class functions / FVNCTIO ---
|
||||
|
||||
fvnctio_tests = [
|
||||
# Lambda assigned to variable, then called
|
||||
(
|
||||
"DESIGNA f VT FVNCTIO (x) VT { REDI (x + I) }\nINVOCA f (V)",
|
||||
Program([], [
|
||||
Designa(ID("f"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])),
|
||||
ExpressionStatement(Invoca(ID("f"), [Numeral("V")])),
|
||||
]),
|
||||
ValInt(6),
|
||||
),
|
||||
# IIFE: immediately invoked lambda
|
||||
(
|
||||
"INVOCA FVNCTIO (x) VT { REDI (x * II) } (III)",
|
||||
Program([], [
|
||||
ExpressionStatement(Invoca(
|
||||
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||
[Numeral("III")],
|
||||
)),
|
||||
]),
|
||||
ValInt(6),
|
||||
),
|
||||
# Zero-arg lambda
|
||||
(
|
||||
"INVOCA FVNCTIO () VT { REDI (XLII) } ()",
|
||||
Program([], [
|
||||
ExpressionStatement(Invoca(
|
||||
Fvnctio([], [Redi([Numeral("XLII")])]),
|
||||
[],
|
||||
)),
|
||||
]),
|
||||
ValInt(42),
|
||||
),
|
||||
# Function passed as argument
|
||||
(
|
||||
"DEFINI apply (f, x) VT { REDI (INVOCA f (x)) }\n"
|
||||
"DESIGNA dbl VT FVNCTIO (n) VT { REDI (n * II) }\n"
|
||||
"INVOCA apply (dbl, V)",
|
||||
Program([], [
|
||||
Defini(ID("apply"), [ID("f"), ID("x")], [
|
||||
Redi([Invoca(ID("f"), [ID("x")])])
|
||||
]),
|
||||
Designa(ID("dbl"), Fvnctio([ID("n")], [
|
||||
Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])
|
||||
])),
|
||||
ExpressionStatement(Invoca(ID("apply"), [ID("dbl"), Numeral("V")])),
|
||||
]),
|
||||
ValInt(10),
|
||||
),
|
||||
# Lambda uses caller-scope variable (copy-caller semantics)
|
||||
(
|
||||
"DESIGNA n VT III\n"
|
||||
"DESIGNA f VT FVNCTIO (x) VT { REDI (x + n) }\n"
|
||||
"INVOCA f (V)",
|
||||
Program([], [
|
||||
Designa(ID("n"), Numeral("III")),
|
||||
Designa(ID("f"), Fvnctio([ID("x")], [
|
||||
Redi([BinOp(ID("x"), ID("n"), "SYMBOL_PLUS")])
|
||||
])),
|
||||
ExpressionStatement(Invoca(ID("f"), [Numeral("V")])),
|
||||
]),
|
||||
ValInt(8),
|
||||
),
|
||||
# Named function passed as value
|
||||
(
|
||||
"DEFINI sqr (x) VT { REDI (x * x) }\n"
|
||||
"DESIGNA f VT sqr\n"
|
||||
"INVOCA f (IV)",
|
||||
Program([], [
|
||||
Defini(ID("sqr"), [ID("x")], [Redi([BinOp(ID("x"), ID("x"), "SYMBOL_TIMES")])]),
|
||||
Designa(ID("f"), ID("sqr")),
|
||||
ExpressionStatement(Invoca(ID("f"), [Numeral("IV")])),
|
||||
]),
|
||||
ValInt(16),
|
||||
),
|
||||
# Nested lambdas
|
||||
(
|
||||
"INVOCA FVNCTIO (x) VT { REDI (INVOCA FVNCTIO (y) VT { REDI (y + I) } (x)) } (V)",
|
||||
Program([], [
|
||||
ExpressionStatement(Invoca(
|
||||
Fvnctio([ID("x")], [
|
||||
Redi([Invoca(
|
||||
Fvnctio([ID("y")], [Redi([BinOp(ID("y"), Numeral("I"), "SYMBOL_PLUS")])]),
|
||||
[ID("x")],
|
||||
)])
|
||||
]),
|
||||
[Numeral("V")],
|
||||
)),
|
||||
]),
|
||||
ValInt(6),
|
||||
),
|
||||
# DICE on a function value
|
||||
(
|
||||
"DESIGNA f VT FVNCTIO (x) VT { REDI (x) }\nDICE(f)",
|
||||
Program([], [
|
||||
Designa(ID("f"), Fvnctio([ID("x")], [Redi([ID("x")])])),
|
||||
ExpressionStatement(BuiltIn("DICE", [ID("f")])),
|
||||
]),
|
||||
ValStr("FVNCTIO"),
|
||||
"FVNCTIO\n",
|
||||
),
|
||||
# Lambda stored in array, called via index
|
||||
(
|
||||
"DESIGNA fns VT [FVNCTIO (x) VT { REDI (x + I) }, FVNCTIO (x) VT { REDI (x * II) }]\n"
|
||||
"INVOCA fns[I] (V)",
|
||||
Program([], [
|
||||
Designa(ID("fns"), DataArray([
|
||||
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])]),
|
||||
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||
])),
|
||||
ExpressionStatement(Invoca(
|
||||
ArrayIndex(ID("fns"), Numeral("I")),
|
||||
[Numeral("V")],
|
||||
)),
|
||||
]),
|
||||
ValInt(6),
|
||||
),
|
||||
# Lambda stored in dict, called via key
|
||||
(
|
||||
'DESIGNA d VT TABVLA {"add" VT FVNCTIO (x) VT { REDI (x + I) }}\n'
|
||||
'INVOCA d["add"] (V)',
|
||||
Program([], [
|
||||
Designa(ID("d"), DataDict([
|
||||
(String("add"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])),
|
||||
])),
|
||||
ExpressionStatement(Invoca(
|
||||
ArrayIndex(ID("d"), String("add")),
|
||||
[Numeral("V")],
|
||||
)),
|
||||
]),
|
||||
ValInt(6),
|
||||
),
|
||||
# Multi-param lambda
|
||||
(
|
||||
"DESIGNA add VT FVNCTIO (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)",
|
||||
Program([], [
|
||||
Designa(ID("add"), Fvnctio([ID("a"), ID("b")], [
|
||||
Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")])
|
||||
])),
|
||||
ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])),
|
||||
]),
|
||||
ValInt(7),
|
||||
),
|
||||
]
|
||||
|
||||
class TestFvnctio(unittest.TestCase):
|
||||
@parameterized.expand(fvnctio_tests)
|
||||
def test_fvnctio(self, source, nodes, value, output=""):
|
||||
run_test(self, source, nodes, value, output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "keyword.control.cent",
|
||||
"match": "\\b(AETERNVM|ALVID|AVT|CONTINVA|CVM|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|ET|FACE|IN|INVOCA|NON|PER|REDI|SI|TVNC|VSQVE|VT)\\b"
|
||||
"match": "\\b(AETERNVM|ALVID|AVT|CONTINVA|CVM|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|ET|FACE|FVNCTIO|IN|INVOCA|NON|PER|REDI|SI|TVNC|VSQVE|VT)\\b"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.comparison.cent",
|
||||
|
||||
Reference in New Issue
Block a user