diff --git a/libs/dusk/src/Ast.zig b/libs/dusk/src/Ast.zig index 7cf09ce5..19bda5ec 100644 --- a/libs/dusk/src/Ast.zig +++ b/libs/dusk/src/Ast.zig @@ -171,7 +171,7 @@ pub const Node = struct { /// RHS : block fn_decl, /// TOK : ident - /// LHS : ? Attributes + /// LHS : ?span(Attribute) /// RHS : type fn_param, @@ -367,7 +367,7 @@ pub const Node = struct { /// RHS : -- user_type, - // ####### Attr ####### + // ####### Attribute ####### // TOK : attr attr, @@ -650,6 +650,7 @@ pub const InterpolationSample = enum { }; pub const AddressSpace = enum { + none, // TODO function, private, workgroup, @@ -658,6 +659,7 @@ pub const AddressSpace = enum { }; pub const AccessMode = enum { + none, // TODO read, write, read_write, diff --git a/libs/dusk/src/AstGen.zig b/libs/dusk/src/AstGen.zig index 733b0884..d9f4fdf1 100644 --- a/libs/dusk/src/AstGen.zig +++ b/libs/dusk/src/AstGen.zig @@ -5,186 +5,622 @@ const IR = @import("IR.zig"); const ErrorMsg = @import("main.zig").ErrorMsg; const AstGen = @This(); -arena: std.mem.Allocator, allocator: std.mem.Allocator, tree: *const Ast, -errors: std.ArrayListUnmanaged(ErrorMsg), -scope: Scope = .top, +strings: std.ArrayListUnmanaged(u8) = .{}, +instructions: std.ArrayListUnmanaged(IR.Inst) = .{}, +refs: std.ArrayListUnmanaged(IR.Ref) = .{}, +scratch: std.ArrayListUnmanaged(IR.Ref) = .{}, +errors: std.ArrayListUnmanaged(ErrorMsg) = .{}, +scope_pool: std.heap.MemoryPool(Scope), pub const Scope = union(enum) { - top, - range: []const Ast.Index, + root: Root, + func: Function, + block: Block, + + pub const Root = struct { + decls: std.StringHashMapUnmanaged(?IR.Ref) = .{}, + }; + + /// parent is always Root + pub const Function = struct { + parent: *Scope, + decls: std.StringHashMapUnmanaged(?IR.Ref) = .{}, + }; + + pub const Block = struct { + parent: *Scope, + decls: std.StringHashMapUnmanaged(?IR.Ref) = .{}, + }; + + pub fn parent(self: *Scope) ?*Scope { + return switch (self.*) { + .root => null, + inline .func, .block => |*r| r.parent, + }; + } + + pub fn decls(self: *Scope) *std.StringHashMapUnmanaged(?IR.Ref) { + return switch (self.*) { + inline else => |*r| &r.decls, + }; + } }; pub fn deinit(self: *AstGen) void { + self.instructions.deinit(self.allocator); + self.refs.deinit(self.allocator); + self.strings.deinit(self.allocator); + self.scratch.deinit(self.allocator); + self.scope_pool.deinit(); for (self.errors.items) |*err_msg| err_msg.deinit(self.allocator); self.errors.deinit(self.allocator); } -pub fn translationUnit(self: *AstGen) !?IR.TranslationUnit { +pub fn translationUnit(self: *AstGen) !bool { const global_decls = self.tree.spanToList(0); - var list = std.ArrayList(IR.GlobalDecl).init(self.arena); - defer list.deinit(); + const scratch_top = self.scratch.items.len; + defer self.scratch.shrinkRetainingCapacity(scratch_top); - for (global_decls, 0..) |node, i| { - self.scope = .top; - try self.checkRedeclaration(global_decls[i + 1 ..], node); - const global = try self.globalDecl(node) orelse continue; - try list.append(global); + var root_scope = try self.scope_pool.create(); + root_scope.* = .{ .root = .{} }; + try self.scanDecls(root_scope, global_decls); + + for (global_decls) |node| { + const global = try self.globalDecl(root_scope, node) orelse continue; + try self.scratch.append(self.allocator, global); } if (self.errors.items.len > 0) { - return null; + return false; } - return try list.toOwnedSlice(); + _ = try self.addList(self.scratch.items[scratch_top..]); + + return true; } -pub fn globalDecl(self: *AstGen, node: Ast.Index) !?IR.GlobalDecl { - switch (self.tree.nodeTag(node)) { - .global_variable => {}, // TODO - .struct_decl => return .{ .@"struct" = try self.structDecl(node) orelse return null }, - else => std.debug.print("Global Decl TODO: {}\n", .{self.tree.nodeTag(node)}), +pub fn scanDecls(self: *AstGen, scope: *Scope, decls: []const Ast.Index) !void { + std.debug.assert(scope.decls().count() == 0); + for (decls) |decl| { + const loc = self.declNameLoc(decl).?; + const name = loc.slice(self.tree.source); + + // TODO + // if (Token.isReserved(name)) { + // try self.addError( + // loc, + // "the name '{s}' has ben reserved", + // .{name}, + // null, + // ); + // } + + const gop = try scope.decls().getOrPut(self.scope_pool.arena.allocator(), name); + if (gop.found_existing) { + try self.addError( + loc, + "redeclaration of '{s}'", + .{name}, + null, + ); + return; // TODO? + } + gop.value_ptr.* = null; + } +} + +pub fn globalDecl(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const decl = switch (self.tree.nodeTag(node)) { + .global_variable => try self.globalVariable(scope, node), + .type_alias => try self.typeAlias(scope, node), // TODO: this returns a type not a real decl + .struct_decl => try self.structDecl(scope, node), + else => return null, + }; + + const gop = scope.decls().getOrPutAssumeCapacity(self.declNameLoc(node).?.slice(self.tree.source)); + std.debug.assert(gop.found_existing); + gop.value_ptr.* = decl orelse IR.null_ref; + + return decl; +} + +pub fn typeAlias(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + return self.allTypes(scope, self.tree.nodeLHS(node)); +} + +pub fn globalVariable(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const gv = self.tree.extraData(Ast.Node.GlobalVarDecl, self.tree.nodeLHS(node)); + // for (self.tree.spanToList(gv.attrs), 0..) |attr_node, i| { + // const attr = switch (self.tree.nodeTag(attr_node)) { + // .attr => {}, + // }; + // } + var var_type = IR.null_ref; + if (gv.type != Ast.null_index) { + var_type = try self.allTypes(scope, gv.type) orelse return null; } - return null; + var addr_space: Ast.AddressSpace = .none; + if (gv.access_mode != Ast.null_index) { + const addr_space_loc = self.tree.tokenLoc(gv.addr_space); + addr_space = std.meta.stringToEnum(Ast.AddressSpace, addr_space_loc.slice(self.tree.source)).?; + } + + var access_mode: Ast.AccessMode = .none; + if (gv.access_mode != Ast.null_index) { + const access_mode_loc = self.tree.tokenLoc(gv.access_mode); + access_mode = std.meta.stringToEnum(Ast.AccessMode, access_mode_loc.slice(self.tree.source)).?; + } + + const name_index = try self.addString(self.declNameLoc(node).?.slice(self.tree.source)); + return try self.addInst(.global_variable, .{ + .global_variable = .{ + .name = name_index, + .type = var_type, + .addr_space = addr_space, + .access_mode = access_mode, + .attrs = 0, // TODO + }, + }); } -pub fn structDecl(self: *AstGen, node: Ast.Index) !?IR.StructDecl { - var members_arr = std.ArrayList(IR.StructMember).init(self.arena); - defer members_arr.deinit(); +pub fn structDecl(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const scratch_top = self.scratch.items.len; + defer self.scratch.shrinkRetainingCapacity(scratch_top); const member_list = self.tree.spanToList(self.tree.nodeLHS(node)); for (member_list, 0..) |member_node, i| { - try self.checkRedeclaration(member_list[i + 1 ..], member_node); const member_loc = self.tree.tokenLoc(self.tree.nodeToken(member_node)); const member_type_node = self.tree.nodeRHS(member_node); - const member_type_token = self.tree.nodeToken(member_type_node); - const member_type_loc = self.tree.tokenLoc(member_type_token); - const member_type_name = member_type_loc.slice(self.tree.source); + const member_type_name = self.tree.tokenLoc(self.tree.nodeToken(member_type_node)); + const member_type_ref = try self.allTypes(scope, member_type_node) orelse continue; - var inner_type = member_type_node; - while (true) { - switch (self.tree.nodeTag(inner_type)) { - .bool_type => { - try members_arr.append(.{ - .name = member_loc.slice(self.tree.source), - .type = .bool, - }); - }, - .number_type => { - try members_arr.append(.{ - .name = member_loc.slice(self.tree.source), - .type = .{ .number = self.numberType(inner_type) }, - }); - }, - .vector_type => { - try members_arr.append(.{ - .name = member_loc.slice(self.tree.source), - .type = .{ .vector = try self.vectorType(inner_type) orelse break }, - }); - }, - .matrix_type => { - try members_arr.append(.{ - .name = member_loc.slice(self.tree.source), - .type = .{ .matrix = try self.matrixType(inner_type) orelse break }, - }); - }, - .atomic_type => { - try members_arr.append(.{ - .name = member_loc.slice(self.tree.source), - .type = .{ .atomic = try self.atomicType(inner_type) orelse break }, - }); - }, - .array_type => { - const array_type = try self.arrayType(inner_type) orelse return null; - - try members_arr.append(.{ - .name = member_loc.slice(self.tree.source), - .type = .{ .array = array_type }, - }); - - if (array_type.size == null and i + 1 != member_list.len) { - try self.addError( - member_loc, - "struct member with runtime-sized array type, must be the last member of the structure", - .{}, - null, - ); - return null; - } - }, - .user_type => { - const decl_node = try self.expectFindDeclNode(member_type_loc, member_type_name) orelse break; - switch (self.tree.nodeTag(decl_node)) { - .type_alias => { - inner_type = self.tree.nodeLHS(decl_node); - continue; - }, - .struct_decl => try members_arr.append(.{ - .name = member_loc.slice(self.tree.source), - .type = .{ .@"struct" = try self.structType(member_type_node) orelse return null }, - }), - else => { - try self.addError( - member_type_loc, - "'{s}' is neither an struct or type alias", - .{member_type_name}, - null, - ); - }, - } - }, - else => { + switch (self.instructions.items[member_type_ref].tag) { + .bool_type, + .i32_type, + .u32_type, + .f32_type, + .f16_type, + .vector_type, + .matrix_type, + .atomic_type, + .struct_decl, + => {}, + .array_type => { + if (self.instructions.items[member_type_ref].data.array_type.size == IR.null_ref and i + 1 != member_list.len) { try self.addError( member_loc, - "invalid struct member type '{s}'", - .{member_type_name}, + "struct member with runtime-sized array type, must be the last member of the structure", + .{}, null, ); - }, - } - - break; + continue; + } + }, + .ptr_type, + .sampler_type, + .comparison_sampler_type, + .sampled_texture_type, + .multisampled_texture_type, + .storage_texture_type, + .depth_texture_type, + .external_sampled_texture_type, + => { + try self.addError( + member_loc, + "invalid struct member type '{s}'", + .{member_type_name.slice(self.tree.source)}, + null, + ); + continue; + }, + else => unreachable, } + + const name_index = try self.addString(member_loc.slice(self.tree.source)); + const member_inst = try self.addInst(.struct_member, .{ + .struct_member = .{ + .name = name_index, + .type = member_type_ref, + .@"align" = 0, // TODO + }, + }); + try self.scratch.append(self.allocator, member_inst); } - const struct_name = self.declNameToken(node) orelse unreachable; - return .{ - .name = self.tree.tokenLoc(struct_name).slice(self.tree.source), - .members = try members_arr.toOwnedSlice(), - }; + const name = self.declNameLoc(node).?.slice(self.tree.source); + const name_index = try self.addString(name); + const list = try self.addList(self.scratch.items[scratch_top..]); + const inst = try self.addInst(.struct_decl, .{ + .struct_decl = .{ + .name = name_index, + .members = list, + }, + }); + + return inst; } -pub fn numberType(self: *AstGen, node: Ast.Index) IR.NumberType { - const token = self.tree.nodeToken(node); - const token_tag = self.tree.tokenTag(token); - return switch (token_tag) { - .k_i32 => .i32, - .k_u32 => .u32, - .k_f32 => .f32, - .k_f16 => .f16, +pub fn addString(self: *AstGen, str: []const u8) error{OutOfMemory}!u32 { + const len = str.len + 1; + try self.strings.ensureUnusedCapacity(self.allocator, len); + self.strings.appendSliceAssumeCapacity(str); + self.strings.appendAssumeCapacity('\x00'); + return @intCast(u32, self.strings.items.len - len); +} + +pub fn addList(self: *AstGen, list: []const IR.Ref) error{OutOfMemory}!u32 { + const len = list.len + 1; + try self.refs.ensureUnusedCapacity(self.allocator, len); + self.refs.appendAssumeCapacity(@intCast(u32, list.len)); + self.refs.appendSliceAssumeCapacity(list); + return @intCast(u32, self.refs.items.len - list.len); +} + +pub fn addInst(self: *AstGen, tag: IR.Inst.Tag, data: IR.Inst.Data) error{OutOfMemory}!IR.Ref { + try self.instructions.append(self.allocator, .{ .tag = tag, .data = data }); + return @intCast(u32, self.instructions.items.len - 1); +} + +// // pub fn expression(self: *AstGen, node: Ast.Index) !?IR.Expression { +// // const lhs = self.tree.nodeLHS(node); +// // const rhs = self.tree.nodeRHS(node); +// // const loc = self.tree.tokenLoc(self.tree.nodeToken(node)); +// // return switch (self.tree.nodeTag(node)) { +// // .mul => { +// // const lir = try self.expression(lhs) orelse return null; +// // const rir = try self.expression(rhs) orelse return null; + +// // const is_valid_op = +// // (lir == .number and rir == .number) or +// // ((lir == .construct and lir.construct == .vector) and rir == .number) or +// // (lir == .number and (rir == .construct and rir.construct == .vector)) or +// // ((lir == .construct and lir.construct == .vector) and (rir == .construct and rir.construct == .vector)) or +// // ((lir == .construct and lir.construct == .matrix) and (rir == .construct and rir.construct == .matrix)); +// // if (!is_valid_op) { +// // try self.addError( +// // loc, +// // "invalid operation with '{s}' and '{s}'", +// // .{ @tagName(std.meta.activeTag(lir)), @tagName(std.meta.activeTag(rir)) }, +// // null, +// // ); +// // return null; +// // } +// // }, +// // // .div, +// // // .mod, +// // // .add, +// // // .sub, +// // // .shift_left, +// // // .shift_right, +// // // .binary_and, +// // // .binary_or, +// // // .binary_xor, +// // // .circuit_and, +// // // .circuit_or, +// // .number_literal => .{ .literal = try self.create(.{ .number = try self.create(try self.numberLiteral(node) orelse return null) }) }, +// // .bool_literal => .{ .literal = try self.create(.{ .bool = self.boolLiteral(node) }) }, +// // else => return null, // TODO +// // }; +// // } + +// // pub fn numberLiteral(self: *AstGen, node: Ast.Index) !?IR.NumberLiteral { +// // const loc = self.tree.tokenLoc(self.tree.nodeToken(node)); +// // const str = loc.slice(self.tree.source); + +// // if (std.mem.startsWith(u8, str, "0") and +// // !std.mem.endsWith(u8, str, "i") and +// // !std.mem.endsWith(u8, str, "u") and +// // !std.mem.endsWith(u8, str, "f") and +// // !std.mem.endsWith(u8, str, "h")) +// // { +// // try self.addError( +// // loc, +// // "number literal cannot have leading 0", +// // .{str}, +// // null, +// // ); +// // return null; +// // } + +// // return null; +// // } + +// // pub fn boolLiteral(self: *AstGen, node: Ast.Index) bool { +// // const loc = self.tree.tokenLoc(self.tree.nodeToken(node)); +// // const str = loc.slice(self.tree.source); +// // return str[0] == 't'; +// // } + +pub fn allTypes(self: *AstGen, scope: *Scope, node: Ast.Index) error{OutOfMemory}!?IR.Ref { + return switch (self.tree.nodeTag(node)) { + .bool_type => try self.boolType(node), + .number_type => try self.numberType(node), + .vector_type => try self.vectorType(scope, node) orelse return null, + .matrix_type => try self.matrixType(scope, node) orelse return null, + .atomic_type => try self.atomicType(scope, node) orelse return null, + .array_type => try self.arrayType(scope, node) orelse return null, + .user_type => { + const node_loc = self.tree.tokenLoc(self.tree.nodeToken(node)); + const decl = try self.declRef(scope, node_loc) orelse return null; + switch (self.instructions.items[decl].tag) { + .bool_type, + .i32_type, + .u32_type, + .f32_type, + .f16_type, + .vector_type, + .matrix_type, + .atomic_type, + .array_type, + .ptr_type, + .sampler_type, + .comparison_sampler_type, + .sampled_texture_type, + .multisampled_texture_type, + .storage_texture_type, + .depth_texture_type, + .external_sampled_texture_type, + .struct_decl, + => return decl, + .global_variable => { + try self.addError( + node_loc, + "'{s}' is not a type", + .{node_loc.slice(self.tree.source)}, + null, + ); + return null; + }, + else => unreachable, + } + }, + .sampler_type => try self.samplerType(node), + .sampled_texture_type => try self.sampledTextureType(scope, node) orelse return null, + .multisampled_texture_type => try self.multisampledTextureType(scope, node) orelse return null, + .storage_texture_type => try self.storageTextureType(node) orelse return null, + .depth_texture_type => try self.depthTextureType(node) orelse return null, + .external_texture_type => try self.externalTextureType(node), else => unreachable, }; } -pub fn vectorType(self: *AstGen, node: Ast.Index) !?IR.VectorType { - const token = self.tree.nodeToken(node); - const token_tag = self.tree.tokenTag(token); - const size: IR.VectorType.Size = switch (token_tag) { - .k_vec2 => .vec2, - .k_vec3 => .vec3, - .k_vec4 => .vec4, +pub fn sampledTextureType(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const component_type_node = self.tree.nodeLHS(node); + const component_type = try self.allTypes(scope, component_type_node) orelse return null; + switch (self.instructions.items[component_type].tag) { + .i32_type, + .u32_type, + .f32_type, + => {}, + .bool_type, + .f16_type, + .vector_type, + .matrix_type, + .atomic_type, + .array_type, + .ptr_type, + .sampler_type, + .comparison_sampler_type, + .sampled_texture_type, + .multisampled_texture_type, + .storage_texture_type, + .depth_texture_type, + .external_sampled_texture_type, + .struct_decl, + => { + try self.addError( + self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), + "invalid sampled texture component type", + .{}, + try ErrorMsg.Note.create( + self.allocator, + null, + "must be 'i32', 'u32' or 'f32'", + .{}, + ), + ); + return null; + }, else => unreachable, - }; - const type_node = self.tree.nodeLHS(node); - const component_type: IR.VectorType.Type = switch (self.tree.nodeTag(type_node)) { - .bool_type => .bool, - .number_type => .{ .number = self.numberType(type_node) }, + } + + const tag = self.tree.tokenTag(self.tree.nodeToken(node)); + return try self.addInst( + .sampled_texture_type, + .{ + .sampled_texture_type = .{ + .kind = switch (tag) { + .k_texture_sampled_1d => .@"1d", + .k_texture_sampled_2d => .@"2d", + .k_texture_sampled_2d_array => .@"2d_array", + .k_texture_sampled_3d => .@"3d", + .k_texture_sampled_cube => .cube, + .k_texture_sampled_cube_array => .cube_array, + else => unreachable, + }, + .component_type = component_type, + }, + }, + ); +} + +pub fn multisampledTextureType(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const component_type_node = self.tree.nodeLHS(node); + const component_type = try self.allTypes(scope, component_type_node) orelse return null; + switch (self.instructions.items[component_type].tag) { + .i32_type, + .u32_type, + .f32_type, + => {}, + .bool_type, + .f16_type, + .vector_type, + .matrix_type, + .atomic_type, + .array_type, + .ptr_type, + .sampler_type, + .comparison_sampler_type, + .sampled_texture_type, + .multisampled_texture_type, + .storage_texture_type, + .depth_texture_type, + .external_sampled_texture_type, + .struct_decl, + => { + try self.addError( + self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), + "invalid multisampled texture component type", + .{}, + try ErrorMsg.Note.create( + self.allocator, + null, + "must be 'i32', 'u32' or 'f32'", + .{}, + ), + ); + return null; + }, + else => unreachable, + } + + const tag = self.tree.tokenTag(self.tree.nodeToken(node)); + return try self.addInst( + .multisampled_texture_type, + .{ + .multisampled_texture_type = .{ + .kind = switch (tag) { + .k_texture_multisampled_2d => .@"2d", + else => unreachable, + }, + .component_type = component_type, + }, + }, + ); +} + +pub fn storageTextureType(self: *AstGen, node: Ast.Index) !?IR.Ref { + const texel_format_loc = self.tree.tokenLoc(self.tree.nodeLHS(node)); + const texel_format = std.meta.stringToEnum(Ast.TexelFormat, texel_format_loc.slice(self.tree.source)).?; + + const access_mode_loc = self.tree.tokenLoc(self.tree.nodeRHS(node)); + const access_mode_full = std.meta.stringToEnum(Ast.AccessMode, access_mode_loc.slice(self.tree.source)).?; + const access_mode: IR.Inst.Data.MultisampledTextureTypeKind = switch (access_mode_full) { + .write => .write, else => { try self.addError( - self.tree.tokenLoc(self.tree.nodeToken(type_node)), + access_mode_loc, + "invalid access mode", + .{}, + try ErrorMsg.Note.create(self.allocator, null, "only 'write' is allowed", .{}), + ); + return null; + }, + }; + + const tag = self.tree.tokenTag(self.tree.nodeToken(node)); + return try self.addInst( + .storage_texture_type, + .{ + .storage_texture_type = .{ + .kind = switch (tag) { + .k_texture_storage_1d => .@"1d", + .k_texture_storage_2d => .@"2d", + .k_texture_storage_2d_array => .@"2d_array", + .k_texture_storage_3d => .@"3d", + else => unreachable, + }, + .texel_format = texel_format, + .access_mode = access_mode, + }, + }, + ); +} + +pub fn depthTextureType(self: *AstGen, node: Ast.Index) !?IR.Ref { + const tag = self.tree.tokenTag(self.tree.nodeToken(node)); + return try self.addInst( + .depth_texture_type, + .{ + .depth_texture_type = switch (tag) { + .k_texture_depth_2d => .@"2d", + .k_texture_depth_2d_array => .@"2d_array", + .k_texture_depth_cube => .cube, + .k_texture_depth_cube_array => .cube_array, + .k_texture_depth_multisampled_2d => .multisampled_2d, + else => unreachable, + }, + }, + ); +} + +pub fn externalTextureType(self: *AstGen, node: Ast.Index) !?IR.Ref { + std.debug.assert(self.tree.nodeTag(node) == .external_texture_type); + return try self.addInst(.external_sampled_texture_type, .{ .none = 0 }); +} + +pub fn boolType(self: *AstGen, node: Ast.Index) !IR.Ref { + const inst_tag: IR.Inst.Tag = switch (self.tree.nodeTag(node)) { + .bool_type => .bool_type, + else => unreachable, + }; + return try self.addInst(inst_tag, .{ .none = 0 }); +} + +pub fn numberType(self: *AstGen, node: Ast.Index) !IR.Ref { + const token = self.tree.nodeToken(node); + const token_tag = self.tree.tokenTag(token); + const inst_tag: IR.Inst.Tag = switch (token_tag) { + .k_i32 => .i32_type, + .k_u32 => .u32_type, + .k_f32 => .f32_type, + .k_f16 => .f16_type, + else => unreachable, + }; + return try self.addInst(inst_tag, .{ .none = 0 }); +} + +pub fn samplerType(self: *AstGen, node: Ast.Index) !IR.Ref { + const token = self.tree.nodeToken(node); + const token_tag = self.tree.tokenTag(token); + const inst_tag: IR.Inst.Tag = switch (token_tag) { + .k_sampler => .sampler_type, + .k_comparison_sampler => .comparison_sampler_type, + else => unreachable, + }; + return try self.addInst(inst_tag, .{ .none = 0 }); +} + +pub fn vectorType(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const component_type_node = self.tree.nodeLHS(node); + const component_type = try self.allTypes(scope, component_type_node) orelse return null; + switch (self.instructions.items[component_type].tag) { + .bool_type, + .i32_type, + .u32_type, + .f32_type, + .f16_type, + => {}, + .vector_type, + .matrix_type, + .atomic_type, + .array_type, + .ptr_type, + .sampler_type, + .comparison_sampler_type, + .sampled_texture_type, + .multisampled_texture_type, + .storage_texture_type, + .depth_texture_type, + .external_sampled_texture_type, + .struct_decl, + => { + try self.addError( + self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), "invalid vector component type", .{}, try ErrorMsg.Note.create( @@ -196,35 +632,53 @@ pub fn vectorType(self: *AstGen, node: Ast.Index) !?IR.VectorType { ); return null; }, - }; - return .{ .size = size, .component_type = component_type }; + else => unreachable, + } + + const token_tag = self.tree.tokenTag(self.tree.nodeToken(node)); + return try self.addInst( + .vector_type, + .{ + .vector_type = .{ + .size = switch (token_tag) { + .k_vec2 => .two, + .k_vec3 => .three, + .k_vec4 => .four, + else => unreachable, + }, + .component_type = component_type, + }, + }, + ); } -pub fn matrixType(self: *AstGen, node: Ast.Index) !?IR.MatrixType { - const token = self.tree.nodeToken(node); - const token_tag = self.tree.tokenTag(token); - const size: IR.MatrixType.Size = switch (token_tag) { - .k_mat2x2 => .mat2x2, - .k_mat2x3 => .mat2x3, - .k_mat2x4 => .mat2x4, - .k_mat3x2 => .mat3x2, - .k_mat3x3 => .mat3x3, - .k_mat3x4 => .mat3x4, - .k_mat4x2 => .mat4x2, - .k_mat4x3 => .mat4x3, - .k_mat4x4 => .mat4x4, - else => unreachable, - }; - const type_node = self.tree.nodeLHS(node); - const type_token = self.tree.nodeToken(type_node); - const type_token_tag = self.tree.tokenTag(type_token); - const component_type: IR.MatrixType.Type = switch (type_token_tag) { - .k_f32 => .f32, - .k_f16 => .f16, - else => { +pub fn matrixType(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const component_type_node = self.tree.nodeLHS(node); + const component_type = try self.allTypes(scope, component_type_node) orelse return null; + switch (self.instructions.items[component_type].tag) { + .f32_type, + .f16_type, + => {}, + .bool_type, + .i32_type, + .u32_type, + .vector_type, + .matrix_type, + .atomic_type, + .array_type, + .ptr_type, + .sampler_type, + .comparison_sampler_type, + .sampled_texture_type, + .multisampled_texture_type, + .storage_texture_type, + .depth_texture_type, + .external_sampled_texture_type, + .struct_decl, + => { try self.addError( - self.tree.tokenLoc(self.tree.nodeToken(type_node)), - "invalid matrix element type", + self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), + "invalid matrix component type", .{}, try ErrorMsg.Note.create( self.allocator, @@ -235,20 +689,58 @@ pub fn matrixType(self: *AstGen, node: Ast.Index) !?IR.MatrixType { ); return null; }, - }; - return .{ .size = size, .component_type = component_type }; + else => unreachable, + } + + const token_tag = self.tree.tokenTag(self.tree.nodeToken(node)); + return try self.addInst( + .matrix_type, + .{ + .matrix_type = .{ + .cols = switch (token_tag) { + .k_mat2x2, .k_mat2x3, .k_mat2x4 => .two, + .k_mat3x2, .k_mat3x3, .k_mat3x4 => .three, + .k_mat4x2, .k_mat4x3, .k_mat4x4 => .four, + else => unreachable, + }, + .rows = switch (token_tag) { + .k_mat2x2, .k_mat3x2, .k_mat4x2 => .two, + .k_mat2x3, .k_mat3x3, .k_mat4x3 => .three, + .k_mat2x4, .k_mat3x4, .k_mat4x4 => .four, + else => unreachable, + }, + .component_type = component_type, + }, + }, + ); } -pub fn atomicType(self: *AstGen, node: Ast.Index) !?IR.AtomicType { - const type_node = self.tree.nodeLHS(node); - const type_token = self.tree.nodeToken(type_node); - const type_token_tag = self.tree.tokenTag(type_token); - const component_type: IR.AtomicType.Type = switch (type_token_tag) { - .k_i32 => .i32, - .k_u32 => .u32, - else => { +pub fn atomicType(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const component_type_node = self.tree.nodeLHS(node); + const component_type = try self.allTypes(scope, component_type_node) orelse return null; + switch (self.instructions.items[component_type].tag) { + .i32_type, + .u32_type, + => {}, + .bool_type, + .f32_type, + .f16_type, + .vector_type, + .matrix_type, + .atomic_type, + .array_type, + .ptr_type, + .sampler_type, + .comparison_sampler_type, + .sampled_texture_type, + .multisampled_texture_type, + .storage_texture_type, + .depth_texture_type, + .external_sampled_texture_type, + .struct_decl, + => { try self.addError( - self.tree.tokenLoc(self.tree.nodeToken(type_node)), + self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), "invalid atomic component type", .{}, try ErrorMsg.Note.create( @@ -260,144 +752,98 @@ pub fn atomicType(self: *AstGen, node: Ast.Index) !?IR.AtomicType { ); return null; }, - }; - return .{ .component_type = component_type }; + else => unreachable, + } + + return try self.addInst(.atomic_type, .{ .atomic_type = .{ .component_type = component_type } }); } -pub fn arrayType(self: *AstGen, node: Ast.Index) !?IR.ArrayType { - var component_type_node = self.tree.nodeLHS(node); - var component_type: IR.ArrayType.Type = undefined; - while (true) { - switch (self.tree.nodeTag(component_type_node)) { - .bool_type => component_type = .bool, - .number_type => component_type = .{ .number = self.numberType(component_type_node) }, - .vector_type => component_type = .{ .vector = try self.vectorType(component_type_node) orelse return null }, - .matrix_type => component_type = .{ .matrix = try self.matrixType(component_type_node) orelse return null }, - .atomic_type => component_type = .{ .atomic = try self.atomicType(component_type_node) orelse return null }, - .array_type => { - var array_type = try self.arena.create(IR.ArrayType.Type); - - if (try self.arrayType(component_type_node)) |ty| { - if (ty.size != null) { - array_type.* = ty.component_type; - component_type = .{ .array = array_type }; - break; - } else { - try self.addError( - self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), - "array componet type can not be a runtime-sized array", - .{}, - null, - ); - } - } - - return null; - }, - .user_type => { - const component_type_loc = self.tree.tokenLoc(self.tree.nodeToken(component_type_node)); - const component_type_name = component_type_loc.slice(self.tree.source); - const decl_node = try self.expectFindDeclNode(component_type_loc, component_type_name) orelse break; - switch (self.tree.nodeTag(decl_node)) { - .type_alias => { - component_type_node = self.tree.nodeLHS(decl_node); - continue; - }, - .struct_decl => component_type = .{ .@"struct" = try self.structType(component_type_node) orelse return null }, - else => { - try self.addError( - component_type_loc, - "'{s}' is neither an struct or type alias", - .{component_type_name}, - null, - ); - return null; - }, - } - }, - else => { +pub fn arrayType(self: *AstGen, scope: *Scope, node: Ast.Index) !?IR.Ref { + const component_type_node = self.tree.nodeLHS(node); + const component_type = try self.allTypes(scope, component_type_node) orelse return null; + switch (self.instructions.items[component_type].tag) { + .bool_type, + .i32_type, + .u32_type, + .f32_type, + .f16_type, + .vector_type, + .matrix_type, + .atomic_type, + .struct_decl, + => {}, + .array_type => { + if (self.instructions.items[component_type].data.array_type.size == IR.null_ref) { try self.addError( self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), - "invalid array component type", + "array componet type can not be a runtime-sized array", .{}, null, ); return null; - }, - } - break; + } + }, + .ptr_type, + .sampler_type, + .comparison_sampler_type, + .sampled_texture_type, + .multisampled_texture_type, + .storage_texture_type, + .depth_texture_type, + .external_sampled_texture_type, + => { + try self.addError( + self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), + "invalid array component type", + .{}, + null, + ); + return null; + }, + else => unreachable, } const size_node = self.tree.nodeRHS(node); - if (size_node == Ast.null_index) { - return .{ .component_type = component_type }; + var size_ref = IR.null_ref; + if (size_node != Ast.null_index) { + // TODO } - return .{ .component_type = component_type, .size = undefined }; // TODO + return try self.addInst( + .array_type, + .{ + .array_type = .{ + .component_type = component_type, + .size = size_ref, + }, + }, + ); } -pub fn structType(self: *AstGen, node: Ast.Index) !?[]const u8 { - const ref_loc = self.tree.tokenLoc(self.tree.nodeToken(node)); - return ref_loc.slice(self.tree.source); -} - -/// UNUSED -/// returns the actual type of a type alias -pub fn fetchTypeAliasType(self: *AstGen, node: Ast.Index) void { - std.debug.assert(self.tree.nodeTag(node) == .type_alias); - if (self.tree.nodeTag(node) == .type_alias) { - return self.fetchTypeAliasType(self.tree.nodeLHS(node)); - } - return self.tree.nodeLHS(node); -} - -pub fn expectFindDeclNode(self: *AstGen, ref_loc: Token.Loc, name: []const u8) !?Ast.Index { - return self.findDeclNode(name) orelse { - try self.addError( - ref_loc, - "use of undeclared identifier '{s}'", - .{name}, - null, - ); - return null; - }; -} - -pub fn findDeclNode(self: *AstGen, name: []const u8) ?Ast.Index { - for (self.scopeToRange()) |node| { - const node_token = self.declNameToken(node) orelse continue; - if (std.mem.eql(u8, name, self.tree.tokenLoc(node_token).slice(self.tree.source))) { - return node; +pub fn declRef(self: *AstGen, scope: *Scope, loc: Token.Loc) !?IR.Ref { + const name = loc.slice(self.tree.source); + var s = scope; + while (true) { + const decl = scope.decls().get(name) orelse { + s = s.parent() orelse break; + continue; + } orelse return null; + if (decl == IR.null_ref) { + break; } + return decl; } + try self.addError( + loc, + "use of undeclared identifier '{s}'", + .{name}, + null, + ); return null; } -pub fn checkRedeclaration(self: *AstGen, range: []const Ast.Index, decl_node: Ast.Index) !void { - const decl_token_loc = self.tree.tokenLoc(self.declNameToken(decl_node).?); - const decl_name = decl_token_loc.slice(self.tree.source); - for (range) |redecl_node| { - std.debug.assert(decl_node != redecl_node); - const redecl_token_loc = self.tree.tokenLoc(self.declNameToken(redecl_node).?); - const redecl_name = redecl_token_loc.slice(self.tree.source); - if (std.mem.eql(u8, decl_name, redecl_name)) { - try self.addError( - redecl_token_loc, - "redeclaration of '{s}'", - .{decl_name}, - try ErrorMsg.Note.create( - self.allocator, - decl_token_loc, - "other declaration here", - .{}, - ), - ); - } - } -} - -pub fn declNameToken(self: *AstGen, node: Ast.Index) ?Ast.Index { - return switch (self.tree.nodeTag(node)) { +pub fn declNameLoc(self: *AstGen, node: Ast.Index) ?Token.Loc { + const token = switch (self.tree.nodeTag(node)) { .global_variable => self.tree.extraData(Ast.Node.GlobalVarDecl, self.tree.nodeLHS(node)).name, .struct_decl, .fn_decl, @@ -406,15 +852,9 @@ pub fn declNameToken(self: *AstGen, node: Ast.Index) ?Ast.Index { .type_alias, => self.tree.nodeToken(node) + 1, .struct_member => self.tree.nodeToken(node), - else => null, - }; -} - -pub fn scopeToRange(self: AstGen) []const Ast.Index { - return switch (self.scope) { - .top => self.tree.spanToList(0), - .range => |r| r, + else => return null, }; + return self.tree.tokenLoc(token); } pub fn addError( diff --git a/libs/dusk/src/IR.zig b/libs/dusk/src/IR.zig index ddbb25d5..24e5b641 100644 --- a/libs/dusk/src/IR.zig +++ b/libs/dusk/src/IR.zig @@ -4,11 +4,15 @@ const Ast = @import("Ast.zig"); const ErrorMsg = @import("main.zig").ErrorMsg; const IR = @This(); -arena: std.heap.ArenaAllocator, -root: TranslationUnit, +allocator: std.mem.Allocator, +instructions: []const Inst, +refs: []const Ref, +strings: []const u8, pub fn deinit(self: IR) void { - self.arena.deinit(); + self.allocator.free(self.instructions); + self.allocator.free(self.refs); + self.allocator.free(self.strings); } pub const AstGenResult = union(enum) { @@ -17,193 +21,276 @@ pub const AstGenResult = union(enum) { }; pub fn generate(allocator: std.mem.Allocator, tree: *const Ast) !AstGenResult { - var arena = std.heap.ArenaAllocator.init(allocator); - errdefer arena.deinit(); - var astgen = AstGen{ - .arena = arena.allocator(), .allocator = allocator, .tree = tree, - .errors = .{}, + .scope_pool = std.heap.MemoryPool(AstGen.Scope).init(allocator), }; defer astgen.deinit(); - const root = try astgen.translationUnit() orelse { - arena.deinit(); - return .{ - .errors = try astgen.errors.toOwnedSlice(allocator), - }; - }; + if (!try astgen.translationUnit()) { + return .{ .errors = try astgen.errors.toOwnedSlice(allocator) }; + } - return .{ .ir = .{ .arena = arena, .root = root } }; + return .{ .ir = .{ + .allocator = allocator, + .instructions = try astgen.instructions.toOwnedSlice(allocator), + .refs = try astgen.refs.toOwnedSlice(allocator), + .strings = try astgen.strings.toOwnedSlice(allocator), + } }; } -pub const TranslationUnit = []const GlobalDecl; +pub const Ref = u32; +pub const null_ref: Ref = std.math.maxInt(Ref); -pub const GlobalDecl = union(enum) { - variable: GlobalVariable, - @"struct": StructDecl, -}; +pub const Inst = packed struct { + tag: Tag, + data: Data, -pub const GlobalVariable = struct { - name: []const u8, - type: union(enum) {}, - expr: Expression, -}; + pub const Tag = enum(u6) { + /// data is global_variable + global_variable, -pub const StructDecl = struct { - name: []const u8, - members: []const StructMember, -}; + /// data is struct_decl + struct_decl, + /// data is struct_member + struct_member, -pub const StructMember = struct { - name: []const u8, - type: Type, + /// data is attr_simple + attr_simple, + /// data is attr_expr + attr_expr, + /// data is attr_builtin + attr_builtin, + /// data is attr_workgroup + attr_workgroup, + /// data is attr_interpolate + attr_interpolate, - pub const Type = union(enum) { - bool, - @"struct": []const u8, - number: NumberType, - vector: VectorType, - matrix: MatrixType, - atomic: AtomicType, - array: ArrayType, + /// data is none + bool_type, + /// data is none + i32_type, + /// data is none + u32_type, + /// data is none + f32_type, + /// data is none + f16_type, + /// data is vector_type + vector_type, + /// data is matrix_type + matrix_type, + /// data is atomic_type + atomic_type, + /// data is array_type + array_type, + /// data is ptr_type + ptr_type, + /// data is none + sampler_type, + /// data is none + comparison_sampler_type, + /// data is sampled_texture_type + sampled_texture_type, + /// data is multisampled_texture_type + multisampled_texture_type, + /// data is storage_texture_type + storage_texture_type, + /// data is depth_texture_type + depth_texture_type, + /// data is none + external_sampled_texture_type, + + /// data is integer_literal + integer_literal, + /// data is float_literal + float_literal, + /// data is none + true_literal, + /// data is none + false_literal, + + /// data is ref + not, + /// data is ref + negate, + /// data is ref + deref, + /// data is ref + addr_of, + + /// data is binary + mul, + /// data is binary + div, + /// data is binary + mod, + /// data is binary + add, + /// data is binary + sub, + /// data is binary + shift_left, + /// data is binary + shift_right, + /// data is binary + binary_and, + /// data is binary + binary_or, + /// data is binary + binary_xor, + /// data is binary + circuit_and, + /// data is binary + circuit_or, + /// data is binary + equal, + /// data is binary + not_equal, + /// data is binary + less, + /// data is binary + less_equal, + /// data is binary + greater, + /// data is binary + greater_equal, + + /// data is binary + index, + /// data is member_access + member_access, + /// data is binary (lhs is expr, rhs is type) + bitcast, }; -}; -pub const Expression = union(enum) { - literal: Literal, - ident: []const u8, - unary: struct { - op: UnaryOperator, - expr: *const Expression, - }, - binary: struct { - op: BinaryOperator, - lhs: *const Expression, - rhs: *const Expression, - }, - index: struct { - base: *const Expression, - index: *const Expression, - }, - member: struct { - base: *const Expression, - field: []const u8, - }, - bitcast: struct { - expr: *const Expression, - to: union(enum) { - number: NumberType, - vector: VectorType, + pub const Data = packed union { + /// TODO: https://github.com/ziglang/zig/issues/14980 + none: u1, + ref: Ref, + global_variable: packed struct { + /// index to null-terminated string in `strings` + name: u32, + type: Ref, + addr_space: Ast.AddressSpace = .none, + access_mode: Ast.AccessMode = .none, + /// length of attributes + attrs: u4 = 0, + }, + struct_decl: packed struct { + /// index to null-terminated string in `strings` + name: u32, + /// length of the member Ref's which comes after this + members: u32, + }, + struct_member: packed struct { + /// index to null-terminated string in `strings` + name: u32, + type: Ref, + @"align": u29, // 0 means null + }, + /// attributes with no argument. + attr_simple: enum { + invariant, + @"const", + vertex, + fragment, + compute, + }, + /// attributes with an expression argument. + attr_expr: packed struct { + kind: enum { + @"align", + binding, + group, + id, + location, + size, + }, + expr: Ref, + }, + /// @builtin attribute which accepts a BuiltinValue argument. + attr_builtin: Ast.BuiltinValue, + /// @workgroup attribute. accepts at laest 1 argument. + attr_workgroup: packed struct { + expr0: Ref, + expr1: Ref = null_ref, + expr2: Ref = null_ref, + }, + /// @interpolate attribute. accepts 2 arguments. + attr_interpolate: packed struct { + type: Ast.InterpolationType, + sample: Ast.InterpolationSample, + }, + vector_type: packed struct { + component_type: Ref, + size: enum { two, three, four }, + }, + matrix_type: packed struct { + component_type: Ref, + cols: enum { two, three, four }, + rows: enum { two, three, four }, + }, + atomic_type: packed struct { component_type: Ref }, + array_type: packed struct { + component_type: Ref, + size: Ref = null_ref, + }, + ptr_type: packed struct { + component_type: Ref, + addr_space: Ast.AddressSpace, + access_mode: Ast.AccessMode, + }, + sampled_texture_type: packed struct { + kind: enum { + @"1d", + @"2d", + @"2d_array", + @"3d", + cube, + cube_array, + }, + component_type: Ref, + }, + multisampled_texture_type: packed struct { + kind: enum { @"2d" }, + component_type: Ref, + }, + storage_texture_type: packed struct { + kind: enum { + @"1d", + @"2d", + @"2d_array", + @"3d", + }, + texel_format: Ast.TexelFormat, + access_mode: MultisampledTextureTypeKind, + }, + depth_texture_type: enum { + @"2d", + @"2d_array", + cube, + cube_array, + multisampled_2d, + }, + integer_literal: i64, + float_literal: f64, + /// meaning of LHS and RHS depends on the corresponding Tag. + binary: packed struct { + lhs: Ref, + rhs: Ref, + }, + member_access: packed struct { + base: Ref, + /// index to null-terminated string in `strings` + name: u32, }, - }, -}; -pub const Literal = union(enum) { - number: NumberLiteral, - bool: bool, -}; - -pub const NumberLiteral = union(enum) { - int: i64, - float: f64, -}; - -pub const UnaryOperator = enum { - not, - negate, - addr_of, - deref, -}; - -pub const BinaryOperator = enum { - mul, - div, - mod, - add, - sub, - shift_left, - shift_right, - binary_and, - binary_or, - binary_xor, - circuit_and, - circuit_or, - equal, - not_equal, - less, - less_equal, - greater, - greater_equal, -}; - -pub const NumberType = enum { - i32, - u32, - f32, - f16, -}; - -pub const VectorType = struct { - size: Size, - component_type: Type, - - pub const Type = union(enum) { - bool, - number: NumberType, + pub const MultisampledTextureTypeKind = enum { write }; }; - pub const Size = enum { - vec2, - vec3, - vec4, - }; -}; - -pub const MatrixType = struct { - size: Size, - component_type: Type, - - pub const Type = enum { - f32, - f16, - abstract_float, - }; - - pub const Size = enum { - mat2x2, - mat2x3, - mat2x4, - mat3x2, - mat3x3, - mat3x4, - mat4x2, - mat4x3, - mat4x4, - }; -}; - -pub const AtomicType = struct { - component_type: Type, - - pub const Type = enum { - u32, - i32, - }; -}; - -pub const ArrayType = struct { - component_type: Type, - size: ?NumberLiteral = null, - - pub const Type = union(enum) { - bool, - number: NumberType, - @"struct": []const u8, - vector: VectorType, - matrix: MatrixType, - atomic: AtomicType, - array: *Type, - }; + comptime { + std.debug.assert(@bitSizeOf(Inst) <= 104); // 13B + } }; diff --git a/libs/dusk/src/Parser.zig b/libs/dusk/src/Parser.zig index 2131e343..14a3e667 100644 --- a/libs/dusk/src/Parser.zig +++ b/libs/dusk/src/Parser.zig @@ -2,10 +2,8 @@ const std = @import("std"); const Ast = @import("Ast.zig"); const Token = @import("Token.zig"); -const Tokenizer = @import("Tokenizer.zig"); const Extension = @import("main.zig").Extension; const ErrorMsg = @import("main.zig").ErrorMsg; -const comptimePrint = std.fmt.comptimePrint; const fieldNames = std.meta.fieldNames; const Parser = @This(); @@ -1877,7 +1875,7 @@ fn addExtra(p: *Parser, extra: anytype) error{OutOfMemory}!Ast.Index { try p.extra.ensureUnusedCapacity(p.allocator, fields.len); const result = @intCast(Ast.Index, p.extra.items.len); inline for (fields) |field| { - comptime std.debug.assert(field.type == Ast.Index or field.type == Ast.Index); + comptime std.debug.assert(field.type == Ast.Index); p.extra.appendAssumeCapacity(@field(extra, field.name)); } return result; diff --git a/libs/dusk/test/main.zig b/libs/dusk/test/main.zig index 724abafc..e6f2d17c 100644 --- a/libs/dusk/test/main.zig +++ b/libs/dusk/test/main.zig @@ -152,6 +152,10 @@ fn expectError(source: [:0]const u8, err: dusk.ErrorMsg) !void { .{ err.note.?.msg, err_list[0].note.?.msg }, ); } + if (err.note == null) { + std.debug.print("\x1b[31mnote missed: {s}\x1b[0m\n", .{err_list[0].note.?.msg}); + return error.NoteMissed; + } try expect(std.mem.eql(u8, err.note.?.msg, err_list[0].note.?.msg)); if (err_list[0].note.?.loc) |_| { errdefer { @@ -232,7 +236,22 @@ test "variable & expressions" { // try expect(ir.nodeTag(@"7") == .number_literal); } -test "analyser errors" { +test "simple analyse's result" { + // { + // const source = + // \\type T0 = f32; + // \\type T1 = T0; + // \\type T2 = T1; + // \\type T3 = T2; + // \\struct S0 { m0: T3 } + // ; + // var ir = try expectIR(source); + // // try std.testing.expect(ir.root[0].@"struct".members[0].type.number == .f32); + // ir.deinit(); + // } +} + +test "must error" { { const source = "^"; try expectError(source, .{ @@ -260,18 +279,17 @@ test "analyser errors" { \\struct S0 { m: S1 } ; try expectError(source, .{ - .msg = "'S1' is neither an struct or type alias", + .msg = "'S1' is not a type", .loc = .{ .start = 27, .end = 29 }, }); } { const source = - \\type T = sampler; - \\struct S0 { m: T } + \\struct S0 { m: sampler } ; try expectError(source, .{ - .msg = "invalid struct member type 'T'", - .loc = .{ .start = 30, .end = 31 }, + .msg = "invalid struct member type 'sampler'", + .loc = .{ .start = 12, .end = 13 }, }); } { @@ -285,4 +303,23 @@ test "analyser errors" { .note = .{ .msg = "other declaration here", .loc = .{ .start = 4, .end = 6 } }, }); } + { + const source = "struct S { m0: vec2 }"; + try expectError(source, .{ + .msg = "invalid vector component type", + .loc = .{ .start = 20, .end = 27 }, + .note = .{ .msg = "must be 'i32', 'u32', 'f32', 'f16' or 'bool'" }, + }); + } + { + const source = + \\type T0 = sampler; + \\type T1 = texture_1d; + ; + try expectError(source, .{ + .msg = "invalid sampled texture component type", + .loc = .{ .start = 40, .end = 42 }, + .note = .{ .msg = "must be 'i32', 'u32' or 'f32'" }, + }); + } }