Sign in

    mdparse

    Markdown 语法解析:把 Markdown 源文解析成 AST(fork 自 mizchi/markdown,仅保留解析链路)

    Download zip
    Author
    Version
    0.1.0
    License
    MIT
    Last updated
    8 hours ago
    Downloads
    3

    #mdparse — Markdown 语法解析

    把 Markdown 源文解析成 AST。只做解析:不含渲染、序列化、HTML 输出、增量解析。

    派生自 mizchi/markdown 0.8.3,保留其解析链路。来源、删改范围与授权见 NOTICE.md 与 LICENSE。

    #用法

    作为模块依赖:

    import { "NoahLiu/mdparse@0.1.0", }

    ///|
    fn example() -> Unit {
    let res = @mdparse.parse("# 标题\n\n段落含 **粗体** 与 [链接](https://example.com)。\n")
    for block in res.document.children {
    println(block)
    }
    // 引用式链接的文档级定义:label → url
    for def in res.definitions {
    println("\{def.label} => \{def.url}")
    }
    }

    parse(source, wikilinks? = false) 返回 ParseResult:

    字段类型含义
    documentDocument文档根,children : Array[Block]
    definitionsArray[LinkDefinition]引用式链接/图片的文档级定义(label、url)

    #支持的语法

    • 块级:ATX/Setext 标题、围栏代码(info 串)、缩进代码、引用、GFM 提示块、有序/无序列表(含 start)、任务列表、表格(列对齐)、定义列表、分隔线、脚注定义、数学块、HTML 块、:::name 指令块、frontmatter
    • 行内:粗体、斜体、删除线、行内代码、链接、引用式链接、自动链接、图片、引用式图片、脚注引用、软/硬换行、行内 HTML、行内数学、[[WikiLink]]、行内指令

    #AST

    Block 与 Inline 两个枚举是解析结果的全部形态;渲染、遍历、测试都按它们模式匹配。字段以 types.mbt 为准。

    #平台

    supported_targets = "js+wasm+wasm-gc+native",与上游一致。纯 MoonBit,无第三方依赖(只用 moonbitlang/core),不依赖任何宿主能力,可作为通用 Markdown 解析层单独发布或复用到其他项目。

    AlertKind

    pub(all) enum AlertKind {
    Note
    Tip
    Important
    Warning
    Caution
    } derive(Eq,
    Debug
    )

    GitHub alert kind (> [!NOTE], etc.).
    impl Show for AlertKind

    AlertKind::slug

    fn AlertKind::slug(self : AlertKind) -> String

    Stable lowercase name used by renderers and JSON APIs.

    Block

    pub(all) enum Block {
    ThematicBreak(marker~ : Char, count~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Heading(level~ : Int, style~ : HeadingStyle, children~ : Array[Inline], closing_hashes~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Paragraph(children~ : Array[Inline], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    FencedCode(fence_marker~ : FenceMarker, fence_length~ : Int, info~ : String, code~ : String, indent~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    MathBlock(value~ : String, fence_length~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Directive(name~ : String, meta~ : String, children~ : Array[Block], fence_length~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    DefinitionList(items~ : Array[DefinitionItem], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Attributed(block~ : Block, attributes~ : Array[MarkdownAttribute], span~ : Span)
    IndentedCode(code~ : String, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Blockquote(children~ : Array[Block], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Alert(kind~ : AlertKind, children~ : Array[Block], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    BulletList(marker~ : BulletMarker, tight~ : Bool, items~ : Array[ListItem], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    OrderedList(start~ : Int, delimiter~ : OrderedDelimiter, tight~ : Bool, items~ : Array[ListItem], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    HtmlBlock(html~ : String, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Table(header~ : Array[TableCell], alignments~ : Array[TableAlign], rows~ : Array[Array[TableCell]], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    BlankLines(count~ : Int, span~ : Span)
    FootnoteDefinition(label~ : String, children~ : Array[Block], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    }

    Block-level nodes

    BulletMarker

    pub(all) enum BulletMarker {
    Dash
    Asterisk
    Plus
    } derive(Eq,
    Debug
    )

    List marker for unordered lists: -, *, +

    CodeBlockInfo

    pub(all) struct CodeBlockInfo {
    lang : String
    filename : String
    meta : String
    }

    Parsed info string from code block fence.

    Format: "lang:filename {meta}"

    Examples:
    • "javascript" → { lang: "javascript", filename: "", meta: "" }
    • "js:app.js" → { lang: "js", filename: "app.js", meta: "" }
    • "ts:index.ts {highlight=[1,3]}" → { lang: "ts", filename: "index.ts", meta: "{highlight=[1,3]}" }

    DefinitionItem

    pub(all) struct DefinitionItem {
    term : Array[Inline]
    definitions : Array[Array[Inline]]
    }

    One term and one or more inline definitions in a definition list.

    Document

    pub(all) struct Document {
    frontmatter : Frontmatter?
    children : Array[Block]
    definitions : Array[LinkDefinition]
    span : Span
    }

    Document root

    EmphasisMarker

    pub(all) enum EmphasisMarker {
    Asterisk
    Underscore
    } derive(Eq,
    Debug
    )

    Emphasis marker: * or _

    FenceMarker

    pub(all) enum FenceMarker {
    Backtick
    Tilde
    } derive(Eq,
    Debug
    )

    Fence marker for code blocks: ``` or ~~~
    impl Show for FenceMarker

    Frontmatter

    pub(all) struct Frontmatter {
    raw : String
    entries : Array[(String, String)]
    span : Span
    }

    Frontmatter (YAML)

    HardBreakStyle

    pub(all) enum HardBreakStyle {
    TwoSpaces
    Backslash
    } derive(Eq,
    Debug
    )

    Hard break style

    HeadingStyle

    pub(all) enum HeadingStyle {
    Atx
    Setext
    } derive(Eq,
    Debug
    )

    Heading style

    Inline

    pub(all) enum Inline {
    Text(content~ : String, span~ : Span)
    SoftBreak(span~ : Span)
    HardBreak(style~ : HardBreakStyle, span~ : Span)
    Emphasis(marker~ : EmphasisMarker, children~ : Array[Inline], span~ : Span)
    Strong(marker~ : EmphasisMarker, children~ : Array[Inline], span~ : Span)
    Strikethrough(children~ : Array[Inline], span~ : Span)
    Code(content~ : String, backtick_count~ : Int, span~ : Span)
    Directive(name~ : String, label~ : String, attributes~ : Array[MarkdownAttribute], span~ : Span)
    WikiLink(target~ : String, label~ : String, fragment~ : String, span~ : Span)
    Link(children~ : Array[Inline], url~ : String, title~ : String, span~ : Span)
    RefLink(children~ : Array[Inline], label~ : String, style~ : ReferenceStyle, span~ : Span)
    Autolink(url~ : String, is_email~ : Bool, span~ : Span)
    Image(alt~ : String, url~ : String, title~ : String, span~ : Span)
    RefImage(alt~ : String, label~ : String, style~ : ReferenceStyle, span~ : Span)
    HtmlInline(html~ : String, span~ : Span)
    FootnoteReference(label~ : String, span~ : Span)
    }

    Inline-level nodes

    LinkDefinition

    pub(all) struct LinkDefinition {
    label : String
    url : String
    title : String
    span : Span
    }

    Link reference definition [label]: url "title"

    ListItem

    pub(all) struct ListItem {
    children : Array[Block]
    checked : Bool?
    marker_offset : Int
    content_offset : Int
    span : Span
    }

    List item

    MarkdownAttribute

    pub(all) struct MarkdownAttribute {
    name : String
    value : String
    } derive(Eq,
    Debug
    )

    One normalized block attribute from {#id .class key=value}.

    OrderedDelimiter

    pub(all) enum OrderedDelimiter {
    Dot
    Paren
    } derive(Eq,
    Debug
    )

    Ordered list delimiter: . or )

    ParseResult

    pub(all) struct ParseResult {
    document : Document
    definitions : Array[LinkDefinition]
    }

    Parse result

    ReferenceStyle

    pub(all) enum ReferenceStyle {
    Full
    Collapsed
    Shortcut
    } derive(Eq,
    Debug
    )

    Syntax used by a link or image reference.

    RenderOptions

    pub struct RenderOptions {
    code_highlighter : (CodeBlockInfo, String) -> String?
    math_block_renderer : (String) -> String?
    }

    Render options with plugin support

    RenderOptions::default

    fn RenderOptions::default() -> RenderOptions

    Create default render options

    RenderOptions::with_highlighter

    fn RenderOptions::with_highlighter(highlighter : (CodeBlockInfo, String) -> String) -> RenderOptions

    Create render options with code highlighter

    RenderOptions::with_math_block_renderer

    fn RenderOptions::with_math_block_renderer(self : RenderOptions, renderer : (String) -> String) -> RenderOptions

    The hook result is treated as trusted HTML; escaping belongs to the hook.

    RenderOptions::with_simple_highlighter

    fn RenderOptions::with_simple_highlighter(highlighter : (String, String) -> String) -> RenderOptions

    Create render options with simple highlighter (lang, code) -> String

    Scanner

    pub(all) struct Scanner {
    source : String
    chars : Array[Char]
    pos : Int
    len : Int
    utf16_offsets : Array[Int]?
    }

    Scanner state - uses UTF-16 indexing directly for BMP input and Array[Char] for non-BMP input. Handles Unicode correctly by tracking UTF-16 offsets for non-BMP characters.

    Scanner::advance

    fn Scanner::advance(self : Scanner, n : Int) -> Unit

    Advance position by n characters

    Scanner::consume

    fn Scanner::consume(self : Scanner) -> Char?

    Consume and return current character (O(1))

    Scanner::consume_str

    fn Scanner::consume_str(self : Scanner, s : String) -> Bool

    Match and consume a string

    Scanner::count_char

    fn Scanner::count_char(self : Scanner, c : Char) -> Int

    Count consecutive occurrences of a character from current position - O(1) per char

    Scanner::count_leading_spaces

    fn Scanner::count_leading_spaces(self : Scanner) -> Int

    Count leading spaces (without advancing) - O(1) per char

    Scanner::is_blank_line

    fn Scanner::is_blank_line(self : Scanner) -> Bool

    Check if current line is blank (only whitespace) - O(1) per char

    Scanner::is_eof

    fn Scanner::is_eof(self : Scanner) -> Bool

    Check if at end of input

    Scanner::matches

    fn Scanner::matches(self : Scanner, s : String) -> Bool

    Match a string at current position - optimized with Array[Char]

    Scanner::new

    fn Scanner::new(source : String) -> Scanner

    Create a new scanner

    Scanner::peek

    fn Scanner::peek(self : Scanner) -> Char?

    Peek current character (O(1) with Array[Char])

    Scanner::peek_at

    fn Scanner::peek_at(self : Scanner, offset : Int) -> Char?

    Peek character at offset from current position (O(1))

    Scanner::read_line

    fn Scanner::read_line(self : Scanner) -> String

    Read until end of line (not consuming newline) - O(1) per char

    Scanner::remaining

    fn Scanner::remaining(self : Scanner) -> String

    Get remaining substring from current position

    Scanner::restore

    fn Scanner::restore(self : Scanner, pos : Int) -> Unit

    Restore to saved position

    Scanner::save

    fn Scanner::save(self : Scanner) -> Int

    Save current position

    Scanner::skip_line

    fn Scanner::skip_line(self : Scanner) -> Unit

    Skip to next line (consuming newline if present) - O(1) per char

    Scanner::skip_spaces

    fn Scanner::skip_spaces(self : Scanner) -> Int

    Skip whitespace (space and tab only) - O(1) per char

    Scanner::substring

    fn Scanner::substring(self : Scanner, start : Int, end : Int) -> String

    Get substring from start to end position (code point indices)

    Span

    pub(all) struct Span {
    from : Int
    to : Int
    } derive(Eq,
    Debug
    )

    Using #valtype to avoid heap allocation for this frequently-used small struct
    impl Show for Span

    Span::empty

    fn Span::empty() -> Span

    Empty span (for synthetic nodes)

    Span::new

    fn Span::new(from : Int, to : Int) -> Span

    Create a span

    TableAlign

    pub(all) enum TableAlign {
    Left
    Center
    Right
    None
    } derive(Eq,
    Debug
    )

    Table alignment
    impl Show for TableAlign

    TableCell

    pub(all) struct TableCell {
    children : Array[Inline]
    span : Span
    }

    Table cell

    Trivia

    pub(all) struct Trivia {
    content : String
    } derive(Eq,
    Debug
    )

    Trivia represents non-semantic characters that should be preserved
    impl Show for Trivia

    Trivia::empty

    fn Trivia::empty() -> Trivia

    Trivia::new

    fn Trivia::new(content : String) -> Trivia

    char_is

    fn char_is(opt : Char?, c : Char) -> Bool

    Check if char option matches specific char

    char_is_digit

    fn char_is_digit(opt : Char?) -> Bool

    Check if char option is a digit

    is_alphanumeric

    fn is_alphanumeric(c : Char) -> Bool

    Check if character is alphanumeric

    is_digit

    fn is_digit(c : Char) -> Bool

    Check if character is a digit

    is_letter

    fn is_letter(c : Char) -> Bool

    Check if character is ASCII letter

    is_punctuation

    fn is_punctuation(c : Char) -> Bool

    Check if character is a punctuation mark

    is_unicode_punctuation

    fn is_unicode_punctuation(c : Char) -> Bool

    A "Unicode punctuation character" as CommonMark 0.31.2 defines it: anything in a punctuation (P*) or symbol (S*) general category.

    The ranges below cover the blocks that actually carry punctuation and symbols; letters, digits and marks are deliberately excluded so that flanking is decided correctly for ordinary text.

    is_unicode_whitespace

    fn is_unicode_whitespace(c : Char) -> Bool

    A "Unicode whitespace character" as CommonMark defines it: anything in the Zs general category, plus tab, line feed, form feed and carriage return.

    is_whitespace

    fn is_whitespace(c : Char) -> Bool

    Check if character is ASCII whitespace

    parse

    fn parse(source : String, strict? : Bool, wikilinks? : Bool) -> ParseResult

    Parse markdown source into CST.

    strict is accepted for backwards compatibility; the parser always follows the CommonMark rules now.

    parse_code_block_info

    fn parse_code_block_info(info : String) -> CodeBlockInfo

    Parse code block info string into components

    parse_inlines

    fn parse_inlines(text : String, strict? : Bool, wikilinks? : Bool) -> Array[Inline]

    Parse inline content from text.

    strict is accepted for backwards compatibility; the parser now always follows the CommonMark algorithm.