@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,152 @@
|
|
|
1
|
+
# Lint
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import { lint, RULES } from "jsr:@ursamu/mushcode/lint";
|
|
5
|
+
import type { Diagnostic, Severity, LintOptions, RuleId } from "jsr:@ursamu/mushcode/lint";
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## `lint(root, opts?)`
|
|
9
|
+
|
|
10
|
+
Run static analysis on an AST and return an array of diagnostics. All four
|
|
11
|
+
built-in rules run by default.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
function lint(root: ASTNode, opts?: LintOptions): Diagnostic[]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
19
|
+
import { lint } from "jsr:@ursamu/mushcode/lint";
|
|
20
|
+
|
|
21
|
+
const ast = parse("$+finger *:@pemit %#=[u(me/FN_FINGER,%0)]");
|
|
22
|
+
const diags = lint(ast);
|
|
23
|
+
diags.forEach(d => console.log(`[${d.severity}] ${d.rule}: ${d.message}`));
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## `LintOptions`
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
interface LintOptions {
|
|
30
|
+
rules?: string[]; // subset of RULES to enable; omit to run all
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run only specific rules:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const diags = lint(ast, { rules: ["missing-wildcard", "arg-count"] });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## `RULES`
|
|
41
|
+
|
|
42
|
+
Tuple of every built-in rule ID:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const RULES = [
|
|
46
|
+
"missing-wildcard",
|
|
47
|
+
"iter-var-outside-iter",
|
|
48
|
+
"arg-count",
|
|
49
|
+
"register-before-set",
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
type RuleId = (typeof RULES)[number];
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## `Diagnostic`
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
interface Diagnostic {
|
|
59
|
+
rule: string; // rule ID that produced this diagnostic
|
|
60
|
+
severity: Severity; // "error" | "warning" | "info"
|
|
61
|
+
message: string;
|
|
62
|
+
node: ASTNode; // AST node most closely associated with the issue
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type Severity = "error" | "warning" | "info";
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The `node` field can be used with its `loc` property to report source positions:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
diags.forEach(d => {
|
|
72
|
+
const loc = d.node.loc?.start;
|
|
73
|
+
const pos = loc ? ` (line ${loc.line}, col ${loc.column})` : "";
|
|
74
|
+
console.error(`[${d.severity}] ${d.rule}${pos}: ${d.message}`);
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Built-in rules
|
|
79
|
+
|
|
80
|
+
### `missing-wildcard`
|
|
81
|
+
|
|
82
|
+
A `DollarPattern` or `ListenPattern` has no wildcard (`*` or `?`) segment in
|
|
83
|
+
its pattern. This usually means the trigger will only fire on an exact string
|
|
84
|
+
match with no arguments, which is rarely intentional.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// triggers the rule — "$finger" matches only the literal string "finger"
|
|
88
|
+
lint(parse("$finger:@pemit %#=ok"));
|
|
89
|
+
|
|
90
|
+
// clean — "$finger *" accepts one wildcard argument
|
|
91
|
+
lint(parse("$finger *:@pemit %#=ok"));
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `iter-var-outside-iter`
|
|
95
|
+
|
|
96
|
+
`##` (iter item) or `#@` (iter index) is used outside of an `iter()` function
|
|
97
|
+
call. These special variables are only defined inside an iteration body; using
|
|
98
|
+
them elsewhere always produces an empty string.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// triggers the rule — ## used at top level
|
|
102
|
+
lint(parse("@pemit %#=##"));
|
|
103
|
+
|
|
104
|
+
// clean — ## inside iter body
|
|
105
|
+
lint(parse("[iter(a b c,##)]"));
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `arg-count`
|
|
109
|
+
|
|
110
|
+
A built-in function is called with fewer arguments than its `minArgs` or more
|
|
111
|
+
than its `maxArgs`. The rule checks against known function arities for all
|
|
112
|
+
functions registered in the standard library.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// triggers the rule — add() requires at least 2 arguments
|
|
116
|
+
lint(parse("[add(1)]"));
|
|
117
|
+
|
|
118
|
+
// triggers the rule — sub() takes exactly 2 arguments
|
|
119
|
+
lint(parse("[sub(1,2,3)]"));
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `register-before-set`
|
|
123
|
+
|
|
124
|
+
A named register (`%qX` or `r(X)`) is read before `setq(X, …)` has been called
|
|
125
|
+
anywhere in the same expression tree. This does not track runtime flow, only
|
|
126
|
+
whether any `setq` for that register name appears anywhere in the tree.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// triggers the rule — %q0 read before any setq(0, …)
|
|
130
|
+
lint(parse("[r(0)] [setq(0,hello)]"));
|
|
131
|
+
|
|
132
|
+
// clean — setq before r
|
|
133
|
+
lint(parse("[setq(0,hello)][r(0)]"));
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Example: format diagnostics as editor annotations
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
140
|
+
import { lint } from "jsr:@ursamu/mushcode/lint";
|
|
141
|
+
import type { Diagnostic } from "jsr:@ursamu/mushcode/lint";
|
|
142
|
+
|
|
143
|
+
function annotate(src: string): { line: number; col: number; message: string }[] {
|
|
144
|
+
const ast = parse(src);
|
|
145
|
+
const diags = lint(ast);
|
|
146
|
+
return diags.map((d: Diagnostic) => ({
|
|
147
|
+
line: d.node.loc?.start.line ?? 1,
|
|
148
|
+
col: d.node.loc?.start.column ?? 1,
|
|
149
|
+
message: `[${d.rule}] ${d.message}`,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
```
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Parser
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import { parse, ParseError } from "jsr:@ursamu/mushcode/parse";
|
|
5
|
+
import type { ASTNode, NodeType, SourceLocation, StartRule } from "jsr:@ursamu/mushcode/parse";
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## `parse(text, startRule?)`
|
|
9
|
+
|
|
10
|
+
Parse a raw softcode string and return its AST root node.
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
function parse(text: string, startRule?: StartRule): ASTNode
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| Parameter | Type | Default | Description |
|
|
17
|
+
|-------------|-------------|-------------|-------------------------------------------|
|
|
18
|
+
| `text` | `string` | — | Softcode source to parse |
|
|
19
|
+
| `startRule` | `StartRule` | `"Start"` | Grammar entry point (see below) |
|
|
20
|
+
|
|
21
|
+
Throws `ParseError` on syntax errors.
|
|
22
|
+
|
|
23
|
+
### Start rules
|
|
24
|
+
|
|
25
|
+
- `"Start"` — normal softcode: commands, function calls, substitutions, patterns
|
|
26
|
+
- `"LockExpr"` — lock expression string, e.g. `flag^WIZARD|attr^STAFF:1`
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Normal softcode
|
|
30
|
+
const ast = parse("$+finger *:@pemit %#=[u(me/FN_FINGER,%0)]");
|
|
31
|
+
|
|
32
|
+
// Lock expression
|
|
33
|
+
const lock = parse("flag^WIZARD|!flag^GUEST", "LockExpr");
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## `ParseError`
|
|
37
|
+
|
|
38
|
+
Thrown when the parser encounters invalid input.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
class ParseError extends Error {
|
|
42
|
+
readonly location?: { line: number; column: number; offset: number };
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
try {
|
|
48
|
+
parse("[unclosed(");
|
|
49
|
+
} catch (e) {
|
|
50
|
+
if (e instanceof ParseError) {
|
|
51
|
+
console.error(`Syntax error at line ${e.location?.line}: ${e.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Types
|
|
57
|
+
|
|
58
|
+
### `ASTNode`
|
|
59
|
+
|
|
60
|
+
Every node in the tree conforms to this type:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
type ASTNode = {
|
|
64
|
+
type: string; // node type discriminant
|
|
65
|
+
loc?: SourceLocation; // source span (always present unless disabled)
|
|
66
|
+
[key: string]: any; // node-specific fields
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Node-specific fields are accessed by checking `node.type` first:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
if (ast.type === "FunctionCall") {
|
|
74
|
+
console.log(ast.name); // string
|
|
75
|
+
console.log((ast.args as ASTNode[]).length);
|
|
76
|
+
}
|
|
77
|
+
if (ast.type === "Literal") {
|
|
78
|
+
console.log(ast.value as string);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `SourceLocation`
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
interface SourceLocation {
|
|
86
|
+
start: SourcePosition;
|
|
87
|
+
end: SourcePosition;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface SourcePosition {
|
|
91
|
+
offset: number; // byte offset from start of input (0-based)
|
|
92
|
+
line: number; // 1-based line number
|
|
93
|
+
column: number; // 1-based column number
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const ast = parse("add(1,2)");
|
|
99
|
+
// ast.loc.start → { offset: 0, line: 1, column: 1 }
|
|
100
|
+
// ast.loc.end → { offset: 8, line: 1, column: 9 }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `NodeType`
|
|
104
|
+
|
|
105
|
+
Union of every type string the parser can produce:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
type NodeType =
|
|
109
|
+
| "Literal" | "Escape" | "Substitution" | "SpecialVar" | "Wildcard"
|
|
110
|
+
| "EvalBlock" | "BracedString" | "Text" | "Arg"
|
|
111
|
+
| "FunctionCall" | "DollarPattern" | "ListenPattern"
|
|
112
|
+
| "PatternAlts" | "Pattern"
|
|
113
|
+
| "CommandList" | "AtCommand" | "AttributeSet" | "UserCommand"
|
|
114
|
+
| "TagRef"
|
|
115
|
+
| "LockOr" | "LockAnd" | "LockNot" | "LockMe" | "LockDbref"
|
|
116
|
+
| "LockFlagCheck" | "LockTypeCheck" | "LockAttrCheck" | "LockPlayerName";
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Node reference
|
|
120
|
+
|
|
121
|
+
### Expression nodes
|
|
122
|
+
|
|
123
|
+
| Type | Key fields | Description |
|
|
124
|
+
|----------------|-----------------------------------------|----------------------------------------|
|
|
125
|
+
| `Literal` | `value: string` | Plain text segment |
|
|
126
|
+
| `Escape` | `char: string` | Backslash-escaped character |
|
|
127
|
+
| `Substitution` | `code: string` | `%X` substitution (code = `"#"`, `"0"`, `"qA"`, …) |
|
|
128
|
+
| `SpecialVar` | `code: string` | `##` (iter item) or `#@` (iter index) |
|
|
129
|
+
| `TagRef` | `name: string` | `#tagname` reference |
|
|
130
|
+
| `EvalBlock` | `parts: ASTNode[]` | `[…]` evaluated block |
|
|
131
|
+
| `BracedString` | `parts: ASTNode[]` | `{…}` braced literal |
|
|
132
|
+
| `Arg` | `parts: ASTNode[]` | One comma-separated function argument |
|
|
133
|
+
| `FunctionCall` | `name: string`, `args: ASTNode[]` | `name(arg1,arg2,…)` |
|
|
134
|
+
|
|
135
|
+
### Command nodes
|
|
136
|
+
|
|
137
|
+
| Type | Key fields | Description |
|
|
138
|
+
|----------------|-------------------------------------------------------------------|--------------------------|
|
|
139
|
+
| `CommandList` | `commands: ASTNode[]` | `;`-separated statements |
|
|
140
|
+
| `AtCommand` | `name: string`, `switches: string[]`, `object?: ASTNode`, `value?: ASTNode` | `@name/sw obj=val` |
|
|
141
|
+
| `AttributeSet` | `attribute: string`, `object: ASTNode`, `value?: ASTNode` | `&ATTR obj=val` |
|
|
142
|
+
| `UserCommand` | `parts: ASTNode[]` | Freeform typed command |
|
|
143
|
+
|
|
144
|
+
### Pattern nodes
|
|
145
|
+
|
|
146
|
+
| Type | Key fields | Description |
|
|
147
|
+
|-----------------|-----------------------------------------|------------------------------------|
|
|
148
|
+
| `DollarPattern` | `pattern: ASTNode`, `action: ASTNode` | `$pattern:action` trigger |
|
|
149
|
+
| `ListenPattern` | `pattern: ASTNode`, `action: ASTNode` | `^pattern:action` listen trigger |
|
|
150
|
+
| `Pattern` | `parts: ASTNode[]` | Pattern body segments |
|
|
151
|
+
| `PatternAlts` | `patterns: ASTNode[]` | `;`-separated pattern alternatives |
|
|
152
|
+
| `Wildcard` | `wildcard: string` | `"*"` or `"?"` |
|
|
153
|
+
|
|
154
|
+
### Lock nodes (LockExpr start rule)
|
|
155
|
+
|
|
156
|
+
| Type | Key fields |
|
|
157
|
+
|-------------------|-----------------------------------------|
|
|
158
|
+
| `LockOr` | `left: ASTNode`, `right: ASTNode` |
|
|
159
|
+
| `LockAnd` | `left: ASTNode`, `right: ASTNode` |
|
|
160
|
+
| `LockNot` | `operand: ASTNode` |
|
|
161
|
+
| `LockMe` | _(no extra fields)_ |
|
|
162
|
+
| `LockDbref` | `dbref: string` |
|
|
163
|
+
| `LockFlagCheck` | `flag: string` |
|
|
164
|
+
| `LockTypeCheck` | `objtype: string` |
|
|
165
|
+
| `LockAttrCheck` | `attr: string`, `value: string` |
|
|
166
|
+
| `LockPlayerName` | `name: string` |
|
|
167
|
+
|
|
168
|
+
## Examples
|
|
169
|
+
|
|
170
|
+
### Collecting all substitution codes in a softcode string
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
174
|
+
import { findAll } from "jsr:@ursamu/mushcode/traverse";
|
|
175
|
+
|
|
176
|
+
const ast = parse("@pemit %#=Hello %N, you have %0 coins.");
|
|
177
|
+
const subs = findAll(ast, "Substitution");
|
|
178
|
+
console.log(subs.map(n => n.code)); // ["#", "N", "0"]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Checking for parse errors with location info
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { parse, ParseError } from "jsr:@ursamu/mushcode/parse";
|
|
185
|
+
|
|
186
|
+
function safeParse(src: string) {
|
|
187
|
+
try {
|
|
188
|
+
return { ok: true, ast: parse(src) };
|
|
189
|
+
} catch (e) {
|
|
190
|
+
if (e instanceof ParseError) {
|
|
191
|
+
return { ok: false, error: e.message, line: e.location?.line };
|
|
192
|
+
}
|
|
193
|
+
throw e;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Print
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import { print } from "jsr:@ursamu/mushcode/print";
|
|
5
|
+
import type { PrintOptions, PrintMode } from "jsr:@ursamu/mushcode/print";
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## `print(node, opts?)`
|
|
9
|
+
|
|
10
|
+
Convert an AST node back to a canonical softcode string.
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
function print(node: ASTNode, opts?: PrintOptions): string
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The output is semantically equivalent to the original source. Whitespace around
|
|
17
|
+
`@command` syntax may be normalised (e.g. `@pemit%#=x` becomes `@pemit %#=x`).
|
|
18
|
+
|
|
19
|
+
Lock-expression nodes (`LockOr`, `LockAnd`, etc.) produced by the `"LockExpr"`
|
|
20
|
+
start rule are handled automatically.
|
|
21
|
+
|
|
22
|
+
## `PrintOptions`
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
interface PrintOptions {
|
|
26
|
+
mode?: PrintMode; // default: "compact"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type PrintMode = "compact" | "pretty";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Mode | `CommandList` separator |
|
|
33
|
+
|-------------|-------------------------|
|
|
34
|
+
| `"compact"` | `;` |
|
|
35
|
+
| `"pretty"` | `;\n` |
|
|
36
|
+
|
|
37
|
+
## Examples
|
|
38
|
+
|
|
39
|
+
### Round-trip a softcode string
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
43
|
+
import { print } from "jsr:@ursamu/mushcode/print";
|
|
44
|
+
|
|
45
|
+
const src = "$+say *:@pemit/noeval %#=You say: %0";
|
|
46
|
+
const ast = parse(src);
|
|
47
|
+
console.log(print(ast)); // "$+say *:@pemit/noeval %#=You say: %0"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Pretty-print a command list
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
54
|
+
import { print } from "jsr:@ursamu/mushcode/print";
|
|
55
|
+
|
|
56
|
+
const ast = parse("@pemit %#=Line one;@pemit %#=Line two;@pemit %#=Line three");
|
|
57
|
+
console.log(print(ast, { mode: "pretty" }));
|
|
58
|
+
// @pemit %#=Line one
|
|
59
|
+
// @pemit %#=Line two
|
|
60
|
+
// @pemit %#=Line three
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Print a transformed tree
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
67
|
+
import { transform } from "jsr:@ursamu/mushcode/traverse";
|
|
68
|
+
import { print } from "jsr:@ursamu/mushcode/print";
|
|
69
|
+
|
|
70
|
+
// Strip all TagRef nodes before printing
|
|
71
|
+
const ast = parse("@pemit %#=[get(#room/DESC)]");
|
|
72
|
+
const out = transform(ast, (n) => n.type === "TagRef" ? null : undefined);
|
|
73
|
+
console.log(print(out));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Print a lock expression
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { parse } from "jsr:@ursamu/mushcode/parse";
|
|
80
|
+
import { print } from "jsr:@ursamu/mushcode/print";
|
|
81
|
+
|
|
82
|
+
const lock = parse("flag^WIZARD|!flag^GUEST", "LockExpr");
|
|
83
|
+
console.log(print(lock)); // "flag^WIZARD|!flag^GUEST"
|
|
84
|
+
```
|