@rhost/testkit 1.5.1 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +4 -1
- package/dist/cli/init.js.map +1 -1
- package/node_modules/@ursamu/mushcode/.github/workflows/publish.yml +36 -0
- package/node_modules/@ursamu/mushcode/LICENSE +21 -0
- package/node_modules/@ursamu/mushcode/README.md +110 -0
- package/node_modules/@ursamu/mushcode/_dist/mod.d.ts +36 -0
- package/node_modules/@ursamu/mushcode/_dist/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/parser/mod.d.ts +41 -0
- package/node_modules/@ursamu/mushcode/_dist/parser/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/commands.d.ts +15 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/commands.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/deps.d.ts +18 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/deps.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/mod.d.ts +20 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/tags.d.ts +6 -0
- package/node_modules/@ursamu/mushcode/_dist/src/analyze/tags.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/context.d.ts +85 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/context.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/engine.d.ts +48 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/engine.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/mod.d.ts +26 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/stdlib/mod.d.ts +3 -0
- package/node_modules/@ursamu/mushcode/_dist/src/eval/stdlib/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/lint/mod.d.ts +38 -0
- package/node_modules/@ursamu/mushcode/_dist/src/lint/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/print/mod.d.ts +18 -0
- package/node_modules/@ursamu/mushcode/_dist/src/print/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/print/printer.d.ts +15 -0
- package/node_modules/@ursamu/mushcode/_dist/src/print/printer.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/mod.d.ts +19 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/mod.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/transform.d.ts +27 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/transform.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/walk.d.ts +27 -0
- package/node_modules/@ursamu/mushcode/_dist/src/traverse/walk.d.ts.map +1 -0
- package/node_modules/@ursamu/mushcode/deno.json +26 -0
- package/node_modules/@ursamu/mushcode/deno.lock +42 -0
- package/node_modules/@ursamu/mushcode/docs/analyze.md +145 -0
- package/node_modules/@ursamu/mushcode/docs/eval.md +312 -0
- package/node_modules/@ursamu/mushcode/docs/lint.md +152 -0
- package/node_modules/@ursamu/mushcode/docs/parser.md +196 -0
- package/node_modules/@ursamu/mushcode/docs/print.md +84 -0
- package/node_modules/@ursamu/mushcode/docs/stdlib.md +418 -0
- package/node_modules/@ursamu/mushcode/docs/traverse.md +167 -0
- package/node_modules/@ursamu/mushcode/grammar/mux-softcode.pegjs +781 -0
- package/node_modules/@ursamu/mushcode/mod.js +44 -0
- package/node_modules/@ursamu/mushcode/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/mod.ts +63 -0
- package/node_modules/@ursamu/mushcode/package.json +38 -0
- package/node_modules/@ursamu/mushcode/parser/mod.js +47 -0
- package/node_modules/@ursamu/mushcode/parser/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/parser/mod.ts +99 -0
- package/node_modules/@ursamu/mushcode/parser/mux-softcode.js +3833 -0
- package/node_modules/@ursamu/mushcode/parser/mux-softcode.mjs +3837 -0
- package/node_modules/@ursamu/mushcode/src/analyze/commands.js +29 -0
- package/node_modules/@ursamu/mushcode/src/analyze/commands.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/analyze/commands.ts +46 -0
- package/node_modules/@ursamu/mushcode/src/analyze/deps.js +45 -0
- package/node_modules/@ursamu/mushcode/src/analyze/deps.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/analyze/deps.ts +51 -0
- package/node_modules/@ursamu/mushcode/src/analyze/mod.js +18 -0
- package/node_modules/@ursamu/mushcode/src/analyze/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/analyze/mod.ts +20 -0
- package/node_modules/@ursamu/mushcode/src/analyze/tags.js +11 -0
- package/node_modules/@ursamu/mushcode/src/analyze/tags.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/analyze/tags.ts +11 -0
- package/node_modules/@ursamu/mushcode/src/eval/context.js +22 -0
- package/node_modules/@ursamu/mushcode/src/eval/context.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/context.ts +177 -0
- package/node_modules/@ursamu/mushcode/src/eval/engine.js +238 -0
- package/node_modules/@ursamu/mushcode/src/eval/engine.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/engine.ts +276 -0
- package/node_modules/@ursamu/mushcode/src/eval/mod.js +25 -0
- package/node_modules/@ursamu/mushcode/src/eval/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/mod.ts +31 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.js +56 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.ts +16 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.js +91 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.ts +104 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.js +91 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.ts +98 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.js +79 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.ts +84 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.js +120 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.ts +115 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.js +17 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.ts +19 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.js +28 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.ts +31 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.js +153 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.ts +154 -0
- package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.js +212 -0
- package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.ts +68 -0
- package/node_modules/@ursamu/mushcode/src/lint/mod.js +60 -0
- package/node_modules/@ursamu/mushcode/src/lint/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/mod.ts +96 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.js +37 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.ts +44 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.js +55 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.ts +60 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.js +31 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.ts +40 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.js +59 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.ts +64 -0
- package/node_modules/@ursamu/mushcode/src/print/lock_printer.js +43 -0
- package/node_modules/@ursamu/mushcode/src/print/lock_printer.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/print/lock_printer.ts +41 -0
- package/node_modules/@ursamu/mushcode/src/print/mod.js +17 -0
- package/node_modules/@ursamu/mushcode/src/print/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/print/mod.ts +18 -0
- package/node_modules/@ursamu/mushcode/src/print/printer.js +91 -0
- package/node_modules/@ursamu/mushcode/src/print/printer.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/print/printer.ts +132 -0
- package/node_modules/@ursamu/mushcode/src/traverse/child_slots.js +129 -0
- package/node_modules/@ursamu/mushcode/src/traverse/child_slots.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/traverse/child_slots.ts +51 -0
- package/node_modules/@ursamu/mushcode/src/traverse/mod.js +17 -0
- package/node_modules/@ursamu/mushcode/src/traverse/mod.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/traverse/mod.ts +19 -0
- package/node_modules/@ursamu/mushcode/src/traverse/transform.js +70 -0
- package/node_modules/@ursamu/mushcode/src/traverse/transform.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/traverse/transform.ts +84 -0
- package/node_modules/@ursamu/mushcode/src/traverse/walk.js +55 -0
- package/node_modules/@ursamu/mushcode/src/traverse/walk.js.map +1 -0
- package/node_modules/@ursamu/mushcode/src/traverse/walk.ts +82 -0
- package/node_modules/@ursamu/mushcode/tests/01-literals.test.ts +105 -0
- package/node_modules/@ursamu/mushcode/tests/02-substitutions.test.ts +145 -0
- package/node_modules/@ursamu/mushcode/tests/03-function-calls.test.ts +184 -0
- package/node_modules/@ursamu/mushcode/tests/04-eval-blocks.test.ts +110 -0
- package/node_modules/@ursamu/mushcode/tests/05-braced-strings.test.ts +119 -0
- package/node_modules/@ursamu/mushcode/tests/06-commands.test.ts +222 -0
- package/node_modules/@ursamu/mushcode/tests/07-dollar-patterns.test.ts +156 -0
- package/node_modules/@ursamu/mushcode/tests/08-lock-expressions.test.ts +159 -0
- package/node_modules/@ursamu/mushcode/tests/09-edge-cases.test.ts +162 -0
- package/node_modules/@ursamu/mushcode/tests/10-regression.test.ts +211 -0
- package/node_modules/@ursamu/mushcode/tests/11-tags.test.ts +357 -0
- package/node_modules/@ursamu/mushcode/tests/12-locations.test.ts +162 -0
- package/node_modules/@ursamu/mushcode/tests/13-eval.test.ts +389 -0
- package/node_modules/@ursamu/mushcode/tests/analyze.test.ts +194 -0
- package/node_modules/@ursamu/mushcode/tests/helpers.ts +69 -0
- package/node_modules/@ursamu/mushcode/tests/lint.test.ts +232 -0
- package/node_modules/@ursamu/mushcode/tests/print.test.ts +204 -0
- package/node_modules/@ursamu/mushcode/tests/traverse.test.ts +211 -0
- package/package.json +10 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { parse } from "../../parser/mod.js";
|
|
2
|
+
// ── EvalEngine ────────────────────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* AST-based softcode evaluator.
|
|
5
|
+
*
|
|
6
|
+
* Register functions and commands, then call `evalString()` to evaluate a
|
|
7
|
+
* raw softcode string or `eval()` to evaluate an already-parsed node.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const engine = new EvalEngine(myAccessor);
|
|
11
|
+
* registerStdlib(engine);
|
|
12
|
+
* engine.registerFunction("u", { eval: "lazy", minArgs: 1, maxArgs: Infinity, exec: uImpl });
|
|
13
|
+
* const result = await engine.evalString("[add(1,2)]", ctx);
|
|
14
|
+
* // → "3"
|
|
15
|
+
*/ export class EvalEngine {
|
|
16
|
+
accessor;
|
|
17
|
+
functions;
|
|
18
|
+
commands;
|
|
19
|
+
subHandlers;
|
|
20
|
+
fallbackCommand;
|
|
21
|
+
constructor(/** The host-provided database accessor (passed to DB stdlib functions). */ accessor){
|
|
22
|
+
this.accessor = accessor;
|
|
23
|
+
this.functions = new Map();
|
|
24
|
+
this.commands = new Map();
|
|
25
|
+
this.subHandlers = [];
|
|
26
|
+
}
|
|
27
|
+
/** Register a softcode function by name (case-insensitive). Returns `this` for chaining. */ registerFunction(name, impl) {
|
|
28
|
+
this.functions.set(name.toLowerCase(), impl);
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
/** Register a `@command` handler by name (case-insensitive). Returns `this` for chaining. */ registerCommand(name, impl) {
|
|
32
|
+
this.commands.set(name.toLowerCase(), impl);
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Register a custom `%<code>` substitution handler.
|
|
37
|
+
*
|
|
38
|
+
* Custom handlers are checked **before** built-in substitutions, so you can
|
|
39
|
+
* override any built-in code. `match` is either an exact code string (e.g.
|
|
40
|
+
* `"s"` for `%s`) or a predicate (e.g. `code => code.startsWith("V")` for
|
|
41
|
+
* `%Va`–`%Vz`).
|
|
42
|
+
*
|
|
43
|
+
* @returns `this` for chaining.
|
|
44
|
+
*/ registerSub(match, fn) {
|
|
45
|
+
this.subHandlers.push({
|
|
46
|
+
match,
|
|
47
|
+
fn
|
|
48
|
+
});
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register a fallback handler for `@commands` that have no specific handler registered.
|
|
53
|
+
*
|
|
54
|
+
* @returns `this` for chaining.
|
|
55
|
+
*/ registerCommandFallback(fn) {
|
|
56
|
+
this.fallbackCommand = fn;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
/** Parse and evaluate a raw softcode string. */ async evalString(source, ctx) {
|
|
60
|
+
const ast = parse(source, "Start");
|
|
61
|
+
return await this.eval(ast, ctx);
|
|
62
|
+
}
|
|
63
|
+
// ── Core evaluator ──────────────────────────────────────────────────────────
|
|
64
|
+
/** Evaluate a node to a string. */ async eval(node, ctx) {
|
|
65
|
+
if (ctx.signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
66
|
+
if (ctx.depth > ctx.maxDepth) return "#-1 EVALUATION DEPTH EXCEEDED";
|
|
67
|
+
// deno-lint-ignore no-explicit-any
|
|
68
|
+
const n = node;
|
|
69
|
+
switch(node.type){
|
|
70
|
+
// ── Leaves ──────────────────────────────────────────────────────────────
|
|
71
|
+
case "Literal":
|
|
72
|
+
return n.value;
|
|
73
|
+
case "Escape":
|
|
74
|
+
return n.char;
|
|
75
|
+
case "TagRef":
|
|
76
|
+
return "#" + n.name;
|
|
77
|
+
case "Substitution":
|
|
78
|
+
return await this.evalSub(n.code, ctx);
|
|
79
|
+
case "SpecialVar":
|
|
80
|
+
return this.evalSpecialVar(n.code, ctx);
|
|
81
|
+
// ── Containers that just concatenate their parts ─────────────────────
|
|
82
|
+
case "EvalBlock":
|
|
83
|
+
case "Arg":
|
|
84
|
+
case "Text":
|
|
85
|
+
case "Pattern":
|
|
86
|
+
case "BracedString":
|
|
87
|
+
case "UserCommand":
|
|
88
|
+
return this.evalParts(n.parts, ctx);
|
|
89
|
+
// ── Function call ────────────────────────────────────────────────────
|
|
90
|
+
case "FunctionCall":
|
|
91
|
+
return this.evalFunction(node, ctx);
|
|
92
|
+
// ── Command nodes — side effects only, return empty string ──────────
|
|
93
|
+
case "CommandList":
|
|
94
|
+
case "AtCommand":
|
|
95
|
+
case "AttributeSet":
|
|
96
|
+
await this.exec(node, ctx);
|
|
97
|
+
return "";
|
|
98
|
+
// ── Patterns — produce their printed form; registration is host work ─
|
|
99
|
+
case "DollarPattern":
|
|
100
|
+
case "ListenPattern":
|
|
101
|
+
return "";
|
|
102
|
+
default:
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Execute a node for its side effects (commands). */ async exec(node, ctx) {
|
|
107
|
+
// deno-lint-ignore no-explicit-any
|
|
108
|
+
const n = node;
|
|
109
|
+
switch(node.type){
|
|
110
|
+
case "CommandList":
|
|
111
|
+
for (const cmd of n.commands){
|
|
112
|
+
await this.exec(cmd, ctx);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
case "AtCommand":
|
|
116
|
+
{
|
|
117
|
+
const name = n.name.toLowerCase();
|
|
118
|
+
const impl = this.commands.get(name);
|
|
119
|
+
const obj = n.object ? await this.eval(n.object, ctx) : null;
|
|
120
|
+
const val = n.value ? await this.eval(n.value, ctx) : null;
|
|
121
|
+
if (impl) {
|
|
122
|
+
await impl.exec(n.switches, obj, val, ctx, this);
|
|
123
|
+
} else if (this.fallbackCommand) {
|
|
124
|
+
await this.fallbackCommand(n.name, n.switches, obj, val, ctx, this);
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "AttributeSet":
|
|
129
|
+
{
|
|
130
|
+
const impl = this.commands.get("&");
|
|
131
|
+
if (!impl) break;
|
|
132
|
+
const obj = await this.eval(n.object, ctx);
|
|
133
|
+
const val = n.value ? await this.eval(n.value, ctx) : null;
|
|
134
|
+
await impl.exec([], obj, val != null ? `${n.attribute}=${val}` : n.attribute, ctx, this);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
default:
|
|
138
|
+
// Non-command in exec position — evaluate for side effects (e.g. a UserCommand
|
|
139
|
+
// that contains embedded [pemit(...)] blocks).
|
|
140
|
+
await this.eval(node, ctx);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
144
|
+
async evalParts(parts, ctx) {
|
|
145
|
+
const chunks = [];
|
|
146
|
+
let total = 0;
|
|
147
|
+
for (const p of parts){
|
|
148
|
+
const chunk = await this.eval(p, ctx);
|
|
149
|
+
total += chunk.length;
|
|
150
|
+
if (total > ctx.maxOutputLen) return "#-1 OUTPUT LIMIT EXCEEDED";
|
|
151
|
+
chunks.push(chunk);
|
|
152
|
+
}
|
|
153
|
+
return chunks.join("");
|
|
154
|
+
}
|
|
155
|
+
async evalFunction(node, ctx) {
|
|
156
|
+
// deno-lint-ignore no-explicit-any
|
|
157
|
+
const n = node;
|
|
158
|
+
const name = n.name.toLowerCase();
|
|
159
|
+
const impl = this.functions.get(name);
|
|
160
|
+
if (!impl) return `#-1 FUNCTION (${n.name}) NOT FOUND`;
|
|
161
|
+
const args = n.args;
|
|
162
|
+
if (args.length < impl.minArgs) return `#-1 FUNCTION (${n.name}) REQUIRES AT LEAST ${impl.minArgs} ARGUMENT(S)`;
|
|
163
|
+
if (args.length > impl.maxArgs) return `#-1 FUNCTION (${n.name}) TAKES AT MOST ${impl.maxArgs} ARGUMENT(S)`;
|
|
164
|
+
try {
|
|
165
|
+
if (impl.eval === "lazy") {
|
|
166
|
+
const thunks = args.map((arg)=>(override)=>this.eval(arg, override ? {
|
|
167
|
+
...ctx,
|
|
168
|
+
...override
|
|
169
|
+
} : ctx));
|
|
170
|
+
return await impl.exec(thunks, ctx, this) ?? "";
|
|
171
|
+
}
|
|
172
|
+
const vals = [];
|
|
173
|
+
for (const arg of args)vals.push(await this.eval(arg, ctx));
|
|
174
|
+
return await impl.exec(vals, ctx, this) ?? "";
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return `#-1 ${e instanceof Error ? e.message.toUpperCase() : "ERROR"}`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async evalSub(code, ctx) {
|
|
180
|
+
// Custom substitution handlers — checked before built-ins
|
|
181
|
+
for (const { match, fn } of this.subHandlers){
|
|
182
|
+
if (typeof match === "string" ? match === code : match(code)) {
|
|
183
|
+
return fn(code, ctx);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Positional arguments
|
|
187
|
+
if (/^[0-9]$/.test(code)) return ctx.args[parseInt(code)] ?? "";
|
|
188
|
+
// Registers
|
|
189
|
+
if (code.startsWith("q")) return ctx.registers.get(code.slice(1)) ?? "";
|
|
190
|
+
// Context objects
|
|
191
|
+
if (code === "#") return ctx.enactor;
|
|
192
|
+
if (code === ":") return ctx.enactor; // objid — same as enactor in flat-UUID systems
|
|
193
|
+
if (code === "!") return ctx.executor;
|
|
194
|
+
if (code === "@") return ctx.caller ?? "";
|
|
195
|
+
if (code === "+") return String(ctx.args.length);
|
|
196
|
+
// Formatting characters
|
|
197
|
+
if (code === "r" || code === "R") return "\r\n";
|
|
198
|
+
if (code === "t" || code === "T") return "\t";
|
|
199
|
+
if (code === "b" || code === "B") return " ";
|
|
200
|
+
if (code === "%") return "%";
|
|
201
|
+
if (code === "[") return "[";
|
|
202
|
+
if (code === "]") return "]";
|
|
203
|
+
if (code === ",") return ",";
|
|
204
|
+
if (code === ";") return ";";
|
|
205
|
+
if (code === "\\") return "\\";
|
|
206
|
+
// Name — needs DB
|
|
207
|
+
if (code === "N") return this.accessor.getName(ctx.enactor);
|
|
208
|
+
if (code === "n") return (await this.accessor.getName(ctx.enactor)).toLowerCase();
|
|
209
|
+
// Location — needs DB
|
|
210
|
+
if (code === "L") return await this.resolveLocation(ctx);
|
|
211
|
+
// Iter variables via %i0–%i9
|
|
212
|
+
if (/^i[0-9]$/.test(code)) {
|
|
213
|
+
const depth = parseInt(code[1]);
|
|
214
|
+
return ctx.iterStack[depth]?.item ?? "";
|
|
215
|
+
}
|
|
216
|
+
// Attribute shorthand %=ATTRNAME — read from enactor
|
|
217
|
+
if (code.startsWith("=")) return await this.accessor.getAttr(ctx.enactor, code.slice(1)) ?? "";
|
|
218
|
+
// ANSI codes — pass through as-is for the host to render
|
|
219
|
+
if (/^[xXcC]/.test(code)) return `%${code}`;
|
|
220
|
+
return "";
|
|
221
|
+
}
|
|
222
|
+
evalSpecialVar(code, ctx) {
|
|
223
|
+
const frame = ctx.iterStack[0];
|
|
224
|
+
if (code === "##") return frame?.item ?? "";
|
|
225
|
+
if (code === "#@") return frame ? String(frame.index) : "";
|
|
226
|
+
if (code === "#$") return ""; // last name-lookup dbref — host concern
|
|
227
|
+
return "";
|
|
228
|
+
}
|
|
229
|
+
async resolveLocation(ctx) {
|
|
230
|
+
try {
|
|
231
|
+
const target = await this.accessor.resolveTarget(ctx.enactor, "loc(me)");
|
|
232
|
+
return target ?? "";
|
|
233
|
+
} catch {
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sources":["./engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import type { ASTNode } from "../../parser/mod.js";
|
|
2
|
+
import { parse } from "../../parser/mod.js";
|
|
3
|
+
import type {
|
|
4
|
+
EvalContext, EvalThunk,
|
|
5
|
+
FunctionImpl, CommandImpl,
|
|
6
|
+
ObjectAccessor, IEvalEngine,
|
|
7
|
+
IterFrame, SubHandlerFn, CommandFallbackFn,
|
|
8
|
+
} from "./context.ts";
|
|
9
|
+
|
|
10
|
+
// ── EvalEngine ────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AST-based softcode evaluator.
|
|
14
|
+
*
|
|
15
|
+
* Register functions and commands, then call `evalString()` to evaluate a
|
|
16
|
+
* raw softcode string or `eval()` to evaluate an already-parsed node.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const engine = new EvalEngine(myAccessor);
|
|
20
|
+
* registerStdlib(engine);
|
|
21
|
+
* engine.registerFunction("u", { eval: "lazy", minArgs: 1, maxArgs: Infinity, exec: uImpl });
|
|
22
|
+
* const result = await engine.evalString("[add(1,2)]", ctx);
|
|
23
|
+
* // → "3"
|
|
24
|
+
*/
|
|
25
|
+
export class EvalEngine implements IEvalEngine {
|
|
26
|
+
private readonly functions = new Map<string, FunctionImpl>();
|
|
27
|
+
private readonly commands = new Map<string, CommandImpl>();
|
|
28
|
+
private readonly subHandlers: Array<{ match: string | ((code: string) => boolean); fn: SubHandlerFn }> = [];
|
|
29
|
+
private fallbackCommand?: CommandFallbackFn;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
/** The host-provided database accessor (passed to DB stdlib functions). */
|
|
33
|
+
readonly accessor: ObjectAccessor,
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
/** Register a softcode function by name (case-insensitive). Returns `this` for chaining. */
|
|
37
|
+
registerFunction(name: string, impl: FunctionImpl): this {
|
|
38
|
+
this.functions.set(name.toLowerCase(), impl);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Register a `@command` handler by name (case-insensitive). Returns `this` for chaining. */
|
|
43
|
+
registerCommand(name: string, impl: CommandImpl): this {
|
|
44
|
+
this.commands.set(name.toLowerCase(), impl);
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Register a custom `%<code>` substitution handler.
|
|
50
|
+
*
|
|
51
|
+
* Custom handlers are checked **before** built-in substitutions, so you can
|
|
52
|
+
* override any built-in code. `match` is either an exact code string (e.g.
|
|
53
|
+
* `"s"` for `%s`) or a predicate (e.g. `code => code.startsWith("V")` for
|
|
54
|
+
* `%Va`–`%Vz`).
|
|
55
|
+
*
|
|
56
|
+
* @returns `this` for chaining.
|
|
57
|
+
*/
|
|
58
|
+
registerSub(
|
|
59
|
+
match: string | ((code: string) => boolean),
|
|
60
|
+
fn: SubHandlerFn,
|
|
61
|
+
): this {
|
|
62
|
+
this.subHandlers.push({ match, fn });
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register a fallback handler for `@commands` that have no specific handler registered.
|
|
68
|
+
*
|
|
69
|
+
* @returns `this` for chaining.
|
|
70
|
+
*/
|
|
71
|
+
registerCommandFallback(fn: CommandFallbackFn): this {
|
|
72
|
+
this.fallbackCommand = fn;
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Parse and evaluate a raw softcode string. */
|
|
77
|
+
async evalString(source: string, ctx: EvalContext): Promise<string> {
|
|
78
|
+
const ast = parse(source, "Start");
|
|
79
|
+
return await this.eval(ast, ctx);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Core evaluator ──────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/** Evaluate a node to a string. */
|
|
85
|
+
async eval(node: ASTNode, ctx: EvalContext): Promise<string> {
|
|
86
|
+
if (ctx.signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
87
|
+
if (ctx.depth > ctx.maxDepth) return "#-1 EVALUATION DEPTH EXCEEDED";
|
|
88
|
+
|
|
89
|
+
// deno-lint-ignore no-explicit-any
|
|
90
|
+
const n = node as any;
|
|
91
|
+
|
|
92
|
+
switch (node.type) {
|
|
93
|
+
// ── Leaves ──────────────────────────────────────────────────────────────
|
|
94
|
+
case "Literal": return n.value as string;
|
|
95
|
+
case "Escape": return n.char as string;
|
|
96
|
+
case "TagRef": return "#" + (n.name as string);
|
|
97
|
+
|
|
98
|
+
case "Substitution": return await this.evalSub(n.code as string, ctx);
|
|
99
|
+
case "SpecialVar": return this.evalSpecialVar(n.code as string, ctx);
|
|
100
|
+
|
|
101
|
+
// ── Containers that just concatenate their parts ─────────────────────
|
|
102
|
+
case "EvalBlock":
|
|
103
|
+
case "Arg":
|
|
104
|
+
case "Text":
|
|
105
|
+
case "Pattern":
|
|
106
|
+
case "BracedString":
|
|
107
|
+
case "UserCommand":
|
|
108
|
+
return this.evalParts(n.parts as ASTNode[], ctx);
|
|
109
|
+
|
|
110
|
+
// ── Function call ────────────────────────────────────────────────────
|
|
111
|
+
case "FunctionCall":
|
|
112
|
+
return this.evalFunction(node, ctx);
|
|
113
|
+
|
|
114
|
+
// ── Command nodes — side effects only, return empty string ──────────
|
|
115
|
+
case "CommandList":
|
|
116
|
+
case "AtCommand":
|
|
117
|
+
case "AttributeSet":
|
|
118
|
+
await this.exec(node, ctx);
|
|
119
|
+
return "";
|
|
120
|
+
|
|
121
|
+
// ── Patterns — produce their printed form; registration is host work ─
|
|
122
|
+
case "DollarPattern":
|
|
123
|
+
case "ListenPattern":
|
|
124
|
+
return "";
|
|
125
|
+
|
|
126
|
+
default:
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Execute a node for its side effects (commands). */
|
|
132
|
+
async exec(node: ASTNode, ctx: EvalContext): Promise<void> {
|
|
133
|
+
// deno-lint-ignore no-explicit-any
|
|
134
|
+
const n = node as any;
|
|
135
|
+
|
|
136
|
+
switch (node.type) {
|
|
137
|
+
case "CommandList":
|
|
138
|
+
for (const cmd of n.commands as ASTNode[]) {
|
|
139
|
+
await this.exec(cmd, ctx);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case "AtCommand": {
|
|
144
|
+
const name = (n.name as string).toLowerCase();
|
|
145
|
+
const impl = this.commands.get(name);
|
|
146
|
+
const obj = n.object ? await this.eval(n.object as ASTNode, ctx) : null;
|
|
147
|
+
const val = n.value ? await this.eval(n.value as ASTNode, ctx) : null;
|
|
148
|
+
if (impl) {
|
|
149
|
+
await impl.exec(n.switches as string[], obj, val, ctx, this);
|
|
150
|
+
} else if (this.fallbackCommand) {
|
|
151
|
+
await this.fallbackCommand(n.name as string, n.switches as string[], obj, val, ctx, this);
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case "AttributeSet": {
|
|
157
|
+
const impl = this.commands.get("&");
|
|
158
|
+
if (!impl) break;
|
|
159
|
+
const obj = await this.eval(n.object as ASTNode, ctx);
|
|
160
|
+
const val = n.value ? await this.eval(n.value as ASTNode, ctx) : null;
|
|
161
|
+
await impl.exec([], obj, val != null ? `${n.attribute}=${val}` : n.attribute, ctx, this);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
default:
|
|
166
|
+
// Non-command in exec position — evaluate for side effects (e.g. a UserCommand
|
|
167
|
+
// that contains embedded [pemit(...)] blocks).
|
|
168
|
+
await this.eval(node, ctx);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
private async evalParts(parts: ASTNode[], ctx: EvalContext): Promise<string> {
|
|
175
|
+
const chunks: string[] = [];
|
|
176
|
+
let total = 0;
|
|
177
|
+
for (const p of parts) {
|
|
178
|
+
const chunk = await this.eval(p, ctx);
|
|
179
|
+
total += chunk.length;
|
|
180
|
+
if (total > ctx.maxOutputLen) return "#-1 OUTPUT LIMIT EXCEEDED";
|
|
181
|
+
chunks.push(chunk);
|
|
182
|
+
}
|
|
183
|
+
return chunks.join("");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async evalFunction(node: ASTNode, ctx: EvalContext): Promise<string> {
|
|
187
|
+
// deno-lint-ignore no-explicit-any
|
|
188
|
+
const n = node as any;
|
|
189
|
+
const name = (n.name as string).toLowerCase();
|
|
190
|
+
const impl = this.functions.get(name);
|
|
191
|
+
|
|
192
|
+
if (!impl) return `#-1 FUNCTION (${n.name}) NOT FOUND`;
|
|
193
|
+
|
|
194
|
+
const args = n.args as ASTNode[];
|
|
195
|
+
if (args.length < impl.minArgs)
|
|
196
|
+
return `#-1 FUNCTION (${n.name}) REQUIRES AT LEAST ${impl.minArgs} ARGUMENT(S)`;
|
|
197
|
+
if (args.length > impl.maxArgs)
|
|
198
|
+
return `#-1 FUNCTION (${n.name}) TAKES AT MOST ${impl.maxArgs} ARGUMENT(S)`;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
if (impl.eval === "lazy") {
|
|
202
|
+
const thunks: EvalThunk[] = args.map(arg =>
|
|
203
|
+
(override?: Partial<EvalContext>) =>
|
|
204
|
+
this.eval(arg, override ? { ...ctx, ...override } : ctx)
|
|
205
|
+
);
|
|
206
|
+
return (await impl.exec(thunks, ctx, this)) ?? "";
|
|
207
|
+
}
|
|
208
|
+
const vals: string[] = [];
|
|
209
|
+
for (const arg of args) vals.push(await this.eval(arg, ctx));
|
|
210
|
+
return (await impl.exec(vals, ctx, this)) ?? "";
|
|
211
|
+
} catch (e: unknown) {
|
|
212
|
+
return `#-1 ${e instanceof Error ? e.message.toUpperCase() : "ERROR"}`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async evalSub(code: string, ctx: EvalContext): Promise<string> {
|
|
217
|
+
// Custom substitution handlers — checked before built-ins
|
|
218
|
+
for (const { match, fn } of this.subHandlers) {
|
|
219
|
+
if (typeof match === "string" ? match === code : match(code)) {
|
|
220
|
+
return fn(code, ctx);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Positional arguments
|
|
224
|
+
if (/^[0-9]$/.test(code)) return ctx.args[parseInt(code)] ?? "";
|
|
225
|
+
// Registers
|
|
226
|
+
if (code.startsWith("q")) return ctx.registers.get(code.slice(1)) ?? "";
|
|
227
|
+
// Context objects
|
|
228
|
+
if (code === "#") return ctx.enactor;
|
|
229
|
+
if (code === ":") return ctx.enactor; // objid — same as enactor in flat-UUID systems
|
|
230
|
+
if (code === "!") return ctx.executor;
|
|
231
|
+
if (code === "@") return ctx.caller ?? "";
|
|
232
|
+
if (code === "+") return String(ctx.args.length);
|
|
233
|
+
// Formatting characters
|
|
234
|
+
if (code === "r" || code === "R") return "\r\n";
|
|
235
|
+
if (code === "t" || code === "T") return "\t";
|
|
236
|
+
if (code === "b" || code === "B") return " ";
|
|
237
|
+
if (code === "%") return "%";
|
|
238
|
+
if (code === "[") return "[";
|
|
239
|
+
if (code === "]") return "]";
|
|
240
|
+
if (code === ",") return ",";
|
|
241
|
+
if (code === ";") return ";";
|
|
242
|
+
if (code === "\\") return "\\";
|
|
243
|
+
// Name — needs DB
|
|
244
|
+
if (code === "N") return this.accessor.getName(ctx.enactor);
|
|
245
|
+
if (code === "n") return (await this.accessor.getName(ctx.enactor)).toLowerCase();
|
|
246
|
+
// Location — needs DB
|
|
247
|
+
if (code === "L") return await this.resolveLocation(ctx);
|
|
248
|
+
// Iter variables via %i0–%i9
|
|
249
|
+
if (/^i[0-9]$/.test(code)) {
|
|
250
|
+
const depth = parseInt(code[1]);
|
|
251
|
+
return ctx.iterStack[depth]?.item ?? "";
|
|
252
|
+
}
|
|
253
|
+
// Attribute shorthand %=ATTRNAME — read from enactor
|
|
254
|
+
if (code.startsWith("=")) return (await this.accessor.getAttr(ctx.enactor, code.slice(1))) ?? "";
|
|
255
|
+
// ANSI codes — pass through as-is for the host to render
|
|
256
|
+
if (/^[xXcC]/.test(code)) return `%${code}`;
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private evalSpecialVar(code: string, ctx: EvalContext): string {
|
|
261
|
+
const frame: IterFrame | undefined = ctx.iterStack[0];
|
|
262
|
+
if (code === "##") return frame?.item ?? "";
|
|
263
|
+
if (code === "#@") return frame ? String(frame.index) : "";
|
|
264
|
+
if (code === "#$") return ""; // last name-lookup dbref — host concern
|
|
265
|
+
return "";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private async resolveLocation(ctx: EvalContext): Promise<string> {
|
|
269
|
+
try {
|
|
270
|
+
const target = await this.accessor.resolveTarget(ctx.enactor, "loc(me)");
|
|
271
|
+
return target ?? "";
|
|
272
|
+
} catch {
|
|
273
|
+
return "";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* AST-based softcode evaluator: `EvalEngine`, `makeContext()`, `registerStdlib()`,
|
|
4
|
+
* and the full type vocabulary for contexts, accessors, and function registrations.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { EvalEngine, makeContext, registerStdlib } from "@ursamu/mushcode/eval";
|
|
9
|
+
*
|
|
10
|
+
* const engine = new EvalEngine({
|
|
11
|
+
* getAttr: async (_id, _attr) => null,
|
|
12
|
+
* resolveTarget: async (_from, expr) => expr === "me" ? "player-uuid" : null,
|
|
13
|
+
* getName: async (_id) => "Tester",
|
|
14
|
+
* hasFlag: async (_id, _flag) => false,
|
|
15
|
+
* });
|
|
16
|
+
* registerStdlib(engine);
|
|
17
|
+
*
|
|
18
|
+
* const ctx = makeContext({ enactor: "player-uuid", executor: "player-uuid" });
|
|
19
|
+
* const result = await engine.evalString("[add(1,2)] [capstr(hello)]", ctx);
|
|
20
|
+
* console.log(result); // "3 Hello"
|
|
21
|
+
* ```
|
|
22
|
+
*/ export { EvalEngine } from "./engine.js";
|
|
23
|
+
export { makeContext } from "./context.js";
|
|
24
|
+
export { registerStdlib } from "./stdlib/mod.js";
|
|
25
|
+
//# sourceMappingURL=mod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","sources":["./mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,SAAS,UAAU,sBAA+B;AAClD,SAAS,WAAW,uBAA+B;AACnD,SAAS,cAAc,0BAA+B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* AST-based softcode evaluator: `EvalEngine`, `makeContext()`, `registerStdlib()`,
|
|
4
|
+
* and the full type vocabulary for contexts, accessors, and function registrations.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { EvalEngine, makeContext, registerStdlib } from "@ursamu/mushcode/eval";
|
|
9
|
+
*
|
|
10
|
+
* const engine = new EvalEngine({
|
|
11
|
+
* getAttr: async (_id, _attr) => null,
|
|
12
|
+
* resolveTarget: async (_from, expr) => expr === "me" ? "player-uuid" : null,
|
|
13
|
+
* getName: async (_id) => "Tester",
|
|
14
|
+
* hasFlag: async (_id, _flag) => false,
|
|
15
|
+
* });
|
|
16
|
+
* registerStdlib(engine);
|
|
17
|
+
*
|
|
18
|
+
* const ctx = makeContext({ enactor: "player-uuid", executor: "player-uuid" });
|
|
19
|
+
* const result = await engine.evalString("[add(1,2)] [capstr(hello)]", ctx);
|
|
20
|
+
* console.log(result); // "3 Hello"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export { EvalEngine } from "./engine.js";
|
|
24
|
+
export { makeContext } from "./context.js";
|
|
25
|
+
export { registerStdlib } from "./stdlib/mod.js";
|
|
26
|
+
export type {
|
|
27
|
+
EvalContext, EvalThunk,
|
|
28
|
+
ObjectAccessor, IEvalEngine,
|
|
29
|
+
FunctionImpl, CommandImpl,
|
|
30
|
+
IterFrame, SubHandlerFn, CommandFallbackFn,
|
|
31
|
+
} from "./context.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
function toNum(s) {
|
|
2
|
+
const n = Number(s);
|
|
3
|
+
if (!isFinite(n)) throw new Error(`ARGUMENT (${s}) IS NOT A NUMBER`);
|
|
4
|
+
return n;
|
|
5
|
+
}
|
|
6
|
+
export const compareFunctions = {
|
|
7
|
+
eq: {
|
|
8
|
+
minArgs: 2,
|
|
9
|
+
maxArgs: 2,
|
|
10
|
+
exec (args) {
|
|
11
|
+
const [a, b] = args;
|
|
12
|
+
return toNum(a) === toNum(b) ? "1" : "0";
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
neq: {
|
|
16
|
+
minArgs: 2,
|
|
17
|
+
maxArgs: 2,
|
|
18
|
+
exec (args) {
|
|
19
|
+
const [a, b] = args;
|
|
20
|
+
return toNum(a) !== toNum(b) ? "1" : "0";
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
gt: {
|
|
24
|
+
minArgs: 2,
|
|
25
|
+
maxArgs: 2,
|
|
26
|
+
exec (args) {
|
|
27
|
+
const [a, b] = args;
|
|
28
|
+
return toNum(a) > toNum(b) ? "1" : "0";
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
gte: {
|
|
32
|
+
minArgs: 2,
|
|
33
|
+
maxArgs: 2,
|
|
34
|
+
exec (args) {
|
|
35
|
+
const [a, b] = args;
|
|
36
|
+
return toNum(a) >= toNum(b) ? "1" : "0";
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
lt: {
|
|
40
|
+
minArgs: 2,
|
|
41
|
+
maxArgs: 2,
|
|
42
|
+
exec (args) {
|
|
43
|
+
const [a, b] = args;
|
|
44
|
+
return toNum(a) < toNum(b) ? "1" : "0";
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
lte: {
|
|
48
|
+
minArgs: 2,
|
|
49
|
+
maxArgs: 2,
|
|
50
|
+
exec (args) {
|
|
51
|
+
const [a, b] = args;
|
|
52
|
+
return toNum(a) <= toNum(b) ? "1" : "0";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sources":["./compare.ts"],"names":[],"mappings":"AAEA,SAAS,MAAM,CAAS;EACtB,MAAM,IAAI,OAAO;EACjB,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,CAAC,UAAU,EAAE,EAAE,iBAAiB,CAAC;EACnE,OAAO;AACT;AAEA,OAAO,MAAM,mBAAiD;EAC5D,IAAK;IAAE,SAAS;IAAG,SAAS;IAAG,MAAK,IAAI;MAAI,MAAM,CAAC,GAAE,EAAE,GAAG;MAAkB,OAAO,MAAM,OAAO,MAAM,KAAK,MAAM;IAAK;EAAE;EACxH,KAAK;IAAE,SAAS;IAAG,SAAS;IAAG,MAAK,IAAI;MAAI,MAAM,CAAC,GAAE,EAAE,GAAG;MAAkB,OAAO,MAAM,OAAO,MAAM,KAAK,MAAM;IAAK;EAAE;EACxH,IAAK;IAAE,SAAS;IAAG,SAAS;IAAG,MAAK,IAAI;MAAI,MAAM,CAAC,GAAE,EAAE,GAAG;MAAkB,OAAO,MAAM,KAAO,MAAM,KAAK,MAAM;IAAK;EAAE;EACxH,KAAK;IAAE,SAAS;IAAG,SAAS;IAAG,MAAK,IAAI;MAAI,MAAM,CAAC,GAAE,EAAE,GAAG;MAAkB,OAAO,MAAM,MAAO,MAAM,KAAK,MAAM;IAAK;EAAE;EACxH,IAAK;IAAE,SAAS;IAAG,SAAS;IAAG,MAAK,IAAI;MAAI,MAAM,CAAC,GAAE,EAAE,GAAG;MAAkB,OAAO,MAAM,KAAO,MAAM,KAAK,MAAM;IAAK;EAAE;EACxH,KAAK;IAAE,SAAS;IAAG,SAAS;IAAG,MAAK,IAAI;MAAI,MAAM,CAAC,GAAE,EAAE,GAAG;MAAkB,OAAO,MAAM,MAAO,MAAM,KAAK,MAAM;IAAK;EAAE;AAC1H,EAAE"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FunctionImpl } from "../context.ts";
|
|
2
|
+
|
|
3
|
+
function toNum(s: string): number {
|
|
4
|
+
const n = Number(s);
|
|
5
|
+
if (!isFinite(n)) throw new Error(`ARGUMENT (${s}) IS NOT A NUMBER`);
|
|
6
|
+
return n;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const compareFunctions: Record<string, FunctionImpl> = {
|
|
10
|
+
eq: { minArgs: 2, maxArgs: 2, exec(args) { const [a,b] = args as string[]; return toNum(a) === toNum(b) ? "1" : "0"; } },
|
|
11
|
+
neq: { minArgs: 2, maxArgs: 2, exec(args) { const [a,b] = args as string[]; return toNum(a) !== toNum(b) ? "1" : "0"; } },
|
|
12
|
+
gt: { minArgs: 2, maxArgs: 2, exec(args) { const [a,b] = args as string[]; return toNum(a) > toNum(b) ? "1" : "0"; } },
|
|
13
|
+
gte: { minArgs: 2, maxArgs: 2, exec(args) { const [a,b] = args as string[]; return toNum(a) >= toNum(b) ? "1" : "0"; } },
|
|
14
|
+
lt: { minArgs: 2, maxArgs: 2, exec(args) { const [a,b] = args as string[]; return toNum(a) < toNum(b) ? "1" : "0"; } },
|
|
15
|
+
lte: { minArgs: 2, maxArgs: 2, exec(args) { const [a,b] = args as string[]; return toNum(a) <= toNum(b) ? "1" : "0"; } },
|
|
16
|
+
};
|