// Package parser contains stuff that are related to parsing a Markdown text.
package parser import ( ) // A Reference interface represents a link reference in Markdown text. type Reference interface { // String implements Stringer. String() string // Label returns a label of the reference. Label() []byte // Destination returns a destination(URL) of the reference. Destination() []byte // Title returns a title of the reference. Title() []byte } type reference struct { label []byte destination []byte title []byte } // NewReference returns a new Reference. func (, , []byte) Reference { return &reference{, , } } func ( *reference) () []byte { return .label } func ( *reference) () []byte { return .destination } func ( *reference) () []byte { return .title } func ( *reference) () string { return fmt.Sprintf("Reference{Label:%s, Destination:%s, Title:%s}", .label, .destination, .title) } // An IDs interface is a collection of the element ids. type IDs interface { // Generate generates a new element id. Generate(value []byte, kind ast.NodeKind) []byte // Put puts a given element id to the used ids table. Put(value []byte) } type ids struct { values map[string]bool } func newIDs() IDs { return &ids{ values: map[string]bool{}, } } func ( *ids) ( []byte, ast.NodeKind) []byte { = util.TrimLeftSpace() = util.TrimRightSpace() := []byte{} for := 0; < len(); { := [] := util.UTF8Len() += int() if != 1 { continue } if util.IsAlphaNumeric() { if 'A' <= && <= 'Z' { += 'a' - 'A' } = append(, ) } else if util.IsSpace() || == '-' || == '_' { = append(, '-') } } if len() == 0 { if == ast.KindHeading { = []byte("heading") } else { = []byte("id") } } if , := .values[util.BytesToReadOnlyString()]; ! { .values[util.BytesToReadOnlyString()] = true return } for := 1; ; ++ { := fmt.Sprintf("%s-%d", , ) if , := .values[]; ! { .values[] = true return []byte() } } } func ( *ids) ( []byte) { .values[util.BytesToReadOnlyString()] = true } // ContextKey is a key that is used to set arbitrary values to the context. type ContextKey int // ContextKeyMax is a maximum value of the ContextKey. var ContextKeyMax ContextKey // NewContextKey return a new ContextKey value. func () ContextKey { ContextKeyMax++ return ContextKeyMax } // A Context interface holds a information that are necessary to parse // Markdown text. type Context interface { // String implements Stringer. String() string // Get returns a value associated with the given key. Get(ContextKey) interface{} // ComputeIfAbsent computes a value if a value associated with the given key is absent and returns the value. ComputeIfAbsent(ContextKey, func() interface{}) interface{} // Set sets the given value to the context. Set(ContextKey, interface{}) // AddReference adds the given reference to this context. AddReference(Reference) // Reference returns (a reference, true) if a reference associated with // the given label exists, otherwise (nil, false). Reference(label string) (Reference, bool) // References returns a list of references. References() []Reference // IDs returns a collection of the element ids. IDs() IDs // BlockOffset returns a first non-space character position on current line. // This value is valid only for BlockParser.Open. // BlockOffset returns -1 if current line is blank. BlockOffset() int // BlockOffset sets a first non-space character position on current line. // This value is valid only for BlockParser.Open. SetBlockOffset(int) // BlockIndent returns an indent width on current line. // This value is valid only for BlockParser.Open. // BlockIndent returns -1 if current line is blank. BlockIndent() int // BlockIndent sets an indent width on current line. // This value is valid only for BlockParser.Open. SetBlockIndent(int) // FirstDelimiter returns a first delimiter of the current delimiter list. FirstDelimiter() *Delimiter // LastDelimiter returns a last delimiter of the current delimiter list. LastDelimiter() *Delimiter // PushDelimiter appends the given delimiter to the tail of the current // delimiter list. PushDelimiter(delimiter *Delimiter) // RemoveDelimiter removes the given delimiter from the current delimiter list. RemoveDelimiter(d *Delimiter) // ClearDelimiters clears the current delimiter list. ClearDelimiters(bottom ast.Node) // OpenedBlocks returns a list of nodes that are currently in parsing. OpenedBlocks() []Block // SetOpenedBlocks sets a list of nodes that are currently in parsing. SetOpenedBlocks([]Block) // LastOpenedBlock returns a last node that is currently in parsing. LastOpenedBlock() Block // IsInLinkLabel returns true if current position seems to be in link label. IsInLinkLabel() bool } // A ContextConfig struct is a data structure that holds configuration of the Context. type ContextConfig struct { IDs IDs } // An ContextOption is a functional option type for the Context. type ContextOption func(*ContextConfig) // WithIDs is a functional option for the Context. func ( IDs) ContextOption { return func( *ContextConfig) { .IDs = } } type parseContext struct { store []interface{} ids IDs refs map[string]Reference blockOffset int blockIndent int delimiters *Delimiter lastDelimiter *Delimiter openedBlocks []Block } // NewContext returns a new Context. func ( ...ContextOption) Context { := &ContextConfig{ IDs: newIDs(), } for , := range { () } return &parseContext{ store: make([]interface{}, ContextKeyMax+1), refs: map[string]Reference{}, ids: .IDs, blockOffset: -1, blockIndent: -1, delimiters: nil, lastDelimiter: nil, openedBlocks: []Block{}, } } func ( *parseContext) ( ContextKey) interface{} { return .store[] } func ( *parseContext) ( ContextKey, func() interface{}) interface{} { := .store[] if == nil { = () .store[] = } return } func ( *parseContext) ( ContextKey, interface{}) { .store[] = } func ( *parseContext) () IDs { return .ids } func ( *parseContext) () int { return .blockOffset } func ( *parseContext) ( int) { .blockOffset = } func ( *parseContext) () int { return .blockIndent } func ( *parseContext) ( int) { .blockIndent = } func ( *parseContext) () *Delimiter { return .lastDelimiter } func ( *parseContext) () *Delimiter { return .delimiters } func ( *parseContext) ( *Delimiter) { if .delimiters == nil { .delimiters = .lastDelimiter = } else { := .lastDelimiter .lastDelimiter = .NextDelimiter = .PreviousDelimiter = } } func ( *parseContext) ( *Delimiter) { if .PreviousDelimiter == nil { .delimiters = .NextDelimiter } else { .PreviousDelimiter.NextDelimiter = .NextDelimiter if .NextDelimiter != nil { .NextDelimiter.PreviousDelimiter = .PreviousDelimiter } } if .NextDelimiter == nil { .lastDelimiter = .PreviousDelimiter } if .delimiters != nil { .delimiters.PreviousDelimiter = nil } if .lastDelimiter != nil { .lastDelimiter.NextDelimiter = nil } .NextDelimiter = nil .PreviousDelimiter = nil if .Length != 0 { ast.MergeOrReplaceTextSegment(.Parent(), , .Segment) } else { .Parent().RemoveChild(.Parent(), ) } } func ( *parseContext) ( ast.Node) { if .lastDelimiter == nil { return } var ast.Node for = .lastDelimiter; != nil && != ; { := .PreviousSibling() if , := .(*Delimiter); { .RemoveDelimiter() } = } } func ( *parseContext) ( Reference) { := util.ToLinkReference(.Label()) if , := .refs[]; ! { .refs[] = } } func ( *parseContext) ( string) (Reference, bool) { , := .refs[] return , } func ( *parseContext) () []Reference { := make([]Reference, 0, len(.refs)) for , := range .refs { = append(, ) } return } func ( *parseContext) () string { := []string{} for , := range .refs { = append(, .String()) } return fmt.Sprintf("Context{Store:%#v, Refs:%s}", .store, strings.Join(, ",")) } func ( *parseContext) () []Block { return .openedBlocks } func ( *parseContext) ( []Block) { .openedBlocks = } func ( *parseContext) () Block { if := len(.openedBlocks); != 0 { return .openedBlocks[-1] } return Block{} } func ( *parseContext) () bool { := .Get(linkLabelStateKey) return != nil } // State represents parser's state. // State is designed to use as a bit flag. type State int const ( // None is a default value of the [State]. None State = 1 << iota // Continue indicates parser can continue parsing. Continue // Close indicates parser cannot parse anymore. Close // HasChildren indicates parser may have child blocks. HasChildren // NoChildren indicates parser does not have child blocks. NoChildren // RequireParagraph indicates parser requires that the last node // must be a paragraph and is not converted to other nodes by // ParagraphTransformers. RequireParagraph ) // A Config struct is a data structure that holds configuration of the Parser. type Config struct { Options map[OptionName]interface{} BlockParsers util.PrioritizedSlice /*<BlockParser>*/ InlineParsers util.PrioritizedSlice /*<InlineParser>*/ ParagraphTransformers util.PrioritizedSlice /*<ParagraphTransformer>*/ ASTTransformers util.PrioritizedSlice /*<ASTTransformer>*/ EscapedSpace bool } // NewConfig returns a new Config. func () *Config { return &Config{ Options: map[OptionName]interface{}{}, BlockParsers: util.PrioritizedSlice{}, InlineParsers: util.PrioritizedSlice{}, ParagraphTransformers: util.PrioritizedSlice{}, ASTTransformers: util.PrioritizedSlice{}, } } // An Option interface is a functional option type for the Parser. type Option interface { SetParserOption(*Config) } // OptionName is a name of parser options. type OptionName string // Attribute is an option name that spacify attributes of elements. const optAttribute OptionName = "Attribute" type withAttribute struct { } func ( *withAttribute) ( *Config) { .Options[optAttribute] = true } // WithAttribute is a functional option that enables custom attributes. func () Option { return &withAttribute{} } // A Parser interface parses Markdown text into AST nodes. type Parser interface { // Parse parses the given Markdown text into AST nodes. Parse(reader text.Reader, opts ...ParseOption) ast.Node // AddOption adds the given option to this parser. AddOptions(...Option) } // A SetOptioner interface sets the given option to the object. type SetOptioner interface { // SetOption sets the given option to the object. // Unacceptable options may be passed. // Thus implementations must ignore unacceptable options. SetOption(name OptionName, value interface{}) } // A BlockParser interface parses a block level element like Paragraph, List, // Blockquote etc. type BlockParser interface { // Trigger returns a list of characters that triggers Parse method of // this parser. // If Trigger returns a nil, Open will be called with any lines. Trigger() []byte // Open parses the current line and returns a result of parsing. // // Open must not parse beyond the current line. // If Open has been able to parse the current line, Open must advance a reader // position by consumed byte length. // // If Open has not been able to parse the current line, Open should returns // (nil, NoChildren). If Open has been able to parse the current line, Open // should returns a new Block node and returns HasChildren or NoChildren. Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) // Continue parses the current line and returns a result of parsing. // // Continue must not parse beyond the current line. // If Continue has been able to parse the current line, Continue must advance // a reader position by consumed byte length. // // If Continue has not been able to parse the current line, Continue should // returns Close. If Continue has been able to parse the current line, // Continue should returns (Continue | NoChildren) or // (Continue | HasChildren) Continue(node ast.Node, reader text.Reader, pc Context) State // Close will be called when the parser returns Close. Close(node ast.Node, reader text.Reader, pc Context) // CanInterruptParagraph returns true if the parser can interrupt paragraphs, // otherwise false. CanInterruptParagraph() bool // CanAcceptIndentedLine returns true if the parser can open new node when // the given line is being indented more than 3 spaces. CanAcceptIndentedLine() bool } // An InlineParser interface parses an inline level element like CodeSpan, Link etc. type InlineParser interface { // Trigger returns a list of characters that triggers Parse method of // this parser. // Trigger characters must be a punctuation or a halfspace. // Halfspaces triggers this parser when character is any spaces characters or // a head of line Trigger() []byte // Parse parse the given block into an inline node. // // Parse can parse beyond the current line. // If Parse has been able to parse the current line, it must advance a reader // position by consumed byte length. Parse(parent ast.Node, block text.Reader, pc Context) ast.Node } // A CloseBlocker interface is a callback function that will be // called when block is closed in the inline parsing. type CloseBlocker interface { // CloseBlock will be called when a block is closed. CloseBlock(parent ast.Node, block text.Reader, pc Context) } // A ParagraphTransformer transforms parsed Paragraph nodes. // For example, link references are searched in parsed Paragraphs. type ParagraphTransformer interface { // Transform transforms the given paragraph. Transform(node *ast.Paragraph, reader text.Reader, pc Context) } // ASTTransformer transforms entire Markdown document AST tree. type ASTTransformer interface { // Transform transforms the given AST tree. Transform(node *ast.Document, reader text.Reader, pc Context) } // DefaultBlockParsers returns a new list of default BlockParsers. // Priorities of default BlockParsers are: // // SetextHeadingParser, 100 // ThematicBreakParser, 200 // ListParser, 300 // ListItemParser, 400 // CodeBlockParser, 500 // ATXHeadingParser, 600 // FencedCodeBlockParser, 700 // BlockquoteParser, 800 // HTMLBlockParser, 900 // ParagraphParser, 1000 func () []util.PrioritizedValue { return []util.PrioritizedValue{ util.Prioritized(NewSetextHeadingParser(), 100), util.Prioritized(NewThematicBreakParser(), 200), util.Prioritized(NewListParser(), 300), util.Prioritized(NewListItemParser(), 400), util.Prioritized(NewCodeBlockParser(), 500), util.Prioritized(NewATXHeadingParser(), 600), util.Prioritized(NewFencedCodeBlockParser(), 700), util.Prioritized(NewBlockquoteParser(), 800), util.Prioritized(NewHTMLBlockParser(), 900), util.Prioritized(NewParagraphParser(), 1000), } } // DefaultInlineParsers returns a new list of default InlineParsers. // Priorities of default InlineParsers are: // // CodeSpanParser, 100 // LinkParser, 200 // AutoLinkParser, 300 // RawHTMLParser, 400 // EmphasisParser, 500 func () []util.PrioritizedValue { return []util.PrioritizedValue{ util.Prioritized(NewCodeSpanParser(), 100), util.Prioritized(NewLinkParser(), 200), util.Prioritized(NewAutoLinkParser(), 300), util.Prioritized(NewRawHTMLParser(), 400), util.Prioritized(NewEmphasisParser(), 500), } } // DefaultParagraphTransformers returns a new list of default ParagraphTransformers. // Priorities of default ParagraphTransformers are: // // LinkReferenceParagraphTransformer, 100 func () []util.PrioritizedValue { return []util.PrioritizedValue{ util.Prioritized(LinkReferenceParagraphTransformer, 100), } } // A Block struct holds a node and correspond parser pair. type Block struct { // Node is a BlockNode. Node ast.Node // Parser is a BlockParser. Parser BlockParser } type parser struct { options map[OptionName]interface{} blockParsers [256][]BlockParser freeBlockParsers []BlockParser inlineParsers [256][]InlineParser closeBlockers []CloseBlocker paragraphTransformers []ParagraphTransformer astTransformers []ASTTransformer escapedSpace bool config *Config initSync sync.Once } type withBlockParsers struct { value []util.PrioritizedValue } func ( *withBlockParsers) ( *Config) { .BlockParsers = append(.BlockParsers, .value...) } // WithBlockParsers is a functional option that allow you to add // BlockParsers to the parser. func ( ...util.PrioritizedValue) Option { return &withBlockParsers{} } type withInlineParsers struct { value []util.PrioritizedValue } func ( *withInlineParsers) ( *Config) { .InlineParsers = append(.InlineParsers, .value...) } // WithInlineParsers is a functional option that allow you to add // InlineParsers to the parser. func ( ...util.PrioritizedValue) Option { return &withInlineParsers{} } type withParagraphTransformers struct { value []util.PrioritizedValue } func ( *withParagraphTransformers) ( *Config) { .ParagraphTransformers = append(.ParagraphTransformers, .value...) } // WithParagraphTransformers is a functional option that allow you to add // ParagraphTransformers to the parser. func ( ...util.PrioritizedValue) Option { return &withParagraphTransformers{} } type withASTTransformers struct { value []util.PrioritizedValue } func ( *withASTTransformers) ( *Config) { .ASTTransformers = append(.ASTTransformers, .value...) } // WithASTTransformers is a functional option that allow you to add // ASTTransformers to the parser. func ( ...util.PrioritizedValue) Option { return &withASTTransformers{} } type withEscapedSpace struct { } func ( *withEscapedSpace) ( *Config) { .EscapedSpace = true } // WithEscapedSpace is a functional option indicates that a '\' escaped half-space(0x20) should not trigger parsers. func () Option { return &withEscapedSpace{} } type withOption struct { name OptionName value interface{} } func ( *withOption) ( *Config) { .Options[.name] = .value } // WithOption is a functional option that allow you to set // an arbitrary option to the parser. func ( OptionName, interface{}) Option { return &withOption{, } } // NewParser returns a new Parser with given options. func ( ...Option) Parser { := NewConfig() for , := range { .SetParserOption() } := &parser{ options: map[OptionName]interface{}{}, config: , } return } func ( *parser) ( ...Option) { for , := range { .SetParserOption(.config) } } func ( *parser) ( util.PrioritizedValue, map[OptionName]interface{}) { , := .Value.(BlockParser) if ! { panic(fmt.Sprintf("%v is not a BlockParser", .Value)) } := .Trigger() , := .Value.(SetOptioner) if { for , := range { .SetOption(, ) } } if == nil { .freeBlockParsers = append(.freeBlockParsers, ) } else { for , := range { if .blockParsers[] == nil { .blockParsers[] = []BlockParser{} } .blockParsers[] = append(.blockParsers[], ) } } } func ( *parser) ( util.PrioritizedValue, map[OptionName]interface{}) { , := .Value.(InlineParser) if ! { panic(fmt.Sprintf("%v is not a InlineParser", .Value)) } := .Trigger() , := .Value.(SetOptioner) if { for , := range { .SetOption(, ) } } if , := .(CloseBlocker); { .closeBlockers = append(.closeBlockers, ) } for , := range { if .inlineParsers[] == nil { .inlineParsers[] = []InlineParser{} } .inlineParsers[] = append(.inlineParsers[], ) } } func ( *parser) ( util.PrioritizedValue, map[OptionName]interface{}) { , := .Value.(ParagraphTransformer) if ! { panic(fmt.Sprintf("%v is not a ParagraphTransformer", .Value)) } , := .Value.(SetOptioner) if { for , := range { .SetOption(, ) } } .paragraphTransformers = append(.paragraphTransformers, ) } func ( *parser) ( util.PrioritizedValue, map[OptionName]interface{}) { , := .Value.(ASTTransformer) if ! { panic(fmt.Sprintf("%v is not a ASTTransformer", .Value)) } , := .Value.(SetOptioner) if { for , := range { .SetOption(, ) } } .astTransformers = append(.astTransformers, ) } // A ParseConfig struct is a data structure that holds configuration of the Parser.Parse. type ParseConfig struct { Context Context } // A ParseOption is a functional option type for the Parser.Parse. type ParseOption func(c *ParseConfig) // WithContext is a functional option that allow you to override // a default context. func ( Context) ParseOption { return func( *ParseConfig) { .Context = } } func ( *parser) ( text.Reader, ...ParseOption) ast.Node { .initSync.Do(func() { .config.BlockParsers.Sort() for , := range .config.BlockParsers { .addBlockParser(, .config.Options) } for := range .blockParsers { if .blockParsers[] != nil { .blockParsers[] = append(.blockParsers[], .freeBlockParsers...) } } .config.InlineParsers.Sort() for , := range .config.InlineParsers { .addInlineParser(, .config.Options) } .config.ParagraphTransformers.Sort() for , := range .config.ParagraphTransformers { .addParagraphTransformer(, .config.Options) } .config.ASTTransformers.Sort() for , := range .config.ASTTransformers { .addASTTransformer(, .config.Options) } .escapedSpace = .config.EscapedSpace .config = nil }) := &ParseConfig{} for , := range { () } if .Context == nil { .Context = NewContext() } := .Context := ast.NewDocument() .parseBlocks(, , ) := text.NewBlockReader(.Source(), nil) .walkBlock(, func( ast.Node) { .parseBlock(, , ) }) for , := range .astTransformers { .Transform(, , ) } // root.Dump(reader.Source(), 0) return } func ( *parser) ( *ast.Paragraph, text.Reader, Context) bool { for , := range .paragraphTransformers { .Transform(, , ) if .Parent() == nil { return true } } return false } func ( *parser) (, int, text.Reader, Context) { := .OpenedBlocks() for := ; >= ; -- { := [].Node , := .(*ast.Paragraph) if && .Parent() != nil { .transformParagraph(, , ) } if .Parent() != nil { // closes only if node has not been transformed [].Parser.Close([].Node, , ) } } if == len()-1 { = [0:] } else { = append([0:], [+1:]...) } .SetOpenedBlocks() } type blockOpenResult int const ( paragraphContinuation blockOpenResult = iota + 1 newBlocksOpened noBlocksOpened ) func ( *parser) ( ast.Node, bool, text.Reader, Context) blockOpenResult { := blockOpenResult(noBlocksOpened) := false := .LastOpenedBlock() if .Node != nil { = ast.IsParagraph(.Node) } : var []BlockParser , := .PeekLine() , := util.IndentWidth(, .LineOffset()) if >= len() { .SetBlockOffset(-1) .SetBlockIndent(-1) } else { .SetBlockOffset() .SetBlockIndent() } if == nil || [0] == '\n' { goto } = .freeBlockParsers if < len() { = .blockParsers[[]] if == nil { = .freeBlockParsers } } if == nil { goto } for , := range { if && == noBlocksOpened && !.CanInterruptParagraph() { continue } if > 3 && !.CanAcceptIndentedLine() { continue } = .LastOpenedBlock() := .Node , := .Open(, , ) if != nil { // Parser requires last node to be a paragraph. // With table extension: // // 0 // -: // - // // '-' on 3rd line seems a Setext heading because 1st and 2nd lines // are being paragraph when the Settext heading parser tries to parse the 3rd // line. // But 1st line and 2nd line are a table. Thus this paragraph will be transformed // by a paragraph transformer. So this text should be converted to a table and // an empty list. if &RequireParagraph != 0 { if == .LastChild() { // Opened paragraph may be transformed by ParagraphTransformers in // closeBlocks(). .Parser.Close(, , ) := .OpenedBlocks() .SetOpenedBlocks([0 : len()-1]) if .transformParagraph(.(*ast.Paragraph), , ) { // Paragraph has been transformed. // So this parser is considered as failing. = false goto } } } .SetBlankPreviousLines() if != nil && .Parent() == nil { := len(.OpenedBlocks()) - 1 .closeBlocks(, , , ) } .AppendChild(, ) = newBlocksOpened := Block{, } .SetOpenedBlocks(append(.OpenedBlocks(), )) if &HasChildren != 0 { = goto // try child block } break // no children, can not open more blocks on this line } } : if == noBlocksOpened && { := .Parser.Continue(.Node, , ) if &Continue != 0 { = paragraphContinuation } } return } type lineStat struct { lineNum int level int isBlank bool } func isBlankLine(, int, []lineStat) bool { := true for := len() - 1 - ; >= 0; -- { = false := [] if .lineNum == { if .level < && .isBlank { return true } else if .level == { return .isBlank } } if .lineNum < { return } } return } func ( *parser) ( ast.Node, text.Reader, Context) { .SetOpenedBlocks([]Block{}) := make([]lineStat, 0, 128) var bool for { // process blocks separated by blank lines , , := .SkipBlankLines() if ! { return } , := .Position() if != 0 { = [0:0] := len(.OpenedBlocks()) for := 0; < ; ++ { = append(, lineStat{ - 1, , != 0}) } } = isBlankLine(-1, 0, ) // first, we try to open blocks if .openBlocks(, , , ) != newBlocksOpened { return } .AdvanceLine() for { // process opened blocks line by line := .OpenedBlocks() := len() if == 0 { break } := - 1 for := 0; < ; ++ { := [] , := .PeekLine() if == nil { .closeBlocks(, 0, , ) .AdvanceLine() return } , := .Position() = append(, lineStat{, , util.IsBlank()}) // If node is a paragraph, p.openBlocks determines whether it is continuable. // So we do not process paragraphs here. if !ast.IsParagraph(.Node) { := .Parser.Continue(.Node, , ) if &Continue != 0 { // When current node is a container block and has no children, // we try to open new child nodes if &HasChildren != 0 && == { = isBlankLine(-1, , ) .openBlocks(.Node, , , ) break } continue } } // current node may be closed or lazy continuation = isBlankLine(-1, , ) := if != 0 { = [-1].Node } := [].Node := .openBlocks(, , , ) if != paragraphContinuation { // lastNode is a paragraph and was transformed by the paragraph // transformers. if [].Node != { -- } .closeBlocks(, , , ) } break } .AdvanceLine() } } } func ( *parser) ( ast.Node, func( ast.Node)) { for := .FirstChild(); != nil; = .NextSibling() { .(, ) } () } const ( lineBreakHard uint8 = 1 << iota lineBreakSoft lineBreakVisible ) func ( *parser) ( text.BlockReader, ast.Node, Context) { if .IsRaw() { return } := false := .Source() .Reset(.Lines()) for { : , := .PeekLine() if == nil { break } := len() var uint8 := [-1] == '\n' if (( >= 3 && [-2] == '\\' && [-3] != '\\') || ( == 2 && [-2] == '\\')) && { // ends with \\n -= 2 |= lineBreakHard | lineBreakVisible } else if (( >= 4 && [-3] == '\\' && [-2] == '\r' && [-4] != '\\') || ( == 3 && [-3] == '\\' && [-2] == '\r')) && { // ends with \\r\n -= 3 |= lineBreakHard | lineBreakVisible } else if >= 3 && [-3] == ' ' && [-2] == ' ' && { // ends with [space][space]\n -= 3 |= lineBreakHard } else if >= 4 && [-4] == ' ' && [-3] == ' ' && [-2] == '\r' && { // ends with [space][space]\r\n -= 4 |= lineBreakHard } else if { // If the line ends with a newline character, but it is not a hardlineBreak, then it is a softLinebreak // If the line ends with a hardlineBreak, then it cannot end with a softLinebreak // See https://spec.commonmark.org/0.30/#soft-line-breaks |= lineBreakSoft } , := .Position() := 0 for := 0; < ; ++ { := [] if == '\n' { break } := util.IsSpace() && != '\r' && != '\n' := util.IsPunct() if ( && !) || && !( && .escapedSpace) || == 0 { := if || ( == 0 && !) { = ' ' } := .inlineParsers[] if != nil { .Advance() = 0 , := .Position() if != 0 { , := .Position() ast.MergeOrAppendTextSegment(, .Between()) _, = .Position() } var ast.Node for , := range { = .Parse(, , ) if != nil { break } .SetPosition(, ) } if != nil { .AppendChild(, ) goto } } } if { = false ++ continue } if == '\\' { = true ++ continue } = false ++ } if != 0 { .Advance() } , := .Position() if != { continue } := .Between() var *ast.Text if &(lineBreakHard|lineBreakVisible) == lineBreakHard|lineBreakVisible { = ast.NewTextSegment() } else { = ast.NewTextSegment(.TrimRightSpace()) } .SetSoftLineBreak(&lineBreakSoft != 0) .SetHardLineBreak(&lineBreakHard != 0) .AppendChild(, ) .AdvanceLine() } ProcessDelimiters(nil, ) for , := range .closeBlockers { .CloseBlock(, , ) } }