diff --git a/core/runner/crypto.go b/core/runner/crypto.go new file mode 100644 index 0000000..39ef1e3 --- /dev/null +++ b/core/runner/crypto.go @@ -0,0 +1,404 @@ +package runner + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/binary" + "encoding/hex" + "fmt" + "hash" + "math" + mrand "math/rand/v2" + "sync" + "time" + + luajit "git.sharkk.net/Sky/LuaJIT-to-Go" +) + +var ( + // Map to store state-specific RNGs + stateRngs = make(map[*luajit.State]*mrand.PCG) + stateRngsMu sync.Mutex +) + +// RegisterCryptoFunctions registers all crypto functions with the Lua state +func RegisterCryptoFunctions(state *luajit.State) error { + // Create a state-specific RNG + stateRngsMu.Lock() + stateRngs[state] = mrand.NewPCG(uint64(time.Now().UnixNano()), uint64(time.Now().UnixNano()>>32)) + stateRngsMu.Unlock() + + // Register hash functions + if err := state.RegisterGoFunction("__crypto_hash", cryptoHash); err != nil { + return err + } + + // Register HMAC functions + if err := state.RegisterGoFunction("__crypto_hmac", cryptoHmac); err != nil { + return err + } + + // Register UUID generation + if err := state.RegisterGoFunction("__crypto_uuid", cryptoUuid); err != nil { + return err + } + + // Register random functions + if err := state.RegisterGoFunction("__crypto_random", cryptoRandom); err != nil { + return err + } + if err := state.RegisterGoFunction("__crypto_random_bytes", cryptoRandomBytes); err != nil { + return err + } + if err := state.RegisterGoFunction("__crypto_random_int", cryptoRandomInt); err != nil { + return err + } + if err := state.RegisterGoFunction("__crypto_random_seed", cryptoRandomSeed); err != nil { + return err + } + + // Override Lua's math.random + if err := OverrideLuaRandom(state); err != nil { + return err + } + + return nil +} + +// CleanupCrypto cleans up resources when a state is closed +func CleanupCrypto(state *luajit.State) { + stateRngsMu.Lock() + delete(stateRngs, state) + stateRngsMu.Unlock() +} + +// cryptoHash generates hash digests using various algorithms +func cryptoHash(state *luajit.State) int { + if !state.IsString(1) || !state.IsString(2) { + state.PushString("hash: expected (string data, string algorithm)") + return 1 + } + + data := state.ToString(1) + algorithm := state.ToString(2) + + var h hash.Hash + + switch algorithm { + case "md5": + h = md5.New() + case "sha1": + h = sha1.New() + case "sha256": + h = sha256.New() + case "sha512": + h = sha512.New() + default: + state.PushString(fmt.Sprintf("unsupported algorithm: %s", algorithm)) + return 1 + } + + h.Write([]byte(data)) + hashBytes := h.Sum(nil) + + // Output format + outputFormat := "hex" + if state.GetTop() >= 3 && state.IsString(3) { + outputFormat = state.ToString(3) + } + + switch outputFormat { + case "hex": + state.PushString(hex.EncodeToString(hashBytes)) + case "binary": + state.PushString(string(hashBytes)) + default: + state.PushString(hex.EncodeToString(hashBytes)) + } + + return 1 +} + +// cryptoHmac generates HMAC using various hash algorithms +func cryptoHmac(state *luajit.State) int { + if !state.IsString(1) || !state.IsString(2) || !state.IsString(3) { + state.PushString("hmac: expected (string data, string key, string algorithm)") + return 1 + } + + data := state.ToString(1) + key := state.ToString(2) + algorithm := state.ToString(3) + + var h func() hash.Hash + + switch algorithm { + case "md5": + h = md5.New + case "sha1": + h = sha1.New + case "sha256": + h = sha256.New + case "sha512": + h = sha512.New + default: + state.PushString(fmt.Sprintf("unsupported algorithm: %s", algorithm)) + return 1 + } + + mac := hmac.New(h, []byte(key)) + mac.Write([]byte(data)) + macBytes := mac.Sum(nil) + + // Output format + outputFormat := "hex" + if state.GetTop() >= 4 && state.IsString(4) { + outputFormat = state.ToString(4) + } + + switch outputFormat { + case "hex": + state.PushString(hex.EncodeToString(macBytes)) + case "binary": + state.PushString(string(macBytes)) + default: + state.PushString(hex.EncodeToString(macBytes)) + } + + return 1 +} + +// cryptoUuid generates a random UUID v4 +func cryptoUuid(state *luajit.State) int { + uuid := make([]byte, 16) + _, err := rand.Read(uuid) + if err != nil { + state.PushString(fmt.Sprintf("uuid: generation error: %v", err)) + return 1 + } + + // Set version (4) and variant (RFC 4122) + uuid[6] = (uuid[6] & 0x0F) | 0x40 + uuid[8] = (uuid[8] & 0x3F) | 0x80 + + uuidStr := fmt.Sprintf("%x-%x-%x-%x-%x", + uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]) + + state.PushString(uuidStr) + return 1 +} + +// cryptoRandomBytes generates random bytes +func cryptoRandomBytes(state *luajit.State) int { + if !state.IsNumber(1) { + state.PushString("random_bytes: expected (number length)") + return 1 + } + + length := int(state.ToNumber(1)) + if length <= 0 { + state.PushString("random_bytes: length must be positive") + return 1 + } + + // Check if secure + secure := true + if state.GetTop() >= 2 && state.IsBoolean(2) { + secure = state.ToBoolean(2) + } + + bytes := make([]byte, length) + + if secure { + _, err := rand.Read(bytes) + if err != nil { + state.PushString(fmt.Sprintf("random_bytes: error: %v", err)) + return 1 + } + } else { + stateRngsMu.Lock() + stateRng, ok := stateRngs[state] + stateRngsMu.Unlock() + + if !ok { + state.PushString("random_bytes: RNG not initialized") + return 1 + } + + for i := range bytes { + bytes[i] = byte(stateRng.Uint64() & 0xFF) + } + } + + // Output format + outputFormat := "binary" + if state.GetTop() >= 3 && state.IsString(3) { + outputFormat = state.ToString(3) + } + + switch outputFormat { + case "binary": + state.PushString(string(bytes)) + case "hex": + state.PushString(hex.EncodeToString(bytes)) + default: + state.PushString(string(bytes)) + } + + return 1 +} + +// cryptoRandomInt generates a random integer in range [min, max] +func cryptoRandomInt(state *luajit.State) int { + if !state.IsNumber(1) || !state.IsNumber(2) { + state.PushString("random_int: expected (number min, number max)") + return 1 + } + + min := int64(state.ToNumber(1)) + max := int64(state.ToNumber(2)) + + if max <= min { + state.PushString("random_int: max must be greater than min") + return 1 + } + + // Check if secure + secure := true + if state.GetTop() >= 3 && state.IsBoolean(3) { + secure = state.ToBoolean(3) + } + + range_size := max - min + 1 + + var result int64 + + if secure { + bytes := make([]byte, 8) + _, err := rand.Read(bytes) + if err != nil { + state.PushString(fmt.Sprintf("random_int: error: %v", err)) + return 1 + } + + val := binary.BigEndian.Uint64(bytes) + result = min + int64(val%uint64(range_size)) + } else { + stateRngsMu.Lock() + stateRng, ok := stateRngs[state] + stateRngsMu.Unlock() + + if !ok { + state.PushString("random_int: RNG not initialized") + return 1 + } + + result = min + int64(stateRng.Uint64()%uint64(range_size)) + } + + state.PushNumber(float64(result)) + return 1 +} + +// cryptoRandom implements math.random functionality +func cryptoRandom(state *luajit.State) int { + numArgs := state.GetTop() + + // Check if secure + secure := false + + // math.random() - return [0,1) + if numArgs == 0 { + if secure { + bytes := make([]byte, 8) + _, err := rand.Read(bytes) + if err != nil { + state.PushString(fmt.Sprintf("random: error: %v", err)) + return 1 + } + val := binary.BigEndian.Uint64(bytes) + state.PushNumber(float64(val) / float64(math.MaxUint64)) + } else { + stateRngsMu.Lock() + stateRng, ok := stateRngs[state] + stateRngsMu.Unlock() + + if !ok { + state.PushString("random: RNG not initialized") + return 1 + } + + state.PushNumber(float64(stateRng.Uint64()) / float64(math.MaxUint64)) + } + return 1 + } + + // math.random(n) - return integer [1,n] + if numArgs == 1 && state.IsNumber(1) { + n := int64(state.ToNumber(1)) + if n < 1 { + state.PushString("random: upper bound must be >= 1") + return 1 + } + + state.PushNumber(1) // min + state.PushNumber(float64(n)) // max + state.PushBoolean(secure) // secure flag + return cryptoRandomInt(state) + } + + // math.random(m, n) - return integer [m,n] + if numArgs >= 2 && state.IsNumber(1) && state.IsNumber(2) { + state.PushBoolean(secure) // secure flag + return cryptoRandomInt(state) + } + + state.PushString("random: invalid arguments") + return 1 +} + +// cryptoRandomSeed sets seed for non-secure RNG +func cryptoRandomSeed(state *luajit.State) int { + if !state.IsNumber(1) { + state.PushString("randomseed: expected (number seed)") + return 1 + } + + seed := uint64(state.ToNumber(1)) + + stateRngsMu.Lock() + stateRngs[state] = mrand.NewPCG(seed, seed>>32) + stateRngsMu.Unlock() + + return 0 +} + +// OverrideLuaRandom replaces Lua's math.random with Go implementation +func OverrideLuaRandom(state *luajit.State) error { + if err := state.RegisterGoFunction("go_math_random", cryptoRandom); err != nil { + return err + } + + if err := state.RegisterGoFunction("go_math_randomseed", cryptoRandomSeed); err != nil { + return err + } + + // Replace original functions + return state.DoString(` + -- Save original functions + _G._original_math_random = math.random + _G._original_math_randomseed = math.randomseed + + -- Replace with Go implementations + math.random = go_math_random + math.randomseed = go_math_randomseed + + -- Clean up global namespace + go_math_random = nil + go_math_randomseed = nil + `) +} diff --git a/core/runner/crypto.lua b/core/runner/crypto.lua new file mode 100644 index 0000000..ea50c06 --- /dev/null +++ b/core/runner/crypto.lua @@ -0,0 +1,148 @@ +--[[ +crypto.lua - Cryptographic functions powered by Go +]]-- + +local crypto = {} + +-- ====================================================================== +-- HASHING FUNCTIONS +-- ====================================================================== + +-- Generate hash digest using various algorithms +-- Algorithms: md5, sha1, sha256, sha512 +-- Formats: hex (default), binary +function crypto.hash(data, algorithm, format) + if type(data) ~= "string" then + error("crypto.hash: data must be a string", 2) + end + + algorithm = algorithm or "sha256" + format = format or "hex" + + return __crypto_hash(data, algorithm, format) +end + +-- Convenience functions for common hash algorithms +function crypto.md5(data, format) + return crypto.hash(data, "md5", format) +end + +function crypto.sha1(data, format) + return crypto.hash(data, "sha1", format) +end + +function crypto.sha256(data, format) + return crypto.hash(data, "sha256", format) +end + +function crypto.sha512(data, format) + return crypto.hash(data, "sha512", format) +end + +-- ====================================================================== +-- HMAC FUNCTIONS +-- ====================================================================== + +-- Generate HMAC using various algorithms +-- Algorithms: md5, sha1, sha256, sha512 +-- Formats: hex (default), binary +function crypto.hmac(data, key, algorithm, format) + if type(data) ~= "string" then + error("crypto.hmac: data must be a string", 2) + end + + if type(key) ~= "string" then + error("crypto.hmac: key must be a string", 2) + end + + algorithm = algorithm or "sha256" + format = format or "hex" + + return __crypto_hmac(data, key, algorithm, format) +end + +-- Convenience functions for common HMAC algorithms +function crypto.hmac_md5(data, key, format) + return crypto.hmac(data, key, "md5", format) +end + +function crypto.hmac_sha1(data, key, format) + return crypto.hmac(data, key, "sha1", format) +end + +function crypto.hmac_sha256(data, key, format) + return crypto.hmac(data, key, "sha256", format) +end + +function crypto.hmac_sha512(data, key, format) + return crypto.hmac(data, key, "sha512", format) +end + +-- ====================================================================== +-- RANDOM FUNCTIONS +-- ====================================================================== + +-- Generate random bytes +-- Formats: binary (default), hex +function crypto.random_bytes(length, secure, format) + if type(length) ~= "number" or length <= 0 then + error("crypto.random_bytes: length must be positive", 2) + end + + secure = secure ~= false -- Default to secure + format = format or "binary" + + return __crypto_random_bytes(length, secure, format) +end + +-- Generate random integer in range [min, max] +function crypto.random_int(min, max, secure) + if type(min) ~= "number" or type(max) ~= "number" then + error("crypto.random_int: min and max must be numbers", 2) + end + + if max <= min then + error("crypto.random_int: max must be greater than min", 2) + end + + secure = secure ~= false -- Default to secure + + return __crypto_random_int(min, max, secure) +end + +-- Generate random string of specified length +function crypto.random_string(length, charset, secure) + if type(length) ~= "number" or length <= 0 then + error("crypto.random_string: length must be positive", 2) + end + + secure = secure ~= false -- Default to secure + + -- Default character set: alphanumeric + charset = charset or "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + + if type(charset) ~= "string" or #charset == 0 then + error("crypto.random_string: charset must be non-empty", 2) + end + + local result = "" + local charset_length = #charset + + for i = 1, length do + local index = crypto.random_int(1, charset_length, secure) + result = result .. charset:sub(index, index) + end + + return result +end + +-- ====================================================================== +-- UUID FUNCTIONS +-- ====================================================================== + +-- Generate random UUID (v4) +function crypto.uuid() + return __crypto_uuid() +end + +return crypto \ No newline at end of file diff --git a/core/runner/embed.go b/core/runner/embed.go index e4d2c8b..b465bd6 100644 --- a/core/runner/embed.go +++ b/core/runner/embed.go @@ -28,6 +28,12 @@ var utilLuaCode string //go:embed string.lua var stringLuaCode string +//go:embed table.lua +var tableLuaCode string + +//go:embed crypto.lua +var cryptoLuaCode string + // ModuleInfo holds information about an embeddable Lua module type ModuleInfo struct { Name string // Module name @@ -44,6 +50,8 @@ var ( {Name: "fs", Code: fsLuaCode}, {Name: "util", Code: utilLuaCode}, {Name: "string", Code: stringLuaCode}, + {Name: "table", Code: tableLuaCode}, + {Name: "crypto", Code: cryptoLuaCode}, } ) diff --git a/core/runner/sandbox.go b/core/runner/sandbox.go index bc8122c..2997ac6 100644 --- a/core/runner/sandbox.go +++ b/core/runner/sandbox.go @@ -117,6 +117,10 @@ func (s *Sandbox) registerCoreFunctions(state *luajit.State) error { return err } + if err := RegisterCryptoFunctions(state); err != nil { + return err + } + return nil } diff --git a/core/runner/string.lua b/core/runner/string.lua index c29960d..954fae3 100644 --- a/core/runner/string.lua +++ b/core/runner/string.lua @@ -190,8 +190,8 @@ end -- INSTALL EXTENSIONS INTO STRING LIBRARY -- ====================================================================== -for name, func in pairs(string_ext) do - string[name] = func +for name, func in pairs(string) do + string_ext[name] = func end return string_ext \ No newline at end of file diff --git a/core/runner/table.lua b/core/runner/table.lua new file mode 100644 index 0000000..6001909 --- /dev/null +++ b/core/runner/table.lua @@ -0,0 +1,1092 @@ +--[[ +table.lua - Extended table library functions +]]-- + +local table_ext = {} + +-- ====================================================================== +-- SET OPERATIONS +-- ====================================================================== + +-- Remove duplicate values (like array_unique) +function table_ext.unique(t) + if type(t) ~= "table" then return {} end + + local seen = {} + local result = {} + + for _, v in ipairs(t) do + if not seen[v] then + seen[v] = true + table.insert(result, v) + end + end + + return result +end + +-- Return items in first table that are present in all other tables (like array_intersect) +function table_ext.intersect(t1, ...) + if type(t1) ~= "table" then return {} end + + local args = {...} + local result = {} + + -- Convert all tables to sets for O(1) lookups + local sets = {} + for i, t in ipairs(args) do + if type(t) ~= "table" then + return {} + end + + sets[i] = {} + for _, v in ipairs(t) do + sets[i][v] = true + end + end + + -- Check each element in t1 against all other tables + for _, v in ipairs(t1) do + local present_in_all = true + + for i = 1, #args do + if not sets[i][v] then + present_in_all = false + break + end + end + + if present_in_all then + table.insert(result, v) + end + end + + return result +end + +-- Return items in first table that are not present in other tables (like array_diff) +function table_ext.diff(t1, ...) + if type(t1) ~= "table" then return {} end + + local args = {...} + local result = {} + + -- Build unified set of elements from other tables + local others = {} + for _, t in ipairs(args) do + if type(t) == "table" then + for _, v in ipairs(t) do + others[v] = true + end + end + end + + -- Add elements from t1 that aren't in other tables + for _, v in ipairs(t1) do + if not others[v] then + table.insert(result, v) + end + end + + return result +end + +-- ====================================================================== +-- SEARCH AND FILTERING +-- ====================================================================== + +-- Check if value exists in table (like in_array) +function table_ext.contains(t, value) + if type(t) ~= "table" then return false end + + for _, v in ipairs(t) do + if v == value then + return true + end + end + + return false +end + +-- Find key for a value (like array_search) +function table_ext.find(t, value) + if type(t) ~= "table" then return nil end + + for k, v in pairs(t) do + if v == value then + return k + end + end + + return nil +end + +-- Filter table elements (like array_filter) +function table_ext.filter(t, func) + if type(t) ~= "table" or type(func) ~= "function" then return {} end + + local result = {} + + for k, v in pairs(t) do + if func(v, k) then + if type(k) == "number" and k % 1 == 0 and k > 0 then + -- For array-like tables, maintain numerical indices + table.insert(result, v) + else + -- For associative tables, preserve the key + result[k] = v + end + end + end + + return result +end + +-- ====================================================================== +-- TRANSFORMATION FUNCTIONS +-- ====================================================================== + +-- Apply a function to all values (like array_map) +function table_ext.map(t, func) + if type(t) ~= "table" or type(func) ~= "function" then return {} end + + local result = {} + + for k, v in pairs(t) do + if type(k) == "number" and k % 1 == 0 and k > 0 then + -- For array-like tables, maintain numerical indices + table.insert(result, func(v, k)) + else + -- For associative tables, preserve the key + result[k] = func(v, k) + end + end + + return result +end + +-- Reduce a table to a single value (like array_reduce) +function table_ext.reduce(t, func, initial) + if type(t) ~= "table" or type(func) ~= "function" then + return initial + end + + local result = initial + + for k, v in pairs(t) do + if result == nil then + result = v + else + result = func(result, v, k) + end + end + + return result +end + +-- ====================================================================== +-- ADVANCED OPERATIONS +-- ====================================================================== + +-- Split table into chunks (like array_chunk) +function table_ext.chunk(t, size) + if type(t) ~= "table" or type(size) ~= "number" or size <= 0 then + return {} + end + + local result = {} + local chunk = {} + local count = 0 + + for _, v in ipairs(t) do + count = count + 1 + chunk[count] = v + + if count == size then + table.insert(result, chunk) + chunk = {} + count = 0 + end + end + + -- Add the last chunk if it has any elements + if count > 0 then + table.insert(result, chunk) + end + + return result +end + +-- Extract a column from a table of tables (like array_column) +function table_ext.column(t, column_key, index_key) + if type(t) ~= "table" or column_key == nil then return {} end + + local result = {} + + for _, row in ipairs(t) do + if type(row) == "table" and row[column_key] ~= nil then + if index_key ~= nil and row[index_key] ~= nil then + result[row[index_key]] = row[column_key] + else + table.insert(result, row[column_key]) + end + end + end + + return result +end + +-- Merge tables (like array_merge, but preserves keys) +function table_ext.merge(...) + local result = {} + + for _, t in ipairs({...}) do + if type(t) == "table" then + for k, v in pairs(t) do + if type(k) == "number" and k % 1 == 0 and k > 0 then + -- For array-like tables, append values + table.insert(result, v) + else + -- For associative tables, overwrite with latest value + result[k] = v + end + end + end + end + + return result +end + +-- ====================================================================== +-- KEY MANIPULATION +-- ====================================================================== + +-- Exchange keys with values (like array_flip) +function table_ext.flip(t) + if type(t) ~= "table" then return {} end + + local result = {} + + for k, v in pairs(t) do + if type(v) == "string" or type(v) == "number" then + result[v] = k + end + end + + return result +end + +-- Get all keys from a table (like array_keys) +function table_ext.keys(t) + if type(t) ~= "table" then return {} end + + local result = {} + + for k, _ in pairs(t) do + table.insert(result, k) + end + + return result +end + +-- Get all values from a table (like array_values) +function table_ext.values(t) + if type(t) ~= "table" then return {} end + + local result = {} + + for _, v in pairs(t) do + table.insert(result, v) + end + + return result +end + +-- ====================================================================== +-- STATISTICAL FUNCTIONS +-- ====================================================================== + +-- Sum all values (like array_sum) +function table_ext.sum(t) + if type(t) ~= "table" then return 0 end + + local sum = 0 + + for _, v in pairs(t) do + if type(v) == "number" then + sum = sum + v + end + end + + return sum +end + +-- Multiply all values (like array_product) +function table_ext.product(t) + if type(t) ~= "table" then return 0 end + + local product = 1 + local has_number = false + + for _, v in pairs(t) do + if type(v) == "number" then + product = product * v + has_number = true + end + end + + return has_number and product or 0 +end + +-- Count value occurrences (like array_count_values) +function table_ext.count_values(t) + if type(t) ~= "table" then return {} end + + local result = {} + + for _, v in pairs(t) do + if type(v) == "string" or type(v) == "number" then + result[v] = (result[v] or 0) + 1 + end + end + + return result +end + +-- ====================================================================== +-- CREATION HELPERS +-- ====================================================================== + +-- Create a table with a range of values (like range) +function table_ext.range(start, stop, step) + if type(start) ~= "number" then return {} end + + step = step or 1 + + local result = {} + + if not stop then + stop = start + start = 1 + end + + if (step > 0 and start > stop) or (step < 0 and start < stop) then + return {} + end + + local i = start + while (step > 0 and i <= stop) or (step < 0 and i >= stop) do + table.insert(result, i) + i = i + step + end + + return result +end + +-- Fill a table with a value (like array_fill) +function table_ext.fill(start_index, count, value) + if type(start_index) ~= "number" or type(count) ~= "number" or count < 0 then + return {} + end + + local result = {} + + for i = 0, count - 1 do + result[start_index + i] = value + end + + return result +end + +-- ====================================================================== +-- ADDITIONAL USEFUL FUNCTIONS +-- ====================================================================== + +-- Reverse a table (array part only) +function table_ext.reverse(t) + if type(t) ~= "table" then return {} end + + local result = {} + local count = #t + + for i = count, 1, -1 do + table.insert(result, t[i]) + end + + return result +end + +-- Get the max value in a table +function table_ext.max(t) + if type(t) ~= "table" or #t == 0 then return nil end + + local max = t[1] + + for i = 2, #t do + if t[i] > max then + max = t[i] + end + end + + return max +end + +-- Get the min value in a table +function table_ext.min(t) + if type(t) ~= "table" or #t == 0 then return nil end + + local min = t[1] + + for i = 2, #t do + if t[i] < min then + min = t[i] + end + end + + return min +end + +-- Check if all elements satisfy a condition +function table_ext.all(t, func) + if type(t) ~= "table" or type(func) ~= "function" then return false end + + for k, v in pairs(t) do + if not func(v, k) then + return false + end + end + + return true +end + +-- Check if any element satisfies a condition +function table_ext.any(t, func) + if type(t) ~= "table" or type(func) ~= "function" then return false end + + for k, v in pairs(t) do + if func(v, k) then + return true + end + end + + return false +end + +-- ====================================================================== +-- TABLE UTILITIES +-- ====================================================================== + +-- Check if table is empty +function table_ext.is_empty(t) + if type(t) ~= "table" then return true end + return next(t) == nil +end + +-- Get table length (works for both array and hash parts) +function table_ext.size(t) + if type(t) ~= "table" then return 0 end + + local count = 0 + for _ in pairs(t) do + count = count + 1 + end + + return count +end + +-- Get a slice of a table +function table_ext.slice(t, start, stop) + if type(t) ~= "table" then return {} end + + local len = #t + start = start or 1 + stop = stop or len + + -- Convert negative indices + if start < 0 then start = len + start + 1 end + if stop < 0 then stop = len + stop + 1 end + + -- Ensure bounds + start = math.max(1, math.min(start, len + 1)) + stop = math.max(0, math.min(stop, len)) + + local result = {} + for i = start, stop do + table.insert(result, t[i]) + end + + return result +end + +-- ====================================================================== +-- SORTING FUNCTIONS +-- ====================================================================== + +-- Sort array values (like sort) +function table_ext.sort(t) + if type(t) ~= "table" then return t end + table.sort(t) + return t +end + +-- Sort array values in reverse order (like rsort) +function table_ext.rsort(t) + if type(t) ~= "table" then return t end + table.sort(t, function(a, b) return a > b end) + return t +end + +-- Sort and maintain index association (like asort) +function table_ext.asort(t) + if type(t) ~= "table" then return t end + + local keys, result = {}, {} + for k in pairs(t) do + table.insert(keys, k) + end + + table.sort(keys, function(a, b) + return t[a] < t[b] + end) + + for _, k in ipairs(keys) do + result[k] = t[k] + end + + return result +end + +-- Sort in reverse order and maintain index association (like arsort) +function table_ext.arsort(t) + if type(t) ~= "table" then return t end + + local keys, result = {}, {} + for k in pairs(t) do + table.insert(keys, k) + end + + table.sort(keys, function(a, b) + return t[a] > t[b] + end) + + for _, k in ipairs(keys) do + result[k] = t[k] + end + + return result +end + +-- Sort by keys (like ksort) +function table_ext.ksort(t) + if type(t) ~= "table" then return t end + + local keys, result = {}, {} + for k in pairs(t) do + table.insert(keys, k) + end + + table.sort(keys) + + for _, k in ipairs(keys) do + result[k] = t[k] + end + + return result +end + +-- Sort by keys in reverse order (like krsort) +function table_ext.krsort(t) + if type(t) ~= "table" then return t end + + local keys, result = {}, {} + for k in pairs(t) do + table.insert(keys, k) + end + + table.sort(keys, function(a, b) return a > b end) + + for _, k in ipairs(keys) do + result[k] = t[k] + end + + return result +end + +-- Sort using custom comparison function (like usort) +function table_ext.usort(t, compare_func) + if type(t) ~= "table" or type(compare_func) ~= "function" then return t end + + table.sort(t, compare_func) + return t +end + +-- Sort maintaining keys using custom comparison function (like uasort) +function table_ext.uasort(t, compare_func) + if type(t) ~= "table" or type(compare_func) ~= "function" then return t end + + local keys, result = {}, {} + for k in pairs(t) do + table.insert(keys, k) + end + + table.sort(keys, function(a, b) + return compare_func(t[a], t[b]) + end) + + for _, k in ipairs(keys) do + result[k] = t[k] + end + + return result +end + +-- Sort by keys using custom comparison function (like uksort) +function table_ext.uksort(t, compare_func) + if type(t) ~= "table" or type(compare_func) ~= "function" then return t end + + local keys, result = {}, {} + for k in pairs(t) do + table.insert(keys, k) + end + + table.sort(keys, compare_func) + + for _, k in ipairs(keys) do + result[k] = t[k] + end + + return result +end + +-- Natural order sort (like natsort) +function table_ext.natsort(t) + if type(t) ~= "table" then return t end + + local function natural_compare(a, b) + local function get_chunks(s) + if type(s) ~= "string" then s = tostring(s) end + local chunks = {} + for num, alpha in s:gmatch("(%d+)([^%d]*)") do + table.insert(chunks, {n=true, val=tonumber(num)}) + if alpha ~= "" then + table.insert(chunks, {n=false, val=alpha}) + end + end + return chunks + end + + local a_chunks = get_chunks(a) + local b_chunks = get_chunks(b) + + for i = 1, math.min(#a_chunks, #b_chunks) do + if a_chunks[i].n ~= b_chunks[i].n then + return a_chunks[i].n -- numbers come before strings + elseif a_chunks[i].val ~= b_chunks[i].val then + if a_chunks[i].n then + return a_chunks[i].val < b_chunks[i].val + else + return a_chunks[i].val < b_chunks[i].val + end + end + end + + return #a_chunks < #b_chunks + end + + table.sort(t, natural_compare) + return t +end + +-- Natural case-insensitive sort (like natcasesort) +function table_ext.natcasesort(t) + if type(t) ~= "table" then return t end + + local function case_insensitive_natural_compare(a, b) + if type(a) == "string" and type(b) == "string" then + return table_ext.natural_compare(a:lower(), b:lower()) + else + return table_ext.natural_compare(a, b) + end + end + + return table_ext.usort(t, case_insensitive_natural_compare) +end + +-- ====================================================================== +-- ARRAY MODIFICATION FUNCTIONS +-- ====================================================================== + +-- Push one or more elements onto the end (like array_push) +function table_ext.push(t, ...) + if type(t) ~= "table" then return 0 end + + local count = 0 + for _, v in ipairs({...}) do + table.insert(t, v) + count = count + 1 + end + + return count +end + +-- Pop the element off the end (like array_pop) +function table_ext.pop(t) + if type(t) ~= "table" or #t == 0 then return nil end + + local value = t[#t] + t[#t] = nil + return value +end + +-- Shift an element off the beginning (like array_shift) +function table_ext.shift(t) + if type(t) ~= "table" or #t == 0 then return nil end + + local value = t[1] + table.remove(t, 1) + return value +end + +-- Prepend elements to the beginning (like array_unshift) +function table_ext.unshift(t, ...) + if type(t) ~= "table" then return 0 end + + local args = {...} + for i = #args, 1, -1 do + table.insert(t, 1, args[i]) + end + + return #t +end + +-- Pad array to specified length (like array_pad) +function table_ext.pad(t, size, value) + if type(t) ~= "table" then return {} end + + local result = table_ext.deep_copy(t) + local current_size = #result + + if size == current_size then return result end + + if size > current_size then + -- Pad to the right + for i = current_size + 1, size do + result[i] = value + end + else + -- Pad to the left (negative size) + local abs_size = math.abs(size) + if abs_size < current_size then + local temp = {} + for i = 1, abs_size do + if i <= abs_size - current_size then + temp[i] = value + else + temp[i] = result[i - (abs_size - current_size)] + end + end + result = temp + else + -- Fill completely with padding value + result = {} + for i = 1, abs_size do + result[i] = value + end + end + end + + return result +end + +-- Remove a portion and replace it (like array_splice) +function table_ext.splice(t, offset, length, ...) + if type(t) ~= "table" then return {} end + + local result = table_ext.deep_copy(t) + local size = #result + + -- Handle negative offset + if offset < 0 then + offset = size + offset + end + + -- Ensure offset is valid + offset = math.max(1, math.min(offset, size + 1)) + + -- Handle negative or nil length + if length == nil then + length = size - offset + 1 + elseif length < 0 then + length = math.max(0, size - offset + length + 1) + end + + -- Extract removed portion + local removed = {} + for i = offset, offset + length - 1 do + if i <= size then + table.insert(removed, result[i]) + end + end + + -- Remove portion from original + for i = 1, length do + table.remove(result, offset) + end + + -- Insert replacement values + local replacements = {...} + for i = #replacements, 1, -1 do + table.insert(result, offset, replacements[i]) + end + + return removed, result +end + +-- Randomize array order (like shuffle) +function table_ext.shuffle(t) + if type(t) ~= "table" then return t end + + local result = table_ext.deep_copy(t) + local size = #result + + for i = size, 2, -1 do + local j = math.random(i) + result[i], result[j] = result[j], result[i] + end + + return result +end + +-- Pick random keys from array (like array_rand) +function table_ext.rand(t, num_keys) + if type(t) ~= "table" then return nil end + + local size = #t + if size == 0 then return nil end + + num_keys = num_keys or 1 + num_keys = math.min(num_keys, size) + + if num_keys <= 0 then return nil end + + if num_keys == 1 then + return math.random(size) + else + local keys = {} + local result = {} + + -- Create a list of all possible keys + for i = 1, size do + keys[i] = i + end + + -- Select random keys + for i = 1, num_keys do + local j = math.random(#keys) + table.insert(result, keys[j]) + table.remove(keys, j) + end + + table.sort(result) + return result + end +end + +-- ====================================================================== +-- ARRAY INSPECTION FUNCTIONS +-- ====================================================================== + +-- Check if key exists (like array_key_exists) +function table_ext.key_exists(key, t) + if type(t) ~= "table" then return false end + return t[key] ~= nil +end + +-- Get the first key (like array_key_first) +function table_ext.key_first(t) + if type(t) ~= "table" then return nil end + + -- For array-like tables + if #t > 0 then return 1 end + + -- For associative tables + local first_key = nil + for k in pairs(t) do + first_key = k + break + end + + return first_key +end + +-- Get the last key (like array_key_last) +function table_ext.key_last(t) + if type(t) ~= "table" then return nil end + + -- For array-like tables + if #t > 0 then return #t end + + -- For associative tables (no guaranteed order, return any key) + local last_key = nil + for k in pairs(t) do + last_key = k + end + + return last_key +end + +-- Check if table is a list (like array_is_list) +function table_ext.is_list(t) + if type(t) ~= "table" then return false end + + local count = 0 + for k in pairs(t) do + count = count + 1 + if type(k) ~= "number" or k <= 0 or math.floor(k) ~= k or k > count then + return false + end + end + + return true +end + +-- ====================================================================== +-- OTHER IMPORTANT FUNCTIONS +-- ====================================================================== + +-- Create array with keys from one array, values from another (like array_combine) +function table_ext.combine(keys, values) + if type(keys) ~= "table" or type(values) ~= "table" then return {} end + + local result = {} + local key_count = #keys + local value_count = #values + + for i = 1, math.min(key_count, value_count) do + result[keys[i]] = values[i] + end + + return result +end + +-- Replace elements from one array into another (like array_replace) +function table_ext.replace(t, ...) + if type(t) ~= "table" then return {} end + + local result = table_ext.deep_copy(t) + + for _, replacement in ipairs({...}) do + if type(replacement) == "table" then + for k, v in pairs(replacement) do + result[k] = v + end + end + end + + return result +end + +-- Replace elements recursively (like array_replace_recursive) +function table_ext.replace_recursive(t, ...) + if type(t) ~= "table" then return {} end + + local result = table_ext.deep_copy(t) + + for _, replacement in ipairs({...}) do + if type(replacement) == "table" then + for k, v in pairs(replacement) do + if type(v) == "table" and type(result[k]) == "table" then + result[k] = table_ext.replace_recursive(result[k], v) + else + result[k] = v + end + end + end + end + + return result +end + +-- Apply function to each element (like array_walk) +function table_ext.walk(t, callback, user_data) + if type(t) ~= "table" or type(callback) ~= "function" then return t end + + for k, v in pairs(t) do + t[k] = callback(v, k, user_data) + end + + return t +end + +-- Apply function recursively (like array_walk_recursive) +function table_ext.walk_recursive(t, callback, user_data) + if type(t) ~= "table" or type(callback) ~= "function" then return t end + + for k, v in pairs(t) do + if type(v) == "table" then + table_ext.walk_recursive(v, callback, user_data) + else + t[k] = callback(v, k, user_data) + end + end + + return t +end + +-- Sort multiple arrays (simplified array_multisort) +function table_ext.multisort(...) + local args = {...} + if #args == 0 then return end + + -- First argument is the main table + local main = args[1] + if type(main) ~= "table" then return end + + -- Create a table of indices + local indices = {} + for i = 1, #main do + indices[i] = i + end + + -- Sort the indices based on the arrays + table.sort(indices, function(a, b) + for i = 1, #args do + local arr = args[i] + if type(arr) == "table" then + if arr[a] ~= arr[b] then + return arr[a] < arr[b] + end + end + end + return a < b + end) + + -- Reorder all arrays based on sorted indices + for i = 1, #args do + local arr = args[i] + if type(arr) == "table" then + local temp = table_ext.deep_copy(arr) + for j = 1, #indices do + arr[j] = temp[indices[j]] + end + end + end +end + +-- Efficient deep copy function +function table_ext.deep_copy(obj) + if type(obj) ~= 'table' then return obj end + local res = {} + for k, v in pairs(obj) do res[k] = table_ext.deep_copy(v) end + return res +end + +-- ====================================================================== +-- INSTALL EXTENSIONS INTO TABLE LIBRARY +-- ====================================================================== + +for name, func in pairs(table) do + table_ext[name] = func +end + +return table_ext \ No newline at end of file diff --git a/core/runner/util.lua b/core/runner/util.lua index ed4c219..99ecef8 100644 --- a/core/runner/util.lua +++ b/core/runner/util.lua @@ -14,31 +14,6 @@ function util.generate_token(length) return __generate_token(length or 32) end --- Deep copy of tables -function util.deep_copy(obj) - if type(obj) ~= 'table' then return obj end - local res = {} - for k, v in pairs(obj) do res[k] = util.deep_copy(v) end - return res -end - --- Merge tables -function util.merge_tables(t1, t2) - if type(t1) ~= 'table' or type(t2) ~= 'table' then - error("Both arguments must be tables", 2) - end - - local result = util.deep_copy(t1) - for k, v in pairs(t2) do - if type(v) == 'table' and type(result[k]) == 'table' then - result[k] = util.merge_tables(result[k], v) - else - result[k] = v - end - end - return result -end - -- ====================================================================== -- HTML ENTITY FUNCTIONS -- ======================================================================