package uritemplate
import (
"bytes"
"unicode"
"unicode/utf8"
)
type matcher struct {
prog *prog
list1 threadList
list2 threadList
matched bool
cap map [string ][]int
input string
}
func (m *matcher ) at (pos int ) (rune , int , bool ) {
if l := len (m .input ); pos < l {
c := m .input [pos ]
if c < utf8 .RuneSelf {
return rune (c ), 1 , pos +1 < l
}
r , size := utf8 .DecodeRuneInString (m .input [pos :])
return r , size , pos +size < l
}
return -1 , 0 , false
}
func (m *matcher ) add (list *threadList , pc uint32 , pos int , next bool , cap map [string ][]int ) {
if i := list .sparse [pc ]; i < uint32 (len (list .dense )) && list .dense [i ].pc == pc {
return
}
n := len (list .dense )
list .dense = list .dense [:n +1 ]
list .sparse [pc ] = uint32 (n )
e := &list .dense [n ]
e .pc = pc
e .t = nil
op := &m .prog .op [pc ]
switch op .code {
default :
panic ("unhandled opcode" )
case opRune , opRuneClass , opEnd :
e .t = &thread {
op : &m .prog .op [pc ],
cap : make (map [string ][]int , len (m .cap )),
}
for k , v := range cap {
e .t .cap [k ] = make ([]int , len (v ))
copy (e .t .cap [k ], v )
}
case opLineBegin :
if pos == 0 {
m .add (list , pc +1 , pos , next , cap )
}
case opLineEnd :
if !next {
m .add (list , pc +1 , pos , next , cap )
}
case opCapStart , opCapEnd :
ocap := make (map [string ][]int , len (m .cap ))
for k , v := range cap {
ocap [k ] = make ([]int , len (v ))
copy (ocap [k ], v )
}
ocap [op .name ] = append (ocap [op .name ], pos )
m .add (list , pc +1 , pos , next , ocap )
case opSplit :
m .add (list , pc +1 , pos , next , cap )
m .add (list , op .i , pos , next , cap )
case opJmp :
m .add (list , op .i , pos , next , cap )
case opJmpIfNotDefined :
m .add (list , pc +1 , pos , next , cap )
m .add (list , op .i , pos , next , cap )
case opJmpIfNotFirst :
m .add (list , pc +1 , pos , next , cap )
m .add (list , op .i , pos , next , cap )
case opJmpIfNotEmpty :
m .add (list , op .i , pos , next , cap )
m .add (list , pc +1 , pos , next , cap )
case opNoop :
m .add (list , pc +1 , pos , next , cap )
}
}
func (m *matcher ) step (clist *threadList , nlist *threadList , r rune , pos int , nextPos int , next bool ) {
debug .Printf ("===== %q =====" , string (r ))
for i := 0 ; i < len (clist .dense ); i ++ {
e := clist .dense [i ]
if debug {
var buf bytes .Buffer
dumpProg (&buf , m .prog , e .pc )
debug .Printf ("\n%s" , buf .String ())
}
if e .t == nil {
continue
}
t := e .t
op := t .op
switch op .code {
default :
panic ("unhandled opcode" )
case opRune :
if op .r == r {
m .add (nlist , e .pc +1 , nextPos , next , t .cap )
}
case opRuneClass :
ret := false
if !ret && op .rc &runeClassU == runeClassU {
ret = ret || unicode .Is (rangeUnreserved , r )
}
if !ret && op .rc &runeClassR == runeClassR {
ret = ret || unicode .Is (rangeReserved , r )
}
if !ret && op .rc &runeClassPctE == runeClassPctE {
ret = ret || unicode .Is (unicode .ASCII_Hex_Digit , r )
}
if ret {
m .add (nlist , e .pc +1 , nextPos , next , t .cap )
}
case opEnd :
m .matched = true
for k , v := range t .cap {
m .cap [k ] = make ([]int , len (v ))
copy (m .cap [k ], v )
}
clist .dense = clist .dense [:0 ]
}
}
clist .dense = clist .dense [:0 ]
}
func (m *matcher ) match () bool {
pos := 0
clist , nlist := &m .list1 , &m .list2
for {
if len (clist .dense ) == 0 && m .matched {
break
}
r , width , next := m .at (pos )
if !m .matched {
m .add (clist , 0 , pos , next , m .cap )
}
m .step (clist , nlist , r , pos , pos +width , next )
if width < 1 {
break
}
pos += width
clist , nlist = nlist , clist
}
return m .matched
}
func (tmpl *Template ) Match (expansion string ) Values {
tmpl .mu .Lock ()
if tmpl .prog == nil {
c := compiler {}
c .init ()
c .compile (tmpl )
tmpl .prog = c .prog
}
prog := tmpl .prog
tmpl .mu .Unlock ()
n := len (prog .op )
m := matcher {
prog : prog ,
list1 : threadList {
dense : make ([]threadEntry , 0 , n ),
sparse : make ([]uint32 , n ),
},
list2 : threadList {
dense : make ([]threadEntry , 0 , n ),
sparse : make ([]uint32 , n ),
},
cap : make (map [string ][]int , prog .numCap ),
input : expansion ,
}
if !m .match () {
return nil
}
match := make (Values , len (m .cap ))
for name , indices := range m .cap {
v := Value {V : make ([]string , len (indices )/2 )}
for i := range v .V {
v .V [i ] = pctDecode (expansion [indices [2 *i ]:indices [2 *i +1 ]])
}
if len (v .V ) == 1 {
v .T = ValueTypeString
} else {
v .T = ValueTypeList
}
match [name ] = v
}
return match
}
The pages are generated with Golds v0.8.4 . (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 .