diff --git a/README.md b/README.md index 984097e..932a7d2 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,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). +### 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(n)` diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index 2788188..38e9d81 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -1441,6 +1441,10 @@ class BuiltIn(Node): if isinstance(params[0], (ValList, ValStr, ValDict)): return vtable, ValInt(len(params[0].value())) 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 "CLAVES": if not isinstance(params[0], ValDict): raise CentvrionError("CLAVES requires a dict") diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index 61863c9..bdd66a4 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -237,6 +237,9 @@ def _emit_builtin(node, ctx): case "LONGITVDO": lines.append(f"CentValue {tmp} = cent_longitudo({param_vars[0]});") + case "LITTERA": + lines.append(f"CentValue {tmp} = cent_littera({param_vars[0]});") + case "FORTVITVS_NVMERVS": if not ctx.has_module("FORS"): lines.append('cent_runtime_error("FORS module required for FORTVITVS_NVMERVS");') diff --git a/centvrion/compiler/runtime/cent_runtime.c b/centvrion/compiler/runtime/cent_runtime.c index c29e086..3248ff2 100644 --- a/centvrion/compiler/runtime/cent_runtime.c +++ b/centvrion/compiler/runtime/cent_runtime.c @@ -644,6 +644,10 @@ CentValue cent_longitudo(CentValue v) { return cent_null(); /* unreachable; silences warning */ } +CentValue cent_littera(CentValue v) { + return cent_str(cent_make_string(v)); +} + CentValue cent_typvs(CentValue v) { switch (v.type) { case CENT_INT: return cent_str("NVMERVS"); diff --git a/centvrion/compiler/runtime/cent_runtime.h b/centvrion/compiler/runtime/cent_runtime.h index b3707dc..a6c8e3b 100644 --- a/centvrion/compiler/runtime/cent_runtime.h +++ b/centvrion/compiler/runtime/cent_runtime.h @@ -223,6 +223,7 @@ void cent_dic(CentValue v); /* DIC */ CentValue cent_avdi(void); /* AVDI */ CentValue cent_avdi_numerus(void); /* AVDI_NVMERVS */ CentValue cent_longitudo(CentValue v); /* LONGITVDO */ +CentValue cent_littera(CentValue v); /* LITTERA */ CentValue cent_fortuitus_numerus(CentValue lo, CentValue hi); /* FORTVITVS_NVMERVS */ CentValue cent_fortuita_electionis(CentValue lst); /* FORTVITA_ELECTIO */ CentValue cent_decimatio(CentValue lst); /* DECIMATIO */ diff --git a/centvrion/lexer.py b/centvrion/lexer.py index 9951af8..09b1523 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -54,6 +54,7 @@ builtin_tokens = [("BUILTIN", i) for i in [ "EVERRE", "FORTVITVS_NVMERVS", "FORTVITA_ELECTIO", + "LITTERA", "LONGITVDO", "NVMERVS", "ORDINA", diff --git a/snippets/littera.cent b/snippets/littera.cent new file mode 100644 index 0000000..4f05f84 --- /dev/null +++ b/snippets/littera.cent @@ -0,0 +1,3 @@ +DESIGNA n VT VII +DESIGNA s VT LITTERA(n) & " est septem" +DIC(s) diff --git a/snippets/littera.png b/snippets/littera.png new file mode 100644 index 0000000..ac6ecf2 Binary files /dev/null and b/snippets/littera.png differ diff --git a/snippets/syntaxes/centvrion.sublime-syntax b/snippets/syntaxes/centvrion.sublime-syntax index 9c7c496..2bcd36a 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|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|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 d155a2e..bd65d74 100644 --- a/tests.py +++ b/tests.py @@ -646,6 +646,38 @@ builtin_tests = [ ("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 diff --git a/vscode-extension/snippets/cent.json b/vscode-extension/snippets/cent.json index 95ba2d0..9140b13 100644 --- a/vscode-extension/snippets/cent.json +++ b/vscode-extension/snippets/cent.json @@ -72,6 +72,7 @@ "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" }, "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" }, diff --git a/vscode-extension/syntaxes/cent.tmLanguage.json b/vscode-extension/syntaxes/cent.tmLanguage.json index ee3bcd0..aac2132 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|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|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TYPVS)\\b" } ] },