#lang racket/base
(require syntax/kerncase
         syntax/stx)
(provide (protect-out head-local-expand-and-categorize-syntaxes
                      categorize-expanded-syntaxes
                      head-local-expand-syntaxes))

;; head-local-expand-syntaxes : syntax boolean boolean -> stxs ^ 6
;; Setting allow-def-after-expr? allows def/expr interleaving.
(define (head-local-expand-and-categorize-syntaxes x allow-def-after-expr?)
  (define estxs (head-local-expand-syntaxes x allow-def-after-expr?))
  (define-values (defs vdefs sdefs exprs)
    (categorize-expanded-syntaxes estxs))
  (values estxs estxs defs vdefs sdefs exprs))

;; categorize-expanded-syntaxes : (listof stx) -> stxs ^ 4
;; Split head-expanded stxs into
;;   definitions, values-definitions, syntaxes-definitions, exprs
;; (definitions include both values-definitions and syntaxes-definitions.)
(define (categorize-expanded-syntaxes estxs0)
  (let loop ([estxs estxs0] [defs null] [vdefs null] [sdefs null] [exprs null])
    (cond [(pair? estxs)
           (let ([ee (car estxs)])
             (syntax-case ee (begin define-values define-syntaxes)
               [(define-values . _)
                (loop (cdr estxs)
                      (cons ee defs)
                      (cons ee vdefs)
                      sdefs
                      exprs)]
               [(define-syntaxes (var ...) rhs)
                (loop (cdr estxs)
                      (cons ee defs)
                      vdefs
                      (cons ee sdefs)
                      exprs)]
               [_
                (loop (cdr estxs)
                      defs
                      vdefs
                      sdefs
                      (cons ee exprs))]))]
          [(null? estxs)
           (values (reverse defs)
                   (reverse vdefs)
                   (reverse sdefs)
                   (reverse exprs))])))

;; head-local-expand-syntaxes : syntax boolean -> (listof syntax)
(define (head-local-expand-syntaxes x allow-def-after-expr?)
  (let ([intdef (syntax-local-make-definition-context)]
        [ctx '(block)])
    (let loop ([x x] [ex null] [expr? #f])
      (cond [(stx-pair? x)
             (let ([ee (local-expand (stx-car x)
                                     ctx
                                     (kernel-form-identifier-list)
                                     intdef)])
               (syntax-case ee (begin define-values define-syntaxes)
                 [(begin e ...)
                  (loop (append (syntax->list #'(e ...)) (stx-cdr x)) ex expr?)]
                 [(begin . _)
                  (raise-syntax-error #f "bad begin form" ee)]
                 [(define-values (var ...) rhs)
                  (andmap identifier? (syntax->list #'(var ...)))
                  (begin
                    (when (and expr? (not allow-def-after-expr?))
                      (raise-syntax-error #f "definition after expression" ee))
                    (syntax-local-bind-syntaxes (syntax->list #'(var ...)) #f intdef)
                    (loop (stx-cdr x) (cons ee ex) expr?))]
                 [(define-values . _)
                  (raise-syntax-error #f "bad define-values form" ee)]
                 [(define-syntaxes (var ...) rhs)
                  (andmap identifier? (syntax->list #'(var ...)))
                  (begin
                    (when (and expr? (not allow-def-after-expr?))
                      (raise-syntax-error #f "definition after expression" ee))
                    (syntax-local-bind-syntaxes (syntax->list #'(var ...))
                                                #'rhs
                                                intdef)
                    (loop (stx-cdr x) (cons ee ex) expr?))]
                 [(define-syntaxes . _)
                  (raise-syntax-error #f "bad define-syntaxes form" ee)]
                 [_
                  (loop (stx-cdr x) (cons ee ex) #t)]))]
            [(stx-null? x)
             (internal-definition-context-seal intdef)
             (reverse ex)]))))
