Init
This commit is contained in:
commit
c4b460894d
2 changed files with 205 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
zig-cache/
|
||||
zig-out/
|
203
markdown.zig
Normal file
203
markdown.zig
Normal file
|
@ -0,0 +1,203 @@
|
|||
pub const std = @import("std");
|
||||
|
||||
pub const Result = struct { result: Block, rest: []const u8 };
|
||||
const definedBlockTypes = [_]type{ Heading, CodeBlock };
|
||||
const blockTypes = definedBlockTypes ++ [_]type{Paragraph};
|
||||
|
||||
pub const Block = union(enum) {
|
||||
paragraph: Paragraph,
|
||||
heading: Heading,
|
||||
code_block: CodeBlock,
|
||||
};
|
||||
|
||||
pub const Paragraph = struct {
|
||||
text: []const u8,
|
||||
|
||||
pub fn parse(allocator: ?*std.mem.Allocator, input: []const u8) !?Result {
|
||||
_ = allocator;
|
||||
var rest = input;
|
||||
if (rest.len == 0) return null;
|
||||
while (rest.len > 0) : (rest = rest[1..]) {
|
||||
inline for (definedBlockTypes) |BlockType| {
|
||||
if (try BlockType.parse(null, rest)) |_| {
|
||||
const text = trim(input[0 .. input.len - rest.len]);
|
||||
if (text.len == 0) return null;
|
||||
return Result{ .result = .{
|
||||
.paragraph = .{ .text = text },
|
||||
}, .rest = rest };
|
||||
}
|
||||
}
|
||||
if (std.mem.startsWith(u8, rest, "\n\n")) {
|
||||
const text = trim(input[0 .. input.len - rest.len]);
|
||||
if (text.len == 0) return null;
|
||||
return Result{ .result = .{
|
||||
.paragraph = .{ .text = text },
|
||||
}, .rest = rest };
|
||||
}
|
||||
}
|
||||
const text = trim(input);
|
||||
if (text.len == 0) return null;
|
||||
return Result{ .result = .{
|
||||
.paragraph = .{ .text = text },
|
||||
}, .rest = input[input.len..] };
|
||||
}
|
||||
};
|
||||
|
||||
pub const Heading = struct {
|
||||
text: []const u8,
|
||||
level: Level,
|
||||
|
||||
pub const Level = enum(u3) {
|
||||
one = 1,
|
||||
two = 2,
|
||||
three = 3,
|
||||
four = 4,
|
||||
five = 5,
|
||||
six = 6,
|
||||
};
|
||||
|
||||
pub fn parse(_: ?*std.mem.Allocator, input: []const u8) !?Result {
|
||||
inline for (std.meta.fields(Level)) |level| {
|
||||
const str = ("#" ** level.value) ++ " ";
|
||||
if (std.mem.startsWith(u8, input, str)) {
|
||||
const text = untilNewline(input[str.len..]);
|
||||
return Result{ .result = .{
|
||||
.heading = .{
|
||||
.text = text,
|
||||
.level = @intToEnum(Level, level.value),
|
||||
},
|
||||
}, .rest = input[str.len + text.len ..] };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const CodeBlock = struct {
|
||||
language: []const u8,
|
||||
text: []const u8,
|
||||
|
||||
pub fn parse(_: ?*std.mem.Allocator, input: []const u8) !?Result {
|
||||
if (std.mem.startsWith(u8, input, "```")) {
|
||||
const language = std.mem.sliceTo(input[3..], '\n');
|
||||
const rest = input[3 + language.len + 1 ..];
|
||||
if (std.mem.indexOf(u8, rest, "```")) |end| {
|
||||
return Result{
|
||||
.result = .{
|
||||
.code_block = .{
|
||||
.language = language,
|
||||
.text = trim(rest[0..end]),
|
||||
},
|
||||
},
|
||||
.rest = rest[end + 3 ..],
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn untilNewline(input: []const u8) []const u8 {
|
||||
return if (std.mem.indexOf(u8, input, "\n")) |index| input[0..index] else input;
|
||||
}
|
||||
|
||||
/// Returns the index of where the next block starts.
|
||||
pub fn untilNextBlock(input: []const u8) usize {
|
||||
for (input) |_, i| {
|
||||
const rest = input[i..];
|
||||
if (std.mem.startsWith(u8, rest, "\n\n") or
|
||||
Heading.parse(rest) != null or
|
||||
CodeBlock.parse(rest) != null)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return input.len;
|
||||
}
|
||||
pub fn trim(input: []const u8) []const u8 {
|
||||
return std.mem.trim(u8, input, " \n");
|
||||
}
|
||||
|
||||
pub const Tree = std.ArrayList(Block);
|
||||
pub fn parse(
|
||||
allocator: *std.mem.Allocator,
|
||||
input: []const u8,
|
||||
) !Tree {
|
||||
var tree = Tree.init(allocator);
|
||||
errdefer tree.deinit();
|
||||
|
||||
const orig = trim(input);
|
||||
var rest = orig;
|
||||
while (rest.len > 0) {
|
||||
var ran = false;
|
||||
inline for (blockTypes) |blockType| {
|
||||
if (try blockType.parse(allocator, rest)) |result| {
|
||||
try tree.append(result.result);
|
||||
rest = result.rest;
|
||||
// It would be better to just continue the outer while loop but
|
||||
// that makes the compiler crash.
|
||||
ran = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ran) rest = rest[1..];
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = &gpa.allocator;
|
||||
|
||||
const input = try std.io.getStdIn().readToEndAlloc(allocator, 10241024);
|
||||
defer allocator.free(input);
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
|
||||
const parsed = try parse(allocator, input);
|
||||
defer parsed.deinit();
|
||||
|
||||
for (parsed.items) |block| {
|
||||
switch (block) {
|
||||
.paragraph => |p| try stdout.print("<p>{s}</p>", .{p.text}),
|
||||
.heading => |h| try stdout.print("<h{0}>{1s}</h{0}>", .{ @enumToInt(h.level), h.text }),
|
||||
.code_block => |c| try stdout.print("<pre><code>{s}</code></pre>", .{c.text}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "parse headings and paragraph" {
|
||||
const allocator = std.testing.allocator;
|
||||
const input =
|
||||
\\# Hola
|
||||
\\## Hola
|
||||
\\
|
||||
\\### Heey
|
||||
\\
|
||||
\\:)
|
||||
\\## Hola mundo
|
||||
;
|
||||
|
||||
const parsed = try parse(allocator, input);
|
||||
defer parsed.deinit();
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 5), parsed.items.len);
|
||||
|
||||
try std.testing.expectEqualStrings("heading", @tagName(parsed.items[0]));
|
||||
try std.testing.expect(parsed.items[0].heading.level == .one);
|
||||
try std.testing.expectEqualStrings("Hola", parsed.items[0].heading.text);
|
||||
|
||||
try std.testing.expectEqualStrings("heading", @tagName(parsed.items[1]));
|
||||
try std.testing.expect(parsed.items[1].heading.level == .two);
|
||||
try std.testing.expectEqualStrings("Hola", parsed.items[1].heading.text);
|
||||
|
||||
try std.testing.expectEqualStrings("heading", @tagName(parsed.items[2]));
|
||||
try std.testing.expect(parsed.items[2].heading.level == .three);
|
||||
try std.testing.expectEqualStrings("Heey", parsed.items[2].heading.text);
|
||||
|
||||
try std.testing.expectEqualStrings("paragraph", @tagName(parsed.items[3]));
|
||||
try std.testing.expectEqualStrings(":)", parsed.items[3].paragraph.text);
|
||||
|
||||
try std.testing.expectEqualStrings("heading", @tagName(parsed.items[4]));
|
||||
try std.testing.expectEqualStrings("Hola mundo", parsed.items[4].heading.text);
|
||||
}
|
Reference in a new issue