diff --git a/generator/main.zig b/generator/main.zig index 7ca9df0..fb300af 100644 --- a/generator/main.zig +++ b/generator/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const xml = @import("xml.zig"); const Registry = @import("registry.zig").Registry; +const vk_render = @import("render.zig").render; pub fn main() !void { if (std.os.argv.len <= 1) { @@ -22,8 +23,11 @@ pub fn main() !void { const registry = Registry.fromXml(std.heap.page_allocator, spec.root); defer registry.deinit(); + // registry.dump(); - registry.dump(); + const stdout_file = std.io.getStdOut(); + var stdout = stdout_file.outStream(); + try vk_render(&stdout.stream, registry); } test "main" { diff --git a/generator/registry.zig b/generator/registry.zig index aebd713..f6f4dd6 100644 --- a/generator/registry.zig +++ b/generator/registry.zig @@ -11,6 +11,7 @@ pub const Registry = struct { declarations: SegmentedList(Declaration, 0), declarations_by_name: StringHashMap(*Declaration), api_constants: SegmentedList(ApiConstant, 0), + tags: SegmentedList(TagInfo, 0), extensions: SegmentedList(ExtensionInfo, 0), fn init(allocator: *Allocator) !*Registry { @@ -25,6 +26,7 @@ pub const Registry = struct { .declarations = undefined, .declarations_by_name = StringHashMap(*Declaration).init(allocator), .api_constants = undefined, + .tags = undefined, .extensions = undefined }; @@ -33,6 +35,7 @@ pub const Registry = struct { registry.declarations = SegmentedList(Declaration, 0).init(®istry.arena.allocator); registry.api_constants = SegmentedList(ApiConstant, 0).init(®istry.arena.allocator); + registry.tags = SegmentedList(TagInfo, 0).init(®istry.arena.allocator); registry.extensions = SegmentedList(ExtensionInfo, 0).init(®istry.arena.allocator); return registry; @@ -47,6 +50,7 @@ pub const Registry = struct { processEnums(registry, root); processCommands(registry, root); processFeatures(registry, root); + processTags(registry, root); processExtensions(registry, root); return registry; @@ -77,6 +81,10 @@ pub const Registry = struct { self.api_constants.push(.{.name = name, .expr = expr}) catch unreachable; } + fn addTag(self: *Registry, name: []const u8, author: []const u8) void { + self.tags.push(.{.name = name, .author = author}) catch unreachable; + } + fn findDefinitionByName(self: *Registry, name: []const u8) ?*Definition { if (self.declarations_by_name.get(name)) |kv| { return &kv.value.definition; @@ -86,11 +94,13 @@ pub const Registry = struct { } fn dump(self: *Registry) void { + const indent = " " ** 4; + { std.debug.warn("Definitions:\n", .{}); var it = self.declarations.iterator(0); while (it.next()) |decl| { - std.debug.warn(" {} ({})\n", .{decl.name, std.meta.tagName(decl.definition)}); + std.debug.warn(indent ++ "{} ({})\n", .{decl.name, std.meta.tagName(decl.definition)}); } } @@ -98,7 +108,15 @@ pub const Registry = struct { std.debug.warn("API constants:\n", .{}); var it = self.api_constants.iterator(0); while (it.next()) |kv| { - std.debug.warn(" {} = {}\n", .{kv.name, kv.expr}); + std.debug.warn(indent ++ "{} = {}\n", .{kv.name, kv.expr}); + } + } + + { + std.debug.warn("Tags:\n", .{}); + var it = self.tags.iterator(0); + while (it.next()) |tag| { + std.debug.warn(indent ++ "{} ({})\n", .{tag.name, tag.author}); } } @@ -106,29 +124,34 @@ pub const Registry = struct { std.debug.warn("Extensions:\n", .{}); var it = self.extensions.iterator(0); while (it.next()) |ext| { - std.debug.warn(" {}: {}, version {}\n", .{ext.number, ext.name, ext.version}); + std.debug.warn(indent ++ "{}: {}, version {}\n", .{ext.number, ext.name, ext.version}); } } } }; -const ApiConstant = struct { +pub const ApiConstant = struct { name: []const u8, expr: []const u8 }; -const ExtensionInfo = struct { +pub const ExtensionInfo = struct { name: []const u8, number: u32, version: u32, }; -const Declaration = struct { +pub const TagInfo = struct { + name: []const u8, + author: []const u8 +}; + +pub const Declaration = struct { name: []const u8, definition: Definition }; -const Definition = union(enum) { +pub const Definition = union(enum) { Struct: StructInfo, Enum: EnumInfo, Bitmask: BitmaskInfo, @@ -139,16 +162,16 @@ const Definition = union(enum) { BaseType: TypeInfo }; -const HandleInfo = struct { +pub const HandleInfo = struct { dispatchable: bool }; -const BitmaskInfo = struct { +pub const BitmaskInfo = struct { bits_enum: ?[]const u8 }; // Type info of fields, function parameters, and return types. -const TypeInfo = struct { +pub const TypeInfo = struct { const PointerSize = enum { One, Many, // The length is given by some expression which cannot be expressed in Zig @@ -334,7 +357,7 @@ const TypeInfo = struct { } }; -const StructInfo = struct { +pub const StructInfo = struct { const Member = struct { name: []const u8, type_info: TypeInfo @@ -367,7 +390,7 @@ const StructInfo = struct { } }; -const CommandInfo = struct { +pub const CommandInfo = struct { const Parameter = struct { name: []const u8, type_info: TypeInfo @@ -453,9 +476,10 @@ const CommandInfo = struct { } }; -const EnumInfo = struct { +pub const EnumInfo = struct { const Value = union(enum) { Bitpos: u5, //log2(u32) + HexValue: i32, // Combined flags and such Value: i32, Alias: []const u8, }; @@ -474,7 +498,16 @@ const EnumInfo = struct { } fn addVariant(self: *EnumInfo, name: []const u8, value: Value) void { - const ptr = self.variants.push(.{.name = name, .value = value}) catch unreachable; + // Sometimes a variant is added multiple times with different 'require' parts of an extension + // so filter out any duplicates. + var it = self.variants.iterator(0); + while (it.next()) |variant| { + if (mem.eql(u8, variant.name, name)) { + return; + } + } + + _ = self.variants.push(.{.name = name, .value = value}) catch unreachable; } fn processVariantFromXml(self: *EnumInfo, variant: *xml.Element, ext_nr: ?u32) void { @@ -490,7 +523,11 @@ const EnumInfo = struct { // given by the `extnumber` field (in the case of a feature), or given in the parent // tag. In the latter case its passed via the `ext_nr` parameter. if (variant.getAttribute("value")) |value_str| { - break :blk Value{.Value = parseInt(i32, value_str) catch unreachable}; + if (mem.startsWith(u8, value_str, "0x")) { + break :blk Value{.HexValue = std.fmt.parseInt(i32, value_str[2..], 16) catch unreachable}; + } else { + break :blk Value{.Value = std.fmt.parseInt(i32, value_str, 10) catch unreachable}; + } } else if (variant.getAttribute("bitpos")) |bitpos_str| { break :blk Value{.Bitpos = std.fmt.parseInt(u5, bitpos_str, 10) catch unreachable}; } else if (variant.getAttribute("alias")) |alias| { @@ -666,6 +703,16 @@ fn processCommands(registry: *Registry, root: *xml.Element) void { } } +fn processTags(registry: *Registry, root: *xml.Element) void { + var tags = root.findChildByTag("tags").?; + var it = tags.findChildrenByTag("tag"); + while (it.next()) |tag| { + const name = tag.getAttribute("name").?; + const author = tag.getAttribute("author").?; + registry.addTag(name, author); + } +} + fn processExtensions(registry: *Registry, root: *xml.Element) void { var extensions = root.findChildByTag("extensions").?; var ext_it = extensions.findChildrenByTag("extension"); @@ -736,11 +783,3 @@ fn count(haystack: []const u8, needle: u8) usize { return total; } - -/// Parse an integer in either base-10 or base-16 when prefixed with '0x'. -fn parseInt(comptime T: type, source: []const u8) !T { - return if (mem.startsWith(u8, source, "0x")) - try std.fmt.parseInt(T, source[2..], 16) - else - try std.fmt.parseInt(T, source, 10); -} diff --git a/generator/render.zig b/generator/render.zig new file mode 100644 index 0000000..c375239 --- /dev/null +++ b/generator/render.zig @@ -0,0 +1,207 @@ +const std = @import("std"); +const ast = std.zig.ast; +const mem = std.mem; +const Allocator = mem.Allocator; +const reg = @import("registry.zig"); +const Registry = reg.Registry; + +const base_indent = " " ** 4; + +pub fn render(out: var, registry: *Registry) !void { + try renderApiConstants(out, registry); + try out.write("\n"); + try renderDeclarations(out, registry); + try renderTest(out); +} + +fn trimNamespace(name: []const u8) []const u8 { + if (mem.startsWith(u8, name, "VK_")) { + return name["VK_".len ..]; + } else if (mem.startsWith(u8, name, "vk")) { + return name["vk".len ..]; + } else if (mem.startsWith(u8, name, "Vk")) { + return name["Vk".len ..]; + } else { + unreachable; + } +} + +fn getAuthorTag(registry: *Registry, name: []const u8) ?[]const u8 { + var it = registry.tags.iterator(0); + while (it.next()) |tag| { + if (mem.endsWith(u8, name, tag.name)) { + return tag; + } + } + + return null; +} + +fn trimTag(registry: *Registry, name: []const u8) []const u8 { + const tag = getAuthorTag(name) orelse return name; + return mem.trimRight(u8, name[0 .. name.len - tag.name.len], "_"); +} + +fn isValidZigIdentifier(name: []const u8) bool { + for (name) |c, i| { + switch (c) { + '_', 'a'...'z', 'A'...'Z' => {}, + '0' ... '9' => if (i == 0) return false, + else => return false + } + } + + return true; +} + +fn writeIdentifier(out: var, name: []const u8) !void { + if (!isValidZigIdentifier(name) or std.zig.Token.getKeyword(name) != null) { + try out.print("@\"{}\"", .{name}); + } else { + try out.write(name); + } +} + +fn eqlIgnoreCase(lhs: []const u8, rhs: []const u8) bool { + if (lhs.len != rhs.len) { + return false; + } + + for (lhs) |c, i| { + if (std.ascii.toLower(c) != std.ascii.toLower(rhs[i])) { + return false; + } + } + + return true; +} + +fn renderApiConstantExpr(out: var, constexpr: []const u8) !void { + // There are only a few different kinds of tokens in the expressions, + // all of which can be tokenized by the Zig tokenizer. The only parts which cannot + // be parsed properly are 'f', 'U', and 'ULL' suffixes. + // Render the C expression by simply substituting those values + + // omit enclosing parenthesis + const expr = if (constexpr[0] == '(' and constexpr[constexpr.len - 1] == ')') + constexpr[1 .. constexpr.len - 1] + else + constexpr; + + var tokenizer = std.zig.Tokenizer.init(expr); + var peek_tok: ?std.zig.Token = null; + + while (true) { + const tok = peek_tok orelse tokenizer.next(); + const text = expr[tok.start .. tok.end]; + peek_tok = null; + + switch (tok.id) { + .LParen, .RParen, .Tilde => try out.write(text), + .Identifier => try writeIdentifier(out, trimNamespace(text)), + .Minus => try out.write(" - "), + .FloatLiteral => { + try out.print("@as(f32, {})", .{text}); + + // Float literal has to be followed by an 'f' identifier. + const suffix = tokenizer.next(); + const suffix_text = expr[suffix.start .. suffix.end]; + if (suffix.id != .Identifier or !mem.eql(u8, suffix_text, "f")) { + return error.ExpectedFloatSuffix; + } + }, + .IntegerLiteral => { + const suffix = tokenizer.next(); + const suffix_text = expr[suffix.start .. suffix.end]; + + if (suffix.id != .Identifier) { + // Only need to check here because any identifier following an integer + // that is not 'U' or 'ULL' is a syntax error. + peek_tok = suffix; + try out.write(text); + } else if (mem.eql(u8, suffix_text, "U")) { + try out.print("@as(u32, {})", .{text}); + } else if (mem.eql(u8, suffix_text, "ULL")) { + try out.print("@as(u64, {})", .{text}); + } else { + return error.InvalidIntSuffix; + } + }, + .Eof => return, + else => return error.UnexpectedToken + } + } +} + +fn renderApiConstants(out: var, registry: *Registry) !void { + var it = registry.api_constants.iterator(0); + while (it.next()) |constant| { + try out.write("const "); + try writeIdentifier(out, trimNamespace(constant.name)); + try out.write(" = "); + try renderApiConstantExpr(out, constant.expr); + try out.write(";\n"); + } +} + +fn renderDeclarations(out: var, registry: *Registry) !void { + var it = registry.declarations.iterator(0); + while (it.next()) |decl| { + if (decl.definition == .Command) continue; // handled seperately + + switch (decl.definition) { + .Enum => |*info| try renderEnum(out, registry, decl.name, info), + else => {} + } + } +} + +fn renderEnum(out: var, registry: *Registry, name: []const u8, enum_info: *reg.EnumInfo) !void { + if (enum_info.variants.count() == 0) { + return; // Skip empty declarations, which are unused bitflags + } + + const trimmed_name = trimNamespace(name); + + try out.write("const "); + try writeIdentifier(out, trimmed_name); + try out.write(" = extern enum {\n"); + + var prefix_len: usize = 0; + var snake_prefix_len: usize = 0; + var segment_it = mem.separate(enum_info.variants.at(0).name, "_"); + while (segment_it.next()) |segment| { + if (prefix_len + segment.len <= name.len and eqlIgnoreCase(segment, name[prefix_len .. prefix_len + segment.len])) { + prefix_len += segment.len; + snake_prefix_len += segment.len + 1; + } else { + break; + } + } + + var it = enum_info.variants.iterator(0); + while (it.next()) |variant| { + if (variant.value == .Alias) continue; // Skip aliases + + try out.write(base_indent); + try writeIdentifier(out, variant.name[snake_prefix_len ..]); + + switch (variant.value) { + .Value => |value| try out.print(" = {},\n", .{value}), + .HexValue => |value| try out.print(" = 0x{X},\n", .{value}), + .Bitpos => |value| try out.print(" = 1 << {},\n", .{value}), + .Alias => unreachable + } + } + + try out.write("};\n\n"); +} + +fn renderTest(out: var) !void { + try out.write( + \\test "Semantic analysis" { + \\ @import("std").meta.refAllDecls(@This()); + \\} + \\ + ); +} diff --git a/generator/xml.zig b/generator/xml.zig index 5cc3090..1a768db 100644 --- a/generator/xml.zig +++ b/generator/xml.zig @@ -323,7 +323,6 @@ fn parseEqAttrValue(ctx: *ParseContext, alloc: *Allocator) ![]const u8 { fn parseNameNoDupe(ctx: *ParseContext) ![]const u8 { // XML's spec on names is very long, so to make this easier // we just take any character that is not special and not whitespace - const begin = ctx.offset; while (ctx.peek()) |ch| {