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