update json, math and string modules
This commit is contained in:
parent
898b29b86a
commit
0012a7089d
@ -5,51 +5,41 @@ import (
|
|||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetJSONFunctions returns all JSON-related Go functions
|
func GetFunctionList() map[string]luajit.GoFunction {
|
||||||
func GetJSONFunctions() map[string]luajit.GoFunction {
|
|
||||||
return map[string]luajit.GoFunction{
|
return map[string]luajit.GoFunction{
|
||||||
"json_encode": func(s *luajit.State) int {
|
"json_encode": json_encode,
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
"json_decode": json_decode,
|
||||||
return s.PushError("json_encode: %v", err)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func json_encode(s *luajit.State) int {
|
||||||
value, err := s.ToValue(1)
|
value, err := s.ToValue(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s.PushError("json_encode: failed to read value: %v", err)
|
s.PushNil()
|
||||||
|
s.PushString("failed to read value")
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(value)
|
data, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s.PushError("json_encode: %v", err)
|
s.PushNil()
|
||||||
|
s.PushString("encoding failed")
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
s.PushString(string(data))
|
s.PushString(string(data))
|
||||||
return 1
|
return 1
|
||||||
},
|
|
||||||
|
|
||||||
"json_decode": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("json_decode: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonStr, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("json_decode: input must be a string")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func json_decode(s *luajit.State) int {
|
||||||
|
jsonStr := s.ToString(1)
|
||||||
var result any
|
var result any
|
||||||
if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
|
if err := json.Unmarshal([]byte(jsonStr), &result); err != nil {
|
||||||
// Return nil and error string instead of PushError for JSON parsing errors
|
|
||||||
s.PushNil()
|
s.PushNil()
|
||||||
s.PushString(err.Error())
|
s.PushString("invalid JSON")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.PushValue(result); err != nil {
|
s.PushValue(result)
|
||||||
return s.PushError("json_decode: failed to push result: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,20 @@ package math
|
|||||||
|
|
||||||
import luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
import luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||||
|
|
||||||
// GetMathFunctions returns all math-related Go functions
|
func GetFunctionList() map[string]luajit.GoFunction {
|
||||||
func GetMathFunctions() map[string]luajit.GoFunction {
|
|
||||||
return map[string]luajit.GoFunction{
|
return map[string]luajit.GoFunction{
|
||||||
"math_factorial": func(s *luajit.State) int {
|
"math_factorial": math_factorial,
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
"math_gcd": math_gcd,
|
||||||
return s.PushError("math_factorial: %v", err)
|
"math_lcm": math_lcm,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := s.SafeToNumber(1)
|
func math_factorial(s *luajit.State) int {
|
||||||
if err != nil || n < 0 || n != float64(int(n)) {
|
n := s.ToNumber(1)
|
||||||
return s.PushError("math_factorial: argument must be a non-negative integer")
|
if n < 0 || n != float64(int(n)) || n > 170 {
|
||||||
}
|
s.PushNil()
|
||||||
|
s.PushString("invalid argument")
|
||||||
if n > 170 {
|
return 2
|
||||||
return s.PushError("math_factorial: argument too large (max 170)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result := 1.0
|
result := 1.0
|
||||||
@ -26,48 +25,23 @@ func GetMathFunctions() map[string]luajit.GoFunction {
|
|||||||
|
|
||||||
s.PushNumber(result)
|
s.PushNumber(result)
|
||||||
return 1
|
return 1
|
||||||
},
|
|
||||||
|
|
||||||
"math_gcd": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("math_gcd: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a, err := s.SafeToNumber(1)
|
func math_gcd(s *luajit.State) int {
|
||||||
if err != nil || a != float64(int(a)) {
|
a := int(s.ToNumber(1))
|
||||||
return s.PushError("math_gcd: first argument must be an integer")
|
b := int(s.ToNumber(2))
|
||||||
|
|
||||||
|
for b != 0 {
|
||||||
|
a, b = b, a%b
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := s.SafeToNumber(2)
|
s.PushNumber(float64(a))
|
||||||
if err != nil || b != float64(int(b)) {
|
|
||||||
return s.PushError("math_gcd: second argument must be an integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
ia, ib := int(a), int(b)
|
|
||||||
for ib != 0 {
|
|
||||||
ia, ib = ib, ia%ib
|
|
||||||
}
|
|
||||||
|
|
||||||
s.PushNumber(float64(ia))
|
|
||||||
return 1
|
return 1
|
||||||
},
|
|
||||||
|
|
||||||
"math_lcm": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("math_lcm: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a, err := s.SafeToNumber(1)
|
func math_lcm(s *luajit.State) int {
|
||||||
if err != nil || a != float64(int(a)) {
|
a := int(s.ToNumber(1))
|
||||||
return s.PushError("math_lcm: first argument must be an integer")
|
b := int(s.ToNumber(2))
|
||||||
}
|
|
||||||
|
|
||||||
b, err := s.SafeToNumber(2)
|
|
||||||
if err != nil || b != float64(int(b)) {
|
|
||||||
return s.PushError("math_lcm: second argument must be an integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
ia, ib := int(a), int(b)
|
|
||||||
|
|
||||||
// Calculate GCD
|
// Calculate GCD
|
||||||
gcd := func(x, y int) int {
|
gcd := func(x, y int) int {
|
||||||
@ -77,9 +51,7 @@ func GetMathFunctions() map[string]luajit.GoFunction {
|
|||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
result := ia * ib / gcd(ia, ib)
|
result := a * b / gcd(a, b)
|
||||||
s.PushNumber(float64(result))
|
s.PushNumber(float64(result))
|
||||||
return 1
|
return 1
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,45 @@
|
|||||||
package string
|
package string
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||||
"golang.org/x/text/cases"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxStringLength = 10_000_000 // 10MB limit for safety
|
maxStringLength = 10_000_000 // 10MB limit for safety
|
||||||
maxRepeatCount = 1_000_000 // Prevent excessive memory usage
|
|
||||||
maxRandomLength = 100_000 // Reasonable limit for random strings
|
maxRandomLength = 100_000 // Reasonable limit for random strings
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateStringLength(s string) error {
|
func GetFunctionList() map[string]luajit.GoFunction {
|
||||||
if len(s) > maxStringLength {
|
|
||||||
return fmt.Errorf("string too large (max %d bytes)", maxStringLength)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetStringFunctions() map[string]luajit.GoFunction {
|
|
||||||
return map[string]luajit.GoFunction{
|
return map[string]luajit.GoFunction{
|
||||||
"string_split": func(s *luajit.State) int {
|
"string_split": string_split,
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
"string_join": string_join,
|
||||||
return s.PushError("string_split: %v", err)
|
"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,
|
||||||
}
|
}
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_split: first argument must be a string")
|
|
||||||
}
|
|
||||||
sep, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_split: second argument must be a string")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateStringLength(str); err != nil {
|
func string_split(s *luajit.State) int {
|
||||||
return s.PushError("string_split: %v", err)
|
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
|
// Handle empty separator - split into characters
|
||||||
@ -54,31 +49,23 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
for i, r := range runes {
|
for i, r := range runes {
|
||||||
parts[i] = string(r)
|
parts[i] = string(r)
|
||||||
}
|
}
|
||||||
if err := s.PushValue(parts); err != nil {
|
s.PushValue(parts)
|
||||||
return s.PushError("string_split: failed to push result: %v", err)
|
|
||||||
}
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(str, sep)
|
parts := strings.Split(str, sep)
|
||||||
if err := s.PushValue(parts); err != nil {
|
s.PushValue(parts)
|
||||||
return s.PushError("string_split: failed to push result: %v", err)
|
|
||||||
}
|
|
||||||
return 1
|
return 1
|
||||||
},
|
}
|
||||||
|
|
||||||
"string_join": func(s *luajit.State) int {
|
func string_join(s *luajit.State) int {
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
arr, err := s.ToValue(1)
|
||||||
return s.PushError("string_join: %v", err)
|
|
||||||
}
|
|
||||||
arr, err := s.SafeToTable(1)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s.PushError("string_join: first argument must be a table")
|
s.PushNil()
|
||||||
}
|
s.PushString("invalid array")
|
||||||
sep, err := s.SafeToString(2)
|
return 2
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_join: second argument must be a string")
|
|
||||||
}
|
}
|
||||||
|
sep := s.ToString(2)
|
||||||
|
|
||||||
var parts []string
|
var parts []string
|
||||||
switch v := arr.(type) {
|
switch v := arr.(type) {
|
||||||
@ -90,512 +77,40 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
if val == nil {
|
if val == nil {
|
||||||
parts[i] = ""
|
parts[i] = ""
|
||||||
} else {
|
} else {
|
||||||
parts[i] = fmt.Sprintf("%v", val)
|
parts[i] = s.ToString(-1) // Convert via Lua
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case map[string]any:
|
|
||||||
if len(v) == 0 {
|
|
||||||
parts = []string{}
|
|
||||||
} else {
|
|
||||||
return s.PushError("string_join: first argument must be an array, not a map")
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return s.PushError("string_join: first argument must be an array")
|
s.PushNil()
|
||||||
|
s.PushString("not an array")
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
result := strings.Join(parts, sep)
|
result := strings.Join(parts, sep)
|
||||||
if err := validateStringLength(result); err != nil {
|
if len(result) > maxStringLength {
|
||||||
return s.PushError("string_join: result %v", err)
|
s.PushNil()
|
||||||
|
s.PushString("result too large")
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
s.PushString(result)
|
s.PushString(result)
|
||||||
return 1
|
return 1
|
||||||
},
|
|
||||||
|
|
||||||
"string_trim": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_trim: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_trim: argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushString(strings.TrimSpace(str))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_trim_left": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_trim_left: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_trim_left: first argument must be a string")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.GetTop() >= 2 && !s.IsNil(2) {
|
func string_slice(s *luajit.State) int {
|
||||||
cutset, err := s.SafeToString(2)
|
str := s.ToString(1)
|
||||||
if err != nil {
|
start := int(s.ToNumber(2))
|
||||||
return s.PushError("string_trim_left: second argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushString(strings.TrimLeft(str, cutset))
|
|
||||||
} else {
|
|
||||||
s.PushString(strings.TrimLeftFunc(str, unicode.IsSpace))
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_trim_right": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_trim_right: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_trim_right: first argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.GetTop() >= 2 && !s.IsNil(2) {
|
|
||||||
cutset, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_trim_right: second argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushString(strings.TrimRight(str, cutset))
|
|
||||||
} else {
|
|
||||||
s.PushString(strings.TrimRightFunc(str, unicode.IsSpace))
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_upper": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_upper: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_upper: argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushString(strings.ToUpper(str))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_lower": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_lower: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_lower: argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushString(strings.ToLower(str))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_title": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_title: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_title: argument must be a string")
|
|
||||||
}
|
|
||||||
caser := cases.Title(language.English, cases.NoLower)
|
|
||||||
s.PushString(caser.String(str))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_contains": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("string_contains: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_contains: first argument must be a string")
|
|
||||||
}
|
|
||||||
substr, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_contains: second argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushBoolean(strings.Contains(str, substr))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_starts_with": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("string_starts_with: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_starts_with: first argument must be a string")
|
|
||||||
}
|
|
||||||
prefix, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_starts_with: second argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushBoolean(strings.HasPrefix(str, prefix))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_ends_with": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("string_ends_with: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_ends_with: first argument must be a string")
|
|
||||||
}
|
|
||||||
suffix, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_ends_with: second argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushBoolean(strings.HasSuffix(str, suffix))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_replace": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(3); err != nil {
|
|
||||||
return s.PushError("string_replace: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_replace: first argument must be a string")
|
|
||||||
}
|
|
||||||
old, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_replace: second argument must be a string")
|
|
||||||
}
|
|
||||||
new, err := s.SafeToString(3)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_replace: third argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
if old == "" {
|
|
||||||
return s.PushError("string_replace: cannot replace empty string")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := strings.ReplaceAll(str, old, new)
|
|
||||||
if err := validateStringLength(result); err != nil {
|
|
||||||
return s.PushError("string_replace: result %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.PushString(result)
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_replace_n": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(4); err != nil {
|
|
||||||
return s.PushError("string_replace_n: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_replace_n: first argument must be a string")
|
|
||||||
}
|
|
||||||
old, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_replace_n: second argument must be a string")
|
|
||||||
}
|
|
||||||
new, err := s.SafeToString(3)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_replace_n: third argument must be a string")
|
|
||||||
}
|
|
||||||
n, err := s.SafeToNumber(4)
|
|
||||||
if err != nil || n != float64(int(n)) || n < 0 {
|
|
||||||
return s.PushError("string_replace_n: fourth argument must be a non-negative integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
if old == "" {
|
|
||||||
return s.PushError("string_replace_n: cannot replace empty string")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := strings.Replace(str, old, new, int(n))
|
|
||||||
s.PushString(result)
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_index": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("string_index: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_index: first argument must be a string")
|
|
||||||
}
|
|
||||||
substr, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_index: second argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
if substr == "" {
|
|
||||||
s.PushNumber(1) // Empty string found at position 1
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
index := strings.Index(str, substr)
|
|
||||||
if index == -1 {
|
|
||||||
s.PushNumber(0) // Not found
|
|
||||||
} else {
|
|
||||||
s.PushNumber(float64(index + 1)) // Convert to 1-indexed
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_last_index": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("string_last_index: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_last_index: first argument must be a string")
|
|
||||||
}
|
|
||||||
substr, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_last_index: second argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
if substr == "" {
|
|
||||||
s.PushNumber(float64(utf8.RuneCountInString(str) + 1)) // Empty string at end
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
index := strings.LastIndex(str, substr)
|
|
||||||
if index == -1 {
|
|
||||||
s.PushNumber(0) // Not found
|
|
||||||
} else {
|
|
||||||
s.PushNumber(float64(index + 1)) // Convert to 1-indexed
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_count": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("string_count: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_count: first argument must be a string")
|
|
||||||
}
|
|
||||||
substr, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_count: second argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
if substr == "" {
|
|
||||||
// Empty string matches at every position including boundaries
|
|
||||||
s.PushNumber(float64(utf8.RuneCountInString(str) + 1))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
count := strings.Count(str, substr)
|
|
||||||
s.PushNumber(float64(count))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_repeat": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
|
||||||
return s.PushError("string_repeat: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_repeat: first argument must be a string")
|
|
||||||
}
|
|
||||||
count, err := s.SafeToNumber(2)
|
|
||||||
if err != nil || count < 0 || count != float64(int(count)) {
|
|
||||||
return s.PushError("string_repeat: second argument must be a non-negative integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
n := int(count)
|
|
||||||
if n == 0 {
|
|
||||||
s.PushString("")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for potential overflow
|
|
||||||
if len(str) > 0 && n > maxRepeatCount/len(str) {
|
|
||||||
return s.PushError("string_repeat: result would be too large")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := strings.Repeat(str, n)
|
|
||||||
s.PushString(result)
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_reverse": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_reverse: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_reverse: argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !utf8.ValidString(str) {
|
if !utf8.ValidString(str) {
|
||||||
return s.PushError("string_reverse: invalid UTF-8 string")
|
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
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_length": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_length: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_length: argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushNumber(float64(utf8.RuneCountInString(str)))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_byte_length": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_byte_length: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_byte_length: argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushNumber(float64(len(str)))
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_lines": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_lines: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_lines: argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different line endings
|
|
||||||
str = strings.ReplaceAll(str, "\r\n", "\n")
|
|
||||||
str = strings.ReplaceAll(str, "\r", "\n")
|
|
||||||
lines := strings.Split(str, "\n")
|
|
||||||
|
|
||||||
if err := s.PushValue(lines); err != nil {
|
|
||||||
return s.PushError("string_lines: failed to push result: %v", err)
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_words": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_words: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_words: argument must be a string")
|
|
||||||
}
|
|
||||||
words := strings.Fields(str)
|
|
||||||
if err := s.PushValue(words); err != nil {
|
|
||||||
return s.PushError("string_words: failed to push result: %v", err)
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_pad_left": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(2); err != nil {
|
|
||||||
return s.PushError("string_pad_left: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_pad_left: first argument must be a string")
|
|
||||||
}
|
|
||||||
width, err := s.SafeToNumber(2)
|
|
||||||
if err != nil || width != float64(int(width)) || width < 0 {
|
|
||||||
return s.PushError("string_pad_left: second argument must be a non-negative integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
padChar := " "
|
|
||||||
if s.GetTop() >= 3 && !s.IsNil(3) {
|
|
||||||
if p, err := s.SafeToString(3); err == nil && utf8.RuneCountInString(p) > 0 {
|
|
||||||
runes := []rune(p)
|
|
||||||
padChar = string(runes[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentLen := utf8.RuneCountInString(str)
|
|
||||||
targetLen := int(width)
|
|
||||||
if currentLen >= targetLen {
|
|
||||||
s.PushString(str)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
padLen := targetLen - currentLen
|
|
||||||
if padLen > maxRepeatCount {
|
|
||||||
return s.PushError("string_pad_left: padding too large")
|
|
||||||
}
|
|
||||||
|
|
||||||
padding := strings.Repeat(padChar, padLen)
|
|
||||||
s.PushString(padding + str)
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_pad_right": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(2); err != nil {
|
|
||||||
return s.PushError("string_pad_right: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_pad_right: first argument must be a string")
|
|
||||||
}
|
|
||||||
width, err := s.SafeToNumber(2)
|
|
||||||
if err != nil || width != float64(int(width)) || width < 0 {
|
|
||||||
return s.PushError("string_pad_right: second argument must be a non-negative integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
padChar := " "
|
|
||||||
if s.GetTop() >= 3 && !s.IsNil(3) {
|
|
||||||
if p, err := s.SafeToString(3); err == nil && utf8.RuneCountInString(p) > 0 {
|
|
||||||
runes := []rune(p)
|
|
||||||
padChar = string(runes[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentLen := utf8.RuneCountInString(str)
|
|
||||||
targetLen := int(width)
|
|
||||||
if currentLen >= targetLen {
|
|
||||||
s.PushString(str)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
padLen := targetLen - currentLen
|
|
||||||
if padLen > maxRepeatCount {
|
|
||||||
return s.PushError("string_pad_right: padding too large")
|
|
||||||
}
|
|
||||||
|
|
||||||
padding := strings.Repeat(padChar, padLen)
|
|
||||||
s.PushString(str + padding)
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_slice": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(2); err != nil {
|
|
||||||
return s.PushError("string_slice: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_slice: first argument must be a string")
|
|
||||||
}
|
|
||||||
start, err := s.SafeToNumber(2)
|
|
||||||
if err != nil || start != float64(int(start)) {
|
|
||||||
return s.PushError("string_slice: second argument must be an integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !utf8.ValidString(str) {
|
|
||||||
return s.PushError("string_slice: invalid UTF-8 string")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runes := []rune(str)
|
runes := []rune(str)
|
||||||
length := len(runes)
|
length := len(runes)
|
||||||
startIdx := int(start) - 1 // Convert from 1-indexed to 0-indexed
|
startIdx := start - 1 // Convert from 1-indexed
|
||||||
|
|
||||||
// Handle negative start index
|
|
||||||
if startIdx < 0 {
|
if startIdx < 0 {
|
||||||
startIdx = 0
|
startIdx = 0
|
||||||
}
|
}
|
||||||
@ -606,12 +121,11 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
|
|
||||||
endIdx := length
|
endIdx := length
|
||||||
if s.GetTop() >= 3 && !s.IsNil(3) {
|
if s.GetTop() >= 3 && !s.IsNil(3) {
|
||||||
end, err := s.SafeToNumber(3)
|
end := int(s.ToNumber(3))
|
||||||
if err == nil && end == float64(int(end)) {
|
if end < 0 {
|
||||||
endIdx = int(end)
|
endIdx = length + end + 1
|
||||||
// Handle negative end index (from end of string)
|
} else {
|
||||||
if endIdx < 0 {
|
endIdx = end
|
||||||
endIdx = length + endIdx + 1
|
|
||||||
}
|
}
|
||||||
if endIdx < 0 {
|
if endIdx < 0 {
|
||||||
endIdx = 0
|
endIdx = 0
|
||||||
@ -620,7 +134,6 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
endIdx = length
|
endIdx = length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if startIdx >= endIdx {
|
if startIdx >= endIdx {
|
||||||
s.PushString("")
|
s.PushString("")
|
||||||
@ -629,21 +142,41 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
|
|
||||||
s.PushString(string(runes[startIdx:endIdx]))
|
s.PushString(string(runes[startIdx:endIdx]))
|
||||||
return 1
|
return 1
|
||||||
},
|
}
|
||||||
|
|
||||||
"regex_match": func(s *luajit.State) int {
|
func string_reverse(s *luajit.State) int {
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
str := s.ToString(1)
|
||||||
return s.PushError("regex_match: %v", err)
|
|
||||||
|
if !utf8.ValidString(str) {
|
||||||
|
s.PushNil()
|
||||||
|
s.PushString("invalid UTF-8")
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
pattern, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
runes := []rune(str)
|
||||||
return s.PushError("regex_match: first argument must be a string")
|
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
runes[i], runes[j] = runes[j], runes[i]
|
||||||
}
|
}
|
||||||
str, err := s.SafeToString(2)
|
s.PushString(string(runes))
|
||||||
if err != nil {
|
return 1
|
||||||
return s.PushError("regex_match: second argument must be a string")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
re, err := regexp.Compile(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.PushBoolean(false)
|
s.PushBoolean(false)
|
||||||
@ -652,20 +185,11 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
|
|
||||||
s.PushBoolean(re.MatchString(str))
|
s.PushBoolean(re.MatchString(str))
|
||||||
return 1
|
return 1
|
||||||
},
|
}
|
||||||
|
|
||||||
"regex_find": func(s *luajit.State) int {
|
func regex_find(s *luajit.State) int {
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
pattern := s.ToString(1)
|
||||||
return s.PushError("regex_find: %v", err)
|
str := s.ToString(2)
|
||||||
}
|
|
||||||
pattern, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("regex_find: first argument must be a string")
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("regex_find: second argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
re, err := regexp.Compile(pattern)
|
re, err := regexp.Compile(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -680,61 +204,34 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
s.PushString(match)
|
s.PushString(match)
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
},
|
}
|
||||||
|
|
||||||
"regex_find_all": func(s *luajit.State) int {
|
func regex_find_all(s *luajit.State) int {
|
||||||
if err := s.CheckExactArgs(2); err != nil {
|
pattern := s.ToString(1)
|
||||||
return s.PushError("regex_find_all: %v", err)
|
str := s.ToString(2)
|
||||||
}
|
|
||||||
pattern, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("regex_find_all: first argument must be a string")
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("regex_find_all: second argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
re, err := regexp.Compile(pattern)
|
re, err := regexp.Compile(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Return empty array for invalid patterns
|
s.PushValue([]string{})
|
||||||
if err := s.PushValue([]string{}); err != nil {
|
|
||||||
return s.PushError("regex_find_all: failed to push result: %v", err)
|
|
||||||
}
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := re.FindAllString(str, -1)
|
matches := re.FindAllString(str, -1)
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
matches = []string{} // Return empty array instead of nil
|
matches = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.PushValue(matches); err != nil {
|
s.PushValue(matches)
|
||||||
return s.PushError("regex_find_all: failed to push result: %v", err)
|
|
||||||
}
|
|
||||||
return 1
|
return 1
|
||||||
},
|
}
|
||||||
|
|
||||||
"regex_replace": func(s *luajit.State) int {
|
func regex_replace(s *luajit.State) int {
|
||||||
if err := s.CheckExactArgs(3); err != nil {
|
pattern := s.ToString(1)
|
||||||
return s.PushError("regex_replace: %v", err)
|
str := s.ToString(2)
|
||||||
}
|
replacement := s.ToString(3)
|
||||||
pattern, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("regex_replace: first argument must be a string")
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(2)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("regex_replace: second argument must be a string")
|
|
||||||
}
|
|
||||||
replacement, err := s.SafeToString(3)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("regex_replace: third argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
re, err := regexp.Compile(pattern)
|
re, err := regexp.Compile(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Return original string for invalid patterns
|
|
||||||
s.PushString(str)
|
s.PushString(str)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -742,134 +239,40 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
result := re.ReplaceAllString(str, replacement)
|
result := re.ReplaceAllString(str, replacement)
|
||||||
s.PushString(result)
|
s.PushString(result)
|
||||||
return 1
|
return 1
|
||||||
},
|
|
||||||
|
|
||||||
"string_to_number": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_to_number: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_to_number: argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim whitespace for more lenient parsing
|
|
||||||
str = strings.TrimSpace(str)
|
|
||||||
|
|
||||||
// Try float first for more general parsing
|
|
||||||
if num, err := strconv.ParseFloat(str, 64); err == nil {
|
|
||||||
s.PushNumber(num)
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func random_string(s *luajit.State) int {
|
||||||
|
length := int(s.ToNumber(1))
|
||||||
|
if length < 0 || length > maxRandomLength {
|
||||||
s.PushNil()
|
s.PushNil()
|
||||||
return 1
|
s.PushString("invalid length")
|
||||||
},
|
return 2
|
||||||
|
|
||||||
"string_is_numeric": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_is_numeric: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_is_numeric: argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
str = strings.TrimSpace(str)
|
|
||||||
if str == "" {
|
|
||||||
s.PushBoolean(false)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err1 := strconv.ParseFloat(str, 64)
|
|
||||||
s.PushBoolean(err1 == nil)
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_is_alpha": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_is_alpha: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_is_alpha: argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(str) == 0 {
|
|
||||||
s.PushBoolean(false)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range str {
|
|
||||||
if !unicode.IsLetter(r) {
|
|
||||||
s.PushBoolean(false)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.PushBoolean(true)
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_is_alphanumeric": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("string_is_alphanumeric: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_is_alphanumeric: argument must be a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(str) == 0 {
|
|
||||||
s.PushBoolean(false)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range str {
|
|
||||||
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
|
|
||||||
s.PushBoolean(false)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.PushBoolean(true)
|
|
||||||
return 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"random_string": func(s *luajit.State) int {
|
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
|
||||||
return s.PushError("random_string: %v", err)
|
|
||||||
}
|
|
||||||
length, err := s.SafeToNumber(1)
|
|
||||||
if err != nil || length != float64(int(length)) || length < 0 {
|
|
||||||
return s.PushError("random_string: first argument must be a non-negative integer")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
if s.GetTop() >= 2 && !s.IsNil(2) {
|
if s.GetTop() >= 2 && !s.IsNil(2) {
|
||||||
if custom, err := s.SafeToString(2); err == nil && len(custom) > 0 {
|
charset = s.ToString(2)
|
||||||
charset = custom
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n := int(length)
|
if length == 0 {
|
||||||
if n == 0 {
|
|
||||||
s.PushString("")
|
s.PushString("")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if n > maxRandomLength {
|
|
||||||
return s.PushError("random_string: length too large (max %d)", maxRandomLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate charset for UTF-8
|
|
||||||
if !utf8.ValidString(charset) {
|
if !utf8.ValidString(charset) {
|
||||||
return s.PushError("random_string: charset must be valid UTF-8")
|
s.PushNil()
|
||||||
|
s.PushString("invalid charset")
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
charsetRunes := []rune(charset)
|
charsetRunes := []rune(charset)
|
||||||
if len(charsetRunes) == 0 {
|
if len(charsetRunes) == 0 {
|
||||||
return s.PushError("random_string: charset cannot be empty")
|
s.PushNil()
|
||||||
|
s.PushString("empty charset")
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]rune, n)
|
result := make([]rune, length)
|
||||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
for i := range result {
|
for i := range result {
|
||||||
result[i] = charsetRunes[rnd.Intn(len(charsetRunes))]
|
result[i] = charsetRunes[rnd.Intn(len(charsetRunes))]
|
||||||
@ -877,18 +280,10 @@ func GetStringFunctions() map[string]luajit.GoFunction {
|
|||||||
|
|
||||||
s.PushString(string(result))
|
s.PushString(string(result))
|
||||||
return 1
|
return 1
|
||||||
},
|
}
|
||||||
|
|
||||||
"string_is_valid_utf8": func(s *luajit.State) int {
|
func string_is_valid_utf8(s *luajit.State) int {
|
||||||
if err := s.CheckMinArgs(1); err != nil {
|
str := s.ToString(1)
|
||||||
return s.PushError("string_is_valid_utf8: %v", err)
|
|
||||||
}
|
|
||||||
str, err := s.SafeToString(1)
|
|
||||||
if err != nil {
|
|
||||||
return s.PushError("string_is_valid_utf8: argument must be a string")
|
|
||||||
}
|
|
||||||
s.PushBoolean(utf8.ValidString(str))
|
s.PushBoolean(utf8.ValidString(str))
|
||||||
return 1
|
return 1
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,245 +2,253 @@
|
|||||||
|
|
||||||
local str = {}
|
local str = {}
|
||||||
|
|
||||||
-- Helper function to handle errors from Go functions
|
|
||||||
local function safe_call(func, ...)
|
|
||||||
local success, result = pcall(func, ...)
|
|
||||||
if not success then
|
|
||||||
error(result, 2)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Helper to validate arguments
|
|
||||||
local function validate_string(s, func_name, arg_num)
|
|
||||||
if type(s) ~= "string" then
|
|
||||||
error(string.format("%s: argument %d must be a string, got %s",
|
|
||||||
func_name, arg_num or 1, type(s)), 3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function validate_number(n, func_name, arg_num)
|
|
||||||
if type(n) ~= "number" then
|
|
||||||
error(string.format("%s: argument %d must be a number, got %s",
|
|
||||||
func_name, arg_num or 1, type(n)), 3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function validate_table(t, func_name, arg_num)
|
|
||||||
if type(t) ~= "table" then
|
|
||||||
error(string.format("%s: argument %d must be a table, got %s",
|
|
||||||
func_name, arg_num or 1, type(t)), 3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
-- BASIC STRING OPERATIONS
|
-- BASIC STRING OPERATIONS (Pure Lua)
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
function str.split(s, delimiter)
|
function str.split(s, delimiter)
|
||||||
validate_string(s, "str.split", 1)
|
if type(s) ~= "string" then error("str.split: first argument must be a string", 2) end
|
||||||
validate_string(delimiter, "str.split", 2)
|
if type(delimiter) ~= "string" then error("str.split: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_split, s, delimiter)
|
return moonshark.string_split(s, delimiter)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.join(arr, separator)
|
function str.join(arr, separator)
|
||||||
validate_table(arr, "str.join", 1)
|
if type(arr) ~= "table" then error("str.join: first argument must be a table", 2) end
|
||||||
validate_string(separator, "str.join", 2)
|
if type(separator) ~= "string" then error("str.join: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_join, arr, separator)
|
return moonshark.string_join(arr, separator)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.trim(s)
|
function str.trim(s)
|
||||||
validate_string(s, "str.trim")
|
if type(s) ~= "string" then error("str.trim: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_trim, s)
|
return s:match("^%s*(.-)%s*$")
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.trim_left(s, cutset)
|
function str.trim_left(s, cutset)
|
||||||
validate_string(s, "str.trim_left", 1)
|
if type(s) ~= "string" then error("str.trim_left: first argument must be a string", 2) end
|
||||||
if cutset ~= nil then
|
if cutset then
|
||||||
validate_string(cutset, "str.trim_left", 2)
|
if type(cutset) ~= "string" then error("str.trim_left: second argument must be a string", 2) end
|
||||||
|
local pattern = "^[" .. cutset:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") .. "]*"
|
||||||
|
return s:gsub(pattern, "")
|
||||||
|
else
|
||||||
|
return s:match("^%s*(.*)")
|
||||||
end
|
end
|
||||||
return safe_call(moonshark.string_trim_left, s, cutset)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.trim_right(s, cutset)
|
function str.trim_right(s, cutset)
|
||||||
validate_string(s, "str.trim_right", 1)
|
if type(s) ~= "string" then error("str.trim_right: first argument must be a string", 2) end
|
||||||
if cutset ~= nil then
|
if cutset then
|
||||||
validate_string(cutset, "str.trim_right", 2)
|
if type(cutset) ~= "string" then error("str.trim_right: second argument must be a string", 2) end
|
||||||
|
local pattern = "[" .. cutset:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") .. "]*$"
|
||||||
|
return s:gsub(pattern, "")
|
||||||
|
else
|
||||||
|
return s:match("(.-)%s*$")
|
||||||
end
|
end
|
||||||
return safe_call(moonshark.string_trim_right, s, cutset)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.upper(s)
|
function str.upper(s)
|
||||||
validate_string(s, "str.upper")
|
if type(s) ~= "string" then error("str.upper: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_upper, s)
|
return s:upper()
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.lower(s)
|
function str.lower(s)
|
||||||
validate_string(s, "str.lower")
|
if type(s) ~= "string" then error("str.lower: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_lower, s)
|
return s:lower()
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.title(s)
|
function str.title(s)
|
||||||
validate_string(s, "str.title")
|
if type(s) ~= "string" then error("str.title: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_title, s)
|
return s:gsub("(%a)([%w_']*)", function(first, rest)
|
||||||
|
return first:upper() .. rest:lower()
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.contains(s, substr)
|
function str.contains(s, substr)
|
||||||
validate_string(s, "str.contains", 1)
|
if type(s) ~= "string" then error("str.contains: first argument must be a string", 2) end
|
||||||
validate_string(substr, "str.contains", 2)
|
if type(substr) ~= "string" then error("str.contains: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_contains, s, substr)
|
return s:find(substr, 1, true) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.starts_with(s, prefix)
|
function str.starts_with(s, prefix)
|
||||||
validate_string(s, "str.starts_with", 1)
|
if type(s) ~= "string" then error("str.starts_with: first argument must be a string", 2) end
|
||||||
validate_string(prefix, "str.starts_with", 2)
|
if type(prefix) ~= "string" then error("str.starts_with: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_starts_with, s, prefix)
|
return s:sub(1, #prefix) == prefix
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.ends_with(s, suffix)
|
function str.ends_with(s, suffix)
|
||||||
validate_string(s, "str.ends_with", 1)
|
if type(s) ~= "string" then error("str.ends_with: first argument must be a string", 2) end
|
||||||
validate_string(suffix, "str.ends_with", 2)
|
if type(suffix) ~= "string" then error("str.ends_with: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_ends_with, s, suffix)
|
return s:sub(-#suffix) == suffix
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.replace(s, old, new)
|
function str.replace(s, old, new)
|
||||||
validate_string(s, "str.replace", 1)
|
if type(s) ~= "string" then error("str.replace: first argument must be a string", 2) end
|
||||||
validate_string(old, "str.replace", 2)
|
if type(old) ~= "string" then error("str.replace: second argument must be a string", 2) end
|
||||||
validate_string(new, "str.replace", 3)
|
if type(new) ~= "string" then error("str.replace: third argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_replace, s, old, new)
|
if old == "" then error("str.replace: cannot replace empty string", 2) end
|
||||||
|
return s:gsub(old:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"), new)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.replace_n(s, old, new, n)
|
function str.replace_n(s, old, new, n)
|
||||||
validate_string(s, "str.replace_n", 1)
|
if type(s) ~= "string" then error("str.replace_n: first argument must be a string", 2) end
|
||||||
validate_string(old, "str.replace_n", 2)
|
if type(old) ~= "string" then error("str.replace_n: second argument must be a string", 2) end
|
||||||
validate_string(new, "str.replace_n", 3)
|
if type(new) ~= "string" then error("str.replace_n: third argument must be a string", 2) end
|
||||||
validate_number(n, "str.replace_n", 4)
|
if type(n) ~= "number" or n < 0 or n ~= math.floor(n) then
|
||||||
if n < 0 or n ~= math.floor(n) then
|
error("str.replace_n: fourth argument must be a non-negative integer", 2)
|
||||||
error("str.replace_n: count must be a non-negative integer", 2)
|
|
||||||
end
|
end
|
||||||
return safe_call(moonshark.string_replace_n, s, old, new, n)
|
if old == "" then error("str.replace_n: cannot replace empty string", 2) end
|
||||||
|
local escaped = old:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
|
||||||
|
return (s:gsub(escaped, new, n))
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.index(s, substr)
|
function str.index(s, substr)
|
||||||
validate_string(s, "str.index", 1)
|
if type(s) ~= "string" then error("str.index: first argument must be a string", 2) end
|
||||||
validate_string(substr, "str.index", 2)
|
if type(substr) ~= "string" then error("str.index: second argument must be a string", 2) end
|
||||||
local idx = safe_call(moonshark.string_index, s, substr)
|
local pos = s:find(substr, 1, true)
|
||||||
return idx > 0 and idx or nil
|
return pos
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.last_index(s, substr)
|
function str.last_index(s, substr)
|
||||||
validate_string(s, "str.last_index", 1)
|
if type(s) ~= "string" then error("str.last_index: first argument must be a string", 2) end
|
||||||
validate_string(substr, "str.last_index", 2)
|
if type(substr) ~= "string" then error("str.last_index: second argument must be a string", 2) end
|
||||||
local idx = safe_call(moonshark.string_last_index, s, substr)
|
local last_pos = nil
|
||||||
return idx > 0 and idx or nil
|
local pos = 1
|
||||||
|
while true do
|
||||||
|
local found = s:find(substr, pos, true)
|
||||||
|
if not found then break end
|
||||||
|
last_pos = found
|
||||||
|
pos = found + 1
|
||||||
|
end
|
||||||
|
return last_pos
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.count(s, substr)
|
function str.count(s, substr)
|
||||||
validate_string(s, "str.count", 1)
|
if type(s) ~= "string" then error("str.count: first argument must be a string", 2) end
|
||||||
validate_string(substr, "str.count", 2)
|
if type(substr) ~= "string" then error("str.count: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_count, s, substr)
|
if substr == "" then return #s + 1 end
|
||||||
|
local count = 0
|
||||||
|
local pos = 1
|
||||||
|
while true do
|
||||||
|
local found = s:find(substr, pos, true)
|
||||||
|
if not found then break end
|
||||||
|
count = count + 1
|
||||||
|
pos = found + #substr
|
||||||
|
end
|
||||||
|
return count
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.repeat_(s, n)
|
function str.repeat_(s, n)
|
||||||
validate_string(s, "str.repeat_", 1)
|
if type(s) ~= "string" then error("str.repeat_: first argument must be a string", 2) end
|
||||||
validate_number(n, "str.repeat_", 2)
|
if type(n) ~= "number" or n < 0 or n ~= math.floor(n) then
|
||||||
if n < 0 or n ~= math.floor(n) then
|
error("str.repeat_: second argument must be a non-negative integer", 2)
|
||||||
error("str.repeat_: count must be a non-negative integer", 2)
|
|
||||||
end
|
end
|
||||||
return safe_call(moonshark.string_repeat, s, n)
|
return string.rep(s, n)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.reverse(s)
|
function str.reverse(s)
|
||||||
validate_string(s, "str.reverse")
|
if type(s) ~= "string" then error("str.reverse: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_reverse, s)
|
local result, err = moonshark.string_reverse(s)
|
||||||
|
if not result then error("str.reverse: " .. err, 2) end
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.length(s)
|
function str.length(s)
|
||||||
validate_string(s, "str.length")
|
if type(s) ~= "string" then error("str.length: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_length, s)
|
return moonshark.string_length(s)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.byte_length(s)
|
function str.byte_length(s)
|
||||||
validate_string(s, "str.byte_length")
|
if type(s) ~= "string" then error("str.byte_length: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_byte_length, s)
|
return #s
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.lines(s)
|
function str.lines(s)
|
||||||
validate_string(s, "str.lines")
|
if type(s) ~= "string" then error("str.lines: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_lines, s)
|
s = s:gsub("\r\n", "\n"):gsub("\r", "\n")
|
||||||
|
local lines = {}
|
||||||
|
for line in (s .. "\n"):gmatch("([^\n]*)\n") do
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
if #lines > 0 and lines[#lines] == "" then
|
||||||
|
table.remove(lines)
|
||||||
|
end
|
||||||
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.words(s)
|
function str.words(s)
|
||||||
validate_string(s, "str.words")
|
if type(s) ~= "string" then error("str.words: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_words, s)
|
local words = {}
|
||||||
|
for word in s:gmatch("%S+") do
|
||||||
|
table.insert(words, word)
|
||||||
|
end
|
||||||
|
return words
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.pad_left(s, width, pad_char)
|
function str.pad_left(s, width, pad_char)
|
||||||
validate_string(s, "str.pad_left", 1)
|
if type(s) ~= "string" then error("str.pad_left: first argument must be a string", 2) end
|
||||||
validate_number(width, "str.pad_left", 2)
|
if type(width) ~= "number" or width < 0 or width ~= math.floor(width) then
|
||||||
if width < 0 or width ~= math.floor(width) then
|
error("str.pad_left: second argument must be a non-negative integer", 2)
|
||||||
error("str.pad_left: width must be a non-negative integer", 2)
|
|
||||||
end
|
end
|
||||||
if pad_char ~= nil then
|
pad_char = pad_char or " "
|
||||||
validate_string(pad_char, "str.pad_left", 3)
|
if type(pad_char) ~= "string" then error("str.pad_left: third argument must be a string", 2) end
|
||||||
end
|
if #pad_char == 0 then pad_char = " " else pad_char = pad_char:sub(1,1) end
|
||||||
return safe_call(moonshark.string_pad_left, s, width, pad_char)
|
local current_len = str.length(s)
|
||||||
|
if current_len >= width then return s end
|
||||||
|
return string.rep(pad_char, width - current_len) .. s
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.pad_right(s, width, pad_char)
|
function str.pad_right(s, width, pad_char)
|
||||||
validate_string(s, "str.pad_right", 1)
|
if type(s) ~= "string" then error("str.pad_right: first argument must be a string", 2) end
|
||||||
validate_number(width, "str.pad_right", 2)
|
if type(width) ~= "number" or width < 0 or width ~= math.floor(width) then
|
||||||
if width < 0 or width ~= math.floor(width) then
|
error("str.pad_right: second argument must be a non-negative integer", 2)
|
||||||
error("str.pad_right: width must be a non-negative integer", 2)
|
|
||||||
end
|
end
|
||||||
if pad_char ~= nil then
|
pad_char = pad_char or " "
|
||||||
validate_string(pad_char, "str.pad_right", 3)
|
if type(pad_char) ~= "string" then error("str.pad_right: third argument must be a string", 2) end
|
||||||
end
|
if #pad_char == 0 then pad_char = " " else pad_char = pad_char:sub(1,1) end
|
||||||
return safe_call(moonshark.string_pad_right, s, width, pad_char)
|
local current_len = str.length(s)
|
||||||
|
if current_len >= width then return s end
|
||||||
|
return s .. string.rep(pad_char, width - current_len)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.slice(s, start, end_pos)
|
function str.slice(s, start, end_pos)
|
||||||
validate_string(s, "str.slice", 1)
|
if type(s) ~= "string" then error("str.slice: first argument must be a string", 2) end
|
||||||
validate_number(start, "str.slice", 2)
|
if type(start) ~= "number" or start ~= math.floor(start) then
|
||||||
if start ~= math.floor(start) then
|
error("str.slice: second argument must be an integer", 2)
|
||||||
error("str.slice: start must be an integer", 2)
|
|
||||||
end
|
end
|
||||||
if end_pos ~= nil then
|
if end_pos ~= nil and (type(end_pos) ~= "number" or end_pos ~= math.floor(end_pos)) then
|
||||||
validate_number(end_pos, "str.slice", 3)
|
error("str.slice: third argument must be an integer", 2)
|
||||||
if end_pos ~= math.floor(end_pos) then
|
|
||||||
error("str.slice: end position must be an integer", 2)
|
|
||||||
end
|
end
|
||||||
end
|
local result, err = moonshark.string_slice(s, start, end_pos)
|
||||||
return safe_call(moonshark.string_slice, s, start, end_pos)
|
if not result then error("str.slice: " .. err, 2) end
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
-- REGULAR EXPRESSIONS
|
-- REGULAR EXPRESSIONS (Go Functions)
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
function str.match(pattern, s)
|
function str.match(pattern, s)
|
||||||
validate_string(pattern, "str.match", 1)
|
if type(pattern) ~= "string" then error("str.match: first argument must be a string", 2) end
|
||||||
validate_string(s, "str.match", 2)
|
if type(s) ~= "string" then error("str.match: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.regex_match, pattern, s)
|
return moonshark.regex_match(pattern, s)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.find(pattern, s)
|
function str.find(pattern, s)
|
||||||
validate_string(pattern, "str.find", 1)
|
if type(pattern) ~= "string" then error("str.find: first argument must be a string", 2) end
|
||||||
validate_string(s, "str.find", 2)
|
if type(s) ~= "string" then error("str.find: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.regex_find, pattern, s)
|
return moonshark.regex_find(pattern, s)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.find_all(pattern, s)
|
function str.find_all(pattern, s)
|
||||||
validate_string(pattern, "str.find_all", 1)
|
if type(pattern) ~= "string" then error("str.find_all: first argument must be a string", 2) end
|
||||||
validate_string(s, "str.find_all", 2)
|
if type(s) ~= "string" then error("str.find_all: second argument must be a string", 2) end
|
||||||
return safe_call(moonshark.regex_find_all, pattern, s)
|
return moonshark.regex_find_all(pattern, s)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.gsub(pattern, s, replacement)
|
function str.gsub(pattern, s, replacement)
|
||||||
validate_string(pattern, "str.gsub", 1)
|
if type(pattern) ~= "string" then error("str.gsub: first argument must be a string", 2) end
|
||||||
validate_string(s, "str.gsub", 2)
|
if type(s) ~= "string" then error("str.gsub: second argument must be a string", 2) end
|
||||||
validate_string(replacement, "str.gsub", 3)
|
if type(replacement) ~= "string" then error("str.gsub: third argument must be a string", 2) end
|
||||||
return safe_call(moonshark.regex_replace, pattern, s, replacement)
|
return moonshark.regex_replace(pattern, s, replacement)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
@ -248,23 +256,27 @@ end
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
function str.to_number(s)
|
function str.to_number(s)
|
||||||
validate_string(s, "str.to_number")
|
if type(s) ~= "string" then error("str.to_number: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_to_number, s)
|
s = str.trim(s)
|
||||||
|
return tonumber(s)
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.is_numeric(s)
|
function str.is_numeric(s)
|
||||||
validate_string(s, "str.is_numeric")
|
if type(s) ~= "string" then error("str.is_numeric: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_is_numeric, s)
|
s = str.trim(s)
|
||||||
|
return tonumber(s) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.is_alpha(s)
|
function str.is_alpha(s)
|
||||||
validate_string(s, "str.is_alpha")
|
if type(s) ~= "string" then error("str.is_alpha: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_is_alpha, s)
|
if #s == 0 then return false end
|
||||||
|
return s:match("^%a+$") ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.is_alphanumeric(s)
|
function str.is_alphanumeric(s)
|
||||||
validate_string(s, "str.is_alphanumeric")
|
if type(s) ~= "string" then error("str.is_alphanumeric: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_is_alphanumeric, s)
|
if #s == 0 then return false end
|
||||||
|
return s:match("^%w+$") ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function str.is_empty(s)
|
function str.is_empty(s)
|
||||||
@ -276,105 +288,74 @@ function str.is_blank(s)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function str.is_utf8(s)
|
function str.is_utf8(s)
|
||||||
validate_string(s, "str.is_utf8")
|
if type(s) ~= "string" then error("str.is_utf8: argument must be a string", 2) end
|
||||||
return safe_call(moonshark.string_is_valid_utf8, s)
|
return moonshark.string_is_valid_utf8(s)
|
||||||
end
|
|
||||||
|
|
||||||
function str.is_valid_utf8(s)
|
|
||||||
validate_string(s, "str.is_valid_utf8")
|
|
||||||
return safe_call(moonshark.string_is_valid_utf8, s)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
-- ADVANCED STRING OPERATIONS
|
-- ADVANCED STRING OPERATIONS (Pure Lua)
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
-- Capitalize first letter of each word (Pure Lua - faster)
|
|
||||||
function str.capitalize(s)
|
function str.capitalize(s)
|
||||||
validate_string(s, "str.capitalize")
|
if type(s) ~= "string" then error("str.capitalize: argument must be a string", 2) end
|
||||||
return s:gsub("(%a)([%w_']*)", function(first, rest)
|
return s:gsub("(%a)([%w_']*)", function(first, rest)
|
||||||
return first:upper() .. rest:lower()
|
return first:upper() .. rest:lower()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Convert string to camelCase (Pure Lua - faster)
|
|
||||||
function str.camel_case(s)
|
function str.camel_case(s)
|
||||||
validate_string(s, "str.camel_case")
|
if type(s) ~= "string" then error("str.camel_case: argument must be a string", 2) end
|
||||||
local words = {}
|
local words = str.words(s)
|
||||||
for word in s:gmatch("%S+") do
|
|
||||||
table.insert(words, word:lower())
|
|
||||||
end
|
|
||||||
if #words == 0 then return s end
|
if #words == 0 then return s end
|
||||||
|
local result = words[1]:lower()
|
||||||
local result = words[1]
|
|
||||||
for i = 2, #words do
|
for i = 2, #words do
|
||||||
result = result .. words[i]:gsub("^%l", string.upper)
|
result = result .. words[i]:sub(1,1):upper() .. words[i]:sub(2):lower()
|
||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Convert string to PascalCase (Pure Lua - faster)
|
|
||||||
function str.pascal_case(s)
|
function str.pascal_case(s)
|
||||||
validate_string(s, "str.pascal_case")
|
if type(s) ~= "string" then error("str.pascal_case: argument must be a string", 2) end
|
||||||
local words = {}
|
local words = str.words(s)
|
||||||
for word in s:gmatch("%S+") do
|
|
||||||
table.insert(words, word:lower())
|
|
||||||
end
|
|
||||||
local result = ""
|
local result = ""
|
||||||
for _, word in ipairs(words) do
|
for _, word in ipairs(words) do
|
||||||
result = result .. word:gsub("^%l", string.upper)
|
result = result .. word:sub(1,1):upper() .. word:sub(2):lower()
|
||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Convert string to snake_case (Pure Lua - faster)
|
|
||||||
function str.snake_case(s)
|
function str.snake_case(s)
|
||||||
validate_string(s, "str.snake_case")
|
if type(s) ~= "string" then error("str.snake_case: argument must be a string", 2) end
|
||||||
local words = {}
|
local words = str.words(s)
|
||||||
for word in s:gmatch("%S+") do
|
local result = {}
|
||||||
table.insert(words, word:lower())
|
for _, word in ipairs(words) do
|
||||||
|
table.insert(result, word:lower())
|
||||||
end
|
end
|
||||||
return table.concat(words, "_")
|
return table.concat(result, "_")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Convert string to kebab-case (Pure Lua - faster)
|
|
||||||
function str.kebab_case(s)
|
function str.kebab_case(s)
|
||||||
validate_string(s, "str.kebab_case")
|
if type(s) ~= "string" then error("str.kebab_case: argument must be a string", 2) end
|
||||||
local words = {}
|
local words = str.words(s)
|
||||||
for word in s:gmatch("%S+") do
|
local result = {}
|
||||||
table.insert(words, word:lower())
|
for _, word in ipairs(words) do
|
||||||
|
table.insert(result, word:lower())
|
||||||
end
|
end
|
||||||
return table.concat(words, "-")
|
return table.concat(result, "-")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Convert string to SCREAMING_SNAKE_CASE (Pure Lua - faster)
|
|
||||||
function str.screaming_snake_case(s)
|
|
||||||
validate_string(s, "str.screaming_snake_case")
|
|
||||||
local words = {}
|
|
||||||
for word in s:gmatch("%S+") do
|
|
||||||
table.insert(words, word:upper())
|
|
||||||
end
|
|
||||||
return table.concat(words, "_")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Center text within given width (Pure Lua - faster)
|
|
||||||
function str.center(s, width, fill_char)
|
function str.center(s, width, fill_char)
|
||||||
validate_string(s, "str.center", 1)
|
if type(s) ~= "string" then error("str.center: first argument must be a string", 2) end
|
||||||
validate_number(width, "str.center", 2)
|
if type(width) ~= "number" or width < 0 or width ~= math.floor(width) then
|
||||||
if width < 0 or width ~= math.floor(width) then
|
error("str.center: second argument must be a non-negative integer", 2)
|
||||||
error("str.center: width must be a non-negative integer", 2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
fill_char = fill_char or " "
|
fill_char = fill_char or " "
|
||||||
if fill_char ~= nil then
|
if type(fill_char) ~= "string" or #fill_char == 0 then
|
||||||
validate_string(fill_char, "str.center", 3)
|
error("str.center: fill character must be a non-empty string", 2)
|
||||||
if #fill_char == 0 then
|
|
||||||
error("str.center: fill character cannot be empty", 2)
|
|
||||||
end
|
|
||||||
fill_char = fill_char:sub(1,1) -- Use only first character
|
|
||||||
end
|
end
|
||||||
|
fill_char = fill_char:sub(1,1)
|
||||||
|
|
||||||
local len = #s
|
local len = str.length(s)
|
||||||
if len >= width then return s end
|
if len >= width then return s end
|
||||||
|
|
||||||
local pad_total = width - len
|
local pad_total = width - len
|
||||||
@ -384,192 +365,68 @@ function str.center(s, width, fill_char)
|
|||||||
return string.rep(fill_char, pad_left) .. s .. string.rep(fill_char, pad_right)
|
return string.rep(fill_char, pad_left) .. s .. string.rep(fill_char, pad_right)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Truncate string to maximum length (Pure Lua - faster)
|
|
||||||
function str.truncate(s, max_length, suffix)
|
function str.truncate(s, max_length, suffix)
|
||||||
validate_string(s, "str.truncate", 1)
|
if type(s) ~= "string" then error("str.truncate: first argument must be a string", 2) end
|
||||||
validate_number(max_length, "str.truncate", 2)
|
if type(max_length) ~= "number" or max_length < 0 or max_length ~= math.floor(max_length) then
|
||||||
if max_length < 0 or max_length ~= math.floor(max_length) then
|
error("str.truncate: second argument must be a non-negative integer", 2)
|
||||||
error("str.truncate: max_length must be a non-negative integer", 2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
suffix = suffix or "..."
|
suffix = suffix or "..."
|
||||||
validate_string(suffix, "str.truncate", 3)
|
if type(suffix) ~= "string" then error("str.truncate: third argument must be a string", 2) end
|
||||||
|
|
||||||
if #s <= max_length then
|
local len = str.length(s)
|
||||||
return s
|
if len <= max_length then return s end
|
||||||
end
|
|
||||||
|
|
||||||
local suffix_len = #suffix
|
local suffix_len = str.length(suffix)
|
||||||
if max_length <= suffix_len then
|
if max_length <= suffix_len then
|
||||||
return suffix:sub(1, max_length)
|
return str.slice(suffix, 1, max_length)
|
||||||
end
|
end
|
||||||
|
|
||||||
local main_part = s:sub(1, max_length - suffix_len)
|
local main_part = str.slice(s, 1, max_length - suffix_len)
|
||||||
main_part = main_part:gsub("%s+$", "") -- trim right
|
main_part = str.trim_right(main_part)
|
||||||
return main_part .. suffix
|
return main_part .. suffix
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Wrap text to specified width (Pure Lua - much faster)
|
|
||||||
function str.wrap(s, width)
|
|
||||||
validate_string(s, "str.wrap", 1)
|
|
||||||
validate_number(width, "str.wrap", 2)
|
|
||||||
if width <= 0 or width ~= math.floor(width) then
|
|
||||||
error("str.wrap: width must be a positive integer", 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
local words = {}
|
|
||||||
for word in s:gmatch("%S+") do
|
|
||||||
table.insert(words, word)
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines = {}
|
|
||||||
local current_line = ""
|
|
||||||
|
|
||||||
for _, word in ipairs(words) do
|
|
||||||
local word_len = #word
|
|
||||||
local current_len = #current_line
|
|
||||||
|
|
||||||
-- Handle words longer than width
|
|
||||||
if word_len > width then
|
|
||||||
if current_line ~= "" then
|
|
||||||
table.insert(lines, current_line)
|
|
||||||
current_line = ""
|
|
||||||
end
|
|
||||||
-- Break long word
|
|
||||||
while #word > width do
|
|
||||||
table.insert(lines, word:sub(1, width))
|
|
||||||
word = word:sub(width + 1)
|
|
||||||
end
|
|
||||||
if #word > 0 then
|
|
||||||
current_line = word
|
|
||||||
end
|
|
||||||
elseif current_len + word_len + 1 <= width then
|
|
||||||
if current_line == "" then
|
|
||||||
current_line = word
|
|
||||||
else
|
|
||||||
current_line = current_line .. " " .. word
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if current_line ~= "" then
|
|
||||||
table.insert(lines, current_line)
|
|
||||||
end
|
|
||||||
current_line = word
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if current_line ~= "" then
|
|
||||||
table.insert(lines, current_line)
|
|
||||||
end
|
|
||||||
|
|
||||||
return lines
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove common leading whitespace (Pure Lua - faster)
|
|
||||||
function str.dedent(s)
|
|
||||||
validate_string(s, "str.dedent")
|
|
||||||
local lines = {}
|
|
||||||
for line in (s.."\n"):gmatch("([^\n]*)\n") do
|
|
||||||
table.insert(lines, line)
|
|
||||||
end
|
|
||||||
if #lines <= 1 then return s end
|
|
||||||
|
|
||||||
-- Find minimum indentation (excluding empty lines)
|
|
||||||
local min_indent = math.huge
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
local trimmed = line:gsub("%s", "")
|
|
||||||
if trimmed ~= "" then
|
|
||||||
local indent = line:match("^%s*")
|
|
||||||
if indent then
|
|
||||||
min_indent = math.min(min_indent, #indent)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if min_indent == math.huge or min_indent == 0 then
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove common indentation
|
|
||||||
for i, line in ipairs(lines) do
|
|
||||||
local trimmed = line:gsub("%s", "")
|
|
||||||
if trimmed ~= "" then
|
|
||||||
lines[i] = line:sub(min_indent + 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return table.concat(lines, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Escape special characters for regex
|
|
||||||
function str.escape_regex(s)
|
function str.escape_regex(s)
|
||||||
validate_string(s, "str.escape_regex")
|
if type(s) ~= "string" then error("str.escape_regex: argument must be a string", 2) end
|
||||||
return s:gsub("([%.%+%*%?%[%]%^%$%(%)%{%}%|%\\])", "\\%1")
|
return s:gsub("([%.%+%*%?%[%]%^%$%(%)%{%}%|%\\])", "\\%1")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Quote string for shell usage
|
|
||||||
function str.shell_quote(s)
|
|
||||||
validate_string(s, "str.shell_quote")
|
|
||||||
return "'" .. s:gsub("'", "'\"'\"'") .. "'"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- URL encode string
|
|
||||||
function str.url_encode(s)
|
function str.url_encode(s)
|
||||||
validate_string(s, "str.url_encode")
|
if type(s) ~= "string" then error("str.url_encode: argument must be a string", 2) end
|
||||||
return s:gsub("([^%w%-%.%_%~])", function(c)
|
return s:gsub("([^%w%-%.%_%~])", function(c)
|
||||||
return string.format("%%%02X", string.byte(c))
|
return string.format("%%%02X", string.byte(c))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- URL decode string
|
|
||||||
function str.url_decode(s)
|
function str.url_decode(s)
|
||||||
validate_string(s, "str.url_decode")
|
if type(s) ~= "string" then error("str.url_decode: argument must be a string", 2) end
|
||||||
local result = s:gsub("%%(%x%x)", function(hex)
|
local result = s:gsub("%%(%x%x)", function(hex)
|
||||||
local byte = tonumber(hex, 16)
|
local byte = tonumber(hex, 16)
|
||||||
if byte then
|
return byte and string.char(byte) or ("%" .. hex)
|
||||||
return string.char(byte)
|
|
||||||
else
|
|
||||||
return "%" .. hex -- Invalid hex, keep original
|
|
||||||
end
|
|
||||||
end):gsub("+", " ")
|
end):gsub("+", " ")
|
||||||
|
|
||||||
-- Validate result is UTF-8
|
if not str.is_utf8(result) then
|
||||||
if not str.is_valid_utf8(result) then
|
|
||||||
error("str.url_decode: result is not valid UTF-8", 2)
|
error("str.url_decode: result is not valid UTF-8", 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- STRING COMPARISON
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
-- Case-insensitive comparison
|
|
||||||
function str.iequals(a, b)
|
|
||||||
validate_string(a, "str.iequals", 1)
|
|
||||||
validate_string(b, "str.iequals", 2)
|
|
||||||
return str.lower(a) == str.lower(b)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Levenshtein distance (Pure Lua - much faster)
|
|
||||||
function str.distance(a, b)
|
function str.distance(a, b)
|
||||||
validate_string(a, "str.distance", 1)
|
if type(a) ~= "string" then error("str.distance: first argument must be a string", 2) end
|
||||||
validate_string(b, "str.distance", 2)
|
if type(b) ~= "string" then error("str.distance: second argument must be a string", 2) end
|
||||||
|
|
||||||
local len_a, len_b = #a, #b
|
local len_a, len_b = str.length(a), str.length(b)
|
||||||
|
|
||||||
-- Handle empty strings
|
|
||||||
if len_a == 0 then return len_b end
|
if len_a == 0 then return len_b end
|
||||||
if len_b == 0 then return len_a end
|
if len_b == 0 then return len_a end
|
||||||
|
|
||||||
-- Limit computation for very long strings
|
|
||||||
if len_a > 1000 or len_b > 1000 then
|
if len_a > 1000 or len_b > 1000 then
|
||||||
error("str.distance: strings too long for distance calculation", 2)
|
error("str.distance: strings too long for distance calculation", 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
local matrix = {}
|
local matrix = {}
|
||||||
|
|
||||||
-- Initialize matrix
|
|
||||||
for i = 0, len_a do
|
for i = 0, len_a do
|
||||||
matrix[i] = {[0] = i}
|
matrix[i] = {[0] = i}
|
||||||
end
|
end
|
||||||
@ -577,14 +434,13 @@ function str.distance(a, b)
|
|||||||
matrix[0][j] = j
|
matrix[0][j] = j
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Fill matrix
|
|
||||||
for i = 1, len_a do
|
for i = 1, len_a do
|
||||||
for j = 1, len_b do
|
for j = 1, len_b do
|
||||||
local cost = (a:sub(i,i) == b:sub(j,j)) and 0 or 1
|
local cost = (str.slice(a, i, i) == str.slice(b, j, j)) and 0 or 1
|
||||||
matrix[i][j] = math.min(
|
matrix[i][j] = math.min(
|
||||||
matrix[i-1][j] + 1, -- deletion
|
matrix[i-1][j] + 1,
|
||||||
matrix[i][j-1] + 1, -- insertion
|
matrix[i][j-1] + 1,
|
||||||
matrix[i-1][j-1] + cost -- substitution
|
matrix[i-1][j-1] + cost
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -592,173 +448,44 @@ function str.distance(a, b)
|
|||||||
return matrix[len_a][len_b]
|
return matrix[len_a][len_b]
|
||||||
end
|
end
|
||||||
|
|
||||||
-- String similarity (0-1) (Pure Lua - faster)
|
|
||||||
function str.similarity(a, b)
|
function str.similarity(a, b)
|
||||||
validate_string(a, "str.similarity", 1)
|
if type(a) ~= "string" then error("str.similarity: first argument must be a string", 2) end
|
||||||
validate_string(b, "str.similarity", 2)
|
if type(b) ~= "string" then error("str.similarity: second argument must be a string", 2) end
|
||||||
|
|
||||||
local max_len = math.max(#a, #b)
|
local max_len = math.max(str.length(a), str.length(b))
|
||||||
if max_len == 0 then return 1.0 end
|
if max_len == 0 then return 1.0 end
|
||||||
|
|
||||||
local dist = str.distance(a, b)
|
local dist = str.distance(a, b)
|
||||||
return 1.0 - (dist / max_len)
|
return 1.0 - (dist / max_len)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- TEMPLATE FUNCTIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
-- Simple template substitution (Pure Lua - faster)
|
|
||||||
function str.template(template, vars)
|
function str.template(template, vars)
|
||||||
validate_string(template, "str.template", 1)
|
if type(template) ~= "string" then error("str.template: first argument must be a string", 2) end
|
||||||
if vars ~= nil then
|
vars = vars or {}
|
||||||
validate_table(vars, "str.template", 2)
|
if type(vars) ~= "table" then error("str.template: second argument must be a table", 2) end
|
||||||
else
|
|
||||||
vars = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
return template:gsub("%${([%w_]+)}", function(var)
|
return template:gsub("%${([%w_]+)}", function(var)
|
||||||
local value = vars[var]
|
local value = vars[var]
|
||||||
if value == nil then
|
return value ~= nil and tostring(value) or ""
|
||||||
return ""
|
|
||||||
else
|
|
||||||
return tostring(value)
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Advanced template with functions (Pure Lua - faster)
|
function str.random(length, charset)
|
||||||
function str.template_advanced(template, context)
|
if type(length) ~= "number" or length < 0 or length ~= math.floor(length) then
|
||||||
validate_string(template, "str.template_advanced", 1)
|
error("str.random: first argument must be a non-negative integer", 2)
|
||||||
if context ~= nil then
|
|
||||||
validate_table(context, "str.template_advanced", 2)
|
|
||||||
else
|
|
||||||
context = {}
|
|
||||||
end
|
end
|
||||||
|
if charset ~= nil and type(charset) ~= "string" then
|
||||||
return template:gsub("%${([^}]+)}", function(expr)
|
error("str.random: second argument must be a string", 2)
|
||||||
-- Simple variable substitution
|
|
||||||
if context[expr] then
|
|
||||||
return tostring(context[expr])
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Handle simple expressions like ${var.prop}
|
|
||||||
local parts = {}
|
|
||||||
for part in expr:gmatch("[^%.]+") do
|
|
||||||
table.insert(parts, part)
|
|
||||||
end
|
|
||||||
|
|
||||||
local value = context
|
|
||||||
for _, part in ipairs(parts) do
|
|
||||||
if type(value) == "table" and value[part] ~= nil then
|
|
||||||
value = value[part]
|
|
||||||
else
|
|
||||||
return ""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return tostring(value)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- UTILITY FUNCTIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
-- Check if string contains only whitespace
|
|
||||||
function str.is_whitespace(s)
|
|
||||||
validate_string(s, "str.is_whitespace")
|
|
||||||
return s:match("^%s*$") ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove all whitespace
|
|
||||||
function str.strip_whitespace(s)
|
|
||||||
validate_string(s, "str.strip_whitespace")
|
|
||||||
return s:gsub("%s", "")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Normalize whitespace (replace multiple spaces with single space)
|
|
||||||
function str.normalize_whitespace(s)
|
|
||||||
validate_string(s, "str.normalize_whitespace")
|
|
||||||
return str.trim(s:gsub("%s+", " "))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Extract numbers from string
|
|
||||||
function str.extract_numbers(s)
|
|
||||||
validate_string(s, "str.extract_numbers")
|
|
||||||
local numbers = {}
|
|
||||||
for num in s:gmatch("%-?%d+%.?%d*") do
|
|
||||||
local n = tonumber(num)
|
|
||||||
if n then table.insert(numbers, n) end
|
|
||||||
end
|
|
||||||
return numbers
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove diacritics/accents
|
|
||||||
function str.remove_accents(s)
|
|
||||||
validate_string(s, "str.remove_accents")
|
|
||||||
local accents = {
|
|
||||||
["à"] = "a", ["á"] = "a", ["â"] = "a", ["ã"] = "a", ["ä"] = "a", ["å"] = "a",
|
|
||||||
["è"] = "e", ["é"] = "e", ["ê"] = "e", ["ë"] = "e",
|
|
||||||
["ì"] = "i", ["í"] = "i", ["î"] = "i", ["ï"] = "i",
|
|
||||||
["ò"] = "o", ["ó"] = "o", ["ô"] = "o", ["õ"] = "o", ["ö"] = "o",
|
|
||||||
["ù"] = "u", ["ú"] = "u", ["û"] = "u", ["ü"] = "u",
|
|
||||||
["ñ"] = "n", ["ç"] = "c", ["ý"] = "y", ["ÿ"] = "y",
|
|
||||||
-- Uppercase versions
|
|
||||||
["À"] = "A", ["Á"] = "A", ["Â"] = "A", ["Ã"] = "A", ["Ä"] = "A", ["Å"] = "A",
|
|
||||||
["È"] = "E", ["É"] = "E", ["Ê"] = "E", ["Ë"] = "E",
|
|
||||||
["Ì"] = "I", ["Í"] = "I", ["Î"] = "I", ["Ï"] = "I",
|
|
||||||
["Ò"] = "O", ["Ó"] = "O", ["Ô"] = "O", ["Õ"] = "O", ["Ö"] = "O",
|
|
||||||
["Ù"] = "U", ["Ú"] = "U", ["Û"] = "U", ["Ü"] = "U",
|
|
||||||
["Ñ"] = "N", ["Ç"] = "C", ["Ý"] = "Y", ["Ÿ"] = "Y"
|
|
||||||
}
|
|
||||||
|
|
||||||
local result = s
|
|
||||||
for accented, plain in pairs(accents) do
|
|
||||||
result = result:gsub(accented, plain)
|
|
||||||
end
|
end
|
||||||
|
local result, err = moonshark.random_string(length, charset)
|
||||||
|
if not result then error("str.random: " .. err, 2) end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Generate random string
|
|
||||||
function str.random(length, charset)
|
|
||||||
validate_number(length, "str.random", 1)
|
|
||||||
if length < 0 or length ~= math.floor(length) then
|
|
||||||
error("str.random: length must be a non-negative integer", 2)
|
|
||||||
end
|
|
||||||
if charset ~= nil then
|
|
||||||
validate_string(charset, "str.random", 2)
|
|
||||||
end
|
|
||||||
return safe_call(moonshark.random_string, length, charset)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Generate slug from string (Pure Lua - faster)
|
|
||||||
function str.slug(s)
|
function str.slug(s)
|
||||||
validate_string(s, "str.slug")
|
if type(s) ~= "string" then error("str.slug: argument must be a string", 2) end
|
||||||
|
|
||||||
-- Remove accents (simplified but faster)
|
|
||||||
local accents = {
|
|
||||||
["à"] = "a", ["á"] = "a", ["â"] = "a", ["ã"] = "a", ["ä"] = "a", ["å"] = "a",
|
|
||||||
["è"] = "e", ["é"] = "e", ["ê"] = "e", ["ë"] = "e",
|
|
||||||
["ì"] = "i", ["í"] = "i", ["î"] = "i", ["ï"] = "i",
|
|
||||||
["ò"] = "o", ["ó"] = "o", ["ô"] = "o", ["õ"] = "o", ["ö"] = "o",
|
|
||||||
["ù"] = "u", ["ú"] = "u", ["û"] = "u", ["ü"] = "u",
|
|
||||||
["ñ"] = "n", ["ç"] = "c", ["ý"] = "y", ["ÿ"] = "y",
|
|
||||||
-- Uppercase versions
|
|
||||||
["À"] = "A", ["Á"] = "A", ["Â"] = "A", ["Ã"] = "A", ["Ä"] = "A", ["Å"] = "A",
|
|
||||||
["È"] = "E", ["É"] = "E", ["Ê"] = "E", ["Ë"] = "E",
|
|
||||||
["Ì"] = "I", ["Í"] = "I", ["Î"] = "I", ["Ï"] = "I",
|
|
||||||
["Ò"] = "O", ["Ó"] = "O", ["Ô"] = "O", ["Õ"] = "O", ["Ö"] = "O",
|
|
||||||
["Ù"] = "U", ["Ú"] = "U", ["Û"] = "U", ["Ü"] = "U",
|
|
||||||
["Ñ"] = "N", ["Ç"] = "C", ["Ý"] = "Y", ["Ÿ"] = "Y"
|
|
||||||
}
|
|
||||||
|
|
||||||
local result = s:lower()
|
local result = s:lower()
|
||||||
for accented, plain in pairs(accents) do
|
|
||||||
result = result:gsub(accented:lower(), plain:lower())
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Keep only alphanumeric characters and spaces, then convert spaces to hyphens
|
|
||||||
result = result:gsub("[^%w%s]", "")
|
result = result:gsub("[^%w%s]", "")
|
||||||
result = result:gsub("%s+", "-")
|
result = result:gsub("%s+", "-")
|
||||||
result = result:gsub("^%-+", ""):gsub("%-+$", "")
|
result = result:gsub("^%-+", ""):gsub("%-+$", "")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user