diff --git a/generator/main.zig b/generator/main.zig index fb300af..d42699b 100644 --- a/generator/main.zig +++ b/generator/main.zig @@ -9,14 +9,14 @@ pub fn main() !void { return; } - const file = try std.fs.cwd().openFileC(std.os.argv[1], .{}); + const file = try std.fs.cwd().openFileZ(std.os.argv[1], .{}); defer file.close(); - const size = try file.seekableStream().stream.getEndPos(); + const size = try file.seekableStream().getEndPos(); const source = try std.heap.page_allocator.alloc(u8, size); defer std.heap.page_allocator.free(source); - _ = try file.inStream().stream.read(source); + _ = try file.inStream().read(source); const spec = try xml.parse(std.heap.page_allocator, source); defer spec.deinit(); @@ -27,7 +27,7 @@ pub fn main() !void { const stdout_file = std.io.getStdOut(); var stdout = stdout_file.outStream(); - try vk_render(&stdout.stream, registry); + try vk_render(stdout, registry); } test "main" { diff --git a/generator/registry.zig b/generator/registry.zig index 9694ccb..26e01df 100644 --- a/generator/registry.zig +++ b/generator/registry.zig @@ -56,7 +56,7 @@ pub const Registry = struct { return registry; } - fn deinit(self: Registry) void { + pub fn deinit(self: Registry) void { self.declarations_by_name.deinit(); // Copy to stack so that the arena doesn't destroy itself @@ -212,7 +212,7 @@ pub const TypeInfo = struct { // Read the sizes of each pointer if (elem.getAttribute("len")) |lens| { - var len_it = std.mem.separate(lens, ","); + var len_it = std.mem.split(lens, ","); for (type_info.pointers) |*ptr, i| { ptr.size = if (len_it.next()) |len| lenToPointerSize(len) else .One; } @@ -307,7 +307,7 @@ pub const TypeInfo = struct { // while the others are in the `post`. // Check the outer pointers - var const_it = std.mem.separate(post, "*"); + var const_it = std.mem.split(post, "*"); var i: usize = self.pointers.len; while (i > 0) { i -= 1; @@ -344,7 +344,11 @@ pub const TypeInfo = struct { } } - try output(context, self.name); + if (self.name) |name| { + try output(context, self.name); + } else { + try output(context, "@Type(.Opaque)"); + } } fn lenToPointerSize(len: []const u8) PointerSize { @@ -463,7 +467,7 @@ pub const CommandInfo = struct { const ncodes = 1 + count(text, ','); const codes = allocator.alloc([]const u8, ncodes) catch unreachable; - var it = mem.separate(text, ","); + var it = mem.split(text, ","); for (codes) |*code, i| { code.* = it.next().?; @@ -650,7 +654,18 @@ fn processFuncPointerType(registry: *Registry, ty: *xml.Element) void { } fn processBaseType(registry: *Registry, ty: *xml.Element) void { + const special_names = [_][]const u8{ // handled in render.zig + "ANativeWindow", + "AHardwareBuffer", + "CAMetalLayer" + }; + const name = ty.getCharData("name").?; + + for (special_names) |special| { + if (mem.eql(u8, name, special)) return; + } + const type_info = TypeInfo.fromXml(®istry.arena.allocator, ty); registry.addDefinition(name, .{.BaseType = type_info}); } diff --git a/generator/render.zig b/generator/render.zig index c86d9c4..e10f83d 100644 --- a/generator/render.zig +++ b/generator/render.zig @@ -13,12 +13,12 @@ const ForeignType = struct { }; const foreign_types = [_]ForeignType{ - .{.name = "Display", .expr = "@OpaqueType()"}, + .{.name = "Display", .expr = "Type(.Opaque)"}, .{.name = "VisualID", .expr = @typeName(c_uint)}, .{.name = "Window", .expr = @typeName(c_ulong)}, .{.name = "RROutput", .expr = @typeName(c_ulong)}, - .{.name = "wl_display", .expr = "@OpaqueType()"}, - .{.name = "wl_surface", .expr = "@OpaqueType()"}, + .{.name = "wl_display", .expr = "@Type(.Opaque)"}, + .{.name = "wl_surface", .expr = "@Type(.Opaque)"}, .{.name = "HINSTANCE", .expr = "std.os.HINSTANCE"}, .{.name = "HWND", .expr = "*@OpaqueType()"}, .{.name = "HMONITOR", .expr = "*OpaqueType()"}, @@ -32,9 +32,9 @@ const foreign_types = [_]ForeignType{ .{.name = "zx_handle_t", .expr = @typeName(u32)}, .{.name = "GgpStreamDescriptor", .expr = @typeName(u32)}, // TODO: Remove GGP-related code .{.name = "GgpFrameToken", .expr = @typeName(u32)}, - .{.name = "ANativeWindow", .expr = "@OpaqueType()"}, - .{.name = "AHardwareBuffer", .expr = "@OpaqueType()"}, - .{.name = "CAMetalLayer", .expr = "@OpaqueType()"}, + .{.name = "ANativeWindow", .expr = "@Type(.Opaque)"}, + .{.name = "AHardwareBuffer", .expr = "@Type(.Opaque)"}, + .{.name = "CAMetalLayer", .expr = "@Type(.Opaque)"}, }; const foreign_types_namespace = "foreign"; @@ -59,10 +59,10 @@ const builtin_types = [_]BuiltinType{ }; pub fn render(out: var, registry: *Registry) !void { - try out.write("const std = @import(\"std\");\n\n"); + try out.writeAll("const std = @import(\"std\");\n\n"); try renderApiConstants(out, registry); try renderForeignTypes(out); - try out.write("\n"); + try out.writeAll("\n"); try renderDeclarations(out, registry); try renderTest(out); } @@ -140,14 +140,14 @@ fn writeIdentifier(out: var, name: []const u8) !void { if (!isValidZigIdentifier(name) or isZigReservedIdentifier(name) or std.zig.Token.getKeyword(name) != null) { try out.print("@\"{}\"", .{name}); } else { - try out.write(name); + try out.writeAll(name); } } fn writeConstAssignmemt(out: var, name: []const u8) !void { - try out.write("pub const "); + try out.writeAll("pub const "); try writeIdentifier(out, name); - try out.write(" = "); + try out.writeAll(" = "); } fn eqlIgnoreCase(lhs: []const u8, rhs: []const u8) bool { @@ -173,13 +173,13 @@ fn renderTypeInfo(out: var, registry: *Registry, type_info: reg.TypeInfo) !void // Apparently Vulkan optional-ness information is not correct, so every pointer // is considered optional switch (ptr.size) { - .One => try out.write("?*"), - .Many => try out.write("?[*]"), - .ZeroTerminated => try out.write("?[*:0]") + .One => try out.writeAll("?*"), + .Many => try out.writeAll("?[*]"), + .ZeroTerminated => try out.writeAll("?[*:0]") } if (ptr.is_const) { - try out.write("const "); + try out.writeAll("const "); } } @@ -194,7 +194,7 @@ fn renderTypeInfo(out: var, registry: *Registry, type_info: reg.TypeInfo) !void // Some types can be mapped directly to a built-in type for (builtin_types) |bty| { if (mem.eql(u8, type_info.name, bty.c_name)) { - try out.write(bty.zig_name); + try out.writeAll(bty.zig_name); return; } } @@ -203,9 +203,9 @@ fn renderTypeInfo(out: var, registry: *Registry, type_info: reg.TypeInfo) !void // If its not a pointer, its a return type, so `void` should be emitted. if (mem.eql(u8, type_info.name, "void")) { if (type_info.pointers.len > 0) { - try out.write("c_void"); + try out.writeAll("c_void"); } else { - try out.write("void"); + try out.writeAll("void"); } return; @@ -219,60 +219,61 @@ fn renderTypeInfo(out: var, registry: *Registry, type_info: reg.TypeInfo) !void try writeIdentifier(out, trimNamespace(type_info.name)); } -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 - +fn renderApiConstantExpr(out: var, constant_expr: []const u8) !void { // omit enclosing parenthesis - const expr = if (constexpr[0] == '(' and constexpr[constexpr.len - 1] == ')') - constexpr[1 .. constexpr.len - 1] + const expr = if (constant_expr[0] == '(' and constant_expr[constant_expr.len - 1] == ')') + constant_expr[1 .. constant_expr.len - 1] else - constexpr; + constant_expr; - 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; + var i: usize = 0; + while (i < expr.len) { + switch (expr[i]) { + '(', ')', '~' => try out.writeByte(expr[i]), + '-' => try out.writeAll(" - "), + 'a'...'z', 'A'...'Z', '_' => { + var j = i; + while (j < expr.len) : (j += 1) { + switch (expr[j]) { + 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, + else => break + } } - }, - .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}); + try writeIdentifier(out, trimNamespace(expr[i .. j])); + i = j; + continue; + }, + '0'...'9' => { + var j = i; + while (j < expr.len) : (j += 1) { + switch (expr[j]) { + '0'...'9', '.' => {}, + else => break + } + } + + if (mem.startsWith(u8, expr[j..], "f")) { + try out.print("@as(f32, {})", .{expr[i .. j]}); + j += "f".len; + } else if (mem.startsWith(u8, expr[j..], "ULL")) { + try out.print("@as(u32, {})", .{expr[i .. j]}); + j += "ULL".len; + } else if (mem.startsWith(u8, expr[j..], "U")) { + try out.print("@as(u64, {})", .{expr[i .. j]}); + j += "U".len; } else { - return error.InvalidIntSuffix; + try out.writeAll(expr[i .. j]); } + + i = j; + continue; }, - .Eof => return, - else => return error.UnexpectedToken + ' ' => {}, + else => return error.InvalidConstantExpr } + + i += 1; } } @@ -281,23 +282,23 @@ fn renderApiConstants(out: var, registry: *Registry) !void { while (it.next()) |constant| { try writeConstAssignmemt(out, trimNamespace(constant.name)); try renderApiConstantExpr(out, constant.expr); - try out.write(";\n"); + try out.writeAll(";\n"); } - try out.write("\n"); + try out.writeAll("\n"); } fn renderForeignTypes(out: var) !void { try writeConstAssignmemt(out, foreign_types_namespace); - try out.write("struct {\n"); + try out.writeAll("struct {\n"); for (foreign_types) |fty| { - try out.write(base_indent); + try out.writeAll(base_indent); try writeConstAssignmemt(out, fty.name); try out.print("{};\n", .{fty.expr}); } - try out.write("};\n"); + try out.writeAll("};\n"); } fn renderDeclarations(out: var, registry: *Registry) !void { @@ -314,7 +315,7 @@ fn renderDeclarations(out: var, registry: *Registry) !void { .BaseType => |type_info| { try writeConstAssignmemt(out, trimNamespace(decl.name)); try renderTypeInfo(out, registry, type_info); - try out.write(";\n\n"); + try out.writeAll(";\n\n"); }, else => {} } @@ -334,14 +335,14 @@ fn renderEnum(out: var, name: []const u8, enum_info: *reg.EnumInfo) !void { const trimmed_name = trimNamespace(name); try writeConstAssignmemt(out, trimmed_name); - try out.write("extern enum {\n"); + try out.writeAll("extern enum {\n"); // Calculate the length of the enum namespace, by iterating through the segments // of the variant (in screaming snake case) and comparing it to the name of the enum, // until the two differ. var prefix_len: usize = 0; var snake_prefix_len: usize = 0; - var segment_it = mem.separate(enum_info.variants.at(0).name, "_"); + var segment_it = mem.split(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; @@ -355,7 +356,7 @@ fn renderEnum(out: var, name: []const u8, enum_info: *reg.EnumInfo) !void { while (it.next()) |variant| { if (variant.value == .Alias) continue; // Skip aliases - try out.write(base_indent); + try out.writeAll(base_indent); try writeIdentifier(out, variant.name[snake_prefix_len ..]); switch (variant.value) { @@ -366,7 +367,7 @@ fn renderEnum(out: var, name: []const u8, enum_info: *reg.EnumInfo) !void { } } - try out.write("};\n\n"); + try out.writeAll("};\n\n"); } fn renderAlias(out: var, registry: *Registry, name: []const u8, alias: []const u8) !void { @@ -383,52 +384,52 @@ fn renderAlias(out: var, registry: *Registry, name: []const u8, alias: []const u try writeConstAssignmemt(out, trimNamespace(name)); try writeIdentifier(out, trimNamespace(alias)); - try out.write(";\n\n"); + try out.writeAll(";\n\n"); } fn renderFnPtr(out: var, registry: *Registry, name: []const u8, info: *reg.CommandInfo) !void { try writeConstAssignmemt(out, trimNamespace(name)); - try out.write("extern fn("); + try out.writeAll("?fn("); if (info.parameters.count() > 0) { - try out.write("\n"); + try out.writeAll("\n"); var it = info.parameters.iterator(0); while (it.next()) |param| { - try out.write(base_indent); + try out.writeAll(base_indent); try writeIdentifier(out, param.name); - try out.write(": "); + try out.writeAll(": "); try renderTypeInfo(out, registry, param.type_info); - try out.write(",\n"); + try out.writeAll(",\n"); } } - try out.write(") "); + try out.writeAll(") callconv(.C) "); try renderTypeInfo(out, registry, info.return_type_info); - try out.write(";\n\n"); + try out.writeAll(";\n\n"); } fn renderContainer(out: var, registry: *Registry, kind: enum{Struct, Union}, name: []const u8, info: *reg.ContainerInfo) !void { try writeConstAssignmemt(out, trimNamespace(name)); switch (kind) { - .Struct => try out.write("extern struct {\n"), - .Union => try out.write("extern union {\n") + .Struct => try out.writeAll("extern struct {\n"), + .Union => try out.writeAll("extern union {\n") } var it = info.members.iterator(0); while (it.next()) |member| { - try out.write(base_indent); + try out.writeAll(base_indent); try writeIdentifier(out, member.name); - try out.write(": "); + try out.writeAll(": "); try renderTypeInfo(out, registry, member.type_info); - try out.write(",\n"); + try out.writeAll(",\n"); } - try out.write("};\n\n"); + try out.writeAll("};\n\n"); } fn renderTest(out: var) !void { - try out.write( + try out.writeAll( \\test "Semantic analysis" { \\ std.meta.refAllDecls(@This()); \\} diff --git a/generator/xml.zig b/generator/xml.zig index 1a768db..9debba6 100644 --- a/generator/xml.zig +++ b/generator/xml.zig @@ -32,7 +32,7 @@ pub const Element = struct { }; } - fn getAttribute(self: *Element, attrib_name: []const u8) ?[]const u8 { + pub fn getAttribute(self: *Element, attrib_name: []const u8) ?[]const u8 { var it = self.attributes.iterator(0); while (it.next()) |child| { if (mem.eql(u8, child.*.name, attrib_name)) { @@ -43,7 +43,7 @@ pub const Element = struct { return null; } - fn getCharData(self: *Element, child_tag: []const u8) ?[]const u8 { + pub fn getCharData(self: *Element, child_tag: []const u8) ?[]const u8 { const child = self.findChildByTag(child_tag) orelse return null; if (child.children.count() != 1) { return null; @@ -55,11 +55,11 @@ pub const Element = struct { }; } - fn findChildByTag(self: *Element, tag: []const u8) ?*Element { + pub fn findChildByTag(self: *Element, tag: []const u8) ?*Element { return self.findChildrenByTag(tag).next(); } - fn findChildrenByTag(self: *Element, tag: []const u8) FindChildrenByTagIterator { + pub fn findChildrenByTag(self: *Element, tag: []const u8) FindChildrenByTagIterator { return .{ .inner = self.children.iterator(0), .tag = tag @@ -70,7 +70,7 @@ pub const Element = struct { inner: ContentList.Iterator, tag: []const u8, - fn next(self: *FindChildrenByTagIterator) ?*Element { + pub fn next(self: *FindChildrenByTagIterator) ?*Element { while (self.inner.next()) |child| { if (child.* != .Element or !mem.eql(u8, child.*.Element.tag, self.tag)) { continue; @@ -424,15 +424,19 @@ fn tryParseElement(ctx: *ParseContext, alloc: *Allocator) !?*Element { } test "tryParseElement" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + var alloc = &arena.allocator; + { var ctx = ParseContext.init("<= a='b'/>"); - testing.expectEqual(@as(?*Element, null), try tryParseElement(&ctx, std.debug.global_allocator)); + testing.expectEqual(@as(?*Element, null), try tryParseElement(&ctx, alloc)); testing.expectEqual(@as(?u8, '<'), ctx.peek()); } { var ctx = ParseContext.init(""); - const elem = try tryParseElement(&ctx, std.debug.global_allocator); + const elem = try tryParseElement(&ctx, alloc); testing.expectEqualSlices(u8, elem.?.tag, "python"); const size_attr = elem.?.attributes.at(0).*; @@ -446,14 +450,14 @@ test "tryParseElement" { { var ctx = ParseContext.init("test"); - const elem = try tryParseElement(&ctx, std.debug.global_allocator); + const elem = try tryParseElement(&ctx, alloc); testing.expectEqualSlices(u8, elem.?.tag, "python"); testing.expectEqualSlices(u8, elem.?.children.at(0).CharData, "test"); } { var ctx = ParseContext.init("bdf"); - const elem = try tryParseElement(&ctx, std.debug.global_allocator); + const elem = try tryParseElement(&ctx, alloc); testing.expectEqualSlices(u8, elem.?.tag, "a"); testing.expectEqualSlices(u8, elem.?.children.at(0).CharData, "b"); testing.expectEqualSlices(u8, elem.?.children.at(1).Element.tag, "c"); @@ -508,15 +512,19 @@ fn tryParseProlog(ctx: *ParseContext, alloc: *Allocator) !?*XmlDecl { } test "tryParseProlog" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + var alloc = &arena.allocator; + { var ctx = ParseContext.init(""); - testing.expectEqual(@as(?*XmlDecl, null), try tryParseProlog(&ctx, std.debug.global_allocator)); + testing.expectEqual(@as(?*XmlDecl, null), try tryParseProlog(&ctx, alloc)); testing.expectEqual(@as(?u8, '<'), ctx.peek()); } { var ctx = ParseContext.init(""); - const decl = try tryParseProlog(&ctx, std.debug.global_allocator); + const decl = try tryParseProlog(&ctx, alloc); testing.expectEqualSlices(u8, "aa", decl.?.version); testing.expectEqual(@as(?[]const u8, null), decl.?.encoding); testing.expectEqual(@as(?bool, null), decl.?.standalone); @@ -524,7 +532,7 @@ test "tryParseProlog" { { var ctx = ParseContext.init(""); - const decl = try tryParseProlog(&ctx, std.debug.global_allocator); + const decl = try tryParseProlog(&ctx, alloc); testing.expectEqualSlices(u8, "aa", decl.?.version); testing.expectEqualSlices(u8, "bbb", decl.?.encoding.?); testing.expectEqual(@as(?bool, true), decl.?.standalone.?); @@ -584,10 +592,14 @@ fn dupeAndUnescape(alloc: *Allocator, text: []const u8) ![]const u8 { } test "dupeAndUnescape" { - testing.expectEqualSlices(u8, "test", try dupeAndUnescape(std.debug.global_allocator, "test")); - testing.expectEqualSlices(u8, "ad\"e'f<", try dupeAndUnescape(std.debug.global_allocator, "a<b&c>d"e'f<")); - testing.expectError(error.InvalidEntity, dupeAndUnescape(std.debug.global_allocator, "python&")); - testing.expectError(error.InvalidEntity, dupeAndUnescape(std.debug.global_allocator, "python&&")); - testing.expectError(error.InvalidEntity, dupeAndUnescape(std.debug.global_allocator, "python&test;")); - testing.expectError(error.InvalidEntity, dupeAndUnescape(std.debug.global_allocator, "python&boa")); + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + var alloc = &arena.allocator; + + testing.expectEqualSlices(u8, "test", try dupeAndUnescape(alloc, "test")); + testing.expectEqualSlices(u8, "ad\"e'f<", try dupeAndUnescape(alloc, "a<b&c>d"e'f<")); + testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&")); + testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&&")); + testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&test;")); + testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&boa")); }