@rhost/testkit 1.5.0 → 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.
Files changed (157) hide show
  1. package/node_modules/@ursamu/mushcode/.github/workflows/publish.yml +36 -0
  2. package/node_modules/@ursamu/mushcode/LICENSE +21 -0
  3. package/node_modules/@ursamu/mushcode/README.md +110 -0
  4. package/node_modules/@ursamu/mushcode/_dist/mod.d.ts +36 -0
  5. package/node_modules/@ursamu/mushcode/_dist/mod.d.ts.map +1 -0
  6. package/node_modules/@ursamu/mushcode/_dist/parser/mod.d.ts +41 -0
  7. package/node_modules/@ursamu/mushcode/_dist/parser/mod.d.ts.map +1 -0
  8. package/node_modules/@ursamu/mushcode/_dist/src/analyze/commands.d.ts +15 -0
  9. package/node_modules/@ursamu/mushcode/_dist/src/analyze/commands.d.ts.map +1 -0
  10. package/node_modules/@ursamu/mushcode/_dist/src/analyze/deps.d.ts +18 -0
  11. package/node_modules/@ursamu/mushcode/_dist/src/analyze/deps.d.ts.map +1 -0
  12. package/node_modules/@ursamu/mushcode/_dist/src/analyze/mod.d.ts +20 -0
  13. package/node_modules/@ursamu/mushcode/_dist/src/analyze/mod.d.ts.map +1 -0
  14. package/node_modules/@ursamu/mushcode/_dist/src/analyze/tags.d.ts +6 -0
  15. package/node_modules/@ursamu/mushcode/_dist/src/analyze/tags.d.ts.map +1 -0
  16. package/node_modules/@ursamu/mushcode/_dist/src/eval/context.d.ts +85 -0
  17. package/node_modules/@ursamu/mushcode/_dist/src/eval/context.d.ts.map +1 -0
  18. package/node_modules/@ursamu/mushcode/_dist/src/eval/engine.d.ts +48 -0
  19. package/node_modules/@ursamu/mushcode/_dist/src/eval/engine.d.ts.map +1 -0
  20. package/node_modules/@ursamu/mushcode/_dist/src/eval/mod.d.ts +26 -0
  21. package/node_modules/@ursamu/mushcode/_dist/src/eval/mod.d.ts.map +1 -0
  22. package/node_modules/@ursamu/mushcode/_dist/src/eval/stdlib/mod.d.ts +3 -0
  23. package/node_modules/@ursamu/mushcode/_dist/src/eval/stdlib/mod.d.ts.map +1 -0
  24. package/node_modules/@ursamu/mushcode/_dist/src/lint/mod.d.ts +38 -0
  25. package/node_modules/@ursamu/mushcode/_dist/src/lint/mod.d.ts.map +1 -0
  26. package/node_modules/@ursamu/mushcode/_dist/src/print/mod.d.ts +18 -0
  27. package/node_modules/@ursamu/mushcode/_dist/src/print/mod.d.ts.map +1 -0
  28. package/node_modules/@ursamu/mushcode/_dist/src/print/printer.d.ts +15 -0
  29. package/node_modules/@ursamu/mushcode/_dist/src/print/printer.d.ts.map +1 -0
  30. package/node_modules/@ursamu/mushcode/_dist/src/traverse/mod.d.ts +19 -0
  31. package/node_modules/@ursamu/mushcode/_dist/src/traverse/mod.d.ts.map +1 -0
  32. package/node_modules/@ursamu/mushcode/_dist/src/traverse/transform.d.ts +27 -0
  33. package/node_modules/@ursamu/mushcode/_dist/src/traverse/transform.d.ts.map +1 -0
  34. package/node_modules/@ursamu/mushcode/_dist/src/traverse/walk.d.ts +27 -0
  35. package/node_modules/@ursamu/mushcode/_dist/src/traverse/walk.d.ts.map +1 -0
  36. package/node_modules/@ursamu/mushcode/deno.json +26 -0
  37. package/node_modules/@ursamu/mushcode/deno.lock +42 -0
  38. package/node_modules/@ursamu/mushcode/docs/analyze.md +145 -0
  39. package/node_modules/@ursamu/mushcode/docs/eval.md +312 -0
  40. package/node_modules/@ursamu/mushcode/docs/lint.md +152 -0
  41. package/node_modules/@ursamu/mushcode/docs/parser.md +196 -0
  42. package/node_modules/@ursamu/mushcode/docs/print.md +84 -0
  43. package/node_modules/@ursamu/mushcode/docs/stdlib.md +418 -0
  44. package/node_modules/@ursamu/mushcode/docs/traverse.md +167 -0
  45. package/node_modules/@ursamu/mushcode/grammar/mux-softcode.pegjs +781 -0
  46. package/node_modules/@ursamu/mushcode/mod.js +44 -0
  47. package/node_modules/@ursamu/mushcode/mod.js.map +1 -0
  48. package/node_modules/@ursamu/mushcode/mod.ts +63 -0
  49. package/node_modules/@ursamu/mushcode/package.json +38 -0
  50. package/node_modules/@ursamu/mushcode/parser/mod.js +47 -0
  51. package/node_modules/@ursamu/mushcode/parser/mod.js.map +1 -0
  52. package/node_modules/@ursamu/mushcode/parser/mod.ts +99 -0
  53. package/node_modules/@ursamu/mushcode/parser/mux-softcode.js +3833 -0
  54. package/node_modules/@ursamu/mushcode/parser/mux-softcode.mjs +3837 -0
  55. package/node_modules/@ursamu/mushcode/src/analyze/commands.js +29 -0
  56. package/node_modules/@ursamu/mushcode/src/analyze/commands.js.map +1 -0
  57. package/node_modules/@ursamu/mushcode/src/analyze/commands.ts +46 -0
  58. package/node_modules/@ursamu/mushcode/src/analyze/deps.js +45 -0
  59. package/node_modules/@ursamu/mushcode/src/analyze/deps.js.map +1 -0
  60. package/node_modules/@ursamu/mushcode/src/analyze/deps.ts +51 -0
  61. package/node_modules/@ursamu/mushcode/src/analyze/mod.js +18 -0
  62. package/node_modules/@ursamu/mushcode/src/analyze/mod.js.map +1 -0
  63. package/node_modules/@ursamu/mushcode/src/analyze/mod.ts +20 -0
  64. package/node_modules/@ursamu/mushcode/src/analyze/tags.js +11 -0
  65. package/node_modules/@ursamu/mushcode/src/analyze/tags.js.map +1 -0
  66. package/node_modules/@ursamu/mushcode/src/analyze/tags.ts +11 -0
  67. package/node_modules/@ursamu/mushcode/src/eval/context.js +22 -0
  68. package/node_modules/@ursamu/mushcode/src/eval/context.js.map +1 -0
  69. package/node_modules/@ursamu/mushcode/src/eval/context.ts +177 -0
  70. package/node_modules/@ursamu/mushcode/src/eval/engine.js +238 -0
  71. package/node_modules/@ursamu/mushcode/src/eval/engine.js.map +1 -0
  72. package/node_modules/@ursamu/mushcode/src/eval/engine.ts +276 -0
  73. package/node_modules/@ursamu/mushcode/src/eval/mod.js +25 -0
  74. package/node_modules/@ursamu/mushcode/src/eval/mod.js.map +1 -0
  75. package/node_modules/@ursamu/mushcode/src/eval/mod.ts +31 -0
  76. package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.js +56 -0
  77. package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.js.map +1 -0
  78. package/node_modules/@ursamu/mushcode/src/eval/stdlib/compare.ts +16 -0
  79. package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.js +91 -0
  80. package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.js.map +1 -0
  81. package/node_modules/@ursamu/mushcode/src/eval/stdlib/db.ts +104 -0
  82. package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.js +91 -0
  83. package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.js.map +1 -0
  84. package/node_modules/@ursamu/mushcode/src/eval/stdlib/iter.ts +98 -0
  85. package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.js +79 -0
  86. package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.js.map +1 -0
  87. package/node_modules/@ursamu/mushcode/src/eval/stdlib/logic.ts +84 -0
  88. package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.js +120 -0
  89. package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.js.map +1 -0
  90. package/node_modules/@ursamu/mushcode/src/eval/stdlib/math.ts +115 -0
  91. package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.js +17 -0
  92. package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.js.map +1 -0
  93. package/node_modules/@ursamu/mushcode/src/eval/stdlib/mod.ts +19 -0
  94. package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.js +28 -0
  95. package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.js.map +1 -0
  96. package/node_modules/@ursamu/mushcode/src/eval/stdlib/register.ts +31 -0
  97. package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.js +153 -0
  98. package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.js.map +1 -0
  99. package/node_modules/@ursamu/mushcode/src/eval/stdlib/string.ts +154 -0
  100. package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.js +212 -0
  101. package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.js.map +1 -0
  102. package/node_modules/@ursamu/mushcode/src/lint/builtin_arities.ts +68 -0
  103. package/node_modules/@ursamu/mushcode/src/lint/mod.js +60 -0
  104. package/node_modules/@ursamu/mushcode/src/lint/mod.js.map +1 -0
  105. package/node_modules/@ursamu/mushcode/src/lint/mod.ts +96 -0
  106. package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.js +37 -0
  107. package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.js.map +1 -0
  108. package/node_modules/@ursamu/mushcode/src/lint/rules/arg_count.ts +44 -0
  109. package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.js +55 -0
  110. package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.js.map +1 -0
  111. package/node_modules/@ursamu/mushcode/src/lint/rules/iter_var_outside_iter.ts +60 -0
  112. package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.js +31 -0
  113. package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.js.map +1 -0
  114. package/node_modules/@ursamu/mushcode/src/lint/rules/missing_wildcard.ts +40 -0
  115. package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.js +59 -0
  116. package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.js.map +1 -0
  117. package/node_modules/@ursamu/mushcode/src/lint/rules/register_before_set.ts +64 -0
  118. package/node_modules/@ursamu/mushcode/src/print/lock_printer.js +43 -0
  119. package/node_modules/@ursamu/mushcode/src/print/lock_printer.js.map +1 -0
  120. package/node_modules/@ursamu/mushcode/src/print/lock_printer.ts +41 -0
  121. package/node_modules/@ursamu/mushcode/src/print/mod.js +17 -0
  122. package/node_modules/@ursamu/mushcode/src/print/mod.js.map +1 -0
  123. package/node_modules/@ursamu/mushcode/src/print/mod.ts +18 -0
  124. package/node_modules/@ursamu/mushcode/src/print/printer.js +91 -0
  125. package/node_modules/@ursamu/mushcode/src/print/printer.js.map +1 -0
  126. package/node_modules/@ursamu/mushcode/src/print/printer.ts +132 -0
  127. package/node_modules/@ursamu/mushcode/src/traverse/child_slots.js +129 -0
  128. package/node_modules/@ursamu/mushcode/src/traverse/child_slots.js.map +1 -0
  129. package/node_modules/@ursamu/mushcode/src/traverse/child_slots.ts +51 -0
  130. package/node_modules/@ursamu/mushcode/src/traverse/mod.js +17 -0
  131. package/node_modules/@ursamu/mushcode/src/traverse/mod.js.map +1 -0
  132. package/node_modules/@ursamu/mushcode/src/traverse/mod.ts +19 -0
  133. package/node_modules/@ursamu/mushcode/src/traverse/transform.js +70 -0
  134. package/node_modules/@ursamu/mushcode/src/traverse/transform.js.map +1 -0
  135. package/node_modules/@ursamu/mushcode/src/traverse/transform.ts +84 -0
  136. package/node_modules/@ursamu/mushcode/src/traverse/walk.js +55 -0
  137. package/node_modules/@ursamu/mushcode/src/traverse/walk.js.map +1 -0
  138. package/node_modules/@ursamu/mushcode/src/traverse/walk.ts +82 -0
  139. package/node_modules/@ursamu/mushcode/tests/01-literals.test.ts +105 -0
  140. package/node_modules/@ursamu/mushcode/tests/02-substitutions.test.ts +145 -0
  141. package/node_modules/@ursamu/mushcode/tests/03-function-calls.test.ts +184 -0
  142. package/node_modules/@ursamu/mushcode/tests/04-eval-blocks.test.ts +110 -0
  143. package/node_modules/@ursamu/mushcode/tests/05-braced-strings.test.ts +119 -0
  144. package/node_modules/@ursamu/mushcode/tests/06-commands.test.ts +222 -0
  145. package/node_modules/@ursamu/mushcode/tests/07-dollar-patterns.test.ts +156 -0
  146. package/node_modules/@ursamu/mushcode/tests/08-lock-expressions.test.ts +159 -0
  147. package/node_modules/@ursamu/mushcode/tests/09-edge-cases.test.ts +162 -0
  148. package/node_modules/@ursamu/mushcode/tests/10-regression.test.ts +211 -0
  149. package/node_modules/@ursamu/mushcode/tests/11-tags.test.ts +357 -0
  150. package/node_modules/@ursamu/mushcode/tests/12-locations.test.ts +162 -0
  151. package/node_modules/@ursamu/mushcode/tests/13-eval.test.ts +389 -0
  152. package/node_modules/@ursamu/mushcode/tests/analyze.test.ts +194 -0
  153. package/node_modules/@ursamu/mushcode/tests/helpers.ts +69 -0
  154. package/node_modules/@ursamu/mushcode/tests/lint.test.ts +232 -0
  155. package/node_modules/@ursamu/mushcode/tests/print.test.ts +204 -0
  156. package/node_modules/@ursamu/mushcode/tests/traverse.test.ts +211 -0
  157. package/package.json +4 -1
@@ -0,0 +1,184 @@
1
+ // ============================================================================
2
+ // 03 — Function calls inside eval blocks
3
+ // ============================================================================
4
+
5
+ import { assertEquals } from "jsr:@std/assert@^1";
6
+ import { describe, it } from "jsr:/@std/testing@^1/bdd";
7
+ import { mustParse, findAll, findFirst } from "./helpers.ts";
8
+
9
+ function evalFn(src: string) {
10
+ const ast = mustParse(src);
11
+ return findFirst(ast, "FunctionCall");
12
+ }
13
+
14
+ describe("Zero-argument functions", () => {
15
+ it("lwho() → FunctionCall, args:[]", () => {
16
+ const fn = evalFn("[lwho()]");
17
+ assertEquals(fn.name, "lwho");
18
+ assertEquals(fn.args.length, 0);
19
+ });
20
+
21
+ it("secs() → FunctionCall, args:[]", () => {
22
+ const fn = evalFn("[secs()]");
23
+ assertEquals(fn.name, "secs");
24
+ assertEquals(fn.args.length, 0);
25
+ });
26
+
27
+ it("time() → FunctionCall, args:[]", () => {
28
+ const fn = evalFn("[time()]");
29
+ assertEquals(fn.name, "time");
30
+ assertEquals(fn.args.length, 0);
31
+ });
32
+ });
33
+
34
+ describe("One-argument functions", () => {
35
+ it("name(%#) → FunctionCall, 1 arg containing Substitution", () => {
36
+ const fn = evalFn("[name(%#)]");
37
+ assertEquals(fn.name, "name");
38
+ assertEquals(fn.args.length, 1);
39
+ assertEquals(fn.args[0].parts[0].type, "Substitution");
40
+ assertEquals(fn.args[0].parts[0].code, "#");
41
+ });
42
+
43
+ it("strlen(hello) → FunctionCall, 1 literal arg", () => {
44
+ const fn = evalFn("[strlen(hello)]");
45
+ assertEquals(fn.args[0].parts[0].value, "hello");
46
+ });
47
+ });
48
+
49
+ describe("Multi-argument functions", () => {
50
+ it("add(1,2) → two literal args", () => {
51
+ const fn = evalFn("[add(1,2)]");
52
+ assertEquals(fn.name, "add");
53
+ assertEquals(fn.args.length, 2);
54
+ assertEquals(fn.args[0].parts[0].value, "1");
55
+ assertEquals(fn.args[1].parts[0].value, "2");
56
+ });
57
+
58
+ it("if(gt(%0,10),big,small) → 3 args", () => {
59
+ const fn = evalFn("[if(gt(%0,10),big,small)]");
60
+ assertEquals(fn.name, "if");
61
+ assertEquals(fn.args.length, 3);
62
+ });
63
+
64
+ it("setq(0,hello) → 2 args, first is digit literal", () => {
65
+ const fn = evalFn("[setq(0,hello)]");
66
+ assertEquals(fn.args[0].parts[0].value, "0");
67
+ assertEquals(fn.args[1].parts[0].value, "hello");
68
+ });
69
+ });
70
+
71
+ describe("Empty arguments", () => {
72
+ it("setq(0,) → second arg is empty Arg node", () => {
73
+ const fn = evalFn("[setq(0,)]");
74
+ assertEquals(fn.args.length, 2);
75
+ assertEquals(fn.args[1].parts.length, 0);
76
+ });
77
+
78
+ it("f(,b) → first arg empty, second is 'b'", () => {
79
+ const fn = evalFn("[f(,b)]");
80
+ assertEquals(fn.args[0].parts.length, 0);
81
+ assertEquals(fn.args[1].parts[0].value, "b");
82
+ });
83
+
84
+ it("iter(list,,delim) → middle arg is empty", () => {
85
+ const fn = evalFn("[iter(list,,delim)]");
86
+ assertEquals(fn.args.length, 3);
87
+ assertEquals(fn.args[1].parts.length, 0);
88
+ });
89
+ });
90
+
91
+ describe("Nested function calls", () => {
92
+ it("add(mul(2,3),1) → outer add, inner mul", () => {
93
+ const add = evalFn("[add(mul(2,3),1)]");
94
+ assertEquals(add.name, "add");
95
+ const mul = findFirst(add.args[0], "FunctionCall");
96
+ assertEquals(mul.name, "mul");
97
+ assertEquals(mul.args[0].parts[0].value, "2");
98
+ assertEquals(mul.args[1].parts[0].value, "3");
99
+ });
100
+
101
+ it("if(gt(%0,10),big,small) → nested gt inside if", () => {
102
+ const ifFn = evalFn("[if(gt(%0,10),big,small)]");
103
+ const gt = findFirst(ifFn.args[0], "FunctionCall");
104
+ assertEquals(gt.name, "gt");
105
+ });
106
+
107
+ it("setq(0,pmatch(trim(%0))) → 3 levels of nesting", () => {
108
+ const fn = evalFn("[setq(0,pmatch(trim(%0)))]");
109
+ assertEquals(fn.name, "setq");
110
+ const pmatch = findFirst(fn.args[1], "FunctionCall");
111
+ assertEquals(pmatch.name, "pmatch");
112
+ const trim = findFirst(pmatch.args[0], "FunctionCall");
113
+ assertEquals(trim.name, "trim");
114
+ });
115
+ });
116
+
117
+ describe("Function call with substitutions in args", () => {
118
+ it("u(me/FN_FINGER,%0) → u with 2 args, second is Substitution(0)", () => {
119
+ const fn = evalFn("[u(me/FN_FINGER,%0)]");
120
+ assertEquals(fn.name, "u");
121
+ assertEquals(fn.args[0].parts[0].value, "me/FN_FINGER");
122
+ assertEquals(fn.args[1].parts[0].code, "0");
123
+ });
124
+
125
+ it("ansi(hg,SUCCESS) → 2 literal args", () => {
126
+ const fn = evalFn("[ansi(hg,SUCCESS)]");
127
+ assertEquals(fn.args[0].parts[0].value, "hg");
128
+ assertEquals(fn.args[1].parts[0].value, "SUCCESS");
129
+ });
130
+ });
131
+
132
+ describe("Back-to-back eval blocks", () => {
133
+ it("[setq(0,hello)][r(0)] → two FunctionCalls", () => {
134
+ const ast = mustParse("[setq(0,hello)][r(0)]");
135
+ const fns = findAll(ast, "FunctionCall");
136
+ assertEquals(fns.length, 2);
137
+ assertEquals(fns[0].name, "setq");
138
+ assertEquals(fns[1].name, "r");
139
+ });
140
+ });
141
+
142
+ describe("Function args with braces protecting commas", () => {
143
+ it("iter([lcon(%L)],{##: name},, ) → brace-protected output template", () => {
144
+ const fn = evalFn("[iter([lcon(%L)],{##: name},,)]");
145
+ assertEquals(fn.name, "iter");
146
+ assertEquals(fn.args[1].parts[0].type, "BracedString");
147
+ });
148
+ });
149
+
150
+ describe("Case-insensitive function names (grammar preserves case)", () => {
151
+ it("ADD(1,2) → name is 'ADD'", () => {
152
+ const fn = evalFn("[ADD(1,2)]");
153
+ assertEquals(fn.name, "ADD");
154
+ });
155
+
156
+ it("Strlen(x) → name is 'Strlen'", () => {
157
+ const fn = evalFn("[Strlen(x)]");
158
+ assertEquals(fn.name, "Strlen");
159
+ });
160
+ });
161
+
162
+ describe("Underscore-prefixed function names", () => {
163
+ it("_myfunc(x) → FunctionCall with name '_myfunc'", () => {
164
+ const fn = evalFn("[_myfunc(x)]");
165
+ assertEquals(fn.name, "_myfunc");
166
+ });
167
+ });
168
+
169
+ describe("Bare ( in argument (was broken before fix)", () => {
170
+ it("add(1,(2)) → second arg is Literal '(2)'", () => {
171
+ const fn = evalFn("[add(1,(2))]");
172
+ assertEquals(fn.args[1].parts[0].value, "(2)");
173
+ });
174
+
175
+ it("pemit(%#,(text)) → second arg contains literal (text)", () => {
176
+ const fn = evalFn("[pemit(%#,(text))]");
177
+ assertEquals(fn.args[1].parts[0].value, "(text)");
178
+ });
179
+
180
+ it("ansi(h,(bold text)) → literal (bold text) as arg", () => {
181
+ const fn = evalFn("[ansi(h,(bold text))]");
182
+ assertEquals(fn.args[1].parts[0].value, "(bold text)");
183
+ });
184
+ });
@@ -0,0 +1,110 @@
1
+ // ============================================================================
2
+ // 04 — Eval blocks [ ... ]
3
+ // ============================================================================
4
+
5
+ import { assertEquals } from "jsr:@std/assert@^1";
6
+ import { describe, it } from "jsr:/@std/testing@^1/bdd";
7
+ import { mustParse, findAll, findFirst } from "./helpers.ts";
8
+
9
+ describe("Basic eval blocks", () => {
10
+ it("[add(1,2)] → EvalBlock containing FunctionCall", () => {
11
+ const ast = mustParse("[add(1,2)]");
12
+ // Start always returns UserCommand; the eval block is its first part
13
+ assertEquals(ast.type, "UserCommand");
14
+ assertEquals(ast.parts[0].type, "EvalBlock");
15
+ });
16
+
17
+ it("[name(%#)] → EvalBlock with FunctionCall(name)", () => {
18
+ const ast = mustParse("[name(%#)]");
19
+ assertEquals(ast.parts[0].type, "EvalBlock");
20
+ const fn = findFirst(ast, "FunctionCall");
21
+ assertEquals(fn.name, "name");
22
+ });
23
+
24
+ it("text before eval: hello [name(%#)] → UserCommand with mixed parts", () => {
25
+ const ast = mustParse("hello [name(%#)]");
26
+ assertEquals(ast.type, "UserCommand");
27
+ const blocks = findAll(ast, "EvalBlock");
28
+ assertEquals(blocks.length, 1);
29
+ });
30
+ });
31
+
32
+ describe("Nested eval blocks", () => {
33
+ it("[[lwho()]] → outer EvalBlock wrapping inner EvalBlock", () => {
34
+ const ast = mustParse("[[lwho()]]");
35
+ assertEquals(ast.parts[0].type, "EvalBlock");
36
+ const inner = findAll(ast, "EvalBlock");
37
+ // outer + inner
38
+ assertEquals(inner.length >= 2, true);
39
+ });
40
+
41
+ it("[add([mul(2,3)],1)] → nested function in arg", () => {
42
+ const outer = findFirst(mustParse("[add([mul(2,3)],1)]"), "FunctionCall");
43
+ assertEquals(outer.name, "add");
44
+ const innerBlock = findFirst(outer.args[0], "EvalBlock");
45
+ assertEquals(innerBlock.type, "EvalBlock");
46
+ const mul = findFirst(innerBlock, "FunctionCall");
47
+ assertEquals(mul.name, "mul");
48
+ });
49
+ });
50
+
51
+ describe("Multiple eval blocks in sequence", () => {
52
+ it("[setq(0,hello)][r(0)] → two top-level EvalBlocks", () => {
53
+ const ast = mustParse("[setq(0,hello)][r(0)]");
54
+ assertEquals(ast.type, "UserCommand");
55
+ const blocks = findAll(ast, "EvalBlock");
56
+ assertEquals(blocks.length, 2);
57
+ });
58
+
59
+ it("Name: [name(%#)] (Loc: [loc(%#)]) → two blocks interleaved in text", () => {
60
+ const ast = mustParse("Name: [name(%#)] Loc: [loc(%#)]");
61
+ const blocks = findAll(ast, "EvalBlock");
62
+ assertEquals(blocks.length, 2);
63
+ });
64
+ });
65
+
66
+ describe("Eval block in AtCommand value", () => {
67
+ it("@pemit %#=Hello [name(%#)]! → EvalBlock in value", () => {
68
+ const ast = mustParse("@pemit %#=Hello [name(%#)]!");
69
+ assertEquals(ast.type, "AtCommand");
70
+ assertEquals(ast.name, "pemit");
71
+ const blocks = findAll(ast.value, "EvalBlock");
72
+ assertEquals(blocks.length, 1);
73
+ });
74
+ });
75
+
76
+ describe("Eval block in dollar pattern action", () => {
77
+ it("$+finger *:@pemit %#=[u(me/FN_FINGER,%0)] → EvalBlock in action", () => {
78
+ const ast = mustParse("$+finger *:@pemit %#=[u(me/FN_FINGER,%0)]");
79
+ assertEquals(ast.type, "DollarPattern");
80
+ const blocks = findAll(ast, "EvalBlock");
81
+ assertEquals(blocks.length >= 1, true);
82
+ const fn = findFirst(ast, "FunctionCall");
83
+ assertEquals(fn.name, "u");
84
+ });
85
+ });
86
+
87
+ describe("Eval blocks with substitutions", () => {
88
+ it("[r(%q0)] → EvalBlock with FunctionCall using register sub", () => {
89
+ const ast = mustParse("[r(%q0)]");
90
+ const fn = findFirst(ast, "FunctionCall");
91
+ assertEquals(fn.name, "r");
92
+ assertEquals(fn.args[0].parts[0].type, "Substitution");
93
+ assertEquals(fn.args[0].parts[0].code, "q0"); // %q0 → register named q0
94
+ });
95
+
96
+ it("[r(%qA)] → register %qA works in eval (was broken)", () => {
97
+ const ast = mustParse("[r(%qA)]");
98
+ const fn = findFirst(ast, "FunctionCall");
99
+ assertEquals(fn.args[0].parts[0].code, "qA");
100
+ });
101
+ });
102
+
103
+ describe("Eval block containing only literal text", () => {
104
+ it("[hello] → EvalBlock with Literal parts", () => {
105
+ const ast = mustParse("[hello]");
106
+ assertEquals(ast.parts[0].type, "EvalBlock");
107
+ const lits = findAll(ast, "Literal");
108
+ assertEquals(lits[0].value, "hello");
109
+ });
110
+ });
@@ -0,0 +1,119 @@
1
+ // ============================================================================
2
+ // 05 — Braced strings { ... }
3
+ // ============================================================================
4
+
5
+ import { assertEquals } from "jsr:@std/assert@^1";
6
+ import { describe, it } from "jsr:/@std/testing@^1/bdd";
7
+ import { mustParse, findAll, findFirst } from "./helpers.ts";
8
+
9
+ describe("Basic braced strings", () => {
10
+ it("{text} → BracedString with Literal", () => {
11
+ // Start returns UserCommand; the braced string is the first (and only) part
12
+ const ast = mustParse("{text}");
13
+ assertEquals(ast.parts[0].type, "BracedString");
14
+ assertEquals(ast.parts[0].parts[0].value, "text");
15
+ });
16
+
17
+ it("{} → empty BracedString", () => {
18
+ const ast = mustParse("{}");
19
+ assertEquals(ast.parts[0].type, "BracedString");
20
+ assertEquals(ast.parts[0].parts.length, 0);
21
+ });
22
+
23
+ it("{don't;split;this} → semicolons are literals inside braces", () => {
24
+ const ast = mustParse("{don't;split;this}");
25
+ assertEquals(ast.parts[0].type, "BracedString");
26
+ // Only one BracedString — the semicolons did not create a CommandList
27
+ assertEquals(findAll(ast, "CommandList").length, 0);
28
+ });
29
+
30
+ it("{a,b,c} → commas are literals inside braces", () => {
31
+ const ast = mustParse("{a,b,c}");
32
+ assertEquals(ast.parts[0].type, "BracedString");
33
+ // All text with commas coalesced into one Literal
34
+ assertEquals(findAll(ast, "Literal")[0].value, "a,b,c");
35
+ });
36
+ });
37
+
38
+ describe("Nested braces", () => {
39
+ it("{ outer { inner } more } → nested BracedStrings", () => {
40
+ const ast = mustParse("{ outer { inner } more }");
41
+ assertEquals(ast.parts[0].type, "BracedString");
42
+ const nested = findAll(ast.parts[0], "BracedString");
43
+ // outer BracedString + one inner
44
+ assertEquals(nested.length, 2);
45
+ });
46
+
47
+ it("{{{deep}}} → triple nesting", () => {
48
+ const ast = mustParse("{{{deep}}}");
49
+ const all = findAll(ast, "BracedString");
50
+ assertEquals(all.length, 3);
51
+ });
52
+ });
53
+
54
+ describe("Eval blocks still active inside braces", () => {
55
+ it("{[add(1,2)]} → EvalBlock inside BracedString", () => {
56
+ const ast = mustParse("{[add(1,2)]}");
57
+ assertEquals(ast.parts[0].type, "BracedString");
58
+ const block = findFirst(ast, "EvalBlock");
59
+ assertEquals(block.type, "EvalBlock");
60
+ });
61
+
62
+ it("{@pemit %#=Hello, %0!} → substitution inside brace", () => {
63
+ const ast = mustParse("{@pemit %#=Hello, %0!}");
64
+ const subs = findAll(ast, "Substitution");
65
+ assertEquals(subs.length >= 1, true);
66
+ });
67
+ });
68
+
69
+ describe("Braces in command list actions", () => {
70
+ it("@dolist lwho()={@pemit ##=Restart in 5 min.} → single command in brace", () => {
71
+ const ast = mustParse("@dolist lwho()={@pemit ##=Restart in 5 min.}");
72
+ assertEquals(ast.type, "AtCommand");
73
+ assertEquals(ast.name, "dolist");
74
+ // The value is a BracedString wrapping the @pemit command
75
+ const brace = findFirst(ast.value, "BracedString");
76
+ assertEquals(brace.type, "BracedString");
77
+ });
78
+
79
+ it("@switch x=1,{big},{small} → brace-protected command bodies", () => {
80
+ const ast = mustParse("@switch x=1,{big},{small}");
81
+ assertEquals(ast.type, "AtCommand");
82
+ const braces = findAll(ast.value, "BracedString");
83
+ assertEquals(braces.length, 2);
84
+ });
85
+ });
86
+
87
+ describe("Braces in function arguments protect commas", () => {
88
+ it("iter(list,{##: score},, ) → brace protects colon-space in template", () => {
89
+ const ast = mustParse("[iter(list,{##: score},,)]");
90
+ const fn = findFirst(ast, "FunctionCall");
91
+ assertEquals(fn.name, "iter");
92
+ assertEquals(fn.args[1].parts[0].type, "BracedString");
93
+ });
94
+
95
+ it("f({a,b}) → single arg because comma is inside braces", () => {
96
+ const fn = findFirst(mustParse("[f({a,b})]"), "FunctionCall");
97
+ assertEquals(fn.args.length, 1);
98
+ assertEquals(fn.args[0].parts[0].type, "BracedString");
99
+ });
100
+ });
101
+
102
+ describe("Braces with special chars that would otherwise break parsing", () => {
103
+ it("{use \\; here} → escape inside brace", () => {
104
+ const ast = mustParse("{use \\; here}");
105
+ const esc = findAll(ast, "Escape");
106
+ assertEquals(esc.length, 1);
107
+ assertEquals(esc[0].char, ";");
108
+ });
109
+
110
+ it("{has = sign} → equals sign is literal inside brace", () => {
111
+ const ast = mustParse("{has = sign}");
112
+ assertEquals(ast.parts[0].type, "BracedString");
113
+ // should parse without treating = as command separator
114
+ const lits = findAll(ast.parts[0], "Literal");
115
+ // deno-lint-ignore no-explicit-any
116
+ const combined = lits.map((l) => (l as any).value as string).join("");
117
+ assertEquals(combined.includes("="), true);
118
+ });
119
+ });
@@ -0,0 +1,222 @@
1
+ // ============================================================================
2
+ // 06 — Commands: @commands, &attribute-set, user commands, command lists
3
+ // ============================================================================
4
+
5
+ import { assertEquals } from "jsr:@std/assert@^1";
6
+ import { describe, it } from "jsr:/@std/testing@^1/bdd";
7
+ import { mustParse, findAll, findFirst } from "./helpers.ts";
8
+
9
+ // ── @commands ─────────────────────────────────────────────────────────────────
10
+
11
+ describe("@commands — basic", () => {
12
+ it("@pemit %#=Hello → AtCommand(pemit), object=%#, value=Hello", () => {
13
+ const ast = mustParse("@pemit %#=Hello");
14
+ assertEquals(ast.type, "AtCommand");
15
+ assertEquals(ast.name, "pemit");
16
+ assertEquals(ast.switches.length, 0);
17
+ assertEquals(ast.object.parts[0].type, "Substitution");
18
+ assertEquals(ast.object.parts[0].code, "#");
19
+ });
20
+
21
+ it("@set me=SAFE → AtCommand(set), object=me, value=SAFE", () => {
22
+ const ast = mustParse("@set me=SAFE");
23
+ assertEquals(ast.type, "AtCommand");
24
+ assertEquals(ast.name, "set");
25
+ assertEquals(ast.object.parts[0].value, "me");
26
+ });
27
+
28
+ it("@examine → AtCommand with no body (object:null, value:null)", () => {
29
+ const ast = mustParse("@examine");
30
+ assertEquals(ast.type, "AtCommand");
31
+ assertEquals(ast.name, "examine");
32
+ assertEquals(ast.object, null);
33
+ assertEquals(ast.value, null);
34
+ });
35
+
36
+ it("@examine Wizard → AtCommand with object only, no =value", () => {
37
+ const ast = mustParse("@examine Wizard");
38
+ assertEquals(ast.type, "AtCommand");
39
+ assertEquals(ast.object.parts[0].value, "Wizard");
40
+ assertEquals(ast.value, null);
41
+ });
42
+ });
43
+
44
+ describe("@commands — switches", () => {
45
+ it("@lock/enter me=flag^WIZARD → one switch 'enter'", () => {
46
+ const ast = mustParse("@lock/enter me=flag^WIZARD");
47
+ assertEquals(ast.type, "AtCommand");
48
+ assertEquals(ast.name, "lock");
49
+ assertEquals(ast.switches, ["enter"]);
50
+ });
51
+
52
+ it("@pemit/list/noeval %#=x → two switches", () => {
53
+ const ast = mustParse("@pemit/list/noeval %#=x");
54
+ assertEquals(ast.switches, ["list", "noeval"]);
55
+ });
56
+
57
+ it("@trigger me/ATTR=arg1,arg2 → no switches, object=me/ATTR", () => {
58
+ const ast = mustParse("@trigger me/ATTR=arg1,arg2");
59
+ assertEquals(ast.name, "trigger");
60
+ assertEquals(ast.switches.length, 0);
61
+ });
62
+ });
63
+
64
+ describe("@commands — complex objects and values", () => {
65
+ it("@pemit %#=[u(me/FN,%0)] → eval block in value", () => {
66
+ const ast = mustParse("@pemit %#=[u(me/FN,%0)]");
67
+ const blocks = findAll(ast.value, "EvalBlock");
68
+ assertEquals(blocks.length, 1);
69
+ });
70
+
71
+ it("@trigger #123/ATTR=hello → dbref object", () => {
72
+ const ast = mustParse("@trigger #123/ATTR=hello");
73
+ assertEquals(ast.name, "trigger");
74
+ });
75
+
76
+ it("@dolist [lwho()]={@pemit ##=Restart} → eval in object position", () => {
77
+ const ast = mustParse("@dolist [lwho()]={@pemit ##=Restart}");
78
+ assertEquals(ast.name, "dolist");
79
+ const block = findFirst(ast.object, "EvalBlock");
80
+ assertEquals(block.type, "EvalBlock");
81
+ });
82
+
83
+ it("@switch [gt(%0,10)]=1,{big},{small} → value has BracedStrings", () => {
84
+ const ast = mustParse("@switch [gt(%0,10)]=1,{big},{small}");
85
+ assertEquals(ast.name, "switch");
86
+ const braces = findAll(ast.value, "BracedString");
87
+ assertEquals(braces.length, 2);
88
+ });
89
+ });
90
+
91
+ // ── &attribute-set ────────────────────────────────────────────────────────────
92
+
93
+ describe("&attribute-set commands", () => {
94
+ it("&DATA_SCORE me=100 → AttributeSet", () => {
95
+ const ast = mustParse("&DATA_SCORE me=100");
96
+ assertEquals(ast.type, "AttributeSet");
97
+ assertEquals(ast.attribute, "DATA_SCORE");
98
+ assertEquals(ast.value.parts[0].value, "100");
99
+ });
100
+
101
+ it("&FN_ADD me=[add(%0,%1)] → value is EvalBlock", () => {
102
+ const ast = mustParse("&FN_ADD me=[add(%0,%1)]");
103
+ assertEquals(ast.type, "AttributeSet");
104
+ assertEquals(ast.attribute, "FN_ADD");
105
+ const block = findFirst(ast.value, "EvalBlock");
106
+ assertEquals(block.type, "EvalBlock");
107
+ });
108
+
109
+ it("&CMD_FINGER Global=$+finger * → sets a dollar-pattern value", () => {
110
+ const ast = mustParse("&CMD_FINGER Global=$+finger *:@pemit %#=Hello");
111
+ assertEquals(ast.type, "AttributeSet");
112
+ assertEquals(ast.value.type, "DollarPattern");
113
+ });
114
+
115
+ it("&SCORE me (no =) → AttributeSet with value:null (clear form)", () => {
116
+ const ast = mustParse("&SCORE me");
117
+ assertEquals(ast.type, "AttributeSet");
118
+ assertEquals(ast.attribute, "SCORE");
119
+ assertEquals(ast.value, null);
120
+ });
121
+
122
+ it("&MY-ATTR me=x → hyphen in attribute name", () => {
123
+ const ast = mustParse("&MY-ATTR me=x");
124
+ assertEquals(ast.attribute, "MY-ATTR");
125
+ });
126
+ });
127
+
128
+ // ── RhostMUSH hidden attribute convention (_-prefix) ─────────────────────────
129
+
130
+ describe("hidden attribute flag (_-prefix, RhostMUSH)", () => {
131
+ it("&PUBLIC_ATTR me=val → hidden:false", () => {
132
+ const ast = mustParse("&PUBLIC_ATTR me=val");
133
+ assertEquals(ast.hidden, false);
134
+ });
135
+
136
+ it("&_INTERNAL me=val → hidden:true", () => {
137
+ const ast = mustParse("&_INTERNAL me=val");
138
+ assertEquals(ast.type, "AttributeSet");
139
+ assertEquals(ast.hidden, true);
140
+ });
141
+
142
+ it("&_COR_STATE me=active → hidden:true (_COR_ system prefix)", () => {
143
+ const ast = mustParse("&_COR_STATE me=active");
144
+ assertEquals(ast.hidden, true);
145
+ assertEquals(ast.attribute, "_COR_STATE");
146
+ });
147
+
148
+ it("&_SECRET me (clear form) → hidden:true, value:null", () => {
149
+ const ast = mustParse("&_SECRET me");
150
+ assertEquals(ast.hidden, true);
151
+ assertEquals(ast.value, null);
152
+ });
153
+
154
+ it("&_FN_HELPER me=[add(%0,%1)] → hidden:true with eval value", () => {
155
+ const ast = mustParse("&_FN_HELPER me=[add(%0,%1)]");
156
+ assertEquals(ast.hidden, true);
157
+ assertEquals(ast.type, "AttributeSet");
158
+ });
159
+
160
+ it("hidden flag survives a command list", () => {
161
+ const ast = mustParse("&PUBLIC me=1;&_HIDDEN me=2");
162
+ assertEquals(ast.type, "CommandList");
163
+ assertEquals(ast.commands[0].hidden, false);
164
+ assertEquals(ast.commands[1].hidden, true);
165
+ });
166
+ });
167
+
168
+ // ── User commands ─────────────────────────────────────────────────────────────
169
+
170
+ describe("User / soft commands", () => {
171
+ it("say Hello → UserCommand", () => {
172
+ const ast = mustParse("say Hello");
173
+ assertEquals(ast.type, "UserCommand");
174
+ });
175
+
176
+ it("go north → UserCommand", () => {
177
+ const ast = mustParse("go north");
178
+ assertEquals(ast.type, "UserCommand");
179
+ });
180
+
181
+ it("+finger Bob → UserCommand starting with +", () => {
182
+ const ast = mustParse("+finger Bob");
183
+ assertEquals(ast.type, "UserCommand");
184
+ });
185
+
186
+ it("look → UserCommand", () => {
187
+ const ast = mustParse("look");
188
+ assertEquals(ast.type, "UserCommand");
189
+ });
190
+ });
191
+
192
+ // ── Command lists ─────────────────────────────────────────────────────────────
193
+
194
+ describe("Command lists (semicolon-separated)", () => {
195
+ it("@pemit %#=Hi;@pemit %#=Bye → CommandList with two AtCommands", () => {
196
+ const ast = mustParse("@pemit %#=Hi;@pemit %#=Bye");
197
+ assertEquals(ast.type, "CommandList");
198
+ assertEquals(ast.commands.length, 2);
199
+ assertEquals(ast.commands[0].type, "AtCommand");
200
+ assertEquals(ast.commands[1].type, "AtCommand");
201
+ });
202
+
203
+ it("say Hi;go north;look → CommandList with 3 commands", () => {
204
+ const ast = mustParse("say Hi;go north;look");
205
+ assertEquals(ast.type, "CommandList");
206
+ assertEquals(ast.commands.length, 3);
207
+ });
208
+
209
+ it("single command → returned directly, not wrapped in CommandList", () => {
210
+ const ast = mustParse("@pemit %#=Hello");
211
+ assertEquals(ast.type, "AtCommand");
212
+ });
213
+
214
+ it("{cmd;cmd};cmd → brace protects inner semicolons from list split", () => {
215
+ const ast = mustParse("{cmd1;cmd2};cmd3");
216
+ assertEquals(ast.type, "CommandList");
217
+ assertEquals(ast.commands.length, 2);
218
+ // First command is a UserCommand whose only part is the BracedString
219
+ assertEquals(ast.commands[0].type, "UserCommand");
220
+ assertEquals(ast.commands[0].parts[0].type, "BracedString");
221
+ });
222
+ });