@futpib/parser 1.0.4 → 1.0.6

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 (250) hide show
  1. package/.claude/settings.local.json +24 -0
  2. package/.github/workflows/main.yml +1 -0
  3. package/build/androidPackageParser.js +30 -32
  4. package/build/arbitraryDalvikBytecode.d.ts +3 -3
  5. package/build/arbitraryDalvikBytecode.js +33 -27
  6. package/build/arbitraryDalvikExecutable.js +55 -17
  7. package/build/arbitraryJava.d.ts +31 -0
  8. package/build/arbitraryJava.js +532 -0
  9. package/build/arbitraryJavaScript.d.ts +3 -0
  10. package/build/arbitraryJavaScript.js +263 -0
  11. package/build/arbitraryJavascript.d.ts +3 -0
  12. package/build/arbitraryJavascript.js +263 -0
  13. package/build/arbitraryZig.d.ts +3 -0
  14. package/build/arbitraryZig.js +240 -0
  15. package/build/arbitraryZipStream.d.ts +1 -1
  16. package/build/arrayParser.js +72 -13
  17. package/build/backsmali.d.ts +4 -3
  18. package/build/backsmali.js +26 -6
  19. package/build/bash.d.ts +6 -1
  20. package/build/bashParser.js +131 -90
  21. package/build/bashParser.test.js +162 -0
  22. package/build/bashParserEdgeCases.test.d.ts +1 -0
  23. package/build/bashParserEdgeCases.test.js +117 -0
  24. package/build/dalvikBytecodeParser/addressConversion.d.ts +110 -0
  25. package/build/dalvikBytecodeParser/addressConversion.js +334 -0
  26. package/build/dalvikBytecodeParser/formatParsers.d.ts +7 -6
  27. package/build/dalvikBytecodeParser/formatParsers.js +13 -14
  28. package/build/dalvikBytecodeParser.d.ts +60 -31
  29. package/build/dalvikBytecodeParser.js +92 -35
  30. package/build/dalvikBytecodeParser.test-d.d.ts +1 -0
  31. package/build/dalvikBytecodeParser.test-d.js +268 -0
  32. package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +9 -8
  33. package/build/dalvikBytecodeUnparser/formatUnparsers.js +13 -12
  34. package/build/dalvikBytecodeUnparser.d.ts +2 -2
  35. package/build/dalvikBytecodeUnparser.js +23 -23
  36. package/build/dalvikBytecodeUnparser.test.js +7 -7
  37. package/build/dalvikExecutable.d.ts +3 -3
  38. package/build/dalvikExecutable.test-d.d.ts +1 -0
  39. package/build/dalvikExecutable.test-d.js +59 -0
  40. package/build/dalvikExecutableParser/typedNumbers.d.ts +18 -0
  41. package/build/dalvikExecutableParser/typedNumbers.js +3 -0
  42. package/build/dalvikExecutableParser.d.ts +2 -1
  43. package/build/dalvikExecutableParser.js +96 -77
  44. package/build/dalvikExecutableParser.test.js +24 -3
  45. package/build/dalvikExecutableParserAgainstSmaliParser.test.js +3 -0
  46. package/build/dalvikExecutableUnparser/poolScanners.d.ts +2 -2
  47. package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +3 -3
  48. package/build/dalvikExecutableUnparser/sectionUnparsers.js +26 -11
  49. package/build/dalvikExecutableUnparser.d.ts +2 -2
  50. package/build/dalvikExecutableUnparser.test.js +2 -1
  51. package/build/disjunctionParser.d.ts +5 -3
  52. package/build/disjunctionParser.js +79 -17
  53. package/build/disjunctionParser.test-d.d.ts +1 -0
  54. package/build/disjunctionParser.test-d.js +72 -0
  55. package/build/elementSwitchParser.d.ts +4 -0
  56. package/build/{exactElementSwitchParser.js → elementSwitchParser.js} +3 -4
  57. package/build/elementSwitchParser.test-d.d.ts +1 -0
  58. package/build/elementSwitchParser.test-d.js +44 -0
  59. package/build/exactSequenceParser.d.ts +4 -2
  60. package/build/exactSequenceParser.test-d.d.ts +1 -0
  61. package/build/exactSequenceParser.test-d.js +36 -0
  62. package/build/fetchCid.js +2 -66
  63. package/build/index.d.ts +3 -2
  64. package/build/index.js +2 -1
  65. package/build/index.test.js +16 -1
  66. package/build/inputReader.d.ts +10 -0
  67. package/build/inputReader.js +36 -0
  68. package/build/java.d.ts +502 -0
  69. package/build/java.js +2 -0
  70. package/build/javaKeyStoreParser.js +14 -17
  71. package/build/javaParser.d.ts +51 -0
  72. package/build/javaParser.js +1538 -0
  73. package/build/javaParser.test.d.ts +1 -0
  74. package/build/javaParser.test.js +1287 -0
  75. package/build/javaScript.d.ts +35 -0
  76. package/build/javaScript.js +1 -0
  77. package/build/javaScriptParser.d.ts +9 -0
  78. package/build/javaScriptParser.js +34 -0
  79. package/build/javaScriptUnparser.d.ts +3 -0
  80. package/build/javaScriptUnparser.js +4 -0
  81. package/build/javaScriptUnparser.test.d.ts +1 -0
  82. package/build/javaScriptUnparser.test.js +24 -0
  83. package/build/javaUnparser.d.ts +2 -0
  84. package/build/javaUnparser.js +519 -0
  85. package/build/javaUnparser.test.d.ts +1 -0
  86. package/build/javaUnparser.test.js +24 -0
  87. package/build/javascript.d.ts +35 -0
  88. package/build/javascript.js +1 -0
  89. package/build/javascriptParser.d.ts +9 -0
  90. package/build/javascriptParser.js +34 -0
  91. package/build/javascriptUnparser.d.ts +3 -0
  92. package/build/javascriptUnparser.js +4 -0
  93. package/build/javascriptUnparser.test.d.ts +1 -0
  94. package/build/javascriptUnparser.test.js +24 -0
  95. package/build/jsonParser.js +2 -12
  96. package/build/lazyMessageError.d.ts +3 -0
  97. package/build/lookaheadParser.js +60 -3
  98. package/build/negativeLookaheadParser.js +70 -11
  99. package/build/nonEmptyArrayParser.js +72 -13
  100. package/build/objectParser.d.ts +12 -0
  101. package/build/objectParser.js +31 -0
  102. package/build/objectParser.test-d.d.ts +1 -0
  103. package/build/objectParser.test-d.js +112 -0
  104. package/build/objectParser.test.d.ts +1 -0
  105. package/build/objectParser.test.js +55 -0
  106. package/build/optionalParser.js +69 -10
  107. package/build/parser.d.ts +4 -0
  108. package/build/parser.js +3 -1
  109. package/build/parser.test.js +114 -1
  110. package/build/parserConsumedSequenceParser.js +66 -7
  111. package/build/parserContext.d.ts +6 -0
  112. package/build/parserContext.js +20 -11
  113. package/build/parserError.d.ts +119 -27
  114. package/build/parserError.js +16 -8
  115. package/build/regexpParser.js +33 -3
  116. package/build/regexpParser.test.js +31 -0
  117. package/build/regularExpressionParser.js +35 -15
  118. package/build/separatedArrayParser.js +73 -14
  119. package/build/separatedNonEmptyArrayParser.js +73 -14
  120. package/build/sliceBoundedParser.js +62 -5
  121. package/build/smaliParser.d.ts +7 -7
  122. package/build/smaliParser.js +185 -268
  123. package/build/smaliParser.test.js +58 -0
  124. package/build/stringEscapes.d.ts +5 -0
  125. package/build/stringEscapes.js +244 -0
  126. package/build/symbolicExpression.d.ts +29 -0
  127. package/build/symbolicExpression.js +1 -0
  128. package/build/symbolicExpressionParser.d.ts +4 -0
  129. package/build/symbolicExpressionParser.js +123 -0
  130. package/build/symbolicExpressionParser.test.d.ts +1 -0
  131. package/build/symbolicExpressionParser.test.js +289 -0
  132. package/build/terminatedArrayParser.js +113 -38
  133. package/build/terminatedArrayParser.test.js +4 -2
  134. package/build/tupleParser.d.ts +7 -15
  135. package/build/tupleParser.js +1 -0
  136. package/build/unionParser.d.ts +5 -3
  137. package/build/unionParser.js +7 -2
  138. package/build/unionParser.test-d.d.ts +1 -0
  139. package/build/unionParser.test-d.js +72 -0
  140. package/build/unionParser.test.js +10 -11
  141. package/build/zig.d.ts +280 -0
  142. package/build/zig.js +2 -0
  143. package/build/zigParser.d.ts +3 -0
  144. package/build/zigParser.js +1119 -0
  145. package/build/zigParser.test.d.ts +1 -0
  146. package/build/zigParser.test.js +1590 -0
  147. package/build/zigUnparser.d.ts +2 -0
  148. package/build/zigUnparser.js +460 -0
  149. package/build/zigUnparser.test.d.ts +1 -0
  150. package/build/zigUnparser.test.js +24 -0
  151. package/build/zipParser.js +19 -32
  152. package/build/zipUnparser.js +19 -7
  153. package/build/zipUnparser.test.js +1 -1
  154. package/node_modules-@types/s-expression/index.d.ts +5 -0
  155. package/package.json +24 -6
  156. package/src/androidPackageParser.ts +33 -60
  157. package/src/arbitraryDalvikBytecode.ts +39 -31
  158. package/src/arbitraryDalvikExecutable.ts +65 -20
  159. package/src/arbitraryJava.ts +804 -0
  160. package/src/arbitraryJavaScript.ts +410 -0
  161. package/src/arbitraryZig.ts +380 -0
  162. package/src/arrayParser.ts +1 -3
  163. package/src/backsmali.ts +35 -4
  164. package/src/bash.ts +8 -1
  165. package/src/bashParser.test.ts +258 -0
  166. package/src/bashParser.ts +180 -143
  167. package/src/dalvikBytecodeParser/addressConversion.ts +496 -0
  168. package/src/dalvikBytecodeParser/formatParsers.ts +19 -29
  169. package/src/dalvikBytecodeParser.test-d.ts +310 -0
  170. package/src/dalvikBytecodeParser.ts +194 -69
  171. package/src/dalvikBytecodeUnparser/formatUnparsers.ts +27 -26
  172. package/src/dalvikBytecodeUnparser.test.ts +7 -7
  173. package/src/dalvikBytecodeUnparser.ts +31 -30
  174. package/src/dalvikExecutable.test-d.ts +132 -0
  175. package/src/dalvikExecutable.ts +3 -3
  176. package/src/dalvikExecutableParser/typedNumbers.ts +11 -0
  177. package/src/dalvikExecutableParser.test.ts +37 -3
  178. package/src/dalvikExecutableParser.test.ts.md +163 -2
  179. package/src/dalvikExecutableParser.test.ts.snap +0 -0
  180. package/src/dalvikExecutableParser.ts +121 -139
  181. package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +4 -0
  182. package/src/dalvikExecutableUnparser/poolScanners.ts +6 -6
  183. package/src/dalvikExecutableUnparser/sectionUnparsers.ts +38 -14
  184. package/src/dalvikExecutableUnparser.test.ts +3 -2
  185. package/src/dalvikExecutableUnparser.ts +4 -4
  186. package/src/disjunctionParser.test-d.ts +105 -0
  187. package/src/disjunctionParser.ts +18 -15
  188. package/src/elementSwitchParser.test-d.ts +74 -0
  189. package/src/elementSwitchParser.ts +51 -0
  190. package/src/exactSequenceParser.test-d.ts +43 -0
  191. package/src/exactSequenceParser.ts +13 -8
  192. package/src/fetchCid.ts +2 -76
  193. package/src/index.test.ts +22 -1
  194. package/src/index.ts +7 -1
  195. package/src/inputReader.ts +53 -0
  196. package/src/java.ts +708 -0
  197. package/src/javaKeyStoreParser.ts +18 -32
  198. package/src/javaParser.test.ts +1592 -0
  199. package/src/javaParser.ts +2640 -0
  200. package/src/javaScript.ts +36 -0
  201. package/src/javaScriptParser.ts +57 -0
  202. package/src/javaScriptUnparser.test.ts +37 -0
  203. package/src/javaScriptUnparser.ts +7 -0
  204. package/src/javaUnparser.test.ts +37 -0
  205. package/src/javaUnparser.ts +640 -0
  206. package/src/jsonParser.ts +6 -27
  207. package/src/lookaheadParser.ts +2 -6
  208. package/src/negativeLookaheadParser.ts +1 -3
  209. package/src/nonEmptyArrayParser.ts +1 -3
  210. package/src/objectParser.test-d.ts +152 -0
  211. package/src/objectParser.test.ts +71 -0
  212. package/src/objectParser.ts +69 -0
  213. package/src/optionalParser.ts +1 -3
  214. package/src/parser.test.ts +151 -4
  215. package/src/parser.ts +11 -1
  216. package/src/parserConsumedSequenceParser.ts +2 -4
  217. package/src/parserContext.ts +26 -11
  218. package/src/parserError.ts +17 -3
  219. package/src/regexpParser.test.ts +78 -0
  220. package/src/regexpParser.ts +35 -3
  221. package/src/regularExpressionParser.ts +36 -37
  222. package/src/separatedArrayParser.ts +1 -3
  223. package/src/separatedNonEmptyArrayParser.ts +1 -3
  224. package/src/sliceBoundedParser.test.ts +2 -2
  225. package/src/sliceBoundedParser.ts +15 -19
  226. package/src/smaliParser.test.ts +64 -0
  227. package/src/smaliParser.test.ts.md +12 -12
  228. package/src/smaliParser.test.ts.snap +0 -0
  229. package/src/smaliParser.ts +246 -534
  230. package/src/stringEscapes.ts +253 -0
  231. package/src/symbolicExpression.ts +17 -0
  232. package/src/symbolicExpressionParser.test.ts +466 -0
  233. package/src/symbolicExpressionParser.ts +190 -0
  234. package/src/terminatedArrayParser.test.ts +9 -6
  235. package/src/terminatedArrayParser.ts +25 -29
  236. package/src/tupleParser.ts +21 -18
  237. package/src/unionParser.test-d.ts +105 -0
  238. package/src/unionParser.test.ts +18 -17
  239. package/src/unionParser.ts +28 -16
  240. package/src/zig.ts +411 -0
  241. package/src/zigParser.test.ts +1693 -0
  242. package/src/zigParser.ts +1745 -0
  243. package/src/zigUnparser.test.ts +37 -0
  244. package/src/zigUnparser.ts +615 -0
  245. package/src/zipParser.ts +20 -56
  246. package/src/zipUnparser.test.ts +1 -1
  247. package/src/zipUnparser.ts +22 -7
  248. package/tsconfig.json +2 -2
  249. package/build/exactElementSwitchParser.d.ts +0 -3
  250. package/src/exactElementSwitchParser.ts +0 -41
@@ -0,0 +1,466 @@
1
+ import test from 'ava';
2
+ import { testProp, fc } from '@fast-check/ava';
3
+ import sExpression from 's-expression';
4
+ import { runParser } from './parser.js';
5
+
6
+ const seed = process.env.SEED ? Number(process.env.SEED) : undefined;
7
+ import { stringParserInputCompanion } from './parserInputCompanion.js';
8
+ import {
9
+ symbolicExpressionParser,
10
+ symbolicExpressionDocumentParser,
11
+ } from './symbolicExpressionParser.js';
12
+ import { type SymbolicExpression } from './symbolicExpression.js';
13
+
14
+ // Convert s-expression package output to our discriminated union format
15
+ function convertToDiscriminatedUnion(value: unknown): SymbolicExpression {
16
+ if (Array.isArray(value)) {
17
+ // Handle quote forms: ['quote', x], ['quasiquote', x], etc.
18
+ if (value.length === 2 && typeof value[0] === 'string') {
19
+ const [type, inner] = value;
20
+ if (type === 'quote') {
21
+ return { type: 'quote', value: convertToDiscriminatedUnion(inner) };
22
+ }
23
+
24
+ if (type === 'quasiquote') {
25
+ return { type: 'quasiquote', value: convertToDiscriminatedUnion(inner) };
26
+ }
27
+
28
+ if (type === 'unquote') {
29
+ return { type: 'unquote', value: convertToDiscriminatedUnion(inner) };
30
+ }
31
+
32
+ if (type === 'unquote-splicing') {
33
+ return { type: 'unquote-splicing', value: convertToDiscriminatedUnion(inner) };
34
+ }
35
+ }
36
+
37
+ return { type: 'list', value: value.map(convertToDiscriminatedUnion) };
38
+ }
39
+
40
+ if (value instanceof String) {
41
+ return { type: 'string', value: value.valueOf() };
42
+ }
43
+
44
+ if (typeof value === 'string') {
45
+ return { type: 'atom', value };
46
+ }
47
+
48
+ throw new Error(`Unknown value type: ${typeof value}`);
49
+ }
50
+
51
+ // Basic atom parsing
52
+ test('atom - simple', async t => {
53
+ const result = await runParser(
54
+ symbolicExpressionParser,
55
+ 'a',
56
+ stringParserInputCompanion,
57
+ );
58
+
59
+ t.deepEqual(result, { type: 'atom', value: 'a' });
60
+ });
61
+
62
+ test('atom - with numbers', async t => {
63
+ const result = await runParser(
64
+ symbolicExpressionParser,
65
+ 'abc123',
66
+ stringParserInputCompanion,
67
+ );
68
+
69
+ t.deepEqual(result, { type: 'atom', value: 'abc123' });
70
+ });
71
+
72
+ // List parsing
73
+ test('list - empty', async t => {
74
+ const result = await runParser(
75
+ symbolicExpressionParser,
76
+ '()',
77
+ stringParserInputCompanion,
78
+ );
79
+
80
+ t.deepEqual(result, { type: 'list', value: [] });
81
+ });
82
+
83
+ test('list - single element', async t => {
84
+ const result = await runParser(
85
+ symbolicExpressionParser,
86
+ '(a)',
87
+ stringParserInputCompanion,
88
+ );
89
+
90
+ t.deepEqual(result, {
91
+ type: 'list',
92
+ value: [{ type: 'atom', value: 'a' }],
93
+ });
94
+ });
95
+
96
+ test('list - multiple elements', async t => {
97
+ const result = await runParser(
98
+ symbolicExpressionParser,
99
+ '(a b c)',
100
+ stringParserInputCompanion,
101
+ );
102
+
103
+ t.deepEqual(result, {
104
+ type: 'list',
105
+ value: [
106
+ { type: 'atom', value: 'a' },
107
+ { type: 'atom', value: 'b' },
108
+ { type: 'atom', value: 'c' },
109
+ ],
110
+ });
111
+ });
112
+
113
+ test('list - nested', async t => {
114
+ const result = await runParser(
115
+ symbolicExpressionParser,
116
+ '((a b c)(()()))',
117
+ stringParserInputCompanion,
118
+ );
119
+
120
+ t.deepEqual(result, {
121
+ type: 'list',
122
+ value: [
123
+ {
124
+ type: 'list',
125
+ value: [
126
+ { type: 'atom', value: 'a' },
127
+ { type: 'atom', value: 'b' },
128
+ { type: 'atom', value: 'c' },
129
+ ],
130
+ },
131
+ {
132
+ type: 'list',
133
+ value: [
134
+ { type: 'list', value: [] },
135
+ { type: 'list', value: [] },
136
+ ],
137
+ },
138
+ ],
139
+ });
140
+ });
141
+
142
+ // String parsing
143
+ test('string - simple', async t => {
144
+ const result = await runParser(
145
+ symbolicExpressionParser,
146
+ '"hello"',
147
+ stringParserInputCompanion,
148
+ );
149
+
150
+ t.deepEqual(result, { type: 'string', value: 'hello' });
151
+ });
152
+
153
+ test('string - with escape sequences', async t => {
154
+ const result = await runParser(
155
+ symbolicExpressionParser,
156
+ '"hello\\nworld"',
157
+ stringParserInputCompanion,
158
+ );
159
+
160
+ t.deepEqual(result, { type: 'string', value: 'hello\nworld' });
161
+ });
162
+
163
+ test('string - in list', async t => {
164
+ const result = await runParser(
165
+ symbolicExpressionParser,
166
+ '(a "hello")',
167
+ stringParserInputCompanion,
168
+ );
169
+
170
+ t.deepEqual(result, {
171
+ type: 'list',
172
+ value: [
173
+ { type: 'atom', value: 'a' },
174
+ { type: 'string', value: 'hello' },
175
+ ],
176
+ });
177
+ });
178
+
179
+ // Quote parsing
180
+ test('quote - atom', async t => {
181
+ const result = await runParser(
182
+ symbolicExpressionParser,
183
+ "'a",
184
+ stringParserInputCompanion,
185
+ );
186
+
187
+ t.deepEqual(result, {
188
+ type: 'quote',
189
+ value: { type: 'atom', value: 'a' },
190
+ });
191
+ });
192
+
193
+ test('quote - empty list', async t => {
194
+ const result = await runParser(
195
+ symbolicExpressionParser,
196
+ "'()",
197
+ stringParserInputCompanion,
198
+ );
199
+
200
+ t.deepEqual(result, {
201
+ type: 'quote',
202
+ value: { type: 'list', value: [] },
203
+ });
204
+ });
205
+
206
+ test('quote - list', async t => {
207
+ const result = await runParser(
208
+ symbolicExpressionParser,
209
+ "'(a b c)",
210
+ stringParserInputCompanion,
211
+ );
212
+
213
+ t.deepEqual(result, {
214
+ type: 'quote',
215
+ value: {
216
+ type: 'list',
217
+ value: [
218
+ { type: 'atom', value: 'a' },
219
+ { type: 'atom', value: 'b' },
220
+ { type: 'atom', value: 'c' },
221
+ ],
222
+ },
223
+ });
224
+ });
225
+
226
+ test('quote - nested', async t => {
227
+ const result = await runParser(
228
+ symbolicExpressionParser,
229
+ "''a",
230
+ stringParserInputCompanion,
231
+ );
232
+
233
+ t.deepEqual(result, {
234
+ type: 'quote',
235
+ value: {
236
+ type: 'quote',
237
+ value: { type: 'atom', value: 'a' },
238
+ },
239
+ });
240
+ });
241
+
242
+ // Quasiquote parsing
243
+ test('quasiquote - atom', async t => {
244
+ const result = await runParser(
245
+ symbolicExpressionParser,
246
+ '`a',
247
+ stringParserInputCompanion,
248
+ );
249
+
250
+ t.deepEqual(result, {
251
+ type: 'quasiquote',
252
+ value: { type: 'atom', value: 'a' },
253
+ });
254
+ });
255
+
256
+ test('quasiquote - list', async t => {
257
+ const result = await runParser(
258
+ symbolicExpressionParser,
259
+ '`(a b c)',
260
+ stringParserInputCompanion,
261
+ );
262
+
263
+ t.deepEqual(result, {
264
+ type: 'quasiquote',
265
+ value: {
266
+ type: 'list',
267
+ value: [
268
+ { type: 'atom', value: 'a' },
269
+ { type: 'atom', value: 'b' },
270
+ { type: 'atom', value: 'c' },
271
+ ],
272
+ },
273
+ });
274
+ });
275
+
276
+ // Unquote parsing
277
+ test('unquote - atom', async t => {
278
+ const result = await runParser(
279
+ symbolicExpressionParser,
280
+ ',a',
281
+ stringParserInputCompanion,
282
+ );
283
+
284
+ t.deepEqual(result, {
285
+ type: 'unquote',
286
+ value: { type: 'atom', value: 'a' },
287
+ });
288
+ });
289
+
290
+ test('unquote - list', async t => {
291
+ const result = await runParser(
292
+ symbolicExpressionParser,
293
+ ',(a b c)',
294
+ stringParserInputCompanion,
295
+ );
296
+
297
+ t.deepEqual(result, {
298
+ type: 'unquote',
299
+ value: {
300
+ type: 'list',
301
+ value: [
302
+ { type: 'atom', value: 'a' },
303
+ { type: 'atom', value: 'b' },
304
+ { type: 'atom', value: 'c' },
305
+ ],
306
+ },
307
+ });
308
+ });
309
+
310
+ // Unquote-splicing parsing
311
+ test('unquote-splicing - atom', async t => {
312
+ const result = await runParser(
313
+ symbolicExpressionParser,
314
+ ',@a',
315
+ stringParserInputCompanion,
316
+ );
317
+
318
+ t.deepEqual(result, {
319
+ type: 'unquote-splicing',
320
+ value: { type: 'atom', value: 'a' },
321
+ });
322
+ });
323
+
324
+ test('unquote-splicing - list', async t => {
325
+ const result = await runParser(
326
+ symbolicExpressionParser,
327
+ ',@(a b c)',
328
+ stringParserInputCompanion,
329
+ );
330
+
331
+ t.deepEqual(result, {
332
+ type: 'unquote-splicing',
333
+ value: {
334
+ type: 'list',
335
+ value: [
336
+ { type: 'atom', value: 'a' },
337
+ { type: 'atom', value: 'b' },
338
+ { type: 'atom', value: 'c' },
339
+ ],
340
+ },
341
+ });
342
+ });
343
+
344
+ // Complex expressions
345
+ test('complex - quote inside list', async t => {
346
+ const result = await runParser(
347
+ symbolicExpressionParser,
348
+ "(a '(a b c))",
349
+ stringParserInputCompanion,
350
+ );
351
+
352
+ t.deepEqual(result, {
353
+ type: 'list',
354
+ value: [
355
+ { type: 'atom', value: 'a' },
356
+ {
357
+ type: 'quote',
358
+ value: {
359
+ type: 'list',
360
+ value: [
361
+ { type: 'atom', value: 'a' },
362
+ { type: 'atom', value: 'b' },
363
+ { type: 'atom', value: 'c' },
364
+ ],
365
+ },
366
+ },
367
+ ],
368
+ });
369
+ });
370
+
371
+ test('complex - multiple quotes in list', async t => {
372
+ const result = await runParser(
373
+ symbolicExpressionParser,
374
+ "((a 'b 'c))",
375
+ stringParserInputCompanion,
376
+ );
377
+
378
+ t.deepEqual(result, {
379
+ type: 'list',
380
+ value: [
381
+ {
382
+ type: 'list',
383
+ value: [
384
+ { type: 'atom', value: 'a' },
385
+ { type: 'quote', value: { type: 'atom', value: 'b' } },
386
+ { type: 'quote', value: { type: 'atom', value: 'c' } },
387
+ ],
388
+ },
389
+ ],
390
+ });
391
+ });
392
+
393
+ // Document parser with whitespace
394
+ test('document - with leading whitespace', async t => {
395
+ const result = await runParser(
396
+ symbolicExpressionDocumentParser,
397
+ ' a',
398
+ stringParserInputCompanion,
399
+ );
400
+
401
+ t.deepEqual(result, { type: 'atom', value: 'a' });
402
+ });
403
+
404
+ test('document - with trailing whitespace', async t => {
405
+ const result = await runParser(
406
+ symbolicExpressionDocumentParser,
407
+ 'a ',
408
+ stringParserInputCompanion,
409
+ );
410
+
411
+ t.deepEqual(result, { type: 'atom', value: 'a' });
412
+ });
413
+
414
+ // Arbitrary symbolic expression generator for property-based testing
415
+ const arbitraryAtomChar = fc.string({ minLength: 1, maxLength: 10 }).filter(s =>
416
+ s.length > 0 && !/[\s()"'`,;]/.test(s),
417
+ );
418
+
419
+ const arbitraryStringContent = fc.string({ maxLength: 20 }).map(s =>
420
+ // Escape special characters for string literals
421
+ s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t'),
422
+ );
423
+
424
+ const arbitrarySymbolicExpression: fc.Arbitrary<string> = fc.letrec(tie => ({
425
+ atom: arbitraryAtomChar,
426
+ string: arbitraryStringContent.map(s => `"${s}"`),
427
+ list: fc.array(tie('expression') as fc.Arbitrary<string>, { maxLength: 5 }).map(exprs => `(${exprs.join(' ')})`),
428
+ quote: (tie('expression') as fc.Arbitrary<string>).map(e => `'${e}`),
429
+ quasiquote: (tie('expression') as fc.Arbitrary<string>).map(e => `\`${e}`),
430
+ unquote: (tie('expression') as fc.Arbitrary<string>).map(e => `,${e}`),
431
+ unquoteSplicing: (tie('expression') as fc.Arbitrary<string>).map(e => `,@${e}`),
432
+ expression: fc.oneof(
433
+ { weight: 5, arbitrary: tie('atom') as fc.Arbitrary<string> },
434
+ { weight: 2, arbitrary: tie('string') as fc.Arbitrary<string> },
435
+ { weight: 3, arbitrary: tie('list') as fc.Arbitrary<string> },
436
+ { weight: 1, arbitrary: tie('quote') as fc.Arbitrary<string> },
437
+ { weight: 1, arbitrary: tie('quasiquote') as fc.Arbitrary<string> },
438
+ { weight: 1, arbitrary: tie('unquote') as fc.Arbitrary<string> },
439
+ { weight: 1, arbitrary: tie('unquoteSplicing') as fc.Arbitrary<string> },
440
+ ),
441
+ })).expression as fc.Arbitrary<string>;
442
+
443
+ // Property-based test comparing with s-expression package
444
+ testProp(
445
+ 'matches s-expression package output',
446
+ [arbitrarySymbolicExpression],
447
+ async (t, input) => {
448
+ const reference = sExpression(input);
449
+
450
+ if (reference instanceof Error) {
451
+ // If s-expression fails, our parser should also fail
452
+ await t.throwsAsync(
453
+ () => runParser(symbolicExpressionParser, input, stringParserInputCompanion),
454
+ );
455
+ } else {
456
+ const expected = convertToDiscriminatedUnion(reference);
457
+ const actual = await runParser(
458
+ symbolicExpressionParser,
459
+ input,
460
+ stringParserInputCompanion,
461
+ );
462
+ t.deepEqual(actual, expected);
463
+ }
464
+ },
465
+ { seed },
466
+ );
@@ -0,0 +1,190 @@
1
+ import { type Parser, setParserName } from './parser.js';
2
+ import { createDisjunctionParser } from './disjunctionParser.js';
3
+ import { createExactSequenceParser } from './exactSequenceParser.js';
4
+ import { promiseCompose } from './promiseCompose.js';
5
+ import { createTupleParser } from './tupleParser.js';
6
+ import { createArrayParser } from './arrayParser.js';
7
+ import { createParserAccessorParser } from './parserAccessorParser.js';
8
+ import { createRegExpParser } from './regexpParser.js';
9
+ import {
10
+ type SymbolicExpression,
11
+ type SymbolicExpressionAtom,
12
+ type SymbolicExpressionString,
13
+ type SymbolicExpressionList,
14
+ type SymbolicExpressionQuote,
15
+ type SymbolicExpressionQuasiquote,
16
+ type SymbolicExpressionUnquote,
17
+ type SymbolicExpressionUnquoteSplicing,
18
+ } from './symbolicExpression.js';
19
+
20
+ // Whitespace parser (spaces, tabs, newlines)
21
+ const symbolicExpressionWhitespaceParser: Parser<string, string> = promiseCompose(
22
+ createRegExpParser(/\s+/),
23
+ match => match[0],
24
+ );
25
+
26
+ setParserName(symbolicExpressionWhitespaceParser, 'symbolicExpressionWhitespaceParser');
27
+
28
+ const symbolicExpressionOptionalWhitespaceParser: Parser<string, string> = promiseCompose(
29
+ createRegExpParser(/\s*/),
30
+ match => match[0],
31
+ );
32
+
33
+ setParserName(symbolicExpressionOptionalWhitespaceParser, 'symbolicExpressionOptionalWhitespaceParser');
34
+
35
+ // String literal parser: "..." with escape sequences
36
+ const symbolicExpressionStringParser: Parser<SymbolicExpressionString, string> = promiseCompose(
37
+ createRegExpParser(/"(?:[^"\\]|\\.)*"/s),
38
+ match => {
39
+ // Remove surrounding quotes and process escape sequences in a single pass
40
+ const raw = match[0].slice(1, -1);
41
+ const value = raw.replace(/\\(.)/gs, (_, char: string) => {
42
+ switch (char) {
43
+ case 'n': return '\n';
44
+ case 'r': return '\r';
45
+ case 't': return '\t';
46
+ case 'f': return '\f';
47
+ case 'b': return '\b';
48
+ case '"': return '"';
49
+ case '\\': return '\\';
50
+ default: return char;
51
+ }
52
+ });
53
+ return {
54
+ type: 'string' as const,
55
+ value,
56
+ };
57
+ },
58
+ );
59
+
60
+ setParserName(symbolicExpressionStringParser, 'symbolicExpressionStringParser');
61
+
62
+ // Atom parser: unquoted symbols (any chars except whitespace, parens, quotes, etc.)
63
+ // Supports backslash escapes: \x becomes x, trailing \ becomes nothing
64
+ const symbolicExpressionAtomParser: Parser<SymbolicExpressionAtom, string> = promiseCompose(
65
+ createRegExpParser(/(?:[^\s()"'`,;\\]|\\.)+\\?|\\$/),
66
+ match => {
67
+ const raw = match[0];
68
+ // Process backslash escapes: \x becomes x, trailing \ becomes nothing
69
+ const value = raw.replace(/\\(.?)/g, '$1');
70
+ return {
71
+ type: 'atom' as const,
72
+ value,
73
+ };
74
+ },
75
+ );
76
+
77
+ setParserName(symbolicExpressionAtomParser, 'symbolicExpressionAtomParser');
78
+
79
+ // Quote parser: 'expr
80
+ const symbolicExpressionQuoteParser: Parser<SymbolicExpressionQuote, string> = promiseCompose(
81
+ createTupleParser([
82
+ createExactSequenceParser("'"),
83
+ symbolicExpressionOptionalWhitespaceParser,
84
+ createParserAccessorParser(() => symbolicExpressionParser),
85
+ ]),
86
+ ([, , expr]) => ({
87
+ type: 'quote' as const,
88
+ value: expr,
89
+ }),
90
+ );
91
+
92
+ setParserName(symbolicExpressionQuoteParser, 'symbolicExpressionQuoteParser');
93
+
94
+ // Quasiquote parser: `expr
95
+ const symbolicExpressionQuasiquoteParser: Parser<SymbolicExpressionQuasiquote, string> = promiseCompose(
96
+ createTupleParser([
97
+ createExactSequenceParser('`'),
98
+ symbolicExpressionOptionalWhitespaceParser,
99
+ createParserAccessorParser(() => symbolicExpressionParser),
100
+ ]),
101
+ ([, , expr]) => ({
102
+ type: 'quasiquote' as const,
103
+ value: expr,
104
+ }),
105
+ );
106
+
107
+ setParserName(symbolicExpressionQuasiquoteParser, 'symbolicExpressionQuasiquoteParser');
108
+
109
+ // Unquote-splicing parser: ,@expr (must come before unquote)
110
+ const symbolicExpressionUnquoteSplicingParser: Parser<SymbolicExpressionUnquoteSplicing, string> = promiseCompose(
111
+ createTupleParser([
112
+ createExactSequenceParser(',@'),
113
+ symbolicExpressionOptionalWhitespaceParser,
114
+ createParserAccessorParser(() => symbolicExpressionParser),
115
+ ]),
116
+ ([, , expr]) => ({
117
+ type: 'unquote-splicing' as const,
118
+ value: expr,
119
+ }),
120
+ );
121
+
122
+ setParserName(symbolicExpressionUnquoteSplicingParser, 'symbolicExpressionUnquoteSplicingParser');
123
+
124
+ // Unquote parser: ,expr
125
+ const symbolicExpressionUnquoteParser: Parser<SymbolicExpressionUnquote, string> = promiseCompose(
126
+ createTupleParser([
127
+ createExactSequenceParser(','),
128
+ symbolicExpressionOptionalWhitespaceParser,
129
+ createParserAccessorParser(() => symbolicExpressionParser),
130
+ ]),
131
+ ([, , expr]) => ({
132
+ type: 'unquote' as const,
133
+ value: expr,
134
+ }),
135
+ );
136
+
137
+ setParserName(symbolicExpressionUnquoteParser, 'symbolicExpressionUnquoteParser');
138
+
139
+ // List element parser with optional whitespace
140
+ const symbolicExpressionListElementParser: Parser<SymbolicExpression, string> = promiseCompose(
141
+ createTupleParser([
142
+ createParserAccessorParser(() => symbolicExpressionParser),
143
+ symbolicExpressionOptionalWhitespaceParser,
144
+ ]),
145
+ ([expr]) => expr,
146
+ );
147
+
148
+ setParserName(symbolicExpressionListElementParser, 'symbolicExpressionListElementParser');
149
+
150
+ // List parser: (...)
151
+ const symbolicExpressionListParser: Parser<SymbolicExpressionList, string> = promiseCompose(
152
+ createTupleParser([
153
+ createExactSequenceParser('('),
154
+ symbolicExpressionOptionalWhitespaceParser,
155
+ createArrayParser(symbolicExpressionListElementParser),
156
+ createExactSequenceParser(')'),
157
+ ]),
158
+ ([, , elements]) => ({
159
+ type: 'list' as const,
160
+ value: elements,
161
+ }),
162
+ );
163
+
164
+ setParserName(symbolicExpressionListParser, 'symbolicExpressionListParser');
165
+
166
+ // Main expression parser (union of all expression types)
167
+ // Order matters: unquote-splicing before unquote, etc.
168
+ export const symbolicExpressionParser: Parser<SymbolicExpression, string> = createDisjunctionParser([
169
+ symbolicExpressionListParser,
170
+ symbolicExpressionStringParser,
171
+ symbolicExpressionQuoteParser,
172
+ symbolicExpressionQuasiquoteParser,
173
+ symbolicExpressionUnquoteSplicingParser,
174
+ symbolicExpressionUnquoteParser,
175
+ symbolicExpressionAtomParser,
176
+ ]);
177
+
178
+ setParserName(symbolicExpressionParser, 'symbolicExpressionParser');
179
+
180
+ // Top-level parser that handles leading/trailing whitespace
181
+ export const symbolicExpressionDocumentParser: Parser<SymbolicExpression, string> = promiseCompose(
182
+ createTupleParser([
183
+ symbolicExpressionOptionalWhitespaceParser,
184
+ symbolicExpressionParser,
185
+ symbolicExpressionOptionalWhitespaceParser,
186
+ ]),
187
+ ([, expr]) => expr,
188
+ );
189
+
190
+ setParserName(symbolicExpressionDocumentParser, 'symbolicExpressionDocumentParser');
@@ -14,12 +14,15 @@ import { createUnionParser } from './unionParser.js';
14
14
  import { createExactElementParser } from './exactElementParser.js';
15
15
 
16
16
  test('terminatedArrayParser of union parsers', async t => {
17
- const parser: Parser<[ number[], number ], Uint8Array> = createTerminatedArrayParser(
18
- createUnionParser([
19
- createExactElementParser(1),
20
- createExactElementParser(2),
21
- ]),
22
- createExactElementParser(0),
17
+ const elementParser: Parser<number, Uint8Array> = createUnionParser([
18
+ createExactElementParser(1),
19
+ createExactElementParser(2),
20
+ ]);
21
+ const terminatorParser: Parser<number, Uint8Array> = createExactElementParser(0);
22
+
23
+ const parser = createTerminatedArrayParser(
24
+ elementParser,
25
+ terminatorParser,
23
26
  );
24
27
 
25
28
  const input = new Uint8Array([ 0 ]);