package parser
import (
"fmt"
"strings"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
type DelimiterProcessor interface {
IsDelimiter (byte ) bool
CanOpenCloser (opener, closer *Delimiter ) bool
OnMatch (consumes int ) ast .Node
}
type Delimiter struct {
ast .BaseInline
Segment text .Segment
CanOpen bool
CanClose bool
Length int
OriginalLength int
Char byte
PreviousDelimiter *Delimiter
NextDelimiter *Delimiter
Processor DelimiterProcessor
}
func (d *Delimiter ) Inline () {}
func (d *Delimiter ) Dump (source []byte , level int ) {
fmt .Printf ("%sDelimiter: \"%s\"\n" , strings .Repeat (" " , level ), string (d .Text (source )))
}
var kindDelimiter = ast .NewNodeKind ("Delimiter" )
func (d *Delimiter ) Kind () ast .NodeKind {
return kindDelimiter
}
func (d *Delimiter ) Text (source []byte ) []byte {
return d .Segment .Value (source )
}
func (d *Delimiter ) ConsumeCharacters (n int ) {
d .Length -= n
d .Segment = d .Segment .WithStop (d .Segment .Start + d .Length )
}
func (d *Delimiter ) CalcComsumption (closer *Delimiter ) int {
if (d .CanClose || closer .CanOpen ) && (d .OriginalLength +closer .OriginalLength )%3 == 0 && closer .OriginalLength %3 != 0 {
return 0
}
if d .Length >= 2 && closer .Length >= 2 {
return 2
}
return 1
}
func NewDelimiter (canOpen , canClose bool , length int , char byte , processor DelimiterProcessor ) *Delimiter {
c := &Delimiter {
BaseInline : ast .BaseInline {},
CanOpen : canOpen ,
CanClose : canClose ,
Length : length ,
OriginalLength : length ,
Char : char ,
PreviousDelimiter : nil ,
NextDelimiter : nil ,
Processor : processor ,
}
return c
}
func ScanDelimiter (line []byte , before rune , min int , processor DelimiterProcessor ) *Delimiter {
i := 0
c := line [i ]
j := i
if !processor .IsDelimiter (c ) {
return nil
}
for ; j < len (line ) && c == line [j ]; j ++ {
}
if (j - i ) >= min {
after := rune (' ' )
if j != len (line ) {
after = util .ToRune (line , j )
}
var canOpen , canClose bool
beforeIsPunctuation := util .IsPunctRune (before )
beforeIsWhitespace := util .IsSpaceRune (before )
afterIsPunctuation := util .IsPunctRune (after )
afterIsWhitespace := util .IsSpaceRune (after )
isLeft := !afterIsWhitespace &&
(!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation )
isRight := !beforeIsWhitespace &&
(!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation )
if line [i ] == '_' {
canOpen = isLeft && (!isRight || beforeIsPunctuation )
canClose = isRight && (!isLeft || afterIsPunctuation )
} else {
canOpen = isLeft
canClose = isRight
}
return NewDelimiter (canOpen , canClose , j -i , c , processor )
}
return nil
}
func ProcessDelimiters (bottom ast .Node , pc Context ) {
lastDelimiter := pc .LastDelimiter ()
if lastDelimiter == nil {
return
}
var closer *Delimiter
if bottom != nil {
if bottom != lastDelimiter {
for c := lastDelimiter .PreviousSibling (); c != nil && c != bottom ; {
if d , ok := c .(*Delimiter ); ok {
closer = d
}
c = c .PreviousSibling ()
}
}
} else {
closer = pc .FirstDelimiter ()
}
if closer == nil {
pc .ClearDelimiters (bottom )
return
}
for closer != nil {
if !closer .CanClose {
closer = closer .NextDelimiter
continue
}
consume := 0
found := false
maybeOpener := false
var opener *Delimiter
for opener = closer .PreviousDelimiter ; opener != nil && opener != bottom ; opener = opener .PreviousDelimiter {
if opener .CanOpen && opener .Processor .CanOpenCloser (opener , closer ) {
maybeOpener = true
consume = opener .CalcComsumption (closer )
if consume > 0 {
found = true
break
}
}
}
if !found {
next := closer .NextDelimiter
if !maybeOpener && !closer .CanOpen {
pc .RemoveDelimiter (closer )
}
closer = next
continue
}
opener .ConsumeCharacters (consume )
closer .ConsumeCharacters (consume )
node := opener .Processor .OnMatch (consume )
parent := opener .Parent ()
child := opener .NextSibling ()
for child != nil && child != closer {
next := child .NextSibling ()
node .AppendChild (node , child )
child = next
}
parent .InsertAfter (parent , opener , node )
for c := opener .NextDelimiter ; c != nil && c != closer ; {
next := c .NextDelimiter
pc .RemoveDelimiter (c )
c = next
}
if opener .Length == 0 {
pc .RemoveDelimiter (opener )
}
if closer .Length == 0 {
next := closer .NextDelimiter
pc .RemoveDelimiter (closer )
closer = next
}
}
pc .ClearDelimiters (bottom )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .