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.30/#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.30/#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 var canOpen, canClose bool
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 && c != bottom; {
166 if d, ok := c.(*Delimiter); ok {
167 closer = d
168 }
169 c = c.PreviousSibling()
170 }
171 }
172 } else {
173 closer = pc.FirstDelimiter()
174 }
175 if closer == nil {
176 pc.ClearDelimiters(bottom)
177 return
178 }
179 for closer != nil {
180 if !closer.CanClose {
181 closer = closer.NextDelimiter
182 continue
183 }
184 consume := 0
185 found := false
186 maybeOpener := false
187 var opener *Delimiter
188 for opener = closer.PreviousDelimiter; opener != nil && opener != bottom; opener = opener.PreviousDelimiter {
189 if opener.CanOpen && opener.Processor.CanOpenCloser(opener, closer) {
190 maybeOpener = true
191 consume = opener.CalcComsumption(closer)
192 if consume > 0 {
193 found = true
194 break
195 }
196 }
197 }
198 if !found {
199 next := closer.NextDelimiter
200 if !maybeOpener && !closer.CanOpen {
201 pc.RemoveDelimiter(closer)
202 }
203 closer = next
204 continue
205 }
206 opener.ConsumeCharacters(consume)
207 closer.ConsumeCharacters(consume)
208
209 node := opener.Processor.OnMatch(consume)
210
211 parent := opener.Parent()
212 child := opener.NextSibling()
213
214 for child != nil && child != closer {
215 next := child.NextSibling()
216 node.AppendChild(node, child)
217 child = next
218 }
219 parent.InsertAfter(parent, opener, node)
220
221 for c := opener.NextDelimiter; c != nil && c != closer; {
222 next := c.NextDelimiter
223 pc.RemoveDelimiter(c)
224 c = next
225 }
226
227 if opener.Length == 0 {
228 pc.RemoveDelimiter(opener)
229 }
230
231 if closer.Length == 0 {
232 next := closer.NextDelimiter
233 pc.RemoveDelimiter(closer)
234 closer = next
235 }
236 }
237 pc.ClearDelimiters(bottom)
238}