#lang racket/base
(require (for-syntax racket/base)
         (prefix-in yacc: parser-tools/yacc)
         (for-syntax racket/pretty))
(provide parser
         options
         productions
         definitions)

(define-syntax options
  (lambda (stx)
    (raise-syntax-error #f "options keyword used out of context" stx)))

(define-syntax productions
  (lambda (stx)
    (raise-syntax-error #f "productions keyword used out of context" stx)))

(define-syntax definitions
  (lambda (stx)
    (raise-syntax-error #f "definitions keyword used out of context" stx)))

(define-syntax (parser* stx)
  (syntax-case stx ()
    [(parser form ...)
     (let ([stop-list (list #'begin #'options #'productions #'definitions)]
           [forms (syntax->list #'(form ...))])
       (define-values (opts prods defs)
         (let loop ([forms forms] [opts null] [prods null] [defs null])
           (if (pair? forms)
               (let ([eform0 (local-expand (car forms) 'expression stop-list)]
                     [forms (cdr forms)])
                 (syntax-case eform0 (begin options productions definitions)
                   [(begin subform ...)
                    (loop (append (syntax->list #'(subform ...)) forms) opts prods defs)]
                   [(options subform ...)
                    (loop forms (append (syntax->list #'(subform ...)) opts) prods defs)]
                   [(productions subform ...)
                    (loop forms opts (append (syntax->list #'(subform ...)) prods) defs)]
                   [(definitions subform ...)
                    (loop forms opts prods (append (syntax->list #'(subform ...)) defs))]
                   [else
                    (raise-syntax-error #f "bad parser subform" eform0)]))
               (values opts prods defs))))
       (with-syntax ([(opt ...) opts]
                     [(prod ...) prods]
                     [(def ...) defs])
         #'(let ()
             def ...
             (#%expression (yacc:parser opt ... (grammar prod ...))))))]))

(define-syntax-rule (parser . content)
  ;; Ensure that local expansion doesn't add
  ;; marks due to use of a macro in the enclosing
  ;; binding scope:
  (let ()
    (parser* . content)))
