mymarkdown

My markdown
git clone https://git.grace.moe/mymarkdown
Log | Files | Refs

commit 9a3da9d4fa2075ca5a908dd115845df4e769c987
parent 9a9d1790e01a3bc646f5a478f3a8825c2b49de2c
Author: gracefu <81774659+gracefuu@users.noreply.github.com>
Date:   Thu, 22 May 2025 02:59:34 +0800

revamp build and test layout

Diffstat:
Mbuild.zig | 144+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/Ast.zig | 22++++++++++------------
Msrc/IndentationScanner.zig | 1-
Msrc/padded_str.zig | 2+-
Msrc/padded_str_impl.zig | 12------------
Msrc/root.zig | 8++------
Asrc/test.zig | 607+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/test/test.zig | 592-------------------------------------------------------------------------------
8 files changed, 702 insertions(+), 686 deletions(-)

diff --git a/build.zig b/build.zig @@ -1,86 +1,106 @@ const std = @import("std"); +/// ## Table of contents +/// +/// - Opts (`zig build -D<opt>` setup) +/// - Deps (external `build.zig` Builds) +/// - Mods (Code + semantically relevant config, e.g. target and optimization levels) +/// - Compiles (Module + "implementation details" of how to do the compile, e.g. use_llvm) +/// - Bins (Compile + Run, a hack to make the build system work better[^fno-emit-bin]) +/// - Steps (`zig build <command>` setup) +/// +/// [^fno-emit-bin]: See issue [#18877](https://github.com/ziglang/zig/issues/18877) pub fn build(b: *std.Build) !void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - const llvm = b.option( + // ======== + // Opts + const target_opt = b.standardTargetOptions(.{}); + const optimize_opt = b.standardOptimizeOption(.{}); + const llvm_opt = b.option( bool, "llvm", "Force llvm to be used or not (default: whatever the compiler default is)", - ); + ) orelse null; - const enable_tracy = b.option( + const tracy_opt = b.option( bool, "tracy", - "Enable Tracy profiling", + "Enable Tracy profiling (default: false)", ) orelse false; - const tracy = b.dependency("tracy", .{ .enable = enable_tracy }); + // ======== + // Deps + const tracy_dep = b.dependency("tracy", .{ + .enable = tracy_opt, + }); - const mymarkdown = b.addModule("mymarkdown", .{ + // ======== + // Mods + const mymarkdown_mod = b.addModule("mymarkdown", .{ .root_source_file = b.path("src/root.zig"), - .target = target, - .optimize = optimize, + .target = target_opt, + .optimize = optimize_opt, + .imports = &.{ + .{ .name = "tracy", .module = tracy_dep.module("tracy") }, + }, }); - mymarkdown.addImport("tracy", tracy.module("tracy")); - const mymarkdown_cli = b.addModule("mymarkdown", .{ + + const mymarkdown_cli_mod = b.addModule("mymarkdown_cli", .{ .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .target = target_opt, + .optimize = optimize_opt, + .imports = &.{ + .{ .name = "mymarkdown", .module = mymarkdown_mod }, + .{ .name = "tracy", .module = tracy_dep.module("tracy") }, + }, }); - mymarkdown_cli.addImport("mymarkdown", mymarkdown); - mymarkdown_cli.addImport("tracy", tracy.module("tracy")); + // ======== + // Compiles const mymarkdown_cli_compile = b.addExecutable(.{ .name = "mymarkdown", - .root_module = mymarkdown_cli, - .use_llvm = llvm, + .root_module = mymarkdown_cli_mod, + .use_llvm = llvm_opt, }); - const check = b.step("check", "Check if the mymarkdown CLI compiles"); - setupTestStep(b, target, optimize, mymarkdown, mymarkdown_cli, check, llvm); - setupRunStep(b, mymarkdown_cli_compile); - try installAndCheck(b, check, mymarkdown_cli_compile); -} -fn installAndCheck(b: *std.Build, check: *std.Build.Step, exe: *std.Build.Step.Compile) !void { - const copy = try b.allocator.create(std.Build.Step.Compile); - copy.* = exe.*; - check.dependOn(&copy.step); - b.installArtifact(exe); -} + const mymarkdown_test_compile = b.addTest(.{ + .name = "mymarkdown_test", + .root_module = mymarkdown_mod, + .use_llvm = llvm_opt, + }); -fn setupTestStep( - b: *std.Build, - target: std.Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, - mymarkdown: *std.Build.Module, - mymarkdown_cli: *std.Build.Module, - check: *std.Build.Step, - llvm: ?bool, -) void { - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(check); - test_step.dependOn(&b.addRunArtifact(b.addTest(.{ - .root_module = mymarkdown, - .target = target, - .optimize = optimize, - .use_llvm = llvm, - })).step); - test_step.dependOn(&b.addRunArtifact(b.addTest(.{ - .root_module = mymarkdown_cli, - .target = target, - .optimize = optimize, - .use_llvm = llvm, - })).step); -} + // ======== + // Bins + const mymarkdown_cli_bin: Binary = try .make(b, mymarkdown_cli_compile); + const mymarkdown_test_bin: Binary = try .make(b, mymarkdown_test_compile); -fn setupRunStep( - b: *std.Build, - mymarkdown_cli_compile: *std.Build.Step.Compile, -) void { - const run_exe = b.addRunArtifact(mymarkdown_cli_compile); - if (b.args) |args| run_exe.addArgs(args); - const run_exe_step = b.step("run", "Run the mymarkdown CLI"); - run_exe_step.dependOn(&run_exe.step); + // ======== + // Steps + const check_step = b.step("check", "Check if the mymarkdown CLI compiles"); + check_step.dependOn(&mymarkdown_cli_compile.step); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(check_step); + mymarkdown_test_bin.runInStep(test_step); + + const run_step = b.step("run", "Run the mymarkdown CLI"); + mymarkdown_cli_bin.runInStep(run_step); + mymarkdown_cli_bin.install(b); } + +const Binary = struct { + run: *std.Build.Step.Run, + + fn make(b: *std.Build, compile: *std.Build.Step.Compile) !Binary { + const copy = try b.allocator.create(std.Build.Step.Compile); + copy.* = compile.*; + return .{ .run = b.addRunArtifact(copy) }; + } + + fn runInStep(runnable: Binary, step: *std.Build.Step) void { + step.dependOn(&runnable.run.step); + } + + fn install(runnable: Binary, b: *std.Build) void { + b.installArtifact(runnable.run.producer.?); + } +}; diff --git a/src/Ast.zig b/src/Ast.zig @@ -114,18 +114,16 @@ pub const Error = utils.Packed(union(enum(u8)) { pub const format = utils.unionFormat(@This()); }); -test "Tracking size of Node struct" { - try std.testing.expectEqual(24, @bitSizeOf(Node.Idx)); - try std.testing.expectEqual(4, @sizeOf(Node.Idx)); - try std.testing.expectEqual(64, @bitSizeOf(Node)); - try std.testing.expectEqual(8, @sizeOf(Node)); -} - -test "Tracking size of Error struct" { - try std.testing.expectEqual(24, @bitSizeOf(Error.Idx)); - try std.testing.expectEqual(4, @sizeOf(Error.Idx)); - try std.testing.expectEqual(64, @bitSizeOf(Error)); - try std.testing.expectEqual(8, @sizeOf(Error)); +comptime { + assert(24 == @bitSizeOf(Node.Idx)); + assert(4 == @sizeOf(Node.Idx)); + assert(64 == @bitSizeOf(Node)); + assert(8 == @sizeOf(Node)); + + assert(24 == @bitSizeOf(Error.Idx)); + assert(4 == @sizeOf(Error.Idx)); + assert(64 == @bitSizeOf(Error)); + assert(8 == @sizeOf(Error)); } pub const Tagged = struct { diff --git a/src/IndentationScanner.zig b/src/IndentationScanner.zig @@ -174,7 +174,6 @@ const testing = std.testing; const padded_str = @import("padded_str.zig"); const PaddedMany = @import("padded_str.zig").PaddedMany; const PaddedSlice = @import("padded_str.zig").PaddedSlice; -const str = @import("str.zig"); const IndentationScanner = @This(); diff --git a/src/padded_str.zig b/src/padded_str.zig @@ -1,3 +1,3 @@ -pub const PADDING = @import("root.zig").PADDING; +pub const PADDING = 128; pub const PaddedSlice = @import("padded_str_impl.zig").PaddedSlice(PADDING); pub const PaddedMany = @import("padded_str_impl.zig").PaddedMany(PADDING); diff --git a/src/padded_str_impl.zig b/src/padded_str_impl.zig @@ -289,15 +289,3 @@ pub fn PaddedMany(comptime PADDING_: usize) type { } }; } - -test "PaddedSlice" { - const testing = std.testing; - const input = "line 1\n"; - const padded_input = try PaddedSlice(128).pad(testing.allocator, input); - defer testing.allocator.free(padded_input); - var slice: PaddedSlice(128) = try .init(padded_input); - const line_maybe, slice = slice.splitOneLine(); - if (line_maybe) |_| { - return; - } else return error.Sad; -} diff --git a/src/root.zig b/src/root.zig @@ -8,13 +8,9 @@ pub const parse2 = AstGen2.parse; pub const AstGen3 = @import("AstGen3.zig"); pub const parse3 = AstGen3.parse; -pub const PADDING = 128; +pub const PADDING = @import("padded_str.zig").PADDING; test { - _ = @import("test/test.zig"); - _ = Ast; - _ = AstGen; - _ = AstGen2; - _ = AstGen3; _ = @import("IndentationScanner.zig"); + _ = @import("test.zig"); } diff --git a/src/test.zig b/src/test.zig @@ -0,0 +1,607 @@ +const std = @import("std"); +const parse = @import("AstGen.zig").parse; +const parse2 = @import("AstGen2.zig").parse; +const parse3 = @import("AstGen3.zig").parse; +const Ast = @import("Ast.zig"); +const PADDING = @import("padded_str.zig").PADDING; + +const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator(.{}); +const ArenaAllocator = std.heap.ArenaAllocator; + +// This is in a function so the result is memoized. +fn padInput(comptime input: []const u8) []const u8 { + return input ++ "\n" ** 128; +} + +fn readFile(path: []const u8) !std.ArrayList(u8) { + var input_instance: std.ArrayList(u8) = .init(std.testing.allocator); + errdefer input_instance.deinit(); + const input_file = try std.fs.cwd().openFile(path, .{}); + defer input_file.close(); + const file_size = (try input_file.stat()).size; + try input_instance.ensureTotalCapacity(file_size + PADDING); + try input_file.reader().readAllArrayList(&input_instance, std.math.maxInt(u32) - PADDING); + try input_instance.appendNTimes('\n', PADDING); + return input_instance; +} + +fn testParse(comptime input: []const u8, expected: []const u8) !void { + try testParseWithFn(input, expected, parse); + try testParseWithFn(input, expected, parse3); +} + +fn testParseWithFn(comptime input: []const u8, expected: []const u8, parseFn: anytype) !void { + var arena: ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + + const safe_input = padInput(input); + + const ast = try parseFn(std.testing.allocator, arena.allocator(), safe_input); + var ast_render: std.ArrayListUnmanaged(u8) = .empty; + defer ast_render.deinit(std.testing.allocator); + try ast.renderAst(ast_render.writer(std.testing.allocator), safe_input); + try std.testing.expectEqualStrings(expected, ast_render.items); +} + +test "Empty" { + try testParse("", + \\.document + \\ + ); +} + +test "Happy path paragraph" { + try testParse( + \\text + \\ + \\text + \\text + \\ + \\text + \\ text + \\ + , + \\.document + \\ .paragraph + \\ .text + \\ "text" + \\ .paragraph + \\ .text + \\ "text" + \\ .space_text + \\ "text" + \\ .paragraph + \\ .text + \\ "text" + \\ .space_text + \\ " text" + \\ + ); +} + +test "Happy path headings" { + try testParse( + \\# text + \\# text + \\# text + \\ text + \\ + \\# text + \\ + \\# text + \\ text + \\ + \\# text + \\ text + \\ + \\## text + \\## text + \\## text + \\ text + \\ + \\## text + \\ + \\## text + \\ text + \\ + \\## text + \\ text + \\ + , + \\.document + \\ .heading + \\ .marker + \\ "#" + \\ .text + \\ "text" + \\ .heading + \\ .marker + \\ "#" + \\ .text + \\ "text" + \\ .heading + \\ .marker + \\ "#" + \\ .text + \\ "text" + \\ .space_text + \\ "text" + \\ .heading + \\ .marker + \\ "#" + \\ .text + \\ "text" + \\ .heading + \\ .marker + \\ "#" + \\ .text + \\ "text" + \\ .space_text + \\ "text" + \\ .heading + \\ .marker + \\ "#" + \\ .text + \\ "text" + \\ .space_text + \\ " text" + \\ .heading + \\ .marker + \\ "##" + \\ .text + \\ "text" + \\ .heading + \\ .marker + \\ "##" + \\ .text + \\ "text" + \\ .heading + \\ .marker + \\ "##" + \\ .text + \\ "text" + \\ .space_text + \\ "text" + \\ .heading + \\ .marker + \\ "##" + \\ .text + \\ "text" + \\ .heading + \\ .marker + \\ "##" + \\ .text + \\ "text" + \\ .space_text + \\ "text" + \\ .heading + \\ .marker + \\ "##" + \\ .text + \\ "text" + \\ .space_text + \\ " text" + \\ + ); +} + +test "Happy path quote" { + try testParse( + \\> text + \\ text + \\ + \\> text + \\ text + \\> text + \\> text + \\text + \\ + , + \\.document + \\ .quote + \\ .paragraph + \\ .text + \\ "text" + \\ .space_text + \\ "text" + \\ .quote + \\ .paragraph + \\ .text + \\ "text" + \\ .space_text + \\ " text" + \\ .quote + \\ .paragraph + \\ .text + \\ "text" + \\ .quote + \\ .paragraph + \\ .text + \\ "text" + \\ .paragraph + \\ .text + \\ "text" + \\ + ); +} + +test "Happy path list" { + try testParseWithFn( + \\- text + \\- [ ] text + \\. text + \\: text + \\-- text + \\-- [ ] text + \\.. text + \\:: text + \\ + , + \\.document + \\ .list + \\ .unordered_item + \\ .marker + \\ "-" + \\ .text + \\ "text" + \\ .list + \\ .task_item + \\ .marker + \\ "- [ ]" + \\ .text + \\ "text" + \\ .list + \\ .ordered_item + \\ .marker + \\ "." + \\ .text + \\ "text" + \\ .list + \\ .term_item + \\ .marker + \\ ":" + \\ .text + \\ "text" + \\ .list + \\ .unordered_item + \\ .marker + \\ "--" + \\ .text + \\ "text" + \\ .list + \\ .task_item + \\ .marker + \\ "-- [ ]" + \\ .text + \\ "text" + \\ .list + \\ .ordered_item + \\ .marker + \\ ".." + \\ .text + \\ "text" + \\ .list + \\ .term_item + \\ .marker + \\ "::" + \\ .text + \\ "text" + \\ + , parse3); +} + +test "List grouping" { + try testParseWithFn( + \\- text + \\- text + \\-- text + \\- text + \\ + \\- text + \\paragraph + \\- text + \\. text + \\ + , + \\.document + \\ .list + \\ .unordered_item + \\ .marker + \\ "-" + \\ .text + \\ "text" + \\ .unordered_item + \\ .marker + \\ "-" + \\ .text + \\ "text" + \\ .unordered_item + \\ .marker + \\ "--" + \\ .text + \\ "text" + \\ .unordered_item + \\ .marker + \\ "-" + \\ .text + \\ "text" + \\ .unordered_item + \\ .marker + \\ "-" + \\ .text + \\ "text" + \\ .paragraph + \\ .text + \\ "paragraph" + \\ .list + \\ .unordered_item + \\ .marker + \\ "-" + \\ .text + \\ "text" + \\ .list + \\ .ordered_item + \\ .marker + \\ "." + \\ .text + \\ "text" + \\ + , parse3); +} + +test "Happy path list elaboration" { + try testParseWithFn( + \\- a + \\+ bb + \\ + \\ ccc + \\+ dddd + \\## heading + \\++ subtitle + \\ + , + \\.document + \\ .list + \\ .unordered_item + \\ .marker + \\ "-" + \\ .text + \\ "a" + \\ .elaboration + \\ .marker + \\ "+" + \\ .paragraph + \\ .text + \\ "bb" + \\ .paragraph + \\ .text + \\ "ccc" + \\ .elaboration + \\ .marker + \\ "+" + \\ .paragraph + \\ .text + \\ "dddd" + \\ .heading + \\ .marker + \\ "##" + \\ .text + \\ "heading" + \\ .elaboration + \\ .marker + \\ "++" + \\ .paragraph + \\ .text + \\ "subtitle" + \\ + , parse3); +} + +test "Elaboration errors" { + try testParseWithFn( + \\- a + \\++ bb + \\-- ccc + \\+ dddd + \\paragraph + \\+ eeeee + \\ + , + \\.document + \\ .list + \\ .unordered_item + \\ .error .incorrect_elaboration_marker + \\ .marker + \\ "-" + \\ .text + \\ "a" + \\ .elaboration + \\ .marker + \\ "++" + \\ .paragraph + \\ .text + \\ "bb" + \\ .unordered_item + \\ .error .incorrect_elaboration_marker + \\ .marker + \\ "--" + \\ .text + \\ "ccc" + \\ .elaboration + \\ .marker + \\ "+" + \\ .paragraph + \\ .text + \\ "dddd" + \\ .paragraph + \\ .text + \\ "paragraph" + \\ .elaboration + \\ .error .elaboration_after_unelaboratable_node + \\ .marker + \\ "+" + \\ .paragraph + \\ .text + \\ "eeeee" + \\ + , parse3); +} + +test "Thematic break" { + try testParse( + \\a + \\*** + \\bb + \\* + \\ccc + \\ + \\**bold text** + \\ + , + \\.document + \\ .paragraph + \\ .text + \\ "a" + \\ .thematic_break + \\ "***" + \\ .paragraph + \\ .text + \\ "bb" + \\ .space_text + \\ "*" + \\ .space_text + \\ "ccc" + \\ .paragraph + \\ .text + \\ "**bold text**" + \\ + ); +} + +test "Mixed indentation" { + // AstGen1 used to try very hard to recover when it sees a tab + try testParseWithFn("" ++ + "> aaa\n" ++ + "\n" ++ + "\tbbbbb\n", + \\.document + \\ .quote + \\ .error .inconsistent_indentation at 3:1 + \\ .paragraph + \\ .text + \\ "aaa" + \\ .paragraph + \\ .text + \\ "bbbbb" + \\ + , parse); + + try testParseWithFn("" ++ + "> aaa\n" ++ + "\n" ++ + "\tbbbbb\n", + \\.document + \\ .quote + \\ .paragraph + \\ .text + \\ "aaa" + \\ .paragraph + \\ .error .inconsistent_indentation at 3:1 + \\ .text + \\ "bbbbb" + \\ + , parse3); +} + +test "Tabs in text" { + try testParse("" ++ + "aaa\n" ++ + "\tbbbbb\n", + \\.document + \\ .paragraph + \\ .text + \\ "aaa" + \\ .space_text + \\ "\tbbbbb" + \\ + ); +} + +test "Empty line in heading" { + try testParse( + \\# heading + \\ + \\ text + \\ + \\text + \\ + , + \\.document + \\ .heading + \\ .marker + \\ "#" + \\ .text + \\ "heading" + \\ .space_text + \\ .error .unexpected_block_in_inline_context + \\ "text" + \\ .paragraph + \\ .text + \\ "text" + \\ + ); +} + +test "Super long line" { + const input = try std.testing.allocator.create([(1 << 24) * 4]u8); + defer std.testing.allocator.destroy(input); + @memset(input, 'a'); + var arena: ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const ast = try parse(std.testing.allocator, arena.allocator(), input); + const taggedAst = try ast.toTagged(arena.allocator()); + try std.testing.expectEqualDeep(@as(Ast.Tagged, .{ + .nodes = &.{ + .{ .document = .{ .num_children = 1 } }, + .{ .paragraph = .{ .off = 0, .num_children = 1 } }, + .{ .text = .{ .off = 0, .len = 16777215 } }, + .{ .text = .{ .off = 16777215, .len = 16777215 } }, + .{ .text = .{ .off = 33554430, .len = 16777215 } }, + .{ .text = .{ .off = 50331645, .len = 16777215 } }, + .{ .text = .{ .off = 67108860, .len = 4 } }, + }, + .errors = &.{}, + .extra = &.{}, + }), taggedAst); +} + +test "Many short lines" { + const input = try std.testing.allocator.create([(1 << 25) - 4 + PADDING]u8); + defer std.testing.allocator.destroy(input); + @memset(@as(*[(1 << 24) - 2][2]u8, @ptrCast(input)), "a\n"[0..2].*); + @memset(input[(1 << 25) - 4 ..], '\n'); + + var arena: ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const ast = try parse(std.testing.allocator, arena.allocator(), input); + + try std.testing.expectEqual(1 << 24, ast.nodes.len); + try std.testing.expectEqual( + @as(Ast.Node.Tagged, .{ .document = .{ .num_children = 1 } }), + ast.nodes[0].toTagged(), + ); + try std.testing.expectEqual( + @as(Ast.Node.Tagged, .{ .paragraph = .{ .off = 0, .num_children = (1 << 24) - 2 } }), + ast.nodes[1].toTagged(), + ); + try std.testing.expectEqual( + @as(Ast.Node.Tagged, .{ .text = .{ .off = 0, .len = 1 } }), + ast.nodes[2].toTagged(), + ); + for (1..(1 << 24) - 2) |i| { + try std.testing.expectEqual( + @as(Ast.Node.Tagged, .{ .space_text = .{ .off = @intCast(i * 2), .len = 1 } }), + ast.nodes[i + 2].toTagged(), + ); + } +} diff --git a/src/test/test.zig b/src/test/test.zig @@ -1,592 +0,0 @@ -const std = @import("std"); -const parse = @import("../AstGen.zig").parse; -const parse2 = @import("../AstGen2.zig").parse; -const parse3 = @import("../AstGen3.zig").parse; -const Ast = @import("../Ast.zig"); - -const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator(.{}); -const ArenaAllocator = std.heap.ArenaAllocator; - -// This is in a function so the result is memoized. -fn padInput(comptime input: []const u8) []const u8 { - return input ++ "\n" ** 128; -} - -fn testParse(comptime input: []const u8, expected: []const u8) !void { - try testParseWithFn(input, expected, parse); - try testParseWithFn(input, expected, parse3); -} - -fn testParseWithFn(comptime input: []const u8, expected: []const u8, parseFn: anytype) !void { - var arena: ArenaAllocator = .init(std.testing.allocator); - defer arena.deinit(); - - const safe_input = padInput(input); - - const ast = try parseFn(std.testing.allocator, arena.allocator(), safe_input); - var ast_render: std.ArrayListUnmanaged(u8) = .empty; - defer ast_render.deinit(std.testing.allocator); - try ast.renderAst(ast_render.writer(std.testing.allocator), safe_input); - try std.testing.expectEqualStrings(expected, ast_render.items); -} - -test "Empty" { - try testParse("", - \\.document - \\ - ); -} - -test "Happy path paragraph" { - try testParse( - \\text - \\ - \\text - \\text - \\ - \\text - \\ text - \\ - , - \\.document - \\ .paragraph - \\ .text - \\ "text" - \\ .paragraph - \\ .text - \\ "text" - \\ .space_text - \\ "text" - \\ .paragraph - \\ .text - \\ "text" - \\ .space_text - \\ " text" - \\ - ); -} - -test "Happy path headings" { - try testParse( - \\# text - \\# text - \\# text - \\ text - \\ - \\# text - \\ - \\# text - \\ text - \\ - \\# text - \\ text - \\ - \\## text - \\## text - \\## text - \\ text - \\ - \\## text - \\ - \\## text - \\ text - \\ - \\## text - \\ text - \\ - , - \\.document - \\ .heading - \\ .marker - \\ "#" - \\ .text - \\ "text" - \\ .heading - \\ .marker - \\ "#" - \\ .text - \\ "text" - \\ .heading - \\ .marker - \\ "#" - \\ .text - \\ "text" - \\ .space_text - \\ "text" - \\ .heading - \\ .marker - \\ "#" - \\ .text - \\ "text" - \\ .heading - \\ .marker - \\ "#" - \\ .text - \\ "text" - \\ .space_text - \\ "text" - \\ .heading - \\ .marker - \\ "#" - \\ .text - \\ "text" - \\ .space_text - \\ " text" - \\ .heading - \\ .marker - \\ "##" - \\ .text - \\ "text" - \\ .heading - \\ .marker - \\ "##" - \\ .text - \\ "text" - \\ .heading - \\ .marker - \\ "##" - \\ .text - \\ "text" - \\ .space_text - \\ "text" - \\ .heading - \\ .marker - \\ "##" - \\ .text - \\ "text" - \\ .heading - \\ .marker - \\ "##" - \\ .text - \\ "text" - \\ .space_text - \\ "text" - \\ .heading - \\ .marker - \\ "##" - \\ .text - \\ "text" - \\ .space_text - \\ " text" - \\ - ); -} - -test "Happy path quote" { - try testParse( - \\> text - \\ text - \\ - \\> text - \\ text - \\> text - \\> text - \\text - \\ - , - \\.document - \\ .quote - \\ .paragraph - \\ .text - \\ "text" - \\ .space_text - \\ "text" - \\ .quote - \\ .paragraph - \\ .text - \\ "text" - \\ .space_text - \\ " text" - \\ .quote - \\ .paragraph - \\ .text - \\ "text" - \\ .quote - \\ .paragraph - \\ .text - \\ "text" - \\ .paragraph - \\ .text - \\ "text" - \\ - ); -} - -test "Happy path list" { - try testParseWithFn( - \\- text - \\- [ ] text - \\. text - \\: text - \\-- text - \\-- [ ] text - \\.. text - \\:: text - \\ - , - \\.document - \\ .list - \\ .unordered_item - \\ .marker - \\ "-" - \\ .text - \\ "text" - \\ .list - \\ .task_item - \\ .marker - \\ "- [ ]" - \\ .text - \\ "text" - \\ .list - \\ .ordered_item - \\ .marker - \\ "." - \\ .text - \\ "text" - \\ .list - \\ .term_item - \\ .marker - \\ ":" - \\ .text - \\ "text" - \\ .list - \\ .unordered_item - \\ .marker - \\ "--" - \\ .text - \\ "text" - \\ .list - \\ .task_item - \\ .marker - \\ "-- [ ]" - \\ .text - \\ "text" - \\ .list - \\ .ordered_item - \\ .marker - \\ ".." - \\ .text - \\ "text" - \\ .list - \\ .term_item - \\ .marker - \\ "::" - \\ .text - \\ "text" - \\ - , parse3); -} - -test "List grouping" { - try testParseWithFn( - \\- text - \\- text - \\-- text - \\- text - \\ - \\- text - \\paragraph - \\- text - \\. text - \\ - , - \\.document - \\ .list - \\ .unordered_item - \\ .marker - \\ "-" - \\ .text - \\ "text" - \\ .unordered_item - \\ .marker - \\ "-" - \\ .text - \\ "text" - \\ .unordered_item - \\ .marker - \\ "--" - \\ .text - \\ "text" - \\ .unordered_item - \\ .marker - \\ "-" - \\ .text - \\ "text" - \\ .unordered_item - \\ .marker - \\ "-" - \\ .text - \\ "text" - \\ .paragraph - \\ .text - \\ "paragraph" - \\ .list - \\ .unordered_item - \\ .marker - \\ "-" - \\ .text - \\ "text" - \\ .list - \\ .ordered_item - \\ .marker - \\ "." - \\ .text - \\ "text" - \\ - , parse3); -} - -test "Happy path list elaboration" { - try testParseWithFn( - \\- a - \\+ bb - \\ - \\ ccc - \\+ dddd - \\## heading - \\++ subtitle - \\ - , - \\.document - \\ .list - \\ .unordered_item - \\ .marker - \\ "-" - \\ .text - \\ "a" - \\ .elaboration - \\ .marker - \\ "+" - \\ .paragraph - \\ .text - \\ "bb" - \\ .paragraph - \\ .text - \\ "ccc" - \\ .elaboration - \\ .marker - \\ "+" - \\ .paragraph - \\ .text - \\ "dddd" - \\ .heading - \\ .marker - \\ "##" - \\ .text - \\ "heading" - \\ .elaboration - \\ .marker - \\ "++" - \\ .paragraph - \\ .text - \\ "subtitle" - \\ - , parse3); -} - -test "Elaboration errors" { - try testParseWithFn( - \\- a - \\++ bb - \\-- ccc - \\+ dddd - \\paragraph - \\+ eeeee - \\ - , - \\.document - \\ .list - \\ .unordered_item - \\ .error .incorrect_elaboration_marker - \\ .marker - \\ "-" - \\ .text - \\ "a" - \\ .elaboration - \\ .marker - \\ "++" - \\ .paragraph - \\ .text - \\ "bb" - \\ .unordered_item - \\ .error .incorrect_elaboration_marker - \\ .marker - \\ "--" - \\ .text - \\ "ccc" - \\ .elaboration - \\ .marker - \\ "+" - \\ .paragraph - \\ .text - \\ "dddd" - \\ .paragraph - \\ .text - \\ "paragraph" - \\ .elaboration - \\ .error .elaboration_after_unelaboratable_node - \\ .marker - \\ "+" - \\ .paragraph - \\ .text - \\ "eeeee" - \\ - , parse3); -} - -test "Thematic break" { - try testParse( - \\a - \\*** - \\bb - \\* - \\ccc - \\ - \\**bold text** - \\ - , - \\.document - \\ .paragraph - \\ .text - \\ "a" - \\ .thematic_break - \\ "***" - \\ .paragraph - \\ .text - \\ "bb" - \\ .space_text - \\ "*" - \\ .space_text - \\ "ccc" - \\ .paragraph - \\ .text - \\ "**bold text**" - \\ - ); -} - -test "Mixed indentation" { - // AstGen1 used to try very hard to recover when it sees a tab - try testParseWithFn("" ++ - "> aaa\n" ++ - "\n" ++ - "\tbbbbb\n", - \\.document - \\ .quote - \\ .error .inconsistent_indentation at 3:1 - \\ .paragraph - \\ .text - \\ "aaa" - \\ .paragraph - \\ .text - \\ "bbbbb" - \\ - , parse); - - try testParseWithFn("" ++ - "> aaa\n" ++ - "\n" ++ - "\tbbbbb\n", - \\.document - \\ .quote - \\ .paragraph - \\ .text - \\ "aaa" - \\ .paragraph - \\ .error .inconsistent_indentation at 3:1 - \\ .text - \\ "bbbbb" - \\ - , parse3); -} - -test "Tabs in text" { - try testParse("" ++ - "aaa\n" ++ - "\tbbbbb\n", - \\.document - \\ .paragraph - \\ .text - \\ "aaa" - \\ .space_text - \\ "\tbbbbb" - \\ - ); -} - -test "Empty line in heading" { - try testParse( - \\# heading - \\ - \\ text - \\ - \\text - \\ - , - \\.document - \\ .heading - \\ .marker - \\ "#" - \\ .text - \\ "heading" - \\ .space_text - \\ .error .unexpected_block_in_inline_context - \\ "text" - \\ .paragraph - \\ .text - \\ "text" - \\ - ); -} - -test "Super long line" { - const input = try std.testing.allocator.create([(1 << 24) * 4]u8); - defer std.testing.allocator.destroy(input); - @memset(input, 'a'); - var arena: ArenaAllocator = .init(std.testing.allocator); - defer arena.deinit(); - const ast = try parse(std.testing.allocator, arena.allocator(), input); - const taggedAst = try ast.toTagged(arena.allocator()); - try std.testing.expectEqualDeep(@as(Ast.Tagged, .{ - .nodes = &.{ - .{ .document = .{ .num_children = 1 } }, - .{ .paragraph = .{ .off = 0, .num_children = 1 } }, - .{ .text = .{ .off = 0, .len = 16777215 } }, - .{ .text = .{ .off = 16777215, .len = 16777215 } }, - .{ .text = .{ .off = 33554430, .len = 16777215 } }, - .{ .text = .{ .off = 50331645, .len = 16777215 } }, - .{ .text = .{ .off = 67108860, .len = 4 } }, - }, - .errors = &.{}, - .extra = &.{}, - }), taggedAst); -} - -test "Many short lines" { - const input = try std.testing.allocator.create([(1 << 25) - 4]u8); - defer std.testing.allocator.destroy(input); - @memset(@as(*[(1 << 24) - 2][2]u8, @ptrCast(input)), "a\n"[0..2].*); - - var arena: ArenaAllocator = .init(std.testing.allocator); - defer arena.deinit(); - const ast = try parse(std.testing.allocator, arena.allocator(), input); - try std.testing.expectEqual(1 << 24, ast.nodes.len); - try std.testing.expectEqual( - @as(Ast.Node.Tagged, .{ .document = .{ .num_children = 1 } }), - ast.nodes[0].toTagged(), - ); - try std.testing.expectEqual( - @as(Ast.Node.Tagged, .{ .paragraph = .{ .off = 0, .num_children = (1 << 24) - 2 } }), - ast.nodes[1].toTagged(), - ); - try std.testing.expectEqual( - @as(Ast.Node.Tagged, .{ .text = .{ .off = 0, .len = 1 } }), - ast.nodes[2].toTagged(), - ); - for (1..(1 << 24) - 2) |i| { - try std.testing.expectEqual( - @as(Ast.Node.Tagged, .{ .space_text = .{ .off = @intCast(i * 2), .len = 1 } }), - ast.nodes[i + 2].toTagged(), - ); - } -}