Moon Healing Reformation!!!

(I know it would be refactoration, but that sounds much worse)
This commit is contained in:
2026-07-16 09:40:32 +02:00
parent 033039858b
commit 69e5c62382
15 changed files with 1775 additions and 1661 deletions
+470
View File
@@ -0,0 +1,470 @@
package parser
import "../mime"
import "../token"
import "core:strings"
StyleToggle :: struct {
amount: int,
type: token.TokenType,
}
parse_inline_style :: proc(tok: ^token.Token, symbol: byte, toggles: []StyleToggle) {
if tok.children != nil {
for i := 0; i < len(tok.children); i += 1 {
parse_inline_style(tok.children[i], symbol, toggles)
}
}
if tok.type == .Text {
str, ok := tok.value.(string)
if !ok {
return
}
symbol_found := false
for i := 0; i < len(str); i += 1 {
if str[i] == symbol {
symbol_found = true
break
}
}
if !symbol_found {
return
}
tok.value = nil
active_styles: [256]bool
current_node := tok
i := 0
start := 0
for i < len(str) {
if str[i] == symbol {
if i > start {
push_token(current_node, token.TokenType.Text, str[start:i])
}
count := 0
symbol_start := i
for i < len(str) && str[i] == symbol {
count += 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, token.TokenType.Text, str[symbol_start:i])
start = i
continue
}
for count > 0 {
matched := false
for toggle in toggles {
if count >= toggle.amount {
active_styles[int(toggle.type)] = !active_styles[int(toggle.type)]
count -= toggle.amount
matched = true
break
}
}
if !matched {
push_token(
current_node,
token.TokenType.Text,
str[symbol_start:symbol_start + count],
)
count = 0
break
}
}
current_node = tok
for toggle in toggles {
if active_styles[int(toggle.type)] {
last :=
current_node.children[len(current_node.children) - 1] if len(current_node.children) > 0 else nil
current_node =
last if last != nil && last.type == toggle.type else push_token(current_node, toggle.type, nil)
}
}
start = i
} else {
i += 1
}
}
if i > start {
push_token(current_node, token.TokenType.Text, str[start:i])
}
}
}
parse_inline_code :: proc(tok: ^token.Token) {
if tok.type == .CodeBlock || tok.type == .InlineCode {
return
}
if tok.children != nil {
for i := 0; i < len(tok.children); i += 1 {
parse_inline_code(tok.children[i])
}
}
if tok.type == .Text {
str, ok := tok.value.(string)
if !ok {
return
}
start_idx := strings.index_byte(str, '`')
if start_idx == -1 {
return
}
end_idx := strings.index_byte(str[start_idx + 1:], '`')
if end_idx == -1 {
return
}
end_idx += start_idx + 1
code := str[start_idx + 1:end_idx]
tok.value = nil
if start_idx > 0 {
push_token(tok, .Text, str[:start_idx])
}
push_token(tok, .InlineCode, code)
if end_idx + 1 < len(str) {
rem := push_token(tok, .Text, str[end_idx + 1:])
parse_inline_code(rem)
}
}
}
parse_inline_footnotes :: proc(tok: ^token.Token) {
if tok.type == .CodeBlock || tok.type == .InlineCode {
return
}
if tok.children != nil {
for i := 0; i < len(tok.children); i += 1 {
parse_inline_footnotes(tok.children[i])
}
}
if tok.type == .Text {
str, ok := tok.value.(string)
if !ok {
return
}
start_idx := strings.index(str, "[&")
if start_idx == -1 {
return
}
end_idx := strings.index_byte(str[start_idx:], ']')
if end_idx == -1 {
return
}
end_idx += start_idx
id := str[start_idx + 2:end_idx]
tok.value = nil
if start_idx > 0 {
push_token(tok, .Text, str[:start_idx])
}
push_token(tok, .FootnoteRef, id)
if end_idx + 1 < len(str) {
rem := push_token(tok, .Text, str[end_idx + 1:])
parse_inline_footnotes(rem)
}
}
}
match_pair :: proc(s: string, open1, close1, open2, close2: byte) -> (int, string, string, bool) {
if len(s) == 0 || s[0] != open1 {
return 0, "", "", false
}
depth := 1
close1_idx := -1
for i := 1; i < len(s); i += 1 {
if s[i] == open1 {
depth += 1
} else if s[i] == close1 {
depth -= 1
if depth == 0 {
close1_idx = i
break
}
}
}
if close1_idx == -1 {
return 0, "", "", false
}
if close1_idx + 1 < len(s) && s[close1_idx + 1] == open2 {
depth = 1
close2_idx := -1
for i := close1_idx + 2; i < len(s); i += 1 {
if s[i] == open2 {
depth += 1
} else if s[i] == close2 {
depth -= 1
if depth == 0 {
close2_idx = i
break
}
}
}
if close2_idx != -1 {
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(tok: ^token.Token) {
if tok.type == .CodeBlock || tok.type == .InlineCode {
return
}
if tok.children != nil {
for i := 0; i < len(tok.children); i += 1 {
parse_links(tok.children[i])
}
}
if tok.type == .Text {
str, ok := tok.value.(string)
if !ok {
return
}
start_idx, end_idx, is_link, url, alt, is_header := find_link(str)
if !is_link {
return
}
tok.value = nil
if start_idx > 0 {
push_token(tok, .Text, str[:start_idx])
}
link_token := push_token(tok, .HeaderLink if is_header else .Link, url)
push_token(link_token, .Text, alt)
if end_idx < len(str) {
rem := push_token(tok, .Text, str[end_idx:])
parse_links(rem)
}
}
}
find_variable :: proc(
str: string,
) -> (
start_idx, end_idx: int,
is_var: bool,
name: string,
val: string,
is_def: bool,
) {
for i := 0; i < len(str); i += 1 {
if str[i] == '$' && i + 1 < len(str) && str[i + 1] == '(' {
if end, inner, ok := match_single(str[i + 1:], '(', ')'); ok {
eq := strings.index_byte(inner, '=')
if eq != -1 {
return i,
i + 1 + end,
true,
strings.trim_space(inner[:eq]),
strings.trim_space(inner[eq + 1:]),
true
} else if mime.is_url(inner) {
continue
} else {
return i, i + 1 + end, true, strings.trim_space(inner), "", false
}
}
}
}
return -1, -1, false, "", "", false
}
parse_variables :: proc(tok: ^token.Token) {
if tok.type == .CodeBlock || tok.type == .InlineCode {
return
}
if tok.children != nil {
for i := 0; i < len(tok.children); i += 1 {
parse_variables(tok.children[i])
}
}
if tok.type == .Text {
str, ok := tok.value.(string)
if !ok {
return
}
start_idx, end_idx, is_var, name, val, is_def := find_variable(str)
if !is_var {
return
}
tok.value = nil
if start_idx > 0 {
push_token(tok, .Text, str[:start_idx])
}
if is_def {
def_token := push_token(tok, .VariableDef, name)
push_token(def_token, .Text, val)
} else {
push_token(tok, .Variable, name)
}
if end_idx < len(str) {
rem := push_token(tok, .Text, str[end_idx:])
parse_variables(rem)
}
}
}
resolve_variables :: proc(tok: ^token.Token, vars: ^map[string]string) {
if tok.type == .VariableDef {
if name, ok := tok.value.(string); ok {
if len(tok.children) > 0 {
if val, vok := tok.children[0].value.(string); vok {
vars[name] = val
}
}
}
return
}
if tok.type == .Variable {
if name, ok := tok.value.(string); ok {
if val, found := vars[name]; found {
if tok.children == nil {
push_token(tok, .Text, val)
}
}
}
return
}
if tok.type == .Link || tok.type == .HeaderLink {
if url, ok := tok.value.(string); ok {
for i := 0; i < len(url); i += 1 {
if url[i] == '$' && i + 1 < len(url) && url[i + 1] == '(' {
if end, inner, ok := match_single(url[i + 1:], '(', ')'); ok {
var_name := strings.trim_space(inner)
if val, found := vars[var_name]; found {
b := strings.builder_make(context.temp_allocator)
strings.write_string(&b, url[:i])
strings.write_string(&b, val)
strings.write_string(&b, url[i + 1 + end:])
tok.value = strings.to_string(b)
break
}
}
}
}
}
}
if tok.children != nil {
for child in tok.children {
resolve_variables(child, vars)
}
}
}
pass_through_children :: proc(tok: ^token.Token) {
if tok.type == .CodeBlock || tok.type == .InlineCode {
return
}
parse_inline_style(tok, '*', []StyleToggle{{2, .Bold}, {1, .Italics}})
parse_inline_style(tok, '~', []StyleToggle{{2, .Strikethrough}})
parse_inline_style(tok, '-', []StyleToggle{{2, .Strikethrough}})
parse_inline_style(tok, '^', []StyleToggle{{1, .Superscript}})
parse_inline_style(tok, '_', []StyleToggle{{2, .Underline}, {1, .Subscript}})
}