#lang racket/base
(require brag/rules/stx-types
         brag/codegen/flatten
         rackunit)


(define (make-fresh-name)
  (let ([n 0])
    (lambda ()
      (set! n (add1 n))
      (string->symbol (format "r~a" n)))))


;; Simple literals
(check-equal? (map syntax->datum (flatten-rule #'(rule expr (lit "hello"))))
              '((prim-rule lit expr [(lit "hello")])))

(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr
                                         (seq (lit "hello")
                                              (lit "world")))))
              '((prim-rule seq expr [(lit "hello") (lit "world")])))


(check-equal? (map syntax->datum (flatten-rule #'(rule expr (token HELLO))))
              '((prim-rule token expr [(token HELLO)])))

(check-equal? (map syntax->datum (flatten-rule #'(rule expr (id rule-2))))
              '((prim-rule id expr [(id rule-2)])))


;; Sequences of primitives
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr (seq (lit "1") (seq (lit "2") (lit "3"))))))
              '((prim-rule seq expr 
                           [(lit "1") (lit "2") (lit "3")])))

(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr (seq (seq (lit "1") (lit "2")) (lit "3")))))
              '((prim-rule seq expr 
                           [(lit "1") (lit "2") (lit "3")])))


(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr (seq (seq (lit "1")) (seq (lit "2") (lit "3"))))))
              '((prim-rule seq expr 
                           [(lit "1") (lit "2") (lit "3")])))



;; choices
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr (choice (id rule-2) (id rule-3)))))
              '((prim-rule choice expr
                           [(id rule-2)]
                           [(id rule-3)])))

(check-equal? (map syntax->datum
                   (flatten-rule #'(rule sexp (choice (seq (lit "(") (lit ")"))
                                                      (seq)))
                                 #:fresh-name (make-fresh-name)))
              '((prim-rule choice sexp
                           [(lit "(") (lit ")")] [])))

(check-equal? (map syntax->datum
                   (flatten-rule #'(rule sexp (choice (seq (seq (lit "(") (token BLAH))
                                                           (lit ")"))
                                                      (seq)))
                                 #:fresh-name (make-fresh-name)))
              '((prim-rule choice sexp
                           [(lit "(") (token BLAH) (lit ")")] [])))




;; maybe
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr (maybe (id rule-2)))))
              '((prim-rule maybe expr
                           [(id rule-2)]
                           [])))
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr (maybe (token HUH)))))
              '((prim-rule maybe expr
                           [(token HUH)]
                           [])))
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr (maybe (seq (lit "hello") (lit "world"))))))
              '((prim-rule maybe expr
                           [(lit "hello") (lit "world")]
                           [])))




;; repeat
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule rule-2+ (repeat 0 #f (id rule-2)))))
              '((prim-rule maybe rule-2+ ((inferred-id %rule1 repeat)) ())
                (inferred-prim-rule repeat %rule1
                                    ((inferred-id %rule1 repeat) (id rule-2))
                                    ((id rule-2)))))
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule rule-2+ (repeat 0 #f (seq (lit "+") (id rule-2))))))
              '((prim-rule maybe rule-2+ ((inferred-id %rule2 repeat)) ())
                (inferred-prim-rule repeat %rule2
                                    ((inferred-id %rule2 repeat) (lit "+") (id rule-2))
                                    ((lit "+") (id rule-2)))))

(check-equal? (map syntax->datum
                   (flatten-rule #'(rule rule-2+ (repeat 1 #f (id rule-2)))))
              '((prim-rule repeat rule-2+
                           [(inferred-id rule-2+ repeat) (id rule-2)]
                           [(id rule-2)])))
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule rule-2+ (repeat 1 #f (seq (lit "-") (id rule-2))))))
              '((prim-rule repeat rule-2+
                           [(inferred-id rule-2+ repeat) (lit "-") (id rule-2)]
                           [(lit "-") (id rule-2)])))






;; Mixtures

;; choice and maybe
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule sexp (choice (lit "x")
                                                      (maybe (lit "y"))))
                                 #:fresh-name (make-fresh-name)))
              '((prim-rule choice sexp
                           [(lit "x")]
                           [(inferred-id r1 maybe)])
                (inferred-prim-rule maybe r1
                                    [(lit "y")]
                                    [])))
;; choice, maybe, repeat
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule sexp (choice (lit "x")
                                                      (maybe (repeat 1 #f (lit "y")))))
                                 #:fresh-name (make-fresh-name)))
              '((prim-rule choice sexp
                           [(lit "x")]
                           [(inferred-id r1 maybe)])
                (inferred-prim-rule maybe r1
                                    [(inferred-id r2 repeat)]
                                    [])
                (inferred-prim-rule repeat r2
                                    [(inferred-id r2 repeat) (lit "y")]
                                    [(lit "y")])))
;; choice, seq
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule sexp (choice (seq (lit "x") (lit "y"))
                                                      (seq (lit "z") (lit "w"))))
                                 #:fresh-name (make-fresh-name)))
              '((prim-rule choice sexp
                           [(lit "x") (lit "y")]
                           [(lit "z") (lit "w")])))

;; maybe, choice
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule sexp (maybe (choice (seq (lit "x") (lit "y"))
                                                             (seq (lit "z") (lit "w")))))
                                 #:fresh-name (make-fresh-name)))
              '((prim-rule maybe sexp
                           [(inferred-id r1 choice)]
                           [])
                (inferred-prim-rule choice r1
                                    [(lit "x") (lit "y")]
                                    [(lit "z") (lit "w")])))


;; seq, repeat
(check-equal? (map syntax->datum
                   (flatten-rule #'(rule expr (seq (id term) (repeat 0 #f (seq (lit "+") (id term)))))
                                 #:fresh-name (make-fresh-name)))
              '((prim-rule seq expr ((id term) (inferred-id r1 repeat)))
                (prim-rule maybe r1 ((inferred-id r2 repeat)) ())
                (inferred-prim-rule repeat r2
                                    ((inferred-id r2 repeat) (lit "+") (id term))
                                    ((lit "+") (id term)))))


;; larger example: simple arithmetic
(check-equal? (map syntax->datum
                   (flatten-rules (syntax->list
                                   #'((rule expr (seq (id term) (repeat 0 #f (seq (lit "+") (id term)))))
                                      (rule term (seq (id factor) (repeat 0 #f (seq (lit "*") (id factor)))))
                                      (rule factor (token INT))))
                                  #:fresh-name (make-fresh-name)))
              
              '((prim-rule seq expr ((id term) (inferred-id r1 repeat)))
                (prim-rule maybe r1 ((inferred-id r2 repeat)) ())
                (inferred-prim-rule repeat r2
                 ((inferred-id r2 repeat) (lit "+") (id term))
                 ((lit "+") (id term)))
                (prim-rule seq term ((id factor) (inferred-id r3 repeat)))
                (prim-rule maybe r3 ((inferred-id r4 repeat)) ())
                (inferred-prim-rule repeat r4
                 ((inferred-id r4 repeat) (lit "*") (id factor))
                 ((lit "*") (id factor)))
                (prim-rule token factor ((token INT)))))
