@futpib/parser 1.0.3 → 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 (262) 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 +89 -0
  20. package/build/bash.js +1 -0
  21. package/build/bashParser.d.ts +6 -0
  22. package/build/bashParser.js +335 -0
  23. package/build/bashParser.test.d.ts +1 -0
  24. package/build/bashParser.test.js +343 -0
  25. package/build/bashParserEdgeCases.test.d.ts +1 -0
  26. package/build/bashParserEdgeCases.test.js +117 -0
  27. package/build/dalvikBytecodeParser/addressConversion.d.ts +110 -0
  28. package/build/dalvikBytecodeParser/addressConversion.js +334 -0
  29. package/build/dalvikBytecodeParser/formatParsers.d.ts +7 -6
  30. package/build/dalvikBytecodeParser/formatParsers.js +13 -14
  31. package/build/dalvikBytecodeParser.d.ts +60 -31
  32. package/build/dalvikBytecodeParser.js +92 -35
  33. package/build/dalvikBytecodeParser.test-d.d.ts +1 -0
  34. package/build/dalvikBytecodeParser.test-d.js +268 -0
  35. package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +9 -8
  36. package/build/dalvikBytecodeUnparser/formatUnparsers.js +13 -12
  37. package/build/dalvikBytecodeUnparser.d.ts +2 -2
  38. package/build/dalvikBytecodeUnparser.js +23 -23
  39. package/build/dalvikBytecodeUnparser.test.js +7 -7
  40. package/build/dalvikExecutable.d.ts +3 -3
  41. package/build/dalvikExecutable.test-d.d.ts +1 -0
  42. package/build/dalvikExecutable.test-d.js +59 -0
  43. package/build/dalvikExecutableParser/typedNumbers.d.ts +18 -0
  44. package/build/dalvikExecutableParser/typedNumbers.js +3 -0
  45. package/build/dalvikExecutableParser.d.ts +2 -1
  46. package/build/dalvikExecutableParser.js +96 -77
  47. package/build/dalvikExecutableParser.test.js +24 -3
  48. package/build/dalvikExecutableParserAgainstSmaliParser.test.js +3 -0
  49. package/build/dalvikExecutableUnparser/poolScanners.d.ts +2 -2
  50. package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +3 -3
  51. package/build/dalvikExecutableUnparser/sectionUnparsers.js +26 -11
  52. package/build/dalvikExecutableUnparser.d.ts +2 -2
  53. package/build/dalvikExecutableUnparser.test.js +2 -1
  54. package/build/disjunctionParser.d.ts +5 -3
  55. package/build/disjunctionParser.js +79 -17
  56. package/build/disjunctionParser.test-d.d.ts +1 -0
  57. package/build/disjunctionParser.test-d.js +72 -0
  58. package/build/elementSwitchParser.d.ts +4 -0
  59. package/build/{exactElementSwitchParser.js → elementSwitchParser.js} +3 -4
  60. package/build/elementSwitchParser.test-d.d.ts +1 -0
  61. package/build/elementSwitchParser.test-d.js +44 -0
  62. package/build/exactSequenceParser.d.ts +4 -2
  63. package/build/exactSequenceParser.test-d.d.ts +1 -0
  64. package/build/exactSequenceParser.test-d.js +36 -0
  65. package/build/fetchCid.js +2 -66
  66. package/build/index.d.ts +25 -2
  67. package/build/index.js +23 -1
  68. package/build/index.test.js +16 -1
  69. package/build/inputReader.d.ts +10 -0
  70. package/build/inputReader.js +36 -0
  71. package/build/java.d.ts +502 -0
  72. package/build/java.js +2 -0
  73. package/build/javaKeyStoreParser.js +14 -17
  74. package/build/javaParser.d.ts +51 -0
  75. package/build/javaParser.js +1538 -0
  76. package/build/javaParser.test.d.ts +1 -0
  77. package/build/javaParser.test.js +1287 -0
  78. package/build/javaScript.d.ts +35 -0
  79. package/build/javaScript.js +1 -0
  80. package/build/javaScriptParser.d.ts +9 -0
  81. package/build/javaScriptParser.js +34 -0
  82. package/build/javaScriptUnparser.d.ts +3 -0
  83. package/build/javaScriptUnparser.js +4 -0
  84. package/build/javaScriptUnparser.test.d.ts +1 -0
  85. package/build/javaScriptUnparser.test.js +24 -0
  86. package/build/javaUnparser.d.ts +2 -0
  87. package/build/javaUnparser.js +519 -0
  88. package/build/javaUnparser.test.d.ts +1 -0
  89. package/build/javaUnparser.test.js +24 -0
  90. package/build/javascript.d.ts +35 -0
  91. package/build/javascript.js +1 -0
  92. package/build/javascriptParser.d.ts +9 -0
  93. package/build/javascriptParser.js +34 -0
  94. package/build/javascriptUnparser.d.ts +3 -0
  95. package/build/javascriptUnparser.js +4 -0
  96. package/build/javascriptUnparser.test.d.ts +1 -0
  97. package/build/javascriptUnparser.test.js +24 -0
  98. package/build/jsonParser.js +2 -12
  99. package/build/lazyMessageError.d.ts +3 -0
  100. package/build/lookaheadParser.js +60 -3
  101. package/build/negativeLookaheadParser.js +70 -11
  102. package/build/nonEmptyArrayParser.js +72 -13
  103. package/build/objectParser.d.ts +12 -0
  104. package/build/objectParser.js +31 -0
  105. package/build/objectParser.test-d.d.ts +1 -0
  106. package/build/objectParser.test-d.js +112 -0
  107. package/build/objectParser.test.d.ts +1 -0
  108. package/build/objectParser.test.js +55 -0
  109. package/build/optionalParser.js +69 -10
  110. package/build/parser.d.ts +4 -0
  111. package/build/parser.js +3 -1
  112. package/build/parser.test.js +114 -1
  113. package/build/parserConsumedSequenceParser.js +66 -7
  114. package/build/parserContext.d.ts +6 -0
  115. package/build/parserContext.js +20 -11
  116. package/build/parserError.d.ts +119 -27
  117. package/build/parserError.js +16 -8
  118. package/build/regexpParser.d.ts +2 -0
  119. package/build/regexpParser.js +101 -0
  120. package/build/regexpParser.test.d.ts +1 -0
  121. package/build/regexpParser.test.js +114 -0
  122. package/build/regularExpression.d.ts +63 -0
  123. package/build/regularExpression.js +1 -0
  124. package/build/regularExpressionParser.d.ts +3 -0
  125. package/build/regularExpressionParser.js +600 -0
  126. package/build/regularExpressionParser.test.d.ts +1 -0
  127. package/build/regularExpressionParser.test.js +89 -0
  128. package/build/separatedArrayParser.js +73 -14
  129. package/build/separatedNonEmptyArrayParser.js +73 -14
  130. package/build/sliceBoundedParser.js +62 -5
  131. package/build/smaliParser.d.ts +7 -7
  132. package/build/smaliParser.js +185 -268
  133. package/build/smaliParser.test.js +58 -0
  134. package/build/stringEscapes.d.ts +5 -0
  135. package/build/stringEscapes.js +244 -0
  136. package/build/symbolicExpression.d.ts +29 -0
  137. package/build/symbolicExpression.js +1 -0
  138. package/build/symbolicExpressionParser.d.ts +4 -0
  139. package/build/symbolicExpressionParser.js +123 -0
  140. package/build/symbolicExpressionParser.test.d.ts +1 -0
  141. package/build/symbolicExpressionParser.test.js +289 -0
  142. package/build/terminatedArrayParser.js +113 -38
  143. package/build/terminatedArrayParser.test.js +4 -2
  144. package/build/tupleParser.d.ts +7 -15
  145. package/build/tupleParser.js +1 -0
  146. package/build/unionParser.d.ts +5 -3
  147. package/build/unionParser.js +7 -2
  148. package/build/unionParser.test-d.d.ts +1 -0
  149. package/build/unionParser.test-d.js +72 -0
  150. package/build/unionParser.test.js +10 -11
  151. package/build/zig.d.ts +280 -0
  152. package/build/zig.js +2 -0
  153. package/build/zigParser.d.ts +3 -0
  154. package/build/zigParser.js +1119 -0
  155. package/build/zigParser.test.d.ts +1 -0
  156. package/build/zigParser.test.js +1590 -0
  157. package/build/zigUnparser.d.ts +2 -0
  158. package/build/zigUnparser.js +460 -0
  159. package/build/zigUnparser.test.d.ts +1 -0
  160. package/build/zigUnparser.test.js +24 -0
  161. package/build/zipParser.js +19 -32
  162. package/build/zipUnparser.js +19 -7
  163. package/build/zipUnparser.test.js +1 -1
  164. package/node_modules-@types/s-expression/index.d.ts +5 -0
  165. package/package.json +25 -6
  166. package/src/androidPackageParser.ts +33 -60
  167. package/src/arbitraryDalvikBytecode.ts +39 -31
  168. package/src/arbitraryDalvikExecutable.ts +65 -20
  169. package/src/arbitraryJava.ts +804 -0
  170. package/src/arbitraryJavaScript.ts +410 -0
  171. package/src/arbitraryZig.ts +380 -0
  172. package/src/arrayParser.ts +1 -3
  173. package/src/backsmali.ts +35 -4
  174. package/src/bash.ts +127 -0
  175. package/src/bashParser.test.ts +590 -0
  176. package/src/bashParser.ts +498 -0
  177. package/src/dalvikBytecodeParser/addressConversion.ts +496 -0
  178. package/src/dalvikBytecodeParser/formatParsers.ts +19 -29
  179. package/src/dalvikBytecodeParser.test-d.ts +310 -0
  180. package/src/dalvikBytecodeParser.ts +194 -69
  181. package/src/dalvikBytecodeUnparser/formatUnparsers.ts +27 -26
  182. package/src/dalvikBytecodeUnparser.test.ts +7 -7
  183. package/src/dalvikBytecodeUnparser.ts +31 -30
  184. package/src/dalvikExecutable.test-d.ts +132 -0
  185. package/src/dalvikExecutable.ts +3 -3
  186. package/src/dalvikExecutableParser/typedNumbers.ts +11 -0
  187. package/src/dalvikExecutableParser.test.ts +37 -3
  188. package/src/dalvikExecutableParser.test.ts.md +163 -2
  189. package/src/dalvikExecutableParser.test.ts.snap +0 -0
  190. package/src/dalvikExecutableParser.ts +121 -139
  191. package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +4 -0
  192. package/src/dalvikExecutableUnparser/poolScanners.ts +6 -6
  193. package/src/dalvikExecutableUnparser/sectionUnparsers.ts +38 -14
  194. package/src/dalvikExecutableUnparser.test.ts +3 -2
  195. package/src/dalvikExecutableUnparser.ts +4 -4
  196. package/src/disjunctionParser.test-d.ts +105 -0
  197. package/src/disjunctionParser.ts +18 -15
  198. package/src/elementSwitchParser.test-d.ts +74 -0
  199. package/src/elementSwitchParser.ts +51 -0
  200. package/src/exactSequenceParser.test-d.ts +43 -0
  201. package/src/exactSequenceParser.ts +13 -8
  202. package/src/fetchCid.ts +2 -76
  203. package/src/index.test.ts +22 -1
  204. package/src/index.ts +119 -2
  205. package/src/inputReader.ts +53 -0
  206. package/src/java.ts +708 -0
  207. package/src/javaKeyStoreParser.ts +18 -32
  208. package/src/javaParser.test.ts +1592 -0
  209. package/src/javaParser.ts +2640 -0
  210. package/src/javaScript.ts +36 -0
  211. package/src/javaScriptParser.ts +57 -0
  212. package/src/javaScriptUnparser.test.ts +37 -0
  213. package/src/javaScriptUnparser.ts +7 -0
  214. package/src/javaUnparser.test.ts +37 -0
  215. package/src/javaUnparser.ts +640 -0
  216. package/src/jsonParser.ts +6 -27
  217. package/src/lookaheadParser.ts +2 -6
  218. package/src/negativeLookaheadParser.ts +1 -3
  219. package/src/nonEmptyArrayParser.ts +1 -3
  220. package/src/objectParser.test-d.ts +152 -0
  221. package/src/objectParser.test.ts +71 -0
  222. package/src/objectParser.ts +69 -0
  223. package/src/optionalParser.ts +1 -3
  224. package/src/parser.test.ts +151 -4
  225. package/src/parser.ts +11 -1
  226. package/src/parserConsumedSequenceParser.ts +2 -4
  227. package/src/parserContext.ts +26 -11
  228. package/src/parserError.ts +17 -3
  229. package/src/regexpParser.test.ts +264 -0
  230. package/src/regexpParser.ts +126 -0
  231. package/src/regularExpression.ts +24 -0
  232. package/src/regularExpressionParser.test.ts +102 -0
  233. package/src/regularExpressionParser.ts +920 -0
  234. package/src/separatedArrayParser.ts +1 -3
  235. package/src/separatedNonEmptyArrayParser.ts +1 -3
  236. package/src/sliceBoundedParser.test.ts +2 -2
  237. package/src/sliceBoundedParser.ts +15 -19
  238. package/src/smaliParser.test.ts +64 -0
  239. package/src/smaliParser.test.ts.md +12 -12
  240. package/src/smaliParser.test.ts.snap +0 -0
  241. package/src/smaliParser.ts +246 -534
  242. package/src/stringEscapes.ts +253 -0
  243. package/src/symbolicExpression.ts +17 -0
  244. package/src/symbolicExpressionParser.test.ts +466 -0
  245. package/src/symbolicExpressionParser.ts +190 -0
  246. package/src/terminatedArrayParser.test.ts +9 -6
  247. package/src/terminatedArrayParser.ts +25 -29
  248. package/src/tupleParser.ts +21 -18
  249. package/src/unionParser.test-d.ts +105 -0
  250. package/src/unionParser.test.ts +18 -17
  251. package/src/unionParser.ts +28 -16
  252. package/src/zig.ts +411 -0
  253. package/src/zigParser.test.ts +1693 -0
  254. package/src/zigParser.ts +1745 -0
  255. package/src/zigUnparser.test.ts +37 -0
  256. package/src/zigUnparser.ts +615 -0
  257. package/src/zipParser.ts +20 -56
  258. package/src/zipUnparser.test.ts +1 -1
  259. package/src/zipUnparser.ts +22 -7
  260. package/tsconfig.json +2 -2
  261. package/build/exactElementSwitchParser.d.ts +0 -3
  262. package/src/exactElementSwitchParser.ts +0 -41
@@ -0,0 +1,1693 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import test from 'ava';
4
+ import envPaths from 'env-paths';
5
+ import { execa } from 'execa';
6
+ import PromiseMutex from 'p-mutex';
7
+ import { temporaryDirectory } from 'tempy';
8
+ import { hasExecutable } from './hasExecutable.js';
9
+ import { runParser } from './parser.js';
10
+ import { stringParserInputCompanion } from './parserInputCompanion.js';
11
+ import { zigSourceFileParser } from './zigParser.js';
12
+
13
+ const zigPaths = envPaths('parser.futpib.github.io');
14
+
15
+ const hasZigPromise = hasExecutable('zig');
16
+
17
+ const zigCommit = '0.15.2';
18
+
19
+ const zigGitCacheMutex = new PromiseMutex();
20
+
21
+ async function ensureZigGitCache() {
22
+ return zigGitCacheMutex.withLock(async () => {
23
+ const cacheDir = path.join(zigPaths.cache, 'zig.git');
24
+
25
+ try {
26
+ await fs.access(cacheDir);
27
+ return cacheDir;
28
+ } catch {
29
+ // Cache miss, clone the bare repo
30
+ }
31
+
32
+ await fs.mkdir(path.dirname(cacheDir), { recursive: true });
33
+
34
+ const tempDir = `${cacheDir}.tmp`;
35
+ await fs.rm(tempDir, { recursive: true, force: true });
36
+
37
+ await execa('git', ['clone', '--bare', '--depth=1', '--branch', zigCommit, 'https://codeberg.org/ziglang/zig.git', tempDir]);
38
+
39
+ await fs.rename(tempDir, cacheDir);
40
+
41
+ return cacheDir;
42
+ });
43
+ }
44
+
45
+ async function cloneZigRepo(): Promise<{ path: string; [Symbol.asyncDispose]: () => Promise<void> }> {
46
+ const gitDir = await ensureZigGitCache();
47
+
48
+ const worktree = temporaryDirectory();
49
+ await execa('git', ['--git-dir', gitDir, 'worktree', 'add', '--detach', worktree, zigCommit]);
50
+
51
+ return {
52
+ path: worktree,
53
+ [Symbol.asyncDispose]: async () => {
54
+ await execa('git', ['--git-dir', gitDir, 'worktree', 'remove', '--force', worktree]);
55
+ },
56
+ };
57
+ }
58
+
59
+ const zigAstDumperSnippet = `
60
+ const std = @import("std");
61
+
62
+ pub fn main() !void {
63
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
64
+ defer _ = gpa.deinit();
65
+ const allocator = gpa.allocator();
66
+
67
+ const args = try std.process.argsAlloc(allocator);
68
+ defer std.process.argsFree(allocator, args);
69
+
70
+ const file_path = args[1];
71
+ const source = try std.fs.cwd().readFileAllocOptions(allocator, file_path, std.math.maxInt(u32), null, .@"1", 0);
72
+ defer allocator.free(source);
73
+
74
+ var tree = try std.zig.Ast.parse(allocator, source, .zig);
75
+ defer tree.deinit(allocator);
76
+
77
+ var list: std.ArrayList(u8) = .{};
78
+ defer list.deinit(allocator);
79
+
80
+ try dumpRoot(allocator, &tree, list.writer(allocator));
81
+ try list.writer(allocator).writeByte('\\n');
82
+ _ = try std.posix.write(std.posix.STDOUT_FILENO, list.items);
83
+ }
84
+
85
+ fn writeJsonString(writer: anytype, s: []const u8) !void {
86
+ try writer.writeByte('"');
87
+ for (s) |c| {
88
+ switch (c) {
89
+ '"' => try writer.writeAll("\\\\\\""),
90
+ '\\\\' => try writer.writeAll("\\\\\\\\"),
91
+ '\\n' => try writer.writeAll("\\\\n"),
92
+ '\\r' => try writer.writeAll("\\\\r"),
93
+ '\\t' => try writer.writeAll("\\\\t"),
94
+ else => try writer.writeByte(c),
95
+ }
96
+ }
97
+ try writer.writeByte('"');
98
+ }
99
+
100
+ fn unescapeZigStringLiteral(allocator: std.mem.Allocator, raw: []const u8) ![]const u8 {
101
+ var list: std.ArrayList(u8) = .{};
102
+ errdefer list.deinit(allocator);
103
+ var i: usize = 0;
104
+ while (i < raw.len) {
105
+ if (raw[i] == '\\\\' and i + 1 < raw.len) {
106
+ const next = raw[i + 1];
107
+ switch (next) {
108
+ 'n' => { try list.append(allocator, '\\n'); i += 2; },
109
+ 't' => { try list.append(allocator, '\\t'); i += 2; },
110
+ 'r' => { try list.append(allocator, '\\r'); i += 2; },
111
+ '\\\\' => { try list.append(allocator, '\\\\'); i += 2; },
112
+ '"' => { try list.append(allocator, '"'); i += 2; },
113
+ '\\'' => { try list.append(allocator, '\\''); i += 2; },
114
+ 'x' => {
115
+ if (i + 3 < raw.len) {
116
+ const byte = std.fmt.parseInt(u8, raw[i+2..i+4], 16) catch {
117
+ try list.append(allocator, raw[i]);
118
+ i += 1;
119
+ continue;
120
+ };
121
+ try list.append(allocator, byte);
122
+ i += 4;
123
+ } else {
124
+ try list.append(allocator, raw[i]);
125
+ i += 1;
126
+ }
127
+ },
128
+ 'u' => {
129
+ if (i + 2 < raw.len and raw[i + 2] == '{') {
130
+ const close = std.mem.indexOfScalarPos(u8, raw, i + 3, '}') orelse {
131
+ try list.append(allocator, raw[i]);
132
+ i += 1;
133
+ continue;
134
+ };
135
+ const codepoint = std.fmt.parseInt(u21, raw[i+3..close], 16) catch {
136
+ try list.append(allocator, raw[i]);
137
+ i += 1;
138
+ continue;
139
+ };
140
+ var buf: [4]u8 = undefined;
141
+ const len = std.unicode.utf8Encode(codepoint, &buf) catch {
142
+ try list.append(allocator, raw[i]);
143
+ i += 1;
144
+ continue;
145
+ };
146
+ try list.appendSlice(allocator, buf[0..len]);
147
+ i = close + 1;
148
+ } else {
149
+ try list.append(allocator, raw[i]);
150
+ i += 1;
151
+ }
152
+ },
153
+ else => {
154
+ try list.append(allocator, raw[i]);
155
+ i += 1;
156
+ },
157
+ }
158
+ } else {
159
+ try list.append(allocator, raw[i]);
160
+ i += 1;
161
+ }
162
+ }
163
+ return list.toOwnedSlice(allocator);
164
+ }
165
+
166
+ const Ast = std.zig.Ast;
167
+ const Node = Ast.Node;
168
+
169
+ fn dumpRoot(allocator: std.mem.Allocator, tree: *const Ast, writer: anytype) !void {
170
+ try writer.writeAll("{\\"type\\":\\"Root\\",\\"members\\":[");
171
+ const root_decls = tree.rootDecls();
172
+ for (root_decls, 0..) |decl, i| {
173
+ if (i > 0) try writer.writeByte(',');
174
+ try dumpNode(allocator, tree, decl, writer);
175
+ }
176
+ try writer.writeAll("]}");
177
+ }
178
+
179
+ fn dumpNode(allocator: std.mem.Allocator, tree: *const Ast, node_index: Node.Index, writer: anytype) anyerror!void {
180
+ if (node_index == .root) {
181
+ try writer.writeAll("null");
182
+ return;
183
+ }
184
+ const tag = tree.nodeTag(node_index);
185
+ const data = tree.nodeData(node_index);
186
+ const main_token = tree.nodeMainToken(node_index);
187
+
188
+ switch (tag) {
189
+ .identifier => {
190
+ const name = tree.tokenSlice(main_token);
191
+ if (std.mem.eql(u8, name, "true")) {
192
+ try writer.writeAll("{\\"type\\":\\"BoolLiteral\\",\\"value\\":true}");
193
+ } else if (std.mem.eql(u8, name, "false")) {
194
+ try writer.writeAll("{\\"type\\":\\"BoolLiteral\\",\\"value\\":false}");
195
+ } else if (std.mem.eql(u8, name, "null")) {
196
+ try writer.writeAll("{\\"type\\":\\"NullLiteral\\"}");
197
+ } else if (std.mem.eql(u8, name, "undefined")) {
198
+ try writer.writeAll("{\\"type\\":\\"UndefinedLiteral\\"}");
199
+ } else {
200
+ try writer.writeAll("{\\"type\\":\\"Identifier\\",\\"name\\":");
201
+ try writeJsonString(writer, name);
202
+ try writer.writeByte('}');
203
+ }
204
+ },
205
+ .number_literal => {
206
+ const slice = tree.tokenSlice(main_token);
207
+ try writer.writeAll("{\\"type\\":\\"IntegerLiteral\\",\\"value\\":");
208
+ try writeJsonString(writer, slice);
209
+ try writer.writeByte('}');
210
+ },
211
+ .string_literal => {
212
+ const slice = tree.tokenSlice(main_token);
213
+ const inner = slice[1..slice.len - 1];
214
+ const unescaped = try unescapeZigStringLiteral(allocator, inner);
215
+ defer allocator.free(unescaped);
216
+ try writer.writeAll("{\\"type\\":\\"StringLiteral\\",\\"value\\":");
217
+ try writeJsonString(writer, unescaped);
218
+ try writer.writeByte('}');
219
+ },
220
+ .enum_literal => {
221
+ const name = tree.tokenSlice(main_token);
222
+ try writer.writeAll("{\\"type\\":\\"EnumLiteral\\",\\"name\\":");
223
+ try writeJsonString(writer, name);
224
+ try writer.writeByte('}');
225
+ },
226
+ .builtin_call_two, .builtin_call_two_comma => {
227
+ const name = tree.tokenSlice(main_token);
228
+ try writer.writeAll("{\\"type\\":\\"BuiltinCallExpr\\",\\"name\\":");
229
+ try writeJsonString(writer, name[1..]);
230
+ try writer.writeAll(",\\"args\\":[");
231
+ const args = data.opt_node_and_opt_node;
232
+ var arg_count: u32 = 0;
233
+ if (args[0].unwrap()) |arg| {
234
+ try dumpNode(allocator, tree,arg, writer);
235
+ arg_count += 1;
236
+ }
237
+ if (args[1].unwrap()) |arg| {
238
+ if (arg_count > 0) try writer.writeByte(',');
239
+ try dumpNode(allocator, tree,arg, writer);
240
+ }
241
+ try writer.writeAll("]}");
242
+ },
243
+ .builtin_call, .builtin_call_comma => {
244
+ const name = tree.tokenSlice(main_token);
245
+ try writer.writeAll("{\\"type\\":\\"BuiltinCallExpr\\",\\"name\\":");
246
+ try writeJsonString(writer, name[1..]);
247
+ try writer.writeAll(",\\"args\\":[");
248
+ const args_range = tree.extraDataSlice(data.extra_range, Node.Index);
249
+ for (args_range, 0..) |arg, i| {
250
+ if (i > 0) try writer.writeByte(',');
251
+ try dumpNode(allocator, tree,arg, writer);
252
+ }
253
+ try writer.writeAll("]}");
254
+ },
255
+ .field_access => {
256
+ const fa = data.node_and_token;
257
+ try writer.writeAll("{\\"type\\":\\"FieldAccessExpr\\",\\"operand\\":");
258
+ try dumpNode(allocator, tree,fa[0], writer);
259
+ try writer.writeAll(",\\"member\\":");
260
+ try writeJsonString(writer, tree.tokenSlice(fa[1]));
261
+ try writer.writeByte('}');
262
+ },
263
+ .call_one, .call_one_comma => {
264
+ const callee, const first_arg = data.node_and_opt_node;
265
+ try writer.writeAll("{\\"type\\":\\"CallExpr\\",\\"callee\\":");
266
+ try dumpNode(allocator, tree,callee, writer);
267
+ try writer.writeAll(",\\"args\\":[");
268
+ if (first_arg.unwrap()) |arg| {
269
+ try dumpNode(allocator, tree,arg, writer);
270
+ }
271
+ try writer.writeAll("]}");
272
+ },
273
+ .call, .call_comma => {
274
+ var buf1: [1]Node.Index = undefined;
275
+ const full_call = tree.fullCall(&buf1, node_index) orelse {
276
+ try writer.writeAll("{\\"type\\":\\"Unknown\\",\\"tag\\":\\"call_error\\"}");
277
+ return;
278
+ };
279
+ try writer.writeAll("{\\"type\\":\\"CallExpr\\",\\"callee\\":");
280
+ try dumpNode(allocator, tree,full_call.ast.fn_expr, writer);
281
+ try writer.writeAll(",\\"args\\":[");
282
+ for (full_call.ast.params, 0..) |arg, i| {
283
+ if (i > 0) try writer.writeByte(',');
284
+ try dumpNode(allocator, tree,arg, writer);
285
+ }
286
+ try writer.writeAll("]}");
287
+ },
288
+ .add, .sub, .mul, .div, .mod,
289
+ .shl, .shr,
290
+ .bit_and, .bit_or, .bit_xor,
291
+ .equal_equal, .bang_equal,
292
+ .less_than, .greater_than, .less_or_equal, .greater_or_equal,
293
+ .bool_and, .bool_or,
294
+ .@"orelse",
295
+ .@"catch",
296
+ .array_cat, .array_mult,
297
+ .merge_error_sets,
298
+ .error_union,
299
+ => {
300
+ const lhs, const rhs = data.node_and_node;
301
+ const op_str = switch (tag) {
302
+ .add => "+",
303
+ .sub => "-",
304
+ .mul => "*",
305
+ .div => "/",
306
+ .mod => "%",
307
+ .shl => "<<",
308
+ .shr => ">>",
309
+ .bit_and => "&",
310
+ .bit_or => "|",
311
+ .bit_xor => "^",
312
+ .equal_equal => "==",
313
+ .bang_equal => "!=",
314
+ .less_than => "<",
315
+ .greater_than => ">",
316
+ .less_or_equal => "<=",
317
+ .greater_or_equal => ">=",
318
+ .bool_and => "and",
319
+ .bool_or => "or",
320
+ .@"orelse" => "orelse",
321
+ .@"catch" => "catch",
322
+ .array_cat => "++",
323
+ .array_mult => "**",
324
+ .merge_error_sets => "||",
325
+ .error_union => "!",
326
+ else => unreachable,
327
+ };
328
+ if (tag == .error_union) {
329
+ try writer.writeAll("{\\"type\\":\\"ErrorUnionType\\",\\"error\\":");
330
+ try dumpNode(allocator, tree,lhs, writer);
331
+ try writer.writeAll(",\\"payload\\":");
332
+ try dumpNode(allocator, tree,rhs, writer);
333
+ try writer.writeByte('}');
334
+ } else {
335
+ try writer.writeAll("{\\"type\\":\\"BinaryExpr\\",\\"operator\\":");
336
+ try writeJsonString(writer, op_str);
337
+ try writer.writeAll(",\\"left\\":");
338
+ try dumpNode(allocator, tree,lhs, writer);
339
+ try writer.writeAll(",\\"right\\":");
340
+ try dumpNode(allocator, tree,rhs, writer);
341
+ try writer.writeByte('}');
342
+ }
343
+ },
344
+ .negation => {
345
+ try writer.writeAll("{\\"type\\":\\"UnaryExpr\\",\\"operator\\":\\"-\\",\\"operand\\":");
346
+ try dumpNode(allocator, tree,data.node, writer);
347
+ try writer.writeByte('}');
348
+ },
349
+ .bit_not => {
350
+ try writer.writeAll("{\\"type\\":\\"UnaryExpr\\",\\"operator\\":\\"~\\",\\"operand\\":");
351
+ try dumpNode(allocator, tree,data.node, writer);
352
+ try writer.writeByte('}');
353
+ },
354
+ .bool_not => {
355
+ try writer.writeAll("{\\"type\\":\\"UnaryExpr\\",\\"operator\\":\\"!\\",\\"operand\\":");
356
+ try dumpNode(allocator, tree,data.node, writer);
357
+ try writer.writeByte('}');
358
+ },
359
+ .address_of => {
360
+ try writer.writeAll("{\\"type\\":\\"UnaryExpr\\",\\"operator\\":\\"&\\",\\"operand\\":");
361
+ try dumpNode(allocator, tree,data.node, writer);
362
+ try writer.writeByte('}');
363
+ },
364
+ .@"try" => {
365
+ try writer.writeAll("{\\"type\\":\\"TryExpr\\",\\"operand\\":");
366
+ try dumpNode(allocator, tree,data.node, writer);
367
+ try writer.writeByte('}');
368
+ },
369
+ .@"comptime" => {
370
+ try writer.writeAll("{\\"type\\":\\"ComptimeExpr\\",\\"operand\\":");
371
+ try dumpNode(allocator, tree,data.node, writer);
372
+ try writer.writeByte('}');
373
+ },
374
+ .optional_type => {
375
+ try writer.writeAll("{\\"type\\":\\"OptionalType\\",\\"child\\":");
376
+ try dumpNode(allocator, tree,data.node, writer);
377
+ try writer.writeByte('}');
378
+ },
379
+ .@"return" => {
380
+ try writer.writeAll("{\\"type\\":\\"ReturnStmt\\"");
381
+ if (data.opt_node.unwrap()) |val| {
382
+ try writer.writeAll(",\\"value\\":");
383
+ try dumpNode(allocator, tree,val, writer);
384
+ }
385
+ try writer.writeByte('}');
386
+ },
387
+ .@"break" => {
388
+ const label_tok, const val = data.opt_token_and_opt_node;
389
+ try writer.writeAll("{\\"type\\":\\"BreakStmt\\"");
390
+ if (label_tok != .none) {
391
+ try writer.writeAll(",\\"label\\":");
392
+ try writeJsonString(writer, tree.tokenSlice(@intFromEnum(label_tok)));
393
+ }
394
+ if (val.unwrap()) |v| {
395
+ try writer.writeAll(",\\"value\\":");
396
+ try dumpNode(allocator, tree,v, writer);
397
+ }
398
+ try writer.writeByte('}');
399
+ },
400
+ .@"continue" => {
401
+ const label_tok, const val = data.opt_token_and_opt_node;
402
+ _ = val;
403
+ try writer.writeAll("{\\"type\\":\\"ContinueStmt\\"");
404
+ if (label_tok != .none) {
405
+ try writer.writeAll(",\\"label\\":");
406
+ try writeJsonString(writer, tree.tokenSlice(@intFromEnum(label_tok)));
407
+ }
408
+ try writer.writeByte('}');
409
+ },
410
+ .grouped_expression => {
411
+ try dumpNode(allocator, tree,data.node_and_token[0], writer);
412
+ },
413
+ .block_two, .block_two_semicolon => {
414
+ try writer.writeAll("{\\"type\\":\\"BlockExpr\\",\\"statements\\":[");
415
+ const stmts = data.opt_node_and_opt_node;
416
+ var count: u32 = 0;
417
+ if (stmts[0].unwrap()) |s| {
418
+ try dumpNode(allocator, tree,s, writer);
419
+ count += 1;
420
+ }
421
+ if (stmts[1].unwrap()) |s| {
422
+ if (count > 0) try writer.writeByte(',');
423
+ try dumpNode(allocator, tree,s, writer);
424
+ }
425
+ try writer.writeAll("]}");
426
+ },
427
+ .block, .block_semicolon => {
428
+ try writer.writeAll("{\\"type\\":\\"BlockExpr\\",\\"statements\\":[");
429
+ const stmts = tree.extraDataSlice(data.extra_range, Node.Index);
430
+ for (stmts, 0..) |stmt, i| {
431
+ if (i > 0) try writer.writeByte(',');
432
+ try dumpNode(allocator, tree,stmt, writer);
433
+ }
434
+ try writer.writeAll("]}");
435
+ },
436
+ .simple_var_decl => {
437
+ try dumpVarDecl(allocator, tree,node_index, writer);
438
+ },
439
+ .global_var_decl => {
440
+ try dumpVarDecl(allocator, tree,node_index, writer);
441
+ },
442
+ .local_var_decl => {
443
+ try dumpVarDecl(allocator, tree,node_index, writer);
444
+ },
445
+ .aligned_var_decl => {
446
+ try dumpVarDecl(allocator, tree,node_index, writer);
447
+ },
448
+ .fn_decl => {
449
+ try dumpFnDecl(allocator, tree,node_index, writer);
450
+ },
451
+ .fn_proto_simple => {
452
+ try dumpFnProtoAsDecl(allocator, tree,node_index, writer, null);
453
+ },
454
+ .fn_proto_multi => {
455
+ try dumpFnProtoAsDecl(allocator, tree,node_index, writer, null);
456
+ },
457
+ .fn_proto_one => {
458
+ try dumpFnProtoAsDecl(allocator, tree,node_index, writer, null);
459
+ },
460
+ .fn_proto => {
461
+ try dumpFnProtoAsDecl(allocator, tree,node_index, writer, null);
462
+ },
463
+ .test_decl => {
464
+ try dumpTestDecl(allocator, tree,node_index, writer);
465
+ },
466
+ .assign => {
467
+ const lhs, const rhs = data.node_and_node;
468
+ try writer.writeAll("{\\"type\\":\\"AssignStmt\\",\\"target\\":");
469
+ try dumpNode(allocator, tree,lhs, writer);
470
+ try writer.writeAll(",\\"operator\\":\\"=\\",\\"value\\":");
471
+ try dumpNode(allocator, tree,rhs, writer);
472
+ try writer.writeByte('}');
473
+ },
474
+ .assign_add => {
475
+ const lhs, const rhs = data.node_and_node;
476
+ try writer.writeAll("{\\"type\\":\\"AssignStmt\\",\\"target\\":");
477
+ try dumpNode(allocator, tree,lhs, writer);
478
+ try writer.writeAll(",\\"operator\\":\\"+=\\",\\"value\\":");
479
+ try dumpNode(allocator, tree,rhs, writer);
480
+ try writer.writeByte('}');
481
+ },
482
+ .@"defer" => {
483
+ try writer.writeAll("{\\"type\\":\\"DeferStmt\\",\\"isErrdefer\\":false,\\"body\\":");
484
+ try dumpNode(allocator, tree,data.node, writer);
485
+ try writer.writeByte('}');
486
+ },
487
+ .@"errdefer" => {
488
+ const body = data.opt_token_and_node[1];
489
+ try writer.writeAll("{\\"type\\":\\"DeferStmt\\",\\"isErrdefer\\":true,\\"body\\":");
490
+ try dumpNode(allocator, tree,body, writer);
491
+ try writer.writeByte('}');
492
+ },
493
+ .if_simple => {
494
+ const full_if = tree.ifSimple(node_index);
495
+ try dumpIfExpr(allocator, tree,full_if, writer);
496
+ },
497
+ .@"if" => {
498
+ const full_if = tree.ifFull(node_index);
499
+ try dumpIfExpr(allocator, tree,full_if, writer);
500
+ },
501
+ .struct_init_dot_two, .struct_init_dot_two_comma => {
502
+ var buf2: [2]Node.Index = undefined;
503
+ const si = tree.structInitDotTwo(&buf2, node_index);
504
+ try dumpStructInit(allocator, tree,si, writer);
505
+ },
506
+ .struct_init_dot, .struct_init_dot_comma => {
507
+ const si = tree.structInitDot(node_index);
508
+ try dumpStructInit(allocator, tree,si, writer);
509
+ },
510
+ .struct_init_one, .struct_init_one_comma => {
511
+ var buf1: [1]Node.Index = undefined;
512
+ const si = tree.structInitOne(&buf1, node_index);
513
+ try dumpStructInit(allocator, tree,si, writer);
514
+ },
515
+ .struct_init, .struct_init_comma => {
516
+ const si = tree.structInit(node_index);
517
+ try dumpStructInit(allocator, tree,si, writer);
518
+ },
519
+ .array_init_one, .array_init_one_comma => {
520
+ var buf1: [1]Node.Index = undefined;
521
+ const ai = tree.arrayInitOne(&buf1, node_index);
522
+ try dumpArrayInit(allocator, tree,ai, writer);
523
+ },
524
+ .array_init_dot_two, .array_init_dot_two_comma => {
525
+ var buf2: [2]Node.Index = undefined;
526
+ const ai = tree.arrayInitDotTwo(&buf2, node_index);
527
+ try dumpArrayInit(allocator, tree,ai, writer);
528
+ },
529
+ .array_init_dot, .array_init_dot_comma => {
530
+ const ai = tree.arrayInitDot(node_index);
531
+ try dumpArrayInit(allocator, tree,ai, writer);
532
+ },
533
+ .array_init, .array_init_comma => {
534
+ const ai = tree.arrayInit(node_index);
535
+ try dumpArrayInit(allocator, tree,ai, writer);
536
+ },
537
+ .deref => {
538
+ try writer.writeAll("{\\"type\\":\\"FieldAccessExpr\\",\\"operand\\":");
539
+ try dumpNode(allocator, tree,data.node, writer);
540
+ try writer.writeAll(",\\"member\\":\\"*\\"}");
541
+ },
542
+ .unwrap_optional => {
543
+ try writer.writeAll("{\\"type\\":\\"FieldAccessExpr\\",\\"operand\\":");
544
+ try dumpNode(allocator, tree,data.node, writer);
545
+ try writer.writeAll(",\\"member\\":\\"?\\"}");
546
+ },
547
+ .slice_open => {
548
+ const lhs, const start = data.node_and_node;
549
+ try writer.writeAll("{\\"type\\":\\"SliceExpr\\",\\"operand\\":");
550
+ try dumpNode(allocator, tree,lhs, writer);
551
+ try writer.writeAll(",\\"start\\":");
552
+ try dumpNode(allocator, tree,start, writer);
553
+ try writer.writeAll("}");
554
+ },
555
+ .slice, .slice_sentinel => {
556
+ const full_slice = tree.fullSlice(node_index) orelse {
557
+ try writer.writeAll("{\\"type\\":\\"Unknown\\",\\"tag\\":\\"slice_error\\"}");
558
+ return;
559
+ };
560
+ try writer.writeAll("{\\"type\\":\\"SliceExpr\\",\\"operand\\":");
561
+ try dumpNode(allocator, tree,full_slice.ast.sliced, writer);
562
+ try writer.writeAll(",\\"start\\":");
563
+ try dumpNode(allocator, tree,full_slice.ast.start, writer);
564
+ if (full_slice.ast.end.unwrap()) |end| {
565
+ try writer.writeAll(",\\"end\\":");
566
+ try dumpNode(allocator, tree,end, writer);
567
+ }
568
+ try writer.writeAll("}");
569
+ },
570
+ .array_access => {
571
+ const lhs, const index = data.node_and_node;
572
+ try writer.writeAll("{\\"type\\":\\"IndexExpr\\",\\"operand\\":");
573
+ try dumpNode(allocator, tree,lhs, writer);
574
+ try writer.writeAll(",\\"index\\":");
575
+ try dumpNode(allocator, tree,index, writer);
576
+ try writer.writeAll("}");
577
+ },
578
+ .array_type => {
579
+ const full_at = tree.arrayType(node_index);
580
+ try writer.writeAll("{\\"type\\":\\"ArrayType\\",\\"length\\":");
581
+ try dumpNode(allocator, tree,full_at.ast.elem_count, writer);
582
+ try writer.writeAll(",\\"child\\":");
583
+ try dumpNode(allocator, tree,full_at.ast.elem_type, writer);
584
+ try writer.writeAll("}");
585
+ },
586
+ .array_type_sentinel => {
587
+ const full_at = tree.arrayTypeSentinel(node_index);
588
+ try writer.writeAll("{\\"type\\":\\"ArrayType\\",\\"length\\":");
589
+ try dumpNode(allocator, tree,full_at.ast.elem_count, writer);
590
+ if (full_at.ast.sentinel.unwrap()) |sentinel| {
591
+ try writer.writeAll(",\\"sentinel\\":");
592
+ try dumpNode(allocator, tree,sentinel, writer);
593
+ }
594
+ try writer.writeAll(",\\"child\\":");
595
+ try dumpNode(allocator, tree,full_at.ast.elem_type, writer);
596
+ try writer.writeAll("}");
597
+ },
598
+ .ptr_type_aligned, .ptr_type_sentinel, .ptr_type, .ptr_type_bit_range => {
599
+ const full_pt = tree.fullPtrType(node_index) orelse {
600
+ try writer.writeAll("{\\"type\\":\\"Unknown\\",\\"tag\\":\\"ptr_type_error\\"}");
601
+ return;
602
+ };
603
+ try writer.writeAll("{\\"type\\":\\"PointerType\\",\\"size\\":");
604
+ const size_str = switch (full_pt.size) {
605
+ .one => "\\"one\\"",
606
+ .many => "\\"many\\"",
607
+ .slice => "\\"slice\\"",
608
+ .c => "\\"c\\"",
609
+ };
610
+ try writer.writeAll(size_str);
611
+ try writer.writeAll(",\\"isConst\\":");
612
+ try writer.writeAll(if (full_pt.const_token != null) "true" else "false");
613
+ try writer.writeAll(",\\"child\\":");
614
+ try dumpNode(allocator, tree,full_pt.ast.child_type, writer);
615
+ try writer.writeAll("}");
616
+ },
617
+ .for_simple => {
618
+ try dumpForLoop(allocator, tree,tree.forSimple(node_index), writer);
619
+ },
620
+ .for_range => {
621
+ // lhs..rhs range expression (used inside for inputs)
622
+ const lhs, const rhs_opt = data.node_and_opt_node;
623
+ try writer.writeAll("{\\"type\\":\\"BinaryExpr\\",\\"operator\\":\\"..\\",\\"left\\":");
624
+ try dumpNode(allocator, tree,lhs, writer);
625
+ try writer.writeAll(",\\"right\\":");
626
+ if (rhs_opt.unwrap()) |rhs| {
627
+ try dumpNode(allocator, tree,rhs, writer);
628
+ } else {
629
+ try writer.writeAll("null");
630
+ }
631
+ try writer.writeAll("}");
632
+ },
633
+ .@"for" => {
634
+ try dumpForLoop(allocator, tree,tree.forFull(node_index), writer);
635
+ },
636
+ .while_simple => {
637
+ const full_while = tree.whileSimple(node_index);
638
+ try dumpWhileLoop(allocator, tree,full_while, writer);
639
+ },
640
+ .while_cont => {
641
+ const full_while = tree.whileCont(node_index);
642
+ try dumpWhileLoop(allocator, tree,full_while, writer);
643
+ },
644
+ .@"while" => {
645
+ const full_while = tree.whileFull(node_index);
646
+ try dumpWhileLoop(allocator, tree,full_while, writer);
647
+ },
648
+ .container_decl_two, .container_decl_two_trailing => {
649
+ var buf2: [2]Node.Index = undefined;
650
+ const cd = tree.containerDeclTwo(&buf2, node_index);
651
+ try dumpContainerDecl(allocator, tree,cd, writer);
652
+ },
653
+ .container_decl, .container_decl_trailing => {
654
+ const cd = tree.containerDecl(node_index);
655
+ try dumpContainerDecl(allocator, tree,cd, writer);
656
+ },
657
+ .container_field_init => {
658
+ try dumpContainerField(allocator, tree,tree.containerFieldInit(node_index), writer);
659
+ },
660
+ .container_field => {
661
+ try dumpContainerField(allocator, tree,tree.containerField(node_index), writer);
662
+ },
663
+ .char_literal => {
664
+ const slice = tree.tokenSlice(main_token);
665
+ const inner = slice[1..slice.len - 1];
666
+ const unescaped = try unescapeZigStringLiteral(allocator, inner);
667
+ defer allocator.free(unescaped);
668
+ try writer.writeAll("{\\"type\\":\\"CharLiteral\\",\\"value\\":");
669
+ try writeJsonString(writer, unescaped);
670
+ try writer.writeByte('}');
671
+ },
672
+ .multiline_string_literal => {
673
+ const slice = tree.tokenSlice(main_token);
674
+ try writer.writeAll("{\\"type\\":\\"MultilineStringLiteral\\",\\"value\\":");
675
+ try writeJsonString(writer, slice);
676
+ try writer.writeByte('}');
677
+ },
678
+ .error_set_decl => {
679
+ try writer.writeAll("{\\"type\\":\\"ErrorSetExpr\\",\\"names\\":[");
680
+ // Iterate tokens between the { and } of error set
681
+ var tok = main_token + 2; // skip 'error' and '{'
682
+ var first = true;
683
+ while (true) {
684
+ const tok_tag = tree.tokenTag(tok);
685
+ if (tok_tag == .r_brace) break;
686
+ if (tok_tag == .identifier) {
687
+ if (!first) try writer.writeByte(',');
688
+ first = false;
689
+ try writeJsonString(writer, tree.tokenSlice(tok));
690
+ }
691
+ tok += 1;
692
+ }
693
+ try writer.writeAll("]}");
694
+ },
695
+ .@"switch", .switch_comma => {
696
+ const full_switch = tree.fullSwitch(node_index) orelse {
697
+ try writer.writeAll("{\\"type\\":\\"Unknown\\",\\"tag\\":\\"switch_error\\"}");
698
+ return;
699
+ };
700
+ try writer.writeAll("{\\"type\\":\\"SwitchExpr\\",\\"operand\\":");
701
+ try dumpNode(allocator, tree,full_switch.ast.condition, writer);
702
+ try writer.writeAll(",\\"prongs\\":[");
703
+ for (full_switch.ast.cases, 0..) |case, i| {
704
+ if (i > 0) try writer.writeByte(',');
705
+ const full_case = tree.fullSwitchCase(case) orelse continue;
706
+ try dumpSwitchProng(allocator, tree,full_case, writer);
707
+ }
708
+ try writer.writeAll("]}");
709
+ },
710
+ .assign_mul => {
711
+ const lhs, const rhs = data.node_and_node;
712
+ try writer.writeAll("{\\"type\\":\\"AssignStmt\\",\\"target\\":");
713
+ try dumpNode(allocator, tree,lhs, writer);
714
+ try writer.writeAll(",\\"operator\\":\\"*=\\",\\"value\\":");
715
+ try dumpNode(allocator, tree,rhs, writer);
716
+ try writer.writeByte('}');
717
+ },
718
+ .assign_sub => {
719
+ const lhs, const rhs = data.node_and_node;
720
+ try writer.writeAll("{\\"type\\":\\"AssignStmt\\",\\"target\\":");
721
+ try dumpNode(allocator, tree,lhs, writer);
722
+ try writer.writeAll(",\\"operator\\":\\"-=\\",\\"value\\":");
723
+ try dumpNode(allocator, tree,rhs, writer);
724
+ try writer.writeByte('}');
725
+ },
726
+ else => {
727
+ try writer.writeAll("{\\"type\\":\\"Unknown\\",\\"tag\\":");
728
+ try writeJsonString(writer, @tagName(tag));
729
+ try writer.writeByte('}');
730
+ },
731
+ }
732
+ }
733
+
734
+ fn dumpIfExpr(allocator: std.mem.Allocator, tree: *const Ast, full_if: Ast.full.If, writer: anytype) !void {
735
+ try writer.writeAll("{\\"type\\":\\"IfExpr\\",\\"condition\\":");
736
+ try dumpNode(allocator, tree,full_if.ast.cond_expr, writer);
737
+ try writer.writeAll(",\\"body\\":");
738
+ try dumpNode(allocator, tree,full_if.ast.then_expr, writer);
739
+ if (full_if.ast.else_expr.unwrap()) |else_expr| {
740
+ try writer.writeAll(",\\"elseBody\\":");
741
+ try dumpNode(allocator, tree,else_expr, writer);
742
+ }
743
+ try writer.writeByte('}');
744
+ }
745
+
746
+ fn dumpStructInit(allocator: std.mem.Allocator, tree: *const Ast, si: Ast.full.StructInit, writer: anytype) !void {
747
+ try writer.writeAll("{\\"type\\":\\"StructInitExpr\\"");
748
+ if (si.ast.type_expr.unwrap()) |te| {
749
+ try writer.writeAll(",\\"operand\\":");
750
+ try dumpNode(allocator, tree,te, writer);
751
+ }
752
+ try writer.writeAll(",\\"fields\\":[");
753
+ for (si.ast.fields, 0..) |field, i| {
754
+ if (i > 0) try writer.writeByte(',');
755
+ const name_token = tree.firstToken(field) - 2;
756
+ try writer.writeAll("{\\"type\\":\\"StructInitField\\",\\"name\\":");
757
+ try writeJsonString(writer, tree.tokenSlice(name_token));
758
+ try writer.writeAll(",\\"value\\":");
759
+ try dumpNode(allocator, tree,field, writer);
760
+ try writer.writeByte('}');
761
+ }
762
+ try writer.writeAll("]}");
763
+ }
764
+
765
+ fn dumpVarDecl(allocator: std.mem.Allocator, tree: *const Ast, node_index: Node.Index, writer: anytype) !void {
766
+ const var_decl = tree.fullVarDecl(node_index) orelse {
767
+ try writer.writeAll("{\\"type\\":\\"Unknown\\",\\"tag\\":\\"var_decl_error\\"}");
768
+ return;
769
+ };
770
+ const main_token = tree.nodeMainToken(node_index);
771
+
772
+ const is_const = tree.tokenTag(main_token) == .keyword_const;
773
+ const name_token = main_token + 1;
774
+ const name = tree.tokenSlice(name_token);
775
+
776
+ const is_pub = if (var_decl.visib_token) |tok| tree.tokenTag(tok) == .keyword_pub else false;
777
+ const is_extern = var_decl.extern_export_token != null;
778
+ const is_comptime = var_decl.comptime_token != null;
779
+ const is_threadlocal = var_decl.threadlocal_token != null;
780
+
781
+ const type_str = "VarDecl";
782
+ try writer.writeAll("{\\"type\\":");
783
+ try writeJsonString(writer, type_str);
784
+ try writer.writeAll(",\\"isConst\\":");
785
+ try writer.writeAll(if (is_const) "true" else "false");
786
+ try writer.writeAll(",\\"isPub\\":");
787
+ try writer.writeAll(if (is_pub) "true" else "false");
788
+ try writer.writeAll(",\\"isExtern\\":");
789
+ try writer.writeAll(if (is_extern) "true" else "false");
790
+ try writer.writeAll(",\\"isComptime\\":");
791
+ try writer.writeAll(if (is_comptime) "true" else "false");
792
+ try writer.writeAll(",\\"isThreadlocal\\":");
793
+ try writer.writeAll(if (is_threadlocal) "true" else "false");
794
+ try writer.writeAll(",\\"name\\":");
795
+ try writeJsonString(writer, name);
796
+
797
+ if (var_decl.ast.type_node.unwrap()) |type_node| {
798
+ try writer.writeAll(",\\"typeExpr\\":");
799
+ try dumpNode(allocator, tree,type_node, writer);
800
+ }
801
+ if (var_decl.ast.align_node.unwrap()) |align_node| {
802
+ try writer.writeAll(",\\"alignExpr\\":");
803
+ try dumpNode(allocator, tree,align_node, writer);
804
+ }
805
+ if (var_decl.ast.init_node.unwrap()) |init_node| {
806
+ try writer.writeAll(",\\"initExpr\\":");
807
+ try dumpNode(allocator, tree,init_node, writer);
808
+ }
809
+ try writer.writeByte('}');
810
+ }
811
+
812
+ fn dumpFnDecl(allocator: std.mem.Allocator, tree: *const Ast, node_index: Node.Index, writer: anytype) !void {
813
+ const proto_index, const body_node = tree.nodeData(node_index).node_and_node;
814
+ try dumpFnProtoAsDecl(allocator, tree,proto_index, writer, body_node);
815
+ }
816
+
817
+ fn dumpFnProtoAsDecl(allocator: std.mem.Allocator, tree: *const Ast, proto_index: Node.Index, writer: anytype, body_node: ?Node.Index) !void {
818
+ var buf: [1]Node.Index = undefined;
819
+ const fn_proto = tree.fullFnProto(&buf, proto_index) orelse {
820
+ try writer.writeAll("{\\"type\\":\\"Unknown\\",\\"tag\\":\\"fn_proto_error\\"}");
821
+ return;
822
+ };
823
+
824
+ var is_pub = false;
825
+ var is_extern = false;
826
+ const is_export = false;
827
+ const is_inline = false;
828
+ const is_comptime = false;
829
+
830
+ if (fn_proto.visib_token) |tok| {
831
+ if (tree.tokenTag(tok) == .keyword_pub) is_pub = true;
832
+ }
833
+ if (fn_proto.extern_export_inline_token) |tok| {
834
+ if (tree.tokenTag(tok) == .keyword_extern) is_extern = true;
835
+ }
836
+ if (fn_proto.lib_name) |_| {
837
+ is_extern = true;
838
+ }
839
+
840
+ const name_token = fn_proto.name_token orelse {
841
+ try dumpFnProtoType(allocator, tree,fn_proto, writer);
842
+ return;
843
+ };
844
+ const name = tree.tokenSlice(name_token);
845
+
846
+ try writer.writeAll("{\\"type\\":\\"FnDecl\\",\\"isPub\\":");
847
+ try writer.writeAll(if (is_pub) "true" else "false");
848
+ try writer.writeAll(",\\"isExtern\\":");
849
+ try writer.writeAll(if (is_extern) "true" else "false");
850
+ try writer.writeAll(",\\"isExport\\":");
851
+ try writer.writeAll(if (is_export) "true" else "false");
852
+ try writer.writeAll(",\\"isInline\\":");
853
+ try writer.writeAll(if (is_inline) "true" else "false");
854
+ try writer.writeAll(",\\"isComptime\\":");
855
+ try writer.writeAll(if (is_comptime) "true" else "false");
856
+ try writer.writeAll(",\\"name\\":");
857
+ try writeJsonString(writer, name);
858
+
859
+ // Params
860
+ try writer.writeAll(",\\"params\\":[");
861
+ var param_it = fn_proto.iterate(tree);
862
+ var first_param = true;
863
+ while (param_it.next()) |param| {
864
+ if (!first_param) try writer.writeByte(',');
865
+ first_param = false;
866
+ try writer.writeAll("{\\"type\\":\\"FnParam\\"");
867
+ if (param.name_token) |nt| {
868
+ try writer.writeAll(",\\"name\\":");
869
+ try writeJsonString(writer, tree.tokenSlice(nt));
870
+ }
871
+ const param_is_comptime = param.comptime_noalias != null and tree.tokenTag(param.comptime_noalias.?) == .keyword_comptime;
872
+ const param_is_noalias = param.comptime_noalias != null and tree.tokenTag(param.comptime_noalias.?) == .keyword_noalias;
873
+ try writer.writeAll(",\\"isComptime\\":");
874
+ try writer.writeAll(if (param_is_comptime) "true" else "false");
875
+ try writer.writeAll(",\\"isNoalias\\":");
876
+ try writer.writeAll(if (param_is_noalias) "true" else "false");
877
+ if (param.type_expr) |te| {
878
+ try writer.writeAll(",\\"typeExpr\\":");
879
+ try dumpNode(allocator, tree,te, writer);
880
+ }
881
+ try writer.writeByte('}');
882
+ }
883
+ try writer.writeAll("]");
884
+
885
+ // Return type
886
+ try writer.writeAll(",\\"returnType\\":");
887
+ if (fn_proto.ast.return_type.unwrap()) |rt| {
888
+ try dumpNode(allocator, tree,rt, writer);
889
+ } else {
890
+ try writer.writeAll("{\\"type\\":\\"Identifier\\",\\"name\\":\\"void\\"}");
891
+ }
892
+
893
+ // Body
894
+ if (body_node) |body| {
895
+ if (body != .root) {
896
+ try writer.writeAll(",\\"body\\":");
897
+ try dumpNode(allocator, tree,body, writer);
898
+ }
899
+ }
900
+ try writer.writeByte('}');
901
+ }
902
+
903
+ fn dumpFnProtoType(allocator: std.mem.Allocator, tree: *const Ast, fn_proto: Ast.full.FnProto, writer: anytype) !void {
904
+ try writer.writeAll("{\\"type\\":\\"FnProtoType\\",\\"params\\":[");
905
+ var param_it = fn_proto.iterate(tree);
906
+ var first_param = true;
907
+ while (param_it.next()) |param| {
908
+ if (!first_param) try writer.writeByte(',');
909
+ first_param = false;
910
+ try writer.writeAll("{\\"type\\":\\"FnParam\\"");
911
+ if (param.name_token) |nt| {
912
+ try writer.writeAll(",\\"name\\":");
913
+ try writeJsonString(writer, tree.tokenSlice(nt));
914
+ }
915
+ const param_is_comptime = param.comptime_noalias != null and tree.tokenTag(param.comptime_noalias.?) == .keyword_comptime;
916
+ const param_is_noalias = param.comptime_noalias != null and tree.tokenTag(param.comptime_noalias.?) == .keyword_noalias;
917
+ try writer.writeAll(",\\"isComptime\\":");
918
+ try writer.writeAll(if (param_is_comptime) "true" else "false");
919
+ try writer.writeAll(",\\"isNoalias\\":");
920
+ try writer.writeAll(if (param_is_noalias) "true" else "false");
921
+ if (param.type_expr) |te| {
922
+ try writer.writeAll(",\\"typeExpr\\":");
923
+ try dumpNode(allocator, tree,te, writer);
924
+ }
925
+ try writer.writeByte('}');
926
+ }
927
+ try writer.writeAll("],\\"returnType\\":");
928
+ if (fn_proto.ast.return_type.unwrap()) |rt| {
929
+ try dumpNode(allocator, tree,rt, writer);
930
+ } else {
931
+ try writer.writeAll("{\\"type\\":\\"Identifier\\",\\"name\\":\\"void\\"}");
932
+ }
933
+ try writer.writeByte('}');
934
+ }
935
+
936
+ fn dumpTestDecl(allocator: std.mem.Allocator, tree: *const Ast, node_index: Node.Index, writer: anytype) !void {
937
+ const name_tok, const body = tree.nodeData(node_index).opt_token_and_node;
938
+
939
+ try writer.writeAll("{\\"type\\":\\"TestDecl\\"");
940
+
941
+ if (name_tok != .none) {
942
+ const tok_idx: Ast.TokenIndex = @intFromEnum(name_tok);
943
+ if (tree.tokenTag(tok_idx) == .string_literal) {
944
+ const slice = tree.tokenSlice(tok_idx);
945
+ const inner = slice[1..slice.len - 1];
946
+ const unescaped = try unescapeZigStringLiteral(allocator, inner);
947
+ defer allocator.free(unescaped);
948
+ try writer.writeAll(",\\"name\\":");
949
+ try writeJsonString(writer, unescaped);
950
+ }
951
+ }
952
+
953
+ try writer.writeAll(",\\"body\\":");
954
+ try dumpNode(allocator, tree,body, writer);
955
+ try writer.writeByte('}');
956
+ }
957
+
958
+ fn dumpArrayInit(allocator: std.mem.Allocator, tree: *const Ast, ai: Ast.full.ArrayInit, writer: anytype) !void {
959
+ try writer.writeAll("{\\"type\\":\\"ArrayInitExpr\\"");
960
+ if (ai.ast.type_expr.unwrap()) |te| {
961
+ try writer.writeAll(",\\"operand\\":");
962
+ try dumpNode(allocator, tree,te, writer);
963
+ }
964
+ try writer.writeAll(",\\"elements\\":[");
965
+ for (ai.ast.elements, 0..) |elem, i| {
966
+ if (i > 0) try writer.writeByte(',');
967
+ try dumpNode(allocator, tree,elem, writer);
968
+ }
969
+ try writer.writeAll("]}");
970
+ }
971
+
972
+ fn dumpForLoop(allocator: std.mem.Allocator, tree: *const Ast, full_for: Ast.full.For, writer: anytype) !void {
973
+ try writer.writeAll("{\\"type\\":\\"ForStmt\\",\\"inputs\\":[");
974
+ for (full_for.ast.inputs, 0..) |input, i| {
975
+ if (i > 0) try writer.writeByte(',');
976
+ try dumpNode(allocator, tree,input, writer);
977
+ }
978
+ try writer.writeAll("],\\"captures\\":[");
979
+ // Extract captures from token stream starting at payload_token
980
+ var tok = full_for.payload_token;
981
+ var first_cap = true;
982
+ while (tree.tokenTag(tok) != .pipe) {
983
+ const is_ptr = tree.tokenTag(tok) == .asterisk;
984
+ if (is_ptr) tok += 1;
985
+ if (tree.tokenTag(tok) == .identifier) {
986
+ if (!first_cap) try writer.writeByte(',');
987
+ first_cap = false;
988
+ const name = tree.tokenSlice(tok);
989
+ if (is_ptr) {
990
+ try writer.writeByte('"');
991
+ try writer.writeByte('*');
992
+ try writer.writeAll(name);
993
+ try writer.writeByte('"');
994
+ } else {
995
+ try writeJsonString(writer, name);
996
+ }
997
+ }
998
+ tok += 1;
999
+ }
1000
+ try writer.writeAll("],\\"body\\":");
1001
+ try dumpNode(allocator, tree,full_for.ast.then_expr, writer);
1002
+ try writer.writeAll(",\\"isInline\\":");
1003
+ try writer.writeAll(if (full_for.inline_token != null) "true" else "false");
1004
+ if (full_for.ast.else_expr.unwrap()) |else_expr| {
1005
+ try writer.writeAll(",\\"elseBody\\":");
1006
+ try dumpNode(allocator, tree,else_expr, writer);
1007
+ }
1008
+ if (full_for.label_token) |lt| {
1009
+ try writer.writeAll(",\\"label\\":");
1010
+ try writeJsonString(writer, tree.tokenSlice(lt));
1011
+ }
1012
+ try writer.writeAll("}");
1013
+ }
1014
+
1015
+ fn dumpWhileLoop(allocator: std.mem.Allocator, tree: *const Ast, full_while: Ast.full.While, writer: anytype) !void {
1016
+ try writer.writeAll("{\\"type\\":\\"WhileStmt\\",\\"condition\\":");
1017
+ try dumpNode(allocator, tree,full_while.ast.cond_expr, writer);
1018
+ if (full_while.payload_token) |pt| {
1019
+ try writer.writeAll(",\\"capture\\":");
1020
+ try writeJsonString(writer, tree.tokenSlice(pt));
1021
+ }
1022
+ if (full_while.ast.cont_expr.unwrap()) |cont| {
1023
+ try writer.writeAll(",\\"continuation\\":");
1024
+ try dumpNode(allocator, tree,cont, writer);
1025
+ }
1026
+ try writer.writeAll(",\\"body\\":");
1027
+ try dumpNode(allocator, tree,full_while.ast.then_expr, writer);
1028
+ if (full_while.ast.else_expr.unwrap()) |else_expr| {
1029
+ try writer.writeAll(",\\"elseBody\\":");
1030
+ try dumpNode(allocator, tree,else_expr, writer);
1031
+ }
1032
+ if (full_while.label_token) |lt| {
1033
+ try writer.writeAll(",\\"label\\":");
1034
+ try writeJsonString(writer, tree.tokenSlice(lt));
1035
+ }
1036
+ try writer.writeAll(",\\"isInline\\":");
1037
+ try writer.writeAll(if (full_while.inline_token != null) "true" else "false");
1038
+ try writer.writeAll("}");
1039
+ }
1040
+
1041
+ fn dumpContainerDecl(allocator: std.mem.Allocator, tree: *const Ast, cd: Ast.full.ContainerDecl, writer: anytype) !void {
1042
+ const kw_tok = cd.ast.main_token;
1043
+ const kw = tree.tokenTag(kw_tok);
1044
+ if (kw == .keyword_struct) {
1045
+ try writer.writeAll("{\\"type\\":\\"StructExpr\\",\\"members\\":[");
1046
+ for (cd.ast.members, 0..) |member, i| {
1047
+ if (i > 0) try writer.writeByte(',');
1048
+ try dumpNode(allocator, tree,member, writer);
1049
+ }
1050
+ try writer.writeAll("]}");
1051
+ } else {
1052
+ try writer.writeAll("{\\"type\\":\\"Unknown\\",\\"tag\\":\\"container_decl\\"}");
1053
+ }
1054
+ }
1055
+
1056
+ fn dumpContainerField(allocator: std.mem.Allocator, tree: *const Ast, cf: Ast.full.ContainerField, writer: anytype) !void {
1057
+ try writer.writeAll("{\\"type\\":\\"ContainerField\\",\\"name\\":");
1058
+ try writeJsonString(writer, tree.tokenSlice(cf.ast.main_token));
1059
+ if (cf.ast.type_expr.unwrap()) |te| {
1060
+ try writer.writeAll(",\\"typeExpr\\":");
1061
+ try dumpNode(allocator, tree,te, writer);
1062
+ }
1063
+ if (cf.ast.align_expr.unwrap()) |ae| {
1064
+ try writer.writeAll(",\\"alignExpr\\":");
1065
+ try dumpNode(allocator, tree,ae, writer);
1066
+ }
1067
+ if (cf.ast.value_expr.unwrap()) |dv| {
1068
+ try writer.writeAll(",\\"defaultValue\\":");
1069
+ try dumpNode(allocator, tree,dv, writer);
1070
+ }
1071
+ try writer.writeAll("}");
1072
+ }
1073
+
1074
+ fn dumpSwitchProng(allocator: std.mem.Allocator, tree: *const Ast, full_case: Ast.full.SwitchCase, writer: anytype) !void {
1075
+ try writer.writeAll("{\\"type\\":\\"SwitchProng\\"");
1076
+ const is_else = full_case.ast.values.len == 0;
1077
+ if (is_else) {
1078
+ try writer.writeAll(",\\"isElse\\":true,\\"cases\\":[]");
1079
+ } else {
1080
+ try writer.writeAll(",\\"isElse\\":false,\\"cases\\":[");
1081
+ for (full_case.ast.values, 0..) |item, i| {
1082
+ if (i > 0) try writer.writeByte(',');
1083
+ try dumpNode(allocator, tree,item, writer);
1084
+ }
1085
+ try writer.writeAll("]");
1086
+ }
1087
+ if (full_case.payload_token) |pt| {
1088
+ try writer.writeAll(",\\"capture\\":");
1089
+ try writeJsonString(writer, tree.tokenSlice(pt));
1090
+ }
1091
+ try writer.writeAll(",\\"body\\":");
1092
+ try dumpNode(allocator, tree,full_case.ast.target_expr, writer);
1093
+ try writer.writeAll("}");
1094
+ }
1095
+ `;
1096
+
1097
+ async function runZigDumper(zigSource: string, targetFile: string) {
1098
+ const dir = temporaryDirectory();
1099
+ const dumperFile = path.join(dir, 'dump_ast.zig');
1100
+ await fs.writeFile(dumperFile, zigSource);
1101
+
1102
+ try {
1103
+ const result = await execa('zig', ['run', dumperFile, '--', targetFile], { cwd: dir });
1104
+ return result.stdout.trim();
1105
+ } finally {
1106
+ await fs.rm(dir, { recursive: true, force: true });
1107
+ }
1108
+ }
1109
+
1110
+ test('empty file', async t => {
1111
+ const result = await runParser(
1112
+ zigSourceFileParser,
1113
+ '',
1114
+ stringParserInputCompanion,
1115
+ );
1116
+
1117
+ t.deepEqual(result, {
1118
+ type: 'Root',
1119
+ members: [],
1120
+ });
1121
+ });
1122
+
1123
+ test('single const declaration', async t => {
1124
+ const result = await runParser(
1125
+ zigSourceFileParser,
1126
+ 'const x = 42;',
1127
+ stringParserInputCompanion,
1128
+ );
1129
+
1130
+ t.deepEqual(result, {
1131
+ type: 'Root',
1132
+ members: [{
1133
+ type: 'VarDecl',
1134
+ isConst: true,
1135
+ isPub: false,
1136
+ isExtern: false,
1137
+ isComptime: false,
1138
+ isThreadlocal: false,
1139
+ name: 'x',
1140
+ initExpr: {
1141
+ type: 'IntegerLiteral',
1142
+ value: '42',
1143
+ },
1144
+ }],
1145
+ });
1146
+ });
1147
+
1148
+ test('pub const with type annotation', async t => {
1149
+ const result = await runParser(
1150
+ zigSourceFileParser,
1151
+ 'pub const x: u32 = 0;',
1152
+ stringParserInputCompanion,
1153
+ );
1154
+
1155
+ t.deepEqual(result, {
1156
+ type: 'Root',
1157
+ members: [{
1158
+ type: 'VarDecl',
1159
+ isConst: true,
1160
+ isPub: true,
1161
+ isExtern: false,
1162
+ isComptime: false,
1163
+ isThreadlocal: false,
1164
+ name: 'x',
1165
+ typeExpr: { type: 'Identifier', name: 'u32' },
1166
+ initExpr: { type: 'IntegerLiteral', value: '0' },
1167
+ }],
1168
+ });
1169
+ });
1170
+
1171
+ test('const with @import', async t => {
1172
+ const result = await runParser(
1173
+ zigSourceFileParser,
1174
+ 'const std = @import("std");',
1175
+ stringParserInputCompanion,
1176
+ );
1177
+
1178
+ t.deepEqual(result, {
1179
+ type: 'Root',
1180
+ members: [{
1181
+ type: 'VarDecl',
1182
+ isConst: true,
1183
+ isPub: false,
1184
+ isExtern: false,
1185
+ isComptime: false,
1186
+ isThreadlocal: false,
1187
+ name: 'std',
1188
+ initExpr: {
1189
+ type: 'BuiltinCallExpr',
1190
+ name: 'import',
1191
+ args: [{ type: 'StringLiteral', value: 'std' }],
1192
+ },
1193
+ }],
1194
+ });
1195
+ });
1196
+
1197
+ test('simple function declaration', async t => {
1198
+ const result = await runParser(
1199
+ zigSourceFileParser,
1200
+ 'fn add(a: u32, b: u32) u32 { return a + b; }',
1201
+ stringParserInputCompanion,
1202
+ );
1203
+
1204
+ t.is(result.type, 'Root');
1205
+ t.is(result.members.length, 1);
1206
+ const fn = result.members[0];
1207
+ t.is(fn.type, 'FnDecl');
1208
+ if (fn.type === 'FnDecl') {
1209
+ t.is(fn.name, 'add');
1210
+ t.is(fn.isPub, false);
1211
+ t.is(fn.params.length, 2);
1212
+ t.is(fn.params[0].name, 'a');
1213
+ t.is(fn.params[1].name, 'b');
1214
+ t.truthy(fn.body);
1215
+ }
1216
+ });
1217
+
1218
+ test('pub fn with no params and void return', async t => {
1219
+ const result = await runParser(
1220
+ zigSourceFileParser,
1221
+ 'pub fn main() void {}',
1222
+ stringParserInputCompanion,
1223
+ );
1224
+
1225
+ t.deepEqual(result, {
1226
+ type: 'Root',
1227
+ members: [{
1228
+ type: 'FnDecl',
1229
+ isPub: true,
1230
+ isExtern: false,
1231
+ isExport: false,
1232
+ isInline: false,
1233
+ isComptime: false,
1234
+ name: 'main',
1235
+ params: [],
1236
+ returnType: { type: 'Identifier', name: 'void' },
1237
+ body: {
1238
+ type: 'BlockExpr',
1239
+ statements: [],
1240
+ },
1241
+ }],
1242
+ });
1243
+ });
1244
+
1245
+ test('extern fn without body', async t => {
1246
+ const result = await runParser(
1247
+ zigSourceFileParser,
1248
+ 'extern fn puts(s: [*]const u8) c_int;',
1249
+ stringParserInputCompanion,
1250
+ );
1251
+
1252
+ t.is(result.members.length, 1);
1253
+ const fn = result.members[0];
1254
+ t.is(fn.type, 'FnDecl');
1255
+ if (fn.type === 'FnDecl') {
1256
+ t.is(fn.isExtern, true);
1257
+ t.is(fn.name, 'puts');
1258
+ t.is(fn.body, undefined);
1259
+ }
1260
+ });
1261
+
1262
+ test('test declaration', async t => {
1263
+ const result = await runParser(
1264
+ zigSourceFileParser,
1265
+ 'test "basic test" { const x = 1; }',
1266
+ stringParserInputCompanion,
1267
+ );
1268
+
1269
+ t.deepEqual(result, {
1270
+ type: 'Root',
1271
+ members: [{
1272
+ type: 'TestDecl',
1273
+ name: 'basic test',
1274
+ body: {
1275
+ type: 'BlockExpr',
1276
+ statements: [{
1277
+ type: 'VarDecl',
1278
+ isConst: true,
1279
+ isPub: false,
1280
+ isExtern: false,
1281
+ isComptime: false,
1282
+ isThreadlocal: false,
1283
+ name: 'x',
1284
+ initExpr: { type: 'IntegerLiteral', value: '1' },
1285
+ }],
1286
+ },
1287
+ }],
1288
+ });
1289
+ });
1290
+
1291
+ test('usingnamespace declaration', async t => {
1292
+ const result = await runParser(
1293
+ zigSourceFileParser,
1294
+ 'pub usingnamespace @import("c");',
1295
+ stringParserInputCompanion,
1296
+ );
1297
+
1298
+ t.deepEqual(result, {
1299
+ type: 'Root',
1300
+ members: [{
1301
+ type: 'UsingnamespaceDecl',
1302
+ isPub: true,
1303
+ expression: {
1304
+ type: 'BuiltinCallExpr',
1305
+ name: 'import',
1306
+ args: [{ type: 'StringLiteral', value: 'c' }],
1307
+ },
1308
+ }],
1309
+ });
1310
+ });
1311
+
1312
+ test('function with return statement', async t => {
1313
+ const result = await runParser(
1314
+ zigSourceFileParser,
1315
+ 'fn foo() u32 { return 42; }',
1316
+ stringParserInputCompanion,
1317
+ );
1318
+
1319
+ const fn = result.members[0];
1320
+ if (fn.type === 'FnDecl' && fn.body) {
1321
+ t.is(fn.body.statements.length, 1);
1322
+ t.deepEqual(fn.body.statements[0], {
1323
+ type: 'ReturnStmt',
1324
+ value: { type: 'IntegerLiteral', value: '42' },
1325
+ });
1326
+ } else {
1327
+ t.fail('Expected FnDecl with body');
1328
+ }
1329
+ });
1330
+
1331
+ test('field access expression', async t => {
1332
+ const result = await runParser(
1333
+ zigSourceFileParser,
1334
+ 'const x = std.debug.print;',
1335
+ stringParserInputCompanion,
1336
+ );
1337
+
1338
+ const decl = result.members[0];
1339
+ if (decl.type === 'VarDecl') {
1340
+ t.deepEqual(decl.initExpr, {
1341
+ type: 'FieldAccessExpr',
1342
+ operand: {
1343
+ type: 'FieldAccessExpr',
1344
+ operand: { type: 'Identifier', name: 'std' },
1345
+ member: 'debug',
1346
+ },
1347
+ member: 'print',
1348
+ });
1349
+ } else {
1350
+ t.fail('Expected VarDecl');
1351
+ }
1352
+ });
1353
+
1354
+ test('function call expression', async t => {
1355
+ const result = await runParser(
1356
+ zigSourceFileParser,
1357
+ 'const x = foo(1, 2);',
1358
+ stringParserInputCompanion,
1359
+ );
1360
+
1361
+ const decl = result.members[0];
1362
+ if (decl.type === 'VarDecl') {
1363
+ t.deepEqual(decl.initExpr, {
1364
+ type: 'CallExpr',
1365
+ callee: { type: 'Identifier', name: 'foo' },
1366
+ args: [
1367
+ { type: 'IntegerLiteral', value: '1' },
1368
+ { type: 'IntegerLiteral', value: '2' },
1369
+ ],
1370
+ });
1371
+ } else {
1372
+ t.fail('Expected VarDecl');
1373
+ }
1374
+ });
1375
+
1376
+ test('binary expression', async t => {
1377
+ const result = await runParser(
1378
+ zigSourceFileParser,
1379
+ 'const x = a + b;',
1380
+ stringParserInputCompanion,
1381
+ );
1382
+
1383
+ const decl = result.members[0];
1384
+ if (decl.type === 'VarDecl') {
1385
+ t.deepEqual(decl.initExpr, {
1386
+ type: 'BinaryExpr',
1387
+ operator: '+',
1388
+ left: { type: 'Identifier', name: 'a' },
1389
+ right: { type: 'Identifier', name: 'b' },
1390
+ });
1391
+ } else {
1392
+ t.fail('Expected VarDecl');
1393
+ }
1394
+ });
1395
+
1396
+ test('operator precedence: multiplication before addition', async t => {
1397
+ const result = await runParser(
1398
+ zigSourceFileParser,
1399
+ 'const x = a + b * c;',
1400
+ stringParserInputCompanion,
1401
+ );
1402
+
1403
+ const decl = result.members[0];
1404
+ if (decl.type === 'VarDecl') {
1405
+ t.deepEqual(decl.initExpr, {
1406
+ type: 'BinaryExpr',
1407
+ operator: '+',
1408
+ left: { type: 'Identifier', name: 'a' },
1409
+ right: {
1410
+ type: 'BinaryExpr',
1411
+ operator: '*',
1412
+ left: { type: 'Identifier', name: 'b' },
1413
+ right: { type: 'Identifier', name: 'c' },
1414
+ },
1415
+ });
1416
+ } else {
1417
+ t.fail('Expected VarDecl');
1418
+ }
1419
+ });
1420
+
1421
+ test('error union type', async t => {
1422
+ const result = await runParser(
1423
+ zigSourceFileParser,
1424
+ 'fn foo() anyerror!u32 {}',
1425
+ stringParserInputCompanion,
1426
+ );
1427
+
1428
+ const fn = result.members[0];
1429
+ if (fn.type === 'FnDecl') {
1430
+ t.deepEqual(fn.returnType, {
1431
+ type: 'ErrorUnionType',
1432
+ error: { type: 'Identifier', name: 'anyerror' },
1433
+ payload: { type: 'Identifier', name: 'u32' },
1434
+ });
1435
+ } else {
1436
+ t.fail('Expected FnDecl');
1437
+ }
1438
+ });
1439
+
1440
+ test('if expression in const', async t => {
1441
+ const result = await runParser(
1442
+ zigSourceFileParser,
1443
+ 'const x = if (a) b else c;',
1444
+ stringParserInputCompanion,
1445
+ );
1446
+
1447
+ const decl = result.members[0];
1448
+ if (decl.type === 'VarDecl') {
1449
+ t.deepEqual(decl.initExpr, {
1450
+ type: 'IfExpr',
1451
+ condition: { type: 'Identifier', name: 'a' },
1452
+ body: { type: 'Identifier', name: 'b' },
1453
+ elseBody: { type: 'Identifier', name: 'c' },
1454
+ });
1455
+ } else {
1456
+ t.fail('Expected VarDecl');
1457
+ }
1458
+ });
1459
+
1460
+ test('struct init expression', async t => {
1461
+ const result = await runParser(
1462
+ zigSourceFileParser,
1463
+ 'const x = .{ .a = 1, .b = 2 };',
1464
+ stringParserInputCompanion,
1465
+ );
1466
+
1467
+ const decl = result.members[0];
1468
+ if (decl.type === 'VarDecl') {
1469
+ t.deepEqual(decl.initExpr, {
1470
+ type: 'StructInitExpr',
1471
+ fields: [
1472
+ { type: 'StructInitField', name: 'a', value: { type: 'IntegerLiteral', value: '1' } },
1473
+ { type: 'StructInitField', name: 'b', value: { type: 'IntegerLiteral', value: '2' } },
1474
+ ],
1475
+ });
1476
+ } else {
1477
+ t.fail('Expected VarDecl');
1478
+ }
1479
+ });
1480
+
1481
+ test('enum literal', async t => {
1482
+ const result = await runParser(
1483
+ zigSourceFileParser,
1484
+ 'const x = .foo;',
1485
+ stringParserInputCompanion,
1486
+ );
1487
+
1488
+ const decl = result.members[0];
1489
+ if (decl.type === 'VarDecl') {
1490
+ t.deepEqual(decl.initExpr, {
1491
+ type: 'EnumLiteral',
1492
+ name: 'foo',
1493
+ });
1494
+ } else {
1495
+ t.fail('Expected VarDecl');
1496
+ }
1497
+ });
1498
+
1499
+ test('multiple declarations', async t => {
1500
+ const source = `
1501
+ const std = @import("std");
1502
+
1503
+ pub fn main() void {}
1504
+
1505
+ test "hello" {}
1506
+ `;
1507
+ const result = await runParser(
1508
+ zigSourceFileParser,
1509
+ source,
1510
+ stringParserInputCompanion,
1511
+ );
1512
+
1513
+ t.is(result.members.length, 3);
1514
+ t.is(result.members[0].type, 'VarDecl');
1515
+ t.is(result.members[1].type, 'FnDecl');
1516
+ t.is(result.members[2].type, 'TestDecl');
1517
+ });
1518
+
1519
+ test('line comments are skipped', async t => {
1520
+ const source = `
1521
+ // This is a comment
1522
+ const x = 1;
1523
+ // Another comment
1524
+ const y = 2;
1525
+ `;
1526
+ const result = await runParser(
1527
+ zigSourceFileParser,
1528
+ source,
1529
+ stringParserInputCompanion,
1530
+ );
1531
+
1532
+ t.is(result.members.length, 2);
1533
+ });
1534
+
1535
+ test('try expression', async t => {
1536
+ const result = await runParser(
1537
+ zigSourceFileParser,
1538
+ 'const x = try foo();',
1539
+ stringParserInputCompanion,
1540
+ );
1541
+
1542
+ const decl = result.members[0];
1543
+ if (decl.type === 'VarDecl') {
1544
+ t.deepEqual(decl.initExpr, {
1545
+ type: 'TryExpr',
1546
+ operand: {
1547
+ type: 'CallExpr',
1548
+ callee: { type: 'Identifier', name: 'foo' },
1549
+ args: [],
1550
+ },
1551
+ });
1552
+ } else {
1553
+ t.fail('Expected VarDecl');
1554
+ }
1555
+ });
1556
+
1557
+ test('unary negation', async t => {
1558
+ const result = await runParser(
1559
+ zigSourceFileParser,
1560
+ 'const x = -a;',
1561
+ stringParserInputCompanion,
1562
+ );
1563
+
1564
+ const decl = result.members[0];
1565
+ if (decl.type === 'VarDecl') {
1566
+ t.deepEqual(decl.initExpr, {
1567
+ type: 'UnaryExpr',
1568
+ operator: '-',
1569
+ operand: { type: 'Identifier', name: 'a' },
1570
+ });
1571
+ } else {
1572
+ t.fail('Expected VarDecl');
1573
+ }
1574
+ });
1575
+
1576
+ test('bool and null literals', async t => {
1577
+ const source = `
1578
+ const a = true;
1579
+ const b = false;
1580
+ const c = null;
1581
+ const d = undefined;
1582
+ `;
1583
+ const result = await runParser(
1584
+ zigSourceFileParser,
1585
+ source,
1586
+ stringParserInputCompanion,
1587
+ );
1588
+
1589
+ t.is(result.members.length, 4);
1590
+ if (result.members[0].type === 'VarDecl') {
1591
+ t.deepEqual(result.members[0].initExpr, { type: 'BoolLiteral', value: true });
1592
+ }
1593
+ if (result.members[1].type === 'VarDecl') {
1594
+ t.deepEqual(result.members[1].initExpr, { type: 'BoolLiteral', value: false });
1595
+ }
1596
+ if (result.members[2].type === 'VarDecl') {
1597
+ t.deepEqual(result.members[2].initExpr, { type: 'NullLiteral' });
1598
+ }
1599
+ if (result.members[3].type === 'VarDecl') {
1600
+ t.deepEqual(result.members[3].initExpr, { type: 'UndefinedLiteral' });
1601
+ }
1602
+ });
1603
+
1604
+ test('assignment statement in function body', async t => {
1605
+ const result = await runParser(
1606
+ zigSourceFileParser,
1607
+ 'fn foo() void { x = 1; }',
1608
+ stringParserInputCompanion,
1609
+ );
1610
+
1611
+ const fn = result.members[0];
1612
+ if (fn.type === 'FnDecl' && fn.body) {
1613
+ t.deepEqual(fn.body.statements[0], {
1614
+ type: 'AssignStmt',
1615
+ target: { type: 'Identifier', name: 'x' },
1616
+ operator: '=',
1617
+ value: { type: 'IntegerLiteral', value: '1' },
1618
+ });
1619
+ } else {
1620
+ t.fail('Expected FnDecl with body');
1621
+ }
1622
+ });
1623
+
1624
+ test('defer statement', async t => {
1625
+ const result = await runParser(
1626
+ zigSourceFileParser,
1627
+ 'fn foo() void { defer bar(); }',
1628
+ stringParserInputCompanion,
1629
+ );
1630
+
1631
+ const fn = result.members[0];
1632
+ if (fn.type === 'FnDecl' && fn.body) {
1633
+ const stmt = fn.body.statements[0];
1634
+ t.is(stmt.type, 'DeferStmt');
1635
+ if (stmt.type === 'DeferStmt') {
1636
+ t.is(stmt.isErrdefer, false);
1637
+ }
1638
+ } else {
1639
+ t.fail('Expected FnDecl with body');
1640
+ }
1641
+ });
1642
+
1643
+ test('orelse expression', async t => {
1644
+ const result = await runParser(
1645
+ zigSourceFileParser,
1646
+ 'const x = a orelse b;',
1647
+ stringParserInputCompanion,
1648
+ );
1649
+
1650
+ const decl = result.members[0];
1651
+ if (decl.type === 'VarDecl') {
1652
+ t.deepEqual(decl.initExpr, {
1653
+ type: 'BinaryExpr',
1654
+ operator: 'orelse',
1655
+ left: { type: 'Identifier', name: 'a' },
1656
+ right: { type: 'Identifier', name: 'b' },
1657
+ });
1658
+ } else {
1659
+ t.fail('Expected VarDecl');
1660
+ }
1661
+ });
1662
+
1663
+ // Integration test: compare with Zig reference parser
1664
+ const compareWithZigParser = test.macro({
1665
+ title: (_, relativePath: string) => `compare: ${relativePath}`,
1666
+ exec: async (t, relativePath: string) => {
1667
+ if (!await hasZigPromise) {
1668
+ t.pass('skipping test because zig is not available');
1669
+ return;
1670
+ }
1671
+
1672
+ await using repo = await cloneZigRepo();
1673
+ const filePath = path.join(repo.path, relativePath);
1674
+
1675
+ // Parse with Zig reference (std.zig.Ast)
1676
+ const zigOutput = await runZigDumper(zigAstDumperSnippet, filePath);
1677
+ const expected = JSON.parse(zigOutput);
1678
+
1679
+ // Parse with our parser
1680
+ const source = await fs.readFile(filePath, 'utf-8');
1681
+ const actual = await runParser(
1682
+ zigSourceFileParser,
1683
+ source,
1684
+ stringParserInputCompanion,
1685
+ );
1686
+
1687
+ // Compare full AST
1688
+ t.deepEqual(actual, expected);
1689
+ },
1690
+ });
1691
+
1692
+ test(compareWithZigParser, 'lib/std/compress.zig');
1693
+ test(compareWithZigParser, 'lib/std/once.zig');