Skip to content

Commit 9b2af16

Browse files
author
carpentry-heartbeat[bot]
committed
perf: replace O(n²) String.append loops with StringBuf
join-children, join-segments, and escape-string built strings byte-by-byte using String.append, causing quadratic allocation for large ASTs and strings. Switch to StringBuf for amortised O(n) appends. Adds strbuf@0.1.0 dependency.
1 parent f40620c commit 9b2af16

2 files changed

Lines changed: 22 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
44
the project follows [Semantic Versioning](https://semver.org/).
55

6+
## Unreleased
7+
8+
- `Form.str` uses `StringBuf` for `join-children`, `join-segments`,
9+
and `escape-string`, replacing O(n²) `String.append` loops with
10+
amortised O(n) appends. Adds a dependency on `strbuf@0.1.0`.
11+
612
## [0.3.0]
713

814
- `Located` gains an `end Info` field tracking the position just past

carp-reader.carp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
(load "git@github.com:carpentry-org/parsec@0.2.0")
2+
(load "git@github.com:carpentry-org/strbuf@0.1.0")
23

34
(doc Info "is the parser-captured source position of a form: byte
45
offset `pos`, `line`, and `col`.")
@@ -40,26 +41,25 @@ nested. Synthesized nodes (reader-macro expansions) carry
4041
(private join-children)
4142
(hidden join-children)
4243
(defn join-children [items]
43-
(let-do [out @""]
44+
(let-do [sb (StringBuf.create)]
4445
(for [i 0 (Array.length items)]
4546
(do
46-
(when (Int.> i 0) (set! out (String.append &out " ")))
47-
(set! out
48-
(String.append &out
49-
&(Form.str
50-
(Located.form (Box.peek (Array.unsafe-nth items
51-
i))))))))
52-
out))
47+
(when (Int.> i 0) (StringBuf.append-str &sb " "))
48+
(StringBuf.append-str &sb
49+
&(Form.str
50+
(Located.form (Box.peek (Array.unsafe-nth items
51+
i)))))))
52+
(StringBuf.to-string &sb)))
5353

5454
(private join-segments)
5555
(hidden join-segments)
5656
(defn join-segments [segs]
57-
(let-do [out @""]
57+
(let-do [sb (StringBuf.create)]
5858
(for [i 0 (Array.length segs)]
5959
(do
60-
(when (Int.> i 0) (set! out (String.append &out ".")))
61-
(set! out (String.append &out (Array.unsafe-nth segs i)))))
62-
out))
60+
(when (Int.> i 0) (StringBuf.append-str &sb "."))
61+
(StringBuf.append-str &sb (Array.unsafe-nth segs i))))
62+
(StringBuf.to-string &sb)))
6363

6464
(private escape-string-byte)
6565
(hidden escape-string-byte)
@@ -85,7 +85,7 @@ protocol-style strings (HTTP, Redis) keep their line endings explicit
8585
on the source side. A standalone `\\n` is kept raw — multi-line doc
8686
strings round-trip readably.")
8787
(defn escape-string [s]
88-
(let-do [out @""
88+
(let-do [sb (StringBuf.create)
8989
n (String.length s)
9090
i 0]
9191
(while-do (Int.< i n)
@@ -94,11 +94,11 @@ strings round-trip readably.")
9494
(and (Int.< (Int.inc i) n)
9595
(Char.= (String.char-at s (Int.inc i)) \newline)))]
9696
(if crlf?
97-
(do (set! out (String.append &out "\\r\\n")) (set! i (Int.+ i 2)))
97+
(do (StringBuf.append-str &sb "\\r\\n") (set! i (Int.+ i 2)))
9898
(do
99-
(set! out (String.append &out &(escape-string-byte b)))
99+
(StringBuf.append-str &sb &(escape-string-byte b))
100100
(set! i (Int.inc i))))))
101-
out))
101+
(StringBuf.to-string &sb)))
102102

103103
(private show-char)
104104
(hidden show-char)

0 commit comments

Comments
 (0)