diff options
author | eudoxia <uplink@distress.network> | 2022-03-31 10:19:21 -0400 |
---|---|---|
committer | eudoxia <uplink@distress.network> | 2022-03-31 10:19:21 -0400 |
commit | 27f4773b122a2758cfaf88537c9e1a8ad5df7e69 (patch) | |
tree | f6a31476fee7208d1ceb49186b243b106b09a5bd /src |
initial
Diffstat (limited to 'src')
-rw-r--r-- | src/data.zig | 235 | ||||
-rw-r--r-- | src/log.zig | 21 | ||||
-rw-r--r-- | src/tangle.zig | 44 | ||||
-rw-r--r-- | src/weave.zig | 37 |
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 = §ions.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; +} |