summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vincent Rischmann2022-09-11 17:36:30 +0200
committerGravatar Vincent Rischmann2022-09-18 02:30:19 +0200
commit80f311044c508203b85e67ab3c8f895bd506c518 (patch)
tree27f7c2badbc69c2aca7756a19d5276f872010947
parentc: add the sqlite3ext file (diff)
downloadzig-sqlite-80f311044c508203b85e67ab3c8f895bd506c518.tar.gz
zig-sqlite-80f311044c508203b85e67ab3c8f895bd506c518.tar.xz
zig-sqlite-80f311044c508203b85e67ab3c8f895bd506c518.zip
add a tool to preprocess files
-rw-r--r--tools/preprocess_files.zig254
1 files changed, 254 insertions, 0 deletions
diff --git a/tools/preprocess_files.zig b/tools/preprocess_files.zig
new file mode 100644
index 0000000..d5fce7b
--- /dev/null
+++ b/tools/preprocess_files.zig
@@ -0,0 +1,254 @@
1const std = @import("std");
2const debug = std.debug;
3const fmt = std.fmt;
4const heap = std.heap;
5const mem = std.mem;
6
7// This tool is used to preprocess the sqlite3 headers to make them usable to build loadable extensions.
8//
9// Due to limitations of `zig translate-c` (used by @cImport) the code produced by @cImport'ing the sqlite3ext.h header is unusable.
10// The sqlite3ext.h header redefines the SQLite API like this:
11//
12// #define sqlite3_open_v2 sqlite3_api->open_v2
13//
14// This is not supported by `zig translate-c`, if there's already a definition for a function the aliasing macros won't do anything:
15// translate-c keeps generating the code for the function defined in sqlite3.h
16//
17// Even if there's no definition already (we could for example remove the definition manually from the sqlite3.h file),
18// the code generated fails to compile because it references the variable sqlite3_api which is not defined
19//
20// And even if the sqlite3_api is defined before, the generated code fails to compile because the functions are defined as consts and
21// can only reference comptime stuff, however sqlite3_api is a runtime variable.
22//
23// The only viable option is to completely reomve the original function definitions and redefine all functions in Zig which forward
24// calls to the sqlite3_api object.
25//
26// This works but it requires fairly extensive modifications of both sqlite3.h and sqlite3ext.h which is time consuming to do manually;
27// this tool is intended to automate all these modifications.
28
29fn readOriginalData(allocator: mem.Allocator, path: []const u8) ![]const u8 {
30 var file = try std.fs.cwd().openFile(path, .{});
31 defer file.close();
32
33 var reader = file.reader();
34
35 const data = reader.readAllAlloc(allocator, 1024 * 1024);
36 return data;
37}
38
39const Processor = struct {
40 const Range = union(enum) {
41 delete: struct {
42 start: usize,
43 end: usize,
44 },
45 replace: struct {
46 start: usize,
47 end: usize,
48 replacement: []const u8,
49 },
50 };
51
52 allocator: mem.Allocator,
53
54 data: []const u8,
55 pos: usize,
56
57 range_start: usize,
58 ranges: std.ArrayList(Range),
59
60 fn init(allocator: mem.Allocator, data: []const u8) !Processor {
61 return .{
62 .allocator = allocator,
63 .data = data,
64 .pos = 0,
65 .range_start = 0,
66 .ranges = try std.ArrayList(Range).initCapacity(allocator, 4096),
67 };
68 }
69
70 fn readable(self: *Processor) []const u8 {
71 if (self.pos >= self.data.len) return "";
72
73 return self.data[self.pos..];
74 }
75
76 fn previousByte(self: *Processor) ?u8 {
77 if (self.pos <= 0) return null;
78 return self.data[self.pos - 1];
79 }
80
81 fn skipUntil(self: *Processor, needle: []const u8) bool {
82 const pos = mem.indexOfPos(u8, self.data, self.pos, needle);
83 if (pos) |p| {
84 self.pos = p;
85 return true;
86 }
87 return false;
88 }
89
90 fn consume(self: *Processor, needle: []const u8) void {
91 debug.assert(self.startsWith(needle));
92
93 self.pos += needle.len;
94 }
95
96 fn startsWith(self: *Processor, needle: []const u8) bool {
97 if (self.pos >= self.data.len) return false;
98
99 const data = self.data[self.pos..];
100 return mem.startsWith(u8, data, needle);
101 }
102
103 fn rangeStart(self: *Processor) void {
104 self.range_start = self.pos;
105 }
106
107 fn rangeDelete(self: *Processor) void {
108 self.ranges.appendAssumeCapacity(Range{
109 .delete = .{
110 .start = self.range_start,
111 .end = self.pos,
112 },
113 });
114 }
115
116 fn rangeReplace(self: *Processor, replacement: []const u8) void {
117 self.ranges.appendAssumeCapacity(Range{
118 .replace = .{
119 .start = self.range_start,
120 .end = self.pos,
121 .replacement = replacement,
122 },
123 });
124 }
125
126 fn dump(self: *Processor, writer: anytype) !void {
127 var pos: usize = 0;
128 for (self.ranges.items) |range| {
129 switch (range) {
130 .delete => |dr| {
131 const to_write = self.data[pos..dr.start];
132 try writer.writeAll(to_write);
133 pos = dr.end;
134 },
135 .replace => |rr| {
136 const to_write = self.data[pos..rr.start];
137 try writer.writeAll(to_write);
138 try writer.writeAll(rr.replacement);
139 pos = rr.end;
140 },
141 }
142
143 // debug.print("excluded range: start={d} end={d} slice=\"{s}\"\n", .{
144 // range.start,
145 // range.end,
146 // processor.data[range.start..range.end],
147 // });
148 }
149
150 // Finally append the remaining data in the buffer (the last range will probably not be the end of the file)
151 if (pos < self.data.len) {
152 const remaining_data = self.data[pos..];
153 try writer.writeAll(remaining_data);
154 }
155 }
156};
157
158fn preprocessSqlite3HeaderFile(gpa: mem.Allocator) !void {
159 var arena = heap.ArenaAllocator.init(gpa);
160 defer arena.deinit();
161 const allocator = arena.allocator();
162
163 //
164
165 var data = try readOriginalData(allocator, "c/sqlite3.h");
166
167 var processor = try Processor.init(allocator, data);
168
169 while (true) {
170 // Everything function definition is declared with SQLITE_API.
171 // Stop the loop if there's none in the remaining data.
172 if (!processor.skipUntil("SQLITE_API ")) break;
173
174 // If the byte just before is not a LN it's not a function definition.
175 // There are a couple instances where SQLITE_API appears in a comment.
176 const previous_byte = processor.previousByte() orelse 0;
177 if (previous_byte != '\n') {
178 processor.consume("SQLITE_API ");
179 continue;
180 }
181
182 // Now we assume we're at the start of a function definition.
183 //
184 // We keep track of every function definition by marking its start and end position in the data.
185
186 processor.rangeStart();
187
188 processor.consume("SQLITE_API ");
189 if (processor.startsWith("SQLITE_EXTERN ")) {
190 // This is not a function definition, ignore it.
191 // try processor.unmark();
192 continue;
193 }
194
195 _ = processor.skipUntil(");\n");
196 processor.consume(");\n");
197
198 processor.rangeDelete();
199 }
200
201 // Write the result to the file
202 var output_file = try std.fs.cwd().createFile("./c/loadable-ext-sqlite3.h", .{ .mode = 0o0644 });
203 defer output_file.close();
204
205 try processor.dump(output_file.writer());
206}
207
208fn preprocessSqlite3ExtHeaderFile(gpa: mem.Allocator) !void {
209 var arena = heap.ArenaAllocator.init(gpa);
210 defer arena.deinit();
211 const allocator = arena.allocator();
212
213 //
214
215 var data = try readOriginalData(allocator, "c/sqlite3ext.h");
216
217 var processor = try Processor.init(allocator, data);
218
219 // Replace the include line
220
221 debug.assert(processor.skipUntil("#include \"sqlite3.h\""));
222
223 processor.rangeStart();
224 processor.consume("#include \"sqlite3.h\"");
225 processor.rangeReplace("#include \"loadable-ext-sqlite3.h\"");
226
227 // Delete all #define macros
228
229 while (true) {
230 if (!processor.skipUntil("#define sqlite3_")) break;
231
232 processor.rangeStart();
233
234 processor.consume("#define sqlite3_");
235 _ = processor.skipUntil("\n");
236 processor.consume("\n");
237
238 processor.rangeDelete();
239 }
240
241 // Write the result to the file
242 var output_file = try std.fs.cwd().createFile("./c/loadable-ext-sqlite3ext.h", .{ .mode = 0o0644 });
243 defer output_file.close();
244
245 try processor.dump(output_file.writer());
246}
247
248pub fn main() !void {
249 var gpa = heap.GeneralPurposeAllocator(.{}){};
250 defer if (gpa.deinit()) debug.panic("leaks detected\n", .{});
251
252 try preprocessSqlite3HeaderFile(gpa.allocator());
253 try preprocessSqlite3ExtHeaderFile(gpa.allocator());
254}