diff --git a/README.tlm b/README.tlm index 2d54b1c..020e849 100644 --- a/README.tlm +++ b/README.tlm @@ -4,7 +4,7 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam blandit dolor nibh, vitae tempor enim pretium non. Sed in condimentum turpis. Vivamus placerat molestie ullamcorper. Maecenas nec risus nibh. Nullam vel vestibulum sapien. Integer ante erat, aliquam id tellus in, malesuada finibus nunc. Sed porttitor ante orci, eget molestie nibh posuere nec. #[Link on the right](http://example.com) >> -(http://example.com)[Example.com]^ << +(http://example.com)[Example.com]# << #[Link to nekoweb.org in the middle](https://n0va.nekoweb.org) ^^ #[https://krzak.org] #(https://krzak.org) diff --git a/src/html.odin b/src/html.odin index d19eafa..7f29771 100644 --- a/src/html.odin +++ b/src/html.odin @@ -76,6 +76,97 @@ print_html :: proc(token: ^Token) { } fmt.print("") + case TokenType.Table: + fmt.print("\n") + for child in token.children { + print_html(child) + } + fmt.print("
\n") + + case TokenType.TableHeaderRow: + fmt.print("") + for child in token.children { + align := "" + if len(child.children) > 0 { + first_child := child.children[0] + if first_child.type == .Left { + align = "left" + } else if first_child.type == .Right { + align = "right" + } else if first_child.type == .Middle { + align = "center" + } + } + if align != "" { + fmt.printf("", align) + for grandchild in child.children[0].children { + print_html(grandchild) + } + } else { + fmt.print("") + for grandchild in child.children { + print_html(grandchild) + } + } + fmt.print("") + } + fmt.print("\n") + + case TokenType.TableRow: + fmt.print("") + for child in token.children { + print_html(child) + } + fmt.print("\n") + + case TokenType.TableCell: + align := "" + if len(token.children) > 0 { + first_child := token.children[0] + if first_child.type == .Left { + align = "left" + } else if first_child.type == .Right { + align = "right" + } else if first_child.type == .Middle { + align = "center" + } + } + if align != "" { + fmt.printf("", align) + for child in token.children[0].children { + print_html(child) + } + } else { + fmt.print("") + for child in token.children { + print_html(child) + } + } + fmt.print("") + + case TokenType.TableSeparator: + fmt.print("\n") + + case TokenType.Left: + fmt.print("
") + for child in token.children { + print_html(child) + } + fmt.print("
") + + case TokenType.Right: + fmt.print("
") + for child in token.children { + print_html(child) + } + fmt.print("
") + + case TokenType.Middle: + fmt.print("
") + for child in token.children { + print_html(child) + } + fmt.print("
") case TokenType.BreakLine: fmt.print("
\n") diff --git a/src/main.odin b/src/main.odin index 0797f87..735d58d 100644 --- a/src/main.odin +++ b/src/main.odin @@ -39,6 +39,22 @@ print_token :: proc(token: ^Token, depth: int = 0) { type_str = "ListItem" case TokenType.Strikethrough: type_str = "Strikethrough" + case TokenType.Table: + type_str = "Table" + case TokenType.TableRow: + type_str = "TableRow" + case TokenType.TableHeaderRow: + type_str = "TableHeaderRow" + case TokenType.TableCell: + type_str = "TableCell" + case TokenType.TableSeparator: + type_str = "TableSeparator" + case TokenType.Left: + type_str = "Left" + case TokenType.Right: + type_str = "Right" + case TokenType.Middle: + type_str = "Middle" } value_str := "" diff --git a/src/parser.odin b/src/parser.odin index e812a90..4e03ac9 100644 --- a/src/parser.odin +++ b/src/parser.odin @@ -16,6 +16,14 @@ TokenType :: enum { OrderedList, ListItem, Strikethrough, + Table, + TableRow, + TableHeaderRow, + TableCell, + TableSeparator, + Left, + Right, + Middle, } TokenValue :: union { @@ -37,6 +45,87 @@ push_token :: proc(parent: ^Token, type: TokenType, value: TokenValue = nil) -> return token } +is_table_separator :: proc(line: string) -> bool { + inner := line[1:len(line) - 1] + cells := strings.split(inner, "|", context.temp_allocator) + if len(cells) == 0 { + return false + } + for cell in cells { + trimmed := strings.trim_space(cell) + if len(trimmed) == 0 { + return false + } + for ch in trimmed { + if ch != '-' { + return false + } + } + } + return true +} + +parse_table_cells :: proc(line: string) -> []string { + inner := line[1:len(line) - 1] + cells := strings.split(inner, "|", context.temp_allocator) + result := make([]string, len(cells), context.temp_allocator) + for cell, i in cells { + result[i] = strings.trim_space(cell) + } + return result +} + +get_alignment_token :: proc(text: string) -> (TokenType, bool) { + trimmed := strings.trim_space(text) + if len(trimmed) == 0 { + return .Text, false + } + if strings.has_suffix(trimmed, ">>") || strings.has_prefix(trimmed, ">>") { + return .Right, true + } + if strings.has_suffix(trimmed, "<<") || strings.has_prefix(trimmed, "<<") { + return .Left, true + } + if strings.has_suffix(trimmed, "^^") || strings.has_prefix(trimmed, "^^") { + return .Middle, true + } + return .Text, false +} + +push_text :: proc(parent: ^Token, text: string) { + align_type, has_align := get_alignment_token(text) + cleaned := strip_alignment_markers(text) + + target := parent + if has_align { + target = push_token(parent, align_type, nil) + } + push_token(target, TokenType.Text, cleaned) +} + +strip_alignment_markers :: proc(text: string) -> string { + trimmed := strings.trim_space(text) + if strings.has_suffix(trimmed, ">>") { + return strings.trim_space(trimmed[:len(trimmed) - 2]) + } + if strings.has_prefix(trimmed, ">>") { + return strings.trim_space(trimmed[2:]) + } + if strings.has_suffix(trimmed, "<<") { + return strings.trim_space(trimmed[:len(trimmed) - 2]) + } + if strings.has_prefix(trimmed, "<<") { + return strings.trim_space(trimmed[2:]) + } + if strings.has_suffix(trimmed, "^^") { + return strings.trim_space(trimmed[:len(trimmed) - 2]) + } + if strings.has_prefix(trimmed, "^^") { + return strings.trim_space(trimmed[2:]) + } + return trimmed +} + parse_header :: proc(line: string) -> (int, string) { if len(line) == 0 { return 0, line @@ -161,9 +250,19 @@ add_breaklines :: proc(token: ^Token) { child := token.children[i] add_breaklines(child) append(&new_children, child) - if child.type == TokenType.Text && - i + 1 < len(token.children) && - token.children[i + 1].type == TokenType.Text { + is_child_inline := + child.type == TokenType.Text || + child.type == TokenType.Left || + child.type == TokenType.Right || + child.type == TokenType.Middle + is_next_inline := + i + 1 < len(token.children) && + (token.children[i + 1].type == TokenType.Text || + token.children[i + 1].type == TokenType.Left || + token.children[i + 1].type == TokenType.Right || + token.children[i + 1].type == TokenType.Middle) + + if is_child_inline && is_next_inline { br := new(Token) br^ = Token{TokenType.BreakLine, nil, nil, token} append(&new_children, br) @@ -189,7 +288,7 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { header_level, text := parse_header(line) if header_level > 0 { current_token = push_token(&root, TokenType.Header, header_level) - push_token(current_token, TokenType.Text, text) + push_text(current_token, text) current_token = &root continue } @@ -200,7 +299,7 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { push_token(current_token, TokenType.Paragraph, nil) } last_p := current_token.children[len(current_token.children) - 1] - push_token(last_p, TokenType.Text, text[2:]) + push_text(last_p, text[2:]) continue } @@ -209,7 +308,7 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { current_token = push_token(&root, TokenType.UnorderedList, nil) } li := push_token(current_token, TokenType.ListItem, nil) - push_token(li, TokenType.Text, text[2:]) + push_text(li, text[2:]) continue } @@ -228,24 +327,47 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { current_token = push_token(&root, TokenType.OrderedList, item) } li := push_token(current_token, TokenType.ListItem, nil) - push_token(li, TokenType.Text, text) + push_text(li, text) continue } } + if len(text) >= 3 && text[0] == '|' && text[len(text) - 1] == '|' { + if current_token.type != TokenType.Table { + current_token = push_token(&root, TokenType.Table, nil) + } + + if is_table_separator(text) { + row_count := len(current_token.children) + if row_count == 1 { + current_token.children[0].type = TokenType.TableHeaderRow + } else if row_count > 1 { + push_token(current_token, TokenType.TableSeparator, nil) + } + } else { + row := push_token(current_token, TokenType.TableRow, nil) + cells := parse_table_cells(text) + for cell_text in cells { + cell := push_token(row, TokenType.TableCell, nil) + push_text(cell, cell_text) + } + } + continue + } + // LAST-CASE SCENARIO: NORMAL TEXT if current_token.type == .Blockquote { last_p := current_token.children[len(current_token.children) - 1] - push_token(last_p, TokenType.Text, line) + push_text(last_p, line) } else if current_token.type == .UnorderedList || current_token.type == .OrderedList { last_li := current_token.children[len(current_token.children) - 1] - push_token(last_li, TokenType.Text, line) + push_text(last_li, line) } else { if current_token.type != .Paragraph { current_token = push_token(&root, TokenType.Paragraph, nil) } - push_token(current_token, TokenType.Text, line) + push_text(current_token, line) } } diff --git a/styles.css b/styles.css index 07c8e25..ef745d6 100644 --- a/styles.css +++ b/styles.css @@ -49,8 +49,13 @@ th, td { border: 1px solid #444; padding: 0.5em; - text-align: left; } th { background: #2a2a2a; } +tr.table-sep td { + border: none; + border-top: 3px solid #ff6b9d; + padding: 0; + height: 0; +}