From b231e66b49afa7ff2765817da0902cb8be43d53c Mon Sep 17 00:00:00 2001 From: Brett Broadhurst Date: Sun, 29 Mar 2026 16:34:54 -0600 Subject: [PATCH] refactor: no more optional lists in ast nodes --- src/Ast.zig | 11 +++--- src/Ast/Render.zig | 65 +++++++++++++-------------------- src/AstGen.zig | 91 +++++++++++++++++++++------------------------- src/Parse.zig | 6 +-- src/compile.zig | 15 +++++--- 5 files changed, 84 insertions(+), 104 deletions(-) diff --git a/src/Ast.zig b/src/Ast.zig index 47629b2..380e973 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -9,8 +9,7 @@ const assert = std.debug.assert; filename: []const u8, source: []const u8, -// TODO: Make this non-nullable. Empty files are valid. -root: ?*Node = null, +root: *Node, errors: []const Error, pub const Node = struct { @@ -98,7 +97,7 @@ pub const Node = struct { rhs: ?*Node, }, list: struct { - items: ?[]*Node, + items: []*Node, }, choice_expr: struct { start_expr: ?*Node, @@ -111,7 +110,7 @@ pub const Node = struct { }, knot_decl: struct { prototype: *Node, - children: ?[]*Node, + children: []*Node, }, }; @@ -145,7 +144,7 @@ pub const Node = struct { gpa: std.mem.Allocator, tag: Tag, span: Span, - items: ?[]*Node, + items: []*Node, ) !*Node { const node = try Node.create(gpa, tag, span); node.data = .{ @@ -195,7 +194,7 @@ pub const Node = struct { tag: Tag, loc: Span, prototype: *Node, - children: ?[]*Node, + children: []*Node, ) !*Node { const node = try Node.create(gpa, tag, loc); node.data = .{ diff --git a/src/Ast/Render.zig b/src/Ast/Render.zig index a9127f7..9b8e36f 100644 --- a/src/Ast/Render.zig +++ b/src/Ast/Render.zig @@ -337,10 +337,12 @@ fn renderAstWalk( node: *const Ast.Node, options: NodeOptions, ) !void { + const gpa = r.gpa; var children: std.ArrayListUnmanaged(?*const Ast.Node) = .empty; - defer children.deinit(r.gpa); + defer children.deinit(gpa); try renderAstNode(r, writer, node, options); + switch (node.tag) { .false_literal, .true_literal, @@ -361,19 +363,14 @@ fn renderAstWalk( .choice_stmt, .content, => { - const list = node.data.list.items; - if (list) |items| for (items) |n| { - try children.append(r.gpa, n); - }; + const data = node.data.list; + for (data.items) |child_node| try children.append(gpa, child_node); }, .choice_expr => { - const lhs = node.data.choice_expr.start_expr; - const mhs = node.data.choice_expr.option_expr; - const rhs = node.data.choice_expr.inner_expr; - - if (lhs) |n| try children.append(r.gpa, n); - if (mhs) |n| try children.append(r.gpa, n); - if (rhs) |n| try children.append(r.gpa, n); + const data = node.data.choice_expr; + if (data.start_expr) |lhs| try children.append(gpa, lhs); + if (data.option_expr) |mhs| try children.append(gpa, mhs); + if (data.inner_expr) |rhs| try children.append(gpa, rhs); }, .add_expr, .subtract_expr, @@ -417,38 +414,28 @@ fn renderAstWalk( .else_branch, .invalid, => { - const lhs = node.data.bin.lhs; - const rhs = node.data.bin.rhs; - - if (lhs) |n| try children.append(r.gpa, n); - if (rhs) |n| try children.append(r.gpa, n); + const data = node.data.bin; + if (data.lhs) |lhs| try children.append(gpa, lhs); + if (data.rhs) |rhs| try children.append(gpa, rhs); }, .knot_decl => { - const lhs = node.data.knot_decl.prototype; - try children.append(r.gpa, lhs); - - const list = node.data.knot_decl.children; - if (list) |items| for (items) |n| { - try children.append(r.gpa, n); - }; + const data = node.data.knot_decl; + try children.append(gpa, data.prototype); + for (data.children) |child_node| try children.append(gpa, child_node); }, .switch_stmt, .if_stmt, .multi_if_stmt => { - const expr = node.data.switch_stmt.condition_expr; - if (expr) |n| try children.append(r.gpa, n); - - const list = node.data.switch_stmt.cases; - for (list) |case_stmt| { - try children.append(r.gpa, case_stmt); - } + const data = node.data.switch_stmt; + if (data.condition_expr) |expr_node| try children.append(gpa, expr_node); + for (data.cases) |case_stmt| try children.append(gpa, case_stmt); }, .inline_logic_expr => { - const lhs = node.data.bin.lhs; - if (lhs) |n| try children.append(r.gpa, n); + const data = node.data.bin; + if (data.lhs) |lhs| try children.append(gpa, lhs); }, } for (children.items, 0..) |maybe_child, i| { const old_len = if (!options.is_root) - try r.prefix.pushChildPrefix(r.gpa, options.is_last) + try r.prefix.pushChildPrefix(gpa, options.is_last) else 0; const child_is_last = (i == children.items.len - 1); @@ -483,11 +470,9 @@ pub fn renderTree( defer r.prefix.deinit(gpa); defer r.lines.deinit(gpa); - if (ast.root) |root| { - try r.renderAstWalk(writer, root, .{ - .is_root = true, - .is_last = true, - }); - } + try r.renderAstWalk(writer, ast.root, .{ + .is_root = true, + .is_last = true, + }); try writer.flush(); } diff --git a/src/AstGen.zig b/src/AstGen.zig index c93feba..a4ddb5d 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -196,8 +196,7 @@ pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Ir { const fatal = if (tree.errors.len == 0) fatal: { // TODO: Make sure this is never null. - const root_node = tree.root.?; - file(&block, &file_scope, root_node) catch |err| switch (err) { + file(&block, &file_scope, tree.root) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.SemanticError => break :fatal true, else => |e| return e, @@ -789,9 +788,10 @@ fn exprStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst } fn inlineLogicExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref { - // TODO: Maybe we should introduce a unary node type to avoid optional checks? - const main_node = node.data.bin.lhs.?; - return expr(gi, scope, main_node); + if (node.data.bin.lhs) |lhs| { + return expr(gi, scope, lhs); + } + return .none; } fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void { @@ -817,7 +817,7 @@ fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void } stmt_has_else = true; }, - else => unreachable, + inline else => |tag| @panic("Unexpected node " ++ @tagName(tag)), } } } @@ -996,11 +996,11 @@ fn switchStmt( return switch_br.toRef(); } -fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerError!Ir.Inst.Ref { +fn contentExpr(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref { // FIXME: This is a placeholder until we figure out what this function should be returning. // TODO: Make sure that this is not nullable. - const node_list = expr_node.data.list.items.?; - for (node_list) |child_node| { + const data = node.data.list; + for (data.items) |child_node| { switch (child_node.tag) { .string_literal => { const result = try stringLiteral(block, child_node); @@ -1051,15 +1051,13 @@ fn choiceStmt( ) InnerError!void { const astgen = parent_block.astgen; const gpa = astgen.gpa; - const choice_branches = stmt_node.data.list.items.?; - assert(choice_branches.len != 0); - + const data = stmt_node.data.list; const choice_br = try parent_block.makePayloadNode(.choice_br); var case_indexes: std.ArrayListUnmanaged(u32) = .empty; - try case_indexes.ensureUnusedCapacity(gpa, choice_branches.len); + try case_indexes.ensureUnusedCapacity(gpa, data.items.len); defer case_indexes.deinit(gpa); - for (choice_branches) |branch_stmt| { + for (data.items) |branch_stmt| { assert(branch_stmt.tag == .choice_star_stmt or branch_stmt.tag == .choice_plus_stmt); const branch_data = branch_stmt.data.bin; const branch_expr = branch_data.lhs.?.data.choice_expr; @@ -1082,7 +1080,6 @@ fn choiceStmt( if (branch_data.rhs) |branch_body| { _ = try blockStmt(&sub_block, scope, branch_body); } - _ = try sub_block.addUnaryNode(.implicit_ret, .none); const body = sub_block.instructionsSlice(); const case_extra_len = @typeInfo(Ir.Inst.SwitchBr.Case).@"struct".fields.len + body.len; @@ -1106,7 +1103,7 @@ fn choiceStmt( astgen.instructions.items[@intFromEnum(choice_br)].data.payload = .{ .extra_index = astgen.addExtraAssumeCapacity( Ir.Inst.ChoiceBr{ - .cases_len = @intCast(choice_branches.len), + .cases_len = @intCast(data.items.len), }, ), .src_offset = @intCast(stmt_node.loc.start), @@ -1172,7 +1169,6 @@ fn callExpr( const gpa = astgen.gpa; const data = node.data.bin; const callee_node = data.lhs.?; - const args_node = data.rhs; const callee = try calleeExpr(gi, scope, callee_node); const scratch_top = astgen.scratch.items.len; @@ -1180,7 +1176,7 @@ fn callExpr( // FIXME: List nodes should not have optional slices. // This hack is an abomination. - const arguments: ?[]*Ast.Node = if (args_node) |n| if (n.data.list.items) |items| items else null else null; + const arguments: ?[]*Ast.Node = if (data.rhs) |args_node| args_node.data.list.items else null; const args_count = if (arguments) |args| args.len else 0; try astgen.scratch.resize(gpa, scratch_top + args_count); @@ -1365,10 +1361,10 @@ fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void { } } -fn blockStmt(block: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void { +fn blockStmt(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void { // TODO: Make sure that this value is concrete to omit check. - const block_stmts = stmt_node.data.list.items.?; - try blockInner(block, scope, block_stmts); + const data = node.data.list; + try blockInner(block, scope, data.items); } fn defaultBlock( @@ -1377,6 +1373,7 @@ fn defaultBlock( body_node: *const Ast.Node, ) InnerError!void { const astgen = gi.astgen; + const data = body_node.data.list; const decl_inst = try gi.addAsIndex(.{ .tag = .declaration, .data = .{ .payload = undefined }, @@ -1385,9 +1382,7 @@ fn defaultBlock( defer decl_scope.unstack(); const knot_inst = try decl_scope.makePayloadNode(.decl_knot); - // TODO: Make sure that this value is concrete to omit check. - const block_stmts = body_node.data.list.items.?; - try blockInner(&decl_scope, scope, block_stmts); + try blockInner(&decl_scope, scope, data.items); var stub_scope = decl_scope.makeSubBlock(); defer stub_scope.unstack(); @@ -1423,8 +1418,8 @@ fn stitchDeclInner( const stitch_inst = try decl_block.makePayloadNode(.decl_stitch); if (prototype_data.rhs) |args_node| { - const args_list = args_node.data.list.items.?; - for (args_list) |arg| { + const args_data = args_node.data.list; + for (args_data.items) |arg| { assert(arg.tag == .parameter_decl); const arg_str = try astgen.strFromNode(arg); const arg_inst = try decl_block.addStrTok(.param, arg_str.index, arg.loc.start); @@ -1437,8 +1432,8 @@ fn stitchDeclInner( } } if (body_node) |body| { - const body_list = body.data.list.items.?; - try blockInner(&decl_block, scope, body_list); + const body_data = body.data.list; + try blockInner(&decl_block, scope, body_data.items); } else { try blockInner(&decl_block, scope, &.{}); } @@ -1484,8 +1479,8 @@ fn functionDeclInner( const stitch_inst = try decl_block.makePayloadNode(.decl_function); if (prototype_data.rhs) |args_node| { - const args_list = args_node.data.list.items.?; - for (args_list) |arg| { + const args_data = args_node.data.list; + for (args_data.items) |arg| { assert(arg.tag == .parameter_decl); const arg_str = try astgen.strFromNode(arg); const arg_inst = try decl_block.addStrTok(.param, arg_str.index, arg.loc.start); @@ -1525,10 +1520,9 @@ fn functionDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) In fn knotDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerError!void { const astgen = gi.astgen; - const knot_data = decl_node.data.knot_decl; - const prototype_node = knot_data.prototype; + const data = decl_node.data.knot_decl; + const prototype_node = data.prototype; const identifier_node = prototype_node.data.bin.lhs.?; - const nested_decls_list = knot_data.children.?; const decl_inst = try gi.addAsIndex(.{ .tag = .declaration, .data = .{ .payload = undefined }, @@ -1543,8 +1537,8 @@ fn knotDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerE defer child_scope.deinit(); if (prototype_node.data.bin.rhs) |args_node| { - const args_list = args_node.data.list.items.?; - for (args_list) |arg| { + const args_data = args_node.data.list; + for (args_data.items) |arg| { assert(arg.tag == .parameter_decl); const arg_str = try astgen.strFromNode(arg); const arg_inst = try child_block.addStrTok(.param, arg_str.index, arg.loc.start); @@ -1556,8 +1550,8 @@ fn knotDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerE }); } } - if (nested_decls_list.len > 0) { - const first_child = nested_decls_list[0]; + if (data.children.len > 0) { + const first_child = data.children[0]; if (first_child.tag == .block_stmt) { try blockStmt(&child_block, &child_scope, first_child); node_index += 1; @@ -1567,7 +1561,7 @@ fn knotDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerE var nested_block = child_block.makeSubBlock(); defer nested_block.unstack(); - for (nested_decls_list[node_index..]) |nested_decl_node| { + for (data.children[node_index..]) |nested_decl_node| { switch (nested_decl_node.tag) { .stitch_decl => try stitchDecl(&nested_block, &child_scope, nested_decl_node), .function_decl => try functionDecl(&nested_block, &child_scope, nested_decl_node), @@ -1585,12 +1579,12 @@ fn knotDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerE }); } -fn file(gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void { +fn file(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void { + const astgen = gi.astgen; + const data = node.data.list; const file_inst = try gi.addAsIndex(.{ .tag = .file, - .data = .{ - .payload = undefined, - }, + .data = .{ .payload = undefined }, }); var node_index: usize = 0; @@ -1598,15 +1592,14 @@ fn file(gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void { defer file_scope.unstack(); // TODO: Make sure this is non-nullable. - const nested_decls_list = file_node.data.list.items orelse return; - if (nested_decls_list.len > 0) { - const first_child = nested_decls_list[0]; + if (data.items.len > 0) { + const first_child = data.items[0]; if (first_child.tag == .block_stmt) { try defaultBlock(&file_scope, scope, first_child); node_index += 1; } } - for (nested_decls_list[node_index..]) |child_node| { + for (data.items[node_index..]) |child_node| { switch (child_node.tag) { .knot_decl => try knotDecl(gi, scope, child_node), .stitch_decl => try stitchDecl(gi, scope, child_node), @@ -1615,9 +1608,9 @@ fn file(gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void { } } - const globals_len = gi.astgen.globals.items.len; - try gi.astgen.instructions.ensureUnusedCapacity(gi.astgen.gpa, globals_len); - for (gi.astgen.globals.items) |global| { + const globals_len = astgen.globals.items.len; + try astgen.instructions.ensureUnusedCapacity(astgen.gpa, globals_len); + for (astgen.globals.items) |global| { gi.instructions.appendAssumeCapacity(global); } return file_scope.setBlockBody(file_inst); diff --git a/src/Parse.zig b/src/Parse.zig index 20f07f7..4255a51 100644 --- a/src/Parse.zig +++ b/src/Parse.zig @@ -268,11 +268,11 @@ fn makeNodeSequence( loc: Ast.Node.Span, scratch_offset: usize, ) Error!*Ast.Node { - var list: ?[]*Ast.Node = null; if (!p.isScratchEmpty(context)) { - list = try p.nodeListFromScratch(scratch_offset, p.scratch.items.len); + const list = try p.nodeListFromScratch(scratch_offset, p.scratch.items.len); + return .createList(p.arena, tag, loc, list); } - return .createList(p.arena, tag, loc, list); + return .createList(p.arena, tag, loc, &.{}); } fn isBlockStackEmpty(p: *Parse, context: *const StmtContext) bool { diff --git a/src/compile.zig b/src/compile.zig index 7a54542..ee20a88 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -347,6 +347,15 @@ pub const Module = struct { options: Options, ) !Module { const tree = try Ast.parse(gpa, arena, options.source_bytes, options.filename, 0); + if (options.dump_writer) |w| { + if (options.dump_ast) { + try w.writeAll("=== AST ===\n"); + try tree.render(gpa, w, .{ + .use_color = options.dump_use_color, + }); + } + } + var module: Module = .{ .gpa = gpa, .arena = arena, @@ -356,12 +365,6 @@ pub const Module = struct { errdefer module.deinit(); if (options.dump_writer) |w| { - if (options.dump_ast) { - try w.writeAll("=== AST ===\n"); - try module.tree.render(gpa, w, .{ - .use_color = options.dump_use_color, - }); - } if (options.dump_ir) { try w.writeAll("=== Semantic IR ===\n"); try module.ir.dumpInfo(w);