// Copyright 2022 DistressNetwork° // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. 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 Content = union(enum) { 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; } const CommandType = enum { start, add, end, ref, none }; fn command_type(line: []const u8) CommandType { if (std.mem.startsWith(u8, line, k_start)) { return .start; } else if (std.mem.startsWith(u8, line, k_add)) { return .add; } else if (std.mem.eql(u8, line, k_end)) { return .end; } else if (std.mem.startsWith(u8, std.mem.trimLeft(u8, line, " \t"), k_ref)) { return .ref; } else { return .none; } } 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]; switch (command_type(line)) { .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; }, .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; }, .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]; switch (command_type(line)) { .start, .add => { log(.err, "line {d}: unexpected section start", .{i + 1}); return error.UnexpectedStart; }, .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; }, .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 switch (command_type(line)) { .start => { current_name = line[(k_start.len)..]; try buffer.append(try std.mem.join(alloc, current_name, conf_start)); }, .add => { current_name = line[(k_add.len)..]; try buffer.append(try std.mem.join(alloc, current_name, conf_add)); }, .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 })); }, .end => { try buffer.append(try std.mem.join(alloc, current_name, conf_end)); }, else => { try buffer.append(line); }, } } return buffer.toOwnedSlice(); }