delimiter.go

  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}