authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2023-11-02 22:05:13-07:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-11-03 22:18:40+02:00
loge6b9312bd01a3c9a23c59b1268c96879e41bc5a5
treecf1f50c587250e875a12dcfa72b799ae72aedf74
parent9ea0c3b4c67ab0cbee7ac49f65112bb052002438

aro: Add stub definitions to enable bootstrapping

Co-authored-by: Veikka Tuominen <git@vexu.eu>

5 files changed, 695 insertions(+), 2 deletions(-)

CMakeLists.txt+5-1
...@@ -645,6 +645,8 @@ set(ZIG_STAGE2_SOURCES...@@ -645,6 +645,8 @@ set(ZIG_STAGE2_SOURCES
645 "${CMAKE_SOURCE_DIR}/src/value.zig"645 "${CMAKE_SOURCE_DIR}/src/value.zig"
646 "${CMAKE_SOURCE_DIR}/src/wasi_libc.zig"646 "${CMAKE_SOURCE_DIR}/src/wasi_libc.zig"
647 "${CMAKE_SOURCE_DIR}/src/windows_sdk.zig"647 "${CMAKE_SOURCE_DIR}/src/windows_sdk.zig"
648 "${CMAKE_SOURCE_DIR}/src/stubs/aro_builtins.zig"
649 "${CMAKE_SOURCE_DIR}/src/stubs/aro_names.zig"
648)650)
649651
650if(MSVC)652if(MSVC)
...@@ -815,7 +817,9 @@ set(BUILD_ZIG2_ARGS...@@ -815,7 +817,9 @@ set(BUILD_ZIG2_ARGS
815 -OReleaseSmall817 -OReleaseSmall
816 --name zig2 -femit-bin="${ZIG2_C_SOURCE}"818 --name zig2 -femit-bin="${ZIG2_C_SOURCE}"
817 --mod "build_options::${ZIG_CONFIG_ZIG_OUT}"819 --mod "build_options::${ZIG_CONFIG_ZIG_OUT}"
818 --mod "aro::deps/aro/lib.zig"820 --mod "Builtins/Builtin.def::src/stubs/aro_builtins.zig"
821 --mod "Attribute/names.def::src/stubs/aro_names.zig"
822 --mod "aro:Builtins/Builtin.def,Attribute/names.def:deps/aro/lib.zig"
819 --deps build_options,aro823 --deps build_options,aro
820 -target "${ZIG_HOST_TARGET_TRIPLE}"824 -target "${ZIG_HOST_TARGET_TRIPLE}"
821)825)
build.zig+6-1
...@@ -8,6 +8,7 @@ const io = std.io;...@@ -8,6 +8,7 @@ const io = std.io;
8const fs = std.fs;8const fs = std.fs;
9const InstallDirectoryOptions = std.Build.InstallDirectoryOptions;9const InstallDirectoryOptions = std.Build.InstallDirectoryOptions;
10const assert = std.debug.assert;10const assert = std.debug.assert;
11const GenerateDef = @import("deps/aro/build/GenerateDef.zig");
1112
12const zig_version = std.SemanticVersion{ .major = 0, .minor = 12, .patch = 0 };13const zig_version = std.SemanticVersion{ .major = 0, .minor = 12, .patch = 0 };
13const stack_size = 32 * 1024 * 1024;14const stack_size = 32 * 1024 * 1024;
...@@ -581,9 +582,13 @@ fn addCompilerStep(...@@ -581,9 +582,13 @@ fn addCompilerStep(
581 .max_rss = 7_000_000_000,582 .max_rss = 7_000_000_000,
582 });583 });
583 exe.stack_size = stack_size;584 exe.stack_size = stack_size;
584 exe.addAnonymousModule("aro", .{585 const aro_module = b.createModule(.{
585 .source_file = .{ .path = "deps/aro/lib.zig" },586 .source_file = .{ .path = "deps/aro/lib.zig" },
586 });587 });
588 GenerateDef.add(b, "deps/aro/Builtins/Builtin.def", "Builtins/Builtin.def", exe, aro_module);
589 GenerateDef.add(b, "deps/aro/Attribute/names.def", "Attribute/names.def", exe, aro_module);
590
591 exe.addModule("aro", aro_module);
587 return exe;592 return exe;
588}593}
589594
deps/aro/build/GenerateDef.zig created+641
...@@ -0,0 +1,641 @@
1const std = @import("std");
2const GenerateDef = @This();
3const Step = std.Build.Step;
4const Allocator = std.mem.Allocator;
5const GeneratedFile = std.Build.GeneratedFile;
6
7step: Step,
8path: []const u8,
9generated_file: GeneratedFile,
10
11pub const base_id: Step.Id = .custom;
12
13pub fn add(
14 owner: *std.Build,
15 def_file_path: []const u8,
16 import_path: []const u8,
17 compile_step: *Step.Compile,
18 aro_module: *std.Build.Module,
19) void {
20 const self = owner.allocator.create(GenerateDef) catch @panic("OOM");
21
22 const name = owner.fmt("GenerateDef {s}", .{def_file_path});
23 self.* = .{
24 .step = Step.init(.{
25 .id = base_id,
26 .name = name,
27 .owner = owner,
28 .makeFn = make,
29 }),
30 .path = def_file_path,
31 .generated_file = .{ .step = &self.step },
32 };
33
34 const module = owner.createModule(.{
35 .source_file = .{ .generated = &self.generated_file },
36 });
37 compile_step.addModule(import_path, module);
38 compile_step.step.dependOn(&self.step);
39 aro_module.dependencies.put(import_path, module) catch @panic("OOM");
40}
41
42fn make(step: *Step, prog_node: *std.Progress.Node) !void {
43 _ = prog_node;
44 const b = step.owner;
45 const self = @fieldParentPtr(GenerateDef, "step", step);
46 const arena = b.allocator;
47
48 var man = b.cache.obtain();
49 defer man.deinit();
50
51 // Random bytes to make GenerateDef unique. Refresh this with new
52 // random bytes when GenerateDef implementation is modified in a
53 // non-backwards-compatible way.
54 man.hash.add(@as(u32, 0xDCC14144));
55
56 const contents = try b.build_root.handle.readFileAlloc(arena, self.path, std.math.maxInt(u32));
57 man.hash.addBytes(contents);
58
59 const out_name = b.fmt("{s}.zig", .{std.fs.path.stem(self.path)});
60 if (try step.cacheHit(&man)) {
61 const digest = man.final();
62 self.generated_file.path = try b.cache_root.join(arena, &.{
63 "o", &digest, out_name,
64 });
65 return;
66 }
67
68 const digest = man.final();
69
70 const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, out_name });
71 const sub_path_dirname = std.fs.path.dirname(sub_path).?;
72
73 b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
74 return step.fail("unable to make path '{}{s}': {s}", .{
75 b.cache_root, sub_path_dirname, @errorName(err),
76 });
77 };
78
79 const output = try self.generate(contents);
80 b.cache_root.handle.writeFile(sub_path, output) catch |err| {
81 return step.fail("unable to write file '{}{s}': {s}", .{
82 b.cache_root, sub_path, @errorName(err),
83 });
84 };
85
86 self.generated_file.path = try b.cache_root.join(arena, &.{sub_path});
87 try man.writeManifest();
88}
89
90const Value = struct {
91 name: []const u8,
92 properties: []const []const u8,
93};
94
95fn generate(self: *GenerateDef, input: []const u8) ![]const u8 {
96 const arena = self.step.owner.allocator;
97
98 var values = std.StringArrayHashMap([]const []const u8).init(arena);
99 defer values.deinit();
100 var properties = std.ArrayList([]const u8).init(arena);
101 defer properties.deinit();
102 var headers = std.ArrayList([]const u8).init(arena);
103 defer headers.deinit();
104
105 var value_name: ?[]const u8 = null;
106 var it = std.mem.tokenizeAny(u8, input, "\r\n");
107 while (it.next()) |line_untrimmed| {
108 const line = std.mem.trim(u8, line_untrimmed, " \t");
109 if (line.len == 0 or line[0] == '#') continue;
110 if (std.mem.startsWith(u8, line, "const ") or std.mem.startsWith(u8, line, "pub const ")) {
111 try headers.append(line);
112 continue;
113 }
114 if (line[0] == '.') {
115 if (value_name == null) {
116 return self.step.fail("property not attached to a value:\n\"{s}\"", .{line});
117 }
118 try properties.append(line);
119 continue;
120 }
121
122 if (value_name) |name| {
123 const old = try values.fetchPut(name, try properties.toOwnedSlice());
124 if (old != null) return self.step.fail("duplicate value \"{s}\"", .{name});
125 }
126 value_name = line;
127 }
128
129 if (value_name) |name| {
130 const old = try values.fetchPut(name, try properties.toOwnedSlice());
131 if (old != null) return self.step.fail("duplicate value \"{s}\"", .{name});
132 }
133
134 {
135 var sorted_list = try arena.dupe([]const u8, values.keys());
136 defer arena.free(sorted_list);
137 std.mem.sort([]const u8, sorted_list, {}, struct {
138 pub fn lessThan(_: void, a: []const u8, b: []const u8) bool {
139 return std.mem.lessThan(u8, a, b);
140 }
141 }.lessThan);
142
143 var longest_name: usize = 0;
144 var shortest_name: usize = std.math.maxInt(usize);
145
146 var builder = try DafsaBuilder.init(arena);
147 defer builder.deinit();
148 for (sorted_list) |name| {
149 try builder.insert(name);
150 longest_name = @max(name.len, longest_name);
151 shortest_name = @min(name.len, shortest_name);
152 }
153 try builder.finish();
154 builder.calcNumbers();
155
156 // As a sanity check, confirm that the minimal perfect hashing doesn't
157 // have any collisions
158 {
159 var index_set = std.AutoHashMap(usize, void).init(arena);
160 defer index_set.deinit();
161
162 for (values.keys()) |name| {
163 const index = builder.getUniqueIndex(name).?;
164 const result = try index_set.getOrPut(index);
165 if (result.found_existing) {
166 return self.step.fail("clobbered {}, name={s}\n", .{ index, name });
167 }
168 }
169 }
170
171 var values_array = try arena.alloc(Value, values.count());
172 defer arena.free(values_array);
173
174 for (values.keys(), values.values()) |name, props| {
175 const unique_index = builder.getUniqueIndex(name).?;
176 const data_index = unique_index - 1;
177 values_array[data_index] = .{ .name = name, .properties = props };
178 }
179
180 var out_buf = std.ArrayList(u8).init(arena);
181 defer out_buf.deinit();
182 const writer = out_buf.writer();
183
184 try writer.print(
185 \\//! Autogenerated by GenerateDef from {s}, do not edit
186 \\
187 \\const std = @import("std");
188 \\
189 \\pub fn with(comptime Properties: type) type {{
190 \\return struct {{
191 \\
192 , .{self.path});
193 for (headers.items) |line| {
194 try writer.print("{s}\n", .{line});
195 }
196 try writer.writeAll(
197 \\
198 \\tag: Tag,
199 \\properties: Properties,
200 \\
201 \\/// Integer starting at 0 derived from the unique index,
202 \\/// corresponds with the data array index.
203 \\pub const Tag = enum(u16) { _ };
204 \\
205 \\const Self = @This();
206 \\
207 \\pub fn fromName(name: []const u8) ?@This() {
208 \\ const data_index = tagFromName(name) orelse return null;
209 \\ return data[@intFromEnum(data_index)];
210 \\}
211 \\
212 \\pub fn tagFromName(name: []const u8) ?Tag {
213 \\ const unique_index = uniqueIndex(name) orelse return null;
214 \\ return @enumFromInt(unique_index - 1);
215 \\}
216 \\
217 \\pub fn fromTag(tag: Tag) @This() {
218 \\ return data[@intFromEnum(tag)];
219 \\}
220 \\
221 \\pub fn nameFromTagIntoBuf(tag: Tag, name_buf: []u8) []u8 {
222 \\ std.debug.assert(name_buf.len >= longest_name);
223 \\ const unique_index = @intFromEnum(tag) + 1;
224 \\ return nameFromUniqueIndex(unique_index, name_buf);
225 \\}
226 \\
227 \\pub fn nameFromTag(tag: Tag) NameBuf {
228 \\ var name_buf: NameBuf = undefined;
229 \\ const unique_index = @intFromEnum(tag) + 1;
230 \\ const name = nameFromUniqueIndex(unique_index, &name_buf.buf);
231 \\ name_buf.len = @intCast(name.len);
232 \\ return name_buf;
233 \\}
234 \\
235 \\pub const NameBuf = struct {
236 \\ buf: [longest_name]u8 = undefined,
237 \\ len: std.math.IntFittingRange(0, longest_name),
238 \\
239 \\ pub fn span(self: *const NameBuf) []const u8 {
240 \\ return self.buf[0..self.len];
241 \\ }
242 \\};
243 \\
244 \\pub fn exists(name: []const u8) bool {
245 \\ if (name.len < shortest_name or name.len > longest_name) return false;
246 \\
247 \\ var index: u16 = 0;
248 \\ for (name) |c| {
249 \\ index = findInList(dafsa[index].child_index, c) orelse return false;
250 \\ }
251 \\ return dafsa[index].end_of_word;
252 \\}
253 \\
254 \\
255 );
256 try writer.print("pub const shortest_name = {};\n", .{shortest_name});
257 try writer.print("pub const longest_name = {};\n\n", .{longest_name});
258 try writer.writeAll(
259 \\/// Search siblings of `first_child_index` for the `char`
260 \\/// If found, returns the index of the node within the `dafsa` array.
261 \\/// Otherwise, returns `null`.
262 \\pub fn findInList(first_child_index: u16, char: u8) ?u16 {
263 \\ var index = first_child_index;
264 \\ while (true) {
265 \\ if (dafsa[index].char == char) return index;
266 \\ if (dafsa[index].end_of_list) return null;
267 \\ index += 1;
268 \\ }
269 \\ unreachable;
270 \\}
271 \\
272 \\/// Returns a unique (minimal perfect hash) index (starting at 1) for the `name`,
273 \\/// or null if the name was not found.
274 \\pub fn uniqueIndex(name: []const u8) ?u16 {
275 \\ if (name.len < shortest_name or name.len > longest_name) return null;
276 \\
277 \\ var index: u16 = 0;
278 \\ var node_index: u16 = 0;
279 \\
280 \\ for (name) |c| {
281 \\ const child_index = findInList(dafsa[node_index].child_index, c) orelse return null;
282 \\ var sibling_index = dafsa[node_index].child_index;
283 \\ while (true) {
284 \\ const sibling_c = dafsa[sibling_index].char;
285 \\ std.debug.assert(sibling_c != 0);
286 \\ if (sibling_c < c) {
287 \\ index += dafsa[sibling_index].number;
288 \\ }
289 \\ if (dafsa[sibling_index].end_of_list) break;
290 \\ sibling_index += 1;
291 \\ }
292 \\ node_index = child_index;
293 \\ if (dafsa[node_index].end_of_word) index += 1;
294 \\ }
295 \\
296 \\ if (!dafsa[node_index].end_of_word) return null;
297 \\
298 \\ return index;
299 \\}
300 \\
301 \\/// Returns a slice of `buf` with the name associated with the given `index`.
302 \\/// This function should only be called with an `index` that
303 \\/// is already known to exist within the `dafsa`, e.g. an index
304 \\/// returned from `uniqueIndex`.
305 \\pub fn nameFromUniqueIndex(index: u16, buf: []u8) []u8 {
306 \\ std.debug.assert(index >= 1 and index <= data.len);
307 \\
308 \\ var node_index: u16 = 0;
309 \\ var count: u16 = index;
310 \\ var fbs = std.io.fixedBufferStream(buf);
311 \\ const w = fbs.writer();
312 \\
313 \\ while (true) {
314 \\ var sibling_index = dafsa[node_index].child_index;
315 \\ while (true) {
316 \\ if (dafsa[sibling_index].number > 0 and dafsa[sibling_index].number < count) {
317 \\ count -= dafsa[sibling_index].number;
318 \\ } else {
319 \\ w.writeByte(dafsa[sibling_index].char) catch unreachable;
320 \\ node_index = sibling_index;
321 \\ if (dafsa[node_index].end_of_word) {
322 \\ count -= 1;
323 \\ }
324 \\ break;
325 \\ }
326 \\
327 \\ if (dafsa[sibling_index].end_of_list) break;
328 \\ sibling_index += 1;
329 \\ }
330 \\ if (count == 0) break;
331 \\ }
332 \\
333 \\ return fbs.getWritten();
334 \\}
335 \\
336 \\
337 );
338 try writer.writeAll(
339 \\/// We're 1 bit shy of being able to fit this in a u32:
340 \\/// - char only contains 0-9, a-z, A-Z, and _, so it could use a enum(u6) with a way to convert <-> u8
341 \\/// (note: this would have a performance cost that may make the u32 not worth it)
342 \\/// - number has a max value of > 2047 and < 4095 (the first _ node has the largest number),
343 \\/// so it could fit into a u12
344 \\/// - child_index currently has a max of > 4095 and < 8191, so it could fit into a u13
345 \\///
346 \\/// with the end_of_word/end_of_list 2 bools, that makes 33 bits total
347 \\const Node = packed struct(u64) {
348 \\ char: u8,
349 \\ /// Nodes are numbered with "an integer which gives the number of words that
350 \\ /// would be accepted by the automaton starting from that state." This numbering
351 \\ /// allows calculating "a one-to-one correspondence between the integers 1 to L
352 \\ /// (L is the number of words accepted by the automaton) and the words themselves."
353 \\ ///
354 \\ /// Essentially, this allows us to have a minimal perfect hashing scheme such that
355 \\ /// it's possible to store & lookup the properties of each builtin using a separate array.
356 \\ number: u16,
357 \\ /// If true, this node is the end of a valid builtin.
358 \\ /// Note: This does not necessarily mean that this node does not have child nodes.
359 \\ end_of_word: bool,
360 \\ /// If true, this node is the end of a sibling list.
361 \\ /// If false, then (index + 1) will contain the next sibling.
362 \\ end_of_list: bool,
363 \\ /// Padding bits to get to u64, unsure if there's some way to use these to improve something.
364 \\ _extra: u22 = 0,
365 \\ /// Index of the first child of this node.
366 \\ child_index: u16,
367 \\};
368 \\
369 \\
370 );
371 try builder.writeDafsa(writer);
372 try writeData(writer, values_array);
373 try writer.writeAll(
374 \\};
375 \\}
376 \\
377 );
378
379 return out_buf.toOwnedSlice();
380 }
381}
382
383fn writeData(writer: anytype, values: []const Value) !void {
384 try writer.writeAll("pub const data = blk: {\n");
385 try writer.print(" @setEvalBranchQuota({});\n", .{values.len});
386 try writer.writeAll(" break :blk [_]@This(){\n");
387 for (values, 0..) |value, i| {
388 try writer.print(" // {s}\n", .{value.name});
389 try writer.print(" .{{ .tag = @enumFromInt({}), .properties = .{{", .{i});
390 for (value.properties, 0..) |property, j| {
391 if (j != 0) try writer.writeByte(',');
392 try writer.writeByte(' ');
393 try writer.writeAll(property);
394 }
395 if (value.properties.len != 0) try writer.writeByte(' ');
396 try writer.writeAll("} },\n");
397 }
398 try writer.writeAll(" };\n");
399 try writer.writeAll("};\n");
400}
401
402const DafsaBuilder = struct {
403 root: *Node,
404 arena: std.heap.ArenaAllocator.State,
405 allocator: Allocator,
406 unchecked_nodes: std.ArrayListUnmanaged(UncheckedNode),
407 minimized_nodes: std.HashMapUnmanaged(*Node, *Node, Node.DuplicateContext, std.hash_map.default_max_load_percentage),
408 previous_word_buf: [128]u8 = undefined,
409 previous_word: []u8 = &[_]u8{},
410
411 const UncheckedNode = struct {
412 parent: *Node,
413 char: u8,
414 child: *Node,
415 };
416
417 pub fn init(allocator: Allocator) !DafsaBuilder {
418 var arena = std.heap.ArenaAllocator.init(allocator);
419 errdefer arena.deinit();
420
421 var root = try arena.allocator().create(Node);
422 root.* = .{};
423 return DafsaBuilder{
424 .root = root,
425 .allocator = allocator,
426 .arena = arena.state,
427 .unchecked_nodes = .{},
428 .minimized_nodes = .{},
429 };
430 }
431
432 pub fn deinit(self: *DafsaBuilder) void {
433 self.arena.promote(self.allocator).deinit();
434 self.unchecked_nodes.deinit(self.allocator);
435 self.minimized_nodes.deinit(self.allocator);
436 self.* = undefined;
437 }
438
439 const Node = struct {
440 children: [256]?*Node = [_]?*Node{null} ** 256,
441 is_terminal: bool = false,
442 number: usize = 0,
443
444 const DuplicateContext = struct {
445 pub fn hash(ctx: @This(), key: *Node) u64 {
446 _ = ctx;
447 var hasher = std.hash.Wyhash.init(0);
448 std.hash.autoHash(&hasher, key.children);
449 std.hash.autoHash(&hasher, key.is_terminal);
450 return hasher.final();
451 }
452
453 pub fn eql(ctx: @This(), a: *Node, b: *Node) bool {
454 _ = ctx;
455 return a.is_terminal == b.is_terminal and std.mem.eql(?*Node, &a.children, &b.children);
456 }
457 };
458
459 pub fn calcNumbers(self: *Node) void {
460 self.number = @intFromBool(self.is_terminal);
461 for (self.children) |maybe_child| {
462 const child = maybe_child orelse continue;
463 // A node's number is the sum of the
464 // numbers of its immediate child nodes.
465 child.calcNumbers();
466 self.number += child.number;
467 }
468 }
469
470 pub fn numDirectChildren(self: *const Node) u8 {
471 var num: u8 = 0;
472 for (self.children) |child| {
473 if (child != null) num += 1;
474 }
475 return num;
476 }
477 };
478
479 pub fn insert(self: *DafsaBuilder, str: []const u8) !void {
480 if (std.mem.order(u8, str, self.previous_word) == .lt) {
481 @panic("insertion order must be sorted");
482 }
483
484 var common_prefix_len: usize = 0;
485 for (0..@min(str.len, self.previous_word.len)) |i| {
486 if (str[i] != self.previous_word[i]) break;
487 common_prefix_len += 1;
488 }
489
490 try self.minimize(common_prefix_len);
491
492 var node = if (self.unchecked_nodes.items.len == 0)
493 self.root
494 else
495 self.unchecked_nodes.getLast().child;
496
497 for (str[common_prefix_len..]) |c| {
498 std.debug.assert(node.children[c] == null);
499
500 var arena = self.arena.promote(self.allocator);
501 var child = try arena.allocator().create(Node);
502 self.arena = arena.state;
503
504 child.* = .{};
505 node.children[c] = child;
506 try self.unchecked_nodes.append(self.allocator, .{
507 .parent = node,
508 .char = c,
509 .child = child,
510 });
511 node = node.children[c].?;
512 }
513 node.is_terminal = true;
514
515 self.previous_word = self.previous_word_buf[0..str.len];
516 @memcpy(self.previous_word, str);
517 }
518
519 pub fn minimize(self: *DafsaBuilder, down_to: usize) !void {
520 if (self.unchecked_nodes.items.len == 0) return;
521 while (self.unchecked_nodes.items.len > down_to) {
522 const unchecked_node = self.unchecked_nodes.pop();
523 if (self.minimized_nodes.getPtr(unchecked_node.child)) |child| {
524 unchecked_node.parent.children[unchecked_node.char] = child.*;
525 } else {
526 try self.minimized_nodes.put(self.allocator, unchecked_node.child, unchecked_node.child);
527 }
528 }
529 }
530
531 pub fn finish(self: *DafsaBuilder) !void {
532 try self.minimize(0);
533 }
534
535 fn nodeCount(self: *const DafsaBuilder) usize {
536 return self.minimized_nodes.count();
537 }
538
539 fn edgeCount(self: *const DafsaBuilder) usize {
540 var count: usize = 0;
541 var it = self.minimized_nodes.iterator();
542 while (it.next()) |entry| {
543 for (entry.key_ptr.*.children) |child| {
544 if (child != null) count += 1;
545 }
546 }
547 return count;
548 }
549
550 fn contains(self: *const DafsaBuilder, str: []const u8) bool {
551 var node = self.root;
552 for (str) |c| {
553 node = node.children[c] orelse return false;
554 }
555 return node.is_terminal;
556 }
557
558 fn calcNumbers(self: *const DafsaBuilder) void {
559 self.root.calcNumbers();
560 }
561
562 fn getUniqueIndex(self: *const DafsaBuilder, str: []const u8) ?usize {
563 var index: usize = 0;
564 var node = self.root;
565
566 for (str) |c| {
567 const child = node.children[c] orelse return null;
568 for (node.children, 0..) |sibling, sibling_c| {
569 if (sibling == null) continue;
570 if (sibling_c < c) {
571 index += sibling.?.number;
572 }
573 }
574 node = child;
575 if (node.is_terminal) index += 1;
576 }
577
578 return index;
579 }
580
581 fn writeDafsa(self: *const DafsaBuilder, writer: anytype) !void {
582 try writer.writeAll("const dafsa = [_]Node{\n");
583
584 // write root
585 try writer.writeAll(" .{ .char = 0, .end_of_word = false, .end_of_list = true, .number = 0, .child_index = 1 },\n");
586
587 var queue = std.ArrayList(*Node).init(self.allocator);
588 defer queue.deinit();
589
590 var child_indexes = std.AutoHashMap(*Node, usize).init(self.allocator);
591 defer child_indexes.deinit();
592
593 try child_indexes.ensureTotalCapacity(@intCast(self.edgeCount()));
594
595 var first_available_index: usize = self.root.numDirectChildren() + 1;
596 first_available_index = try writeDafsaChildren(self.root, writer, &queue, &child_indexes, first_available_index);
597
598 while (queue.items.len > 0) {
599 // TODO: something with better time complexity
600 const node = queue.orderedRemove(0);
601
602 first_available_index = try writeDafsaChildren(node, writer, &queue, &child_indexes, first_available_index);
603 }
604
605 try writer.writeAll("};\n");
606 }
607
608 fn writeDafsaChildren(
609 node: *Node,
610 writer: anytype,
611 queue: *std.ArrayList(*Node),
612 child_indexes: *std.AutoHashMap(*Node, usize),
613 first_available_index: usize,
614 ) !usize {
615 var cur_available_index = first_available_index;
616 const num_children = node.numDirectChildren();
617 var child_i: usize = 0;
618 for (node.children, 0..) |maybe_child, c_usize| {
619 const child = maybe_child orelse continue;
620 const c: u8 = @intCast(c_usize);
621 const is_last_child = child_i == num_children - 1;
622
623 if (!child_indexes.contains(child)) {
624 const child_num_children = child.numDirectChildren();
625 if (child_num_children > 0) {
626 child_indexes.putAssumeCapacityNoClobber(child, cur_available_index);
627 cur_available_index += child_num_children;
628 }
629 try queue.append(child);
630 }
631
632 try writer.print(
633 " .{{ .char = '{c}', .end_of_word = {}, .end_of_list = {}, .number = {}, .child_index = {} }},\n",
634 .{ c, child.is_terminal, is_last_child, child.number, child_indexes.get(child) orelse 0 },
635 );
636
637 child_i += 1;
638 }
639 return cur_available_index;
640 }
641};
src/stubs/aro_builtins.zig created+33
...@@ -0,0 +1,33 @@
1//! Stub implementation only used when bootstrapping stage2
2//! Keep in sync with deps/aro/build/GenerateDef.zig
3
4pub fn with(comptime Properties: type) type {
5 return struct {
6 tag: Tag = @enumFromInt(0),
7 properties: Properties = undefined,
8 pub const max_param_count = 1;
9 pub const longest_name = 0;
10 pub const data = [_]@This(){.{}};
11 pub inline fn fromName(_: []const u8) ?@This() {
12 return .{};
13 }
14 pub fn nameFromUniqueIndex(_: u16, _: []u8) []u8 {
15 return "";
16 }
17 pub fn uniqueIndex(_: []const u8) ?u16 {
18 return null;
19 }
20 pub const Tag = enum(u16) { _ };
21 pub fn nameFromTag(_: Tag) NameBuf {
22 return .{};
23 }
24 pub fn tagFromName(name: []const u8) ?Tag {
25 return @enumFromInt(name.len);
26 }
27 pub const NameBuf = struct {
28 pub fn span(_: *const NameBuf) []const u8 {
29 return "";
30 }
31 };
32 };
33}
src/stubs/aro_names.zig created+10
...@@ -0,0 +1,10 @@
1//! Stub implementation only used when bootstrapping stage2
2//! Keep in sync with deps/aro/build/GenerateDef.zig
3
4pub fn with(comptime _: type) type {
5 return struct {
6 pub inline fn fromName(_: []const u8) ?@This() {
7 return null;
8 }
9 };
10}