Compare commits
3 Commits
e2ce25aa24
...
4f67b1b2dc
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f67b1b2dc | |||
| 7960f6e3da | |||
| c570d72b02 |
16
README.md
16
README.md
@@ -38,6 +38,8 @@ Strings are concatenated with `&`:
|
|||||||
|
|
||||||
`NVLLVS` coerces to an empty string when used with `&`. Note: `+` is for arithmetic only — using it on strings raises an error.
|
`NVLLVS` coerces to an empty string when used with `&`. Note: `+` is for arithmetic only — using it on strings raises an error.
|
||||||
|
|
||||||
|
Integer modulo is `RELIQVVM`: `VII RELIQVVM III` evaluates to `I`. Under the `FRACTIO` module it returns a fraction, so `IIIS RELIQVVM IS` is `S` (i.e. 1/2).
|
||||||
|
|
||||||
### Integers
|
### Integers
|
||||||
Integers must be written in roman numerals using the following symbols:
|
Integers must be written in roman numerals using the following symbols:
|
||||||
|
|
||||||
@@ -92,7 +94,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, and `MINVS` (<) and `PLVS` (>) function as inequality evaluation.
|
In conditionals, `EST` functions as an equality evaluation, `DISPAR` as not-equal, and `MINVS` (<) and `PLVS` (>) function as inequality evaluation.
|
||||||
|
|
||||||
### ALVID
|
### ALVID
|
||||||
|
|
||||||
@@ -139,6 +141,18 @@ The keyword `ET` can be used as a boolean "and". The keyword `AVT` can be used a
|
|||||||
> XI
|
> XI
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### AETERNVM loops
|
||||||
|
|
||||||
|
`AETERNVM FACE { ... }` is shorthand for an infinite loop — equivalent
|
||||||
|
to `DVM FALSITAS FACE { ... }` but without relying on `DVM`'s inverted
|
||||||
|
condition. Exit the loop with `ERVMPE` (or `REDI` from inside a function).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
> X
|
||||||
|
```
|
||||||
|
|
||||||
### PER loops
|
### PER loops
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -37,7 +37,9 @@ OP_STR = {
|
|||||||
"SYMBOL_PLUS": "+", "SYMBOL_MINUS": "-",
|
"SYMBOL_PLUS": "+", "SYMBOL_MINUS": "-",
|
||||||
"SYMBOL_TIMES": "*", "SYMBOL_DIVIDE": "/",
|
"SYMBOL_TIMES": "*", "SYMBOL_DIVIDE": "/",
|
||||||
"SYMBOL_AMPERSAND": "&",
|
"SYMBOL_AMPERSAND": "&",
|
||||||
"KEYWORD_EST": "EST", "KEYWORD_MINVS": "MINVS",
|
"KEYWORD_RELIQVVM": "RELIQVVM",
|
||||||
|
"KEYWORD_EST": "EST", "KEYWORD_DISPAR": "DISPAR",
|
||||||
|
"KEYWORD_MINVS": "MINVS",
|
||||||
"KEYWORD_PLVS": "PLVS", "KEYWORD_ET": "ET", "KEYWORD_AVT": "AVT",
|
"KEYWORD_PLVS": "PLVS", "KEYWORD_ET": "ET", "KEYWORD_AVT": "AVT",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,6 +588,14 @@ class BinOp(Node):
|
|||||||
if isinstance(lv, Fraction) or isinstance(rv, Fraction) or "FRACTIO" in vtable["#modules"]:
|
if isinstance(lv, Fraction) or isinstance(rv, Fraction) or "FRACTIO" in vtable["#modules"]:
|
||||||
return vtable, ValFrac(Fraction(lv or 0) / Fraction(rv or 0))
|
return vtable, ValFrac(Fraction(lv or 0) / Fraction(rv or 0))
|
||||||
return vtable, ValInt((lv or 0) // (rv or 0))
|
return vtable, ValInt((lv or 0) // (rv or 0))
|
||||||
|
case "KEYWORD_RELIQVVM":
|
||||||
|
if isinstance(lv, (str, list)) or isinstance(rv, (str, list)):
|
||||||
|
raise CentvrionError("Cannot use RELIQVVM on strings or arrays")
|
||||||
|
if (rv or 0) == 0:
|
||||||
|
raise CentvrionError("Modulo by zero")
|
||||||
|
if isinstance(lv, Fraction) or isinstance(rv, Fraction) or "FRACTIO" in vtable["#modules"]:
|
||||||
|
return vtable, ValFrac(Fraction(lv or 0) % Fraction(rv or 0))
|
||||||
|
return vtable, ValInt((lv or 0) % (rv or 0))
|
||||||
case "KEYWORD_MINVS":
|
case "KEYWORD_MINVS":
|
||||||
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 MINVS")
|
raise CentvrionError("Cannot compare strings or arrays with MINVS")
|
||||||
@@ -596,6 +606,8 @@ class BinOp(Node):
|
|||||||
return vtable, ValBool((lv or 0) > (rv or 0))
|
return vtable, ValBool((lv or 0) > (rv or 0))
|
||||||
case "KEYWORD_EST":
|
case "KEYWORD_EST":
|
||||||
return vtable, ValBool(lv == rv)
|
return vtable, ValBool(lv == rv)
|
||||||
|
case "KEYWORD_DISPAR":
|
||||||
|
return vtable, ValBool(lv != rv)
|
||||||
case "KEYWORD_ET":
|
case "KEYWORD_ET":
|
||||||
return vtable, ValBool(bool(left) and bool(right))
|
return vtable, ValBool(bool(left) and bool(right))
|
||||||
case "KEYWORD_AVT":
|
case "KEYWORD_AVT":
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ _BINOP_FN = {
|
|||||||
"SYMBOL_TIMES": "cent_mul",
|
"SYMBOL_TIMES": "cent_mul",
|
||||||
"SYMBOL_DIVIDE": "cent_div",
|
"SYMBOL_DIVIDE": "cent_div",
|
||||||
"SYMBOL_AMPERSAND": "cent_concat",
|
"SYMBOL_AMPERSAND": "cent_concat",
|
||||||
|
"KEYWORD_RELIQVVM": "cent_mod",
|
||||||
"KEYWORD_EST": "cent_eq",
|
"KEYWORD_EST": "cent_eq",
|
||||||
|
"KEYWORD_DISPAR": "cent_neq",
|
||||||
"KEYWORD_MINVS": "cent_lt",
|
"KEYWORD_MINVS": "cent_lt",
|
||||||
"KEYWORD_PLVS": "cent_gt",
|
"KEYWORD_PLVS": "cent_gt",
|
||||||
"KEYWORD_ET": "cent_and",
|
"KEYWORD_ET": "cent_and",
|
||||||
@@ -77,6 +79,8 @@ def emit_expr(node, ctx):
|
|||||||
tmp = ctx.fresh_tmp()
|
tmp = ctx.fresh_tmp()
|
||||||
if node.op == "SYMBOL_DIVIDE" and ctx.has_module("FRACTIO"):
|
if node.op == "SYMBOL_DIVIDE" and ctx.has_module("FRACTIO"):
|
||||||
fn = "cent_div_frac"
|
fn = "cent_div_frac"
|
||||||
|
elif node.op == "KEYWORD_RELIQVVM" and ctx.has_module("FRACTIO"):
|
||||||
|
fn = "cent_mod_frac"
|
||||||
else:
|
else:
|
||||||
fn = _BINOP_FN[node.op]
|
fn = _BINOP_FN[node.op]
|
||||||
return l_lines + r_lines + [f"CentValue {tmp} = {fn}({l_var}, {r_var});"], tmp
|
return l_lines + r_lines + [f"CentValue {tmp} = {fn}({l_var}, {r_var});"], tmp
|
||||||
|
|||||||
@@ -396,6 +396,30 @@ CentValue cent_div_frac(CentValue a, CentValue b) {
|
|||||||
return frac_reduce(an * bd, ad * bn);
|
return frac_reduce(an * bd, ad * bn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CentValue cent_mod(CentValue a, CentValue b) {
|
||||||
|
if (a.type != CENT_INT || b.type != CENT_INT)
|
||||||
|
cent_type_error("'RELIQVVM' requires two integers");
|
||||||
|
if (b.ival == 0)
|
||||||
|
cent_runtime_error("modulo by zero");
|
||||||
|
return cent_int(a.ival % b.ival);
|
||||||
|
}
|
||||||
|
|
||||||
|
CentValue cent_mod_frac(CentValue a, CentValue b) {
|
||||||
|
long an, ad, bn, bd;
|
||||||
|
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
|
||||||
|
if (bn == 0) cent_runtime_error("modulo by zero");
|
||||||
|
/* a/b mod c/d over a common denominator ad*bd:
|
||||||
|
num_a = an*bd, num_b = bn*ad
|
||||||
|
result = (num_a - floor(num_a/num_b) * num_b) / (ad*bd)
|
||||||
|
Use floored division so the result matches Python's Fraction.__mod__. */
|
||||||
|
long num_a = an * bd;
|
||||||
|
long num_b = bn * ad;
|
||||||
|
long q = num_a / num_b;
|
||||||
|
if ((num_a % num_b != 0) && ((num_a < 0) != (num_b < 0))) q -= 1;
|
||||||
|
long new_num = num_a - q * num_b;
|
||||||
|
return frac_reduce(new_num, ad * bd);
|
||||||
|
}
|
||||||
|
|
||||||
CentValue cent_eq(CentValue a, CentValue b) {
|
CentValue cent_eq(CentValue a, CentValue b) {
|
||||||
if ((a.type == CENT_INT || a.type == CENT_FRAC) &&
|
if ((a.type == CENT_INT || a.type == CENT_FRAC) &&
|
||||||
(b.type == CENT_INT || b.type == CENT_FRAC)) {
|
(b.type == CENT_INT || b.type == CENT_FRAC)) {
|
||||||
@@ -414,6 +438,11 @@ CentValue cent_eq(CentValue a, CentValue b) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CentValue cent_neq(CentValue a, CentValue b) {
|
||||||
|
CentValue r = cent_eq(a, b);
|
||||||
|
return cent_bool(!r.bval);
|
||||||
|
}
|
||||||
|
|
||||||
CentValue cent_lt(CentValue a, CentValue b) {
|
CentValue cent_lt(CentValue a, CentValue b) {
|
||||||
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
|
||||||
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
|
||||||
|
|||||||
@@ -158,7 +158,10 @@ CentValue cent_sub(CentValue a, CentValue b); /* INT-INT or FRAC-FRAC/INT */
|
|||||||
CentValue cent_mul(CentValue a, CentValue b); /* INT*INT or FRAC*FRAC/INT */
|
CentValue cent_mul(CentValue a, CentValue b); /* INT*INT or FRAC*FRAC/INT */
|
||||||
CentValue cent_div(CentValue a, CentValue b); /* INT/INT integer div */
|
CentValue cent_div(CentValue a, CentValue b); /* INT/INT integer div */
|
||||||
CentValue cent_div_frac(CentValue a, CentValue b); /* FRACTIO: exact div → FRAC */
|
CentValue cent_div_frac(CentValue a, CentValue b); /* FRACTIO: exact div → FRAC */
|
||||||
|
CentValue cent_mod(CentValue a, CentValue b); /* INT%INT integer modulo */
|
||||||
|
CentValue cent_mod_frac(CentValue a, CentValue b); /* FRACTIO: floored mod → FRAC */
|
||||||
CentValue cent_eq (CentValue a, CentValue b); /* EST → BOOL */
|
CentValue cent_eq (CentValue a, CentValue b); /* EST → 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_and(CentValue a, CentValue b); /* ET → BOOL */
|
CentValue cent_and(CentValue a, CentValue b); /* ET → BOOL */
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ from rply import LexerGenerator
|
|||||||
valid_characters = '|'.join(list("abcdefghiklmnopqrstvxyz_"))
|
valid_characters = '|'.join(list("abcdefghiklmnopqrstvxyz_"))
|
||||||
|
|
||||||
keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
||||||
|
"AETERNVM",
|
||||||
"ALVID",
|
"ALVID",
|
||||||
"AVT",
|
"AVT",
|
||||||
"DEFINI",
|
"DEFINI",
|
||||||
"DESIGNA",
|
"DESIGNA",
|
||||||
|
"DISPAR",
|
||||||
"DONICVM",
|
"DONICVM",
|
||||||
"DVM",
|
"DVM",
|
||||||
"CONTINVA",
|
"CONTINVA",
|
||||||
@@ -23,6 +25,7 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [
|
|||||||
"PER",
|
"PER",
|
||||||
"PLVS",
|
"PLVS",
|
||||||
"REDI",
|
"REDI",
|
||||||
|
"RELIQVVM",
|
||||||
"SI",
|
"SI",
|
||||||
"TVNC",
|
"TVNC",
|
||||||
"VSQVE",
|
"VSQVE",
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ class Parser():
|
|||||||
precedence=[
|
precedence=[
|
||||||
('left', ["KEYWORD_AVT"]),
|
('left', ["KEYWORD_AVT"]),
|
||||||
('left', ["KEYWORD_ET"]),
|
('left', ["KEYWORD_ET"]),
|
||||||
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST"]),
|
('left', ["KEYWORD_PLVS", "KEYWORD_MINVS", "KEYWORD_EST", "KEYWORD_DISPAR"]),
|
||||||
('left', ["SYMBOL_AMPERSAND", "SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
('left', ["SYMBOL_AMPERSAND", "SYMBOL_PLUS", "SYMBOL_MINUS"]),
|
||||||
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]),
|
('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE", "KEYWORD_RELIQVVM"]),
|
||||||
('right', ["UMINUS", "UNOT"]),
|
('right', ["UMINUS", "UNOT"]),
|
||||||
('left', ["SYMBOL_LBRACKET", "INDEX"]),
|
('left', ["SYMBOL_LBRACKET", "INDEX"]),
|
||||||
]
|
]
|
||||||
@@ -121,6 +121,11 @@ class Parser():
|
|||||||
def dum(tokens):
|
def dum(tokens):
|
||||||
return ast_nodes.DumStatement(tokens[1], tokens[4])
|
return ast_nodes.DumStatement(tokens[1], tokens[4])
|
||||||
|
|
||||||
|
# AETERNVM is sugar for `DVM FALSITAS` — same AST, no observable difference.
|
||||||
|
@self.pg.production('dum_statement : KEYWORD_AETERNVM KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
|
def aeternvm(tokens):
|
||||||
|
return ast_nodes.DumStatement(ast_nodes.Bool(False), tokens[3])
|
||||||
|
|
||||||
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
|
@self.pg.production('per_statement : KEYWORD_PER id KEYWORD_IN expression KEYWORD_FACE SYMBOL_LCURL statements SYMBOL_RCURL')
|
||||||
def per(tokens):
|
def per(tokens):
|
||||||
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
|
return ast_nodes.PerStatement(tokens[3], tokens[1], tokens[6])
|
||||||
@@ -191,7 +196,9 @@ class Parser():
|
|||||||
@self.pg.production('expression : expression SYMBOL_PLUS expression')
|
@self.pg.production('expression : expression SYMBOL_PLUS expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_TIMES expression')
|
@self.pg.production('expression : expression SYMBOL_TIMES expression')
|
||||||
@self.pg.production('expression : expression SYMBOL_DIVIDE expression')
|
@self.pg.production('expression : expression SYMBOL_DIVIDE expression')
|
||||||
|
@self.pg.production('expression : expression KEYWORD_RELIQVVM expression')
|
||||||
@self.pg.production('expression : expression KEYWORD_EST expression')
|
@self.pg.production('expression : expression KEYWORD_EST 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_ET expression')
|
@self.pg.production('expression : expression KEYWORD_ET expression')
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
\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{FACE} \textit{scope}} \\
|
\languageline{statement}{\texttt{DVM} \textit{expression} \texttt{FACE} \textit{scope}} \\
|
||||||
|
\languageline{statement}{\texttt{AETERNVM} \texttt{FACE} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FACE} \textit{scope}} \\
|
\languageline{statement}{\texttt{PER} \textbf{id} \texttt{IN} \textit{expression} \texttt{FACE} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FACE} \textit{scope}} \\
|
\languageline{statement}{\texttt{DONICVM} \textbf{id} \texttt{VT} \textit{expression} \texttt{VSQVE} \textit{expression} \texttt{FACE} \textit{scope}} \\
|
||||||
\languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\
|
\languageline{statement}{\texttt{REDI(} \textit{optional-expressions} \texttt{)}} \\
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
\item \textbf{string}: \\ Any text encased in " characters.
|
\item \textbf{string}: \\ Any text encased in " characters.
|
||||||
\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{EST} (equality), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string 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{ET} (and), \texttt{AVT} (or), \texttt{\&} (string 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}
|
||||||
|
|
||||||
|
|||||||
8
snippets/aeternvm.cent
Normal file
8
snippets/aeternvm.cent
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
DESIGNA x VT NVLLVS
|
||||||
|
AETERNVM FACE {
|
||||||
|
DESIGNA x VT x+I
|
||||||
|
SI x EST X TVNC {
|
||||||
|
ERVMPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DICE(x)
|
||||||
@@ -65,7 +65,7 @@ contexts:
|
|||||||
scope: support.class.module.centvrion
|
scope: support.class.module.centvrion
|
||||||
|
|
||||||
keywords:
|
keywords:
|
||||||
- match: '\b(ALVID|AVT|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVS|NON|PER|PLVS|REDI|SI|TVNC|VSQVE|VT|CVM)\b'
|
- match: '\b(AETERNVM|ALVID|AVT|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|EST|ET|FACE|INVOCA|IN|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TVNC|VSQVE|VT|CVM)\b'
|
||||||
scope: keyword.control.centvrion
|
scope: keyword.control.centvrion
|
||||||
|
|
||||||
operators:
|
operators:
|
||||||
|
|||||||
107
tests.py
107
tests.py
@@ -136,6 +136,9 @@ arithmetic_tests = [
|
|||||||
("III * IV", Program([], [ExpressionStatement(BinOp(Numeral("III"), Numeral("IV"), "SYMBOL_TIMES"))]), ValInt(12)),
|
("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 / 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 / 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(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
|
("(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- III", Program([ModuleCall("SVBNVLLA")], [ExpressionStatement(UnaryMinus(Numeral("III")))]), ValInt(-3)), # unary negation
|
||||||
@@ -153,7 +156,7 @@ class TestArithmetic(unittest.TestCase):
|
|||||||
# --- Precedence and associativity ---
|
# --- Precedence and associativity ---
|
||||||
#
|
#
|
||||||
# Precedence (lowest → highest):
|
# Precedence (lowest → highest):
|
||||||
# AVT < ET < (EST, PLVS, MINVS) < (+ -) < (* /) < UMINUS < INDEX
|
# AVT < ET < (EST, DISPAR, PLVS, MINVS) < (+ -) < (* / RELIQVVM) < UMINUS < INDEX
|
||||||
|
|
||||||
precedence_tests = [
|
precedence_tests = [
|
||||||
# * binds tighter than -: 10 - (2*3) = 4, not (10-2)*3 = 24
|
# * binds tighter than -: 10 - (2*3) = 4, not (10-2)*3 = 24
|
||||||
@@ -176,6 +179,14 @@ precedence_tests = [
|
|||||||
("I EST II ET II EST II",
|
("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"))]),
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("I"), Numeral("II"), "KEYWORD_EST"), BinOp(Numeral("II"), Numeral("II"), "KEYWORD_EST"), "KEYWORD_ET"))]),
|
||||||
ValBool(False)),
|
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
|
# ET binds tighter than AVT: True OR (False AND False) = True
|
||||||
("VERITAS AVT FALSITAS ET FALSITAS",
|
("VERITAS AVT FALSITAS ET FALSITAS",
|
||||||
Program([], [ExpressionStatement(BinOp(Bool(True), BinOp(Bool(False), Bool(False), "KEYWORD_ET"), "KEYWORD_AVT"))]),
|
Program([], [ExpressionStatement(BinOp(Bool(True), BinOp(Bool(False), Bool(False), "KEYWORD_ET"), "KEYWORD_AVT"))]),
|
||||||
@@ -208,6 +219,19 @@ precedence_tests = [
|
|||||||
("XII / II / III",
|
("XII / II / III",
|
||||||
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("XII"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_DIVIDE"))]),
|
Program([], [ExpressionStatement(BinOp(BinOp(Numeral("XII"), Numeral("II"), "SYMBOL_DIVIDE"), Numeral("III"), "SYMBOL_DIVIDE"))]),
|
||||||
ValInt(2)),
|
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
|
# left-associativity of AVT: (False OR True) OR False = True
|
||||||
("FALSITAS AVT VERITAS AVT FALSITAS",
|
("FALSITAS AVT VERITAS AVT FALSITAS",
|
||||||
Program([], [ExpressionStatement(BinOp(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"), Bool(False), "KEYWORD_AVT"))]),
|
Program([], [ExpressionStatement(BinOp(BinOp(Bool(False), Bool(True), "KEYWORD_AVT"), Bool(False), "KEYWORD_AVT"))]),
|
||||||
@@ -318,6 +342,50 @@ control_tests = [
|
|||||||
DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DICE", [ID("x")])), Erumpe()]),
|
DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DICE", [ID("x")])), Erumpe()]),
|
||||||
]),
|
]),
|
||||||
ValStr("I"), "I\n"),
|
ValStr("I"), "I\n"),
|
||||||
|
# AETERNVM is sugar for DVM FALSITAS — must produce the same AST.
|
||||||
|
("DESIGNA x VT I\nAETERNVM FACE {\nDICE(x)\nERVMPE\n}",
|
||||||
|
Program([], [
|
||||||
|
Designa(ID("x"), Numeral("I")),
|
||||||
|
DumStatement(Bool(False), [ExpressionStatement(BuiltIn("DICE", [ID("x")])), Erumpe()]),
|
||||||
|
]),
|
||||||
|
ValStr("I"), "I\n"),
|
||||||
|
# AETERNVM with counter + ERVMPE on condition
|
||||||
|
("DESIGNA x VT I\nAETERNVM FACE {\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 FACE {\nDESIGNA x VT x + I\nSI x PLVS V TVNC { ERVMPE }\nSI x EST III TVNC { CONTINVA }\nDICE(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("DICE", [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 FACE {\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 foreach
|
||||||
("PER i IN [I, II, III] FACE { DICE(i) }",
|
("PER i IN [I, II, III] FACE { DICE(i) }",
|
||||||
Program([], [PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
Program([], [PerStatement(DataArray([Numeral("I"), Numeral("II"), Numeral("III")]), ID("i"), [ExpressionStatement(BuiltIn("DICE", [ID("i")]))])]),
|
||||||
@@ -719,6 +787,15 @@ comparison_tests = [
|
|||||||
# NVLLVS coerces to 0 in comparisons
|
# NVLLVS coerces to 0 in comparisons
|
||||||
("V PLVS NVLLVS", Program([], [ExpressionStatement(BinOp(Numeral("V"), Nullus(), "KEYWORD_PLVS"))]), ValBool(True)),
|
("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)),
|
("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)),
|
||||||
]
|
]
|
||||||
|
|
||||||
class TestComparisons(unittest.TestCase):
|
class TestComparisons(unittest.TestCase):
|
||||||
@@ -1431,6 +1508,34 @@ fractio_tests = [
|
|||||||
]),
|
]),
|
||||||
ValFrac(Fraction(5))
|
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
|
# String concatenation with fraction
|
||||||
("CVM FRACTIO\nDICE(IIIS & \"!\")",
|
("CVM FRACTIO\nDICE(IIIS & \"!\")",
|
||||||
Program([ModuleCall("FRACTIO")], [
|
Program([ModuleCall("FRACTIO")], [
|
||||||
|
|||||||
@@ -8,12 +8,18 @@
|
|||||||
{
|
{
|
||||||
"include": "#strings"
|
"include": "#strings"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"include": "#fractions"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"include": "#keywords"
|
"include": "#keywords"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"include": "#builtins"
|
"include": "#builtins"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"include": "#modules"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"include": "#constants"
|
"include": "#constants"
|
||||||
},
|
},
|
||||||
@@ -39,15 +45,19 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "keyword.control.cent",
|
"name": "keyword.control.cent",
|
||||||
"match": "\\b(ALVID|AVT|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|ET|FACE|INVOCA|IN|PER|SI|TVNC|VSQVE|VT|CVM)\\b"
|
"match": "\\b(AETERNVM|ALVID|AVT|CONTINVA|CVM|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|ET|FACE|IN|INVOCA|NON|PER|REDI|SI|TVNC|VSQVE|VT)\\b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "keyword.operator.comparison.cent",
|
"name": "keyword.operator.comparison.cent",
|
||||||
"match": "\\b(EST|MINVS|PLVS)\\b"
|
"match": "\\b(DISPAR|EST|MINVS|PLVS)\\b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "keyword.operator.arithmetic.cent",
|
"name": "keyword.operator.arithmetic.cent",
|
||||||
"match": "(\\*|\\+|-|/)"
|
"match": "\\b(RELIQVVM)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.arithmetic.cent",
|
||||||
|
"match": "(\\*|\\+|-|/|&)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -55,7 +65,15 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "support.function.builtin.cent",
|
"name": "support.function.builtin.cent",
|
||||||
"match": "\\b(AVDI_NVMERVS|AVDI|DICE|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO|REDI)\\b"
|
"match": "\\b(AVDI_NVMERVS|AVDI|DICE|EVERRO|FORTIS_NVMERVS|FORTIS_ELECTIONIS|LONGITVDO)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "support.class.module.cent",
|
||||||
|
"match": "\\b(FORS|FRACTIO|MAGNVM|SVBNVLLA)\\b"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -63,7 +81,7 @@
|
|||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
"name": "constant.numeric.roman.cent",
|
"name": "constant.numeric.roman.cent",
|
||||||
"match": "\\b[IVXLCDM]+\\b"
|
"match": "\\b[IVXLCDM][IVXLCDM_]*\\b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "constant.language.roman.cent",
|
"name": "constant.language.roman.cent",
|
||||||
@@ -75,6 +93,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"fractions": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.fraction.cent",
|
||||||
|
"match": "\\b[IVXLCDM][IVXLCDM_]*(?:S[S:.|]*|:[S:.|]+|\\.[S:.|]*)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.fraction.cent",
|
||||||
|
"match": "(?<![IVXLCDM_])(?:S[S:.|]+|:[S:.|]+)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"strings": {
|
"strings": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
@@ -111,4 +141,4 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scopeName": "source.cent"
|
"scopeName": "source.cent"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user