mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 18:48:33 -04:00
244 lines
8.0 KiB
Zig
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);
|
|
}
|