diff --git a/README.tlm b/README.tlm index 1b16d29..94fcd21 100644 --- a/README.tlm +++ b/README.tlm @@ -4,13 +4,13 @@ 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) ^ Links with no alt text -#[https://krzak.org/krzak-icon.png] +#[https://krzak.org/krzak-icon.png|] *italic* **bold** @@ -52,5 +52,7 @@ main :: proc() { H_2_O_4_U is uranium peroxide Here is some __underlined__ text +[Go to Top](#Heading) + [&1] Ordered list item 2 not included [&named] This is the definition for a named footnote diff --git a/src/html.odin b/src/html.odin index 55b4aa4..8d94979 100644 --- a/src/html.odin +++ b/src/html.odin @@ -1,6 +1,24 @@ package main import "core:fmt" +import "core:strings" + +get_text_content :: proc(token: ^Token, b: ^strings.Builder) { + if token.type == .Text { + if v, ok := token.value.(string); ok { + strings.write_string(b, v) + } + } + for child in token.children { + get_text_content(child, b) + } +} + +slugifyifyifyify :: proc(text: string) -> string { + lower := strings.to_lower(text, context.temp_allocator) + res, _ := strings.replace_all(lower, " ", "-", context.temp_allocator) + return res +} print_html :: proc(token: ^Token) { switch token.type { @@ -19,7 +37,14 @@ print_html :: proc(token: ^Token) { if v, ok := token.value.(int); ok { level = v } - fmt.printf("", level) + + b := strings.builder_make(context.temp_allocator) + for child in token.children { + get_text_content(child, &b) + } + slug := slugifyifyifyify(strings.to_string(b)) + + fmt.printf("", level, slug) for child in token.children { print_html(child) } @@ -234,6 +259,25 @@ print_html :: proc(token: ^Token) { } fmt.print("") + case TokenType.HeaderLink: + if target, ok := token.value.(string); ok { + slug := slugifyifyifyify(target) + fmt.printf("", slug) + for child in token.children { + print_html(child) + } + fmt.print("") + } + + case TokenType.Link: + if url, ok := token.value.(string); ok { + fmt.printf("", url) + 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 b032076..2d0dff0 100644 --- a/src/main.odin +++ b/src/main.odin @@ -71,6 +71,10 @@ print_token :: proc(token: ^Token, depth: int = 0) { type_str = "Superscript" case TokenType.Subscript: type_str = "Subscript" + case TokenType.HeaderLink: + type_str = "HeaderLink" + case TokenType.Link: + type_str = "Link" } value_str := "" diff --git a/src/parser.odin b/src/parser.odin index cbb62d1..f4abf89 100644 --- a/src/parser.odin +++ b/src/parser.odin @@ -32,6 +32,8 @@ TokenType :: enum { Underline, Superscript, Subscript, + HeaderLink, + Link, } TokenValue :: union { @@ -351,6 +353,129 @@ parse_inline_footnotes :: proc(token: ^Token) { } } +match_pair :: proc(s: string, open1, close1, open2, close2: byte) -> (int, string, string, bool) { + if len(s) == 0 || s[0] != open1 { + return 0, "", "", false + } + close1_idx := strings.index_byte(s, close1) + if close1_idx == -1 { + return 0, "", "", false + } + + if close1_idx + 1 < len(s) && s[close1_idx + 1] == open2 { + close2_idx := strings.index_byte(s[close1_idx + 2:], close2) + if close2_idx != -1 { + close2_idx += close1_idx + 2 + return close2_idx + 1, s[1:close1_idx], s[close1_idx + 2:close2_idx], true + } + } + return 0, "", "", false +} + +match_single :: proc(s: string, open, close: byte) -> (int, string, bool) { + if len(s) == 0 || s[0] != open { + return 0, "", false + } + close_idx := strings.index_byte(s, close) + if close_idx == -1 { + return 0, "", false + } + return close_idx + 1, s[1:close_idx], true +} + +find_link :: proc( + str: string, +) -> ( + start_idx, end_idx: int, + is_link: bool, + url: string, + alt: string, + is_header: bool, +) { + for i := 0; i < len(str); i += 1 { + if str[i] == '#' && i + 1 < len(str) { + if str[i + 1] == '[' { + if end, alt_text, url_text, ok := match_pair(str[i + 1:], '[', ']', '(', ')'); ok { + return i, i + 1 + end, true, url_text, alt_text, false + } + if end, inner, ok := match_single(str[i + 1:], '[', ']'); ok { + return i, i + 1 + end, true, inner, inner, false + } + } else if str[i + 1] == '(' { + if end, url_text, alt_text, ok := match_pair(str[i + 1:], '(', ')', '[', ']'); ok { + return i, i + 1 + end, true, url_text, alt_text, false + } + if end, inner, ok := match_single(str[i + 1:], '(', ')'); ok { + return i, i + 1 + end, true, inner, inner, false + } + } + } + + if str[i] == '[' { + if end, alt_text, url_text, ok := match_pair(str[i:], '[', ']', '(', ')'); ok { + if strings.has_prefix(url_text, "#") { + return i, i + end, true, url_text[1:], alt_text, true + } + } + if end, inner, ok := match_single(str[i:], '[', ']'); ok { + if strings.has_prefix(inner, "#") { + return i, i + end, true, inner[1:], inner[1:], true + } + } + } else if str[i] == '(' { + if end, url_text, alt_text, ok := match_pair(str[i:], '(', ')', '[', ']'); ok { + if strings.has_prefix(url_text, "#") { + return i, i + end, true, url_text[1:], alt_text, true + } + } + if end, inner, ok := match_single(str[i:], '(', ')'); ok { + if strings.has_prefix(inner, "#") { + return i, i + end, true, inner[1:], inner[1:], true + } + } + } + } + + return -1, -1, false, "", "", false +} + +parse_links :: proc(token: ^Token) { + if token.type == .CodeBlock || token.type == .InlineCode { + return + } + if token.children != nil { + for i := 0; i < len(token.children); i += 1 { + parse_links(token.children[i]) + } + } + + if token.type == .Text { + str, ok := token.value.(string) + if !ok { + return + } + + start_idx, end_idx, is_link, url, alt, is_header := find_link(str) + if !is_link { + return + } + + token.value = nil + + if start_idx > 0 { + push_token(token, .Text, str[:start_idx]) + } + + link_token := push_token(token, .HeaderLink if is_header else .Link, url) + push_token(link_token, .Text, alt) + + if end_idx < len(str) { + rem := push_token(token, .Text, str[end_idx:]) + parse_links(rem) + } + } +} + add_breaklines :: proc(token: ^Token) { if token.type == .CodeBlock { return @@ -542,8 +667,9 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { for token in root.children { parse_inline_code(token) - pass_through_children(token) + parse_links(token) parse_inline_footnotes(token) + pass_through_children(token) } // FOURTH PASS