forked from mirror/vulkan-zig
DeclarationResolver
This commit is contained in:
174
generator/generator.zig
Normal file
174
generator/generator.zig
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const reg = @import("registry.zig");
|
||||||
|
const xml = @import("xml.zig");
|
||||||
|
const parseXml = @import("registry/parse.zig").parseXml;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const FeatureLevel = reg.FeatureLevel;
|
||||||
|
|
||||||
|
fn cmpFeatureLevels(a: FeatureLevel, b: FeatureLevel) std.math.Order {
|
||||||
|
if (a.major > b.major) {
|
||||||
|
return .gt;
|
||||||
|
} if (a.major < b.major) {
|
||||||
|
return .lt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.minor > b.minor) {
|
||||||
|
return .gt;
|
||||||
|
} else if (a.minor < b.minor) {
|
||||||
|
return .lt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .eq;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeclarationResolver = struct {
|
||||||
|
const DeclarationSet = std.StringHashMap(void);
|
||||||
|
const EnumExtensionMap = std.StringHashMap(std.ArrayList(reg.Enum.Field));
|
||||||
|
|
||||||
|
allocator: *Allocator,
|
||||||
|
reg_arena: *Allocator,
|
||||||
|
registry: *reg.Registry,
|
||||||
|
declarations: DeclarationSet,
|
||||||
|
enum_extensions: EnumExtensionMap,
|
||||||
|
|
||||||
|
fn init(allocator: *Allocator, reg_arena: *Allocator, registry: *reg.Registry) DeclarationResolver {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.reg_arena = reg_arena,
|
||||||
|
.registry = registry,
|
||||||
|
.declarations = DeclarationSet.init(allocator),
|
||||||
|
.enum_extensions = EnumExtensionMap.init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: DeclarationResolver) void {
|
||||||
|
var it = self.enum_extensions.iterator();
|
||||||
|
while (it.next()) |kv| {
|
||||||
|
kv.value.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.enum_extensions.deinit();
|
||||||
|
self.declarations.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn putEnumExtension(self: *DeclarationResolver, enum_name: []const u8, field: reg.Enum.Field) !void {
|
||||||
|
const res = try self.enum_extensions.getOrPut(enum_name);
|
||||||
|
if (!res.found_existing) {
|
||||||
|
res.kv.value = std.ArrayList(reg.Enum.Field).init(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
try res.kv.value.append(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addRequire(self: *DeclarationResolver, req: reg.Require) !void {
|
||||||
|
for (req.types) |type_name| {
|
||||||
|
_ = try self.declarations.put(type_name, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (req.commands) |command| {
|
||||||
|
_ = try self.declarations.put(command, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (req.extends) |enum_ext| {
|
||||||
|
try self.putEnumExtension(enum_ext.extends, enum_ext.field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mergeEnumFields(self: *DeclarationResolver, name: []const u8, base_enum: *reg.Enum) !void {
|
||||||
|
// If there are no extensions for this enum, assume its valid.
|
||||||
|
const extensions = self.enum_extensions.get(name) orelse return;
|
||||||
|
|
||||||
|
// Todo: cache this in DeclarationResolver
|
||||||
|
var field_map = std.StringHashMap(reg.Enum.Value).init(self.allocator);
|
||||||
|
defer field_map.deinit();
|
||||||
|
|
||||||
|
for (base_enum.fields) |field| {
|
||||||
|
_ = try field_map.put(field.name, field.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume that if a field name clobbers, the value is the same
|
||||||
|
for (extensions.value.items) |field| {
|
||||||
|
_ = try field_map.put(field.name, field.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const new_fields = try self.reg_arena.alloc(reg.Enum.Field, field_map.count());
|
||||||
|
|
||||||
|
var it = field_map.iterator();
|
||||||
|
for (new_fields) |*field| {
|
||||||
|
const kv = it.next().?;
|
||||||
|
field.* = .{
|
||||||
|
.name = kv.key,
|
||||||
|
.value = kv.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existing base_enum.fields was allocatued by `self.reg_arena`, so
|
||||||
|
// it gets cleaned up whenever that is deinited.
|
||||||
|
base_enum.fields = new_fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(self: *DeclarationResolver) !void {
|
||||||
|
for (self.registry.features) |feature| {
|
||||||
|
for (feature.requires) |req| {
|
||||||
|
try self.addRequire(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.registry.extensions) |ext| {
|
||||||
|
for (ext.requires) |req| {
|
||||||
|
try self.addRequire(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all the enum fields.
|
||||||
|
// Assume that all keys of enum_extensions appear in `self.registry.decls`
|
||||||
|
for (self.registry.decls) |*decl| {
|
||||||
|
if (decl.decl_type == .enumeration) {
|
||||||
|
try self.mergeEnumFields(decl.name, &decl.decl_type.enumeration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap-remove all declarations that are not required.
|
||||||
|
// Some declarations may exist in `self.declarations` that do not exit in
|
||||||
|
// `self.registry.decls`, these are mostly macros and other stuff not parsed.
|
||||||
|
var i: usize = 0;
|
||||||
|
var count = self.registry.decls.len;
|
||||||
|
while (i < count) {
|
||||||
|
const decl = self.registry.decls[i];
|
||||||
|
if (self.declarations.contains(decl.name)) {
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
count -= 1;
|
||||||
|
self.registry.decls[i] = self.registry.decls[count];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.registry.decls = self.reg_arena.shrink(self.registry.decls, count);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Generator = struct {
|
||||||
|
gpa: *Allocator,
|
||||||
|
registry_arena: std.heap.ArenaAllocator,
|
||||||
|
registry: reg.Registry,
|
||||||
|
|
||||||
|
pub fn init(allocator: *Allocator, spec: *xml.Element) !Generator {
|
||||||
|
const result = try parseXml(allocator, spec);
|
||||||
|
return Generator{
|
||||||
|
.gpa = allocator,
|
||||||
|
.registry_arena = result.arena,
|
||||||
|
.registry = result.registry,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Generator) void {
|
||||||
|
self.registry_arena.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve registry.declarations according to registry.extensions and registry.features
|
||||||
|
pub fn resolveDeclarations(self: *Generator) !void {
|
||||||
|
var resolver = DeclarationResolver.init(self.gpa, &self.registry_arena.allocator, &self.registry);
|
||||||
|
defer resolver.deinit();
|
||||||
|
try resolver.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const xml = @import("xml.zig");
|
const xml = @import("xml.zig");
|
||||||
const parseXml = @import("registry/parse.zig").parseXml;
|
const parseXml = @import("registry/parse.zig").parseXml;
|
||||||
const registry = @import("registry.zig");
|
const reg = @import("registry.zig");
|
||||||
|
const vkgen = @import("generator.zig");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
fn renderType(type_info: registry.TypeInfo) void {
|
fn renderType(type_info: reg.TypeInfo) void {
|
||||||
switch (type_info) {
|
switch (type_info) {
|
||||||
.container => |c| {
|
.container => |c| {
|
||||||
if (c.is_union) {
|
if (c.is_union) {
|
||||||
@@ -45,12 +47,12 @@ fn renderType(type_info: registry.TypeInfo) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dumpRegistry(reg: registry.Registry) void {
|
fn dumpRegistry(registry: reg.Registry) void {
|
||||||
for (reg.tags) |tag| {
|
for (registry.tags) |tag| {
|
||||||
std.debug.warn("tag: name = {}, author = {}\n", .{tag.name, tag.author});
|
std.debug.warn("tag: name = {}, author = {}\n", .{tag.name, tag.author});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (reg.api_constants) |api_constant| {
|
for (registry.api_constants) |api_constant| {
|
||||||
std.debug.warn("constant: name = {}, ", .{api_constant.name});
|
std.debug.warn("constant: name = {}, ", .{api_constant.name});
|
||||||
switch (api_constant.value) {
|
switch (api_constant.value) {
|
||||||
.expr => |expr| std.debug.warn("expr = {}\n", .{expr}),
|
.expr => |expr| std.debug.warn("expr = {}\n", .{expr}),
|
||||||
@@ -58,35 +60,80 @@ fn dumpRegistry(reg: registry.Registry) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (reg.decls) |decl| {
|
for (registry.decls) |decl| {
|
||||||
std.debug.warn("decl: name = {}; ", .{decl.name});
|
std.debug.warn("decl: name = {}; ", .{decl.name});
|
||||||
renderType(decl.decl_type);
|
renderType(decl.decl_type);
|
||||||
std.debug.warn("\n", .{});
|
std.debug.warn("\n", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ProfilingAllocator = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
parent_allocator: *Allocator,
|
||||||
|
max_usage: usize,
|
||||||
|
current_usage: usize,
|
||||||
|
|
||||||
|
fn init(parent_allocator: *Allocator) ProfilingAllocator {
|
||||||
|
return ProfilingAllocator{
|
||||||
|
.allocator = Allocator{
|
||||||
|
.reallocFn = realloc,
|
||||||
|
.shrinkFn = shrink,
|
||||||
|
},
|
||||||
|
.parent_allocator = parent_allocator,
|
||||||
|
.max_usage = 0,
|
||||||
|
.current_usage = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
|
||||||
|
const self = @fieldParentPtr(ProfilingAllocator, "allocator", allocator);
|
||||||
|
self.current_usage = self.current_usage - old_mem.len + new_size;
|
||||||
|
if (self.current_usage > self.max_usage) {
|
||||||
|
self.max_usage = self.current_usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.parent_allocator.reallocFn(self.parent_allocator, old_mem, old_align, new_size, new_align);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
|
||||||
|
const self = @fieldParentPtr(ProfilingAllocator, "allocator", allocator);
|
||||||
|
return self.parent_allocator.shrinkFn(self.parent_allocator, old_mem, old_align, new_size, new_align);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
// if (std.os.argv.len <= 1) {
|
if (std.os.argv.len <= 1) {
|
||||||
// std.debug.warn("Usage: vulkan-zig-gen <path-to-vk.xml>\n", .{});
|
std.debug.warn("Usage: vulkan-zig-gen <path-to-vk.xml>\n", .{});
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
var prof_alloc = ProfilingAllocator.init(std.heap.page_allocator);
|
||||||
|
const allocator = &prof_alloc.allocator;
|
||||||
|
|
||||||
const file = try std.fs.cwd().openFileZ(std.os.argv[1], .{});
|
const file = try std.fs.cwd().openFileZ(std.os.argv[1], .{});
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
const size = try file.seekableStream().getEndPos();
|
const size = try file.seekableStream().getEndPos();
|
||||||
const source = try std.heap.page_allocator.alloc(u8, size);
|
const source = try allocator.alloc(u8, size);
|
||||||
defer std.heap.page_allocator.free(source);
|
defer allocator.free(source);
|
||||||
|
|
||||||
_ = try file.inStream().read(source);
|
_ = try file.inStream().read(source);
|
||||||
|
|
||||||
const spec = try xml.parse(std.heap.page_allocator, source);
|
const spec = try xml.parse(allocator, source);
|
||||||
defer spec.deinit();
|
defer spec.deinit();
|
||||||
|
|
||||||
const result = try parseXml(std.heap.page_allocator, spec.root);
|
// const result = try parseXml(std.heap.page_allocator, spec.root);
|
||||||
defer result.deinit();
|
// defer result.deinit();
|
||||||
|
|
||||||
dumpRegistry(result.registry);
|
// dumpRegistry(result.registry);
|
||||||
|
|
||||||
|
var gen = try vkgen.Generator.init(allocator, spec.root);
|
||||||
|
defer gen.deinit();
|
||||||
|
|
||||||
|
try gen.resolveDeclarations();
|
||||||
|
|
||||||
|
std.debug.warn("Total declarations: {}\n", .{gen.registry.decls.len});
|
||||||
|
std.debug.warn("Total memory usage: {} KiB\n", .{@divTrunc(prof_alloc.max_usage, 1024)});
|
||||||
}
|
}
|
||||||
|
|
||||||
test "main" {
|
test "main" {
|
||||||
|
|||||||
@@ -579,7 +579,7 @@ fn parseRequire(allocator: *Allocator, require: *xml.Element, extnumber: ?u31) !
|
|||||||
};
|
};
|
||||||
|
|
||||||
return registry.Require{
|
return registry.Require{
|
||||||
.extends = extends,
|
.extends = allocator.shrink(extends, i_extends),
|
||||||
.types = types,
|
.types = types,
|
||||||
.commands = commands,
|
.commands = commands,
|
||||||
.required_feature_level = required_feature_level,
|
.required_feature_level = required_feature_level,
|
||||||
@@ -594,12 +594,17 @@ fn parseExtensions(allocator: *Allocator, root: *xml.Element) ![]registry.Extens
|
|||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
var it = extensions_elem.findChildrenByTag("extension");
|
var it = extensions_elem.findChildrenByTag("extension");
|
||||||
while (it.next()) |extension| {
|
while (it.next()) |extension| {
|
||||||
if (try parseExtension(allocator, extension)) |ext| {
|
// Some extensions (in particular 94) are disabled, so just skip them
|
||||||
extensions[i] = ext;
|
if (extension.getAttribute("supported")) |supported| {
|
||||||
i += 1;
|
if (mem.eql(u8, supported, "disabled")) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extensions[i] = try parseExtension(allocator, extension);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
return allocator.shrink(extensions, i);
|
return allocator.shrink(extensions, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,12 +624,7 @@ fn findExtVersion(extension: *xml.Element) !u32 {
|
|||||||
return error.InvalidRegistry;
|
return error.InvalidRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseExtension(allocator: *Allocator, extension: *xml.Element) !?registry.Extension {
|
fn parseExtension(allocator: *Allocator, extension: *xml.Element) !registry.Extension {
|
||||||
// Some extensions (in particular 94) are disabled, so just skip them
|
|
||||||
if (extension.getAttribute("supported")) |supported| {
|
|
||||||
if (mem.eql(u8, supported, "disabled")) return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = extension.getAttribute("name") orelse return error.InvalidRegistry;
|
const name = extension.getAttribute("name") orelse return error.InvalidRegistry;
|
||||||
const platform = extension.getAttribute("platform");
|
const platform = extension.getAttribute("platform");
|
||||||
const version = try findExtVersion(extension);
|
const version = try findExtVersion(extension);
|
||||||
|
|||||||
@@ -1,515 +1,3 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const ast = std.zig.ast;
|
const registry = @import("registry.zig");
|
||||||
const mem = std.mem;
|
const Allocator = std.mem.Allocator,
|
||||||
const Allocator = mem.Allocator;
|
|
||||||
const reg = @import("registry.zig");
|
|
||||||
const Registry = reg.Registry;
|
|
||||||
|
|
||||||
const base_indent = " " ** 4;
|
|
||||||
|
|
||||||
const ForeignType = struct {
|
|
||||||
name: []const u8,
|
|
||||||
expr: []const u8
|
|
||||||
};
|
|
||||||
|
|
||||||
const foreign_types = [_]ForeignType{
|
|
||||||
.{.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 = "@Type(.Opaque)"},
|
|
||||||
.{.name = "wl_surface", .expr = "@Type(.Opaque)"},
|
|
||||||
.{.name = "HINSTANCE", .expr = "u32; //std.os.HINSTANCE"},
|
|
||||||
.{.name = "HWND", .expr = "*@Type(.Opaque)"},
|
|
||||||
.{.name = "HMONITOR", .expr = "*@Type(.Opaque)"},
|
|
||||||
.{.name = "HANDLE", .expr = "u32; //std.os.HANDLE"},
|
|
||||||
.{.name = "SECURITY_ATTRIBUTES", .expr = "u32; //std.os.SECURITY_ATTRIBUTES"},
|
|
||||||
.{.name = "DWORD", .expr = "u32; //std.os.DWORD"},
|
|
||||||
.{.name = "LPCWSTR", .expr = "u32; //std.os.LPCWSTR"},
|
|
||||||
.{.name = "xcb_connection_t", .expr = "@Type(.Opaque)"},
|
|
||||||
.{.name = "xcb_visualid_t", .expr = @typeName(u32)},
|
|
||||||
.{.name = "xcb_window_t", .expr = @typeName(u32)},
|
|
||||||
.{.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 = "@Type(.Opaque)"},
|
|
||||||
.{.name = "AHardwareBuffer", .expr = "@Type(.Opaque)"},
|
|
||||||
.{.name = "CAMetalLayer", .expr = "@Type(.Opaque)"},
|
|
||||||
};
|
|
||||||
|
|
||||||
const foreign_types_namespace = "foreign";
|
|
||||||
|
|
||||||
const BuiltinType = struct {
|
|
||||||
c_name: []const u8,
|
|
||||||
zig_name: []const u8
|
|
||||||
};
|
|
||||||
|
|
||||||
const builtin_types = [_]BuiltinType{
|
|
||||||
.{.c_name = "void", .zig_name = @typeName(void)},
|
|
||||||
.{.c_name = "char", .zig_name = @typeName(u8)},
|
|
||||||
.{.c_name = "float", .zig_name = @typeName(f32)},
|
|
||||||
.{.c_name = "double", .zig_name = @typeName(f64)},
|
|
||||||
.{.c_name = "uint8_t", .zig_name = @typeName(u8)},
|
|
||||||
.{.c_name = "uint16_t", .zig_name = @typeName(u16)},
|
|
||||||
.{.c_name = "uint32_t", .zig_name = @typeName(u32)},
|
|
||||||
.{.c_name = "uint64_t", .zig_name = @typeName(u64)},
|
|
||||||
.{.c_name = "int32_t", .zig_name = @typeName(i32)},
|
|
||||||
.{.c_name = "int64_t", .zig_name = @typeName(i64)},
|
|
||||||
.{.c_name = "size_t", .zig_name = @typeName(usize)},
|
|
||||||
.{.c_name = "int", .zig_name = @typeName(c_int)},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn render(out: var, registry: *Registry) !void {
|
|
||||||
try out.writeAll("const std = @import(\"std\");\n");
|
|
||||||
try renderApiConstants(out, registry);
|
|
||||||
try renderForeignTypes(out);
|
|
||||||
try out.writeAll("\n");
|
|
||||||
try renderDeclarations(out, registry);
|
|
||||||
try renderTest(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trimNamespace(name: []const u8) []const u8 {
|
|
||||||
const prefixes = [_][]const u8{"VK_", "vk", "Vk", "PFN_vk"};
|
|
||||||
for (prefixes) |prefix| {
|
|
||||||
if (mem.startsWith(u8, name, prefix)) {
|
|
||||||
return name[prefix.len..];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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], "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifted from src-self-hosted/translate_c.zig
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifted from src-self-hosted/translate_c.zig
|
|
||||||
fn isZigReservedIdentifier(name: []const u8) bool {
|
|
||||||
if (name.len > 1 and (name[0] == 'u' or name[0] == 'i')) {
|
|
||||||
for (name[1..]) |c| {
|
|
||||||
switch (c) {
|
|
||||||
'0'...'9' => {},
|
|
||||||
else => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// void is invalid in c so it doesn't need to be checked.
|
|
||||||
const reserved_names = [_][]const u8 {
|
|
||||||
"comptime_float", "comptime_int", "bool", "isize",
|
|
||||||
"usize", "f16", "f32", "f64", "f128", "c_longdouble",
|
|
||||||
"noreturn", "type", "anyerror", "c_short", "c_ushort",
|
|
||||||
"c_int", "c_uint", "c_long", "c_ulong", "c_longlong", "c_ulonglong"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (reserved_names) |reserved| {
|
|
||||||
if (mem.eql(u8, reserved, name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.writeAll(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Identifier = struct {
|
|
||||||
name: []const u8,
|
|
||||||
|
|
||||||
fn init(name: []const u8) Identifier {
|
|
||||||
return .{.name = name};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needEscape(self: Identifier) bool {
|
|
||||||
return !isValidZigIdentifier(self.name)
|
|
||||||
or isZigReservedIdentifier(self.name)
|
|
||||||
or std.zig.Token.getKeyword(self.name) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format(
|
|
||||||
self: Identifier,
|
|
||||||
fmt: []const u8,
|
|
||||||
options: std.fmt.FormatOptions,
|
|
||||||
out_stream: var
|
|
||||||
) @TypeOf(out_stream).Error!void {
|
|
||||||
if (self.needEscape()){
|
|
||||||
try out_stream.print("@\"{}\"", .{self.name});
|
|
||||||
} else {
|
|
||||||
try out_stream.writeAll(self.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn writeConstAssignmemt(out: var, name: []const u8) !void {
|
|
||||||
try out.print("pub const {} = ", .{Identifier.init(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 renderTypeInfo(out: var, registry: *Registry, type_info: reg.TypeInfo) !void {
|
|
||||||
// If the type is a `void*`, it needs to be translated to `*c_void`.
|
|
||||||
// If its not a pointer, its a return type, so `void` should be emitted.
|
|
||||||
const is_void_ptr = mem.eql(u8, type_info.name, "void") and type_info.pointers.len > 0;
|
|
||||||
|
|
||||||
if (type_info.array_size) |array_size| {
|
|
||||||
try out.print("[{}]", .{trimNamespace(array_size)});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (type_info.pointers) |ptr| {
|
|
||||||
// Apparently Vulkan optional-ness information is not correct, so every pointer
|
|
||||||
// is considered optional
|
|
||||||
switch (ptr.size) {
|
|
||||||
.One => try out.writeAll("?*"),
|
|
||||||
.Many => if (is_void_ptr)
|
|
||||||
try out.writeAll("?*")
|
|
||||||
else
|
|
||||||
try out.writeAll("?[*]"),
|
|
||||||
.ZeroTerminated => try out.writeAll("?[*:0]")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ptr.is_const) {
|
|
||||||
try out.writeAll("const ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_void_ptr) {
|
|
||||||
try out.writeAll("c_void");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the type is foreign, add the appropriate namespace specifier
|
|
||||||
for (foreign_types) |fty| {
|
|
||||||
if (mem.eql(u8, type_info.name, fty.name)) {
|
|
||||||
try out.print(foreign_types_namespace ++ ".{}", .{type_info.name});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.writeAll(bty.zig_name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the type is defined by Vulkan otherwise
|
|
||||||
if (registry.findDefinitionByName(type_info.name)) |def| {
|
|
||||||
if (def.* == .FnPtr) {
|
|
||||||
try out.writeAll("PFN_");
|
|
||||||
}
|
|
||||||
|
|
||||||
try writeIdentifier(out, trimNamespace(type_info.name));
|
|
||||||
} else {
|
|
||||||
return error.InvalidType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderApiConstantExpr(out: var, constant_expr: []const u8) !void {
|
|
||||||
// omit enclosing parenthesis
|
|
||||||
const expr = if (constant_expr[0] == '(' and constant_expr[constant_expr.len - 1] == ')')
|
|
||||||
constant_expr[1 .. constant_expr.len - 1]
|
|
||||||
else
|
|
||||||
constant_expr;
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
try out.writeAll(expr[i .. j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
i = j;
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
' ' => {},
|
|
||||||
else => return error.InvalidConstantExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderApiConstants(out: var, registry: *Registry) !void {
|
|
||||||
var it = registry.api_constants.iterator(0);
|
|
||||||
while (it.next()) |constant| {
|
|
||||||
try writeConstAssignmemt(out, trimNamespace(constant.name));
|
|
||||||
try renderApiConstantExpr(out, constant.expr);
|
|
||||||
try out.writeAll(";\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
try out.writeAll("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderForeignTypes(out: var) !void {
|
|
||||||
try writeConstAssignmemt(out, foreign_types_namespace);
|
|
||||||
try out.writeAll("struct {\n");
|
|
||||||
|
|
||||||
for (foreign_types) |fty| {
|
|
||||||
try out.writeAll(base_indent);
|
|
||||||
try writeConstAssignmemt(out, fty.name);
|
|
||||||
try out.print("{};\n", .{fty.expr});
|
|
||||||
}
|
|
||||||
|
|
||||||
try out.writeAll("};\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderDeclarations(out: var, registry: *Registry) !void {
|
|
||||||
var it = registry.declarations.iterator(0);
|
|
||||||
while (it.next()) |decl| {
|
|
||||||
switch (decl.definition) {
|
|
||||||
.Struct => |*info| try renderContainer(out, registry, .Struct, decl.name, info),
|
|
||||||
.Union => |*info| try renderContainer(out, registry, .Union, decl.name, info),
|
|
||||||
.Enum => |*info| try renderEnum(out, decl.name, info),
|
|
||||||
.Bitmask => |*info| try renderBitmask(out, decl.name, info),
|
|
||||||
.Handle => |*info| try renderHandle(out, decl.name, info),
|
|
||||||
.FnPtr => |*info| try renderFnPtr(out, registry, decl.name, info),
|
|
||||||
.Command => |*info| try renderCommand(out, registry, decl.name, info),
|
|
||||||
.Alias => |alias| try renderAlias(out, registry, decl.name, alias),
|
|
||||||
.BaseType => |type_info| {
|
|
||||||
try writeConstAssignmemt(out, trimNamespace(decl.name));
|
|
||||||
try renderTypeInfo(out, registry, type_info);
|
|
||||||
try out.writeAll(";\n");
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderCommand(out: var, registry: *Registry, name: []const u8, info: *reg.CommandInfo) !void {
|
|
||||||
try out.writeAll("extern fn ");
|
|
||||||
try writeIdentifier(out, name);
|
|
||||||
try out.writeAll("(");
|
|
||||||
|
|
||||||
if (info.parameters.count() > 0) {
|
|
||||||
try out.writeAll("\n");
|
|
||||||
var it = info.parameters.iterator(0);
|
|
||||||
while (it.next()) |param| {
|
|
||||||
try out.writeAll(base_indent);
|
|
||||||
try writeIdentifier(out, param.name);
|
|
||||||
try out.writeAll(": ");
|
|
||||||
try renderTypeInfo(out, registry, param.type_info);
|
|
||||||
try out.writeAll(",\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try out.writeAll(") callconv(.C) ");
|
|
||||||
try renderTypeInfo(out, registry, info.return_type_info);
|
|
||||||
try out.writeAll(";\n\n");
|
|
||||||
|
|
||||||
// var buf: [256]u8 = undefined;
|
|
||||||
// const trimmed = trimNamespace(name);
|
|
||||||
// mem.copy(u8, &buf, trimNamespace(name));
|
|
||||||
// buf[0] = std.ascii.toLower(buf[0]);
|
|
||||||
|
|
||||||
// try writeConstAssignmemt(out, buf[0 .. trimmed.len]);
|
|
||||||
// try writeIdentifier(out, name);
|
|
||||||
// try out.writeAll(";\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderHandle(out: var, name: []const u8, info: *const reg.HandleInfo) !void {
|
|
||||||
try writeConstAssignmemt(out, trimNamespace(name));
|
|
||||||
// Todo: (non)-dispatchable
|
|
||||||
try out.writeAll("*@Type(.Opaque);\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderBitmask(out: var, name: []const u8, info: *const reg.BitmaskInfo) !void {
|
|
||||||
try writeConstAssignmemt(out, trimNamespace(name));
|
|
||||||
try out.writeAll("Flags;\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shouldSkipEnum(enum_info: *reg.EnumInfo) bool {
|
|
||||||
// Skip empty declarations (which are unused bitflags)
|
|
||||||
return enum_info.variants.count() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderEnum(out: var, name: []const u8, enum_info: *reg.EnumInfo) !void {
|
|
||||||
if (shouldSkipEnum(enum_info)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const trimmed_name = trimNamespace(name);
|
|
||||||
|
|
||||||
try writeConstAssignmemt(out, trimmed_name);
|
|
||||||
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.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;
|
|
||||||
snake_prefix_len += segment.len + 1; // Add one for the underscore
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var it = enum_info.variants.iterator(0);
|
|
||||||
while (it.next()) |variant| {
|
|
||||||
if (variant.value == .Alias) continue; // Skip aliases
|
|
||||||
|
|
||||||
try out.writeAll(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.writeAll("};\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderAlias(out: var, registry: *Registry, name: []const u8, alias: []const u8) !void {
|
|
||||||
// An declaration may be aliased to a bit flag enum which has no members, in which case
|
|
||||||
// the alias also has to be skipped.
|
|
||||||
var def = registry.findDefinitionByName(alias).?;
|
|
||||||
while (def.* == .Alias) {
|
|
||||||
def = registry.findDefinitionByName(def.Alias).?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((def.* == .Enum and shouldSkipEnum(&def.Enum)) or def.* == .Command) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try writeConstAssignmemt(out, trimNamespace(name));
|
|
||||||
try writeIdentifier(out, trimNamespace(alias));
|
|
||||||
try out.writeAll(";\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderFnPtr(out: var, registry: *Registry, name: []const u8, info: *reg.CommandInfo) !void {
|
|
||||||
try out.writeAll("pub const PFN_");
|
|
||||||
try writeIdentifier(out, name);
|
|
||||||
try out.writeAll(" = ");
|
|
||||||
|
|
||||||
try out.writeAll("?fn(");
|
|
||||||
|
|
||||||
if (info.parameters.count() > 0) {
|
|
||||||
try out.writeAll("\n");
|
|
||||||
var it = info.parameters.iterator(0);
|
|
||||||
while (it.next()) |param| {
|
|
||||||
try out.writeAll(base_indent);
|
|
||||||
try writeIdentifier(out, param.name);
|
|
||||||
try out.writeAll(": ");
|
|
||||||
try renderTypeInfo(out, registry, param.type_info);
|
|
||||||
try out.writeAll(",\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try out.writeAll(") callconv(.C) ");
|
|
||||||
try renderTypeInfo(out, registry, info.return_type_info);
|
|
||||||
try out.writeAll(";\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.writeAll("extern struct {\n"),
|
|
||||||
.Union => try out.writeAll("extern union {\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
var it = info.members.iterator(0);
|
|
||||||
while (it.next()) |member| {
|
|
||||||
try out.writeAll(base_indent);
|
|
||||||
try writeIdentifier(out, member.name);
|
|
||||||
try out.writeAll(": ");
|
|
||||||
try renderTypeInfo(out, registry, member.type_info);
|
|
||||||
try out.writeAll(",\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
try out.writeAll("};\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderTest(out: var) !void {
|
|
||||||
try out.writeAll(
|
|
||||||
\\test "Semantic analysis" {
|
|
||||||
\\ @setEvalBranchQuota(2000);
|
|
||||||
\\ std.meta.refAllDecls(@This());
|
|
||||||
\\}
|
|
||||||
\\
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub const Content = union(enum) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Element = struct {
|
pub const Element = struct {
|
||||||
pub const AttributeList = SegmentedList(*Attribute, 0);
|
pub const AttributeList = SegmentedList(*Attribute, 1);
|
||||||
pub const ContentList = SegmentedList(Content, 0);
|
pub const ContentList = SegmentedList(Content, 0);
|
||||||
|
|
||||||
tag: []const u8,
|
tag: []const u8,
|
||||||
|
|||||||
Reference in New Issue
Block a user