290 lines
5.2 KiB
Go
290 lines
5.2 KiB
Go
package string
|
|
|
|
import (
|
|
"math/rand"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
|
)
|
|
|
|
const (
|
|
maxStringLength = 10_000_000 // 10MB limit for safety
|
|
maxRandomLength = 100_000 // Reasonable limit for random strings
|
|
)
|
|
|
|
func GetFunctionList() map[string]luajit.GoFunction {
|
|
return map[string]luajit.GoFunction{
|
|
"string_split": string_split,
|
|
"string_join": string_join,
|
|
"string_slice": string_slice,
|
|
"string_reverse": string_reverse,
|
|
"string_length": string_length,
|
|
"string_byte_length": string_byte_length,
|
|
"regex_match": regex_match,
|
|
"regex_find": regex_find,
|
|
"regex_find_all": regex_find_all,
|
|
"regex_replace": regex_replace,
|
|
"random_string": random_string,
|
|
"string_is_valid_utf8": string_is_valid_utf8,
|
|
}
|
|
}
|
|
|
|
func string_split(s *luajit.State) int {
|
|
str := s.ToString(1)
|
|
sep := s.ToString(2)
|
|
|
|
if len(str) > maxStringLength {
|
|
s.PushNil()
|
|
s.PushString("string too large")
|
|
return 2
|
|
}
|
|
|
|
// Handle empty separator - split into characters
|
|
if sep == "" {
|
|
runes := []rune(str)
|
|
parts := make([]string, len(runes))
|
|
for i, r := range runes {
|
|
parts[i] = string(r)
|
|
}
|
|
s.PushValue(parts)
|
|
return 1
|
|
}
|
|
|
|
parts := strings.Split(str, sep)
|
|
s.PushValue(parts)
|
|
return 1
|
|
}
|
|
|
|
func string_join(s *luajit.State) int {
|
|
arr, err := s.ToValue(1)
|
|
if err != nil {
|
|
s.PushNil()
|
|
s.PushString("invalid array")
|
|
return 2
|
|
}
|
|
sep := s.ToString(2)
|
|
|
|
var parts []string
|
|
switch v := arr.(type) {
|
|
case []string:
|
|
parts = v
|
|
case []any:
|
|
parts = make([]string, len(v))
|
|
for i, val := range v {
|
|
if val == nil {
|
|
parts[i] = ""
|
|
} else {
|
|
parts[i] = s.ToString(-1) // Convert via Lua
|
|
}
|
|
}
|
|
default:
|
|
s.PushNil()
|
|
s.PushString("not an array")
|
|
return 2
|
|
}
|
|
|
|
result := strings.Join(parts, sep)
|
|
if len(result) > maxStringLength {
|
|
s.PushNil()
|
|
s.PushString("result too large")
|
|
return 2
|
|
}
|
|
|
|
s.PushString(result)
|
|
return 1
|
|
}
|
|
|
|
func string_slice(s *luajit.State) int {
|
|
str := s.ToString(1)
|
|
start := int(s.ToNumber(2))
|
|
|
|
if !utf8.ValidString(str) {
|
|
s.PushNil()
|
|
s.PushString("invalid UTF-8")
|
|
return 2
|
|
}
|
|
|
|
runes := []rune(str)
|
|
length := len(runes)
|
|
startIdx := start - 1 // Convert from 1-indexed
|
|
|
|
if startIdx < 0 {
|
|
startIdx = 0
|
|
}
|
|
if startIdx >= length {
|
|
s.PushString("")
|
|
return 1
|
|
}
|
|
|
|
endIdx := length
|
|
if s.GetTop() >= 3 && !s.IsNil(3) {
|
|
end := int(s.ToNumber(3))
|
|
if end < 0 {
|
|
endIdx = length + end + 1
|
|
} else {
|
|
endIdx = end
|
|
}
|
|
if endIdx < 0 {
|
|
endIdx = 0
|
|
}
|
|
if endIdx > length {
|
|
endIdx = length
|
|
}
|
|
}
|
|
|
|
if startIdx >= endIdx {
|
|
s.PushString("")
|
|
return 1
|
|
}
|
|
|
|
s.PushString(string(runes[startIdx:endIdx]))
|
|
return 1
|
|
}
|
|
|
|
func string_reverse(s *luajit.State) int {
|
|
str := s.ToString(1)
|
|
|
|
if !utf8.ValidString(str) {
|
|
s.PushNil()
|
|
s.PushString("invalid UTF-8")
|
|
return 2
|
|
}
|
|
|
|
runes := []rune(str)
|
|
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
|
runes[i], runes[j] = runes[j], runes[i]
|
|
}
|
|
s.PushString(string(runes))
|
|
return 1
|
|
}
|
|
|
|
func string_length(s *luajit.State) int {
|
|
str := s.ToString(1)
|
|
s.PushNumber(float64(utf8.RuneCountInString(str)))
|
|
return 1
|
|
}
|
|
|
|
func string_byte_length(s *luajit.State) int {
|
|
str := s.ToString(1)
|
|
s.PushNumber(float64(len(str)))
|
|
return 1
|
|
}
|
|
|
|
func regex_match(s *luajit.State) int {
|
|
pattern := s.ToString(1)
|
|
str := s.ToString(2)
|
|
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
s.PushBoolean(false)
|
|
return 1
|
|
}
|
|
|
|
s.PushBoolean(re.MatchString(str))
|
|
return 1
|
|
}
|
|
|
|
func regex_find(s *luajit.State) int {
|
|
pattern := s.ToString(1)
|
|
str := s.ToString(2)
|
|
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
s.PushNil()
|
|
return 1
|
|
}
|
|
|
|
match := re.FindString(str)
|
|
if match == "" {
|
|
s.PushNil()
|
|
} else {
|
|
s.PushString(match)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func regex_find_all(s *luajit.State) int {
|
|
pattern := s.ToString(1)
|
|
str := s.ToString(2)
|
|
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
s.PushValue([]string{})
|
|
return 1
|
|
}
|
|
|
|
matches := re.FindAllString(str, -1)
|
|
if matches == nil {
|
|
matches = []string{}
|
|
}
|
|
|
|
s.PushValue(matches)
|
|
return 1
|
|
}
|
|
|
|
func regex_replace(s *luajit.State) int {
|
|
pattern := s.ToString(1)
|
|
str := s.ToString(2)
|
|
replacement := s.ToString(3)
|
|
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
s.PushString(str)
|
|
return 1
|
|
}
|
|
|
|
result := re.ReplaceAllString(str, replacement)
|
|
s.PushString(result)
|
|
return 1
|
|
}
|
|
|
|
func random_string(s *luajit.State) int {
|
|
length := int(s.ToNumber(1))
|
|
if length < 0 || length > maxRandomLength {
|
|
s.PushNil()
|
|
s.PushString("invalid length")
|
|
return 2
|
|
}
|
|
|
|
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
if s.GetTop() >= 2 && !s.IsNil(2) {
|
|
charset = s.ToString(2)
|
|
}
|
|
|
|
if length == 0 {
|
|
s.PushString("")
|
|
return 1
|
|
}
|
|
|
|
if !utf8.ValidString(charset) {
|
|
s.PushNil()
|
|
s.PushString("invalid charset")
|
|
return 2
|
|
}
|
|
|
|
charsetRunes := []rune(charset)
|
|
if len(charsetRunes) == 0 {
|
|
s.PushNil()
|
|
s.PushString("empty charset")
|
|
return 2
|
|
}
|
|
|
|
result := make([]rune, length)
|
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
for i := range result {
|
|
result[i] = charsetRunes[rnd.Intn(len(charsetRunes))]
|
|
}
|
|
|
|
s.PushString(string(result))
|
|
return 1
|
|
}
|
|
|
|
func string_is_valid_utf8(s *luajit.State) int {
|
|
str := s.ToString(1)
|
|
s.PushBoolean(utf8.ValidString(str))
|
|
return 1
|
|
}
|