#lang racket
(require
racket/date
(except-in srfi/13 string-replace)
"errors.rkt"
"nodes.rkt"
"utils.rkt"
"yaml.rkt")
(provide make-representer)
(define (make-representer serialize
#:scalar-style [default-style #f]
#:style [default-flow-style #f])
(define yaml-representers '())
(define represented-objects (make-hash))
(define object-keeper '())
(define alias-key #f)
(define (represent data)
(serialize (represent-data data))
(set! represented-objects (make-hash))
(set! object-keeper '())
(set! alias-key #f))
(define (represent-data data)
(call/cc
(λ (return)
(if (ignore-aliases? data)
(set! alias-key #f)
(set! alias-key (eq-hash-code data)))
(when alias-key
(when (hash-has-key? represented-objects alias-key)
(return (hash-ref represented-objects alias-key)))
(append! object-keeper (list data)))
(let loop ([kvs yaml-representers])
(if (null? kvs)
(scalar-node #f #f #f data #f)
(let ([type? (caar kvs)] [repr (cdar kvs)])
(if (type? data)
(repr data)
(loop (cdr kvs)))))))))
(define (represent-scalar tag value [style #f])
(unless style
(set! style default-style))
(let ([node (scalar-node #f #f tag value style)])
(when alias-key
(hash-set! represented-objects alias-key node))
node))
(define (represent-sequence tag sequence [flow-style #f])
(let ([value '()]
[best-style #t])
(for ([item sequence])
(let ([node-item (represent-data item)])
(when (not (and (scalar-node? node-item)
(not (scalar-node-style node-item))))
(set! best-style #f))
(append! value (list node-item))))
(unless flow-style
(if (not (eq? 'best default-flow-style))
(set! flow-style default-flow-style)
(set! flow-style best-style)))
(let ([node (sequence-node #f #f tag value flow-style)])
(when alias-key
(hash-set! represented-objects alias-key node))
node)))
(define (represent-mapping tag mapping [flow-style #f])
(let ([value '()]
[best-style #t])
(for ([(key val) mapping])
(let* ([node-key (represent-data key)]
[node-value (represent-data val)])
(when (not (and (scalar-node? node-key)
(not (scalar-node-style node-key))))
(set! best-style #f))
(when (not (and (scalar-node? node-value)
(not (scalar-node-style node-value))))
(set! best-style #f))
(append! value (list (cons node-key node-value)))))
(unless flow-style
(if (not (eq? 'best default-flow-style))
(set! flow-style default-flow-style)
(set! flow-style best-style)))
(let ([node (mapping-node #f #f tag value flow-style)])
(when alias-key
(hash-set! represented-objects alias-key node))
node)))
(define (ignore-aliases? data)
(or (equal? (yaml-null) data)
(boolean? data)
(string? data)
(number? data)))
(define (represent-null data)
(represent-scalar "tag:yaml.org,2002:null" "null"))
(define (represent-str data)
(represent-scalar "tag:yaml.org,2002:str" data))
(define (represent-bool data)
(represent-scalar "tag:yaml.org,2002:bool" (if data "true" "false")))
(define (represent-int data)
(represent-scalar "tag:yaml.org,2002:int" (number->string data)))
(define (represent-float data)
(represent-scalar
"tag:yaml.org,2002:float"
(cond
[(nan? data) ".nan"]
[(infinite? data)
(if (equal? data +inf.0) ".inf" "-.inf")]
[else
(let ([value (number->string data)])
(if (and (not (string-index value #\.))
(string-index value #\e))
(string-replace value "e" ".0e" #:all? #f)
value))])))
(define (represent-list data)
(represent-sequence "tag:yaml.org,2002:seq" data))
(define (represent-hash data)
(represent-mapping "tag:yaml.org,2002:map" data))
(define (represent-set data)
(let ([value (make-hash)])
(for ([key data])
(hash-set! value key (yaml-null)))
(represent-mapping "tag:yaml.org,2002:set" value)))
(define (represent-date data)
(parameterize ([date-display-format 'iso-8601])
(let* ([time? (or (not (zero? (date-second data)))
(not (zero? (date-minute data)))
(not (zero? (date-hour data))))]
[value (date->string data time?)])
(when (and (date*? data)
(not (zero? (date*-nanosecond data))))
(let ([microsecond (/ (date*-nanosecond data) 1000)])
(set! value (format "~a.~a" value microsecond))))
(represent-scalar "tag:yaml.org,2002:timestamp" value))))
(define (represent-pair data)
(let ([value (list (car data) (cdr data))])
(represent-sequence "tag:yaml.org,2002:pair" value)))
(define (represent-struct data)
(define-values (struct-type skipped?)
(struct-info data))
(define-values (name a b c d e f g)
(struct-type-info struct-type))
(represent-mapping
(format "tag:yaml.org,2002:struct:~a" name)
(make-hash (gen->yaml data))))
(define (add-representer! type? representer)
(append! yaml-representers (list (cons type? representer))))
(add-representer! (λ (x) (equal? x (yaml-null))) represent-null)
(add-representer! string? represent-str)
(add-representer! boolean? represent-bool)
(add-representer! integer? represent-int)
(add-representer! real? represent-float)
(add-representer! list? represent-list)
(add-representer! hash? represent-hash)
(add-representer! set? represent-set)
(add-representer! date? represent-date)
(add-representer! pair? represent-pair)
(add-representer! yaml-struct? represent-struct)
(values represent))