🐐 ORDINA with comparitor
This commit is contained in:
13
README.md
13
README.md
@@ -357,9 +357,18 @@ Returns the length of `array` (element count), `string` (character count), or `d
|
||||
Returns the keys of `dict` as an array.
|
||||
|
||||
### ORDINA
|
||||
`ORDINA(array)`
|
||||
`ORDINA(array)` or `ORDINA(array, comparator)`
|
||||
|
||||
Sorts an array in ascending order. Returns a new sorted array. All elements must be the same type — integers, fractions, or strings. Integers and fractions sort numerically; strings sort lexicographically.
|
||||
Sorts an array. Returns a new sorted array; the original is unchanged.
|
||||
|
||||
Without a comparator, sorts in ascending order. All elements must be the same type — integers, fractions, or strings. Integers and fractions sort numerically; strings sort lexicographically.
|
||||
|
||||
With a comparator, the type-uniformity rule is dropped — the comparator decides ordering. The comparator must be a function of exactly two parameters and must return `VERAX`. `comparator(a, b)` returns `VERITAS` iff `a` should come **before** `b`; two elements are treated as equal when both `comparator(a, b)` and `comparator(b, a)` are `FALSITAS`.
|
||||
|
||||

|
||||
```
|
||||
> [V III II I]
|
||||
```
|
||||
|
||||
### ADDE
|
||||
`ADDE(array, value)`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import functools
|
||||
import http.server
|
||||
import re
|
||||
import time
|
||||
@@ -1328,6 +1329,22 @@ class TemptaStatement(Node):
|
||||
return vtable, last_val
|
||||
|
||||
|
||||
def _call_func(func: ValFunc, args: list, vtable: dict, callee_desc: str = "function"):
|
||||
if len(args) != len(func.params):
|
||||
raise CentvrionError(
|
||||
f"{callee_desc} expects {len(func.params)} argument(s), got {len(args)}"
|
||||
)
|
||||
func_vtable = vtable.copy()
|
||||
for i, param in enumerate(func.params):
|
||||
func_vtable[param.name] = args[i]
|
||||
func_vtable["#return"] = None
|
||||
for statement in func.body:
|
||||
func_vtable, _ = statement.eval(func_vtable)
|
||||
if func_vtable["#return"] is not None:
|
||||
return func_vtable["#return"]
|
||||
return ValNul()
|
||||
|
||||
|
||||
class Invoca(Node):
|
||||
def __init__(self, callee, parameters) -> None:
|
||||
self.callee = callee
|
||||
@@ -1354,21 +1371,9 @@ class Invoca(Node):
|
||||
callee_desc = (self.callee.name
|
||||
if isinstance(self.callee, ID) else "expression")
|
||||
raise CentvrionError(f"{callee_desc} is not a function")
|
||||
if len(params) != len(func.params):
|
||||
callee_desc = (self.callee.name
|
||||
if isinstance(self.callee, ID) else "FVNCTIO")
|
||||
raise CentvrionError(
|
||||
f"{callee_desc} expects {len(func.params)} argument(s), got {len(params)}"
|
||||
)
|
||||
func_vtable = vtable.copy()
|
||||
for i, param in enumerate(func.params):
|
||||
func_vtable[param.name] = params[i]
|
||||
func_vtable["#return"] = None
|
||||
for statement in func.body:
|
||||
func_vtable, _ = statement.eval(func_vtable)
|
||||
if func_vtable["#return"] is not None:
|
||||
return vtable, func_vtable["#return"]
|
||||
return vtable, ValNul()
|
||||
callee_desc = (self.callee.name
|
||||
if isinstance(self.callee, ID) else "FVNCTIO")
|
||||
return vtable, _call_func(func, params, vtable, callee_desc)
|
||||
|
||||
|
||||
class BuiltIn(Node):
|
||||
@@ -1546,11 +1551,30 @@ class BuiltIn(Node):
|
||||
d[k.value()] = v
|
||||
return vtable, ValDict(d)
|
||||
case "ORDINA":
|
||||
if not 1 <= len(params) <= 2:
|
||||
raise CentvrionError("ORDINA takes 1 or 2 arguments")
|
||||
if not isinstance(params[0], ValList):
|
||||
raise CentvrionError("ORDINA requires an array")
|
||||
items = list(params[0].value())
|
||||
if not items:
|
||||
return vtable, ValList([])
|
||||
if len(params) == 2:
|
||||
cmp = params[1]
|
||||
if not isinstance(cmp, ValFunc):
|
||||
raise CentvrionError("ORDINA comparator must be a function")
|
||||
if len(cmp.params) != 2:
|
||||
raise CentvrionError("ORDINA comparator must take 2 arguments")
|
||||
def adapter(a, b):
|
||||
r = _call_func(cmp, [a, b], vtable, "ORDINA comparator")
|
||||
if not isinstance(r, ValBool):
|
||||
raise CentvrionError("ORDINA comparator must return VERAX")
|
||||
if r.value():
|
||||
return -1
|
||||
r2 = _call_func(cmp, [b, a], vtable, "ORDINA comparator")
|
||||
if not isinstance(r2, ValBool):
|
||||
raise CentvrionError("ORDINA comparator must return VERAX")
|
||||
return 1 if r2.value() else 0
|
||||
return vtable, ValList(sorted(items, key=functools.cmp_to_key(adapter)))
|
||||
all_numeric = all(isinstance(i, (ValInt, ValFrac)) for i in items)
|
||||
all_string = all(isinstance(i, ValStr) for i in items)
|
||||
if not (all_numeric or all_string):
|
||||
|
||||
@@ -311,7 +311,14 @@ def _emit_builtin(node, ctx):
|
||||
lines.append(f"CentValue {tmp} = cent_dict_keys({param_vars[0]});")
|
||||
|
||||
case "ORDINA":
|
||||
lines.append(f"CentValue {tmp} = cent_ordina({param_vars[0]});")
|
||||
if len(param_vars) == 1:
|
||||
lines.append(f"CentValue {tmp} = cent_ordina({param_vars[0]});")
|
||||
elif len(param_vars) == 2:
|
||||
lines.append(
|
||||
f"CentValue {tmp} = cent_ordina_cmp({param_vars[0]}, {param_vars[1]}, _scope);"
|
||||
)
|
||||
else:
|
||||
raise CentvrionError("ORDINA takes 1 or 2 arguments")
|
||||
|
||||
case "ADDE":
|
||||
lines.append(f"CentValue {tmp} = cent_adde({param_vars[0]}, {param_vars[1]});")
|
||||
|
||||
@@ -848,6 +848,55 @@ CentValue cent_ordina(CentValue lst) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* User-comparator sort: single-threaded runtime, so the active comparator
|
||||
and its calling scope are stashed in file-scope statics. Save/restore
|
||||
them around the qsort call so nested ORDINA(..., cmp) calls inside a
|
||||
comparator still work. */
|
||||
static CentValue _cmp_active;
|
||||
static CentScope _cmp_scope;
|
||||
|
||||
static int _ordina_user_comparator(const void *a, const void *b) {
|
||||
const CentValue *va = (const CentValue *)a;
|
||||
const CentValue *vb = (const CentValue *)b;
|
||||
CentScope s1 = cent_scope_copy(&_cmp_scope);
|
||||
cent_scope_set(&s1, _cmp_active.fnval.param_names[0], *va);
|
||||
cent_scope_set(&s1, _cmp_active.fnval.param_names[1], *vb);
|
||||
CentValue r1 = _cmp_active.fnval.fn(s1);
|
||||
if (r1.type != CENT_BOOL)
|
||||
cent_type_error("'ORDINA' comparator must return VERAX");
|
||||
if (r1.bval) return -1;
|
||||
CentScope s2 = cent_scope_copy(&_cmp_scope);
|
||||
cent_scope_set(&s2, _cmp_active.fnval.param_names[0], *vb);
|
||||
cent_scope_set(&s2, _cmp_active.fnval.param_names[1], *va);
|
||||
CentValue r2 = _cmp_active.fnval.fn(s2);
|
||||
if (r2.type != CENT_BOOL)
|
||||
cent_type_error("'ORDINA' comparator must return VERAX");
|
||||
return r2.bval ? 1 : 0;
|
||||
}
|
||||
|
||||
CentValue cent_ordina_cmp(CentValue lst, CentValue cmp, CentScope scope) {
|
||||
if (lst.type != CENT_LIST)
|
||||
cent_type_error("'ORDINA' requires a list");
|
||||
if (cmp.type != CENT_FUNC)
|
||||
cent_type_error("'ORDINA' comparator must be a function");
|
||||
if (cmp.fnval.param_count != 2)
|
||||
cent_runtime_error("'ORDINA' comparator must take 2 arguments");
|
||||
int len = lst.lval.len;
|
||||
CentValue result = cent_list_new(len);
|
||||
for (int i = 0; i < len; i++)
|
||||
cent_list_push(&result, lst.lval.items[i]);
|
||||
if (len > 1) {
|
||||
CentValue saved_cmp = _cmp_active;
|
||||
CentScope saved_scope = _cmp_scope;
|
||||
_cmp_active = cmp;
|
||||
_cmp_scope = scope;
|
||||
qsort(result.lval.items, len, sizeof(CentValue), _ordina_user_comparator);
|
||||
_cmp_active = saved_cmp;
|
||||
_cmp_scope = saved_scope;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static long _index_arg(CentValue idx, const char *name) {
|
||||
if (idx.type == CENT_INT)
|
||||
return idx.ival;
|
||||
|
||||
@@ -239,6 +239,7 @@ CentValue cent_senatus(CentValue *args, int n); /* SENATVS */
|
||||
CentValue cent_typvs(CentValue v); /* TYPVS */
|
||||
void cent_dormi(CentValue n); /* DORMI */
|
||||
CentValue cent_ordina(CentValue lst); /* ORDINA */
|
||||
CentValue cent_ordina_cmp(CentValue lst, CentValue cmp, CentScope scope); /* ORDINA w/ comparator */
|
||||
CentValue cent_adde(CentValue lst, CentValue v); /* ADDE */
|
||||
CentValue cent_tolle(CentValue lst, CentValue idx); /* TOLLE */
|
||||
CentValue cent_insere(CentValue lst, CentValue idx, CentValue v); /* INSERE */
|
||||
|
||||
4
snippets/ordina_cmp.cent
Normal file
4
snippets/ordina_cmp.cent
Normal file
@@ -0,0 +1,4 @@
|
||||
DEFINI gt (a, b) VT {
|
||||
REDI (a PLVS b)
|
||||
}
|
||||
DIC (ORDINA([II, V, I, III], gt))
|
||||
BIN
snippets/ordina_cmp.png
Normal file
BIN
snippets/ordina_cmp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -76,6 +76,16 @@ builtin_tests = [
|
||||
("CVM FRACTIO\nORDINA([III, S, II])", Program([ModuleCall("FRACTIO")], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("III"), Fractio("S"), Numeral("II")])]))]), ValList([ValFrac(Fraction(1, 2)), ValInt(2), ValInt(3)])),
|
||||
# ORDINA: array passed via variable
|
||||
("DESIGNA x VT [III, I, II]\nORDINA(x)", Program([], [Designa(ID("x"), DataArray([Numeral("III"), Numeral("I"), Numeral("II")])), ExpressionStatement(BuiltIn("ORDINA", [ID("x")]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])),
|
||||
# ORDINA: descending sort with named comparator
|
||||
("DEFINI gt (a, b) VT { REDI (a PLVS b) }\nORDINA([II, V, I, III], gt)", Program([], [Defini(ID("gt"), [ID("a"), ID("b")], [Redi([BinOp(ID("a"), ID("b"), "KEYWORD_PLVS")])]), ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("II"), Numeral("V"), Numeral("I"), Numeral("III")]), ID("gt")]))]), ValList([ValInt(5), ValInt(3), ValInt(2), ValInt(1)])),
|
||||
# ORDINA: ascending sort with inline FVNCTIO comparator
|
||||
("ORDINA([V, I, III], FVNCTIO (a, b) VT { REDI (a MINVS b) })", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("V"), Numeral("I"), Numeral("III")]), Fvnctio([ID("a"), ID("b")], [Redi([BinOp(ID("a"), ID("b"), "KEYWORD_MINVS")])])]))]), ValList([ValInt(1), ValInt(3), ValInt(5)])),
|
||||
# ORDINA: empty list with comparator
|
||||
("ORDINA([], FVNCTIO (a, b) VT { REDI (VERITAS) })", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([]), Fvnctio([ID("a"), ID("b")], [Redi([Bool(True)])])]))]), ValList([])),
|
||||
# ORDINA: single element with comparator
|
||||
("ORDINA([VII], FVNCTIO (a, b) VT { REDI (VERITAS) })", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([Numeral("VII")]), Fvnctio([ID("a"), ID("b")], [Redi([Bool(True)])])]))]), ValList([ValInt(7)])),
|
||||
# ORDINA: comparator sorting two-element subarrays by first element
|
||||
("ORDINA([[II, I], [I, II]], FVNCTIO (a, b) VT { REDI (a[I] PLVS b[I]) })", Program([], [ExpressionStatement(BuiltIn("ORDINA", [DataArray([DataArray([Numeral("II"), Numeral("I")]), DataArray([Numeral("I"), Numeral("II")])]), Fvnctio([ID("a"), ID("b")], [Redi([BinOp(ArrayIndex(ID("a"), Numeral("I")), ArrayIndex(ID("b"), Numeral("I")), "KEYWORD_PLVS")])])]))]), ValList([ValList([ValInt(2), ValInt(1)]), ValList([ValInt(1), ValInt(2)])])),
|
||||
# ADDE: append to non-empty
|
||||
("ADDE([I, II], III)", Program([], [ExpressionStatement(BuiltIn("ADDE", [DataArray([Numeral("I"), Numeral("II")]), Numeral("III")]))]), ValList([ValInt(1), ValInt(2), ValInt(3)])),
|
||||
# ADDE: append to empty
|
||||
|
||||
@@ -71,6 +71,10 @@ error_tests = [
|
||||
("ORDINA(I)", CentvrionError), # ORDINA on non-array
|
||||
('ORDINA([I, "a"])', CentvrionError), # ORDINA mixed types
|
||||
("DESIGNA x VT I\nORDINA(x)", CentvrionError), # ORDINA on id (non-array)
|
||||
("ORDINA([I, II], V)", CentvrionError), # ORDINA comparator not a function
|
||||
("DEFINI bad (a) VT { REDI (VERITAS) }\nORDINA([I, II], bad)", CentvrionError), # ORDINA comparator wrong arity
|
||||
("DEFINI bad (a, b) VT { REDI (V) }\nORDINA([I, II], bad)", CentvrionError), # ORDINA comparator returns non-bool
|
||||
("ORDINA([I], V, V)", CentvrionError), # ORDINA too many args
|
||||
("SENATVS(I)", CentvrionError), # SENATVS requires booleans
|
||||
("SENATVS(VERITAS, I)", CentvrionError), # SENATVS mixed types
|
||||
("SENATVS([I, II, III])", CentvrionError), # SENATVS array of non-bools
|
||||
|
||||
Reference in New Issue
Block a user