Compare commits
9 Commits
693054491f
...
afb1622b3a
| Author | SHA1 | Date | |
|---|---|---|---|
| afb1622b3a | |||
| 9003d49b20 | |||
| 378c28102c | |||
| 559b1b100e | |||
| 5961884219 | |||
| 80d430970a | |||
| e61009b6ef | |||
| 108e69291d | |||
| 78b1dd7667 |
106
README.md
@@ -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:
|
||||
|
||||
```
|
||||
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:
|
||||
|
||||
```
|
||||
DESIGNA a, b, c VT [I, II, III]
|
||||
```
|
||||

|
||||
|
||||
## Data types
|
||||
### NVLLVS
|
||||
@@ -69,17 +64,27 @@ Strings are concatenated with `&`:
|
||||
|
||||
Double-quoted strings support interpolation with `{expression}`:
|
||||
|
||||
```
|
||||
DESIGNA nomen VT "Marcus"
|
||||
DICE("Salve, {nomen}!") // → Salve, Marcus!
|
||||
DICE("Sum: {III + IV}") // → Sum: VII
|
||||
DICE("{nomen} has {V} cats") // → Marcus has V cats
|
||||
```
|
||||

|
||||
|
||||
Any expression can appear inside `{}`. Values are coerced to strings the same way as with `&` (integers become Roman numerals, booleans become `VERITAS`/`FALSITAS`, etc.).
|
||||
|
||||
Single-quoted strings do **not** interpolate — `'{nomen}'` is the literal text `{nomen}`. Use `{{` and `}}` for literal braces in double-quoted strings: `"use {{braces}}"` → `use {braces}`.
|
||||
|
||||
#### 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).
|
||||
|
||||
### Integers
|
||||
@@ -115,10 +120,14 @@ Arrays are defined using square brackets (`[]`) and commas (`,`):
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
```
|
||||
> [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:
|
||||
|
||||

|
||||
@@ -127,29 +136,27 @@ Individual elements can be accessed by index using square brackets. Indexing is
|
||||
> I
|
||||
```
|
||||
|
||||
A sub-array can be extracted with `VSQVE` inside the index brackets. Both bounds are inclusive and 1-based:
|
||||
|
||||

|
||||
|
||||
```
|
||||
> [XX, XXX, XL]
|
||||
```
|
||||
|
||||
### Dicts (TABVLA)
|
||||
|
||||
Dicts are key-value maps created with the `TABVLA` keyword and curly braces:
|
||||
|
||||
```
|
||||
DESIGNA d VT TABVLA {"nomen" VT "Marcus", "aetas" VT XXV}
|
||||
```
|
||||

|
||||
|
||||
Keys must be strings or integers. Values are accessed and assigned with square brackets:
|
||||
|
||||
```
|
||||
DICE(d["nomen"]) // → Marcus
|
||||
DESIGNA d["aetas"] VT XXVI // update existing key
|
||||
DESIGNA d["novus"] VT I // insert new key
|
||||
```
|
||||

|
||||
|
||||
Iterating over a dict with `PER` loops over its keys:
|
||||
|
||||
```
|
||||
PER k IN d FACE {
|
||||
DICE(k)
|
||||
}
|
||||
```
|
||||

|
||||
|
||||
`LONGITVDO(dict)` returns the number of entries. `CLAVES(dict)` returns the keys as an array.
|
||||
|
||||
@@ -198,7 +205,7 @@ The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used a
|
||||

|
||||
|
||||
```
|
||||
> XLV
|
||||
> LV
|
||||
```
|
||||
|
||||
### DVM loops
|
||||
@@ -233,6 +240,24 @@ condition. Exit the loop with `ERVMPE` (or `REDI` from inside a function).
|
||||
> 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 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.
|
||||
|
||||
### 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(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.
|
||||
|
||||
### 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 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 `_`.
|
||||
|
||||
### 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
|
||||

|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import re
|
||||
import random
|
||||
import time
|
||||
from fractions import Fraction
|
||||
|
||||
from rply.token import BaseBox
|
||||
@@ -279,7 +280,7 @@ class DataRangeArray(Node):
|
||||
raise CentvrionError("Range bounds must be numbers")
|
||||
from_int = from_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):
|
||||
@@ -832,6 +833,17 @@ class ArrayIndex(Node):
|
||||
if k not in d:
|
||||
raise CentvrionError(f"Key not found in dict")
|
||||
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):
|
||||
raise CentvrionError("Cannot index a non-array value")
|
||||
if isinstance(index, ValInt):
|
||||
@@ -846,6 +858,59 @@ class ArrayIndex(Node):
|
||||
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):
|
||||
def __init__(self, test, statements, else_part) -> None:
|
||||
self.test = test
|
||||
@@ -980,6 +1045,45 @@ class PerStatement(Node):
|
||||
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):
|
||||
def __init__(self, callee, parameters) -> None:
|
||||
self.callee = callee
|
||||
@@ -1121,6 +1225,58 @@ class BuiltIn(Node):
|
||||
case "EVERRO":
|
||||
print("\033[2J\033[H", end="", flush=True)
|
||||
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 _:
|
||||
raise NotImplementedError(self.builtin)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from centvrion.errors import CentvrionError
|
||||
from centvrion.ast_nodes import (
|
||||
String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID,
|
||||
BinOp, UnaryMinus, UnaryNot,
|
||||
ArrayIndex, DataArray, DataRangeArray, DataDict,
|
||||
ArrayIndex, ArraySlice, DataArray, DataRangeArray, DataDict,
|
||||
BuiltIn, Invoca, Fvnctio,
|
||||
num_to_int, frac_to_fraction,
|
||||
)
|
||||
@@ -120,6 +120,15 @@ def emit_expr(node, ctx):
|
||||
tmp = ctx.fresh_tmp()
|
||||
return arr_lines + idx_lines + [f"CentValue {tmp} = cent_list_index({arr_var}, {idx_var});"], tmp
|
||||
|
||||
if isinstance(node, ArraySlice):
|
||||
arr_lines, arr_var = emit_expr(node.array, ctx)
|
||||
lo_lines, lo_var = emit_expr(node.from_index, ctx)
|
||||
hi_lines, hi_var = emit_expr(node.to_index, ctx)
|
||||
tmp = ctx.fresh_tmp()
|
||||
return arr_lines + lo_lines + hi_lines + [
|
||||
f"CentValue {tmp} = cent_list_slice({arr_var}, {lo_var}, {hi_var});"
|
||||
], tmp
|
||||
|
||||
if isinstance(node, DataArray):
|
||||
lines = []
|
||||
tmp = ctx.fresh_tmp()
|
||||
@@ -135,10 +144,10 @@ def emit_expr(node, ctx):
|
||||
hi_lines, hi_var = emit_expr(node.to_value, ctx)
|
||||
tmp = ctx.fresh_tmp()
|
||||
i_var = ctx.fresh_tmp()
|
||||
cap = f"({hi_var}.ival > {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival) : 0)"
|
||||
cap = f"({hi_var}.ival >= {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival + 1) : 0)"
|
||||
lines = lo_lines + hi_lines + [
|
||||
f"CentValue {tmp} = cent_list_new({cap});",
|
||||
f"for (long {i_var} = {lo_var}.ival; {i_var} < {hi_var}.ival; {i_var}++) {{",
|
||||
f"for (long {i_var} = {lo_var}.ival; {i_var} <= {hi_var}.ival; {i_var}++) {{",
|
||||
f" cent_list_push(&{tmp}, cent_int({i_var}));",
|
||||
"}",
|
||||
]
|
||||
@@ -251,10 +260,43 @@ def _emit_builtin(node, ctx):
|
||||
case "CLAVES":
|
||||
lines.append(f"CentValue {tmp} = cent_dict_keys({param_vars[0]});")
|
||||
|
||||
case "ORDINA":
|
||||
lines.append(f"CentValue {tmp} = cent_ordina({param_vars[0]});")
|
||||
|
||||
case "EVERRO":
|
||||
lines.append("cent_everro();")
|
||||
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 _:
|
||||
raise NotImplementedError(node.builtin)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from centvrion.ast_nodes import (
|
||||
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
|
||||
|
||||
@@ -125,6 +126,24 @@ def emit_stmt(node, ctx):
|
||||
if isinstance(node, Continva):
|
||||
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):
|
||||
lines, _ = emit_expr(node.expression, ctx)
|
||||
return lines
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
CentArena *cent_arena;
|
||||
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 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -50,11 +54,19 @@ void *cent_arena_alloc(CentArena *a, size_t n) {
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
@@ -545,6 +557,71 @@ CentValue cent_longitudo(CentValue v) {
|
||||
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) {
|
||||
if (lo.type != CENT_INT || hi.type != CENT_INT)
|
||||
cent_type_error("'FORTIS_NVMERVS' requires two integers");
|
||||
@@ -602,6 +679,36 @@ void cent_semen(CentValue seed) {
|
||||
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 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -626,6 +733,22 @@ void cent_list_push(CentValue *lst, CentValue v) {
|
||||
CentValue cent_list_index(CentValue lst, CentValue idx) {
|
||||
if (lst.type == CENT_DICT)
|
||||
return cent_dict_get(lst, idx);
|
||||
if (lst.type == CENT_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)
|
||||
cent_type_error("index requires a list or dict");
|
||||
long i;
|
||||
@@ -640,6 +763,36 @@ CentValue cent_list_index(CentValue lst, CentValue idx) {
|
||||
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) {
|
||||
if (lst->type == CENT_DICT) {
|
||||
cent_dict_set(lst, idx, v);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Types */
|
||||
@@ -145,8 +146,13 @@ static inline CentValue cent_dict_val(CentValue *keys, CentValue *vals, int len,
|
||||
/* Error handling */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void cent_type_error(const char *msg); /* type mismatch → exit(1) */
|
||||
void cent_runtime_error(const char *msg); /* runtime fault → exit(1) */
|
||||
#define CENT_TRY_STACK_MAX 64
|
||||
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 */
|
||||
@@ -220,6 +226,12 @@ CentValue cent_decimatio(CentValue lst); /* DECIMATIO */
|
||||
void cent_semen(CentValue seed); /* SEMEN */
|
||||
void cent_everro(void); /* EVERRO */
|
||||
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 */
|
||||
@@ -228,6 +240,7 @@ CentValue cent_senatus(CentValue *args, int n); /* SENATVS */
|
||||
CentValue cent_list_new(int cap);
|
||||
void cent_list_push(CentValue *lst, CentValue v);
|
||||
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);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
@@ -6,6 +6,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
||||
"AETERNVM",
|
||||
"ALVID",
|
||||
"AVGE",
|
||||
"CAPE",
|
||||
"AVT",
|
||||
"DEFINI",
|
||||
"DESIGNA",
|
||||
@@ -32,6 +33,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
||||
"SI",
|
||||
"TVNC",
|
||||
"TABVLA",
|
||||
"TEMPTA",
|
||||
"VSQVE",
|
||||
"VT",
|
||||
"VERITAS",
|
||||
@@ -44,12 +46,18 @@ builtin_tokens = [("BUILTIN", i) for i in [
|
||||
"CLAVES",
|
||||
"DECIMATIO",
|
||||
"DICE",
|
||||
"DORMI",
|
||||
"EVERRO",
|
||||
"FORTIS_NVMERVS",
|
||||
"FORTIS_ELECTIONIS",
|
||||
"LONGITVDO",
|
||||
"ORDINA",
|
||||
"SEMEN",
|
||||
"SENATVS"
|
||||
"SENATVS",
|
||||
"TYPVS",
|
||||
"LEGE",
|
||||
"SCRIBE",
|
||||
"ADIVNGE"
|
||||
]]
|
||||
|
||||
data_tokens = [
|
||||
@@ -62,6 +70,7 @@ module_tokens = [("MODULE", i) for i in [
|
||||
"FORS",
|
||||
"FRACTIO",
|
||||
"MAGNVM",
|
||||
"SCRIPTA",
|
||||
"SVBNVLLA"
|
||||
]]
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ class Parser():
|
||||
@self.pg.production('statement : dum_statement')
|
||||
@self.pg.production('statement : donicum_statement')
|
||||
@self.pg.production('statement : si_statement')
|
||||
@self.pg.production('statement : tempta_statement')
|
||||
def nested_statements(tokens):
|
||||
return tokens[0]
|
||||
|
||||
@@ -203,6 +204,10 @@ class Parser():
|
||||
def per(tokens):
|
||||
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')
|
||||
def donicum(tokens):
|
||||
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
|
||||
@@ -326,6 +331,10 @@ class Parser():
|
||||
def array_index(tokens):
|
||||
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
|
||||
@self.pg.production('ids : SYMBOL_LPARENS id_list')
|
||||
def ids(tokens):
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
DESIGNA arr VT [V, III, VIII, I, IX, II, VII, IV, VI, X]
|
||||
DESIGNA n VT LONGITVDO(arr)
|
||||
|
||||
DONICVM i VT I VSQVE n FACE {
|
||||
DONICVM k VT I VSQVE n - i + I FACE {
|
||||
DONICVM i VT I VSQVE n - I FACE {
|
||||
DONICVM k VT I VSQVE n - i FACE {
|
||||
SI arr[k] PLVS arr[k + I] TVNC {
|
||||
DESIGNA temp VT arr[k]
|
||||
DESIGNA arr[k] VT arr[k + I]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// Returns the bottommost empty row in col, or NVLLVS if full
|
||||
DEFINI find_slot(b, col) VT {
|
||||
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 {
|
||||
DESIGNA ans VT r
|
||||
}
|
||||
@@ -16,32 +16,32 @@ DEFINI find_slot(b, col) VT {
|
||||
|
||||
// Returns VERITAS if player has four in a row
|
||||
DEFINI est_victor(b, player) VT {
|
||||
DONICVM r VT I VSQVE VII FACE {
|
||||
DONICVM c VT I VSQVE V FACE {
|
||||
DONICVM r VT I VSQVE VI FACE {
|
||||
DONICVM c VT I VSQVE IV FACE {
|
||||
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 {
|
||||
REDI(VERITAS)
|
||||
}
|
||||
}
|
||||
}
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT I VSQVE VIII FACE {
|
||||
DONICVM r VT I VSQVE III FACE {
|
||||
DONICVM c VT I VSQVE VII FACE {
|
||||
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 {
|
||||
REDI(VERITAS)
|
||||
}
|
||||
}
|
||||
}
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT I VSQVE V FACE {
|
||||
DONICVM r VT I VSQVE III FACE {
|
||||
DONICVM c VT I VSQVE IV FACE {
|
||||
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 {
|
||||
REDI(VERITAS)
|
||||
}
|
||||
}
|
||||
}
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT IV VSQVE VIII FACE {
|
||||
DONICVM r VT I VSQVE III FACE {
|
||||
DONICVM c VT IV VSQVE VII FACE {
|
||||
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 {
|
||||
REDI(VERITAS)
|
||||
@@ -53,9 +53,9 @@ DEFINI est_victor(b, player) VT {
|
||||
|
||||
DEFINI print_board(b) VT {
|
||||
DICE("+---+---+---+---+---+---+---+")
|
||||
DONICVM r VT I VSQVE VII FACE {
|
||||
DONICVM r VT I VSQVE VI FACE {
|
||||
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]
|
||||
SI cell EST I TVNC {
|
||||
DESIGNA line VT line & "X | "
|
||||
@@ -101,35 +101,35 @@ DEFINI score_fenestram(a, b, c, d) VT {
|
||||
DEFINI aestima(b) VT {
|
||||
DESIGNA score VT NVLLVS
|
||||
// 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 {
|
||||
DESIGNA score VT score + I
|
||||
}
|
||||
}
|
||||
// Horizontal windows (6 rows x 4 starting columns = 24)
|
||||
DONICVM r VT I VSQVE VII FACE {
|
||||
DONICVM c VT I VSQVE V FACE {
|
||||
DONICVM r VT I VSQVE VI FACE {
|
||||
DONICVM c VT I VSQVE IV FACE {
|
||||
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])
|
||||
}
|
||||
}
|
||||
// Vertical windows (3 starting rows x 7 columns = 21)
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT I VSQVE VIII FACE {
|
||||
DONICVM r VT I VSQVE III FACE {
|
||||
DONICVM c VT I VSQVE VII FACE {
|
||||
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])
|
||||
}
|
||||
}
|
||||
// Diagonal up-right windows (3 starting rows x 4 starting columns = 12)
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT I VSQVE V FACE {
|
||||
DONICVM r VT I VSQVE III FACE {
|
||||
DONICVM c VT I VSQVE IV FACE {
|
||||
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])
|
||||
}
|
||||
}
|
||||
// Diagonal up-left windows (3 starting rows x 4 starting columns = 12)
|
||||
DONICVM r VT I VSQVE IV FACE {
|
||||
DONICVM c VT IV VSQVE VIII FACE {
|
||||
DONICVM r VT I VSQVE III FACE {
|
||||
DONICVM c VT IV VSQVE VII FACE {
|
||||
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])
|
||||
}
|
||||
@@ -219,8 +219,8 @@ DEFINI ai_move(b) VT {
|
||||
}
|
||||
|
||||
// --- Board setup ---
|
||||
DESIGNA board VT [I VSQVE XLIII]
|
||||
DONICVM i VT I VSQVE XLIII FACE {
|
||||
DESIGNA board VT [I VSQVE XLII]
|
||||
DONICVM i VT I VSQVE XLII FACE {
|
||||
DESIGNA board[i] VT NVLLVS
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Prints an X×X multiplication table
|
||||
DESIGNA n VT X
|
||||
|
||||
DONICVM i VT I VSQVE n + I FACE {
|
||||
DONICVM i VT I VSQVE n FACE {
|
||||
DESIGNA line VT ""
|
||||
DONICVM k VT I VSQVE n + I FACE {
|
||||
DONICVM k VT I VSQVE n FACE {
|
||||
DESIGNA line VT line & i * k & " "
|
||||
}
|
||||
DICE(line)
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
DESIGNA n VT L
|
||||
|
||||
DONICVM i VT II VSQVE n + I FACE {
|
||||
DONICVM i VT II VSQVE n FACE {
|
||||
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 {
|
||||
DESIGNA is_prime VT FALSITAS
|
||||
}
|
||||
|
||||
2
language/main.aux
Normal file
@@ -0,0 +1,2 @@
|
||||
\relax
|
||||
\gdef \@abspage@last{2}
|
||||
269
language/main.log
Normal 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).
|
||||
@@ -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{REDI(} \textit{optional-expressions} \texttt{)}} \\
|
||||
\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} \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}{\textit{literal}} \\
|
||||
\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}{\textbf{unop} \textit{expression}} \\ \hline
|
||||
\languageline{literal}{\textbf{string}} \\
|
||||
@@ -66,7 +70,7 @@
|
||||
\languageline{literal}{\textbf{numeral}} \\
|
||||
\languageline{literal}{\textbf{bool}} \\
|
||||
\languageline{literal}{\texttt{[} \textit{optional-expressions} \texttt{]}} \\
|
||||
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{]}} \\
|
||||
\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{optional-dict-items}{\textit{dict-items}} \\
|
||||
@@ -94,10 +98,10 @@
|
||||
\newpage
|
||||
\begin{itemize}
|
||||
\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{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{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.
|
||||
|
||||
BIN
snippets/aeternvm.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
2
snippets/array_slice.cent
Normal 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
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.1 KiB |
1
snippets/destructure_array.cent
Normal file
@@ -0,0 +1 @@
|
||||
DESIGNA a, b, c VT [I, II, III]
|
||||
BIN
snippets/destructure_array.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
2
snippets/destructure_fn.cent
Normal 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
|
After Width: | Height: | Size: 20 KiB |
3
snippets/dict_access.cent
Normal 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
|
After Width: | Height: | Size: 20 KiB |
1
snippets/dict_create.cent
Normal file
@@ -0,0 +1 @@
|
||||
DESIGNA d VT TABVLA {"nomen" VT "Marcus", "aetas" VT XXV}
|
||||
BIN
snippets/dict_create.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
3
snippets/dict_per.cent
Normal file
@@ -0,0 +1,3 @@
|
||||
PER k IN d FACE {
|
||||
DICE(k)
|
||||
}
|
||||
BIN
snippets/dict_per.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
4
snippets/string_interp.cent
Normal 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
|
After Width: | Height: | Size: 30 KiB |
@@ -9,8 +9,8 @@ contexts:
|
||||
main:
|
||||
- include: comments
|
||||
- include: strings
|
||||
- include: fractions
|
||||
- include: keywords
|
||||
- include: fractions
|
||||
- include: numerals
|
||||
- include: constants
|
||||
- include: builtins
|
||||
@@ -70,15 +70,15 @@ contexts:
|
||||
scope: constant.language.centvrion
|
||||
|
||||
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
|
||||
|
||||
modules:
|
||||
- match: '\b(FORS|FRACTIO|MAGNVM|SVBNVLLA)\b'
|
||||
- match: '\b(FORS|FRACTIO|MAGNVM|SCRIPTA|SVBNVLLA)\b'
|
||||
scope: support.class.module.centvrion
|
||||
|
||||
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
|
||||
|
||||
operators:
|
||||
|
||||
593
tests.py
@@ -2,6 +2,7 @@ import os
|
||||
import random
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from unittest.mock import patch
|
||||
@@ -10,12 +11,12 @@ from parameterized import parameterized
|
||||
from fractions import Fraction
|
||||
|
||||
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,
|
||||
Erumpe, ExpressionStatement, Fvnctio, ID, InterpolatedString, Invoca,
|
||||
ModuleCall, Nullus, Numeral, PerStatement, Program, Redi, SiStatement,
|
||||
String, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, fraction_to_frac,
|
||||
num_to_int, int_to_num, make_string,
|
||||
String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||
)
|
||||
from centvrion.compiler.emitter import compile_program
|
||||
from centvrion.errors import CentvrionError
|
||||
@@ -283,13 +284,13 @@ assignment_tests = [
|
||||
Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), "SYMBOL_PLUS")),
|
||||
ExpressionStatement(ID("x"))]),
|
||||
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",
|
||||
Program([], [Designa(ID("s"), Nullus()),
|
||||
PerStatement(DataRangeArray(Numeral("I"), Numeral("III")), ID("i"),
|
||||
[Designa(ID("s"), BinOp(ID("s"), ID("i"), "SYMBOL_PLUS"))]),
|
||||
ExpressionStatement(ID("s"))]),
|
||||
ValInt(3)),
|
||||
ValInt(6)),
|
||||
]
|
||||
|
||||
class TestAssignment(unittest.TestCase):
|
||||
@@ -469,7 +470,7 @@ control_tests = [
|
||||
# DONICVM range loop
|
||||
("DONICVM i VT I VSQVE V FACE { DICE(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):
|
||||
@@ -567,6 +568,44 @@ builtin_tests = [
|
||||
("SENATVS([FALSITAS, FALSITAS, VERITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(False), Bool(False), Bool(True)])]))]), ValBool(False)),
|
||||
# SENATVS: array input, empty → FALSITAS
|
||||
("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):
|
||||
@@ -600,6 +639,10 @@ error_tests = [
|
||||
("I * \"hello\"", CentvrionError), # multiplication with string
|
||||
("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings
|
||||
("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
|
||||
("SEMEN(I)", CentvrionError), # requires FORS module
|
||||
('CVM FORS\nSEMEN("abc")', CentvrionError), # SEMEN requires integer seed
|
||||
@@ -610,9 +653,15 @@ error_tests = [
|
||||
("DECIMATIO([I, II, III])", CentvrionError), # FORS required for DECIMATIO
|
||||
("CVM FORS\nDECIMATIO(I)", CentvrionError), # DECIMATIO requires an 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(VERITAS, I)", CentvrionError), # SENATVS mixed types
|
||||
("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
|
||||
("SI I TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: int
|
||||
("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 [I]", CentvrionError), # destructure length mismatch: too many 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):
|
||||
@@ -1146,10 +1205,10 @@ class TestFunctionEdge(unittest.TestCase):
|
||||
# --- Loop edge cases ---
|
||||
|
||||
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) }",
|
||||
Program([], [PerStatement(DataRangeArray(Numeral("III"), Numeral("III")), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
||||
ValNul(), ""),
|
||||
ValStr("III"), "III\n"),
|
||||
# empty array — body never runs
|
||||
("PER i IN [] FACE { DICE(i) }",
|
||||
Program([], [PerStatement(DataArray([]), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
||||
@@ -1233,7 +1292,7 @@ loop_edge_tests = [
|
||||
ExpressionStatement(ID("cnt")),
|
||||
]),
|
||||
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",
|
||||
Program([], [
|
||||
Designa(ID("cnt"), Numeral("I")),
|
||||
@@ -1245,7 +1304,7 @@ loop_edge_tests = [
|
||||
),
|
||||
ExpressionStatement(ID("cnt")),
|
||||
]),
|
||||
ValInt(3)),
|
||||
ValInt(4)),
|
||||
# DVM condition true from start — body never runs
|
||||
("DESIGNA x VT I\nDVM VERITAS FACE {\nDESIGNA x VT x + I\n}\nx",
|
||||
Program([], [
|
||||
@@ -1254,10 +1313,21 @@ loop_edge_tests = [
|
||||
ExpressionStatement(ID("x")),
|
||||
]),
|
||||
ValInt(1), ""),
|
||||
# single iteration: [I VSQVE II] = [1]
|
||||
# two iterations: [I VSQVE II] = [1, 2]
|
||||
("DONICVM i VT I VSQVE II FACE { DICE(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"),
|
||||
# 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):
|
||||
@@ -1382,7 +1452,7 @@ array_index_tests = [
|
||||
Program([], [Designa(ID("a"), DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])), ExpressionStatement(ArrayIndex(ID("a"), Numeral("II")))]),
|
||||
ValInt(20)), # second element
|
||||
# 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
|
||||
("[I, II, III][I + I]",
|
||||
Program([], [ExpressionStatement(ArrayIndex(
|
||||
@@ -1461,6 +1531,145 @@ class TestArrayIndexAssign(unittest.TestCase):
|
||||
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 ---
|
||||
|
||||
comment_tests = [
|
||||
@@ -1551,15 +1760,15 @@ scope_tests = [
|
||||
]),
|
||||
ValInt(100)),
|
||||
# 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",
|
||||
Program([], [
|
||||
PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [Designa(ID("nop"), Numeral("I"))]),
|
||||
ExpressionStatement(ID("i")),
|
||||
]),
|
||||
ValInt(3)),
|
||||
ValInt(4)),
|
||||
# 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",
|
||||
Program([], [
|
||||
Designa(ID("cnt"), Numeral("I")),
|
||||
@@ -1569,7 +1778,7 @@ scope_tests = [
|
||||
]),
|
||||
ExpressionStatement(ID("cnt")),
|
||||
]),
|
||||
ValInt(4)),
|
||||
ValInt(5)),
|
||||
# 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",
|
||||
Program([], [
|
||||
@@ -2199,5 +2408,357 @@ class TestFvnctio(unittest.TestCase):
|
||||
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__":
|
||||
unittest.main()
|
||||
|
||||