This commit is contained in:
2026-07-15 13:09:56 +02:00
parent 5cc4e43431
commit b244a89eae
3 changed files with 116 additions and 46 deletions
+4 -1
View File
@@ -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) ^^ #[Link to nekoweb.org in the middle](https://n0va.nekoweb.org) ^^
#[https://krzak.org] #[https://krzak.org]
#(https://krzak.org) #(https://krzak.org)
^ Links with no alt text \^ Links with no alt text
$[https://krzak.org/krzak-icon.png|img] $[https://krzak.org/krzak-icon.png|img]
$[/staircase.mp3] $[/staircase.mp3]
@@ -56,6 +56,8 @@ Here is some __underlined__ text
[Go to Top](#Heading) [Go to Top](#Heading)
! Escaped star: \*
[&&1] Ordered list item 2 not included [&&1] Ordered list item 2 not included
[&&named] This is the definition for a named footnote [&&named] This is the definition for a named footnote
@@ -65,6 +67,7 @@ $(counter=0)
$(link=https://git.krzak.org/N0VA/TypoML) $(link=https://git.krzak.org/N0VA/TypoML)
`tlm.counter` = $(counter) `tlm.counter` = $(counter)
\^ This is JS!!!
#($(link))[Repo link] #($(link))[Repo link]
+3 -1
View File
@@ -5,8 +5,10 @@ import "core:strings"
import "core:unicode" import "core:unicode"
escape_html :: proc(s: string, allocator := context.temp_allocator) -> string { escape_html :: proc(s: string, allocator := context.temp_allocator) -> string {
escaped := decode_escapes(s, allocator)
b := strings.builder_make(allocator) b := strings.builder_make(allocator)
for ch in s { for ch in escaped {
switch ch { switch ch {
case '&': case '&':
strings.write_string(&b, "&") strings.write_string(&b, "&")
+109 -44
View File
@@ -121,6 +121,15 @@ push_text :: proc(parent: ^Token, text: string) {
push_token(target, TokenType.Text, cleaned) 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 { strip_alignment_markers :: proc(text: string) -> string {
trimmed := strings.trim_space(text) trimmed := strings.trim_space(text)
if strings.has_suffix(trimmed, ">>") { if strings.has_suffix(trimmed, ">>") {
@@ -144,6 +153,92 @@ strip_alignment_markers :: proc(text: string) -> string {
return trimmed 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) { parse_header :: proc(line: string) -> (int, string) {
if len(line) == 0 { if len(line) == 0 {
return 0, line return 0, line
@@ -221,6 +316,14 @@ parse_inline_style :: proc(token: ^Token, symbol: byte, toggles: []StyleToggle)
i += 1 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 { for count > 0 {
matched := false matched := false
for toggle in toggles { 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 { parse :: proc(lines: []string, allocator := context.allocator) -> Token {
root := Token{TokenType.Root, nil, nil, nil} root := Token{TokenType.Root, nil, nil, nil}
current_token := &root current_token := &root
@@ -900,6 +969,8 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token {
continue continue
} }
line = unescape_backslashes(line)
if strings.has_prefix(line, "```") { if strings.has_prefix(line, "```") {
lang := strings.trim_space(line[3:]) lang := strings.trim_space(line[3:])
current_token = push_token(&root, TokenType.CodeBlock, lang) 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) push_token(current_token, TokenType.Paragraph, nil)
} }
last_p := current_token.children[len(current_token.children) - 1] last_p := current_token.children[len(current_token.children) - 1]
push_text(last_p, text[2:]) push_text_line(last_p, text[2:])
continue continue
} }
@@ -1025,15 +1096,15 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token {
if current_token.type == .Blockquote { if current_token.type == .Blockquote {
last_p := current_token.children[len(current_token.children) - 1] 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 { } else if current_token.type == .UnorderedList || current_token.type == .OrderedList {
last_li := current_token.children[len(current_token.children) - 1] last_li := current_token.children[len(current_token.children) - 1]
push_text(last_li, line) push_text_line(last_li, line)
} else { } else {
if current_token.type != .Paragraph { if current_token.type != .Paragraph {
current_token = push_token(&root, TokenType.Paragraph, nil) 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) pass_through_children(token)
} }
// THIRD PASS
for token in root.children {
add_breaklines(token)
}
// VARIABLE RESOLUTION PASS // VARIABLE RESOLUTION PASS
vars: map[string]string vars: map[string]string