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, ¤t_line); if (token_found) break; } try self.inlineParser(alloc, ¤t_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; } };