summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoreudoxia <uplink@distress.network>2022-03-31 10:19:21 -0400
committereudoxia <uplink@distress.network>2022-03-31 10:19:21 -0400
commit27f4773b122a2758cfaf88537c9e1a8ad5df7e69 (patch)
treef6a31476fee7208d1ceb49186b243b106b09a5bd /src
initial
Diffstat (limited to 'src')
-rw-r--r--src/data.zig235
-rw-r--r--src/log.zig21
-rw-r--r--src/tangle.zig44
-rw-r--r--src/weave.zig37
4 files changed, 337 insertions, 0 deletions
diff --git a/src/data.zig b/src/data.zig
new file mode 100644
index 0000000..f2ee9d9
--- /dev/null
+++ b/src/data.zig
@@ -0,0 +1,235 @@
+const std = @import("std");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+
+pub const input_max = 0x1_0000_0000;
+pub const dereference_max = 250;
+
+pub const k_start = "@: ";
+pub const k_add = "@+ ";
+pub const k_end = "@.";
+pub const k_ref = "@= ";
+pub const k_root = "*";
+
+pub const kc_start = "@start ";
+pub const kc_add = "@add ";
+pub const kc_end = "@end ";
+pub const kc_ref = "@ref ";
+pub const kc_esc = "@@";
+pub const kc_nl = "\\n";
+
+pub const Section = struct {
+ name: []const u8,
+ content: []const Content,
+};
+
+pub const CodeType = enum { literal, reference };
+pub const Content = union(CodeType) {
+ literal: LineRange,
+ reference: []const u8,
+};
+
+pub const LineRange = struct {
+ start: u32,
+ end: u32,
+};
+
+pub const Errors = error{
+ UnexpectedStart,
+ UnexpectedEnd,
+ DereferenceLimit,
+ NotFound,
+};
+
+pub fn split_lines(file: []const u8, alloc: Allocator) ![][]const u8 {
+ var buffer = std.ArrayList([]const u8).init(alloc);
+ defer buffer.deinit();
+
+ var iterator = std.mem.split(u8, file, "\n");
+ while (iterator.next()) |line| {
+ try buffer.append(line);
+ }
+ if ((buffer.items.len > 0) and std.mem.eql(u8, buffer.items[buffer.items.len - 1], "")) {
+ _ = buffer.pop();
+ }
+
+ return buffer.toOwnedSlice();
+}
+
+pub fn get_conf(lines: [][]const u8, key: []const u8, alloc: Allocator) ![][]const u8 {
+ for (lines) |line| {
+ if (std.mem.startsWith(u8, line, key)) {
+ return try fmt_conf(line, key, alloc);
+ }
+ }
+ log(.err, "config declaration '{s}' not found", .{std.mem.trimRight(u8, key, " \t")});
+ return error.NotFound;
+}
+
+fn fmt_conf(line: []const u8, key: []const u8, alloc: Allocator) ![][]const u8 {
+ var buffer = std.ArrayList([]const u8).init(alloc);
+ defer buffer.deinit();
+
+ var iterator = std.mem.split(u8, line[(key.len)..], kc_esc);
+ while (iterator.next()) |str| {
+ try buffer.append(try std.mem.replaceOwned(u8, alloc, str, kc_nl, "\n"));
+ }
+
+ return buffer.toOwnedSlice();
+}
+
+fn search(list: []Section, name: []const u8) !usize {
+ for (list) |section, index| {
+ if (std.mem.eql(u8, section.name, name)) return index;
+ }
+ log(.err, "section '{s}' not found", .{name});
+ return error.NotFound;
+}
+// TODO return last match instead?
+
+pub fn parse(lines: [][]const u8, alloc: Allocator) ![]Section {
+ var sections = std.ArrayList(Section).init(alloc);
+ defer sections.deinit();
+
+ var i: u32 = 0;
+ while (i < lines.len) {
+ const line = lines[i];
+ if (std.mem.startsWith(u8, line, k_start)) {
+ const name = line[(k_start.len)..];
+ log(.debug, "({d}) starting section '{s}'", .{ i + 1, name });
+
+ const section = try parse_code(lines, i + 1, alloc);
+ try sections.append(.{ .name = name, .content = section.content });
+
+ log(.debug, "({d}) ending section '{s}'", .{ section.index, name });
+ i = section.index;
+ } else if (std.mem.startsWith(u8, line, k_add)) {
+ const name = line[(k_add.len)..];
+ log(.debug, "({d}) appending to section '{s}'", .{ i + 1, name });
+
+ const section = try parse_code(lines, i + 1, alloc);
+ const index = try search(sections.items, name);
+ const old = &sections.items[index];
+ const new = try std.mem.concat(alloc, Content, &[_][]const Content{ old.*.content, section.content });
+ old.*.content = new;
+
+ log(.debug, "({d}) ending section '{s}'", .{ section.index, name });
+ i = section.index;
+ } else if (std.mem.eql(u8, line, k_end)) {
+ log(.err, "line {d}: unexpected section end", .{i + 1});
+ return error.UnexpectedEnd;
+ } else {
+ i += 1;
+ }
+ }
+
+ return sections.toOwnedSlice();
+}
+
+fn parse_code(lines: [][]const u8, index: u32, alloc: Allocator) !CodeReturn {
+ var content = std.ArrayList(Content).init(alloc);
+ defer content.deinit();
+
+ var i = index;
+ while (i < lines.len) {
+ const line = lines[i];
+ if (std.mem.startsWith(u8, line, k_start) or std.mem.startsWith(u8, line, k_add)) {
+ log(.err, "line {d}: unexpected section start", .{i + 1});
+ return error.UnexpectedStart;
+ } else if (std.mem.startsWith(u8, std.mem.trimLeft(u8, line, " \t"), k_ref)) {
+ const ref_name = std.mem.trimLeft(u8, line, " \t")[(k_ref.len)..];
+ try content.append(.{ .reference = ref_name });
+ log(.debug, "({d}) \tappended reference '{s}'", .{ i + 1, ref_name });
+ i += 1;
+ } else if (std.mem.eql(u8, line, k_end)) {
+ break;
+ } else {
+ if (content.items.len > 0) {
+ switch (content.items[content.items.len - 1]) {
+ .literal => |*range| {
+ range.*.end = i;
+ },
+ .reference => {
+ try content.append(.{ .literal = .{ .start = i, .end = i } });
+ log(.debug, "({d}) \tappending literal", .{i + 1});
+ },
+ }
+ } else {
+ try content.append(.{ .literal = .{ .start = i, .end = i } });
+ log(.debug, "({d}) \tappending literal", .{i + 1});
+ }
+ i += 1;
+ }
+ }
+
+ return CodeReturn{ .content = content.toOwnedSlice(), .index = i + 1 };
+}
+const CodeReturn = struct {
+ content: []const Content,
+ index: u32,
+};
+
+pub fn codegen(lines: [][]const u8, list: []Section, alloc: Allocator) ![][]const u8 {
+ const root = try search(list, k_root);
+ return try codegen_main(lines, list, root, 0, alloc);
+}
+
+fn codegen_main(lines: [][]const u8, list: []Section, index: usize, depth: u8, alloc: Allocator) anyerror![][]const u8 {
+ var buffer = std.ArrayList([]const u8).init(alloc);
+ defer buffer.deinit();
+
+ const section = list[index];
+ log(.debug, "generating section '{s}'", .{section.name});
+ for (section.content) |content| switch (content) {
+ .literal => |range| {
+ log(.debug, "adding literal range {d}-{d}", .{ range.start + 1, range.end + 1 });
+ try buffer.appendSlice(lines[(range.start)..(range.end + 1)]);
+ },
+ .reference => |name| {
+ if (depth > dereference_max) {
+ log(.err, "section dereferencing recursion depth exceeded (max {d})", .{dereference_max});
+ return error.DereferenceLimit;
+ }
+ const ref = try search(list, name);
+ const code = try codegen_main(lines, list, ref, depth + 1, alloc);
+ try buffer.appendSlice(code);
+ },
+ };
+
+ log(.debug, "ending section '{s}'", .{section.name});
+ return buffer.toOwnedSlice();
+}
+
+pub fn textgen(lines: [][]const u8, alloc: Allocator) ![][]const u8 {
+ var buffer = std.ArrayList([]const u8).init(alloc);
+ defer buffer.deinit();
+
+ const conf_start = try get_conf(lines, kc_start, alloc);
+ const conf_add = get_conf(lines, kc_add, alloc) catch conf_start;
+ const conf_end = try get_conf(lines, kc_end, alloc);
+ const conf_ref = try get_conf(lines, kc_ref, alloc);
+
+ var current_name: []const u8 = undefined;
+ for (lines) |line| {
+ if (std.mem.startsWith(u8, line, kc_start) or std.mem.startsWith(u8, line, kc_add) or std.mem.startsWith(u8, line, kc_end) or std.mem.startsWith(u8, line, kc_ref)) {
+ continue;
+ } else if (std.mem.startsWith(u8, line, k_start)) {
+ current_name = line[(k_start.len)..];
+ try buffer.append(try std.mem.join(alloc, current_name, conf_start));
+ } else if (std.mem.startsWith(u8, line, k_add)) {
+ current_name = line[(k_add.len)..];
+ try buffer.append(try std.mem.join(alloc, current_name, conf_add));
+ } else if (std.mem.startsWith(u8, line, k_end)) {
+ try buffer.append(try std.mem.join(alloc, current_name, conf_end));
+ } else if (std.mem.startsWith(u8, std.mem.trimLeft(u8, line, " \t"), k_ref)) {
+ const start = std.mem.indexOf(u8, line, k_ref).?;
+ const ref = try std.mem.join(alloc, line[(start + k_ref.len)..], conf_ref);
+ try buffer.append(try std.mem.concat(alloc, u8, &[_][]const u8{ line[0..start], ref }));
+ } else {
+ try buffer.append(line);
+ }
+ }
+
+ return buffer.toOwnedSlice();
+}
diff --git a/src/log.zig b/src/log.zig
new file mode 100644
index 0000000..661b97f
--- /dev/null
+++ b/src/log.zig
@@ -0,0 +1,21 @@
+const std = @import("std");
+const Level = std.log.Level;
+
+pub const log_level: Level = .warn;
+
+pub fn log(
+ comptime level: Level,
+ comptime format: []const u8,
+ args: anytype,
+) void {
+ if (@enumToInt(level) > @enumToInt(log_level)) return;
+
+ const msg = "[" ++ switch (level) {
+ .info => "ok",
+ .err => "err",
+ else => level.asText(),
+ } ++ "]\t" ++ format ++ "\n";
+
+ const stderr = std.io.getStdErr().writer();
+ nosuspend stderr.print(msg, args) catch return;
+}
diff --git a/src/tangle.zig b/src/tangle.zig
new file mode 100644
index 0000000..1b967c7
--- /dev/null
+++ b/src/tangle.zig
@@ -0,0 +1,44 @@
+const std = @import("std");
+const data = @import("data.zig");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+
+pub fn main() !u8 {
+ const stdin = std.io.getStdIn();
+ const stdout = std.io.getStdOut();
+
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+ var alloc = arena.allocator();
+ defer arena.deinit();
+
+ const input = stdin.reader().readAllAlloc(alloc, data.input_max) catch |err| switch (err) {
+ error.StreamTooLong => {
+ log(.err, "input too large (maximum {})", .{std.fmt.fmtIntSizeBin(data.input_max)});
+ return 1;
+ },
+ else => |e| return e,
+ };
+
+ const lines = try data.split_lines(input, alloc);
+
+ const sections = data.parse(lines, alloc) catch |err| switch (err) {
+ error.UnexpectedStart, error.UnexpectedEnd => {
+ return 1;
+ },
+ else => |e| return e,
+ };
+
+ const code = data.codegen(lines, sections, alloc) catch |err| switch (err) {
+ error.DereferenceLimit, error.NotFound => {
+ return 1;
+ },
+ else => |e| return e,
+ };
+
+ for (code) |line| {
+ try stdout.writer().print("{s}\n", .{line});
+ }
+
+ return 0;
+}
diff --git a/src/weave.zig b/src/weave.zig
new file mode 100644
index 0000000..8a1f5c4
--- /dev/null
+++ b/src/weave.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+const data = @import("data.zig");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+
+pub fn main() !u8 {
+ const stdin = std.io.getStdIn();
+ const stdout = std.io.getStdOut();
+
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+ var alloc = arena.allocator();
+ defer arena.deinit();
+
+ const input = stdin.reader().readAllAlloc(alloc, data.input_max) catch |err| switch (err) {
+ error.StreamTooLong => {
+ log(.err, "input too large (maximum {})", .{std.fmt.fmtIntSizeBin(data.input_max)});
+ return 1;
+ },
+ else => |e| return e,
+ };
+
+ const lines = try data.split_lines(input, alloc);
+
+ const text = data.textgen(lines, alloc) catch |err| switch (err) {
+ error.NotFound => {
+ return 1;
+ },
+ else => |e| return e,
+ };
+
+ for (text) |line| {
+ try stdout.writer().print("{s}\n", .{line});
+ }
+
+ return 0;
+}