From 7fde7d495b9a2bf73eb5c02123735629bf679f29 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Sat, 10 May 2025 13:00:49 -0500 Subject: [PATCH] add math library, move lua code --- .gitignore | 1 + core/runner/embed.go | 24 +- core/runner/{ => lua}/crypto.lua | 0 core/runner/{ => lua}/fs.lua | 0 core/runner/{ => lua}/json.lua | 0 core/runner/lua/math.lua | 802 ++++++++++++++++++++++++++++++ core/runner/{ => lua}/sandbox.lua | 0 core/runner/{ => lua}/sqlite.lua | 102 +++- core/runner/{ => lua}/string.lua | 0 core/runner/{ => lua}/table.lua | 0 core/runner/lua/time.lua | 130 +++++ core/runner/{ => lua}/util.lua | 0 12 files changed, 1046 insertions(+), 13 deletions(-) rename core/runner/{ => lua}/crypto.lua (100%) rename core/runner/{ => lua}/fs.lua (100%) rename core/runner/{ => lua}/json.lua (100%) create mode 100644 core/runner/lua/math.lua rename core/runner/{ => lua}/sandbox.lua (100%) rename core/runner/{ => lua}/sqlite.lua (70%) rename core/runner/{ => lua}/string.lua (100%) rename core/runner/{ => lua}/table.lua (100%) create mode 100644 core/runner/lua/time.lua rename core/runner/{ => lua}/util.lua (100%) diff --git a/.gitignore b/.gitignore index 94f53e4..fe0b89c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ go.work /config.lua test/ /init.lua +/moonshark diff --git a/core/runner/embed.go b/core/runner/embed.go index b465bd6..c4ab3ca 100644 --- a/core/runner/embed.go +++ b/core/runner/embed.go @@ -10,30 +10,36 @@ import ( luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) -//go:embed sandbox.lua +//go:embed lua/sandbox.lua var sandboxLuaCode string -//go:embed json.lua +//go:embed lua/json.lua var jsonLuaCode string -//go:embed sqlite.lua +//go:embed lua/sqlite.lua var sqliteLuaCode string -//go:embed fs.lua +//go:embed lua/fs.lua var fsLuaCode string -//go:embed util.lua +//go:embed lua/util.lua var utilLuaCode string -//go:embed string.lua +//go:embed lua/string.lua var stringLuaCode string -//go:embed table.lua +//go:embed lua/table.lua var tableLuaCode string -//go:embed crypto.lua +//go:embed lua/crypto.lua var cryptoLuaCode string +//go:embed lua/time.lua +var timeLuaCode string + +//go:embed lua/math.lua +var mathLuaCode string + // ModuleInfo holds information about an embeddable Lua module type ModuleInfo struct { Name string // Module name @@ -52,6 +58,8 @@ var ( {Name: "string", Code: stringLuaCode}, {Name: "table", Code: tableLuaCode}, {Name: "crypto", Code: cryptoLuaCode}, + {Name: "time", Code: timeLuaCode}, + {Name: "math", Code: mathLuaCode}, } ) diff --git a/core/runner/crypto.lua b/core/runner/lua/crypto.lua similarity index 100% rename from core/runner/crypto.lua rename to core/runner/lua/crypto.lua diff --git a/core/runner/fs.lua b/core/runner/lua/fs.lua similarity index 100% rename from core/runner/fs.lua rename to core/runner/lua/fs.lua diff --git a/core/runner/json.lua b/core/runner/lua/json.lua similarity index 100% rename from core/runner/json.lua rename to core/runner/lua/json.lua diff --git a/core/runner/lua/math.lua b/core/runner/lua/math.lua new file mode 100644 index 0000000..f801ae2 --- /dev/null +++ b/core/runner/lua/math.lua @@ -0,0 +1,802 @@ +--[[ +math.lua - High-performance math library +]]-- + +local math_ext = {} + +-- Import standard math functions +for name, func in pairs(_G.math) do + math_ext[name] = func +end + +-- ====================================================================== +-- CONSTANTS (higher precision) +-- ====================================================================== + +math_ext.pi = 3.14159265358979323846 +math_ext.tau = 6.28318530717958647693 -- 2*pi +math_ext.e = 2.71828182845904523536 +math_ext.phi = 1.61803398874989484820 -- Golden ratio +math_ext.sqrt2 = 1.41421356237309504880 +math_ext.sqrt3 = 1.73205080756887729353 +math_ext.ln2 = 0.69314718055994530942 +math_ext.ln10 = 2.30258509299404568402 +math_ext.infinity = 1/0 +math_ext.nan = 0/0 + +-- ====================================================================== +-- EXTENDED FUNCTIONS +-- ====================================================================== + +-- Cube root (handles negative numbers correctly) +function math_ext.cbrt(x) + return x < 0 and -(-x)^(1/3) or x^(1/3) +end + +-- Hypotenuse of right-angled triangle +function math_ext.hypot(x, y) + return math.sqrt(x * x + y * y) +end + +-- Check if value is NaN +function math_ext.isnan(x) + return x ~= x +end + +-- Check if value is finite +function math_ext.isfinite(x) + return x > -math_ext.infinity and x < math_ext.infinity +end + +-- Sign function (-1, 0, 1) +function math_ext.sign(x) + return x > 0 and 1 or (x < 0 and -1 or 0) +end + +-- Clamp value between min and max +function math_ext.clamp(x, min, max) + return x < min and min or (x > max and max or x) +end + +-- Linear interpolation +function math_ext.lerp(a, b, t) + return a + (b - a) * t +end + +-- Smooth step interpolation +function math_ext.smoothstep(a, b, t) + t = math_ext.clamp((t - a) / (b - a), 0, 1) + return t * t * (3 - 2 * t) +end + +-- Map value from one range to another +function math_ext.map(x, in_min, in_max, out_min, out_max) + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min +end + +-- Round to nearest integer +function math_ext.round(x) + return x >= 0 and math.floor(x + 0.5) or math.ceil(x - 0.5) +end + +-- Round to specified decimal places +function math_ext.roundto(x, decimals) + local mult = 10 ^ (decimals or 0) + return math.floor(x * mult + 0.5) / mult +end + +-- Normalize angle to [-π, π] +function math_ext.normalize_angle(angle) + return angle - 2 * math_ext.pi * math.floor((angle + math_ext.pi) / (2 * math_ext.pi)) +end + +-- Distance between points +function math_ext.distance(x1, y1, x2, y2) + local dx, dy = x2 - x1, y2 - y1 + return math.sqrt(dx * dx + dy * dy) +end + +-- ====================================================================== +-- RANDOM NUMBER FUNCTIONS +-- ====================================================================== + +-- Random float in range [min, max) +function math_ext.randomf(min, max) + if not min and not max then + return math.random() + elseif not max then + max = min + min = 0 + end + return min + math.random() * (max - min) +end + +-- Random integer in range [min, max] +function math_ext.randint(min, max) + if not max then + max = min + min = 1 + end + return math.floor(math.random() * (max - min + 1) + min) +end + +-- Random boolean with probability p (default 0.5) +function math_ext.randboolean(p) + p = p or 0.5 + return math.random() < p +end + +-- ====================================================================== +-- STATISTICS FUNCTIONS +-- ====================================================================== + +-- Sum of values +function math_ext.sum(t) + if type(t) ~= "table" then return 0 end + + local sum = 0 + for i=1, #t do + if type(t[i]) == "number" then + sum = sum + t[i] + end + end + return sum +end + +-- Mean (average) of values +function math_ext.mean(t) + if type(t) ~= "table" or #t == 0 then return 0 end + + local sum = 0 + local count = 0 + for i=1, #t do + if type(t[i]) == "number" then + sum = sum + t[i] + count = count + 1 + end + end + return count > 0 and sum / count or 0 +end + +-- Median of values +function math_ext.median(t) + if type(t) ~= "table" or #t == 0 then return 0 end + + local nums = {} + local count = 0 + for i=1, #t do + if type(t[i]) == "number" then + count = count + 1 + nums[count] = t[i] + end + end + + if count == 0 then return 0 end + + table.sort(nums) + + if count % 2 == 0 then + return (nums[count/2] + nums[count/2 + 1]) / 2 + else + return nums[math.ceil(count/2)] + end +end + +-- Variance of values +function math_ext.variance(t) + if type(t) ~= "table" then return 0 end + + local count = 0 + local m = math_ext.mean(t) + local sum = 0 + + for i=1, #t do + if type(t[i]) == "number" then + local dev = t[i] - m + sum = sum + dev * dev + count = count + 1 + end + end + + return count > 1 and sum / count or 0 +end + +-- Standard deviation +function math_ext.stdev(t) + return math.sqrt(math_ext.variance(t)) +end + +-- Population variance +function math_ext.pvariance(t) + if type(t) ~= "table" then return 0 end + + local count = 0 + local m = math_ext.mean(t) + local sum = 0 + + for i=1, #t do + if type(t[i]) == "number" then + local dev = t[i] - m + sum = sum + dev * dev + count = count + 1 + end + end + + return count > 0 and sum / count or 0 +end + +-- Population standard deviation +function math_ext.pstdev(t) + return math.sqrt(math_ext.pvariance(t)) +end + +-- Mode (most common value) +function math_ext.mode(t) + if type(t) ~= "table" or #t == 0 then return nil end + + local counts = {} + local most_frequent = nil + local max_count = 0 + + for i=1, #t do + local v = t[i] + counts[v] = (counts[v] or 0) + 1 + if counts[v] > max_count then + max_count = counts[v] + most_frequent = v + end + end + + return most_frequent +end + +-- Min and max simultaneously (faster than calling both separately) +function math_ext.minmax(t) + if type(t) ~= "table" or #t == 0 then return nil, nil end + + local min, max + for i=1, #t do + if type(t[i]) == "number" then + min = t[i] + max = t[i] + break + end + end + + if min == nil then return nil, nil end + + for i=1, #t do + if type(t[i]) == "number" then + if t[i] < min then min = t[i] end + if t[i] > max then max = t[i] end + end + end + + return min, max +end + +-- ====================================================================== +-- VECTOR OPERATIONS (2D/3D vectors) +-- ====================================================================== + +-- 2D Vector operations +math_ext.vec2 = { + new = function(x, y) + return {x = x or 0, y = y or 0} + end, + + copy = function(v) + return {x = v.x, y = v.y} + end, + + add = function(a, b) + return {x = a.x + b.x, y = a.y + b.y} + end, + + sub = function(a, b) + return {x = a.x - b.x, y = a.y - b.y} + end, + + mul = function(a, b) + if type(b) == "number" then + return {x = a.x * b, y = a.y * b} + end + return {x = a.x * b.x, y = a.y * b.y} + end, + + div = function(a, b) + if type(b) == "number" then + local inv = 1 / b + return {x = a.x * inv, y = a.y * inv} + end + return {x = a.x / b.x, y = a.y / b.y} + end, + + dot = function(a, b) + return a.x * b.x + a.y * b.y + end, + + length = function(v) + return math.sqrt(v.x * v.x + v.y * v.y) + end, + + length_squared = function(v) + return v.x * v.x + v.y * v.y + end, + + distance = function(a, b) + local dx, dy = b.x - a.x, b.y - a.y + return math.sqrt(dx * dx + dy * dy) + end, + + distance_squared = function(a, b) + local dx, dy = b.x - a.x, b.y - a.y + return dx * dx + dy * dy + end, + + normalize = function(v) + local len = math.sqrt(v.x * v.x + v.y * v.y) + if len > 1e-10 then + local inv_len = 1 / len + return {x = v.x * inv_len, y = v.y * inv_len} + end + return {x = 0, y = 0} + end, + + rotate = function(v, angle) + local c, s = math.cos(angle), math.sin(angle) + return { + x = v.x * c - v.y * s, + y = v.x * s + v.y * c + } + end, + + angle = function(v) + return math.atan2(v.y, v.x) + end, + + lerp = function(a, b, t) + t = math_ext.clamp(t, 0, 1) + return { + x = a.x + (b.x - a.x) * t, + y = a.y + (b.y - a.y) * t + } + end, + + reflect = function(v, normal) + local dot = v.x * normal.x + v.y * normal.y + return { + x = v.x - 2 * dot * normal.x, + y = v.y - 2 * dot * normal.y + } + end +} + +-- 3D Vector operations +math_ext.vec3 = { + new = function(x, y, z) + return {x = x or 0, y = y or 0, z = z or 0} + end, + + copy = function(v) + return {x = v.x, y = v.y, z = v.z} + end, + + add = function(a, b) + return {x = a.x + b.x, y = a.y + b.y, z = a.z + b.z} + end, + + sub = function(a, b) + return {x = a.x - b.x, y = a.y - b.y, z = a.z - b.z} + end, + + mul = function(a, b) + if type(b) == "number" then + return {x = a.x * b, y = a.y * b, z = a.z * b} + end + return {x = a.x * b.x, y = a.y * b.y, z = a.z * b.z} + end, + + div = function(a, b) + if type(b) == "number" then + local inv = 1 / b + return {x = a.x * inv, y = a.y * inv, z = a.z * inv} + end + return {x = a.x / b.x, y = a.y / b.y, z = a.z / b.z} + end, + + dot = function(a, b) + return a.x * b.x + a.y * b.y + a.z * b.z + end, + + cross = function(a, b) + return { + x = a.y * b.z - a.z * b.y, + y = a.z * b.x - a.x * b.z, + z = a.x * b.y - a.y * b.x + } + end, + + length = function(v) + return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) + end, + + length_squared = function(v) + return v.x * v.x + v.y * v.y + v.z * v.z + end, + + distance = function(a, b) + local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z + return math.sqrt(dx * dx + dy * dy + dz * dz) + end, + + distance_squared = function(a, b) + local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z + return dx * dx + dy * dy + dz * dz + end, + + normalize = function(v) + local len = math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) + if len > 1e-10 then + local inv_len = 1 / len + return {x = v.x * inv_len, y = v.y * inv_len, z = v.z * inv_len} + end + return {x = 0, y = 0, z = 0} + end, + + lerp = function(a, b, t) + t = math_ext.clamp(t, 0, 1) + return { + x = a.x + (b.x - a.x) * t, + y = a.y + (b.y - a.y) * t, + z = a.z + (b.z - a.z) * t + } + end, + + reflect = function(v, normal) + local dot = v.x * normal.x + v.y * normal.y + v.z * normal.z + return { + x = v.x - 2 * dot * normal.x, + y = v.y - 2 * dot * normal.y, + z = v.z - 2 * dot * normal.z + } + end +} + +-- ====================================================================== +-- MATRIX OPERATIONS (2x2 and 3x3 matrices) +-- ====================================================================== + +math_ext.mat2 = { + -- Create a new 2x2 matrix + new = function(a, b, c, d) + return { + {a or 1, b or 0}, + {c or 0, d or 1} + } + end, + + -- Create identity matrix + identity = function() + return {{1, 0}, {0, 1}} + end, + + -- Matrix multiplication + mul = function(a, b) + return { + { + a[1][1] * b[1][1] + a[1][2] * b[2][1], + a[1][1] * b[1][2] + a[1][2] * b[2][2] + }, + { + a[2][1] * b[1][1] + a[2][2] * b[2][1], + a[2][1] * b[1][2] + a[2][2] * b[2][2] + } + } + end, + + -- Determinant + det = function(m) + return m[1][1] * m[2][2] - m[1][2] * m[2][1] + end, + + -- Inverse matrix + inverse = function(m) + local det = m[1][1] * m[2][2] - m[1][2] * m[2][1] + if math.abs(det) < 1e-10 then + return nil -- Matrix is not invertible + end + + local inv_det = 1 / det + return { + {m[2][2] * inv_det, -m[1][2] * inv_det}, + {-m[2][1] * inv_det, m[1][1] * inv_det} + } + end, + + -- Rotation matrix + rotation = function(angle) + local cos, sin = math.cos(angle), math.sin(angle) + return { + {cos, -sin}, + {sin, cos} + } + end, + + -- Apply matrix to vector + transform = function(m, v) + return { + x = m[1][1] * v.x + m[1][2] * v.y, + y = m[2][1] * v.x + m[2][2] * v.y + } + end, + + -- Scale matrix + scale = function(sx, sy) + sy = sy or sx + return { + {sx, 0}, + {0, sy} + } + end +} + +math_ext.mat3 = { + -- Create identity matrix 3x3 + identity = function() + return { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1} + } + end, + + -- Create a 2D transformation matrix (translation, rotation, scale) + transform = function(x, y, angle, sx, sy) + sx = sx or 1 + sy = sy or sx + local cos, sin = math.cos(angle), math.sin(angle) + return { + {cos * sx, -sin * sy, x}, + {sin * sx, cos * sy, y}, + {0, 0, 1} + } + end, + + -- Matrix multiplication + mul = function(a, b) + local result = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0} + } + + for i = 1, 3 do + for j = 1, 3 do + for k = 1, 3 do + result[i][j] = result[i][j] + a[i][k] * b[k][j] + end + end + end + + return result + end, + + -- Apply matrix to point (homogeneous coordinates) + transform_point = function(m, v) + local x = m[1][1] * v.x + m[1][2] * v.y + m[1][3] + local y = m[2][1] * v.x + m[2][2] * v.y + m[2][3] + local w = m[3][1] * v.x + m[3][2] * v.y + m[3][3] + + if math.abs(w) < 1e-10 then + return {x = 0, y = 0} + end + + return {x = x / w, y = y / w} + end, + + -- Translation matrix + translation = function(x, y) + return { + {1, 0, x}, + {0, 1, y}, + {0, 0, 1} + } + end, + + -- Rotation matrix + rotation = function(angle) + local cos, sin = math.cos(angle), math.sin(angle) + return { + {cos, -sin, 0}, + {sin, cos, 0}, + {0, 0, 1} + } + end, + + -- Scale matrix + scale = function(sx, sy) + sy = sy or sx + return { + {sx, 0, 0}, + {0, sy, 0}, + {0, 0, 1} + } + end, + + -- Determinant + det = function(m) + return m[1][1] * (m[2][2] * m[3][3] - m[2][3] * m[3][2]) - + m[1][2] * (m[2][1] * m[3][3] - m[2][3] * m[3][1]) + + m[1][3] * (m[2][1] * m[3][2] - m[2][2] * m[3][1]) + end +} + +-- ====================================================================== +-- GEOMETRY FUNCTIONS +-- ====================================================================== + +math_ext.geometry = { + -- Distance from point to line + point_line_distance = function(px, py, x1, y1, x2, y2) + local dx, dy = x2 - x1, y2 - y1 + local len_sq = dx * dx + dy * dy + + if len_sq < 1e-10 then + return math_ext.distance(px, py, x1, y1) + end + + local t = ((px - x1) * dx + (py - y1) * dy) / len_sq + t = math_ext.clamp(t, 0, 1) + + local nearestX = x1 + t * dx + local nearestY = y1 + t * dy + + return math_ext.distance(px, py, nearestX, nearestY) + end, + + -- Check if point is inside polygon + point_in_polygon = function(px, py, vertices) + local inside = false + local n = #vertices / 2 + + for i = 1, n do + local x1, y1 = vertices[i*2-1], vertices[i*2] + local x2, y2 + + if i == n then + x2, y2 = vertices[1], vertices[2] + else + x2, y2 = vertices[i*2+1], vertices[i*2+2] + end + + if ((y1 > py) ~= (y2 > py)) and + (px < (x2 - x1) * (py - y1) / (y2 - y1) + x1) then + inside = not inside + end + end + + return inside + end, + + -- Area of a triangle + triangle_area = function(x1, y1, x2, y2, x3, y3) + return math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2) + end, + + -- Check if point is inside triangle + point_in_triangle = function(px, py, x1, y1, x2, y2, x3, y3) + local area = math_ext.geometry.triangle_area(x1, y1, x2, y2, x3, y3) + local area1 = math_ext.geometry.triangle_area(px, py, x2, y2, x3, y3) + local area2 = math_ext.geometry.triangle_area(x1, y1, px, py, x3, y3) + local area3 = math_ext.geometry.triangle_area(x1, y1, x2, y2, px, py) + + return math.abs(area - (area1 + area2 + area3)) < 1e-10 + end, + + -- Check if two line segments intersect + line_intersect = function(x1, y1, x2, y2, x3, y3, x4, y4) + local d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1) + + if math.abs(d) < 1e-10 then + return false, nil, nil -- Lines are parallel + end + + local ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d + local ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d + + if ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1 then + local x = x1 + ua * (x2 - x1) + local y = y1 + ua * (y2 - y1) + return true, x, y + end + + return false, nil, nil + end, + + -- Closest point on line segment to point + closest_point_on_segment = function(px, py, x1, y1, x2, y2) + local dx, dy = x2 - x1, y2 - y1 + local len_sq = dx * dx + dy * dy + + if len_sq < 1e-10 then + return x1, y1 + end + + local t = ((px - x1) * dx + (py - y1) * dy) / len_sq + t = math_ext.clamp(t, 0, 1) + + return x1 + t * dx, y1 + t * dy + end +} + +-- ====================================================================== +-- INTERPOLATION FUNCTIONS +-- ====================================================================== + +math_ext.interpolation = { + -- Cubic Bezier interpolation + bezier = function(t, p0, p1, p2, p3) + t = math_ext.clamp(t, 0, 1) + local t2 = t * t + local t3 = t2 * t + local mt = 1 - t + local mt2 = mt * mt + local mt3 = mt2 * mt + + return p0 * mt3 + 3 * p1 * mt2 * t + 3 * p2 * mt * t2 + p3 * t3 + end, + + -- Catmull-Rom spline interpolation + catmull_rom = function(t, p0, p1, p2, p3) + t = math_ext.clamp(t, 0, 1) + local t2 = t * t + local t3 = t2 * t + + return 0.5 * ( + (2 * p1) + + (-p0 + p2) * t + + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 + + (-p0 + 3 * p1 - 3 * p2 + p3) * t3 + ) + end, + + -- Hermite interpolation + hermite = function(t, p0, p1, m0, m1) + t = math_ext.clamp(t, 0, 1) + local t2 = t * t + local t3 = t2 * t + local h00 = 2 * t3 - 3 * t2 + 1 + local h10 = t3 - 2 * t2 + t + local h01 = -2 * t3 + 3 * t2 + local h11 = t3 - t2 + + return h00 * p0 + h10 * m0 + h01 * p1 + h11 * m1 + end, + + -- Quadratic Bezier interpolation + quadratic_bezier = function(t, p0, p1, p2) + t = math_ext.clamp(t, 0, 1) + local mt = 1 - t + return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2 + end, + + -- Step interpolation + step = function(t, edge, x) + return t < edge and 0 or x + end, + + -- Smoothstep interpolation + smoothstep = function(edge0, edge1, x) + local t = math_ext.clamp((x - edge0) / (edge1 - edge0), 0, 1) + return t * t * (3 - 2 * t) + end, + + -- Smootherstep interpolation (Ken Perlin) + smootherstep = function(edge0, edge1, x) + local t = math_ext.clamp((x - edge0) / (edge1 - edge0), 0, 1) + return t * t * t * (t * (t * 6 - 15) + 10) + end +} + +return math_ext diff --git a/core/runner/sandbox.lua b/core/runner/lua/sandbox.lua similarity index 100% rename from core/runner/sandbox.lua rename to core/runner/lua/sandbox.lua diff --git a/core/runner/sqlite.lua b/core/runner/lua/sqlite.lua similarity index 70% rename from core/runner/sqlite.lua rename to core/runner/lua/sqlite.lua index 002abeb..0adf5f4 100644 --- a/core/runner/sqlite.lua +++ b/core/runner/lua/sqlite.lua @@ -69,25 +69,116 @@ local connection_mt = { -- Create a new table create_table = function(self, table_name, ...) - local columns = {...} + local columns = {} + local indices = {} + + -- Process all arguments + for _, def in ipairs({...}) do + if type(def) == "string" then + -- Check if it's an index definition + local index_type, index_def = def:match("^(UNIQUE%s+INDEX:|INDEX:)(.+)") + + if index_def then + -- Parse index definition: INDEX:idx_name(col1,col2) + local index_name, columns_str = index_def:match("([%w_]+)%(([^)]+)%)") + + if index_name and columns_str then + -- Split columns by comma + local index_columns = {} + for col in columns_str:gmatch("[^,]+") do + table.insert(index_columns, col:match("^%s*(.-)%s*$")) -- Trim whitespace + end + + table.insert(indices, { + name = index_name, + columns = index_columns, + unique = (index_type == "UNIQUE INDEX:") + }) + end + else + -- Regular column definition + table.insert(columns, def) + end + end + end if #columns == 0 then error("connection:create_table: no columns specified", 2) end + -- Create the table local query = string.format("CREATE TABLE IF NOT EXISTS %s (%s)", table_name, table.concat(columns, ", ")) - return self:exec(query) + local result = self:exec(query) + + -- Create indices + if #indices > 0 then + self:begin() + + for _, idx in ipairs(indices) do + local unique = idx.unique and "UNIQUE " or "" + + local index_query = string.format( + "CREATE %sINDEX IF NOT EXISTS %s ON %s (%s)", + unique, + idx.name, + table_name, + table.concat(idx.columns, ", ") + ) + + self:exec(index_query) + end + + self:commit() + end + + return result end, -- Insert a row or multiple rows - insert = function(self, table_name, data) + insert = function(self, table_name, data, columns) if type(data) ~= "table" then error("connection:insert: data must be a table", 2) end - -- Single row + if columns and type(columns) == "table" then + local placeholders = {} + for _ in ipairs(columns) do + table.insert(placeholders, "?") + end + + local query = string.format( + "INSERT INTO %s (%s) VALUES (%s)", + table_name, + table.concat(columns, ", "), + table.concat(placeholders, ", ") + ) + + local use_transaction = #data > 1 and type(data[1]) == "table" + + if use_transaction then + self:begin() + end + + local affected = 0 + + if #data > 0 and type(data[1]) == "table" then + for _, row in ipairs(data) do + local result = self:exec(query, row) + affected = affected + result + end + else + affected = self:exec(query, data) + end + + if use_transaction then + self:commit() + end + + return affected + end + if data[1] == nil and next(data) ~= nil then local columns = {} local placeholders = {} @@ -109,8 +200,8 @@ local connection_mt = { return self:exec(query, params) end - -- Multiple rows if #data > 0 and type(data[1]) == "table" then + self:begin() local affected = 0 for _, row in ipairs(data) do @@ -118,6 +209,7 @@ local connection_mt = { affected = affected + result end + self:commit() return affected end diff --git a/core/runner/string.lua b/core/runner/lua/string.lua similarity index 100% rename from core/runner/string.lua rename to core/runner/lua/string.lua diff --git a/core/runner/table.lua b/core/runner/lua/table.lua similarity index 100% rename from core/runner/table.lua rename to core/runner/lua/table.lua diff --git a/core/runner/lua/time.lua b/core/runner/lua/time.lua new file mode 100644 index 0000000..5026b45 --- /dev/null +++ b/core/runner/lua/time.lua @@ -0,0 +1,130 @@ +--[[ +time.lua - High performance timing functions +]]-- + +local ffi = require('ffi') +local is_windows = (ffi.os == "Windows") + +-- Define C structures and functions based on platform +if is_windows then + ffi.cdef[[ + typedef struct { + int64_t QuadPart; + } LARGE_INTEGER; + int QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount); + int QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency); + ]] +else + ffi.cdef[[ + typedef long time_t; + typedef struct timeval { + long tv_sec; + long tv_usec; + } timeval; + int gettimeofday(struct timeval* tv, void* tz); + time_t time(time_t* t); + ]] +end + +local time = {} +local has_initialized = false +local start_time, timer_freq + +-- Initialize timing system based on platform +local function init() + if has_initialized then return end + + if ffi.os == "Windows" then + local frequency = ffi.new("LARGE_INTEGER") + ffi.C.QueryPerformanceFrequency(frequency) + timer_freq = tonumber(frequency.QuadPart) + + local counter = ffi.new("LARGE_INTEGER") + ffi.C.QueryPerformanceCounter(counter) + start_time = tonumber(counter.QuadPart) + else + -- Nothing special needed for Unix platform init + start_time = ffi.C.time(nil) + end + + has_initialized = true +end + +-- PHP-compatible microtime implementation +function time.microtime(get_as_float) + init() + + if ffi.os == "Windows" then + local counter = ffi.new("LARGE_INTEGER") + ffi.C.QueryPerformanceCounter(counter) + local now = tonumber(counter.QuadPart) + local seconds = math.floor((now - start_time) / timer_freq) + local microseconds = ((now - start_time) % timer_freq) * 1000000 / timer_freq + + if get_as_float then + return seconds + microseconds / 1000000 + else + return string.format("0.%06d %d", microseconds, seconds) + end + else + local tv = ffi.new("struct timeval") + ffi.C.gettimeofday(tv, nil) + + if get_as_float then + return tonumber(tv.tv_sec) + tonumber(tv.tv_usec) / 1000000 + else + return string.format("0.%06d %d", tv.tv_usec, tv.tv_sec) + end + end +end + +-- High-precision monotonic timer (returns seconds with microsecond precision) +function time.monotonic() + init() + + if ffi.os == "Windows" then + local counter = ffi.new("LARGE_INTEGER") + ffi.C.QueryPerformanceCounter(counter) + local now = tonumber(counter.QuadPart) + return (now - start_time) / timer_freq + else + local tv = ffi.new("struct timeval") + ffi.C.gettimeofday(tv, nil) + return tonumber(tv.tv_sec) - start_time + tonumber(tv.tv_usec) / 1000000 + end +end + +-- Benchmark function that measures execution time +function time.benchmark(func, iterations, warmup) + iterations = iterations or 1000 + warmup = warmup or 10 + + -- Warmup + for i=1, warmup do func() end + + local start = time.microtime(true) + for i=1, iterations do + func() + end + local finish = time.microtime(true) + + local elapsed = (finish - start) * 1000000 -- Convert to microseconds + return elapsed / iterations +end + +-- Simple sleep function using coroutine yielding +function time.sleep(seconds) + if type(seconds) ~= "number" or seconds <= 0 then + return + end + + local start = time.monotonic() + while time.monotonic() - start < seconds do + -- Use coroutine.yield to avoid consuming CPU + coroutine.yield() + end +end + +_G.microtime = time.microtime + +return time diff --git a/core/runner/util.lua b/core/runner/lua/util.lua similarity index 100% rename from core/runner/util.lua rename to core/runner/lua/util.lua