diff --git a/README.md b/README.md index 9d21987..fa5fab7 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,11 @@ Sorts an array in ascending order. Returns a new sorted array. All elements must Returns VERITAS if a strict majority of the arguments are VERITAS, FALSITAS otherwise. Also accepts a single array of booleans. All values must be booleans. Ties return FALSITAS. +### NVMERVS +`NVMERVS(string)` + +Parses a Roman numeral string and returns its integer value. The argument must be a string containing a valid Roman numeral. Respects the `MAGNVM` and `SVBNVLLA` modules for large and negative numbers respectively. + ### TYPVS `TYPVS(value)` diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index 4a44f80..b508889 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -1185,6 +1185,13 @@ class BuiltIn(Node): raise CentvrionError(f"Invalid numeral input: {raw!r}") case "AVDI": return vtable, ValStr(input()) + case "NVMERVS": + if len(params) != 1: + raise CentvrionError("NVMERVS takes exactly I argument") + val = params[0] + if not isinstance(val, ValStr): + raise CentvrionError(f"NVMERVS expects a string, got {type(val).__name__}") + return vtable, ValInt(num_to_int(val.value(), magnvm, svbnvlla)) case "DIC": print_string = ' '.join( make_string(i, magnvm, svbnvlla) for i in params diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index 38ae4b9..a754593 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -297,6 +297,13 @@ def _emit_builtin(node, ctx): lines.append(f"cent_adivnge({param_vars[0]}, {param_vars[1]});") lines.append(f"CentValue {tmp} = cent_null();") + case "NVMERVS": + if len(param_vars) != 1: + lines.append(f'cent_runtime_error("NVMERVS takes exactly I argument");') + lines.append(f"CentValue {tmp} = cent_null();") + else: + lines.append(f"CentValue {tmp} = cent_numerus({param_vars[0]});") + case "QVAERE": lines.append(f"CentValue {tmp} = cent_qvaere({param_vars[0]}, {param_vars[1]});") diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index bbdb609..bcb0622 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -569,6 +569,12 @@ CentValue cent_avdi_numerus(void) { return cent_int(cent_roman_to_int(s.sval)); } +CentValue cent_numerus(CentValue s) { + if (s.type != CENT_STR) + cent_type_error("'NVMERVS' expects a string"); + return cent_int(cent_roman_to_int(s.sval)); +} + CentValue cent_longitudo(CentValue v) { if (v.type == CENT_LIST) return cent_int(v.lval.len); if (v.type == CENT_STR) return cent_int((long)strlen(v.sval)); diff --git a/centvrion/compiler/runtime/cent_runtime.h b/centvrion/compiler/runtime/cent_runtime.h index 876c123..15b7b90 100644 --- a/centvrion/compiler/runtime/cent_runtime.h +++ b/centvrion/compiler/runtime/cent_runtime.h @@ -232,6 +232,7 @@ CentValue cent_ordina(CentValue lst); /* ORDINA */ CentValue cent_lege(CentValue path); /* LEGE */ void cent_scribe(CentValue path, CentValue content); /* SCRIBE */ void cent_adivnge(CentValue path, CentValue content); /* ADIVNGE */ +CentValue cent_numerus(CentValue s); /* NVMERVS */ CentValue cent_qvaere(CentValue pattern, CentValue text); /* QVAERE */ CentValue cent_svbstitve(CentValue pattern, CentValue replacement, CentValue text); /* SVBSTITVE */ CentValue cent_scinde(CentValue str, CentValue delim); /* SCINDE */ diff --git a/centvrion/lexer.py b/centvrion/lexer.py index 250fbc4..ff0ea41 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -51,6 +51,7 @@ builtin_tokens = [("BUILTIN", i) for i in [ "FORTVITVS_NVMERVS", "FORTVITA_ELECTIO", "LONGITVDO", + "NVMERVS", "ORDINA", "SEMEN", "SENATVS", diff --git a/centvrion/parser.py b/centvrion/parser.py index 0a3b8d2..b757ad3 100644 --- a/centvrion/parser.py +++ b/centvrion/parser.py @@ -346,20 +346,21 @@ class Parser(): def parens(tokens): return tokens[1] - @self.pg.production('dict_items : ') @self.pg.production('dict_items : expression KEYWORD_VT expression') - @self.pg.production('dict_items : expression KEYWORD_VT expression SYMBOL_COMMA dict_items') + @self.pg.production('dict_items : expression KEYWORD_VT expression SYMBOL_COMMA opt_newline dict_items') def dict_items(calls): - if len(calls) == 0: - return [] - elif len(calls) == 3: + if len(calls) == 3: return [(calls[0], calls[2])] else: - return [(calls[0], calls[2])] + calls[4] + return [(calls[0], calls[2])] + calls[5] - @self.pg.production('expression : KEYWORD_TABVLA SYMBOL_LCURL dict_items SYMBOL_RCURL') + @self.pg.production('expression : KEYWORD_TABVLA SYMBOL_LCURL opt_newline SYMBOL_RCURL') + def dict_literal_empty(tokens): + return ast_nodes.DataDict([]) + + @self.pg.production('expression : KEYWORD_TABVLA SYMBOL_LCURL opt_newline dict_items opt_newline SYMBOL_RCURL') def dict_literal(tokens): - return ast_nodes.DataDict(tokens[2]) + return ast_nodes.DataDict(tokens[3]) @self.pg.production('expression : SYMBOL_LBRACKET array_items SYMBOL_RBRACKET') def array(tokens): diff --git a/snippets/syntaxes/centvrion.sublime-syntax b/snippets/syntaxes/centvrion.sublime-syntax index d17bac2..9e62ec0 100644 --- a/snippets/syntaxes/centvrion.sublime-syntax +++ b/snippets/syntaxes/centvrion.sublime-syntax @@ -70,7 +70,7 @@ contexts: scope: constant.language.centvrion builtins: - - match: '\b(ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|LEGE|LONGITVDO|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|LONGITVDO|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TYPVS)\b' scope: support.function.builtin.centvrion modules: diff --git a/tests.py b/tests.py index 35dd624..1becd99 100644 --- a/tests.py +++ b/tests.py @@ -638,6 +638,14 @@ builtin_tests = [ ('SVBSTITVE("(a)(b)", "\\2\\1", "ab")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)"), String("\\2\\1"), String("ab")]))]), ValStr("ba")), # SVBSTITVE: backreference with unmatched group (ignored) ('SVBSTITVE("(a)(b)?", "\\1\\2", "a")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)?"), String("\\1\\2"), String("a")]))]), ValStr("a")), + # 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) @@ -668,6 +676,9 @@ error_tests = [ ("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 @@ -2236,6 +2247,22 @@ dict_tests = [ ('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): diff --git a/vscode-extension/syntaxes/cent.tmLanguage.json b/vscode-extension/syntaxes/cent.tmLanguage.json index f541e50..e9d2634 100644 --- a/vscode-extension/syntaxes/cent.tmLanguage.json +++ b/vscode-extension/syntaxes/cent.tmLanguage.json @@ -65,7 +65,7 @@ "patterns": [ { "name": "support.function.builtin.cent", - "match": "\\b(ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|LEGE|LONGITVDO|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|LONGITVDO|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TYPVS)\\b" } ] },