Fix grep builtin: use Jerboa hash tables instead of Chez native hashtables

ober

58c18dc0d9ccb9bbcd6fad520848d02ccd0e5ad5

diff --git a/lib/jerboa-coreutils/grep.sls b/lib/jerboa-coreutils/grep.sls
index 8e6ba8f..870c084 100644
--- a/lib/jerboa-coreutils/grep.sls
+++ b/lib/jerboa-coreutils/grep.sls
@@ -131,13 +131,16 @@
                                    (not (string=? e ".."))))
                   entries)))))
 
-  ;; Collect files to grep, handling recursion
+  ;; Collect files to grep, handling recursion.
+  ;; todo items are (path . cmdline?) pairs — command-line symlinks are always
+  ;; followed; discovered symlinks are skipped with -r (only -R follows them).
   (def (collect-files paths recursive? deref? includes excludes exclude-dirs
                       suppress-errors?)
-    (let loop ([todo paths] [acc '()])
+    (let loop ([todo (map (lambda (p) (cons p #t)) paths)] [acc '()])
       (if (null? todo)
         (reverse acc)
-        (let ([path (car todo)])
+        (let ([path (caar todo)]
+              [cmdline? (cdar todo)])
           (if (string=? path "-")
             (loop (cdr todo) (cons "-" acc))
             (with-catch
@@ -148,6 +151,10 @@
                 (loop (cdr todo) acc))
               (lambda ()
                 (cond
+                  ;; With -r (not -R), skip symlinks found during traversal
+                  ;; Command-line symlinks are always followed (matches GNU grep)
+                  [(and (not cmdline?) (not deref?) (file-symbolic-link? path))
+                   (loop (cdr todo) acc)]
                   [(file-directory? path)
                    (if recursive?
                      (let* ([base (path-basename path)]
@@ -156,12 +163,14 @@
                          (loop (cdr todo) acc)
                          (let* ([entries (list-directory path)]
                                 [children (map (lambda (e)
-                                                 (if (and (> (string-length path) 0)
-                                                          (char=? (string-ref path
-                                                                    (- (string-length path) 1))
-                                                                  #\/))
-                                                   (string-append path e)
-                                                   (string-append path "/" e)))
+                                                 (cons
+                                                   (if (and (> (string-length path) 0)
+                                                            (char=? (string-ref path
+                                                                      (- (string-length path) 1))
+                                                                    #\/))
+                                                     (string-append path e)
+                                                     (string-append path "/" e))
+                                                   #f))
                                                entries)])
                            (loop (append children (cdr todo)) acc))))
                      (begin
@@ -181,10 +190,8 @@
                        [(matches-any-glob? name excludes)
                         (loop (cdr todo) acc)]
                        [else (loop (cdr todo) (cons path acc))]))]
-                  ;; Symlinks in non-deref mode
-                  [(and (file-exists? path) (not (file-regular? path))
-                        (not (file-directory? path)))
-                   ;; Skip special files silently
+                  ;; Skip special files (pipes, devices, etc.) silently
+                  [(file-exists? path)
                    (loop (cdr todo) acc)]
                   [else
                    (unless suppress-errors?
@@ -769,57 +776,50 @@
                                                       word-match? line-match?)))])
 
                         ;; Build options hash for grep-file
-                        (let ([file-opts (make-hashtable string-hash string=?)])
-                          (hashtable-set! file-opts "show-filename" show-filename?)
-                          (hashtable-set! file-opts "line-number" show-lineno?)
-                          (hashtable-set! file-opts "byte-offset" show-byte-offset?)
-                          (hashtable-set! file-opts "invert-match" invert?)
-                          (hashtable-set! file-opts "count" count-only?)
-                          (hashtable-set! file-opts "quiet" quiet?)
-                          (hashtable-set! file-opts "only-matching"
+                        ;; Use Jerboa make-hash-table so grep-file can use
+                        ;; hash-ref / hash-get to read values.
+                        (let ([opts-ht (make-hash-table)])
+                          (hash-put! opts-ht 'show-filename show-filename?)
+                          (hash-put! opts-ht 'line-number show-lineno?)
+                          (hash-put! opts-ht 'byte-offset show-byte-offset?)
+                          (hash-put! opts-ht 'invert-match invert?)
+                          (hash-put! opts-ht 'count count-only?)
+                          (hash-put! opts-ht 'quiet quiet?)
+                          (hash-put! opts-ht 'only-matching
                             (and only-matching? (not invert?)))
-                          (hashtable-set! file-opts "files-with-matches"
+                          (hash-put! opts-ht 'files-with-matches
                             files-with-match?)
-                          (hashtable-set! file-opts "files-without-match"
+                          (hash-put! opts-ht 'files-without-match
                             files-without-match?)
-                          (hashtable-set! file-opts "max-count"
+                          (hash-put! opts-ht 'max-count
                             (if (< max-count 0) 999999999 max-count))
-                          (hashtable-set! file-opts "colorize" colorize?)
-                          (hashtable-set! file-opts "null" null-sep?)
-                          (hashtable-set! file-opts "null-data" zero-data?)
-                          (hashtable-set! file-opts "before-context" before-ctx)
-                          (hashtable-set! file-opts "after-context" after-ctx)
-                          (hashtable-set! file-opts "label" label)
-                          (hashtable-set! file-opts "initial-tab" initial-tab?)
-                          (hashtable-set! file-opts "line-buffered" line-buffered?)
-                          (hashtable-set! file-opts "no-messages" suppress-errors?)
-
-                          ;; Wrap hash in getopt-compatible accessor
-                          (let ([opts-ht (make-eq-hashtable)])
-                            (for-each
-                              (lambda (key)
-                                (hashtable-set! opts-ht
-                                  (string->symbol key)
-                                  (hashtable-ref file-opts key #f)))
-                              (vector->list (hashtable-keys file-opts)))
-
-                            ;; Process all files
-                            (let ([total-matches
-                                   (let loop ([flist file-list] [total 0])
-                                     (if (null? flist)
-                                       total
-                                       (let ([count (grep-file (car flist) rx opts-ht)])
-                                         ;; Quiet mode: exit immediately on first match
-                                         (if (and quiet? (> count 0))
-                                           (exit 0)
-                                           (loop (cdr flist) (+ total count))))))])
-
-                              ;; Clean up
-                              (pcre2-free! rx)
-                              (flush-output-port)
-
-                              ;; Exit code: 0=match, 1=no match, 2=error
-                              (exit (if (> total-matches 0) 0 1))))))))
+                          (hash-put! opts-ht 'colorize colorize?)
+                          (hash-put! opts-ht 'null null-sep?)
+                          (hash-put! opts-ht 'null-data zero-data?)
+                          (hash-put! opts-ht 'before-context before-ctx)
+                          (hash-put! opts-ht 'after-context after-ctx)
+                          (hash-put! opts-ht 'label label)
+                          (hash-put! opts-ht 'initial-tab initial-tab?)
+                          (hash-put! opts-ht 'line-buffered line-buffered?)
+                          (hash-put! opts-ht 'no-messages suppress-errors?)
+
+                          ;; Process all files
+                          (let ([total-matches
+                                 (let loop ([flist file-list] [total 0])
+                                   (if (null? flist)
+                                     total
+                                     (let ([count (grep-file (car flist) rx opts-ht)])
+                                       ;; Quiet mode: exit immediately on first match
+                                       (if (and quiet? (> count 0))
+                                         (exit 0)
+                                         (loop (cdr flist) (+ total count))))))])
+
+                            ;; Clean up
+                            (pcre2-free! rx)
+                            (flush-output-port)
+
+                            ;; Exit code: 0=match, 1=no match, 2=error
+                            (exit (if (> total-matches 0) 0 1)))))))
                   args
                   'program: "grep"
                   'help: "Search for PATTERNS in each FILE."