tur/markdown/inline
the CommonMark inline parser: a block's text into inlines.
A port of commonmark.js 0.31.2's inlines.js. Each paragraph's and heading's text is scanned once, left to right, into a flat list of inlines under a root: text runs, code spans, breaks, autolinks, raw HTML, and a text inline for every run of * or _ and every [ or ![. The runs go on a delimiter stack and the brackets on a bracket stack; a ] that closes a link or image moves what follows its bracket into the new inline, and the emphasis algorithm then pairs the delimiter runs (spec section 6.2) into emphasis and strong emphasis.
Inlines live in the document's inline arena, linked by index. A text inline is a byte range of a pool String, usually the block's trimmed text, so splitting a delimiter run off is moving an end index.
Since: markdown-library-plan M2
MdDelim
(defstruct MdDelim [])
a run of * or _ on the delimiter stack: its byte, how many are left and were there, its text inline, its neighbours, and whether it can open or close emphasis.
MdBracket
(defstruct MdBracket [])
a [ or ![ on the bracket stack: its text inline, the bracket and delimiter below it, where its [ is, and its state.
MdIP
(defstruct MdIP [])
the inline parser for one block: the document, the subject's pool index and length, the position, the root inline, the two stacks, and whether strikethrough, extended autolinks and math are on.
md/-new-delims
(md/-new-delims :(Vec MdDelim))
internal: an empty delimiter Vec.
md/-new-brackets
(md/-new-brackets :(Vec MdBracket))
internal: an empty bracket Vec.
md/-inl-new!
(md/-inl-new! [v :(Vec MdInl) k :MdIKind str :int start :int end :int] :int)
internal: a fresh detached inline of kind k over [start, end) of pool String str; its index.
md/-inl-append!
(md/-inl-append! [v :(Vec MdInl) p :int c :int] :void)
internal: make inline c the last child of inline p.
md/-inl-unlink!
(md/-inl-unlink! [v :(Vec MdInl) i :int] :void)
internal: take inline i out of its parent's children.
md/-inl-insert-after!
(md/-inl-insert-after! [v :(Vec MdInl) a :int b :int] :void)
internal: put detached inline b just after inline a.
md/-inl-move-until!
(md/-inl-move-until! [v :(Vec MdInl) from :int until :int into :int] :void)
internal: move the inlines after from, up to (not including) until, or to the end when until is -1, into inline into.
md/-ip-s
(md/-ip-s [^borrow ip :rc<MdIP>] :String)
internal: the subject (borrowed).
md/-ip-c
(md/-ip-c [^borrow ip :rc<MdIP> i :int] :int)
internal: the subject's byte at i, or -1 outside it.
md/-ip-v
(md/-ip-v [^borrow ip :rc<MdIP>] :(Vec MdInl))
internal: the inline arena.
md/-ip-add-str!
(md/-ip-add-str! [^borrow ip :rc<MdIP> s :String] :int)
internal: put s in the document's pool; its index.
md/-ip-leaf!
(md/-ip-leaf! [^borrow ip :rc<MdIP> k :MdIKind str :int start :int end :int] :int)
internal: append an inline of kind k over [start, end) of pool String str to the root; its index.
md/-ip-text!
(md/-ip-text! [^borrow ip :rc<MdIP> start :int end :int] :int)
internal: append the subject's bytes [start, end) as a text inline; its index.
md/-ip-own!
(md/-ip-own! [^borrow ip :rc<MdIP> k :MdIKind s :String] :int)
internal: append an inline of kind k holding all of s, which the pool takes over; its index.
md/-ip-advance!
(md/-ip-advance! [^borrow ip :rc<MdIP> n :int] :void)
internal: move the position on by n.
md/-ip-run
(md/-ip-run [^borrow ip :rc<MdIP> i :int c :int] :int)
internal: how many of byte c repeat from i in the subject.
md/-dl
(md/-dl [^borrow ip :rc<MdIP> i :int] :MdDelim)
internal: delimiter i.
md/-dl-put!
(md/-dl-put! [^borrow ip :rc<MdIP> i :int d :MdDelim] :void)
internal: replace delimiter i.
md/-remove-delim!
(md/-remove-delim! [^borrow ip :rc<MdIP> i :int] :void)
internal: take delimiter i off the stack.
md/-remove-delims-between!
(md/-remove-delims-between! [^borrow ip :rc<MdIP> bottom :int top :int] :void)
internal: drop the delimiters strictly between bottom and top.
md/-push-delim!
(md/-push-delim! [^borrow ip :rc<MdIP> cc :int n :int node :int can-open :bool can-close :bool] :void)
internal: push a delimiter run onto the stack.
md/-bottom-index
(md/-bottom-index [c :MdDelim] :int)
internal: which openers-bottom slot a closer uses: by byte, whether it can also open, and its original length mod 3 (a ~ closer, as cmark-gfm keeps it, by its length mod 3 alone).
md/-find-opener
(md/-find-opener [^borrow ip :rc<MdIP> closer :int stop1 :int stop2 :int] :int)
internal: the nearest opener below closer that can pair with it, stopping at stop1 or stop2, or -1.
md/-pair-delims!
(md/-pair-delims! [^borrow ip :rc<MdIP> opener :int closer :int] :int)
internal: turn the matched opener and closer into emphasis or strong emphasis; the closer to look at next.
md/-pair-tilde!
(md/-pair-tilde! [^borrow ip :rc<MdIP> opener :int closer :int] :int)
internal: a ~ opener and closer: strikethrough when they are the same length (cmark-gfm's rule), either way taking every delimiter from the closer down to the opener off the stack; the closer to look at next.
md/-emph-step!
(md/-emph-step! [^borrow ip :rc<MdIP> bottoms :(Vec int) stack-bottom :int closer :int] :int)
internal: handle the closer at closer; the next closer to look at.
md/-new-bottoms
(md/-new-bottoms [stack-bottom :int] :(Vec int))
internal: the openers-bottom slots, each stack-bottom: fourteen for * and _, three for ~.
md/-process-emphasis!
(md/-process-emphasis! [^borrow ip :rc<MdIP> stack-bottom :int] :void)
internal: pair the delimiter runs above stack-bottom into emphasis, then drop them all.
md/-bk
(md/-bk [^borrow ip :rc<MdIP> i :int] :MdBracket)
internal: bracket i.
md/-add-bracket!
(md/-add-bracket! [^borrow ip :rc<MdIP> node :int index :int image :bool] :void)
internal: push a [ (or ![) whose text inline is node and whose [ is at index.
md/-remove-bracket!
(md/-remove-bracket! [^borrow ip :rc<MdIP>] :void)
internal: pop the bracket stack.
md/-deactivate-links!
(md/-deactivate-links! [^borrow ip :rc<MdIP>] :void)
internal: no links in links -- mark every [ still on the stack inactive.
md/-find-doc-ref
(md/-find-doc-ref [d :MdDoc label :String] :int)
internal: the reference of d whose normalised label is label, or -1.
md/-lookup-ref
(md/-lookup-ref [^borrow ip :rc<MdIP> a :int b :int] :int)
internal: the reference whose label is [a, b) of the subject (brackets included), or -1.
md/-make-link!
(md/-make-link! [^borrow ip :rc<MdIP> top :int dest :int title :int] :void)
internal: the link or image for the bracket at top, holding everything after its [.
md/-inline-link-end
(md/-inline-link-end [^borrow ip :rc<MdIP> i :int out :(Vec int)] :int)
internal: the end of an inline link's ( destination title ) at i, or -1; the destination and title bounds go in out.
md/-close-inline!
(md/-close-inline! [^borrow ip :rc<MdIP> top :int] :bool)
internal: a ] followed by an inline link's ( ... ); whether it made the link.
md/-close-ref!
(md/-close-ref! [^borrow ip :rc<MdIP> top :int startpos :int] :bool)
internal: a ] followed by a full, collapsed or shortcut reference; whether it made the link.
md/-close-literal!
(md/-close-literal! [^borrow ip :rc<MdIP> startpos :int] :void)
internal: append the ] just before startpos as text.
md/-close-link!
(md/-close-link! [^borrow ip :rc<MdIP> top :int startpos :int] :bool)
internal: a ] after an active bracket: make the inline link or the reference link it closes; whether it made one.
md/-parse-close-bracket!
(md/-parse-close-bracket! [^borrow ip :rc<MdIP>] :void)
internal: a ] -- a link or image when a [ before it matches, else a literal ].
md/-strip-final-spaces!
(md/-strip-final-spaces! [^borrow ip :rc<MdIP> i :int] :bool)
internal: drop the trailing spaces of text inline i; whether there were two or more (a hard break).
md/-parse-newline!
(md/-parse-newline! [^borrow ip :rc<MdIP>] :void)
internal: a line ending: a hard break after two spaces, else a soft break; the next line's leading spaces go.
md/-parse-backslash!
(md/-parse-backslash! [^borrow ip :rc<MdIP>] :void)
internal: a backslash: a hard break before a line ending, an escaped punctuation character, or itself.
md/-closing-ticks
(md/-closing-ticks [^borrow ip :rc<MdIP> i0 :int n :int] :int)
internal: where a run of exactly n backticks starts at or after i, or -1.
md/-code-sp?
(md/-code-sp? [c :int] :bool)
internal: does byte c count as a space in a code span (a space, or a line ending, which becomes one)?
md/-code-all-sp?
(md/-code-all-sp? [s :String a :int b :int] :bool)
internal: is every byte of [a, b) of s a code-span space?
md/-code-contents
(md/-code-contents [s :String a :int b :int] :String)
internal: a code span's [a, b) of s: line endings as spaces, and one space stripped from each end when both ends have one and it is not all spaces.
md/-parse-backticks!
(md/-parse-backticks! [^borrow ip :rc<MdIP>] :void)
internal: a run of backticks: a code span when a run of the same length closes it, else literal backticks.
md/-math-ws?
(md/-math-ws? [c :int] :bool)
internal: is c a space, a tab or a line ending, or past the subject's end (-1)?
md/-math-closes-at?
(md/-math-closes-at? [^borrow ip :rc<MdIP> a :int n :int j :int] :bool)
internal: does the closing $ (n = 1) or $$ (n = 2) of a formula whose contents start at a sit at j?
md/-math-close
(md/-math-close [^borrow ip :rc<MdIP> a :int n :int] :int)
internal: where the closing $ (n = 1) or $$ (n = 2) of a formula whose contents start at a is, or -1.
md/-parse-dollar!
(md/-parse-dollar! [^borrow ip :rc<MdIP>] :bool)
internal: a $ -- an inline formula, a display one at $$, or (false) neither.
md/-cp-before
(md/-cp-before [s :String i :int] :int)
internal: the code point before byte i of s, or a line feed at the start.
md/-cp-at
(md/-cp-at [s :String i :int] :int)
internal: the code point at byte i of s, or a line feed at the end.
md/-cp-before-skip
(md/-cp-before-skip [s :String i :int] :int)
internal: the code point before byte i of s as cmark-gfm reads it with strikethrough on, past any ~ to the left; a line feed at the start, or when a ~ or a broken sequence is all there is.
md/-cp-after-skip
(md/-cp-after-skip [s :String i0 :int n :int] :int)
internal: the code point at byte i of s (which is n bytes long) as cmark-gfm reads it with strikethrough on, past any ~; a line feed at the end or at a broken sequence.
md/-parse-delim!
(md/-parse-delim! [^borrow ip :rc<MdIP> cc :int] :void)
internal: a run of *, _ or ~: a text inline, and a delimiter when it can open or close (the flanking rules, spec 6.2); with strikethrough on, a ~ next to the run is looked past, as cmark-gfm does.
md/-parse-open-bracket!
(md/-parse-open-bracket! [^borrow ip :rc<MdIP>] :void)
internal: a [, which may open a link.
md/-parse-bang!
(md/-parse-bang! [^borrow ip :rc<MdIP>] :void)
internal: a !, which before a [ may open an image.
md/-autolink!
(md/-autolink! [^borrow ip :rc<MdIP> i :int e :int pre :cstr] :void)
internal: append the autolink [i, e) of the subject, its destination prefixed by pre (mailto: for an email address).
md/-parse-lt!
(md/-parse-lt! [^borrow ip :rc<MdIP>] :bool)
internal: a < -- an email or URI autolink, raw HTML, or (false) none of them.
md/-parse-entity!
(md/-parse-entity! [^borrow ip :rc<MdIP>] :bool)
internal: a character reference, appended as its text; whether there was one.
md/-al-alpha?
(md/-al-alpha? [c :int] :bool)
internal: an ASCII letter?
md/-al-alnum?
(md/-al-alnum? [c :int] :bool)
internal: an ASCII letter or digit?
md/-al-space?
(md/-al-space? [c :int] :bool)
internal: cmark's isspace: space, tab, LF, VT, FF or CR.
md/-hostchar?
(md/-hostchar? [s :String i :int end :int] :bool)
internal: is the character at byte i of s (before end) a valid host character: neither Unicode space nor punctuation?
md/-al-trail?
(md/-al-trail? [c :int] :bool)
internal: is c one of the bytes autolink_delim trims off a link's end: ? ! . , : * _ ~ ' and ".
md/-al-entity-cut
(md/-al-entity-cut [s :String a :int n :int] :int)
internal: the link [a, a+n) of s ends in ;: its length without the &name; it ends in, or with only the ; gone.
md/-al-delim
(md/-al-delim [s :String a :int n0 :int] :int)
internal: cmark-gfm's autolink_delim: the link [a, a+n) of s cut at a <, then trimmed of trailing punctuation, an unmatched ) and an entity-like &name;; its new length.
md/-al-domain-stop?
(md/-al-domain-stop? [s :String i :int end :int] :bool)
internal: does a domain end at byte i of s (before end): neither a host character nor a -?
md/-check-domain
(md/-check-domain [s :String a :int size :int allow-short :bool] :int)
internal: cmark-gfm's check_domain over [a, a+size) of s: the length of the domain there, or 0 when an underscore sits in its last two labels or (unless allow-short) it has no dot.
md/-al-extend
(md/-al-extend [s :String a :int n0 :int size :int] :int)
internal: from n, past every byte of [a, a+size) of s that is not a space or a <.
md/-al-node!
(md/-al-node! [^borrow ip :rc<MdIP> str :int a :int b :int dest-raw :String] :int)
internal: a detached link to dest-raw (normalised) whose text is bytes [a, b) of pool String str; its index.
md/-al-link!
(md/-al-link! [^borrow ip :rc<MdIP> a :int b :int dest-raw :String] :void)
internal: append a link to dest-raw (normalised) holding the subject's bytes [a, b) as its text.
md/-www-match!
(md/-www-match! [^borrow ip :rc<MdIP>] :bool)
internal: at a w, a www. link: after a space, one of *_~( or the start, with a domain of two or more labels; whether there was one.
md/-unput!
(md/-unput! [^borrow ip :rc<MdIP> n0 :int] :void)
internal: take n bytes back off the end of the root's trailing text inlines (cmark_node_unput), dropping any left empty.
md/-scheme-at?
(md/-scheme-at? [s :String a :int len :int sch :cstr k :int] :bool)
internal: does [a, a+len) of s start with the k-byte scheme sch (any case) followed by a host character?
md/-safe-scheme?
(md/-safe-scheme? [s :String a :int len :int] :bool)
internal: does [a, a+len) of s start with http://, https:// or ftp:// (any case) followed by a host character?
md/-url-match!
(md/-url-match! [^borrow ip :rc<MdIP>] :bool)
internal: at a :, a link whose scheme (http, https or ftp) is the letters just before it, followed by // and a domain; whether there was one.
md/-is-text?
(md/-is-text? [v :(Vec MdInl) i :int] :bool)
internal: is inline i a text inline?
md/-byte-index
(md/-byte-index [s :String c :int i :int end :int] :int)
internal: the first index of byte c in [i, end) of s, or -1.
md/-text-has-at?
(md/-text-has-at? [^borrow ip :rc<MdIP> i :int] :bool)
internal: does text inline i hold an @?
md/-consolidate!
(md/-consolidate! [^borrow ip :rc<MdIP> i :int] :void)
internal: cmark's text consolidation where it can matter: when text inline i and the text inlines straight after it hold an @ between them, join them into one over a fresh pool String.
md/-al-protocol?
(md/-al-protocol? [s :String p :cstr k :int at :int rw :int mr :int] :bool)
internal: cmark-gfm's validate_protocol: does the k-byte protocol p end just before the rw bytes rewound from the @ at at, with a non-alphanumeric byte or the start of the scan (mr bytes back) before it?
MdMailScan
(defstruct MdMailScan :copy [rw :int auto :bool xmpp :bool])
internal: postprocess_text's walk left from an @: how many bytes it took, and whether a mailto: or xmpp: it passed means no mailto: is added (auto false) and a / may follow the domain (xmpp).
MdMailEnd
(defstruct MdMailEnd :copy [le :int np :int at :bool])
internal: postprocess_text's walk right from an @: the length it took, the dots it has passed, and whether it stopped at another @.
MdMailHit
(defstruct MdMailHit [])
internal: an email address postprocess_text found -- the bytes [a, b), and whether to add mailto: -- or, when found is false, where to look for the next @.
md/-mail-local?
(md/-mail-local? [c :int] :bool)
internal: can byte c come before the @ of an email address: a letter, a digit, or one of . + - _?
md/-mail-rewind
(md/-mail-rewind [s :String at :int mr :int sc :MdMailScan] :MdMailScan)
internal: postprocess_text's walk left from the @ at at, over at most mr bytes, carrying on from sc's auto and xmpp.
md/-mail-dot?
(md/-mail-dot? [s :String i :int end :int] :bool)
internal: is byte i of s a dot with a letter or digit after it, before end?
md/-mail-domain
(md/-mail-domain [s :String at :int end :int np0 :int xmpp :bool] :MdMailEnd)
internal: postprocess_text's walk right from the @ at at, before end, counting dots on from np0; a / is allowed when xmpp.
md/-mail-check
(md/-mail-check [s :String at :int m :MdMailEnd sc :MdMailScan] :MdMailHit)
internal: the end of postprocess_text for the @ at at, whose walks gave sc and m: the address, or where to look next.
md/-mail-link!
(md/-mail-link! [^borrow ip :rc<MdIP> str :int s :String a :int b :int auto :bool] :int)
internal: a detached link for the email address [a, b) of pool String str (whose text is s), with mailto: added when auto; its index.
md/-mail-find
(md/-mail-find [s :String p0 :int at0 :int end :int] :MdMailHit)
internal: postprocess_text from the @ at at0, in text whose scan starts at p0 and ends at end: the email address there, or where to look next.
A second @ in the domain moves the search to it, keeping what the first walk left found (a mailto: or xmpp:, and the dots), as cmark-gfm's goto does.
md/-email-text!
(md/-email-text! [^borrow ip :rc<MdIP> t0 :int] :int)
internal: cmark-gfm's postprocess_text on text inline t0: each email address in it becomes a mailto: link (xmpp: and mailto: ones keep their own scheme); the last inline it left in t0's place.
md/-inl-leaf?
(md/-inl-leaf? [k :MdIKind] :bool)
internal: is an inline of kind k a leaf, which cmark's iterator enters but never exits?
md/-is-link?
(md/-is-link? [v :(Vec MdInl) i :int] :bool)
internal: is inline i a link?
md/-email-pass!
(md/-email-pass! [^borrow ip :rc<MdIP>] :void)
internal: cmark-gfm's autolink postprocess for one block: an email address in a text inline becomes a link, unless it is inside a link.
The walk is cmark's iterator, enter and exit events, and "inside a link" is its one flag: the exit of a link inside a link clears it, so the rest of the outer link's text is searched too.
md/-special?
(md/-special? [c :int] :bool)
internal: can byte c start something other than plain text?
md/-ip-special?
(md/-ip-special? [^borrow ip :rc<MdIP> c :int] :bool)
internal: can byte c start something other than plain text, with this block's extensions?
md/-parse-string!
(md/-parse-string! [^borrow ip :rc<MdIP>] :bool)
internal: a run of plain text; whether there was one.
md/-parse-inline!
(md/-parse-inline! [^borrow ip :rc<MdIP>] :void)
internal: parse the inline at the position.
md/-text-run?
(md/-text-run? [v :(Vec MdInl) i :int j :int] :bool)
internal: is inline j a text inline that carries on straight from inline i's bytes?
md/-merge-text!
(md/-merge-text! [v :(Vec MdInl) first :int] :void)
internal: join each run of adjacent text inlines over consecutive bytes (an unmatched * between two words, say) into one, for the inlines from first on.
md/-inline-block!
(md/-inline-block! [d :MdDoc i :int] :void)
internal: parse block i's text (trimmed first) into an inline tree under a fresh root.
md/-parse-inlines!
(md/-parse-inlines! [d :MdDoc] :void)
internal: parse every paragraph and heading still in the tree.