diff --git a/libs/dusk/src/Analyse.zig b/libs/dusk/src/Analyse.zig deleted file mode 100644 index b9fdd88d..00000000 --- a/libs/dusk/src/Analyse.zig +++ /dev/null @@ -1,182 +0,0 @@ -const std = @import("std"); -const Ast = @import("Ast.zig"); -const Token = @import("Token.zig"); -const ErrorMsg = @import("main.zig").ErrorMsg; -const Analyse = @This(); - -allocator: std.mem.Allocator, -tree: *const Ast, -errors: std.ArrayListUnmanaged(ErrorMsg), - -pub fn deinit(self: *Analyse) void { - for (self.errors.items) |*err_msg| err_msg.deinit(self.allocator); - self.errors.deinit(self.allocator); -} - -pub fn analyseRoot(self: *Analyse) !void { - const global_items = self.tree.spanToList(0); - - for (global_items, 0..) |node_i, i| { - try self.checkRedeclaration(global_items[i + 1 ..], node_i); - try self.globalDecl(global_items, node_i); - } - - if (self.errors.items.len > 0) { - return error.Analysing; - } -} - -pub fn globalDecl(self: *Analyse, parent_scope: []const Ast.Index, node_i: Ast.Index) !void { - switch (self.tree.nodeTag(node_i)) { - .global_variable => {}, // TODO - .struct_decl => try self.structDecl(parent_scope, node_i), - else => std.debug.print("Global Decl TODO: {}\n", .{self.tree.nodeTag(node_i)}), - } -} - -pub fn structDecl(self: *Analyse, parent_scope: []const Ast.Index, node: Ast.Index) !void { - 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_loc = self.tree.tokenLoc(self.tree.nodeToken(member_type_node)); - const member_type_name = member_type_loc.slice(self.tree.source); - - var inner_type = member_type_node; - while (true) { - switch (self.tree.nodeTag(inner_type)) { - .scalar_type, - .vector_type, - .matrix_type, - .atomic_type, - => {}, - .array_type => { - if (self.tree.nodeRHS(member_type_node) == Ast.null_index and - i != member_list.len - 1) - { - try self.addError( - member_loc, - "struct member with runtime-sized array type, must be the last member of the structure", - .{}, - null, - ); - } - }, - .user_type => { - const decl_node = try self.expectFindTypeAliasOrStructDeclNode(member_type_loc, parent_scope, member_type_name) orelse break; - if (self.tree.nodeTag(decl_node) == .type_alias) { - inner_type = self.tree.nodeLHS(decl_node); - continue; - } - }, - else => { - try self.addError( - member_loc, - "invalid struct member type '{s}'", - .{member_type_name}, - null, - ); - }, - } - break; - } - } -} - -/// UNUSED -/// returns the actual type of a type alias -pub fn fetchTypeAliasType(self: *Analyse, 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 expectFindTypeAliasOrStructDeclNode(self: *Analyse, ref_loc: Token.Loc, scope_items: []const Ast.Index, name: []const u8) !?Ast.Index { - if (try self.expectFindDeclNode(ref_loc, scope_items, name)) |decl_node| { - switch (self.tree.nodeTag(decl_node)) { - .struct_decl, .type_alias => return decl_node, - else => { - try self.addError( - ref_loc, - "'{s}' is neither an struct or type alias", - .{name}, - null, - ); - }, - } - } - return null; -} - -pub fn expectFindDeclNode(self: *Analyse, ref_loc: Token.Loc, scope_items: []const Ast.Index, name: []const u8) !?Ast.Index { - return self.findDeclNode(scope_items, name) orelse { - try self.addError( - ref_loc, - "use of undeclared identifier '{s}'", - .{name}, - null, - ); - return null; - }; -} - -pub fn findDeclNode(self: *Analyse, scope_items: []const Ast.Index, name: []const u8) ?Ast.Index { - for (scope_items) |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; - } - } - return null; -} - -pub fn checkRedeclaration(self: *Analyse, scope_items: []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 (scope_items) |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: *Analyse, node: Ast.Index) ?Ast.Index { - return switch (self.tree.nodeTag(node)) { - .global_variable => self.tree.extraData(Ast.Node.GlobalVarDecl, self.tree.nodeLHS(node)).name, - .struct_decl, - .fn_decl, - .global_constant, - .override, - .type_alias, - => self.tree.nodeToken(node) + 1, - .struct_member => self.tree.nodeToken(node), - else => null, - }; -} - -pub fn addError( - self: *Analyse, - loc: Token.Loc, - comptime format: []const u8, - args: anytype, - note: ?ErrorMsg.Note, -) !void { - const err_msg = try ErrorMsg.create(self.allocator, loc, format, args, note); - try self.errors.append(self.allocator, err_msg); -} diff --git a/libs/dusk/src/Ast.zig b/libs/dusk/src/Ast.zig index f8b82c1b..7cf09ce5 100644 --- a/libs/dusk/src/Ast.zig +++ b/libs/dusk/src/Ast.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const Analyse = @import("Analyse.zig"); const Parser = @import("Parser.zig"); const Token = @import("Token.zig"); const Tokenizer = @import("Tokenizer.zig"); @@ -30,22 +29,27 @@ pub const ParseResult = union(enum) { /// parses a TranslationUnit (WGSL Program) pub fn parse(allocator: std.mem.Allocator, source: [:0]const u8) !ParseResult { - const estimated_tokens = source.len / 8; - var tokens = std.MultiArrayList(Token){}; - try tokens.ensureTotalCapacity(allocator, estimated_tokens); - - var tokenizer = Tokenizer.init(source); - while (true) { - const tok = tokenizer.next(); - try tokens.append(allocator, tok); - if (tok.tag == .eof) break; - } - var p = Parser{ .allocator = allocator, .source = source, .tok_i = 0, - .tokens = tokens.toOwnedSlice(), + .tokens = blk: { + const estimated_tokens = source.len / 8; + + var tokens = std.MultiArrayList(Token){}; + errdefer tokens.deinit(allocator); + + try tokens.ensureTotalCapacity(allocator, estimated_tokens); + + var tokenizer = Tokenizer.init(source); + while (true) { + const tok = tokenizer.next(); + try tokens.append(allocator, tok); + if (tok.tag == .eof) break; + } + + break :blk tokens; + }, .nodes = .{}, .extra = .{}, .scratch = .{}, @@ -53,48 +57,25 @@ pub fn parse(allocator: std.mem.Allocator, source: [:0]const u8) !ParseResult { .extensions = Extension.Array.initFill(false), }; defer p.deinit(); - errdefer p.tokens.deinit(allocator); // TODO: make sure tokens:nodes ratio is right - const estimated_node_count = (tokens.len + 2) / 2; + const estimated_node_count = (p.tokens.len + 2) / 2; try p.nodes.ensureTotalCapacity(allocator, estimated_node_count); - p.parseRoot() catch |err| { - if (err == error.Parsing) { - p.tokens.deinit(allocator); - return .{ .errors = try p.errors.toOwnedSlice(allocator) }; - } - return err; + _ = try p.translationUnit() orelse { + return .{ .errors = try p.errors.toOwnedSlice(allocator) }; }; return .{ .tree = .{ .source = source, - .tokens = p.tokens, + .tokens = p.tokens.toOwnedSlice(), .nodes = p.nodes.toOwnedSlice(), .extra = try p.extra.toOwnedSlice(allocator), }, }; } -pub fn analyse(tree: Ast, allocator: std.mem.Allocator) !?[]ErrorMsg { - var analyser = Analyse{ - .allocator = allocator, - .tree = &tree, - .errors = .{}, - }; - defer analyser.deinit(); - - analyser.analyseRoot() catch |err| { - if (err == error.Analysing) { - return try analyser.errors.toOwnedSlice(allocator); - } - return err; - }; - - return null; -} - pub fn spanToList(tree: Ast, span: Ast.Index) []const Ast.Index { std.debug.assert(tree.nodeTag(span) == .span); return tree.extra[tree.nodeLHS(span)..tree.nodeRHS(span)]; @@ -313,7 +294,12 @@ pub const Node = struct { /// TOK : k_i32, k_u32, k_f32, k_f16, k_bool /// LHS : -- /// RHS : -- - scalar_type, + number_type, + + /// TOK : k_bool + /// LHS : -- + /// RHS : -- + bool_type, /// TOK : k_sampler, k_comparison_sampler /// LHS : -- @@ -523,8 +509,8 @@ pub const Node = struct { /// vector prefix (e.g. vec2) and matrix prefix (e.g. mat2x2) LHS is null /// see callExpr in Parser.zig if you don't understand this /// - /// TOK : ident, k_array, 'scalar keywords', 'vector keywords', 'matrix keywords' - /// LHS : (scalar_type, vector_type, matrix_type, array_type)? + /// TOK : ident, k_array, k_bool, 'number type keywords', 'vector keywords', 'matrix keywords' + /// LHS : (number_type, bool_type, vector_type, matrix_type, array_type)? /// RHS : arguments (Expr span) call, diff --git a/libs/dusk/src/AstGen.zig b/libs/dusk/src/AstGen.zig new file mode 100644 index 00000000..733b0884 --- /dev/null +++ b/libs/dusk/src/AstGen.zig @@ -0,0 +1,429 @@ +const std = @import("std"); +const Ast = @import("Ast.zig"); +const Token = @import("Token.zig"); +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, + +pub const Scope = union(enum) { + top, + range: []const Ast.Index, +}; + +pub fn deinit(self: *AstGen) void { + for (self.errors.items) |*err_msg| err_msg.deinit(self.allocator); + self.errors.deinit(self.allocator); +} + +pub fn translationUnit(self: *AstGen) !?IR.TranslationUnit { + const global_decls = self.tree.spanToList(0); + + var list = std.ArrayList(IR.GlobalDecl).init(self.arena); + defer list.deinit(); + + 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); + } + + if (self.errors.items.len > 0) { + return null; + } + + return try list.toOwnedSlice(); +} + +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)}), + } + + return null; +} + +pub fn structDecl(self: *AstGen, node: Ast.Index) !?IR.StructDecl { + var members_arr = std.ArrayList(IR.StructMember).init(self.arena); + defer members_arr.deinit(); + + 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); + + 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 => { + try self.addError( + member_loc, + "invalid struct member type '{s}'", + .{member_type_name}, + null, + ); + }, + } + + break; + } + } + + const struct_name = self.declNameToken(node) orelse unreachable; + return .{ + .name = self.tree.tokenLoc(struct_name).slice(self.tree.source), + .members = try members_arr.toOwnedSlice(), + }; +} + +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, + 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, + 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) }, + else => { + try self.addError( + self.tree.tokenLoc(self.tree.nodeToken(type_node)), + "invalid vector component type", + .{}, + try ErrorMsg.Note.create( + self.allocator, + null, + "must be 'i32', 'u32', 'f32', 'f16' or 'bool'", + .{}, + ), + ); + return null; + }, + }; + return .{ .size = size, .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 => { + try self.addError( + self.tree.tokenLoc(self.tree.nodeToken(type_node)), + "invalid matrix element type", + .{}, + try ErrorMsg.Note.create( + self.allocator, + null, + "must be 'f32' or 'f16'", + .{}, + ), + ); + return null; + }, + }; + return .{ .size = size, .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 => { + try self.addError( + self.tree.tokenLoc(self.tree.nodeToken(type_node)), + "invalid atomic component type", + .{}, + try ErrorMsg.Note.create( + self.allocator, + null, + "must be 'i32' or 'u32'", + .{}, + ), + ); + return null; + }, + }; + return .{ .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 => { + try self.addError( + self.tree.tokenLoc(self.tree.nodeToken(component_type_node)), + "invalid array component type", + .{}, + null, + ); + return null; + }, + } + break; + } + + const size_node = self.tree.nodeRHS(node); + if (size_node == Ast.null_index) { + return .{ .component_type = component_type }; + } + + return .{ .component_type = component_type, .size = undefined }; // TODO +} + +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; + } + } + 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)) { + .global_variable => self.tree.extraData(Ast.Node.GlobalVarDecl, self.tree.nodeLHS(node)).name, + .struct_decl, + .fn_decl, + .global_constant, + .override, + .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, + }; +} + +pub fn addError( + self: *AstGen, + loc: Token.Loc, + comptime format: []const u8, + args: anytype, + note: ?ErrorMsg.Note, +) !void { + const err_msg = try ErrorMsg.create(self.allocator, loc, format, args, note); + try self.errors.append(self.allocator, err_msg); +} diff --git a/libs/dusk/src/IR.zig b/libs/dusk/src/IR.zig new file mode 100644 index 00000000..ddbb25d5 --- /dev/null +++ b/libs/dusk/src/IR.zig @@ -0,0 +1,209 @@ +const std = @import("std"); +const AstGen = @import("AstGen.zig"); +const Ast = @import("Ast.zig"); +const ErrorMsg = @import("main.zig").ErrorMsg; +const IR = @This(); + +arena: std.heap.ArenaAllocator, +root: TranslationUnit, + +pub fn deinit(self: IR) void { + self.arena.deinit(); +} + +pub const AstGenResult = union(enum) { + ir: IR, + errors: []ErrorMsg, +}; + +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 = .{}, + }; + defer astgen.deinit(); + + const root = try astgen.translationUnit() orelse { + arena.deinit(); + return .{ + .errors = try astgen.errors.toOwnedSlice(allocator), + }; + }; + + return .{ .ir = .{ .arena = arena, .root = root } }; +} + +pub const TranslationUnit = []const GlobalDecl; + +pub const GlobalDecl = union(enum) { + variable: GlobalVariable, + @"struct": StructDecl, +}; + +pub const GlobalVariable = struct { + name: []const u8, + type: union(enum) {}, + expr: Expression, +}; + +pub const StructDecl = struct { + name: []const u8, + members: []const StructMember, +}; + +pub const StructMember = struct { + name: []const u8, + type: Type, + + pub const Type = union(enum) { + bool, + @"struct": []const u8, + number: NumberType, + vector: VectorType, + matrix: MatrixType, + atomic: AtomicType, + array: ArrayType, + }; +}; + +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 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 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, + }; +}; diff --git a/libs/dusk/src/Parser.zig b/libs/dusk/src/Parser.zig index ca718c91..2131e343 100644 --- a/libs/dusk/src/Parser.zig +++ b/libs/dusk/src/Parser.zig @@ -12,7 +12,7 @@ const Parser = @This(); allocator: std.mem.Allocator, source: [:0]const u8, tok_i: Ast.Index, -tokens: Ast.TokenList.Slice, +tokens: std.MultiArrayList(Token), nodes: std.MultiArrayList(Ast.Node), extra: std.ArrayListUnmanaged(Ast.Index), scratch: std.ArrayListUnmanaged(Ast.Index), @@ -20,6 +20,7 @@ errors: std.ArrayListUnmanaged(ErrorMsg), extensions: Extension.Array, pub fn deinit(p: *Parser) void { + p.tokens.deinit(p.allocator); p.nodes.deinit(p.allocator); p.extra.deinit(p.allocator); p.scratch.deinit(p.allocator); @@ -27,10 +28,10 @@ pub fn deinit(p: *Parser) void { p.errors.deinit(p.allocator); } -pub fn parseRoot(p: *Parser) !void { +pub fn translationUnit(p: *Parser) !?Ast.Index { const root = try p.addNode(.{ .tag = .span, .main_token = undefined }); - while (try p.globalDirective()) |ext| { + while (try p.globalDirectiveRecoverable()) |ext| { p.extensions.set(ext, true); } @@ -40,12 +41,24 @@ pub fn parseRoot(p: *Parser) !void { } if (p.errors.items.len > 0) { - return error.Parsing; + return null; } try p.extra.appendSlice(p.allocator, p.scratch.items); p.nodes.items(.lhs)[root] = @intCast(Ast.Index, p.extra.items.len - p.scratch.items.len); p.nodes.items(.rhs)[root] = @intCast(Ast.Index, p.extra.items.len); + + return root; +} + +pub fn globalDirectiveRecoverable(p: *Parser) !?Extension { + return p.globalDirective() catch |err| switch (err) { + error.Parsing => { + p.findNextGlobalDirective(); + return null; + }, + else => return err, + }; } pub fn globalDirective(p: *Parser) !?Extension { @@ -61,7 +74,7 @@ pub fn globalDirective(p: *Parser) !?Extension { pub fn expectGlobalDeclRecoverable(p: *Parser) !?Ast.Index { return p.expectGlobalDecl() catch |err| switch (err) { error.Parsing => { - p.findNextGlobal(); + p.findNextGlobalDecl(); return null; }, else => return err, @@ -1056,13 +1069,29 @@ pub fn typeSpecifier(p: *Parser) !?Ast.Index { } pub fn typeSpecifierWithoutIdent(p: *Parser) !?Ast.Index { - if (p.isVectorPrefix() or p.isMatrixPrefix()) { + if (p.isVectorPrefix()) { const main_token = p.advanceToken(); + _ = try p.expectToken(.less_than); const elem_type = try p.expectTypeSpecifier(); _ = try p.expectToken(.greater_than); + return try p.addNode(.{ - .tag = if (p.isVectorPrefix()) .vector_type else .matrix_type, + .tag = .vector_type, + .main_token = main_token, + .lhs = elem_type, + }); + } + + if (p.isMatrixPrefix()) { + const main_token = p.advanceToken(); + + _ = try p.expectToken(.less_than); + const elem_type = try p.expectTypeSpecifier(); + _ = try p.expectToken(.greater_than); + + return try p.addNode(.{ + .tag = .matrix_type, .main_token = main_token, .lhs = elem_type, }); @@ -1074,10 +1103,13 @@ pub fn typeSpecifierWithoutIdent(p: *Parser) !?Ast.Index { .k_u32, .k_f32, .k_f16, - .k_bool, => { _ = p.advanceToken(); - return try p.addNode(.{ .tag = .scalar_type, .main_token = main_token }); + return try p.addNode(.{ .tag = .number_type, .main_token = main_token }); + }, + .k_bool => { + _ = p.advanceToken(); + return try p.addNode(.{ .tag = .bool_type, .main_token = main_token }); }, .k_sampler, .k_comparison_sampler => { _ = p.advanceToken(); @@ -1328,7 +1360,8 @@ pub fn callExpr(p: *Parser) !?Ast.Index { const type_node = try p.typeSpecifierWithoutIdent() orelse return null; const tag = p.nodes.items(.tag)[type_node]; switch (tag) { - .scalar_type, + .bool_type, + .number_type, .vector_type, .matrix_type, .array_type, @@ -1730,7 +1763,20 @@ pub fn componentOrSwizzleSpecifier(p: *Parser, prefix: Ast.Index) !Ast.Index { } } -fn findNextGlobal(p: *Parser) void { +fn findNextGlobalDirective(p: *Parser) void { + while (true) { + switch (p.peekToken(.tag, 0)) { + .k_enable, .k_require, .eof => return, + .semicolon => { + _ = p.advanceToken(); + return; + }, + else => _ = p.advanceToken(), + } + } +} + +fn findNextGlobalDecl(p: *Parser) void { var level: Ast.Index = 0; while (true) { switch (p.peekToken(.tag, 0)) { diff --git a/libs/dusk/src/Token.zig b/libs/dusk/src/Token.zig index e563b585..16019334 100644 --- a/libs/dusk/src/Token.zig +++ b/libs/dusk/src/Token.zig @@ -214,6 +214,8 @@ pub const Tag = enum { k_override, /// 'ptr' k_ptr, + /// 'require' + k_require, /// 'return' k_return, /// 'sampler' @@ -363,6 +365,7 @@ pub const Tag = enum { .k_mat4x4 => "mat4x4", .k_override => "override", .k_ptr => "ptr", + .k_require => "require", .k_return => "return", .k_sampler => "sampler", .k_comparison_sampler => "sampler_comparison", @@ -433,6 +436,7 @@ pub const keywords = std.ComptimeStringMap(Tag, .{ .{ "mat4x4", .k_mat4x4 }, .{ "override", .k_override }, .{ "ptr", .k_ptr }, + .{ "require", .k_require }, .{ "return", .k_return }, .{ "sampler", .k_sampler }, .{ "sampler_comparison", .k_comparison_sampler }, diff --git a/libs/dusk/src/main.zig b/libs/dusk/src/main.zig index f9153a80..c883369f 100644 --- a/libs/dusk/src/main.zig +++ b/libs/dusk/src/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); pub const Ast = @import("Ast.zig"); -pub const Analyse = @import("Analyse.zig"); +pub const IR = @import("IR.zig"); pub const Parser = @import("Parser.zig"); pub const Token = @import("Token.zig"); pub const Tokenizer = @import("Tokenizer.zig"); diff --git a/libs/dusk/test/main.zig b/libs/dusk/test/main.zig index e1bafaf8..724abafc 100644 --- a/libs/dusk/test/main.zig +++ b/libs/dusk/test/main.zig @@ -86,17 +86,19 @@ fn printCode(writer: anytype, term: std.debug.TTY.Config, source: []const u8, lo try writer.writeByte('\n'); } -fn expectTree(source: [:0]const u8) !dusk.Ast { +fn expectIR(source: [:0]const u8) !dusk.IR { var res = try dusk.Ast.parse(allocator, source); switch (res) { .tree => |*tree| { - errdefer tree.deinit(allocator); - if (try tree.analyse(allocator)) |errors| { - try printErrors(errors, source, null); - allocator.free(errors); - return error.Analysing; + defer tree.deinit(allocator); + switch (try dusk.IR.generate(allocator, tree)) { + .ir => |ir| return ir, + .errors => |err_msgs| { + try printErrors(err_msgs, source, null); + allocator.free(err_msgs); + return error.ExpectedIR; + }, } - return tree.*; }, .errors => |err_msgs| { try printErrors(err_msgs, source, null); @@ -107,20 +109,27 @@ fn expectTree(source: [:0]const u8) !dusk.Ast { } fn expectError(source: [:0]const u8, err: dusk.ErrorMsg) !void { - var res = try dusk.Ast.parse(allocator, source); + var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 12 }){}; + const all = gpa.allocator(); + defer _ = gpa.deinit(); + var res = try dusk.Ast.parse(all, source); const err_list = switch (res) { .tree => |*tree| blk: { - defer tree.deinit(allocator); - if (try tree.analyse(allocator)) |errors| { - break :blk errors; + defer tree.deinit(all); + switch (try dusk.IR.generate(all, tree)) { + .ir => |*ir| { + ir.deinit(); + return error.ExpectedError; + }, + .errors => |err_msgs| break :blk err_msgs, } return error.ExpectedError; }, .errors => |err_msgs| err_msgs, }; defer { - for (err_list) |*err_msg| err_msg.deinit(allocator); - allocator.free(err_list); + for (err_list) |*err_msg| err_msg.deinit(all); + all.free(err_list); } { errdefer { @@ -162,65 +171,65 @@ fn expectError(source: [:0]const u8, err: dusk.ErrorMsg) !void { test "empty" { const source = ""; - var tree = try expectTree(source); - defer tree.deinit(allocator); + var ir = try expectIR(source); + defer ir.deinit(); } test "boids" { const source = @embedFile("boids.wgsl"); - var tree = try expectTree(source); - defer tree.deinit(allocator); + var ir = try expectIR(source); + defer ir.deinit(); } test "gkurve" { if (true) return error.SkipZigTest; const source = @embedFile("gkurve.wgsl"); - var tree = try expectTree(source); - defer tree.deinit(allocator); + var ir = try expectIR(source); + defer ir.deinit(); } test "variable & expressions" { - const source = "var expr = 1 + 5 + 2 * 3 > 6 >> 7;"; + // const source = "var expr = 1 + 5 + 2 * 3 > 6 >> 7;"; - var tree = try expectTree(source); - defer tree.deinit(allocator); + // var ir = try expectIR(source); + // defer ir.deinit(); - const root_node = 0; - try expect(tree.nodeLHS(root_node) + 1 == tree.nodeRHS(root_node)); + // const root_node = 0; + // try expect(ir.nodeLHS(root_node) + 1 == ir.nodeRHS(root_node)); - const variable = tree.spanToList(root_node)[0]; - const variable_name = tree.tokenLoc(tree.extraData(dusk.Ast.Node.GlobalVarDecl, tree.nodeLHS(variable)).name); - try expect(std.mem.eql(u8, "expr", variable_name.slice(source))); - try expect(tree.nodeTag(variable) == .global_variable); - try expect(tree.tokenTag(tree.nodeToken(variable)) == .k_var); + // const variable = ir.spanToList(root_node)[0]; + // const variable_name = ir.tokenLoc(ir.extraData(dusk.Ast.Node.GlobalVarDecl, ir.nodeLHS(variable)).name); + // try expect(std.mem.eql(u8, "expr", variable_name.slice(source))); + // try expect(ir.nodeTag(variable) == .global_variable); + // try expect(ir.tokenTag(ir.nodeToken(variable)) == .k_var); - const expr = tree.nodeRHS(variable); - try expect(tree.nodeTag(expr) == .greater); + // const expr = ir.nodeRHS(variable); + // try expect(ir.nodeTag(expr) == .greater); - const @"1 + 5 + 2 * 3" = tree.nodeLHS(expr); - try expect(tree.nodeTag(@"1 + 5 + 2 * 3") == .add); + // const @"1 + 5 + 2 * 3" = ir.nodeLHS(expr); + // try expect(ir.nodeTag(@"1 + 5 + 2 * 3") == .add); - const @"1 + 5" = tree.nodeLHS(@"1 + 5 + 2 * 3"); - try expect(tree.nodeTag(@"1 + 5") == .add); + // const @"1 + 5" = ir.nodeLHS(@"1 + 5 + 2 * 3"); + // try expect(ir.nodeTag(@"1 + 5") == .add); - const @"1" = tree.nodeLHS(@"1 + 5"); - try expect(tree.nodeTag(@"1") == .number_literal); + // const @"1" = ir.nodeLHS(@"1 + 5"); + // try expect(ir.nodeTag(@"1") == .number_literal); - const @"5" = tree.nodeRHS(@"1 + 5"); - try expect(tree.nodeTag(@"5") == .number_literal); + // const @"5" = ir.nodeRHS(@"1 + 5"); + // try expect(ir.nodeTag(@"5") == .number_literal); - const @"2 * 3" = tree.nodeRHS(@"1 + 5 + 2 * 3"); - try expect(tree.nodeTag(@"2 * 3") == .mul); + // const @"2 * 3" = ir.nodeRHS(@"1 + 5 + 2 * 3"); + // try expect(ir.nodeTag(@"2 * 3") == .mul); - const @"6 >> 7" = tree.nodeRHS(expr); - try expect(tree.nodeTag(@"6 >> 7") == .shift_right); + // const @"6 >> 7" = ir.nodeRHS(expr); + // try expect(ir.nodeTag(@"6 >> 7") == .shift_right); - const @"6" = tree.nodeLHS(@"6 >> 7"); - try expect(tree.nodeTag(@"6") == .number_literal); + // const @"6" = ir.nodeLHS(@"6 >> 7"); + // try expect(ir.nodeTag(@"6") == .number_literal); - const @"7" = tree.nodeRHS(@"6 >> 7"); - try expect(tree.nodeTag(@"7") == .number_literal); + // const @"7" = ir.nodeRHS(@"6 >> 7"); + // try expect(ir.nodeTag(@"7") == .number_literal); } test "analyser errors" {