1package parser 2 3import ( 4 "fmt" 5 "strings" 6 7 "github.com/yuin/goldmark/ast" 8 "github.com/yuin/goldmark/text" 9 "github.com/yuin/goldmark/util" 10) 11 12// A DelimiterProcessor interface provides a set of functions about 13// Delimiter nodes. 14type DelimiterProcessor interface { 15 // IsDelimiter returns true if given character is a delimiter, otherwise false. 16 IsDelimiter(byte) bool 17 18 // CanOpenCloser returns true if given opener can close given closer, otherwise false. 19 CanOpenCloser(opener, closer *Delimiter) bool 20 21 // OnMatch will be called when new matched delimiter found. 22 // OnMatch should return a new Node correspond to the matched delimiter. 23 OnMatch(consumes int) ast.Node 24} 25 26// A Delimiter struct represents a delimiter like '*' of the Markdown text. 27type Delimiter struct { 28 ast.BaseInline 29 30 Segment text.Segment 31 32 // CanOpen is set true if this delimiter can open a span for a new node. 33 // See https://spec.commonmark.org/0.29/#can-open-emphasis for details. 34 CanOpen bool 35 36 // CanClose is set true if this delimiter can close a span for a new node. 37 // See https://spec.commonmark.org/0.29/#can-open-emphasis for details. 38 CanClose bool 39 40 // Length is a remaining length of this delimiter. 41 Length int 42 43 // OriginalLength is a original length of this delimiter. 44 OriginalLength int 45 46 // Char is a character of this delimiter. 47 Char byte 48 49 // PreviousDelimiter is a previous sibling delimiter node of this delimiter. 50 PreviousDelimiter *Delimiter 51 52 // NextDelimiter is a next sibling delimiter node of this delimiter. 53 NextDelimiter *Delimiter 54 55 // Processor is a DelimiterProcessor associated with this delimiter. 56 Processor DelimiterProcessor 57} 58 59// Inline implements Inline.Inline. 60func (d *Delimiter) Inline() {} 61 62// Dump implements Node.Dump. 63func (d *Delimiter) Dump(source []byte, level int) { 64 fmt.Printf("%sDelimiter: \"%s\"\n", strings.Repeat(" ", level), string(d.Text(source))) 65} 66 67var kindDelimiter = ast.NewNodeKind("Delimiter") 68 69// Kind implements Node.Kind 70func (d *Delimiter) Kind() ast.NodeKind { 71 return kindDelimiter 72} 73 74// Text implements Node.Text 75func (d *Delimiter) Text(source []byte) []byte { 76 return d.Segment.Value(source) 77} 78 79// ConsumeCharacters consumes delimiters. 80func (d *Delimiter) ConsumeCharacters(n int) { 81 d.Length -= n 82 d.Segment = d.Segment.WithStop(d.Segment.Start + d.Length) 83} 84 85// CalcComsumption calculates how many characters should be used for opening 86// a new span correspond to given closer. 87func (d *Delimiter) CalcComsumption(closer *Delimiter) int { 88 if (d.CanClose || closer.CanOpen) && (d.OriginalLength+closer.OriginalLength)%3 == 0 && closer.OriginalLength%3 != 0 { 89 return 0 90 } 91 if d.Length >= 2 && closer.Length >= 2 { 92 return 2 93 } 94 return 1 95} 96 97// NewDelimiter returns a new Delimiter node. 98func NewDelimiter(canOpen, canClose bool, length int, char byte, processor DelimiterProcessor) *Delimiter { 99 c := &Delimiter{ 100 BaseInline: ast.BaseInline{}, 101 CanOpen: canOpen, 102 CanClose: canClose, 103 Length: length, 104 OriginalLength: length, 105 Char: char, 106 PreviousDelimiter: nil, 107 NextDelimiter: nil, 108 Processor: processor, 109 } 110 return c 111} 112 113// ScanDelimiter scans a delimiter by given DelimiterProcessor. 114func ScanDelimiter(line []byte, before rune, min int, processor DelimiterProcessor) *Delimiter { 115 i := 0 116 c := line[i] 117 j := i 118 if !processor.IsDelimiter(c) { 119 return nil 120 } 121 for ; j < len(line) && c == line[j]; j++ { 122 } 123 if (j - i) >= min { 124 after := rune(' ') 125 if j != len(line) { 126 after = util.ToRune(line, j) 127 } 128 129 canOpen, canClose := false, false 130 beforeIsPunctuation := util.IsPunctRune(before) 131 beforeIsWhitespace := util.IsSpaceRune(before) 132 afterIsPunctuation := util.IsPunctRune(after) 133 afterIsWhitespace := util.IsSpaceRune(after) 134 135 isLeft := !afterIsWhitespace && 136 (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation) 137 isRight := !beforeIsWhitespace && 138 (!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation) 139 140 if line[i] == '_' { 141 canOpen = isLeft && (!isRight || beforeIsPunctuation) 142 canClose = isRight && (!isLeft || afterIsPunctuation) 143 } else { 144 canOpen = isLeft 145 canClose = isRight 146 } 147 return NewDelimiter(canOpen, canClose, j-i, c, processor) 148 } 149 return nil 150} 151 152// ProcessDelimiters processes the delimiter list in the context. 153// Processing will be stop when reaching the bottom. 154// 155// If you implement an inline parser that can have other inline nodes as 156// children, you should call this function when nesting span has closed. 157func ProcessDelimiters(bottom ast.Node, pc Context) { 158 lastDelimiter := pc.LastDelimiter() 159 if lastDelimiter == nil { 160 return 161 } 162 var closer *Delimiter 163 if bottom != nil { 164 if bottom != lastDelimiter { 165 for c := lastDelimiter.PreviousSibling(); c != nil; { 166 if d, ok := c.(*Delimiter); ok { 167 closer = d 168 } 169 prev := c.PreviousSibling() 170 if prev == bottom { 171 break 172 } 173 c = prev 174 } 175 } 176 } else { 177 closer = pc.FirstDelimiter() 178 } 179 if closer == nil { 180 pc.ClearDelimiters(bottom) 181 return 182 } 183 for closer != nil { 184 if !closer.CanClose { 185 closer = closer.NextDelimiter 186 continue 187 } 188 consume := 0 189 found := false 190 maybeOpener := false 191 var opener *Delimiter 192 for opener = closer.PreviousDelimiter; opener != nil; opener = opener.PreviousDelimiter { 193 if opener.CanOpen && opener.Processor.CanOpenCloser(opener, closer) { 194 maybeOpener = true 195 consume = opener.CalcComsumption(closer) 196 if consume > 0 { 197 found = true 198 break 199 } 200 } 201 } 202 if !found { 203 if !maybeOpener && !closer.CanOpen { 204 pc.RemoveDelimiter(closer) 205 } 206 closer = closer.NextDelimiter 207 continue 208 } 209 opener.ConsumeCharacters(consume) 210 closer.ConsumeCharacters(consume) 211 212 node := opener.Processor.OnMatch(consume) 213 214 parent := opener.Parent() 215 child := opener.NextSibling() 216 217 for child != nil && child != closer { 218 next := child.NextSibling() 219 node.AppendChild(node, child) 220 child = next 221 } 222 parent.InsertAfter(parent, opener, node) 223 224 for c := opener.NextDelimiter; c != nil && c != closer; { 225 next := c.NextDelimiter 226 pc.RemoveDelimiter(c) 227 c = next 228 } 229 230 if opener.Length == 0 { 231 pc.RemoveDelimiter(opener) 232 } 233 234 if closer.Length == 0 { 235 next := closer.NextDelimiter 236 pc.RemoveDelimiter(closer) 237 closer = next 238 } 239 } 240 pc.ClearDelimiters(bottom) 241} 242