perf(xml): linear-time duplicate-child accumulation and tail-recursive filter

ober

88d3d3d13016324a6afd86f346626669bf366539

diff --git a/lib/jerboa-aws/xml.sls b/lib/jerboa-aws/xml.sls
index 1a2b65c..5c7c17a 100644
--- a/lib/jerboa-aws/xml.sls
+++ b/lib/jerboa-aws/xml.sls
@@ -240,25 +240,31 @@
            ;; Set element → list of child hashes
            [(is-set-element? tag)
             (map sxml->hash elem-children)]
-           ;; Container element → hash table
+           ;; Container element → hash table. Repeated child tags accumulate
+           ;; onto a reversed per-tag list (O(1) each) and are reversed once,
+           ;; avoiding the O(n^2) of appending per repeat for elements with
+           ;; many repeated children (S3 Contents, EC2 reservations).
            [else
-            (let ([ht (make-hashtable symbol-hash eq?)])
+            (let ([acc (make-hashtable symbol-hash eq?)])
               (for-each
                 (lambda (child)
                   (when (pair? child)
                     (let* ([child-tag (strip-ns (car child))]
                            [child-val (sxml->hash child)]
-                           [existing (hashtable-ref ht child-tag #f)])
-                      (cond
-                        ;; Duplicate → make/extend list
-                        [existing
-                         (if (and (list? existing) (not (hashtable? (car existing))))
-                           (hashtable-set! ht child-tag (append existing (list child-val)))
-                           (hashtable-set! ht child-tag (list existing child-val)))]
-                        [else
-                         (hashtable-set! ht child-tag child-val)]))))
+                           [existing (hashtable-ref acc child-tag #f)])
+                      (hashtable-set! acc child-tag
+                        (if existing (cons child-val existing) (list child-val))))))
                 elem-children)
-              ht)]))]))
+              (let ([ht (make-hashtable symbol-hash eq?)]
+                    [keys (hashtable-keys acc)])
+                (let loop ([i 0])
+                  (if (>= i (vector-length keys))
+                    ht
+                    (let* ([k (vector-ref keys i)]
+                           [vals (reverse (hashtable-ref acc k '()))])
+                      (hashtable-set! ht k
+                        (if (null? (cdr vals)) (car vals) vals))
+                      (loop (+ i 1)))))))]))]))
 
   ;; Get text content of an SXML element
   (define (sxml-text elem)
@@ -311,17 +317,10 @@
           [else (loop (+ i 1))]))))
 
   (define (filter pred lst)
-    (cond
-      [(null? lst) '()]
-      [(pred (car lst)) (cons (car lst) (filter pred (cdr lst)))]
-      [else (filter pred (cdr lst))]))
-
-  (define (hashtable? x)
-    (or (eq-hashtable? x)
-        (symbol-hashtable? x)
-        (and (record? x) ;; catch generic hashtables
-             (guard (e [#t #f])
-               (hashtable-size x)
-               #t))))
+    (let loop ([lst lst] [acc '()])
+      (cond
+        [(null? lst) (reverse acc)]
+        [(pred (car lst)) (loop (cdr lst) (cons (car lst) acc))]
+        [else (loop (cdr lst) acc)])))
 
   ) ;; end library