gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

delimiter.go (6339B)


      1 package parser
      2 
      3 import (
      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.
     14 type 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.
     27 type 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.
     60 func (d *Delimiter) Inline() {}
     61 
     62 // Dump implements Node.Dump.
     63 func (d *Delimiter) Dump(source []byte, level int) {
     64 	fmt.Printf("%sDelimiter: \"%s\"\n", strings.Repeat("    ", level), string(d.Text(source)))
     65 }
     66 
     67 var kindDelimiter = ast.NewNodeKind("Delimiter")
     68 
     69 // Kind implements Node.Kind
     70 func (d *Delimiter) Kind() ast.NodeKind {
     71 	return kindDelimiter
     72 }
     73 
     74 // Text implements Node.Text
     75 func (d *Delimiter) Text(source []byte) []byte {
     76 	return d.Segment.Value(source)
     77 }
     78 
     79 // ConsumeCharacters consumes delimiters.
     80 func (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.
     87 func (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.
     98 func 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.
    114 func 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.
    157 func 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 }