@rhost/testkit 0.1.0 → 1.3.1

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.
Files changed (110) hide show
  1. package/README.md +393 -5
  2. package/ROADMAP.md +241 -0
  3. package/dist/benchmark.d.ts +44 -0
  4. package/dist/benchmark.d.ts.map +1 -0
  5. package/dist/benchmark.js +118 -0
  6. package/dist/benchmark.js.map +1 -0
  7. package/dist/cli/deploy.d.ts +2 -0
  8. package/dist/cli/deploy.d.ts.map +1 -0
  9. package/dist/cli/deploy.js +120 -0
  10. package/dist/cli/deploy.js.map +1 -0
  11. package/dist/cli/fmt.d.ts +2 -0
  12. package/dist/cli/fmt.d.ts.map +1 -0
  13. package/dist/cli/fmt.js +119 -0
  14. package/dist/cli/fmt.js.map +1 -0
  15. package/dist/cli/index.d.ts +3 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +81 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/init.d.ts +2 -0
  20. package/dist/cli/init.d.ts.map +1 -0
  21. package/dist/cli/init.js +210 -0
  22. package/dist/cli/init.js.map +1 -0
  23. package/dist/cli/validate.d.ts +2 -0
  24. package/dist/cli/validate.d.ts.map +1 -0
  25. package/dist/cli/validate.js +126 -0
  26. package/dist/cli/validate.js.map +1 -0
  27. package/dist/cli/watch.d.ts +2 -0
  28. package/dist/cli/watch.d.ts.map +1 -0
  29. package/dist/cli/watch.js +136 -0
  30. package/dist/cli/watch.js.map +1 -0
  31. package/dist/client.d.ts +48 -0
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +113 -30
  34. package/dist/client.js.map +1 -1
  35. package/dist/container.d.ts.map +1 -1
  36. package/dist/container.js +1 -1
  37. package/dist/container.js.map +1 -1
  38. package/dist/deployer.d.ts +86 -0
  39. package/dist/deployer.d.ts.map +1 -0
  40. package/dist/deployer.js +154 -0
  41. package/dist/deployer.js.map +1 -0
  42. package/dist/expect.d.ts +27 -1
  43. package/dist/expect.d.ts.map +1 -1
  44. package/dist/expect.js +47 -2
  45. package/dist/expect.js.map +1 -1
  46. package/dist/index.d.ts +10 -3
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +39 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/preflight.d.ts +70 -0
  51. package/dist/preflight.d.ts.map +1 -0
  52. package/dist/preflight.js +121 -0
  53. package/dist/preflight.js.map +1 -0
  54. package/dist/reporter.d.ts +2 -0
  55. package/dist/reporter.d.ts.map +1 -1
  56. package/dist/reporter.js +24 -1
  57. package/dist/reporter.js.map +1 -1
  58. package/dist/runner.d.ts +83 -2
  59. package/dist/runner.d.ts.map +1 -1
  60. package/dist/runner.js +137 -8
  61. package/dist/runner.js.map +1 -1
  62. package/dist/snapshots.d.ts +84 -0
  63. package/dist/snapshots.d.ts.map +1 -0
  64. package/dist/snapshots.js +230 -0
  65. package/dist/snapshots.js.map +1 -0
  66. package/dist/validator/builtins.d.ts +18 -0
  67. package/dist/validator/builtins.d.ts.map +1 -0
  68. package/dist/validator/builtins.js +265 -0
  69. package/dist/validator/builtins.js.map +1 -0
  70. package/dist/validator/checker.d.ts +13 -0
  71. package/dist/validator/checker.d.ts.map +1 -0
  72. package/dist/validator/checker.js +111 -0
  73. package/dist/validator/checker.js.map +1 -0
  74. package/dist/validator/clobber.d.ts +7 -0
  75. package/dist/validator/clobber.d.ts.map +1 -0
  76. package/dist/validator/clobber.js +102 -0
  77. package/dist/validator/clobber.js.map +1 -0
  78. package/dist/validator/compat.d.ts +19 -0
  79. package/dist/validator/compat.d.ts.map +1 -0
  80. package/dist/validator/compat.js +58 -0
  81. package/dist/validator/compat.js.map +1 -0
  82. package/dist/validator/formatter.d.ts +21 -0
  83. package/dist/validator/formatter.d.ts.map +1 -0
  84. package/dist/validator/formatter.js +120 -0
  85. package/dist/validator/formatter.js.map +1 -0
  86. package/dist/validator/index.d.ts +57 -0
  87. package/dist/validator/index.d.ts.map +1 -0
  88. package/dist/validator/index.js +133 -0
  89. package/dist/validator/index.js.map +1 -0
  90. package/dist/validator/parser.d.ts +16 -0
  91. package/dist/validator/parser.d.ts.map +1 -0
  92. package/dist/validator/parser.js +260 -0
  93. package/dist/validator/parser.js.map +1 -0
  94. package/dist/validator/tokenizer.d.ts +28 -0
  95. package/dist/validator/tokenizer.d.ts.map +1 -0
  96. package/dist/validator/tokenizer.js +174 -0
  97. package/dist/validator/tokenizer.js.map +1 -0
  98. package/dist/validator/types.d.ts +55 -0
  99. package/dist/validator/types.d.ts.map +1 -0
  100. package/dist/validator/types.js +6 -0
  101. package/dist/validator/types.js.map +1 -0
  102. package/dist/watcher.d.ts +44 -0
  103. package/dist/watcher.d.ts.map +1 -0
  104. package/dist/watcher.js +297 -0
  105. package/dist/watcher.js.map +1 -0
  106. package/dist/world.d.ts +79 -0
  107. package/dist/world.d.ts.map +1 -1
  108. package/dist/world.js +167 -1
  109. package/dist/world.js.map +1 -1
  110. package/package.json +19 -3
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Softcode Formatter — serialize an AST back to a normalized string
4
+ // ---------------------------------------------------------------------------
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.format = format;
7
+ const tokenizer_1 = require("./tokenizer");
8
+ const parser_1 = require("./parser");
9
+ /**
10
+ * Format a MUSHcode expression.
11
+ *
12
+ * Compact mode (default): strips extra whitespace around `(`, `,`, `)` while
13
+ * preserving whitespace inside argument text.
14
+ *
15
+ * Pretty mode: additionally adds newlines + indentation at each nesting level
16
+ * for human readability. Not suitable for direct upload to a MUSH server.
17
+ */
18
+ function format(expr, options = {}) {
19
+ const tokens = (0, tokenizer_1.tokenize)(expr);
20
+ const { nodes } = (0, parser_1.parse)(tokens);
21
+ const serializer = new Serializer(options);
22
+ const formatted = serializer.serializeNodes(nodes, 0);
23
+ return { formatted, changed: formatted !== expr };
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Serializer
27
+ // ---------------------------------------------------------------------------
28
+ class Serializer {
29
+ constructor(opts) {
30
+ this.opts = opts;
31
+ }
32
+ serializeNodes(nodes, depth) {
33
+ return nodes.map(n => this.serializeNode(n, depth)).join('');
34
+ }
35
+ serializeNode(node, depth) {
36
+ switch (node.type) {
37
+ case 'FunctionCall': return this.serializeFunctionCall(node, depth);
38
+ case 'BracketEval': return this.serializeBracketEval(node, depth);
39
+ case 'Substitution': return node.raw;
40
+ case 'RawText': return node.value;
41
+ }
42
+ }
43
+ serializeFunctionCall(node, depth) {
44
+ const name = this.opts.lowercase ? node.name.toLowerCase() : node.name;
45
+ if (node.args.length === 0) {
46
+ return `${name}()`;
47
+ }
48
+ // Trim leading/trailing RawText whitespace from each arg
49
+ const trimmedArgs = node.args.map(arg => trimArgWhitespace(arg));
50
+ const pretty = this.opts.pretty;
51
+ // Pretty mode: wrap if any arg contains a function call
52
+ if (pretty && hasNestedCall(trimmedArgs)) {
53
+ const indent = ' '.repeat(depth + 1);
54
+ const closingIndent = ' '.repeat(depth);
55
+ const serializedArgs = trimmedArgs.map(arg => indent + this.serializeNodes(arg, depth + 1));
56
+ return `${name}(\n${serializedArgs.join(',\n')}\n${closingIndent})`;
57
+ }
58
+ const serializedArgs = trimmedArgs.map(arg => this.serializeNodes(arg, depth + 1));
59
+ return `${name}(${serializedArgs.join(',')})`;
60
+ }
61
+ serializeBracketEval(node, depth) {
62
+ return `[${this.serializeNodes(node.nodes, depth)}]`;
63
+ }
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Helpers
67
+ // ---------------------------------------------------------------------------
68
+ /** Trim leading and trailing RawText-whitespace-only nodes from an arg list. */
69
+ function trimArgWhitespace(arg) {
70
+ if (arg.length === 0)
71
+ return arg;
72
+ let start = 0;
73
+ let end = arg.length - 1;
74
+ // Trim leading whitespace-only RawText nodes
75
+ while (start <= end) {
76
+ const node = arg[start];
77
+ if (node.type === 'RawText' && node.value.trim() === '') {
78
+ start++;
79
+ }
80
+ else {
81
+ break;
82
+ }
83
+ }
84
+ // Trim trailing whitespace-only RawText nodes
85
+ while (end >= start) {
86
+ const node = arg[end];
87
+ if (node.type === 'RawText' && node.value.trim() === '') {
88
+ end--;
89
+ }
90
+ else {
91
+ break;
92
+ }
93
+ }
94
+ if (start > end)
95
+ return [];
96
+ const trimmed = arg.slice(start, end + 1);
97
+ // Also trim leading whitespace from the value of the first RawText node
98
+ // (handles cases like arg starting with a non-whitespace-only RawText that begins with spaces)
99
+ if (trimmed.length > 0 && trimmed[0].type === 'RawText') {
100
+ const first = trimmed[0];
101
+ const trimmedValue = first.value.trimStart();
102
+ if (trimmedValue !== first.value) {
103
+ trimmed[0] = { ...first, value: trimmedValue };
104
+ }
105
+ }
106
+ // And trailing whitespace from the last RawText node
107
+ if (trimmed.length > 0 && trimmed[trimmed.length - 1].type === 'RawText') {
108
+ const last = trimmed[trimmed.length - 1];
109
+ const trimmedValue = last.value.trimEnd();
110
+ if (trimmedValue !== last.value) {
111
+ trimmed[trimmed.length - 1] = { ...last, value: trimmedValue };
112
+ }
113
+ }
114
+ return trimmed;
115
+ }
116
+ /** Returns true if any arg list contains a FunctionCall node (for pretty indentation decisions). */
117
+ function hasNestedCall(args) {
118
+ return args.some(arg => arg.some(node => node.type === 'FunctionCall' || node.type === 'BracketEval'));
119
+ }
120
+ //# sourceMappingURL=formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../src/validator/formatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { CompatibilityReport } from './compat';
2
+ import { ValidationResult } from './types';
3
+ export { ValidationResult, Diagnostic, Severity } from './types';
4
+ export { FunctionSignature, BUILTIN_FUNCTIONS, Platform } from './builtins';
5
+ export { CompatibilityReport, CompatibilityEntry } from './compat';
6
+ /**
7
+ * Validate a RhostMUSH softcode expression without a server connection.
8
+ *
9
+ * The validator runs three stages:
10
+ * 1. **Tokenizer** — converts the expression to a flat token stream
11
+ * 2. **Parser** — builds an AST; detects structural errors (unbalanced parens/brackets)
12
+ * 3. **Semantic checker** — validates function names and argument counts
13
+ *
14
+ * `valid` is `true` unless at least one diagnostic has `severity: 'error'`.
15
+ * Warnings do not affect validity.
16
+ *
17
+ * @example
18
+ * import { validate } from '@rhost/testkit/validator';
19
+ *
20
+ * const result = validate('add(2,3)');
21
+ * // { valid: true, diagnostics: [] }
22
+ *
23
+ * const bad = validate('add(2,3');
24
+ * // { valid: false, diagnostics: [{ code: 'E001', severity: 'error', ... }] }
25
+ *
26
+ * @example Catch wrong arg count
27
+ * validate('abs(1,2)');
28
+ * // { valid: false, diagnostics: [{ code: 'E007', ... }] }
29
+ *
30
+ * @example Unknown function (warning, still valid)
31
+ * validate('myfunc(arg)');
32
+ * // { valid: true, diagnostics: [{ code: 'W005', severity: 'warning', ... }] }
33
+ */
34
+ export declare function validate(expr: string): ValidationResult;
35
+ /**
36
+ * Analyse a softcode expression for cross-platform dialect compatibility.
37
+ *
38
+ * Returns a `CompatibilityReport` listing any functions that are not
39
+ * available on all MUSH platforms (PennMUSH, TinyMUX, RhostMUSH).
40
+ *
41
+ * @example
42
+ * const report = compatibilityReport('encode64(hello)');
43
+ * // report.portable === false
44
+ * // report.restricted === [{ name: 'encode64', platforms: ['rhost'] }]
45
+ */
46
+ export declare function compatibilityReport(expr: string): CompatibilityReport;
47
+ /**
48
+ * Validate the softcode expression contained in a file.
49
+ *
50
+ * The entire file content is treated as a single expression.
51
+ * For files with multiple expressions (e.g. one per line) consider
52
+ * reading and validating each line individually.
53
+ *
54
+ * @throws If the file cannot be read.
55
+ */
56
+ export declare function validateFile(filePath: string): ValidationResult;
57
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validator/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAsB,mBAAmB,EAAsB,MAAM,UAAU,CAAC;AACvF,OAAO,EAAc,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAwBvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAOrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAG/D"}
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // Public API for the RhostMUSH softcode offline validator
4
+ // ---------------------------------------------------------------------------
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.BUILTIN_FUNCTIONS = void 0;
40
+ exports.validate = validate;
41
+ exports.compatibilityReport = compatibilityReport;
42
+ exports.validateFile = validateFile;
43
+ const fs = __importStar(require("fs"));
44
+ const tokenizer_1 = require("./tokenizer");
45
+ const parser_1 = require("./parser");
46
+ const checker_1 = require("./checker");
47
+ const clobber_1 = require("./clobber");
48
+ const compat_1 = require("./compat");
49
+ var builtins_1 = require("./builtins");
50
+ Object.defineProperty(exports, "BUILTIN_FUNCTIONS", { enumerable: true, get: function () { return builtins_1.BUILTIN_FUNCTIONS; } });
51
+ /**
52
+ * Validate a RhostMUSH softcode expression without a server connection.
53
+ *
54
+ * The validator runs three stages:
55
+ * 1. **Tokenizer** — converts the expression to a flat token stream
56
+ * 2. **Parser** — builds an AST; detects structural errors (unbalanced parens/brackets)
57
+ * 3. **Semantic checker** — validates function names and argument counts
58
+ *
59
+ * `valid` is `true` unless at least one diagnostic has `severity: 'error'`.
60
+ * Warnings do not affect validity.
61
+ *
62
+ * @example
63
+ * import { validate } from '@rhost/testkit/validator';
64
+ *
65
+ * const result = validate('add(2,3)');
66
+ * // { valid: true, diagnostics: [] }
67
+ *
68
+ * const bad = validate('add(2,3');
69
+ * // { valid: false, diagnostics: [{ code: 'E001', severity: 'error', ... }] }
70
+ *
71
+ * @example Catch wrong arg count
72
+ * validate('abs(1,2)');
73
+ * // { valid: false, diagnostics: [{ code: 'E007', ... }] }
74
+ *
75
+ * @example Unknown function (warning, still valid)
76
+ * validate('myfunc(arg)');
77
+ * // { valid: true, diagnostics: [{ code: 'W005', severity: 'warning', ... }] }
78
+ */
79
+ function validate(expr) {
80
+ // Empty expression: valid but warn
81
+ if (expr.trim() === '') {
82
+ const diagnostics = [
83
+ {
84
+ severity: 'warning',
85
+ code: 'W001',
86
+ message: 'Expression is empty',
87
+ offset: 0,
88
+ length: 0,
89
+ },
90
+ ];
91
+ return { valid: true, diagnostics };
92
+ }
93
+ const tokens = (0, tokenizer_1.tokenize)(expr);
94
+ const { nodes, diagnostics: structural } = (0, parser_1.parse)(tokens);
95
+ const semantic = (0, checker_1.semanticCheck)(nodes);
96
+ const clobber = (0, clobber_1.registerClobberCheck)(nodes);
97
+ const all = [...structural, ...semantic, ...clobber];
98
+ const valid = !all.some((d) => d.severity === 'error');
99
+ return { valid, diagnostics: all };
100
+ }
101
+ /**
102
+ * Analyse a softcode expression for cross-platform dialect compatibility.
103
+ *
104
+ * Returns a `CompatibilityReport` listing any functions that are not
105
+ * available on all MUSH platforms (PennMUSH, TinyMUX, RhostMUSH).
106
+ *
107
+ * @example
108
+ * const report = compatibilityReport('encode64(hello)');
109
+ * // report.portable === false
110
+ * // report.restricted === [{ name: 'encode64', platforms: ['rhost'] }]
111
+ */
112
+ function compatibilityReport(expr) {
113
+ if (expr.trim() === '') {
114
+ return { restricted: [], portable: true };
115
+ }
116
+ const tokens = (0, tokenizer_1.tokenize)(expr);
117
+ const { nodes } = (0, parser_1.parse)(tokens);
118
+ return (0, compat_1.compatibilityCheck)(nodes);
119
+ }
120
+ /**
121
+ * Validate the softcode expression contained in a file.
122
+ *
123
+ * The entire file content is treated as a single expression.
124
+ * For files with multiple expressions (e.g. one per line) consider
125
+ * reading and validating each line individually.
126
+ *
127
+ * @throws If the file cannot be read.
128
+ */
129
+ function validateFile(filePath) {
130
+ const content = fs.readFileSync(filePath, 'utf-8');
131
+ return validate(content);
132
+ }
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validator/index.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,0DAA0D;AAC1D,8EAA8E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0C9E,4BAwBC;AAaD,kDAOC;AAWD,oCAGC;AAlGD,uCAAyB;AACzB,2CAAuC;AACvC,qCAAiC;AACjC,uCAA0C;AAC1C,uCAAiD;AACjD,qCAAuF;AAIvF,uCAA4E;AAAhD,6GAAA,iBAAiB,OAAA;AAG7C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,SAAgB,QAAQ,CAAC,IAAY;IACnC,mCAAmC;IACnC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvB,MAAM,WAAW,GAAiB;YAChC;gBACE,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,qBAAqB;gBAC9B,MAAM,EAAE,CAAC;gBACT,MAAM,EAAE,CAAC;aACV;SACF,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACtC,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,oBAAQ,EAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,IAAA,cAAK,EAAC,MAAM,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,IAAA,uBAAa,EAAC,KAAK,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAA,8BAAoB,EAAC,KAAK,CAAC,CAAC;IAE5C,MAAM,GAAG,GAAG,CAAC,GAAG,UAAU,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAEvD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvB,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,MAAM,GAAG,IAAA,oBAAQ,EAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,EAAE,KAAK,EAAE,GAAG,IAAA,cAAK,EAAC,MAAM,CAAC,CAAC;IAChC,OAAO,IAAA,2BAAkB,EAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,YAAY,CAAC,QAAgB;IAC3C,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnD,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Token } from './tokenizer';
2
+ import { ASTNode, Diagnostic } from './types';
3
+ export interface ParseResult {
4
+ nodes: ASTNode[];
5
+ /** Structural diagnostics: unclosed parens/brackets, stray ), ], etc. */
6
+ diagnostics: Diagnostic[];
7
+ }
8
+ /**
9
+ * Parse a flat token stream into an AST.
10
+ *
11
+ * Structural errors (unbalanced parens/brackets) are embedded in `diagnostics`.
12
+ * The returned `nodes` array always contains the best-effort parse even when
13
+ * there are errors, enabling downstream semantic checks to still run.
14
+ */
15
+ export declare function parse(tokens: Token[]): ParseResult;
16
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/validator/parser.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,OAAO,EAAmB,UAAU,EAAmD,MAAM,SAAS,CAAC;AAEhH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,yEAAyE;IACzE,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AA4RD;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW,CAIlD"}
@@ -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"}