dusk: create IR instead simple analysing

This commit is contained in:
Ali Chraghi 2023-03-10 12:00:28 +03:30 committed by Stephen Gutekanst
parent 0a790a38f3
commit 51b0e8695f
8 changed files with 785 additions and 284 deletions

View file

@ -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);
}

View file

@ -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,

429
libs/dusk/src/AstGen.zig Normal file
View file

@ -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);
}

209
libs/dusk/src/IR.zig Normal file
View file

@ -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,
};
};

View file

@ -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)) {

View file

@ -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 },

View file

@ -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");

View file

@ -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" {