Compare commits
9 Commits
27c5f7bf56
...
37fdff2db5
| Author | SHA1 | Date | |
|---|---|---|---|
| 37fdff2db5 | |||
| f197c2c3d5 | |||
| dbaf01b6a3 | |||
| 37050e3e3b | |||
| 6d4a456010 | |||
| ea72c91870 | |||
| 76bf509d48 | |||
| bdf72b2bcc | |||
| 92301f3ff6 |
60
README.md
@@ -24,7 +24,7 @@ Variable can consist of lower-case letters, numbers, as well as `_`.
|
|||||||
|
|
||||||
### Compound assignment
|
### Compound assignment
|
||||||
|
|
||||||
`AVGE` (+=) and `MINVE` (-=) are shorthand for incrementing or decrementing a variable:
|
`AVGE` (+=), `MINVE` (-=), `MVLTIPLICA` (*=) and `DIVIDE` (/=) are shorthand for updating a variable with an arithmetic operation:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ Variable can consist of lower-case letters, numbers, as well as `_`.
|
|||||||
> VIII
|
> VIII
|
||||||
```
|
```
|
||||||
|
|
||||||
`x AVGE III` is equivalent to `DESIGNA x VT x + III`.
|
`x AVGE III` is equivalent to `DESIGNA x VT x + III`; `MINVE`, `MVLTIPLICA` and `DIVIDE` expand the same way with subtraction, multiplication and division.
|
||||||
|
|
||||||
### Destructuring
|
### Destructuring
|
||||||
|
|
||||||
@@ -74,15 +74,19 @@ Single-quoted strings do **not** interpolate — `'{nomen}'` is the literal text
|
|||||||
|
|
||||||
Strings support the same indexing and slicing syntax as arrays. Indexing is 1-based and returns a single-character string:
|
Strings support the same indexing and slicing syntax as arrays. Indexing is 1-based and returns a single-character string:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
```
|
```
|
||||||
"SALVTE"[I] @> "S"
|
> S
|
||||||
"SALVTE"[III] @> "L"
|
> L
|
||||||
```
|
```
|
||||||
|
|
||||||
Slicing uses `VSQVE` with inclusive bounds, returning a substring:
|
Slicing uses `VSQVE` with inclusive bounds, returning a substring:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
```
|
```
|
||||||
"SALVTE"[II VSQVE IV] @> "ALV"
|
> 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).
|
||||||
@@ -138,10 +142,7 @@ Individual elements can be accessed by index using square brackets. Indexing is
|
|||||||
|
|
||||||
Arrays are concatenated with `@`:
|
Arrays are concatenated with `@`:
|
||||||
|
|
||||||
```
|

|
||||||
DESIGNA x VT [I, II, III] @ [IV, V]
|
|
||||||
DIC x
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
```
|
||||||
> [I, II, III, IV, V]
|
> [I, II, III, IV, V]
|
||||||
@@ -182,7 +183,7 @@ If-then statements are denoted with the keywords `SI` (if) and `TVNC` (then). Th
|
|||||||
Will return `I` (1), as the conditional evaluates `x` to be true.
|
Will return `I` (1), as the conditional evaluates `x` to be true.
|
||||||
|
|
||||||
### Boolean expressions
|
### Boolean expressions
|
||||||
In conditionals, `EST` functions as an equality evaluation, `DISPAR` as not-equal, and `MINVS` (<) and `PLVS` (>) function as inequality evaluation.
|
In conditionals, `EST` functions as an equality evaluation, `DISPAR` as not-equal, and `MINVS` (<), `PLVS` (>), `HAVD_PLVS` (≤), and `HAVD_MINVS` (≥) function as inequality evaluation.
|
||||||
|
|
||||||
### ALIVD
|
### ALIVD
|
||||||
|
|
||||||
@@ -221,6 +222,16 @@ The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used a
|
|||||||
> LV
|
> LV
|
||||||
```
|
```
|
||||||
|
|
||||||
|
An optional `GRADV` clause sets the stride. The step must be a nonzero
|
||||||
|
integer expression; positive values ascend, negative values descend, and
|
||||||
|
the endpoint is included only when the stride lands on it exactly.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> XXV
|
||||||
|
```
|
||||||
|
|
||||||
### DVM loops
|
### DVM loops
|
||||||
|
|
||||||

|

|
||||||
@@ -255,11 +266,7 @@ condition. Exit the loop with `ERVMPE` (or `REDI` from inside a function).
|
|||||||
|
|
||||||
Variables can be unpacked in `PER` loops, similar to `DESIGNA` destructuring:
|
Variables can be unpacked in `PER` loops, similar to `DESIGNA` destructuring:
|
||||||
|
|
||||||
```
|

|
||||||
PER a, b IN [[I, II], [III, IV]] FAC {
|
|
||||||
DIC(a + b)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
```
|
||||||
> III
|
> III
|
||||||
@@ -270,13 +277,7 @@ PER a, b IN [[I, II], [III, IV]] FAC {
|
|||||||
|
|
||||||
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.
|
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 {
|
|
||||||
DIC(error)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
```
|
||||||
> Division by zero
|
> Division by zero
|
||||||
@@ -375,6 +376,11 @@ Parses a Roman numeral string and returns its integer value. The argument must b
|
|||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
|
### LITTERA
|
||||||
|
`LITTERA(value)`
|
||||||
|
|
||||||
|
Returns `value` formatted as the same display string `DIC` would print. Integers become Roman numerals (zero becomes `NVLLVS`), fractions use the `S`/`:`/`.`/`|` notation, booleans become `VERITAS`/`FALSITAS`, arrays are space-separated in brackets, and dicts use the `{ key VT value, ... }` form. Strings pass through unchanged. Respects `MAGNVM` and `SVBNVLLA` for large and negative numbers. Inverse of `NVMERVS` for integers: `NVMERVS(LITTERA(n)) == n`.
|
||||||
|
|
||||||
### DORMI
|
### DORMI
|
||||||
`DORMI(n)`
|
`DORMI(n)`
|
||||||
|
|
||||||
@@ -395,6 +401,16 @@ Replaces all non-overlapping matches of the regex `pattern` in `string` with `re
|
|||||||
|
|
||||||
Splits `string` by `delimiter` and returns an array of substrings. Both arguments must be strings. If the delimiter is not found, returns a single-element array containing the original string. If the delimiter is an empty string, splits into individual characters.
|
Splits `string` by `delimiter` and returns an array of substrings. Both arguments must be strings. If the delimiter is not found, returns a single-element array containing the original string. If the delimiter is an empty string, splits into individual characters.
|
||||||
|
|
||||||
|
### MAIVSCVLA
|
||||||
|
`MAIVSCVLA(string)`
|
||||||
|
|
||||||
|
Returns a new string with every ASCII letter `a`–`z` replaced by its uppercase counterpart `A`–`Z`. All other bytes (digits, punctuation, non-ASCII) pass through unchanged.
|
||||||
|
|
||||||
|
### MINVSCVLA
|
||||||
|
`MINVSCVLA(string)`
|
||||||
|
|
||||||
|
Returns a new string with every ASCII letter `A`–`Z` replaced by its lowercase counterpart `a`–`z`. All other bytes (digits, punctuation, non-ASCII) pass through unchanged.
|
||||||
|
|
||||||
## 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
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ OP_STR = {
|
|||||||
"KEYWORD_EST": "EST", "KEYWORD_DISPAR": "DISPAR",
|
"KEYWORD_EST": "EST", "KEYWORD_DISPAR": "DISPAR",
|
||||||
"KEYWORD_MINVS": "MINVS",
|
"KEYWORD_MINVS": "MINVS",
|
||||||
"KEYWORD_PLVS": "PLVS", "KEYWORD_ET": "ET", "KEYWORD_AVT": "AVT",
|
"KEYWORD_PLVS": "PLVS", "KEYWORD_ET": "ET", "KEYWORD_AVT": "AVT",
|
||||||
|
"KEYWORD_HAVD_PLVS": "HAVD_PLVS",
|
||||||
|
"KEYWORD_HAVD_MINVS": "HAVD_MINVS",
|
||||||
}
|
}
|
||||||
|
|
||||||
def single_num_to_int(i, m):
|
def single_num_to_int(i, m):
|
||||||
@@ -367,19 +369,28 @@ class DataArray(Node):
|
|||||||
|
|
||||||
|
|
||||||
class DataRangeArray(Node):
|
class DataRangeArray(Node):
|
||||||
def __init__(self, from_value, to_value) -> None:
|
def __init__(self, from_value, to_value, step=None) -> None:
|
||||||
self.from_value = from_value
|
self.from_value = from_value
|
||||||
self.to_value = to_value
|
self.to_value = to_value
|
||||||
|
self.step = step
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return type(self) == type(other) and self.from_value == other.from_value and self.to_value == other.to_value
|
return (type(self) == type(other)
|
||||||
|
and self.from_value == other.from_value
|
||||||
|
and self.to_value == other.to_value
|
||||||
|
and self.step == other.step)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
content_string = rep_join([self.from_value, self.to_value])
|
parts = [self.from_value, self.to_value]
|
||||||
return f"RangeArray([{content_string}])"
|
if self.step is not None:
|
||||||
|
parts.append(self.step)
|
||||||
|
return f"RangeArray([{rep_join(parts)}])"
|
||||||
|
|
||||||
def print(self):
|
def print(self):
|
||||||
return f"[{self.from_value.print()} VSQVE {self.to_value.print()}]"
|
base = f"[{self.from_value.print()} VSQVE {self.to_value.print()}"
|
||||||
|
if self.step is not None:
|
||||||
|
base += f" GRADV {self.step.print()}"
|
||||||
|
return base + "]"
|
||||||
|
|
||||||
def _eval(self, vtable):
|
def _eval(self, vtable):
|
||||||
vtable, from_val = self.from_value.eval(vtable)
|
vtable, from_val = self.from_value.eval(vtable)
|
||||||
@@ -388,7 +399,25 @@ 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
|
||||||
|
if self.step is None:
|
||||||
return vtable, ValList([ValInt(i) for i in range(from_int, to_int + 1)])
|
return vtable, ValList([ValInt(i) for i in range(from_int, to_int + 1)])
|
||||||
|
vtable, step_val = self.step.eval(vtable)
|
||||||
|
if not isinstance(step_val, ValInt):
|
||||||
|
raise CentvrionError("Range step must be a number")
|
||||||
|
step_int = step_val.value()
|
||||||
|
if step_int == 0:
|
||||||
|
raise CentvrionError("Range step cannot be zero")
|
||||||
|
items = []
|
||||||
|
i = from_int
|
||||||
|
if step_int > 0:
|
||||||
|
while i <= to_int:
|
||||||
|
items.append(ValInt(i))
|
||||||
|
i += step_int
|
||||||
|
else:
|
||||||
|
while i >= to_int:
|
||||||
|
items.append(ValInt(i))
|
||||||
|
i += step_int
|
||||||
|
return vtable, ValList(items)
|
||||||
|
|
||||||
|
|
||||||
class DataDict(Node):
|
class DataDict(Node):
|
||||||
@@ -937,6 +966,14 @@ class BinOp(Node):
|
|||||||
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
|
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
|
||||||
raise CentvrionError("Cannot compare strings or arrays with PLVS")
|
raise CentvrionError("Cannot compare strings or arrays with PLVS")
|
||||||
return vtable, ValBool((lv or 0) > (rv or 0))
|
return vtable, ValBool((lv or 0) > (rv or 0))
|
||||||
|
case "KEYWORD_HAVD_PLVS":
|
||||||
|
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
|
||||||
|
raise CentvrionError("Cannot compare strings or arrays with HAVD_PLVS")
|
||||||
|
return vtable, ValBool((lv or 0) <= (rv or 0))
|
||||||
|
case "KEYWORD_HAVD_MINVS":
|
||||||
|
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
|
||||||
|
raise CentvrionError("Cannot compare strings or arrays with HAVD_MINVS")
|
||||||
|
return vtable, ValBool((lv or 0) >= (rv or 0))
|
||||||
case "KEYWORD_EST":
|
case "KEYWORD_EST":
|
||||||
if ((isinstance(left, ValInt) and lv == 0 and isinstance(right, ValNul)) or
|
if ((isinstance(left, ValInt) and lv == 0 and isinstance(right, ValNul)) or
|
||||||
(isinstance(left, ValNul) and isinstance(right, ValInt) and rv == 0)):
|
(isinstance(left, ValNul) and isinstance(right, ValInt) and rv == 0)):
|
||||||
@@ -1431,6 +1468,26 @@ class BuiltIn(Node):
|
|||||||
if isinstance(params[0], (ValList, ValStr, ValDict)):
|
if isinstance(params[0], (ValList, ValStr, ValDict)):
|
||||||
return vtable, ValInt(len(params[0].value()))
|
return vtable, ValInt(len(params[0].value()))
|
||||||
raise CentvrionError("LONGITVDO requires an array, string, or dict")
|
raise CentvrionError("LONGITVDO requires an array, string, or dict")
|
||||||
|
case "LITTERA":
|
||||||
|
if len(params) != 1:
|
||||||
|
raise CentvrionError("LITTERA takes exactly I argument")
|
||||||
|
return vtable, ValStr(make_string(params[0], magnvm, svbnvlla))
|
||||||
|
case "MAIVSCVLA":
|
||||||
|
if len(params) != 1:
|
||||||
|
raise CentvrionError("MAIVSCVLA takes exactly I argument")
|
||||||
|
val = params[0]
|
||||||
|
if not isinstance(val, ValStr):
|
||||||
|
raise CentvrionError(f"MAIVSCVLA expects a string, got {type(val).__name__}")
|
||||||
|
s = val.value()
|
||||||
|
return vtable, ValStr("".join(chr(ord(c) - 32) if "a" <= c <= "z" else c for c in s))
|
||||||
|
case "MINVSCVLA":
|
||||||
|
if len(params) != 1:
|
||||||
|
raise CentvrionError("MINVSCVLA takes exactly I argument")
|
||||||
|
val = params[0]
|
||||||
|
if not isinstance(val, ValStr):
|
||||||
|
raise CentvrionError(f"MINVSCVLA expects a string, got {type(val).__name__}")
|
||||||
|
s = val.value()
|
||||||
|
return vtable, ValStr("".join(chr(ord(c) + 32) if "A" <= c <= "Z" else c for c in s))
|
||||||
case "CLAVES":
|
case "CLAVES":
|
||||||
if not isinstance(params[0], ValDict):
|
if not isinstance(params[0], ValDict):
|
||||||
raise CentvrionError("CLAVES requires a dict")
|
raise CentvrionError("CLAVES requires a dict")
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ _BINOP_FN = {
|
|||||||
"KEYWORD_DISPAR": "cent_neq",
|
"KEYWORD_DISPAR": "cent_neq",
|
||||||
"KEYWORD_MINVS": "cent_lt",
|
"KEYWORD_MINVS": "cent_lt",
|
||||||
"KEYWORD_PLVS": "cent_gt",
|
"KEYWORD_PLVS": "cent_gt",
|
||||||
|
"KEYWORD_HAVD_PLVS": "cent_lte",
|
||||||
|
"KEYWORD_HAVD_MINVS": "cent_gte",
|
||||||
"KEYWORD_ET": "cent_and",
|
"KEYWORD_ET": "cent_and",
|
||||||
"KEYWORD_AVT": "cent_or",
|
"KEYWORD_AVT": "cent_or",
|
||||||
}
|
}
|
||||||
@@ -127,7 +129,7 @@ def emit_expr(node, ctx):
|
|||||||
if isinstance(node, UnaryMinus):
|
if isinstance(node, UnaryMinus):
|
||||||
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
return inner_lines + [f"CentValue {tmp} = cent_int(-{inner_var}.ival);"], tmp
|
return inner_lines + [f"CentValue {tmp} = cent_neg({inner_var});"], tmp
|
||||||
|
|
||||||
if isinstance(node, UnaryNot):
|
if isinstance(node, UnaryNot):
|
||||||
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
inner_lines, inner_var = emit_expr(node.expr, ctx)
|
||||||
@@ -164,6 +166,7 @@ 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()
|
||||||
|
if node.step is None:
|
||||||
cap = f"({hi_var}.ival >= {lo_var}.ival ? (int)({hi_var}.ival - {lo_var}.ival + 1) : 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});",
|
||||||
@@ -172,6 +175,16 @@ def emit_expr(node, ctx):
|
|||||||
"}",
|
"}",
|
||||||
]
|
]
|
||||||
return lines, tmp
|
return lines, tmp
|
||||||
|
step_lines, step_var = emit_expr(node.step, ctx)
|
||||||
|
lines = lo_lines + hi_lines + step_lines + [
|
||||||
|
f'if ({step_var}.type != CENT_INT) cent_type_error("Range step must be a number");',
|
||||||
|
f'if ({step_var}.ival == 0) cent_runtime_error("Range step cannot be zero");',
|
||||||
|
f"CentValue {tmp} = cent_list_new(0);",
|
||||||
|
f"for (long {i_var} = {lo_var}.ival; ({step_var}.ival > 0 ? {i_var} <= {hi_var}.ival : {i_var} >= {hi_var}.ival); {i_var} += {step_var}.ival) {{",
|
||||||
|
f" cent_list_push(&{tmp}, cent_int({i_var}));",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
return lines, tmp
|
||||||
|
|
||||||
if isinstance(node, DataDict):
|
if isinstance(node, DataDict):
|
||||||
lines = []
|
lines = []
|
||||||
@@ -235,6 +248,23 @@ def _emit_builtin(node, ctx):
|
|||||||
case "LONGITVDO":
|
case "LONGITVDO":
|
||||||
lines.append(f"CentValue {tmp} = cent_longitudo({param_vars[0]});")
|
lines.append(f"CentValue {tmp} = cent_longitudo({param_vars[0]});")
|
||||||
|
|
||||||
|
case "LITTERA":
|
||||||
|
lines.append(f"CentValue {tmp} = cent_littera({param_vars[0]});")
|
||||||
|
|
||||||
|
case "MAIVSCVLA":
|
||||||
|
if len(param_vars) != 1:
|
||||||
|
lines.append(f'cent_runtime_error("MAIVSCVLA takes exactly I argument");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_maivscvla({param_vars[0]});")
|
||||||
|
|
||||||
|
case "MINVSCVLA":
|
||||||
|
if len(param_vars) != 1:
|
||||||
|
lines.append(f'cent_runtime_error("MINVSCVLA takes exactly I argument");')
|
||||||
|
lines.append(f"CentValue {tmp} = cent_null();")
|
||||||
|
else:
|
||||||
|
lines.append(f"CentValue {tmp} = cent_minvscvla({param_vars[0]});")
|
||||||
|
|
||||||
case "FORTVITVS_NVMERVS":
|
case "FORTVITVS_NVMERVS":
|
||||||
if not ctx.has_module("FORS"):
|
if not ctx.has_module("FORS"):
|
||||||
lines.append('cent_runtime_error("FORS module required for FORTVITVS_NVMERVS");')
|
lines.append('cent_runtime_error("FORS module required for FORTVITVS_NVMERVS");')
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ def compile_program(program):
|
|||||||
lines.append(" cent_init();")
|
lines.append(" cent_init();")
|
||||||
if "MAGNVM" in ctx.modules:
|
if "MAGNVM" in ctx.modules:
|
||||||
lines.append(" cent_magnvm = 1;")
|
lines.append(" cent_magnvm = 1;")
|
||||||
|
if "SVBNVLLA" in ctx.modules:
|
||||||
|
lines.append(" cent_svbnvlla = 1;")
|
||||||
lines.append(" CentScope _scope = {0};")
|
lines.append(" CentScope _scope = {0};")
|
||||||
lines.append(" CentValue _return_val = cent_null();")
|
lines.append(" CentValue _return_val = cent_null();")
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
CentArena *cent_arena;
|
CentArena *cent_arena;
|
||||||
int cent_magnvm = 0;
|
int cent_magnvm = 0;
|
||||||
|
int cent_svbnvlla = 0;
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Portable xorshift32 RNG (matches Python _CentRng) */
|
/* Portable xorshift32 RNG (matches Python _CentRng) */
|
||||||
@@ -181,7 +182,15 @@ void cent_int_to_roman(long n, char *buf, size_t bufsz) {
|
|||||||
if (bufsz > 6) { memcpy(buf, "NVLLVS", 6); buf[6] = '\0'; }
|
if (bufsz > 6) { memcpy(buf, "NVLLVS", 6); buf[6] = '\0'; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (n < 0 || (n > 3999 && !cent_magnvm))
|
if (n < 0) {
|
||||||
|
if (!cent_svbnvlla)
|
||||||
|
cent_runtime_error("number out of range for Roman numerals");
|
||||||
|
if (bufsz < 2) cent_runtime_error("Roman numeral buffer overflow");
|
||||||
|
buf[0] = '-';
|
||||||
|
cent_int_to_roman(-n, buf + 1, bufsz - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n > 3999 && !cent_magnvm)
|
||||||
cent_runtime_error("number out of range for Roman numerals");
|
cent_runtime_error("number out of range for Roman numerals");
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
if (n > 3999) {
|
if (n > 3999) {
|
||||||
@@ -271,8 +280,13 @@ static int write_val(CentValue v, char *buf, int bufsz) {
|
|||||||
case CENT_FRAC: {
|
case CENT_FRAC: {
|
||||||
long num = v.fval.num, den = v.fval.den;
|
long num = v.fval.num, den = v.fval.den;
|
||||||
if (den < 0) { num = -num; den = -den; }
|
if (den < 0) { num = -num; den = -den; }
|
||||||
if (num < 0)
|
int negative = 0;
|
||||||
|
if (num < 0) {
|
||||||
|
if (!cent_svbnvlla)
|
||||||
cent_runtime_error("cannot display negative numbers without SVBNVLLA");
|
cent_runtime_error("cannot display negative numbers without SVBNVLLA");
|
||||||
|
negative = 1;
|
||||||
|
num = -num;
|
||||||
|
}
|
||||||
long int_part = num / den;
|
long int_part = num / den;
|
||||||
long rem_num = num % den;
|
long rem_num = num % den;
|
||||||
|
|
||||||
@@ -297,11 +311,13 @@ static int write_val(CentValue v, char *buf, int bufsz) {
|
|||||||
for (int i = 0; i < (int)((level_int % 6) % 2); i++) frac_buf[frac_pos++] = '.';
|
for (int i = 0; i < (int)((level_int % 6) % 2); i++) frac_buf[frac_pos++] = '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
n = int_len + frac_pos;
|
n = int_len + frac_pos + (negative ? 1 : 0);
|
||||||
if (buf && n < bufsz) {
|
if (buf && n < bufsz) {
|
||||||
memcpy(buf, int_buf, int_len);
|
int pos = 0;
|
||||||
memcpy(buf + int_len, frac_buf, frac_pos);
|
if (negative) buf[pos++] = '-';
|
||||||
buf[n] = '\0';
|
memcpy(buf + pos, int_buf, int_len); pos += int_len;
|
||||||
|
memcpy(buf + pos, frac_buf, frac_pos); pos += frac_pos;
|
||||||
|
buf[pos] = '\0';
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -391,6 +407,13 @@ static void to_frac(CentValue v, long *num, long *den) {
|
|||||||
else { *num = v.fval.num; *den = v.fval.den; }
|
else { *num = v.fval.num; *den = v.fval.den; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CentValue cent_neg(CentValue v) {
|
||||||
|
if (v.type == CENT_INT) return cent_int(-v.ival);
|
||||||
|
if (v.type == CENT_FRAC) return frac_reduce(-v.fval.num, v.fval.den);
|
||||||
|
cent_type_error("Unary minus requires a number");
|
||||||
|
return cent_null();
|
||||||
|
}
|
||||||
|
|
||||||
CentValue cent_add(CentValue a, CentValue b) {
|
CentValue cent_add(CentValue a, CentValue b) {
|
||||||
if (a.type == CENT_INT && b.type == CENT_INT)
|
if (a.type == CENT_INT && b.type == CENT_INT)
|
||||||
return cent_int(a.ival + b.ival);
|
return cent_int(a.ival + b.ival);
|
||||||
@@ -564,6 +587,28 @@ CentValue cent_gt(CentValue a, CentValue b) {
|
|||||||
return cent_null();
|
return cent_null();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CentValue cent_lte(CentValue a, CentValue b) {
|
||||||
|
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
||||||
|
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
||||||
|
long an, ad, bn, bd;
|
||||||
|
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||||
|
return cent_bool(an * bd <= bn * ad);
|
||||||
|
}
|
||||||
|
cent_type_error("'HAVD_PLVS' requires two integers");
|
||||||
|
return cent_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
CentValue cent_gte(CentValue a, CentValue b) {
|
||||||
|
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
||||||
|
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
||||||
|
long an, ad, bn, bd;
|
||||||
|
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||||
|
return cent_bool(an * bd >= bn * ad);
|
||||||
|
}
|
||||||
|
cent_type_error("'HAVD_MINVS' requires two integers");
|
||||||
|
return cent_null();
|
||||||
|
}
|
||||||
|
|
||||||
CentValue cent_and(CentValue a, CentValue b) {
|
CentValue cent_and(CentValue a, CentValue b) {
|
||||||
if (a.type != CENT_BOOL || b.type != CENT_BOOL)
|
if (a.type != CENT_BOOL || b.type != CENT_BOOL)
|
||||||
cent_type_error("'ET' requires two booleans");
|
cent_type_error("'ET' requires two booleans");
|
||||||
@@ -622,6 +667,34 @@ CentValue cent_longitudo(CentValue v) {
|
|||||||
return cent_null(); /* unreachable; silences warning */
|
return cent_null(); /* unreachable; silences warning */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CentValue cent_littera(CentValue v) {
|
||||||
|
return cent_str(cent_make_string(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
CentValue cent_maivscvla(CentValue v) {
|
||||||
|
if (v.type != CENT_STR) cent_type_error("'MAIVSCVLA' requires a string");
|
||||||
|
size_t len = strlen(v.sval);
|
||||||
|
char *out = cent_arena_alloc(cent_arena, len + 1);
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
unsigned char c = (unsigned char)v.sval[i];
|
||||||
|
out[i] = (c >= 'a' && c <= 'z') ? (char)(c - ('a' - 'A')) : (char)c;
|
||||||
|
}
|
||||||
|
out[len] = '\0';
|
||||||
|
return cent_str(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
CentValue cent_minvscvla(CentValue v) {
|
||||||
|
if (v.type != CENT_STR) cent_type_error("'MINVSCVLA' requires a string");
|
||||||
|
size_t len = strlen(v.sval);
|
||||||
|
char *out = cent_arena_alloc(cent_arena, len + 1);
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
unsigned char c = (unsigned char)v.sval[i];
|
||||||
|
out[i] = (c >= 'A' && c <= 'Z') ? (char)(c + ('a' - 'A')) : (char)c;
|
||||||
|
}
|
||||||
|
out[len] = '\0';
|
||||||
|
return cent_str(out);
|
||||||
|
}
|
||||||
|
|
||||||
CentValue cent_typvs(CentValue v) {
|
CentValue cent_typvs(CentValue v) {
|
||||||
switch (v.type) {
|
switch (v.type) {
|
||||||
case CENT_INT: return cent_str("NVMERVS");
|
case CENT_INT: return cent_str("NVMERVS");
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ extern CentArena *cent_arena;
|
|||||||
/* Set to 1 when CVM MAGNVM is active; enables extended numeral display */
|
/* Set to 1 when CVM MAGNVM is active; enables extended numeral display */
|
||||||
extern int cent_magnvm;
|
extern int cent_magnvm;
|
||||||
|
|
||||||
|
/* Set to 1 when CVM SVBNVLLA is active; enables negative number display */
|
||||||
|
extern int cent_svbnvlla;
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Value constructors */
|
/* Value constructors */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -197,6 +200,7 @@ char *cent_make_string(CentValue v);
|
|||||||
/* Arithmetic and comparison operators */
|
/* Arithmetic and comparison operators */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
CentValue cent_neg(CentValue v); /* unary minus: INT or FRAC */
|
||||||
CentValue cent_add(CentValue a, CentValue b); /* INT+INT or FRAC+FRAC/INT */
|
CentValue cent_add(CentValue a, CentValue b); /* INT+INT or FRAC+FRAC/INT */
|
||||||
CentValue cent_array_concat(CentValue a, CentValue b); /* @ operator: concatenate two arrays */
|
CentValue cent_array_concat(CentValue a, CentValue b); /* @ operator: concatenate two arrays */
|
||||||
CentValue cent_concat(CentValue a, CentValue b); /* & operator: coerce all types to str */
|
CentValue cent_concat(CentValue a, CentValue b); /* & operator: coerce all types to str */
|
||||||
@@ -210,6 +214,8 @@ CentValue cent_eq (CentValue a, CentValue b); /* EST → BOOL */
|
|||||||
CentValue cent_neq(CentValue a, CentValue b); /* DISPAR → BOOL */
|
CentValue cent_neq(CentValue a, CentValue b); /* DISPAR → BOOL */
|
||||||
CentValue cent_lt (CentValue a, CentValue b); /* MINVS → BOOL */
|
CentValue cent_lt (CentValue a, CentValue b); /* MINVS → BOOL */
|
||||||
CentValue cent_gt (CentValue a, CentValue b); /* PLVS → BOOL */
|
CentValue cent_gt (CentValue a, CentValue b); /* PLVS → BOOL */
|
||||||
|
CentValue cent_lte(CentValue a, CentValue b); /* HAVD_PLVS → BOOL */
|
||||||
|
CentValue cent_gte(CentValue a, CentValue b); /* HAVD_MINVS → BOOL */
|
||||||
CentValue cent_and(CentValue a, CentValue b); /* ET → BOOL */
|
CentValue cent_and(CentValue a, CentValue b); /* ET → BOOL */
|
||||||
CentValue cent_or (CentValue a, CentValue b); /* AVT → BOOL */
|
CentValue cent_or (CentValue a, CentValue b); /* AVT → BOOL */
|
||||||
|
|
||||||
@@ -221,6 +227,9 @@ void cent_dic(CentValue v); /* DIC */
|
|||||||
CentValue cent_avdi(void); /* AVDI */
|
CentValue cent_avdi(void); /* AVDI */
|
||||||
CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */
|
CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */
|
||||||
CentValue cent_longitudo(CentValue v); /* LONGITVDO */
|
CentValue cent_longitudo(CentValue v); /* LONGITVDO */
|
||||||
|
CentValue cent_littera(CentValue v); /* LITTERA */
|
||||||
|
CentValue cent_maivscvla(CentValue v); /* MAIVSCVLA */
|
||||||
|
CentValue cent_minvscvla(CentValue v); /* MINVSCVLA */
|
||||||
CentValue cent_fortuitus_numerus(CentValue lo, CentValue hi); /* FORTVITVS_NVMERVS */
|
CentValue cent_fortuitus_numerus(CentValue lo, CentValue hi); /* FORTVITVS_NVMERVS */
|
||||||
CentValue cent_fortuita_electionis(CentValue lst); /* FORTVITA_ELECTIO */
|
CentValue cent_fortuita_electionis(CentValue lst); /* FORTVITA_ELECTIO */
|
||||||
CentValue cent_decimatio(CentValue lst); /* DECIMATIO */
|
CentValue cent_decimatio(CentValue lst); /* DECIMATIO */
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
|||||||
"DEFINI",
|
"DEFINI",
|
||||||
"DESIGNA",
|
"DESIGNA",
|
||||||
"DISPAR",
|
"DISPAR",
|
||||||
|
"DIVIDE",
|
||||||
"DONICVM",
|
"DONICVM",
|
||||||
"DVM",
|
"DVM",
|
||||||
"CONTINVA",
|
"CONTINVA",
|
||||||
@@ -20,10 +21,14 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
|||||||
"FAC",
|
"FAC",
|
||||||
"FALSITAS",
|
"FALSITAS",
|
||||||
"FVNCTIO",
|
"FVNCTIO",
|
||||||
|
"GRADV",
|
||||||
|
"HAVD_MINVS",
|
||||||
|
"HAVD_PLVS",
|
||||||
"INVOCA",
|
"INVOCA",
|
||||||
"IN",
|
"IN",
|
||||||
"MINVE",
|
"MINVE",
|
||||||
"MINVS",
|
"MINVS",
|
||||||
|
"MVLTIPLICA",
|
||||||
"NON",
|
"NON",
|
||||||
"NVLLVS",
|
"NVLLVS",
|
||||||
"PER",
|
"PER",
|
||||||
@@ -50,7 +55,10 @@ builtin_tokens = [("BUILTIN", i) for i in [
|
|||||||
"EVERRE",
|
"EVERRE",
|
||||||
"FORTVITVS_NVMERVS",
|
"FORTVITVS_NVMERVS",
|
||||||
"FORTVITA_ELECTIO",
|
"FORTVITA_ELECTIO",
|
||||||
|
"LITTERA",
|
||||||
"LONGITVDO",
|
"LONGITVDO",
|
||||||
|
"MAIVSCVLA",
|
||||||
|
"MINVSCVLA",
|
||||||
"NVMERVS",
|
"NVMERVS",
|
||||||
"ORDINA",
|
"ORDINA",
|
||||||
"SEMEN",
|
"SEMEN",
|
||||||
@@ -103,8 +111,8 @@ whitespace_tokens = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
all_tokens = (
|
all_tokens = (
|
||||||
keyword_tokens +
|
|
||||||
builtin_tokens +
|
builtin_tokens +
|
||||||
|
keyword_tokens +
|
||||||
module_tokens +
|
module_tokens +
|
||||||
data_tokens +
|
data_tokens +
|
||||||
symbol_tokens +
|
symbol_tokens +
|
||||||
|
|||||||
@@ -115,7 +115,8 @@ class Parser():
|
|||||||
precedence=[
|
precedence=[
|
||||||
('left', ["KEYWORD_AVT"]),
|
('left', ["KEYWORD_AVT"]),
|
||||||
('left', ["KEYWORD_ET"]),
|
('left', ["KEYWORD_ET"]),
|
||||||
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST", "KEYWORD_DISPAR"]),
|
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST", "KEYWORD_DISPAR",
|
||||||
|
"KEYWORD_HAVD_PLVS", "KEYWORD_HAVD_MINVS"]),
|
||||||
('left', ["SYMBOL_AMPERSAND", "SYMBOL_AT", "SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
('left', ["SYMBOL_AMPERSAND", "SYMBOL_AT", "SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
||||||
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE", "KEYWORD_RELIQVVM"]),
|
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE", "KEYWORD_RELIQVVM"]),
|
||||||
('right', ["UMINUS", "UNOT"]),
|
('right', ["UMINUS", "UNOT"]),
|
||||||
@@ -197,6 +198,14 @@ class Parser():
|
|||||||
def statement_minve(tokens):
|
def statement_minve(tokens):
|
||||||
return ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_MINUS"))
|
return ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_MINUS"))
|
||||||
|
|
||||||
|
@self.pg.production('statement : id KEYWORD_MVLTIPLICA expression')
|
||||||
|
def statement_mvltiplica(tokens):
|
||||||
|
return ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_TIMES"))
|
||||||
|
|
||||||
|
@self.pg.production('statement : id KEYWORD_DIVIDE expression')
|
||||||
|
def statement_divide(tokens):
|
||||||
|
return ast_nodes.Designa(tokens[0], ast_nodes.BinOp(tokens[0], tokens[2], "SYMBOL_DIVIDE"))
|
||||||
|
|
||||||
@self.pg.production('statement : expression')
|
@self.pg.production('statement : expression')
|
||||||
def statement_expression(tokens):
|
def statement_expression(tokens):
|
||||||
return ast_nodes.ExpressionStatement(tokens[0])
|
return ast_nodes.ExpressionStatement(tokens[0])
|
||||||
@@ -267,6 +276,11 @@ class Parser():
|
|||||||
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
|
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5])
|
||||||
return ast_nodes.PerStatement(range_array, tokens[1], tokens[8])
|
return ast_nodes.PerStatement(range_array, tokens[1], tokens[8])
|
||||||
|
|
||||||
|
@self.pg.production('donicum_statement : KEYWORD_DONICVM id KEYWORD_VT expression KEYWORD_VSQVE expression KEYWORD_GRADV expression KEYWORD_FAC SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
|
def donicum_step(tokens):
|
||||||
|
range_array = ast_nodes.DataRangeArray(tokens[3], tokens[5], tokens[7])
|
||||||
|
return ast_nodes.PerStatement(range_array, tokens[1], tokens[10])
|
||||||
|
|
||||||
# expressions
|
# expressions
|
||||||
@self.pg.production('expressions : SYMBOL_LPARENS expression_list')
|
@self.pg.production('expressions : SYMBOL_LPARENS expression_list')
|
||||||
def expressions(tokens):
|
def expressions(tokens):
|
||||||
@@ -334,6 +348,8 @@ class Parser():
|
|||||||
@self.pg.production('expression : expression KEYWORD_DISPAR expression')
|
@self.pg.production('expression : expression KEYWORD_DISPAR expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_MINVS expression')
|
@self.pg.production('expression : expression KEYWORD_MINVS expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_PLVS expression')
|
@self.pg.production('expression : expression KEYWORD_PLVS expression')
|
||||||
|
@self.pg.production('expression : expression KEYWORD_HAVD_PLVS expression')
|
||||||
|
@self.pg.production('expression : expression KEYWORD_HAVD_MINVS expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_ET expression')
|
@self.pg.production('expression : expression KEYWORD_ET expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_AVT expression')
|
@self.pg.production('expression : expression KEYWORD_AVT expression')
|
||||||
def binop(tokens):
|
def binop(tokens):
|
||||||
@@ -383,6 +399,10 @@ class Parser():
|
|||||||
def range_array(tokens):
|
def range_array(tokens):
|
||||||
return ast_nodes.DataRangeArray(tokens[1], tokens[3])
|
return ast_nodes.DataRangeArray(tokens[1], tokens[3])
|
||||||
|
|
||||||
|
@self.pg.production('expression : SYMBOL_LBRACKET expression KEYWORD_VSQVE expression KEYWORD_GRADV expression SYMBOL_RBRACKET')
|
||||||
|
def range_array_step(tokens):
|
||||||
|
return ast_nodes.DataRangeArray(tokens[1], tokens[3], tokens[5])
|
||||||
|
|
||||||
@self.pg.production('expression : expression SYMBOL_LBRACKET expression SYMBOL_RBRACKET', precedence='INDEX')
|
@self.pg.production('expression : expression SYMBOL_LBRACKET expression SYMBOL_RBRACKET', precedence='INDEX')
|
||||||
def array_index(tokens):
|
def array_index(tokens):
|
||||||
return ast_nodes.ArrayIndex(tokens[0], tokens[2])
|
return ast_nodes.ArrayIndex(tokens[0], tokens[2])
|
||||||
|
|||||||
@@ -33,13 +33,15 @@
|
|||||||
\languageline{statement}{\texttt{DESIGNA} \textbf{id} \texttt{,} \textit{ids} \texttt{VT} \textit{expression}} \\
|
\languageline{statement}{\texttt{DESIGNA} \textbf{id} \texttt{,} \textit{ids} \texttt{VT} \textit{expression}} \\
|
||||||
\languageline{statement}{\textbf{id} \texttt{AVGE} \textit{expression}} \\
|
\languageline{statement}{\textbf{id} \texttt{AVGE} \textit{expression}} \\
|
||||||
\languageline{statement}{\textbf{id} \texttt{MINVE} \textit{expression}} \\
|
\languageline{statement}{\textbf{id} \texttt{MINVE} \textit{expression}} \\
|
||||||
|
\languageline{statement}{\textbf{id} \texttt{MVLTIPLICA} \textit{expression}} \\
|
||||||
|
\languageline{statement}{\textbf{id} \texttt{DIVIDE} \textit{expression}} \\
|
||||||
\languageline{statement}{\texttt{DEFINI} \textbf{id} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
|
\languageline{statement}{\texttt{DEFINI} \textbf{id} \texttt{(} \textit{optional-ids} \texttt{)} \texttt{VT} \textit{scope}} \\
|
||||||
\languageline{statement}{\textit{if-statement}} \\
|
\languageline{statement}{\textit{if-statement}} \\
|
||||||
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{AETERNVM} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{AETERNVM} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{PER} \textbf{id}\texttt{,} \textbf{id-list} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{PER} \textbf{id}\texttt{,} \textbf{id-list} \texttt{IN} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FAC} \textit{scope}} \\
|
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \textit{optional-step} \texttt{FAC} \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}} \\
|
\languageline{statement}{\texttt{CONTINVA}} \\
|
||||||
@@ -71,7 +73,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{]} \textnormal{\small\ (inclusive on both ends)}} \\
|
\languageline{literal}{\texttt{[} \textit{expression} \texttt{VSQVE} \textit{expression} \textit{optional-step} \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}} \\
|
||||||
@@ -92,6 +94,9 @@
|
|||||||
|
|
||||||
\languageline{expressions}{\textit{expression}, \textit{expressions}} \\
|
\languageline{expressions}{\textit{expression}, \textit{expressions}} \\
|
||||||
\languageline{expressions}{\textit{expression}} \\ \hline
|
\languageline{expressions}{\textit{expression}} \\ \hline
|
||||||
|
|
||||||
|
\languageline{optional-step}{\texttt{GRADV} \textit{expression}} \\
|
||||||
|
\languageline{optional-step}{} \\ \hline
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\end{center}
|
\end{center}
|
||||||
\end{table}
|
\end{table}
|
||||||
@@ -106,7 +111,7 @@
|
|||||||
\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.
|
||||||
\item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{RELIQVVM} (modulo), \texttt{EST} (equality), \texttt{DISPAR} (not-equal), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string concatenation), \texttt{@} (array concatenation).
|
\item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{RELIQVVM} (modulo), \texttt{EST} (equality), \texttt{DISPAR} (not-equal), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{HAVD\_PLVS} ($\leq$), \texttt{HAVD\_MINVS} ($\geq$), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string concatenation), \texttt{@} (array concatenation).
|
||||||
\item \textbf{unop}: \\ Unary operators: \texttt{-} (negation), \texttt{NON} (boolean not).
|
\item \textbf{unop}: \\ Unary operators: \texttt{-} (negation), \texttt{NON} (boolean not).
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
|
|||||||
3
pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tool.pytest.ini_options]
|
||||||
|
python_files = ["[0-9][0-9]_test_*.py", "test_*.py"]
|
||||||
|
testpaths = ["tests"]
|
||||||
2
snippets/array_concat.cent
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DESIGNA x VT [I, II, III] @ [IV, V]
|
||||||
|
DIC(x)
|
||||||
BIN
snippets/array_concat.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -1,3 +1,6 @@
|
|||||||
DESIGNA x VT V
|
DESIGNA x VT V
|
||||||
x AVGE III
|
x AVGE III
|
||||||
|
x MINVE II
|
||||||
|
x MVLTIPLICA IV
|
||||||
|
x DIVIDE III
|
||||||
DIC(x)
|
DIC(x)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 19 KiB |
5
snippets/donicvm_gradv.cent
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
DESIGNA s VT NVLLVS
|
||||||
|
DONICVM i VT I VSQVE X GRADV II FAC {
|
||||||
|
s AVGE i
|
||||||
|
}
|
||||||
|
DIC(s)
|
||||||
BIN
snippets/donicvm_gradv.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
3
snippets/littera.cent
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DESIGNA n VT VII
|
||||||
|
DESIGNA s VT LITTERA(n) & " est septem"
|
||||||
|
DIC(s)
|
||||||
BIN
snippets/littera.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
3
snippets/per_destructure.cent
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
PER a, b IN [[I, II], [III, IV]] FAC {
|
||||||
|
DIC(a + b)
|
||||||
|
}
|
||||||
BIN
snippets/per_destructure.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
2
snippets/string_index.cent
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DIC("SALVTE"[I])
|
||||||
|
DIC("SALVTE"[III])
|
||||||
BIN
snippets/string_index.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
snippets/string_slice.cent
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DIC("SALVTE"[II VSQVE IV])
|
||||||
BIN
snippets/string_slice.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
@@ -70,7 +70,7 @@ contexts:
|
|||||||
scope: constant.language.centvrion
|
scope: constant.language.centvrion
|
||||||
|
|
||||||
builtins:
|
builtins:
|
||||||
- match: '\b(ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|LEGE|LONGITVDO|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TYPVS)\b'
|
- match: '\b(ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TYPVS)\b'
|
||||||
scope: support.function.builtin.centvrion
|
scope: support.function.builtin.centvrion
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
@@ -78,7 +78,7 @@ contexts:
|
|||||||
scope: support.class.module.centvrion
|
scope: support.class.module.centvrion
|
||||||
|
|
||||||
keywords:
|
keywords:
|
||||||
- match: '\b(AETERNVM|ALIVD|AVGE|AVT|CAPE|CONTINVA|DEFINI|DESIGNA|DISPAR|DONICVM|DVM|ERVMPE|EST|ET|FAC|FVNCTIO|INVOCA|IN|MINVE|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT|CVM)\b'
|
- match: '\b(HAVD_PLVS|HAVD_MINVS|AETERNVM|ALIVD|AVGE|AVT|CAPE|CONTINVA|DEFINI|DESIGNA|DISPAR|DIVIDE|DONICVM|DVM|ERVMPE|EST|ET|FAC|FVNCTIO|GRADV|INVOCA|IN|MINVE|MINVS|MVLTIPLICA|NON|PER|PLVS|REDI|RELIQVVM|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT|CVM)\b'
|
||||||
scope: keyword.control.centvrion
|
scope: keyword.control.centvrion
|
||||||
|
|
||||||
operators:
|
operators:
|
||||||
|
|||||||
5
snippets/tempta_cape.cent
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
TEMPTA {
|
||||||
|
DESIGNA x VT I / NVLLVS
|
||||||
|
} CAPE error {
|
||||||
|
DIC(error)
|
||||||
|
}
|
||||||
BIN
snippets/tempta_cape.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
450
tests/01_test_core____.py
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
|
||||||
|
output_tests = [
|
||||||
|
("DIC(\"hello\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"),
|
||||||
|
("DIC(\"world\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("world")]))]), ValStr("world"), "world\n"),
|
||||||
|
("DIC(III)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("III")]))]), ValStr("III"), "III\n"),
|
||||||
|
("DIC(X)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("X")]))]), ValStr("X"), "X\n"),
|
||||||
|
("DIC(MMXXV)", Program([], [ExpressionStatement(BuiltIn("DIC", [Numeral("MMXXV")]))]), ValStr("MMXXV"), "MMXXV\n"),
|
||||||
|
("DIC('hello')", Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"),
|
||||||
|
("DIC('world')", Program([], [ExpressionStatement(BuiltIn("DIC", [String("world")]))]), ValStr("world"), "world\n"),
|
||||||
|
("DIC(\"a\", \"b\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("a"), String("b")]))]), ValStr("a b"), "a b\n"),
|
||||||
|
("DIC(\"line one\")\nDIC(\"line two\")", Program([], [ExpressionStatement(BuiltIn("DIC", [String("line one")])), ExpressionStatement(BuiltIn("DIC", [String("line two")]))]), ValStr("line two"), "line one\nline two\n"),
|
||||||
|
("DIC(DIC(II))", Program([], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("DIC", [Numeral("II")])]))]), ValStr("II"), "II\nII\n"),
|
||||||
|
("EVERRE()", Program([], [ExpressionStatement(BuiltIn("EVERRE", []))]), ValNul(), "\033[2J\033[H"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestOutput(unittest.TestCase):
|
||||||
|
@parameterized.expand(output_tests)
|
||||||
|
def test_output(self, source, nodes, value, output):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- Arithmetic ---
|
||||||
|
|
||||||
|
arithmetic_tests = [
|
||||||
|
("I + I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS"))]), ValInt(2)),
|
||||||
|
("X - III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_MINUS"))]), ValInt(7)),
|
||||||
|
("III * IV", Program([], [ExpressionStatement(BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(12)),
|
||||||
|
("X / II", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))]), ValInt(5)),
|
||||||
|
("X / III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(3)), # integer division: 10 // 3 = 3
|
||||||
|
("X RELIQVVM III", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(1)), # 10 % 3 = 1
|
||||||
|
("IX RELIQVVM III", Program([], [ExpressionStatement(BinOp(Numeral("IX"), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(0)), # exact divisor
|
||||||
|
("VII RELIQVVM X", Program([], [ExpressionStatement(BinOp(Numeral("VII"), Numeral("X"), "KEYWORD_RELIQVVM"))]), ValInt(7)), # dividend < divisor
|
||||||
|
("II + III * IV", Program([], [ExpressionStatement(BinOp(Numeral("II"), BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"), "SYMBOL_PLUS"))]), ValInt(14)), # precedence: 2 + (3*4) = 14
|
||||||
|
("(II + III) * IV", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(20)), # parens: (2+3)*4 = 20
|
||||||
|
("CVM SVBNVLLA\n- III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(Numeral("III")))]), ValInt(-3)), # unary negation
|
||||||
|
("CVM SVBNVLLA\n- (II + III)", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")))]), ValInt(-5)), # unary negation of expression
|
||||||
|
("CVM SVBNVLLA\n- - II", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(UnaryMinus(Numeral("II"))))]), ValInt(2)), # double negation
|
||||||
|
("CVM SVBNVLLA\nIII + - II", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("III"), UnaryMinus(Numeral("II")), "SYMBOL_PLUS"))]), ValInt(1)), # unary in binary context
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestArithmetic(unittest.TestCase):
|
||||||
|
@parameterized.expand(arithmetic_tests)
|
||||||
|
def test_arithmetic(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- Precedence and associativity ---
|
||||||
|
#
|
||||||
|
# Precedence (lowest → highest):
|
||||||
|
# AVT < ET < (EST, DISPAR, PLVS, MINVS) < (+ -) < (* / RELIQVVM) < UMINUS < INDEX
|
||||||
|
|
||||||
|
precedence_tests = [
|
||||||
|
# * binds tighter than -: 10 - (2*3) = 4, not (10-2)*3 = 24
|
||||||
|
("X - II * III",
|
||||||
|
Program([], [ExpressionStatement(BinOp(Numeral("X"), BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), "SYMBOL_MINUS"))]),
|
||||||
|
ValInt(4)),
|
||||||
|
# / binds tighter than +: (10/2) + 3 = 8, not 10/(2+3) = 2
|
||||||
|
("X / II + III",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_PLUS"))]),
|
||||||
|
ValInt(8)),
|
||||||
|
# + binds tighter than EST: (2+3)==5 = True, not 2+(3==5) = type error
|
||||||
|
("II + III EST V",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("V"), "KEYWORD_EST"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
# * binds tighter than PLVS: (2*3)>4 = True
|
||||||
|
("II * III PLVS IV",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), Numeral("IV"), "KEYWORD_PLVS"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
# comparison binds tighter than ET: (1==2) AND (2==2) = False AND True = False
|
||||||
|
("I EST II ET II EST II",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]),
|
||||||
|
ValBool(False)),
|
||||||
|
# + binds tighter than DISPAR: (2+3)!=5 = False, not 2+(3!=5) = type error
|
||||||
|
("II + III DISPAR V",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"), Numeral("V"), "KEYWORD_DISPAR"))]),
|
||||||
|
ValBool(False)),
|
||||||
|
# DISPAR binds tighter than ET: (1!=2) AND (2!=2) = True AND False = False
|
||||||
|
("I DISPAR II ET II DISPAR II",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_DISPAR"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_DISPAR"), "KEYWORD_ET"))]),
|
||||||
|
ValBool(False)),
|
||||||
|
# ET binds tighter than AVT: True OR (False AND False) = True
|
||||||
|
("VERITAS AVT FALSITAS ET FALSITAS",
|
||||||
|
Program([], [ExpressionStatement(BinOp(Bool(True), BinOp(Bool(False), Bool(False), "KEYWORD_ET"), "KEYWORD_AVT"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
# UMINUS binds tighter than *: (-2)*3 = -6, not -(2*3) = -6 (same value, different tree)
|
||||||
|
("CVM SVBNVLLA\n- II * III",
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_TIMES"))]),
|
||||||
|
ValInt(-6)),
|
||||||
|
# UMINUS binds tighter than +: (-2)+3 = 1, not -(2+3) = -5
|
||||||
|
("CVM SVBNVLLA\n- II + III",
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("II")), Numeral("III"), "SYMBOL_PLUS"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
# INDEX binds tighter than UMINUS: -(arr[I]) = -1
|
||||||
|
("CVM SVBNVLLA\n- [I, II, III][I]",
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I"))))]),
|
||||||
|
ValInt(-1)),
|
||||||
|
# INDEX binds tighter than NON: NON (arr[I]) = NON VERITAS = False
|
||||||
|
("NON [VERITAS, FALSITAS][I]",
|
||||||
|
Program([], [ExpressionStatement(UnaryNot(ArrayIndex(DataArray([Bool(True), Bool(False)]), Numeral("I"))))]),
|
||||||
|
ValBool(False)),
|
||||||
|
# INDEX binds tighter than +: (arr[II]) + X = 2 + 10 = 12
|
||||||
|
("[I, II, III][II] + X",
|
||||||
|
Program([], [ExpressionStatement(BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")), Numeral("X"), "SYMBOL_PLUS"))]),
|
||||||
|
ValInt(12)),
|
||||||
|
# left-associativity of -: (10-3)-2 = 5, not 10-(3-2) = 9
|
||||||
|
("X - III - II",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("X"), Numeral("III"), "SYMBOL_MINUS"), Numeral("II"), "SYMBOL_MINUS"))]),
|
||||||
|
ValInt(5)),
|
||||||
|
# left-associativity of /: (12/2)/3 = 2, not 12/(2/3) = 18
|
||||||
|
("XII / II / III",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("XII"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_DIVIDE"))]),
|
||||||
|
ValInt(2)),
|
||||||
|
# RELIQVVM same precedence as *, /; left-associative: (17 % 5) % 2 = 0
|
||||||
|
("XVII RELIQVVM V RELIQVVM II",
|
||||||
|
Program([], [ExpressionStatement(
|
||||||
|
BinOp(BinOp(Numeral("XVII"), Numeral("V"), "KEYWORD_RELIQVVM"),
|
||||||
|
Numeral("II"), "KEYWORD_RELIQVVM"))]),
|
||||||
|
ValInt(0)),
|
||||||
|
# RELIQVVM binds tighter than +: 2 + (7 % 3) = 3, not (2+7) % 3 = 0
|
||||||
|
("II + VII RELIQVVM III",
|
||||||
|
Program([], [ExpressionStatement(
|
||||||
|
BinOp(Numeral("II"),
|
||||||
|
BinOp(Numeral("VII"), Numeral("III"), "KEYWORD_RELIQVVM"),
|
||||||
|
"SYMBOL_PLUS"))]),
|
||||||
|
ValInt(3)),
|
||||||
|
# left-associativity of AVT: (False OR True) OR False = True
|
||||||
|
("FALSITAS AVT VERITAS AVT FALSITAS",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"), Bool(False), "KEYWORD_AVT"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestPrecedence(unittest.TestCase):
|
||||||
|
@parameterized.expand(precedence_tests)
|
||||||
|
def test_precedence(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- Assignment ---
|
||||||
|
|
||||||
|
assignment_tests = [
|
||||||
|
("DESIGNA x VT III\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("III")), ExpressionStatement(ID("x"))]),
|
||||||
|
ValInt(3)),
|
||||||
|
("DESIGNA msg VT \"hello\"\nmsg",
|
||||||
|
Program([], [Designa(ID("msg"), String("hello")), ExpressionStatement(ID("msg"))]),
|
||||||
|
ValStr("hello")),
|
||||||
|
("DESIGNA msg VT 'hello'\nmsg",
|
||||||
|
Program([], [Designa(ID("msg"), String("hello")), ExpressionStatement(ID("msg"))]),
|
||||||
|
ValStr("hello")),
|
||||||
|
("DESIGNA a VT V\nDESIGNA b VT X\na + b",
|
||||||
|
Program([], [Designa(ID("a"), Numeral("V")), Designa(ID("b"), Numeral("X")),
|
||||||
|
ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"))]),
|
||||||
|
ValInt(15)),
|
||||||
|
("DESIGNA x VT II\nDESIGNA x VT x + I\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("II")),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
|
||||||
|
ExpressionStatement(ID("x"))]),
|
||||||
|
ValInt(3)),
|
||||||
|
# Compound assignment — AVGE (+=)
|
||||||
|
("DESIGNA x VT V\nx AVGE III\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("V")),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_PLUS")),
|
||||||
|
ExpressionStatement(ID("x"))]),
|
||||||
|
ValInt(8)),
|
||||||
|
# Compound assignment — MINVE (-=)
|
||||||
|
("DESIGNA x VT X\nx MINVE III\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("X")),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_MINUS")),
|
||||||
|
ExpressionStatement(ID("x"))]),
|
||||||
|
ValInt(7)),
|
||||||
|
# AVGE with complex expression
|
||||||
|
("DESIGNA x VT I\nx AVGE II + III\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("I")),
|
||||||
|
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 inclusive: I VSQVE III = [1, 2, 3])
|
||||||
|
("DESIGNA s VT NVLLVS\nDONICVM i VT I VSQVE III FAC {\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(6)),
|
||||||
|
# Compound assignment — MVLTIPLICA (*=)
|
||||||
|
("DESIGNA x VT III\nx MVLTIPLICA II\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("III")),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")),
|
||||||
|
ExpressionStatement(ID("x"))]),
|
||||||
|
ValInt(6)),
|
||||||
|
# Compound assignment — DIVIDE (/=)
|
||||||
|
("DESIGNA x VT XII\nx DIVIDE III\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("XII")),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("III"), "SYMBOL_DIVIDE")),
|
||||||
|
ExpressionStatement(ID("x"))]),
|
||||||
|
ValInt(4)),
|
||||||
|
# MVLTIPLICA with complex RHS — whole expression is captured before the op
|
||||||
|
("DESIGNA x VT II\nx MVLTIPLICA II + I\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("II")),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("I"), "SYMBOL_PLUS"), "SYMBOL_TIMES")),
|
||||||
|
ExpressionStatement(ID("x"))]),
|
||||||
|
ValInt(6)),
|
||||||
|
# DIVIDE with complex RHS
|
||||||
|
("DESIGNA x VT XX\nx DIVIDE II + II\nx",
|
||||||
|
Program([], [Designa(ID("x"), Numeral("XX")),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), BinOp(Numeral("II"), Numeral("II"), "SYMBOL_PLUS"), "SYMBOL_DIVIDE")),
|
||||||
|
ExpressionStatement(ID("x"))]),
|
||||||
|
ValInt(5)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestAssignment(unittest.TestCase):
|
||||||
|
@parameterized.expand(assignment_tests)
|
||||||
|
def test_assignment(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- Destructuring ---
|
||||||
|
|
||||||
|
destructuring_tests = [
|
||||||
|
# basic: unpack multi-return function
|
||||||
|
(
|
||||||
|
"DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (III, VII)\nx + y",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]),
|
||||||
|
DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("III"), Numeral("VII")])),
|
||||||
|
ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS")),
|
||||||
|
]),
|
||||||
|
ValInt(10),
|
||||||
|
),
|
||||||
|
# unpack array literal
|
||||||
|
(
|
||||||
|
"DESIGNA a, b VT [I, II]\na + b",
|
||||||
|
Program([], [
|
||||||
|
DesignaDestructure([ID("a"), ID("b")], DataArray([Numeral("I"), Numeral("II")])),
|
||||||
|
ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")),
|
||||||
|
]),
|
||||||
|
ValInt(3),
|
||||||
|
),
|
||||||
|
# three variables
|
||||||
|
(
|
||||||
|
"DESIGNA a, b, c VT [X, XX, XXX]\na + b + c",
|
||||||
|
Program([], [
|
||||||
|
DesignaDestructure([ID("a"), ID("b"), ID("c")], DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")])),
|
||||||
|
ExpressionStatement(BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")),
|
||||||
|
]),
|
||||||
|
ValInt(60),
|
||||||
|
),
|
||||||
|
# destructure into individual use
|
||||||
|
(
|
||||||
|
"DEFINI pair (a, b) VT { REDI (a, b) }\nDESIGNA x, y VT INVOCA pair (V, II)\nDIC(x)\nDIC(y)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]),
|
||||||
|
DesignaDestructure([ID("x"), ID("y")], Invoca(ID("pair"), [Numeral("V"), Numeral("II")])),
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [ID("x")])),
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [ID("y")])),
|
||||||
|
]),
|
||||||
|
ValStr("II"),
|
||||||
|
"V\nII\n",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDestructuring(unittest.TestCase):
|
||||||
|
@parameterized.expand(destructuring_tests)
|
||||||
|
def test_destructuring(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- Functions ---
|
||||||
|
|
||||||
|
function_tests = [
|
||||||
|
(
|
||||||
|
"DEFINI bis (n) VT { REDI (n * II) }\nINVOCA bis (III)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("bis"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("bis"), [Numeral("III")])),
|
||||||
|
]),
|
||||||
|
ValInt(6),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"DEFINI add (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("add"), [ID("a"), ID("b")], [Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])),
|
||||||
|
]),
|
||||||
|
ValInt(7),
|
||||||
|
),
|
||||||
|
# Fibonacci: fib(n<3)=1, fib(n)=fib(n-1)+fib(n-2)
|
||||||
|
(
|
||||||
|
"DEFINI fib (n) VT {\nSI n MINVS III TVNC { REDI (I) } ALIVD { REDI (INVOCA fib (n - I) + INVOCA fib (n - II)) }\n}\nINVOCA fib (VII)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("fib"), [ID("n")], [
|
||||||
|
SiStatement(
|
||||||
|
BinOp(ID("n"), Numeral("III"), "KEYWORD_MINVS"),
|
||||||
|
[Redi([Numeral("I")])],
|
||||||
|
[Redi([BinOp(
|
||||||
|
Invoca(ID("fib"), [BinOp(ID("n"), Numeral("I"), "SYMBOL_MINUS")]),
|
||||||
|
Invoca(ID("fib"), [BinOp(ID("n"), Numeral("II"), "SYMBOL_MINUS")]),
|
||||||
|
"SYMBOL_PLUS",
|
||||||
|
)])],
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
ExpressionStatement(Invoca(ID("fib"), [Numeral("VII")])),
|
||||||
|
]),
|
||||||
|
ValInt(13),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestFunctions(unittest.TestCase):
|
||||||
|
@parameterized.expand(function_tests)
|
||||||
|
def test_functions(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- Function edge cases ---
|
||||||
|
|
||||||
|
function_edge_tests = [
|
||||||
|
# no explicit REDI → returns ValNul
|
||||||
|
("DEFINI f () VT { I }\nINVOCA f ()",
|
||||||
|
Program([], [Defini(ID("f"), [], [ExpressionStatement(Numeral("I"))]), ExpressionStatement(Invoca(ID("f"), []))]),
|
||||||
|
ValNul()),
|
||||||
|
# REDI multiple values → ValList
|
||||||
|
(
|
||||||
|
"DEFINI pair (a, b) VT { REDI (a, b) }\nINVOCA pair (I, II)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("pair"), [ID("a"), ID("b")], [Redi([ID("a"), ID("b")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("pair"), [Numeral("I"), Numeral("II")])),
|
||||||
|
]),
|
||||||
|
ValList([ValInt(1), ValInt(2)]),
|
||||||
|
),
|
||||||
|
# function doesn't mutate outer vtable
|
||||||
|
(
|
||||||
|
"DESIGNA x VT I\nDEFINI f () VT { DESIGNA x VT V\nREDI (x) }\nINVOCA f ()\nx",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
Defini(ID("f"), [], [Designa(ID("x"), Numeral("V")), Redi([ID("x")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [])),
|
||||||
|
ExpressionStatement(ID("x")),
|
||||||
|
]),
|
||||||
|
ValInt(1),
|
||||||
|
),
|
||||||
|
# function can read outer vtable (closure-like)
|
||||||
|
(
|
||||||
|
"DESIGNA x VT VII\nDEFINI f () VT { REDI (x) }\nINVOCA f ()",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("VII")),
|
||||||
|
Defini(ID("f"), [], [Redi([ID("x")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [])),
|
||||||
|
]),
|
||||||
|
ValInt(7),
|
||||||
|
),
|
||||||
|
# parameter shadows outer variable inside function
|
||||||
|
(
|
||||||
|
"DESIGNA n VT I\nDEFINI f (n) VT { REDI (n * II) }\nINVOCA f (X)\nn",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("n"), Numeral("I")),
|
||||||
|
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [Numeral("X")])),
|
||||||
|
ExpressionStatement(ID("n")),
|
||||||
|
]),
|
||||||
|
ValInt(1),
|
||||||
|
),
|
||||||
|
# function aliasing: assign f to g, invoke via g
|
||||||
|
(
|
||||||
|
"DEFINI f (n) VT { REDI (n * II) }\nDESIGNA g VT f\nINVOCA g (V)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||||
|
Designa(ID("g"), ID("f")),
|
||||||
|
ExpressionStatement(Invoca(ID("g"), [Numeral("V")])),
|
||||||
|
]),
|
||||||
|
ValInt(10),
|
||||||
|
),
|
||||||
|
# alias is independent: redefining f doesn't affect g
|
||||||
|
(
|
||||||
|
"DEFINI f (n) VT { REDI (n * II) }\nDESIGNA g VT f\nDEFINI f (n) VT { REDI (n * III) }\nINVOCA g (V)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||||
|
Designa(ID("g"), ID("f")),
|
||||||
|
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("III"), "SYMBOL_TIMES")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("g"), [Numeral("V")])),
|
||||||
|
]),
|
||||||
|
ValInt(10),
|
||||||
|
),
|
||||||
|
# REDI inside SI exits function, skips remaining statements in block
|
||||||
|
(
|
||||||
|
"DEFINI f () VT {\nSI VERITAS TVNC {\nREDI (I)\nREDI (II)\n}\n}\nINVOCA f ()",
|
||||||
|
Program([],[
|
||||||
|
Defini(ID("f"), [], [SiStatement(Bool(True),[Redi([Numeral("I")]),Redi([Numeral("II")])],None)]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"),[]))
|
||||||
|
]),
|
||||||
|
ValInt(1),
|
||||||
|
),
|
||||||
|
# REDI inside DVM exits loop and function
|
||||||
|
(
|
||||||
|
"DEFINI f () VT {\nDESIGNA x VT I\nDVM FALSITAS FAC {\nREDI (x)\n}\n}\nINVOCA f ()",
|
||||||
|
Program([],[
|
||||||
|
Defini(ID("f"), [], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(Bool(False), [Redi([ID("x")])])
|
||||||
|
]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"),[]))
|
||||||
|
]),
|
||||||
|
ValInt(1),
|
||||||
|
),
|
||||||
|
# REDI inside PER exits loop and function
|
||||||
|
(
|
||||||
|
"DEFINI f () VT {\nPER x IN [I, II, III] FAC {\nSI x EST II TVNC {\nREDI (x)\n}\n}\n}\nINVOCA f ()",
|
||||||
|
Program([],[
|
||||||
|
Defini(ID("f"), [], [
|
||||||
|
PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("x"), [
|
||||||
|
SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"), [
|
||||||
|
Redi([ID("x")])
|
||||||
|
], None)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"),[]))
|
||||||
|
]),
|
||||||
|
ValInt(2),
|
||||||
|
),
|
||||||
|
# REDI inside nested loops exits all loops and function
|
||||||
|
(
|
||||||
|
"DEFINI f () VT {\nDESIGNA x VT I\nDVM FALSITAS FAC {\nDVM FALSITAS FAC {\nREDI (x)\n}\n}\n}\nINVOCA f ()",
|
||||||
|
Program([],[
|
||||||
|
Defini(ID("f"), [], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(Bool(False), [
|
||||||
|
DumStatement(Bool(False), [
|
||||||
|
Redi([ID("x")])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"),[]))
|
||||||
|
]),
|
||||||
|
ValInt(1),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestFunctionEdge(unittest.TestCase):
|
||||||
|
@parameterized.expand(function_edge_tests)
|
||||||
|
def test_function_edge(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
418
tests/02_test_con_flow.py
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Control flow ---
|
||||||
|
|
||||||
|
control_tests = [
|
||||||
|
# SI without ALIVD — true branch
|
||||||
|
("SI VERITAS TVNC { DESIGNA r VT I }\nr",
|
||||||
|
Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("I"))], None), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
# SI without ALIVD — false branch
|
||||||
|
("SI FALSITAS TVNC { DESIGNA r VT I }",
|
||||||
|
Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("I"))], None)]),
|
||||||
|
ValNul()),
|
||||||
|
# SI with ALIVD — true branch
|
||||||
|
("SI VERITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
# SI with ALIVD — false branch
|
||||||
|
("SI FALSITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(2)),
|
||||||
|
# SI with comparison — equal
|
||||||
|
("SI I EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
# SI with comparison — unequal
|
||||||
|
("SI I EST II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(2)),
|
||||||
|
# SI MINVS
|
||||||
|
("SI I MINVS II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_MINVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
# SI PLVS
|
||||||
|
("SI II PLVS I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
# ALIVD SI chain
|
||||||
|
(
|
||||||
|
"SI I EST II TVNC { DESIGNA r VT I } ALIVD SI I EST I TVNC { DESIGNA r VT II } ALIVD { DESIGNA r VT III }\nr",
|
||||||
|
Program([], [
|
||||||
|
SiStatement(
|
||||||
|
BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"),
|
||||||
|
[Designa(ID("r"), Numeral("I"))],
|
||||||
|
[SiStatement(
|
||||||
|
BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"),
|
||||||
|
[Designa(ID("r"), Numeral("II"))],
|
||||||
|
[Designa(ID("r"), Numeral("III"))],
|
||||||
|
)],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("r")),
|
||||||
|
]),
|
||||||
|
ValInt(2),
|
||||||
|
),
|
||||||
|
# DVM (while not): loops until condition is true
|
||||||
|
(
|
||||||
|
"DESIGNA x VT I\nDVM x EST III FAC {\nDESIGNA x VT x + I\n}\nx",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]),
|
||||||
|
ExpressionStatement(ID("x")),
|
||||||
|
]),
|
||||||
|
ValInt(3),
|
||||||
|
),
|
||||||
|
# DVM with ERVMPE — loop body prints (testing DIC + ERVMPE together)
|
||||||
|
("DESIGNA x VT I\nDVM FALSITAS FAC {\nDIC(x)\nERVMPE\n}",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DIC", [ID("x")])), Erumpe()]),
|
||||||
|
]),
|
||||||
|
ValStr("I"), "I\n"),
|
||||||
|
# AETERNVM is sugar for DVM FALSITAS — must produce the same AST.
|
||||||
|
("DESIGNA x VT I\nAETERNVM FAC {\nDIC(x)\nERVMPE\n}",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DIC", [ID("x")])), Erumpe()]),
|
||||||
|
]),
|
||||||
|
ValStr("I"), "I\n"),
|
||||||
|
# AETERNVM with counter + ERVMPE on condition
|
||||||
|
("DESIGNA x VT I\nAETERNVM FAC {\nSI x EST III TVNC { ERVMPE }\nDESIGNA x VT x + I\n}\nx",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(Bool(False), [
|
||||||
|
SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
|
||||||
|
]),
|
||||||
|
ExpressionStatement(ID("x")),
|
||||||
|
]),
|
||||||
|
ValInt(3)),
|
||||||
|
# AETERNVM with CONTINVA — skip printing III; ERVMPE after V.
|
||||||
|
# Return value is ValNul because the iteration that triggers ERVMPE runs
|
||||||
|
# Designa first (resetting last_val); we test on output, which is the point.
|
||||||
|
("DESIGNA x VT NVLLVS\nAETERNVM FAC {\nDESIGNA x VT x + I\nSI x PLVS V TVNC { ERVMPE }\nSI x EST III TVNC { CONTINVA }\nDIC(x)\n}",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Nullus()),
|
||||||
|
DumStatement(Bool(False), [
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
|
||||||
|
SiStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_PLVS"), [Erumpe()], None),
|
||||||
|
SiStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_EST"), [Continva()], None),
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [ID("x")])),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
ValNul(), "I\nII\nIV\nV\n"),
|
||||||
|
# REDI inside AETERNVM (inside DEFINI) — exits both loop and function
|
||||||
|
(
|
||||||
|
"DEFINI f () VT {\nDESIGNA x VT I\nAETERNVM FAC {\nREDI (x)\n}\n}\nINVOCA f ()",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("f"), [], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(Bool(False), [Redi([ID("x")])]),
|
||||||
|
]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [])),
|
||||||
|
]),
|
||||||
|
ValInt(1),
|
||||||
|
),
|
||||||
|
# PER foreach
|
||||||
|
("PER i IN [I, II, III] FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("III"), "I\nII\nIII\n"),
|
||||||
|
# DONICVM range loop
|
||||||
|
("DONICVM i VT I VSQVE V FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("V")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("V"), "I\nII\nIII\nIV\nV\n"),
|
||||||
|
# PER destructuring
|
||||||
|
("PER a, b IN [[I, II], [III, IV]] FAC { DIC(a + b) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]),
|
||||||
|
[ID("a"), ID("b")],
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")]))])]),
|
||||||
|
ValStr("VII"), "III\nVII\n"),
|
||||||
|
# PER destructuring: three variables
|
||||||
|
("PER a, b, c IN [[I, II, III]] FAC { DIC(a + b + c) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataArray([DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]),
|
||||||
|
[ID("a"), ID("b"), ID("c")],
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"), ID("c"), "SYMBOL_PLUS")]))])]),
|
||||||
|
ValStr("VI"), "VI\n"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestControl(unittest.TestCase):
|
||||||
|
@parameterized.expand(control_tests)
|
||||||
|
def test_control(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- Loop edge cases ---
|
||||||
|
|
||||||
|
loop_edge_tests = [
|
||||||
|
# [III VSQVE III] = [3] — single iteration
|
||||||
|
("DONICVM i VT III VSQVE III FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(DataRangeArray(Numeral("III"), Numeral("III")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("III"), "III\n"),
|
||||||
|
# empty array — body never runs
|
||||||
|
("PER i IN [] FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(DataArray([]), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValNul(), ""),
|
||||||
|
# PER breaks on element 2 — last assigned i is 2
|
||||||
|
("PER i IN [I, II, III] FAC { SI i EST II TVNC { ERVMPE } }\ni",
|
||||||
|
Program([], [
|
||||||
|
PerStatement(
|
||||||
|
DataArray([Numeral("I"), Numeral("II"), Numeral("III")]),
|
||||||
|
ID("i"),
|
||||||
|
[SiStatement(BinOp(ID("i"), Numeral("II"), "KEYWORD_EST"), [Erumpe()], None)],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("i")),
|
||||||
|
]),
|
||||||
|
ValInt(2), ""),
|
||||||
|
# nested DVM: inner always breaks; outer runs until btr==3
|
||||||
|
("DESIGNA btr VT I\nDVM btr EST III FAC {\nDVM FALSITAS FAC {\nERVMPE\n}\nDESIGNA btr VT btr + I\n}\nbtr",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("btr"), Numeral("I")),
|
||||||
|
DumStatement(
|
||||||
|
BinOp(ID("btr"), Numeral("III"), "KEYWORD_EST"),
|
||||||
|
[DumStatement(Bool(False), [Erumpe()]), Designa(ID("btr"), BinOp(ID("btr"), Numeral("I"), "SYMBOL_PLUS"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("btr")),
|
||||||
|
]),
|
||||||
|
ValInt(3), ""),
|
||||||
|
# nested PER: inner always breaks on first element; outer completes both iterations
|
||||||
|
# cnt starts at 1, increments twice → 3
|
||||||
|
("DESIGNA cnt VT I\nPER i IN [I, II] FAC {\nPER k IN [I, II] FAC {\nERVMPE\n}\nDESIGNA cnt VT cnt + I\n}\ncnt",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("cnt"), Numeral("I")),
|
||||||
|
PerStatement(
|
||||||
|
DataArray([Numeral("I"), Numeral("II")]),
|
||||||
|
ID("i"),
|
||||||
|
[PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"), [Erumpe()]),
|
||||||
|
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("cnt")),
|
||||||
|
]),
|
||||||
|
ValInt(3), ""),
|
||||||
|
# PER with CONTINVA: skip odd numbers, sum evens
|
||||||
|
# [I,II,III,IV] → skip I and III; cnt increments on II and IV → cnt = III
|
||||||
|
("DESIGNA cnt VT I\nPER i IN [I, II, III, IV] FAC {\nSI i EST I AVT i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("cnt"), Numeral("I")),
|
||||||
|
PerStatement(
|
||||||
|
DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV")]),
|
||||||
|
ID("i"),
|
||||||
|
[SiStatement(BinOp(BinOp(ID("i"), Numeral("I"), "KEYWORD_EST"), BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), "KEYWORD_AVT"), [Continva()], None),
|
||||||
|
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("cnt")),
|
||||||
|
]),
|
||||||
|
ValInt(3), ""),
|
||||||
|
# DVM with CONTINVA: skip body when x is II, increment regardless
|
||||||
|
# x goes 1→2→3; on x=2 we continue (no DIC); DIC fires for x=1 and x=3
|
||||||
|
("DESIGNA x VT I\nDVM x EST IV FAC {\nSI x EST II TVNC { DESIGNA x VT x + I\nCONTINVA }\nDIC(x)\nDESIGNA x VT x + I\n}\nx",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(
|
||||||
|
BinOp(ID("x"), Numeral("IV"), "KEYWORD_EST"),
|
||||||
|
[SiStatement(BinOp(ID("x"), Numeral("II"), "KEYWORD_EST"),
|
||||||
|
[Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")), Continva()], None),
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [ID("x")])),
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("x")),
|
||||||
|
]),
|
||||||
|
ValInt(4), "I\nIII\n"),
|
||||||
|
# nested PER: CONTINVA in inner only skips rest of inner body; outer still increments
|
||||||
|
("DESIGNA cnt VT I\nPER i IN [I, II] FAC {\nPER k IN [I, II] FAC {\nCONTINVA\nDESIGNA cnt VT cnt + I\n}\nDESIGNA cnt VT cnt + I\n}\ncnt",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("cnt"), Numeral("I")),
|
||||||
|
PerStatement(
|
||||||
|
DataArray([Numeral("I"), Numeral("II")]),
|
||||||
|
ID("i"),
|
||||||
|
[PerStatement(DataArray([Numeral("I"), Numeral("II")]), ID("k"),
|
||||||
|
[Continva(), Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))]),
|
||||||
|
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("cnt")),
|
||||||
|
]),
|
||||||
|
ValInt(3), ""),
|
||||||
|
# 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 FAC {\nSI i EST III TVNC { CONTINVA }\nDESIGNA cnt VT cnt + I\n}\ncnt",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("cnt"), Numeral("I")),
|
||||||
|
PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("IV")),
|
||||||
|
ID("i"),
|
||||||
|
[SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Continva()], None),
|
||||||
|
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("cnt")),
|
||||||
|
]),
|
||||||
|
ValInt(4)),
|
||||||
|
# DVM condition true from start — body never runs
|
||||||
|
("DESIGNA x VT I\nDVM VERITAS FAC {\nDESIGNA x VT x + I\n}\nx",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(Bool(True), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]),
|
||||||
|
ExpressionStatement(ID("x")),
|
||||||
|
]),
|
||||||
|
ValInt(1), ""),
|
||||||
|
# two iterations: [I VSQVE II] = [1, 2]
|
||||||
|
("DONICVM i VT I VSQVE II FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("II")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("II"), "I\nII\n"),
|
||||||
|
# single iteration: [I VSQVE I] = [1]
|
||||||
|
("DONICVM i VT I VSQVE I FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(DataRangeArray(Numeral("I"), Numeral("I")), ID("i"), [ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("I"), "I\n"),
|
||||||
|
# empty range: [V VSQVE I] = []
|
||||||
|
("DESIGNA x VT NVLLVS\nDONICVM i VT V VSQVE I FAC { 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(), ""),
|
||||||
|
# PER destructuring with ERVMPE
|
||||||
|
("DESIGNA r VT I\nPER a, b IN [[I, II], [III, IV], [V, VI]] FAC {\nSI a EST III TVNC { ERVMPE }\nDESIGNA r VT r + a + b\n}\nr",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("r"), Numeral("I")),
|
||||||
|
PerStatement(
|
||||||
|
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), DataArray([Numeral("V"), Numeral("VI")])]),
|
||||||
|
[ID("a"), ID("b")],
|
||||||
|
[SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None),
|
||||||
|
Designa(ID("r"), BinOp(BinOp(ID("r"), ID("a"), "SYMBOL_PLUS"), ID("b"), "SYMBOL_PLUS"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("r")),
|
||||||
|
]),
|
||||||
|
ValInt(4)), # 1 + 1 + 2 = 4, breaks before [III, IV]
|
||||||
|
# PER destructuring with REDI
|
||||||
|
("DEFINI f () VT {\nPER a, b IN [[I, II], [III, IV]] FAC {\nSI a EST III TVNC { REDI (b) }\n}\n}\nINVOCA f ()",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("f"), [],
|
||||||
|
[PerStatement(
|
||||||
|
DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])]),
|
||||||
|
[ID("a"), ID("b")],
|
||||||
|
[SiStatement(BinOp(ID("a"), Numeral("III"), "KEYWORD_EST"), [Redi([ID("b")])], None)],
|
||||||
|
)]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [])),
|
||||||
|
]),
|
||||||
|
ValInt(4)), # returns b=IV when a=III
|
||||||
|
# DONICVM GRADV II, endpoint hit exactly: [I, III, V]
|
||||||
|
("DONICVM i VT I VSQVE V GRADV II FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("V"), Numeral("II")),
|
||||||
|
ID("i"),
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("V"), "I\nIII\nV\n"),
|
||||||
|
# DONICVM GRADV II, endpoint overshot: VI excluded, stops at V
|
||||||
|
("DONICVM i VT I VSQVE VI GRADV II FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("VI"), Numeral("II")),
|
||||||
|
ID("i"),
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("V"), "I\nIII\nV\n"),
|
||||||
|
# DONICVM GRADV I is equivalent to default step
|
||||||
|
("DONICVM i VT I VSQVE III GRADV I FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("III"), Numeral("I")),
|
||||||
|
ID("i"),
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("III"), "I\nII\nIII\n"),
|
||||||
|
# DONICVM descending by I
|
||||||
|
("CVM SVBNVLLA\nDONICVM i VT V VSQVE I GRADV - I FAC { DIC(i) }",
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("V"), Numeral("I"), UnaryMinus(Numeral("I"))),
|
||||||
|
ID("i"),
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("I"), "V\nIV\nIII\nII\nI\n"),
|
||||||
|
# DONICVM descending by II, endpoint overshot
|
||||||
|
("CVM SVBNVLLA\nDONICVM i VT X VSQVE I GRADV - II FAC { DIC(i) }",
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("X"), Numeral("I"), UnaryMinus(Numeral("II"))),
|
||||||
|
ID("i"),
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("II"), "X\nVIII\nVI\nIV\nII\n"),
|
||||||
|
# DONICVM with step bound to a variable
|
||||||
|
("DESIGNA s VT II\nDONICVM i VT I VSQVE V GRADV s FAC { DIC(i) }",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("s"), Numeral("II")),
|
||||||
|
PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("V"), ID("s")),
|
||||||
|
ID("i"),
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("V"), "I\nIII\nV\n"),
|
||||||
|
# DONICVM from == to with nonzero step: single iteration
|
||||||
|
("DONICVM i VT III VSQVE III GRADV II FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("III"), Numeral("III"), Numeral("II")),
|
||||||
|
ID("i"),
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("III"), "III\n"),
|
||||||
|
# DONICVM direction mismatch — negative step with ascending bounds: body never runs
|
||||||
|
("CVM SVBNVLLA\nDESIGNA x VT NVLLVS\nDONICVM i VT I VSQVE X GRADV - I FAC { DESIGNA x VT x + i }\nx",
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [
|
||||||
|
Designa(ID("x"), Nullus()),
|
||||||
|
PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("X"), UnaryMinus(Numeral("I"))),
|
||||||
|
ID("i"),
|
||||||
|
[Designa(ID("x"), BinOp(ID("x"), ID("i"), "SYMBOL_PLUS"))]),
|
||||||
|
ExpressionStatement(ID("x"))]),
|
||||||
|
ValNul(), ""),
|
||||||
|
# Range-array literal with GRADV used via PER
|
||||||
|
("PER i IN [I VSQVE V GRADV II] FAC { DIC(i) }",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("V"), Numeral("II")),
|
||||||
|
ID("i"),
|
||||||
|
[ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("V"), "I\nIII\nV\n"),
|
||||||
|
# DONICVM GRADV II with CONTINVA: skip value V, print the others
|
||||||
|
("DONICVM i VT I VSQVE IX GRADV II FAC {\nSI i EST V TVNC { CONTINVA }\nDIC(i)\n}",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("IX"), Numeral("II")),
|
||||||
|
ID("i"),
|
||||||
|
[SiStatement(BinOp(ID("i"), Numeral("V"), "KEYWORD_EST"), [Continva()], None),
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("IX"), "I\nIII\nVII\nIX\n"),
|
||||||
|
# DONICVM GRADV II with ERVMPE: stop at V (last successful DIC was III)
|
||||||
|
("DONICVM i VT I VSQVE IX GRADV II FAC {\nSI i EST V TVNC { ERVMPE }\nDIC(i)\n}",
|
||||||
|
Program([], [PerStatement(
|
||||||
|
DataRangeArray(Numeral("I"), Numeral("IX"), Numeral("II")),
|
||||||
|
ID("i"),
|
||||||
|
[SiStatement(BinOp(ID("i"), Numeral("V"), "KEYWORD_EST"), [Erumpe()], None),
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [ID("i")]))])]),
|
||||||
|
ValStr("III"), "I\nIII\n"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestLoopEdge(unittest.TestCase):
|
||||||
|
@parameterized.expand(loop_edge_tests)
|
||||||
|
def test_loop_edge(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- SI/DVM: boolean condition enforcement ---
|
||||||
|
|
||||||
|
dvm_bool_condition_tests = [
|
||||||
|
# DVM exits when condition becomes true (boolean comparison)
|
||||||
|
(
|
||||||
|
"DESIGNA x VT I\nDVM x PLVS III FAC {\nDESIGNA x VT x + I\n}\nx",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(BinOp(ID("x"), Numeral("III"), "KEYWORD_PLVS"), [Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS"))]),
|
||||||
|
ExpressionStatement(ID("x")),
|
||||||
|
]),
|
||||||
|
ValInt(4),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDvmBoolCondition(unittest.TestCase):
|
||||||
|
@parameterized.expand(dvm_bool_condition_tests)
|
||||||
|
def test_dvm_bool_condition(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
226
tests/03_test_builtins.py
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Builtins ---
|
||||||
|
|
||||||
|
builtin_tests = [
|
||||||
|
("AVDI_NVMERVS()", Program([], [ExpressionStatement(BuiltIn("AVDI_NVMERVS", []))]), ValInt(3), "", ["III"]),
|
||||||
|
("AVDI_NVMERVS()", Program([], [ExpressionStatement(BuiltIn("AVDI_NVMERVS", []))]), ValInt(10), "", ["X"]),
|
||||||
|
("CVM FORS\nDESIGNA a VT [I, II, III]\nDIC(a[FORTVITVS_NVMERVS(I, LONGITVDO(a))])", Program([ModuleCall("FORS")], [Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), ExpressionStatement(BuiltIn("DIC", [ArrayIndex(ID("a"), BuiltIn("FORTVITVS_NVMERVS", [Numeral("I"), BuiltIn("LONGITVDO", [ID("a")])]))]))]), ValStr("I"), "I\n"),
|
||||||
|
("AVDI()", Program([], [ExpressionStatement(BuiltIn("AVDI", []))]), ValStr("hello"), "", ["hello"]),
|
||||||
|
("LONGITVDO([I, II, III])", Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValInt(3)),
|
||||||
|
("LONGITVDO([])", Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataArray([])]))]), ValInt(0)),
|
||||||
|
('LONGITVDO("salve")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("salve")]))]), ValInt(5)),
|
||||||
|
('LONGITVDO("")', Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [String("")]))]), ValInt(0)),
|
||||||
|
("CVM FORS\nDIC(FORTVITA_ELECTIO([I, II, III]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("FORTVITA_ELECTIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])])]))]), ValStr("I"), "I\n"),
|
||||||
|
("CVM FORS\nSEMEN(XLII)\nDIC(FORTVITVS_NVMERVS(I, C))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("FORTVITVS_NVMERVS", [Numeral("I"), Numeral("C")])]))]), ValStr("XXXIII"), "XXXIII\n"),
|
||||||
|
# DECIMATIO: seed 42, 10 elements → removes 1 (element III)
|
||||||
|
("CVM FORS\nSEMEN(XLII)\nDIC(DECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X")])])]))]), ValStr("[I II IV V VI VII VIII IX X]"), "[I II IV V VI VII VIII IX X]\n"),
|
||||||
|
# DECIMATIO: seed 1, 3 elements → 3//10=0, nothing removed
|
||||||
|
("CVM FORS\nSEMEN(I)\nDIC(DECIMATIO([I, II, III]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("I")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])])]))]), ValStr("[I II III]"), "[I II III]\n"),
|
||||||
|
# DECIMATIO: empty array → empty array
|
||||||
|
("CVM FORS\nDIC(DECIMATIO([]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([])])]))]), ValStr("[]"), "[]\n"),
|
||||||
|
# DECIMATIO: seed 42, 20 elements → removes 2 (elements XIII and XII)
|
||||||
|
("CVM FORS\nSEMEN(XLII)\nDIC(DECIMATIO([I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, XIII, XIV, XV, XVI, XVII, XVIII, XIX, XX]))", Program([ModuleCall("FORS")], [ExpressionStatement(BuiltIn("SEMEN", [Numeral("XLII")])), ExpressionStatement(BuiltIn("DIC", [BuiltIn("DECIMATIO", [DataArray([Numeral("I"), Numeral("II"), Numeral("III"), Numeral("IV"), Numeral("V"), Numeral("VI"), Numeral("VII"), Numeral("VIII"), Numeral("IX"), Numeral("X"), Numeral("XI"), Numeral("XII"), Numeral("XIII"), Numeral("XIV"), Numeral("XV"), Numeral("XVI"), Numeral("XVII"), Numeral("XVIII"), Numeral("XIX"), Numeral("XX")])])]))]), ValStr("[I II III IV V VI VII VIII IX X XI XIV XV XVI XVII XVIII XIX XX]"), "[I II III IV V VI VII VIII IX X XI XIV XV XVI XVII XVIII XIX XX]\n"),
|
||||||
|
# SENATVS: majority true → VERITAS
|
||||||
|
("SENATVS(VERITAS, VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False)]))]), ValBool(True)),
|
||||||
|
# SENATVS: majority false → FALSITAS
|
||||||
|
("SENATVS(FALSITAS, FALSITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False), Bool(False), Bool(True)]))]), ValBool(False)),
|
||||||
|
# SENATVS: tie → FALSITAS
|
||||||
|
("SENATVS(VERITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(False)]))]), ValBool(False)),
|
||||||
|
# SENATVS: 4-arg tie → FALSITAS
|
||||||
|
("SENATVS(VERITAS, VERITAS, FALSITAS, FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(False), Bool(False)]))]), ValBool(False)),
|
||||||
|
# SENATVS: single true → VERITAS
|
||||||
|
("SENATVS(VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True)]))]), ValBool(True)),
|
||||||
|
# SENATVS: single false → FALSITAS
|
||||||
|
("SENATVS(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(False)]))]), ValBool(False)),
|
||||||
|
# SENATVS: empty → FALSITAS (vacuous)
|
||||||
|
("SENATVS()", Program([], [ExpressionStatement(BuiltIn("SENATVS", []))]), ValBool(False)),
|
||||||
|
# SENATVS: all true <20><> VERITAS
|
||||||
|
("SENATVS(VERITAS, VERITAS, VERITAS, VERITAS, VERITAS)", Program([], [ExpressionStatement(BuiltIn("SENATVS", [Bool(True), Bool(True), Bool(True), Bool(True), Bool(True)]))]), ValBool(True)),
|
||||||
|
# SENATVS: array input, majority true → VERITAS
|
||||||
|
("SENATVS([VERITAS, VERITAS, FALSITAS])", Program([], [ExpressionStatement(BuiltIn("SENATVS", [DataArray([Bool(True), Bool(True), Bool(False)])]))]), ValBool(True)),
|
||||||
|
# SENATVS: array input, majority false → FALSITAS
|
||||||
|
("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")),
|
||||||
|
# LITTERA: integer → Roman numeral
|
||||||
|
("LITTERA(V)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Numeral("V")]))]), ValStr("V")),
|
||||||
|
# LITTERA: larger integer
|
||||||
|
("LITTERA(MCMLXXXIV)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Numeral("MCMLXXXIV")]))]), ValStr("MCMLXXXIV")),
|
||||||
|
# LITTERA: zero → NVLLVS
|
||||||
|
("LITTERA(I - I)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("NVLLVS")),
|
||||||
|
# LITTERA: string passthrough
|
||||||
|
('LITTERA("salve")', Program([], [ExpressionStatement(BuiltIn("LITTERA", [String("salve")]))]), ValStr("salve")),
|
||||||
|
# LITTERA: empty string
|
||||||
|
('LITTERA("")', Program([], [ExpressionStatement(BuiltIn("LITTERA", [String("")]))]), ValStr("")),
|
||||||
|
# LITTERA: VERITAS
|
||||||
|
("LITTERA(VERITAS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Bool(True)]))]), ValStr("VERITAS")),
|
||||||
|
# LITTERA: FALSITAS
|
||||||
|
("LITTERA(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Bool(False)]))]), ValStr("FALSITAS")),
|
||||||
|
# LITTERA: NVLLVS
|
||||||
|
("LITTERA(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("LITTERA", [Nullus()]))]), ValStr("NVLLVS")),
|
||||||
|
# LITTERA: array of integers
|
||||||
|
("LITTERA([I, II, III])", Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataArray([Numeral("I"), Numeral("II"), Numeral("III")])]))]), ValStr("[I II III]")),
|
||||||
|
# LITTERA: empty array
|
||||||
|
("LITTERA([])", Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataArray([])]))]), ValStr("[]")),
|
||||||
|
# LITTERA: dict with string keys (make_string emits bare keys, not quoted — matches DIC's output)
|
||||||
|
('LITTERA(TABVLA {"a" VT I})', Program([], [ExpressionStatement(BuiltIn("LITTERA", [DataDict([(String("a"), Numeral("I"))])]))]), ValStr('{a VT I}')),
|
||||||
|
# LITTERA: fraction (requires FRACTIO module)
|
||||||
|
("CVM FRACTIO\nLITTERA(S)", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("LITTERA", [Fractio("S")]))]), ValStr("S")),
|
||||||
|
# LITTERA: negative integer
|
||||||
|
("CVM SVBNVLLA\nLITTERA(-III)", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("LITTERA", [UnaryMinus(Numeral("III"))]))]), ValStr("-III")),
|
||||||
|
# LITTERA: round-trips through NVMERVS
|
||||||
|
('NVMERVS(LITTERA(VII))', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [BuiltIn("LITTERA", [Numeral("VII")])]))]), ValInt(7)),
|
||||||
|
# LITTERA: concatenated with a string via &
|
||||||
|
('LITTERA(V) & " est quinque"', Program([], [ExpressionStatement(BinOp(BuiltIn("LITTERA", [Numeral("V")]), String(" est quinque"), "SYMBOL_AMPERSAND"))]), ValStr("V est quinque")),
|
||||||
|
# LITTERA: via variable
|
||||||
|
("DESIGNA x VT IX\nLITTERA(x)", Program([], [Designa(ID("x"), Numeral("IX")), ExpressionStatement(BuiltIn("LITTERA", [ID("x")]))]), ValStr("IX")),
|
||||||
|
# QVAERE: basic literal match
|
||||||
|
('QVAERE("ab", "abcabc")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("ab"), String("abcabc")]))]), ValList([ValStr("ab"), ValStr("ab")])),
|
||||||
|
# QVAERE: no match → empty list
|
||||||
|
('QVAERE("xyz", "abc")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("xyz"), String("abc")]))]), ValList([])),
|
||||||
|
# QVAERE: regex character class
|
||||||
|
('QVAERE("[a-z]+", "abc123def")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("[a-z]+"), String("abc123def")]))]), ValList([ValStr("abc"), ValStr("def")])),
|
||||||
|
# QVAERE: empty text → empty list
|
||||||
|
('QVAERE("a", "")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a"), String("")]))]), ValList([])),
|
||||||
|
# QVAERE: capture groups still return full match
|
||||||
|
('QVAERE("(a)(b)", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(a)(b)"), String("ab")]))]), ValList([ValStr("ab")])),
|
||||||
|
# QVAERE: empty pattern matches between every character
|
||||||
|
('QVAERE("", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String(""), String("ab")]))]), ValList([ValStr(""), ValStr(""), ValStr("")])),
|
||||||
|
# QVAERE: dot matches any character
|
||||||
|
('QVAERE(".", "ab")', Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("."), String("ab")]))]), ValList([ValStr("a"), ValStr("b")])),
|
||||||
|
# SVBSTITVE: basic literal replacement
|
||||||
|
('SVBSTITVE("a", "b", "aaa")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String("b"), String("aaa")]))]), ValStr("bbb")),
|
||||||
|
# SVBSTITVE: regex character class
|
||||||
|
('SVBSTITVE("[0-9]+", "N", "abc123def456")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("[0-9]+"), String("N"), String("abc123def456")]))]), ValStr("abcNdefN")),
|
||||||
|
# SVBSTITVE: no match → string unchanged
|
||||||
|
('SVBSTITVE("x", "y", "abc")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("x"), String("y"), String("abc")]))]), ValStr("abc")),
|
||||||
|
# SVBSTITVE: empty replacement (deletion)
|
||||||
|
('SVBSTITVE("a", "", "banana")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String(""), String("banana")]))]), ValStr("bnn")),
|
||||||
|
# SVBSTITVE: empty text → empty string
|
||||||
|
('SVBSTITVE("a", "b", "")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a"), String("b"), String("")]))]), ValStr("")),
|
||||||
|
# SVBSTITVE: dot matches any character
|
||||||
|
('SVBSTITVE(".", "x", "ab")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("."), String("x"), String("ab")]))]), ValStr("xx")),
|
||||||
|
# SVBSTITVE: backreference swaps two groups (Roman numerals)
|
||||||
|
('SVBSTITVE("(a)(b)", "\\II\\I", "ab")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)"), String("\\II\\I"), String("ab")]))]), ValStr("ba")),
|
||||||
|
# SVBSTITVE: backreference with unmatched group (ignored)
|
||||||
|
('SVBSTITVE("(a)(b)?", "\\I\\II", "a")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)?"), String("\\I\\II"), String("a")]))]), ValStr("a")),
|
||||||
|
# SVBSTITVE: Roman numeral quantifier in pattern
|
||||||
|
("SVBSTITVE('a{III}', 'x', 'aaa')", Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("a{III}"), String("x"), String("aaa")]))]), ValStr("x")),
|
||||||
|
# QVAERE: Roman numeral quantifier — exact repetition
|
||||||
|
("QVAERE('a{III}', 'aaaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{III}"), String("aaaa")]))]), ValList([ValStr("aaa")])),
|
||||||
|
# QVAERE: Roman numeral quantifier — range
|
||||||
|
("QVAERE('a{II,III}', 'aaaaaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{II,III}"), String("aaaaaa")]))]), ValList([ValStr("aaa"), ValStr("aaa")])),
|
||||||
|
# QVAERE: Roman numeral quantifier — at-least
|
||||||
|
("QVAERE('a{II,}', 'a aa aaa')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("a{II,}"), String("a aa aaa")]))]), ValList([ValStr("aa"), ValStr("aaa")])),
|
||||||
|
# QVAERE: pattern backreference — repeated character
|
||||||
|
("QVAERE('(.)\\I', 'aabcdd')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(.)\\I"), String("aabcdd")]))]), ValList([ValStr("aa"), ValStr("dd")])),
|
||||||
|
# QVAERE: pattern backreference — repeated group
|
||||||
|
("QVAERE('(..)\\I', 'ababcc')", Program([], [ExpressionStatement(BuiltIn("QVAERE", [String("(..)\\I"), String("ababcc")]))]), ValList([ValStr("abab")])),
|
||||||
|
# NVMERVS: basic conversion
|
||||||
|
('NVMERVS("XIV")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("XIV")]))]), ValInt(14)),
|
||||||
|
# NVMERVS: simple single numeral
|
||||||
|
('NVMERVS("I")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("I")]))]), ValInt(1)),
|
||||||
|
# NVMERVS: large numeral
|
||||||
|
('NVMERVS("MMMCMXCIX")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("MMMCMXCIX")]))]), ValInt(3999)),
|
||||||
|
# NVMERVS: subtractive form
|
||||||
|
('NVMERVS("IX")', Program([], [ExpressionStatement(BuiltIn("NVMERVS", [String("IX")]))]), ValInt(9)),
|
||||||
|
# SCINDE: basic split
|
||||||
|
('SCINDE("a,b,c", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("a,b,c"), String(",")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])),
|
||||||
|
# SCINDE: no match (delimiter not found)
|
||||||
|
('SCINDE("abc", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("abc"), String(",")]))]), ValList([ValStr("abc")])),
|
||||||
|
# SCINDE: empty string
|
||||||
|
('SCINDE("", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String(""), String(",")]))]), ValList([ValStr("")])),
|
||||||
|
# SCINDE: multi-char delimiter
|
||||||
|
('SCINDE("a::b::c", "::")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("a::b::c"), String("::")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])),
|
||||||
|
# SCINDE: delimiter at edges
|
||||||
|
('SCINDE(",a,", ",")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String(",a,"), String(",")]))]), ValList([ValStr(""), ValStr("a"), ValStr("")])),
|
||||||
|
# SCINDE: empty delimiter (split into chars)
|
||||||
|
('SCINDE("abc", "")', Program([], [ExpressionStatement(BuiltIn("SCINDE", [String("abc"), String("")]))]), ValList([ValStr("a"), ValStr("b"), ValStr("c")])),
|
||||||
|
# MAIVSCVLA: basic lowercase→uppercase
|
||||||
|
('MAIVSCVLA("hello")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("hello")]))]), ValStr("HELLO")),
|
||||||
|
# MAIVSCVLA: mixed case
|
||||||
|
('MAIVSCVLA("HeLLo")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("HeLLo")]))]), ValStr("HELLO")),
|
||||||
|
# MAIVSCVLA: already uppercase (idempotence)
|
||||||
|
('MAIVSCVLA("HELLO")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("HELLO")]))]), ValStr("HELLO")),
|
||||||
|
# MAIVSCVLA: empty string
|
||||||
|
('MAIVSCVLA("")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("")]))]), ValStr("")),
|
||||||
|
# MAIVSCVLA: Roman-numeral-shaped ASCII (case-only, not numeral-aware)
|
||||||
|
('MAIVSCVLA("xii")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("xii")]))]), ValStr("XII")),
|
||||||
|
# MAIVSCVLA: non-alphabetic chars unchanged
|
||||||
|
('MAIVSCVLA("a,b!1")', Program([], [ExpressionStatement(BuiltIn("MAIVSCVLA", [String("a,b!1")]))]), ValStr("A,B!1")),
|
||||||
|
# MAIVSCVLA: via variable
|
||||||
|
('DESIGNA s VT "foo"\nDIC(MAIVSCVLA(s))', Program([], [Designa(ID("s"), String("foo")), ExpressionStatement(BuiltIn("DIC", [BuiltIn("MAIVSCVLA", [ID("s")])]))]), ValStr("FOO"), "FOO\n"),
|
||||||
|
# MAIVSCVLA: concatenated with &
|
||||||
|
('MAIVSCVLA("hi") & "!"', Program([], [ExpressionStatement(BinOp(BuiltIn("MAIVSCVLA", [String("hi")]), String("!"), "SYMBOL_AMPERSAND"))]), ValStr("HI!")),
|
||||||
|
# MINVSCVLA: basic uppercase→lowercase
|
||||||
|
('MINVSCVLA("HELLO")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("HELLO")]))]), ValStr("hello")),
|
||||||
|
# MINVSCVLA: mixed case
|
||||||
|
('MINVSCVLA("HeLLo")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("HeLLo")]))]), ValStr("hello")),
|
||||||
|
# MINVSCVLA: already lowercase (idempotence)
|
||||||
|
('MINVSCVLA("hello")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("hello")]))]), ValStr("hello")),
|
||||||
|
# MINVSCVLA: empty string
|
||||||
|
('MINVSCVLA("")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("")]))]), ValStr("")),
|
||||||
|
# MINVSCVLA: Roman-numeral-shaped ASCII (case-only, not numeral-aware)
|
||||||
|
('MINVSCVLA("XII")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("XII")]))]), ValStr("xii")),
|
||||||
|
# MINVSCVLA: non-alphabetic chars unchanged
|
||||||
|
('MINVSCVLA("A,B!1")', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [String("A,B!1")]))]), ValStr("a,b!1")),
|
||||||
|
# MINVSCVLA round-trips MAIVSCVLA on lowercase input
|
||||||
|
('MINVSCVLA(MAIVSCVLA("hi"))', Program([], [ExpressionStatement(BuiltIn("MINVSCVLA", [BuiltIn("MAIVSCVLA", [String("hi")])]))]), ValStr("hi")),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestBuiltins(unittest.TestCase):
|
||||||
|
@parameterized.expand(builtin_tests)
|
||||||
|
def test_builtins(self, source, nodes, value, output="", input_lines=[]):
|
||||||
|
run_test(self, source, nodes, value, output, input_lines)
|
||||||
175
tests/04_test_numerals.py
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Roman numeral utilities ---
|
||||||
|
|
||||||
|
class TestNumerals(unittest.TestCase):
|
||||||
|
# num_to_int: valid cases
|
||||||
|
def test_simple_numerals(self):
|
||||||
|
for s, n in [("I",1),("V",5),("X",10),("L",50),("C",100),("D",500),("M",1000)]:
|
||||||
|
self.assertEqual(num_to_int(s, False), n)
|
||||||
|
|
||||||
|
def test_subtractive_forms(self):
|
||||||
|
for s, n in [("IV",4),("IX",9),("XL",40),("XC",90),("CD",400),("CM",900)]:
|
||||||
|
self.assertEqual(num_to_int(s, False), n)
|
||||||
|
|
||||||
|
def test_complex_numerals(self):
|
||||||
|
for s, n in [("XLII",42),("XCIX",99),("MCMXCIX",1999),("MMMCMXCIX",3999)]:
|
||||||
|
self.assertEqual(num_to_int(s, False), n)
|
||||||
|
|
||||||
|
# num_to_int: invalid cases
|
||||||
|
def test_four_in_a_row_raises(self):
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
num_to_int("IIII", False)
|
||||||
|
|
||||||
|
def test_four_x_in_a_row_raises(self):
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
num_to_int("XXXX", False)
|
||||||
|
|
||||||
|
def test_invalid_subtractive_iix_raises(self):
|
||||||
|
# IIX is non-standard — I can't appear twice before X
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
num_to_int("IIX", False)
|
||||||
|
|
||||||
|
def test_invalid_subtractive_im_raises(self):
|
||||||
|
# I can only subtract from V and X, not M
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
num_to_int("IM", False)
|
||||||
|
|
||||||
|
def test_negative_without_svbnvlla_raises(self):
|
||||||
|
with self.assertRaises(CentvrionError):
|
||||||
|
num_to_int("-IV", False)
|
||||||
|
|
||||||
|
def test_negative_with_svbnvlla(self):
|
||||||
|
self.assertEqual(num_to_int("-IV", False, True), -4)
|
||||||
|
self.assertEqual(num_to_int("-XLII", False, True), -42)
|
||||||
|
|
||||||
|
# int_to_num: valid cases
|
||||||
|
def test_int_to_num(self):
|
||||||
|
for n, s in [(0,"NVLLVS"),(1,"I"),(4,"IV"),(9,"IX"),(40,"XL"),(42,"XLII"),(3999,"MMMCMXCIX")]:
|
||||||
|
self.assertEqual(int_to_num(n, False), s)
|
||||||
|
|
||||||
|
def test_int_to_num_above_3999_raises(self):
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
int_to_num(4000, False)
|
||||||
|
|
||||||
|
def test_int_to_num_magnvm(self):
|
||||||
|
# 4000 with MAGNVM enabled
|
||||||
|
self.assertEqual(int_to_num(4000, True), "MV_")
|
||||||
|
|
||||||
|
def test_num_to_int_magnvm_required(self):
|
||||||
|
# Numbers parsed from strings with _ require MAGNVM
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
num_to_int("V_", False)
|
||||||
|
|
||||||
|
# --- Arithmetic: edge cases ---
|
||||||
|
|
||||||
|
arithmetic_edge_tests = [
|
||||||
|
("I - I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"))]), ValInt(0)), # result zero
|
||||||
|
("I - V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-4)), # negative result
|
||||||
|
("I / V", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)), # integer division → 0
|
||||||
|
("M * M", Program([], [ExpressionStatement(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_TIMES"))]), ValInt(1000000)), # large intermediate (not displayed)
|
||||||
|
("(I + II) * (IV - I)", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"), BinOp(Numeral("IV"), Numeral("I"), "SYMBOL_MINUS"), "SYMBOL_TIMES"))]), ValInt(9)), # nested parens
|
||||||
|
# NVLLVS coerces to 0 in integer arithmetic
|
||||||
|
("NVLLVS + V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_PLUS"))]), ValInt(5)),
|
||||||
|
("V + NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_PLUS"))]), ValInt(5)),
|
||||||
|
("NVLLVS + NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_PLUS"))]), ValNul()),
|
||||||
|
("NVLLVS - V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_MINUS"))]), ValInt(-5)),
|
||||||
|
("V - NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "SYMBOL_MINUS"))]), ValInt(5)),
|
||||||
|
# NVLLVS coerces to 0 in modulo and division
|
||||||
|
("NVLLVS RELIQVVM V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_RELIQVVM"))]), ValInt(0)),
|
||||||
|
("NVLLVS / V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "SYMBOL_DIVIDE"))]), ValInt(0)),
|
||||||
|
# floored division and modulo with negative operands (Python semantics)
|
||||||
|
("CVM SVBNVLLA\n- VII RELIQVVM III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), Numeral("III"), "KEYWORD_RELIQVVM"))]), ValInt(2)),
|
||||||
|
("CVM SVBNVLLA\nVII RELIQVVM - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("VII"), UnaryMinus(Numeral("III")), "KEYWORD_RELIQVVM"))]), ValInt(-2)),
|
||||||
|
("CVM SVBNVLLA\n- VII RELIQVVM - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), UnaryMinus(Numeral("III")), "KEYWORD_RELIQVVM"))]), ValInt(-1)),
|
||||||
|
("CVM SVBNVLLA\n- VII / III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), Numeral("III"), "SYMBOL_DIVIDE"))]), ValInt(-3)),
|
||||||
|
("CVM SVBNVLLA\nVII / - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(Numeral("VII"), UnaryMinus(Numeral("III")), "SYMBOL_DIVIDE"))]), ValInt(-3)),
|
||||||
|
("CVM SVBNVLLA\n- VII / - III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("VII")), UnaryMinus(Numeral("III")), "SYMBOL_DIVIDE"))]), ValInt(2)),
|
||||||
|
("CVM SVBNVLLA\n- L RELIQVVM C", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BinOp(UnaryMinus(Numeral("L")), Numeral("C"), "KEYWORD_RELIQVVM"))]), ValInt(50)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestArithmeticEdge(unittest.TestCase):
|
||||||
|
@parameterized.expand(arithmetic_edge_tests)
|
||||||
|
def test_arithmetic_edge(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- MAGNVM module ---
|
||||||
|
# (ValueError for 4000 without MAGNVM is already in error_tests)
|
||||||
|
|
||||||
|
magnvm_tests = [
|
||||||
|
# M+M+M+M = 4000; MAGNVM allows display as "MV_"
|
||||||
|
("CVM MAGNVM\nDIC(M + M + M + M)",
|
||||||
|
Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DIC", [BinOp(BinOp(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS")]))]),
|
||||||
|
ValStr("MV_"), "MV_\n"),
|
||||||
|
# I_ = 1000 with MAGNVM (same value as M, but written with thousands operator)
|
||||||
|
("CVM MAGNVM\nI_",
|
||||||
|
Program([ModuleCall("MAGNVM")], [ExpressionStatement(Numeral("I_"))]),
|
||||||
|
ValInt(1000), ""),
|
||||||
|
# I_ + I_ = 2000; displayed as MM with MAGNVM
|
||||||
|
("CVM MAGNVM\nDIC(I_ + I_)",
|
||||||
|
Program([ModuleCall("MAGNVM")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I_"), Numeral("I_"), "SYMBOL_PLUS")]))]),
|
||||||
|
ValStr("MM"), "MM\n"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestMAGNVM(unittest.TestCase):
|
||||||
|
@parameterized.expand(magnvm_tests)
|
||||||
|
def test_magnvm(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- SVBNVLLA module (display of negatives) ---
|
||||||
|
|
||||||
|
svbnvlla_display_tests = [
|
||||||
|
# DIC prints a negative numeral
|
||||||
|
("CVM SVBNVLLA\nDIC(- III)",
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Numeral("III"))]))]),
|
||||||
|
ValStr("-III"), "-III\n"),
|
||||||
|
# Concat operator & with a negative numeral
|
||||||
|
('CVM SVBNVLLA\nDIC("x: " & - V)',
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(String("x: "), UnaryMinus(Numeral("V")), "SYMBOL_AMPERSAND")]))]),
|
||||||
|
ValStr("x: -V"), "x: -V\n"),
|
||||||
|
# String interpolation of a negative numeral
|
||||||
|
('CVM SVBNVLLA\nDIC("val: {- X}")',
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [InterpolatedString([String("val: "), UnaryMinus(Numeral("X"))])]))]),
|
||||||
|
ValStr("val: -X"), "val: -X\n"),
|
||||||
|
# DIC of LITTERA(negative numeral)
|
||||||
|
("CVM SVBNVLLA\nDIC(LITTERA(- III))",
|
||||||
|
Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("LITTERA", [UnaryMinus(Numeral("III"))])]))]),
|
||||||
|
ValStr("-III"), "-III\n"),
|
||||||
|
# Combined with MAGNVM: negative of a number > 3999
|
||||||
|
("CVM MAGNVM\nCVM SVBNVLLA\nDIC(- (M + M + M + M))",
|
||||||
|
Program([ModuleCall("MAGNVM"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(BinOp(BinOp(BinOp(Numeral("M"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"), Numeral("M"), "SYMBOL_PLUS"))]))]),
|
||||||
|
ValStr("-MV_"), "-MV_\n"),
|
||||||
|
# Negative fraction via int / -int
|
||||||
|
("CVM FRACTIO\nCVM SVBNVLLA\nDIC(V / - II)",
|
||||||
|
Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("V"), UnaryMinus(Numeral("II")), "SYMBOL_DIVIDE")]))]),
|
||||||
|
ValStr("-IIS"), "-IIS\n"),
|
||||||
|
# Negative pure fraction (no integer part)
|
||||||
|
("CVM FRACTIO\nCVM SVBNVLLA\nDIC(I / - II)",
|
||||||
|
Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I"), UnaryMinus(Numeral("II")), "SYMBOL_DIVIDE")]))]),
|
||||||
|
ValStr("-S"), "-S\n"),
|
||||||
|
("CVM FRACTIO\nCVM SVBNVLLA\nDIC(- S)",
|
||||||
|
Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Fractio("S"))]))]),
|
||||||
|
ValStr("-S"), "-S\n"),
|
||||||
|
("CVM FRACTIO\nCVM SVBNVLLA\nDIC(- IIS)",
|
||||||
|
Program([ModuleCall("FRACTIO"), ModuleCall("SVBNVLLA")], [ExpressionStatement(BuiltIn("DIC", [UnaryMinus(Fractio("IIS"))]))]),
|
||||||
|
ValStr("-IIS"), "-IIS\n"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestSVBNVLLADisplay(unittest.TestCase):
|
||||||
|
@parameterized.expand(svbnvlla_display_tests)
|
||||||
|
def test_svbnvlla_display(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
402
tests/05_test_literals.py
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Repr ---
|
||||||
|
|
||||||
|
repr_tests = [
|
||||||
|
("string", String("hello"), "String(hello)"),
|
||||||
|
("numeral", Numeral("III"), "Numeral(III)"),
|
||||||
|
("bool_true", Bool(True), "Bool(True)"),
|
||||||
|
("bool_false", Bool(False), "Bool(False)"),
|
||||||
|
("nullus", Nullus(), "Nullus()"),
|
||||||
|
("erumpe", Erumpe(), "Erumpe()"),
|
||||||
|
("module_call", ModuleCall("FORS"), "FORS"),
|
||||||
|
("id", ID("x"), "ID(x)"),
|
||||||
|
("expression_stmt", ExpressionStatement(String("hi")), "ExpressionStatement(\n String(hi)\n)"),
|
||||||
|
("data_array", DataArray([Numeral("I"), Numeral("II")]), "Array([\n Numeral(I),\n Numeral(II)\n])"),
|
||||||
|
("data_range_array", DataRangeArray(Numeral("I"), Numeral("X")), "RangeArray([\n Numeral(I),\n Numeral(X)\n])"),
|
||||||
|
("designa", Designa(ID("x"), Numeral("III")), "Designa(\n ID(x),\n Numeral(III)\n)"),
|
||||||
|
("binop", BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"), "BinOp(\n Numeral(I),\n Numeral(II),\n SYMBOL_PLUS\n)"),
|
||||||
|
("redi", Redi([Numeral("I")]), "Redi([\n Numeral(I)\n])"),
|
||||||
|
("si_no_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], None), "Si(\n Bool(True),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ]),\n None\n)"),
|
||||||
|
("si_with_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], [ExpressionStatement(Erumpe())]), "Si(\n Bool(True),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ]),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"),
|
||||||
|
("si_empty_else", SiStatement(Bool(True), [ExpressionStatement(Erumpe())], []), "Si(\n Bool(True),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ]),\n statements([])\n)"),
|
||||||
|
("dum", DumStatement(Bool(False), [ExpressionStatement(Erumpe())]), "Dum(\n Bool(False),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"),
|
||||||
|
("per", PerStatement(DataArray([Numeral("I")]), ID("i"), [ExpressionStatement(Erumpe())]), "Per(\n Array([\n Numeral(I)\n ]),\n ID(i),\n statements([\n ExpressionStatement(\n Erumpe()\n )\n ])\n)"),
|
||||||
|
("invoca", Invoca(ID("f"), [Numeral("I")]), "Invoca(\n ID(f),\n parameters([\n Numeral(I)\n ])\n)"),
|
||||||
|
("builtin", BuiltIn("DIC", [String("hi")]), "Builtin(\n DIC,\n parameters([\n String(hi)\n ])\n)"),
|
||||||
|
("defini", Defini(ID("f"), [ID("n")], [ExpressionStatement(Redi([Numeral("I")]))]), "Defini(\n ID(f),\n parameters([\n ID(n)\n ]),\n statements([\n ExpressionStatement(\n Redi([\n Numeral(I)\n ])\n )\n ])\n)"),
|
||||||
|
("program_no_modules", Program([], [ExpressionStatement(Numeral("I"))]), "modules([]),\nstatements([\n ExpressionStatement(\n Numeral(I)\n )\n])"),
|
||||||
|
("program_with_module", Program([ModuleCall("FORS")], [ExpressionStatement(Numeral("I"))]), "modules([\n FORS\n]),\nstatements([\n ExpressionStatement(\n Numeral(I)\n )\n])"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestRepr(unittest.TestCase):
|
||||||
|
@parameterized.expand(repr_tests)
|
||||||
|
def test_repr(self, _, node, expected):
|
||||||
|
self.assertEqual(repr(node), expected)
|
||||||
|
|
||||||
|
# --- make_string ---
|
||||||
|
|
||||||
|
class TestMakeString(unittest.TestCase):
|
||||||
|
def test_str(self):
|
||||||
|
self.assertEqual(make_string(ValStr("hello")), "hello")
|
||||||
|
|
||||||
|
def test_int(self):
|
||||||
|
self.assertEqual(make_string(ValInt(3)), "III")
|
||||||
|
|
||||||
|
def test_int_zero(self):
|
||||||
|
self.assertEqual(make_string(ValInt(0)), "NVLLVS")
|
||||||
|
|
||||||
|
def test_bool_true(self):
|
||||||
|
self.assertEqual(make_string(ValBool(True)), "VERITAS")
|
||||||
|
|
||||||
|
def test_bool_false(self):
|
||||||
|
self.assertEqual(make_string(ValBool(False)), "FALSITAS")
|
||||||
|
|
||||||
|
def test_nul(self):
|
||||||
|
self.assertEqual(make_string(ValNul()), "NVLLVS")
|
||||||
|
|
||||||
|
def test_list(self):
|
||||||
|
self.assertEqual(make_string(ValList([ValInt(1), ValInt(2)])), "[I II]")
|
||||||
|
|
||||||
|
def test_empty_list(self):
|
||||||
|
self.assertEqual(make_string(ValList([])), "[]")
|
||||||
|
|
||||||
|
def test_nested_list(self):
|
||||||
|
self.assertEqual(
|
||||||
|
make_string(ValList([ValStr("a"), ValBool(True)])),
|
||||||
|
"[a VERITAS]"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- DIC with non-integer types ---
|
||||||
|
|
||||||
|
dic_type_tests = [
|
||||||
|
("DIC(VERITAS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Bool(True)]))]), ValStr("VERITAS"), "VERITAS\n"),
|
||||||
|
("DIC(FALSITAS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Bool(False)]))]), ValStr("FALSITAS"), "FALSITAS\n"),
|
||||||
|
("DIC(NVLLVS)", Program([], [ExpressionStatement(BuiltIn("DIC", [Nullus()]))]), ValStr("NVLLVS"), "NVLLVS\n"),
|
||||||
|
('DIC([I, II])', Program([], [ExpressionStatement(BuiltIn("DIC", [DataArray([Numeral("I"), Numeral("II")])]))]), ValStr("[I II]"), "[I II]\n"),
|
||||||
|
('DIC("")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("")]))]), ValStr(""), "\n"),
|
||||||
|
# arithmetic result printed as numeral
|
||||||
|
("DIC(II + III)", Program([], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS")]))]), ValStr("V"), "V\n"),
|
||||||
|
# integer 0 prints as NVLLVS
|
||||||
|
("DIC(I - I)", Program([], [ExpressionStatement(BuiltIn("DIC", [BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("NVLLVS"), "NVLLVS\n"),
|
||||||
|
# multiple args of mixed types
|
||||||
|
('DIC("x", VERITAS)', Program([], [ExpressionStatement(BuiltIn("DIC", [String("x"), Bool(True)]))]), ValStr("x VERITAS"), "x VERITAS\n"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDicTypes(unittest.TestCase):
|
||||||
|
@parameterized.expand(dic_type_tests)
|
||||||
|
def test_dic_types(self, source, nodes, value, output):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- String concatenation ---
|
||||||
|
|
||||||
|
string_concat_tests = [
|
||||||
|
('"hello" & " world"', Program([], [ExpressionStatement(BinOp(String("hello"), String(" world"), "SYMBOL_AMPERSAND"))]), ValStr("hello world")),
|
||||||
|
# NVLLVS coerces to "" in string context
|
||||||
|
('NVLLVS & "hello"', Program([], [ExpressionStatement(BinOp(Nullus(), String("hello"), "SYMBOL_AMPERSAND"))]), ValStr("hello")),
|
||||||
|
('"hello" & NVLLVS', Program([], [ExpressionStatement(BinOp(String("hello"), Nullus(), "SYMBOL_AMPERSAND"))]), ValStr("hello")),
|
||||||
|
('NVLLVS & NVLLVS', Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "SYMBOL_AMPERSAND"))]), ValStr("")),
|
||||||
|
# integers coerce to Roman numerals in string context
|
||||||
|
('"value: " & V', Program([], [ExpressionStatement(BinOp(String("value: "), Numeral("V"), "SYMBOL_AMPERSAND"))]), ValStr("value: V")),
|
||||||
|
('X & " items"', Program([], [ExpressionStatement(BinOp(Numeral("X"), String(" items"), "SYMBOL_AMPERSAND"))]), ValStr("X items")),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestStringConcat(unittest.TestCase):
|
||||||
|
@parameterized.expand(string_concat_tests)
|
||||||
|
def test_string_concat(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- String interpolation ---
|
||||||
|
|
||||||
|
interpolation_tests = [
|
||||||
|
# basic variable interpolation
|
||||||
|
('DESIGNA nomen VT "Marcus"\n"Salve, {nomen}!"',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("nomen"), String("Marcus")),
|
||||||
|
ExpressionStatement(InterpolatedString([String("Salve, "), ID("nomen"), String("!")]))
|
||||||
|
]), ValStr("Salve, Marcus!")),
|
||||||
|
# arithmetic expression inside interpolation
|
||||||
|
('DESIGNA x VT III\n"Sum: {x + II}"',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("III")),
|
||||||
|
ExpressionStatement(InterpolatedString([String("Sum: "), BinOp(ID("x"), Numeral("II"), "SYMBOL_PLUS")]))
|
||||||
|
]), ValStr("Sum: V")),
|
||||||
|
# multiple interpolations
|
||||||
|
('DESIGNA a VT I\nDESIGNA b VT II\n"{a} + {b} = {a + b}"',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), Numeral("I")),
|
||||||
|
Designa(ID("b"), Numeral("II")),
|
||||||
|
ExpressionStatement(InterpolatedString([
|
||||||
|
ID("a"), String(" + "), ID("b"), String(" = "),
|
||||||
|
BinOp(ID("a"), ID("b"), "SYMBOL_PLUS"),
|
||||||
|
]))
|
||||||
|
]), ValStr("I + II = III")),
|
||||||
|
# escaped braces become literal
|
||||||
|
('"use {{braces}}"',
|
||||||
|
Program([], [ExpressionStatement(String("use {braces}"))]),
|
||||||
|
ValStr("use {braces}")),
|
||||||
|
# single-quoted strings ignore braces
|
||||||
|
("'hello {world}'",
|
||||||
|
Program([], [ExpressionStatement(String("hello {world}"))]),
|
||||||
|
ValStr("hello {world}")),
|
||||||
|
# integer coercion
|
||||||
|
('DESIGNA n VT V\n"n is {n}"',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("n"), Numeral("V")),
|
||||||
|
ExpressionStatement(InterpolatedString([String("n is "), ID("n")]))
|
||||||
|
]), ValStr("n is V")),
|
||||||
|
# boolean coercion
|
||||||
|
('DESIGNA b VT VERITAS\n"value: {b}"',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("b"), Bool(True)),
|
||||||
|
ExpressionStatement(InterpolatedString([String("value: "), ID("b")]))
|
||||||
|
]), ValStr("value: VERITAS")),
|
||||||
|
# NVLLVS coercion
|
||||||
|
('"value: {NVLLVS}"',
|
||||||
|
Program([], [
|
||||||
|
ExpressionStatement(InterpolatedString([String("value: "), Nullus()]))
|
||||||
|
]), ValStr("value: NVLLVS")),
|
||||||
|
# integer 0 interpolates as NVLLVS
|
||||||
|
('"value: {I - I}"', Program([], [ExpressionStatement(InterpolatedString([String("value: "), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS")]))]), ValStr("value: NVLLVS")),
|
||||||
|
# expression-only string (no literal parts around it)
|
||||||
|
('DESIGNA x VT "hi"\n"{x}"',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), String("hi")),
|
||||||
|
ExpressionStatement(InterpolatedString([ID("x")]))
|
||||||
|
]), ValStr("hi")),
|
||||||
|
# adjacent interpolations
|
||||||
|
('DESIGNA a VT "x"\nDESIGNA b VT "y"\n"{a}{b}"',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), String("x")),
|
||||||
|
Designa(ID("b"), String("y")),
|
||||||
|
ExpressionStatement(InterpolatedString([ID("a"), ID("b")]))
|
||||||
|
]), ValStr("xy")),
|
||||||
|
# function call inside interpolation
|
||||||
|
("DEFINI f () VT {\nREDI (V)\n}\n\"result: {INVOCA f()}\"",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("f"), [], [Redi([Numeral("V")])]),
|
||||||
|
ExpressionStatement(InterpolatedString([String("result: "), Invoca(ID("f"), [])]))
|
||||||
|
]), ValStr("result: V")),
|
||||||
|
# single-quoted string inside interpolation
|
||||||
|
("DESIGNA x VT 'hello'\n\"{x & '!'}\"",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), String("hello")),
|
||||||
|
ExpressionStatement(InterpolatedString([BinOp(ID("x"), String("!"), "SYMBOL_AMPERSAND")]))
|
||||||
|
]), ValStr("hello!")),
|
||||||
|
# plain double-quoted string (no braces) still works
|
||||||
|
('"hello world"',
|
||||||
|
Program([], [ExpressionStatement(String("hello world"))]),
|
||||||
|
ValStr("hello world")),
|
||||||
|
# interpolation in DIC output
|
||||||
|
('DESIGNA name VT "Roma"\nDIC("Salve, {name}!")',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("name"), String("Roma")),
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [InterpolatedString([String("Salve, "), ID("name"), String("!")])]))
|
||||||
|
]), ValStr("Salve, Roma!"), "Salve, Roma!\n"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestInterpolation(unittest.TestCase):
|
||||||
|
@parameterized.expand(interpolation_tests)
|
||||||
|
def test_interpolation(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- Escape sequences ---
|
||||||
|
|
||||||
|
escape_tests = [
|
||||||
|
# \n → newline
|
||||||
|
('"hello\\nworld"',
|
||||||
|
Program([], [ExpressionStatement(String("hello\nworld"))]),
|
||||||
|
ValStr("hello\nworld")),
|
||||||
|
# \t → tab
|
||||||
|
('"col\\tcol"',
|
||||||
|
Program([], [ExpressionStatement(String("col\tcol"))]),
|
||||||
|
ValStr("col\tcol")),
|
||||||
|
# \r → carriage return
|
||||||
|
('"line\\rover"',
|
||||||
|
Program([], [ExpressionStatement(String("line\rover"))]),
|
||||||
|
ValStr("line\rover")),
|
||||||
|
# \\ → literal backslash
|
||||||
|
('"back\\\\slash"',
|
||||||
|
Program([], [ExpressionStatement(String("back\\slash"))]),
|
||||||
|
ValStr("back\\slash")),
|
||||||
|
# \" → literal double quote
|
||||||
|
('"say \\"salve\\""',
|
||||||
|
Program([], [ExpressionStatement(String('say "salve"'))]),
|
||||||
|
ValStr('say "salve"')),
|
||||||
|
# \' → literal single quote in single-quoted string
|
||||||
|
("'it\\'s'",
|
||||||
|
Program([], [ExpressionStatement(String("it's"))]),
|
||||||
|
ValStr("it's")),
|
||||||
|
# \n in single-quoted string
|
||||||
|
("'hello\\nworld'",
|
||||||
|
Program([], [ExpressionStatement(String("hello\nworld"))]),
|
||||||
|
ValStr("hello\nworld")),
|
||||||
|
# escape inside interpolated string
|
||||||
|
('DESIGNA name VT "Roma"\n"salve\\n{name}"',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("name"), String("Roma")),
|
||||||
|
ExpressionStatement(InterpolatedString([String("salve\n"), ID("name")]))
|
||||||
|
]), ValStr("salve\nRoma")),
|
||||||
|
# DIC with newline escape
|
||||||
|
('DIC("hello\\nworld")',
|
||||||
|
Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello\nworld")]))]),
|
||||||
|
ValStr("hello\nworld"), "hello\nworld\n"),
|
||||||
|
# multiple escapes in one string
|
||||||
|
('"\\t\\n\\\\"',
|
||||||
|
Program([], [ExpressionStatement(String("\t\n\\"))]),
|
||||||
|
ValStr("\t\n\\")),
|
||||||
|
# unknown escapes pass through (regex backrefs)
|
||||||
|
('"\\1\\2"',
|
||||||
|
Program([], [ExpressionStatement(String("\\1\\2"))]),
|
||||||
|
ValStr("\\1\\2")),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestEscapeSequences(unittest.TestCase):
|
||||||
|
@parameterized.expand(escape_tests)
|
||||||
|
def test_escape(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- String index assignment ---
|
||||||
|
|
||||||
|
string_index_assign_tests = [
|
||||||
|
# assign to middle character
|
||||||
|
('DESIGNA s VT "ABCDE"\nDESIGNA s[III] VT "X"\ns',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("s"), String("ABCDE")),
|
||||||
|
DesignaIndex(ID("s"), [Numeral("III")], String("X")),
|
||||||
|
ExpressionStatement(ID("s")),
|
||||||
|
]),
|
||||||
|
ValStr("ABXDE")),
|
||||||
|
# assign to first character
|
||||||
|
('DESIGNA s VT "ABCDE"\nDESIGNA s[I] VT "Z"\ns',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("s"), String("ABCDE")),
|
||||||
|
DesignaIndex(ID("s"), [Numeral("I")], String("Z")),
|
||||||
|
ExpressionStatement(ID("s")),
|
||||||
|
]),
|
||||||
|
ValStr("ZBCDE")),
|
||||||
|
# assign to last character
|
||||||
|
('DESIGNA s VT "ABCDE"\nDESIGNA s[V] VT "Z"\ns',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("s"), String("ABCDE")),
|
||||||
|
DesignaIndex(ID("s"), [Numeral("V")], String("Z")),
|
||||||
|
ExpressionStatement(ID("s")),
|
||||||
|
]),
|
||||||
|
ValStr("ABCDZ")),
|
||||||
|
# variable as index
|
||||||
|
('DESIGNA s VT "ABCDE"\nDESIGNA i VT II\nDESIGNA s[i] VT "X"\ns',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("s"), String("ABCDE")),
|
||||||
|
Designa(ID("i"), Numeral("II")),
|
||||||
|
DesignaIndex(ID("s"), [ID("i")], String("X")),
|
||||||
|
ExpressionStatement(ID("s")),
|
||||||
|
]),
|
||||||
|
ValStr("AXCDE")),
|
||||||
|
# string inside array
|
||||||
|
('DESIGNA a VT ["ABC", "DEF"]\nDESIGNA a[I][II] VT "X"\na[I]',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([String("ABC"), String("DEF")])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], String("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))),
|
||||||
|
]),
|
||||||
|
ValStr("AXC")),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestStringIndexAssign(unittest.TestCase):
|
||||||
|
@parameterized.expand(string_index_assign_tests)
|
||||||
|
def test_string_index_assign(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)
|
||||||
223
tests/06_test_booleans.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Comparison operators ---
|
||||||
|
|
||||||
|
comparison_tests = [
|
||||||
|
# EST on strings
|
||||||
|
('\"hello\" EST \"hello\"', Program([], [ExpressionStatement(BinOp(String("hello"), String("hello"), "KEYWORD_EST"))]), ValBool(True)),
|
||||||
|
('\"hello\" EST \"world\"', Program([], [ExpressionStatement(BinOp(String("hello"), String("world"), "KEYWORD_EST"))]), ValBool(False)),
|
||||||
|
# chain comparisons as conditions
|
||||||
|
("SI III PLVS II TVNC { DESIGNA r VT I }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Numeral("III"), Numeral("II"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], None), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
("SI II PLVS III TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Numeral("II"), Numeral("III"), "KEYWORD_PLVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(2)),
|
||||||
|
# result of comparison is ValBool
|
||||||
|
("I EST I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"))]), ValBool(True)),
|
||||||
|
("I EST II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"))]), ValBool(False)),
|
||||||
|
("I MINVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_MINVS"))]), ValBool(True)),
|
||||||
|
("II MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_MINVS"))]), ValBool(False)),
|
||||||
|
("II PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_PLVS"))]), ValBool(True)),
|
||||||
|
("I PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_PLVS"))]), ValBool(False)),
|
||||||
|
# NVLLVS coerces to 0 in comparisons
|
||||||
|
("V PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_PLVS"))]), ValBool(True)),
|
||||||
|
("NVLLVS MINVS V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_MINVS"))]), ValBool(True)),
|
||||||
|
# DISPAR (not-equal): mirrors EST semantics, negated
|
||||||
|
("I DISPAR II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||||||
|
("I DISPAR I", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||||||
|
('"hello" DISPAR "hello"', Program([], [ExpressionStatement(BinOp(String("hello"), String("hello"), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||||||
|
('"hello" DISPAR "world"', Program([], [ExpressionStatement(BinOp(String("hello"), String("world"), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||||||
|
("VERITAS DISPAR FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||||||
|
("NVLLVS DISPAR NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||||||
|
# cross-type: an int and a string are never equal
|
||||||
|
('I DISPAR "I"', Program([], [ExpressionStatement(BinOp(Numeral("I"), String("I"), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||||||
|
# integer 0 equals NVLLVS
|
||||||
|
("(I - I) EST NVLLVS", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), Nullus(), "KEYWORD_EST"))]), ValBool(True)),
|
||||||
|
("NVLLVS EST (I - I)", Program([], [ExpressionStatement(BinOp(Nullus(), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), "KEYWORD_EST"))]), ValBool(True)),
|
||||||
|
("(I - I) DISPAR NVLLVS", Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), Nullus(), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||||||
|
("NVLLVS DISPAR (I - I)", Program([], [ExpressionStatement(BinOp(Nullus(), BinOp(Numeral("I"), Numeral("I"), "SYMBOL_MINUS"), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||||||
|
# non-zero integer does not equal NVLLVS
|
||||||
|
("I EST NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("I"), Nullus(), "KEYWORD_EST"))]), ValBool(False)),
|
||||||
|
("NVLLVS DISPAR I", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("I"), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||||||
|
# EST / DISPAR on arrays
|
||||||
|
("[I, II] EST [I, II]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II")]), "KEYWORD_EST"))]), ValBool(True)),
|
||||||
|
("[I, II] EST [I, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("III")]), "KEYWORD_EST"))]), ValBool(False)),
|
||||||
|
("[I, II] EST [I, II, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), "KEYWORD_EST"))]), ValBool(False)),
|
||||||
|
("[] EST []", Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([]), "KEYWORD_EST"))]), ValBool(True)),
|
||||||
|
("[I, II] DISPAR [I, III]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("III")]), "KEYWORD_DISPAR"))]), ValBool(True)),
|
||||||
|
("[I, II] DISPAR [I, II]", Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("I"), Numeral("II")]), "KEYWORD_DISPAR"))]), ValBool(False)),
|
||||||
|
# HAVD_PLVS (<=) and HAVD_MINVS (>=)
|
||||||
|
("I HAVD_PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)),
|
||||||
|
("II HAVD_PLVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_HAVD_PLVS"))]), ValBool(False)),
|
||||||
|
# equality boundary — the only case that distinguishes <= from <
|
||||||
|
("II HAVD_PLVS II", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)),
|
||||||
|
("II HAVD_MINVS I", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("I"), "KEYWORD_HAVD_MINVS"))]), ValBool(True)),
|
||||||
|
("I HAVD_MINVS II", Program([], [ExpressionStatement(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_HAVD_MINVS"))]), ValBool(False)),
|
||||||
|
# equality boundary — the only case that distinguishes >= from >
|
||||||
|
("II HAVD_MINVS II",Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_MINVS"))]), ValBool(True)),
|
||||||
|
# NVLLVS coerces to 0
|
||||||
|
("V HAVD_MINVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_HAVD_MINVS"))]), ValBool(True)),
|
||||||
|
("NVLLVS HAVD_PLVS V", Program([], [ExpressionStatement(BinOp(Nullus(), Numeral("V"), "KEYWORD_HAVD_PLVS"))]), ValBool(True)),
|
||||||
|
("NVLLVS HAVD_PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Nullus(), Nullus(), "KEYWORD_HAVD_PLVS"))]), ValBool(True)),
|
||||||
|
# precedence: * binds tighter, so II*III HAVD_PLVS VI parses as (II*III) HAVD_PLVS VI = 6 <= 6 = True
|
||||||
|
("II * III HAVD_PLVS VI",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_TIMES"), Numeral("VI"), "KEYWORD_HAVD_PLVS"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
# control flow: SI ... HAVD_MINVS
|
||||||
|
("SI II HAVD_MINVS II TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Numeral("II"), Numeral("II"), "KEYWORD_HAVD_MINVS"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestComparisons(unittest.TestCase):
|
||||||
|
@parameterized.expand(comparison_tests)
|
||||||
|
def test_comparisons(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- ET and AVT (boolean and/or) ---
|
||||||
|
|
||||||
|
et_avt_tests = [
|
||||||
|
("VERITAS ET VERITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(True), "KEYWORD_ET"))]), ValBool(True)),
|
||||||
|
("VERITAS ET FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_ET"))]), ValBool(False)),
|
||||||
|
("FALSITAS ET VERITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(True), "KEYWORD_ET"))]), ValBool(False)),
|
||||||
|
("FALSITAS ET FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(False), "KEYWORD_ET"))]), ValBool(False)),
|
||||||
|
("VERITAS AVT VERITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(True), "KEYWORD_AVT"))]), ValBool(True)),
|
||||||
|
("VERITAS AVT FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(True), Bool(False), "KEYWORD_AVT"))]), ValBool(True)),
|
||||||
|
("FALSITAS AVT VERITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"))]), ValBool(True)),
|
||||||
|
("FALSITAS AVT FALSITAS", Program([], [ExpressionStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"))]), ValBool(False)),
|
||||||
|
# short-circuit behavior: combined with comparisons
|
||||||
|
("(I EST I) ET (II EST II)",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
("(I EST II) AVT (II EST II)",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_AVT"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
# used as SI condition
|
||||||
|
("SI VERITAS ET VERITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Bool(True), Bool(True), "KEYWORD_ET"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
("SI FALSITAS AVT FALSITAS TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [SiStatement(BinOp(Bool(False), Bool(False), "KEYWORD_AVT"), [Designa(ID("r"), Numeral("I"))], [Designa(ID("r"), Numeral("II"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(2)),
|
||||||
|
# short-circuit: right side not evaluated when result is determined
|
||||||
|
("VERITAS AVT NVLLVS",
|
||||||
|
Program([], [ExpressionStatement(BinOp(Bool(True), Nullus(), "KEYWORD_AVT"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
("FALSITAS ET NVLLVS",
|
||||||
|
Program([], [ExpressionStatement(BinOp(Bool(False), Nullus(), "KEYWORD_ET"))]),
|
||||||
|
ValBool(False)),
|
||||||
|
# short-circuit with side-effect-prone expressions
|
||||||
|
("DESIGNA x VT NVLLVS\nSI x EST NVLLVS AVT [I, II][x] EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Nullus()),
|
||||||
|
SiStatement(
|
||||||
|
BinOp(BinOp(ID("x"), Nullus(), "KEYWORD_EST"), BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II")]), ID("x")), Numeral("I"), "KEYWORD_EST"), "KEYWORD_AVT"),
|
||||||
|
[Designa(ID("r"), Numeral("I"))],
|
||||||
|
[Designa(ID("r"), Numeral("II"))]),
|
||||||
|
ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(1)),
|
||||||
|
("DESIGNA x VT NVLLVS\nSI x DISPAR NVLLVS ET [I, II][x] EST I TVNC { DESIGNA r VT I } ALIVD { DESIGNA r VT II }\nr",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Nullus()),
|
||||||
|
SiStatement(
|
||||||
|
BinOp(BinOp(ID("x"), Nullus(), "KEYWORD_DISPAR"), BinOp(ArrayIndex(DataArray([Numeral("I"), Numeral("II")]), ID("x")), Numeral("I"), "KEYWORD_EST"), "KEYWORD_ET"),
|
||||||
|
[Designa(ID("r"), Numeral("I"))],
|
||||||
|
[Designa(ID("r"), Numeral("II"))]),
|
||||||
|
ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(2)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestEtAvt(unittest.TestCase):
|
||||||
|
@parameterized.expand(et_avt_tests)
|
||||||
|
def test_et_avt(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- NON (boolean not) ---
|
||||||
|
|
||||||
|
non_tests = [
|
||||||
|
("NON VERITAS",
|
||||||
|
Program([], [ExpressionStatement(UnaryNot(Bool(True)))]),
|
||||||
|
ValBool(False)),
|
||||||
|
("NON FALSITAS",
|
||||||
|
Program([], [ExpressionStatement(UnaryNot(Bool(False)))]),
|
||||||
|
ValBool(True)),
|
||||||
|
("NON NON VERITAS",
|
||||||
|
Program([], [ExpressionStatement(UnaryNot(UnaryNot(Bool(True))))]),
|
||||||
|
ValBool(True)),
|
||||||
|
("DESIGNA b VT I EST II\nNON b",
|
||||||
|
Program([], [Designa(ID("b"), BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("b")))]),
|
||||||
|
ValBool(True)),
|
||||||
|
("DESIGNA z VT I EST I\nNON z",
|
||||||
|
Program([], [Designa(ID("z"), BinOp(Numeral("I"), Numeral("I"), "KEYWORD_EST")), ExpressionStatement(UnaryNot(ID("z")))]),
|
||||||
|
ValBool(False)),
|
||||||
|
("NON VERITAS AVT FALSITAS",
|
||||||
|
Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_AVT"))]),
|
||||||
|
ValBool(False)),
|
||||||
|
("NON VERITAS EST FALSITAS",
|
||||||
|
Program([], [ExpressionStatement(BinOp(UnaryNot(Bool(True)), Bool(False), "KEYWORD_EST"))]),
|
||||||
|
ValBool(True)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestNon(unittest.TestCase):
|
||||||
|
@parameterized.expand(non_tests)
|
||||||
|
def test_non(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- Values: equality and truthiness ---
|
||||||
|
|
||||||
|
class TestValues(unittest.TestCase):
|
||||||
|
def test_valint_equality(self):
|
||||||
|
self.assertEqual(ValInt(3), ValInt(3))
|
||||||
|
self.assertNotEqual(ValInt(3), ValInt(4))
|
||||||
|
|
||||||
|
def test_valstr_equality(self):
|
||||||
|
self.assertEqual(ValStr("hi"), ValStr("hi"))
|
||||||
|
self.assertNotEqual(ValStr("hi"), ValStr("bye"))
|
||||||
|
|
||||||
|
def test_valbool_equality(self):
|
||||||
|
self.assertEqual(ValBool(True), ValBool(True))
|
||||||
|
self.assertNotEqual(ValBool(True), ValBool(False))
|
||||||
|
|
||||||
|
def test_valnul_equality(self):
|
||||||
|
self.assertEqual(ValNul(), ValNul())
|
||||||
|
|
||||||
|
def test_vallist_equality(self):
|
||||||
|
self.assertEqual(ValList([ValInt(1)]), ValList([ValInt(1)]))
|
||||||
|
self.assertNotEqual(ValList([ValInt(1)]), ValList([ValInt(2)]))
|
||||||
|
self.assertNotEqual(ValList([ValInt(1)]), ValList([]))
|
||||||
|
|
||||||
|
def test_valint_truthiness(self):
|
||||||
|
self.assertTrue(bool(ValInt(1)))
|
||||||
|
self.assertTrue(bool(ValInt(-1)))
|
||||||
|
self.assertFalse(bool(ValInt(0)))
|
||||||
|
|
||||||
|
def test_valstr_truthiness(self):
|
||||||
|
self.assertTrue(bool(ValStr("x")))
|
||||||
|
self.assertFalse(bool(ValStr("")))
|
||||||
|
|
||||||
|
def test_valbool_truthiness(self):
|
||||||
|
self.assertTrue(bool(ValBool(True)))
|
||||||
|
self.assertFalse(bool(ValBool(False)))
|
||||||
|
|
||||||
|
def test_vallist_truthiness(self):
|
||||||
|
self.assertTrue(bool(ValList([ValInt(1)])))
|
||||||
|
self.assertFalse(bool(ValList([])))
|
||||||
|
|
||||||
|
def test_cross_type_inequality(self):
|
||||||
|
self.assertNotEqual(ValInt(1), ValBool(True))
|
||||||
|
self.assertNotEqual(ValInt(0), ValNul())
|
||||||
|
self.assertNotEqual(ValStr(""), ValNul())
|
||||||
273
tests/07_test_arrays__.py
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Array concatenation ---
|
||||||
|
|
||||||
|
array_concat_tests = [
|
||||||
|
("[I, II] @ [III, IV]",
|
||||||
|
Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")]), "SYMBOL_AT"))]),
|
||||||
|
ValList([ValInt(1), ValInt(2), ValInt(3), ValInt(4)])),
|
||||||
|
("[] @ [I]",
|
||||||
|
Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([Numeral("I")]), "SYMBOL_AT"))]),
|
||||||
|
ValList([ValInt(1)])),
|
||||||
|
("[I] @ []",
|
||||||
|
Program([], [ExpressionStatement(BinOp(DataArray([Numeral("I")]), DataArray([]), "SYMBOL_AT"))]),
|
||||||
|
ValList([ValInt(1)])),
|
||||||
|
("[] @ []",
|
||||||
|
Program([], [ExpressionStatement(BinOp(DataArray([]), DataArray([]), "SYMBOL_AT"))]),
|
||||||
|
ValList([])),
|
||||||
|
('["a"] @ [I]',
|
||||||
|
Program([], [ExpressionStatement(BinOp(DataArray([String("a")]), DataArray([Numeral("I")]), "SYMBOL_AT"))]),
|
||||||
|
ValList([ValStr("a"), ValInt(1)])),
|
||||||
|
# left-associative chaining
|
||||||
|
("[I] @ [II] @ [III]",
|
||||||
|
Program([], [ExpressionStatement(BinOp(BinOp(DataArray([Numeral("I")]), DataArray([Numeral("II")]), "SYMBOL_AT"), DataArray([Numeral("III")]), "SYMBOL_AT"))]),
|
||||||
|
ValList([ValInt(1), ValInt(2), ValInt(3)])),
|
||||||
|
# concat with variable
|
||||||
|
("DESIGNA a VT [I, II]\nDESIGNA b VT [III]\na @ b",
|
||||||
|
Program([], [Designa(ID("a"), DataArray([Numeral("I"), Numeral("II")])), Designa(ID("b"), DataArray([Numeral("III")])), ExpressionStatement(BinOp(ID("a"), ID("b"), "SYMBOL_AT"))]),
|
||||||
|
ValList([ValInt(1), ValInt(2), ValInt(3)])),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestArrayConcat(unittest.TestCase):
|
||||||
|
@parameterized.expand(array_concat_tests)
|
||||||
|
def test_array_concat(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- Array indexing ---
|
||||||
|
# Indexing is 1-based; I is the first element
|
||||||
|
|
||||||
|
array_index_tests = [
|
||||||
|
# basic indexing
|
||||||
|
("[I, II, III][I]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("I")))]), ValInt(1)), # first element
|
||||||
|
("[I, II, III][II]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("II")))]), ValInt(2)), # second element
|
||||||
|
("[I, II, III][III]", Program([], [ExpressionStatement(ArrayIndex(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), Numeral("III")))]), ValInt(3)), # third element
|
||||||
|
# index into a variable
|
||||||
|
("DESIGNA a VT [X, XX, XXX]\na[II]",
|
||||||
|
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,5]
|
||||||
|
# expression as index
|
||||||
|
("[I, II, III][I + I]",
|
||||||
|
Program([], [ExpressionStatement(ArrayIndex(
|
||||||
|
DataArray([Numeral("I"), Numeral("II"), Numeral("III")]),
|
||||||
|
BinOp(Numeral("I"), Numeral("I"), "SYMBOL_PLUS")))]),
|
||||||
|
ValInt(2)),
|
||||||
|
# division result as index (no FRACTIO): IV / II = 2
|
||||||
|
("[X, XX, XXX][IV / II]",
|
||||||
|
Program([], [ExpressionStatement(ArrayIndex(
|
||||||
|
DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")]),
|
||||||
|
BinOp(Numeral("IV"), Numeral("II"), "SYMBOL_DIVIDE")))]),
|
||||||
|
ValInt(20)),
|
||||||
|
# whole-number fraction (from division) as index, with FRACTIO imported
|
||||||
|
("CVM FRACTIO\n[X, XX, XXX][IV / II]",
|
||||||
|
Program([ModuleCall("FRACTIO")], [ExpressionStatement(ArrayIndex(
|
||||||
|
DataArray([Numeral("X"), Numeral("XX"), Numeral("XXX")]),
|
||||||
|
BinOp(Numeral("IV"), Numeral("II"), "SYMBOL_DIVIDE")))]),
|
||||||
|
ValInt(20)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestArrayIndex(unittest.TestCase):
|
||||||
|
@parameterized.expand(array_index_tests)
|
||||||
|
def test_array_index(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- Array index assignment ---
|
||||||
|
|
||||||
|
array_index_assign_tests = [
|
||||||
|
# assign to middle element
|
||||||
|
("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[II]",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("II")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))),
|
||||||
|
]),
|
||||||
|
ValInt(10)),
|
||||||
|
# assign to first element
|
||||||
|
("DESIGNA a VT [I, II, III]\nDESIGNA a[I] VT V\na[I]",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("I")], Numeral("V")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))),
|
||||||
|
]),
|
||||||
|
ValInt(5)),
|
||||||
|
# assign to last element
|
||||||
|
("DESIGNA a VT [I, II, III]\nDESIGNA a[III] VT L\na[III]",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("III")], Numeral("L")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("a"), Numeral("III"))),
|
||||||
|
]),
|
||||||
|
ValInt(50)),
|
||||||
|
# other elements unaffected
|
||||||
|
("DESIGNA a VT [I, II, III]\nDESIGNA a[II] VT X\na[I]",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("II")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("a"), Numeral("I"))),
|
||||||
|
]),
|
||||||
|
ValInt(1)),
|
||||||
|
# expression as index
|
||||||
|
("DESIGNA a VT [I, II, III]\nDESIGNA i VT II\nDESIGNA a[i] VT X\na[II]",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([Numeral("I"), Numeral("II"), Numeral("III")])),
|
||||||
|
Designa(ID("i"), Numeral("II")),
|
||||||
|
DesignaIndex(ID("a"), [ID("i")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("a"), Numeral("II"))),
|
||||||
|
]),
|
||||||
|
ValInt(10)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestArrayIndexAssign(unittest.TestCase):
|
||||||
|
@parameterized.expand(array_index_assign_tests)
|
||||||
|
def test_array_index_assign(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- Multi-dimensional array index assignment ---
|
||||||
|
|
||||||
|
multidim_assign_tests = [
|
||||||
|
# 2D array assignment
|
||||||
|
("DESIGNA a VT [[I, II], [III, IV]]\nDESIGNA a[I][II] VT X\na[I][II]",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), Numeral("II"))),
|
||||||
|
]),
|
||||||
|
ValInt(10)),
|
||||||
|
# other elements unaffected
|
||||||
|
("DESIGNA a VT [[I, II], [III, IV]]\nDESIGNA a[I][II] VT X\na[II][I]",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([DataArray([Numeral("I"), Numeral("II")]), DataArray([Numeral("III"), Numeral("IV")])])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("I"), Numeral("II")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("II")), Numeral("I"))),
|
||||||
|
]),
|
||||||
|
ValInt(3)),
|
||||||
|
# dict inside array
|
||||||
|
('DESIGNA a VT [TABVLA {"x" VT I}]\nDESIGNA a[I]["x"] VT X\na[I]["x"]',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([DataDict([(String("x"), Numeral("I"))])])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("I"), String("x")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), String("x"))),
|
||||||
|
]),
|
||||||
|
ValInt(10)),
|
||||||
|
# array inside dict
|
||||||
|
('DESIGNA d VT TABVLA {"a" VT [I, II]}\nDESIGNA d["a"][I] VT X\nd["a"][I]',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("d"), DataDict([(String("a"), DataArray([Numeral("I"), Numeral("II")]))])),
|
||||||
|
DesignaIndex(ID("d"), [String("a"), Numeral("I")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ArrayIndex(ID("d"), String("a")), Numeral("I"))),
|
||||||
|
]),
|
||||||
|
ValInt(10)),
|
||||||
|
# 3 levels deep
|
||||||
|
("DESIGNA a VT [[[I]]]\nDESIGNA a[I][I][I] VT X\na[I][I][I]",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("a"), DataArray([DataArray([DataArray([Numeral("I")])])])),
|
||||||
|
DesignaIndex(ID("a"), [Numeral("I"), Numeral("I"), Numeral("I")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ArrayIndex(ArrayIndex(ID("a"), Numeral("I")), Numeral("I")), Numeral("I"))),
|
||||||
|
]),
|
||||||
|
ValInt(10)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestMultidimAssign(unittest.TestCase):
|
||||||
|
@parameterized.expand(multidim_assign_tests)
|
||||||
|
def test_multidim_assign(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)
|
||||||
|
|
||||||
|
# --- Multiline arrays ---
|
||||||
|
|
||||||
|
multiline_array_tests = [
|
||||||
|
# newlines after commas
|
||||||
|
("[I,\nII,\nIII]",
|
||||||
|
Program([], [ExpressionStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]))]),
|
||||||
|
ValList([ValInt(1), ValInt(2), ValInt(3)])),
|
||||||
|
# single newline after comma
|
||||||
|
("[I, II,\nIII]",
|
||||||
|
Program([], [ExpressionStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]))]),
|
||||||
|
ValList([ValInt(1), ValInt(2), ValInt(3)])),
|
||||||
|
# empty array still works
|
||||||
|
("[]",
|
||||||
|
Program([], [ExpressionStatement(DataArray([]))]),
|
||||||
|
ValList([])),
|
||||||
|
# nested arrays with newlines
|
||||||
|
("[I,\n[II, III],\nIV]",
|
||||||
|
Program([], [ExpressionStatement(DataArray([
|
||||||
|
Numeral("I"),
|
||||||
|
DataArray([Numeral("II"), Numeral("III")]),
|
||||||
|
Numeral("IV")]))]),
|
||||||
|
ValList([ValInt(1), ValList([ValInt(2), ValInt(3)]), ValInt(4)])),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestMultilineArray(unittest.TestCase):
|
||||||
|
@parameterized.expand(multiline_array_tests)
|
||||||
|
def test_multiline_array(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
191
tests/08_test_tabulas_.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Dict (TABVLA) ---
|
||||||
|
|
||||||
|
dict_tests = [
|
||||||
|
# empty dict
|
||||||
|
("TABVLA {}",
|
||||||
|
Program([], [ExpressionStatement(DataDict([]))]),
|
||||||
|
ValDict({})),
|
||||||
|
# single string key
|
||||||
|
('TABVLA {"a" VT I}',
|
||||||
|
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I"))]))]),
|
||||||
|
ValDict({"a": ValInt(1)})),
|
||||||
|
# multiple entries
|
||||||
|
('TABVLA {"a" VT I, "b" VT II}',
|
||||||
|
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]))]),
|
||||||
|
ValDict({"a": ValInt(1), "b": ValInt(2)})),
|
||||||
|
# integer keys
|
||||||
|
('TABVLA {I VT "one", II VT "two"}',
|
||||||
|
Program([], [ExpressionStatement(DataDict([(Numeral("I"), String("one")), (Numeral("II"), String("two"))]))]),
|
||||||
|
ValDict({1: ValStr("one"), 2: ValStr("two")})),
|
||||||
|
# expression values
|
||||||
|
('TABVLA {"x" VT I + II}',
|
||||||
|
Program([], [ExpressionStatement(DataDict([(String("x"), BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"))]))]),
|
||||||
|
ValDict({"x": ValInt(3)})),
|
||||||
|
# empty dict with newline
|
||||||
|
('TABVLA {\n}',
|
||||||
|
Program([], [ExpressionStatement(DataDict([]))]),
|
||||||
|
ValDict({})),
|
||||||
|
# single entry with surrounding newlines
|
||||||
|
('TABVLA {\n"a" VT I\n}',
|
||||||
|
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I"))]))]),
|
||||||
|
ValDict({"a": ValInt(1)})),
|
||||||
|
# multiple entries with newlines after commas
|
||||||
|
('TABVLA {\n"a" VT I,\n"b" VT II\n}',
|
||||||
|
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]))]),
|
||||||
|
ValDict({"a": ValInt(1), "b": ValInt(2)})),
|
||||||
|
# newlines around every delimiter
|
||||||
|
('TABVLA {\n"a" VT I,\n"b" VT II,\n"c" VT III\n}',
|
||||||
|
Program([], [ExpressionStatement(DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II")), (String("c"), Numeral("III"))]))]),
|
||||||
|
ValDict({"a": ValInt(1), "b": ValInt(2), "c": ValInt(3)})),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDict(unittest.TestCase):
|
||||||
|
@parameterized.expand(dict_tests)
|
||||||
|
def test_dict(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
|
||||||
|
dict_index_tests = [
|
||||||
|
# string key access
|
||||||
|
('TABVLA {"a" VT X}["a"]',
|
||||||
|
Program([], [ExpressionStatement(ArrayIndex(DataDict([(String("a"), Numeral("X"))]), String("a")))]),
|
||||||
|
ValInt(10)),
|
||||||
|
# integer key access
|
||||||
|
('TABVLA {I VT "one"}[I]',
|
||||||
|
Program([], [ExpressionStatement(ArrayIndex(DataDict([(Numeral("I"), String("one"))]), Numeral("I")))]),
|
||||||
|
ValStr("one")),
|
||||||
|
# access via variable
|
||||||
|
('DESIGNA d VT TABVLA {"x" VT V}\nd["x"]',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("d"), DataDict([(String("x"), Numeral("V"))])),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("d"), String("x"))),
|
||||||
|
]),
|
||||||
|
ValInt(5)),
|
||||||
|
# nested dict access
|
||||||
|
('TABVLA {"a" VT TABVLA {"b" VT X}}["a"]["b"]',
|
||||||
|
Program([], [ExpressionStatement(
|
||||||
|
ArrayIndex(ArrayIndex(DataDict([(String("a"), DataDict([(String("b"), Numeral("X"))]))]), String("a")), String("b"))
|
||||||
|
)]),
|
||||||
|
ValInt(10)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDictIndex(unittest.TestCase):
|
||||||
|
@parameterized.expand(dict_index_tests)
|
||||||
|
def test_dict_index(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
|
||||||
|
dict_assign_tests = [
|
||||||
|
# update existing key
|
||||||
|
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["a"] VT X\nd["a"]',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
|
||||||
|
DesignaIndex(ID("d"), [String("a")], Numeral("X")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("d"), String("a"))),
|
||||||
|
]),
|
||||||
|
ValInt(10)),
|
||||||
|
# insert new key
|
||||||
|
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["b"]',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
|
||||||
|
DesignaIndex(ID("d"), [String("b")], Numeral("II")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("d"), String("b"))),
|
||||||
|
]),
|
||||||
|
ValInt(2)),
|
||||||
|
# original key unaffected after insert
|
||||||
|
('DESIGNA d VT TABVLA {"a" VT I}\nDESIGNA d["b"] VT II\nd["a"]',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("d"), DataDict([(String("a"), Numeral("I"))])),
|
||||||
|
DesignaIndex(ID("d"), [String("b")], Numeral("II")),
|
||||||
|
ExpressionStatement(ArrayIndex(ID("d"), String("a"))),
|
||||||
|
]),
|
||||||
|
ValInt(1)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDictAssign(unittest.TestCase):
|
||||||
|
@parameterized.expand(dict_assign_tests)
|
||||||
|
def test_dict_assign(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
|
||||||
|
dict_builtin_tests = [
|
||||||
|
# LONGITVDO on dict
|
||||||
|
('LONGITVDO(TABVLA {"a" VT I, "b" VT II})',
|
||||||
|
Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
|
||||||
|
ValInt(2)),
|
||||||
|
# LONGITVDO on empty dict
|
||||||
|
('LONGITVDO(TABVLA {})',
|
||||||
|
Program([], [ExpressionStatement(BuiltIn("LONGITVDO", [DataDict([])]))]),
|
||||||
|
ValInt(0)),
|
||||||
|
# CLAVES
|
||||||
|
('CLAVES(TABVLA {"a" VT I, "b" VT II})',
|
||||||
|
Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
|
||||||
|
ValList([ValStr("a"), ValStr("b")])),
|
||||||
|
# CLAVES with int keys
|
||||||
|
('CLAVES(TABVLA {I VT "x", II VT "y"})',
|
||||||
|
Program([], [ExpressionStatement(BuiltIn("CLAVES", [DataDict([(Numeral("I"), String("x")), (Numeral("II"), String("y"))])]))]),
|
||||||
|
ValList([ValInt(1), ValInt(2)])),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDictBuiltins(unittest.TestCase):
|
||||||
|
@parameterized.expand(dict_builtin_tests)
|
||||||
|
def test_dict_builtin(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
|
||||||
|
dict_iteration_tests = [
|
||||||
|
# PER iterates over keys
|
||||||
|
('DESIGNA r VT ""\nPER k IN TABVLA {"a" VT I, "b" VT II} FAC {\nDESIGNA r VT r & k\n}\nr',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("r"), String("")),
|
||||||
|
PerStatement(
|
||||||
|
DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))]),
|
||||||
|
ID("k"),
|
||||||
|
[Designa(ID("r"), BinOp(ID("r"), ID("k"), "SYMBOL_AMPERSAND"))],
|
||||||
|
),
|
||||||
|
ExpressionStatement(ID("r")),
|
||||||
|
]),
|
||||||
|
ValStr("ab")),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDictIteration(unittest.TestCase):
|
||||||
|
@parameterized.expand(dict_iteration_tests)
|
||||||
|
def test_dict_iteration(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
|
||||||
|
dict_display_tests = [
|
||||||
|
# DIC on dict
|
||||||
|
('DIC(TABVLA {"a" VT I})',
|
||||||
|
Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([(String("a"), Numeral("I"))])]))]),
|
||||||
|
ValStr("{a VT I}"), "{a VT I}\n"),
|
||||||
|
# DIC on multi-entry dict
|
||||||
|
('DIC(TABVLA {"a" VT I, "b" VT II})',
|
||||||
|
Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([(String("a"), Numeral("I")), (String("b"), Numeral("II"))])]))]),
|
||||||
|
ValStr("{a VT I, b VT II}"), "{a VT I, b VT II}\n"),
|
||||||
|
# DIC on empty dict
|
||||||
|
('DIC(TABVLA {})',
|
||||||
|
Program([], [ExpressionStatement(BuiltIn("DIC", [DataDict([])]))]),
|
||||||
|
ValStr("{}"), "{}\n"),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestDictDisplay(unittest.TestCase):
|
||||||
|
@parameterized.expand(dict_display_tests)
|
||||||
|
def test_dict_display(self, source, nodes, value, output):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
263
tests/09_test_fraction.py
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- FRACTIO module ---
|
||||||
|
|
||||||
|
fractio_tests = [
|
||||||
|
# Basic fraction literals
|
||||||
|
("CVM FRACTIO\nIIIS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("IIIS"))]),
|
||||||
|
ValFrac(Fraction(7, 2))),
|
||||||
|
("CVM FRACTIO\nS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("S"))]),
|
||||||
|
ValFrac(Fraction(1, 2))),
|
||||||
|
("CVM FRACTIO\nS:.",
|
||||||
|
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("S:."))]),
|
||||||
|
ValFrac(Fraction(3, 4))),
|
||||||
|
("CVM FRACTIO\n.",
|
||||||
|
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("."))]),
|
||||||
|
ValFrac(Fraction(1, 12))),
|
||||||
|
("CVM FRACTIO\n:.",
|
||||||
|
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio(":."))]),
|
||||||
|
ValFrac(Fraction(1, 4))),
|
||||||
|
# Integer part with fraction
|
||||||
|
("CVM FRACTIO\nVIIS:|::",
|
||||||
|
Program([ModuleCall("FRACTIO")], [ExpressionStatement(Fractio("VIIS:|::"))]),
|
||||||
|
ValFrac(Fraction(7) + Fraction(100, 144))),
|
||||||
|
# Arithmetic
|
||||||
|
("CVM FRACTIO\nIIIS + S",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_PLUS"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(4))
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIIIS - S",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_MINUS"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(3))
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nS * IV",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("S"), Numeral("IV"), "SYMBOL_TIMES"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(2))
|
||||||
|
),
|
||||||
|
# Division returns fraction
|
||||||
|
("CVM FRACTIO\nI / IV",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Numeral("I"), Numeral("IV"), "SYMBOL_DIVIDE"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(1, 4))
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nI / III",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Numeral("I"), Numeral("III"), "SYMBOL_DIVIDE"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(1, 3))
|
||||||
|
),
|
||||||
|
# Integer division still works without fractions in operands... but with FRACTIO returns ValFrac
|
||||||
|
("CVM FRACTIO\nX / II",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(5))
|
||||||
|
),
|
||||||
|
# Modulo on fractions: 7/2 RELIQVVM 3/2 = 1/2 (7/2 / 3/2 = 7/3, floor=2, 7/2 - 3 = 1/2)
|
||||||
|
("CVM FRACTIO\nIIIS RELIQVVM IS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IS"), "KEYWORD_RELIQVVM"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(1, 2))
|
||||||
|
),
|
||||||
|
# Modulo with mixed operand types: 5/2 RELIQVVM 1 = 1/2
|
||||||
|
("CVM FRACTIO\nIIS RELIQVVM I",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIS"), Numeral("I"), "KEYWORD_RELIQVVM"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(1, 2))
|
||||||
|
),
|
||||||
|
# Int operands under FRACTIO still return a fraction: 10 RELIQVVM 3 = 1 (as Fraction)
|
||||||
|
("CVM FRACTIO\nX RELIQVVM III",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Numeral("X"), Numeral("III"), "KEYWORD_RELIQVVM"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(1))
|
||||||
|
),
|
||||||
|
# Exact multiple under FRACTIO: 3 RELIQVVM 3/2 = 0
|
||||||
|
("CVM FRACTIO\nIII RELIQVVM IS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Numeral("III"), Fractio("IS"), "KEYWORD_RELIQVVM"))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(0))
|
||||||
|
),
|
||||||
|
# String concatenation with fraction
|
||||||
|
("CVM FRACTIO\nDIC(IIIS & \"!\")",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [BinOp(Fractio("IIIS"), String("!"), "SYMBOL_AMPERSAND")]))
|
||||||
|
]),
|
||||||
|
ValStr("IIIS!"), "IIIS!\n"
|
||||||
|
),
|
||||||
|
# Negative fractions
|
||||||
|
("CVM FRACTIO\nCVM SVBNVLLA\n-IIS",
|
||||||
|
Program([ModuleCall("FRACTIO"),ModuleCall("SVBNVLLA")],[
|
||||||
|
ExpressionStatement(UnaryMinus(Fractio("IIS")))
|
||||||
|
]),
|
||||||
|
ValFrac(Fraction(-5,2))
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestFractio(unittest.TestCase):
|
||||||
|
@parameterized.expand(fractio_tests)
|
||||||
|
def test_fractio(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
|
||||||
|
fractio_comparison_tests = [
|
||||||
|
# fraction vs fraction
|
||||||
|
("CVM FRACTIO\nIIIS PLVS III",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("III"), "KEYWORD_PLVS"))
|
||||||
|
]),
|
||||||
|
ValBool(True)
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIII MINVS IIIS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Numeral("III"), Fractio("IIIS"), "KEYWORD_MINVS"))
|
||||||
|
]),
|
||||||
|
ValBool(True)
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIIIS MINVS IV",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("IV"), "KEYWORD_MINVS"))
|
||||||
|
]),
|
||||||
|
ValBool(True)
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIV PLVS IIIS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Numeral("IV"), Fractio("IIIS"), "KEYWORD_PLVS"))
|
||||||
|
]),
|
||||||
|
ValBool(True)
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIIIS PLVS IIIS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_PLVS"))
|
||||||
|
]),
|
||||||
|
ValBool(False)
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIIIS MINVS IIIS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_MINVS"))
|
||||||
|
]),
|
||||||
|
ValBool(False)
|
||||||
|
),
|
||||||
|
# HAVD_PLVS / HAVD_MINVS on fractions — equality boundary distinguishes from MINVS / PLVS
|
||||||
|
("CVM FRACTIO\nIIIS HAVD_PLVS III",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("III"), "KEYWORD_HAVD_PLVS"))
|
||||||
|
]),
|
||||||
|
ValBool(False) # 3.5 <= 3 is false
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIIIS HAVD_MINVS IIIS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_HAVD_MINVS"))
|
||||||
|
]),
|
||||||
|
ValBool(True) # 3.5 >= 3.5 is true (equality boundary)
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIIIS HAVD_PLVS IIIS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_HAVD_PLVS"))
|
||||||
|
]),
|
||||||
|
ValBool(True) # 3.5 <= 3.5 is true (equality boundary)
|
||||||
|
),
|
||||||
|
# equality: fraction == fraction
|
||||||
|
("CVM FRACTIO\nIIIS EST IIIS",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Fractio("IIIS"), "KEYWORD_EST"))
|
||||||
|
]),
|
||||||
|
ValBool(True)
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nIIIS EST IV",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(Fractio("IIIS"), Numeral("IV"), "KEYWORD_EST"))
|
||||||
|
]),
|
||||||
|
ValBool(False)
|
||||||
|
),
|
||||||
|
# equality: fraction == whole number (ValFrac(4) vs ValInt(4))
|
||||||
|
("CVM FRACTIO\nIIIS + S EST IV",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(
|
||||||
|
BinOp(Fractio("IIIS"), Fractio("S"), "SYMBOL_PLUS"),
|
||||||
|
Numeral("IV"), "KEYWORD_EST"))
|
||||||
|
]),
|
||||||
|
ValBool(True)
|
||||||
|
),
|
||||||
|
("CVM FRACTIO\nS + S EST I",
|
||||||
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
ExpressionStatement(BinOp(
|
||||||
|
BinOp(Fractio("S"), Fractio("S"), "SYMBOL_PLUS"),
|
||||||
|
Numeral("I"), "KEYWORD_EST"))
|
||||||
|
]),
|
||||||
|
ValBool(True)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestFractioComparisons(unittest.TestCase):
|
||||||
|
@parameterized.expand(fractio_comparison_tests)
|
||||||
|
def test_fractio_comparison(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFractioHelpers(unittest.TestCase):
|
||||||
|
def test_frac_to_fraction_ordering(self):
|
||||||
|
with self.assertRaises(CentvrionError):
|
||||||
|
frac_to_fraction(".S") # . before S violates highest-to-lowest
|
||||||
|
|
||||||
|
def test_frac_to_fraction_level_overflow(self):
|
||||||
|
with self.assertRaises(CentvrionError):
|
||||||
|
frac_to_fraction("SSSSSS") # SS means S twice = 12/12 = 1, violating < 12/12 constraint
|
||||||
|
|
||||||
|
def test_frac_to_fraction_iiis(self):
|
||||||
|
self.assertEqual(frac_to_fraction("IIIS"), Fraction(7, 2))
|
||||||
|
|
||||||
|
def test_frac_to_fraction_s_colon_dot(self):
|
||||||
|
self.assertEqual(frac_to_fraction("S:."), Fraction(3, 4))
|
||||||
|
|
||||||
|
def test_frac_to_fraction_dot(self):
|
||||||
|
self.assertEqual(frac_to_fraction("."), Fraction(1, 12))
|
||||||
|
|
||||||
|
def test_frac_to_fraction_multilevel(self):
|
||||||
|
self.assertEqual(frac_to_fraction("VIIS:|::"), Fraction(7) + Fraction(100, 144))
|
||||||
|
|
||||||
|
def test_fraction_to_frac_iiis(self):
|
||||||
|
self.assertEqual(fraction_to_frac(Fraction(7, 2)), "IIIS")
|
||||||
|
|
||||||
|
def test_fraction_to_frac_s_colon_dot(self):
|
||||||
|
self.assertEqual(fraction_to_frac(Fraction(3, 4)), "S:.")
|
||||||
|
|
||||||
|
def test_fraction_to_frac_dot(self):
|
||||||
|
self.assertEqual(fraction_to_frac(Fraction(1, 12)), ".")
|
||||||
|
|
||||||
|
def test_fraction_to_frac_multilevel(self):
|
||||||
|
self.assertEqual(
|
||||||
|
fraction_to_frac(Fraction(7) + Fraction(100, 144)),
|
||||||
|
"VIIS:|::"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_roundtrip(self):
|
||||||
|
# Only canonical forms roundtrip — fraction_to_frac always uses max colons before dots
|
||||||
|
for s in ["IIIS", "S:.", ".", "::", "VIIS:|::", "S"]:
|
||||||
|
self.assertEqual(fraction_to_frac(frac_to_fraction(s)), s)
|
||||||
406
tests/10_test_external.py
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- 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, "-lcurl", "-lmicrohttpd"],
|
||||||
|
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, "-lcurl", "-lmicrohttpd"],
|
||||||
|
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, "-lcurl", "-lmicrohttpd"],
|
||||||
|
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")\nDIC(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")\nDIC(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")\nDIC(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")\nDIC(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)
|
||||||
|
|
||||||
|
class TestRete(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
import http.server, threading
|
||||||
|
class Handler(http.server.BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "text/plain")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"SALVE MVNDE")
|
||||||
|
def log_message(self, *args):
|
||||||
|
pass
|
||||||
|
cls.server = http.server.HTTPServer(("127.0.0.1", 0), Handler)
|
||||||
|
cls.port = cls.server.server_address[1]
|
||||||
|
cls.thread = threading.Thread(target=cls.server.serve_forever)
|
||||||
|
cls.thread.daemon = True
|
||||||
|
cls.thread.start()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
cls.server.shutdown()
|
||||||
|
|
||||||
|
def test_pete(self):
|
||||||
|
url = f"http://127.0.0.1:{self.port}/"
|
||||||
|
source = f'CVM RETE\nPETE("{url}")'
|
||||||
|
run_test(self, source,
|
||||||
|
Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("PETE", [String(url)]))]),
|
||||||
|
ValStr("SALVE MVNDE"))
|
||||||
|
|
||||||
|
def test_pete_dic(self):
|
||||||
|
url = f"http://127.0.0.1:{self.port}/"
|
||||||
|
source = f'CVM RETE\nDIC(PETE("{url}"))'
|
||||||
|
run_test(self, source,
|
||||||
|
Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("DIC", [BuiltIn("PETE", [String(url)])]))]),
|
||||||
|
ValStr("SALVE MVNDE"), "SALVE MVNDE\n")
|
||||||
|
|
||||||
|
class TestReteServer(unittest.TestCase):
|
||||||
|
"""Integration tests for PETITVR + AVSCVLTA server functionality."""
|
||||||
|
|
||||||
|
def _wait_for_server(self, port, timeout=2.0):
|
||||||
|
"""Poll until the server is accepting connections."""
|
||||||
|
import socket
|
||||||
|
deadline = time.time() + timeout
|
||||||
|
while time.time() < deadline:
|
||||||
|
try:
|
||||||
|
with socket.create_connection(("127.0.0.1", port), timeout=0.1):
|
||||||
|
return
|
||||||
|
except OSError:
|
||||||
|
time.sleep(0.05)
|
||||||
|
self.fail(f"Server on port {port} did not start within {timeout}s")
|
||||||
|
|
||||||
|
def _free_port(self):
|
||||||
|
"""Find a free port in range 1024-3999 (representable without MAGNVM)."""
|
||||||
|
import socket, random
|
||||||
|
for _ in range(100):
|
||||||
|
port = random.randint(1024, 3999)
|
||||||
|
try:
|
||||||
|
with socket.socket() as s:
|
||||||
|
s.bind(("127.0.0.1", port))
|
||||||
|
return port
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
raise RuntimeError("Could not find a free port in range 1024-3999")
|
||||||
|
|
||||||
|
def _run_server(self, source):
|
||||||
|
"""Parse and eval source in a daemon thread. Returns when server is ready."""
|
||||||
|
import threading
|
||||||
|
lexer = Lexer().get_lexer()
|
||||||
|
tokens = lexer.lex(source + "\n")
|
||||||
|
program = Parser().parse(tokens)
|
||||||
|
t = threading.Thread(target=program.eval, daemon=True)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
def test_basic_handler(self):
|
||||||
|
port = self._free_port()
|
||||||
|
source = (
|
||||||
|
f'CVM RETE\n'
|
||||||
|
f'PETITVR("/", FVNCTIO (petitio) VT {{\n'
|
||||||
|
f'REDI("SALVE MVNDE")\n'
|
||||||
|
f'}})\n'
|
||||||
|
f'AVSCVLTA({int_to_num(port, False)})'
|
||||||
|
)
|
||||||
|
self._run_server(source)
|
||||||
|
self._wait_for_server(port)
|
||||||
|
import urllib.request
|
||||||
|
resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/")
|
||||||
|
self.assertEqual(resp.read().decode(), "SALVE MVNDE")
|
||||||
|
|
||||||
|
def test_multiple_routes(self):
|
||||||
|
port = self._free_port()
|
||||||
|
source = (
|
||||||
|
f'CVM RETE\n'
|
||||||
|
f'PETITVR("/", FVNCTIO (p) VT {{\nREDI("RADIX")\n}})\n'
|
||||||
|
f'PETITVR("/nomen", FVNCTIO (p) VT {{\nREDI("MARCVS")\n}})\n'
|
||||||
|
f'AVSCVLTA({int_to_num(port, False)})'
|
||||||
|
)
|
||||||
|
self._run_server(source)
|
||||||
|
self._wait_for_server(port)
|
||||||
|
import urllib.request
|
||||||
|
resp1 = urllib.request.urlopen(f"http://127.0.0.1:{port}/")
|
||||||
|
self.assertEqual(resp1.read().decode(), "RADIX")
|
||||||
|
resp2 = urllib.request.urlopen(f"http://127.0.0.1:{port}/nomen")
|
||||||
|
self.assertEqual(resp2.read().decode(), "MARCVS")
|
||||||
|
|
||||||
|
def test_404_unmatched(self):
|
||||||
|
port = self._free_port()
|
||||||
|
source = (
|
||||||
|
f'CVM RETE\n'
|
||||||
|
f'PETITVR("/", FVNCTIO (p) VT {{\nREDI("ok")\n}})\n'
|
||||||
|
f'AVSCVLTA({int_to_num(port, False)})'
|
||||||
|
)
|
||||||
|
self._run_server(source)
|
||||||
|
self._wait_for_server(port)
|
||||||
|
import urllib.request, urllib.error
|
||||||
|
with self.assertRaises(urllib.error.HTTPError) as ctx:
|
||||||
|
urllib.request.urlopen(f"http://127.0.0.1:{port}/nonexistent")
|
||||||
|
self.assertEqual(ctx.exception.code, 404)
|
||||||
|
|
||||||
|
def test_request_dict_via(self):
|
||||||
|
port = self._free_port()
|
||||||
|
source = (
|
||||||
|
f'CVM RETE\n'
|
||||||
|
f'PETITVR("/echo", FVNCTIO (petitio) VT {{\n'
|
||||||
|
f'REDI(petitio["via"])\n'
|
||||||
|
f'}})\n'
|
||||||
|
f'AVSCVLTA({int_to_num(port, False)})'
|
||||||
|
)
|
||||||
|
self._run_server(source)
|
||||||
|
self._wait_for_server(port)
|
||||||
|
import urllib.request
|
||||||
|
resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/echo")
|
||||||
|
self.assertEqual(resp.read().decode(), "/echo")
|
||||||
|
|
||||||
|
def test_request_dict_quaestio(self):
|
||||||
|
port = self._free_port()
|
||||||
|
source = (
|
||||||
|
f'CVM RETE\n'
|
||||||
|
f'PETITVR("/q", FVNCTIO (petitio) VT {{\n'
|
||||||
|
f'REDI(petitio["quaestio"])\n'
|
||||||
|
f'}})\n'
|
||||||
|
f'AVSCVLTA({int_to_num(port, False)})'
|
||||||
|
)
|
||||||
|
self._run_server(source)
|
||||||
|
self._wait_for_server(port)
|
||||||
|
import urllib.request
|
||||||
|
resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/q?nomen=Marcus")
|
||||||
|
self.assertEqual(resp.read().decode(), "nomen=Marcus")
|
||||||
|
|
||||||
|
def test_petitvr_stores_route(self):
|
||||||
|
"""PETITVR alone (without AVSCVLTA) just stores a route and returns NVLLVS."""
|
||||||
|
source = 'CVM RETE\nPETITVR("/", FVNCTIO (p) VT {\nREDI("hi")\n})'
|
||||||
|
run_test(self, source,
|
||||||
|
Program([ModuleCall("RETE")], [ExpressionStatement(BuiltIn("PETITVR", [
|
||||||
|
String("/"),
|
||||||
|
Fvnctio([ID("p")], [Redi([String("hi")])])
|
||||||
|
]))]),
|
||||||
|
ValNul())
|
||||||
443
tests/11_test_assorted.py
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Comments ---
|
||||||
|
|
||||||
|
comment_tests = [
|
||||||
|
# trailing line comment
|
||||||
|
('DIC("hello") // this is ignored', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hello")]))]), ValStr("hello"), "hello\n"),
|
||||||
|
# line comment on its own line before code
|
||||||
|
('// ignored\nDIC("hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"),
|
||||||
|
# inline block comment
|
||||||
|
('DIC(/* ignored */ "hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"),
|
||||||
|
# block comment spanning multiple lines
|
||||||
|
('/* line one\nline two */\nDIC("hi")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("hi")]))]), ValStr("hi"), "hi\n"),
|
||||||
|
# block comment mid-expression
|
||||||
|
("II /* ignored */ + III", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"))]), ValInt(5)),
|
||||||
|
# line comment after expression (no output)
|
||||||
|
("II + III // ignored", Program([], [ExpressionStatement(BinOp(Numeral("II"), Numeral("III"), "SYMBOL_PLUS"))]), ValInt(5)),
|
||||||
|
# division still works (/ token not confused with //)
|
||||||
|
("X / II", Program([], [ExpressionStatement(BinOp(Numeral("X"), Numeral("II"), "SYMBOL_DIVIDE"))]), ValInt(5)),
|
||||||
|
# multiple line comments
|
||||||
|
('// first\n// second\nDIC("ok")', Program([], [ExpressionStatement(BuiltIn("DIC", [String("ok")]))]), ValStr("ok"), "ok\n"),
|
||||||
|
# comment-only line between two statements
|
||||||
|
('DESIGNA x VT I\n// set y\nDESIGNA y VT II\nx + y',
|
||||||
|
Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("II")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]),
|
||||||
|
ValInt(3)),
|
||||||
|
# blank line between two statements (double newline)
|
||||||
|
('DESIGNA x VT I\n\nDESIGNA y VT II\nx + y',
|
||||||
|
Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("II")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]),
|
||||||
|
ValInt(3)),
|
||||||
|
# multiple comment-only lines between statements
|
||||||
|
('DESIGNA x VT I\n// one\n// two\nDESIGNA y VT III\nx + y',
|
||||||
|
Program([], [Designa(ID("x"), Numeral("I")), Designa(ID("y"), Numeral("III")), ExpressionStatement(BinOp(ID("x"), ID("y"), "SYMBOL_PLUS"))]),
|
||||||
|
ValInt(4)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestComments(unittest.TestCase):
|
||||||
|
@parameterized.expand(comment_tests)
|
||||||
|
def test_comments(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- Scope ---
|
||||||
|
|
||||||
|
scope_tests = [
|
||||||
|
# SI: variable assigned in true branch persists in outer scope
|
||||||
|
("SI VERITAS TVNC { DESIGNA r VT X }\nr",
|
||||||
|
Program([], [SiStatement(Bool(True), [Designa(ID("r"), Numeral("X"))], None), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(10)),
|
||||||
|
# SI: variable assigned in ALIVD branch persists in outer scope
|
||||||
|
("SI FALSITAS TVNC { DESIGNA r VT X } ALIVD { DESIGNA r VT V }\nr",
|
||||||
|
Program([], [SiStatement(Bool(False), [Designa(ID("r"), Numeral("X"))], [Designa(ID("r"), Numeral("V"))]), ExpressionStatement(ID("r"))]),
|
||||||
|
ValInt(5)),
|
||||||
|
# DVM: variable assigned in body persists after loop exits
|
||||||
|
# x goes 1→2→3→4→5; r tracks x each iteration; loop exits when x==5
|
||||||
|
("DESIGNA x VT I\nDVM x EST V FAC { DESIGNA x VT x + I\nDESIGNA r VT x }\nr",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(BinOp(ID("x"), Numeral("V"), "KEYWORD_EST"), [
|
||||||
|
Designa(ID("x"), BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")),
|
||||||
|
Designa(ID("r"), ID("x")),
|
||||||
|
]),
|
||||||
|
ExpressionStatement(ID("r")),
|
||||||
|
]),
|
||||||
|
ValInt(5)),
|
||||||
|
# PER: loop variable holds last array element after loop (no ERVMPE)
|
||||||
|
("PER i IN [I, II, III] FAC { DESIGNA nop VT I }\ni",
|
||||||
|
Program([], [
|
||||||
|
PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [Designa(ID("nop"), Numeral("I"))]),
|
||||||
|
ExpressionStatement(ID("i")),
|
||||||
|
]),
|
||||||
|
ValInt(3)),
|
||||||
|
# PER: reassigning loop var in body doesn't prevent remaining iterations from running
|
||||||
|
# cnt increments once per iteration (all 3); C=100 doesn't replace next element assignment
|
||||||
|
("DESIGNA cnt VT I\nPER i IN [I, II, III] FAC { DESIGNA i VT C\nDESIGNA cnt VT cnt + I }\ncnt",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("cnt"), Numeral("I")),
|
||||||
|
PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [
|
||||||
|
Designa(ID("i"), Numeral("C")),
|
||||||
|
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS")),
|
||||||
|
]),
|
||||||
|
ExpressionStatement(ID("cnt")),
|
||||||
|
]),
|
||||||
|
ValInt(4)),
|
||||||
|
# PER: loop var after loop reflects the last body assignment, not the last array element
|
||||||
|
# body sets i=C=100 on every iteration; after loop ends, i stays at 100
|
||||||
|
("PER i IN [I, II, III] FAC { DESIGNA i VT C }\ni",
|
||||||
|
Program([], [
|
||||||
|
PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [Designa(ID("i"), Numeral("C"))]),
|
||||||
|
ExpressionStatement(ID("i")),
|
||||||
|
]),
|
||||||
|
ValInt(100)),
|
||||||
|
# DONICVM: counter holds last range value after loop ends
|
||||||
|
# [I VSQVE IV] = [1,2,3,4]; last value assigned by loop is IV=4
|
||||||
|
("DONICVM i VT I VSQVE IV FAC { DESIGNA nop VT I }\ni",
|
||||||
|
Program([], [
|
||||||
|
PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [Designa(ID("nop"), Numeral("I"))]),
|
||||||
|
ExpressionStatement(ID("i")),
|
||||||
|
]),
|
||||||
|
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 4 times → 5
|
||||||
|
("DESIGNA cnt VT I\nDONICVM i VT I VSQVE IV FAC { DESIGNA cnt VT cnt + I\nDESIGNA i VT C }\ncnt",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("cnt"), Numeral("I")),
|
||||||
|
PerStatement(DataRangeArray(Numeral("I"), Numeral("IV")), ID("i"), [
|
||||||
|
Designa(ID("cnt"), BinOp(ID("cnt"), Numeral("I"), "SYMBOL_PLUS")),
|
||||||
|
Designa(ID("i"), Numeral("C")),
|
||||||
|
]),
|
||||||
|
ExpressionStatement(ID("cnt")),
|
||||||
|
]),
|
||||||
|
ValInt(5)),
|
||||||
|
# DONICVM: ERVMPE exits loop early; counter persists at break value
|
||||||
|
("DONICVM i VT I VSQVE X FAC {\nSI i EST III TVNC { ERVMPE }\n}\ni",
|
||||||
|
Program([], [
|
||||||
|
PerStatement(DataRangeArray(Numeral("I"), Numeral("X")), ID("i"), [
|
||||||
|
SiStatement(BinOp(ID("i"), Numeral("III"), "KEYWORD_EST"), [Erumpe()], None),
|
||||||
|
]),
|
||||||
|
ExpressionStatement(ID("i")),
|
||||||
|
]),
|
||||||
|
ValInt(3)),
|
||||||
|
# Function: modifying parameter inside function does not affect outer variable of same name
|
||||||
|
# outer n=1; f receives n=5 and modifies its local copy; outer n unchanged
|
||||||
|
("DESIGNA n VT I\nDEFINI f (n) VT { DESIGNA n VT n + X\nREDI (n) }\nINVOCA f (V)\nn",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("n"), Numeral("I")),
|
||||||
|
Defini(ID("f"), [ID("n")], [Designa(ID("n"), BinOp(ID("n"), Numeral("X"), "SYMBOL_PLUS")), Redi([ID("n")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [Numeral("V")])),
|
||||||
|
ExpressionStatement(ID("n")),
|
||||||
|
]),
|
||||||
|
ValInt(1)),
|
||||||
|
# Function: mutating outer variable inside function (via DESIGNA) is not visible outside
|
||||||
|
# Invoca creates func_vtable = vtable.copy(); mutations to func_vtable don't propagate back
|
||||||
|
("DESIGNA x VT I\nDEFINI f () VT { DESIGNA x VT C\nREDI (x) }\nINVOCA f ()\nx",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
Defini(ID("f"), [], [Designa(ID("x"), Numeral("C")), Redi([ID("x")])]),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [])),
|
||||||
|
ExpressionStatement(ID("x")),
|
||||||
|
]),
|
||||||
|
ValInt(1)),
|
||||||
|
# Function: two successive calls with same parameter name don't share state
|
||||||
|
("DEFINI f (n) VT { REDI (n * II) }\nINVOCA f (III) + INVOCA f (IV)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||||
|
ExpressionStatement(BinOp(Invoca(ID("f"), [Numeral("III")]), Invoca(ID("f"), [Numeral("IV")]), "SYMBOL_PLUS")),
|
||||||
|
]),
|
||||||
|
ValInt(14)),
|
||||||
|
# Function: calling f(I) with param named n does not overwrite outer n=II
|
||||||
|
# f is defined before n is designated; INVOCA creates a local copy, outer vtable unchanged
|
||||||
|
("DEFINI f (n) VT { REDI (n * II) }\nDESIGNA n VT II\nINVOCA f (I)\nn",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("f"), [ID("n")], [Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||||
|
Designa(ID("n"), Numeral("II")),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [Numeral("I")])),
|
||||||
|
ExpressionStatement(ID("n")),
|
||||||
|
]),
|
||||||
|
ValInt(2)),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestScope(unittest.TestCase):
|
||||||
|
@parameterized.expand(scope_tests)
|
||||||
|
def test_scope(self, source, nodes, value):
|
||||||
|
run_test(self, source, nodes, value)
|
||||||
|
|
||||||
|
# --- First-class functions / FVNCTIO ---
|
||||||
|
|
||||||
|
fvnctio_tests = [
|
||||||
|
# Lambda assigned to variable, then called
|
||||||
|
(
|
||||||
|
"DESIGNA f VT FVNCTIO (x) VT { REDI (x + I) }\nINVOCA f (V)",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("f"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [Numeral("V")])),
|
||||||
|
]),
|
||||||
|
ValInt(6),
|
||||||
|
),
|
||||||
|
# IIFE: immediately invoked lambda
|
||||||
|
(
|
||||||
|
"INVOCA FVNCTIO (x) VT { REDI (x * II) } (III)",
|
||||||
|
Program([], [
|
||||||
|
ExpressionStatement(Invoca(
|
||||||
|
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||||
|
[Numeral("III")],
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
ValInt(6),
|
||||||
|
),
|
||||||
|
# Zero-arg lambda
|
||||||
|
(
|
||||||
|
"INVOCA FVNCTIO () VT { REDI (XLII) } ()",
|
||||||
|
Program([], [
|
||||||
|
ExpressionStatement(Invoca(
|
||||||
|
Fvnctio([], [Redi([Numeral("XLII")])]),
|
||||||
|
[],
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
ValInt(42),
|
||||||
|
),
|
||||||
|
# Function passed as argument
|
||||||
|
(
|
||||||
|
"DEFINI apply (f, x) VT { REDI (INVOCA f (x)) }\n"
|
||||||
|
"DESIGNA dbl VT FVNCTIO (n) VT { REDI (n * II) }\n"
|
||||||
|
"INVOCA apply (dbl, V)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("apply"), [ID("f"), ID("x")], [
|
||||||
|
Redi([Invoca(ID("f"), [ID("x")])])
|
||||||
|
]),
|
||||||
|
Designa(ID("dbl"), Fvnctio([ID("n")], [
|
||||||
|
Redi([BinOp(ID("n"), Numeral("II"), "SYMBOL_TIMES")])
|
||||||
|
])),
|
||||||
|
ExpressionStatement(Invoca(ID("apply"), [ID("dbl"), Numeral("V")])),
|
||||||
|
]),
|
||||||
|
ValInt(10),
|
||||||
|
),
|
||||||
|
# Lambda uses caller-scope variable (copy-caller semantics)
|
||||||
|
(
|
||||||
|
"DESIGNA n VT III\n"
|
||||||
|
"DESIGNA f VT FVNCTIO (x) VT { REDI (x + n) }\n"
|
||||||
|
"INVOCA f (V)",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("n"), Numeral("III")),
|
||||||
|
Designa(ID("f"), Fvnctio([ID("x")], [
|
||||||
|
Redi([BinOp(ID("x"), ID("n"), "SYMBOL_PLUS")])
|
||||||
|
])),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [Numeral("V")])),
|
||||||
|
]),
|
||||||
|
ValInt(8),
|
||||||
|
),
|
||||||
|
# Named function passed as value
|
||||||
|
(
|
||||||
|
"DEFINI sqr (x) VT { REDI (x * x) }\n"
|
||||||
|
"DESIGNA f VT sqr\n"
|
||||||
|
"INVOCA f (IV)",
|
||||||
|
Program([], [
|
||||||
|
Defini(ID("sqr"), [ID("x")], [Redi([BinOp(ID("x"), ID("x"), "SYMBOL_TIMES")])]),
|
||||||
|
Designa(ID("f"), ID("sqr")),
|
||||||
|
ExpressionStatement(Invoca(ID("f"), [Numeral("IV")])),
|
||||||
|
]),
|
||||||
|
ValInt(16),
|
||||||
|
),
|
||||||
|
# Nested lambdas
|
||||||
|
(
|
||||||
|
"INVOCA FVNCTIO (x) VT { REDI (INVOCA FVNCTIO (y) VT { REDI (y + I) } (x)) } (V)",
|
||||||
|
Program([], [
|
||||||
|
ExpressionStatement(Invoca(
|
||||||
|
Fvnctio([ID("x")], [
|
||||||
|
Redi([Invoca(
|
||||||
|
Fvnctio([ID("y")], [Redi([BinOp(ID("y"), Numeral("I"), "SYMBOL_PLUS")])]),
|
||||||
|
[ID("x")],
|
||||||
|
)])
|
||||||
|
]),
|
||||||
|
[Numeral("V")],
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
ValInt(6),
|
||||||
|
),
|
||||||
|
# DIC on a function value
|
||||||
|
(
|
||||||
|
"DESIGNA f VT FVNCTIO (x) VT { REDI (x) }\nDIC(f)",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("f"), Fvnctio([ID("x")], [Redi([ID("x")])])),
|
||||||
|
ExpressionStatement(BuiltIn("DIC", [ID("f")])),
|
||||||
|
]),
|
||||||
|
ValStr("FVNCTIO"),
|
||||||
|
"FVNCTIO\n",
|
||||||
|
),
|
||||||
|
# Lambda stored in array, called via index
|
||||||
|
(
|
||||||
|
"DESIGNA fns VT [FVNCTIO (x) VT { REDI (x + I) }, FVNCTIO (x) VT { REDI (x * II) }]\n"
|
||||||
|
"INVOCA fns[I] (V)",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("fns"), DataArray([
|
||||||
|
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])]),
|
||||||
|
Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("II"), "SYMBOL_TIMES")])]),
|
||||||
|
])),
|
||||||
|
ExpressionStatement(Invoca(
|
||||||
|
ArrayIndex(ID("fns"), Numeral("I")),
|
||||||
|
[Numeral("V")],
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
ValInt(6),
|
||||||
|
),
|
||||||
|
# Lambda stored in dict, called via key
|
||||||
|
(
|
||||||
|
'DESIGNA d VT TABVLA {"add" VT FVNCTIO (x) VT { REDI (x + I) }}\n'
|
||||||
|
'INVOCA d["add"] (V)',
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("d"), DataDict([
|
||||||
|
(String("add"), Fvnctio([ID("x")], [Redi([BinOp(ID("x"), Numeral("I"), "SYMBOL_PLUS")])])),
|
||||||
|
])),
|
||||||
|
ExpressionStatement(Invoca(
|
||||||
|
ArrayIndex(ID("d"), String("add")),
|
||||||
|
[Numeral("V")],
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
ValInt(6),
|
||||||
|
),
|
||||||
|
# Multi-param lambda
|
||||||
|
(
|
||||||
|
"DESIGNA add VT FVNCTIO (a, b) VT { REDI (a + b) }\nINVOCA add (III, IV)",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("add"), Fvnctio([ID("a"), ID("b")], [
|
||||||
|
Redi([BinOp(ID("a"), ID("b"), "SYMBOL_PLUS")])
|
||||||
|
])),
|
||||||
|
ExpressionStatement(Invoca(ID("add"), [Numeral("III"), Numeral("IV")])),
|
||||||
|
]),
|
||||||
|
ValInt(7),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestFvnctio(unittest.TestCase):
|
||||||
|
@parameterized.expand(fvnctio_tests)
|
||||||
|
def test_fvnctio(self, source, nodes, value, output=""):
|
||||||
|
run_test(self, source, nodes, value, output)
|
||||||
|
|
||||||
|
# --- 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 FAC {\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)
|
||||||
148
tests/12_test_failures.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
from tests._helpers import (
|
||||||
|
unittest, parameterized, Fraction, time,
|
||||||
|
run_test, run_compiler_error_test,
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac,
|
||||||
|
CentvrionError, _RUNTIME_C, _cent_rng,
|
||||||
|
Lexer, Parser, compile_program,
|
||||||
|
os, subprocess, tempfile, StringIO, patch,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Errors ---
|
||||||
|
|
||||||
|
error_tests = [
|
||||||
|
("x", CentvrionError), # undefined variable
|
||||||
|
("INVOCA f ()", CentvrionError), # undefined function
|
||||||
|
("DESIGNA VT III", SyntaxError), # parse error: missing id after DESIGNA
|
||||||
|
("DESIGNA x III", SyntaxError), # parse error: missing VT
|
||||||
|
("DEFINI f () VT { REDI(I) }\nf()", SyntaxError), # function call without INVOCA (no args)
|
||||||
|
("DEFINI f (x) VT { REDI(x) }\nf(I)", SyntaxError), # function call without INVOCA (with args)
|
||||||
|
("DIC(M + M + M + M)", CentvrionError), # output > 3999 without MAGNVM
|
||||||
|
("IIII", CentvrionError), # invalid Roman numeral in source
|
||||||
|
("FORTVITVS_NVMERVS(I, X)", CentvrionError), # requires FORS module
|
||||||
|
('NVMERVS(I)', CentvrionError), # NVMERVS expects a string, not int
|
||||||
|
('NVMERVS("ABC")', CentvrionError), # invalid Roman numeral string
|
||||||
|
('NVMERVS("XIV", "IX")', CentvrionError), # too many args
|
||||||
|
("DEFINI f (x) VT { REDI(x) }\nINVOCA f (I, II)", CentvrionError), # too many args
|
||||||
|
("DEFINI f (x, y) VT { REDI(x) }\nINVOCA f (I)", CentvrionError), # too few args
|
||||||
|
("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", CentvrionError), # args to zero-param function
|
||||||
|
("SI NVLLVS TVNC { DESIGNA r VT I }", CentvrionError), # NVLLVS cannot be used as boolean
|
||||||
|
("NVLLVS AVT VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in AVT
|
||||||
|
("FALSITAS AVT NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean
|
||||||
|
("VERITAS ET NVLLVS", CentvrionError), # no short-circuit: right side evaluated, NVLLVS not boolean
|
||||||
|
("NVLLVS ET VERITAS", CentvrionError), # NVLLVS cannot be used as boolean in ET
|
||||||
|
('I @ [II]', CentvrionError), # @ requires two arrays (int @ array)
|
||||||
|
('[I] @ "hello"', CentvrionError), # @ requires two arrays (array @ string)
|
||||||
|
('"a" @ "b"', CentvrionError), # @ requires two arrays (string @ string)
|
||||||
|
('"hello" + " world"', CentvrionError), # use & for string concatenation, not +
|
||||||
|
("[I, II][III]", CentvrionError), # index too high
|
||||||
|
("CVM SVBNVLLA\n[I, II][-I]", CentvrionError), # negative index
|
||||||
|
("[I, II][-I]", CentvrionError), # negative value
|
||||||
|
("I / NVLLVS", CentvrionError), # division by zero (NVLLVS coerces to 0)
|
||||||
|
("V RELIQVVM NVLLVS", CentvrionError), # modulo by zero (NVLLVS coerces to 0)
|
||||||
|
("NVLLVS RELIQVVM NVLLVS", CentvrionError), # modulo by zero (both NVLLVS)
|
||||||
|
("I / [I, II]", CentvrionError), # division with array operand
|
||||||
|
("I - \"hello\"", CentvrionError), # subtraction with string
|
||||||
|
("I * \"hello\"", CentvrionError), # multiplication with string
|
||||||
|
("\"hello\" MINVS \"world\"", CentvrionError), # comparison with strings
|
||||||
|
('"a" HAVD_PLVS "b"', CentvrionError), # HAVD_PLVS on strings
|
||||||
|
('[I] HAVD_MINVS [II]', CentvrionError), # HAVD_MINVS on arrays
|
||||||
|
("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
|
||||||
|
("FORTVITA_ELECTIO([])", CentvrionError), # FORS required for FORTVITA_ELECTIO
|
||||||
|
("CVM FORS\nFORTVITA_ELECTIO([])", CentvrionError), # FORTVITA_ELECTIO on empty array
|
||||||
|
("CVM FORS\nFORTVITVS_NVMERVS(X, I)", CentvrionError), # FORTVITVS_NVMERVS a > b
|
||||||
|
("PER i IN I FAC { DIC(i) }", CentvrionError), # PER over non-array
|
||||||
|
("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
|
||||||
|
("CVM FRACTIO\n[I, II, III][IIIS]", CentvrionError), # fractional index (IIIS = 7/2)
|
||||||
|
("CVM FRACTIO\n[I, II, III][I / II]", CentvrionError), # fractional index from division (1/2)
|
||||||
|
("DESIGNA z VT I - I\nSI z TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: zero int
|
||||||
|
("SI [I] TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: non-empty list
|
||||||
|
("SI [] TVNC { DESIGNA r VT I }", CentvrionError), # non-bool SI condition: empty list
|
||||||
|
("DESIGNA x VT I\nDVM x FAC {\nDESIGNA x VT x + I\n}", CentvrionError), # non-bool DVM condition: int
|
||||||
|
("NON I", CentvrionError), # NON on integer
|
||||||
|
("DESIGNA z VT I - I\nNON z", CentvrionError), # NON on zero integer
|
||||||
|
('NON "hello"', CentvrionError), # NON on string
|
||||||
|
("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
|
||||||
|
("PER a, b IN [I, II, III] FAC { DIC(a) }", CentvrionError), # PER destructure: element is not an array
|
||||||
|
("PER a, b IN [[I], [II]] FAC { DIC(a) }", CentvrionError), # PER destructure: wrong number of elements
|
||||||
|
("[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
|
||||||
|
('QVAERE(I, "abc")', CentvrionError), # QVAERE requires strings, not int
|
||||||
|
('QVAERE("abc", I)', CentvrionError), # QVAERE requires strings, not int
|
||||||
|
('QVAERE("[", "abc")', CentvrionError), # QVAERE invalid regex
|
||||||
|
('SVBSTITVE(I, "b", "c")', CentvrionError), # SVBSTITVE requires strings, not int pattern
|
||||||
|
('SVBSTITVE("a", I, "c")', CentvrionError), # SVBSTITVE requires strings, not int replacement
|
||||||
|
('SVBSTITVE("a", "b", I)', CentvrionError), # SVBSTITVE requires strings, not int text
|
||||||
|
('SVBSTITVE("[", "b", "c")', CentvrionError), # SVBSTITVE invalid regex
|
||||||
|
("SVBSTITVE('(a)', '\\1', 'a')", CentvrionError), # Arabic backref in replacement
|
||||||
|
("QVAERE('(.)\\1', 'aa')", CentvrionError), # Arabic backref in pattern
|
||||||
|
("QVAERE('a{3}', 'aaa')", CentvrionError), # Arabic quantifier in pattern
|
||||||
|
('SCINDE(I, ",")', CentvrionError), # SCINDE requires strings, not int
|
||||||
|
('SCINDE("a", I)', CentvrionError), # SCINDE requires strings, not int delimiter
|
||||||
|
('MAIVSCVLA(I)', CentvrionError), # MAIVSCVLA requires a string, not int
|
||||||
|
('MAIVSCVLA()', CentvrionError), # MAIVSCVLA requires exactly 1 arg
|
||||||
|
('MAIVSCVLA("a", "b")', CentvrionError), # MAIVSCVLA too many args
|
||||||
|
('MINVSCVLA(I)', CentvrionError), # MINVSCVLA requires a string, not int
|
||||||
|
('MINVSCVLA()', CentvrionError), # MINVSCVLA requires exactly 1 arg
|
||||||
|
('MINVSCVLA("a", "b")', CentvrionError), # MINVSCVLA too many args
|
||||||
|
('PETE("http://example.com")', CentvrionError), # RETE required for PETE
|
||||||
|
('CVM RETE\nPETE(I)', CentvrionError), # PETE requires a string URL
|
||||||
|
('PETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})', CentvrionError), # RETE required for PETITVR
|
||||||
|
('CVM RETE\nPETITVR(I, FVNCTIO (r) VT {\nREDI("hi")\n})', CentvrionError), # PETITVR path must be string
|
||||||
|
('CVM RETE\nPETITVR("/", "not a func")', CentvrionError), # PETITVR handler must be function
|
||||||
|
('CVM RETE\nAVSCVLTA(LXXX)', CentvrionError), # AVSCVLTA: no routes registered
|
||||||
|
('AVSCVLTA(LXXX)', CentvrionError), # RETE required for AVSCVLTA
|
||||||
|
('CVM RETE\nPETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})\nAVSCVLTA("text")', CentvrionError), # AVSCVLTA port must be integer
|
||||||
|
("DONICVM i VT I VSQVE X GRADV I - I FAC { DIC(i) }", CentvrionError), # GRADV zero step
|
||||||
|
('DONICVM i VT I VSQVE X GRADV "foo" FAC { DIC(i) }', CentvrionError), # GRADV non-integer step
|
||||||
|
]
|
||||||
|
|
||||||
|
class TestErrors(unittest.TestCase):
|
||||||
|
@parameterized.expand(error_tests)
|
||||||
|
def test_errors(self, source, error_type):
|
||||||
|
with self.assertRaises(error_type):
|
||||||
|
run_test(self, source, None, None)
|
||||||
|
|
||||||
|
compiler_error_tests = [(s, e) for s, e in error_tests if e == CentvrionError]
|
||||||
|
|
||||||
|
class TestCompilerErrors(unittest.TestCase):
|
||||||
|
@parameterized.expand(compiler_error_tests)
|
||||||
|
def test_compiler_errors(self, source, error_type):
|
||||||
|
run_compiler_error_test(self, source)
|
||||||
0
tests/__init__.py
Normal file
135
tests/_helpers.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from io import StringIO
|
||||||
|
from unittest.mock import patch
|
||||||
|
from parameterized import parameterized
|
||||||
|
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
from centvrion.ast_nodes import (
|
||||||
|
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, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction,
|
||||||
|
fraction_to_frac, num_to_int, int_to_num, make_string,
|
||||||
|
_cent_rng,
|
||||||
|
)
|
||||||
|
from centvrion.compiler.emitter import compile_program
|
||||||
|
from centvrion.errors import CentvrionError
|
||||||
|
from centvrion.lexer import Lexer
|
||||||
|
from centvrion.parser import Parser
|
||||||
|
from centvrion.values import ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac
|
||||||
|
|
||||||
|
_RUNTIME_C = os.path.join(
|
||||||
|
os.path.dirname(__file__), "..",
|
||||||
|
"centvrion", "compiler", "runtime", "cent_runtime.c"
|
||||||
|
)
|
||||||
|
|
||||||
|
def run_test(self, source, target_nodes, target_value, target_output="", input_lines=[]):
|
||||||
|
_cent_rng.seed(1)
|
||||||
|
|
||||||
|
lexer = Lexer().get_lexer()
|
||||||
|
tokens = lexer.lex(source + "\n")
|
||||||
|
program = Parser().parse(tokens)
|
||||||
|
|
||||||
|
##########################
|
||||||
|
####### Parser Test ######
|
||||||
|
##########################
|
||||||
|
if target_nodes is not None:
|
||||||
|
self.assertEqual(
|
||||||
|
program,
|
||||||
|
target_nodes,
|
||||||
|
f"Parser test:\n{program}\n{target_nodes}"
|
||||||
|
)
|
||||||
|
|
||||||
|
##########################
|
||||||
|
#### Interpreter Test ####
|
||||||
|
##########################
|
||||||
|
captured = StringIO()
|
||||||
|
try:
|
||||||
|
if input_lines:
|
||||||
|
inputs = iter(input_lines)
|
||||||
|
with patch("builtins.input", lambda: next(inputs)), patch("sys.stdout", captured):
|
||||||
|
result = program.eval()
|
||||||
|
else:
|
||||||
|
with patch("sys.stdout", captured):
|
||||||
|
result = program.eval()
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
self.assertEqual(result, target_value, "Return value test")
|
||||||
|
self.assertEqual(captured.getvalue(), target_output, "Output test")
|
||||||
|
|
||||||
|
##########################
|
||||||
|
###### Printer Test ######
|
||||||
|
##########################
|
||||||
|
try:
|
||||||
|
new_text = program.print()
|
||||||
|
new_tokens = Lexer().get_lexer().lex(new_text + "\n")
|
||||||
|
new_nodes = Parser().parse(new_tokens)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"###Printer test###\n{new_text}") from e
|
||||||
|
self.assertEqual(
|
||||||
|
program,
|
||||||
|
new_nodes,
|
||||||
|
f"Printer test\n{source}\n{new_text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
##########################
|
||||||
|
###### Compiler Test #####
|
||||||
|
##########################
|
||||||
|
c_source = compile_program(program)
|
||||||
|
# Force deterministic RNG seed=1 for test reproducibility
|
||||||
|
c_source = c_source.replace("cent_init();", "cent_init(); cent_semen((CentValue){.type=CENT_INT, .ival=1});", 1)
|
||||||
|
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, "-lcurl", "-lmicrohttpd"],
|
||||||
|
check=True, capture_output=True,
|
||||||
|
)
|
||||||
|
stdin_data = "".join(f"{l}\n" for l in input_lines)
|
||||||
|
proc = subprocess.run(
|
||||||
|
[tmp_bin_path],
|
||||||
|
input=stdin_data, capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(proc.returncode, 0, f"Compiler binary exited non-zero:\n{proc.stderr}")
|
||||||
|
self.assertEqual(proc.stdout, target_output, "Compiler output test")
|
||||||
|
finally:
|
||||||
|
os.unlink(tmp_c_path)
|
||||||
|
os.unlink(tmp_bin_path)
|
||||||
|
|
||||||
|
assert target_nodes is not None, "All tests must have target nodes"
|
||||||
|
|
||||||
|
|
||||||
|
def run_compiler_error_test(self, source):
|
||||||
|
lexer = Lexer().get_lexer()
|
||||||
|
tokens = lexer.lex(source + "\n")
|
||||||
|
program = Parser().parse(tokens)
|
||||||
|
try:
|
||||||
|
c_source = compile_program(program)
|
||||||
|
except CentvrionError:
|
||||||
|
return # compile-time detection is valid
|
||||||
|
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, "-lcurl", "-lmicrohttpd"],
|
||||||
|
check=True, capture_output=True,
|
||||||
|
)
|
||||||
|
proc = subprocess.run([tmp_bin_path], capture_output=True, text=True)
|
||||||
|
self.assertNotEqual(proc.returncode, 0, "Expected non-zero exit for error program")
|
||||||
|
self.assertTrue(proc.stderr.strip(), "Expected error message on stderr")
|
||||||
|
finally:
|
||||||
|
os.unlink(tmp_c_path)
|
||||||
|
os.unlink(tmp_bin_path)
|
||||||
@@ -20,6 +20,10 @@
|
|||||||
"language": "cent",
|
"language": "cent",
|
||||||
"scopeName": "source.cent",
|
"scopeName": "source.cent",
|
||||||
"path": "./syntaxes/cent.tmLanguage.json"
|
"path": "./syntaxes/cent.tmLanguage.json"
|
||||||
|
}],
|
||||||
|
"snippets": [{
|
||||||
|
"language": "cent",
|
||||||
|
"path": "./snippets/cent.json"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
98
vscode-extension/snippets/cent.json
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"SI": {
|
||||||
|
"prefix": "SI",
|
||||||
|
"body": ["SI ${1:condicio} TVNC {", " $0", "}"],
|
||||||
|
"description": "if/then conditional"
|
||||||
|
},
|
||||||
|
"DVM": {
|
||||||
|
"prefix": "DVM",
|
||||||
|
"body": ["DVM ${1:condicio} FAC {", " $0", "}"],
|
||||||
|
"description": "while loop"
|
||||||
|
},
|
||||||
|
"PER": {
|
||||||
|
"prefix": "PER",
|
||||||
|
"body": ["PER ${1:elementum} IN ${2:tabula} FAC {", " $0", "}"],
|
||||||
|
"description": "for-each loop"
|
||||||
|
},
|
||||||
|
"DEFINI": {
|
||||||
|
"prefix": "DEFINI",
|
||||||
|
"body": ["DEFINI ${1:nomen} ${2:parametri} VT {", " $0", "}"],
|
||||||
|
"description": "define named function"
|
||||||
|
},
|
||||||
|
"DONICVM": {
|
||||||
|
"prefix": "DONICVM",
|
||||||
|
"body": ["DONICVM ${1:i} VT ${2:I} VSQVE ${3:X} FAC {", " $0", "}"],
|
||||||
|
"description": "numeric range loop"
|
||||||
|
},
|
||||||
|
|
||||||
|
"AETERNVM": { "prefix": "AETERNVM", "body": "AETERNVM", "description": "infinite loop" },
|
||||||
|
"ALIVD": { "prefix": "ALIVD", "body": "ALIVD", "description": "else / else-if branch" },
|
||||||
|
"AVGE": { "prefix": "AVGE", "body": "AVGE", "description": "compound addition assignment (+=)" },
|
||||||
|
"AVT": { "prefix": "AVT", "body": "AVT", "description": "logical or" },
|
||||||
|
"CAPE": { "prefix": "CAPE", "body": "CAPE", "description": "catch block header" },
|
||||||
|
"CONTINVA": { "prefix": "CONTINVA", "body": "CONTINVA", "description": "skip to next loop iteration" },
|
||||||
|
"CVM": { "prefix": "CVM", "body": "CVM", "description": "include module" },
|
||||||
|
"DESIGNA": { "prefix": "DESIGNA", "body": "DESIGNA", "description": "declare and assign a variable" },
|
||||||
|
"DISPAR": { "prefix": "DISPAR", "body": "DISPAR", "description": "not-equal comparison (!=)" },
|
||||||
|
"DIVIDE": { "prefix": "DIVIDE", "body": "DIVIDE", "description": "compound division assignment (/=)" },
|
||||||
|
"ERVMPE": { "prefix": "ERVMPE", "body": "ERVMPE", "description": "break out of loop" },
|
||||||
|
"EST": { "prefix": "EST", "body": "EST", "description": "equality comparison (==)" },
|
||||||
|
"ET": { "prefix": "ET", "body": "ET", "description": "logical and" },
|
||||||
|
"FAC": { "prefix": "FAC", "body": "FAC", "description": "block opener for loops and functions" },
|
||||||
|
"FALSITAS": { "prefix": "FALSITAS", "body": "FALSITAS", "description": "boolean false" },
|
||||||
|
"FVNCTIO": { "prefix": "FVNCTIO", "body": "FVNCTIO", "description": "anonymous function expression" },
|
||||||
|
"GRADV": { "prefix": "GRADV", "body": "GRADV", "description": "DONICVM range step (by a step of)" },
|
||||||
|
"HAVD_MINVS": { "prefix": "HAVD_MINVS", "body": "HAVD_MINVS", "description": "greater-or-equal (>=)" },
|
||||||
|
"HAVD_PLVS": { "prefix": "HAVD_PLVS", "body": "HAVD_PLVS", "description": "less-or-equal (<=)" },
|
||||||
|
"IN": { "prefix": "IN", "body": "IN", "description": "PER-loop iterator separator" },
|
||||||
|
"INVOCA": { "prefix": "INVOCA", "body": "INVOCA", "description": "invoke / call a function" },
|
||||||
|
"MINVE": { "prefix": "MINVE", "body": "MINVE", "description": "compound subtraction assignment (-=)" },
|
||||||
|
"MINVS": { "prefix": "MINVS", "body": "MINVS", "description": "less-than comparison (<)" },
|
||||||
|
"MVLTIPLICA": { "prefix": "MVLTIPLICA", "body": "MVLTIPLICA", "description": "compound multiplication assignment (*=)" },
|
||||||
|
"NON": { "prefix": "NON", "body": "NON", "description": "logical not" },
|
||||||
|
"NVLLVS": { "prefix": "NVLLVS", "body": "NVLLVS", "description": "null value" },
|
||||||
|
"PLVS": { "prefix": "PLVS", "body": "PLVS", "description": "greater-than comparison (>)" },
|
||||||
|
"REDI": { "prefix": "REDI", "body": "REDI", "description": "return from function" },
|
||||||
|
"RELIQVVM": { "prefix": "RELIQVVM", "body": "RELIQVVM", "description": "modulo / remainder" },
|
||||||
|
"TABVLA": { "prefix": "TABVLA", "body": "TABVLA", "description": "dict literal opener" },
|
||||||
|
"TEMPTA": { "prefix": "TEMPTA", "body": "TEMPTA", "description": "try block header" },
|
||||||
|
"TVNC": { "prefix": "TVNC", "body": "TVNC", "description": "then (SI branch)" },
|
||||||
|
"VERITAS": { "prefix": "VERITAS", "body": "VERITAS", "description": "boolean true" },
|
||||||
|
"VSQVE": { "prefix": "VSQVE", "body": "VSQVE", "description": "upper bound of DONICVM range" },
|
||||||
|
"VT": { "prefix": "VT", "body": "VT", "description": "assignment / binding connective" },
|
||||||
|
|
||||||
|
"ADIVNGE": { "prefix": "ADIVNGE", "body": "ADIVNGE", "description": "append string to file (SCRIPTA module)" },
|
||||||
|
"AVDI": { "prefix": "AVDI", "body": "AVDI", "description": "read a line from stdin" },
|
||||||
|
"AVDI_NVMERVS": { "prefix": "AVDI_NVMERVS", "body": "AVDI_NVMERVS", "description": "read a Roman numeral from stdin" },
|
||||||
|
"AVSCVLTA": { "prefix": "AVSCVLTA", "body": "AVSCVLTA", "description": "start HTTP server on port (RETE module)" },
|
||||||
|
"CLAVES": { "prefix": "CLAVES", "body": "CLAVES", "description": "return the keys of a dict" },
|
||||||
|
"DECIMATIO": { "prefix": "DECIMATIO", "body": "DECIMATIO", "description": "remove a random tenth of an array (FORS module)" },
|
||||||
|
"DIC": { "prefix": "DIC", "body": "DIC", "description": "print value(s) to stdout" },
|
||||||
|
"DORMI": { "prefix": "DORMI", "body": "DORMI", "description": "sleep for N seconds" },
|
||||||
|
"EVERRE": { "prefix": "EVERRE", "body": "EVERRE", "description": "clear the terminal screen" },
|
||||||
|
"FORTVITA_ELECTIO": { "prefix": "FORTVITA_ELECTIO", "body": "FORTVITA_ELECTIO", "description": "pick a random element from an array (FORS module)" },
|
||||||
|
"FORTVITVS_NVMERVS": { "prefix": "FORTVITVS_NVMERVS", "body": "FORTVITVS_NVMERVS", "description": "random integer in a range (FORS module)" },
|
||||||
|
"LEGE": { "prefix": "LEGE", "body": "LEGE", "description": "read file contents (SCRIPTA module)" },
|
||||||
|
"LITTERA": { "prefix": "LITTERA", "body": "LITTERA", "description": "coerce any value to its display string" },
|
||||||
|
"LONGITVDO": { "prefix": "LONGITVDO", "body": "LONGITVDO", "description": "length of array, string, or dict" },
|
||||||
|
"MAIVSCVLA": { "prefix": "MAIVSCVLA", "body": "MAIVSCVLA", "description": "uppercase a string (ASCII a-z → A-Z)" },
|
||||||
|
"MINVSCVLA": { "prefix": "MINVSCVLA", "body": "MINVSCVLA", "description": "lowercase a string (ASCII A-Z → a-z)" },
|
||||||
|
"NVMERVS": { "prefix": "NVMERVS", "body": "NVMERVS", "description": "parse a Roman numeral string to an integer" },
|
||||||
|
"ORDINA": { "prefix": "ORDINA", "body": "ORDINA", "description": "sort an array in ascending order" },
|
||||||
|
"PETE": { "prefix": "PETE", "body": "PETE", "description": "HTTP GET request (RETE module)" },
|
||||||
|
"PETITVR": { "prefix": "PETITVR", "body": "PETITVR", "description": "register HTTP GET handler (RETE module)" },
|
||||||
|
"QVAERE": { "prefix": "QVAERE", "body": "QVAERE", "description": "regex findall" },
|
||||||
|
"SCINDE": { "prefix": "SCINDE", "body": "SCINDE", "description": "split a string by delimiter" },
|
||||||
|
"SCRIBE": { "prefix": "SCRIBE", "body": "SCRIBE", "description": "write string to file (SCRIPTA module)" },
|
||||||
|
"SEMEN": { "prefix": "SEMEN", "body": "SEMEN", "description": "seed the random number generator (FORS module)" },
|
||||||
|
"SENATVS": { "prefix": "SENATVS", "body": "SENATVS", "description": "true if a strict majority of booleans are true" },
|
||||||
|
"SVBSTITVE": { "prefix": "SVBSTITVE", "body": "SVBSTITVE", "description": "regex substitute" },
|
||||||
|
"TYPVS": { "prefix": "TYPVS", "body": "TYPVS", "description": "return the type of a value as a string" },
|
||||||
|
|
||||||
|
"FORS": { "prefix": "FORS", "body": "FORS", "description": "randomness module" },
|
||||||
|
"FRACTIO": { "prefix": "FRACTIO", "body": "FRACTIO", "description": "base-12 fractions module" },
|
||||||
|
"MAGNVM": { "prefix": "MAGNVM", "body": "MAGNVM", "description": "large-integer module (thousands suffix _)" },
|
||||||
|
"RETE": { "prefix": "RETE", "body": "RETE", "description": "networking module (HTTP)" },
|
||||||
|
"SCRIPTA": { "prefix": "SCRIPTA", "body": "SCRIPTA", "description": "file I/O module" },
|
||||||
|
"SVBNVLLA": { "prefix": "SVBNVLLA", "body": "SVBNVLLA", "description": "negative-number module (-II syntax)" }
|
||||||
|
}
|
||||||
@@ -45,11 +45,11 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "keyword.control.cent",
|
"name": "keyword.control.cent",
|
||||||
"match": "\\b(AETERNVM|ALIVD|AVGE|AVT|CAPE|CONTINVA|CVM|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|ET|FAC|FVNCTIO|IN|INVOCA|MINVE|NON|PER|REDI|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT)\\b"
|
"match": "\\b(AETERNVM|ALIVD|AVGE|AVT|CAPE|CONTINVA|CVM|DEFINI|DESIGNA|DIVIDE|DONICVM|DVM|ERVMPE|ET|FAC|FVNCTIO|GRADV|IN|INVOCA|MINVE|MVLTIPLICA|NON|PER|REDI|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT)\\b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "keyword.operator.comparison.cent",
|
"name": "keyword.operator.comparison.cent",
|
||||||
"match": "\\b(DISPAR|EST|MINVS|PLVS)\\b"
|
"match": "\\b(HAVD_PLVS|HAVD_MINVS|DISPAR|EST|MINVS|PLVS)\\b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "keyword.operator.arithmetic.cent",
|
"name": "keyword.operator.arithmetic.cent",
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "support.function.builtin.cent",
|
"name": "support.function.builtin.cent",
|
||||||
"match": "\\b(ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|LEGE|LONGITVDO|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TYPVS)\\b"
|
"match": "\\b(ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TYPVS)\\b"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||