@rhost/testkit 1.5.1 → 1.5.2
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/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 +4 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ASTNode } from "../../parser/mod.js";
|
|
2
|
+
/**
|
|
3
|
+
* A function called on each node, top-down.
|
|
4
|
+
*
|
|
5
|
+
* Return values:
|
|
6
|
+
* `undefined` — keep the node as-is and recurse into its children
|
|
7
|
+
* `ASTNode` — replace the node with this value, then recurse into it
|
|
8
|
+
* `null` — when in an array slot, remove the node from the array;
|
|
9
|
+
* when in a single slot, set the field to null
|
|
10
|
+
*/ export type Transformer = (node: ASTNode) => ASTNode | null | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Produce a new tree by applying `fn` to every node top-down.
|
|
13
|
+
*
|
|
14
|
+
* The function is called on each node before its children are processed.
|
|
15
|
+
* Returning a replacement node causes that replacement's children to be
|
|
16
|
+
* processed next (not the original children).
|
|
17
|
+
*
|
|
18
|
+
* The original tree is never mutated; a new object is returned whenever
|
|
19
|
+
* any node in the subtree changes.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Replace every TagRef with a Literal placeholder
|
|
23
|
+
* const out = transform(ast, (n) => {
|
|
24
|
+
* if (n.type === "TagRef") return { type: "Literal", value: `<tag:${n.name}>` };
|
|
25
|
+
* });
|
|
26
|
+
*/ export declare function transform(root: ASTNode, fn: Transformer): ASTNode;
|
|
27
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sources":["../../../src/traverse/transform.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,8BAA8B;AAKnD;;;;;;;;CAQC,GACD,YAAY,eAAe,MAAM,YAAY,UAAU,IAAI,GAAG,SAAS;AAIvE;;;;;;;;;;;;;;;CAeC,GACD,OAAO,iBAAS,UAAU,MAAM,OAAO,EAAE,IAAI,WAAW,GAAG"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ASTNode } from "../../parser/mod.js";
|
|
2
|
+
/**
|
|
3
|
+
* Depth-first visitor hooks.
|
|
4
|
+
*
|
|
5
|
+
* `enter` is called before a node's children are visited.
|
|
6
|
+
* Return `false` to skip all children of this node (leave is NOT called).
|
|
7
|
+
*
|
|
8
|
+
* `leave` is called after all children have been visited.
|
|
9
|
+
*/ export interface Visitor {
|
|
10
|
+
/** Called before a node's children are visited. Return `false` to skip all children (and suppress `leave`). */ enter?: (node: ASTNode) => false | void;
|
|
11
|
+
/** Called after all children have been visited. */ leave?: (node: ASTNode) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Walk an AST depth-first, calling visitor hooks at each node.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const fnNames: string[] = [];
|
|
18
|
+
* walk(ast, {
|
|
19
|
+
* enter(node) {
|
|
20
|
+
* if (node.type === "FunctionCall") fnNames.push(node.name as string);
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
*/ export declare function walk(node: ASTNode, visitor: Visitor): void;
|
|
24
|
+
/** Collect every node of the given type anywhere in the tree. */ export declare function findAll(root: ASTNode, type: string): ASTNode[];
|
|
25
|
+
/** Return the first node of the given type, or `null` if none exists. */ export declare function findFirstOrNull(root: ASTNode, type: string): ASTNode | null;
|
|
26
|
+
/** Return the first node of the given type, or throw if none exists. */ export declare function findFirst(root: ASTNode, type: string): ASTNode;
|
|
27
|
+
//# sourceMappingURL=walk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.d.ts","sources":["../../../src/traverse/walk.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,8BAA8B;AAKnD;;;;;;;CAOC,GACD,iBAAiB;EACf,6GAA6G,GAC7G,SAAS,MAAM,YAAY,KAAK,GAAG,IAAI;EACvC,iDAAiD,GACjD,SAAS,MAAM,YAAY,IAAI;;AAKjC;;;;;;;;;;CAUC,GACD,OAAO,iBAAS,KAAK,MAAM,OAAO,EAAE,SAAS,OAAO,GAAG,IAAI;AA+B3D,+DAA+D,GAC/D,OAAO,iBAAS,QAAQ,MAAM,OAAO,EAAE,MAAM,MAAM,GAAG;AAMtD,uEAAuE,GACvE,OAAO,iBAAS,gBAAgB,MAAM,OAAO,EAAE,MAAM,MAAM,GAAG,UAAU,IAAI;AAI5E,sEAAsE,GACtE,OAAO,iBAAS,UAAU,MAAM,OAAO,EAAE,MAAM,MAAM,GAAG"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ursamu/mushcode",
|
|
3
|
+
"version": "0.3.4",
|
|
4
|
+
"description": "Parser, evaluator, linter, printer, and analyzer for MUX/RhostMUSH softcode.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./mod.ts",
|
|
8
|
+
"./parse": "./parser/mod.ts",
|
|
9
|
+
"./traverse": "./src/traverse/mod.ts",
|
|
10
|
+
"./print": "./src/print/mod.ts",
|
|
11
|
+
"./lint": "./src/lint/mod.ts",
|
|
12
|
+
"./analyze": "./src/analyze/mod.ts",
|
|
13
|
+
"./eval": "./src/eval/mod.ts"
|
|
14
|
+
},
|
|
15
|
+
"tasks": {
|
|
16
|
+
"test": "deno test --allow-read tests/",
|
|
17
|
+
"build": "npx peggy --format es --allowed-start-rules Start,LockExpr -o parser/mux-softcode.mjs grammar/mux-softcode.pegjs && npx peggy --allowed-start-rules Start,LockExpr -o parser/mux-softcode.js grammar/mux-softcode.pegjs"
|
|
18
|
+
},
|
|
19
|
+
"lint": {
|
|
20
|
+
"exclude": ["parser/mux-softcode.mjs", "parser/mux-softcode.js"]
|
|
21
|
+
},
|
|
22
|
+
"imports": {
|
|
23
|
+
"@std/assert": "jsr:@std/assert@^1",
|
|
24
|
+
"@std/testing": "jsr:@std/testing@^1"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "5",
|
|
3
|
+
"specifiers": {
|
|
4
|
+
"jsr:@std/assert@*": "1.0.19",
|
|
5
|
+
"jsr:@std/assert@1": "1.0.19",
|
|
6
|
+
"jsr:@std/assert@^1.0.17": "1.0.19",
|
|
7
|
+
"jsr:@std/internal@^1.0.12": "1.0.12",
|
|
8
|
+
"jsr:@std/path@*": "1.1.4",
|
|
9
|
+
"jsr:@std/testing@*": "1.0.17",
|
|
10
|
+
"jsr:@std/testing@1": "1.0.17"
|
|
11
|
+
},
|
|
12
|
+
"jsr": {
|
|
13
|
+
"@std/assert@1.0.19": {
|
|
14
|
+
"integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e",
|
|
15
|
+
"dependencies": [
|
|
16
|
+
"jsr:@std/internal"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"@std/internal@1.0.12": {
|
|
20
|
+
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
|
|
21
|
+
},
|
|
22
|
+
"@std/path@1.1.4": {
|
|
23
|
+
"integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5",
|
|
24
|
+
"dependencies": [
|
|
25
|
+
"jsr:@std/internal"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"@std/testing@1.0.17": {
|
|
29
|
+
"integrity": "87bdc2700fa98249d48a17cd72413352d3d3680dcfbdb64947fd0982d6bbf681",
|
|
30
|
+
"dependencies": [
|
|
31
|
+
"jsr:@std/assert@^1.0.17",
|
|
32
|
+
"jsr:@std/internal"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"workspace": {
|
|
37
|
+
"dependencies": [
|
|
38
|
+
"jsr:@std/assert@1",
|
|
39
|
+
"jsr:@std/testing@1"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Analyze
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import { extractCommands, extractDeps, extractTagRefs } from "jsr:@ursamu/mushcode/analyze";
|
|
5
|
+
import type { PatternEntry, DepEntry } from "jsr:@ursamu/mushcode/analyze";
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
All three functions accept any `ASTNode` root and scan the entire subtree, so
|
|
9
|
+
they work on `CommandList` roots, single `AttributeSet` values, or any other
|
|
10
|
+
node type.
|
|
11
|
+
|
|
12
|
+
## `extractCommands(root)`
|
|
13
|
+
|
|
14
|
+
Return all `DollarPattern` (`$command`) and `ListenPattern` (`^listen`) nodes
|
|
15
|
+
as a flat list with their printed pattern text.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
function extractCommands(root: ASTNode): PatternEntry[]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
interface PatternEntry {
|
|
23
|
+
type: "dollar" | "listen"; // which trigger kind
|
|
24
|
+
patternText: string; // printed pattern, e.g. "+finger *"
|
|
25
|
+
pattern: ASTNode; // raw Pattern or PatternAlts node
|
|
26
|
+
action: ASTNode; // action expression node
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Example
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
34
|
+
import { extractCommands } from "jsr:@ursamu/mushcode/analyze";
|
|
35
|
+
|
|
36
|
+
const src = `
|
|
37
|
+
$+finger *:@pemit %#=[u(me/FN_FINGER,%0)]
|
|
38
|
+
$+help:@pemit %#=[u(me/FN_HELP)]
|
|
39
|
+
^* says *:@pemit %#=## said something
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const ast = parse(src);
|
|
43
|
+
const cmds = extractCommands(ast);
|
|
44
|
+
|
|
45
|
+
for (const cmd of cmds) {
|
|
46
|
+
console.log(`${cmd.type} "${cmd.patternText}"`);
|
|
47
|
+
}
|
|
48
|
+
// dollar "+finger *"
|
|
49
|
+
// dollar "+help"
|
|
50
|
+
// listen "* says *"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Using the action node
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { print } from "jsr:@ursamu/mushcode/print";
|
|
57
|
+
|
|
58
|
+
for (const cmd of cmds) {
|
|
59
|
+
console.log(` action: ${print(cmd.action)}`);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## `extractDeps(root)`
|
|
64
|
+
|
|
65
|
+
Return dependency edges: `u()` attribute calls, `@trigger` commands, and
|
|
66
|
+
`get()`/`v()` attribute reads.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
function extractDeps(root: ASTNode): DepEntry[]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
interface DepEntry {
|
|
74
|
+
type: "u" | "trigger" | "get"; // dependency kind
|
|
75
|
+
target: string; // printed first argument / object expression
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Dynamic targets (e.g. `u([r(0)]/ATTR)`) are included with their printed form;
|
|
80
|
+
the caller decides how to interpret them.
|
|
81
|
+
|
|
82
|
+
### Example
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
86
|
+
import { extractDeps } from "jsr:@ursamu/mushcode/analyze";
|
|
87
|
+
|
|
88
|
+
const ast = parse("@pemit %#=[u(me/FN_A)][get(#room/DESC)];@trigger #target/EV=%0");
|
|
89
|
+
const deps = extractDeps(ast);
|
|
90
|
+
|
|
91
|
+
for (const dep of deps) {
|
|
92
|
+
console.log(`${dep.type}: ${dep.target}`);
|
|
93
|
+
}
|
|
94
|
+
// u: me/FN_A
|
|
95
|
+
// get: #room/DESC
|
|
96
|
+
// trigger: #target
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Building a dependency graph
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
103
|
+
import { extractCommands } from "jsr:@ursamu/mushcode/analyze";
|
|
104
|
+
import { extractDeps } from "jsr:@ursamu/mushcode/analyze";
|
|
105
|
+
|
|
106
|
+
function graphFor(src: string) {
|
|
107
|
+
const ast = parse(src);
|
|
108
|
+
const cmds = extractCommands(ast);
|
|
109
|
+
return cmds.map(cmd => ({
|
|
110
|
+
pattern: cmd.patternText,
|
|
111
|
+
deps: extractDeps(cmd.action),
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## `extractTagRefs(root)`
|
|
117
|
+
|
|
118
|
+
Return a deduplicated, sorted list of every tag name referenced by `#tagname`
|
|
119
|
+
nodes anywhere in the tree.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
function extractTagRefs(root: ASTNode): string[]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Example
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
129
|
+
import { extractTagRefs } from "jsr:@ursamu/mushcode/analyze";
|
|
130
|
+
|
|
131
|
+
const ast = parse("[get(#room/DESC)][get(#room/NAME)][u(#db/FN)]");
|
|
132
|
+
const tags = extractTagRefs(ast);
|
|
133
|
+
console.log(tags); // ["db", "room"] (deduplicated, sorted)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Checking for unresolved tags
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const knownTags = new Set(["room", "db", "weather"]);
|
|
140
|
+
const refs = extractTagRefs(ast);
|
|
141
|
+
const unknown = refs.filter(tag => !knownTags.has(tag));
|
|
142
|
+
if (unknown.length) {
|
|
143
|
+
console.warn("Unresolved tag refs:", unknown);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Eval
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import { EvalEngine, makeContext, registerStdlib } from "jsr:@ursamu/mushcode/eval";
|
|
5
|
+
import type {
|
|
6
|
+
EvalContext, EvalThunk, ObjectAccessor, IEvalEngine,
|
|
7
|
+
FunctionImpl, CommandImpl, IterFrame,
|
|
8
|
+
} from "jsr:@ursamu/mushcode/eval";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { EvalEngine, makeContext, registerStdlib } from "jsr:@ursamu/mushcode/eval";
|
|
15
|
+
|
|
16
|
+
// 1. Implement ObjectAccessor against your database
|
|
17
|
+
const accessor: ObjectAccessor = {
|
|
18
|
+
async getAttr(id, attr) { return myDb.getAttr(id, attr); },
|
|
19
|
+
async resolveTarget(from, expr) { return myDb.resolve(from, expr); },
|
|
20
|
+
async getName(id) { return myDb.getName(id); },
|
|
21
|
+
async hasFlag(id, flag) { return myDb.hasFlag(id, flag); },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// 2. Create engine and load stdlib
|
|
25
|
+
const engine = new EvalEngine(accessor);
|
|
26
|
+
registerStdlib(engine);
|
|
27
|
+
|
|
28
|
+
// 3. Build a context and evaluate
|
|
29
|
+
const ctx = makeContext({ enactor: "player-uuid", executor: "object-uuid" });
|
|
30
|
+
const result = await engine.evalString("[add(1,2)]", ctx);
|
|
31
|
+
console.log(result); // "3"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## `ObjectAccessor`
|
|
35
|
+
|
|
36
|
+
The interface your host application must implement to connect the evaluator to
|
|
37
|
+
your game database. All methods return Promises.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
interface ObjectAccessor {
|
|
41
|
+
/** Read a named attribute from an object. Returns null if absent. */
|
|
42
|
+
getAttr(objectId: string, attr: string): Promise<string | null>;
|
|
43
|
+
|
|
44
|
+
/** Resolve a target expression to a UUID. Returns null if not found. */
|
|
45
|
+
resolveTarget(from: string, expr: string): Promise<string | null>;
|
|
46
|
+
|
|
47
|
+
/** Return the display name of an object. */
|
|
48
|
+
getName(objectId: string): Promise<string>;
|
|
49
|
+
|
|
50
|
+
/** Check whether an object has a flag. */
|
|
51
|
+
hasFlag(objectId: string, flag: string): Promise<boolean>;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`resolveTarget` receives expressions like `"me"`, `"#uuid"`, `"ObjectName"`,
|
|
56
|
+
or the printed form of an eval block. You decide how to resolve them.
|
|
57
|
+
|
|
58
|
+
### Minimal in-memory implementation
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const objects = new Map([
|
|
62
|
+
["uuid-1", { name: "Alice", attrs: new Map([["DESC", "A person."]]), flags: new Set(["CONNECTED"]) }],
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const accessor: ObjectAccessor = {
|
|
66
|
+
async getAttr(id, attr) {
|
|
67
|
+
return objects.get(id)?.attrs.get(attr.toUpperCase()) ?? null;
|
|
68
|
+
},
|
|
69
|
+
async resolveTarget(from, expr) {
|
|
70
|
+
if (expr === "me") return from;
|
|
71
|
+
for (const [id, obj] of objects) {
|
|
72
|
+
if (id === expr || obj.name === expr) return id;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
},
|
|
76
|
+
async getName(id) {
|
|
77
|
+
return objects.get(id)?.name ?? "#-1 NO SUCH OBJECT";
|
|
78
|
+
},
|
|
79
|
+
async hasFlag(id, flag) {
|
|
80
|
+
return objects.get(id)?.flags.has(flag) ?? false;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## `EvalEngine`
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
class EvalEngine implements IEvalEngine {
|
|
89
|
+
constructor(accessor: ObjectAccessor)
|
|
90
|
+
|
|
91
|
+
registerFunction(name: string, impl: FunctionImpl): this
|
|
92
|
+
registerCommand(name: string, impl: CommandImpl): this
|
|
93
|
+
|
|
94
|
+
evalString(source: string, ctx: EvalContext): Promise<string>
|
|
95
|
+
eval(node: ASTNode, ctx: EvalContext): Promise<string>
|
|
96
|
+
exec(node: ASTNode, ctx: EvalContext): Promise<void>
|
|
97
|
+
|
|
98
|
+
readonly accessor: ObjectAccessor
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- `evalString` parses the source string and evaluates it.
|
|
103
|
+
- `eval` evaluates an already-parsed `ASTNode` to a string.
|
|
104
|
+
- `exec` executes a command node for its side effects (returns nothing).
|
|
105
|
+
|
|
106
|
+
Methods are chainable via `registerFunction` / `registerCommand` return `this`.
|
|
107
|
+
|
|
108
|
+
## `makeContext(partial)`
|
|
109
|
+
|
|
110
|
+
Create an `EvalContext` with safe defaults. The `enactor` and `executor` fields
|
|
111
|
+
are required; everything else is optional.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
function makeContext(
|
|
115
|
+
partial: Partial<EvalContext> & Pick<EvalContext, "enactor" | "executor">
|
|
116
|
+
): EvalContext
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Minimal context
|
|
121
|
+
const ctx = makeContext({ enactor: "player-1", executor: "player-1" });
|
|
122
|
+
|
|
123
|
+
// With positional args and a register pre-set
|
|
124
|
+
const ctx = makeContext({
|
|
125
|
+
enactor: "player-1",
|
|
126
|
+
executor: "object-5",
|
|
127
|
+
args: ["sword", "shield"],
|
|
128
|
+
registers: new Map([["0", "initial"]]),
|
|
129
|
+
maxDepth: 50,
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## `EvalContext`
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
interface EvalContext {
|
|
137
|
+
enactor: string; // %# — who triggered the action
|
|
138
|
+
executor: string; // %! — the object running the code
|
|
139
|
+
caller: string | null; // %@ — previous executor (null at top level)
|
|
140
|
+
args: string[]; // %0–%9
|
|
141
|
+
registers: Map<string, string>; // %q<name>, mutated by setq()
|
|
142
|
+
iterStack: IterFrame[]; // ## and #@ — pushed/popped by iter()
|
|
143
|
+
depth: number; // current u() recursion depth
|
|
144
|
+
maxDepth: number; // recursion limit (default 100)
|
|
145
|
+
signal?: AbortSignal; // optional cancellation
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## `registerStdlib(engine)`
|
|
150
|
+
|
|
151
|
+
Load all built-in softcode functions onto an engine instance.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
function registerStdlib(engine: IEvalEngine): void
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Call this once after creating your engine. You can add custom functions before
|
|
158
|
+
or after calling it.
|
|
159
|
+
|
|
160
|
+
See [stdlib.md](stdlib.md) for the full function reference.
|
|
161
|
+
|
|
162
|
+
## `FunctionImpl`
|
|
163
|
+
|
|
164
|
+
Register a custom function:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
interface FunctionImpl {
|
|
168
|
+
eval?: "eager" | "lazy"; // default: "eager"
|
|
169
|
+
minArgs: number;
|
|
170
|
+
maxArgs: number;
|
|
171
|
+
exec: (
|
|
172
|
+
args: string[] | EvalThunk[],
|
|
173
|
+
ctx: EvalContext,
|
|
174
|
+
engine: IEvalEngine,
|
|
175
|
+
) => Promise<string> | string;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Eager function (default)
|
|
180
|
+
|
|
181
|
+
Arguments are fully evaluated before `exec` is called. `args` is `string[]`.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
engine.registerFunction("double", {
|
|
185
|
+
minArgs: 1, maxArgs: 1,
|
|
186
|
+
exec(args) {
|
|
187
|
+
return String(Number((args as string[])[0]) * 2);
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// [double(21)] → "42"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Lazy function
|
|
195
|
+
|
|
196
|
+
Arguments are passed as `EvalThunk[]`. The function calls only the thunks it
|
|
197
|
+
needs. Required for short-circuit logic.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
engine.registerFunction("coalesce", {
|
|
201
|
+
eval: "lazy",
|
|
202
|
+
minArgs: 1, maxArgs: Infinity,
|
|
203
|
+
async exec(args) {
|
|
204
|
+
for (const thunk of args as EvalThunk[]) {
|
|
205
|
+
const val = await thunk();
|
|
206
|
+
if (val !== "") return val;
|
|
207
|
+
}
|
|
208
|
+
return "";
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## `EvalThunk`
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
type EvalThunk = (ctxOverride?: Partial<EvalContext>) => Promise<string>;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Calling a thunk evaluates its underlying AST node in the current context.
|
|
220
|
+
Pass a partial context override to temporarily change evaluation state — this
|
|
221
|
+
is how `iter()` sets `##` and `#@` without mutating the parent context.
|
|
222
|
+
|
|
223
|
+
## `CommandImpl`
|
|
224
|
+
|
|
225
|
+
Register a custom `@command`:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
interface CommandImpl {
|
|
229
|
+
exec: (
|
|
230
|
+
switches: string[],
|
|
231
|
+
object: string | null,
|
|
232
|
+
value: string | null,
|
|
233
|
+
ctx: EvalContext,
|
|
234
|
+
engine: IEvalEngine,
|
|
235
|
+
) => Promise<void>;
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
engine.registerCommand("pemit", {
|
|
241
|
+
async exec(switches, object, value, ctx, engine) {
|
|
242
|
+
if (!object || !value) return;
|
|
243
|
+
const targetId = await engine.accessor.resolveTarget(ctx.enactor, object);
|
|
244
|
+
if (!targetId) return;
|
|
245
|
+
const noeval = switches.includes("noeval");
|
|
246
|
+
const text = noeval ? value : await engine.evalString(value, ctx);
|
|
247
|
+
await myGame.sendMessage(targetId, text);
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## `IterFrame`
|
|
253
|
+
|
|
254
|
+
The structure pushed onto `ctx.iterStack` by `iter()` and similar functions.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
interface IterFrame {
|
|
258
|
+
item: string; // ## — current element value
|
|
259
|
+
index: number; // #@ — current position (1-based)
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Substitution codes
|
|
264
|
+
|
|
265
|
+
The evaluator handles these `%X` substitutions from `EvalContext`:
|
|
266
|
+
|
|
267
|
+
| Code | Resolves to |
|
|
268
|
+
|-----------------|-----------------------------------------|
|
|
269
|
+
| `%0`–`%9` | Positional args (`ctx.args[n]`) |
|
|
270
|
+
| `%q<name>` | Named register (`ctx.registers`) |
|
|
271
|
+
| `%#` | Enactor UUID |
|
|
272
|
+
| `%:` | Enactor UUID (object ID form) |
|
|
273
|
+
| `%!` | Executor UUID |
|
|
274
|
+
| `%@` | Caller UUID (empty string if none) |
|
|
275
|
+
| `%+` | Argument count |
|
|
276
|
+
| `%r` / `%R` | CRLF (`\r\n`) |
|
|
277
|
+
| `%t` / `%T` | Tab (`\t`) |
|
|
278
|
+
| `%b` / `%B` | Space |
|
|
279
|
+
| `%%` | Literal `%` |
|
|
280
|
+
| `%[` `%]` | Literal `[` `]` |
|
|
281
|
+
| `%,` `%;` | Literal `,` `;` |
|
|
282
|
+
| `%\\` | Literal `\` |
|
|
283
|
+
| `%N` / `%n` | Enactor name (upper / lower) |
|
|
284
|
+
| `%L` | Enactor location (via `resolveTarget`) |
|
|
285
|
+
| `%i0`–`%i9` | Iter item at stack depth n |
|
|
286
|
+
| `%=ATTR` | Enactor attribute shorthand |
|
|
287
|
+
| `%x` `%X` `%c` `%C` | ANSI pass-through (returned as-is) |
|
|
288
|
+
|
|
289
|
+
Special variables (`##`, `#@`) resolve from `ctx.iterStack[0]`.
|
|
290
|
+
|
|
291
|
+
## Cancellation
|
|
292
|
+
|
|
293
|
+
Pass an `AbortSignal` to cancel long-running evaluations:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
const ac = new AbortController();
|
|
297
|
+
const ctx = makeContext({
|
|
298
|
+
enactor: "player-1",
|
|
299
|
+
executor: "player-1",
|
|
300
|
+
signal: ac.signal,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
setTimeout(() => ac.abort(), 500);
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const result = await engine.evalString(complexSrc, ctx);
|
|
307
|
+
} catch (e) {
|
|
308
|
+
if (e instanceof DOMException && e.name === "AbortError") {
|
|
309
|
+
console.log("evaluation cancelled");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|