Files
pandas-markdown-parser/src/attempt_2.zig
sirlilpanda 579729705a wip
2026-08-31 14:04:39 +12:00

266 lines
8.5 KiB
Zig

const Token = @import("Token.zig");
const std = @import("std");
pub fn stripWhiteSpace(line_with_white_space: []const u8) []const u8 {
return std.mem.trimStart(u8, line_with_white_space, " \t");
}
pub fn countWhiteSpace(line_with_white_space: []const u8, tab_width: usize) usize {
var sum: usize = 0;
for (line_with_white_space) |char| {
if (char == ' ') {
sum += 1;
continue;
}
if (char == '\t') {
sum += tab_width;
continue;
}
break;
}
return sum;
}
const DocumentStyle = struct {
const Self = @This();
heading_char: u8 = '#',
heading_levels: usize = 6,
unordered_chars: []const u8 = "-++*",
pub const default = Self{
.heading_char = '#',
.heading_levels = 6,
.unordered_chars = "-++*",
};
};
pub const Document = struct {
const Self = @This();
const Newline = struct {
chars: []const u8,
pub const newline = Newline{
.chars = "\n",
};
pub const newline_carriage_return = Newline{
.chars = "\n\r",
};
};
const FoundToken = bool;
const StartLineParserFn = *const fn (self: *Self, alloc: std.mem.Allocator, line: *[]const u8) anyerror!FoundToken;
const InlineParserFn = *const fn (self: *Self, alloc: std.mem.Allocator, line: *[]const u8) anyerror!FoundToken;
const EndLineParserFn = *const fn (self: *Self, alloc: std.mem.Allocator, line: *[]const u8) anyerror!FoundToken;
const MultiLineParserFn = *const fn (self: *Self, alloc: std.mem.Allocator, line_itter: *std.mem.SplitIterator(u8)) anyerror!void;
tokens: std.ArrayList(Token) = .empty,
tab_width: usize = 4,
newline_char: Newline = .newline,
document_style: DocumentStyle = .default,
// each function will
start_line_parsers: std.ArrayList(StartLineParserFn) = .empty,
inline_parsers: std.ArrayList(InlineParserFn) = .empty,
end_line_parsers: std.ArrayList(EndLineParserFn) = .empty,
pub fn initDefault(alloc: std.mem.Allocator) !Self {
var doc = Self{
.tab_width = 4,
.newline_char = .newline,
.document_style = .default,
};
try doc.appendStartLineParsers(alloc, &[_]StartLineParserFn{
&headingParser,
&listOrderedParser,
});
try doc.appendInlineParsers(alloc, &[_]InlineParserFn{
&baseLineParser,
});
return doc;
}
pub fn appendStartLineParsers(self: *Self, alloc: std.mem.Allocator, parser_fns: []const StartLineParserFn) !void {
try self.start_line_parsers.appendSlice(alloc, parser_fns);
}
pub fn appendInlineParsers(self: *Self, alloc: std.mem.Allocator, parser_fns: []const InlineParserFn) !void {
try self.inline_parsers.appendSlice(alloc, parser_fns);
}
pub fn appendEndLineParsers(self: *Self, alloc: std.mem.Allocator, parser_fns: []const EndLineParserFn) !void {
try self.end_line_parsers.appendSlice(alloc, parser_fns);
}
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
self.tokens.deinit(alloc);
self.start_line_parsers.deinit(alloc);
self.end_line_parsers.deinit(alloc);
self.inline_parsers.deinit(alloc);
}
fn consumeChars(line: *[]const u8, amount_of_chars_to_consume: usize) error{too_many_chars_to_consume}!void {
if (line.len < amount_of_chars_to_consume) return error.too_many_chars_to_consume;
line.* = line.*[amount_of_chars_to_consume..];
}
fn baseLineParser(self: *Self, alloc: std.mem.Allocator, line: *[]const u8) !FoundToken {
try self.tokens.append(alloc, Token{
.data = stripWhiteSpace(line.*),
.padding = countWhiteSpace(line.*, self.tab_width),
.type = .text,
});
return true;
}
// im going to need to hide a stack in here somewhere
fn inlineParser(self: *Self, alloc: std.mem.Allocator, line: *[]const u8) !void {
if (line.len == 0) return;
for (self.inline_parsers.items) |parser| {
_ = try parser(self, alloc, line);
}
}
fn appendNewLineToken(self: *Self, alloc: std.mem.Allocator) !void {
try self.tokens.append(alloc, Token{
.data = "\n",
.padding = 0,
.type = .newline,
});
}
pub fn parse(self: *Self, alloc: std.mem.Allocator, document: []const u8) !void {
var line_iter = std.mem.splitSequence(u8, document, self.newline_char.chars);
while (line_iter.next()) |line| {
var current_line = line;
for (self.start_line_parsers.items) |parser| {
std.debug.print("line : \"{s}\"\n", .{line});
const token_found = try parser(self, alloc, &current_line);
if (token_found) break;
}
try self.inlineParser(alloc, &current_line);
try self.appendNewLineToken(alloc);
}
}
pub fn headingParser(self: *Self, alloc: std.mem.Allocator, line: *[]const u8) !FoundToken {
// ignore empty lines
if (line.len == 0) return false;
const heading_end_index: usize = blk: {
var i: usize = 0;
while (i < self.document_style.heading_levels) : (i += 1) {
if (line.*[i] == self.document_style.heading_char) continue;
break;
}
break :blk i;
};
// if no heading was detected
if (heading_end_index == 0) return false;
// allows for blank headings
const space_remove: usize = if (line.len == heading_end_index) 0 else 1;
try self.tokens.append(alloc, Token{
.data = line.*[0..heading_end_index],
.padding = 0,
.type = .heading,
});
try consumeChars(line, heading_end_index + space_remove);
return true;
}
pub fn listOrderedParser(self: *Self, alloc: std.mem.Allocator, line: *[]const u8) !FoundToken {
const clean_line = stripWhiteSpace(line.*);
const whitespace_chars_removed = countWhiteSpace(line.*, self.tab_width);
// check for the min length of chars
if (clean_line.len < 2) return false;
// find a point within the line
const index_of_dot = blk: {
if (std.mem.indexOf(u8, clean_line, ". ")) |i| {
break :blk i;
}
return false;
};
const number = clean_line[0..index_of_dot];
// if it has any spaces its not a order list id
if (std.mem.containsAtLeastScalar(u8, number, 1, ' ') or
std.mem.containsAtLeastScalar(u8, number, 1, '\t'))
{
return false;
}
// now work out what the number is
// "I": 1,
// "V": 5,
// "X": 10,
// "L": 50,
// "C": 100,
// "D": 500,
// "M": 1000
// roman numerials ill deal with these later
// these can be the same
// numbers
// letters & roman (at least all the i's)
const is_number = blk: {
_ = std.fmt.parseInt(usize, number, 10) catch break :blk false;
break :blk true;
};
const is_letter = std.ascii.isAlphabetic(number[0]);
// might change this so lists can start from 0 like a real programmer
if (is_number or is_letter) {
try self.tokens.append(alloc, Token{
.data = line.*[0 .. whitespace_chars_removed + number.len + 1],
.padding = whitespace_chars_removed,
.type = .ordered_list,
});
try consumeChars(line, whitespace_chars_removed + number.len + 2);
}
return false;
}
pub fn listUnorderedParser(self: *Self, alloc: std.mem.Allocator, line: *[]const u8) !FoundToken {
const clean_line = stripWhiteSpace(line);
const whitespace_chars_removed = @intFromPtr(clean_line.ptr) - @intFromPtr(line.ptr);
// check for the min length of chars
if (clean_line.len < 2) return null;
// check there is a space seperating the char and the text
if (clean_line[1] != ' ') return null;
if (clean_line[0] == '-' or clean_line[0] == '=' or clean_line[0] == '+' or clean_line[0] == '*') {
return LineParserReturn{
// +2 to remove "-. "
.pointer_offset = whitespace_chars_removed + 2,
.token = .{
.data = clean_line[0..1],
.type = .unordered_list,
},
};
}
return null;
}
};