perf(core): accumulate captured logs as chunks, join on read

ober

9843bae520ec2833a46c00a370570c04aff1d807

diff --git a/src/jerboa-emacs/core.ss b/src/jerboa-emacs/core.ss
index a66b5c7..ed4aa34 100644
--- a/src/jerboa-emacs/core.ss
+++ b/src/jerboa-emacs/core.ss
@@ -2008,21 +2008,38 @@
 ;;; Captured output logs (for eval stdout/stderr in Qt)
 ;;;============================================================================
 
-(def *captured-errors* "")
-(def *captured-output* "")
+;; Chunks accumulate in reverse order (cons = O(1)); the joined string is
+;; built lazily on read and cached until the next append/clear. This avoids
+;; quadratic string-append growth on verbose eval/subprocess output.
+(def *captured-errors* '())
+(def *captured-errors-cache* #f)
+(def *captured-output* '())
+(def *captured-output-cache* #f)
 
 (def (append-error-log! text)
-  (set! *captured-errors* (string-append *captured-errors* text))
+  (set! *captured-errors* (cons text *captured-errors*))
+  (set! *captured-errors-cache* #f)
   (jemacs-log! "EVAL-STDERR: " text))
 (def (append-output-log! text)
-  (set! *captured-output* (string-append *captured-output* text)))
-(def (get-error-log) *captured-errors*)
-(def (get-output-log) *captured-output*)
-(def (clear-error-log!) (set! *captured-errors* ""))
-(def (clear-output-log!) (set! *captured-output* ""))
+  (set! *captured-output* (cons text *captured-output*))
+  (set! *captured-output-cache* #f))
+(def (get-error-log)
+  (unless *captured-errors-cache*
+    (set! *captured-errors-cache* (string-join (reverse *captured-errors*) "")))
+  *captured-errors-cache*)
+(def (get-output-log)
+  (unless *captured-output-cache*
+    (set! *captured-output-cache* (string-join (reverse *captured-output*) "")))
+  *captured-output-cache*)
+(def (clear-error-log!)
+  (set! *captured-errors* '())
+  (set! *captured-errors-cache* ""))
+(def (clear-output-log!)
+  (set! *captured-output* '())
+  (set! *captured-output-cache* ""))
 (def (has-captured-output?)
-  (or (> (string-length *captured-errors*) 0)
-      (> (string-length *captured-output*) 0)))
+  (or (pair? *captured-errors*)
+      (pair? *captured-output*)))
 
 ;;;============================================================================
 ;;; REPL shared logic