From d2d09c770d4d53df56366c3373af46445916f301 Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 25 Apr 2026 22:03:30 +0200 Subject: [PATCH] IASON --- DOCS.md | 28 ++ cent | 10 +- centvrion/ast_nodes.py | 71 +++ centvrion/compiler/emit_expr.py | 19 + centvrion/compiler/emitter.py | 1 + centvrion/compiler/runtime/cent_iason.c | 426 ++++++++++++++++++ centvrion/compiler/runtime/cent_iason.h | 14 + centvrion/lexer.py | 5 +- language/main.tex | 2 +- snippets/iason.cent | 1 + snippets/iason.png | Bin 0 -> 4099 bytes snippets/iason_lege.cent | 5 + snippets/iason_lege.png | Bin 0 -> 51859 bytes snippets/iason_scribe.cent | 3 + snippets/iason_scribe.png | Bin 0 -> 34497 bytes snippets/syntaxes/centvrion.sublime-syntax | 4 +- tests/12_test_failures.py | 22 +- tests/13_test_iason___.py | 173 +++++++ tests/_helpers.py | 10 +- .../syntaxes/cent.tmLanguage.json | 4 +- 20 files changed, 782 insertions(+), 16 deletions(-) create mode 100644 centvrion/compiler/runtime/cent_iason.c create mode 100644 centvrion/compiler/runtime/cent_iason.h create mode 100644 snippets/iason.cent create mode 100644 snippets/iason.png create mode 100644 snippets/iason_lege.cent create mode 100644 snippets/iason_lege.png create mode 100644 snippets/iason_scribe.cent create mode 100644 snippets/iason_scribe.png create mode 100644 tests/13_test_iason___.py diff --git a/DOCS.md b/DOCS.md index 833f93e..ea2fd06 100644 --- a/DOCS.md +++ b/DOCS.md @@ -509,6 +509,34 @@ The symbol `|` can be used to denote that the following fraction symbols are 1 " A single "set" of fraction symbols can only represent up to 11/12, as 12/12 can be written as 1. +### IASON + +> ⚠ **Warning.** The `IASON` module enables your program to read and write non-Roman numerals. Numbers handled by `IASON_LEGE` and `IASON_SCRIBE` use the decimal digits `0`–`9` (e.g. `42`, `1789`, `30`), not Roman numerals. This goes against the design philosophy of CENTVRION and should not be used unless absolutely necessary. + +![CVM IASON](snippets/iason.png) + +The `IASON` module adds two builtins for converting between `CENTVRION` values and JSON strings. + +`IASON_LEGE(string)` parses a JSON string and returns the corresponding `CENTVRION` value. Mappings: JSON `null` → `NVLLVS`, `true`/`false` → `VERITAS`/`FALSITAS`, integer → numeral, string → string, array → array, object → `TABVLA` (string keys). + +JSON floats with no fractional part (e.g. `3.0`) come back as integers. Other floats depend on whether the `FRACTIO` module is also loaded: with `FRACTIO`, `0.1` parses to the exact fraction `I:|::|::|S:.|S.|:` (1/10); without it, the value is floored to the nearest integer. + +![IASON_LEGE example](snippets/iason_lege.png) + +``` +> Marcus +> XXX +> [gladius scutum] +``` + +`IASON_SCRIBE(value)` serializes a `CENTVRION` value to a JSON string. Integers and fractions become JSON numbers (fractions via shortest-round-trip float), strings become JSON strings (with the standard escapes), arrays become arrays, dicts become objects (insertion order preserved). Functions and dicts with non-string keys raise an error. + +![IASON_SCRIBE example](snippets/iason_scribe.png) + +``` +> {"nomen": "Marcus", "anni": 30} +``` + ### MAGNVM ![CVM MAGNVM](snippets/magnvm.png) diff --git a/cent b/cent index 8371069..1d0a886 100755 --- a/cent +++ b/cent @@ -53,17 +53,19 @@ def main(): sys.exit(f"CENTVRION error: {e}") else: c_source = compile_program(program) - runtime_c = os.path.join( + runtime_dir = os.path.join( os.path.dirname(__file__), - "centvrion", "compiler", "runtime", "cent_runtime.c" + "centvrion", "compiler", "runtime" ) + runtime_c = os.path.join(runtime_dir, "cent_runtime.c") + iason_c = os.path.join(runtime_dir, "cent_iason.c") out_path = os.path.splitext(file_path)[0] if args["--keep-c"]: tmp_path = out_path + ".c" with open(tmp_path, "w") as f: f.write(c_source) subprocess.run( - ["gcc", "-O2", tmp_path, runtime_c, "-o", out_path, "-lcurl", "-lmicrohttpd"], + ["gcc", "-O2", tmp_path, runtime_c, iason_c, "-o", out_path, "-lcurl", "-lmicrohttpd", "-lm"], check=True, ) else: @@ -72,7 +74,7 @@ def main(): tmp_path = tmp.name try: subprocess.run( - ["gcc", "-O2", tmp_path, runtime_c, "-o", out_path, "-lcurl", "-lmicrohttpd"], + ["gcc", "-O2", tmp_path, runtime_c, iason_c, "-o", out_path, "-lcurl", "-lmicrohttpd", "-lm"], check=True, ) finally: diff --git a/centvrion/ast_nodes.py b/centvrion/ast_nodes.py index c4f3917..6d90aaa 100644 --- a/centvrion/ast_nodes.py +++ b/centvrion/ast_nodes.py @@ -1,5 +1,7 @@ import functools import http.server +import json +import math import re import time import urllib.parse @@ -290,6 +292,51 @@ def frac_to_fraction(s, magnvm=False, svbnvlla=False): return total +def _json_to_val(obj): + if obj is None: + return ValNul() + if isinstance(obj, bool): + return ValBool(obj) + if isinstance(obj, int): + return ValInt(obj) + if isinstance(obj, Fraction): + if obj.denominator == 1: + return ValInt(obj.numerator) + return ValFrac(obj) + if isinstance(obj, str): + return ValStr(obj) + if isinstance(obj, list): + return ValList([_json_to_val(x) for x in obj]) + if isinstance(obj, dict): + return ValDict({k: _json_to_val(v) for k, v in obj.items()}) + raise CentvrionError(f"IASON_LEGE: unsupported JSON value of type {type(obj).__name__}") + + +def _val_to_json(val): + if isinstance(val, ValNul): + return None + if isinstance(val, ValBool): + return val.value() + if isinstance(val, ValInt): + return val.value() + if isinstance(val, ValFrac): + return float(val.value()) + if isinstance(val, ValStr): + return val.value() + if isinstance(val, ValList): + return [_val_to_json(x) for x in val.value()] + if isinstance(val, ValDict): + out = {} + for k, v in val.value().items(): + if not isinstance(k, str): + raise CentvrionError("IASON_SCRIBE: dict keys must be strings to serialize as JSON") + out[k] = _val_to_json(v) + return out + if isinstance(val, ValFunc): + raise CentvrionError("IASON_SCRIBE: cannot serialize a function") + raise CentvrionError(f"IASON_SCRIBE: cannot serialize value of type {type(val).__name__}") + + def fraction_to_frac(f, magnvm=False, svbnvlla=False) -> str: if f < 0: if not svbnvlla: @@ -1782,6 +1829,30 @@ class BuiltIn(Node): server = http.server.HTTPServer(("0.0.0.0", port.value()), _CentHandler) server.serve_forever() return vtable, ValNul() + case "IASON_LEGE": + if "IASON" not in vtable["#modules"]: + raise CentvrionError("Cannot use 'IASON_LEGE' without module 'IASON'") + if len(params) != 1: + raise CentvrionError("IASON_LEGE takes exactly I argument") + s = params[0] + if not isinstance(s, ValStr): + raise CentvrionError("IASON_LEGE requires a string") + fractio = "FRACTIO" in vtable["#modules"] + try: + if fractio: + parsed = json.loads(s.value(), parse_float=Fraction) + else: + parsed = json.loads(s.value(), parse_float=lambda x: math.floor(float(x))) + except json.JSONDecodeError as e: + raise CentvrionError(f"IASON_LEGE: invalid JSON: {e.msg}") + return vtable, _json_to_val(parsed) + case "IASON_SCRIBE": + if "IASON" not in vtable["#modules"]: + raise CentvrionError("Cannot use 'IASON_SCRIBE' without module 'IASON'") + if len(params) != 1: + raise CentvrionError("IASON_SCRIBE takes exactly I argument") + obj = _val_to_json(params[0]) + return vtable, ValStr(json.dumps(obj, ensure_ascii=False)) case _: raise NotImplementedError(self.builtin) diff --git a/centvrion/compiler/emit_expr.py b/centvrion/compiler/emit_expr.py index c95a185..328d978 100644 --- a/centvrion/compiler/emit_expr.py +++ b/centvrion/compiler/emit_expr.py @@ -437,6 +437,25 @@ def _emit_builtin(node, ctx): lines.append(f"cent_avscvlta({param_vars[0]});") lines.append(f"CentValue {tmp} = cent_null();") + case "IASON_LEGE": + if not ctx.has_module("IASON"): + lines.append('cent_runtime_error("IASON module required for IASON_LEGE");') + lines.append(f"CentValue {tmp} = cent_null();") + elif len(param_vars) != 1: + raise _err(node, "IASON_LEGE takes exactly I argument") + else: + fractio_flag = "1" if ctx.has_module("FRACTIO") else "0" + lines.append(f"CentValue {tmp} = cent_iason_lege({param_vars[0]}, {fractio_flag});") + + case "IASON_SCRIBE": + if not ctx.has_module("IASON"): + lines.append('cent_runtime_error("IASON module required for IASON_SCRIBE");') + lines.append(f"CentValue {tmp} = cent_null();") + elif len(param_vars) != 1: + raise _err(node, "IASON_SCRIBE takes exactly I argument") + else: + lines.append(f"CentValue {tmp} = cent_iason_scribe({param_vars[0]});") + case _: raise NotImplementedError(node.builtin) diff --git a/centvrion/compiler/emitter.py b/centvrion/compiler/emitter.py index 4ca6768..0bb4992 100644 --- a/centvrion/compiler/emitter.py +++ b/centvrion/compiler/emitter.py @@ -58,6 +58,7 @@ def compile_program(program): # Includes lines += [ f'#include "{_RUNTIME_DIR}/cent_runtime.h"', + f'#include "{_RUNTIME_DIR}/cent_iason.h"', "", ] diff --git a/centvrion/compiler/runtime/cent_iason.c b/centvrion/compiler/runtime/cent_iason.c new file mode 100644 index 0000000..54c16cf --- /dev/null +++ b/centvrion/compiler/runtime/cent_iason.c @@ -0,0 +1,426 @@ +#include "cent_iason.h" + +#include +#include +#include +#include +#include + +/* ---------- shared helpers ----------------------------------------- */ + +static long iason_gcd(long a, long b) { + if (a < 0) a = -a; + if (b < 0) b = -b; + while (b) { long t = b; b = a % b; a = t; } + return a ? a : 1; +} + +static CentValue iason_frac_reduce(long num, long den) { + if (den < 0) { num = -num; den = -den; } + long g = iason_gcd(num, den); + num /= g; den /= g; + if (den == 1) return cent_int(num); + return cent_frac(num, den); +} + +/* ---------- parser ------------------------------------------------- */ + +typedef struct { + const char *src; + size_t pos; + size_t len; + int fractio; +} IasonParser; + +static void iason_die(const char *msg) { + cent_runtime_error(msg); +} + +static void iason_skip_ws(IasonParser *p) { + while (p->pos < p->len) { + char c = p->src[p->pos]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') p->pos++; + else break; + } +} + +static int iason_peek(IasonParser *p) { + return (p->pos < p->len) ? (unsigned char)p->src[p->pos] : -1; +} + +static void iason_expect(IasonParser *p, char c, const char *msg) { + if (p->pos >= p->len || p->src[p->pos] != c) + iason_die(msg); + p->pos++; +} + +static void iason_expect_word(IasonParser *p, const char *word) { + size_t n = strlen(word); + if (p->len - p->pos < n || memcmp(p->src + p->pos, word, n) != 0) + iason_die("IASON_LEGE: invalid JSON literal"); + p->pos += n; +} + +/* Encode a Unicode codepoint as UTF-8 into buf; returns bytes written. */ +static int iason_utf8_encode(unsigned cp, char *buf) { + if (cp <= 0x7F) { buf[0] = (char)cp; return 1; } + if (cp <= 0x7FF) { buf[0] = (char)(0xC0 | (cp >> 6)); + buf[1] = (char)(0x80 | (cp & 0x3F)); return 2; } + if (cp <= 0xFFFF) { buf[0] = (char)(0xE0 | (cp >> 12)); + buf[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); + buf[2] = (char)(0x80 | (cp & 0x3F)); return 3; } + if (cp <= 0x10FFFF) { buf[0] = (char)(0xF0 | (cp >> 18)); + buf[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); + buf[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); + buf[3] = (char)(0x80 | (cp & 0x3F)); return 4; } + iason_die("IASON_LEGE: codepoint out of range"); + return 0; +} + +static unsigned iason_read_hex4(IasonParser *p) { + if (p->len - p->pos < 4) iason_die("IASON_LEGE: truncated \\u escape"); + unsigned v = 0; + for (int i = 0; i < 4; i++) { + char c = p->src[p->pos++]; + v <<= 4; + if (c >= '0' && c <= '9') v |= c - '0'; + else if (c >= 'a' && c <= 'f') v |= c - 'a' + 10; + else if (c >= 'A' && c <= 'F') v |= c - 'A' + 10; + else iason_die("IASON_LEGE: invalid hex in \\u escape"); + } + return v; +} + +/* Parses a JSON string literal at p->pos (positioned at the opening "), + returns an arena-allocated NUL-terminated UTF-8 string. */ +static char *iason_parse_string(IasonParser *p) { + iason_expect(p, '"', "IASON_LEGE: expected string"); + /* upper bound on output: same as remaining input (escapes shrink). */ + size_t cap = (p->len - p->pos) + 1; + char *buf = cent_arena_alloc(cent_arena, cap); + size_t out = 0; + while (p->pos < p->len) { + unsigned char c = (unsigned char)p->src[p->pos++]; + if (c == '"') { buf[out] = '\0'; return buf; } + if (c == '\\') { + if (p->pos >= p->len) iason_die("IASON_LEGE: trailing \\ in string"); + char esc = p->src[p->pos++]; + switch (esc) { + case '"': buf[out++] = '"'; break; + case '\\': buf[out++] = '\\'; break; + case '/': buf[out++] = '/'; break; + case 'b': buf[out++] = '\b'; break; + case 'f': buf[out++] = '\f'; break; + case 'n': buf[out++] = '\n'; break; + case 'r': buf[out++] = '\r'; break; + case 't': buf[out++] = '\t'; break; + case 'u': { + unsigned cp = iason_read_hex4(p); + if (cp >= 0xD800 && cp <= 0xDBFF) { + /* high surrogate; expect \uXXXX low surrogate */ + if (p->len - p->pos < 6 || p->src[p->pos] != '\\' || p->src[p->pos + 1] != 'u') + iason_die("IASON_LEGE: missing low surrogate after high surrogate"); + p->pos += 2; + unsigned lo = iason_read_hex4(p); + if (lo < 0xDC00 || lo > 0xDFFF) + iason_die("IASON_LEGE: invalid low surrogate"); + cp = 0x10000 + (((cp - 0xD800) << 10) | (lo - 0xDC00)); + } else if (cp >= 0xDC00 && cp <= 0xDFFF) { + iason_die("IASON_LEGE: stray low surrogate"); + } + out += iason_utf8_encode(cp, buf + out); + break; + } + default: iason_die("IASON_LEGE: invalid escape sequence"); + } + } else if (c < 0x20) { + iason_die("IASON_LEGE: unescaped control character in string"); + } else { + buf[out++] = (char)c; + } + } + iason_die("IASON_LEGE: unterminated string"); + return NULL; +} + +/* Cap on fractional digits parsed exactly; beyond this we truncate to + keep `long` arithmetic safe (10^18 fits in int64). */ +#define IASON_MAX_FRAC_DIGITS 18 + +static CentValue iason_parse_number(IasonParser *p) { + size_t start = p->pos; + int negative = 0; + if (p->src[p->pos] == '-') { negative = 1; p->pos++; } + + /* Integer part. */ + if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos])) + iason_die("IASON_LEGE: invalid number"); + if (p->src[p->pos] == '0') { + p->pos++; + } else { + while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) p->pos++; + } + + int has_frac = 0, has_exp = 0; + size_t frac_start = 0, frac_end = 0; + if (p->pos < p->len && p->src[p->pos] == '.') { + has_frac = 1; + p->pos++; + frac_start = p->pos; + if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos])) + iason_die("IASON_LEGE: invalid number"); + while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) p->pos++; + frac_end = p->pos; + } + long exp = 0; + if (p->pos < p->len && (p->src[p->pos] == 'e' || p->src[p->pos] == 'E')) { + has_exp = 1; + p->pos++; + int esign = 1; + if (p->pos < p->len && (p->src[p->pos] == '+' || p->src[p->pos] == '-')) { + if (p->src[p->pos] == '-') esign = -1; + p->pos++; + } + if (p->pos >= p->len || !isdigit((unsigned char)p->src[p->pos])) + iason_die("IASON_LEGE: invalid number"); + while (p->pos < p->len && isdigit((unsigned char)p->src[p->pos])) { + exp = exp * 10 + (p->src[p->pos] - '0'); + p->pos++; + } + exp *= esign; + } + + size_t end = p->pos; + + if (!has_frac && !has_exp) { + /* Pure integer. Use strtol-style parse (we already validated digits). */ + long v = 0; + for (size_t i = (negative ? start + 1 : start); i < end; i++) { + v = v * 10 + (p->src[i] - '0'); + } + return cent_int(negative ? -v : v); + } + + if (!p->fractio) { + /* Floor to int. strtod handles the full grammar here. */ + char *tmp = cent_arena_alloc(cent_arena, end - start + 1); + memcpy(tmp, p->src + start, end - start); + tmp[end - start] = '\0'; + double d = strtod(tmp, NULL); + return cent_int((long)floor(d)); + } + + /* FRACTIO loaded: build an exact fraction from the decimal/exponent form. */ + long num = 0; + /* Integer-part digits */ + size_t int_start = negative ? start + 1 : start; + size_t int_end = has_frac ? (frac_start - 1) : (has_exp ? end - 0 : end); + /* If we have an exponent without a fraction part, find where digits end. */ + if (has_exp && !has_frac) { + int_end = int_start; + while (int_end < p->len && isdigit((unsigned char)p->src[int_end])) int_end++; + } + for (size_t i = int_start; i < int_end; i++) { + num = num * 10 + (p->src[i] - '0'); + } + /* Fractional digits, capped */ + long den = 1; + if (has_frac) { + size_t take = frac_end - frac_start; + if (take > IASON_MAX_FRAC_DIGITS) take = IASON_MAX_FRAC_DIGITS; + for (size_t i = 0; i < take; i++) { + num = num * 10 + (p->src[frac_start + i] - '0'); + den *= 10; + } + } + if (negative) num = -num; + /* Apply exponent: positive shifts num, negative shifts den. */ + while (exp > 0) { num *= 10; exp--; } + while (exp < 0) { den *= 10; exp++; } + return iason_frac_reduce(num, den); +} + +static CentValue iason_parse_value(IasonParser *p); + +static CentValue iason_parse_array(IasonParser *p) { + iason_expect(p, '[', "IASON_LEGE: expected ["); + iason_skip_ws(p); + CentValue lst = cent_list_new(4); + if (iason_peek(p) == ']') { p->pos++; return lst; } + for (;;) { + iason_skip_ws(p); + CentValue elem = iason_parse_value(p); + cent_list_push(&lst, elem); + iason_skip_ws(p); + int c = iason_peek(p); + if (c == ',') { p->pos++; continue; } + if (c == ']') { p->pos++; return lst; } + iason_die("IASON_LEGE: expected , or ] in array"); + } +} + +static CentValue iason_parse_object(IasonParser *p) { + iason_expect(p, '{', "IASON_LEGE: expected {"); + iason_skip_ws(p); + CentValue d = cent_dict_new(4); + if (iason_peek(p) == '}') { p->pos++; return d; } + for (;;) { + iason_skip_ws(p); + if (iason_peek(p) != '"') iason_die("IASON_LEGE: object key must be a string"); + char *key = iason_parse_string(p); + iason_skip_ws(p); + iason_expect(p, ':', "IASON_LEGE: expected : after object key"); + iason_skip_ws(p); + CentValue val = iason_parse_value(p); + cent_dict_set(&d, cent_str(key), val); + iason_skip_ws(p); + int c = iason_peek(p); + if (c == ',') { p->pos++; continue; } + if (c == '}') { p->pos++; return d; } + iason_die("IASON_LEGE: expected , or } in object"); + } +} + +static CentValue iason_parse_value(IasonParser *p) { + iason_skip_ws(p); + int c = iason_peek(p); + if (c < 0) iason_die("IASON_LEGE: unexpected end of input"); + if (c == '{') return iason_parse_object(p); + if (c == '[') return iason_parse_array(p); + if (c == '"') return cent_str(iason_parse_string(p)); + if (c == 't') { iason_expect_word(p, "true"); return cent_bool(1); } + if (c == 'f') { iason_expect_word(p, "false"); return cent_bool(0); } + if (c == 'n') { iason_expect_word(p, "null"); return cent_null(); } + if (c == '-' || (c >= '0' && c <= '9')) return iason_parse_number(p); + iason_die("IASON_LEGE: unexpected character"); + return cent_null(); +} + +CentValue cent_iason_lege(CentValue s, int fractio_loaded) { + if (s.type != CENT_STR) cent_type_error("IASON_LEGE requires a string"); + IasonParser p = { s.sval, 0, strlen(s.sval), fractio_loaded }; + CentValue v = iason_parse_value(&p); + iason_skip_ws(&p); + if (p.pos != p.len) iason_die("IASON_LEGE: trailing data after JSON value"); + return v; +} + +/* ---------- serializer --------------------------------------------- */ + +typedef struct { + char *buf; + size_t len; + size_t cap; +} IasonBuf; + +static void iason_buf_reserve(IasonBuf *b, size_t extra) { + if (b->len + extra <= b->cap) return; + size_t new_cap = b->cap ? b->cap * 2 : 64; + while (new_cap < b->len + extra) new_cap *= 2; + char *nb = cent_arena_alloc(cent_arena, new_cap); + if (b->len) memcpy(nb, b->buf, b->len); + b->buf = nb; + b->cap = new_cap; +} + +static void iason_buf_putc(IasonBuf *b, char c) { + iason_buf_reserve(b, 1); + b->buf[b->len++] = c; +} + +static void iason_buf_puts(IasonBuf *b, const char *s) { + size_t n = strlen(s); + iason_buf_reserve(b, n); + memcpy(b->buf + b->len, s, n); + b->len += n; +} + +static void iason_buf_putn(IasonBuf *b, const char *s, size_t n) { + iason_buf_reserve(b, n); + memcpy(b->buf + b->len, s, n); + b->len += n; +} + +static void iason_emit_string(IasonBuf *b, const char *s) { + iason_buf_putc(b, '"'); + for (const unsigned char *p = (const unsigned char *)s; *p; p++) { + unsigned char c = *p; + switch (c) { + case '"': iason_buf_puts(b, "\\\""); break; + case '\\': iason_buf_puts(b, "\\\\"); break; + case '\b': iason_buf_puts(b, "\\b"); break; + case '\f': iason_buf_puts(b, "\\f"); break; + case '\n': iason_buf_puts(b, "\\n"); break; + case '\r': iason_buf_puts(b, "\\r"); break; + case '\t': iason_buf_puts(b, "\\t"); break; + default: + if (c < 0x20) { + char tmp[8]; + snprintf(tmp, sizeof tmp, "\\u%04x", c); + iason_buf_puts(b, tmp); + } else { + iason_buf_putc(b, (char)c); + } + } + } + iason_buf_putc(b, '"'); +} + +static void iason_emit_value(IasonBuf *b, CentValue v) { + switch (v.type) { + case CENT_NULL: iason_buf_puts(b, "null"); return; + case CENT_BOOL: iason_buf_puts(b, v.bval ? "true" : "false"); return; + case CENT_INT: { + char tmp[32]; + int n = snprintf(tmp, sizeof tmp, "%ld", v.ival); + iason_buf_putn(b, tmp, (size_t)n); + return; + } + case CENT_FRAC: { + double d = (double)v.fval.num / (double)v.fval.den; + /* Shortest round-trippable representation, like Python's float repr. */ + char tmp[64]; + int n = 0; + for (int prec = 15; prec <= 17; prec++) { + n = snprintf(tmp, sizeof tmp, "%.*g", prec, d); + if (strtod(tmp, NULL) == d) break; + } + iason_buf_putn(b, tmp, (size_t)n); + return; + } + case CENT_STR: iason_emit_string(b, v.sval); return; + case CENT_LIST: { + iason_buf_putc(b, '['); + for (int i = 0; i < v.lval.len; i++) { + if (i > 0) iason_buf_puts(b, ", "); + iason_emit_value(b, v.lval.items[i]); + } + iason_buf_putc(b, ']'); + return; + } + case CENT_DICT: { + iason_buf_putc(b, '{'); + for (int i = 0; i < v.dval.len; i++) { + if (i > 0) iason_buf_puts(b, ", "); + CentValue k = v.dval.keys[i]; + if (k.type != CENT_STR) + cent_runtime_error("IASON_SCRIBE: dict keys must be strings to serialize as JSON"); + iason_emit_string(b, k.sval); + iason_buf_puts(b, ": "); + iason_emit_value(b, v.dval.vals[i]); + } + iason_buf_putc(b, '}'); + return; + } + case CENT_FUNC: + cent_runtime_error("IASON_SCRIBE: cannot serialize a function"); + return; + } +} + +CentValue cent_iason_scribe(CentValue v) { + IasonBuf b = { NULL, 0, 0 }; + iason_emit_value(&b, v); + iason_buf_putc(&b, '\0'); + return cent_str(b.buf); +} diff --git a/centvrion/compiler/runtime/cent_iason.h b/centvrion/compiler/runtime/cent_iason.h new file mode 100644 index 0000000..7fc6123 --- /dev/null +++ b/centvrion/compiler/runtime/cent_iason.h @@ -0,0 +1,14 @@ +#ifndef CENT_IASON_H +#define CENT_IASON_H + +#include "cent_runtime.h" + +/* IASON_LEGE — parse a JSON string into a CENTVRION value tree. + When fractio_loaded != 0, JSON floats become exact fractions; otherwise + they are floored to ints. */ +CentValue cent_iason_lege(CentValue s, int fractio_loaded); + +/* IASON_SCRIBE — serialize a CENTVRION value to a JSON string. */ +CentValue cent_iason_scribe(CentValue v); + +#endif /* CENT_IASON_H */ diff --git a/centvrion/lexer.py b/centvrion/lexer.py index 0e68727..914e438 100644 --- a/centvrion/lexer.py +++ b/centvrion/lexer.py @@ -80,7 +80,9 @@ builtin_tokens = [("BUILTIN", i) for i in [ "SCINDE", "PETE", "PETITVR", - "AVSCVLTA" + "AVSCVLTA", + "IASON_LEGE", + "IASON_SCRIBE" ]] data_tokens = [ @@ -92,6 +94,7 @@ data_tokens = [ module_tokens = [("MODULE", i) for i in [ "FORS", "FRACTIO", + "IASON", "MAGNVM", "SCRIPTA", "SVBNVLLA", diff --git a/language/main.tex b/language/main.tex index d2051ec..5231778 100644 --- a/language/main.tex +++ b/language/main.tex @@ -104,7 +104,7 @@ \newpage \begin{itemize} \item \textbf{newline}: \\ Newlines are combined, so a single newline is the same as multiple. - \item \textbf{module-name}: \\ Modules are flags given to the interpreter/compiler, to let it know you want to be using certain rules, functions, or features. Available modules: \texttt{FORS} (randomness), \texttt{FRACTIO} (fractions), \texttt{MAGNVM} (large integers), \texttt{SCRIPTA} (file I/O: \texttt{LEGE}, \texttt{SCRIBE}, \texttt{ADIVNGE}), \texttt{SVBNVLLA} (negative literals), \texttt{RETE} (networking: \texttt{PETE}, \texttt{PETITVR}, \texttt{AVSCVLTA}). + \item \textbf{module-name}: \\ Modules are flags given to the interpreter/compiler, to let it know you want to be using certain rules, functions, or features. Available modules: \texttt{FORS} (randomness), \texttt{FRACTIO} (fractions), \texttt{IASON} (JSON I/O: \texttt{IASON\_LEGE}, \texttt{IASON\_SCRIBE}), \texttt{MAGNVM} (large integers), \texttt{SCRIPTA} (file I/O: \texttt{LEGE}, \texttt{SCRIBE}, \texttt{ADIVNGE}), \texttt{SVBNVLLA} (negative literals), \texttt{RETE} (networking: \texttt{PETE}, \texttt{PETITVR}, \texttt{AVSCVLTA}). \item \textbf{id}: \\ Variable. Can only consist of lowercase characters and underscores, but not the letters j, u, or w. \item \textbf{builtin}: \\ Builtin functions are uppercase latin words. \item \textbf{string}: \\ Any text encased in \texttt{"} or \texttt{'} characters. Single-quoted strings are always literal. Strings support 1-based indexing (\texttt{string[I]}) and inclusive slicing (\texttt{string[I VSQVE III]}), returning single-character strings and substrings respectively. diff --git a/snippets/iason.cent b/snippets/iason.cent new file mode 100644 index 0000000..b754f3d --- /dev/null +++ b/snippets/iason.cent @@ -0,0 +1 @@ +CVM IASON diff --git a/snippets/iason.png b/snippets/iason.png new file mode 100644 index 0000000000000000000000000000000000000000..56ed2bbc0c2c56af2d6597e0dfe009939f2fd84b GIT binary patch literal 4099 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 literal 0 HcmV?d00001 diff --git a/snippets/iason_lege.cent b/snippets/iason_lege.cent new file mode 100644 index 0000000..a712d13 --- /dev/null +++ b/snippets/iason_lege.cent @@ -0,0 +1,5 @@ +CVM IASON +DESIGNA data VT IASON_LEGE('{"nomen": "Marcus", "anni": 30, "armorum": ["gladius", "scutum"]}') +DIC(data["nomen"]) +DIC(data["anni"]) +DIC(data["armorum"]) diff --git a/snippets/iason_lege.png b/snippets/iason_lege.png new file mode 100644 index 0000000000000000000000000000000000000000..2e4dcf0dda3a5cfe1f6e2c6645b4ea77d4ea288c GIT binary patch literal 51859 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 BA@TqK literal 0 HcmV?d00001 diff --git a/snippets/iason_scribe.cent b/snippets/iason_scribe.cent new file mode 100644 index 0000000..55ee2b8 --- /dev/null +++ b/snippets/iason_scribe.cent @@ -0,0 +1,3 @@ +CVM IASON +DESIGNA persona VT TABVLA {"nomen" VT "Marcus", "anni" VT XXX} +DIC(IASON_SCRIBE(persona)) diff --git a/snippets/iason_scribe.png b/snippets/iason_scribe.png new file mode 100644 index 0000000000000000000000000000000000000000..f958a2b799a6c4d41fc67b3341755d0d52eaca88 GIT binary patch literal 34497 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 literal 0 HcmV?d00001 diff --git a/snippets/syntaxes/centvrion.sublime-syntax b/snippets/syntaxes/centvrion.sublime-syntax index d2cfe32..2fecc8d 100644 --- a/snippets/syntaxes/centvrion.sublime-syntax +++ b/snippets/syntaxes/centvrion.sublime-syntax @@ -70,11 +70,11 @@ contexts: scope: constant.language.centvrion builtins: - - match: '\b(ADDE|ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|CONFLA|CRIBRA|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|INSERE|IVNGE|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|MVTA|NECTE|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TOLLE|TYPVS)\b' + - match: '\b(ADDE|ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|CONFLA|CRIBRA|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|IASON_LEGE|IASON_SCRIBE|INSERE|IVNGE|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|MVTA|NECTE|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TOLLE|TYPVS)\b' scope: support.function.builtin.centvrion modules: - - match: '\b(FORS|FRACTIO|MAGNVM|RETE|SCRIPTA|SVBNVLLA)\b' + - match: '\b(FORS|FRACTIO|IASON|MAGNVM|RETE|SCRIPTA|SVBNVLLA)\b' scope: support.class.module.centvrion keywords: diff --git a/tests/12_test_failures.py b/tests/12_test_failures.py index 7d55b79..53bfba0 100644 --- a/tests/12_test_failures.py +++ b/tests/12_test_failures.py @@ -8,7 +8,7 @@ from tests._helpers import ( String, TemptaStatement, UnaryMinus, UnaryNot, Fractio, frac_to_fraction, fraction_to_frac, num_to_int, int_to_num, make_string, ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac, - CentvrionError, _RUNTIME_C, _cent_rng, + CentvrionError, _RUNTIME_C, _IASON_C, _cent_rng, Lexer, Parser, compile_program, os, subprocess, tempfile, StringIO, patch, ) @@ -160,6 +160,24 @@ error_tests = [ ('IVNGE(["a"], II)', CentvrionError), # IVNGE second arg not a list ("IVNGE([VERITAS], [I])", CentvrionError), # IVNGE invalid key type (bool) ("IVNGE([[I]], [II])", CentvrionError), # IVNGE invalid key type (list) + ("IASON_LEGE('null')", CentvrionError), # IASON module required for IASON_LEGE + ("IASON_SCRIBE(NVLLVS)", CentvrionError), # IASON module required for IASON_SCRIBE + ("CVM IASON\nIASON_LEGE(I)", CentvrionError), # IASON_LEGE non-string arg + ("CVM IASON\nIASON_LEGE()", CentvrionError), # IASON_LEGE no args + ("CVM IASON\nIASON_LEGE('null', 'null')", CentvrionError), # IASON_LEGE too many args + ("CVM IASON\nIASON_LEGE('not json')", CentvrionError), # invalid JSON + ("CVM IASON\nIASON_LEGE('[1,]')", CentvrionError), # trailing comma in array + ("CVM IASON\nIASON_LEGE('{\"a\":}')", CentvrionError), # missing value in object + ("CVM IASON\nIASON_LEGE('{\"a\" 1}')", CentvrionError), # missing colon in object + ("CVM IASON\nIASON_LEGE('[1, 2')", CentvrionError), # unterminated array + ("CVM IASON\nIASON_LEGE('{')", CentvrionError), # unterminated object + ("CVM IASON\nIASON_LEGE('\"abc')", CentvrionError), # unterminated string + ("CVM IASON\nIASON_LEGE('[1] junk')", CentvrionError), # trailing data + ("CVM IASON\nIASON_LEGE('[\"a\\\\x\"]')", CentvrionError), # invalid escape + ("CVM IASON\nIASON_SCRIBE()", CentvrionError), # IASON_SCRIBE no args + ("CVM IASON\nIASON_SCRIBE(I, II)", CentvrionError), # IASON_SCRIBE too many args + ('CVM IASON\nIASON_SCRIBE(IVNGE([I], ["v"]))', CentvrionError), # IASON_SCRIBE int dict keys + ("CVM IASON\nIASON_SCRIBE(FVNCTIO (x) VT { REDI(x) })", CentvrionError), # IASON_SCRIBE function ] class TestErrors(unittest.TestCase): @@ -196,7 +214,7 @@ class TestErrorLineNumbers(unittest.TestCase): tmp_bin_path = tmp_bin.name try: subprocess.run( - ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], + ["gcc", "-O2", tmp_c_path, _RUNTIME_C, _IASON_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd", "-lm"], check=True, capture_output=True, ) proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) diff --git a/tests/13_test_iason___.py b/tests/13_test_iason___.py new file mode 100644 index 0000000..fe26198 --- /dev/null +++ b/tests/13_test_iason___.py @@ -0,0 +1,173 @@ +from tests._helpers import ( + unittest, parameterized, Fraction, + run_test, + Bool, BuiltIn, DataArray, DataDict, Designa, ExpressionStatement, ID, + ModuleCall, Nullus, Numeral, Program, String, + ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFrac, +) + + +def _scribe(arg, modules=("IASON",)): + return Program( + [ModuleCall(m) for m in modules], + [ExpressionStatement(BuiltIn("IASON_SCRIBE", [arg]))], + ) + +def _lege(arg, modules=("IASON",)): + return Program( + [ModuleCall(m) for m in modules], + [ExpressionStatement(BuiltIn("IASON_LEGE", [String(arg)]))], + ) + +def _src_lege(arg, extra_modules=()): + modules = ("IASON",) + tuple(extra_modules) + prefix = "\n".join(f"CVM {m}" for m in modules) + "\n" + return prefix + f"IASON_LEGE('{arg}')" + +def _src_scribe(arg_text, extra_modules=()): + modules = ("IASON",) + tuple(extra_modules) + prefix = "\n".join(f"CVM {m}" for m in modules) + "\n" + return prefix + f"IASON_SCRIBE({arg_text})" + + +iason_tests = [ + # ---- Parse: scalars ---- + (_src_lege("null"), _lege("null"), ValNul()), + (_src_lege("true"), _lege("true"), ValBool(True)), + (_src_lege("false"), _lege("false"), ValBool(False)), + (_src_lege("42"), _lege("42"), ValInt(42)), + (_src_lege('"hello"'), _lege('"hello"'), ValStr("hello")), + + # ---- Parse: empty containers ---- + (_src_lege("[]"), _lege("[]"), ValList([])), + (_src_lege("{}"), _lege("{}"), ValDict({})), + + # ---- Parse: array of mixed types ---- + (_src_lege('[1, true, null, "x"]'), + _lege('[1, true, null, "x"]'), + ValList([ValInt(1), ValBool(True), ValNul(), ValStr("x")])), + + # ---- Parse: nested ---- + (_src_lege('{"a": [1, 2], "b": {"c": 3}}'), + _lege('{"a": [1, 2], "b": {"c": 3}}'), + ValDict({ + "a": ValList([ValInt(1), ValInt(2)]), + "b": ValDict({"c": ValInt(3)}), + })), + + # ---- Parse: numbers ---- + (_src_lege("-7"), _lege("-7"), ValInt(-7)), + (_src_lege("0"), _lege("0"), ValInt(0)), + + # ---- Parse: string escapes ---- + # NB: single-quoted CENTVRION strings unescape \n / \" / \\ before the + # JSON parser sees them, so direct parse tests for those escapes would + # have ambiguous semantics. Serialize tests below cover the inverse, and + # this \u test exercises the JSON parser's escape path. + (_src_lege('"\\u00e9"'), + _lege('"\\u00e9"'), + ValStr("é")), + + # ---- Parse: float without FRACTIO floors ---- + (_src_lege("3.7"), _lege("3.7"), ValInt(3)), + (_src_lege("-2.5"), _lege("-2.5"), ValInt(-3)), + (_src_lege("1e2"), _lege("1e2"), ValInt(100)), + + # ---- Parse: float with FRACTIO is exact ---- + (_src_lege("0.5", extra_modules=("FRACTIO",)), + _lege("0.5", modules=("IASON", "FRACTIO")), + ValFrac(Fraction(1, 2))), + (_src_lege("0.1", extra_modules=("FRACTIO",)), + _lege("0.1", modules=("IASON", "FRACTIO")), + ValFrac(Fraction(1, 10))), + (_src_lege("-0.25", extra_modules=("FRACTIO",)), + _lege("-0.25", modules=("IASON", "FRACTIO")), + ValFrac(Fraction(-1, 4))), + (_src_lege("5", extra_modules=("FRACTIO",)), + _lege("5", modules=("IASON", "FRACTIO")), + ValInt(5)), + (_src_lege("3.0", extra_modules=("FRACTIO",)), + _lege("3.0", modules=("IASON", "FRACTIO")), + ValInt(3)), + + # ---- Serialize: scalars ---- + (_src_scribe("NVLLVS"), _scribe(Nullus()), ValStr("null")), + (_src_scribe("VERITAS"), _scribe(Bool(True)), ValStr("true")), + (_src_scribe("FALSITAS"), _scribe(Bool(False)), ValStr("false")), + (_src_scribe("XLII"), _scribe(Numeral("XLII")), ValStr("42")), + (_src_scribe('"hello"'), _scribe(String("hello")), ValStr('"hello"')), + (_src_scribe("[]"), _scribe(DataArray([])), ValStr("[]")), + (_src_scribe("TABVLA {}"), _scribe(DataDict([])), ValStr("{}")), + + # ---- Serialize: nested ---- + (_src_scribe("[I, II, III]"), + _scribe(DataArray([Numeral("I"), Numeral("II"), Numeral("III")])), + ValStr("[1, 2, 3]")), + (_src_scribe('TABVLA {"a" VT I, "b" VT VERITAS}'), + _scribe(DataDict([(String("a"), Numeral("I")), (String("b"), Bool(True))])), + ValStr('{"a": 1, "b": true}')), + + # ---- Serialize: special chars ---- + (_src_scribe('"a\\nb"'), + _scribe(String("a\nb")), + ValStr('"a\\nb"')), + (_src_scribe('"a\\"b"'), + _scribe(String('a"b')), + ValStr('"a\\"b"')), + (_src_scribe('"a\\\\b"'), + _scribe(String("a\\b")), + ValStr('"a\\\\b"')), + + # ---- Round-trip ---- + ("CVM IASON\nDIC(IASON_LEGE('[1, 2, 3]'))", + Program([ModuleCall("IASON")], [ExpressionStatement(BuiltIn("DIC", + [BuiltIn("IASON_LEGE", [String("[1, 2, 3]")])]))]), + ValStr("[I II III]"), "[I II III]\n"), + + ("CVM IASON\nDIC(IASON_SCRIBE(IASON_LEGE('{\"a\": [1, true, null]}')))", + Program([ModuleCall("IASON")], [ExpressionStatement(BuiltIn("DIC", + [BuiltIn("IASON_SCRIBE", + [BuiltIn("IASON_LEGE", [String('{"a": [1, true, null]}')])])]))]), + ValStr('{"a": [1, true, null]}'), + '{"a": [1, true, null]}\n'), + + ("CVM IASON\nCVM FRACTIO\nDIC(IASON_SCRIBE(IASON_LEGE('0.5')))", + Program([ModuleCall("IASON"), ModuleCall("FRACTIO")], + [ExpressionStatement(BuiltIn("DIC", + [BuiltIn("IASON_SCRIBE", + [BuiltIn("IASON_LEGE", [String("0.5")])])]))]), + ValStr("0.5"), "0.5\n"), + + ("CVM IASON\nCVM FRACTIO\nDIC(IASON_SCRIBE(IASON_LEGE('0.1')))", + Program([ModuleCall("IASON"), ModuleCall("FRACTIO")], + [ExpressionStatement(BuiltIn("DIC", + [BuiltIn("IASON_SCRIBE", + [BuiltIn("IASON_LEGE", [String("0.1")])])]))]), + ValStr("0.1"), "0.1\n"), + + # ---- Serialize: insertion order preserved ---- + (_src_scribe('TABVLA {"b" VT II, "a" VT I, "c" VT III}'), + _scribe(DataDict([ + (String("b"), Numeral("II")), + (String("a"), Numeral("I")), + (String("c"), Numeral("III")), + ])), + ValStr('{"b": 2, "a": 1, "c": 3}')), + + # ---- Whitespace-tolerant parse ---- + (_src_lege(" [ 1 , 2 ] "), + _lege(" [ 1 , 2 ] "), + ValList([ValInt(1), ValInt(2)])), + + # ---- Unicode passes through serialize (ensure_ascii=False) ---- + ('CVM IASON\nDIC(IASON_SCRIBE("café"))', + Program([ModuleCall("IASON")], [ExpressionStatement(BuiltIn("DIC", + [BuiltIn("IASON_SCRIBE", [String("café")])]))]), + ValStr('"café"'), '"café"\n'), +] + + +class TestIason(unittest.TestCase): + @parameterized.expand(iason_tests) + def test_iason(self, source, nodes, value, output="", input_lines=[]): + run_test(self, source, nodes, value, output, input_lines) diff --git a/tests/_helpers.py b/tests/_helpers.py index 417f696..23ba73d 100644 --- a/tests/_helpers.py +++ b/tests/_helpers.py @@ -24,10 +24,12 @@ from centvrion.lexer import Lexer from centvrion.parser import Parser from centvrion.values import ValInt, ValStr, ValBool, ValList, ValDict, ValNul, ValFunc, ValFrac -_RUNTIME_C = os.path.join( +_RUNTIME_DIR = os.path.join( os.path.dirname(__file__), "..", - "centvrion", "compiler", "runtime", "cent_runtime.c" + "centvrion", "compiler", "runtime" ) +_RUNTIME_C = os.path.join(_RUNTIME_DIR, "cent_runtime.c") +_IASON_C = os.path.join(_RUNTIME_DIR, "cent_iason.c") def run_test(self, source, target_nodes, target_value, target_output="", input_lines=[]): _cent_rng.seed(1) @@ -92,7 +94,7 @@ def run_test(self, source, target_nodes, target_value, target_output="", input_l tmp_bin_path = tmp_bin.name try: subprocess.run( - ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], + ["gcc", "-O2", tmp_c_path, _RUNTIME_C, _IASON_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd", "-lm"], check=True, capture_output=True, ) stdin_data = "".join(f"{l}\n" for l in input_lines) @@ -124,7 +126,7 @@ def run_compiler_error_test(self, source): tmp_bin_path = tmp_bin.name try: subprocess.run( - ["gcc", "-O2", tmp_c_path, _RUNTIME_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd"], + ["gcc", "-O2", tmp_c_path, _RUNTIME_C, _IASON_C, "-o", tmp_bin_path, "-lcurl", "-lmicrohttpd", "-lm"], check=True, capture_output=True, ) proc = subprocess.run([tmp_bin_path], capture_output=True, text=True) diff --git a/vscode-extension/syntaxes/cent.tmLanguage.json b/vscode-extension/syntaxes/cent.tmLanguage.json index eea8ede..ead9e4c 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(ADDE|ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|CONFLA|CRIBRA|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|INSERE|IVNGE|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|MVTA|NECTE|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TOLLE|TYPVS)\\b" + "match": "\\b(ADDE|ADIVNGE|AVDI_NVMERVS|AVDI|AVSCVLTA|CLAVES|CONFLA|CRIBRA|DECIMATIO|DIC|DORMI|EVERRE|FORTVITVS_NVMERVS|FORTVITA_ELECTIO|IASON_LEGE|IASON_SCRIBE|INSERE|IVNGE|LEGE|LITTERA|LONGITVDO|MAIVSCVLA|MINVSCVLA|MVTA|NECTE|NVMERVS|ORDINA|PETE|PETITVR|QVAERE|SCINDE|SCRIBE|SEMEN|SENATVS|SVBSTITVE|TOLLE|TYPVS)\\b" } ] }, @@ -73,7 +73,7 @@ "patterns": [ { "name": "support.class.module.cent", - "match": "\\b(FORS|FRACTIO|MAGNVM|RETE|SCRIPTA|SVBNVLLA)\\b" + "match": "\\b(FORS|FRACTIO|IASON|MAGNVM|RETE|SCRIPTA|SVBNVLLA)\\b" } ] },