Files
envr/zig-vendor/zig-sqlite/build/Preprocessor.zig
2026-06-16 12:00:41 -04:00

244 lines
8.0 KiB
Zig

const std = @import("std");
const debug = std.debug;
const mem = std.mem;
// This tool is used to preprocess the sqlite3 headers to make them usable to build loadable extensions.
//
// Due to limitations of `zig translate-c` (used by @cImport) the code produced by @cImport'ing the sqlite3ext.h header is unusable.
// The sqlite3ext.h header redefines the SQLite API like this:
//
// #define sqlite3_open_v2 sqlite3_api->open_v2
//
// This is not supported by `zig translate-c`, if there's already a definition for a function the aliasing macros won't do anything:
// translate-c keeps generating the code for the function defined in sqlite3.h
//
// Even if there's no definition already (we could for example remove the definition manually from the sqlite3.h file),
// the code generated fails to compile because it references the variable sqlite3_api which is not defined
//
// And even if the sqlite3_api is defined before, the generated code fails to compile because the functions are defined as consts and
// can only reference comptime stuff, however sqlite3_api is a runtime variable.
//
// The only viable option is to completely reomve the original function definitions and redefine all functions in Zig which forward
// calls to the sqlite3_api object.
//
// This works but it requires fairly extensive modifications of both sqlite3.h and sqlite3ext.h which is time consuming to do manually;
// this tool is intended to automate all these modifications.
fn readOriginalData(io: std.Io, allocator: mem.Allocator, path: []const u8) ![]const u8 {
var file = try std.Io.Dir.cwd().openFile(io, path, .{});
defer file.close(io);
var buf: [1024]u8 = undefined;
var reader = file.reader(io, &buf);
const data = reader.interface.readAlloc(allocator, 1024 * 1024);
return data;
}
const Processor = struct {
const Range = union(enum) {
delete: struct {
start: usize,
end: usize,
},
replace: struct {
start: usize,
end: usize,
replacement: []const u8,
},
};
allocator: mem.Allocator,
data: []const u8,
pos: usize,
range_start: usize,
ranges: std.ArrayList(Range),
fn init(allocator: mem.Allocator, data: []const u8) !Processor {
return .{
.allocator = allocator,
.data = data,
.pos = 0,
.range_start = 0,
.ranges = try std.ArrayList(Range).initCapacity(allocator, 4096),
};
}
fn readable(self: *Processor) []const u8 {
if (self.pos >= self.data.len) return "";
return self.data[self.pos..];
}
fn previousByte(self: *Processor) ?u8 {
if (self.pos <= 0) return null;
return self.data[self.pos - 1];
}
fn skipUntil(self: *Processor, needle: []const u8) bool {
const pos = mem.indexOfPos(u8, self.data, self.pos, needle);
if (pos) |p| {
self.pos = p;
return true;
}
return false;
}
fn consume(self: *Processor, needle: []const u8) void {
debug.assert(self.startsWith(needle));
self.pos += needle.len;
}
fn startsWith(self: *Processor, needle: []const u8) bool {
if (self.pos >= self.data.len) return false;
const data = self.data[self.pos..];
return mem.startsWith(u8, data, needle);
}
fn rangeStart(self: *Processor) void {
self.range_start = self.pos;
}
fn rangeDelete(self: *Processor) void {
self.ranges.appendAssumeCapacity(Range{
.delete = .{
.start = self.range_start,
.end = self.pos,
},
});
}
fn rangeReplace(self: *Processor, replacement: []const u8) void {
self.ranges.appendAssumeCapacity(Range{
.replace = .{
.start = self.range_start,
.end = self.pos,
.replacement = replacement,
},
});
}
fn dump(self: *Processor, writer: anytype) !void {
var pos: usize = 0;
for (self.ranges.items) |range| {
switch (range) {
.delete => |dr| {
const to_write = self.data[pos..dr.start];
try writer.interface.writeAll(to_write);
pos = dr.end;
},
.replace => |rr| {
const to_write = self.data[pos..rr.start];
try writer.interface.writeAll(to_write);
try writer.interface.writeAll(rr.replacement);
pos = rr.end;
},
}
// debug.print("excluded range: start={d} end={d} slice=\"{s}\"\n", .{
// range.start,
// range.end,
// processor.data[range.start..range.end],
// });
}
// Finally append the remaining data in the buffer (the last range will probably not be the end of the file)
if (pos < self.data.len) {
const remaining_data = self.data[pos..];
try writer.interface.writeAll(remaining_data);
}
}
};
pub fn sqlite3(io: std.Io, allocator: mem.Allocator, input_path: []const u8, output_path: []const u8) !void {
const data = try readOriginalData(io, allocator, input_path);
var processor = try Processor.init(allocator, data);
while (true) {
// Everything function definition is declared with SQLITE_API.
// Stop the loop if there's none in the remaining data.
if (!processor.skipUntil("SQLITE_API ")) break;
// If the byte just before is not a LN it's not a function definition.
// There are a couple instances where SQLITE_API appears in a comment.
const previous_byte = processor.previousByte() orelse 0;
if (previous_byte != '\n') {
processor.consume("SQLITE_API ");
continue;
}
// Now we assume we're at the start of a function definition.
//
// We keep track of every function definition by marking its start and end position in the data.
processor.rangeStart();
processor.consume("SQLITE_API ");
if (processor.startsWith("SQLITE_EXTERN ")) {
// This is not a function definition, ignore it.
// try processor.unmark();
continue;
}
_ = processor.skipUntil(");\n");
processor.consume(");\n");
processor.rangeDelete();
}
// Write the result
// FIXME: Handle this
var output_file = try std.Io.Dir.cwd().createFile(io, output_path, .{}); //0o644 });
defer output_file.close(io);
try output_file.writeStreamingAll(io, "/* sqlite3.h edited by the zig-sqlite build script */\n");
var buf: [1024]u8 = undefined;
var out_writer = output_file.writer(io, &buf);
try processor.dump(&out_writer);
}
pub fn sqlite3ext(io: std.Io, allocator: mem.Allocator, input_path: []const u8, output_path: []const u8) !void {
const data = try readOriginalData(io, allocator, input_path);
var processor = try Processor.init(allocator, data);
// Replace the include line
debug.assert(processor.skipUntil("#include \"sqlite3.h\""));
processor.rangeStart();
processor.consume("#include \"sqlite3.h\"");
processor.rangeReplace("#include \"loadable-ext-sqlite3.h\"");
// Delete all #define macros
while (true) {
if (!processor.skipUntil("#define sqlite3_")) break;
processor.rangeStart();
processor.consume("#define sqlite3_");
_ = processor.skipUntil("\n");
processor.consume("\n");
processor.rangeDelete();
}
// Write the result
// FIXME: File permissions
// var output_file = try std.fs.cwd().createFile(output_path, .{ .mode = 0o0644 });
var output_file = try std.Io.Dir.cwd().createFile(io, output_path, .{});
defer output_file.close(io);
try output_file.writeStreamingAll(io, "/* sqlite3ext.h edited by the zig-sqlite build script */\n");
var buf: [1024]u8 = undefined;
var out_writer = output_file.writer(io, &buf);
try processor.dump(&out_writer);
}