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 } //