diff --git a/README.tlm b/README.tlm index c2db2bf..5bd261d 100644 --- a/README.tlm +++ b/README.tlm @@ -8,7 +8,7 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam blandit dolor nibh, #[Link to nekoweb.org in the middle](https://n0va.nekoweb.org) ^^ #[https://krzak.org] #(https://krzak.org) -^ Links with no alt text +\^ Links with no alt text $[https://krzak.org/krzak-icon.png|img] $[/staircase.mp3] @@ -56,6 +56,8 @@ Here is some __underlined__ text [Go to Top](#Heading) +! Escaped star: \* + [&&1] Ordered list item 2 not included [&&named] This is the definition for a named footnote @@ -65,6 +67,7 @@ $(counter=0) $(link=https://git.krzak.org/N0VA/TypoML) `tlm.counter` = $(counter) +\^ This is JS!!! #($(link))[Repo link] diff --git a/src/html.odin b/src/html.odin index 216453b..a2f8782 100644 --- a/src/html.odin +++ b/src/html.odin @@ -5,8 +5,10 @@ import "core:strings" import "core:unicode" escape_html :: proc(s: string, allocator := context.temp_allocator) -> string { + escaped := decode_escapes(s, allocator) + b := strings.builder_make(allocator) - for ch in s { + for ch in escaped { switch ch { case '&': strings.write_string(&b, "&") diff --git a/src/parser.odin b/src/parser.odin index 3bd1f8c..efbd97c 100644 --- a/src/parser.odin +++ b/src/parser.odin @@ -121,6 +121,15 @@ push_text :: proc(parent: ^Token, text: string) { push_token(target, TokenType.Text, cleaned) } +push_text_line :: proc(target: ^Token, text: string) { + if len(target.children) > 0 { + br := new(Token) + br^ = Token{TokenType.BreakLine, nil, nil, target} + append(&target.children, br) + } + push_text(target, text) +} + strip_alignment_markers :: proc(text: string) -> string { trimmed := strings.trim_space(text) if strings.has_suffix(trimmed, ">>") { @@ -144,6 +153,92 @@ strip_alignment_markers :: proc(text: string) -> string { return trimmed } +ESCAPE_PLACEHOLDER_BASE :: 0xE000 // Unicode Private Use Area + +is_escapable_char :: proc(b: byte) -> bool { + switch b { + case '\\', + '*', + '_', + '~', + '^', + '`', + '[', + ']', + '(', + ')', + '$', + '&', + '!', + '>', + '|', + '-', + '=', + '#', + '.': + return true + } + return false +} + +escape_placeholder_rune :: proc(b: byte) -> rune { + return rune(ESCAPE_PLACEHOLDER_BASE + int(b)) +} + +is_escape_placeholder :: proc(r: rune) -> bool { + return r >= ESCAPE_PLACEHOLDER_BASE && r < ESCAPE_PLACEHOLDER_BASE + 256 +} + +escape_placeholder_to_byte :: proc(r: rune) -> byte { + return byte(int(r) - ESCAPE_PLACEHOLDER_BASE) +} + +unescape_backslashes :: proc(s: string, allocator := context.temp_allocator) -> string { + if !strings.contains(s, "\\") { + return s + } + + b := strings.builder_make(allocator) + i := 0 + for i < len(s) { + if s[i] == '\\' && i + 1 < len(s) && is_escapable_char(s[i + 1]) { + strings.write_rune(&b, escape_placeholder_rune(s[i + 1])) + i += 2 + } else { + strings.write_byte(&b, s[i]) + i += 1 + } + } + return strings.to_string(b) +} + +decode_escapes :: proc(s: string, allocator := context.temp_allocator) -> string { + has_placeholder := false + for r in s { + if is_escape_placeholder(r) { + has_placeholder = true + break + } + } + if !has_placeholder { + return s + } + + b := strings.builder_make(allocator) + for r in s { + if is_escape_placeholder(r) { + strings.write_byte(&b, escape_placeholder_to_byte(r)) + } else { + strings.write_rune(&b, r) + } + } + return strings.to_string(b) +} + +is_space_byte :: proc(b: byte) -> bool { + return b == ' ' || b == '\t' +} + parse_header :: proc(line: string) -> (int, string) { if len(line) == 0 { return 0, line @@ -221,6 +316,14 @@ parse_inline_style :: proc(token: ^Token, symbol: byte, toggles: []StyleToggle) i += 1 } + preceded_by_space := symbol_start > 0 && is_space_byte(str[symbol_start - 1]) + followed_by_space := i < len(str) && is_space_byte(str[i]) + if preceded_by_space && followed_by_space { + push_token(current_node, TokenType.Text, str[symbol_start:i]) + start = i + continue + } + for count > 0 { matched := false for toggle in toggles { @@ -827,40 +930,6 @@ resolve_variables :: proc(token: ^Token, vars: ^map[string]string) { } } -add_breaklines :: proc(token: ^Token) { - if token.type == .CodeBlock { - return - } - if token.children == nil { - return - } - - new_children: [dynamic]^Token - for i := 0; i < len(token.children); i += 1 { - child := token.children[i] - add_breaklines(child) - append(&new_children, child) - 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) - } - } - token.children = new_children -} - parse :: proc(lines: []string, allocator := context.allocator) -> Token { root := Token{TokenType.Root, nil, nil, nil} current_token := &root @@ -900,6 +969,8 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { continue } + line = unescape_backslashes(line) + if strings.has_prefix(line, "```") { lang := strings.trim_space(line[3:]) current_token = push_token(&root, TokenType.CodeBlock, lang) @@ -965,7 +1036,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_text(last_p, text[2:]) + push_text_line(last_p, text[2:]) continue } @@ -1025,15 +1096,15 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { if current_token.type == .Blockquote { last_p := current_token.children[len(current_token.children) - 1] - push_text(last_p, line) + push_text_line(last_p, line) } else if current_token.type == .UnorderedList || current_token.type == .OrderedList { last_li := current_token.children[len(current_token.children) - 1] - push_text(last_li, line) + push_text_line(last_li, line) } else { if current_token.type != .Paragraph { current_token = push_token(&root, TokenType.Paragraph, nil) } - push_text(current_token, line) + push_text_line(current_token, line) } } @@ -1048,12 +1119,6 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { pass_through_children(token) } - // THIRD PASS - - for token in root.children { - add_breaklines(token) - } - // VARIABLE RESOLUTION PASS vars: map[string]string