You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
typesetting/quad/quad/atomize.rkt

137 lines
6.2 KiB
Racket

#lang debug racket/base
5 years ago
(require racket/string racket/hash racket/class racket/match racket/list txexpr racket/dict racket/function
5 years ago
"quad.rkt" "param.rkt")
8 years ago
(provide (all-defined-out))
(module+ test (require rackunit))
8 years ago
6 years ago
(define (update-with base-hash . update-hashes)
;; starting with base-hash, add or update keys found in update-hashes
6 years ago
(define h (make-hasheq))
5 years ago
(apply hash-union! #:combine (λ (v1 v2) v2) h base-hash update-hashes)
6 years ago
h)
6 years ago
(module+ test
(check-equal?
((hasheq 'foo "bar" 'zim "zam") . update-with . (hasheq 'zim "BANG") (hasheq 'toe "jam") (hasheq 'foo "zay"))
6 years ago
(make-hasheq '((zim . "BANG") (foo . "zay") (toe . "jam")))))
6 years ago
(define (merge-whitespace qs [white-q? (λ (aq) (char-whitespace? (car (quad-elems aq))))])
;; collapse each sequence of whitespace qs to the first one, and make it a space
;; also drop leading & trailing whitespaces
;; (same behavior as web browsers)
(let loop ([acc null][qs qs])
(if (null? qs)
6 years ago
(flatten acc)
(let*-values ([(bs rest) (splitf-at qs (negate white-q?))]
[(ws rest) (splitf-at rest white-q?)])
6 years ago
(loop (list acc bs (if (and (pair? rest) ;; we precede bs (only #t if rest starts with bs, because we took the ws)
(pair? bs) ;; we follow bs
(pair? ws)) ;; we have ws
5 years ago
(make-quad (quad-attrs (car ws)) #\space)
6 years ago
null)) rest)))))
(module+ test
5 years ago
(check-equal? (merge-whitespace (list (q #\space) (q #\newline) (q #\H) (q #\space) (q #\newline) (q #\space) (q #\i) (q #\newline)))
(list (q #\H) (q #\space) (q #\i))))
5 years ago
(define (atomize qx)
6 years ago
;; normalize a quad by reducing it to one-character quads.
;; propagate attrs downward.
(define atomic-quads
5 years ago
(let loop ([x (if (string? qx) (q #f qx) qx)][attrs (current-default-attrs)])
(match x
5 years ago
[(? char? c) (list (q attrs c))]
[(? string?) (append* (for/list ([c (in-string x)]) ;; strings are exploded
(loop c attrs)))]
5 years ago
[(? quad?) ;; qexprs with attributes are recursed
(define this-attrs (quad-attrs x))
(define elems (quad-elems x))
(define merged-attrs (attrs . update-with . this-attrs))
(append* (for/list ([elem (in-list elems)])
(loop elem merged-attrs)))]
[else (raise-argument-error 'atomize "valid item" x)])))
(merge-whitespace atomic-quads))
8 years ago
(module+ test
(require rackunit)
5 years ago
(check-equal? (atomize (q "Hi")) (list (q #\H) (q #\i)))
(check-equal? (atomize (q "Hi " (q "You"))) (list (q #\H) (q #\i) (q #\space) (q #\Y) (q #\o) (q #\u)))
(check-exn exn:fail:contract? (λ () (atomize #t)))
5 years ago
(check-equal? (atomize (q "H i")) (list (q #\H) (q #\space) (q #\i)))
(check-equal? (atomize (q "H \n\n i")) (list (q #\H) (q #\space) (q #\i))) ;; collapse whitespace to single
6 years ago
;; with attributes
5 years ago
(check-equal? (atomize (q (hasheq 'k "v") "Hi")) (list (q (hasheq 'k "v") #\H) (q (hasheq 'k "v") #\i)))
(check-equal? (atomize (q (hasheq 'k "v") "Hi " (q "You")))
(list
5 years ago
(q (hasheq 'k "v") #\H)
(q (hasheq 'k "v") #\i)
(q (hasheq 'k "v") #\space)
(q (hasheq 'k "v") #\Y)
(q (hasheq 'k "v") #\o)
(q (hasheq 'k "v") #\u)))
(check-equal? (atomize (q (hasheq 'k1 "v1" 'k2 42) "Hi \n\n" (q (hasheq 'k1 "v2" 'k3 "foo") "\n \nYou")))
(list
5 years ago
(q (hasheq 'k1 "v1" 'k2 42) #\H)
(q (hasheq 'k1 "v1" 'k2 42) #\i)
(q (hasheq 'k1 "v1" 'k2 42) #\space)
(q (hasheq 'k1 "v2" 'k2 42 'k3 "foo") #\Y)
(q (hasheq 'k1 "v2" 'k2 42 'k3 "foo") #\o)
(q (hasheq 'k1 "v2" 'k2 42 'k3 "foo") #\u))))
6 years ago
(define whitespace-pat #px"\\s+")
(define (merge-and-isolate-white str)
(for/list ([(m idx) (in-indexed (regexp-match* whitespace-pat str #:gap-select? #t))]
#:when (non-empty-string? m))
(if (even? idx) m " ")))
6 years ago
6 years ago
(define (merge-adjacent-strings xs [isolate-white? #false])
(let loop ([xs xs][acc null])
(match xs
5 years ago
[(== empty) (reverse acc)]
6 years ago
[(list (? string? strs) ..1 others ...)
(loop others (append (reverse ((if isolate-white?
merge-and-isolate-white
6 years ago
list) (apply string-append strs))) acc))]
6 years ago
[(cons x others) (loop others (cons x acc))])))
6 years ago
(define run-key 'run)
(define (same-run? qa qb)
5 years ago
(eq? (hash-ref (quad-attrs qa) run-key) (hash-ref (quad-attrs qb) run-key)))
6 years ago
6 years ago
(define (runify qx)
;; runify a quad by reducing it to a series of "runs",
;; which are multi-character quads with the same formatting.
5 years ago
(define first-run-idx (eq-hash-code (current-default-attrs)))
6 years ago
(define first-attrs (hash-copy (current-default-attrs)))
5 years ago
(hash-set! first-attrs run-key first-run-idx)
6 years ago
(dropf
5 years ago
(let loop ([x (if (string? qx) (make-quad #f (list qx)) qx)]
6 years ago
[attrs first-attrs]
5 years ago
[key first-run-idx])
6 years ago
(match x
5 years ago
[(? quad?) ;; qexprs with attributes are recursed
(define this-attrs (quad-attrs x))
(define elems (quad-elems x))
5 years ago
(define next-key (if (hash-empty? this-attrs) key (eq-hash-code this-attrs)))
6 years ago
(define next-attrs (if (hash-empty? this-attrs) attrs (attrs . update-with . this-attrs)))
(unless (hash-empty? this-attrs) (hash-set! next-attrs run-key next-key))
6 years ago
(append* (for/list ([elem (in-list (merge-adjacent-strings elems 'merge-white))])
5 years ago
(if (string? elem)
5 years ago
(list (make-quad next-attrs elem))
5 years ago
(loop elem next-attrs next-key))))]))
(λ (q) (string=? " " (car (quad-elems q))))))
6 years ago
5 years ago
#;(module+ test
5 years ago
;; this test doesn't work because of presence of 'idx and 'run keys
(check-equal?
5 years ago
(runify (q (hasheq 'foo 42) (q "Hi" " idiot" (q (hasheq 'bar 84) "There") "Eve" "ry" "one")))
(list (q (hasheq 'foo 42) "Hi")
(q (hasheq 'foo 42) " ")
(q (hasheq 'foo 42) "idiot")
(q (hasheq 'foo 42 'bar 84) "There")
(q (hasheq 'foo 42) "Everyone"))))
6 years ago