mirror of https://github.com/tidwall/tile38.git
449 lines
9.4 KiB
Go
449 lines
9.4 KiB
Go
package lua
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/yuin/gopher-lua/pm"
|
|
)
|
|
|
|
const emptyLString LString = LString("")
|
|
|
|
func OpenString(L *LState) int {
|
|
var mod *LTable
|
|
//_, ok := L.G.builtinMts[int(LTString)]
|
|
//if !ok {
|
|
mod = L.RegisterModule(StringLibName, strFuncs).(*LTable)
|
|
gmatch := L.NewClosure(strGmatch, L.NewFunction(strGmatchIter))
|
|
mod.RawSetString("gmatch", gmatch)
|
|
mod.RawSetString("gfind", gmatch)
|
|
mod.RawSetString("__index", mod)
|
|
L.G.builtinMts[int(LTString)] = mod
|
|
//}
|
|
L.Push(mod)
|
|
return 1
|
|
}
|
|
|
|
var strFuncs = map[string]LGFunction{
|
|
"byte": strByte,
|
|
"char": strChar,
|
|
"dump": strDump,
|
|
"find": strFind,
|
|
"format": strFormat,
|
|
"gsub": strGsub,
|
|
"len": strLen,
|
|
"lower": strLower,
|
|
"match": strMatch,
|
|
"rep": strRep,
|
|
"reverse": strReverse,
|
|
"sub": strSub,
|
|
"upper": strUpper,
|
|
}
|
|
|
|
func strByte(L *LState) int {
|
|
str := L.CheckString(1)
|
|
start := L.OptInt(2, 1) - 1
|
|
end := L.OptInt(3, -1)
|
|
l := len(str)
|
|
if start < 0 {
|
|
start = l + start + 1
|
|
}
|
|
if end < 0 {
|
|
end = l + end + 1
|
|
}
|
|
|
|
if L.GetTop() == 2 {
|
|
if start < 0 || start >= l {
|
|
return 0
|
|
}
|
|
L.Push(LNumber(str[start]))
|
|
return 1
|
|
}
|
|
|
|
start = intMax(start, 0)
|
|
end = intMin(end, l)
|
|
if end < 0 || end <= start || start >= l {
|
|
return 0
|
|
}
|
|
|
|
for i := start; i < end; i++ {
|
|
L.Push(LNumber(str[i]))
|
|
}
|
|
return end - start
|
|
}
|
|
|
|
func strChar(L *LState) int {
|
|
top := L.GetTop()
|
|
bytes := make([]byte, L.GetTop())
|
|
for i := 1; i <= top; i++ {
|
|
bytes[i-1] = uint8(L.CheckInt(i))
|
|
}
|
|
L.Push(LString(string(bytes)))
|
|
return 1
|
|
}
|
|
|
|
func strDump(L *LState) int {
|
|
L.RaiseError("GopherLua does not support the string.dump")
|
|
return 0
|
|
}
|
|
|
|
func strFind(L *LState) int {
|
|
str := L.CheckString(1)
|
|
pattern := L.CheckString(2)
|
|
if len(pattern) == 0 {
|
|
L.Push(LNumber(1))
|
|
L.Push(LNumber(0))
|
|
return 2
|
|
}
|
|
init := luaIndex2StringIndex(str, L.OptInt(3, 1), true)
|
|
plain := false
|
|
if L.GetTop() == 4 {
|
|
plain = LVAsBool(L.Get(4))
|
|
}
|
|
|
|
if plain {
|
|
pos := strings.Index(str[init:], pattern)
|
|
if pos < 0 {
|
|
L.Push(LNil)
|
|
return 1
|
|
}
|
|
L.Push(LNumber(init+pos) + 1)
|
|
L.Push(LNumber(init + pos + len(pattern)))
|
|
return 2
|
|
}
|
|
|
|
mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), init, 1)
|
|
if err != nil {
|
|
L.RaiseError(err.Error())
|
|
}
|
|
if len(mds) == 0 {
|
|
L.Push(LNil)
|
|
return 1
|
|
}
|
|
md := mds[0]
|
|
L.Push(LNumber(md.Capture(0) + 1))
|
|
L.Push(LNumber(md.Capture(1)))
|
|
for i := 2; i < md.CaptureLength(); i += 2 {
|
|
if md.IsPosCapture(i) {
|
|
L.Push(LNumber(md.Capture(i)))
|
|
} else {
|
|
L.Push(LString(str[md.Capture(i):md.Capture(i+1)]))
|
|
}
|
|
}
|
|
return md.CaptureLength()/2 + 1
|
|
}
|
|
|
|
func strFormat(L *LState) int {
|
|
str := L.CheckString(1)
|
|
args := make([]interface{}, L.GetTop()-1)
|
|
top := L.GetTop()
|
|
for i := 2; i <= top; i++ {
|
|
args[i-2] = L.Get(i)
|
|
}
|
|
npat := strings.Count(str, "%") - strings.Count(str, "%%")
|
|
L.Push(LString(fmt.Sprintf(str, args[:intMin(npat, len(args))]...)))
|
|
return 1
|
|
}
|
|
|
|
func strGsub(L *LState) int {
|
|
str := L.CheckString(1)
|
|
pat := L.CheckString(2)
|
|
L.CheckTypes(3, LTString, LTTable, LTFunction)
|
|
repl := L.CheckAny(3)
|
|
limit := L.OptInt(4, -1)
|
|
|
|
mds, err := pm.Find(pat, unsafeFastStringToReadOnlyBytes(str), 0, limit)
|
|
if err != nil {
|
|
L.RaiseError(err.Error())
|
|
}
|
|
if len(mds) == 0 {
|
|
L.SetTop(1)
|
|
L.Push(LNumber(0))
|
|
return 2
|
|
}
|
|
switch lv := repl.(type) {
|
|
case LString:
|
|
L.Push(LString(strGsubStr(L, str, string(lv), mds)))
|
|
case *LTable:
|
|
L.Push(LString(strGsubTable(L, str, lv, mds)))
|
|
case *LFunction:
|
|
L.Push(LString(strGsubFunc(L, str, lv, mds)))
|
|
}
|
|
L.Push(LNumber(len(mds)))
|
|
return 2
|
|
}
|
|
|
|
type replaceInfo struct {
|
|
Indicies []int
|
|
String string
|
|
}
|
|
|
|
func checkCaptureIndex(L *LState, m *pm.MatchData, idx int) {
|
|
if idx <= 2 {
|
|
return
|
|
}
|
|
if idx >= m.CaptureLength() {
|
|
L.RaiseError("invalid capture index")
|
|
}
|
|
}
|
|
|
|
func capturedString(L *LState, m *pm.MatchData, str string, idx int) string {
|
|
checkCaptureIndex(L, m, idx)
|
|
if idx >= m.CaptureLength() && idx == 2 {
|
|
idx = 0
|
|
}
|
|
if m.IsPosCapture(idx) {
|
|
return fmt.Sprint(m.Capture(idx))
|
|
} else {
|
|
return str[m.Capture(idx):m.Capture(idx+1)]
|
|
}
|
|
|
|
}
|
|
|
|
func strGsubDoReplace(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 strGsubStr(L *LState, str string, repl string, matches []*pm.MatchData) string {
|
|
infoList := make([]replaceInfo, 0, len(matches))
|
|
for _, match := range matches {
|
|
start, end := match.Capture(0), match.Capture(1)
|
|
sc := newFlagScanner('%', "", "", repl)
|
|
for c, eos := sc.Next(); !eos; c, eos = sc.Next() {
|
|
if !sc.ChangeFlag {
|
|
if sc.HasFlag {
|
|
if c >= '0' && c <= '9' {
|
|
sc.AppendString(capturedString(L, match, str, 2*(int(c)-48)))
|
|
} else {
|
|
sc.AppendChar('%')
|
|
sc.AppendChar(c)
|
|
}
|
|
sc.HasFlag = false
|
|
} else {
|
|
sc.AppendChar(c)
|
|
}
|
|
}
|
|
}
|
|
infoList = append(infoList, replaceInfo{[]int{start, end}, sc.String()})
|
|
}
|
|
|
|
return strGsubDoReplace(str, infoList)
|
|
}
|
|
|
|
func strGsubTable(L *LState, str string, repl *LTable, matches []*pm.MatchData) string {
|
|
infoList := make([]replaceInfo, 0, len(matches))
|
|
for _, match := range matches {
|
|
idx := 0
|
|
if match.CaptureLength() > 2 { // has captures
|
|
idx = 2
|
|
}
|
|
var value LValue
|
|
if match.IsPosCapture(idx) {
|
|
value = L.GetTable(repl, LNumber(match.Capture(idx)))
|
|
} else {
|
|
value = L.GetField(repl, str[match.Capture(idx):match.Capture(idx+1)])
|
|
}
|
|
if !LVIsFalse(value) {
|
|
infoList = append(infoList, replaceInfo{[]int{match.Capture(0), match.Capture(1)}, LVAsString(value)})
|
|
}
|
|
}
|
|
return strGsubDoReplace(str, infoList)
|
|
}
|
|
|
|
func strGsubFunc(L *LState, str string, repl *LFunction, matches []*pm.MatchData) string {
|
|
infoList := make([]replaceInfo, 0, len(matches))
|
|
for _, match := range matches {
|
|
start, end := match.Capture(0), match.Capture(1)
|
|
L.Push(repl)
|
|
nargs := 0
|
|
if match.CaptureLength() > 2 { // has captures
|
|
for i := 2; i < match.CaptureLength(); i += 2 {
|
|
if match.IsPosCapture(i) {
|
|
L.Push(LNumber(match.Capture(i)))
|
|
} else {
|
|
L.Push(LString(capturedString(L, match, str, i)))
|
|
}
|
|
nargs++
|
|
}
|
|
} else {
|
|
L.Push(LString(capturedString(L, match, str, 0)))
|
|
nargs++
|
|
}
|
|
L.Call(nargs, 1)
|
|
ret := L.reg.Pop()
|
|
if !LVIsFalse(ret) {
|
|
infoList = append(infoList, replaceInfo{[]int{start, end}, LVAsString(ret)})
|
|
}
|
|
}
|
|
return strGsubDoReplace(str, infoList)
|
|
}
|
|
|
|
type strMatchData struct {
|
|
str string
|
|
pos int
|
|
matches []*pm.MatchData
|
|
}
|
|
|
|
func strGmatchIter(L *LState) int {
|
|
md := L.CheckUserData(1).Value.(*strMatchData)
|
|
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 match.CaptureLength() == 2 {
|
|
L.Push(LString(str[match.Capture(0):match.Capture(1)]))
|
|
return 1
|
|
}
|
|
|
|
for i := 2; i < match.CaptureLength(); i += 2 {
|
|
if match.IsPosCapture(i) {
|
|
L.Push(LNumber(match.Capture(i)))
|
|
} else {
|
|
L.Push(LString(str[match.Capture(i):match.Capture(i+1)]))
|
|
}
|
|
}
|
|
return match.CaptureLength()/2 - 1
|
|
}
|
|
|
|
func strGmatch(L *LState) int {
|
|
str := L.CheckString(1)
|
|
pattern := L.CheckString(2)
|
|
mds, err := pm.Find(pattern, []byte(str), 0, -1)
|
|
if err != nil {
|
|
L.RaiseError(err.Error())
|
|
}
|
|
L.Push(L.Get(UpvalueIndex(1)))
|
|
ud := L.NewUserData()
|
|
ud.Value = &strMatchData{str, 0, mds}
|
|
L.Push(ud)
|
|
return 2
|
|
}
|
|
|
|
func strLen(L *LState) int {
|
|
str := L.CheckString(1)
|
|
L.Push(LNumber(len(str)))
|
|
return 1
|
|
}
|
|
|
|
func strLower(L *LState) int {
|
|
str := L.CheckString(1)
|
|
L.Push(LString(strings.ToLower(str)))
|
|
return 1
|
|
}
|
|
|
|
func strMatch(L *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
|
|
}
|
|
|
|
mds, err := pm.Find(pattern, unsafeFastStringToReadOnlyBytes(str), offset, 1)
|
|
if err != nil {
|
|
L.RaiseError(err.Error())
|
|
}
|
|
if len(mds) == 0 {
|
|
L.Push(LNil)
|
|
return 0
|
|
}
|
|
md := mds[0]
|
|
nsubs := md.CaptureLength() / 2
|
|
switch nsubs {
|
|
case 1:
|
|
L.Push(LString(str[md.Capture(0):md.Capture(1)]))
|
|
return 1
|
|
default:
|
|
for i := 2; i < md.CaptureLength(); i += 2 {
|
|
if md.IsPosCapture(i) {
|
|
L.Push(LNumber(md.Capture(i)))
|
|
} else {
|
|
L.Push(LString(str[md.Capture(i):md.Capture(i+1)]))
|
|
}
|
|
}
|
|
return nsubs - 1
|
|
}
|
|
}
|
|
|
|
func strRep(L *LState) int {
|
|
str := L.CheckString(1)
|
|
n := L.CheckInt(2)
|
|
if n < 0 {
|
|
L.Push(emptyLString)
|
|
} else {
|
|
L.Push(LString(strings.Repeat(str, n)))
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func strReverse(L *LState) int {
|
|
str := L.CheckString(1)
|
|
bts := []byte(str)
|
|
out := make([]byte, len(bts))
|
|
for i, j := 0, len(bts)-1; j >= 0; i, j = i+1, j-1 {
|
|
out[i] = bts[j]
|
|
}
|
|
L.Push(LString(string(out)))
|
|
return 1
|
|
}
|
|
|
|
func strSub(L *LState) int {
|
|
str := L.CheckString(1)
|
|
start := luaIndex2StringIndex(str, L.CheckInt(2), true)
|
|
end := luaIndex2StringIndex(str, L.OptInt(3, -1), false)
|
|
l := len(str)
|
|
if start >= l || end < start {
|
|
L.Push(emptyLString)
|
|
} else {
|
|
L.Push(LString(str[start:end]))
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func strUpper(L *LState) int {
|
|
str := L.CheckString(1)
|
|
L.Push(LString(strings.ToUpper(str)))
|
|
return 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
|
|
}
|
|
i = intMax(0, i)
|
|
if !start && i > l {
|
|
i = l
|
|
}
|
|
return i
|
|
}
|
|
|
|
//
|