package parser
import (
"fmt"
"strings"
"sync"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
type Reference interface {
String () string
Label () []byte
Destination () []byte
Title () []byte
}
type reference struct {
label []byte
destination []byte
title []byte
}
func NewReference (label , destination , title []byte ) Reference {
return &reference {label , destination , title }
}
func (r *reference ) Label () []byte {
return r .label
}
func (r *reference ) Destination () []byte {
return r .destination
}
func (r *reference ) Title () []byte {
return r .title
}
func (r *reference ) String () string {
return fmt .Sprintf ("Reference{Label:%s, Destination:%s, Title:%s}" , r .label , r .destination , r .title )
}
type IDs interface {
Generate (value []byte , kind ast .NodeKind ) []byte
Put (value []byte )
}
type ids struct {
values map [string ]bool
}
func newIDs() IDs {
return &ids {
values : map [string ]bool {},
}
}
func (s *ids ) Generate (value []byte , kind ast .NodeKind ) []byte {
value = util .TrimLeftSpace (value )
value = util .TrimRightSpace (value )
result := []byte {}
for i := 0 ; i < len (value ); {
v := value [i ]
l := util .UTF8Len (v )
i += int (l )
if l != 1 {
continue
}
if util .IsAlphaNumeric (v ) {
if 'A' <= v && v <= 'Z' {
v += 'a' - 'A'
}
result = append (result , v )
} else if util .IsSpace (v ) || v == '-' || v == '_' {
result = append (result , '-' )
}
}
if len (result ) == 0 {
if kind == ast .KindHeading {
result = []byte ("heading" )
} else {
result = []byte ("id" )
}
}
if _ , ok := s .values [util .BytesToReadOnlyString (result )]; !ok {
s .values [util .BytesToReadOnlyString (result )] = true
return result
}
for i := 1 ; ; i ++ {
newResult := fmt .Sprintf ("%s-%d" , result , i )
if _ , ok := s .values [newResult ]; !ok {
s .values [newResult ] = true
return []byte (newResult )
}
}
}
func (s *ids ) Put (value []byte ) {
s .values [util .BytesToReadOnlyString (value )] = true
}
type ContextKey int
var ContextKeyMax ContextKey
func NewContextKey () ContextKey {
ContextKeyMax ++
return ContextKeyMax
}
type Context interface {
String () string
Get (ContextKey ) interface {}
ComputeIfAbsent (ContextKey , func () interface {}) interface {}
Set (ContextKey , interface {})
AddReference (Reference )
Reference (label string ) (Reference , bool )
References () []Reference
IDs () IDs
BlockOffset () int
SetBlockOffset (int )
BlockIndent () int
SetBlockIndent (int )
FirstDelimiter () *Delimiter
LastDelimiter () *Delimiter
PushDelimiter (delimiter *Delimiter )
RemoveDelimiter (d *Delimiter )
ClearDelimiters (bottom ast .Node )
OpenedBlocks () []Block
SetOpenedBlocks ([]Block )
LastOpenedBlock () Block
IsInLinkLabel () bool
}
type ContextConfig struct {
IDs IDs
}
type ContextOption func (*ContextConfig )
func WithIDs (ids IDs ) ContextOption {
return func (c *ContextConfig ) {
c .IDs = ids
}
}
type parseContext struct {
store []interface {}
ids IDs
refs map [string ]Reference
blockOffset int
blockIndent int
delimiters *Delimiter
lastDelimiter *Delimiter
openedBlocks []Block
}
func NewContext (options ...ContextOption ) Context {
cfg := &ContextConfig {
IDs : newIDs (),
}
for _ , option := range options {
option (cfg )
}
return &parseContext {
store : make ([]interface {}, ContextKeyMax +1 ),
refs : map [string ]Reference {},
ids : cfg .IDs ,
blockOffset : -1 ,
blockIndent : -1 ,
delimiters : nil ,
lastDelimiter : nil ,
openedBlocks : []Block {},
}
}
func (p *parseContext ) Get (key ContextKey ) interface {} {
return p .store [key ]
}
func (p *parseContext ) ComputeIfAbsent (key ContextKey , f func () interface {}) interface {} {
v := p .store [key ]
if v == nil {
v = f ()
p .store [key ] = v
}
return v
}
func (p *parseContext ) Set (key ContextKey , value interface {}) {
p .store [key ] = value
}
func (p *parseContext ) IDs () IDs {
return p .ids
}
func (p *parseContext ) BlockOffset () int {
return p .blockOffset
}
func (p *parseContext ) SetBlockOffset (v int ) {
p .blockOffset = v
}
func (p *parseContext ) BlockIndent () int {
return p .blockIndent
}
func (p *parseContext ) SetBlockIndent (v int ) {
p .blockIndent = v
}
func (p *parseContext ) LastDelimiter () *Delimiter {
return p .lastDelimiter
}
func (p *parseContext ) FirstDelimiter () *Delimiter {
return p .delimiters
}
func (p *parseContext ) PushDelimiter (d *Delimiter ) {
if p .delimiters == nil {
p .delimiters = d
p .lastDelimiter = d
} else {
l := p .lastDelimiter
p .lastDelimiter = d
l .NextDelimiter = d
d .PreviousDelimiter = l
}
}
func (p *parseContext ) RemoveDelimiter (d *Delimiter ) {
if d .PreviousDelimiter == nil {
p .delimiters = d .NextDelimiter
} else {
d .PreviousDelimiter .NextDelimiter = d .NextDelimiter
if d .NextDelimiter != nil {
d .NextDelimiter .PreviousDelimiter = d .PreviousDelimiter
}
}
if d .NextDelimiter == nil {
p .lastDelimiter = d .PreviousDelimiter
}
if p .delimiters != nil {
p .delimiters .PreviousDelimiter = nil
}
if p .lastDelimiter != nil {
p .lastDelimiter .NextDelimiter = nil
}
d .NextDelimiter = nil
d .PreviousDelimiter = nil
if d .Length != 0 {
ast .MergeOrReplaceTextSegment (d .Parent (), d , d .Segment )
} else {
d .Parent ().RemoveChild (d .Parent (), d )
}
}
func (p *parseContext ) ClearDelimiters (bottom ast .Node ) {
if p .lastDelimiter == nil {
return
}
var c ast .Node
for c = p .lastDelimiter ; c != nil && c != bottom ; {
prev := c .PreviousSibling ()
if d , ok := c .(*Delimiter ); ok {
p .RemoveDelimiter (d )
}
c = prev
}
}
func (p *parseContext ) AddReference (ref Reference ) {
key := util .ToLinkReference (ref .Label ())
if _ , ok := p .refs [key ]; !ok {
p .refs [key ] = ref
}
}
func (p *parseContext ) Reference (label string ) (Reference , bool ) {
v , ok := p .refs [label ]
return v , ok
}
func (p *parseContext ) References () []Reference {
ret := make ([]Reference , 0 , len (p .refs ))
for _ , v := range p .refs {
ret = append (ret , v )
}
return ret
}
func (p *parseContext ) String () string {
refs := []string {}
for _ , r := range p .refs {
refs = append (refs , r .String ())
}
return fmt .Sprintf ("Context{Store:%#v, Refs:%s}" , p .store , strings .Join (refs , "," ))
}
func (p *parseContext ) OpenedBlocks () []Block {
return p .openedBlocks
}
func (p *parseContext ) SetOpenedBlocks (v []Block ) {
p .openedBlocks = v
}
func (p *parseContext ) LastOpenedBlock () Block {
if l := len (p .openedBlocks ); l != 0 {
return p .openedBlocks [l -1 ]
}
return Block {}
}
func (p *parseContext ) IsInLinkLabel () bool {
tlist := p .Get (linkLabelStateKey )
return tlist != nil
}
type State int
const (
None State = 1 << iota
Continue
Close
HasChildren
NoChildren
RequireParagraph
)
type Config struct {
Options map [OptionName ]interface {}
BlockParsers util .PrioritizedSlice
InlineParsers util .PrioritizedSlice
ParagraphTransformers util .PrioritizedSlice
ASTTransformers util .PrioritizedSlice
EscapedSpace bool
}
func NewConfig () *Config {
return &Config {
Options : map [OptionName ]interface {}{},
BlockParsers : util .PrioritizedSlice {},
InlineParsers : util .PrioritizedSlice {},
ParagraphTransformers : util .PrioritizedSlice {},
ASTTransformers : util .PrioritizedSlice {},
}
}
type Option interface {
SetParserOption (*Config )
}
type OptionName string
const optAttribute OptionName = "Attribute"
type withAttribute struct {
}
func (o *withAttribute ) SetParserOption (c *Config ) {
c .Options [optAttribute ] = true
}
func WithAttribute () Option {
return &withAttribute {}
}
type Parser interface {
Parse (reader text .Reader , opts ...ParseOption ) ast .Node
AddOptions (...Option )
}
type SetOptioner interface {
SetOption (name OptionName , value interface {})
}
type BlockParser interface {
Trigger () []byte
Open (parent ast .Node , reader text .Reader , pc Context ) (ast .Node , State )
Continue (node ast .Node , reader text .Reader , pc Context ) State
Close (node ast .Node , reader text .Reader , pc Context )
CanInterruptParagraph () bool
CanAcceptIndentedLine () bool
}
type InlineParser interface {
Trigger () []byte
Parse (parent ast .Node , block text .Reader , pc Context ) ast .Node
}
type CloseBlocker interface {
CloseBlock (parent ast .Node , block text .Reader , pc Context )
}
type ParagraphTransformer interface {
Transform (node *ast .Paragraph , reader text .Reader , pc Context )
}
type ASTTransformer interface {
Transform (node *ast .Document , reader text .Reader , pc Context )
}
func DefaultBlockParsers () []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 ),
}
}
func DefaultInlineParsers () []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 ),
}
}
func DefaultParagraphTransformers () []util .PrioritizedValue {
return []util .PrioritizedValue {
util .Prioritized (LinkReferenceParagraphTransformer , 100 ),
}
}
type Block struct {
Node ast .Node
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 (o *withBlockParsers ) SetParserOption (c *Config ) {
c .BlockParsers = append (c .BlockParsers , o .value ...)
}
func WithBlockParsers (bs ...util .PrioritizedValue ) Option {
return &withBlockParsers {bs }
}
type withInlineParsers struct {
value []util .PrioritizedValue
}
func (o *withInlineParsers ) SetParserOption (c *Config ) {
c .InlineParsers = append (c .InlineParsers , o .value ...)
}
func WithInlineParsers (bs ...util .PrioritizedValue ) Option {
return &withInlineParsers {bs }
}
type withParagraphTransformers struct {
value []util .PrioritizedValue
}
func (o *withParagraphTransformers ) SetParserOption (c *Config ) {
c .ParagraphTransformers = append (c .ParagraphTransformers , o .value ...)
}
func WithParagraphTransformers (ps ...util .PrioritizedValue ) Option {
return &withParagraphTransformers {ps }
}
type withASTTransformers struct {
value []util .PrioritizedValue
}
func (o *withASTTransformers ) SetParserOption (c *Config ) {
c .ASTTransformers = append (c .ASTTransformers , o .value ...)
}
func WithASTTransformers (ps ...util .PrioritizedValue ) Option {
return &withASTTransformers {ps }
}
type withEscapedSpace struct {
}
func (o *withEscapedSpace ) SetParserOption (c *Config ) {
c .EscapedSpace = true
}
func WithEscapedSpace () Option {
return &withEscapedSpace {}
}
type withOption struct {
name OptionName
value interface {}
}
func (o *withOption ) SetParserOption (c *Config ) {
c .Options [o .name ] = o .value
}
func WithOption (name OptionName , value interface {}) Option {
return &withOption {name , value }
}
func NewParser (options ...Option ) Parser {
config := NewConfig ()
for _ , opt := range options {
opt .SetParserOption (config )
}
p := &parser {
options : map [OptionName ]interface {}{},
config : config ,
}
return p
}
func (p *parser ) AddOptions (opts ...Option ) {
for _ , opt := range opts {
opt .SetParserOption (p .config )
}
}
func (p *parser ) addBlockParser (v util .PrioritizedValue , options map [OptionName ]interface {}) {
bp , ok := v .Value .(BlockParser )
if !ok {
panic (fmt .Sprintf ("%v is not a BlockParser" , v .Value ))
}
tcs := bp .Trigger ()
so , ok := v .Value .(SetOptioner )
if ok {
for oname , ovalue := range options {
so .SetOption (oname , ovalue )
}
}
if tcs == nil {
p .freeBlockParsers = append (p .freeBlockParsers , bp )
} else {
for _ , tc := range tcs {
if p .blockParsers [tc ] == nil {
p .blockParsers [tc ] = []BlockParser {}
}
p .blockParsers [tc ] = append (p .blockParsers [tc ], bp )
}
}
}
func (p *parser ) addInlineParser (v util .PrioritizedValue , options map [OptionName ]interface {}) {
ip , ok := v .Value .(InlineParser )
if !ok {
panic (fmt .Sprintf ("%v is not a InlineParser" , v .Value ))
}
tcs := ip .Trigger ()
so , ok := v .Value .(SetOptioner )
if ok {
for oname , ovalue := range options {
so .SetOption (oname , ovalue )
}
}
if cb , ok := ip .(CloseBlocker ); ok {
p .closeBlockers = append (p .closeBlockers , cb )
}
for _ , tc := range tcs {
if p .inlineParsers [tc ] == nil {
p .inlineParsers [tc ] = []InlineParser {}
}
p .inlineParsers [tc ] = append (p .inlineParsers [tc ], ip )
}
}
func (p *parser ) addParagraphTransformer (v util .PrioritizedValue , options map [OptionName ]interface {}) {
pt , ok := v .Value .(ParagraphTransformer )
if !ok {
panic (fmt .Sprintf ("%v is not a ParagraphTransformer" , v .Value ))
}
so , ok := v .Value .(SetOptioner )
if ok {
for oname , ovalue := range options {
so .SetOption (oname , ovalue )
}
}
p .paragraphTransformers = append (p .paragraphTransformers , pt )
}
func (p *parser ) addASTTransformer (v util .PrioritizedValue , options map [OptionName ]interface {}) {
at , ok := v .Value .(ASTTransformer )
if !ok {
panic (fmt .Sprintf ("%v is not a ASTTransformer" , v .Value ))
}
so , ok := v .Value .(SetOptioner )
if ok {
for oname , ovalue := range options {
so .SetOption (oname , ovalue )
}
}
p .astTransformers = append (p .astTransformers , at )
}
type ParseConfig struct {
Context Context
}
type ParseOption func (c *ParseConfig )
func WithContext (context Context ) ParseOption {
return func (c *ParseConfig ) {
c .Context = context
}
}
func (p *parser ) Parse (reader text .Reader , opts ...ParseOption ) ast .Node {
p .initSync .Do (func () {
p .config .BlockParsers .Sort ()
for _ , v := range p .config .BlockParsers {
p .addBlockParser (v , p .config .Options )
}
for i := range p .blockParsers {
if p .blockParsers [i ] != nil {
p .blockParsers [i ] = append (p .blockParsers [i ], p .freeBlockParsers ...)
}
}
p .config .InlineParsers .Sort ()
for _ , v := range p .config .InlineParsers {
p .addInlineParser (v , p .config .Options )
}
p .config .ParagraphTransformers .Sort ()
for _ , v := range p .config .ParagraphTransformers {
p .addParagraphTransformer (v , p .config .Options )
}
p .config .ASTTransformers .Sort ()
for _ , v := range p .config .ASTTransformers {
p .addASTTransformer (v , p .config .Options )
}
p .escapedSpace = p .config .EscapedSpace
p .config = nil
})
c := &ParseConfig {}
for _ , opt := range opts {
opt (c )
}
if c .Context == nil {
c .Context = NewContext ()
}
pc := c .Context
root := ast .NewDocument ()
p .parseBlocks (root , reader , pc )
blockReader := text .NewBlockReader (reader .Source (), nil )
p .walkBlock (root , func (node ast .Node ) {
p .parseBlock (blockReader , node , pc )
})
for _ , at := range p .astTransformers {
at .Transform (root , reader , pc )
}
return root
}
func (p *parser ) transformParagraph (node *ast .Paragraph , reader text .Reader , pc Context ) bool {
for _ , pt := range p .paragraphTransformers {
pt .Transform (node , reader , pc )
if node .Parent () == nil {
return true
}
}
return false
}
func (p *parser ) closeBlocks (from , to int , reader text .Reader , pc Context ) {
blocks := pc .OpenedBlocks ()
for i := from ; i >= to ; i -- {
node := blocks [i ].Node
paragraph , ok := node .(*ast .Paragraph )
if ok && node .Parent () != nil {
p .transformParagraph (paragraph , reader , pc )
}
if node .Parent () != nil {
blocks [i ].Parser .Close (blocks [i ].Node , reader , pc )
}
}
if from == len (blocks )-1 {
blocks = blocks [0 :to ]
} else {
blocks = append (blocks [0 :to ], blocks [from +1 :]...)
}
pc .SetOpenedBlocks (blocks )
}
type blockOpenResult int
const (
paragraphContinuation blockOpenResult = iota + 1
newBlocksOpened
noBlocksOpened
)
func (p *parser ) openBlocks (parent ast .Node , blankLine bool , reader text .Reader , pc Context ) blockOpenResult {
result := blockOpenResult (noBlocksOpened )
continuable := false
lastBlock := pc .LastOpenedBlock ()
if lastBlock .Node != nil {
continuable = ast .IsParagraph (lastBlock .Node )
}
retry :
var bps []BlockParser
line , _ := reader .PeekLine ()
w , pos := util .IndentWidth (line , reader .LineOffset ())
if w >= len (line ) {
pc .SetBlockOffset (-1 )
pc .SetBlockIndent (-1 )
} else {
pc .SetBlockOffset (pos )
pc .SetBlockIndent (w )
}
if line == nil || line [0 ] == '\n' {
goto continuable
}
bps = p .freeBlockParsers
if pos < len (line ) {
bps = p .blockParsers [line [pos ]]
if bps == nil {
bps = p .freeBlockParsers
}
}
if bps == nil {
goto continuable
}
for _ , bp := range bps {
if continuable && result == noBlocksOpened && !bp .CanInterruptParagraph () {
continue
}
if w > 3 && !bp .CanAcceptIndentedLine () {
continue
}
lastBlock = pc .LastOpenedBlock ()
last := lastBlock .Node
node , state := bp .Open (parent , reader , pc )
if node != nil {
if state &RequireParagraph != 0 {
if last == parent .LastChild () {
lastBlock .Parser .Close (last , reader , pc )
blocks := pc .OpenedBlocks ()
pc .SetOpenedBlocks (blocks [0 : len (blocks )-1 ])
if p .transformParagraph (last .(*ast .Paragraph ), reader , pc ) {
continuable = false
goto retry
}
}
}
node .SetBlankPreviousLines (blankLine )
if last != nil && last .Parent () == nil {
lastPos := len (pc .OpenedBlocks ()) - 1
p .closeBlocks (lastPos , lastPos , reader , pc )
}
parent .AppendChild (parent , node )
result = newBlocksOpened
be := Block {node , bp }
pc .SetOpenedBlocks (append (pc .OpenedBlocks (), be ))
if state &HasChildren != 0 {
parent = node
goto retry
}
break
}
}
continuable :
if result == noBlocksOpened && continuable {
state := lastBlock .Parser .Continue (lastBlock .Node , reader , pc )
if state &Continue != 0 {
result = paragraphContinuation
}
}
return result
}
type lineStat struct {
lineNum int
level int
isBlank bool
}
func isBlankLine(lineNum , level int , stats []lineStat ) bool {
ret := true
for i := len (stats ) - 1 - level ; i >= 0 ; i -- {
ret = false
s := stats [i ]
if s .lineNum == lineNum {
if s .level < level && s .isBlank {
return true
} else if s .level == level {
return s .isBlank
}
}
if s .lineNum < lineNum {
return ret
}
}
return ret
}
func (p *parser ) parseBlocks (parent ast .Node , reader text .Reader , pc Context ) {
pc .SetOpenedBlocks ([]Block {})
blankLines := make ([]lineStat , 0 , 128 )
var isBlank bool
for {
_ , lines , ok := reader .SkipBlankLines ()
if !ok {
return
}
lineNum , _ := reader .Position ()
if lines != 0 {
blankLines = blankLines [0 :0 ]
l := len (pc .OpenedBlocks ())
for i := 0 ; i < l ; i ++ {
blankLines = append (blankLines , lineStat {lineNum - 1 , i , lines != 0 })
}
}
isBlank = isBlankLine (lineNum -1 , 0 , blankLines )
if p .openBlocks (parent , isBlank , reader , pc ) != newBlocksOpened {
return
}
reader .AdvanceLine ()
for {
openedBlocks := pc .OpenedBlocks ()
l := len (openedBlocks )
if l == 0 {
break
}
lastIndex := l - 1
for i := 0 ; i < l ; i ++ {
be := openedBlocks [i ]
line , _ := reader .PeekLine ()
if line == nil {
p .closeBlocks (lastIndex , 0 , reader , pc )
reader .AdvanceLine ()
return
}
lineNum , _ := reader .Position ()
blankLines = append (blankLines , lineStat {lineNum , i , util .IsBlank (line )})
if !ast .IsParagraph (be .Node ) {
state := be .Parser .Continue (be .Node , reader , pc )
if state &Continue != 0 {
if state &HasChildren != 0 && i == lastIndex {
isBlank = isBlankLine (lineNum -1 , i , blankLines )
p .openBlocks (be .Node , isBlank , reader , pc )
break
}
continue
}
}
isBlank = isBlankLine (lineNum -1 , i , blankLines )
thisParent := parent
if i != 0 {
thisParent = openedBlocks [i -1 ].Node
}
lastNode := openedBlocks [lastIndex ].Node
result := p .openBlocks (thisParent , isBlank , reader , pc )
if result != paragraphContinuation {
if openedBlocks [lastIndex ].Node != lastNode {
lastIndex --
}
p .closeBlocks (lastIndex , i , reader , pc )
}
break
}
reader .AdvanceLine ()
}
}
}
func (p *parser ) walkBlock (block ast .Node , cb func (node ast .Node )) {
for c := block .FirstChild (); c != nil ; c = c .NextSibling () {
p .walkBlock (c , cb )
}
cb (block )
}
const (
lineBreakHard uint8 = 1 << iota
lineBreakSoft
lineBreakVisible
)
func (p *parser ) parseBlock (block text .BlockReader , parent ast .Node , pc Context ) {
if parent .IsRaw () {
return
}
escaped := false
source := block .Source ()
block .Reset (parent .Lines ())
for {
retry :
line , _ := block .PeekLine ()
if line == nil {
break
}
lineLength := len (line )
var lineBreakFlags uint8
hasNewLine := line [lineLength -1 ] == '\n'
if ((lineLength >= 3 && line [lineLength -2 ] == '\\' &&
line [lineLength -3 ] != '\\' ) || (lineLength == 2 && line [lineLength -2 ] == '\\' )) && hasNewLine {
lineLength -= 2
lineBreakFlags |= lineBreakHard | lineBreakVisible
} else if ((lineLength >= 4 && line [lineLength -3 ] == '\\' && line [lineLength -2 ] == '\r' &&
line [lineLength -4 ] != '\\' ) || (lineLength == 3 && line [lineLength -3 ] == '\\' && line [lineLength -2 ] == '\r' )) &&
hasNewLine {
lineLength -= 3
lineBreakFlags |= lineBreakHard | lineBreakVisible
} else if lineLength >= 3 && line [lineLength -3 ] == ' ' && line [lineLength -2 ] == ' ' &&
hasNewLine {
lineLength -= 3
lineBreakFlags |= lineBreakHard
} else if lineLength >= 4 && line [lineLength -4 ] == ' ' && line [lineLength -3 ] == ' ' &&
line [lineLength -2 ] == '\r' && hasNewLine {
lineLength -= 4
lineBreakFlags |= lineBreakHard
} else if hasNewLine {
lineBreakFlags |= lineBreakSoft
}
l , startPosition := block .Position ()
n := 0
for i := 0 ; i < lineLength ; i ++ {
c := line [i ]
if c == '\n' {
break
}
isSpace := util .IsSpace (c ) && c != '\r' && c != '\n'
isPunct := util .IsPunct (c )
if (isPunct && !escaped ) || isSpace && !(escaped && p .escapedSpace ) || i == 0 {
parserChar := c
if isSpace || (i == 0 && !isPunct ) {
parserChar = ' '
}
ips := p .inlineParsers [parserChar ]
if ips != nil {
block .Advance (n )
n = 0
savedLine , savedPosition := block .Position ()
if i != 0 {
_ , currentPosition := block .Position ()
ast .MergeOrAppendTextSegment (parent , startPosition .Between (currentPosition ))
_, startPosition = block .Position ()
}
var inlineNode ast .Node
for _ , ip := range ips {
inlineNode = ip .Parse (parent , block , pc )
if inlineNode != nil {
break
}
block .SetPosition (savedLine , savedPosition )
}
if inlineNode != nil {
parent .AppendChild (parent , inlineNode )
goto retry
}
}
}
if escaped {
escaped = false
n ++
continue
}
if c == '\\' {
escaped = true
n ++
continue
}
escaped = false
n ++
}
if n != 0 {
block .Advance (n )
}
currentL , currentPosition := block .Position ()
if l != currentL {
continue
}
diff := startPosition .Between (currentPosition )
var text *ast .Text
if lineBreakFlags &(lineBreakHard |lineBreakVisible ) == lineBreakHard |lineBreakVisible {
text = ast .NewTextSegment (diff )
} else {
text = ast .NewTextSegment (diff .TrimRightSpace (source ))
}
text .SetSoftLineBreak (lineBreakFlags &lineBreakSoft != 0 )
text .SetHardLineBreak (lineBreakFlags &lineBreakHard != 0 )
parent .AppendChild (parent , text )
block .AdvanceLine ()
}
ProcessDelimiters (nil , pc )
for _ , ip := range p .closeBlockers {
ip .CloseBlock (parent , block , pc )
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .