From 6694c0cf673ef1bf55aab45d582c5ba5508e3965 Mon Sep 17 00:00:00 2001 From: Spencer Brower <6729162+sbrow@users.noreply.github.com> Date: Thu, 16 Jul 2026 18:11:55 -0400 Subject: [PATCH] perf: Replaced a bunch of string array concats with `strings.Builder`s. --- alerts.odin | 17 ++++++-------- emoji.odin | 22 ++++++++---------- footnotes.odin | 33 ++++++++++++++------------ highlight.odin | 61 ++++++++++++++++++++++++++----------------------- render.odin | 17 +++++++------- sectionate.odin | 24 +++++++++++-------- 6 files changed, 90 insertions(+), 84 deletions(-) diff --git a/alerts.odin b/alerts.odin index 2be38ec..852a7f4 100644 --- a/alerts.odin +++ b/alerts.odin @@ -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, "
") if bq_start < 0 { - append(&parts, remaining) + strings.write_string(&sb, remaining) break } bq_close := strings.index(remaining, "
") if bq_close < 0 { - append(&parts, remaining) + strings.write_string(&sb, remaining) break } bq_end := bq_close + len("") 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 { diff --git a/emoji.odin b/emoji.odin index 96a4539..eb8fab1 100644 --- a/emoji.odin +++ b/emoji.odin @@ -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) } diff --git a/footnotes.odin b/footnotes.odin index 4d7c5cd..f15f9a0 100644 --- a/footnotes.odin +++ b/footnotes.odin @@ -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 } diff --git a/highlight.odin b/highlight.odin index 4bc8591..d60c513 100644 --- a/highlight.odin +++ b/highlight.odin @@ -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.. start do append(&parts, s[start:i]) - append(&parts, "&") + if i > start do strings.write_string(&sb, s[start:i]) + strings.write_string(&sb, "&") start = i + 1 case '<': - if i > start do append(&parts, s[start:i]) - append(&parts, "<") + if i > start do strings.write_string(&sb, s[start:i]) + strings.write_string(&sb, "<") start = i + 1 case '>': - if i > start do append(&parts, s[start:i]) - append(&parts, ">") + if i > start do strings.write_string(&sb, s[start:i]) + strings.write_string(&sb, ">") start = i + 1 case '"': - if i > start do append(&parts, s[start:i]) - append(&parts, """) + if i > start do strings.write_string(&sb, s[start:i]) + strings.write_string(&sb, """) 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.. string { case "'", "'": 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 :: `
%s
`, lang, highlighted)) + strings.write_string(&sb, fmt.tprintf(`
%s
`, 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) } diff --git a/render.odin b/render.odin index 50cad52..b1eaef6 100644 --- a/render.odin +++ b/render.odin @@ -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 { diff --git a/sectionate.odin b/sectionate.odin index 6bd347c..5a44441 100644 --- a/sectionate.odin +++ b/sectionate.odin @@ -5,10 +5,12 @@ import "core:strings" wrap_sections :: proc(html: string) -> string { H2 :: " string { break } idx := search_pos + rel + found = true if idx > pos { - append(&parts, "
") - append(&parts, html[pos:idx]) - append(&parts, "
") + strings.write_string(&sb, "
") + strings.write_string(&sb, html[pos:idx]) + strings.write_string(&sb, "
") } pos = idx @@ -28,13 +31,14 @@ wrap_sections :: proc(html: string) -> string { } if pos < len(html) { - append(&parts, "
") - append(&parts, html[pos:]) - append(&parts, "
") + strings.write_string(&sb, "
") + strings.write_string(&sb, html[pos:]) + strings.write_string(&sb, "
") + found = true } - if len(parts) == 0 { + if !found { return html } - return strings.join(parts[:], "") + return strings.to_string(sb) }