Update on this… I’m going down the road of file generation using C template files. The idea here is to enable template-like behaviour in the C files so I can replace keywords in the function names and bodies. That way, I can spawn a bunch of different versions of the same function, append them to a file, and compile it so I can avoid duplicating function definitions. This is particularly helpful because I’m supporting complex numbers and allow for mixed precision operations so the combinatorics would get nasty.
So far, here’s the fundaments for the string replacement algorithm that will iterate through a directory, replace the strings in the files, and then save the new file to the output directory. This will probably be a temporary file that gets deleted at some point just for the compiler to generate the output files. It’s all panics and no try because this is at build time and I don’t have any real use for an error value.
const std = @import("std");
const heap = std.heap;
const mem = std.mem;
fn readFileToBuffer(filename: []const u8, allocator: mem.Allocator) []const u8 {
const f = std.fs.cwd().openFile(filename, .{}) catch @panic("Cannot open file.");
defer f.close();
const f_len = f.getEndPos() catch @panic("Could not get end position.");
const buf = allocator.alloc(u8, f_len) catch @panic("Out of memory.");
_ = f.readAll(buf) catch @panic("Could not read file.");
return buf;
}
fn writeStringToFile(path: []const u8, string: []const u8) void {
var file = std.fs.cwd().createFile(path, .{}) catch @panic("Failed to create file.");
defer file.close();
var writer = file.writer();
_ = writer.writeAll(string) catch @panic("Failed to write file.");
}
pub fn replace(
haystack: []const u8,
needle: []const u8,
replacement: []const u8,
allocator: mem.Allocator
) []u8 {
const new_string: []u8 = allocator.alloc(
u8, mem.replacementSize(u8, haystack, needle, replacement)
) catch @panic("Out of memory.");
_ = mem.replace(u8, haystack, needle, replacement, new_string);
return new_string;
}
pub fn joinPaths(
buffer: []u8,
head: []const u8,
tail: []const u8
) []const u8 {
var i: usize = 0;
while (i < buffer.len and i < head.len) : (i += 1) {
buffer[i] = head[i];
}
buffer[i] = '/';
i += 1;
var j: usize = 0;
while (i < buffer.len and j < tail.len) : ({ i += 1; j += 1; }) {
buffer[i] = tail[j];
}
return buffer[0..i];
}
pub fn main() !void {
var arena = heap.ArenaAllocator.init(heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var path_buffer: [512]u8 = undefined;
var dir: std.fs.Dir = try std.fs.cwd().openDir("test_dir", .{
.access_sub_paths = false, .iterate = true, .no_follow = true
});
defer dir.close();
var itr = dir.iterate();
while (try itr.next()) |path| {
const input = readFileToBuffer(
joinPaths(&path_buffer, "test_dir", path.name), allocator
);
const new_string = replace(
input, "XXXX", "test", allocator
);
std.debug.print("\nOutput: {s}\n", .{ new_string });
writeStringToFile(
joinPaths(&path_buffer, "test_out", path.name), new_string
);
}
}
The only thing I’m worried about at this point is how to link to currently-non existing function definitions (I haven’t played around with extern
in a serious way). I may be able to get away with just declaring functions and leaving their bodies as C files.
Anyone have any thoughts on this so far?