| author | |
| committer | |
| log | 7ec729b3ae9ea368124592d8ef1cef422c51de3b |
| tree | 585c547065d982e21a1923de6a8d41adc9688f9d |
| parent | 31ecf75311f83efb0bc0f108c3a3fb6b4c64e153 |
3 files changed, 335 insertions(+), 599 deletions(-)
src/aro_translate_c.zig+11-293| ... | @@ -10,305 +10,23 @@ const Type = aro.Type; | ... | @@ -10,305 +10,23 @@ const Type = aro.Type; |
| 10 | const ast = @import("translate_c/ast.zig"); | 10 | const ast = @import("translate_c/ast.zig"); |
| 11 | const ZigNode = ast.Node; | 11 | const ZigNode = ast.Node; |
| 12 | const ZigTag = ZigNode.Tag; | 12 | const ZigTag = ZigNode.Tag; |
| 13 | const common = @import("translate_c/common.zig"); | ||
| 14 | const Error = common.Error; | ||
| 15 | const MacroProcessingError = common.MacroProcessingError; | ||
| 16 | const TypeError = common.TypeError; | ||
| 17 | const TransError = common.TransError; | ||
| 18 | const SymbolTable = common.SymbolTable; | ||
| 19 | const AliasList = common.AliasList; | ||
| 20 | const ResultUsed = common.ResultUsed; | ||
| 21 | const Scope = common.ScopeExtra(Context, Type); | ||
| 13 | 22 | ||
| 14 | const Error = mem.Allocator.Error; | ||
| 15 | const TransError = translate_c.TransError; | ||
| 16 | const TypeError = translate_c.TypeError; | ||
| 17 | const ResultUsed = translate_c.ResultUsed; | ||
| 18 | const AliasList = translate_c.AliasList; | ||
| 19 | const SymbolTable = translate_c.SymbolTable; | ||
| 20 | pub const Compilation = aro.Compilation; | 23 | pub const Compilation = aro.Compilation; |
| 21 | 24 | ||
| 22 | const Scope = struct { | ||
| 23 | id: Id, | ||
| 24 | parent: ?*Scope, | ||
| 25 | |||
| 26 | const Id = enum { | ||
| 27 | block, | ||
| 28 | root, | ||
| 29 | condition, | ||
| 30 | loop, | ||
| 31 | do_loop, | ||
| 32 | }; | ||
| 33 | |||
| 34 | /// Used for the scope of condition expressions, for example `if (cond)`. | ||
| 35 | /// The block is lazily initialised because it is only needed for rare | ||
| 36 | /// cases of comma operators being used. | ||
| 37 | const Condition = struct { | ||
| 38 | base: Scope, | ||
| 39 | block: ?Block = null, | ||
| 40 | |||
| 41 | fn getBlockScope(self: *Condition, c: *Context) !*Block { | ||
| 42 | if (self.block) |*b| return b; | ||
| 43 | self.block = try Block.init(c, &self.base, true); | ||
| 44 | return &self.block.?; | ||
| 45 | } | ||
| 46 | |||
| 47 | fn deinit(self: *Condition) void { | ||
| 48 | if (self.block) |*b| b.deinit(); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | |||
| 52 | /// Represents an in-progress ZigNode.Block. This struct is stack-allocated. | ||
| 53 | /// When it is deinitialized, it produces an ZigNode.Block which is allocated | ||
| 54 | /// into the main arena. | ||
| 55 | const Block = struct { | ||
| 56 | base: Scope, | ||
| 57 | statements: std.ArrayList(ZigNode), | ||
| 58 | variables: AliasList, | ||
| 59 | mangle_count: u32 = 0, | ||
| 60 | label: ?[]const u8 = null, | ||
| 61 | |||
| 62 | /// By default all variables are discarded, since we do not know in advance if they | ||
| 63 | /// will be used. This maps the variable's name to the Discard payload, so that if | ||
| 64 | /// the variable is subsequently referenced we can indicate that the discard should | ||
| 65 | /// be skipped during the intermediate AST -> Zig AST render step. | ||
| 66 | variable_discards: std.StringArrayHashMap(*ast.Payload.Discard), | ||
| 67 | |||
| 68 | /// When the block corresponds to a function, keep track of the return type | ||
| 69 | /// so that the return expression can be cast, if necessary | ||
| 70 | return_type: ?Type = null, | ||
| 71 | |||
| 72 | /// C static local variables are wrapped in a block-local struct. The struct | ||
| 73 | /// is named after the (mangled) variable name, the Zig variable within the | ||
| 74 | /// struct itself is given this name. | ||
| 75 | const StaticInnerName = "static"; | ||
| 76 | |||
| 77 | fn init(c: *Context, parent: *Scope, labeled: bool) !Block { | ||
| 78 | var blk = Block{ | ||
| 79 | .base = .{ | ||
| 80 | .id = .block, | ||
| 81 | .parent = parent, | ||
| 82 | }, | ||
| 83 | .statements = std.ArrayList(ZigNode).init(c.gpa), | ||
| 84 | .variables = AliasList.init(c.gpa), | ||
| 85 | .variable_discards = std.StringArrayHashMap(*ast.Payload.Discard).init(c.gpa), | ||
| 86 | }; | ||
| 87 | if (labeled) { | ||
| 88 | blk.label = try blk.makeMangledName(c, "blk"); | ||
| 89 | } | ||
| 90 | return blk; | ||
| 91 | } | ||
| 92 | |||
| 93 | fn deinit(self: *Block) void { | ||
| 94 | self.statements.deinit(); | ||
| 95 | self.variables.deinit(); | ||
| 96 | self.variable_discards.deinit(); | ||
| 97 | self.* = undefined; | ||
| 98 | } | ||
| 99 | |||
| 100 | fn complete(self: *Block, c: *Context) !ZigNode { | ||
| 101 | if (self.base.parent.?.id == .do_loop) { | ||
| 102 | // We reserve 1 extra statement if the parent is a do_loop. This is in case of | ||
| 103 | // do while, we want to put `if (cond) break;` at the end. | ||
| 104 | const alloc_len = self.statements.items.len + @boolToInt(self.base.parent.?.id == .do_loop); | ||
| 105 | var stmts = try c.arena.alloc(ZigNode, alloc_len); | ||
| 106 | stmts.len = self.statements.items.len; | ||
| 107 | @memcpy(stmts[0..self.statements.items.len], self.statements.items); | ||
| 108 | return ZigTag.block.create(c.arena, .{ | ||
| 109 | .label = self.label, | ||
| 110 | .stmts = stmts, | ||
| 111 | }); | ||
| 112 | } | ||
| 113 | if (self.statements.items.len == 0) return ZigTag.empty_block.init(); | ||
| 114 | return ZigTag.block.create(c.arena, .{ | ||
| 115 | .label = self.label, | ||
| 116 | .stmts = try c.arena.dupe(ZigNode, self.statements.items), | ||
| 117 | }); | ||
| 118 | } | ||
| 119 | |||
| 120 | /// Given the desired name, return a name that does not shadow anything from outer scopes. | ||
| 121 | /// Inserts the returned name into the scope. | ||
| 122 | /// The name will not be visible to callers of getAlias. | ||
| 123 | fn reserveMangledName(scope: *Block, c: *Context, name: []const u8) ![]const u8 { | ||
| 124 | return scope.createMangledName(c, name, true); | ||
| 125 | } | ||
| 126 | |||
| 127 | /// Same as reserveMangledName, but enables the alias immediately. | ||
| 128 | fn makeMangledName(scope: *Block, c: *Context, name: []const u8) ![]const u8 { | ||
| 129 | return scope.createMangledName(c, name, false); | ||
| 130 | } | ||
| 131 | |||
| 132 | fn createMangledName(scope: *Block, c: *Context, name: []const u8, reservation: bool) ![]const u8 { | ||
| 133 | const name_copy = try c.arena.dupe(u8, name); | ||
| 134 | var proposed_name = name_copy; | ||
| 135 | while (scope.contains(proposed_name)) { | ||
| 136 | scope.mangle_count += 1; | ||
| 137 | proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ name, scope.mangle_count }); | ||
| 138 | } | ||
| 139 | const new_mangle = try scope.variables.addOne(); | ||
| 140 | if (reservation) { | ||
| 141 | new_mangle.* = .{ .name = name_copy, .alias = name_copy }; | ||
| 142 | } else { | ||
| 143 | new_mangle.* = .{ .name = name_copy, .alias = proposed_name }; | ||
| 144 | } | ||
| 145 | return proposed_name; | ||
| 146 | } | ||
| 147 | |||
| 148 | fn getAlias(scope: *Block, name: []const u8) []const u8 { | ||
| 149 | for (scope.variables.items) |p| { | ||
| 150 | if (mem.eql(u8, p.name, name)) | ||
| 151 | return p.alias; | ||
| 152 | } | ||
| 153 | return scope.base.parent.?.getAlias(name); | ||
| 154 | } | ||
| 155 | |||
| 156 | fn localContains(scope: *Block, name: []const u8) bool { | ||
| 157 | for (scope.variables.items) |p| { | ||
| 158 | if (mem.eql(u8, p.alias, name)) | ||
| 159 | return true; | ||
| 160 | } | ||
| 161 | return false; | ||
| 162 | } | ||
| 163 | |||
| 164 | fn contains(scope: *Block, name: []const u8) bool { | ||
| 165 | if (scope.localContains(name)) | ||
| 166 | return true; | ||
| 167 | return scope.base.parent.?.contains(name); | ||
| 168 | } | ||
| 169 | |||
| 170 | fn discardVariable(scope: *Block, c: *Context, name: []const u8) Error!void { | ||
| 171 | const name_node = try ZigTag.identifier.create(c.arena, name); | ||
| 172 | const discard = try ZigTag.discard.create(c.arena, .{ .should_skip = false, .value = name_node }); | ||
| 173 | try scope.statements.append(discard); | ||
| 174 | try scope.variable_discards.putNoClobber(name, discard.castTag(.discard).?); | ||
| 175 | } | ||
| 176 | }; | ||
| 177 | |||
| 178 | const Root = struct { | ||
| 179 | base: Scope, | ||
| 180 | sym_table: SymbolTable, | ||
| 181 | macro_table: SymbolTable, | ||
| 182 | context: *Context, | ||
| 183 | nodes: std.ArrayList(ZigNode), | ||
| 184 | |||
| 185 | fn init(c: *Context) Root { | ||
| 186 | return .{ | ||
| 187 | .base = .{ | ||
| 188 | .id = .root, | ||
| 189 | .parent = null, | ||
| 190 | }, | ||
| 191 | .sym_table = SymbolTable.init(c.gpa), | ||
| 192 | .macro_table = SymbolTable.init(c.gpa), | ||
| 193 | .context = c, | ||
| 194 | .nodes = std.ArrayList(ZigNode).init(c.gpa), | ||
| 195 | }; | ||
| 196 | } | ||
| 197 | |||
| 198 | fn deinit(scope: *Root) void { | ||
| 199 | scope.sym_table.deinit(); | ||
| 200 | scope.macro_table.deinit(); | ||
| 201 | scope.nodes.deinit(); | ||
| 202 | } | ||
| 203 | |||
| 204 | /// Check if the global scope contains this name, without looking into the "future", e.g. | ||
| 205 | /// ignore the preprocessed decl and macro names. | ||
| 206 | fn containsNow(scope: *Root, name: []const u8) bool { | ||
| 207 | return scope.sym_table.contains(name) or scope.macro_table.contains(name); | ||
| 208 | } | ||
| 209 | |||
| 210 | /// Check if the global scope contains the name, includes all decls that haven't been translated yet. | ||
| 211 | fn contains(scope: *Root, name: []const u8) bool { | ||
| 212 | return scope.containsNow(name) or scope.context.global_names.contains(name); | ||
| 213 | } | ||
| 214 | }; | ||
| 215 | |||
| 216 | fn findBlockScope(inner: *Scope, c: *Context) !*Scope.Block { | ||
| 217 | var scope = inner; | ||
| 218 | while (true) { | ||
| 219 | switch (scope.id) { | ||
| 220 | .root => unreachable, | ||
| 221 | .block => return @fieldParentPtr(Block, "base", scope), | ||
| 222 | .condition => return @fieldParentPtr(Condition, "base", scope).getBlockScope(c), | ||
| 223 | else => scope = scope.parent.?, | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | fn findBlockReturnType(inner: *Scope) Type { | ||
| 229 | var scope = inner; | ||
| 230 | while (true) { | ||
| 231 | switch (scope.id) { | ||
| 232 | .root => unreachable, | ||
| 233 | .block => { | ||
| 234 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 235 | if (block.return_type) |qt| return qt; | ||
| 236 | scope = scope.parent.?; | ||
| 237 | }, | ||
| 238 | else => scope = scope.parent.?, | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | fn getAlias(scope: *Scope, name: []const u8) []const u8 { | ||
| 244 | return switch (scope.id) { | ||
| 245 | .root => return name, | ||
| 246 | .block => @fieldParentPtr(Block, "base", scope).getAlias(name), | ||
| 247 | .loop, .do_loop, .condition => scope.parent.?.getAlias(name), | ||
| 248 | }; | ||
| 249 | } | ||
| 250 | |||
| 251 | fn contains(scope: *Scope, name: []const u8) bool { | ||
| 252 | return switch (scope.id) { | ||
| 253 | .root => @fieldParentPtr(Root, "base", scope).contains(name), | ||
| 254 | .block => @fieldParentPtr(Block, "base", scope).contains(name), | ||
| 255 | .loop, .do_loop, .condition => scope.parent.?.contains(name), | ||
| 256 | }; | ||
| 257 | } | ||
| 258 | |||
| 259 | fn getBreakableScope(inner: *Scope) *Scope { | ||
| 260 | var scope = inner; | ||
| 261 | while (true) { | ||
| 262 | switch (scope.id) { | ||
| 263 | .root => unreachable, | ||
| 264 | .loop, .do_loop => return scope, | ||
| 265 | else => scope = scope.parent.?, | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | /// Appends a node to the first block scope if inside a function, or to the root tree if not. | ||
| 271 | fn appendNode(inner: *Scope, node: ZigNode) !void { | ||
| 272 | var scope = inner; | ||
| 273 | while (true) { | ||
| 274 | switch (scope.id) { | ||
| 275 | .root => { | ||
| 276 | const root = @fieldParentPtr(Root, "base", scope); | ||
| 277 | return root.nodes.append(node); | ||
| 278 | }, | ||
| 279 | .block => { | ||
| 280 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 281 | return block.statements.append(node); | ||
| 282 | }, | ||
| 283 | else => scope = scope.parent.?, | ||
| 284 | } | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | fn skipVariableDiscard(inner: *Scope, name: []const u8) void { | ||
| 289 | var scope = inner; | ||
| 290 | while (true) { | ||
| 291 | switch (scope.id) { | ||
| 292 | .root => return, | ||
| 293 | .block => { | ||
| 294 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 295 | if (block.variable_discards.get(name)) |discard| { | ||
| 296 | discard.data.should_skip = true; | ||
| 297 | return; | ||
| 298 | } | ||
| 299 | }, | ||
| 300 | else => {}, | ||
| 301 | } | ||
| 302 | scope = scope.parent.?; | ||
| 303 | } | ||
| 304 | } | ||
| 305 | }; | ||
| 306 | |||
| 307 | const Context = struct { | 25 | const Context = struct { |
| 308 | gpa: mem.Allocator, | 26 | gpa: mem.Allocator, |
| 309 | arena: mem.Allocator, | 27 | arena: mem.Allocator, |
| 310 | decl_table: std.AutoArrayHashMapUnmanaged(usize, []const u8) = .{}, | 28 | decl_table: std.AutoArrayHashMapUnmanaged(usize, []const u8) = .{}, |
| 311 | alias_list: translate_c.AliasList, | 29 | alias_list: AliasList, |
| 312 | global_scope: *Scope.Root, | 30 | global_scope: *Scope.Root, |
| 313 | mangle_count: u32 = 0, | 31 | mangle_count: u32 = 0, |
| 314 | /// Table of record decls that have been demoted to opaques. | 32 | /// Table of record decls that have been demoted to opaques. |
| ... | @@ -429,7 +147,7 @@ pub fn translate( | ... | @@ -429,7 +147,7 @@ pub fn translate( |
| 429 | var context = Context{ | 147 | var context = Context{ |
| 430 | .gpa = gpa, | 148 | .gpa = gpa, |
| 431 | .arena = arena, | 149 | .arena = arena, |
| 432 | .alias_list = translate_c.AliasList.init(gpa), | 150 | .alias_list = AliasList.init(gpa), |
| 433 | .global_scope = try arena.create(Scope.Root), | 151 | .global_scope = try arena.create(Scope.Root), |
| 434 | .pattern_list = try translate_c.PatternList.init(gpa), | 152 | .pattern_list = try translate_c.PatternList.init(gpa), |
| 435 | .comp = comp, | 153 | .comp = comp, |
src/translate_c.zig+13-306| ... | @@ -7,312 +7,24 @@ const CToken = std.c.Token; | ... | @@ -7,312 +7,24 @@ const CToken = std.c.Token; |
| 7 | const mem = std.mem; | 7 | const mem = std.mem; |
| 8 | const math = std.math; | 8 | const math = std.math; |
| 9 | const meta = std.meta; | 9 | const meta = std.meta; |
| 10 | const CallingConvention = std.builtin.CallingConvention; | ||
| 10 | const ast = @import("translate_c/ast.zig"); | 11 | const ast = @import("translate_c/ast.zig"); |
| 11 | const Node = ast.Node; | 12 | const Node = ast.Node; |
| 12 | const Tag = Node.Tag; | 13 | const Tag = Node.Tag; |
| 13 | 14 | const common = @import("translate_c/common.zig"); | |
| 14 | const CallingConvention = std.builtin.CallingConvention; | 15 | const Error = common.Error; |
| 15 | 16 | const MacroProcessingError = common.MacroProcessingError; | |
| 16 | pub const Error = std.mem.Allocator.Error; | 17 | const TypeError = common.TypeError; |
| 17 | pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; | 18 | const TransError = common.TransError; |
| 18 | pub const TypeError = Error || error{UnsupportedType}; | 19 | const SymbolTable = common.SymbolTable; |
| 19 | pub const TransError = TypeError || error{UnsupportedTranslation}; | 20 | const AliasList = common.AliasList; |
| 20 | 21 | const ResultUsed = common.ResultUsed; | |
| 21 | pub const SymbolTable = std.StringArrayHashMap(Node); | 22 | const Scope = common.ScopeExtra(Context, clang.QualType); |
| 22 | pub const AliasList = std.ArrayList(struct { | ||
| 23 | alias: []const u8, | ||
| 24 | name: []const u8, | ||
| 25 | }); | ||
| 26 | 23 | ||
| 27 | // Maps macro parameter names to token position, for determining if different | 24 | // Maps macro parameter names to token position, for determining if different |
| 28 | // identifiers refer to the same positional argument in different macros. | 25 | // identifiers refer to the same positional argument in different macros. |
| 29 | const ArgsPositionMap = std.StringArrayHashMapUnmanaged(usize); | 26 | const ArgsPositionMap = std.StringArrayHashMapUnmanaged(usize); |
| 30 | 27 | ||
| 31 | const Scope = struct { | ||
| 32 | id: Id, | ||
| 33 | parent: ?*Scope, | ||
| 34 | |||
| 35 | const Id = enum { | ||
| 36 | block, | ||
| 37 | root, | ||
| 38 | condition, | ||
| 39 | loop, | ||
| 40 | do_loop, | ||
| 41 | }; | ||
| 42 | |||
| 43 | /// Used for the scope of condition expressions, for example `if (cond)`. | ||
| 44 | /// The block is lazily initialised because it is only needed for rare | ||
| 45 | /// cases of comma operators being used. | ||
| 46 | const Condition = struct { | ||
| 47 | base: Scope, | ||
| 48 | block: ?Block = null, | ||
| 49 | |||
| 50 | fn getBlockScope(self: *Condition, c: *Context) !*Block { | ||
| 51 | if (self.block) |*b| return b; | ||
| 52 | self.block = try Block.init(c, &self.base, true); | ||
| 53 | return &self.block.?; | ||
| 54 | } | ||
| 55 | |||
| 56 | fn deinit(self: *Condition) void { | ||
| 57 | if (self.block) |*b| b.deinit(); | ||
| 58 | } | ||
| 59 | }; | ||
| 60 | |||
| 61 | /// Represents an in-progress Node.Block. This struct is stack-allocated. | ||
| 62 | /// When it is deinitialized, it produces an Node.Block which is allocated | ||
| 63 | /// into the main arena. | ||
| 64 | const Block = struct { | ||
| 65 | base: Scope, | ||
| 66 | statements: std.ArrayList(Node), | ||
| 67 | variables: AliasList, | ||
| 68 | mangle_count: u32 = 0, | ||
| 69 | label: ?[]const u8 = null, | ||
| 70 | |||
| 71 | /// By default all variables are discarded, since we do not know in advance if they | ||
| 72 | /// will be used. This maps the variable's name to the Discard payload, so that if | ||
| 73 | /// the variable is subsequently referenced we can indicate that the discard should | ||
| 74 | /// be skipped during the intermediate AST -> Zig AST render step. | ||
| 75 | variable_discards: std.StringArrayHashMap(*ast.Payload.Discard), | ||
| 76 | |||
| 77 | /// When the block corresponds to a function, keep track of the return type | ||
| 78 | /// so that the return expression can be cast, if necessary | ||
| 79 | return_type: ?clang.QualType = null, | ||
| 80 | |||
| 81 | /// C static local variables are wrapped in a block-local struct. The struct | ||
| 82 | /// is named after the (mangled) variable name, the Zig variable within the | ||
| 83 | /// struct itself is given this name. | ||
| 84 | const StaticInnerName = "static"; | ||
| 85 | |||
| 86 | fn init(c: *Context, parent: *Scope, labeled: bool) !Block { | ||
| 87 | var blk = Block{ | ||
| 88 | .base = .{ | ||
| 89 | .id = .block, | ||
| 90 | .parent = parent, | ||
| 91 | }, | ||
| 92 | .statements = std.ArrayList(Node).init(c.gpa), | ||
| 93 | .variables = AliasList.init(c.gpa), | ||
| 94 | .variable_discards = std.StringArrayHashMap(*ast.Payload.Discard).init(c.gpa), | ||
| 95 | }; | ||
| 96 | if (labeled) { | ||
| 97 | blk.label = try blk.makeMangledName(c, "blk"); | ||
| 98 | } | ||
| 99 | return blk; | ||
| 100 | } | ||
| 101 | |||
| 102 | fn deinit(self: *Block) void { | ||
| 103 | self.statements.deinit(); | ||
| 104 | self.variables.deinit(); | ||
| 105 | self.variable_discards.deinit(); | ||
| 106 | self.* = undefined; | ||
| 107 | } | ||
| 108 | |||
| 109 | fn complete(self: *Block, c: *Context) !Node { | ||
| 110 | if (self.base.parent.?.id == .do_loop) { | ||
| 111 | // We reserve 1 extra statement if the parent is a do_loop. This is in case of | ||
| 112 | // do while, we want to put `if (cond) break;` at the end. | ||
| 113 | const alloc_len = self.statements.items.len + @intFromBool(self.base.parent.?.id == .do_loop); | ||
| 114 | var stmts = try c.arena.alloc(Node, alloc_len); | ||
| 115 | stmts.len = self.statements.items.len; | ||
| 116 | @memcpy(stmts[0..self.statements.items.len], self.statements.items); | ||
| 117 | return Tag.block.create(c.arena, .{ | ||
| 118 | .label = self.label, | ||
| 119 | .stmts = stmts, | ||
| 120 | }); | ||
| 121 | } | ||
| 122 | if (self.statements.items.len == 0) return Tag.empty_block.init(); | ||
| 123 | return Tag.block.create(c.arena, .{ | ||
| 124 | .label = self.label, | ||
| 125 | .stmts = try c.arena.dupe(Node, self.statements.items), | ||
| 126 | }); | ||
| 127 | } | ||
| 128 | |||
| 129 | /// Given the desired name, return a name that does not shadow anything from outer scopes. | ||
| 130 | /// Inserts the returned name into the scope. | ||
| 131 | /// The name will not be visible to callers of getAlias. | ||
| 132 | fn reserveMangledName(scope: *Block, c: *Context, name: []const u8) ![]const u8 { | ||
| 133 | return scope.createMangledName(c, name, true); | ||
| 134 | } | ||
| 135 | |||
| 136 | /// Same as reserveMangledName, but enables the alias immediately. | ||
| 137 | fn makeMangledName(scope: *Block, c: *Context, name: []const u8) ![]const u8 { | ||
| 138 | return scope.createMangledName(c, name, false); | ||
| 139 | } | ||
| 140 | |||
| 141 | fn createMangledName(scope: *Block, c: *Context, name: []const u8, reservation: bool) ![]const u8 { | ||
| 142 | const name_copy = try c.arena.dupe(u8, name); | ||
| 143 | var proposed_name = name_copy; | ||
| 144 | while (scope.contains(proposed_name)) { | ||
| 145 | scope.mangle_count += 1; | ||
| 146 | proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ name, scope.mangle_count }); | ||
| 147 | } | ||
| 148 | const new_mangle = try scope.variables.addOne(); | ||
| 149 | if (reservation) { | ||
| 150 | new_mangle.* = .{ .name = name_copy, .alias = name_copy }; | ||
| 151 | } else { | ||
| 152 | new_mangle.* = .{ .name = name_copy, .alias = proposed_name }; | ||
| 153 | } | ||
| 154 | return proposed_name; | ||
| 155 | } | ||
| 156 | |||
| 157 | fn getAlias(scope: *Block, name: []const u8) []const u8 { | ||
| 158 | for (scope.variables.items) |p| { | ||
| 159 | if (mem.eql(u8, p.name, name)) | ||
| 160 | return p.alias; | ||
| 161 | } | ||
| 162 | return scope.base.parent.?.getAlias(name); | ||
| 163 | } | ||
| 164 | |||
| 165 | fn localContains(scope: *Block, name: []const u8) bool { | ||
| 166 | for (scope.variables.items) |p| { | ||
| 167 | if (mem.eql(u8, p.alias, name)) | ||
| 168 | return true; | ||
| 169 | } | ||
| 170 | return false; | ||
| 171 | } | ||
| 172 | |||
| 173 | fn contains(scope: *Block, name: []const u8) bool { | ||
| 174 | if (scope.localContains(name)) | ||
| 175 | return true; | ||
| 176 | return scope.base.parent.?.contains(name); | ||
| 177 | } | ||
| 178 | |||
| 179 | fn discardVariable(scope: *Block, c: *Context, name: []const u8) Error!void { | ||
| 180 | const name_node = try Tag.identifier.create(c.arena, name); | ||
| 181 | const discard = try Tag.discard.create(c.arena, .{ .should_skip = false, .value = name_node }); | ||
| 182 | try scope.statements.append(discard); | ||
| 183 | try scope.variable_discards.putNoClobber(name, discard.castTag(.discard).?); | ||
| 184 | } | ||
| 185 | }; | ||
| 186 | |||
| 187 | const Root = struct { | ||
| 188 | base: Scope, | ||
| 189 | sym_table: SymbolTable, | ||
| 190 | macro_table: SymbolTable, | ||
| 191 | context: *Context, | ||
| 192 | nodes: std.ArrayList(Node), | ||
| 193 | |||
| 194 | fn init(c: *Context) Root { | ||
| 195 | return .{ | ||
| 196 | .base = .{ | ||
| 197 | .id = .root, | ||
| 198 | .parent = null, | ||
| 199 | }, | ||
| 200 | .sym_table = SymbolTable.init(c.gpa), | ||
| 201 | .macro_table = SymbolTable.init(c.gpa), | ||
| 202 | .context = c, | ||
| 203 | .nodes = std.ArrayList(Node).init(c.gpa), | ||
| 204 | }; | ||
| 205 | } | ||
| 206 | |||
| 207 | fn deinit(scope: *Root) void { | ||
| 208 | scope.sym_table.deinit(); | ||
| 209 | scope.macro_table.deinit(); | ||
| 210 | scope.nodes.deinit(); | ||
| 211 | } | ||
| 212 | |||
| 213 | /// Check if the global scope contains this name, without looking into the "future", e.g. | ||
| 214 | /// ignore the preprocessed decl and macro names. | ||
| 215 | fn containsNow(scope: *Root, name: []const u8) bool { | ||
| 216 | return scope.sym_table.contains(name) or scope.macro_table.contains(name); | ||
| 217 | } | ||
| 218 | |||
| 219 | /// Check if the global scope contains the name, includes all decls that haven't been translated yet. | ||
| 220 | fn contains(scope: *Root, name: []const u8) bool { | ||
| 221 | return scope.containsNow(name) or scope.context.global_names.contains(name) or scope.context.weak_global_names.contains(name); | ||
| 222 | } | ||
| 223 | }; | ||
| 224 | |||
| 225 | fn findBlockScope(inner: *Scope, c: *Context) !*Scope.Block { | ||
| 226 | var scope = inner; | ||
| 227 | while (true) { | ||
| 228 | switch (scope.id) { | ||
| 229 | .root => unreachable, | ||
| 230 | .block => return @fieldParentPtr(Block, "base", scope), | ||
| 231 | .condition => return @fieldParentPtr(Condition, "base", scope).getBlockScope(c), | ||
| 232 | else => scope = scope.parent.?, | ||
| 233 | } | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | fn findBlockReturnType(inner: *Scope) clang.QualType { | ||
| 238 | var scope = inner; | ||
| 239 | while (true) { | ||
| 240 | switch (scope.id) { | ||
| 241 | .root => unreachable, | ||
| 242 | .block => { | ||
| 243 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 244 | if (block.return_type) |qt| return qt; | ||
| 245 | scope = scope.parent.?; | ||
| 246 | }, | ||
| 247 | else => scope = scope.parent.?, | ||
| 248 | } | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | fn getAlias(scope: *Scope, name: []const u8) []const u8 { | ||
| 253 | return switch (scope.id) { | ||
| 254 | .root => return name, | ||
| 255 | .block => @fieldParentPtr(Block, "base", scope).getAlias(name), | ||
| 256 | .loop, .do_loop, .condition => scope.parent.?.getAlias(name), | ||
| 257 | }; | ||
| 258 | } | ||
| 259 | |||
| 260 | fn contains(scope: *Scope, name: []const u8) bool { | ||
| 261 | return switch (scope.id) { | ||
| 262 | .root => @fieldParentPtr(Root, "base", scope).contains(name), | ||
| 263 | .block => @fieldParentPtr(Block, "base", scope).contains(name), | ||
| 264 | .loop, .do_loop, .condition => scope.parent.?.contains(name), | ||
| 265 | }; | ||
| 266 | } | ||
| 267 | |||
| 268 | fn getBreakableScope(inner: *Scope) *Scope { | ||
| 269 | var scope = inner; | ||
| 270 | while (true) { | ||
| 271 | switch (scope.id) { | ||
| 272 | .root => unreachable, | ||
| 273 | .loop, .do_loop => return scope, | ||
| 274 | else => scope = scope.parent.?, | ||
| 275 | } | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | /// Appends a node to the first block scope if inside a function, or to the root tree if not. | ||
| 280 | fn appendNode(inner: *Scope, node: Node) !void { | ||
| 281 | var scope = inner; | ||
| 282 | while (true) { | ||
| 283 | switch (scope.id) { | ||
| 284 | .root => { | ||
| 285 | const root = @fieldParentPtr(Root, "base", scope); | ||
| 286 | return root.nodes.append(node); | ||
| 287 | }, | ||
| 288 | .block => { | ||
| 289 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 290 | return block.statements.append(node); | ||
| 291 | }, | ||
| 292 | else => scope = scope.parent.?, | ||
| 293 | } | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | fn skipVariableDiscard(inner: *Scope, name: []const u8) void { | ||
| 298 | var scope = inner; | ||
| 299 | while (true) { | ||
| 300 | switch (scope.id) { | ||
| 301 | .root => return, | ||
| 302 | .block => { | ||
| 303 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 304 | if (block.variable_discards.get(name)) |discard| { | ||
| 305 | discard.data.should_skip = true; | ||
| 306 | return; | ||
| 307 | } | ||
| 308 | }, | ||
| 309 | else => {}, | ||
| 310 | } | ||
| 311 | scope = scope.parent.?; | ||
| 312 | } | ||
| 313 | } | ||
| 314 | }; | ||
| 315 | |||
| 316 | pub const Context = struct { | 28 | pub const Context = struct { |
| 317 | gpa: mem.Allocator, | 29 | gpa: mem.Allocator, |
| 318 | arena: mem.Allocator, | 30 | arena: mem.Allocator, |
| ... | @@ -829,7 +541,7 @@ fn transQualTypeMaybeInitialized(c: *Context, scope: *Scope, qt: clang.QualType, | ... | @@ -829,7 +541,7 @@ fn transQualTypeMaybeInitialized(c: *Context, scope: *Scope, qt: clang.QualType, |
| 829 | /// var static = S.*; | 541 | /// var static = S.*; |
| 830 | /// }).static; | 542 | /// }).static; |
| 831 | fn stringLiteralToCharStar(c: *Context, str: Node) Error!Node { | 543 | fn stringLiteralToCharStar(c: *Context, str: Node) Error!Node { |
| 832 | const var_name = Scope.Block.StaticInnerName; | 544 | const var_name = Scope.Block.static_inner_name; |
| 833 | 545 | ||
| 834 | const variables = try c.arena.alloc(Node, 1); | 546 | const variables = try c.arena.alloc(Node, 1); |
| 835 | variables[0] = try Tag.mut_str.create(c.arena, .{ .name = var_name, .init = str }); | 547 | variables[0] = try Tag.mut_str.create(c.arena, .{ .name = var_name, .init = str }); |
| ... | @@ -1423,11 +1135,6 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const clang.EnumDecl) E | ... | @@ -1423,11 +1135,6 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const clang.EnumDecl) E |
| 1423 | } | 1135 | } |
| 1424 | } | 1136 | } |
| 1425 | 1137 | ||
| 1426 | pub const ResultUsed = enum { | ||
| 1427 | used, | ||
| 1428 | unused, | ||
| 1429 | }; | ||
| 1430 | |||
| 1431 | fn transStmt( | 1138 | fn transStmt( |
| 1432 | c: *Context, | 1139 | c: *Context, |
| 1433 | scope: *Scope, | 1140 | scope: *Scope, |
| ... | @@ -2070,7 +1777,7 @@ fn transDeclStmtOne( | ... | @@ -2070,7 +1777,7 @@ fn transDeclStmtOne( |
| 2070 | init_node = try removeCVQualifiers(c, dst_type_node, init_node); | 1777 | init_node = try removeCVQualifiers(c, dst_type_node, init_node); |
| 2071 | } | 1778 | } |
| 2072 | 1779 | ||
| 2073 | const var_name: []const u8 = if (is_static_local) Scope.Block.StaticInnerName else mangled_name; | 1780 | const var_name: []const u8 = if (is_static_local) Scope.Block.static_inner_name else mangled_name; |
| 2074 | var node = try Tag.var_decl.create(c.arena, .{ | 1781 | var node = try Tag.var_decl.create(c.arena, .{ |
| 2075 | .is_pub = false, | 1782 | .is_pub = false, |
| 2076 | .is_const = is_const, | 1783 | .is_const = is_const, |
| ... | @@ -2153,7 +1860,7 @@ fn transDeclRefExpr( | ... | @@ -2153,7 +1860,7 @@ fn transDeclRefExpr( |
| 2153 | if (var_decl.isStaticLocal()) { | 1860 | if (var_decl.isStaticLocal()) { |
| 2154 | ref_expr = try Tag.field_access.create(c.arena, .{ | 1861 | ref_expr = try Tag.field_access.create(c.arena, .{ |
| 2155 | .lhs = ref_expr, | 1862 | .lhs = ref_expr, |
| 2156 | .field_name = Scope.Block.StaticInnerName, | 1863 | .field_name = Scope.Block.static_inner_name, |
| 2157 | }); | 1864 | }); |
| 2158 | } | 1865 | } |
| 2159 | } | 1866 | } |
src/translate_c/common.zig created+311| ... | @@ -0,0 +1,311 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const ast = @import("ast.zig"); | ||
| 3 | const Node = ast.Node; | ||
| 4 | const Tag = Node.Tag; | ||
| 5 | |||
| 6 | const CallingConvention = std.builtin.CallingConvention; | ||
| 7 | |||
| 8 | pub const Error = std.mem.Allocator.Error; | ||
| 9 | pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; | ||
| 10 | pub const TypeError = Error || error{UnsupportedType}; | ||
| 11 | pub const TransError = TypeError || error{UnsupportedTranslation}; | ||
| 12 | |||
| 13 | pub const SymbolTable = std.StringArrayHashMap(Node); | ||
| 14 | pub const AliasList = std.ArrayList(struct { | ||
| 15 | alias: []const u8, | ||
| 16 | name: []const u8, | ||
| 17 | }); | ||
| 18 | |||
| 19 | pub const ResultUsed = enum { | ||
| 20 | used, | ||
| 21 | unused, | ||
| 22 | }; | ||
| 23 | |||
| 24 | pub fn ScopeExtra(comptime Context: type, comptime Type: type) type { | ||
| 25 | return struct { | ||
| 26 | id: Id, | ||
| 27 | parent: ?*Scope, | ||
| 28 | |||
| 29 | const Scope = @This(); | ||
| 30 | |||
| 31 | pub const Id = enum { | ||
| 32 | block, | ||
| 33 | root, | ||
| 34 | condition, | ||
| 35 | loop, | ||
| 36 | do_loop, | ||
| 37 | }; | ||
| 38 | |||
| 39 | /// Used for the scope of condition expressions, for example `if (cond)`. | ||
| 40 | /// The block is lazily initialised because it is only needed for rare | ||
| 41 | /// cases of comma operators being used. | ||
| 42 | pub const Condition = struct { | ||
| 43 | base: Scope, | ||
| 44 | block: ?Block = null, | ||
| 45 | |||
| 46 | pub fn getBlockScope(self: *Condition, c: *Context) !*Block { | ||
| 47 | if (self.block) |*b| return b; | ||
| 48 | self.block = try Block.init(c, &self.base, true); | ||
| 49 | return &self.block.?; | ||
| 50 | } | ||
| 51 | |||
| 52 | pub fn deinit(self: *Condition) void { | ||
| 53 | if (self.block) |*b| b.deinit(); | ||
| 54 | } | ||
| 55 | }; | ||
| 56 | |||
| 57 | /// Represents an in-progress Node.Block. This struct is stack-allocated. | ||
| 58 | /// When it is deinitialized, it produces an Node.Block which is allocated | ||
| 59 | /// into the main arena. | ||
| 60 | pub const Block = struct { | ||
| 61 | base: Scope, | ||
| 62 | statements: std.ArrayList(Node), | ||
| 63 | variables: AliasList, | ||
| 64 | mangle_count: u32 = 0, | ||
| 65 | label: ?[]const u8 = null, | ||
| 66 | |||
| 67 | /// By default all variables are discarded, since we do not know in advance if they | ||
| 68 | /// will be used. This maps the variable's name to the Discard payload, so that if | ||
| 69 | /// the variable is subsequently referenced we can indicate that the discard should | ||
| 70 | /// be skipped during the intermediate AST -> Zig AST render step. | ||
| 71 | variable_discards: std.StringArrayHashMap(*ast.Payload.Discard), | ||
| 72 | |||
| 73 | /// When the block corresponds to a function, keep track of the return type | ||
| 74 | /// so that the return expression can be cast, if necessary | ||
| 75 | return_type: ?Type = null, | ||
| 76 | |||
| 77 | /// C static local variables are wrapped in a block-local struct. The struct | ||
| 78 | /// is named after the (mangled) variable name, the Zig variable within the | ||
| 79 | /// struct itself is given this name. | ||
| 80 | pub const static_inner_name = "static"; | ||
| 81 | |||
| 82 | pub fn init(c: *Context, parent: *Scope, labeled: bool) !Block { | ||
| 83 | var blk = Block{ | ||
| 84 | .base = .{ | ||
| 85 | .id = .block, | ||
| 86 | .parent = parent, | ||
| 87 | }, | ||
| 88 | .statements = std.ArrayList(Node).init(c.gpa), | ||
| 89 | .variables = AliasList.init(c.gpa), | ||
| 90 | .variable_discards = std.StringArrayHashMap(*ast.Payload.Discard).init(c.gpa), | ||
| 91 | }; | ||
| 92 | if (labeled) { | ||
| 93 | blk.label = try blk.makeMangledName(c, "blk"); | ||
| 94 | } | ||
| 95 | return blk; | ||
| 96 | } | ||
| 97 | |||
| 98 | pub fn deinit(self: *Block) void { | ||
| 99 | self.statements.deinit(); | ||
| 100 | self.variables.deinit(); | ||
| 101 | self.variable_discards.deinit(); | ||
| 102 | self.* = undefined; | ||
| 103 | } | ||
| 104 | |||
| 105 | pub fn complete(self: *Block, c: *Context) !Node { | ||
| 106 | if (self.base.parent.?.id == .do_loop) { | ||
| 107 | // We reserve 1 extra statement if the parent is a do_loop. This is in case of | ||
| 108 | // do while, we want to put `if (cond) break;` at the end. | ||
| 109 | const alloc_len = self.statements.items.len + @intFromBool(self.base.parent.?.id == .do_loop); | ||
| 110 | var stmts = try c.arena.alloc(Node, alloc_len); | ||
| 111 | stmts.len = self.statements.items.len; | ||
| 112 | @memcpy(stmts[0..self.statements.items.len], self.statements.items); | ||
| 113 | return Tag.block.create(c.arena, .{ | ||
| 114 | .label = self.label, | ||
| 115 | .stmts = stmts, | ||
| 116 | }); | ||
| 117 | } | ||
| 118 | if (self.statements.items.len == 0) return Tag.empty_block.init(); | ||
| 119 | return Tag.block.create(c.arena, .{ | ||
| 120 | .label = self.label, | ||
| 121 | .stmts = try c.arena.dupe(Node, self.statements.items), | ||
| 122 | }); | ||
| 123 | } | ||
| 124 | |||
| 125 | /// Given the desired name, return a name that does not shadow anything from outer scopes. | ||
| 126 | /// Inserts the returned name into the scope. | ||
| 127 | /// The name will not be visible to callers of getAlias. | ||
| 128 | pub fn reserveMangledName(scope: *Block, c: *Context, name: []const u8) ![]const u8 { | ||
| 129 | return scope.createMangledName(c, name, true); | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Same as reserveMangledName, but enables the alias immediately. | ||
| 133 | pub fn makeMangledName(scope: *Block, c: *Context, name: []const u8) ![]const u8 { | ||
| 134 | return scope.createMangledName(c, name, false); | ||
| 135 | } | ||
| 136 | |||
| 137 | pub fn createMangledName(scope: *Block, c: *Context, name: []const u8, reservation: bool) ![]const u8 { | ||
| 138 | const name_copy = try c.arena.dupe(u8, name); | ||
| 139 | var proposed_name = name_copy; | ||
| 140 | while (scope.contains(proposed_name)) { | ||
| 141 | scope.mangle_count += 1; | ||
| 142 | proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ name, scope.mangle_count }); | ||
| 143 | } | ||
| 144 | const new_mangle = try scope.variables.addOne(); | ||
| 145 | if (reservation) { | ||
| 146 | new_mangle.* = .{ .name = name_copy, .alias = name_copy }; | ||
| 147 | } else { | ||
| 148 | new_mangle.* = .{ .name = name_copy, .alias = proposed_name }; | ||
| 149 | } | ||
| 150 | return proposed_name; | ||
| 151 | } | ||
| 152 | |||
| 153 | pub fn getAlias(scope: *Block, name: []const u8) []const u8 { | ||
| 154 | for (scope.variables.items) |p| { | ||
| 155 | if (std.mem.eql(u8, p.name, name)) | ||
| 156 | return p.alias; | ||
| 157 | } | ||
| 158 | return scope.base.parent.?.getAlias(name); | ||
| 159 | } | ||
| 160 | |||
| 161 | pub fn localContains(scope: *Block, name: []const u8) bool { | ||
| 162 | for (scope.variables.items) |p| { | ||
| 163 | if (std.mem.eql(u8, p.alias, name)) | ||
| 164 | return true; | ||
| 165 | } | ||
| 166 | return false; | ||
| 167 | } | ||
| 168 | |||
| 169 | pub fn contains(scope: *Block, name: []const u8) bool { | ||
| 170 | if (scope.localContains(name)) | ||
| 171 | return true; | ||
| 172 | return scope.base.parent.?.contains(name); | ||
| 173 | } | ||
| 174 | |||
| 175 | pub fn discardVariable(scope: *Block, c: *Context, name: []const u8) Error!void { | ||
| 176 | const name_node = try Tag.identifier.create(c.arena, name); | ||
| 177 | const discard = try Tag.discard.create(c.arena, .{ .should_skip = false, .value = name_node }); | ||
| 178 | try scope.statements.append(discard); | ||
| 179 | try scope.variable_discards.putNoClobber(name, discard.castTag(.discard).?); | ||
| 180 | } | ||
| 181 | }; | ||
| 182 | |||
| 183 | pub const Root = struct { | ||
| 184 | base: Scope, | ||
| 185 | sym_table: SymbolTable, | ||
| 186 | macro_table: SymbolTable, | ||
| 187 | context: *Context, | ||
| 188 | nodes: std.ArrayList(Node), | ||
| 189 | |||
| 190 | pub fn init(c: *Context) Root { | ||
| 191 | return .{ | ||
| 192 | .base = .{ | ||
| 193 | .id = .root, | ||
| 194 | .parent = null, | ||
| 195 | }, | ||
| 196 | .sym_table = SymbolTable.init(c.gpa), | ||
| 197 | .macro_table = SymbolTable.init(c.gpa), | ||
| 198 | .context = c, | ||
| 199 | .nodes = std.ArrayList(Node).init(c.gpa), | ||
| 200 | }; | ||
| 201 | } | ||
| 202 | |||
| 203 | pub fn deinit(scope: *Root) void { | ||
| 204 | scope.sym_table.deinit(); | ||
| 205 | scope.macro_table.deinit(); | ||
| 206 | scope.nodes.deinit(); | ||
| 207 | } | ||
| 208 | |||
| 209 | /// Check if the global scope contains this name, without looking into the "future", e.g. | ||
| 210 | /// ignore the preprocessed decl and macro names. | ||
| 211 | pub fn containsNow(scope: *Root, name: []const u8) bool { | ||
| 212 | return scope.sym_table.contains(name) or scope.macro_table.contains(name); | ||
| 213 | } | ||
| 214 | |||
| 215 | /// Check if the global scope contains the name, includes all decls that haven't been translated yet. | ||
| 216 | pub fn contains(scope: *Root, name: []const u8) bool { | ||
| 217 | return scope.containsNow(name) or scope.context.global_names.contains(name) or scope.context.weak_global_names.contains(name); | ||
| 218 | } | ||
| 219 | }; | ||
| 220 | |||
| 221 | pub fn findBlockScope(inner: *Scope, c: *Context) !*Scope.Block { | ||
| 222 | var scope = inner; | ||
| 223 | while (true) { | ||
| 224 | switch (scope.id) { | ||
| 225 | .root => unreachable, | ||
| 226 | .block => return @fieldParentPtr(Block, "base", scope), | ||
| 227 | .condition => return @fieldParentPtr(Condition, "base", scope).getBlockScope(c), | ||
| 228 | else => scope = scope.parent.?, | ||
| 229 | } | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
| 233 | pub fn findBlockReturnType(inner: *Scope) Type { | ||
| 234 | var scope = inner; | ||
| 235 | while (true) { | ||
| 236 | switch (scope.id) { | ||
| 237 | .root => unreachable, | ||
| 238 | .block => { | ||
| 239 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 240 | if (block.return_type) |ty| return ty; | ||
| 241 | scope = scope.parent.?; | ||
| 242 | }, | ||
| 243 | else => scope = scope.parent.?, | ||
| 244 | } | ||
| 245 | } | ||
| 246 | } | ||
| 247 | |||
| 248 | pub fn getAlias(scope: *Scope, name: []const u8) []const u8 { | ||
| 249 | return switch (scope.id) { | ||
| 250 | .root => return name, | ||
| 251 | .block => @fieldParentPtr(Block, "base", scope).getAlias(name), | ||
| 252 | .loop, .do_loop, .condition => scope.parent.?.getAlias(name), | ||
| 253 | }; | ||
| 254 | } | ||
| 255 | |||
| 256 | pub fn contains(scope: *Scope, name: []const u8) bool { | ||
| 257 | return switch (scope.id) { | ||
| 258 | .root => @fieldParentPtr(Root, "base", scope).contains(name), | ||
| 259 | .block => @fieldParentPtr(Block, "base", scope).contains(name), | ||
| 260 | .loop, .do_loop, .condition => scope.parent.?.contains(name), | ||
| 261 | }; | ||
| 262 | } | ||
| 263 | |||
| 264 | pub fn getBreakableScope(inner: *Scope) *Scope { | ||
| 265 | var scope = inner; | ||
| 266 | while (true) { | ||
| 267 | switch (scope.id) { | ||
| 268 | .root => unreachable, | ||
| 269 | .loop, .do_loop => return scope, | ||
| 270 | else => scope = scope.parent.?, | ||
| 271 | } | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | /// Appends a node to the first block scope if inside a function, or to the root tree if not. | ||
| 276 | pub fn appendNode(inner: *Scope, node: Node) !void { | ||
| 277 | var scope = inner; | ||
| 278 | while (true) { | ||
| 279 | switch (scope.id) { | ||
| 280 | .root => { | ||
| 281 | const root = @fieldParentPtr(Root, "base", scope); | ||
| 282 | return root.nodes.append(node); | ||
| 283 | }, | ||
| 284 | .block => { | ||
| 285 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 286 | return block.statements.append(node); | ||
| 287 | }, | ||
| 288 | else => scope = scope.parent.?, | ||
| 289 | } | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void { | ||
| 294 | var scope = inner; | ||
| 295 | while (true) { | ||
| 296 | switch (scope.id) { | ||
| 297 | .root => return, | ||
| 298 | .block => { | ||
| 299 | const block = @fieldParentPtr(Block, "base", scope); | ||
| 300 | if (block.variable_discards.get(name)) |discard| { | ||
| 301 | discard.data.should_skip = true; | ||
| 302 | return; | ||
| 303 | } | ||
| 304 | }, | ||
| 305 | else => {}, | ||
| 306 | } | ||
| 307 | scope = scope.parent.?; | ||
| 308 | } | ||
| 309 | } | ||
| 310 | }; | ||
| 311 | } | ||