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 }