diff --git a/README.md b/README.md index a8a4eee..2faf99d 100644 --- a/README.md +++ b/README.md @@ -183,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. ### 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 diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index cc44400..2788188 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -64,6 +64,8 @@ OP_STR = { "KEYWORD_EST": "EST", "KEYWORD_DISPAR": "DISPAR", "KEYWORD_MINVS": "MINVS", "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): @@ -937,6 +939,14 @@ class BinOp(Node): if isinstance(lv, (str, list)) or isinstance(rv, (str, list)): raise CentvrionError("Cannot compare strings or arrays with PLVS") 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": if ((isinstance(left, ValInt) and lv == 0 and isinstance(right, ValNul)) or (isinstance(left, ValNul) and isinstance(right, ValInt) and rv == 0)): diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index 0b2317a..61863c9 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -19,6 +19,8 @@ _BINOP_FN = { "KEYWORD_DISPAR": "cent_neq", "KEYWORD_MINVS": "cent_lt", "KEYWORD_PLVS": "cent_gt", + "KEYWORD_HAVD_PLVS": "cent_lte", + "KEYWORD_HAVD_MINVS": "cent_gte", "KEYWORD_ET": "cent_and", "KEYWORD_AVT": "cent_or", } diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index 3045a8f..c29e086 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -564,6 +564,28 @@ CentValue cent_gt(CentValue a, CentValue b) { 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) { if (a.type != CENT_BOOL || b.type != CENT_BOOL) cent_type_error("'ET' requires two booleans"); diff --git a/centvrion/compiler/runtime/cent_runtime.h b/centvrion/compiler/runtime/cent_runtime.h index 237f509..b3707dc 100644 --- a/centvrion/compiler/runtime/cent_runtime.h +++ b/centvrion/compiler/runtime/cent_runtime.h @@ -210,6 +210,8 @@ 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_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_or (CentValue a, CentValue b); /* AVT → BOOL */ diff --git a/centvrion/lexer.py b/centvrion/lexer.py index 48dcc77..61f2a52 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -20,6 +20,8 @@ keyword_tokens = [("KEYWORD_"+i, i) for i in [ "FAC", "FALSITAS", "FVNCTIO", + "HAVD_MINVS", + "HAVD_PLVS", "INVOCA", "IN", "MINVE", diff --git a/centvrion/parser.py b/centvrion/parser.py index 3b2bceb..9f741f4 100644 --- a/centvrion/parser.py +++ b/centvrion/parser.py @@ -115,7 +115,8 @@ class Parser(): precedence=[ ('left', ["KEYWORD_AVT"]), ('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_TIMES", "SYMBOL_DIVIDE", "KEYWORD_RELIQVVM"]), ('right', ["UMINUS", "UNOT"]), @@ -334,6 +335,8 @@ class Parser(): @self.pg.production('expression : expression KEYWORD_DISPAR expression') @self.pg.production('expression : expression KEYWORD_MINVS 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_AVT expression') def binop(tokens): diff --git a/language/main.pdf b/language/main.pdf index d99df9f..abd10f8 100644 Binary files a/language/main.pdf and b/language/main.pdf differ diff --git a/language/main.tex b/language/main.tex index fd9a2c6..b3d2e3d 100644 --- a/language/main.tex +++ b/language/main.tex @@ -106,7 +106,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{numeral}: \\ Roman numerals consisting of the uppercase characters I, V, X, L, C, D, and M. Can also include underscore if the module MAGNVM. \item \textbf{bool}: \\ VERITAS or FALSITAS. - \item \textbf{binop}: \\ Binary operators: \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{RELIQVVM} (modulo), \texttt{EST} (equality), \texttt{DISPAR} (not-equal), \texttt{MINVS} (<), \texttt{PLVS} (>), \texttt{ET} (and), \texttt{AVT} (or), \texttt{\&} (string concatenation), \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). \end{itemize} diff --git a/snippets/syntaxes/centvrion.sublime-syntax b/snippets/syntaxes/centvrion.sublime-syntax index 56d1be0..9e71361 100644 --- a/snippets/syntaxes/centvrion.sublime-syntax +++ b/snippets/syntaxes/centvrion.sublime-syntax @@ -78,7 +78,7 @@ contexts: scope: support.class.module.centvrion 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|DONICVM|DVM|ERVMPE|EST|ET|FAC|FVNCTIO|INVOCA|IN|MINVE|MINVS|NON|PER|PLVS|REDI|RELIQVVM|SI|TABVLA|TEMPTA|TVNC|VSQVE|VT|CVM)\b' scope: keyword.control.centvrion operators: diff --git a/tests.py b/tests.py index f19c046..020ad4b 100644 --- a/tests.py +++ b/tests.py @@ -729,6 +729,8 @@ error_tests = [ ("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 @@ -1302,6 +1304,27 @@ comparison_tests = [ ("[] 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): @@ -2436,6 +2459,25 @@ fractio_comparison_tests = [ ]), 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")], [ diff --git a/vscode-extension/syntaxes/cent.tmLanguage.json b/vscode-extension/syntaxes/cent.tmLanguage.json index 75d07f7..96d8953 100644 --- a/vscode-extension/syntaxes/cent.tmLanguage.json +++ b/vscode-extension/syntaxes/cent.tmLanguage.json @@ -49,7 +49,7 @@ }, { "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",