;;; Copyright (c) 2000-2015 Dipanwita Sarkar, Andrew W. Keep, R. Kent Dybvig, Oscar Waddell
;;; See the accompanying file Copyright for details

(library (tests helpers)
  (export compose disjoin choose reverse-filter fold reduce
          constant? keyword? list-of-user-primitives list-of-system-primitives
          user-primitive? system-primitive? primitive? predicate-primitive?
          value-primitive? effect-primitive? effect-free-primitive? gen-label
          reset-seed gen-symbol set? iota with-values
          empty-set singleton-set
          add-element member? empty? union intersection difference
          variable? datum? list-index primapp sys-primapp app const-datum const
          var quoted-const time printf system interpret pretty-print format set-cons
          define-who)
  (import (rnrs) 
          (tests implementation-helpers) 
          (nanopass helpers)) 
  
  (define-syntax primapp
    (syntax-rules ()
      [(_ expr expr* ...) (expr expr* ...)])) 
  
  (define-syntax sys-primapp
    (syntax-rules ()
      [(_ expr expr* ...) (expr expr* ...)])) 
  
  (define-syntax app
    (syntax-rules ()
      [(_ expr expr* ...) (expr expr* ...)])) 
  
  (define-syntax const-datum
    (syntax-rules ()
      [(_ expr) (quote expr)])) 
  
  (define-syntax const
    (syntax-rules ()
      [(_ expr) expr])) 
  
  (define-syntax var
    (syntax-rules ()
      [(_ expr) expr])) 
  
  (define-syntax quoted-const
    (syntax-rules ()
      [(_ expr) (quote expr)])) 

  (define compose
    (case-lambda
      [() (lambda (x) x)]
      [(f) f]
      [(f . g*) (lambda (x) (f ((apply compose g*) x)))])) 
  
  (define disjoin
    (case-lambda
      [() (lambda (x) #f)]
      [(p?) p?]
      [(p? . q?*) (lambda (x) 
                    (or (p? x) ((apply disjoin q?*) x)))])) 
  
  (define choose
    (lambda (pred? ls)
      (fold (lambda (elt tail)
              (if (pred? elt)
                  (cons elt tail)
                  tail))
            '()
            ls))) 
  
  (define reverse-filter
    (lambda (pred? ls)
      (fold (lambda (elt tail)
              (if (pred? elt)
                  tail
                  (cons elt tail)))
            '()
            ls))) 
  
  ;; fold op base (cons a (cons b (cons c '()))) =
  ;; (op a (op b (op c base)))
  (define fold
    (lambda (op base ls)
      (let recur ([ls ls])
        (if (null? ls)
            base
            (op (car ls) (recur (cdr ls))))))) 
  
  ;; reduce op base (cons a (cons b (cons c '())))
  ;; (op c (op b (op a base)))
  (define reduce 
    (lambda (op base ls)
      (let loop ([ls ls] [ans base])
        (if (null? ls)
            ans
            (loop (cdr ls) (op (car ls) ans)))))) 
  
  ;;; General Scheme helpers for the compiler 
  (define constant?
    (disjoin null? number? char? boolean? string?)) 

  (define keyword?
    (lambda (x)
      (and (memq x '(quote set! if begin let letrec lambda)) #t))) 
  
  (define datum?
    (lambda (x)
      (or (constant? x)
          (null? x)
          (if (pair? x)
            (and (datum? (car x)) (datum? (cdr x)))
            (and (vector? x) (for-all datum? (vector->list x)))))))
  
  (define variable? symbol?)
  
  (define list-of-user-primitives
    '(; not is a special case
      (not 1 not) 
      
      ; predicates
      (< 2 test)
      (<= 2 test)
      (= 2 test)
      (boolean? 1 test)
      (char? 1 test)
      (eq? 2 test)
      (integer? 1 test)
      (null? 1 test)
      (pair? 1 test)
      (procedure? 1 test)

      (vector? 1 test)
      (zero? 1 test) 
      
      ; value-producing
      (* 2 value)
      (+ 2 value)
      (- 2 value)
      (add1 1 value)
      (car 1 value)
      (cdr 1 value)
      (char->integer 1 value)
      (cons 2 value) 

      (make-vector 1 value)
      (quotient 2 value)
      (remainder 2 value) 
      
      (sub1 1 value) 
      
      (vector -1 value)
      (vector-length 1 value)
      (vector-ref 2 value)
      (void 0 value) 
      
      ; side-effecting
      (set-car! 2 effect)
      (set-cdr! 2 effect) 
      
      (vector-set! 3 effect))) 
  
  (define list-of-system-primitives ; these are introduced later by the compiler
    '(; value-producing
      (closure-ref 2 value)
      (make-closure 2 value)
      (procedure-code 1 value) 
      
      ; side-effecting
      (closure-set! 3 effect) 
      
      (fref 1 value)
      (fset! 2 effect)
      (fincr! 1 effect)
      (fdecr! 1 effect)
      (href 2 value)
      (hset! 3 effect)
      (logand 2 value)
      (sll 2 value)
      (sra 2 value))) 
  
  (define user-primitive?
    (lambda (x)
      (and (assq x list-of-user-primitives) #t))) 
  
  (define system-primitive?
    (lambda (x)
      (and (assq x list-of-system-primitives) #t))) 
  
  (define primitive?
    (lambda (x)
      (or (user-primitive? x) (system-primitive? x)))) 
  
  (define predicate-primitive?
    (lambda (x)
      (cond
        [(or (assq x list-of-user-primitives)
             (assq x list-of-system-primitives)) =>
         (lambda (a) (eq? (caddr a) 'test))]
        [else #f]))) 
  
  (define value-primitive?
    (lambda (x) 
      (cond
        [(or (assq x list-of-user-primitives)
             (assq x list-of-system-primitives)) =>
         (lambda (a) (eq? (caddr a) 'value))]
        [else #f]))) 
  
  (define effect-primitive?
    (lambda (x)
      (cond
        [(or (assq x list-of-user-primitives)
             (assq x list-of-system-primitives)) =>
         (lambda (a) (eq? (caddr a) 'effect))]
        [else #f]))) 
  
  (define effect-free-primitive?
    (lambda (x)
      (not (effect-primitive? x))))

  (define gen-label
    ; at some point, gen-label should be redefined to emit
    ; assembler-friendly labels
    (lambda (sym)
      (string->symbol (format "~a%" sym))))

  (define gen-symbol-seed 0)

  (define reset-seed
    (lambda ()
      (set! gen-symbol-seed 0)))
  
  (define gen-symbol
    (lambda (sym)
      (set! gen-symbol-seed (+ gen-symbol-seed 1))
      (string->symbol (format "~a_~s" sym gen-symbol-seed)))) 
  
  (define set?
    (lambda (ls)
      (or (null? ls)
          (and (not (memq (car ls) (cdr ls))) (set? (cdr ls)))))) 
  
  ;;; ====================
  ;;; Extra syntax and helpers for multiple values 
  
  ;;; Set abstraction
  (define empty-set (lambda () '())) 
  
  (define singleton-set (lambda (elt) (list elt))) 
  
  (define add-element
    (lambda (elt set)
      (if (member? elt set)
          set
          (cons elt set)))) 
 
  (define member? memq)
  
  (define empty? null?) 

  (define set-cons
    (lambda (a set)
      (if (memq a set) set (cons a set))))
  
  (define union
    (case-lambda
      [() (empty-set)]
      [(set1 set2)
       (cond
         [(empty? set1) set2]
         [(empty? set2) set1]
         [(eq? set1 set2) set1]
         [else (reduce (lambda (elt set)
                         (if (member? elt set2) set (cons elt set)))
                       set2
                       set1)])]
      [(set1 . sets)
       (if (null? sets)
           set1
           (union set1 (reduce union (empty-set) sets)))])) 
  
  (define intersection
    (lambda (set1 . sets)
      (cond
        [(null? sets) set1]
        [(exists empty? sets) (empty-set)]
        [else (choose
                (lambda (elt)
                  (for-all (lambda (set) (member? elt set)) sets)) set1)])))
  
  (define list-index
    (lambda (a ls)
      (cond
        [(null? ls) -1]
        [(eq? (car ls) a) 0]
        [else (maybe-add1 (list-index a (cdr ls)))])))  

  (define maybe-add1
    (lambda (n)
      (if (= n -1) -1 (+ n 1))))

  (define difference
    (lambda (set1 . sets)
      (let ((sets (reverse-filter empty? sets)))
        (cond
          [(null? sets) set1]
          [else (reverse-filter (lambda (elt)
                                  (exists (lambda (set)
                                            (member? elt set))
                                          sets))
                                set1)])))))
