285 lines
6.0 KiB
Go
285 lines
6.0 KiB
Go
|
package gluare
|
||
|
|
||
|
import (
|
||
|
"github.com/yuin/gopher-lua"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
func Loader(L *lua.LState) int {
|
||
|
mod := L.SetFuncs(L.NewTable(), exports)
|
||
|
mod.RawSetString("gmatch", L.NewClosure(reGmatch, L.NewFunction(reGmatchIter)))
|
||
|
L.Push(mod)
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
var exports = map[string]lua.LGFunction{
|
||
|
"find": reFind,
|
||
|
"gsub": reGsub,
|
||
|
"match": reMatch,
|
||
|
"quote": reQuote,
|
||
|
}
|
||
|
|
||
|
func reQuote(L *lua.LState) int {
|
||
|
str := L.CheckString(1)
|
||
|
L.Push(lua.LString(regexp.QuoteMeta(str)))
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
func reFind(L *lua.LState) int {
|
||
|
str := L.CheckString(1)
|
||
|
pattern := L.CheckString(2)
|
||
|
if len(str) == 0 && len(pattern) == 0 {
|
||
|
L.Push(lua.LNumber(1))
|
||
|
L.Push(lua.LNumber(0))
|
||
|
return 2
|
||
|
}
|
||
|
init := luaIndex2StringIndex(str, L.OptInt(3, 1), true)
|
||
|
plain := false
|
||
|
if L.GetTop() == 4 {
|
||
|
plain = lua.LVAsBool(L.Get(4))
|
||
|
}
|
||
|
|
||
|
if plain {
|
||
|
pos := strings.Index(str[init:], pattern)
|
||
|
if pos < 0 {
|
||
|
L.Push(lua.LNil)
|
||
|
return 1
|
||
|
}
|
||
|
L.Push(lua.LNumber(init+pos) + 1)
|
||
|
L.Push(lua.LNumber(init + pos + len(pattern)))
|
||
|
return 2
|
||
|
}
|
||
|
|
||
|
re, err := regexp.Compile(pattern)
|
||
|
if err != nil {
|
||
|
L.RaiseError(err.Error())
|
||
|
}
|
||
|
stroffset := str[init:]
|
||
|
positions := re.FindStringSubmatchIndex(stroffset)
|
||
|
if positions == nil || (len(positions) > 1 && positions[2] < 0) {
|
||
|
L.Push(lua.LNil)
|
||
|
return 1
|
||
|
}
|
||
|
npos := len(positions)
|
||
|
L.Push(lua.LNumber(init+positions[0]) + 1)
|
||
|
L.Push(lua.LNumber(init + positions[npos-1]))
|
||
|
for i := 2; i < npos; i += 2 {
|
||
|
L.Push(lua.LString(stroffset[positions[i]:positions[i+1]]))
|
||
|
}
|
||
|
return npos/2 + 1
|
||
|
}
|
||
|
|
||
|
func reGsub(L *lua.LState) int {
|
||
|
str := L.CheckString(1)
|
||
|
pat := L.CheckString(2)
|
||
|
L.CheckTypes(3, lua.LTString, lua.LTTable, lua.LTFunction)
|
||
|
repl := L.CheckAny(3)
|
||
|
limit := L.OptInt(4, -1)
|
||
|
|
||
|
re, err := regexp.Compile(pat)
|
||
|
if err != nil {
|
||
|
L.RaiseError(err.Error())
|
||
|
}
|
||
|
matches := re.FindAllStringSubmatchIndex(str, limit)
|
||
|
if matches == nil || len(matches) == 0 {
|
||
|
L.SetTop(1)
|
||
|
L.Push(lua.LNumber(0))
|
||
|
return 2
|
||
|
}
|
||
|
switch lv := repl.(type) {
|
||
|
case lua.LString:
|
||
|
L.Push(lua.LString(reGsubStr(str, re, string(lv), matches)))
|
||
|
case *lua.LTable:
|
||
|
L.Push(lua.LString(reGsubTable(L, str, lv, matches)))
|
||
|
case *lua.LFunction:
|
||
|
L.Push(lua.LString(reGsubFunc(L, str, lv, matches)))
|
||
|
}
|
||
|
L.Push(lua.LNumber(len(matches)))
|
||
|
return 2
|
||
|
}
|
||
|
|
||
|
type replaceInfo struct {
|
||
|
Indicies []int
|
||
|
String string
|
||
|
}
|
||
|
|
||
|
func reGsubDoReplace(str string, info []replaceInfo) string {
|
||
|
offset := 0
|
||
|
buf := []byte(str)
|
||
|
for _, replace := range info {
|
||
|
oldlen := len(buf)
|
||
|
b1 := append([]byte(""), buf[0:offset+replace.Indicies[0]]...)
|
||
|
b2 := []byte("")
|
||
|
index2 := offset + replace.Indicies[1]
|
||
|
if index2 <= len(buf) {
|
||
|
b2 = append(b2, buf[index2:len(buf)]...)
|
||
|
}
|
||
|
buf = append(b1, replace.String...)
|
||
|
buf = append(buf, b2...)
|
||
|
offset += len(buf) - oldlen
|
||
|
}
|
||
|
return string(buf)
|
||
|
}
|
||
|
|
||
|
func reGsubStr(str string, re *regexp.Regexp, repl string, matches [][]int) string {
|
||
|
infoList := make([]replaceInfo, 0, len(matches))
|
||
|
for _, match := range matches {
|
||
|
start, end := match[0], match[1]
|
||
|
if end < 0 {
|
||
|
continue
|
||
|
}
|
||
|
buf := make([]byte, 0, end-start)
|
||
|
buf = re.ExpandString(buf, repl, str, match)
|
||
|
infoList = append(infoList, replaceInfo{[]int{start, end}, string(buf)})
|
||
|
}
|
||
|
|
||
|
return reGsubDoReplace(str, infoList)
|
||
|
}
|
||
|
|
||
|
func reGsubTable(L *lua.LState, str string, repl *lua.LTable, matches [][]int) string {
|
||
|
infoList := make([]replaceInfo, 0, len(matches))
|
||
|
for _, match := range matches {
|
||
|
var key string
|
||
|
start, end := match[0], match[1]
|
||
|
if end < 0 {
|
||
|
continue
|
||
|
}
|
||
|
if len(match) > 2 { // has captures
|
||
|
key = str[match[2]:match[3]]
|
||
|
} else {
|
||
|
key = str[match[0]:match[1]]
|
||
|
}
|
||
|
value := L.GetField(repl, key)
|
||
|
if !lua.LVIsFalse(value) {
|
||
|
infoList = append(infoList, replaceInfo{[]int{start, end}, lua.LVAsString(value)})
|
||
|
}
|
||
|
}
|
||
|
return reGsubDoReplace(str, infoList)
|
||
|
}
|
||
|
|
||
|
func reGsubFunc(L *lua.LState, str string, repl *lua.LFunction, matches [][]int) string {
|
||
|
infoList := make([]replaceInfo, 0, len(matches))
|
||
|
for _, match := range matches {
|
||
|
start, end := match[0], match[1]
|
||
|
if end < 0 {
|
||
|
continue
|
||
|
}
|
||
|
L.Push(repl)
|
||
|
nargs := 0
|
||
|
if len(match) > 2 { // has captures
|
||
|
for i := 2; i < len(match); i += 2 {
|
||
|
L.Push(lua.LString(str[match[i]:match[i+1]]))
|
||
|
nargs++
|
||
|
}
|
||
|
} else {
|
||
|
L.Push(lua.LString(str[start:end]))
|
||
|
nargs++
|
||
|
}
|
||
|
L.Call(nargs, 1)
|
||
|
ret := L.Get(-1)
|
||
|
L.Pop(1)
|
||
|
if !lua.LVIsFalse(ret) {
|
||
|
infoList = append(infoList, replaceInfo{[]int{start, end}, lua.LVAsString(ret)})
|
||
|
}
|
||
|
}
|
||
|
return reGsubDoReplace(str, infoList)
|
||
|
}
|
||
|
|
||
|
type reMatchData struct {
|
||
|
str string
|
||
|
pos int
|
||
|
matches [][]int
|
||
|
}
|
||
|
|
||
|
func reGmatchIter(L *lua.LState) int {
|
||
|
md := L.CheckUserData(1).Value.(*reMatchData)
|
||
|
str := md.str
|
||
|
matches := md.matches
|
||
|
idx := md.pos
|
||
|
md.pos += 1
|
||
|
if idx == len(matches) {
|
||
|
return 0
|
||
|
}
|
||
|
L.Push(L.Get(1))
|
||
|
match := matches[idx]
|
||
|
if len(match) == 2 {
|
||
|
L.Push(lua.LString(str[match[0]:match[1]]))
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
for i := 2; i < len(match); i += 2 {
|
||
|
L.Push(lua.LString(str[match[i]:match[i+1]]))
|
||
|
}
|
||
|
return len(match)/2 - 1
|
||
|
}
|
||
|
|
||
|
func reGmatch(L *lua.LState) int {
|
||
|
str := L.CheckString(1)
|
||
|
pattern := L.CheckString(2)
|
||
|
re, err := regexp.Compile(pattern)
|
||
|
if err != nil {
|
||
|
L.RaiseError(err.Error())
|
||
|
}
|
||
|
L.Push(L.Get(lua.UpvalueIndex(1)))
|
||
|
ud := L.NewUserData()
|
||
|
ud.Value = &reMatchData{str, 0, re.FindAllStringSubmatchIndex(str, -1)}
|
||
|
L.Push(ud)
|
||
|
return 2
|
||
|
}
|
||
|
|
||
|
func reMatch(L *lua.LState) int {
|
||
|
str := L.CheckString(1)
|
||
|
pattern := L.CheckString(2)
|
||
|
offset := L.OptInt(3, 1)
|
||
|
l := len(str)
|
||
|
if offset < 0 {
|
||
|
offset = l + offset + 1
|
||
|
}
|
||
|
offset--
|
||
|
if offset < 0 {
|
||
|
offset = 0
|
||
|
}
|
||
|
|
||
|
re, err := regexp.Compile(pattern)
|
||
|
if err != nil {
|
||
|
L.RaiseError(err.Error())
|
||
|
}
|
||
|
str = str[offset:]
|
||
|
subs := re.FindStringSubmatchIndex(str)
|
||
|
nsubs := len(subs) / 2
|
||
|
switch nsubs {
|
||
|
case 0:
|
||
|
L.Push(lua.LNil)
|
||
|
return 1
|
||
|
case 1:
|
||
|
L.Push(lua.LString(str[subs[0]:subs[1]]))
|
||
|
return 1
|
||
|
default:
|
||
|
for i := 2; i < len(subs); i += 2 {
|
||
|
L.Push(lua.LString(str[subs[i]:subs[i+1]]))
|
||
|
}
|
||
|
return nsubs - 1
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func luaIndex2StringIndex(str string, i int, start bool) int {
|
||
|
if start && i != 0 {
|
||
|
i -= 1
|
||
|
}
|
||
|
l := len(str)
|
||
|
if i < 0 {
|
||
|
i = l + i + 1
|
||
|
}
|
||
|
if 0 > i {
|
||
|
i = 0
|
||
|
}
|
||
|
if !start && i > l {
|
||
|
i = l
|
||
|
}
|
||
|
return i
|
||
|
}
|
||
|
|
||
|
//
|