lang/do-check.ss
#lang scheme

(require "../private/planet.ss"
         scheme/stxparam
         (cce define)
         (for-syntax syntax/parse
                     scheme/match
                     scheme/unit-exptime
                     scheme/require-transform
                     (cce syntax)))

(define-syntax-parameter check-arity? #t)

(define-syntax-rule (unchecked-arity e)
  (syntax-parameterize ([check-arity? #f]) e))

(define-for-syntax (check-arity! arity stx)
  (syntax-parse stx
    [(_ arg ...)
     (let* ([len (length (syntax->list #'(arg ...)))])
       (unless (= len arity)
         (raise-syntax-error #f
           (format "expected ~a arguments, but got ~a arguments instead"
             arity len)
           stx)))]
    [(_ . args)
     (raise-syntax-error #f
       "expected a proper list of arguments, but got an improper list instead"
       stx)]
    [_ (raise-syntax-error #f
         "functions must be applied to arguments"
         stx)]))

(define-for-syntax ((check-arity-transformer arity f) stx)
  (when (syntax-parameter-value #'check-arity?)
    (check-arity! arity stx))
  (f stx))

(define-for-syntax all-scopes (box null))

(define-for-syntax (push! x stack)
  (set-box! stack (cons x (unbox stack))))

(define-for-syntax (pop! x stack)
  (unless (and (pair? (unbox stack)) (eq? (car (unbox stack)) x))
    (error 'begin-below
      "internal error: push!/pop! mismatch: ~s <- ~s" x stack))
  (set-box! stack (cdr (unbox stack)))
  x)

(define-for-syntax (register-marker marker stx)
  (unless (pair? (unbox all-scopes))
    (raise-syntax-error #f
      "defined outside of begin-below" stx marker))
  (let* ([scope (car (unbox all-scopes))])
    (push! (syntax-local-value marker) scope)))

(define-syntax-rule (define-below-marker-for name form)
  (begin (define-syntax name (box #t))
         (in-phase1 (register-marker #'name #'form))
         (in-phase1/pass2 (set-box! (syntax-local-value #'name) #t))))

(define-syntax (begin-below stx)
  (match (syntax-local-context)
    ['expression (raise-syntax-error #f "cannot be used as an expression" stx)]
    [_ (void)])
  (syntax-parse stx
    [(_ term:expr ...)
     #'(begin
         (define-syntax scope (box null))
         (in-phase1 (push! (syntax-local-value #'scope) all-scopes))
         (begin term ...)
         (in-phase1
          (let ([scope (pop! (syntax-local-value #'scope) all-scopes)])
            (for ([marker (in-list (unbox scope))])
              (set-box! marker #f)))))]))

(define-for-syntax ((check-below-transformer marker f) stx)
  (unless (unbox (syntax-local-value marker))
    (raise-syntax-error #f
      "used before definition" stx))
  (f stx))

(define-syntax (rename-below stx)
  (syntax-parse stx
    [(_ [above:id below:id] ...)
     #`(begin
         (define-below-marker-for here #,stx)
         (define-syntaxes [ below ... ]
           (values (check-below-transformer #'here
                     (redirect-transformer #'above))
                   ...)))]))

(define-syntax (define-values-below stx)
  (syntax-parse stx
    [(_ [below:id ...] body:expr)
     (with-syntax* ([(above ...) (generate-temporaries #'(below ...))])
       #`(begin
           (rename-below [above below] ...)
           (define-values [above ...] body)))]))

(define-single-definition define-below define-values-below)

(define-syntax (require-below stx)
  (syntax-parse stx
    [(_ spec:expr ...)
     (define specs #'(only-meta-in 0 spec ...))
     (define-values [ imports sources ] (expand-import specs))
     (define names (map import-local-id imports))
     (define temps (generate-temporaries names))
     (with-syntax ([(name ...) names]
                   [(temp ...) temps])
       #`(begin
           (require (rename-in #,specs [name temp] ...))
           (rename-below [temp name] ...)))]))

(provide
 (for-syntax check-arity-transformer) unchecked-arity
 (for-syntax check-below-transformer) begin-below define-below-marker-for
 rename-below
 define-below
 define-values-below
 require-below
 )