#lang scheme/base (require "private/provide-structs.rkt" scheme/serialize racket/contract/base file/convertible) ;; ---------------------------------------- (define-struct collect-info (fp ht ext-ht ext-demand parts tags gen-prefix relatives parents) #:transparent) (define-struct resolve-info (ci delays undef searches) #:transparent) (define (part-collected-info part ri) (hash-ref (collect-info-parts (resolve-info-ci ri)) part)) (define (collect-put! ci key val) (let ([ht (collect-info-ht ci)]) (let ([old-val (hash-ref ht key #f)]) (when old-val (eprintf "WARNING: collected information for key multiple times: ~e; values: ~e ~e\n" key old-val val)) (hash-set! ht key val)))) (define (resolve-get/where part ri key) (let ([key (tag-key key ri)]) (let ([v (hash-ref (if part (collected-info-info (part-collected-info part ri)) (collect-info-ht (resolve-info-ci ri))) key #f)]) (cond [v (values v #f)] [part (resolve-get/where (collected-info-parent (part-collected-info part ri)) ri key)] [else (define ci (resolve-info-ci ri)) (define (try-ext) (hash-ref (collect-info-ext-ht ci) key #f)) (define v (or (try-ext) (and ((collect-info-ext-demand ci) key ci) (try-ext)))) (if (known-doc? v) (values (known-doc-v v) (known-doc-id v)) (values v #t))])))) (define (resolve-get/ext? part ri key) (define-values (v ext-id) (resolve-get/ext-id* part ri key #f)) (values v (and ext-id #t))) (define (resolve-get/ext-id part ri key) (resolve-get/ext-id* part ri key #f)) (define (resolve-get/ext-id* part ri key search-key) (let-values ([(v ext-id) (resolve-get/where part ri key)]) (when ext-id (hash-set! (resolve-info-undef ri) (tag-key key ri) (if v 'found search-key))) (values v ext-id))) (define (resolve-get part ri key) (resolve-get* part ri key #f)) (define (resolve-get* part ri key search-key) (let-values ([(v ext-id) (resolve-get/ext-id* part ri key search-key)]) v)) (define (resolve-get/tentative part ri key) (let-values ([(v ext-id) (resolve-get/where part ri key)]) v)) (define (resolve-search search-key part ri key) (let ([s-ht (hash-ref (resolve-info-searches ri) search-key (lambda () (let ([s-ht (make-hash)]) (hash-set! (resolve-info-searches ri) search-key s-ht) s-ht)))]) (hash-set! s-ht key #t)) (resolve-get* part ri key search-key)) (define (resolve-get-keys part ri key-pred) (for/list ([k (in-hash-keys (if part (collected-info-info (part-collected-info part ri)) (let ([ci (resolve-info-ci ri)]) ;; Force all xref info: ((collect-info-ext-demand ci) #f ci) (collect-info-ext-ht ci))))] #:when (key-pred k)) k)) (provide (struct-out collect-info) (struct-out resolve-info)) ;; ---------------------------------------- (provide tag?) (define (tag? s) (and (pair? s) (symbol? (car s)) (pair? (cdr s)) (or (string? (cadr s)) (generated-tag? (cadr s)) (and (pair? (cadr s)) (list? (cadr s)) (serializable? (cadr s)))) (null? (cddr s)))) (provide block?) (define (block? p) (or (paragraph? p) (table? p) (itemization? p) (nested-flow? p) (compound-paragraph? p) (delayed-block? p) (traverse-block? p))) (define content-symbols #hasheq([nbsp . #t] [mdash . #t] [ndash . #t] [ldquo . #t] [rdquo . #t] [rsquo . #t] [lsquo . #t] [prime . #t] [rarr . #t] [larr . #t] [alpha . #t] [infin . #t] [lang . #t] [rang . #t])) (provide content?) (define (content? v) (or (string? v) (element? v) (and (list? v) (andmap content? v)) (delayed-element? v) (traverse-element? v) (part-relative-element? v) (multiarg-element? v) (hash-ref content-symbols v #f) (convertible? v))) (provide element-style?) (define (element-style? s) (or (style? s) (not s) (string? s) (symbol? s))) (define (string-without-newline? s) (and (string? s) (not (regexp-match? #rx"\n" s)))) (define (same-lengths? ls) (or (null? ls) (let ([l1 (length (car ls))]) (andmap (λ (l) (= l1 (length l))) (cdr ls))))) ;; ---------------------------------------- (define-struct numberer (tag step-proc initial-value) #:constructor-name numberer #:property prop:serializable (make-serialize-info (lambda (d) (vector (numberer-tag d) (numberer-initial-value d))) #'deserialize-numberer #f (or (current-load-relative-directory) (current-directory)))) (provide deserialize-numberer) (define deserialize-numberer (make-deserialize-info (lambda (tag init-val) (numberer tag #f)) (lambda (tag init-val) (error "cannot allocate numberer for cycle")))) (define (make-numberer spec-proc initial-value) (numberer (generated-tag) spec-proc initial-value)) (define (numberer-step n parent-numbers ci ht) (define tag (generate-tag `(numberer ,(numberer-tag n)) ci)) (define-values (numberer-str new-val) (let ([step (numberer-step-proc n)]) (step (hash-ref ht tag (lambda () (numberer-initial-value n))) parent-numbers))) (values numberer-str (hash-set ht tag new-val))) (define part-number-item? (or/c #f exact-nonnegative-integer? string? (list/c string? string?))) (provide part-number-item? numberer? (contract-out [make-numberer ((any/c (listof part-number-item?) . -> . (values part-number-item? any/c)) any/c . -> . numberer?)] [numberer-step (numberer? (listof part-number-item?) collect-info? hash? . -> . (values part-number-item? hash?))])) ;; ---------------------------------------- (provide-structs [part ([tag-prefix (or/c false/c string?)] [tags (listof tag?)] [title-content (or/c false/c content?)] [style style?] [to-collect list?] [blocks (listof block?)] [parts (listof part?)])] [paragraph ([style style?] [content content?])] [table ([style style?] [blockss (and/c (listof (listof (or/c block? (one-of/c 'cont)))) same-lengths?)])] [delayed-block ([resolve (any/c part? resolve-info? . -> . block?)])] [itemization ([style style?] [blockss (listof (listof block?))])] [nested-flow ([style style?] [blocks (listof block?)])] [compound-paragraph ([style style?] [blocks (listof block?)])] [element ([style element-style?] [content content?])] [(toc-element element) ([toc-content content?])] [(target-element element) ([tag tag?])] [(toc-target-element target-element) ()] [(toc-target2-element toc-target-element) ([toc-content content?])] [(page-target-element target-element) ()] [(redirect-target-element target-element) ([alt-path path-string?] [alt-anchor string?])] [(link-element element) ([tag tag?])] [(index-element element) ([tag tag?] [plain-seq (and/c pair? (listof string-without-newline?))] [entry-seq (listof content?)] [desc any/c])] [(image-element element) ([path (or/c path-string? (cons/c (one-of/c 'collects) (listof bytes?)))] [suffixes (listof #rx"^[.]")] [scale real?])] [multiarg-element ([style element-style?] [contents (listof content?)])] [style ([name (or/c string? symbol? #f)] [properties list?])] ;; properties: [document-version ([text (or/c string? false/c)])] [document-date ([text (or/c string? false/c)])] [target-url ([addr path-string?])] [color-property ([color (or/c string? (list/c byte? byte? byte?))])] [background-color-property ([color (or/c string? (list/c byte? byte? byte?))])] [numberer-property ([numberer numberer?] [argument any/c])] [table-columns ([styles (listof style?)])] [table-cells ([styless (listof (listof style?))])] [box-mode ([top-name string?] [center-name string?] [bottom-name string?])] [collected-info ([number (listof part-number-item?)] [parent (or/c false/c part?)] [info any/c])] [known-doc ([v any/c] [id string?])]) (provide plain) (define plain (make-style #f null)) (define (box-mode* name) (box-mode name name name)) (provide/contract [box-mode* (string? . -> . box-mode?)]) ;; ---------------------------------------- ;; Traverse block has special serialization support: (define-struct traverse-block (traverse) #:property prop:serializable (make-serialize-info (lambda (d) (let ([ri (current-serialize-resolve-info)]) (unless ri (error 'serialize-traverse-block "current-serialize-resolve-info not set")) (vector (traverse-block-block d ri)))) #'deserialize-traverse-block #f (or (current-load-relative-directory) (current-directory))) #:transparent) (define block-traverse-procedure/c (recursive-contract ((symbol? any/c . -> . any/c) (symbol? any/c . -> . any) . -> . (or/c block-traverse-procedure/c block?)))) (provide block-traverse-procedure/c) (provide/contract (struct traverse-block ([traverse block-traverse-procedure/c]))) (provide deserialize-traverse-block) (define deserialize-traverse-block (make-deserialize-info values values)) (define (traverse-block-block b i) (cond [(collect-info? i) (let ([p (hash-ref (collect-info-fp i) b #f)]) (if (block? p) p (error 'traverse-block-block "no block computed for traverse-block: ~e" b)))] [(resolve-info? i) (traverse-block-block b (resolve-info-ci i))])) (provide/contract [traverse-block-block (traverse-block? (or/c resolve-info? collect-info?) . -> . block?)]) ;; ---------------------------------------- ;; Traverse element has special serialization support: (define-struct traverse-element (traverse) #:property prop:serializable (make-serialize-info (lambda (d) (let ([ri (current-serialize-resolve-info)]) (unless ri (error 'serialize-traverse-block "current-serialize-resolve-info not set")) (vector (traverse-element-content d ri)))) #'deserialize-traverse-element #f (or (current-load-relative-directory) (current-directory))) #:transparent) (define element-traverse-procedure/c (recursive-contract ((symbol? any/c . -> . any/c) (symbol? any/c . -> . any) . -> . (or/c element-traverse-procedure/c content?)))) (provide/contract (struct traverse-element ([traverse element-traverse-procedure/c]))) (provide deserialize-traverse-element) (define deserialize-traverse-element (make-deserialize-info values values)) (define (traverse-element-content e i) (cond [(collect-info? i) (let ([c (hash-ref (collect-info-fp i) e #f)]) (if (content? c) c (error 'traverse-block-block "no block computed for traverse-block: ~e" e)))] [(resolve-info? i) (traverse-element-content e (resolve-info-ci i))])) (provide element-traverse-procedure/c) (provide/contract [traverse-element-content (traverse-element? (or/c resolve-info? collect-info?) . -> . content?)]) ;; ---------------------------------------- ;; Delayed element has special serialization support: (define-struct delayed-element (resolve sizer plain) #:property prop:serializable (make-serialize-info (lambda (d) (let ([ri (current-serialize-resolve-info)]) (unless ri (error 'serialize-delayed-element "current-serialize-resolve-info not set")) (with-handlers ([exn:fail:contract? (lambda (exn) (error 'serialize-delayed-element "serialization failed (wrong resolve info? delayed element never rendered?); ~a" (exn-message exn)))]) (vector (delayed-element-content d ri))))) #'deserialize-delayed-element #f (or (current-load-relative-directory) (current-directory))) #:transparent) (provide/contract (struct delayed-element ([resolve (any/c part? resolve-info? . -> . content?)] [sizer (-> any)] [plain (-> any)]))) (module+ deserialize-info (provide deserialize-delayed-element)) (define deserialize-delayed-element (make-deserialize-info values values)) (provide delayed-element-content) (define (delayed-element-content e ri) (hash-ref (resolve-info-delays ri) e)) (provide delayed-block-blocks) (define (delayed-block-blocks p ri) (hash-ref (resolve-info-delays ri) p)) (provide current-serialize-resolve-info) (define current-serialize-resolve-info (make-parameter #f)) ;; ---------------------------------------- ;; part-relative element has special serialization support: (define-struct part-relative-element (collect sizer plain) #:property prop:serializable (make-serialize-info (lambda (d) (let ([ri (current-serialize-resolve-info)]) (unless ri (error 'serialize-part-relative-element "current-serialize-resolve-info not set")) (with-handlers ([exn:fail:contract? (lambda (exn) (error 'serialize-part-relative-element "serialization failed (wrong resolve info? part-relative element never rendered?); ~a" (exn-message exn)))]) (vector (part-relative-element-content d ri))))) #'deserialize-part-relative-element #f (or (current-load-relative-directory) (current-directory))) #:transparent) (provide/contract (struct part-relative-element ([collect (collect-info? . -> . content?)] [sizer (-> any)] [plain (-> any)]))) (module+ deserialize-info (provide deserialize-part-relative-element)) (define deserialize-part-relative-element (make-deserialize-info values values)) (provide part-relative-element-content) (define (part-relative-element-content e ci/ri) (hash-ref (collect-info-relatives (if (resolve-info? ci/ri) (resolve-info-ci ci/ri) ci/ri)) e)) (provide collect-info-parents) ;; ---------------------------------------- ;; Delayed index entry also has special serialization support. ;; It uses the same delay -> value table as delayed-element (define-struct delayed-index-desc (resolve) #:mutable #:property prop:serializable (make-serialize-info (lambda (d) (let ([ri (current-serialize-resolve-info)]) (unless ri (error 'serialize-delayed-index-desc "current-serialize-resolve-info not set")) (with-handlers ([exn:fail:contract? (lambda (exn) (error 'serialize-index-desc "serialization failed (wrong resolve info?); ~a" (exn-message exn)))]) (vector (delayed-element-content d ri))))) #'deserialize-delayed-index-desc #f (or (current-load-relative-directory) (current-directory))) #:transparent) (provide/contract (struct delayed-index-desc ([resolve (any/c part? resolve-info? . -> . any)]))) (module+ deserialize-info (provide deserialize-delayed-index-desc)) (define deserialize-delayed-index-desc (make-deserialize-info values values)) ;; ---------------------------------------- (define-struct (collect-element element) (collect) #:mutable #:property prop:serializable (make-serialize-info (lambda (d) (vector (make-element (element-style d) (element-content d)))) #'deserialize-collect-element #f (or (current-load-relative-directory) (current-directory))) #:transparent) (module+ deserialize-info (provide deserialize-collect-element)) (define deserialize-collect-element (make-deserialize-info values values)) (provide/contract [struct collect-element ([style element-style?] [content content?] [collect (collect-info? . -> . any)])]) ;; ---------------------------------------- (define-struct (render-element element) (render) #:property prop:serializable (make-serialize-info (lambda (d) (vector (make-element (element-style d) (element-content d)))) #'deserialize-render-element #f (or (current-load-relative-directory) (current-directory))) #:transparent) (module+ deserialize-info (provide deserialize-render-element)) (define deserialize-render-element (make-deserialize-info values values)) (provide/contract [struct render-element ([style element-style?] [content content?] [render (any/c part? resolve-info? . -> . any)])]) ;; ---------------------------------------- (define-struct generated-tag () #:property prop:serializable (make-serialize-info (lambda (g) (let ([ri (current-serialize-resolve-info)]) (unless ri (error 'serialize-generated-tag "current-serialize-resolve-info not set")) (let ([t (hash-ref (collect-info-tags (resolve-info-ci ri)) g #f)]) (if t (vector t) (error 'serialize-generated-tag "serialization failed (wrong resolve info?)"))))) #'deserialize-generated-tag #f (or (current-load-relative-directory) (current-directory))) #:transparent) (provide (struct-out generated-tag)) (module+ deserialize-info (provide deserialize-generated-tag)) (define deserialize-generated-tag (make-deserialize-info values values)) (provide generate-tag tag-key current-tag-prefixes add-current-tag-prefix) (define (generate-tag tg ci) (if (generated-tag? (cadr tg)) (let ([t (cadr tg)]) (list (car tg) (let ([tags (collect-info-tags ci)]) (or (hash-ref tags t #f) (let ([key (list* 'gentag (hash-count tags) (collect-info-gen-prefix ci))]) (hash-set! tags t key) key))))) tg)) (define (tag-key tg ri) (if (generated-tag? (cadr tg)) (list (car tg) (hash-ref (collect-info-tags (resolve-info-ci ri)) (cadr tg))) tg)) (define current-tag-prefixes (make-parameter null)) (define (add-current-tag-prefix t) (let ([l (current-tag-prefixes)]) (if (null? l) t (cons (car t) (append l (cdr t)))))) ;; ---------------------------------------- (provide content->string strip-aux) ;; content->port: output-port content -> void ;; Writes the string content of content into op. (define content->port (case-lambda [(op c) (cond [(element? c) (content->port op (element-content c))] [(multiarg-element? c) (content->port op (multiarg-element-contents c))] [(list? c) (for-each (lambda (e) (content->port op e)) c)] [(part-relative-element? c) (content->port op ((part-relative-element-plain c)))] [(delayed-element? c) (content->port op ((delayed-element-plain c)))] [(string? c) (display c op)] [else (display (case c [(mdash) "---"] [(ndash) "--"] [(ldquo rdquo) "\""] [(rsquo) "'"] [(rarr) "->"] [(lang) "<"] [(rang) ">"] [else (format "~s" c)]) op)])] [(op c renderer sec ri) (cond [(and (link-element? c) (null? (element-content c))) (let ([dest (resolve-get sec ri (link-element-tag c))]) ;; FIXME: this is specific to renderer (if dest (content->port op (strip-aux (if (pair? dest) (cadr dest) (vector-ref dest 1))) renderer sec ri) (display "???" op)))] [(element? c) (content->port op (element-content c) renderer sec ri)] [(multiarg-element? c) (content->port op (multiarg-element-contents c) renderer sec ri)] [(list? c) (for-each (lambda (e) (content->port op e renderer sec ri)) c)] [(delayed-element? c) (content->port op (delayed-element-content c ri) renderer sec ri)] [(part-relative-element? c) (content->port op (part-relative-element-content c ri) renderer sec ri)] [else (content->port op c)])])) (define (simple-content->string c) ;; `content->string' is commonly used on a list containing a single string (cond [(string? c) c] [(and (pair? c) (string? (car c)) (null? (cdr c))) (car c)] [else #f])) (define content->string (case-lambda [(c) (or (simple-content->string c) (let ([op (open-output-string)]) (content->port op c) (get-output-string op)))] [(c renderer sec ri) (or (simple-content->string c) (let ([op (open-output-string)]) (content->port op c renderer sec ri) (get-output-string op)))])) (define (aux-element? e) (and (element? e) (let ([s (element-style e)]) (and (style? s) (memq 'aux (style-properties s)))))) (define (strip-aux content) (cond [(null? content) null] [(aux-element? content) null] [(element? content) (define c (element-content content)) (define p (strip-aux c)) (if (equal? c p) content (struct-copy element content [content p]))] [(list? content) (define p (map strip-aux content)) (if (equal? p content) content p)] [else content])) ;; ---------------------------------------- (provide block-width content-width) (define (content-width s) (cond [(string? s) (string-length s)] [(list? s) (for/fold ([v 0]) ([s (in-list s)]) (+ v (content-width s)))] [(element? s) (content-width (element-content s))] [(multiarg-element? s) (content-width (multiarg-element-contents s))] [(delayed-element? s) (content-width ((delayed-element-sizer s)))] [(part-relative-element? s) (content-width ((part-relative-element-sizer s)))] [else 1])) (define (paragraph-width s) (content-width (paragraph-content s))) (define (flow-width f) (apply max 0 (map block-width f))) (define (block-width p) (cond [(paragraph? p) (paragraph-width p)] [(table? p) (table-width p)] [(itemization? p) (itemization-width p)] [(nested-flow? p) (nested-flow-width p)] [(compound-paragraph? p) (compound-paragraph-width p)] [(delayed-block? p) 1] [(eq? p 'cont) 0])) (define (table-width p) (let ([blocks (table-blockss p)]) (if (null? blocks) 0 (let loop ([blocks blocks]) (if (null? (car blocks)) 0 (+ (apply max 0 (map block-width (map car blocks))) (loop (map cdr blocks)))))))) (define (itemization-width p) (apply max 0 (map flow-width (itemization-blockss p)))) (define (nested-flow-width p) (+ 4 (apply max 0 (map block-width (nested-flow-blocks p))))) (define (compound-paragraph-width p) (apply max 0 (map block-width (compound-paragraph-blocks p)))) ;; ---------------------------------------- (define (info-key? l) (and (pair? l) (symbol? (car l)) (pair? (cdr l)))) (provide info-key?) (provide/contract [part-collected-info (part? resolve-info? . -> . collected-info?)] [collect-put! (collect-info? info-key? any/c . -> . any)] [resolve-get ((or/c part? false/c) resolve-info? info-key? . -> . any)] [resolve-get/tentative ((or/c part? false/c) resolve-info? info-key? . -> . any)] [resolve-get/ext? ((or/c part? false/c) resolve-info? info-key? . -> . any)] [resolve-get/ext-id ((or/c part? false/c) resolve-info? info-key? . -> . any)] [resolve-search (any/c (or/c part? false/c) resolve-info? info-key? . -> . any)] [resolve-get-keys ((or/c part? false/c) resolve-info? (info-key? . -> . any/c) . -> . any/c)])