summaryrefslogtreecommitdiff
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
initial
-rw-r--r--.gitignore1
-rwxr-xr-xbuild.sh12
-rw-r--r--data.lp449
-rw-r--r--doc/data.html519
-rw-r--r--doc/header-html30
-rw-r--r--doc/header-lp4
-rw-r--r--doc/log.html102
-rw-r--r--doc/tangle.html141
-rw-r--r--doc/weave.html125
-rwxr-xr-xgen-docs.sh9
-rw-r--r--log.lp63
-rwxr-xr-xmisc/clean.sh3
-rwxr-xr-xmisc/tangle-bbin0 -> 16376 bytes
-rw-r--r--misc/test-tex53
-rw-r--r--misc/test-tex.pdfbin0 -> 21040 bytes
-rw-r--r--misc/test.aux27
-rw-r--r--misc/test.log916
-rw-r--r--misc/test.out0
-rw-r--r--misc/test.pdfbin0 -> 19772 bytes
-rw-r--r--misc/test.tex55
-rw-r--r--src/data.zig235
-rw-r--r--src/log.zig21
-rw-r--r--src/tangle.zig44
-rw-r--r--src/weave.zig37
-rwxr-xr-xtanglebin0 -> 16376 bytes
-rw-r--r--tangle.lp100
-rwxr-xr-xweavebin0 -> 14672 bytes
-rw-r--r--weave.lp85
28 files changed, 3031 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59f7796
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+**/zig-cache
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..d759446
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,12 @@
+#!/bin/sh -e
+
+for file in "data" "log" "tangle" "weave" ; do
+ cat "${file}.lp" | ./tangle | zig fmt --stdin > "src/${file}.zig" ;
+ echo "[ok] generated source '${file}'" ;
+done
+
+for file in "tangle" "weave" ; do
+ zig build-exe -O ReleaseSmall "src/${file}.zig" ;
+ strip -s "${file}" ;
+ echo "[ok] compiled '${file}'" ;
+done
diff --git a/data.lp b/data.lp
new file mode 100644
index 0000000..233395f
--- /dev/null
+++ b/data.lp
@@ -0,0 +1,449 @@
+# data.zig
+
+This file contains the various data processing-related constants and functions referenced by the tangling and weaving processes.
+
+@: *
+@= Imports
+
+@= Processing limits
+
+@= Formatting keywords
+
+@= Configuration keywords
+
+@= Data structure types
+
+@= Error set
+
+@= Line splitting function
+
+@= Configuration searching function
+
+@= Section searching function
+
+@= Parsing functions
+
+@= Code generation functions
+
+@= Text generation function
+@.
+
+## Constants
+
+We first import the standard library and the logging function from `log.zig`.
+
+@: Imports
+const std = @import("std");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+@.
+
+We define the maximum input file size of 4GiB, and the code generation function's maximum recursion depth of 250 nested calls.
+
+@: Processing limits
+pub const input_max = 0x1_0000_0000;
+pub const dereference_max = 250;
+@.
+
+We then define the recognized formatting keywords. These consist of the following:
+
+- `@:`, which begins a new code section;
+- `@+`, which appends content to a previous code section;
+- `@.`, which terminates the definition of a code section;
+- `@=`, which creates a reference to another code section;
+- `*`, which is a reserved section name representing the root level of the source code.
+
+@: Formatting keywords
+pub const k_start = "@: ";
+pub const k_add = "@+ ";
+pub const k_end = "@.";
+pub const k_ref = "@= ";
+pub const k_root = "*";
+@.
+
+We similarly define the recognized configuration keywords, consisting of:
+
+- `@start`, which defines the leading formatted code delimiter when beginning new sections;
+- `@add`, which defines the leading formatted code delimiter when appending to existing sections;
+- `@end`, which defines the trailing formatted code delimiter;
+- `@ref`, which defines the format for section references;
+- `@@`, which is the escape sequence representing the current section name;
+- `\n`, which is the escape sequence representing a newline.
+
+@: Configuration keywords
+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";
+@.
+
+We then define the data structure used for parsing the input into code sections, described as follows:
+
+- The overall structure of the file is an array of `Section`s.
+- A `Section` consists of the section name and an array of `Content` elements.
+- A `Content` element may be either a range of literal lines of code or a reference to another section.
+- A `LineRange` is a pair of integers indicating the starting and ending line numbers of the section.
+
+@: Data structure types
+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,
+};
+@.
+
+We also define the set of errors which may be encountered by the various processing functions, consisting of:
+
+- Unexpected section start commands,
+- Unexpected section end commands,
+- Recursive dereferencing exceeding the specified depth limit,
+- References to nonexistent section names or configuration commands.
+
+@: Error set
+pub const Errors = error{
+ UnexpectedStart,
+ UnexpectedEnd,
+ DereferenceLimit,
+ NotFound,
+};
+@.
+
+## Preprocessing & Searching
+
+The line splitting function is defined, which operates on a buffer as follows.
+
+@: Line splitting function
+pub fn split_lines(file: []const u8, alloc: Allocator) ![][]const u8 {
+ var buffer = std.ArrayList([]const u8).init(alloc);
+ defer buffer.deinit();
+
+ @= Split file at each newline
+
+ return buffer.toOwnedSlice();
+}
+@.
+
+The function simply iteratively splits the file at each newline, and appends each resulting line to the buffer.
+
+@: Split file at each newline
+var iterator = std.mem.split(u8, file, "\n");
+while (iterator.next()) |line| {
+ try buffer.append(line);
+}
+@.
+
+In addition, the final empty line created by the trailing newline at the end of the file (inserted automatically by some text editors) is removed, if it exists. This may only be performed if the file is non-empty, to avoid out-of-bounds indexing.
+
+@+ Split file at each newline
+if ((buffer.items.len > 0) and std.mem.eql(u8, buffer.items[buffer.items.len - 1], "")) {
+ _ = buffer.pop();
+}
+@.
+
+We define the configuration command searching function, which returns a list containing the segments of the split format string. The function will return from within the for loop if the declaration is found, otherwise an error is reported.
+
+@: Configuration searching function
+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;
+}
+
+@= Auxiliary formatting function
+@.
+
+If the declaration is found, its contained format string is split along instances of the section name escape sequence, and each substring has its instances of the newline escape sequence replaced with a literal newline.
+
+@: Auxiliary formatting function
+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();
+}
+@.
+
+We define the code section searching function, which returns the index (into the section list) of the first section with a matching name, or returns an error if none exist.
+
+@: Section searching function
+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?
+@.
+
+## Parsing
+
+We then define the parsing functions, consisting of the main `parse` function which builds the list of `Section`s, and its auxiliary `parse_code` subroutine which builds the contents of each `CodeSection`.
+
+@: Parsing functions
+pub fn parse(lines: [][]const u8, alloc: Allocator) ![]Section {
+ var sections = std.ArrayList(Section).init(alloc);
+ defer sections.deinit();
+
+ @= Main parsing routine
+
+ return sections.toOwnedSlice();
+}
+
+fn parse_code(lines: [][]const u8, index: u32, alloc: Allocator) !CodeReturn {
+ var content = std.ArrayList(Content).init(alloc);
+ defer content.deinit();
+
+ @= Code parsing subroutine
+
+ return CodeReturn{ .content = content.toOwnedSlice(), .index = i + 1 };
+}
+@.
+
+The latter function takes as arguments the list of lines and the allocator similarly to the main function, but it is also passed the index of the current line being processed, and returns the line at which the main function should resume parsing after the code section is parsed. It thus returns a struct consisting of the contents of the code section and the next line number index, as follows.
+
+@+ Parsing functions
+const CodeReturn = struct {
+ content: []const Content,
+ index: u32,
+};
+@.
+
+The main parsing routine iterates over the list of lines, adding code sections where they occur, and otherwise ignoring text sections. If a section end command is encountered in the absence of a preceding starting command, an error is returned.
+
+@: Main parsing routine
+var i: u32 = 0;
+while (i < lines.len) {
+ const line = lines[i];
+ if (std.mem.startsWith(u8, line, k_start)) {
+ @= Add new section
+ } else if (std.mem.startsWith(u8, line, k_add)) {
+ @= Append to section
+ } else if (std.mem.eql(u8, line, k_end)) {
+ log(.err, "line {d}: unexpected section end", .{i + 1});
+ return error.UnexpectedEnd;
+ } else {
+ i += 1;
+ }
+}
+@.
+
+To add a new section, the name (consisting of everything after the starting token) is first retrieved from the starting command. Then the code parsing subroutine is called, beginning at the line after the starting command, and it returns the resulting code section (`section.content`) and the next line at which to resume parsing (`section.index`). The code section is appended to the section list, and the parsing routine continues at the provided index.
+
+@: Add new section
+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;
+@.
+
+To append to an existing section, the section name and the code section contents to be appended are retrieved as above. The index of the section is located, along with its address within the section list. Next, the new contents of the section are created by concatenating the old contents with the newly parsed code section contents. The section list is then updated to point to the new section contents, and the parsing routine continues.
+
+@: Append to section
+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;
+@.
+
+The code parsing subroutine iterates over the list of lines similarly to the main routine. If a starting or appending command is encountered (lacking a matching ending command), an error is raised. Reference commands may be preceded with any amount of whitespace. The loop exits upon encountering an ending command. Otherwise, the line is appended as a literal element.
+
+@: Code parsing subroutine
+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)) {
+ @= Add reference
+ } else if (std.mem.eql(u8, line, k_end)) {
+ break;
+ } else {
+ @= Add literal range
+ }
+}
+@.
+
+To add a reference, the name of the referenced section is retrieved, consisting of the characters following the leading whitespace and the command token. The resulting string is appended to the section contents list, and the parser continues at the next line.
+
+@: Add reference
+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;
+@.
+
+To add a literal range, the parser either updates the end index of the previous literal element, or creates a new literal element if the last element added is a reference. This action of switching on the previous section element must only occur if the section contents list is non-empty, in order to prevent out-of-bounds indexing. Otherwise, the parser unconditionally appends a new literal element to the list. After either case, parsing continues at the next line.
+
+@: Add literal range
+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;
+@.
+
+## Code Generation
+
+We define the source code generation procedure which is split into two functions, consisting of a wrapper function which begins code generation at (the index of) the top-level section, and the main procedure which iterates over the current section contents, recursively resolving section references and appending literal elements to the list of source code lines.
+
+@: Code generation functions
+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| {
+ @= Append literal range
+ },
+ .reference => |name| {
+ @= Resolve reference
+ },
+ };
+
+ log(.debug, "ending section '{s}'", .{section.name});
+ return buffer.toOwnedSlice();
+}
+@.
+
+To append a literal range, the range of lines is simply appended to the buffer.
+
+@: Append literal range
+log(.debug, "adding literal range {d}-{d}", .{ range.start + 1, range.end + 1 });
+try buffer.appendSlice(lines[(range.start)..(range.end + 1)]);
+@.
+
+To resolve a section reference, the function must first check whether the current recursion depth has exceeded the configured limit, and return an error if this occurs. Otherwise, the index of the referenced section is retrieved, its contents are recursively parsed (with an incremented recursion depth), and the resulting source code lines are appended to the buffer.
+
+@: Resolve reference
+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);
+@.
+
+## Text Generation
+
+Finally, we define the text generation function which iterates over the list of lines and produces the literate program text to be passed to an external document processor. In order to keep track of the name of the code section currently being formatted at any given point, the variable `current_name` is continually updated to contain the current name string. Configuration declarations are skipped, and lines which do not contain any formatting commands are appended as they are.
+
+@: Text generation function
+pub fn textgen(lines: [][]const u8, alloc: Allocator) ![][]const u8 {
+ var buffer = std.ArrayList([]const u8).init(alloc);
+ defer buffer.deinit();
+
+ @= Process configuration declarations
+
+ 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)) {
+ @= Format starting command
+ } else if (std.mem.startsWith(u8, line, k_add)) {
+ @= Format appending command
+ } else if (std.mem.startsWith(u8, line, k_end)) {
+ @= Format ending command
+ } else if (std.mem.startsWith(u8, std.mem.trimLeft(u8, line, " \t"), k_ref)) {
+ @= Format reference command
+ } else {
+ try buffer.append(line);
+ }
+ }
+
+ return buffer.toOwnedSlice();
+}
+@.
+
+The formatting strings given by each configuration declaration are first retrieved. If the declaration of the format string for the section appending command is omitted, the format string for the section starting command is used in its place.
+
+@: Process configuration declarations
+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);
+@.
+
+To process a section starting command, the current section name is updated, and the contents of the corresponding formatting command (that is, the segments of the split formatting string) are interspersed with copies of the current section name. The resulting string is then appended to the buffer.
+
+@: Format starting command
+current_name = line[(k_start.len)..];
+try buffer.append(try std.mem.join(alloc, current_name, conf_start));
+@.
+
+Processing a section appending command is performed similarly.
+
+@: Format appending command
+current_name = line[(k_add.len)..];
+try buffer.append(try std.mem.join(alloc, current_name, conf_add));
+@.
+
+Processing a section ending command, however, does not require updating the current section name.
+
+@: Format ending command
+try buffer.append(try std.mem.join(alloc, current_name, conf_end));
+@.
+
+To process a reference command, the index of the reference command keyword is first extracted. Then the formatted reference string is created, to which the reference command line's leading whitespace is prepended (to preserve indentation).
+
+@: Format reference command
+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 }));
+@.
diff --git a/doc/data.html b/doc/data.html
new file mode 100644
index 0000000..676f209
--- /dev/null
+++ b/doc/data.html
@@ -0,0 +1,519 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<style>
+* {box-sizing: border-box;}
+html {
+--fs-sans: Helvetica, "Helvetica Neue", Arial, "San Francisco", sans;
+--fs-mono: jetbrains-mono-regular, Consolas, Menlo, monospace;
+font-size: 16px;
+}
+body {background-color: #111; color: #ddd; margin: 2rem calc(50% - 32rem); font-family: var(--fs-sans); font-size: 1rem; font-style: normal;}
+body * {line-height: 1.5; text-align: justify; text-justify: inter-word;}
+h1,h2 {font-family: var(--fs-sans); margin: 2rem 0 1rem; font-weight: bold; line-height: 1;}
+h1 {font-size: 3rem;}
+h2 {font-size: 2rem;}
+h3 {margin: 1rem; font-size: 1rem; font-weight: normal; font-style: italic;}
+code,code * {font-family: var(--fs-mono); hyphens: none; line-height: 1.25rem;}
+body>pre {background-color: #000; margin: 1rem; padding: 1rem; white-space: pre-wrap; border-radius: 0.25rem;}
+ul,ol {margin: 1rem 0; padding: 0 0 0 2rem;}
+ul ul,ol ol {margin: 0;}
+.ref {font-family: var(--fs-sans); font-style: italic;}
+@media screen and (max-width: 68rem) {body {margin: 2rem calc(50% - 24rem);}}
+@media screen and (max-width: 52rem) {body {margin: 2rem calc(50% - 16rem);}}
+@media screen and (max-width: 36rem) {body {margin: 2rem; hyphens: auto;} h3,body>pre {margin: 1rem 0}}
+</style>
+</head>
+
+<body>
+<h1 id="data.zig">data.zig</h1>
+
+<p>This file contains the various data processing-related constants and functions referenced by the tangling and weaving processes.</p>
+
+<h3 id="section">*:</h3>
+
+<pre><code><span class="ref" >(Imports)</span>
+
+<span class="ref" >(Processing limits)</span>
+
+<span class="ref" >(Formatting keywords)</span>
+
+<span class="ref" >(Configuration keywords)</span>
+
+<span class="ref" >(Data structure types)</span>
+
+<span class="ref" >(Error set)</span>
+
+<span class="ref" >(Line splitting function)</span>
+
+<span class="ref" >(Configuration searching function)</span>
+
+<span class="ref" >(Section searching function)</span>
+
+<span class="ref" >(Parsing functions)</span>
+
+<span class="ref" >(Code generation functions)</span>
+
+<span class="ref" >(Text generation function)</span>
+</code></pre>
+
+<h2 id="constants">Constants</h2>
+
+<p>We first import the standard library and the logging function from <code>log.zig</code>.</p>
+
+<h3 id="imports">Imports:</h3>
+
+<pre><code>const std = @import("std");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+</code></pre>
+
+<p>We define the maximum input file size of 4GiB, and the code generation function&#8217;s maximum recursion depth of 250 nested calls.</p>
+
+<h3 id="processing-limits">Processing limits:</h3>
+
+<pre><code>pub const input_max = 0x1_0000_0000;
+pub const dereference_max = 250;
+</code></pre>
+
+<p>We then define the recognized formatting keywords. These consist of the following:</p>
+
+<ul>
+<li><code>@:</code>, which begins a new code section;</li>
+<li><code>@+</code>, which appends content to a previous code section;</li>
+<li><code>@.</code>, which terminates the definition of a code section;</li>
+<li><code>@=</code>, which creates a reference to another code section;</li>
+<li><code>*</code>, which is a reserved section name representing the root level of the source code.</li>
+</ul>
+
+<h3 id="formatting-keywords">Formatting keywords:</h3>
+
+<pre><code>pub const k_start = "@: ";
+pub const k_add = "@+ ";
+pub const k_end = "@.";
+pub const k_ref = "@= ";
+pub const k_root = "*";
+</code></pre>
+
+<p>We similarly define the recognized configuration keywords, consisting of:</p>
+
+<ul>
+<li><code>@start</code>, which defines the leading formatted code delimiter when beginning new sections;</li>
+<li><code>@add</code>, which defines the leading formatted code delimiter when appending to existing sections;</li>
+<li><code>@end</code>, which defines the trailing formatted code delimiter;</li>
+<li><code>@ref</code>, which defines the format for section references;</li>
+<li><code>@@</code>, which is the escape sequence representing the current section name;</li>
+<li><code>\n</code>, which is the escape sequence representing a newline.</li>
+</ul>
+
+<h3 id="configuration-keywords">Configuration keywords:</h3>
+
+<pre><code>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";
+</code></pre>
+
+<p>We then define the data structure used for parsing the input into code sections, described as follows:</p>
+
+<ul>
+<li>The overall structure of the file is an array of <code>Section</code>s.</li>
+<li>A <code>Section</code> consists of the section name and an array of <code>Content</code> elements.</li>
+<li>A <code>Content</code> element may be either a range of literal lines of code or a reference to another section.</li>
+<li>A <code>LineRange</code> is a pair of integers indicating the starting and ending line numbers of the section.</li>
+</ul>
+
+<h3 id="data-structure-types">Data structure types:</h3>
+
+<pre><code>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,
+};
+</code></pre>
+
+<p>We also define the set of errors which may be encountered by the various processing functions, consisting of:</p>
+
+<ul>
+<li>Unexpected section start commands,</li>
+<li>Unexpected section end commands, </li>
+<li>Recursive dereferencing exceeding the specified depth limit,</li>
+<li>References to nonexistent section names or configuration commands.</li>
+</ul>
+
+<h3 id="error-set">Error set:</h3>
+
+<pre><code>pub const Errors = error{
+ UnexpectedStart,
+ UnexpectedEnd,
+ DereferenceLimit,
+ NotFound,
+};
+</code></pre>
+
+<h2 id="preprocessing-searching">Preprocessing &#38; Searching</h2>
+
+<p>The line splitting function is defined, which operates on a buffer as follows.</p>
+
+<h3 id="line-splitting-function">Line splitting function:</h3>
+
+<pre><code>pub fn split_lines(file: []const u8, alloc: Allocator) ![][]const u8 {
+ var buffer = std.ArrayList([]const u8).init(alloc);
+ defer buffer.deinit();
+
+ <span class="ref" >(Split file at each newline)</span>
+
+ return buffer.toOwnedSlice();
+}
+</code></pre>
+
+<p>The function simply iteratively splits the file at each newline, and appends each resulting line to the buffer.</p>
+
+<h3 id="split-file-at-each-newline">Split file at each newline:</h3>
+
+<pre><code>var iterator = std.mem.split(u8, file, "\n");
+while (iterator.next()) |line| {
+ try buffer.append(line);
+}
+</code></pre>
+
+<p>In addition, the final empty line created by the trailing newline at the end of the file (inserted automatically by some text editors) is removed, if it exists. This may only be performed if the file is non-empty, to avoid out-of-bounds indexing.</p>
+
+<h3 id="split-file-at-each-newline-1">+ Split file at each newline:</h3>
+
+<pre><code>if ((buffer.items.len &#62; 0) and std.mem.eql(u8, buffer.items[buffer.items.len - 1], "")) {
+ _ = buffer.pop();
+}
+</code></pre>
+
+<p>We define the configuration command searching function, which returns a list containing the segments of the split format string. The function will return from within the for loop if the declaration is found, otherwise an error is reported.</p>
+
+<h3 id="configuration-searching-function">Configuration searching function:</h3>
+
+<pre><code>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 &#39;{s}&#39; not found", .{std.mem.trimRight(u8, key, " \t")});
+ return error.NotFound;
+}
+
+<span class="ref" >(Auxiliary formatting function)</span>
+</code></pre>
+
+<p>If the declaration is found, its contained format string is split along instances of the section name escape sequence, and each substring has its instances of the newline escape sequence replaced with a literal newline.</p>
+
+<h3 id="auxiliary-formatting-function">Auxiliary formatting function:</h3>
+
+<pre><code>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();
+}
+</code></pre>
+
+<p>We define the code section searching function, which returns the index (into the section list) of the first section with a matching name, or returns an error if none exist.</p>
+
+<h3 id="section-searching-function">Section searching function:</h3>
+
+<pre><code>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 &#39;{s}&#39; not found", .{name});
+ return error.NotFound;
+}
+&#47;&#47; TODO return last match instead?
+</code></pre>
+
+<h2 id="parsing">Parsing</h2>
+
+<p>We then define the parsing functions, consisting of the main <code>parse</code> function which builds the list of <code>Section</code>s, and its auxiliary <code>parse_code</code> subroutine which builds the contents of each <code>CodeSection</code>.</p>
+
+<h3 id="parsing-functions">Parsing functions:</h3>
+
+<pre><code>pub fn parse(lines: [][]const u8, alloc: Allocator) ![]Section {
+ var sections = std.ArrayList(Section).init(alloc);
+ defer sections.deinit();
+
+ <span class="ref" >(Main parsing routine)</span>
+
+ return sections.toOwnedSlice();
+}
+
+fn parse_code(lines: [][]const u8, index: u32, alloc: Allocator) !CodeReturn {
+ var content = std.ArrayList(Content).init(alloc);
+ defer content.deinit();
+
+ <span class="ref" >(Code parsing subroutine)</span>
+
+ return CodeReturn{ .content = content.toOwnedSlice(), .index = i + 1 };
+}
+</code></pre>
+
+<p>The latter function takes as arguments the list of lines and the allocator similarly to the main function, but it is also passed the index of the current line being processed, and returns the line at which the main function should resume parsing after the code section is parsed. It thus returns a struct consisting of the contents of the code section and the next line number index, as follows.</p>
+
+<h3 id="parsing-functions-1">+ Parsing functions:</h3>
+
+<pre><code>const CodeReturn = struct {
+ content: []const Content,
+ index: u32,
+};
+</code></pre>
+
+<p>The main parsing routine iterates over the list of lines, adding code sections where they occur, and otherwise ignoring text sections. If a section end command is encountered in the absence of a preceding starting command, an error is returned.</p>
+
+<h3 id="main-parsing-routine">Main parsing routine:</h3>
+
+<pre><code>var i: u32 = 0;
+while (i &#60; lines.len) {
+ const line = lines[i];
+ if (std.mem.startsWith(u8, line, k_start)) {
+ <span class="ref" >(Add new section)</span>
+ } else if (std.mem.startsWith(u8, line, k_add)) {
+ <span class="ref" >(Append to section)</span>
+ } else if (std.mem.eql(u8, line, k_end)) {
+ log(.err, "line {d}: unexpected section end", .{i + 1});
+ return error.UnexpectedEnd;
+ } else {
+ i += 1;
+ }
+}
+</code></pre>
+
+<p>To add a new section, the name (consisting of everything after the starting token) is first retrieved from the starting command. Then the code parsing subroutine is called, beginning at the line after the starting command, and it returns the resulting code section (<code>section.content</code>) and the next line at which to resume parsing (<code>section.index</code>). The code section is appended to the section list, and the parsing routine continues at the provided index.</p>
+
+<h3 id="add-new-section">Add new section:</h3>
+
+<pre><code>const name = line[(k_start.len)..];
+log(.debug, "({d}) starting section &#39;{s}&#39;", .{ 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 &#39;{s}&#39;", .{ section.index, name });
+i = section.index;
+</code></pre>
+
+<p>To append to an existing section, the section name and the code section contents to be appended are retrieved as above. The index of the section is located, along with its address within the section list. Next, the new contents of the section are created by concatenating the old contents with the newly parsed code section contents. The section list is then updated to point to the new section contents, and the parsing routine continues.</p>
+
+<h3 id="append-to-section">Append to section:</h3>
+
+<pre><code>const name = line[(k_add.len)..];
+log(.debug, "({d}) appending to section &#39;{s}&#39;", .{ i + 1, name });
+
+const section = try parse_code(lines, i + 1, alloc);
+const index = try search(sections.items, name);
+const old = &#38;sections.items[index];
+const new = try std.mem.concat(alloc, Content, &#38;[_][]const Content{ old.*.content, section.content });
+old.*.content = new;
+
+log(.debug, "({d}) ending section &#39;{s}&#39;", .{ section.index, name });
+i = section.index;
+</code></pre>
+
+<p>The code parsing subroutine iterates over the list of lines similarly to the main routine. If a starting or appending command is encountered (lacking a matching ending command), an error is raised. Reference commands may be preceded with any amount of whitespace. The loop exits upon encountering an ending command. Otherwise, the line is appended as a literal element.</p>
+
+<h3 id="code-parsing-subroutine">Code parsing subroutine:</h3>
+
+<pre><code>var i = index;
+while (i &#60; 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)) {
+ <span class="ref" >(Add reference)</span>
+ } else if (std.mem.eql(u8, line, k_end)) {
+ break;
+ } else {
+ <span class="ref" >(Add literal range)</span>
+ }
+}
+</code></pre>
+
+<p>To add a reference, the name of the referenced section is retrieved, consisting of the characters following the leading whitespace and the command token. The resulting string is appended to the section contents list, and the parser continues at the next line.</p>
+
+<h3 id="add-reference">Add reference:</h3>
+
+<pre><code>const ref_name = std.mem.trimLeft(u8, line, " \t")[(k_ref.len)..];
+try content.append(.{ .reference = ref_name });
+log(.debug, "({d}) \tappended reference &#39;{s}&#39;", .{ i + 1, ref_name });
+i += 1;
+</code></pre>
+
+<p>To add a literal range, the parser either updates the end index of the previous literal element, or creates a new literal element if the last element added is a reference. This action of switching on the previous section element must only occur if the section contents list is non-empty, in order to prevent out-of-bounds indexing. Otherwise, the parser unconditionally appends a new literal element to the list. After either case, parsing continues at the next line.</p>
+
+<h3 id="add-literal-range">Add literal range:</h3>
+
+<pre><code>if (content.items.len &#62; 0) {
+ switch (content.items[content.items.len - 1]) {
+ .literal =&#62; |*range| {
+ range.*.end = i;
+ },
+ .reference =&#62; {
+ 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;
+</code></pre>
+
+<h2 id="code-generation">Code Generation</h2>
+
+<p>We define the source code generation procedure which is split into two functions, consisting of a wrapper function which begins code generation at (the index of) the top-level section, and the main procedure which iterates over the current section contents, recursively resolving section references and appending literal elements to the list of source code lines.</p>
+
+<h3 id="code-generation-functions">Code generation functions:</h3>
+
+<pre><code>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 &#39;{s}&#39;", .{section.name});
+ for (section.content) |content| switch (content) {
+ .literal =&#62; |range| {
+ <span class="ref" >(Append literal range)</span>
+ },
+ .reference =&#62; |name| {
+ <span class="ref" >(Resolve reference)</span>
+ },
+ };
+
+ log(.debug, "ending section &#39;{s}&#39;", .{section.name});
+ return buffer.toOwnedSlice();
+}
+</code></pre>
+
+<p>To append a literal range, the range of lines is simply appended to the buffer.</p>
+
+<h3 id="append-literal-range">Append literal range:</h3>
+
+<pre><code>log(.debug, "adding literal range {d}-{d}", .{ range.start + 1, range.end + 1 });
+try buffer.appendSlice(lines[(range.start)..(range.end + 1)]);
+</code></pre>
+
+<p>To resolve a section reference, the function must first check whether the current recursion depth has exceeded the configured limit, and return an error if this occurs. Otherwise, the index of the referenced section is retrieved, its contents are recursively parsed (with an incremented recursion depth), and the resulting source code lines are appended to the buffer.</p>
+
+<h3 id="resolve-reference">Resolve reference:</h3>
+
+<pre><code>if (depth &#62; 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);
+</code></pre>
+
+<h2 id="text-generation">Text Generation</h2>
+
+<p>Finally, we define the text generation function which iterates over the list of lines and produces the literate program text to be passed to an external document processor. In order to keep track of the name of the code section currently being formatted at any given point, the variable <code>current_name</code> is continually updated to contain the current name string. Configuration declarations are skipped, and lines which do not contain any formatting commands are appended as they are.</p>
+
+<h3 id="text-generation-function">Text generation function:</h3>
+
+<pre><code>pub fn textgen(lines: [][]const u8, alloc: Allocator) ![][]const u8 {
+ var buffer = std.ArrayList([]const u8).init(alloc);
+ defer buffer.deinit();
+
+ <span class="ref" >(Process configuration declarations)</span>
+
+ 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)) {
+ <span class="ref" >(Format starting command)</span>
+ } else if (std.mem.startsWith(u8, line, k_add)) {
+ <span class="ref" >(Format appending command)</span>
+ } else if (std.mem.startsWith(u8, line, k_end)) {
+ <span class="ref" >(Format ending command)</span>
+ } else if (std.mem.startsWith(u8, std.mem.trimLeft(u8, line, " \t"), k_ref)) {
+ <span class="ref" >(Format reference command)</span>
+ } else {
+ try buffer.append(line);
+ }
+ }
+
+ return buffer.toOwnedSlice();
+}
+</code></pre>
+
+<p>The formatting strings given by each configuration declaration are first retrieved. If the declaration of the format string for the section appending command is omitted, the format string for the section starting command is used in its place.</p>
+
+<h3 id="process-configuration-declarations">Process configuration declarations:</h3>
+
+<pre><code>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);
+</code></pre>
+
+<p>To process a section starting command, the current section name is updated, and the contents of the corresponding formatting command (that is, the segments of the split formatting string) are interspersed with copies of the current section name. The resulting string is then appended to the buffer.</p>
+
+<h3 id="format-starting-command">Format starting command:</h3>
+
+<pre><code>current_name = line[(k_start.len)..];
+try buffer.append(try std.mem.join(alloc, current_name, conf_start));
+</code></pre>
+
+<p>Processing a section appending command is performed similarly.</p>
+
+<h3 id="format-appending-command">Format appending command:</h3>
+
+<pre><code>current_name = line[(k_add.len)..];
+try buffer.append(try std.mem.join(alloc, current_name, conf_add));
+</code></pre>
+
+<p>Processing a section ending command, however, does not require updating the current section name.</p>
+
+<h3 id="format-ending-command">Format ending command:</h3>
+
+<pre><code>try buffer.append(try std.mem.join(alloc, current_name, conf_end));
+</code></pre>
+
+<p>To process a reference command, the index of the reference command keyword is first extracted. Then the formatted reference string is created, to which the reference command line&#8217;s leading whitespace is prepended (to preserve indentation). </p>
+
+<h3 id="format-reference-command">Format reference command:</h3>
+
+<pre><code>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, &#38;[_][]const u8{ line[0..start], ref }));
+</code></pre>
+</body>
+</html> \ No newline at end of file
diff --git a/doc/header-html b/doc/header-html
new file mode 100644
index 0000000..449b4c0
--- /dev/null
+++ b/doc/header-html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<style>
+* {box-sizing: border-box;}
+html {
+--fs-sans: Helvetica, "Helvetica Neue", Arial, "San Francisco", sans;
+--fs-mono: jetbrains-mono-regular, Consolas, Menlo, monospace;
+font-size: 16px;
+}
+body {background-color: #111; color: #ddd; margin: 2rem calc(50% - 32rem); font-family: var(--fs-sans); font-size: 1rem; font-style: normal;}
+body * {line-height: 1.5; text-align: justify; text-justify: inter-word;}
+h1,h2 {font-family: var(--fs-sans); margin: 2rem 0 1rem; font-weight: bold; line-height: 1;}
+h1 {font-size: 3rem;}
+h2 {font-size: 2rem;}
+h3 {margin: 1rem; font-size: 1rem; font-weight: normal; font-style: italic;}
+code,code * {font-family: var(--fs-mono); hyphens: none; line-height: 1.25rem;}
+body>pre {background-color: #000; margin: 1rem; padding: 1rem; white-space: pre-wrap; border-radius: 0.25rem;}
+ul,ol {margin: 1rem 0; padding: 0 0 0 2rem;}
+ul ul,ol ol {margin: 0;}
+.ref {font-family: var(--fs-sans); font-style: italic;}
+@media screen and (max-width: 68rem) {body {margin: 2rem calc(50% - 24rem);}}
+@media screen and (max-width: 52rem) {body {margin: 2rem calc(50% - 16rem);}}
+@media screen and (max-width: 36rem) {body {margin: 2rem; hyphens: auto;} h3,body>pre {margin: 1rem 0;}}
+</style>
+</head>
+
+<body>
diff --git a/doc/header-lp b/doc/header-lp
new file mode 100644
index 0000000..9689d88
--- /dev/null
+++ b/doc/header-lp
@@ -0,0 +1,4 @@
+@start ### @@:\n```
+@add ### + @@:\n```
+@end ```
+@ref @= @@ =@
diff --git a/doc/log.html b/doc/log.html
new file mode 100644
index 0000000..f82d41e
--- /dev/null
+++ b/doc/log.html
@@ -0,0 +1,102 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<style>
+* {box-sizing: border-box;}
+html {
+--fs-sans: Helvetica, "Helvetica Neue", Arial, "San Francisco", sans;
+--fs-mono: jetbrains-mono-regular, Consolas, Menlo, monospace;
+font-size: 16px;
+}
+body {background-color: #111; color: #ddd; margin: 2rem calc(50% - 32rem); font-family: var(--fs-sans); font-size: 1rem; font-style: normal;}
+body * {line-height: 1.5; text-align: justify; text-justify: inter-word;}
+h1,h2 {font-family: var(--fs-sans); margin: 2rem 0 1rem; font-weight: bold; line-height: 1;}
+h1 {font-size: 3rem;}
+h2 {font-size: 2rem;}
+h3 {margin: 1rem; font-size: 1rem; font-weight: normal; font-style: italic;}
+code,code * {font-family: var(--fs-mono); hyphens: none; line-height: 1.25rem;}
+body>pre {background-color: #000; margin: 1rem; padding: 1rem; white-space: pre-wrap; border-radius: 0.25rem;}
+ul,ol {margin: 1rem 0; padding: 0 0 0 2rem;}
+ul ul,ol ol {margin: 0;}
+.ref {font-family: var(--fs-sans); font-style: italic;}
+@media screen and (max-width: 68rem) {body {margin: 2rem calc(50% - 24rem);}}
+@media screen and (max-width: 52rem) {body {margin: 2rem calc(50% - 16rem);}}
+@media screen and (max-width: 36rem) {body {margin: 2rem; hyphens: auto;} h3,body>pre {margin: 1rem 0}}
+</style>
+</head>
+
+<body>
+<h1 id="log.zig">log.zig</h1>
+
+<p>This file contains a simple logging function. It is a modified version of the example logging function implementation provided in <code>std.log</code>.</p>
+
+<h3 id="section">*:</h3>
+
+<pre><code><span class="ref" >(Imports)</span>
+
+<span class="ref" >(Level setting)</span>
+
+<span class="ref" >(Logging function)</span>
+</code></pre>
+
+<p>We first import the standard library, and the <code>Level</code> type which is an enum representing the possible log levels.</p>
+
+<h3 id="imports">Imports:</h3>
+
+<pre><code>const std = @import("std");
+const Level = std.log.Level;
+</code></pre>
+
+<p>The logging function is structured such that only log messages equal to or above a certain severity threshold will be printed to the console. This threshold can then be globally modified during development. The threshold constant is defined below.</p>
+
+<h3 id="level-setting">Level setting:</h3>
+
+<pre><code>pub const log_level: Level = .warn;
+</code></pre>
+
+<p>We then define the logging function itself, which accepts a <code>Level</code> value and the format string and argument struct to be passed to the inner print function.</p>
+
+<h3 id="logging-function">Logging function:</h3>
+
+<pre><code>pub fn log(
+ comptime level: Level,
+ comptime format: []const u8,
+ args: anytype,
+) void {
+ <span class="ref" >(Compare with level threshold)</span>
+
+ <span class="ref" >(Define message string)</span>
+
+ <span class="ref" >(Print to console)</span>
+}
+</code></pre>
+
+<p>First the comparison against the severity threshold is made. (A lower integer value signifies a higher severity.) If the severity is lower than the threshold, the function immediately exits.</p>
+
+<h3 id="compare-with-level-threshold">Compare with level threshold:</h3>
+
+<pre><code>if (@enumToInt(level) &#62; @enumToInt(log_level)) return;
+</code></pre>
+
+<p>Next the message string is created. The unformatted content of this string is evaluated at compile-time, before being formatted by the print function at runtime. The &#8220;info&#8221; and &#8220;error&#8221; log levels use custom names, whereas all other levels use their default display names.</p>
+
+<h3 id="define-message-string">Define message string:</h3>
+
+<pre><code>const msg = "[" ++ switch (level) {
+ .info =&#62; "ok",
+ .err =&#62; "err",
+ else =&#62; level.asText(),
+} ++ "]\t" ++ format ++ "\n";
+</code></pre>
+
+<p>Finally, the message is printed to the console. If an error is returned by the <code>print()</code> call, the logging function silently exits.</p>
+
+<h3 id="print-to-console">Print to console:</h3>
+
+<pre><code>const stderr = std.io.getStdErr().writer();
+nosuspend stderr.print(msg, args) catch return;
+</code></pre>
+</body>
+</html> \ No newline at end of file
diff --git a/doc/tangle.html b/doc/tangle.html
new file mode 100644
index 0000000..1c26eae
--- /dev/null
+++ b/doc/tangle.html
@@ -0,0 +1,141 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<style>
+* {box-sizing: border-box;}
+html {
+--fs-sans: Helvetica, "Helvetica Neue", Arial, "San Francisco", sans;
+--fs-mono: jetbrains-mono-regular, Consolas, Menlo, monospace;
+font-size: 16px;
+}
+body {background-color: #111; color: #ddd; margin: 2rem calc(50% - 32rem); font-family: var(--fs-sans); font-size: 1rem; font-style: normal;}
+body * {line-height: 1.5; text-align: justify; text-justify: inter-word;}
+h1,h2 {font-family: var(--fs-sans); margin: 2rem 0 1rem; font-weight: bold; line-height: 1;}
+h1 {font-size: 3rem;}
+h2 {font-size: 2rem;}
+h3 {margin: 1rem; font-size: 1rem; font-weight: normal; font-style: italic;}
+code,code * {font-family: var(--fs-mono); hyphens: none; line-height: 1.25rem;}
+body>pre {background-color: #000; margin: 1rem; padding: 1rem; white-space: pre-wrap; border-radius: 0.25rem;}
+ul,ol {margin: 1rem 0; padding: 0 0 0 2rem;}
+ul ul,ol ol {margin: 0;}
+.ref {font-family: var(--fs-sans); font-style: italic;}
+@media screen and (max-width: 68rem) {body {margin: 2rem calc(50% - 24rem);}}
+@media screen and (max-width: 52rem) {body {margin: 2rem calc(50% - 16rem);}}
+@media screen and (max-width: 36rem) {body {margin: 2rem; hyphens: auto;} h3,body>pre {margin: 1rem 0}}
+</style>
+</head>
+
+<body>
+<h1 id="tangle.zig">tangle.zig</h1>
+
+<p>The structure of this file is quite similar to that of <code>weave.zig</code>, only differing in terms of which functions are used to transform the input data.</p>
+
+<h3 id="section">*:</h3>
+
+<pre><code><span class="ref" >(Imports)</span>
+
+pub fn main() !u8 {
+ <span class="ref" >(IO initialization)</span>
+
+ <span class="ref" >(Allocator initialization)</span>
+
+ <span class="ref" >(Read file from stdin)</span>
+
+ <span class="ref" >(Split into lines)</span>
+
+ <span class="ref" >(Parse lines into sections)</span>
+
+ <span class="ref" >(Generate code)</span>
+
+ <span class="ref" >(Write to stdout)</span>
+
+ return 0;
+}
+</code></pre>
+
+<p>First we import the other files containing the core functions.</p>
+
+<h3 id="imports">Imports:</h3>
+
+<pre><code>const std = @import("std");
+const data = @import("data.zig");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+</code></pre>
+
+<p>Within the main procedure, we first initialize the stdin and stdout interfaces.</p>
+
+<h3 id="io-initialization">IO initialization:</h3>
+
+<pre><code>const stdin = std.io.getStdIn();
+const stdout = std.io.getStdOut();
+</code></pre>
+
+<p>We then initialize the allocator, deferring its deinitialization to the end of the process. Since the overall memory usage pattern is one in which all resources may be freed at once, the arena allocator is the most appropriate choice for this program.</p>
+
+<h3 id="allocator-initialization">Allocator initialization:</h3>
+
+<pre><code>var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+var alloc = arena.allocator();
+defer arena.deinit();
+</code></pre>
+
+<p>The input file is then read from stdin. In the case of input exceeding the maximum permitted file size, the program may report the error and exit normally. All other errors which may be returned are memory allocation failures and should thus yield control to the panic handler.</p>
+
+<h3 id="read-file-from-stdin">Read file from stdin:</h3>
+
+<pre><code>const input = stdin.reader().readAllAlloc(alloc, data.input_max) catch |err| switch (err) {
+ error.StreamTooLong =&#62; {
+ log(.err, "input too large (maximum {})", .{std.fmt.fmtIntSizeBin(data.input_max)});
+ return 1;
+ },
+ else =&#62; |e| return e,
+};
+</code></pre>
+
+<p>We then pass the input into the line splitting function, creating an array of strings.</p>
+
+<h3 id="split-into-lines">Split into lines:</h3>
+
+<pre><code>const lines = try data.split_lines(input, alloc);
+</code></pre>
+
+<p>The lines are then passed into the parsing function, which may return a parsing error. Logging such errors is handled by the function itself, and thus the errors are handled here solely by exiting.</p>
+
+<h3 id="parse-lines-into-sections">Parse lines into sections:</h3>
+
+<pre><code>const sections = data.parse(lines, alloc) catch |err| switch (err) {
+ error.UnexpectedStart,
+ error.UnexpectedEnd =&#62; {
+ return 1;
+ },
+ else =&#62; |e| return e,
+};
+</code></pre>
+
+<p>The code file is then generated. This entails resolving references to section names, which may return an error, handled by exiting as above.</p>
+
+<h3 id="generate-code">Generate code:</h3>
+
+<pre><code>const code = data.codegen(lines, sections, alloc) catch |err| switch (err) {
+ error.DereferenceLimit,
+ error.NotFound =&#62; {
+ return 1;
+ },
+ else =&#62; |e| return e,
+};
+</code></pre>
+
+<p>Finally, the lines of the code file are written to stdout, separated by newlines.</p>
+
+<h3 id="write-to-stdout">Write to stdout:</h3>
+
+<pre><code>for (code) |line| {
+ try stdout.writer().print("{s}\n", .{line});
+}
+</code></pre>
+</body>
+</html> \ No newline at end of file
diff --git a/doc/weave.html b/doc/weave.html
new file mode 100644
index 0000000..6aaf6c8
--- /dev/null
+++ b/doc/weave.html
@@ -0,0 +1,125 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<style>
+* {box-sizing: border-box;}
+html {
+--fs-sans: Helvetica, "Helvetica Neue", Arial, "San Francisco", sans;
+--fs-mono: jetbrains-mono-regular, Consolas, Menlo, monospace;
+font-size: 16px;
+}
+body {background-color: #111; color: #ddd; margin: 2rem calc(50% - 32rem); font-family: var(--fs-sans); font-size: 1rem; font-style: normal;}
+body * {line-height: 1.5; text-align: justify; text-justify: inter-word;}
+h1,h2 {font-family: var(--fs-sans); margin: 2rem 0 1rem; font-weight: bold; line-height: 1;}
+h1 {font-size: 3rem;}
+h2 {font-size: 2rem;}
+h3 {margin: 1rem; font-size: 1rem; font-weight: normal; font-style: italic;}
+code,code * {font-family: var(--fs-mono); hyphens: none; line-height: 1.25rem;}
+body>pre {background-color: #000; margin: 1rem; padding: 1rem; white-space: pre-wrap; border-radius: 0.25rem;}
+ul,ol {margin: 1rem 0; padding: 0 0 0 2rem;}
+ul ul,ol ol {margin: 0;}
+.ref {font-family: var(--fs-sans); font-style: italic;}
+@media screen and (max-width: 68rem) {body {margin: 2rem calc(50% - 24rem);}}
+@media screen and (max-width: 52rem) {body {margin: 2rem calc(50% - 16rem);}}
+@media screen and (max-width: 36rem) {body {margin: 2rem; hyphens: auto;} h3,body>pre {margin: 1rem 0}}
+</style>
+</head>
+
+<body>
+<h1 id="weave.zig">weave.zig</h1>
+
+<p>The structure of this file is quite similar to that of <code>tangle.zig</code>, only differing in terms of which functions are used to transform the input data.</p>
+
+<h3 id="section">*:</h3>
+
+<pre><code><span class="ref" >(Imports)</span>
+
+pub fn main() !u8 {
+ <span class="ref" >(IO initialization)</span>
+
+ <span class="ref" >(Allocator initialization)</span>
+
+ <span class="ref" >(Read file from stdin)</span>
+
+ <span class="ref" >(Split into lines)</span>
+
+ <span class="ref" >(Generate text)</span>
+
+ <span class="ref" >(Write to stdout)</span>
+
+ return 0;
+}
+</code></pre>
+
+<p>First we import the other files containing the core functions.</p>
+
+<h3 id="imports">Imports:</h3>
+
+<pre><code>const std = @import("std");
+const data = @import("data.zig");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+</code></pre>
+
+<p>Within the main procedure, we first initialize the stdin and stdout interfaces.</p>
+
+<h3 id="io-initialization">IO initialization:</h3>
+
+<pre><code>const stdin = std.io.getStdIn();
+const stdout = std.io.getStdOut();
+</code></pre>
+
+<p>We then initialize the allocator, deferring its deinitialization to the end of the process. Since the overall memory usage pattern is one in which all resources may be freed at once, the arena allocator is the most appropriate choice for this program.</p>
+
+<h3 id="allocator-initialization">Allocator initialization:</h3>
+
+<pre><code>var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+var alloc = arena.allocator();
+defer arena.deinit();
+</code></pre>
+
+<p>The input file is then read from stdin. In the case of input exceeding the maximum permitted file size, the program may report the error and exit normally. All other errors which may be returned are memory allocation failures and should thus yield control to the panic handler.</p>
+
+<h3 id="read-file-from-stdin">Read file from stdin:</h3>
+
+<pre><code>const input = stdin.reader().readAllAlloc(alloc, data.input_max) catch |err| switch (err) {
+ error.StreamTooLong =&#62; {
+ log(.err, "input too large (maximum {})", .{std.fmt.fmtIntSizeBin(data.input_max)});
+ return 1;
+ },
+ else =&#62; |e| return e,
+};
+</code></pre>
+
+<p>We then pass the input into the line splitting function, creating an array of strings.</p>
+
+<h3 id="split-into-lines">Split into lines:</h3>
+
+<pre><code>const lines = try data.split_lines(input, alloc);
+</code></pre>
+
+<p>The text file is then generated. This entails searching for the configuration declarations, which may fail and thus return an error. Logging such errors is handled by the function itself, and thus the errors are handled here solely by exiting.</p>
+
+<h3 id="generate-text">Generate text:</h3>
+
+<pre><code>const text = data.textgen(lines, alloc) catch |err| switch (err) {
+ error.NotFound =&#62; {
+ return 1;
+ },
+ else =&#62; |e| return e,
+};
+</code></pre>
+
+<p>Finally, the lines of the text file are written to stdout, separated by newlines.</p>
+
+<h3 id="write-to-stdout">Write to stdout:</h3>
+
+<pre><code>for (text) |line| {
+ try stdout.writer().print("{s}\n", .{line});
+}
+</code></pre>
+</body>
+</html> \ No newline at end of file
diff --git a/gen-docs.sh b/gen-docs.sh
new file mode 100755
index 0000000..12ab3ac
--- /dev/null
+++ b/gen-docs.sh
@@ -0,0 +1,9 @@
+#!/bin/sh -e
+
+for file in "data" "log" "tangle" "weave" ; do
+ cp "doc/header-html" "doc/${file}.html" ;
+ cat "doc/header-lp" "${file}.lp" | ./weave | lowdown -Thtml >> "doc/${file}.html" ;
+ printf "</body>\n</html>" >> "doc/${file}.html" ;
+ sed 's/\(.*\)@= \(.*\) =@/\1<span class="ref" >(\2)<\/span>/1' -i'' "doc/${file}.html" ;
+ echo "[ok] generated text '${file}'" ;
+done
diff --git a/log.lp b/log.lp
new file mode 100644
index 0000000..1809322
--- /dev/null
+++ b/log.lp
@@ -0,0 +1,63 @@
+# log.zig
+
+This file contains a simple logging function. It is a modified version of the example logging function implementation provided in `std.log`.
+
+@: *
+@= Imports
+
+@= Level setting
+
+@= Logging function
+@.
+
+We first import the standard library, and the `Level` type which is an enum representing the possible log levels.
+
+@: Imports
+const std = @import("std");
+const Level = std.log.Level;
+@.
+
+The logging function is structured such that only log messages equal to or above a certain severity threshold will be printed to the console. This threshold can then be globally modified during development. The threshold constant is defined below.
+
+@: Level setting
+pub const log_level: Level = .warn;
+@.
+
+We then define the logging function itself, which accepts a `Level` value and the format string and argument struct to be passed to the inner print function.
+
+@: Logging function
+pub fn log(
+ comptime level: Level,
+ comptime format: []const u8,
+ args: anytype,
+) void {
+ @= Compare with level threshold
+
+ @= Define message string
+
+ @= Print to console
+}
+@.
+
+First the comparison against the severity threshold is made. (A lower integer value signifies a higher severity.) If the severity is lower than the threshold, the function immediately exits.
+
+@: Compare with level threshold
+if (@enumToInt(level) > @enumToInt(log_level)) return;
+@.
+
+Next the message string is created. The unformatted content of this string is evaluated at compile-time, before being formatted by the print function at runtime. The "info" and "error" log levels use custom names, whereas all other levels use their default display names.
+
+@: Define message string
+const msg = "[" ++ switch (level) {
+ .info => "ok",
+ .err => "err",
+ else => level.asText(),
+} ++ "]\t" ++ format ++ "\n";
+@.
+
+Finally, the message is printed to the console. If an error is returned by the `print()` call, the logging function silently exits.
+
+@: Print to console
+const stderr = std.io.getStdErr().writer();
+nosuspend stderr.print(msg, args) catch return;
+@.
diff --git a/misc/clean.sh b/misc/clean.sh
new file mode 100755
index 0000000..0be1003
--- /dev/null
+++ b/misc/clean.sh
@@ -0,0 +1,3 @@
+#!/bin/sh -e
+
+rm -f *.aux *.gz *.log *.out
diff --git a/misc/tangle-b b/misc/tangle-b
new file mode 100755
index 0000000..e9f3671
--- /dev/null
+++ b/misc/tangle-b
Binary files differ
diff --git a/misc/test-tex b/misc/test-tex
new file mode 100644
index 0000000..9ba8dfc
--- /dev/null
+++ b/misc/test-tex
@@ -0,0 +1,53 @@
+@start \vspace{-0.5\unit}{\footnotesize\italic{@@:}}\vspace{-1\unit}\n\begin{codeblock}
+@add \vspace{-0.5\unit}{\footnotesize\italic{+ @@:}}\vspace{-1\unit}\n\begin{codeblock}
+@end \end{codeblock}
+@ref §\textrm{\italic{(@@)}}§
+
+\documentclass[debug]{microstructure}
+
+\author{DistressNetwork°}
+\lstset{escapechar=§}
+
+\begin{document}
+
+\resetv{-3\unit}\section{Hello world in Zig}
+
+This is an example `hello world' program.
+
+@: *
+@= Imports
+
+pub fn main() !void {
+ @= Print
+}
+@.
+
+We first import the standard library.
+
+@: Imports
+const std = @import("std");
+@.
+
+Then we print the desired string.
+
+@: Print
+std.debug.print("Hello world\n", .{});
+@.
+
+We can also print using a format string.
+
+@+ Print
+std.debug.print("Hello world {s}\n", .{"again"});
+@.
+
+Or we can use a \mono{writer()} method and write to stdout directly.
+
+@+ Imports
+const write = std.io.getStdOut().writer().write;
+@.
+
+@+ Print
+_ = try write("Hello stdout\n");
+@.
+
+\end{document}
diff --git a/misc/test-tex.pdf b/misc/test-tex.pdf
new file mode 100644
index 0000000..8ada78b
--- /dev/null
+++ b/misc/test-tex.pdf
Binary files differ
diff --git a/misc/test.aux b/misc/test.aux
new file mode 100644
index 0000000..a6caf53
--- /dev/null
+++ b/misc/test.aux
@@ -0,0 +1,27 @@
+\relax
+\providecommand\zref@newlabel[2]{}
+\providecommand\hyper@newdestlabel[2]{}
+\providecommand\HyperFirstAtBeginDocument{\AtBeginDocument}
+\HyperFirstAtBeginDocument{\ifx\hyper@anchor\@undefined
+\global\let\oldcontentsline\contentsline
+\gdef\contentsline#1#2#3#4{\oldcontentsline{#1}{#2}{#3}}
+\global\let\oldnewlabel\newlabel
+\gdef\newlabel#1#2{\newlabelxx{#1}#2}
+\gdef\newlabelxx#1#2#3#4#5#6{\oldnewlabel{#1}{{#2}{#3}}}
+\AtEndDocument{\ifx\hyper@anchor\@undefined
+\let\contentsline\oldcontentsline
+\let\newlabel\oldnewlabel
+\fi}
+\fi}
+\global\let\hyper@last\relax
+\gdef\HyperFirstAtBeginDocument#1{#1}
+\providecommand*\HyPL@Entry[1]{}
+\HyPL@Entry{0<</S/D>>}
+\pgfsyspdfmark {pgfid1}{0}{0}
+\zref@newlabel{ufgrid@vpointtextupperleft}{\posy{47362867}}
+\zref@newlabel{ufgrid@vpoint1-vskip}{\ufgridvskip{134019}}
+\zref@newlabel{ufgrid@vpoint1}{\posx{4736286}\posy{45784107}\ufgridvskip{134019}\abspage{1}\pagecnt{1}}
+\newlabel{LastPage}{{}{1}{}{page.1}{}}
+\xdef\lastpage@lastpage{1}
+\xdef\lastpage@lastpageHy{1}
+\gdef \@abspage@last{1}
diff --git a/misc/test.log b/misc/test.log
new file mode 100644
index 0000000..a5dc140
--- /dev/null
+++ b/misc/test.log
@@ -0,0 +1,916 @@
+This is XeTeX, Version 3.141592653-2.6-0.999993 (TeX Live 2021/Arch Linux) (preloaded format=xelatex 2022.3.28) 29 MAR 2022 16:54
+entering extended mode
+ restricted \write18 enabled.
+ %&-line parsing enabled.
+**test.tex
+(./test.tex
+LaTeX2e <2021-11-15> patch level 1
+L3 programming layer <2022-02-24> (/etc/texmf/tex/latex/microstructure.cls
+Document Class: microstructure
+(/usr/share/texmf-dist/tex/latex/geometry/geometry.sty
+Package: geometry 2020/01/02 v5.9 Page Geometry
+
+(/usr/share/texmf-dist/tex/latex/graphics/keyval.sty
+Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
+\KV@toks@=\toks16
+)
+(/usr/share/texmf-dist/tex/generic/iftex/ifvtex.sty
+Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead.
+
+(/usr/share/texmf-dist/tex/generic/iftex/iftex.sty
+Package: iftex 2022/02/03 v1.0f TeX engine tests
+))
+\Gm@cnth=\count181
+\Gm@cntv=\count182
+\c@Gm@tempcnt=\count183
+\Gm@bindingoffset=\dimen138
+\Gm@wd@mp=\dimen139
+\Gm@odd@mp=\dimen140
+\Gm@even@mp=\dimen141
+\Gm@layoutwidth=\dimen142
+\Gm@layoutheight=\dimen143
+\Gm@layouthoffset=\dimen144
+\Gm@layoutvoffset=\dimen145
+\Gm@dimlist=\toks17
+)
+(/usr/share/texmf-dist/tex/latex/graphics/graphicx.sty
+Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
+
+(/usr/share/texmf-dist/tex/latex/graphics/graphics.sty
+Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR)
+
+(/usr/share/texmf-dist/tex/latex/graphics/trig.sty
+Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
+)
+(/usr/share/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
+File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
+)
+Package graphics Info: Driver file: xetex.def on input line 107.
+
+(/usr/share/texmf-dist/tex/latex/graphics-def/xetex.def
+File: xetex.def 2021/03/18 v5.0k Graphics/color driver for xetex
+))
+\Gin@req@height=\dimen146
+\Gin@req@width=\dimen147
+)
+(/usr/share/texmf-dist/tex/latex/amsmath/amsmath.sty
+Package: amsmath 2021/10/15 v2.17l AMS math features
+\@mathmargin=\skip47
+
+For additional information on amsmath, use the `?' option.
+(/usr/share/texmf-dist/tex/latex/amsmath/amstext.sty
+Package: amstext 2021/08/26 v2.01 AMS text
+
+(/usr/share/texmf-dist/tex/latex/amsmath/amsgen.sty
+File: amsgen.sty 1999/11/30 v2.0 generic functions
+\@emptytoks=\toks18
+\ex@=\dimen148
+))
+(/usr/share/texmf-dist/tex/latex/amsmath/amsbsy.sty
+Package: amsbsy 1999/11/29 v1.2d Bold Symbols
+\pmbraise@=\dimen149
+)
+(/usr/share/texmf-dist/tex/latex/amsmath/amsopn.sty
+Package: amsopn 2021/08/26 v2.02 operator names
+)
+\inf@bad=\count184
+LaTeX Info: Redefining \frac on input line 234.
+\uproot@=\count185
+\leftroot@=\count186
+LaTeX Info: Redefining \overline on input line 399.
+\classnum@=\count187
+\DOTSCASE@=\count188
+LaTeX Info: Redefining \ldots on input line 496.
+LaTeX Info: Redefining \dots on input line 499.
+LaTeX Info: Redefining \cdots on input line 620.
+\Mathstrutbox@=\box50
+\strutbox@=\box51
+\big@size=\dimen150
+LaTeX Font Info: Redeclaring font encoding OML on input line 743.
+LaTeX Font Info: Redeclaring font encoding OMS on input line 744.
+\macc@depth=\count189
+\c@MaxMatrixCols=\count190
+\dotsspace@=\muskip16
+\c@parentequation=\count191
+\dspbrk@lvl=\count192
+\tag@help=\toks19
+\row@=\count193
+\column@=\count194
+\maxfields@=\count195
+\andhelp@=\toks20
+\eqnshift@=\dimen151
+\alignsep@=\dimen152
+\tagshift@=\dimen153
+\tagwidth@=\dimen154
+\totwidth@=\dimen155
+\lineht@=\dimen156
+\@envbody=\toks21
+\multlinegap=\skip48
+\multlinetaggap=\skip49
+\mathdisplay@stack=\toks22
+LaTeX Info: Redefining \[ on input line 2938.
+LaTeX Info: Redefining \] on input line 2939.
+)
+(/usr/share/texmf-dist/tex/latex/amsfonts/amssymb.sty
+Package: amssymb 2013/01/14 v3.01 AMS font symbols
+
+(/usr/share/texmf-dist/tex/latex/amsfonts/amsfonts.sty
+Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
+\symAMSa=\mathgroup4
+\symAMSb=\mathgroup5
+LaTeX Font Info: Redeclaring math symbol \hbar on input line 98.
+LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold'
+(Font) U/euf/m/n --> U/euf/b/n on input line 106.
+))
+(/usr/share/texmf-dist/tex/generic/ulem/ulem.sty
+\UL@box=\box52
+\UL@hyphenbox=\box53
+\UL@skip=\skip50
+\UL@hook=\toks23
+\UL@height=\dimen157
+\UL@pe=\count196
+\UL@pixel=\dimen158
+\ULC@box=\box54
+Package: ulem 2019/11/18
+\ULdepth=\dimen159
+)
+\unit=\skip51
+
+(/usr/share/texmf-dist/tex/xelatex/mathspec/mathspec.sty
+Package: mathspec 2016/12/22 v0.2b LaTeX Package (Mathematics font selection fo
+r XeLaTeX)
+
+(/usr/share/texmf-dist/tex/latex/etoolbox/etoolbox.sty
+Package: etoolbox 2020/10/05 v2.5k e-TeX tools for LaTeX (JAW)
+\etb@tempcnta=\count197
+)
+(/usr/share/texmf-dist/tex/generic/iftex/ifxetex.sty
+Package: ifxetex 2019/10/25 v0.7 ifxetex legacy package. Use iftex instead.
+)
+(/usr/share/texmf-dist/tex/latex/fontspec/fontspec.sty
+(/usr/share/texmf-dist/tex/latex/l3packages/xparse/xparse.sty
+(/usr/share/texmf-dist/tex/latex/l3kernel/expl3.sty
+Package: expl3 2022-02-24 L3 programming layer (loader)
+
+(/usr/share/texmf-dist/tex/latex/l3backend/l3backend-xetex.def
+File: l3backend-xetex.def 2022-02-07 L3 backend support: XeTeX
+
+(|extractbb --version)
+\c__kernel_sys_dvipdfmx_version_int=\count198
+\l__color_backend_stack_int=\count199
+\g__color_backend_stack_int=\count266
+\g__graphics_track_int=\count267
+\l__pdf_internal_box=\box55
+\g__pdf_backend_object_int=\count268
+\g__pdf_backend_annotation_int=\count269
+\g__pdf_backend_link_int=\count270
+))
+Package: xparse 2022-01-12 L3 Experimental document command parser
+)
+Package: fontspec 2022/01/15 v2.8a Font selection for XeLaTeX and LuaLaTeX
+
+(/usr/share/texmf-dist/tex/latex/fontspec/fontspec-xetex.sty
+Package: fontspec-xetex 2022/01/15 v2.8a Font selection for XeLaTeX and LuaLaTe
+X
+\l__fontspec_script_int=\count271
+\l__fontspec_language_int=\count272
+\l__fontspec_strnum_int=\count273
+\l__fontspec_tmp_int=\count274
+\l__fontspec_tmpa_int=\count275
+\l__fontspec_tmpb_int=\count276
+\l__fontspec_tmpc_int=\count277
+\l__fontspec_em_int=\count278
+\l__fontspec_emdef_int=\count279
+\l__fontspec_strong_int=\count280
+\l__fontspec_strongdef_int=\count281
+\l__fontspec_tmpa_dim=\dimen160
+\l__fontspec_tmpb_dim=\dimen161
+\l__fontspec_tmpc_dim=\dimen162
+
+(/usr/share/texmf-dist/tex/latex/base/fontenc.sty
+Package: fontenc 2021/04/29 v2.0v Standard LaTeX package
+)
+(/usr/share/texmf-dist/tex/latex/fontspec/fontspec.cfg)))
+(/usr/share/texmf-dist/tex/latex/xkeyval/xkeyval.sty
+Package: xkeyval 2020/11/20 v2.8 package option processing (HA)
+
+(/usr/share/texmf-dist/tex/generic/xkeyval/xkeyval.tex
+(/usr/share/texmf-dist/tex/generic/xkeyval/xkvutils.tex
+\XKV@toks=\toks24
+\XKV@tempa@toks=\toks25
+)
+\XKV@depth=\count282
+File: xkeyval.tex 2014/12/03 v2.7a key=value parser (HA)
+))
+\c@eu@=\count283
+\c@eu@i=\count284
+\c@mkern=\count285
+)
+(/usr/share/texmf-dist/tex/latex/realscripts/realscripts.sty
+Package: realscripts 2016/02/13 v0.3d Access OpenType subscripts and superscrip
+ts
+\subsupersep=\dimen163
+)
+(/usr/share/texmf-dist/tex/latex/microtype/microtype.sty
+Package: microtype 2022/03/14 v3.0d Micro-typographical refinements (RS)
+\MT@toks=\toks26
+\MT@tempbox=\box56
+\MT@count=\count286
+LaTeX Info: Redefining \noprotrusionifhmode on input line 1027.
+LaTeX Info: Redefining \leftprotrusion on input line 1028.
+LaTeX Info: Redefining \rightprotrusion on input line 1036.
+LaTeX Info: Redefining \textls on input line 1195.
+\MT@outer@kern=\dimen164
+LaTeX Info: Redefining \textmicrotypecontext on input line 1781.
+\MT@listname@count=\count287
+
+(/usr/share/texmf-dist/tex/latex/microtype/microtype-xetex.def
+File: microtype-xetex.def 2022/03/14 v3.0d Definitions specific to xetex (RS)
+LaTeX Info: Redefining \lsstyle on input line 236.
+)
+Package microtype Info: Loading configuration file microtype.cfg.
+
+(/usr/share/texmf-dist/tex/latex/microtype/microtype.cfg
+File: microtype.cfg 2022/03/14 v3.0d microtype main configuration file (RS)
+))
+(/usr/share/texmf-dist/tex/latex/returntogrid/returntogrid.sty
+Package: returntogrid 2018/08/21 v0.2 return to a grid position
+
+(/usr/share/texmf-dist/tex/latex/eso-pic/eso-pic.sty
+Package: eso-pic 2020/10/14 v3.0a eso-pic (RN)
+\ESO@tempdima=\dimen165
+\ESO@tempdimb=\dimen166
+
+(/usr/share/texmf-dist/tex/latex/xcolor/xcolor.sty
+Package: xcolor 2021/10/31 v2.13 LaTeX color extensions (UK)
+
+(/usr/share/texmf-dist/tex/latex/graphics-cfg/color.cfg
+File: color.cfg 2016/01/02 v1.6 sample color configuration
+)
+Package xcolor Info: Driver file: xetex.def on input line 227.
+Package xcolor Info: Model `cmy' substituted by `cmy0' on input line 1352.
+Package xcolor Info: Model `RGB' extended on input line 1368.
+Package xcolor Info: Model `HTML' substituted by `rgb' on input line 1370.
+Package xcolor Info: Model `Hsb' substituted by `hsb' on input line 1371.
+Package xcolor Info: Model `tHsb' substituted by `hsb' on input line 1372.
+Package xcolor Info: Model `HSB' substituted by `hsb' on input line 1373.
+Package xcolor Info: Model `Gray' substituted by `gray' on input line 1374.
+Package xcolor Info: Model `wave' substituted by `hsb' on input line 1375.
+))
+(/usr/share/texmf-dist/tex/latex/zref/zref-savepos.sty
+Package: zref-savepos 2020-07-03 v2.32 Module savepos for zref (HO)
+
+(/usr/share/texmf-dist/tex/latex/zref/zref-base.sty
+Package: zref-base 2020-07-03 v2.32 Module base for zref (HO)
+
+(/usr/share/texmf-dist/tex/generic/ltxcmds/ltxcmds.sty
+Package: ltxcmds 2020-05-10 v1.25 LaTeX kernel commands for general use (HO)
+)
+(/usr/share/texmf-dist/tex/generic/infwarerr/infwarerr.sty
+Package: infwarerr 2019/12/03 v1.5 Providing info/warning/error messages (HO)
+)
+(/usr/share/texmf-dist/tex/generic/kvsetkeys/kvsetkeys.sty
+Package: kvsetkeys 2019/12/15 v1.18 Key value parser (HO)
+)
+(/usr/share/texmf-dist/tex/generic/kvdefinekeys/kvdefinekeys.sty
+Package: kvdefinekeys 2019-12-19 v1.6 Define keys (HO)
+)
+(/usr/share/texmf-dist/tex/generic/pdftexcmds/pdftexcmds.sty
+Package: pdftexcmds 2020-06-27 v0.33 Utility functions of pdfTeX for LuaTeX (HO
+)
+Package pdftexcmds Info: \pdf@primitive is available.
+Package pdftexcmds Info: \pdf@ifprimitive is available.
+Package pdftexcmds Info: \pdfdraftmode not found.
+)
+(/usr/share/texmf-dist/tex/generic/etexcmds/etexcmds.sty
+Package: etexcmds 2019/12/15 v1.7 Avoid name clashes with e-TeX commands (HO)
+)
+(/usr/share/texmf-dist/tex/latex/auxhook/auxhook.sty
+Package: auxhook 2019-12-17 v1.6 Hooks for auxiliary files (HO)
+)
+Package zref Info: New property list: main on input line 764.
+Package zref Info: New property: default on input line 765.
+Package zref Info: New property: page on input line 766.
+)
+Package zref Info: New property list: savepos on input line 80.
+Package zref Info: New property: posx on input line 82.
+Package zref Info: New property: posy on input line 83.
+)
+(/usr/share/texmf-dist/tex/latex/zref/zref-abspage.sty
+Package: zref-abspage 2020-07-03 v2.32 Module abspage for zref (HO)
+
+(/usr/share/texmf-dist/tex/latex/base/atbegshi-ltx.sty
+Package: atbegshi-ltx 2021/01/10 v1.0c Emulation of the original atbegshi
+package with kernel methods
+)
+\c@abspage=\count288
+Package zref Info: New property: abspage on input line 66.
+)
+\l__ufgrid_vskip_int=\count289
+\g__ufgrid_vpoint_int=\count290
+\g__ufgrid_hpoint_int=\count291
+\l__ufgrid_tempa_int=\count292
+\l__ufgrid_tab_cor_int=\count293
+\l__ufgrid_debug_tab_dim=\dimen167
+Package zref Info: New property: ufgridvskip on input line 58.
+Package zref Info: New property: pagecnt on input line 59.
+)
+(/usr/share/texmf-dist/tex/latex/hyperref/hyperref.sty
+Package: hyperref 2022-02-21 v7.00n Hypertext links for LaTeX
+
+(/usr/share/texmf-dist/tex/generic/pdfescape/pdfescape.sty
+Package: pdfescape 2019/12/09 v1.15 Implements pdfTeX's escape features (HO)
+)
+(/usr/share/texmf-dist/tex/latex/hycolor/hycolor.sty
+Package: hycolor 2020-01-27 v1.10 Color options for hyperref/bookmark (HO)
+)
+(/usr/share/texmf-dist/tex/latex/letltxmacro/letltxmacro.sty
+Package: letltxmacro 2019/12/03 v1.6 Let assignment for LaTeX macros (HO)
+)
+(/usr/share/texmf-dist/tex/latex/kvoptions/kvoptions.sty
+Package: kvoptions 2020-10-07 v3.14 Key value format for package options (HO)
+)
+\@linkdim=\dimen168
+\Hy@linkcounter=\count294
+\Hy@pagecounter=\count295
+
+(/usr/share/texmf-dist/tex/latex/hyperref/pd1enc.def
+File: pd1enc.def 2022-02-21 v7.00n Hyperref: PDFDocEncoding definition (HO)
+)
+(/usr/share/texmf-dist/tex/generic/intcalc/intcalc.sty
+Package: intcalc 2019/12/15 v1.3 Expandable calculations with integers (HO)
+)
+\Hy@SavedSpaceFactor=\count296
+
+(/usr/share/texmf-dist/tex/latex/hyperref/puenc.def
+File: puenc.def 2022-02-21 v7.00n Hyperref: PDF Unicode definition (HO)
+)
+Package hyperref Info: Option `debug' set `true' on input line 4018.
+Package hyperref Info: Hyper figures OFF on input line 4137.
+Package hyperref Info: Link nesting OFF on input line 4142.
+Package hyperref Info: Hyper index ON on input line 4145.
+Package hyperref Info: Plain pages OFF on input line 4152.
+Package hyperref Info: Backreferencing OFF on input line 4157.
+Package hyperref Info: Implicit mode ON; LaTeX internals redefined.
+Package hyperref Info: Bookmarks ON on input line 4390.
+\c@Hy@tempcnt=\count297
+
+(/usr/share/texmf-dist/tex/latex/url/url.sty
+\Urlmuskip=\muskip17
+Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc.
+)
+LaTeX Info: Redefining \url on input line 4749.
+\XeTeXLinkMargin=\dimen169
+
+(/usr/share/texmf-dist/tex/generic/bitset/bitset.sty
+Package: bitset 2019/12/09 v1.3 Handle bit-vector datatype (HO)
+
+(/usr/share/texmf-dist/tex/generic/bigintcalc/bigintcalc.sty
+Package: bigintcalc 2019/12/15 v1.5 Expandable calculations on big integers (HO
+)
+))
+\Fld@menulength=\count298
+\Field@Width=\dimen170
+\Fld@charsize=\dimen171
+Package hyperref Info: Hyper figures OFF on input line 6027.
+Package hyperref Info: Link nesting OFF on input line 6032.
+Package hyperref Info: Hyper index ON on input line 6035.
+Package hyperref Info: backreferencing OFF on input line 6042.
+Package hyperref Info: Link coloring OFF on input line 6047.
+Package hyperref Info: Link coloring with OCG OFF on input line 6052.
+Package hyperref Info: PDF/A mode OFF on input line 6057.
+LaTeX Info: Redefining \ref on input line 6097.
+LaTeX Info: Redefining \pageref on input line 6101.
+\Hy@abspage=\count299
+\c@Item=\count300
+\c@Hfootnote=\count301
+)
+Package hyperref Info: Driver (autodetected): hxetex.
+
+(/usr/share/texmf-dist/tex/latex/hyperref/hxetex.def
+File: hxetex.def 2022-02-21 v7.00n Hyperref driver for XeTeX
+
+(/usr/share/texmf-dist/tex/generic/stringenc/stringenc.sty
+Package: stringenc 2019/11/29 v1.12 Convert strings between diff. encodings (HO
+)
+)
+\pdfm@box=\box57
+\c@Hy@AnnotLevel=\count302
+\HyField@AnnotCount=\count303
+\Fld@listcount=\count304
+\c@bookmark@seq@number=\count305
+
+(/usr/share/texmf-dist/tex/latex/rerunfilecheck/rerunfilecheck.sty
+Package: rerunfilecheck 2019/12/05 v1.9 Rerun checks for auxiliary files (HO)
+
+(/usr/share/texmf-dist/tex/latex/base/atveryend-ltx.sty
+Package: atveryend-ltx 2020/08/19 v1.0a Emulation of the original atveryend pac
+kage
+with kernel methods
+)
+(/usr/share/texmf-dist/tex/generic/uniquecounter/uniquecounter.sty
+Package: uniquecounter 2019/12/15 v1.4 Provide unlimited unique counter (HO)
+)
+Package uniquecounter Info: New unique counter `rerunfilecheck' on input line 2
+86.
+)
+\Hy@SectionHShift=\skip52
+)
+(/usr/share/texmf-dist/tex/latex/footmisc/footmisc.sty
+Package: footmisc 2022/03/08 v6.0d a miscellany of footnote facilities
+\FN@temptoken=\toks27
+\footnotemargin=\dimen172
+\@outputbox@depth=\dimen173
+Package footmisc Info: Declaring symbol style bringhurst on input line 695.
+Package footmisc Info: Declaring symbol style chicago on input line 703.
+Package footmisc Info: Declaring symbol style wiley on input line 712.
+Package footmisc Info: Declaring symbol style lamport-robust on input line 723.
+
+Package footmisc Info: Declaring symbol style lamport* on input line 743.
+Package footmisc Info: Declaring symbol style lamport*-robust on input line 764
+.
+)
+(/usr/share/texmf-dist/tex/latex/enumitem/enumitem.sty
+Package: enumitem 2019/06/20 v3.9 Customized lists
+\labelindent=\skip53
+\enit@outerparindent=\dimen174
+\enit@toks=\toks28
+\enit@inbox=\box58
+\enit@count@id=\count306
+\enitdp@description=\count307
+)
+(/usr/share/texmf-dist/tex/latex/listings/listings.sty
+\lst@mode=\count308
+\lst@gtempboxa=\box59
+\lst@token=\toks29
+\lst@length=\count309
+\lst@currlwidth=\dimen175
+\lst@column=\count310
+\lst@pos=\count311
+\lst@lostspace=\dimen176
+\lst@width=\dimen177
+\lst@newlines=\count312
+\lst@lineno=\count313
+\abovecaptionskip=\skip54
+\belowcaptionskip=\skip55
+\lst@maxwidth=\dimen178
+
+(/usr/share/texmf-dist/tex/latex/listings/lstmisc.sty
+File: lstmisc.sty 2020/03/24 1.8d (Carsten Heinz)
+\c@lstnumber=\count314
+\lst@skipnumbers=\count315
+\lst@framebox=\box60
+)
+(/usr/share/texmf-dist/tex/latex/listings/listings.cfg
+File: listings.cfg 2020/03/24 1.8d listings configuration
+))
+Package: listings 2020/03/24 1.8d (Carsten Heinz)
+
+(/usr/share/texmf-dist/tex/latex/tools/tabularx.sty
+Package: tabularx 2020/01/15 v2.11c `tabularx' package (DPC)
+
+(/usr/share/texmf-dist/tex/latex/tools/array.sty
+Package: array 2021/10/04 v2.5f Tabular extension package (FMi)
+\col@sep=\dimen179
+\ar@mcellbox=\box61
+\extrarowheight=\dimen180
+\NC@list=\toks30
+\extratabsurround=\skip56
+\backup@length=\skip57
+\ar@cellbox=\box62
+)
+\TX@col@width=\dimen181
+\TX@old@table=\dimen182
+\TX@old@col=\dimen183
+\TX@target=\dimen184
+\TX@delta=\dimen185
+\TX@cols=\count316
+\TX@ftn=\toks31
+)
+(/usr/share/texmf-dist/tex/latex/lastpage/lastpage.sty
+Package: lastpage 2021/09/03 v1.2n Refers to last page's name (HMM; JPG)
+)
+(/usr/share/texmf-dist/tex/latex/datetime2/datetime2.sty
+Package: datetime2 2021/03/21 v1.5.7 (NLCT) date and time formats
+
+(/usr/share/texmf-dist/tex/latex/tracklang/tracklang.sty
+Package: tracklang 2020/06/30 v1.5 (NLCT) Track Languages
+
+(/usr/share/texmf-dist/tex/generic/tracklang/tracklang.tex)))
+(/usr/share/texmf-dist/tex/latex/fancyhdr/fancyhdr.sty
+Package: fancyhdr 2021/01/28 v4.0.1 Extensive control of page headers and foote
+rs
+\f@nch@headwidth=\skip58
+\f@nch@O@elh=\skip59
+\f@nch@O@erh=\skip60
+\f@nch@O@olh=\skip61
+\f@nch@O@orh=\skip62
+\f@nch@O@elf=\skip63
+\f@nch@O@erf=\skip64
+\f@nch@O@olf=\skip65
+\f@nch@O@orf=\skip66
+)
+(/usr/share/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty
+(/usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty
+(/usr/share/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty
+(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.tex
+\pgfutil@everybye=\toks32
+\pgfutil@tempdima=\dimen186
+\pgfutil@tempdimb=\dimen187
+
+(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-lists.tex))
+(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def
+\pgfutil@abb=\box63
+)
+(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex
+(/usr/share/texmf-dist/tex/generic/pgf/pgf.revision.tex)
+Package: pgfrcs 2021/05/15 v3.1.9a (3.1.9a)
+))
+Package: pgf 2021/05/15 v3.1.9a (3.1.9a)
+
+(/usr/share/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty
+(/usr/share/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty
+(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex
+Package: pgfsys 2021/05/15 v3.1.9a (3.1.9a)
+
+(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex
+\pgfkeys@pathtoks=\toks33
+\pgfkeys@temptoks=\toks34
+
+(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.code.tex
+\pgfkeys@tmptoks=\toks35
+))
+\pgf@x=\dimen188
+\pgf@y=\dimen189
+\pgf@xa=\dimen190
+\pgf@ya=\dimen191
+\pgf@xb=\dimen192
+\pgf@yb=\dimen193
+\pgf@xc=\dimen194
+\pgf@yc=\dimen195
+\pgf@xd=\dimen196
+\pgf@yd=\dimen197
+\w@pgf@writea=\write3
+\r@pgf@reada=\read2
+\c@pgf@counta=\count317
+\c@pgf@countb=\count318
+\c@pgf@countc=\count319
+\c@pgf@countd=\count320
+\t@pgf@toka=\toks36
+\t@pgf@tokb=\toks37
+\t@pgf@tokc=\toks38
+\pgf@sys@id@count=\count321
+
+(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg
+File: pgf.cfg 2021/05/15 v3.1.9a (3.1.9a)
+)
+Driver file for pgf: pgfsys-xetex.def
+
+(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-xetex.def
+File: pgfsys-xetex.def 2021/05/15 v3.1.9a (3.1.9a)
+
+(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-dvipdfmx.def
+File: pgfsys-dvipdfmx.def 2021/05/15 v3.1.9a (3.1.9a)
+
+(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-pdf.def
+File: pgfsys-common-pdf.def 2021/05/15 v3.1.9a (3.1.9a)
+)
+\pgfsys@objnum=\count322
+)))
+(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.code.tex
+File: pgfsyssoftpath.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgfsyssoftpath@smallbuffer@items=\count323
+\pgfsyssoftpath@bigbuffer@items=\count324
+)
+(/usr/share/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.code.tex
+File: pgfsysprotocol.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+))
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex
+Package: pgfcore 2021/05/15 v3.1.9a (3.1.9a)
+
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex)
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex
+\pgfmath@dimen=\dimen198
+\pgfmath@count=\count325
+\pgfmath@box=\box64
+\pgfmath@toks=\toks39
+\pgfmath@stack@operand=\toks40
+\pgfmath@stack@operation=\toks41
+)
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.tex
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic.code.tex)
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigonometric.code
+.tex)
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.random.code.tex)
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.comparison.code.te
+x) (/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.code.tex)
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round.code.tex)
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.code.tex)
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integerarithmetics
+.code.tex))) (/usr/share/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex
+\c@pgfmathroundto@lastzeros=\count326
+)) (/usr/share/texmf-dist/tex/generic/pgf/math/pgfint.code.tex)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.code.tex
+File: pgfcorepoints.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgf@picminx=\dimen199
+\pgf@picmaxx=\dimen256
+\pgf@picminy=\dimen257
+\pgf@picmaxy=\dimen258
+\pgf@pathminx=\dimen259
+\pgf@pathmaxx=\dimen260
+\pgf@pathminy=\dimen261
+\pgf@pathmaxy=\dimen262
+\pgf@xx=\dimen263
+\pgf@xy=\dimen264
+\pgf@yx=\dimen265
+\pgf@yy=\dimen266
+\pgf@zx=\dimen267
+\pgf@zy=\dimen268
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconstruct.code.tex
+File: pgfcorepathconstruct.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgf@path@lastx=\dimen269
+\pgf@path@lasty=\dimen270
+) (/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage.code.tex
+File: pgfcorepathusage.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgf@shorten@end@additional=\dimen271
+\pgf@shorten@start@additional=\dimen272
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.code.tex
+File: pgfcorescopes.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgfpic=\box65
+\pgf@hbox=\box66
+\pgf@layerbox@main=\box67
+\pgf@picture@serial@count=\count327
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicstate.code.tex
+File: pgfcoregraphicstate.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgflinewidth=\dimen273
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransformations.code.t
+ex
+File: pgfcoretransformations.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgf@pt@x=\dimen274
+\pgf@pt@y=\dimen275
+\pgf@pt@temp=\dimen276
+) (/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.code.tex
+File: pgfcorequick.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.code.tex
+File: pgfcoreobjects.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathprocessing.code.te
+x
+File: pgfcorepathprocessing.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+) (/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.code.tex
+File: pgfcorearrows.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgfarrowsep=\dimen277
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.code.tex
+File: pgfcoreshade.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgf@max=\dimen278
+\pgf@sys@shading@range@num=\count328
+\pgf@shadingcount=\count329
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.code.tex
+File: pgfcoreimage.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.code.tex
+File: pgfcoreexternal.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgfexternal@startupbox=\box68
+))
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.code.tex
+File: pgfcorelayers.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransparency.code.tex
+File: pgfcoretransparency.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+) (/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.code.tex
+File: pgfcorepatterns.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+)
+(/usr/share/texmf-dist/tex/generic/pgf/basiclayer/pgfcorerdf.code.tex
+File: pgfcorerdf.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+)))
+(/usr/share/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.code.tex
+File: pgfmoduleshapes.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgfnodeparttextbox=\box69
+)
+(/usr/share/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.tex
+File: pgfmoduleplot.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+)
+(/usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty
+Package: pgfcomp-version-0-65 2021/05/15 v3.1.9a (3.1.9a)
+\pgf@nodesepstart=\dimen279
+\pgf@nodesepend=\dimen280
+)
+(/usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty
+Package: pgfcomp-version-1-18 2021/05/15 v3.1.9a (3.1.9a)
+))
+(/usr/share/texmf-dist/tex/latex/pgf/utilities/pgffor.sty
+(/usr/share/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty
+(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex))
+(/usr/share/texmf-dist/tex/latex/pgf/math/pgfmath.sty
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex))
+(/usr/share/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex
+Package: pgffor 2021/05/15 v3.1.9a (3.1.9a)
+
+(/usr/share/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex)
+\pgffor@iter=\dimen281
+\pgffor@skip=\dimen282
+\pgffor@stack=\toks42
+\pgffor@toks=\toks43
+))
+(/usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.code.tex
+Package: tikz 2021/05/15 v3.1.9a (3.1.9a)
+
+(/usr/share/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothandlers.code.te
+x
+File: pgflibraryplothandlers.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgf@plot@mark@count=\count330
+\pgfplotmarksize=\dimen283
+)
+\tikz@lastx=\dimen284
+\tikz@lasty=\dimen285
+\tikz@lastxsaved=\dimen286
+\tikz@lastysaved=\dimen287
+\tikz@lastmovetox=\dimen288
+\tikz@lastmovetoy=\dimen289
+\tikzleveldistance=\dimen290
+\tikzsiblingdistance=\dimen291
+\tikz@figbox=\box70
+\tikz@figbox@bg=\box71
+\tikz@tempbox=\box72
+\tikz@tempbox@bg=\box73
+\tikztreelevel=\count331
+\tikznumberofchildren=\count332
+\tikznumberofcurrentchild=\count333
+\tikz@fig@count=\count334
+ (/usr/share/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.code.tex
+File: pgfmodulematrix.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+\pgfmatrixcurrentrow=\count335
+\pgfmatrixcurrentcolumn=\count336
+\pgf@matrix@numberofcolumns=\count337
+)
+\tikz@expandcount=\count338
+
+(/usr/share/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrary
+topaths.code.tex
+File: tikzlibrarytopaths.code.tex 2021/05/15 v3.1.9a (3.1.9a)
+)))) (./test.aux)
+\openout1 = `test.aux'.
+
+LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 7.
+LaTeX Font Info: Trying to load font information for TS1+cmr on input line 7
+.
+
+(/usr/share/texmf-dist/tex/latex/base/ts1cmr.fd
+File: ts1cmr.fd 2019/12/16 v2.5j Standard LaTeX font definitions
+)
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for TU/lmr/m/n on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for PD1/pdf/m/n on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+LaTeX Font Info: Checking defaults for PU/pdf/m/n on input line 7.
+LaTeX Font Info: ... okay on input line 7.
+
+*geometry* driver: auto-detecting
+*geometry* detected driver: xetex
+*geometry* verbose mode - [ preamble ] result:
+* driver: xetex
+* paper: letterpaper
+* layout: <same size as paper>
+* layoutoffset:(h,v)=(0.0pt,0.0pt)
+* modes:
+* h-part:(L,W,R)=(72.26999pt, 469.75502pt, 72.26999pt)
+* v-part:(T,H,B)=(72.26999pt, 650.43001pt, 72.26999pt)
+* \paperwidth=614.295pt
+* \paperheight=794.96999pt
+* \textwidth=469.75502pt
+* \textheight=650.43001pt
+* \oddsidemargin=0.0pt
+* \evensidemargin=0.0pt
+* \topmargin=-24.09pt
+* \headheight=12.045pt
+* \headsep=12.045pt
+* \topskip=10.0pt
+* \footskip=18.06749pt
+* \marginparwidth=0.0pt
+* \marginparsep=0.0pt
+* \columnsep=0.0pt
+* \skip\footins=12.045pt
+* \hoffset=0.0pt
+* \voffset=0.0pt
+* \mag=1000
+* \@twocolumnfalse
+* \@twosidefalse
+* \@mparswitchfalse
+* \@reversemarginfalse
+* (1in=72.27pt=25.4mm, 1cm=28.453pt)
+
+LaTeX Info: Redefining \microtypecontext on input line 7.
+Package microtype Info: Applying patch `item' on input line 7.
+Package microtype Info: Applying patch `toc' on input line 7.
+Package microtype Info: Applying patch `eqnum' on input line 7.
+Package microtype Info: Applying patch `footnote' on input line 7.
+Package microtype Info: Character protrusion enabled (level 2).
+Package microtype Info: Using default protrusion set `alltext'.
+Package microtype Info: No adjustment of tracking.
+Package microtype Info: No adjustment of spacing.
+Package microtype Info: No adjustment of kerning.
+Package microtype Info: Loading generic protrusion settings for font family
+(microtype) `NeueHaasGroteskTextPro' (encoding: TU).
+(microtype) For optimal results, create family-specific settings.
+(microtype) See the microtype manual for details.
+Package hyperref Info: Link coloring OFF on input line 7.
+(/usr/share/texmf-dist/tex/latex/hyperref/nameref.sty
+Package: nameref 2021-04-02 v2.47 Cross-referencing by name of section
+
+(/usr/share/texmf-dist/tex/latex/refcount/refcount.sty
+Package: refcount 2019/12/15 v3.6 Data extraction from label references (HO)
+)
+(/usr/share/texmf-dist/tex/generic/gettitlestring/gettitlestring.sty
+Package: gettitlestring 2019/12/15 v1.6 Cleanup title references (HO)
+)
+\c@section@level=\count339
+)
+LaTeX Info: Redefining \ref on input line 7.
+LaTeX Info: Redefining \pageref on input line 7.
+LaTeX Info: Redefining \nameref on input line 7.
+
+(./test.out) (./test.out)
+\@outlinefile=\write4
+\openout4 = `test.out'.
+
+Package hyperref Info: Anchor `Doc-Start' on input line 7.
+\c@lstlisting=\count340
+Package lastpage Info: Please have a look at the pageslts package at
+(lastpage) https://www.ctan.org/pkg/pageslts
+(lastpage) ! on input line 7.
+Package microtype Info: Loading generic protrusion settings for font family
+(microtype) `NeueHaasGroteskDisplayPro' (encoding: TU).
+(microtype) For optimal results, create family-specific settings.
+(microtype) See the microtype manual for details.
+Package hyperref Info: Anchor `lstlisting.-1' on input line 14.
+Package microtype Info: Loading generic protrusion settings for font family
+(microtype) `JetBrainsMono' (encoding: TU).
+(microtype) For optimal results, create family-specific settings.
+(microtype) See the microtype manual for details.
+Package hyperref Info: Anchor `lstnumber.-1.1' on input line 15.
+Package hyperref Info: Anchor `lstnumber.-1.2' on input line 17.
+Package hyperref Info: Anchor `lstnumber.-1.3' on input line 17.
+Package hyperref Info: Anchor `lstnumber.-1.4' on input line 18.
+Package hyperref Info: Anchor `lstnumber.-1.5' on input line 19.
+Package hyperref Info: Anchor `lstlisting.-2' on input line 25.
+Package hyperref Info: Anchor `lstnumber.-2.1' on input line 26.
+Package hyperref Info: Anchor `lstlisting.-3' on input line 32.
+Package hyperref Info: Anchor `lstnumber.-3.1' on input line 33.
+Package hyperref Info: Anchor `lstlisting.-4' on input line 39.
+Package hyperref Info: Anchor `lstnumber.-4.1' on input line 40.
+Package hyperref Info: Anchor `lstlisting.-5' on input line 46.
+Package hyperref Info: Anchor `lstnumber.-5.1' on input line 47.
+Package hyperref Info: Anchor `lstlisting.-6' on input line 51.
+Package hyperref Info: Anchor `lstnumber.-6.1' on input line 52.
+
+AED: lastpage setting LastPage
+LaTeX Font Info: Font shape `TU/NeueHaasGroteskTextPro(0)/m/sl' in size <12.
+045> not available
+(Font) Font shape `TU/NeueHaasGroteskTextPro(0)/m/it' tried instea
+d on input line 55.
+
+Package fancyhdr Warning: \headheight is too small (12.045pt):
+(fancyhdr) Make it at least 20.07497pt, for example:
+(fancyhdr) \setlength{\headheight}{20.07497pt}.
+(fancyhdr) You might also make \topmargin smaller to compensate:
+
+(fancyhdr) \addtolength{\topmargin}{-8.02997pt}.
+
+Package hyperref Info: Reference (link) `page.1' on input line 55.
+Package hyperref Info: Anchor `page.1' on input line 55.
+[1] (./test.aux)
+Package rerunfilecheck Info: File `test.out' has not changed.
+(rerunfilecheck) Checksum: D41D8CD98F00B204E9800998ECF8427E;0.
+ )
+Here is how much of TeX's memory you used:
+ 29789 strings out of 476180
+ 608437 string characters out of 5815297
+ 1038819 words of memory out of 5000000
+ 50058 multiletter control sequences out of 15000+600000
+ 470061 words of font info for 72 fonts, out of 8000000 for 9000
+ 1348 hyphenation exceptions out of 8191
+ 102i,13n,124p,1001b,1032s stack positions out of 5000i,500n,10000p,200000b,80000s
+
+Output written on test.pdf (1 page).
diff --git a/misc/test.out b/misc/test.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/misc/test.out
diff --git a/misc/test.pdf b/misc/test.pdf
new file mode 100644
index 0000000..18d3e4e
--- /dev/null
+++ b/misc/test.pdf
Binary files differ
diff --git a/misc/test.tex b/misc/test.tex
new file mode 100644
index 0000000..7085f90
--- /dev/null
+++ b/misc/test.tex
@@ -0,0 +1,55 @@
+
+\documentclass[debug]{microstructure}
+
+\author{DistressNetwork°}
+\lstset{escapechar=§}
+
+\begin{document}
+
+\resetv{-3\unit}\section{Hello world in Zig}
+
+This is an example `hello world' program.
+
+\vspace{-0.5\unit}{\footnotesize\italic{*:}}\vspace{-1\unit}
+\begin{codeblock}
+§\textrm{\italic{(Imports)}}§
+
+pub fn main() !void {
+ §\textrm{\italic{(Print)}}§
+}
+\end{codeblock}
+
+We first import the standard library.
+
+\vspace{-0.5\unit}{\footnotesize\italic{Imports:}}\vspace{-1\unit}
+\begin{codeblock}
+const std = @import("std");
+\end{codeblock}
+
+Then we print the desired string.
+
+\vspace{-0.5\unit}{\footnotesize\italic{Print:}}\vspace{-1\unit}
+\begin{codeblock}
+std.debug.print("Hello world\n", .{});
+\end{codeblock}
+
+We can also print using a format string.
+
+\vspace{-0.5\unit}{\footnotesize\italic{+ Print:}}\vspace{-1\unit}
+\begin{codeblock}
+std.debug.print("Hello world {s}\n", .{"again"});
+\end{codeblock}
+
+Or we can use a \mono{writer()} method and write to stdout directly.
+
+\vspace{-0.5\unit}{\footnotesize\italic{+ Imports:}}\vspace{-1\unit}
+\begin{codeblock}
+const write = std.io.getStdOut().writer().write;
+\end{codeblock}
+
+\vspace{-0.5\unit}{\footnotesize\italic{+ Print:}}\vspace{-1\unit}
+\begin{codeblock}
+_ = try write("Hello stdout\n");
+\end{codeblock}
+
+\end{document}
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;
+}
diff --git a/tangle b/tangle
new file mode 100755
index 0000000..e9f3671
--- /dev/null
+++ b/tangle
Binary files differ
diff --git a/tangle.lp b/tangle.lp
new file mode 100644
index 0000000..f0a99e1
--- /dev/null
+++ b/tangle.lp
@@ -0,0 +1,100 @@
+# tangle.zig
+
+The structure of this file is quite similar to that of `weave.zig`, only differing in terms of which functions are used to transform the input data.
+
+@: *
+@= Imports
+
+pub fn main() !u8 {
+ @= IO initialization
+
+ @= Allocator initialization
+
+ @= Read file from stdin
+
+ @= Split into lines
+
+ @= Parse lines into sections
+
+ @= Generate code
+
+ @= Write to stdout
+
+ return 0;
+}
+@.
+
+First we import the other files containing the core functions.
+
+@: Imports
+const std = @import("std");
+const data = @import("data.zig");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+@.
+
+Within the main procedure, we first initialize the stdin and stdout interfaces.
+
+@: IO initialization
+const stdin = std.io.getStdIn();
+const stdout = std.io.getStdOut();
+@.
+
+We then initialize the allocator, deferring its deinitialization to the end of the process. Since the overall memory usage pattern is one in which all resources may be freed at once, the arena allocator is the most appropriate choice for this program.
+
+@: Allocator initialization
+var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+var alloc = arena.allocator();
+defer arena.deinit();
+@.
+
+The input file is then read from stdin. In the case of input exceeding the maximum permitted file size, the program may report the error and exit normally. All other errors which may be returned are memory allocation failures and should thus yield control to the panic handler.
+
+@: Read file from stdin
+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,
+};
+@.
+
+We then pass the input into the line splitting function, creating an array of strings.
+
+@: Split into lines
+const lines = try data.split_lines(input, alloc);
+@.
+
+The lines are then passed into the parsing function, which may return a parsing error. Logging such errors is handled by the function itself, and thus the errors are handled here solely by exiting.
+
+@: Parse lines into sections
+const sections = data.parse(lines, alloc) catch |err| switch (err) {
+ error.UnexpectedStart,
+ error.UnexpectedEnd => {
+ return 1;
+ },
+ else => |e| return e,
+};
+@.
+
+The code file is then generated. This entails resolving references to section names, which may return an error, handled by exiting as above.
+
+@: Generate code
+const code = data.codegen(lines, sections, alloc) catch |err| switch (err) {
+ error.DereferenceLimit,
+ error.NotFound => {
+ return 1;
+ },
+ else => |e| return e,
+};
+@.
+
+Finally, the lines of the code file are written to stdout, separated by newlines.
+
+@: Write to stdout
+for (code) |line| {
+ try stdout.writer().print("{s}\n", .{line});
+}
+@.
diff --git a/weave b/weave
new file mode 100755
index 0000000..9bf4fc7
--- /dev/null
+++ b/weave
Binary files differ
diff --git a/weave.lp b/weave.lp
new file mode 100644
index 0000000..538b0a6
--- /dev/null
+++ b/weave.lp
@@ -0,0 +1,85 @@
+# weave.zig
+
+The structure of this file is quite similar to that of `tangle.zig`, only differing in terms of which functions are used to transform the input data.
+
+@: *
+@= Imports
+
+pub fn main() !u8 {
+ @= IO initialization
+
+ @= Allocator initialization
+
+ @= Read file from stdin
+
+ @= Split into lines
+
+ @= Generate text
+
+ @= Write to stdout
+
+ return 0;
+}
+@.
+
+First we import the other files containing the core functions.
+
+@: Imports
+const std = @import("std");
+const data = @import("data.zig");
+const log = @import("log.zig").log;
+
+const Allocator = std.mem.Allocator;
+@.
+
+Within the main procedure, we first initialize the stdin and stdout interfaces.
+
+@: IO initialization
+const stdin = std.io.getStdIn();
+const stdout = std.io.getStdOut();
+@.
+
+We then initialize the allocator, deferring its deinitialization to the end of the process. Since the overall memory usage pattern is one in which all resources may be freed at once, the arena allocator is the most appropriate choice for this program.
+
+@: Allocator initialization
+var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+var alloc = arena.allocator();
+defer arena.deinit();
+@.
+
+The input file is then read from stdin. In the case of input exceeding the maximum permitted file size, the program may report the error and exit normally. All other errors which may be returned are memory allocation failures and should thus yield control to the panic handler.
+
+@: Read file from stdin
+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,
+};
+@.
+
+We then pass the input into the line splitting function, creating an array of strings.
+
+@: Split into lines
+const lines = try data.split_lines(input, alloc);
+@.
+
+The text file is then generated. This entails searching for the configuration declarations, which may fail and thus return an error. Logging such errors is handled by the function itself, and thus the errors are handled here solely by exiting.
+
+@: Generate text
+const text = data.textgen(lines, alloc) catch |err| switch (err) {
+ error.NotFound => {
+ return 1;
+ },
+ else => |e| return e,
+};
+@.
+
+Finally, the lines of the text file are written to stdout, separated by newlines.
+
+@: Write to stdout
+for (text) |line| {
+ try stdout.writer().print("{s}\n", .{line});
+}
+@.