| ... | ... | @@ -0,0 +1,641 @@ |
| 1 | const std = @import("std"); |
| 2 | const GenerateDef = @This(); |
| 3 | const Step = std.Build.Step; |
| 4 | const Allocator = std.mem.Allocator; |
| 5 | const GeneratedFile = std.Build.GeneratedFile; |
| 6 | |
| 7 | step: Step, |
| 8 | path: []const u8, |
| 9 | generated_file: GeneratedFile, |
| 10 | |
| 11 | pub const base_id: Step.Id = .custom; |
| 12 | |
| 13 | pub 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 | |
| 42 | fn 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 | |
| 90 | const Value = struct { |
| 91 | name: []const u8, |
| 92 | properties: []const []const u8, |
| 93 | }; |
| 94 | |
| 95 | fn 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 | |
| 383 | fn 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 | |
| 402 | const 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 | }; |