#lang scheme
(require (planet dherman/struct:2/datatype)
scheme/contract
(except-in scheme/list empty flatten))
(define (write-doc v port write?)
(fprintf port "#<struct:doc>"))
(define-datatype (doc ([prop:custom-write write-doc]))
[NIL ()]
[CAT (left right)]
[NEST (depth doc)]
[LABEL (label doc)]
[MARKUP (f doc)]
[TEXT (text)]
[LINE (break?)]
[GROUP (doc)]
[COLUMN (f)]
[NESTING (f)])
(define (doc->string doc)
(match doc
[(struct NIL ()) "NIL"]
[(struct CAT (x y)) (format "(CAT ~a ~a)" (doc->string x) (doc->string y))]
[(struct NEST (n x)) (format "(NEST ~a ~a)" n (doc->string x))]
[(struct LABEL (l x)) (format "(LABEL ~v ~a)" l (doc->string x))]
[(struct MARKUP (f x)) (format "(MARKUP ~a ~a)" f (doc->string x))]
[(struct LINE (break?)) (format "(LINE ~a)" break?)]
[(struct GROUP (x)) (format "(GROUP ~a)" (doc->string x))]
[(struct TEXT (t)) (format "~v" t)]
[(struct COLUMN (f)) (format "(COLUMN ~a)" f)]
[(struct NESTING (f)) (format "(NESTING ~a)" f)]))
(define-datatype simple-doc
[SEMPTY ()]
[STEXT (text rest)]
[SPUSH (f rest)]
[SPOP (rest)]
[SLINE (is rest)])
(define empty (make-NIL))
(define (nest i x) (make-NEST i x))
(define (text s) (make-TEXT s))
(define (label l d) (make-LABEL l d))
(define (markup f d) (make-MARKUP f d))
(define (column f) (make-COLUMN f))
(define (nesting f) (make-NESTING f))
(define (group x) (make-GROUP x))
(define (char c) (if (char=? c #\newline) line (text (string c))))
(define line (make-LINE #f))
(define break (make-LINE #t))
(define soft-line (group line))
(define soft-break (group break))
(define (fill/break f x)
(width x (lambda (w)
(if (> w f)
(nest f break)
(text (spaces (- f w)))))))
(define (fill f d)
(width d (lambda (w)
(if (>= w f)
empty
(text (spaces (- f w)))))))
(define (width d f)
(column (lambda (k1)
(h-append d (column (lambda (k2)
(f (- k2 k1))))))))
(define (indent i d)
(hang i (h-append (text (spaces i)) d)))
(define (hang i d)
(align (nest i d)))
(define (align d)
(column (lambda (k)
(nesting (lambda (i)
(nest (- k i) d))))))
(define comma (char #\,))
(define semi (char #\;))
(define colon (char #\:))
(define lparen (char #\())
(define rparen (char #\)))
(define lbracket (char #\[))
(define rbracket (char #\]))
(define langle (char #\<))
(define rangle (char #\>))
(define lbrace (char #\{))
(define rbrace (char #\}))
(define space (char #\space))
(define ellipsis (text "..."))
(define squote (char #\'))
(define dquote (char #\"))
(define dot (char #\.))
(define backslash (char #\\))
(define equals (char #\=))
(define (foldr1 f xs)
(match xs
[(list x) x]
[(list x xs ...) (f x (foldr1 f xs))]))
(define (fold f ds)
(if (null? ds)
empty
(foldr1 f ds)))
(define (cat-with sep)
(letrec ([f (match-lambda
[(list) empty]
[(list x) x]
[(list x y) (h-append x sep y)]
[(list d ds ...) (h-append d sep (f ds))])])
(lambda ds
(f ds))))
(define h-append
(letrec ([f (match-lambda
[(list) empty]
[(list x) x]
[(list x y) (make-CAT x y)]
[(list d ds ...) (make-CAT d (f ds))])])
(lambda ds
(f ds))))
(define hs-append (cat-with space))
(define v-append (cat-with line))
(define vs-append (cat-with soft-line))
(define vb-append (cat-with break))
(define vsb-append (cat-with soft-break))
(define hs-concat (lambda (ds) (fold hs-append ds)))
(define v-concat (lambda (ds) (fold v-append ds)))
(define vs-concat (lambda (ds) (fold vs-append ds)))
(define v-concat/s (compose group v-concat))
(define h-concat (lambda (ds) (fold h-append ds)))
(define vb-concat (lambda (ds) (fold vb-append ds)))
(define vsb-concat (lambda (ds) (fold vsb-append ds)))
(define vb-concat/s (compose group vb-concat))
(define (next-newline s i)
(if (or (>= i (string-length s))
(char=? (string-ref s i) #\newline))
i
(next-newline s (add1 i))))
(define (split-newlines s)
(let ([len (string-length s)])
(let f ([start 0])
(cond
[(>= start len)
null]
[(char=? (string-ref s start) #\newline)
(cons "\n" (f (add1 start)))]
[else (let ([end (next-newline s start)])
(if (= end len)
(list (substring s start))
(cons (substring s start end)
(cons "\n" (f (add1 end))))))]))))
(define (string->doc s)
(foldr h-append
empty
(map (lambda (s)
(if (string=? "\n" s) line (text s)))
(split-newlines s))))
(define (value->doc x)
(string->doc (format "~a" x)))
(define (apply-infix p ds)
(match ds
[(list) null]
[(list d) (list d)]
[(list d ds ...) (cons (h-append d p) (apply-infix p ds))]))
(define (spaces n)
(build-string n (lambda (i) #\space)))
(define (extend s n)
(string-append s (spaces n)))
(define (flatten doc)
(match doc
[(struct CAT (x y)) (make-CAT (flatten x) (flatten y))]
[(struct NEST (n x)) (flatten x)]
[(struct LABEL (l x)) (flatten x)]
[(struct MARKUP (f x)) (make-MARKUP f (flatten x))]
[(struct LINE (#t)) (make-NIL)]
[(struct LINE (#f)) (make-TEXT " ")]
[(struct GROUP (x)) (flatten x)]
[(struct COLUMN (f)) (make-COLUMN (compose flatten f))]
[(struct NESTING (f)) (make-NESTING (compose flatten f))]
[_ doc]))
(define-values (backtrack! backtrack?)
(let ()
(define-struct backtrack ())
(values (make-backtrack) backtrack?)))
(define (too-big? text col width)
(> (+ col (string-length text)) width))
(define (layout width doc)
(let best ([col 0] [docs (list (cons "" doc))] [alternate? #f])
(match docs
[(list) (make-SEMPTY)]
[(list #f docs* ...)
(make-SPOP (best col docs* alternate?))]
[(list (cons is (struct NIL ())) docs* ...)
(best col docs* alternate?)]
[(list (cons is (struct CAT (x y))) docs* ...)
(best col (cons (cons is x)
(cons (cons is y) docs*)) alternate?)]
[(list (cons is (struct NEST (n x))) docs* ...)
(best col (cons (cons (extend is n) x) docs*) alternate?)]
[(list (cons is (struct LABEL (l x))) docs* ...)
(best col (cons (cons (string-append is l) x) docs*) alternate?)]
[(list (cons is (struct MARKUP (f x))) docs* ...)
(make-SPUSH f (best col (cons (cons is x) (cons #f docs*)) alternate?))]
[(list (cons is (struct LINE (_))) docs* ...)
(make-SLINE is (best (string-length is) docs* alternate?))]
[(list (cons is (struct GROUP (x))) docs* ...)
(with-handlers ([backtrack? (lambda (exn)
(best col (cons (cons is x) docs*) alternate?))])
(best col (cons (cons is (flatten x)) docs*) #t))]
[(list (cons is (struct TEXT (t))) docs* ...)
(if (and alternate? (too-big? t col width))
(raise backtrack!)
(make-STEXT t (best (+ col (string-length t)) docs* alternate?)))]
[(list (cons is (struct COLUMN (f))) docs* ...)
(best col (cons (cons is (f col)) docs*) alternate?)]
[(list (cons is (struct NESTING (f))) docs* ...)
(best col (cons (cons is (f (string-length is))) docs*) alternate?)])))
(define current-page-width (make-parameter 80))
(define (pretty-print doc [port (current-output-port)] [width (current-page-width)])
(let print ([sdoc (layout width doc)])
(match sdoc
[(struct SEMPTY ()) (void)]
[(struct STEXT (t rest))
(display t port)
(print rest)]
[(struct SPUSH (f rest))
(print rest)]
[(struct SPOP (rest))
(print rest)]
[(struct SLINE (is rest))
(newline port)
(display is port)
(print rest)])))
(define (pretty-format doc [width (current-page-width)])
(let ([out (open-output-string)])
(pretty-print doc out width)
(get-output-string out)))
(define (pretty-markup doc combine [width (current-page-width)])
(car (let markup ([sdoc (layout width doc)])
(match sdoc
[(struct SEMPTY ()) (list "")]
[(struct STEXT (t rest))
(let ([r (markup rest)])
(cons (combine t (car r))
(cdr r)))]
[(struct SPUSH (f rest))
(let ([r (markup rest)])
(cons (combine (f (car r)) (cadr r))
(cddr r)))]
[(struct SPOP (rest))
(cons "" (markup rest))]
[(struct SLINE (is rest))
(let ([r (markup rest)])
(cons (combine (string-append "\n" is)
(car r))
(cdr r)))]))))
(provide/contract [pretty-print ((doc?) (output-port? natural-number/c) . ->* . any)]
[pretty-format ((doc?) (natural-number/c) . ->* . string?)]
[pretty-markup ((doc? (any/c any/c . -> . any)) (natural-number/c) . ->* . any)]
[current-page-width parameter?])
(provide/contract [doc? (any/c . -> . boolean?)]
[string->doc (string? . -> . doc?)]
[value->doc (any/c . -> . doc?)])
(provide/contract [empty doc?]
[char (char? . -> . doc?)]
[text (string? . -> . doc?)]
[nest (natural-number/c doc? . -> . doc?)]
[label (string? doc? . -> . doc?)]
[markup (procedure? doc? . -> . doc?)]
[group (doc? . -> . doc?)]
[line doc?]
[break doc?]
[soft-line doc?]
[soft-break doc?])
(provide/contract [align (doc? . -> . doc?)]
[hang (natural-number/c doc? . -> . doc?)]
[indent (natural-number/c doc? . -> . doc?)])
(provide/contract [h-append (() () #:rest (listof doc?) . ->* . doc?)]
[hs-append (() () #:rest (listof doc?) . ->* . doc?)]
[vs-append (() () #:rest (listof doc?) . ->* . doc?)]
[vsb-append (() () #:rest (listof doc?) . ->* . doc?)]
[v-append (() () #:rest (listof doc?) . ->* . doc?)]
[vb-append (() () #:rest (listof doc?) . ->* . doc?)])
(provide/contract [v-concat/s ((listof doc?) . -> . doc?)]
[vs-concat ((listof doc?) . -> . doc?)]
[hs-concat ((listof doc?) . -> . doc?)]
[v-concat ((listof doc?) . -> . doc?)])
(provide/contract [vb-concat/s ((listof doc?) . -> . doc?)]
[vsb-concat ((listof doc?) . -> . doc?)]
[h-concat ((listof doc?) . -> . doc?)]
[vb-concat ((listof doc?) . -> . doc?)])
(provide/contract [apply-infix (doc? (listof doc?) . -> . (listof doc?))])
(provide/contract [fill (natural-number/c doc? . -> . doc?)]
[fill/break (natural-number/c doc? . -> . doc?)])
(provide/contract [lparen doc?]
[rparen doc?]
[lbrace doc?]
[rbrace doc?]
[lbracket doc?]
[rbracket doc?]
[langle doc?]
[rangle doc?]
[squote doc?]
[dquote doc?]
[semi doc?]
[colon doc?]
[comma doc?]
[space doc?]
[dot doc?]
[backslash doc?]
[equals doc?]
[ellipsis doc?])