private/serializer.rkt
;;;;;; serializer.rkt - YAML serializer.    -*- Mode: Racket -*-

#lang racket

(require
 "emitter.rkt"
 "errors.rkt"
 "events.rkt"
 "nodes.rkt"
 "resolver.rkt"
 "utils.rkt")

(provide make-serializer)

(define ANCHOR-TEMPLATE "id~a")

(define (serializer-error msg) (error 'serializer msg))

(define (make-serializer [out (current-output-port)]
                         #:canonical [canonical #f]
                         #:indent [default-indent #f]
                         #:width [default-width #f]
                         #:allow-unicode [allow-unicode #f]
                         #:line-break [line-break #f]
                         #:explicit-start [explicit-start #f]
                         #:explicit-end [explicit-end #f]
                         #:version [version #f]
                         #:tags [tags #f])
  (define emit (make-emitter out
                             #:canonical canonical
                             #:indent default-indent
                             #:width default-width
                             #:allow-unicode allow-unicode
                             #:line-break line-break))
  (define serialized-nodes (make-hasheq))
  (define anchors (make-hasheq))
  (define last-anchor-id 0)
  (define closed 'None)

  (define (open)
    (cond
     [(eq? 'None closed)
      (emit (stream-start-event #f #f))
      (set! closed #f)]
     [(eq? #t closed)
      (serializer-error "serializer is closed")]
     [else
      (serializer-error "serializer is already opened")]))

  (define (close)
    (cond
     [(eq? 'None closed)
      (serializer-error "serializer is not opened")]
     [(eq? #f closed)
      (emit (stream-end-event #f #f))
      (set! closed #t)]))

  (define (serialize node)
    (cond
     [(eq? 'None closed)
      (serializer-error "serializer is not opened")]
     [(eq? #t closed)
      (serializer-error "serializer is closed")])
    (emit (document-start-event #f #f explicit-start version tags))
    (anchor-node node)
    (serialize-node node)
    (emit (document-end-event #f #f explicit-end))
    (set! serialized-nodes (make-hasheq))
    (set! anchors (make-hasheq))
    (set! last-anchor-id 0))

  (define (anchor-node node)
    (cond
     [(hash-has-key? anchors node)
      (unless (hash-ref anchors node)
        (hash-set! anchors node (generate-anchor node)))]
     [else
      (hash-set! anchors node #f)
      (cond
       [(sequence-node? node)
        (for ([item (sequence-node-value node)])
          (anchor-node item))]
       [(mapping-node? node)
        (for ([kv (mapping-node-value node)])
          (anchor-node (car kv))
          (anchor-node (cdr kv)))])]))

  (define (generate-anchor node)
    (set! last-anchor-id (add1 last-anchor-id))
    (let ([str (number->string last-anchor-id)])
      (while (< (string-length str) 3)
        (set! str (string-append "0" str)))
      (format ANCHOR-TEMPLATE str)))

  (define (serialize-node node)
    (let ([alias (hash-ref anchors node)])
      (cond
       [(hash-has-key? serialized-nodes node)
        (emit (alias-event #f #f alias))]
       [else
        (hash-set! serialized-nodes node #t)
        (cond
         [(scalar-node? node)
          (let* ([tag (scalar-node-tag node)]
                 [value (scalar-node-value node)]
                 [style (scalar-node-style node)]
                 [detected-tag
                  (resolve 'scalar value (cons #t #f))]
                 [default-tag
                   (resolve 'scalar value (cons #f #t))]
                 [implicit (cons (equal? tag detected-tag)
                                 (equal? tag default-tag))])
            (emit (scalar-event #f #f alias tag implicit value style)))]
         [(sequence-node? node)
          (let* ([tag (sequence-node-tag node)]
                 [value (sequence-node-value node)]
                 [implicit (equal? tag (resolve 'sequence value #t))]
                 [flow-style (sequence-node-flow-style node)])
            (emit (sequence-start-event #f #f alias tag implicit flow-style))
            (for ([item value])
              (serialize-node item))
            (emit (sequence-end-event #f #f)))]
         [(mapping-node? node)
          (let* ([tag (mapping-node-tag node)]
                 [value (mapping-node-value node)]
                 [implicit (equal? tag (resolve 'mapping value #t))]
                 [flow-style (mapping-node-flow-style node)])
            (emit
             (mapping-start-event #f #f alias tag implicit flow-style))
            (for ([kv value])
              (serialize-node (car kv))
              (serialize-node (cdr kv)))
            (emit (mapping-end-event #f #f)))])])))

  (values open close serialize))