lang/private/list.rkt
#lang s-exp "../kernel.rkt"

;; dyoo: This is taken from collects/racket/private/list.rkt.  The hope is that, eventually,
;; once I can support #%kernel, I won't need to do this fork to get at these...
;;
;; The major changes I made were: comment out sort and the compose/compose1 functions.
;; We don't have have support for keywords, and I will need to look at the implementation of
;; raw-sort in a moment to see if it's fine.


(provide foldl
         foldr

         remv
         remq
         remove
         remv*
         remq*
         remove*

         memf
         assf
         findf

         assq
         assv
         assoc

         filter

;;         sort

         build-vector
         build-string
         build-list

         compose
         compose1
         )

(require (only-in "../unsafe/ops.rkt" unsafe-car unsafe-cdr))
         
;;(#%require (rename "sort.rkt" raw-sort sort)
;;           (for-syntax "stxcase-scheme.rkt")
;;           (only '#%unsafe unsafe-car unsafe-cdr))

;; (provide sort)
;; (define (sort lst less? #:key [getkey #f] #:cache-keys? [cache-keys? #f])
;;   (unless (list? lst) (raise-type-error 'sort "proper list" lst))
;;   (unless (and (procedure? less?) (procedure-arity-includes? less? 2))
;;     (raise-type-error 'sort "procedure of arity 2" less?))
;;   (when (and getkey (not (and (procedure? getkey)
;;                               (procedure-arity-includes? getkey 1))))
;;     (raise-type-error 'sort "procedure of arity 1" getkey))
;;   ;; don't provide the extra args if not needed, it's a bit faster
;;   (if getkey (raw-sort lst less? getkey cache-keys?) (raw-sort lst less?)))

(define (do-remove who item list equal?)
  (unless (list? list)
    (raise-type-error who "list" list))
  (let loop ([list list])
    (cond [(null? list) null]
          [(equal? item (car list)) (cdr list)]
          [else (cons (car list) (loop (cdr list)))])))

(define remove
  (case-lambda
    [(item list) (do-remove 'remove item list equal?)]
    [(item list equal?)
     (unless (and (procedure? equal?)
                  (procedure-arity-includes? equal? 2))
       (raise-type-error 'remove "procedure (arity 2)" equal?))
     (do-remove 'remove item list equal?)]))

(define (remq item list)
  (do-remove 'remq item list eq?))

(define (remv item list)
  (do-remove 'remv item list eqv?))

(define (do-remove* who l r equal?)
  (unless (list? l)
    (raise-type-error who "list" l))
  (unless (list? r)
    (raise-type-error who "list" r))
  (let rloop ([r r])
    (cond
     [(null? r) null]
     [else (let ([first-r (car r)])
             (let loop ([l-rest l])
               (cond
                [(null? l-rest) (cons first-r (rloop (cdr r)))]
                [(equal? (car l-rest) first-r) (rloop (cdr r))]
                [else (loop (cdr l-rest))])))])))

(define remove*
  (case-lambda
    [(l r) (do-remove* 'remove* l r equal?)]
    [(l r equal?)
     (unless (and (procedure? equal?)
                  (procedure-arity-includes? equal? 2))
       (raise-type-error 'remove* "procedure (arity 2)" equal?))
     (do-remove* 'remove* l r equal?)]))

(define (remq* l r)
  (do-remove* 'remq* l r eq?))

(define (remv* l r)
  (do-remove* 'remv* l r eqv?))

(define (memf f list)
  (unless (and (procedure? f) (procedure-arity-includes? f 1))
    (raise-type-error 'memf "procedure (arity 1)" f))
  (let loop ([l list])
    (cond
     [(null? l) #f]
     [(not (pair? l))
      (raise-mismatch-error 'memf
                            "not a proper list: "
                            list)]
     [else (if (f (car l)) l (loop (cdr l)))])))

(define (findf f list)
  (unless (and (procedure? f) (procedure-arity-includes? f 1))
    (raise-type-error 'findf "procedure (arity 1)" f))
  (let loop ([l list])
    (cond
     [(null? l) #f]
     [(not (pair? l))
      (raise-mismatch-error 'findf
                            "not a proper list: "
                            list)]
     [else (let ([a (car l)])
             (if (f a)
                 a
                 (loop (cdr l))))])))

(define (bad-list who orig-l)
  (raise-mismatch-error who
                        "not a proper list: "
                        orig-l))
(define (bad-item who a orig-l)
  (raise-mismatch-error who
                        "non-pair found in list: "
                        a
                        " in "
                        orig-l))

(define-values (assq assv assoc assf)
  (let ()
    (define-syntax-rule (assoc-loop who x orig-l is-equal?)
      (let loop ([l orig-l][t orig-l])
        (cond
         [(pair? l)
          (let ([a (unsafe-car l)])
            (if (pair? a)
                (if (is-equal? x (unsafe-car a))
                    a
                    (let ([l (unsafe-cdr l)])
                      (cond
                       ;; [(eq? l t) (bad-list who orig-l)]
                       [(pair? l)
                        (let ([a (unsafe-car l)])
                          (if (pair? a)
                              (if (is-equal? x (unsafe-car a))
                                  a
                                  (let ([t (unsafe-cdr t)]
                                        [l (unsafe-cdr l)])
                                    (if (eq? l t) 
                                        (bad-list who orig-l)
                                        (loop l t))))
                              (bad-item who a orig-l)))]
                       [(null? l) #f]
                       [else (bad-list who orig-l)])))
                (bad-item who a orig-l)))]
         [(null? l) #f]
         [else (bad-list who orig-l)])))
    (let ([assq
           (lambda (x l)
             (assoc-loop 'assq x l eq?))]
          [assv
           (lambda (x l)
             (assoc-loop 'assv x l eqv?))]
          [assoc
           (case-lambda
             [(x l) (assoc-loop 'assoc x l equal?)]
             [(x l is-equal?)
              (unless (and (procedure? is-equal?)
                           (procedure-arity-includes? is-equal? 2))
                (raise-type-error 'assoc "procedure (arity 2)" is-equal?))
              (assoc-loop 'assoc x l is-equal?)])]
          [assf
           (lambda (f l)
             (unless (and (procedure? f) (procedure-arity-includes? f 1))
               (raise-type-error 'assf "procedure (arity 1)" f))
             (assoc-loop 'assf #f l (lambda (_ a) (f a))))])
      (values assq assv assoc assf))))

;; fold : ((A B -> B) B (listof A) -> B)
;; fold : ((A1 ... An B -> B) B (listof A1) ... (listof An) -> B)

;; foldl builds "B" from the beginning of the list to the end of the
;; list and foldr builds the "B" from the end of the list to the
;; beginning of the list.

(define (mapadd f l last)
  (let loop ([l l])
    (if (null? l)
        (list last)
        (cons (f (car l)) (loop (cdr l))))))


(define (check-fold name proc init l more)
  (unless (procedure? proc)
    (apply raise-type-error name "procedure" 0 proc init l more))
  (unless (list? l)
    (apply raise-type-error name "list" 2 proc init l more))
  (if (null? more)
      (unless (procedure-arity-includes? proc 2)
        (raise-mismatch-error name "given procedure does not accept 2 arguments: " proc))
      (let ([len (length l)])
        (let loop ([more more][n 3])
          (unless (null? more)
            (unless (list? (car more))
              (apply raise-type-error name "list" n proc init l more))
            (unless (= len (length (car more)))
              (raise-mismatch-error name 
                                    "given list does not have the same size as the first list: "
                                    (car more)))
            (loop (cdr more) (add1 n))))
        (unless (procedure-arity-includes? proc (+ 2 (length more)))
          (raise-mismatch-error name 
                                (format "given procedure does not accept ~a arguments: " 
                                        (+ 2 (length more)))
                                proc)))))

(define foldl
  (case-lambda
    [(f init l)
     (check-fold 'foldl f init l null)
     (let loop ([init init] [l l])
       (if (null? l) init (loop (f (car l) init) (cdr l))))]
    [(f init l . ls)
     (check-fold 'foldl f init l ls)
     (let loop ([init init] [ls (cons l ls)])
       (if (pair? (car ls)) ; `check-fold' ensures all lists have equal length
           (loop (apply f (mapadd car ls init)) (map cdr ls))
           init))]))

(define foldr
  (case-lambda
    [(f init l)
     (check-fold 'foldr f init l null)
     (let loop ([init init] [l l])
       (if (null? l)
           init
           (f (car l) (loop init (cdr l)))))]
    [(f init l . ls)
     (check-fold 'foldr f init l ls)
     (let loop ([ls (cons l ls)])
       (if (pair? (car ls)) ; `check-fold' ensures all lists have equal length
           (apply f (mapadd car ls (loop (map cdr ls))))
           init))]))

(define (filter f list)
  (unless (and (procedure? f)
               (procedure-arity-includes? f 1))
    (raise-type-error 'filter "procedure (arity 1)" f))
  (unless (list? list)
    (raise-type-error 'filter "proper list" list))
  ;; accumulating the result and reversing it is currently slightly
  ;; faster than a plain loop
  (let loop ([l list] [result null])
    (if (null? l)
        (reverse result)
        (loop (cdr l) (if (f (car l)) (cons (car l) result) result)))))

;; (build-vector n f) returns a vector 0..n-1 where the ith element is (f i).
;; The eval order is guaranteed to be: 0, 1, 2, ..., n-1.
;; eg: (build-vector 4 (lambda (i) i)) ==> #4(0 1 2 3)
(define (build-vector n fcn)
  (unless (exact-nonnegative-integer? n)
    (raise-type-error 'build-vector "exact-nonnegative-integer" n))
  (unless (and (procedure? fcn)
               (procedure-arity-includes? fcn 1))
    (raise-type-error 'build-vector "procedure (arity 1)" fcn))
  (let ([vec (make-vector n)])
    (let loop ((i 0))
      (if (= i n)
          vec
          (begin (vector-set! vec i (fcn i)) (loop (add1 i)))))))

(define (build-string n fcn)
  (unless (exact-nonnegative-integer? n)
    (raise-type-error 'build-string "exact-nonnegative-integer" n))
  (unless (and (procedure? fcn)
               (procedure-arity-includes? fcn 1))
    (raise-type-error 'build-string "procedure (arity 1)" fcn))
  (let ([str (make-string n)])
    (let loop ((i 0))
      (if (= i n)
          str
          (begin (string-set! str i (fcn i)) (loop (add1 i)))))))

(define (build-list n fcn)
  (unless (exact-nonnegative-integer? n)
    (raise-type-error 'build-list "exact-nonnegative-integer" n))
  (unless (and (procedure? fcn)
               (procedure-arity-includes? fcn 1))
    (raise-type-error 'build-list "procedure (arity 1)" fcn))
  (let recr ([j 0] [i n])
    (cond [(zero? i) null]
          [else (cons (fcn j)
                      (recr (add1 j) (sub1 i)))])))

(define-values [compose1 compose]
  (let ()
    (define-syntax-rule (app1 E1 E2) (E1 E2))
    (define-syntax-rule (app* E1 E2) (call-with-values (lambda () E2) E1))
    (define-syntax-rule (mk-simple-compose app f g)
      (let*-values
          ([(arity) (procedure-arity g)]
           [(required-kwds allowed-kwds) (values '() '())  #; (procedure-keywords g)]
           [(composed)
            ;; FIXME: would be nice to use `procedure-reduce-arity' and
            ;; `procedure-reduce-keyword-arity' in the places marked below,
            ;; but they currently add a significant overhead.
            (if (eq? 1 arity)
                (lambda (x) (app f (g x)))
                (case-lambda          ; <--- here
                  [(x)   (app f (g x))]
                  [(x y) (app f (g x y))]
                  [args  (app f (apply g args))]))])
        composed #;(if (null? allowed-kwds)
            composed
            (make-keyword-procedure   ; <--- and here
             (lambda (kws kw-args . xs)
               (app f (keyword-apply g kws kw-args xs)))
             composed))))
    (define-syntax-rule (can-compose* name n g f fs)
      (unless (null? (let-values ([(req _) (values '() '()) #;(procedure-keywords g)]) req))
        (apply raise-type-error 'name "procedure (no required keywords)"
               n f fs)))
    (define-syntax-rule (can-compose1 name n g f fs)
      (begin (unless (procedure-arity-includes? g 1)
               (apply raise-type-error 'name "procedure (arity 1)" n f fs))
             ;; need to check this too (see PR 11978)
             (can-compose* name n g f fs)))
    (define (pipeline1 f rfuns)
      ;; (very) slightly slower alternative:
      ;; (if (null? rfuns)
      ;;   f
      ;;   (pipeline1 (let ([fst (car rfuns)]) (lambda (x) (fst (f x))))
      ;;              (cdr rfuns)))
      (lambda (x)
        (let loop ([x x] [f f] [rfuns rfuns])
          (if (null? rfuns)
              (f x)
              (loop (f x) (car rfuns) (cdr rfuns))))))
    (define (pipeline* f rfuns)
      ;; use the other composition style in this case, to optimize an
      ;; occasional arity-1 procedure in the pipeline
      (if (eqv? 1 (procedure-arity f))
          ;; if `f' is single arity, then going in reverse they will *all* be
          ;; single arities
          (let loop ([f f] [rfuns rfuns])
            (if (null? rfuns)
                f
                (loop (let ([fst (car rfuns)])
                        (if (eqv? 1 (procedure-arity fst))
                            (lambda (x) (fst (f x)))
                            (lambda (x) (app* fst (f x)))))
                      (cdr rfuns))))
          ;; otherwise, going in reverse means that they're all n-ary, which
          ;; means that the list of arguments will be built for each stage, so
          ;; to avoid that go forward in this case
          (let ([funs (reverse (cons f rfuns))])
            (let loop ([f (car funs)] [funs (cdr funs)])
              (if (null? funs)
                  f
                  (loop (let ([fst (car funs)])
                          (if (eqv? 1 (procedure-arity f))
                              (if (eqv? 1 (procedure-arity fst))
                                  (lambda (x) (f (fst x)))
                                  (lambda xs (f (apply fst xs))))
                              (if (eqv? 1 (procedure-arity fst))
                                  (lambda (x) (app* f (fst x)))
                                  (lambda xs (app* f (apply fst xs))))))
                        (cdr funs)))))))
    (define-syntax-rule (mk name app can-compose pipeline mk-simple-compose)
      (define name
        (let ([simple-compose mk-simple-compose])
          (case-lambda
            [(f)
             (if (procedure? f) f (raise-type-error 'name "procedure" 0 f))]
            [(f g)
             (unless (procedure? f)
               (raise-type-error 'name "procedure" 0 f g))
             (unless (procedure? g)
               (raise-type-error 'name "procedure" 1 f g))
             (can-compose name 0 f f '())
             (simple-compose f g)]
            [() values]
            [(f0 . fs0)
             (let loop ([f f0] [fs fs0] [i 0] [rfuns '()])
               (unless (procedure? f)
                 (apply raise-type-error 'name "procedure" i f0 fs0))
               (if (pair? fs)
                   (begin (can-compose name i f f0 fs0)
                          (loop (car fs) (cdr fs) (add1 i) (cons f rfuns)))
                   (simple-compose (pipeline (car rfuns) (cdr rfuns)) f)))]))))
    (mk compose1 app1 can-compose1 pipeline1
        (lambda (f g) (mk-simple-compose app1 f g)))
    (mk compose  app* can-compose* pipeline*
        (lambda (f g)
          (if (eqv? 1 (procedure-arity f))
              (mk-simple-compose app1 f g)
              (mk-simple-compose app* f g))))
    (values compose1 compose)))