perf: Replaced a bunch of string array concats with strings.Builders.

This commit is contained in:
Spencer Brower
2026-07-16 18:11:55 -04:00
parent c7b42ce7aa
commit 6694c0cf67
6 changed files with 90 additions and 84 deletions
+7 -10
View File
@@ -21,37 +21,34 @@ ALERT_EMOJIS: map[string]string = {
}
inject_alerts :: proc(html: string) -> string {
parts: [dynamic]string
defer delete(parts)
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
remaining := html
for {
bq_start := strings.index(remaining, "<blockquote>")
if bq_start < 0 {
append(&parts, remaining)
strings.write_string(&sb, remaining)
break
}
bq_close := strings.index(remaining, "</blockquote>")
if bq_close < 0 {
append(&parts, remaining)
strings.write_string(&sb, remaining)
break
}
bq_end := bq_close + len("</blockquote>")
bq := remaining[bq_start:bq_end]
// Append text before this blockquote
append(&parts, remaining[:bq_start])
// Transform if it's an alert, otherwise keep as-is
append(&parts, transform_alert(bq))
strings.write_string(&sb, remaining[:bq_start])
strings.write_string(&sb, transform_alert(bq))
remaining = remaining[bq_end:]
}
return strings.join(parts[:], "")
return strings.to_string(sb)
}
transform_alert :: proc(bq: string) -> string {
+9 -13
View File
@@ -384,30 +384,27 @@ expand_emoji :: proc(text: string) -> string {
return text
}
parts: [dynamic]string
defer delete(parts)
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
remaining := text
for {
colon := strings.index(remaining, ":")
if colon < 0 {
append(&parts, remaining)
strings.write_string(&sb, remaining)
break
}
// Find the closing colon
after := remaining[colon + 1:]
end := strings.index(after, ":")
if end < 0 {
append(&parts, remaining)
strings.write_string(&sb, remaining)
break
}
shortcode := remaining[colon + 1 : colon + 1 + end]
// Validate: shortcode must be all lowercase letters, digits, or underscores
// and must not contain whitespace
valid := true
for c in shortcode {
if !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') && c != '_' && c != '+' && c != '-' {
@@ -416,23 +413,22 @@ expand_emoji :: proc(text: string) -> string {
}
}
if !valid || len(shortcode) == 0 {
append(&parts, remaining[:colon + 1])
strings.write_string(&sb, remaining[:colon + 1])
remaining = remaining[colon + 1:]
continue
}
emoji, found := EMOJIS[shortcode]
if !found {
append(&parts, remaining[:colon + 1])
strings.write_string(&sb, remaining[:colon + 1])
remaining = remaining[colon + 1:]
continue
}
// Replace :shortcode: with emoji
append(&parts, remaining[:colon])
append(&parts, emoji)
strings.write_string(&sb, remaining[:colon])
strings.write_string(&sb, emoji)
remaining = remaining[colon + 1 + end + 1:]
}
return strings.join(parts[:], "")
return strings.to_string(sb)
}
+18 -15
View File
@@ -20,11 +20,11 @@ strip_definitions :: proc(
clean_body: string,
sn_defs, mn_defs: map[string]string,
) {
// TODO: Should this be temp allocated?
lines := strings.split(body, "\n")
defer delete(lines)
output_lines: [dynamic]string
defer delete(output_lines)
out_sb := strings.builder_make()
defer strings.builder_destroy(&out_sb)
i := 0
for i < len(lines) {
@@ -32,48 +32,51 @@ strip_definitions :: proc(
id, def_text, kind, is_def := parse_def_line(line)
if !is_def {
append(&output_lines, line)
if strings.builder_len(out_sb) > 0 {
strings.write_string(&out_sb, "\n")
}
strings.write_string(&out_sb, line)
i += 1
continue
}
// Collect definition text (initial line + multi-line continuations)
def_parts: [dynamic]string
def_sb := strings.builder_make()
if def_text != "" {
append(&def_parts, def_text)
strings.write_string(&def_sb, def_text)
}
i += 1
for i < len(lines) {
next := lines[i]
// Stop at blank lines
if len(next) == 0 {
break
}
// Stop at new note definitions
_, _, _, is_new_def := parse_def_line(next)
if is_new_def {
break
}
// Include as continuation (trim indented lines)
if strings.builder_len(def_sb) > 0 {
strings.write_string(&def_sb, "\n")
}
if is_indented(next) {
append(&def_parts, strings.trim_left(next, " \t"))
strings.write_string(&def_sb, strings.trim_left(next, " \t"))
} else {
append(&def_parts, next)
strings.write_string(&def_sb, next)
}
i += 1
}
joined := strings.join(def_parts[:], "\n")
joined := strings.clone(strings.to_string(def_sb))
strings.builder_destroy(&def_sb)
if kind == .Marginnote {
mn_defs[id] = joined
} else {
sn_defs[id] = joined
}
delete(def_parts)
}
clean_body = strings.join(output_lines[:], "\n")
clean_body = strings.clone(strings.to_string(out_sb))
return
}
+33 -28
View File
@@ -244,37 +244,39 @@ capture_name_to_css :: proc(name: string) -> string {
}
escape_html :: proc(s: string) -> string {
parts: [dynamic]string
defer delete(parts)
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
start := 0
for i in 0..<len(s) {
switch s[i] {
case '&':
if i > start do append(&parts, s[start:i])
append(&parts, "&amp;")
if i > start do strings.write_string(&sb, s[start:i])
strings.write_string(&sb, "&amp;")
start = i + 1
case '<':
if i > start do append(&parts, s[start:i])
append(&parts, "&lt;")
if i > start do strings.write_string(&sb, s[start:i])
strings.write_string(&sb, "&lt;")
start = i + 1
case '>':
if i > start do append(&parts, s[start:i])
append(&parts, "&gt;")
if i > start do strings.write_string(&sb, s[start:i])
strings.write_string(&sb, "&gt;")
start = i + 1
case '"':
if i > start do append(&parts, s[start:i])
append(&parts, "&quot;")
if i > start do strings.write_string(&sb, s[start:i])
strings.write_string(&sb, "&quot;")
start = i + 1
}
}
if start < len(s) do append(&parts, s[start:])
if len(parts) == 0 do return s
return strings.join(parts[:], "")
if start == 0 do return s
if start < len(s) do strings.write_string(&sb, s[start:])
return strings.to_string(sb)
}
unescape_html :: proc(s: string) -> string {
parts: [dynamic]string
defer delete(parts)
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
start := 0
for i in 0..<len(s) {
if s[i] != '&' do continue
@@ -290,13 +292,13 @@ unescape_html :: proc(s: string) -> string {
case "&#39;", "&apos;": replacement = "'"
case: continue
}
if i > start do append(&parts, s[start:i])
append(&parts, replacement)
if i > start do strings.write_string(&sb, s[start:i])
strings.write_string(&sb, replacement)
start = i + semi + 1
}
if start < len(s) do append(&parts, s[start:])
if len(parts) == 0 do return s
return strings.join(parts[:], "")
if start == 0 do return s
if start < len(s) do strings.write_string(&sb, s[start:])
return strings.to_string(sb)
}
highlight_block :: proc(code: string, lang: string, file_path: string) -> string {
@@ -416,19 +418,22 @@ highlight_code :: proc(html: string, file_path: string) -> string {
PREFIX :: `<pre><code class="language-`
CODE_END :: `</code></pre>`
parts: [dynamic]string
defer delete(parts)
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
pos := 0
found := false
for {
rel := strings.index(html[pos:], PREFIX)
if rel < 0 {
break
}
found = true
idx := pos + rel
if idx > pos {
append(&parts, html[pos:idx])
strings.write_string(&sb, html[pos:idx])
}
lang_start := idx + len(PREFIX)
@@ -455,17 +460,17 @@ highlight_code :: proc(html: string, file_path: string) -> string {
code := html[code_start:end_idx]
highlighted := highlight_block(code, lang, file_path)
append(&parts, fmt.tprintf(`<pre><code class="language-%s">%s</code></pre>`, lang, highlighted))
strings.write_string(&sb, fmt.tprintf(`<pre><code class="language-%s">%s</code></pre>`, lang, highlighted))
pos = end_idx + len(CODE_END)
}
if pos < len(html) {
append(&parts, html[pos:])
if pos < len(html) && found {
strings.write_string(&sb, html[pos:])
}
if len(parts) == 0 {
if !found {
return html
}
return strings.join(parts[:], "")
return strings.to_string(sb)
}
+9 -8
View File
@@ -70,14 +70,15 @@ Posts_Data :: struct {
}
strip_html_tags :: proc(s: string) -> string {
parts: [dynamic]string
defer delete(parts)
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
in_tag := false
start := 0
for i in 0 ..< len(s) {
if s[i] == '<' && !in_tag {
if i > start {
append(&parts, s[start:i])
strings.write_string(&sb, s[start:i])
}
in_tag = true
} else if s[i] == '>' && in_tag {
@@ -85,13 +86,13 @@ strip_html_tags :: proc(s: string) -> string {
start = i + 1
}
}
if !in_tag && start < len(s) {
append(&parts, s[start:])
}
if len(parts) == 0 {
if start == 0 {
return s
}
return strings.join(parts[:], "")
if !in_tag && start < len(s) {
strings.write_string(&sb, s[start:])
}
return strings.to_string(sb)
}
og_type :: proc(is_article: bool) -> string {
+14 -10
View File
@@ -5,10 +5,12 @@ import "core:strings"
wrap_sections :: proc(html: string) -> string {
H2 :: "<h2"
parts: [dynamic]string
defer delete(parts)
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
pos := 0
search_pos := 0
found := false
for {
rel := strings.index(html[search_pos:], H2)
@@ -16,11 +18,12 @@ wrap_sections :: proc(html: string) -> string {
break
}
idx := search_pos + rel
found = true
if idx > pos {
append(&parts, "<section>")
append(&parts, html[pos:idx])
append(&parts, "</section>")
strings.write_string(&sb, "<section>")
strings.write_string(&sb, html[pos:idx])
strings.write_string(&sb, "</section>")
}
pos = idx
@@ -28,13 +31,14 @@ wrap_sections :: proc(html: string) -> string {
}
if pos < len(html) {
append(&parts, "<section>")
append(&parts, html[pos:])
append(&parts, "</section>")
strings.write_string(&sb, "<section>")
strings.write_string(&sb, html[pos:])
strings.write_string(&sb, "</section>")
found = true
}
if len(parts) == 0 {
if !found {
return html
}
return strings.join(parts[:], "")
return strings.to_string(sb)
}