Compare commits

...

4 Commits

Author SHA1 Message Date
791ed2491e 🐐 NVMERVS 2026-04-22 12:24:11 +02:00
3af2115e7d 🐐 VSCode extension fixes 2026-04-22 11:52:18 +02:00
25e88a6362 🐐 SCINDE 2026-04-22 11:48:54 +02:00
0da0907a62 🐐 VSCode extension fixes 2026-04-22 11:44:32 +02:00
10 changed files with 140 additions and 15 deletions

View File

@@ -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. 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
`TYPVS(value)` `TYPVS(value)`
@@ -359,6 +364,11 @@ Returns an array of all non-overlapping matches of the regex `pattern` in `strin
Replaces all non-overlapping matches of the regex `pattern` in `string` with `replacement`. All three arguments must be strings. The replacement string supports backreferences (`\1`, `\2`, etc.) to captured groups. Returns the resulting string. Raises an error if the pattern is invalid. Replaces all non-overlapping matches of the regex `pattern` in `string` with `replacement`. All three arguments must be strings. The replacement string supports backreferences (`\1`, `\2`, etc.) to captured groups. Returns the resulting string. Raises an error if the pattern is invalid.
### SCINDE
`SCINDE(string, delimiter)`
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.
## 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

View File

@@ -1185,6 +1185,13 @@ class BuiltIn(Node):
raise CentvrionError(f"Invalid numeral input: {raw!r}") raise CentvrionError(f"Invalid numeral input: {raw!r}")
case "AVDI": case "AVDI":
return vtable, ValStr(input()) 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": case "DIC":
print_string = ' '.join( print_string = ' '.join(
make_string(i, magnvm, svbnvlla) for i in params make_string(i, magnvm, svbnvlla) for i in params
@@ -1328,6 +1335,18 @@ class BuiltIn(Node):
except re.error as e: except re.error as e:
raise CentvrionError(f"Invalid regex: {e}") raise CentvrionError(f"Invalid regex: {e}")
return vtable, ValStr(result) return vtable, ValStr(result)
case "SCINDE":
string = params[0]
delimiter = params[1]
if not isinstance(string, ValStr) or not isinstance(delimiter, ValStr):
raise CentvrionError("SCINDE requires two strings")
s = string.value()
d = delimiter.value()
if d == "":
parts = [ValStr(c) for c in s]
else:
parts = [ValStr(p) for p in s.split(d)]
return vtable, ValList(parts)
case "PETE": case "PETE":
if "RETE" not in vtable["#modules"]: if "RETE" not in vtable["#modules"]:
raise CentvrionError("Cannot use 'PETE' without module 'RETE'") raise CentvrionError("Cannot use 'PETE' without module 'RETE'")

View File

@@ -297,12 +297,22 @@ def _emit_builtin(node, ctx):
lines.append(f"cent_adivnge({param_vars[0]}, {param_vars[1]});") lines.append(f"cent_adivnge({param_vars[0]}, {param_vars[1]});")
lines.append(f"CentValue {tmp} = cent_null();") 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": case "QVAERE":
lines.append(f"CentValue {tmp} = cent_qvaere({param_vars[0]}, {param_vars[1]});") lines.append(f"CentValue {tmp} = cent_qvaere({param_vars[0]}, {param_vars[1]});")
case "SVBSTITVE": case "SVBSTITVE":
lines.append(f"CentValue {tmp} = cent_svbstitve({param_vars[0]}, {param_vars[1]}, {param_vars[2]});") lines.append(f"CentValue {tmp} = cent_svbstitve({param_vars[0]}, {param_vars[1]}, {param_vars[2]});")
case "SCINDE":
lines.append(f"CentValue {tmp} = cent_scinde({param_vars[0]}, {param_vars[1]});")
case "PETE": case "PETE":
if not ctx.has_module("RETE"): if not ctx.has_module("RETE"):
lines.append('cent_runtime_error("RETE module required for PETE");') lines.append('cent_runtime_error("RETE module required for PETE");')

View File

@@ -569,6 +569,12 @@ CentValue cent_avdi_numerus(void) {
return cent_int(cent_roman_to_int(s.sval)); 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) { CentValue cent_longitudo(CentValue v) {
if (v.type == CENT_LIST) return cent_int(v.lval.len); if (v.type == CENT_LIST) return cent_int(v.lval.len);
if (v.type == CENT_STR) return cent_int((long)strlen(v.sval)); if (v.type == CENT_STR) return cent_int((long)strlen(v.sval));
@@ -1015,6 +1021,40 @@ CentValue cent_svbstitve(CentValue pattern, CentValue replacement, CentValue tex
return cent_str(result); return cent_str(result);
} }
CentValue cent_scinde(CentValue str, CentValue delim) {
if (str.type != CENT_STR || delim.type != CENT_STR)
cent_type_error("'SCINDE' requires two strings");
const char *s = str.sval;
const char *d = delim.sval;
size_t dlen = strlen(d);
CentValue result = cent_list_new(8);
if (dlen == 0) {
/* empty delimiter: split into individual characters */
for (const char *p = s; *p; p++) {
char *buf = cent_arena_alloc(cent_arena, 2);
buf[0] = *p;
buf[1] = '\0';
cent_list_push(&result, cent_str(buf));
}
return result;
}
const char *cursor = s;
for (;;) {
const char *found = strstr(cursor, d);
if (!found) {
cent_list_push(&result, cent_str(cursor));
break;
}
size_t len = found - cursor;
char *buf = cent_arena_alloc(cent_arena, len + 1);
memcpy(buf, cursor, len);
buf[len] = '\0';
cent_list_push(&result, cent_str(buf));
cursor = found + dlen;
}
return result;
}
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Networking (RETE) */ /* Networking (RETE) */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */

View File

@@ -232,8 +232,10 @@ CentValue cent_ordina(CentValue lst); /* ORDINA */
CentValue cent_lege(CentValue path); /* LEGE */ CentValue cent_lege(CentValue path); /* LEGE */
void cent_scribe(CentValue path, CentValue content); /* SCRIBE */ void cent_scribe(CentValue path, CentValue content); /* SCRIBE */
void cent_adivnge(CentValue path, CentValue content); /* ADIVNGE */ void cent_adivnge(CentValue path, CentValue content); /* ADIVNGE */
CentValue cent_numerus(CentValue s); /* NVMERVS */
CentValue cent_qvaere(CentValue pattern, CentValue text); /* QVAERE */ CentValue cent_qvaere(CentValue pattern, CentValue text); /* QVAERE */
CentValue cent_svbstitve(CentValue pattern, CentValue replacement, CentValue text); /* SVBSTITVE */ CentValue cent_svbstitve(CentValue pattern, CentValue replacement, CentValue text); /* SVBSTITVE */
CentValue cent_scinde(CentValue str, CentValue delim); /* SCINDE */
CentValue cent_pete(CentValue url); /* PETE */ CentValue cent_pete(CentValue url); /* PETE */
void cent_petitvr(CentValue path, CentValue handler, CentScope scope); /* PETITVR */ void cent_petitvr(CentValue path, CentValue handler, CentScope scope); /* PETITVR */
void cent_avscvlta(CentValue port); /* AVSCVLTA */ void cent_avscvlta(CentValue port); /* AVSCVLTA */

View File

@@ -51,6 +51,7 @@ builtin_tokens = [("BUILTIN", i) for i in [
"FORTVITVS_NVMERVS", "FORTVITVS_NVMERVS",
"FORTVITA_ELECTIO", "FORTVITA_ELECTIO",
"LONGITVDO", "LONGITVDO",
"NVMERVS",
"ORDINA", "ORDINA",
"SEMEN", "SEMEN",
"SENATVS", "SENATVS",
@@ -60,6 +61,7 @@ builtin_tokens = [("BUILTIN", i) for i in [
"ADIVNGE", "ADIVNGE",
"QVAERE", "QVAERE",
"SVBSTITVE", "SVBSTITVE",
"SCINDE",
"PETE", "PETE",
"PETITVR", "PETITVR",
"AVSCVLTA" "AVSCVLTA"

View File

@@ -346,20 +346,21 @@ class Parser():
def parens(tokens): def parens(tokens):
return tokens[1] 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')
@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): def dict_items(calls):
if len(calls) == 0: if len(calls) == 3:
return []
elif len(calls) == 3:
return [(calls[0], calls[2])] return [(calls[0], calls[2])]
else: 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): 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') @self.pg.production('expression : SYMBOL_LBRACKET array_items SYMBOL_RBRACKET')
def array(tokens): def array(tokens):

View File

@@ -70,7 +70,7 @@ contexts:
scope: constant.language.centvrion scope: constant.language.centvrion
builtins: builtins:
- match: '\b(ADIVNGE|AVDI_NVMERVS|AVDI|CLAVES|DECIMATIO|DIC|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|LEGE|LONGITVDO|ORDINA|PETE|QVAERE|SCRIBE|SEMEN|SENATVS|SVBSTITVE)\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 scope: support.function.builtin.centvrion
modules: modules:

View File

@@ -638,6 +638,26 @@ builtin_tests = [
('SVBSTITVE("(a)(b)", "\\2\\1", "ab")', Program([], [ExpressionStatement(BuiltIn("SVBSTITVE", [String("(a)(b)"), String("\\2\\1"), String("ab")]))]), ValStr("ba")), ('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: 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")), ('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)
('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")])),
] ]
class TestBuiltins(unittest.TestCase): class TestBuiltins(unittest.TestCase):
@@ -656,6 +676,9 @@ error_tests = [
("DIC(M + M + M + M)", CentvrionError), # output > 3999 without MAGNVM ("DIC(M + M + M + M)", CentvrionError), # output > 3999 without MAGNVM
("IIII", CentvrionError), # invalid Roman numeral in source ("IIII", CentvrionError), # invalid Roman numeral in source
("FORTVITVS_NVMERVS(I, X)", CentvrionError), # requires FORS module ("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) 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 (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 ("DEFINI f () VT { REDI(I) }\nINVOCA f (I)", CentvrionError), # args to zero-param function
@@ -726,6 +749,8 @@ error_tests = [
('SVBSTITVE("a", I, "c")', CentvrionError), # SVBSTITVE requires strings, not int replacement ('SVBSTITVE("a", I, "c")', CentvrionError), # SVBSTITVE requires strings, not int replacement
('SVBSTITVE("a", "b", I)', CentvrionError), # SVBSTITVE requires strings, not int text ('SVBSTITVE("a", "b", I)', CentvrionError), # SVBSTITVE requires strings, not int text
('SVBSTITVE("[", "b", "c")', CentvrionError), # SVBSTITVE invalid regex ('SVBSTITVE("[", "b", "c")', CentvrionError), # SVBSTITVE invalid regex
('SCINDE(I, ",")', CentvrionError), # SCINDE requires strings, not int
('SCINDE("a", I)', CentvrionError), # SCINDE requires strings, not int delimiter
('PETE("http://example.com")', CentvrionError), # RETE required for PETE ('PETE("http://example.com")', CentvrionError), # RETE required for PETE
('CVM RETE\nPETE(I)', CentvrionError), # PETE requires a string URL ('CVM RETE\nPETE(I)', CentvrionError), # PETE requires a string URL
('PETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})', CentvrionError), # RETE required for PETITVR ('PETITVR("/", FVNCTIO (r) VT {\nREDI("hi")\n})', CentvrionError), # RETE required for PETITVR
@@ -2222,6 +2247,22 @@ dict_tests = [
('TABVLA {"x" VT I + II}', ('TABVLA {"x" VT I + II}',
Program([], [ExpressionStatement(DataDict([(String("x"), BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"))]))]), Program([], [ExpressionStatement(DataDict([(String("x"), BinOp(Numeral("I"), Numeral("II"), "SYMBOL_PLUS"))]))]),
ValDict({"x": ValInt(3)})), 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): class TestDict(unittest.TestCase):

View File

@@ -8,9 +8,6 @@
{ {
"include": "#strings" "include": "#strings"
}, },
{
"include": "#fractions"
},
{ {
"include": "#keywords" "include": "#keywords"
}, },
@@ -20,6 +17,9 @@
{ {
"include": "#modules" "include": "#modules"
}, },
{
"include": "#fractions"
},
{ {
"include": "#constants" "include": "#constants"
}, },
@@ -45,7 +45,7 @@
"patterns": [ "patterns": [
{ {
"name": "keyword.control.cent", "name": "keyword.control.cent",
"match": "\\b(AETERNVM|ALIVD|AVT|CONTINVA|CVM|DEFINI|DESIGNA|DONICVM|DVM|ERVMPE|ET|FAC|FVNCTIO|IN|INVOCA|NON|PER|REDI|SI|TVNC|VSQVE|VT)\\b" "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"
}, },
{ {
"name": "keyword.operator.comparison.cent", "name": "keyword.operator.comparison.cent",
@@ -65,7 +65,7 @@
"patterns": [ "patterns": [
{ {
"name": "support.function.builtin.cent", "name": "support.function.builtin.cent",
"match": "\\b(AVDI_NVMERVS|AVDI|DECIMATIO|DIC|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|LONGITVDO|SEMEN)\\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"
} }
] ]
}, },
@@ -73,7 +73,7 @@
"patterns": [ "patterns": [
{ {
"name": "support.class.module.cent", "name": "support.class.module.cent",
"match": "\\b(FORS|FRACTIO|MAGNVM|SVBNVLLA)\\b" "match": "\\b(FORS|FRACTIO|MAGNVM|RETE|SCRIPTA|SVBNVLLA)\\b"
} }
] ]
}, },