Compare commits

..

9 Commits

Author SHA1 Message Date
afb1622b3a 🐐 TEMPTA/CAPE 2026-04-21 23:19:45 +02:00
9003d49b20 🐐 String indexing and slicing 2026-04-21 23:07:40 +02:00
378c28102c 🐐 Array slicing 2026-04-21 22:53:40 +02:00
559b1b100e 🐐 VSQVE change 2026-04-21 22:35:22 +02:00
5961884219 🐐 Snippets 2026-04-21 22:16:03 +02:00
80d430970a 🐐 SCRIPTA 2026-04-21 22:00:19 +02:00
e61009b6ef 🐐 ORDINA 2026-04-21 21:48:56 +02:00
108e69291d 🐐 DORMI 2026-04-21 21:30:59 +02:00
78b1dd7667 🐐 TYPVS 2026-04-21 21:09:59 +02:00
35 changed files with 1391 additions and 88 deletions

106
README.md
View File

@@ -38,16 +38,11 @@ Variable can consist of lower-case letters, numbers, as well as `_`.
Multiple variables can be assigned at once by unpacking an array or multi-return function: Multiple variables can be assigned at once by unpacking an array or multi-return function:
``` ![Destructuring function](snippets/destructure_fn.png)
DEFINI pair (a, b) VT { REDI (a, b) }
DESIGNA x, y VT INVOCA pair (III, VII)
```
The number of targets must match the length of the array. This also works with array literals: The number of targets must match the length of the array. This also works with array literals:
``` ![Destructuring array](snippets/destructure_array.png)
DESIGNA a, b, c VT [I, II, III]
```
## Data types ## Data types
### NVLLVS ### NVLLVS
@@ -69,17 +64,27 @@ Strings are concatenated with `&`:
Double-quoted strings support interpolation with `{expression}`: Double-quoted strings support interpolation with `{expression}`:
``` ![String interpolation](snippets/string_interp.png)
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.). 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}`. 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}`.
#### String Indexing and Slicing
Strings support the same indexing and slicing syntax as arrays. Indexing is 1-based and returns a single-character string:
```
"SALVTE"[I] @> "S"
"SALVTE"[III] @> "L"
```
Slicing uses `VSQVE` with inclusive bounds, returning a substring:
```
"SALVTE"[II VSQVE IV] @> "ALV"
```
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). 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 ### Integers
@@ -115,10 +120,14 @@ Arrays are defined using square brackets (`[]`) and commas (`,`):
![Array literal](snippets/array_literal.png) ![Array literal](snippets/array_literal.png)
An array of integers can also be initialized with the `VSQVE` keyword: An array of integers can also be initialized with the `VSQVE` keyword. The range is inclusive on both ends:
![Array with VSQVE](snippets/array_vsqve.png) ![Array with VSQVE](snippets/array_vsqve.png)
```
> [I, II, III, IV, V, VI, VII, VIII, IX, X]
```
Individual elements can be accessed by index using square brackets. Indexing is 1-based, so `I` refers to the first element: Individual elements can be accessed by index using square brackets. Indexing is 1-based, so `I` refers to the first element:
![Array indexing](snippets/array_index.png) ![Array indexing](snippets/array_index.png)
@@ -127,29 +136,27 @@ Individual elements can be accessed by index using square brackets. Indexing is
> I > I
``` ```
A sub-array can be extracted with `VSQVE` inside the index brackets. Both bounds are inclusive and 1-based:
![Array slicing](snippets/array_slice.png)
```
> [XX, XXX, XL]
```
### Dicts (TABVLA) ### Dicts (TABVLA)
Dicts are key-value maps created with the `TABVLA` keyword and curly braces: Dicts are key-value maps created with the `TABVLA` keyword and curly braces:
``` ![Dict creation](snippets/dict_create.png)
DESIGNA d VT TABVLA {"nomen" VT "Marcus", "aetas" VT XXV}
```
Keys must be strings or integers. Values are accessed and assigned with square brackets: Keys must be strings or integers. Values are accessed and assigned with square brackets:
``` ![Dict access](snippets/dict_access.png)
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: Iterating over a dict with `PER` loops over its keys:
``` ![Dict iteration](snippets/dict_per.png)
PER k IN d FACE {
DICE(k)
}
```
`LONGITVDO(dict)` returns the number of entries. `CLAVES(dict)` returns the keys as an array. `LONGITVDO(dict)` returns the number of entries. `CLAVES(dict)` returns the keys as an array.
@@ -198,7 +205,7 @@ The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used a
![DONICVM loop](snippets/donicvm.png) ![DONICVM loop](snippets/donicvm.png)
``` ```
> XLV > LV
``` ```
### DVM loops ### DVM loops
@@ -233,6 +240,24 @@ condition. Exit the loop with `ERVMPE` (or `REDI` from inside a function).
> V > V
``` ```
## Error handling
Errors can be caught using `TEMPTA` (temptare = to try) and `CAPE` (capere = to catch). The `CAPE` block binds the error message to a variable as a string.
```
TEMPTA {
DESIGNA x VT I / NVLLVS
} CAPE error {
DICE(error)
}
```
```
> Division by zero
```
If the try block succeeds, the catch block is skipped. If an error occurs in the catch block, it propagates up. `TEMPTA`/`CAPE` blocks can be nested.
## Functions ## Functions
Functions are defined with the `DEFINI` and `VT` keywords. The `REDI` keyword is used to return. `REDI` can also be used to end the program, if used outside of a function. Functions are defined with the `DEFINI` and `VT` keywords. The `REDI` keyword is used to return. `REDI` can also be used to end the program, if used outside of a function.
@@ -304,11 +329,26 @@ Returns the length of `array` (element count), `string` (character count), or `d
Returns the keys of `dict` as an array. Returns the keys of `dict` as an array.
### ORDINA
`ORDINA(array)`
Sorts an array in ascending order. Returns a new sorted array. All elements must be the same type — integers, fractions, or strings. Integers and fractions sort numerically; strings sort lexicographically.
### SENATVS ### SENATVS
`SENATVS(bool, ...)` or `SENATVS([bool])` `SENATVS(bool, ...)` or `SENATVS([bool])`
Returns VERITAS if a strict majority of the arguments are VERITAS, FALSITAS otherwise. Also accepts a single array of booleans. All values must be booleans. Ties return FALSITAS. Returns VERITAS if a strict majority of the arguments are VERITAS, FALSITAS otherwise. Also accepts a single array of booleans. All values must be booleans. Ties return FALSITAS.
### TYPVS
`TYPVS(value)`
Returns the type of `value` as a string: `NVMERVS` (integer), `LITTERA` (string), `VERAX` (boolean), `CATALOGVS` (list), `FRACTIO` (fraction), `TABVLA` (dict), `FVNCTIO` (function), or `NVLLVS` (null).
### DORMI
`DORMI(n)`
Sleeps for `n` seconds, where `n` is an integer, fraction, or NVLLVS (treated as 0). Returns nothing meaningful.
## Modules ## Modules
Modules are additions to the base `CENTVRION` syntax. They add or change certain features. Modules are included in your code by having Modules are additions to the base `CENTVRION` syntax. They add or change certain features. Modules are included in your code by having
@@ -353,6 +393,16 @@ When `_` is added _after_ a numeric symbol, the symbol becomes 1.000 times large
All integer symbols except `I` may be given a `_`. All integer symbols except `I` may be given a `_`.
### SCRIPTA
The `SCRIPTA` module adds file I/O to your `CENTVRION` program. It adds 3 new built-in functions: `LEGE string`, `SCRIBE string string`, and `ADIVNGE string string`.
`LEGE string` reads the contents of the file at the given path and returns them as a string.
`SCRIBE string string` writes the second argument to the file at the path given by the first argument, overwriting any existing content.
`ADIVNGE string string` appends the second argument to the file at the path given by the first argument.
### SVBNVLLA ### SVBNVLLA
![CVM SVBNVLLA](snippets/svbnvlla.png) ![CVM SVBNVLLA](snippets/svbnvlla.png)

View File

@@ -1,5 +1,6 @@
import re import re
import random import random
import time
from fractions import Fraction from fractions import Fraction
from rply.token import BaseBox from rply.token import BaseBox
@@ -279,7 +280,7 @@ class DataRangeArray(Node):
raise CentvrionError("Range bounds must be numbers") raise CentvrionError("Range bounds must be numbers")
from_int = from_val.value() or 0 from_int = from_val.value() or 0
to_int = to_val.value() or 0 to_int = to_val.value() or 0
return vtable, ValList([ValInt(i) for i in range(from_int, to_int)]) return vtable, ValList([ValInt(i) for i in range(from_int, to_int + 1)])
class DataDict(Node): class DataDict(Node):
@@ -832,6 +833,17 @@ class ArrayIndex(Node):
if k not in d: if k not in d:
raise CentvrionError(f"Key not found in dict") raise CentvrionError(f"Key not found in dict")
return vtable, d[k] return vtable, d[k]
if isinstance(array, ValStr):
if isinstance(index, ValInt):
i = index.value()
elif isinstance(index, ValFrac) and index.value().denominator == 1:
i = index.value().numerator
else:
raise CentvrionError("String index must be a number")
s = array.value()
if i < 1 or i > len(s):
raise CentvrionError(f"Index {i} out of range for string of length {len(s)}")
return vtable, ValStr(s[i - 1])
if not isinstance(array, ValList): if not isinstance(array, ValList):
raise CentvrionError("Cannot index a non-array value") raise CentvrionError("Cannot index a non-array value")
if isinstance(index, ValInt): if isinstance(index, ValInt):
@@ -846,6 +858,59 @@ class ArrayIndex(Node):
return vtable, lst[i - 1] return vtable, lst[i - 1]
def _to_index_int(val):
if isinstance(val, ValInt):
return val.value()
if isinstance(val, ValFrac) and val.value().denominator == 1:
return val.value().numerator
raise CentvrionError("Array index must be a number")
class ArraySlice(Node):
def __init__(self, array, from_index, to_index) -> None:
self.array = array
self.from_index = from_index
self.to_index = to_index
def __eq__(self, other):
return (type(self) == type(other)
and self.array == other.array
and self.from_index == other.from_index
and self.to_index == other.to_index)
def __repr__(self) -> str:
return f"ArraySlice({self.array!r}, {self.from_index!r}, {self.to_index!r})"
def print(self):
return f"{self.array.print()}[{self.from_index.print()} VSQVE {self.to_index.print()}]"
def _eval(self, vtable):
vtable, array = self.array.eval(vtable)
vtable, from_val = self.from_index.eval(vtable)
vtable, to_val = self.to_index.eval(vtable)
if isinstance(array, ValStr):
from_int = _to_index_int(from_val)
to_int = _to_index_int(to_val)
s = array.value()
if from_int < 1 or to_int > len(s) or from_int > to_int:
raise CentvrionError(
f"Slice [{from_int} VSQVE {to_int}] out of range"
f" for string of length {len(s)}"
)
return vtable, ValStr(s[from_int - 1 : to_int])
if not isinstance(array, ValList):
raise CentvrionError("Cannot slice a non-array value")
from_int = _to_index_int(from_val)
to_int = _to_index_int(to_val)
lst = array.value()
if from_int < 1 or to_int > len(lst) or from_int > to_int:
raise CentvrionError(
f"Slice [{from_int} VSQVE {to_int}] out of range"
f" for array of length {len(lst)}"
)
return vtable, ValList(lst[from_int - 1 : to_int])
class SiStatement(Node): class SiStatement(Node):
def __init__(self, test, statements, else_part) -> None: def __init__(self, test, statements, else_part) -> None:
self.test = test self.test = test
@@ -980,6 +1045,45 @@ class PerStatement(Node):
return vtable, last_val return vtable, last_val
class TemptaStatement(Node):
def __init__(self, try_statements, error_var, catch_statements) -> None:
self.try_statements = try_statements
self.error_var = error_var
self.catch_statements = catch_statements
def __eq__(self, other):
return (type(self) == type(other)
and self.try_statements == other.try_statements
and self.error_var == other.error_var
and self.catch_statements == other.catch_statements)
def __repr__(self) -> str:
try_stmts = f"try([{rep_join(self.try_statements)}])"
catch_stmts = f"catch([{rep_join(self.catch_statements)}])"
tempta_string = rep_join([try_stmts, repr(self.error_var), catch_stmts])
return f"Tempta({tempta_string})"
def print(self):
try_body = "\n".join(s.print() for s in self.try_statements)
catch_body = "\n".join(s.print() for s in self.catch_statements)
return f"TEMPTA {{\n{try_body}\n}} CAPE {self.error_var.print()} {{\n{catch_body}\n}}"
def _eval(self, vtable):
last_val = ValNul()
try:
for statement in self.try_statements:
vtable, last_val = statement.eval(vtable)
if vtable["#return"] is not None or vtable["#break"] or vtable["#continue"]:
return vtable, last_val
except CentvrionError as e:
vtable[self.error_var.name] = ValStr(str(e))
for statement in self.catch_statements:
vtable, last_val = statement.eval(vtable)
if vtable["#return"] is not None or vtable["#break"] or vtable["#continue"]:
return vtable, last_val
return vtable, last_val
class Invoca(Node): class Invoca(Node):
def __init__(self, callee, parameters) -> None: def __init__(self, callee, parameters) -> None:
self.callee = callee self.callee = callee
@@ -1121,6 +1225,58 @@ class BuiltIn(Node):
case "EVERRO": case "EVERRO":
print("\033[2J\033[H", end="", flush=True) print("\033[2J\033[H", end="", flush=True)
return vtable, ValNul() return vtable, ValNul()
case "ORDINA":
if not isinstance(params[0], ValList):
raise CentvrionError("ORDINA requires an array")
items = list(params[0].value())
if not items:
return vtable, ValList([])
all_numeric = all(isinstance(i, (ValInt, ValFrac)) for i in items)
all_string = all(isinstance(i, ValStr) for i in items)
if not (all_numeric or all_string):
raise CentvrionError("ORDINA requires all elements to be numbers or all strings")
return vtable, ValList(sorted(items, key=lambda v: v.value()))
case "TYPVS":
type_map = {
ValInt: "NVMERVS", ValStr: "LITTERA", ValBool: "VERAX",
ValList: "CATALOGVS", ValFrac: "FRACTIO", ValDict: "TABVLA",
ValFunc: "FVNCTIO", ValNul: "NVLLVS",
}
return vtable, ValStr(type_map[type(params[0])])
case "DORMI":
v = params[0]
if isinstance(v, ValNul):
seconds = 0
elif isinstance(v, ValInt):
seconds = v.value()
elif isinstance(v, ValFrac):
seconds = float(v.value())
else:
raise CentvrionError("DORMI requires a number or NVLLVS")
time.sleep(seconds)
return vtable, ValNul()
case "LEGE":
if "SCRIPTA" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'LEGE' without module 'SCRIPTA'")
path = make_string(params[0], magnvm, svbnvlla)
with open(path, "r") as f:
return vtable, ValStr(f.read())
case "SCRIBE":
if "SCRIPTA" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'SCRIBE' without module 'SCRIPTA'")
path = make_string(params[0], magnvm, svbnvlla)
content = make_string(params[1], magnvm, svbnvlla)
with open(path, "w") as f:
f.write(content)
return vtable, ValNul()
case "ADIVNGE":
if "SCRIPTA" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'ADIVNGE' without module 'SCRIPTA'")
path = make_string(params[0], magnvm, svbnvlla)
content = make_string(params[1], magnvm, svbnvlla)
with open(path, "a") as f:
f.write(content)
return vtable, ValNul()
case _: case _:
raise NotImplementedError(self.builtin) raise NotImplementedError(self.builtin)

View File

@@ -2,7 +2,7 @@ from centvrion.errors import CentvrionError
from centvrion.ast_nodes import ( from centvrion.ast_nodes import (
String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID, String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID,
BinOp, UnaryMinus, UnaryNot, BinOp, UnaryMinus, UnaryNot,
ArrayIndex, DataArray, DataRangeArray, DataDict, ArrayIndex, ArraySlice, DataArray, DataRangeArray, DataDict,
BuiltIn, Invoca, Fvnctio, BuiltIn, Invoca, Fvnctio,
num_to_int, frac_to_fraction, num_to_int, frac_to_fraction,
) )
@@ -120,6 +120,15 @@ def emit_expr(node, ctx):
tmp = ctx.fresh_tmp() tmp = ctx.fresh_tmp()
return arr_lines + idx_lines + [f"CentValue {tmp} = cent_list_index({arr_var}, {idx_var});"], tmp return arr_lines + idx_lines + [f"CentValue {tmp} = cent_list_index({arr_var}, {idx_var});"], tmp
if isinstance(node, ArraySlice):
arr_lines, arr_var = emit_expr(node.array, ctx)
lo_lines, lo_var = emit_expr(node.from_index, ctx)
hi_lines, hi_var = emit_expr(node.to_index, ctx)
tmp = ctx.fresh_tmp()
return arr_lines + lo_lines + hi_lines + [
f"CentValue {tmp} = cent_list_slice({arr_var}, {lo_var}, {hi_var});"
], tmp
if isinstance(node, DataArray): if isinstance(node, DataArray):
lines = [] lines = []
tmp = ctx.fresh_tmp() tmp = ctx.fresh_tmp()
@@ -135,10 +144,10 @@ def emit_expr(node, ctx):
hi_lines, hi_var = emit_expr(node.to_value, ctx) hi_lines, hi_var = emit_expr(node.to_value, ctx)
tmp = ctx.fresh_tmp() tmp = ctx.fresh_tmp()
i_var = ctx.fresh_tmp() i_var = ctx.fresh_tmp()
cap = f"({hi_var}.ival > {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival) : 0)" cap = f"({hi_var}.ival >= {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival + 1) : 0)"
lines = lo_lines + hi_lines + [ lines = lo_lines + hi_lines + [
f"CentValue {tmp} = cent_list_new({cap});", f"CentValue {tmp} = cent_list_new({cap});",
f"for (long {i_var} = {lo_var}.ival; {i_var} < {hi_var}.ival; {i_var}++) {{", f"for (long {i_var} = {lo_var}.ival; {i_var} <= {hi_var}.ival; {i_var}++) {{",
f" cent_list_push(&{tmp}, cent_int({i_var}));", f" cent_list_push(&{tmp}, cent_int({i_var}));",
"}", "}",
] ]
@@ -251,10 +260,43 @@ def _emit_builtin(node, ctx):
case "CLAVES": case "CLAVES":
lines.append(f"CentValue {tmp} = cent_dict_keys({param_vars[0]});") lines.append(f"CentValue {tmp} = cent_dict_keys({param_vars[0]});")
case "ORDINA":
lines.append(f"CentValue {tmp} = cent_ordina({param_vars[0]});")
case "EVERRO": case "EVERRO":
lines.append("cent_everro();") lines.append("cent_everro();")
lines.append(f"CentValue {tmp} = cent_null();") lines.append(f"CentValue {tmp} = cent_null();")
case "TYPVS":
lines.append(f"CentValue {tmp} = cent_typvs({param_vars[0]});")
case "DORMI":
lines.append(f"cent_dormi({param_vars[0]});")
lines.append(f"CentValue {tmp} = cent_null();")
case "LEGE":
if not ctx.has_module("SCRIPTA"):
lines.append('cent_runtime_error("SCRIPTA module required for LEGE");')
lines.append(f"CentValue {tmp} = cent_null();")
else:
lines.append(f"CentValue {tmp} = cent_lege({param_vars[0]});")
case "SCRIBE":
if not ctx.has_module("SCRIPTA"):
lines.append('cent_runtime_error("SCRIPTA module required for SCRIBE");')
lines.append(f"CentValue {tmp} = cent_null();")
else:
lines.append(f"cent_scribe({param_vars[0]}, {param_vars[1]});")
lines.append(f"CentValue {tmp} = cent_null();")
case "ADIVNGE":
if not ctx.has_module("SCRIPTA"):
lines.append('cent_runtime_error("SCRIPTA module required for ADIVNGE");')
lines.append(f"CentValue {tmp} = cent_null();")
else:
lines.append(f"cent_adivnge({param_vars[0]}, {param_vars[1]});")
lines.append(f"CentValue {tmp} = cent_null();")
case _: case _:
raise NotImplementedError(node.builtin) raise NotImplementedError(node.builtin)

View File

@@ -1,6 +1,7 @@
from centvrion.ast_nodes import ( from centvrion.ast_nodes import (
Designa, DesignaIndex, DesignaDestructure, SiStatement, DumStatement, Designa, DesignaIndex, DesignaDestructure, SiStatement, DumStatement,
PerStatement, Defini, Redi, Erumpe, Continva, ExpressionStatement, ID, PerStatement, TemptaStatement, Defini, Redi, Erumpe, Continva,
ExpressionStatement, ID,
) )
from centvrion.compiler.emit_expr import emit_expr from centvrion.compiler.emit_expr import emit_expr
@@ -125,6 +126,24 @@ def emit_stmt(node, ctx):
if isinstance(node, Continva): if isinstance(node, Continva):
return ["continue;"] return ["continue;"]
if isinstance(node, TemptaStatement):
lines = [
"_cent_try_depth++;",
"if (setjmp(_cent_try_stack[_cent_try_depth - 1]) == 0) {",
]
try_lines = _emit_body(node.try_statements, ctx)
lines += [f" {l}" for l in try_lines]
lines += [
" _cent_try_depth--;",
"} else {",
" _cent_try_depth--;",
f' cent_scope_set(&_scope, "{node.error_var.name}", cent_str(_cent_error_msg));',
]
catch_lines = _emit_body(node.catch_statements, ctx)
lines += [f" {l}" for l in catch_lines]
lines += ["}"]
return lines
if isinstance(node, ExpressionStatement): if isinstance(node, ExpressionStatement):
lines, _ = emit_expr(node.expression, ctx) lines, _ = emit_expr(node.expression, ctx)
return lines return lines

View File

@@ -11,6 +11,10 @@
CentArena *cent_arena; CentArena *cent_arena;
int cent_magnvm = 0; int cent_magnvm = 0;
jmp_buf _cent_try_stack[CENT_TRY_STACK_MAX];
int _cent_try_depth = 0;
const char *_cent_error_msg = NULL;
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Arena allocator */ /* Arena allocator */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -50,11 +54,19 @@ void *cent_arena_alloc(CentArena *a, size_t n) {
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
void cent_type_error(const char *msg) { void cent_type_error(const char *msg) {
if (_cent_try_depth > 0) {
_cent_error_msg = msg;
longjmp(_cent_try_stack[_cent_try_depth - 1], 1);
}
fprintf(stderr, "CENTVRION type error: %s\n", msg); fprintf(stderr, "CENTVRION type error: %s\n", msg);
exit(1); exit(1);
} }
void cent_runtime_error(const char *msg) { void cent_runtime_error(const char *msg) {
if (_cent_try_depth > 0) {
_cent_error_msg = msg;
longjmp(_cent_try_stack[_cent_try_depth - 1], 1);
}
fprintf(stderr, "CENTVRION error: %s\n", msg); fprintf(stderr, "CENTVRION error: %s\n", msg);
exit(1); exit(1);
} }
@@ -545,6 +557,71 @@ CentValue cent_longitudo(CentValue v) {
return cent_null(); /* unreachable; silences warning */ return cent_null(); /* unreachable; silences warning */
} }
CentValue cent_typvs(CentValue v) {
switch (v.type) {
case CENT_INT: return cent_str("NVMERVS");
case CENT_STR: return cent_str("LITTERA");
case CENT_BOOL: return cent_str("VERAX");
case CENT_LIST: return cent_str("CATALOGVS");
case CENT_FRAC: return cent_str("FRACTIO");
case CENT_DICT: return cent_str("TABVLA");
case CENT_FUNC: return cent_str("FVNCTIO");
case CENT_NULL: return cent_str("NVLLVS");
}
return cent_str("IGNOTA"); /* unreachable */
}
void cent_dormi(CentValue n) {
struct timespec ts;
if (n.type == CENT_NULL) {
ts.tv_sec = 0; ts.tv_nsec = 0;
} else if (n.type == CENT_INT) {
ts.tv_sec = n.ival; ts.tv_nsec = 0;
} else if (n.type == CENT_FRAC) {
long sec = n.fval.num / n.fval.den;
long rem = n.fval.num % n.fval.den;
ts.tv_sec = sec;
ts.tv_nsec = rem * 1000000000L / n.fval.den;
} else {
cent_type_error("'DORMI' requires a number or NVLLVS");
}
nanosleep(&ts, NULL);
}
/* ---- SCRIPTA module ---- */
CentValue cent_lege(CentValue path) {
const char *p = cent_make_string(path);
FILE *f = fopen(p, "r");
if (!f) cent_runtime_error("LEGE: cannot open file");
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = cent_arena_alloc(cent_arena, len + 1);
fread(buf, 1, len, f);
buf[len] = '\0';
fclose(f);
return cent_str(buf);
}
void cent_scribe(CentValue path, CentValue content) {
const char *p = cent_make_string(path);
const char *c = cent_make_string(content);
FILE *f = fopen(p, "w");
if (!f) cent_runtime_error("SCRIBE: cannot open file");
fputs(c, f);
fclose(f);
}
void cent_adivnge(CentValue path, CentValue content) {
const char *p = cent_make_string(path);
const char *c = cent_make_string(content);
FILE *f = fopen(p, "a");
if (!f) cent_runtime_error("ADIVNGE: cannot open file");
fputs(c, f);
fclose(f);
}
CentValue cent_fortis_numerus(CentValue lo, CentValue hi) { CentValue cent_fortis_numerus(CentValue lo, CentValue hi) {
if (lo.type != CENT_INT || hi.type != CENT_INT) if (lo.type != CENT_INT || hi.type != CENT_INT)
cent_type_error("'FORTIS_NVMERVS' requires two integers"); cent_type_error("'FORTIS_NVMERVS' requires two integers");
@@ -602,6 +679,36 @@ void cent_semen(CentValue seed) {
srand((unsigned)seed.ival); srand((unsigned)seed.ival);
} }
static int _ordina_comparator(const void *a, const void *b) {
const CentValue *va = (const CentValue *)a;
const CentValue *vb = (const CentValue *)b;
if ((va->type == CENT_INT || va->type == CENT_FRAC) &&
(vb->type == CENT_INT || vb->type == CENT_FRAC)) {
long an, ad, bn, bd;
to_frac(*va, &an, &ad);
to_frac(*vb, &bn, &bd);
long lhs = an * bd;
long rhs = bn * ad;
return (lhs > rhs) - (lhs < rhs);
}
if (va->type == CENT_STR && vb->type == CENT_STR)
return strcmp(va->sval, vb->sval);
cent_type_error("'ORDINA' requires all elements to be the same type");
return 0;
}
CentValue cent_ordina(CentValue lst) {
if (lst.type != CENT_LIST)
cent_type_error("'ORDINA' requires a list");
int len = lst.lval.len;
CentValue result = cent_list_new(len);
for (int i = 0; i < len; i++)
cent_list_push(&result, lst.lval.items[i]);
if (len > 1)
qsort(result.lval.items, len, sizeof(CentValue), _ordina_comparator);
return result;
}
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Array helpers */ /* Array helpers */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -626,6 +733,22 @@ void cent_list_push(CentValue *lst, CentValue v) {
CentValue cent_list_index(CentValue lst, CentValue idx) { CentValue cent_list_index(CentValue lst, CentValue idx) {
if (lst.type == CENT_DICT) if (lst.type == CENT_DICT)
return cent_dict_get(lst, idx); return cent_dict_get(lst, idx);
if (lst.type == CENT_STR) {
long i;
if (idx.type == CENT_INT)
i = idx.ival;
else if (idx.type == CENT_FRAC && idx.fval.den == 1)
i = idx.fval.num;
else
cent_type_error("string index must be an integer");
long slen = (long)strlen(lst.sval);
if (i < 1 || i > slen)
cent_runtime_error("string index out of range");
char *ch = cent_arena_alloc(cent_arena, 2);
ch[0] = lst.sval[i - 1];
ch[1] = '\0';
return cent_str(ch);
}
if (lst.type != CENT_LIST) if (lst.type != CENT_LIST)
cent_type_error("index requires a list or dict"); cent_type_error("index requires a list or dict");
long i; long i;
@@ -640,6 +763,36 @@ CentValue cent_list_index(CentValue lst, CentValue idx) {
return lst.lval.items[i - 1]; return lst.lval.items[i - 1];
} }
CentValue cent_list_slice(CentValue lst, CentValue lo, CentValue hi) {
if (lst.type == CENT_STR) {
if (lo.type != CENT_INT || hi.type != CENT_INT)
cent_type_error("slice indices must be integers");
long from = lo.ival;
long to = hi.ival;
long slen = (long)strlen(lst.sval);
if (from < 1 || to > slen || from > to)
cent_runtime_error("string slice out of range");
int len = (int)(to - from + 1);
char *buf = cent_arena_alloc(cent_arena, len + 1);
memcpy(buf, lst.sval + from - 1, len);
buf[len] = '\0';
return cent_str(buf);
}
if (lst.type != CENT_LIST)
cent_type_error("slice requires a list");
if (lo.type != CENT_INT || hi.type != CENT_INT)
cent_type_error("slice indices must be integers");
long from = lo.ival;
long to = hi.ival;
if (from < 1 || to > lst.lval.len || from > to)
cent_runtime_error("slice out of range");
int len = (int)(to - from + 1);
CentValue result = cent_list_new(len);
for (long j = from; j <= to; j++)
cent_list_push(&result, lst.lval.items[j - 1]);
return result;
}
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v) { void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v) {
if (lst->type == CENT_DICT) { if (lst->type == CENT_DICT) {
cent_dict_set(lst, idx, v); cent_dict_set(lst, idx, v);

View File

@@ -3,6 +3,7 @@
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
#include <setjmp.h>
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Types */ /* Types */
@@ -145,8 +146,13 @@ static inline CentValue cent_dict_val(CentValue *keys, CentValue *vals, int len,
/* Error handling */ /* Error handling */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
void cent_type_error(const char *msg); /* type mismatch → exit(1) */ #define CENT_TRY_STACK_MAX 64
void cent_runtime_error(const char *msg); /* runtime fault → exit(1) */ extern jmp_buf _cent_try_stack[];
extern int _cent_try_depth;
extern const char *_cent_error_msg;
void cent_type_error(const char *msg); /* type mismatch → longjmp or exit(1) */
void cent_runtime_error(const char *msg); /* runtime fault → longjmp or exit(1) */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Truthiness — conditions must be booleans; anything else is a fault */ /* Truthiness — conditions must be booleans; anything else is a fault */
@@ -220,6 +226,12 @@ CentValue cent_decimatio(CentValue lst); /* DECIMATIO */
void cent_semen(CentValue seed); /* SEMEN */ void cent_semen(CentValue seed); /* SEMEN */
void cent_everro(void); /* EVERRO */ void cent_everro(void); /* EVERRO */
CentValue cent_senatus(CentValue *args, int n); /* SENATVS */ CentValue cent_senatus(CentValue *args, int n); /* SENATVS */
CentValue cent_typvs(CentValue v); /* TYPVS */
void cent_dormi(CentValue n); /* DORMI */
CentValue cent_ordina(CentValue lst); /* ORDINA */
CentValue cent_lege(CentValue path); /* LEGE */
void cent_scribe(CentValue path, CentValue content); /* SCRIBE */
void cent_adivnge(CentValue path, CentValue content); /* ADIVNGE */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Array helpers */ /* Array helpers */
@@ -228,6 +240,7 @@ CentValue cent_senatus(CentValue *args, int n); /* SENATVS */
CentValue cent_list_new(int cap); CentValue cent_list_new(int cap);
void cent_list_push(CentValue *lst, CentValue v); void cent_list_push(CentValue *lst, CentValue v);
CentValue cent_list_index(CentValue lst, CentValue idx); /* 1-based */ CentValue cent_list_index(CentValue lst, CentValue idx); /* 1-based */
CentValue cent_list_slice(CentValue lst, CentValue lo, CentValue hi); /* 1-based, inclusive */
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v); void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v);
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */

View File

@@ -6,6 +6,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
"AETERNVM", "AETERNVM",
"ALVID", "ALVID",
"AVGE", "AVGE",
"CAPE",
"AVT", "AVT",
"DEFINI", "DEFINI",
"DESIGNA", "DESIGNA",
@@ -32,6 +33,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
"SI", "SI",
"TVNC", "TVNC",
"TABVLA", "TABVLA",
"TEMPTA",
"VSQVE", "VSQVE",
"VT", "VT",
"VERITAS", "VERITAS",
@@ -44,12 +46,18 @@ builtin_tokens = [("BUILTIN", i) for i in [
"CLAVES", "CLAVES",
"DECIMATIO", "DECIMATIO",
"DICE", "DICE",
"DORMI",
"EVERRO", "EVERRO",
"FORTIS_NVMERVS", "FORTIS_NVMERVS",
"FORTIS_ELECTIONIS", "FORTIS_ELECTIONIS",
"LONGITVDO", "LONGITVDO",
"ORDINA",
"SEMEN", "SEMEN",
"SENATVS" "SENATVS",
"TYPVS",
"LEGE",
"SCRIBE",
"ADIVNGE"
]] ]]
data_tokens = [ data_tokens = [
@@ -62,6 +70,7 @@ module_tokens = [("MODULE", i) for i in [
"FORS", "FORS",
"FRACTIO", "FRACTIO",
"MAGNVM", "MAGNVM",
"SCRIPTA",
"SVBNVLLA" "SVBNVLLA"
]] ]]

View File

@@ -163,6 +163,7 @@ class Parser():
@self.pg.production('statement : dum_statement') @self.pg.production('statement : dum_statement')
@self.pg.production('statement : donicum_statement') @self.pg.production('statement : donicum_statement')
@self.pg.production('statement : si_statement') @self.pg.production('statement : si_statement')
@self.pg.production('statement : tempta_statement')
def nested_statements(tokens): def nested_statements(tokens):
return tokens[0] return tokens[0]
@@ -203,6 +204,10 @@ class Parser():
def per(tokens): def per(tokens):
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6]) return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
@self.pg.production('tempta_statement : KEYWORD_TEMPTA SYMBOL_LCURL statements SYMBOL_RCURL KEYWORD_CAPE id SYMBOL_LCURL statements SYMBOL_RCURL')
def tempta(tokens):
return ast_nodes.TemptaStatement(tokens[2], tokens[5], tokens[7])
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL') @self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
def donicum(tokens): def donicum(tokens):
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5]) range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
@@ -326,6 +331,10 @@ class Parser():
def array_index(tokens): def array_index(tokens):
return ast_nodes.ArrayIndex(tokens[0], tokens[2]) return ast_nodes.ArrayIndex(tokens[0], tokens[2])
@self.pg.production('expression : expression SYMBOL_LBRACKET expression KEYWORD_VSQVE expression SYMBOL_RBRACKET', precedence='INDEX')
def array_slice(tokens):
return ast_nodes.ArraySlice(tokens[0], tokens[2], tokens[4])
# ids # ids
@self.pg.production('ids : SYMBOL_LPARENS id_list') @self.pg.production('ids : SYMBOL_LPARENS id_list')
def ids(tokens): def ids(tokens):

View File

@@ -3,8 +3,8 @@
DESIGNA arr VT [V, III, VIII, I, IX, II, VII, IV, VI, X] DESIGNA arr VT [V, III, VIII, I, IX, II, VII, IV, VI, X]
DESIGNA n VT LONGITVDO(arr) DESIGNA n VT LONGITVDO(arr)
DONICVM i VT I VSQVE n FACE { DONICVM i VT I VSQVE n - I FACE {
DONICVM k VT I VSQVE n - i + I FACE { DONICVM k VT I VSQVE n - i FACE {
SI arr[k] PLVS arr[k + I] TVNC { SI arr[k] PLVS arr[k + I] TVNC {
DESIGNA temp VT arr[k] DESIGNA temp VT arr[k]
DESIGNA arr[k] VT arr[k + I] DESIGNA arr[k] VT arr[k + I]

View File

@@ -6,7 +6,7 @@
// Returns the bottommost empty row in col, or NVLLVS if full // Returns the bottommost empty row in col, or NVLLVS if full
DEFINI find_slot(b, col) VT { DEFINI find_slot(b, col) VT {
DESIGNA ans VT NVLLVS DESIGNA ans VT NVLLVS
DONICVM r VT I VSQVE VII FACE { DONICVM r VT I VSQVE VI FACE {
SI b[(r - I) * VII + col] EST NVLLVS TVNC { SI b[(r - I) * VII + col] EST NVLLVS TVNC {
DESIGNA ans VT r DESIGNA ans VT r
} }
@@ -16,32 +16,32 @@ DEFINI find_slot(b, col) VT {
// Returns VERITAS if player has four in a row // Returns VERITAS if player has four in a row
DEFINI est_victor(b, player) VT { DEFINI est_victor(b, player) VT {
DONICVM r VT I VSQVE VII FACE { DONICVM r VT I VSQVE VI FACE {
DONICVM c VT I VSQVE V FACE { DONICVM c VT I VSQVE IV FACE {
DESIGNA idx VT (r - I) * VII + c DESIGNA idx VT (r - I) * VII + c
SI b[idx] EST player ET b[idx + I] EST player ET b[idx + II] EST player ET b[idx + III] EST player TVNC { SI b[idx] EST player ET b[idx + I] EST player ET b[idx + II] EST player ET b[idx + III] EST player TVNC {
REDI(VERITAS) REDI(VERITAS)
} }
} }
} }
DONICVM r VT I VSQVE IV FACE { DONICVM r VT I VSQVE III FACE {
DONICVM c VT I VSQVE VIII FACE { DONICVM c VT I VSQVE VII FACE {
DESIGNA idx VT (r - I) * VII + c DESIGNA idx VT (r - I) * VII + c
SI b[idx] EST player ET b[idx + VII] EST player ET b[idx + XIV] EST player ET b[idx + XXI] EST player TVNC { SI b[idx] EST player ET b[idx + VII] EST player ET b[idx + XIV] EST player ET b[idx + XXI] EST player TVNC {
REDI(VERITAS) REDI(VERITAS)
} }
} }
} }
DONICVM r VT I VSQVE IV FACE { DONICVM r VT I VSQVE III FACE {
DONICVM c VT I VSQVE V FACE { DONICVM c VT I VSQVE IV FACE {
DESIGNA idx VT (r - I) * VII + c DESIGNA idx VT (r - I) * VII + c
SI b[idx] EST player ET b[idx + VIII] EST player ET b[idx + XVI] EST player ET b[idx + XXIV] EST player TVNC { SI b[idx] EST player ET b[idx + VIII] EST player ET b[idx + XVI] EST player ET b[idx + XXIV] EST player TVNC {
REDI(VERITAS) REDI(VERITAS)
} }
} }
} }
DONICVM r VT I VSQVE IV FACE { DONICVM r VT I VSQVE III FACE {
DONICVM c VT IV VSQVE VIII FACE { DONICVM c VT IV VSQVE VII FACE {
DESIGNA idx VT (r - I) * VII + c DESIGNA idx VT (r - I) * VII + c
SI b[idx] EST player ET b[idx + VI] EST player ET b[idx + XII] EST player ET b[idx + XVIII] EST player TVNC { SI b[idx] EST player ET b[idx + VI] EST player ET b[idx + XII] EST player ET b[idx + XVIII] EST player TVNC {
REDI(VERITAS) REDI(VERITAS)
@@ -53,9 +53,9 @@ DEFINI est_victor(b, player) VT {
DEFINI print_board(b) VT { DEFINI print_board(b) VT {
DICE("+---+---+---+---+---+---+---+") DICE("+---+---+---+---+---+---+---+")
DONICVM r VT I VSQVE VII FACE { DONICVM r VT I VSQVE VI FACE {
DESIGNA line VT "| " DESIGNA line VT "| "
DONICVM c VT I VSQVE VIII FACE { DONICVM c VT I VSQVE VII FACE {
DESIGNA cell VT b[(r - I) * VII + c] DESIGNA cell VT b[(r - I) * VII + c]
SI cell EST I TVNC { SI cell EST I TVNC {
DESIGNA line VT line & "X | " DESIGNA line VT line & "X | "
@@ -101,35 +101,35 @@ DEFINI score_fenestram(a, b, c, d) VT {
DEFINI aestima(b) VT { DEFINI aestima(b) VT {
DESIGNA score VT NVLLVS DESIGNA score VT NVLLVS
// Center column preference: each AI piece in column IV is worth +1 // Center column preference: each AI piece in column IV is worth +1
DONICVM r VT I VSQVE VII FACE { DONICVM r VT I VSQVE VI FACE {
SI b[(r - I) * VII + IV] EST II TVNC { SI b[(r - I) * VII + IV] EST II TVNC {
DESIGNA score VT score + I DESIGNA score VT score + I
} }
} }
// Horizontal windows (6 rows x 4 starting columns = 24) // Horizontal windows (6 rows x 4 starting columns = 24)
DONICVM r VT I VSQVE VII FACE { DONICVM r VT I VSQVE VI FACE {
DONICVM c VT I VSQVE V FACE { DONICVM c VT I VSQVE IV FACE {
DESIGNA idx VT (r - I) * VII + c DESIGNA idx VT (r - I) * VII + c
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + I], b[idx + II], b[idx + III]) DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + I], b[idx + II], b[idx + III])
} }
} }
// Vertical windows (3 starting rows x 7 columns = 21) // Vertical windows (3 starting rows x 7 columns = 21)
DONICVM r VT I VSQVE IV FACE { DONICVM r VT I VSQVE III FACE {
DONICVM c VT I VSQVE VIII FACE { DONICVM c VT I VSQVE VII FACE {
DESIGNA idx VT (r - I) * VII + c DESIGNA idx VT (r - I) * VII + c
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VII], b[idx + XIV], b[idx + XXI]) DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VII], b[idx + XIV], b[idx + XXI])
} }
} }
// Diagonal up-right windows (3 starting rows x 4 starting columns = 12) // Diagonal up-right windows (3 starting rows x 4 starting columns = 12)
DONICVM r VT I VSQVE IV FACE { DONICVM r VT I VSQVE III FACE {
DONICVM c VT I VSQVE V FACE { DONICVM c VT I VSQVE IV FACE {
DESIGNA idx VT (r - I) * VII + c DESIGNA idx VT (r - I) * VII + c
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VIII], b[idx + XVI], b[idx + XXIV]) DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VIII], b[idx + XVI], b[idx + XXIV])
} }
} }
// Diagonal up-left windows (3 starting rows x 4 starting columns = 12) // Diagonal up-left windows (3 starting rows x 4 starting columns = 12)
DONICVM r VT I VSQVE IV FACE { DONICVM r VT I VSQVE III FACE {
DONICVM c VT IV VSQVE VIII FACE { DONICVM c VT IV VSQVE VII FACE {
DESIGNA idx VT (r - I) * VII + c DESIGNA idx VT (r - I) * VII + c
DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VI], b[idx + XII], b[idx + XVIII]) DESIGNA score VT score + INVOCA score_fenestram(b[idx], b[idx + VI], b[idx + XII], b[idx + XVIII])
} }
@@ -219,8 +219,8 @@ DEFINI ai_move(b) VT {
} }
// --- Board setup --- // --- Board setup ---
DESIGNA board VT [I VSQVE XLIII] DESIGNA board VT [I VSQVE XLII]
DONICVM i VT I VSQVE XLIII FACE { DONICVM i VT I VSQVE XLII FACE {
DESIGNA board[i] VT NVLLVS DESIGNA board[i] VT NVLLVS
} }

View File

@@ -1,9 +1,9 @@
// Prints an X×X multiplication table // Prints an X×X multiplication table
DESIGNA n VT X DESIGNA n VT X
DONICVM i VT I VSQVE n + I FACE { DONICVM i VT I VSQVE n FACE {
DESIGNA line VT "" DESIGNA line VT ""
DONICVM k VT I VSQVE n + I FACE { DONICVM k VT I VSQVE n FACE {
DESIGNA line VT line & i * k & " " DESIGNA line VT line & i * k & " "
} }
DICE(line) DICE(line)

View File

@@ -2,9 +2,9 @@
DESIGNA n VT L DESIGNA n VT L
DONICVM i VT II VSQVE n + I FACE { DONICVM i VT II VSQVE n FACE {
DESIGNA is_prime VT VERITAS DESIGNA is_prime VT VERITAS
DONICVM k VT II VSQVE i FACE { DONICVM k VT II VSQVE i - I FACE {
SI (i / k) * k EST i TVNC { SI (i / k) * k EST i TVNC {
DESIGNA is_prime VT FALSITAS DESIGNA is_prime VT FALSITAS
} }

2
language/main.aux Normal file
View File

@@ -0,0 +1,2 @@
\relax
\gdef \@abspage@last{2}

269
language/main.log Normal file
View File

@@ -0,0 +1,269 @@
This is XeTeX, Version 3.141592653-2.6-0.999998 (TeX Live 2026/Arch Linux) (preloaded format=xelatex 2026.4.8) 21 APR 2026 22:51
entering extended mode
restricted \write18 enabled.
%&-line parsing enabled.
**main.tex
(./main.tex
LaTeX2e <2025-11-01>
L3 programming layer <2026-01-19>
(/usr/share/texmf-dist/tex/latex/base/article.cls
Document Class: article 2025/01/22 v1.4n Standard LaTeX document class
(/usr/share/texmf-dist/tex/latex/base/size10.clo
File: size10.clo 2025/01/22 v1.4n Standard LaTeX file (size option)
)
\c@part=\count271
\c@section=\count272
\c@subsection=\count273
\c@subsubsection=\count274
\c@paragraph=\count275
\c@subparagraph=\count276
\c@figure=\count277
\c@table=\count278
\abovecaptionskip=\skip49
\belowcaptionskip=\skip50
\bibindent=\dimen148
)
(/usr/share/texmf-dist/tex/latex/geometry/geometry.sty
Package: geometry 2020/01/02 v5.9 Page Geometry
(/usr/share/texmf-dist/tex/latex/graphics/keyval.sty
Package: keyval 2022/05/29 v1.15 key=value parser (DPC)
\KV@toks@=\toks17
)
(/usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead.
(/usr/share/texmf-dist/tex/generic/iftex/iftex.sty
Package: iftex 2024/12/12 v1.0g TeX engine tests
))
\Gm@cnth=\count279
\Gm@cntv=\count280
\c@Gm@tempcnt=\count281
\Gm@bindingoffset=\dimen149
\Gm@wd@mp=\dimen150
\Gm@odd@mp=\dimen151
\Gm@even@mp=\dimen152
\Gm@layoutwidth=\dimen153
\Gm@layoutheight=\dimen154
\Gm@layouthoffset=\dimen155
\Gm@layoutvoffset=\dimen156
\Gm@dimlist=\toks18
)
(/usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
(/usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
(/usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
Package: expl3 2026-01-19 L3 programming layer (loader)
(/usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
File: l3backend-xetex.def 2025-10-09 L3 backend support: XeTeX
\g__graphics_track_int=\count282
\g__pdfannot_backend_int=\count283
\g__pdfannot_backend_link_int=\count284
))
Package: xparse 2025-10-09 L3 Experimental document command parser
)
Package: fontspec 2025/09/29 v2.9g Font selection for XeLaTeX and LuaLaTeX
(/usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
Package: fontspec-xetex 2025/09/29 v2.9g Font selection for XeLaTeX and LuaLaTe
X
\l__fontspec_script_int=\count285
\l__fontspec_language_int=\count286
\l__fontspec_strnum_int=\count287
\l__fontspec_tmp_int=\count288
\l__fontspec_tmpa_int=\count289
\l__fontspec_tmpb_int=\count290
\l__fontspec_tmpc_int=\count291
\l__fontspec_em_int=\count292
\l__fontspec_emdef_int=\count293
\l__fontspec_strong_int=\count294
\l__fontspec_strongdef_int=\count295
\l__fontspec_tmpa_dim=\dimen157
\l__fontspec_tmpb_dim=\dimen158
\l__fontspec_tmpc_dim=\dimen159
(/usr/share/texmf-dist/tex/latex/base/fontenc.sty
Package: fontenc 2025/07/18 v2.1d Standard LaTeX package
)
(/usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg)))
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono scale = 0.7.
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono scale = 0.7.
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono/B scale = 0.7.
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono/I scale = 0.7.
Package fontspec Info:
(fontspec) Hurmit Nerd Font Mono/BI scale = 0.7.
Package fontspec Info:
(fontspec) Font family 'HurmitNerdFontMono(0)' created for font
(fontspec) 'Hurmit Nerd Font Mono' with options
(fontspec) [WordSpace={1,0,0},HyphenChar=None,PunctuationSpace=Word
Space,Scale=0.7].
(fontspec)
(fontspec) This font family consists of the following NFSS
(fontspec) series/shapes:
(fontspec)
(fontspec) - 'normal' (m/n) with NFSS spec.: <->s*[0.7]"Hurmit
(fontspec) Nerd Font Mono/OT:script=DFLT;language=dflt;"
(fontspec) - 'bold' (b/n) with NFSS spec.: <->s*[0.7]"Hurmit Nerd
(fontspec) Font Mono/B/OT:script=DFLT;language=dflt;"
(fontspec) - 'italic' (m/it) with NFSS spec.: <->s*[0.7]"Hurmit
(fontspec) Nerd Font Mono/I/OT:script=DFLT;language=dflt;"
(fontspec) - 'bold italic' (b/it) with NFSS spec.:
(fontspec) <->s*[0.7]"Hurmit Nerd Font
(fontspec) Mono/BI/OT:script=DFLT;language=dflt;"
No file main.aux.
\openout1 = `main.aux'.
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for TU/lmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 11.
LaTeX Font Info: ... okay on input line 11.
*geometry* driver: auto-detecting
*geometry* detected driver: xetex
*geometry* verbose mode - [ preamble ] result:
* driver: xetex
* paper: a4paper
* layout: <same size as paper>
* layoutoffset:(h,v)=(0.0pt,0.0pt)
* modes:
* h-part:(L,W,R)=(72.26999pt, 452.9679pt, 72.26999pt)
* v-part:(T,H,B)=(72.26999pt, 700.50687pt, 72.26999pt)
* \paperwidth=597.50787pt
* \paperheight=845.04684pt
* \textwidth=452.9679pt
* \textheight=700.50687pt
* \oddsidemargin=0.0pt
* \evensidemargin=0.0pt
* \topmargin=-37.0pt
* \headheight=12.0pt
* \headsep=25.0pt
* \topskip=10.0pt
* \footskip=30.0pt
* \marginparwidth=57.0pt
* \marginparsep=11.0pt
* \columnsep=10.0pt
* \skip\footins=9.0pt plus 4.0pt minus 2.0pt
* \hoffset=0.0pt
* \voffset=0.0pt
* \mag=1000
* \@twocolumnfalse
* \@twosidefalse
* \@mparswitchfalse
* \@reversemarginfalse
* (1in=72.27pt=25.4mm, 1cm=28.453pt)
Package fontspec Info:
(fontspec) Adjusting the maths setup (use [no-math] to avoid
(fontspec) this).
\symlegacymaths=\mathgroup4
LaTeX Font Info: Overwriting symbol font `legacymaths' in version `bold'
(Font) OT1/cmr/m/n --> OT1/cmr/bx/n on input line 11.
LaTeX Font Info: Redeclaring math accent \acute on input line 11.
LaTeX Font Info: Redeclaring math accent \grave on input line 11.
LaTeX Font Info: Redeclaring math accent \ddot on input line 11.
LaTeX Font Info: Redeclaring math accent \tilde on input line 11.
LaTeX Font Info: Redeclaring math accent \bar on input line 11.
LaTeX Font Info: Redeclaring math accent \breve on input line 11.
LaTeX Font Info: Redeclaring math accent \check on input line 11.
LaTeX Font Info: Redeclaring math accent \hat on input line 11.
LaTeX Font Info: Redeclaring math accent \dot on input line 11.
LaTeX Font Info: Redeclaring math accent \mathring on input line 11.
LaTeX Font Info: Redeclaring math symbol \colon on input line 11.
LaTeX Font Info: Redeclaring math symbol \Gamma on input line 11.
LaTeX Font Info: Redeclaring math symbol \Delta on input line 11.
LaTeX Font Info: Redeclaring math symbol \Theta on input line 11.
LaTeX Font Info: Redeclaring math symbol \Lambda on input line 11.
LaTeX Font Info: Redeclaring math symbol \Xi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Pi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Sigma on input line 11.
LaTeX Font Info: Redeclaring math symbol \Upsilon on input line 11.
LaTeX Font Info: Redeclaring math symbol \Phi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Psi on input line 11.
LaTeX Font Info: Redeclaring math symbol \Omega on input line 11.
LaTeX Font Info: Redeclaring math symbol \mathdollar on input line 11.
LaTeX Font Info: Redeclaring symbol font `operators' on input line 11.
LaTeX Font Info: Encoding `OT1' has changed to `TU' for symbol font
(Font) `operators' in the math version `normal' on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `normal'
(Font) OT1/cmr/m/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Encoding `OT1' has changed to `TU' for symbol font
(Font) `operators' in the math version `bold' on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `bold'
(Font) OT1/cmr/bx/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `normal'
(Font) TU/lmr/m/n --> TU/lmr/m/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathit' in version `normal'
(Font) OT1/cmr/m/it --> TU/lmr/m/it on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathbf' in version `normal'
(Font) OT1/cmr/bx/n --> TU/lmr/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathsf' in version `normal'
(Font) OT1/cmss/m/n --> TU/lmss/m/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathtt' in version `normal'
(Font) OT1/cmtt/m/n --> TU/HurmitNerdFontMono(0)/m/n on input
line 11.
LaTeX Font Info: Overwriting symbol font `operators' in version `bold'
(Font) TU/lmr/m/n --> TU/lmr/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathit' in version `bold'
(Font) OT1/cmr/bx/it --> TU/lmr/b/it on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathsf' in version `bold'
(Font) OT1/cmss/bx/n --> TU/lmss/b/n on input line 11.
LaTeX Font Info: Overwriting math alphabet `\mathtt' in version `bold'
(Font) OT1/cmtt/m/n --> TU/HurmitNerdFontMono(0)/b/n on input
line 11.
LaTeX Font Info: External font `cmex10' loaded for size
(Font) <7> on input line 14.
LaTeX Font Info: External font `cmex10' loaded for size
(Font) <5> on input line 14.
LaTeX Font Info: Font shape `TU/HurmitNerdFontMono(0)/m/n' will be
(Font) scaled to size 6.99997pt on input line 22.
LaTeX Warning: Float too large for page by 44.293pt on input line 93.
[1
] [2] (./main.aux)
***********
LaTeX2e <2025-11-01>
L3 programming layer <2026-01-19>
***********
)
Here is how much of TeX's memory you used:
3526 strings out of 470191
106539 string characters out of 5479698
562689 words of memory out of 5000000
32135 multiletter control sequences out of 15000+600000
627857 words of font info for 57 fonts, out of 8000000 for 9000
14 hyphenation exceptions out of 8191
73i,9n,93p,432b,328s stack positions out of 10000i,1000n,20000p,200000b,200000s
Output written on main.pdf (2 pages).

Binary file not shown.

View File

@@ -41,7 +41,10 @@
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FACE} \textit{scope}} \\ \languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FACE} \textit{scope}} \\
\languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\ \languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\
\languageline{statement}{\texttt{ERVMPE}} \\ \languageline{statement}{\texttt{ERVMPE}} \\
\languageline{statement}{\texttt{CONTINVA}} \\ \hline \languageline{statement}{\texttt{CONTINVA}} \\
\languageline{statement}{\textit{try-statement}} \\ \hline
\languageline{try-statement}{\texttt{TEMPTA} \textit{scope} \texttt{CAPE} \textbf{id} \textit{scope}} \\ \hline
\languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope}} \\ \languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope}} \\
\languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope} \textit{optional-newline} \textit{else-statement}} \\ \hline \languageline{if-statement}{\texttt{SI} \textit{expression} \texttt{TVNC} \textit{scope} \textit{optional-newline} \textit{else-statement}} \\ \hline
@@ -59,6 +62,7 @@
\languageline{expression}{\texttt{FVNCTIO} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\ \languageline{expression}{\texttt{FVNCTIO} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
\languageline{expression}{\textit{literal}} \\ \languageline{expression}{\textit{literal}} \\
\languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{]}} \\ \languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{]}} \\
\languageline{expression}{\textit{expression} \texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]} \textnormal{\small\ (inclusive slice)}} \\
\languageline{expression}{\textit{expression} \textbf{binop} \textit{expression}} \\ \languageline{expression}{\textit{expression} \textbf{binop} \textit{expression}} \\
\languageline{expression}{\textbf{unop} \textit{expression}} \\ \hline \languageline{expression}{\textbf{unop} \textit{expression}} \\ \hline
\languageline{literal}{\textbf{string}} \\ \languageline{literal}{\textbf{string}} \\
@@ -66,7 +70,7 @@
\languageline{literal}{\textbf{numeral}} \\ \languageline{literal}{\textbf{numeral}} \\
\languageline{literal}{\textbf{bool}} \\ \languageline{literal}{\textbf{bool}} \\
\languageline{literal}{\texttt{[} \textit{optional-expressions} \texttt{]}} \\ \languageline{literal}{\texttt{[} \textit{optional-expressions} \texttt{]}} \\
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]}} \\ \languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]} \textnormal{\small\ (inclusive on both ends)}} \\
\languageline{literal}{\texttt{TABVLA} \texttt{\{} \textit{optional-dict-items} \texttt{\}}} \\ \hline \languageline{literal}{\texttt{TABVLA} \texttt{\{} \textit{optional-dict-items} \texttt{\}}} \\ \hline
\languageline{optional-dict-items}{\textit{dict-items}} \\ \languageline{optional-dict-items}{\textit{dict-items}} \\
@@ -94,10 +98,10 @@
\newpage \newpage
\begin{itemize} \begin{itemize}
\item \textbf{newline}: \\ Newlines are combined, so a single newline is the same as multiple. \item \textbf{newline}: \\ Newlines are combined, so a single newline is the same as multiple.
\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{module-name}: \\ Modules are flags given to the interpreter/compiler, to let it know you want to be using certain rules, functions, or features. Available modules: \texttt{FORS} (randomness), \texttt{FRACTIO} (fractions), \texttt{MAGNVM} (large integers), \texttt{SCRIPTA} (file I/O: \texttt{LEGE}, \texttt{SCRIBE}, \texttt{ADIVNGE}), \texttt{SVBNVLLA} (negative literals).
\item \textbf{id}: \\ Variable. Can only consist of lowercase characters and underscores, but not the letters j, u, or w. \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{builtin}: \\ Builtin functions are uppercase latin words.
\item \textbf{string}: \\ Any text encased in \texttt{"} or \texttt{'} characters. Single-quoted strings are always literal. \item \textbf{string}: \\ Any text encased in \texttt{"} or \texttt{'} characters. Single-quoted strings are always literal. Strings support 1-based indexing (\texttt{string[I]}) and inclusive slicing (\texttt{string[I VSQVE III]}), returning single-character strings and substrings respectively.
\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{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{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{bool}: \\ VERITAS or FALSITAS.

BIN
snippets/aeternvm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,2 @@
DESIGNA x VT [X, XX, XXX, XL, L]
DICE(x[II VSQVE IV])

BIN
snippets/array_slice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -0,0 +1 @@
DESIGNA a, b, c VT [I, II, III]

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,2 @@
DEFINI pair (a, b) VT { REDI (a, b) }
DESIGNA x, y VT INVOCA pair (III, VII)

BIN
snippets/destructure_fn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,3 @@
DICE(d["nomen"])
DESIGNA d["aetas"] VT XXVI
DESIGNA d["novus"] VT I

BIN
snippets/dict_access.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1 @@
DESIGNA d VT TABVLA {"nomen" VT "Marcus", "aetas" VT XXV}

BIN
snippets/dict_create.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

3
snippets/dict_per.cent Normal file
View File

@@ -0,0 +1,3 @@
PER k IN d FACE {
DICE(k)
}

BIN
snippets/dict_per.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,4 @@
DESIGNA nomen VT "Marcus"
DICE("Salve, {nomen}!")
DICE("Sum: {III + IV}")
DICE("{nomen} has {V} cats")

BIN
snippets/string_interp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -9,8 +9,8 @@ contexts:
main: main:
- include: comments - include: comments
- include: strings - include: strings
- include: fractions
- include: keywords - include: keywords
- include: fractions
- include: numerals - include: numerals
- include: constants - include: constants
- include: builtins - include: builtins
@@ -70,15 +70,15 @@ contexts:
scope: constant.language.centvrion scope: constant.language.centvrion
builtins: builtins:
- match: '\b(AVDI_NVMERVS|AVDI|CLAVES|DECIMATIO|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|SEMEN|SENATVS)\b' - match: '\b(ADIVNGE|AVDI_NVMERVS|AVDI|CLAVES|DECIMATIO|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LEGE|LONGITVDO|ORDINA|SCRIBE|SEMEN|SENATVS)\b'
scope: support.function.builtin.centvrion scope: support.function.builtin.centvrion
modules: modules:
- match: '\b(FORS|FRACTIO|MAGNVM|SVBNVLLA)\b' - match: '\b(FORS|FRACTIO|MAGNVM|SCRIPTA|SVBNVLLA)\b'
scope: support.class.module.centvrion scope: support.class.module.centvrion
keywords: keywords:
- 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' - match: '\b(AETERNVM|ALVID|AVGE|AVT|CAPE|CONTINVA|DEFINI|DESIGNA|DISPAR|DONICVM|DVM|ERVMPE|EST|ET|FACE|FVNCTIO|INVOCA|IN|MINVE|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT|CVM)\b'
scope: keyword.control.centvrion scope: keyword.control.centvrion
operators: operators:

593
tests.py
View File

@@ -2,6 +2,7 @@ import os
import random import random
import subprocess import subprocess
import tempfile import tempfile
import time
import unittest import unittest
from io import StringIO from io import StringIO
from unittest.mock import patch from unittest.mock import patch
@@ -10,12 +11,12 @@ from parameterized import parameterized
from fractions import Fraction from fractions import Fraction
from centvrion.ast_nodes import ( from centvrion.ast_nodes import (
ArrayIndex, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray, ArrayIndex, ArraySlice, Bool, BinOp, BuiltIn, DataArray, DataDict, DataRangeArray,
Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement, Defini, Continva, Designa, DesignaDestructure, DesignaIndex, DumStatement,
Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca, Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca,
ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement, ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement,
String, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, fraction_to_frac, String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
num_to_int, int_to_num, make_string, fraction_to_frac, num_to_int, int_to_num, make_string,
) )
from centvrion.compiler.emitter import compile_program from centvrion.compiler.emitter import compile_program
from centvrion.errors import CentvrionError from centvrion.errors import CentvrionError
@@ -283,13 +284,13 @@ assignment_tests = [
Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), "SYMBOL_PLUS")), Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), "SYMBOL_PLUS")),
ExpressionStatement(ID("x"))]), ExpressionStatement(ID("x"))]),
ValInt(6)), ValInt(6)),
# AVGE inside a loop (DONICVM range is exclusive of upper bound: I VSQVE III = [1, 2]) # AVGE inside a loop (DONICVM range is inclusive: I VSQVE III = [1, 2, 3])
("DESIGNA s VT NVLLVS\nDONICVM i VT I VSQVE III FACE {\ns AVGE i\n}\ns", ("DESIGNA s VT NVLLVS\nDONICVM i VT I VSQVE III FACE {\ns AVGE i\n}\ns",
Program([], [Designa(ID("s"), Nullus()), Program([], [Designa(ID("s"), Nullus()),
PerStatement(DataRangeArray(Numeral("I"), Numeral("III")), ID("i"), PerStatement(DataRangeArray(Numeral("I"), Numeral("III")), ID("i"),
[Designa(ID("s"), BinOp(ID("s"), ID("i"), "SYMBOL_PLUS"))]), [Designa(ID("s"), BinOp(ID("s"), ID("i"), "SYMBOL_PLUS"))]),
ExpressionStatement(ID("s"))]), ExpressionStatement(ID("s"))]),
ValInt(3)), ValInt(6)),
] ]
class TestAssignment(unittest.TestCase): class TestAssignment(unittest.TestCase):
@@ -469,7 +470,7 @@ control_tests = [
# DONICVM range loop # DONICVM range loop
("DONICVM i VT I VSQVE V FACE { DICE(i) }", ("DONICVM i VT I VSQVE V FACE { DICE(i) }",
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]), Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
ValStr("IV"), "I\nII\nIII\nIV\n"), ValStr("V"), "I\nII\nIII\nIV\nV\n"),
] ]
class TestControl(unittest.TestCase): class TestControl(unittest.TestCase):
@@ -567,6 +568,44 @@ builtin_tests = [
("SENATVS([FALSITAS, FALSITAS, VERITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(False), Bool(False), Bool(True)])]))]), ValBool(False)), ("SENATVS([FALSITAS, FALSITAS, VERITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(False), Bool(False), Bool(True)])]))]), ValBool(False)),
# SENATVS: array input, empty → FALSITAS # SENATVS: array input, empty → FALSITAS
("SENATVS([])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([])]))]), ValBool(False)), ("SENATVS([])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([])]))]), ValBool(False)),
# ORDINA: sort integers
("ORDINA([III, I, II])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Numeral("I"), Numeral("II")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])),
# ORDINA: sort strings
('ORDINA(["c", "a", "b"])', Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([String("c"), String("a"), String("b")])]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])),
# ORDINA: empty list
("ORDINA([])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([])]))]), ValList([])),
# ORDINA: single element
("ORDINA([V])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("V")])]))]), ValList([ValInt(5)])),
# ORDINA: already sorted
("ORDINA([I, II, III])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])),
# ORDINA: duplicates
("ORDINA([II, I, II])", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("II"), Numeral("I"), Numeral("II")])]))]), ValList([ValInt(1), ValInt(2), ValInt(2)])),
# ORDINA: negative numbers
("CVM SVBNVLLA\nORDINA([-II, III, -I])", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([UnaryMinus(Numeral("II")), Numeral("III"), UnaryMinus(Numeral("I"))])]))]), ValList([ValInt(-2), ValInt(-1), ValInt(3)])),
# ORDINA: fractions only
("CVM FRACTIO\nORDINA([IIIS, S, IIS])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Fractio("IIIS"), Fractio("S"), Fractio("IIS")])]))]), ValList([ValFrac(Fraction(1, 2)), ValFrac(Fraction(5, 2)), ValFrac(Fraction(7, 2))])),
# ORDINA: mixed integers and fractions
("CVM FRACTIO\nORDINA([III, S, II])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Fractio("S"), Numeral("II")])]))]), ValList([ValFrac(Fraction(1, 2)), ValInt(2), ValInt(3)])),
# ORDINA: array passed via variable
("DESIGNA x VT [III, I, II]\nORDINA(x)", Program([], [Designa(ID("x"), DataArray([Numeral("III"), Numeral("I"), Numeral("II")])), ExpressionStatement(BuiltIn("ORDINA", [ID("x")]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])),
# TYPVS: integer
("TYPVS(V)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Numeral("V")]))]), ValStr("NVMERVS")),
# TYPVS: string
('TYPVS("hello")', Program([], [ExpressionStatement(BuiltIn("TYPVS", [String("hello")]))]), ValStr("LITTERA")),
# TYPVS: boolean
("TYPVS(VERITAS)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Bool(True)]))]), ValStr("VERAX")),
# TYPVS: list
("TYPVS([I, II])", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataArray([Numeral("I"), Numeral("II")])]))]), ValStr("CATALOGVS")),
# TYPVS: empty list
("TYPVS([])", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataArray([])]))]), ValStr("CATALOGVS")),
# TYPVS: fraction
("CVM FRACTIO\nTYPVS(S)", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("TYPVS", [Fractio("S")]))]), ValStr("FRACTIO")),
# TYPVS: dict
("TYPVS(TABVLA {})", Program([], [ExpressionStatement(BuiltIn("TYPVS", [DataDict([])]))]), ValStr("TABVLA")),
# TYPVS: function
("TYPVS(FVNCTIO () VT { REDI(I) })", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Fvnctio([], [Redi([Numeral("I")])])]))]), ValStr("FVNCTIO")),
# TYPVS: null
("TYPVS(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("TYPVS", [Nullus()]))]), ValStr("NVLLVS")),
] ]
class TestBuiltins(unittest.TestCase): class TestBuiltins(unittest.TestCase):
@@ -600,6 +639,10 @@ error_tests = [
("I * \"hello\"", CentvrionError), # multiplication with string ("I * \"hello\"", CentvrionError), # multiplication with string
("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings ("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings
("I[I]", CentvrionError), # indexing a non-array ("I[I]", CentvrionError), # indexing a non-array
('"SALVTE"[VII]', CentvrionError), # string index out of range
('"SALVTE"[NVLLVS]', CentvrionError), # string index with non-integer
('"SALVTE"[II VSQVE VII]', CentvrionError), # string slice out of range
('"SALVTE"[III VSQVE II]', CentvrionError), # string slice from > to
("DESIGNA x VT I\nDESIGNA x[I] VT II", CentvrionError), # index-assign to non-array ("DESIGNA x VT I\nDESIGNA x[I] VT II", CentvrionError), # index-assign to non-array
("SEMEN(I)", CentvrionError), # requires FORS module ("SEMEN(I)", CentvrionError), # requires FORS module
('CVM FORS\nSEMEN("abc")', CentvrionError), # SEMEN requires integer seed ('CVM FORS\nSEMEN("abc")', CentvrionError), # SEMEN requires integer seed
@@ -610,9 +653,15 @@ error_tests = [
("DECIMATIO([I, II, III])", CentvrionError), # FORS required for DECIMATIO ("DECIMATIO([I, II, III])", CentvrionError), # FORS required for DECIMATIO
("CVM FORS\nDECIMATIO(I)", CentvrionError), # DECIMATIO requires an array ("CVM FORS\nDECIMATIO(I)", CentvrionError), # DECIMATIO requires an array
("LONGITVDO(I)", CentvrionError), # LONGITVDO on non-array ("LONGITVDO(I)", CentvrionError), # LONGITVDO on non-array
("ORDINA(I)", CentvrionError), # ORDINA on non-array
('ORDINA([I, "a"])', CentvrionError), # ORDINA mixed types
("DESIGNA x VT I\nORDINA(x)", CentvrionError), # ORDINA on id (non-array)
("SENATVS(I)", CentvrionError), # SENATVS requires booleans ("SENATVS(I)", CentvrionError), # SENATVS requires booleans
("SENATVS(VERITAS, I)", CentvrionError), # SENATVS mixed types ("SENATVS(VERITAS, I)", CentvrionError), # SENATVS mixed types
("SENATVS([I, II, III])", CentvrionError), # SENATVS array of non-bools ("SENATVS([I, II, III])", CentvrionError), # SENATVS array of non-bools
('LEGE("x.txt")', CentvrionError), # SCRIPTA required for LEGE
('SCRIBE("x.txt", "hi")', CentvrionError), # SCRIPTA required for SCRIBE
('ADIVNGE("x.txt", "hi")', CentvrionError), # SCRIPTA required for ADIVNGE
("DESIGNA x VT I\nINVOCA x ()", CentvrionError), # invoking a non-function ("DESIGNA x VT I\nINVOCA x ()", CentvrionError), # invoking a non-function
("SI I TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: int ("SI I TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: int
("IIIS", CentvrionError), # fraction without FRACTIO module ("IIIS", CentvrionError), # fraction without FRACTIO module
@@ -628,6 +677,16 @@ error_tests = [
("DESIGNA a, b VT III", CentvrionError), # destructure non-array ("DESIGNA a, b VT III", CentvrionError), # destructure non-array
("DESIGNA a, b VT [I]", CentvrionError), # destructure length mismatch: too many targets ("DESIGNA a, b VT [I]", CentvrionError), # destructure length mismatch: too many targets
("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets ("DESIGNA a, b VT [I, II, III]", CentvrionError), # destructure length mismatch: too few targets
("[I, II, III][II VSQVE IV]", CentvrionError), # slice upper bound out of range
("[I, II, III][NVLLVS VSQVE II]", CentvrionError), # slice with non-integer bound
("I[I VSQVE II]", CentvrionError), # slice on non-array
("[I, II, III][III VSQVE I]", CentvrionError), # slice from > to
("CVM SVBNVLLA\n[I, II, III][-I VSQVE II]", CentvrionError), # slice with negative lower bound
("CVM SVBNVLLA\n[I, II, III][I VSQVE -I]", CentvrionError), # slice with negative upper bound
("CVM FRACTIO\n[I, II, III][IIIS VSQVE III]", CentvrionError), # slice with fractional lower bound
("CVM FRACTIO\n[I, II, III][I VSQVE IIIS]", CentvrionError), # slice with fractional upper bound
("CVM FRACTIO\n[I, II, III][I / II VSQVE III]", CentvrionError), # slice with division-fraction lower bound
("TEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nDESIGNA y VT I / NVLLVS\n}", CentvrionError), # uncaught error in catch block propagates
] ]
class TestErrors(unittest.TestCase): class TestErrors(unittest.TestCase):
@@ -1146,10 +1205,10 @@ class TestFunctionEdge(unittest.TestCase):
# --- Loop edge cases --- # --- Loop edge cases ---
loop_edge_tests = [ loop_edge_tests = [
# range(3, 3) is empty — body never runs, program returns ValNul # [III VSQVE III] = [3] — single iteration
("DONICVM i VT III VSQVE III FACE { DICE(i) }", ("DONICVM i VT III VSQVE III FACE { DICE(i) }",
Program([], [PerStatement(DataRangeArray(Numeral("III"), Numeral("III")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]), Program([], [PerStatement(DataRangeArray(Numeral("III"), Numeral("III")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
ValNul(), ""), ValStr("III"), "III\n"),
# empty array — body never runs # empty array — body never runs
("PER i IN [] FACE { DICE(i) }", ("PER i IN [] FACE { DICE(i) }",
Program([], [PerStatement(DataArray([]), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]), Program([], [PerStatement(DataArray([]), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
@@ -1233,7 +1292,7 @@ loop_edge_tests = [
ExpressionStatement(ID("cnt")), ExpressionStatement(ID("cnt")),
]), ]),
ValInt(3), ""), ValInt(3), ""),
# DONICVM with CONTINVA: skip value III, count remaining # DONICVM with CONTINVA: skip value III, count remaining (I VSQVE IV = [1,2,3,4], skip 3 → 3 increments)
("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FACE {\nSI i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt", ("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FACE {\nSI i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt",
Program([], [ Program([], [
Designa(ID("cnt"), Numeral("I")), Designa(ID("cnt"), Numeral("I")),
@@ -1245,7 +1304,7 @@ loop_edge_tests = [
), ),
ExpressionStatement(ID("cnt")), ExpressionStatement(ID("cnt")),
]), ]),
ValInt(3)), ValInt(4)),
# DVM condition true from start — body never runs # DVM condition true from start — body never runs
("DESIGNA x VT I\nDVM VERITAS FACE {\nDESIGNA x VT x + I\n}\nx", ("DESIGNA x VT I\nDVM VERITAS FACE {\nDESIGNA x VT x + I\n}\nx",
Program([], [ Program([], [
@@ -1254,10 +1313,21 @@ loop_edge_tests = [
ExpressionStatement(ID("x")), ExpressionStatement(ID("x")),
]), ]),
ValInt(1), ""), ValInt(1), ""),
# single iteration: [I VSQVE II] = [1] # two iterations: [I VSQVE II] = [1, 2]
("DONICVM i VT I VSQVE II FACE { DICE(i) }", ("DONICVM i VT I VSQVE II FACE { DICE(i) }",
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("II")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]), Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("II")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
ValStr("II"), "I\nII\n"),
# single iteration: [I VSQVE I] = [1]
("DONICVM i VT I VSQVE I FACE { DICE(i) }",
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("I")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
ValStr("I"), "I\n"), ValStr("I"), "I\n"),
# empty range: [V VSQVE I] = []
("DESIGNA x VT NVLLVS\nDONICVM i VT V VSQVE I FACE { DESIGNA x VT x + i }\nx",
Program([], [Designa(ID("x"), Nullus()),
PerStatement(DataRangeArray(Numeral("V"), Numeral("I")), ID("i"),
[Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]),
ExpressionStatement(ID("x"))]),
ValNul(), ""),
] ]
class TestLoopEdge(unittest.TestCase): class TestLoopEdge(unittest.TestCase):
@@ -1382,7 +1452,7 @@ array_index_tests = [
Program([], [Designa(ID("a"), DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])), ExpressionStatement(ArrayIndex(ID("a"), Numeral("II")))]), Program([], [Designa(ID("a"), DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])), ExpressionStatement(ArrayIndex(ID("a"), Numeral("II")))]),
ValInt(20)), # second element ValInt(20)), # second element
# index into range array # index into range array
("[I VSQVE V][II]", Program([], [ExpressionStatement(ArrayIndex(DataRangeArray(Numeral("I"), Numeral("V")), Numeral("II")))]), ValInt(2)), # second element of [1,2,3,4] ("[I VSQVE V][II]", Program([], [ExpressionStatement(ArrayIndex(DataRangeArray(Numeral("I"), Numeral("V")), Numeral("II")))]), ValInt(2)), # second element of [1,2,3,4,5]
# expression as index # expression as index
("[I, II, III][I + I]", ("[I, II, III][I + I]",
Program([], [ExpressionStatement(ArrayIndex( Program([], [ExpressionStatement(ArrayIndex(
@@ -1461,6 +1531,145 @@ class TestArrayIndexAssign(unittest.TestCase):
run_test(self, source, nodes, value) run_test(self, source, nodes, value)
# --- Array slicing ---
array_slice_tests = [
# basic slice from middle
("[X, XX, XXX, XL, L][II VSQVE IV]",
Program([], [ExpressionStatement(ArraySlice(
DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX"), Numeral("XL"), Numeral("L")]),
Numeral("II"), Numeral("IV")))]),
ValList([ValInt(20), ValInt(30), ValInt(40)])),
# slice of length 1
("[I, II, III][II VSQVE II]",
Program([], [ExpressionStatement(ArraySlice(
DataArray([Numeral("I"), Numeral("II"), Numeral("III")]),
Numeral("II"), Numeral("II")))]),
ValList([ValInt(2)])),
# full array slice
("[I, II, III][I VSQVE III]",
Program([], [ExpressionStatement(ArraySlice(
DataArray([Numeral("I"), Numeral("II"), Numeral("III")]),
Numeral("I"), Numeral("III")))]),
ValList([ValInt(1), ValInt(2), ValInt(3)])),
# slice on variable
("DESIGNA a VT [I, II, III, IV, V]\na[II VSQVE IV]",
Program([], [
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V")])),
ExpressionStatement(ArraySlice(ID("a"), Numeral("II"), Numeral("IV"))),
]),
ValList([ValInt(2), ValInt(3), ValInt(4)])),
# slice then index (chained)
("[I, II, III, IV][I VSQVE III][II]",
Program([], [ExpressionStatement(ArrayIndex(
ArraySlice(
DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]),
Numeral("I"), Numeral("III")),
Numeral("II")))]),
ValInt(2)),
# slice on range array
("[I VSQVE X][III VSQVE VII]",
Program([], [ExpressionStatement(ArraySlice(
DataRangeArray(Numeral("I"), Numeral("X")),
Numeral("III"), Numeral("VII")))]),
ValList([ValInt(3), ValInt(4), ValInt(5), ValInt(6), ValInt(7)])),
# expression as slice bounds
("[I, II, III, IV, V][I + I VSQVE II + II]",
Program([], [ExpressionStatement(ArraySlice(
DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V")]),
BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"),
BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS")))]),
ValList([ValInt(2), ValInt(3), ValInt(4)])),
]
class TestArraySlice(unittest.TestCase):
@parameterized.expand(array_slice_tests)
def test_array_slice(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- String indexing ---
string_index_tests = [
# first character
('"SALVTE"[I]',
Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("I")))]),
ValStr("S")),
# last character
('"SALVTE"[VI]',
Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("VI")))]),
ValStr("E")),
# middle character
('"SALVTE"[III]',
Program([], [ExpressionStatement(ArrayIndex(String("SALVTE"), Numeral("III")))]),
ValStr("L")),
# string index via variable
('DESIGNA s VT "SALVTE"\ns[II]',
Program([], [
Designa(ID("s"), String("SALVTE")),
ExpressionStatement(ArrayIndex(ID("s"), Numeral("II"))),
]),
ValStr("A")),
# expression as index
('"SALVTE"[I + II]',
Program([], [ExpressionStatement(ArrayIndex(
String("SALVTE"),
BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS")))]),
ValStr("L")),
]
class TestStringIndex(unittest.TestCase):
@parameterized.expand(string_index_tests)
def test_string_index(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- String slicing ---
string_slice_tests = [
# substring from middle
('"SALVTE"[II VSQVE IV]',
Program([], [ExpressionStatement(ArraySlice(
String("SALVTE"), Numeral("II"), Numeral("IV")))]),
ValStr("ALV")),
# full string slice
('"SALVTE"[I VSQVE VI]',
Program([], [ExpressionStatement(ArraySlice(
String("SALVTE"), Numeral("I"), Numeral("VI")))]),
ValStr("SALVTE")),
# single-char slice
('"SALVTE"[III VSQVE III]',
Program([], [ExpressionStatement(ArraySlice(
String("SALVTE"), Numeral("III"), Numeral("III")))]),
ValStr("L")),
# slice on variable
('DESIGNA s VT "SALVTE"\ns[II VSQVE IV]',
Program([], [
Designa(ID("s"), String("SALVTE")),
ExpressionStatement(ArraySlice(ID("s"), Numeral("II"), Numeral("IV"))),
]),
ValStr("ALV")),
# chaining: slice then index
('"SALVTE"[I VSQVE III][II]',
Program([], [ExpressionStatement(ArrayIndex(
ArraySlice(String("SALVTE"), Numeral("I"), Numeral("III")),
Numeral("II")))]),
ValStr("A")),
# expression as slice bounds
('"SALVTE"[I + I VSQVE II + II]',
Program([], [ExpressionStatement(ArraySlice(
String("SALVTE"),
BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"),
BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS")))]),
ValStr("ALV")),
]
class TestStringSlice(unittest.TestCase):
@parameterized.expand(string_slice_tests)
def test_string_slice(self, source, nodes, value):
run_test(self, source, nodes, value)
# --- Comments --- # --- Comments ---
comment_tests = [ comment_tests = [
@@ -1551,15 +1760,15 @@ scope_tests = [
]), ]),
ValInt(100)), ValInt(100)),
# DONICVM: counter holds last range value after loop ends # DONICVM: counter holds last range value after loop ends
# [I VSQVE IV] = [1,2,3]; last value assigned by loop is III=3 # [I VSQVE IV] = [1,2,3,4]; last value assigned by loop is IV=4
("DONICVM i VT I VSQVE IV FACE { DESIGNA nop VT I }\ni", ("DONICVM i VT I VSQVE IV FACE { DESIGNA nop VT I }\ni",
Program([], [ Program([], [
PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [Designa(ID("nop"), Numeral("I"))]), PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [Designa(ID("nop"), Numeral("I"))]),
ExpressionStatement(ID("i")), ExpressionStatement(ID("i")),
]), ]),
ValInt(3)), ValInt(4)),
# DONICVM: reassigning counter inside body doesn't reduce the number of iterations # DONICVM: reassigning counter inside body doesn't reduce the number of iterations
# range [I VSQVE IV] evaluated once; i reset each time; cnt still increments 3 times → 4 # range [I VSQVE IV] evaluated once; i reset each time; cnt still increments 4 times → 5
("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FACE { DESIGNA cnt VT cnt + I\nDESIGNA i VT C }\ncnt", ("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FACE { DESIGNA cnt VT cnt + I\nDESIGNA i VT C }\ncnt",
Program([], [ Program([], [
Designa(ID("cnt"), Numeral("I")), Designa(ID("cnt"), Numeral("I")),
@@ -1569,7 +1778,7 @@ scope_tests = [
]), ]),
ExpressionStatement(ID("cnt")), ExpressionStatement(ID("cnt")),
]), ]),
ValInt(4)), ValInt(5)),
# DONICVM: ERVMPE exits loop early; counter persists at break value # DONICVM: ERVMPE exits loop early; counter persists at break value
("DONICVM i VT I VSQVE X FACE {\nSI i EST III TVNC { ERVMPE }\n}\ni", ("DONICVM i VT I VSQVE X FACE {\nSI i EST III TVNC { ERVMPE }\n}\ni",
Program([], [ Program([], [
@@ -2199,5 +2408,357 @@ class TestFvnctio(unittest.TestCase):
run_test(self, source, nodes, value, output) run_test(self, source, nodes, value, output)
# --- DORMI ---
dormi_tests = [
("DORMI(NVLLVS)",
Program([], [ExpressionStatement(BuiltIn("DORMI", [Nullus()]))]),
ValNul()),
]
class TestDormi(unittest.TestCase):
@parameterized.expand(dormi_tests)
def test_dormi(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
def test_dormi_timing_int(self):
source = "DORMI(I)\n"
lexer = Lexer().get_lexer()
tokens = lexer.lex(source)
program = Parser().parse(tokens)
start = time.time()
program.eval()
elapsed = time.time() - start
self.assertAlmostEqual(elapsed, 1.0, delta=0.5)
def test_dormi_timing_int_compiled(self):
source = "DORMI(I)\n"
lexer = Lexer().get_lexer()
tokens = lexer.lex(source)
program = Parser().parse(tokens)
c_source = compile_program(program)
with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c:
tmp_c.write(c_source)
tmp_c_path = tmp_c.name
with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin:
tmp_bin_path = tmp_bin.name
try:
subprocess.run(
["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path],
check=True, capture_output=True,
)
start = time.time()
proc = subprocess.run([tmp_bin_path], capture_output=True, text=True)
elapsed = time.time() - start
self.assertEqual(proc.returncode, 0)
self.assertAlmostEqual(elapsed, 1.0, delta=0.5)
finally:
os.unlink(tmp_c_path)
os.unlink(tmp_bin_path)
def test_dormi_timing_frac(self):
source = "CVM FRACTIO\nDORMI(S)\n"
lexer = Lexer().get_lexer()
tokens = lexer.lex(source)
program = Parser().parse(tokens)
start = time.time()
program.eval()
elapsed = time.time() - start
self.assertAlmostEqual(elapsed, 0.5, delta=0.5)
def test_dormi_timing_frac_compiled(self):
source = "CVM FRACTIO\nDORMI(S)\n"
lexer = Lexer().get_lexer()
tokens = lexer.lex(source)
program = Parser().parse(tokens)
c_source = compile_program(program)
with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c:
tmp_c.write(c_source)
tmp_c_path = tmp_c.name
with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin:
tmp_bin_path = tmp_bin.name
try:
subprocess.run(
["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path],
check=True, capture_output=True,
)
start = time.time()
proc = subprocess.run([tmp_bin_path], capture_output=True, text=True)
elapsed = time.time() - start
self.assertEqual(proc.returncode, 0)
self.assertAlmostEqual(elapsed, 0.5, delta=0.5)
finally:
os.unlink(tmp_c_path)
os.unlink(tmp_bin_path)
class TestScripta(unittest.TestCase):
def _run_scripta(self, source):
lexer = Lexer().get_lexer()
tokens = lexer.lex(source + "\n")
program = Parser().parse(tokens)
# printer round-trip
new_text = program.print()
new_tokens = Lexer().get_lexer().lex(new_text + "\n")
new_nodes = Parser().parse(new_tokens)
self.assertEqual(program, new_nodes, f"Printer test\n{source}\n{new_text}")
return program
def _compile_and_run(self, program):
c_source = compile_program(program)
with tempfile.NamedTemporaryFile(suffix=".c", delete=False, mode="w") as tmp_c:
tmp_c.write(c_source)
tmp_c_path = tmp_c.name
with tempfile.NamedTemporaryFile(suffix="", delete=False) as tmp_bin:
tmp_bin_path = tmp_bin.name
try:
subprocess.run(
["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path],
check=True, capture_output=True,
)
proc = subprocess.run([tmp_bin_path], capture_output=True, text=True)
self.assertEqual(proc.returncode, 0, f"Compiler binary exited non-zero:\n{proc.stderr}")
return proc.stdout
finally:
os.unlink(tmp_c_path)
os.unlink(tmp_bin_path)
def test_scribe_and_lege(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
path = f.name
try:
source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE MVNDE")\nDICE(LEGE("{path}"))'
program = self._run_scripta(source)
captured = StringIO()
with patch("sys.stdout", captured):
program.eval()
self.assertEqual(captured.getvalue(), "SALVE MVNDE\n")
with open(path) as f:
self.assertEqual(f.read(), "SALVE MVNDE")
finally:
if os.path.exists(path):
os.unlink(path)
def test_scribe_and_lege_compiled(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
path = f.name
try:
source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE MVNDE")\nDICE(LEGE("{path}"))'
program = self._run_scripta(source)
output = self._compile_and_run(program)
self.assertEqual(output, "SALVE MVNDE\n")
with open(path) as f:
self.assertEqual(f.read(), "SALVE MVNDE")
finally:
if os.path.exists(path):
os.unlink(path)
def test_adivnge(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
path = f.name
try:
source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE")\nADIVNGE("{path}", " MVNDE")\nDICE(LEGE("{path}"))'
program = self._run_scripta(source)
captured = StringIO()
with patch("sys.stdout", captured):
program.eval()
self.assertEqual(captured.getvalue(), "SALVE MVNDE\n")
finally:
if os.path.exists(path):
os.unlink(path)
def test_adivnge_compiled(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
path = f.name
try:
source = f'CVM SCRIPTA\nSCRIBE("{path}", "SALVE")\nADIVNGE("{path}", " MVNDE")\nDICE(LEGE("{path}"))'
program = self._run_scripta(source)
output = self._compile_and_run(program)
self.assertEqual(output, "SALVE MVNDE\n")
finally:
if os.path.exists(path):
os.unlink(path)
def test_scribe_overwrites(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
path = f.name
try:
source = f'CVM SCRIPTA\nSCRIBE("{path}", "first")\nSCRIBE("{path}", "second")\nLEGE("{path}")'
program = self._run_scripta(source)
result = program.eval()
self.assertEqual(result, ValStr("second"))
finally:
if os.path.exists(path):
os.unlink(path)
def test_lege_empty_file(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False, mode="w") as f:
path = f.name
try:
source = f'CVM SCRIPTA\nLEGE("{path}")'
program = self._run_scripta(source)
result = program.eval()
self.assertEqual(result, ValStr(""))
finally:
os.unlink(path)
def test_lege_preexisting_content(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False, mode="w") as f:
f.write("hello from python")
path = f.name
try:
source = f'CVM SCRIPTA\nLEGE("{path}")'
program = self._run_scripta(source)
result = program.eval()
self.assertEqual(result, ValStr("hello from python"))
finally:
os.unlink(path)
def test_scribe_returns_nullus(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
path = f.name
try:
source = f'CVM SCRIPTA\nSCRIBE("{path}", "x")'
program = self._run_scripta(source)
result = program.eval()
self.assertEqual(result, ValNul())
finally:
if os.path.exists(path):
os.unlink(path)
def test_adivnge_returns_nullus(self):
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
path = f.name
try:
source = f'CVM SCRIPTA\nADIVNGE("{path}", "x")'
program = self._run_scripta(source)
result = program.eval()
self.assertEqual(result, ValNul())
finally:
if os.path.exists(path):
os.unlink(path)
# --- Tempta/Cape (try/catch) ---
tempta_tests = [
# Try block succeeds — catch not entered
(
"TEMPTA {\nDESIGNA r VT I\n} CAPE e {\nDESIGNA r VT II\n}\nr",
Program([], [
TemptaStatement(
[Designa(ID("r"), Numeral("I"))],
ID("e"),
[Designa(ID("r"), Numeral("II"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(1),
),
# Try block errors — caught by catch
(
"TEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT II\n}\nr",
Program([], [
TemptaStatement(
[Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[Designa(ID("r"), Numeral("II"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(2),
),
# Error variable contains the error message
(
'DESIGNA e VT NVLLVS\nTEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nNVLLVS\n}\ne',
Program([], [
Designa(ID("e"), Nullus()),
TemptaStatement(
[Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[ExpressionStatement(Nullus())],
),
ExpressionStatement(ID("e")),
]),
ValStr("Division by zero"),
),
# Nested tempta — inner catches, outer unaffected
(
"DESIGNA r VT NVLLVS\nTEMPTA {\nTEMPTA {\nDESIGNA r VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT I\n}\n} CAPE e {\nDESIGNA r VT II\n}\nr",
Program([], [
Designa(ID("r"), Nullus()),
TemptaStatement(
[TemptaStatement(
[Designa(ID("r"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[Designa(ID("r"), Numeral("I"))],
)],
ID("e"),
[Designa(ID("r"), Numeral("II"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(1),
),
# REDI inside catch block
(
"DEFINI f () VT {\nTEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nREDI (III)\n}\nREDI (IV)\n}\nINVOCA f ()",
Program([], [
Defini(ID("f"), [], [
TemptaStatement(
[Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[Redi([Numeral("III")])],
),
Redi([Numeral("IV")]),
]),
ExpressionStatement(Invoca(ID("f"), [])),
]),
ValInt(3),
),
# ERVMPE inside catch block (inside a loop)
(
"DESIGNA r VT NVLLVS\nDVM r EST I FACE {\nTEMPTA {\nDESIGNA x VT I / NVLLVS\n} CAPE e {\nDESIGNA r VT I\nERVMPE\n}\n}\nr",
Program([], [
Designa(ID("r"), Nullus()),
DumStatement(
BinOp(ID("r"), Numeral("I"), "KEYWORD_EST"),
[TemptaStatement(
[Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE"))],
ID("e"),
[Designa(ID("r"), Numeral("I")), Erumpe()],
)],
),
ExpressionStatement(ID("r")),
]),
ValInt(1),
),
# Statement after error in try block is not executed
(
"DESIGNA r VT NVLLVS\nTEMPTA {\nDESIGNA x VT I / NVLLVS\nDESIGNA r VT III\n} CAPE e {\nDESIGNA r VT II\n}\nr",
Program([], [
Designa(ID("r"), Nullus()),
TemptaStatement(
[Designa(ID("x"), BinOp(Numeral("I"), Nullus(), "SYMBOL_DIVIDE")),
Designa(ID("r"), Numeral("III"))],
ID("e"),
[Designa(ID("r"), Numeral("II"))],
),
ExpressionStatement(ID("r")),
]),
ValInt(2),
),
]
class TestTempta(unittest.TestCase):
@parameterized.expand(tempta_tests)
def test_tempta(self, source, nodes, value, output=""):
run_test(self, source, nodes, value, output)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()