#lang racket/base
(require syntax/parse/private/residual-ct ;; keep abs. path
         "rep-attrs.rkt"
         "kws.rkt"
         unstable/struct
         (for-syntax racket/base
                     syntax/stx
                     racket/syntax))
(provide (all-defined-out))

#|
Uses Arguments from kws.rkt
|#

#|
A Base is (listof IAttr)
  If P = (make-pattern Attrs ...) and A is in Attrs,
  the depth of A is with respect to P,
  not with respect to the entire enclosing pattern.
|#

#|
A SinglePattern is one of
  (pat:any Base)
  (pat:var Base id id Arguments (listof IAttr) nat/#f bool stx)
  (pat:literal Base identifier ct-phase ct-phase)
  (pat:datum Base datum)
  (pat:action Base ActionPattern SinglePattern)
  (pat:head Base HeadPattern SinglePattern)
  (pat:dots Base (listof EllipsisHeadPattern) SinglePattern)
  (pat:and Base (listof SinglePattern))
  (pat:or Base (listof SinglePattern))
  (pat:not Base SinglePattern)
  (pat:pair Base SinglePattern SinglePattern)
  (pat:vector Base SinglePattern)
  (pat:box Base SinglePattern)
  (pat:pstruct Base key SinglePattern)
  (pat:describe Base SinglePattern stx boolean stx)
  (pat:delimit Base SinglePattern)
  (pat:commit Base SinglePattern)
  (pat:reflect Base stx Arguments (listof SAttr) id (listof IAttr))
  (pat:post Base SinglePattern)
  (pat:integrated Base id/#f id string stx)

A ListPattern is a subtype of SinglePattern; one of
  (pat:datum Base '())
  (pat:action Base ActionPattern ListPattern)
  (pat:head Base HeadPattern ListPattern)
  (pat:pair Base SinglePattern ListPattern)
  (pat:dots Base EllipsisHeadPattern SinglePattern)
|#

(define-struct pat:any (attrs) #:prefab)
(define-struct pat:var (attrs name parser argu nested-attrs attr-count commit? role) #:prefab)
(define-struct pat:literal (attrs id input-phase lit-phase) #:prefab)
(define-struct pat:datum (attrs datum) #:prefab)
(define-struct pat:action (attrs action inner) #:prefab)
(define-struct pat:head (attrs head tail) #:prefab)
(define-struct pat:dots (attrs heads tail) #:prefab)
(define-struct pat:and (attrs patterns) #:prefab)
(define-struct pat:or (attrs patterns) #:prefab)
(define-struct pat:not (attrs pattern) #:prefab)
(define-struct pat:pair (attrs head tail) #:prefab)
(define-struct pat:vector (attrs pattern) #:prefab)
(define-struct pat:box (attrs pattern) #:prefab)
(define-struct pat:pstruct (attrs key pattern) #:prefab)
(define-struct pat:describe (attrs pattern description transparent? role) #:prefab)
(define-struct pat:delimit (attrs pattern) #:prefab)
(define-struct pat:commit (attrs pattern) #:prefab)
(define-struct pat:reflect (attrs obj argu attr-decls name nested-attrs) #:prefab)
(define-struct pat:post (attrs pattern) #:prefab)
(define-struct pat:integrated (attrs name predicate description role) #:prefab)

#|
A ActionPattern is one of
  (action:cut Base)
  (action:fail Base stx stx)
  (action:bind Base (listof clause:attr))
* (action:and Base (listof ActionPattern))
  (action:parse Base SinglePattern stx)
  (action:do Base (listof stx))
  (action:post Base ActionPattern)

action:and is desugared below in create-* procedures
|#

(define-struct action:cut (attrs) #:prefab)
(define-struct action:fail (attrs when message) #:prefab)
(define-struct action:bind (attrs clauses) #:prefab)
(define-struct action:and (attrs patterns) #:prefab)
(define-struct action:parse (attrs pattern expr) #:prefab)
(define-struct action:do (attrs stmts) #:prefab)
(define-struct action:post (attrs pattern) #:prefab)

#|
A HeadPattern is one of 
  (hpat:var Base id id Arguments (listof IAttr) nat/#f bool stx)
  (hpat:seq Base ListPattern)
  (hpat:action Base ActionPattern HeadPattern)
  (hpat:and Base HeadPattern SinglePattern)
  (hpat:or Base (listof HeadPattern))
  (hpat:optional Base HeadPattern (listof clause:attr))
  (hpat:describe Base HeadPattern stx/#f boolean stx)
  (hpat:delimit Base HeadPattern)
  (hpat:commit Base HeadPattern)
  (hpat:reflect Base stx Arguments (listof SAttr) id (listof IAttr))
  (hpat:post Base HeadPattern)
  (hpat:peek Base HeadPattern)
  (hpat:peek-not Base HeadPattern)
|#

(define-struct hpat:var (attrs name parser argu nested-attrs attr-count commit? role) #:prefab)
(define-struct hpat:seq (attrs inner) #:prefab)
(define-struct hpat:action (attrs action inner) #:prefab)
(define-struct hpat:and (attrs head single) #:prefab)
(define-struct hpat:or (attrs patterns) #:prefab)
(define-struct hpat:optional (attrs inner defaults) #:prefab)
(define-struct hpat:describe (attrs pattern description transparent? role) #:prefab)
(define-struct hpat:delimit (attrs pattern) #:prefab)
(define-struct hpat:commit (attrs pattern) #:prefab)
(define-struct hpat:reflect (attrs obj argu attr-decls name nested-attrs) #:prefab)
(define-struct hpat:post (attrs pattern) #:prefab)
(define-struct hpat:peek (attrs pattern) #:prefab)
(define-struct hpat:peek-not (attrs pattern) #:prefab)

#|
An EllipsisHeadPattern is
  (ehpat Base HeadPattern RepConstraint)

A RepConstraint is one of
  (rep:once stx stx stx)
  (rep:optional stx stx (listof clause:attr))
  (rep:bounds nat/#f nat/#f stx stx stx)
  #f
|#

(define-struct ehpat (attrs head repc) #:prefab)
(define-struct rep:once (name under-message over-message) #:prefab)
(define-struct rep:optional (name over-message defaults) #:prefab)
(define-struct rep:bounds (min max name under-message over-message) #:prefab)


#|
A SideClause is one of
  (clause:fail stx stx)
  (clause:with pattern stx (listof stx))
  (clause:attr IAttr stx)
  (clause:do (listof stx))
|#
(define-struct clause:fail (condition message) #:prefab)
(define-struct clause:with (pattern expr definitions) #:prefab)
(define-struct clause:attr (attr expr) #:prefab)
(define-struct clause:do (stmts) #:prefab)

(define (pattern? x)
  (or (pat:any? x)
      (pat:var? x)
      (pat:literal? x)
      (pat:datum? x)
      (pat:action? x)
      (pat:head? x)
      (pat:dots? x)
      (pat:and? x)
      (pat:or? x)
      (pat:not? x)
      (pat:pair? x)
      (pat:vector? x)
      (pat:box? x)
      (pat:pstruct? x)
      (pat:describe? x)
      (pat:delimit? x)
      (pat:commit? x)
      (pat:reflect? x)
      (pat:post? x)
      (pat:integrated? x)))

(define (action-pattern? x)
  (or (action:cut? x)
      (action:bind? x)
      (action:fail? x)
      (action:and? x)
      (action:parse? x)
      (action:do? x)
      (action:post? x)))

(define (head-pattern? x)
  (or (hpat:var? x)
      (hpat:seq? x)
      (hpat:action? x)
      (hpat:and? x)
      (hpat:or? x)
      (hpat:optional? x)
      (hpat:describe? x)
      (hpat:delimit? x)
      (hpat:commit? x)
      (hpat:reflect? x)
      (hpat:post? x)
      (hpat:peek? x)
      (hpat:peek-not? x)))

(define (ellipsis-head-pattern? x)
  (ehpat? x))

(define single-pattern? pattern?)

(define (single-or-head-pattern? x)
  (or (single-pattern? x)
      (head-pattern? x)))

(define pattern-attrs
  (let ()
    (define-syntax (mk-get-attrs stx)
      (syntax-case stx ()
        [(_ struct ...)
         (with-syntax
             ([([pred accessor] ...)
               (for/list ([s (in-list (stx->list #'(struct ...)))])
                 (list (format-id s "~a?" (syntax-e s))
                       (format-id s "~a-attrs" (syntax-e s))))])
           #'(lambda (x)
               (cond [(pred x) (accessor x)] ...
                     [else (raise-type-error 'pattern-attrs "pattern" x)])))]))
    (mk-get-attrs pat:any pat:var pat:datum pat:literal pat:action pat:head
                  pat:dots pat:and pat:or pat:not pat:describe
                  pat:pair pat:vector pat:box pat:pstruct
                  pat:delimit pat:commit pat:reflect pat:post pat:integrated
                  action:cut action:bind action:fail action:and action:parse
                  action:do action:post
                  hpat:var hpat:seq hpat:action hpat:and hpat:or hpat:describe
                  hpat:optional hpat:delimit hpat:commit hpat:reflect hpat:post
                  hpat:peek hpat:peek-not
                  ehpat)))

;; ----

;; Helpers to handle attribute calculations
;; Too complicated for a few pattern forms; those are handled in rep.rkt

(define (create-pat:any)
  (make pat:any null))

(define (create-pat:var name parser argu nested-attrs attr-count commit? role)
  (let ([attrs
         (if name (cons (make attr name 0 #t) nested-attrs) nested-attrs)])
    (make pat:var attrs name parser argu nested-attrs attr-count commit? role)))

(define (create-pat:reflect obj argu attr-decls name nested-attrs)
  (let ([attrs
         (if name (cons (make attr name 0 #t) nested-attrs) nested-attrs)])
    (make pat:reflect attrs obj argu attr-decls name nested-attrs)))

(define (create-pat:datum datum)
  (make pat:datum null datum))

(define (create-pat:literal literal input-phase lit-phase)
  (make pat:literal null literal input-phase lit-phase))

(define (create-pat:action g sp)
  (cond [(action:and? g)
         (for/fold ([sp sp]) ([g (in-list (reverse (action:and-patterns g)))])
           (create-pat:action g sp))]
        [else
         (let ([attrs (append-iattrs (map pattern-attrs (list g sp)))])
           (make pat:action attrs g sp))]))

(define (create-pat:head headp tailp)
  (let ([attrs (append-iattrs (map pattern-attrs (list headp tailp)))])
    (make pat:head attrs headp tailp)))

(define (create-pat:pair headp tailp)
  (make pat:pair (append-iattrs (map pattern-attrs (list headp tailp))) headp tailp))

(define (create-pat:vector pattern)
  (make pat:vector (pattern-attrs pattern) pattern))

(define (create-pat:box pattern)
  (make pat:box (pattern-attrs pattern) pattern))

(define (create-pat:pstruct key pattern)
  (make pat:pstruct (pattern-attrs pattern) key pattern))

(define (create-pat:describe p description transparent? role)
  (make pat:describe (pattern-attrs p) p description transparent? role))

(define (create-pat:and patterns)
  (let ([attrs (append-iattrs (map pattern-attrs patterns))])
    (make pat:and attrs patterns)))

(define (create-pat:or patterns)
  (let ([attrs (union-iattrs (map pattern-attrs patterns))])
    (make pat:or attrs patterns)))

(define (create-pat:not pattern)
  (make pat:not null pattern))

(define (create-pat:dots headps tailp)
  (let ([attrs (append-iattrs (map pattern-attrs (cons tailp headps)))])
    (make pat:dots attrs headps tailp)))

(define (create-pat:delimit pattern)
  (make pat:delimit (pattern-attrs pattern) pattern))

(define (create-pat:commit pattern)
  (make pat:commit (pattern-attrs pattern) pattern))

(define (create-pat:post pattern)
  (make pat:post (pattern-attrs pattern) pattern))

(define (create-pat:integrated name predicate description role)
  (let ([attrs (if name (list (make attr name 0 #t)) null)])
    (make pat:integrated attrs name predicate description role)))

;; ----

(define (create-action:cut)
  (make action:cut null))

(define (create-action:fail condition message)
  (make action:fail null condition message))

(define (create-action:bind clauses)
  (make action:bind (map clause:attr-attr clauses) clauses))

(define (create-action:and patterns)
  (let ([attrs (append-iattrs (map pattern-attrs patterns))])
    (make action:and attrs patterns)))

(define (create-action:parse pattern expr)
  (make action:parse (pattern-attrs pattern) pattern expr))

(define (create-action:do stmts)
  (make action:do null stmts))

(define (create-action:post pattern)
  (make action:post (pattern-attrs pattern) pattern))

;; ----

(define (create-hpat:var name parser argu nested-attrs attr-count commit? role)
  (let ([attrs
         (if name (cons (make attr name 0 #t) nested-attrs) nested-attrs)])
    (make hpat:var attrs name parser argu nested-attrs attr-count commit? role)))

(define (create-hpat:reflect obj argu attr-decls name nested-attrs)
  (let ([attrs
         (if name (cons (make attr name 0 #t) nested-attrs) nested-attrs)])
    (make hpat:reflect attrs obj argu attr-decls name nested-attrs)))

(define (create-hpat:seq lp)
  (make hpat:seq (pattern-attrs lp) lp))

(define (create-hpat:action g hp)
  (cond [(action:and? g)
         (for/fold ([hp hp]) ([g (in-list (reverse (action:and-patterns g)))])
           (create-hpat:action g hp))]
        [else
         (let ([attrs (append-iattrs (map pattern-attrs (list g hp)))])
           (make hpat:action attrs g hp))]))

(define (create-hpat:describe p description transparent? role)
  (make hpat:describe (pattern-attrs p) p description transparent? role))

(define (create-hpat:and hp sp)
  (make hpat:and (append-iattrs (map pattern-attrs (list hp sp))) hp sp))

(define (create-hpat:or patterns)
  (let ([attrs (union-iattrs (map pattern-attrs patterns))])
    (make hpat:or attrs patterns)))

(define (create-hpat:delimit pattern)
  (make hpat:delimit (pattern-attrs pattern) pattern))

(define (create-hpat:commit pattern)
  (make hpat:commit (pattern-attrs pattern) pattern))

(define (create-hpat:post pattern)
  (make hpat:post (pattern-attrs pattern) pattern))

(define (create-hpat:peek pattern)
  (make hpat:peek (pattern-attrs pattern) pattern))

(define (create-hpat:peek-not pattern)
  (make hpat:peek-not null pattern))

;; ----

(define (action/head-pattern->list-pattern p)
  (cond [(action-pattern? p)
         (create-pat:action p (create-pat:any))]
        [(hpat:seq? p)
         ;; simplification: just extract list pattern from hpat:seq
         (hpat:seq-inner p)]
        [else
         (create-pat:head p (create-pat:datum '()))]))

(define (action-pattern->single-pattern gp)
  (create-pat:action gp (create-pat:any)))
