|
13 | 13 | "common.rkt"
|
14 | 14 | "inline.rkt")
|
15 | 15 |
|
16 |
| -(provide read-document string->document) |
| 16 | +(provide read-document |
| 17 | + string->document |
| 18 | + current-parse-footnotes?) |
17 | 19 |
|
18 | 20 | ;; -----------------------------------------------------------------------------
|
19 | 21 |
|
|
47 | 49 |
|
48 | 50 | ;; -----------------------------------------------------------------------------
|
49 | 51 |
|
| 52 | +(define current-parse-footnotes? (make-parameter #f (λ (x) (and x #t)))) |
| 53 | + |
50 | 54 | (struct o:blockquote (blocks) #:transparent)
|
51 | 55 | (struct o:list ; see Note [Lists overview]
|
52 | 56 | (blocks ; blocks of the current open list item, or #f if no list item
|
|
61 | 65 | style ; (or/c 'tight 'loose)
|
62 | 66 | start-num) ; (or/c exact-nonnegative-integer? #f)
|
63 | 67 | #:transparent)
|
| 68 | +(struct o:footnote-definition (blocks label) #:transparent) |
64 | 69 |
|
65 | 70 | ; see Note [Open lists without an open item]
|
66 | 71 | (define (accumulate-list-blockss open-list)
|
|
82 | 87 | (read-document (open-input-string str)))
|
83 | 88 |
|
84 | 89 | (define (read-document in)
|
| 90 | + (define footnotes? (current-parse-footnotes?)) |
| 91 | + |
85 | 92 | (define root-blocks (make-gvector))
|
86 | 93 | (define link-reference-defns (make-hash))
|
| 94 | + (define footnote-defns (make-gvector)) |
| 95 | + (define footnote-defn-labels (make-hash)) |
87 | 96 |
|
88 | 97 | ;; ---------------------------------------------------------------------------
|
89 | 98 | ;; open blocks
|
|
121 | 130 | ; see Note [Open list tightness]
|
122 | 131 | [style (if (o:list-end-blank? oc)
|
123 | 132 | 'loose
|
124 |
| - (o:list-style oc))])])) |
| 133 | + (o:list-style oc))])] |
| 134 | + [(o:footnote-definition blocks label) |
| 135 | + (o:footnote-definition (cons block blocks) label)])) |
125 | 136 | (gvector-set! open-containers (sub1 ocs) oc*)]))
|
126 | 137 |
|
127 | 138 | (define (open-leaf! new-open-leaf)
|
|
167 | 178 | ; If there’s an open leaf, we need to take care to close it /before/ we
|
168 | 179 | ; remove the open container.
|
169 | 180 | (close-leaf!)
|
170 |
| - (add-block! |
171 |
| - #:close-unentered? #f |
172 |
| - (match (gvector-remove-last! open-containers) |
173 |
| - [(o:blockquote blocks) |
174 |
| - (blockquote (reverse blocks))] |
175 |
| - [(? o:list? open-list) |
176 |
| - ; see Note [Transfer `end-blank?` to parent lists] |
177 |
| - (when (and (o:list-end-blank? open-list) |
178 |
| - (not (zero? (gvector-count open-containers)))) |
179 |
| - (define last-idx (sub1 (gvector-count open-containers))) |
180 |
| - (define parent-container (gvector-ref open-containers last-idx)) |
181 |
| - (when (o:list? parent-container) |
182 |
| - (gvector-set! open-containers |
183 |
| - last-idx |
184 |
| - (struct-copy o:list parent-container |
185 |
| - [end-blank? #t])))) |
186 |
| - |
187 |
| - (itemization (reverse (accumulate-list-blockss open-list)) |
188 |
| - (o:list-style open-list) |
189 |
| - (o:list-start-num open-list))]))) |
| 181 | + (define new-block |
| 182 | + (match (gvector-remove-last! open-containers) |
| 183 | + [(o:blockquote blocks) |
| 184 | + (blockquote (reverse blocks))] |
| 185 | + [(? o:list? open-list) |
| 186 | + ; see Note [Transfer `end-blank?` to parent lists] |
| 187 | + (when (and (o:list-end-blank? open-list) |
| 188 | + (not (zero? (gvector-count open-containers)))) |
| 189 | + (define last-idx (sub1 (gvector-count open-containers))) |
| 190 | + (define parent-container (gvector-ref open-containers last-idx)) |
| 191 | + (when (o:list? parent-container) |
| 192 | + (gvector-set! open-containers |
| 193 | + last-idx |
| 194 | + (struct-copy o:list parent-container |
| 195 | + [end-blank? #t])))) |
| 196 | + |
| 197 | + (itemization (reverse (accumulate-list-blockss open-list)) |
| 198 | + (o:list-style open-list) |
| 199 | + (o:list-start-num open-list))] |
| 200 | + [(o:footnote-definition blocks label) |
| 201 | + (gvector-add! footnote-defns (footnote-definition (reverse blocks) label)) |
| 202 | + #f])) |
| 203 | + (when new-block |
| 204 | + (add-block! new-block #:close-unentered? #f))) |
190 | 205 |
|
191 | 206 | (define (unentered-containers?)
|
192 | 207 | (< entered-containers (gvector-count open-containers)))
|
|
410 | 425 | (close-leaf!)
|
411 | 426 | (close-unentered-containers!)
|
412 | 427 | (document (for/list ([block (in-gvector root-blocks)])
|
413 |
| - (finish-block-content block)))] |
| 428 | + (finish-block-content block)) |
| 429 | + (for/list ([footnote-defn (in-gvector footnote-defns)]) |
| 430 | + (match-define (footnote-definition blocks label) footnote-defn) |
| 431 | + (footnote-definition (map finish-block-content blocks) label)))] |
414 | 432 | [else
|
415 | 433 | (mode:enter-containers)]))
|
416 | 434 |
|
|
451 | 469 | (open-container! (o:list '() '() indent marker-char #f #f 'tight start-num)
|
452 | 470 | #:enter? #f)])
|
453 | 471 | 0])]
|
454 |
| - [else #f])])) |
| 472 | + [else #f])] |
| 473 | + [(? o:footnote-definition?) |
| 474 | + (try-enter-indent 4)])) |
455 | 475 |
|
456 | 476 | (define (mode:enter-containers)
|
457 | 477 | (cond
|
|
675 | 695 | (open-container! (o:list '() '() indent marker-char #f #f 'tight start-num))
|
676 | 696 | (mode:content-start)])]
|
677 | 697 |
|
| 698 | + ;; Extension: Footnote definitions |
| 699 | + [(and footnotes? |
| 700 | + (regexp-try/opt-indent |
| 701 | + ; This is the regexp used by _scan_footnote_definition in cmark-gfm: |
| 702 | + ; <https://github.com/github/cmark-gfm/blob/766f161ef6d61019acf3a69f5099489e7d14cd49/src/scanners.re#L323> |
| 703 | + ; Note that it consumes all whitespace after the label, so it is |
| 704 | + ; impossible for a footnote to start with an indented code block on the |
| 705 | + ; same line as the label (but one can begin on the following line). |
| 706 | + (px "^" |
| 707 | + "\\[\\^" |
| 708 | + (:group "[^]" :space: "]+") |
| 709 | + "\\]:[ \t]*"))) |
| 710 | + => (match-lambda |
| 711 | + [(list _ _ label-bytes) |
| 712 | + (define label (bytes->string/utf-8 label-bytes)) |
| 713 | + (define normalized-label (normalize-link-label label)) |
| 714 | + (unless (hash-has-key? footnote-defn-labels normalized-label) |
| 715 | + (hash-set! footnote-defn-labels normalized-label label)) |
| 716 | + (open-container! (o:footnote-definition '() label)) |
| 717 | + (mode:content-start)])] |
| 718 | + |
678 | 719 | ;; § 4.8 Paragraphs
|
679 | 720 | [else
|
680 | 721 | (define line (~> (read-line in 'any)
|
|
717 | 758 | (define (finish-block-content block)
|
718 | 759 | (match block
|
719 | 760 | [(heading content depth)
|
720 |
| - (heading (string->inline content link-reference-defns) depth)] |
| 761 | + (heading (parse-inline content) depth)] |
721 | 762 | [(paragraph content)
|
722 |
| - (paragraph (string->inline content link-reference-defns))] |
| 763 | + (paragraph (parse-inline content))] |
723 | 764 | [(blockquote blocks)
|
724 | 765 | (blockquote (map finish-block-content blocks))]
|
725 | 766 | [(itemization blockss style start-num)
|
726 | 767 | (itemization (map (λ~>> (map finish-block-content)) blockss) style start-num)]
|
727 | 768 | [_ block]))
|
728 | 769 |
|
| 770 | + (define (parse-inline content) |
| 771 | + (string->inline content |
| 772 | + #:link-defns link-reference-defns |
| 773 | + #:footnote-defns footnote-defn-labels)) |
| 774 | + |
729 | 775 | (mode:line-start))
|
730 | 776 |
|
731 | 777 | ; see Note [Tabs and tab stops]
|
|
0 commit comments