@rhost/testkit 0.1.1 → 1.3.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/README.md +393 -5
- package/ROADMAP.md +241 -0
- package/dist/benchmark.d.ts +44 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +118 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/cli/deploy.d.ts +2 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/deploy.js +120 -0
- package/dist/cli/deploy.js.map +1 -0
- package/dist/cli/fmt.d.ts +2 -0
- package/dist/cli/fmt.d.ts.map +1 -0
- package/dist/cli/fmt.js +119 -0
- package/dist/cli/fmt.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +81 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +210 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/validate.d.ts +2 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +126 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/cli/watch.d.ts +2 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +136 -0
- package/dist/cli/watch.js.map +1 -0
- package/dist/client.d.ts +56 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +117 -30
- package/dist/client.js.map +1 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +1 -1
- package/dist/container.js.map +1 -1
- package/dist/deployer.d.ts +86 -0
- package/dist/deployer.d.ts.map +1 -0
- package/dist/deployer.js +154 -0
- package/dist/deployer.js.map +1 -0
- package/dist/expect.d.ts +27 -1
- package/dist/expect.d.ts.map +1 -1
- package/dist/expect.js +47 -2
- package/dist/expect.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/dist/preflight.d.ts +70 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +121 -0
- package/dist/preflight.js.map +1 -0
- package/dist/reporter.d.ts +2 -0
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +24 -1
- package/dist/reporter.js.map +1 -1
- package/dist/runner.d.ts +83 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +137 -8
- package/dist/runner.js.map +1 -1
- package/dist/snapshots.d.ts +84 -0
- package/dist/snapshots.d.ts.map +1 -0
- package/dist/snapshots.js +230 -0
- package/dist/snapshots.js.map +1 -0
- package/dist/validator/builtins.d.ts +18 -0
- package/dist/validator/builtins.d.ts.map +1 -0
- package/dist/validator/builtins.js +265 -0
- package/dist/validator/builtins.js.map +1 -0
- package/dist/validator/checker.d.ts +13 -0
- package/dist/validator/checker.d.ts.map +1 -0
- package/dist/validator/checker.js +111 -0
- package/dist/validator/checker.js.map +1 -0
- package/dist/validator/clobber.d.ts +7 -0
- package/dist/validator/clobber.d.ts.map +1 -0
- package/dist/validator/clobber.js +102 -0
- package/dist/validator/clobber.js.map +1 -0
- package/dist/validator/compat.d.ts +19 -0
- package/dist/validator/compat.d.ts.map +1 -0
- package/dist/validator/compat.js +58 -0
- package/dist/validator/compat.js.map +1 -0
- package/dist/validator/formatter.d.ts +21 -0
- package/dist/validator/formatter.d.ts.map +1 -0
- package/dist/validator/formatter.js +120 -0
- package/dist/validator/formatter.js.map +1 -0
- package/dist/validator/index.d.ts +57 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +133 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/validator/parser.d.ts +16 -0
- package/dist/validator/parser.d.ts.map +1 -0
- package/dist/validator/parser.js +260 -0
- package/dist/validator/parser.js.map +1 -0
- package/dist/validator/tokenizer.d.ts +28 -0
- package/dist/validator/tokenizer.d.ts.map +1 -0
- package/dist/validator/tokenizer.js +174 -0
- package/dist/validator/tokenizer.js.map +1 -0
- package/dist/validator/types.d.ts +55 -0
- package/dist/validator/types.d.ts.map +1 -0
- package/dist/validator/types.js +6 -0
- package/dist/validator/types.js.map +1 -0
- package/dist/watcher.d.ts +44 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +297 -0
- package/dist/watcher.js.map +1 -0
- package/dist/world.d.ts +79 -0
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +167 -1
- package/dist/world.js.map +1 -1
- package/package.json +19 -3
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// RhostMUSH softcode recursive-descent parser
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parse = parse;
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Parser class
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
class Parser {
|
|
11
|
+
constructor(tokens) {
|
|
12
|
+
this.tokens = tokens;
|
|
13
|
+
this.pos = 0;
|
|
14
|
+
this.diagnostics = [];
|
|
15
|
+
}
|
|
16
|
+
peek() {
|
|
17
|
+
return this.pos < this.tokens.length ? this.tokens[this.pos] : null;
|
|
18
|
+
}
|
|
19
|
+
consume() {
|
|
20
|
+
if (this.pos >= this.tokens.length) {
|
|
21
|
+
throw new Error('Unexpected end of token stream');
|
|
22
|
+
}
|
|
23
|
+
return this.tokens[this.pos++];
|
|
24
|
+
}
|
|
25
|
+
// -------------------------------------------------------------------------
|
|
26
|
+
// Top-level entry point
|
|
27
|
+
// -------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* Parse the entire token stream as a top-level expression.
|
|
30
|
+
* Stray `)` and `]` at the top level are reported as errors.
|
|
31
|
+
* Top-level commas are treated as literal text.
|
|
32
|
+
*/
|
|
33
|
+
parseTopLevel() {
|
|
34
|
+
const nodes = [];
|
|
35
|
+
while (this.peek() !== null) {
|
|
36
|
+
const tok = this.peek();
|
|
37
|
+
if (tok.type === 'RPAREN') {
|
|
38
|
+
this.diagnostics.push({
|
|
39
|
+
severity: 'error',
|
|
40
|
+
code: 'E002',
|
|
41
|
+
message: `Unexpected ')' — no matching '(' was opened`,
|
|
42
|
+
offset: tok.offset,
|
|
43
|
+
length: 1,
|
|
44
|
+
});
|
|
45
|
+
this.consume();
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (tok.type === 'RBRACKET') {
|
|
49
|
+
this.diagnostics.push({
|
|
50
|
+
severity: 'error',
|
|
51
|
+
code: 'E004',
|
|
52
|
+
message: `Unexpected ']' — no matching '[' was opened`,
|
|
53
|
+
offset: tok.offset,
|
|
54
|
+
length: 1,
|
|
55
|
+
});
|
|
56
|
+
this.consume();
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (tok.type === 'COMMA') {
|
|
60
|
+
// Top-level comma is literal text (not inside a function call)
|
|
61
|
+
nodes.push({ type: 'RawText', value: ',', offset: tok.offset });
|
|
62
|
+
this.consume();
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const node = this.parseNode();
|
|
66
|
+
if (node !== null)
|
|
67
|
+
nodes.push(node);
|
|
68
|
+
}
|
|
69
|
+
return nodes;
|
|
70
|
+
}
|
|
71
|
+
// -------------------------------------------------------------------------
|
|
72
|
+
// Node dispatch
|
|
73
|
+
// -------------------------------------------------------------------------
|
|
74
|
+
/**
|
|
75
|
+
* Parse a single AST node. Returns null only at EOF.
|
|
76
|
+
*/
|
|
77
|
+
parseNode() {
|
|
78
|
+
const tok = this.peek();
|
|
79
|
+
if (tok === null)
|
|
80
|
+
return null;
|
|
81
|
+
if (tok.type === 'FNAME') {
|
|
82
|
+
return this.parseFunctionCall();
|
|
83
|
+
}
|
|
84
|
+
if (tok.type === 'LBRACKET') {
|
|
85
|
+
return this.parseBracketEval();
|
|
86
|
+
}
|
|
87
|
+
if (tok.type === 'SUBST') {
|
|
88
|
+
this.consume();
|
|
89
|
+
return { type: 'Substitution', raw: tok.value, offset: tok.offset };
|
|
90
|
+
}
|
|
91
|
+
// ESC, TEXT, LPAREN (bare paren not after identifier), anything else → RawText
|
|
92
|
+
this.consume();
|
|
93
|
+
return { type: 'RawText', value: tok.value, offset: tok.offset };
|
|
94
|
+
}
|
|
95
|
+
// -------------------------------------------------------------------------
|
|
96
|
+
// Function call: FNAME LPAREN [arglist] RPAREN
|
|
97
|
+
// -------------------------------------------------------------------------
|
|
98
|
+
parseFunctionCall() {
|
|
99
|
+
const nameTok = this.consume(); // FNAME
|
|
100
|
+
// Tokenizer guarantees LPAREN immediately follows FNAME
|
|
101
|
+
const lparenTok = this.consume(); // LPAREN
|
|
102
|
+
const node = {
|
|
103
|
+
type: 'FunctionCall',
|
|
104
|
+
name: nameTok.value,
|
|
105
|
+
nameOffset: nameTok.offset,
|
|
106
|
+
args: [],
|
|
107
|
+
offset: lparenTok.offset,
|
|
108
|
+
};
|
|
109
|
+
// Immediately closed: name()
|
|
110
|
+
if (this.peek()?.type === 'RPAREN') {
|
|
111
|
+
this.consume();
|
|
112
|
+
return node;
|
|
113
|
+
}
|
|
114
|
+
// EOF immediately after opening paren
|
|
115
|
+
if (this.peek() === null) {
|
|
116
|
+
this.diagnostics.push(this.unclosedParen(nameTok.value, lparenTok.offset));
|
|
117
|
+
return node;
|
|
118
|
+
}
|
|
119
|
+
// Parse comma-separated argument expressions
|
|
120
|
+
while (true) {
|
|
121
|
+
const arg = this.parseArgExpression(nameTok.value);
|
|
122
|
+
node.args.push(arg);
|
|
123
|
+
const next = this.peek();
|
|
124
|
+
if (next === null) {
|
|
125
|
+
// EOF without closing paren
|
|
126
|
+
this.diagnostics.push(this.unclosedParen(nameTok.value, lparenTok.offset));
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (next.type === 'RPAREN') {
|
|
130
|
+
this.consume();
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
if (next.type === 'COMMA') {
|
|
134
|
+
this.consume();
|
|
135
|
+
// Trailing comma — push one final empty arg and close on next RPAREN
|
|
136
|
+
if (this.peek()?.type === 'RPAREN') {
|
|
137
|
+
node.args.push([]);
|
|
138
|
+
this.consume();
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (this.peek() === null) {
|
|
142
|
+
node.args.push([]);
|
|
143
|
+
this.diagnostics.push(this.unclosedParen(nameTok.value, lparenTok.offset));
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Unexpected token — shouldn't happen with a well-formed token stream
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
return node;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Parse one argument expression, stopping (without consuming) at a
|
|
155
|
+
* COMMA or RPAREN that closes the current function call.
|
|
156
|
+
* LBRACKET/RBRACKET are handled recursively by parseBracketEval, so
|
|
157
|
+
* they do NOT prematurely end an argument even if they contain commas.
|
|
158
|
+
*/
|
|
159
|
+
parseArgExpression(funcName) {
|
|
160
|
+
const nodes = [];
|
|
161
|
+
while (true) {
|
|
162
|
+
const tok = this.peek();
|
|
163
|
+
if (tok === null)
|
|
164
|
+
break;
|
|
165
|
+
// These end the current argument
|
|
166
|
+
if (tok.type === 'COMMA' || tok.type === 'RPAREN')
|
|
167
|
+
break;
|
|
168
|
+
// Stray ] inside a function argument
|
|
169
|
+
if (tok.type === 'RBRACKET') {
|
|
170
|
+
this.diagnostics.push({
|
|
171
|
+
severity: 'error',
|
|
172
|
+
code: 'E004',
|
|
173
|
+
message: `Unexpected ']' inside argument of '${funcName}'`,
|
|
174
|
+
offset: tok.offset,
|
|
175
|
+
length: 1,
|
|
176
|
+
});
|
|
177
|
+
this.consume();
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const node = this.parseNode();
|
|
181
|
+
if (node !== null)
|
|
182
|
+
nodes.push(node);
|
|
183
|
+
}
|
|
184
|
+
return nodes;
|
|
185
|
+
}
|
|
186
|
+
// -------------------------------------------------------------------------
|
|
187
|
+
// Bracket eval: LBRACKET expression RBRACKET
|
|
188
|
+
// -------------------------------------------------------------------------
|
|
189
|
+
parseBracketEval() {
|
|
190
|
+
const lbracketTok = this.consume(); // LBRACKET
|
|
191
|
+
const innerNodes = [];
|
|
192
|
+
while (true) {
|
|
193
|
+
const tok = this.peek();
|
|
194
|
+
if (tok === null) {
|
|
195
|
+
this.diagnostics.push({
|
|
196
|
+
severity: 'error',
|
|
197
|
+
code: 'E003',
|
|
198
|
+
message: `Unclosed '[' — missing closing ']'`,
|
|
199
|
+
offset: lbracketTok.offset,
|
|
200
|
+
length: 1,
|
|
201
|
+
});
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
if (tok.type === 'RBRACKET') {
|
|
205
|
+
this.consume();
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
// Stray ) inside a bracket eval at the bracket's top level
|
|
209
|
+
if (tok.type === 'RPAREN') {
|
|
210
|
+
this.diagnostics.push({
|
|
211
|
+
severity: 'error',
|
|
212
|
+
code: 'E002',
|
|
213
|
+
message: `Unexpected ')' inside '[ ]' expression`,
|
|
214
|
+
offset: tok.offset,
|
|
215
|
+
length: 1,
|
|
216
|
+
});
|
|
217
|
+
this.consume();
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
// Top-level comma inside a bracket eval is literal text
|
|
221
|
+
if (tok.type === 'COMMA') {
|
|
222
|
+
innerNodes.push({ type: 'RawText', value: ',', offset: tok.offset });
|
|
223
|
+
this.consume();
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const node = this.parseNode();
|
|
227
|
+
if (node !== null)
|
|
228
|
+
innerNodes.push(node);
|
|
229
|
+
}
|
|
230
|
+
return { type: 'BracketEval', nodes: innerNodes, offset: lbracketTok.offset };
|
|
231
|
+
}
|
|
232
|
+
// -------------------------------------------------------------------------
|
|
233
|
+
// Helpers
|
|
234
|
+
// -------------------------------------------------------------------------
|
|
235
|
+
unclosedParen(funcName, offset) {
|
|
236
|
+
return {
|
|
237
|
+
severity: 'error',
|
|
238
|
+
code: 'E001',
|
|
239
|
+
message: `Unclosed '(' for function '${funcName}' — missing closing ')'`,
|
|
240
|
+
offset,
|
|
241
|
+
length: 1,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Public entry point
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
/**
|
|
249
|
+
* Parse a flat token stream into an AST.
|
|
250
|
+
*
|
|
251
|
+
* Structural errors (unbalanced parens/brackets) are embedded in `diagnostics`.
|
|
252
|
+
* The returned `nodes` array always contains the best-effort parse even when
|
|
253
|
+
* there are errors, enabling downstream semantic checks to still run.
|
|
254
|
+
*/
|
|
255
|
+
function parse(tokens) {
|
|
256
|
+
const parser = new Parser(tokens);
|
|
257
|
+
const nodes = parser.parseTopLevel();
|
|
258
|
+
return { nodes, diagnostics: parser.diagnostics };
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/validator/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type TokenType = 'FNAME' | 'LPAREN' | 'RPAREN' | 'COMMA' | 'LBRACKET' | 'RBRACKET' | 'SUBST' | 'ESC' | 'TEXT';
|
|
2
|
+
export interface Token {
|
|
3
|
+
type: TokenType;
|
|
4
|
+
/** The raw text of this token as it appears in the source */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Character offset in the original expression string */
|
|
7
|
+
offset: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Tokenize a RhostMUSH softcode expression into a flat token stream.
|
|
11
|
+
*
|
|
12
|
+
* Rules (in priority order):
|
|
13
|
+
* 1. `\X` → ESC(X) — escape always wins
|
|
14
|
+
* 2. `%%` → TEXT('%') — literal percent
|
|
15
|
+
* 3. `%qX` / `%iX` → SUBST('%qX') — 3-char register subst
|
|
16
|
+
* 4. `%X` (known) → SUBST('%X') — 2-char substitution
|
|
17
|
+
* 5. `%` (unknown) → TEXT('%') — treat as literal
|
|
18
|
+
* 6. `[` → LBRACKET
|
|
19
|
+
* 7. `]` → RBRACKET
|
|
20
|
+
* 8. `)` → RPAREN
|
|
21
|
+
* 9. `,` → COMMA
|
|
22
|
+
* 10. `word(` → FNAME(word) + LPAREN('(')
|
|
23
|
+
* 11. `word` → TEXT(word) — identifier not before (
|
|
24
|
+
* 12. `(` → TEXT('(') — ( not after identifier
|
|
25
|
+
* 13. <run> → TEXT(run) — anything else
|
|
26
|
+
*/
|
|
27
|
+
export declare function tokenize(input: string): Token[];
|
|
28
|
+
//# sourceMappingURL=tokenizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer.d.ts","sourceRoot":"","sources":["../../src/validator/tokenizer.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,UAAU,GACV,UAAU,GACV,OAAO,GACP,KAAK,GACL,MAAM,CAAC;AAEX,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAsCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,CAoI/C"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// RhostMUSH softcode tokenizer
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.tokenize = tokenize;
|
|
7
|
+
// Characters that end a plain-text accumulation run.
|
|
8
|
+
// These must be checked before starting a new TEXT run.
|
|
9
|
+
const STRUCTURAL_RE = /[\\%\[\]()\,,a-zA-Z_]/;
|
|
10
|
+
function isIdentStart(ch) {
|
|
11
|
+
const c = ch.charCodeAt(0);
|
|
12
|
+
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95; // A-Z, a-z, _
|
|
13
|
+
}
|
|
14
|
+
function isIdentChar(ch) {
|
|
15
|
+
const c = ch.charCodeAt(0);
|
|
16
|
+
return ((c >= 65 && c <= 90) ||
|
|
17
|
+
(c >= 97 && c <= 122) ||
|
|
18
|
+
(c >= 48 && c <= 57) ||
|
|
19
|
+
c === 95); // A-Z, a-z, 0-9, _
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Single-character percent substitutions recognised by RhostMUSH.
|
|
23
|
+
* Anything else after % is treated as a literal %, not a substitution.
|
|
24
|
+
*/
|
|
25
|
+
const SINGLE_CHAR_SUBST = new Set([
|
|
26
|
+
'#', 'N', 'n', 'L', 'l', 'P', 'p', 'T', 't', 'B', 'b',
|
|
27
|
+
'R', 'r', 'A', 'a', 'O', 'o', 'S', 's', 'X', 'x', '+',
|
|
28
|
+
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
29
|
+
'C', 'c', // ANSI carry
|
|
30
|
+
'v', 'V', // attribute value
|
|
31
|
+
'w', 'W', // attribute name
|
|
32
|
+
'u', 'U', // room name
|
|
33
|
+
'f', 'F', // from
|
|
34
|
+
'k', 'K', // money
|
|
35
|
+
'm', 'M', // last entered command
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Tokenize a RhostMUSH softcode expression into a flat token stream.
|
|
39
|
+
*
|
|
40
|
+
* Rules (in priority order):
|
|
41
|
+
* 1. `\X` → ESC(X) — escape always wins
|
|
42
|
+
* 2. `%%` → TEXT('%') — literal percent
|
|
43
|
+
* 3. `%qX` / `%iX` → SUBST('%qX') — 3-char register subst
|
|
44
|
+
* 4. `%X` (known) → SUBST('%X') — 2-char substitution
|
|
45
|
+
* 5. `%` (unknown) → TEXT('%') — treat as literal
|
|
46
|
+
* 6. `[` → LBRACKET
|
|
47
|
+
* 7. `]` → RBRACKET
|
|
48
|
+
* 8. `)` → RPAREN
|
|
49
|
+
* 9. `,` → COMMA
|
|
50
|
+
* 10. `word(` → FNAME(word) + LPAREN('(')
|
|
51
|
+
* 11. `word` → TEXT(word) — identifier not before (
|
|
52
|
+
* 12. `(` → TEXT('(') — ( not after identifier
|
|
53
|
+
* 13. <run> → TEXT(run) — anything else
|
|
54
|
+
*/
|
|
55
|
+
function tokenize(input) {
|
|
56
|
+
const tokens = [];
|
|
57
|
+
let pos = 0;
|
|
58
|
+
const len = input.length;
|
|
59
|
+
while (pos < len) {
|
|
60
|
+
const ch = input[pos];
|
|
61
|
+
// ---- 1. Backslash escape ----
|
|
62
|
+
if (ch === '\\') {
|
|
63
|
+
if (pos + 1 < len) {
|
|
64
|
+
tokens.push({ type: 'ESC', value: input[pos + 1], offset: pos });
|
|
65
|
+
pos += 2;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Trailing backslash: literal backslash
|
|
69
|
+
tokens.push({ type: 'TEXT', value: '\\', offset: pos });
|
|
70
|
+
pos++;
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// ---- 2–5. Percent substitutions ----
|
|
75
|
+
if (ch === '%') {
|
|
76
|
+
if (pos + 1 >= len) {
|
|
77
|
+
tokens.push({ type: 'TEXT', value: '%', offset: pos });
|
|
78
|
+
pos++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const next = input[pos + 1];
|
|
82
|
+
// %% → literal %
|
|
83
|
+
if (next === '%') {
|
|
84
|
+
tokens.push({ type: 'TEXT', value: '%', offset: pos });
|
|
85
|
+
pos += 2;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// %q0–%q9, %qA–%qZ (q-register), %i0–%i9 (iter var) → 3-char subst
|
|
89
|
+
if ((next === 'q' || next === 'Q' || next === 'i' || next === 'I') &&
|
|
90
|
+
pos + 2 < len && /[0-9a-zA-Z]/.test(input[pos + 2])) {
|
|
91
|
+
tokens.push({ type: 'SUBST', value: input.slice(pos, pos + 3), offset: pos });
|
|
92
|
+
pos += 3;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// Single-char known substitutions
|
|
96
|
+
if (SINGLE_CHAR_SUBST.has(next)) {
|
|
97
|
+
tokens.push({ type: 'SUBST', value: input.slice(pos, pos + 2), offset: pos });
|
|
98
|
+
pos += 2;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Unknown %X: literal %
|
|
102
|
+
tokens.push({ type: 'TEXT', value: '%', offset: pos });
|
|
103
|
+
pos++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// ---- 6–9. Single structural characters ----
|
|
107
|
+
if (ch === '[') {
|
|
108
|
+
tokens.push({ type: 'LBRACKET', value: '[', offset: pos });
|
|
109
|
+
pos++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (ch === ']') {
|
|
113
|
+
tokens.push({ type: 'RBRACKET', value: ']', offset: pos });
|
|
114
|
+
pos++;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (ch === ')') {
|
|
118
|
+
tokens.push({ type: 'RPAREN', value: ')', offset: pos });
|
|
119
|
+
pos++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (ch === ',') {
|
|
123
|
+
tokens.push({ type: 'COMMA', value: ',', offset: pos });
|
|
124
|
+
pos++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// ---- 10–11. Identifier: FNAME+LPAREN or plain TEXT ----
|
|
128
|
+
if (isIdentStart(ch)) {
|
|
129
|
+
const start = pos;
|
|
130
|
+
while (pos < len && isIdentChar(input[pos])) {
|
|
131
|
+
pos++;
|
|
132
|
+
}
|
|
133
|
+
const word = input.slice(start, pos);
|
|
134
|
+
if (pos < len && input[pos] === '(') {
|
|
135
|
+
// Function call: emit FNAME then LPAREN
|
|
136
|
+
tokens.push({ type: 'FNAME', value: word, offset: start });
|
|
137
|
+
tokens.push({ type: 'LPAREN', value: '(', offset: pos });
|
|
138
|
+
pos++;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Bare identifier — text
|
|
142
|
+
tokens.push({ type: 'TEXT', value: word, offset: start });
|
|
143
|
+
}
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// ---- 12. '(' not after an identifier: literal ----
|
|
147
|
+
if (ch === '(') {
|
|
148
|
+
tokens.push({ type: 'TEXT', value: '(', offset: pos });
|
|
149
|
+
pos++;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
// ---- 13. Plain text accumulation ----
|
|
153
|
+
{
|
|
154
|
+
const start = pos;
|
|
155
|
+
while (pos < len) {
|
|
156
|
+
const c = input[pos];
|
|
157
|
+
// Stop at any character that starts a higher-priority rule
|
|
158
|
+
if (c === '\\' || c === '%' ||
|
|
159
|
+
c === '[' || c === ']' ||
|
|
160
|
+
c === '(' || c === ')' ||
|
|
161
|
+
c === ',' ||
|
|
162
|
+
isIdentStart(c)) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
pos++;
|
|
166
|
+
}
|
|
167
|
+
if (pos > start) {
|
|
168
|
+
tokens.push({ type: 'TEXT', value: input.slice(start, pos), offset: start });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return tokens;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=tokenizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer.js","sourceRoot":"","sources":["../../src/validator/tokenizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type Severity = 'error' | 'warning';
|
|
2
|
+
/** A single diagnostic produced by the validator. */
|
|
3
|
+
export interface Diagnostic {
|
|
4
|
+
severity: Severity;
|
|
5
|
+
/** Error/warning code, e.g. 'E001', 'W002' */
|
|
6
|
+
code: string;
|
|
7
|
+
message: string;
|
|
8
|
+
/** Character offset in the original expression string */
|
|
9
|
+
offset: number;
|
|
10
|
+
/** Span length in characters (0 = point diagnostic) */
|
|
11
|
+
length: number;
|
|
12
|
+
}
|
|
13
|
+
/** Result returned by `validate()` */
|
|
14
|
+
export interface ValidationResult {
|
|
15
|
+
/** false if any diagnostic has severity 'error' */
|
|
16
|
+
valid: boolean;
|
|
17
|
+
diagnostics: Diagnostic[];
|
|
18
|
+
}
|
|
19
|
+
export type ASTNode = FunctionCallNode | BracketEvalNode | SubstitutionNode | RawTextNode;
|
|
20
|
+
/** A function call: `name(arg1, arg2, ...)` */
|
|
21
|
+
export interface FunctionCallNode {
|
|
22
|
+
type: 'FunctionCall';
|
|
23
|
+
/** Original casing preserved from source */
|
|
24
|
+
name: string;
|
|
25
|
+
/** Offset of the first character of the name */
|
|
26
|
+
nameOffset: number;
|
|
27
|
+
/**
|
|
28
|
+
* Each element is the list of nodes forming one argument.
|
|
29
|
+
* An empty inner array means an empty/missing argument.
|
|
30
|
+
*/
|
|
31
|
+
args: ASTNode[][];
|
|
32
|
+
/** Offset of the opening parenthesis */
|
|
33
|
+
offset: number;
|
|
34
|
+
}
|
|
35
|
+
/** An inline evaluation: `[expression]` */
|
|
36
|
+
export interface BracketEvalNode {
|
|
37
|
+
type: 'BracketEval';
|
|
38
|
+
nodes: ASTNode[];
|
|
39
|
+
/** Offset of the '[' */
|
|
40
|
+
offset: number;
|
|
41
|
+
}
|
|
42
|
+
/** A percent-substitution: `%#`, `%N`, `%q0`, `%0`–`%9`, `%%`, etc. */
|
|
43
|
+
export interface SubstitutionNode {
|
|
44
|
+
type: 'Substitution';
|
|
45
|
+
/** The raw substitution text as it appears in the source */
|
|
46
|
+
raw: string;
|
|
47
|
+
offset: number;
|
|
48
|
+
}
|
|
49
|
+
/** Literal text or an escaped character that requires no further analysis */
|
|
50
|
+
export interface RawTextNode {
|
|
51
|
+
type: 'RawText';
|
|
52
|
+
value: string;
|
|
53
|
+
offset: number;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/validator/types.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,QAAQ,CAAC;IACnB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAMD,MAAM,MAAM,OAAO,GACf,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,WAAW,CAAC;AAEhB,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC;IAClB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uEAAuE;AACvE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Shared types for the RhostMUSH softcode validator
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/validator/types.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,oDAAoD;AACpD,8EAA8E"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface WatchOptions {
|
|
2
|
+
/** Absolute paths of test files to watch and run */
|
|
3
|
+
files: string[];
|
|
4
|
+
/** Debounce delay in ms before re-running after a change. Default: 300 */
|
|
5
|
+
debounceMs?: number;
|
|
6
|
+
/** Clear terminal between runs. Default: true */
|
|
7
|
+
clearScreen?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class RhostWatcher {
|
|
10
|
+
private readonly options;
|
|
11
|
+
private readonly debounceMs;
|
|
12
|
+
private readonly clearScreen;
|
|
13
|
+
private watcher;
|
|
14
|
+
private debounceTimer;
|
|
15
|
+
private pendingFiles;
|
|
16
|
+
private currentProcess;
|
|
17
|
+
private stopped;
|
|
18
|
+
private runCount;
|
|
19
|
+
constructor(options: WatchOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Start watching. Runs an initial pass immediately, then re-runs on change.
|
|
22
|
+
* Blocks until `stop()` is called (or SIGINT).
|
|
23
|
+
*/
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
/** Graceful shutdown: cancel pending timers, close watcher, kill child. */
|
|
26
|
+
stop(): Promise<void>;
|
|
27
|
+
private setupWatcher;
|
|
28
|
+
private runFiles;
|
|
29
|
+
private spawnFile;
|
|
30
|
+
private printBanner;
|
|
31
|
+
private printRunHeader;
|
|
32
|
+
/**
|
|
33
|
+
* Find the longest common directory path shared by all given dirs.
|
|
34
|
+
* Falls back to '/' (or drive root on Windows) if nothing in common.
|
|
35
|
+
*/
|
|
36
|
+
private commonRoot;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Recursively walk `rootDir` and return all test files matching
|
|
40
|
+
* `*.test.ts`, `*.test.js`, `*.spec.ts`, `*.spec.js`.
|
|
41
|
+
* Skips `node_modules`, `dist`, `.git`, `.next`, `.turbo`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function discoverTestFiles(rootDir: string): string[];
|
|
44
|
+
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAMD,qBAAa,YAAY;IAWX,OAAO,CAAC,QAAQ,CAAC,OAAO;IAVpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IAEtC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;gBAEQ,OAAO,EAAE,YAAY;IAKlD;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B5B,2EAA2E;IACrE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B3B,OAAO,CAAC,YAAY;YA2CN,QAAQ;IAuBtB,OAAO,CAAC,SAAS;IAyCjB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,cAAc;IAqBtB;;;OAGG;IACH,OAAO,CAAC,UAAU;CAoBnB;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAI3D"}
|