diff --git a/common/space_lua/lua.test.ts b/common/space_lua/lua.test.ts index 6f23083a..d9e3966a 100644 --- a/common/space_lua/lua.test.ts +++ b/common/space_lua/lua.test.ts @@ -29,6 +29,10 @@ Deno.test("[Lua] OS tests", async () => { await runLuaTest("./stdlib/os_test.lua"); }); +Deno.test("[Lua] Math tests", async () => { + await runLuaTest("./stdlib/math_test.lua"); +}); + Deno.test("[Lua] JS tests", async () => { await runLuaTest("./stdlib/js_test.lua"); }); diff --git a/common/space_lua/stdlib.ts b/common/space_lua/stdlib.ts index 2c122a4b..904244fa 100644 --- a/common/space_lua/stdlib.ts +++ b/common/space_lua/stdlib.ts @@ -27,6 +27,7 @@ import { type LuaQueryCollection, } from "$common/space_lua/query_collection.ts"; import { templateApi } from "$common/space_lua/stdlib/template.ts"; +import { mathApi } from "$common/space_lua/stdlib/math.ts"; const printFunction = new LuaBuiltinFunction(async (_sf, ...args) => { console.log("[Lua]", ...(await Promise.all(args.map(luaToString)))); @@ -235,6 +236,7 @@ export function luaBuildStandardEnv() { env.set("table", tableApi); env.set("os", osApi); env.set("js", jsApi); + env.set("math", mathApi); // Non-standard env.set("each", eachFunction); env.set("space_lua", spaceLuaApi); diff --git a/common/space_lua/stdlib/math.md b/common/space_lua/stdlib/math.md new file mode 100644 index 00000000..f8a5eee6 --- /dev/null +++ b/common/space_lua/stdlib/math.md @@ -0,0 +1,221 @@ +API docs for Lua's `math` module. + +## math.random(m?, n?) +Generates random numbers. Without arguments, returns a float in [0,1). With one argument m, returns integer in [1,m]. With two arguments, returns integer in [m,n]. + +Example: +```lua +print(math.random()) -- prints: 0.7259081864761557 +print(math.random(10)) -- prints: 7 (random number from 1-10) +print(math.random(5,10)) -- prints: 8 (random number from 5-10) +``` + +## math.abs(x) +Returns the absolute value of `x`. + +Example: +```lua +print(math.abs(-5)) -- prints: 5 +print(math.abs(3.7)) -- prints: 3.7 +``` + +## math.ceil(x) +Returns the smallest integer larger than or equal to `x`. + +Example: +```lua +print(math.ceil(3.3)) -- prints: 4 +print(math.ceil(-3.3)) -- prints: -3 +``` + +## math.floor(x) +Returns the largest integer smaller than or equal to `x`. + +Example: +```lua +print(math.floor(3.7)) -- prints: 3 +print(math.floor(-3.7)) -- prints: -4 +``` + +## math.max(...) +Returns the maximum value among its arguments. + +Example: +```lua +print(math.max(1, 2, 3, 4)) -- prints: 4 +print(math.max(-5, -2, -10)) -- prints: -2 +``` + +## math.min(...) +Returns the minimum value among its arguments. + +Example: +```lua +print(math.min(1, 2, 3, 4)) -- prints: 1 +print(math.min(-5, -2, -10)) -- prints: -10 +``` + +## math.fmod(x, y) +Returns the remainder of the division of `x` by `y`. + +Example: +```lua +print(math.fmod(7, 3)) -- prints: 1 +print(math.fmod(7, 2)) -- prints: 1 +``` + +## math.modf(x) +Returns the integral part and fractional part of `x`. + +Example: +```lua +local int, frac = table.unpack(math.modf(3.7)) +print(int, frac) -- prints: 3 0.7 +``` + +## math.exp(x) +Returns e raised to the power of `x`. + +Example: +```lua +print(math.exp(0)) -- prints: 1 +print(math.exp(1)) -- prints: 2.718281828459045 +``` + +## math.log(x, base?) +Returns the natural logarithm of `x` or the logarithm of `x` to the given base. + +Example: +```lua +print(math.log(math.exp(1))) -- prints: 1 +print(math.log(8, 2)) -- prints: 3 +``` + +## math.pow(x, y) +Returns `x` raised to the power `y`. + +Example: +```lua +print(math.pow(2, 3)) -- prints: 8 +print(math.pow(3, 2)) -- prints: 9 +``` + +## math.sqrt(x) +Returns the square root of `x`. + +Example: +```lua +print(math.sqrt(9)) -- prints: 3 +print(math.sqrt(2)) -- prints: 1.4142135623730951 +``` + +## math.cos(x) +Returns the cosine of `x` (in radians). + +Example: +```lua +print(math.cos(0)) -- prints: 1 +``` + +## math.sin(x) +Returns the sine of `x` (in radians). + +Example: +```lua +print(math.sin(0)) -- prints: 0 +``` + +## math.tan(x) +Returns the tangent of `x` (in radians). + +Example: +```lua +print(math.tan(0)) -- prints: 0 +``` + +## math.acos(x) +Returns the arc cosine of `x` (in radians). + +Example: +```lua +print(math.acos(1)) -- prints: 0 +print(math.acos(0)) -- prints: 1.5707963267948966 +``` + +## math.asin(x) +Returns the arc sine of `x` (in radians). + +Example: +```lua +print(math.asin(0)) -- prints: 0 +print(math.asin(1)) -- prints: 1.5707963267948966 +``` + +## math.atan(y, x?) +Returns the arc tangent of `y/x` (in radians). If `x` is not provided, returns the arc tangent of `y`. + +Example: +```lua +print(math.atan(0)) -- prints: 0 +print(math.atan(1, 1)) -- prints: 0.7853981633974483 +``` + +## math.cosh(x) +Returns the hyperbolic cosine of `x`. + +Example: +```lua +print(math.cosh(0)) -- prints: 1 +print(math.cosh(1)) -- prints: 1.5430806348152437 +``` + +## math.sinh(x) +Returns the hyperbolic sine of `x`. + +Example: +```lua +print(math.sinh(0)) -- prints: 0 +print(math.sinh(1)) -- prints: 1.1752011936438014 +``` + +## math.tanh(x) +Returns the hyperbolic tangent of `x`. + +Example: +```lua +print(math.tanh(0)) -- prints: 0 +print(math.tanh(1)) -- prints: 0.7615941559557649 +``` + +## math.deg(x) +Converts angle `x` from radians to degrees. + + +## math.rad(x) +Converts angle `x` from degrees to radians. + +Example: +```lua +print(math.rad(180)) -- prints: 3.141592653589793 +print(math.rad(90)) -- prints: 1.5707963267948966 +``` + +## math.ult(m, n) +Returns true if `m` is less than `n` when they are considered unsigned integers. + +Example: +```lua +print(math.ult(1, 2)) -- prints: true +print(math.ult(2, 1)) -- prints: false +``` + +# Non-standard Extensions +## math.cosine_similarity(vecA, vecB) +Returns the cosine similarity between two vectors. + +Example: +```lua +local vec1 = {1, 2, 3} +local vec2 = {4, 5, 6} +print(math.cosine_similarity(vec1, vec2)) -- prints: 0.9746318461970762 +``` diff --git a/common/space_lua/stdlib/math.ts b/common/space_lua/stdlib/math.ts new file mode 100644 index 00000000..f95ecbd3 --- /dev/null +++ b/common/space_lua/stdlib/math.ts @@ -0,0 +1,94 @@ +import { + LuaBuiltinFunction, + LuaRuntimeError, + LuaTable, +} from "$common/space_lua/runtime.ts"; + +export const mathApi = new LuaTable({ + // Random number generation + random: new LuaBuiltinFunction((_sf, m?: number, n?: number) => { + if (m === undefined && n === undefined) { + // random() returns [0,1) + return Math.random(); + } else if (n === undefined) { + // random(m) returns [1,m] + return Math.floor(Math.random() * m!) + 1; + } else { + // random(m,n) returns [m,n] + return Math.floor(Math.random() * (n - m! + 1)) + m!; + } + }), + + // Basic functions + abs: new LuaBuiltinFunction((_sf, x: number) => Math.abs(x)), + ceil: new LuaBuiltinFunction((_sf, x: number) => Math.ceil(x)), + floor: new LuaBuiltinFunction((_sf, x: number) => Math.floor(x)), + max: new LuaBuiltinFunction((_sf, ...args: number[]) => Math.max(...args)), + min: new LuaBuiltinFunction((_sf, ...args: number[]) => Math.min(...args)), + + // Rounding and remainder + fmod: new LuaBuiltinFunction((_sf, x: number, y: number) => x % y), + modf: new LuaBuiltinFunction((_sf, x: number) => { + const int = Math.floor(x); + const frac = x - int; + return new LuaTable([int, frac]); + }), + + // Power and logarithms + exp: new LuaBuiltinFunction((_sf, x: number) => Math.exp(x)), + log: new LuaBuiltinFunction((_sf, x: number, base?: number) => { + if (base === undefined) { + return Math.log(x); + } + return Math.log(x) / Math.log(base); + }), + pow: new LuaBuiltinFunction((_sf, x: number, y: number) => Math.pow(x, y)), + sqrt: new LuaBuiltinFunction((_sf, x: number) => Math.sqrt(x)), + + // Trigonometric functions + cos: new LuaBuiltinFunction((_sf, x: number) => Math.cos(x)), + sin: new LuaBuiltinFunction((_sf, x: number) => Math.sin(x)), + tan: new LuaBuiltinFunction((_sf, x: number) => Math.tan(x)), + acos: new LuaBuiltinFunction((_sf, x: number) => Math.acos(x)), + asin: new LuaBuiltinFunction((_sf, x: number) => Math.asin(x)), + atan: new LuaBuiltinFunction((_sf, y: number, x?: number) => { + if (x === undefined) { + return Math.atan(y); + } + return Math.atan2(y, x); + }), + + // Hyperbolic functions + cosh: new LuaBuiltinFunction((_sf, x: number) => Math.cosh(x)), + sinh: new LuaBuiltinFunction((_sf, x: number) => Math.sinh(x)), + tanh: new LuaBuiltinFunction((_sf, x: number) => Math.tanh(x)), + + // Additional utility + deg: new LuaBuiltinFunction((_sf, x: number) => x * 180 / Math.PI), + rad: new LuaBuiltinFunction((_sf, x: number) => x * Math.PI / 180), + ult: new LuaBuiltinFunction((_sf, m: number, n: number) => { + // Unsigned less than comparison + return (m >>> 0) < (n >>> 0); + }), + + // Keep the cosine_similarity utility function + cosine_similarity: new LuaBuiltinFunction( + (sf, vecA: number[], vecB: number[]) => { + if (vecA.length !== vecB.length) { + throw new LuaRuntimeError("Vectors must be of the same length", sf); + } + + let dotProduct = 0; + let normA = 0; + let normB = 0; + + for (let i = 0; i < vecA.length; i++) { + dotProduct += vecA[i] * vecB[i]; + normA += vecA[i] ** 2; + normB += vecB[i] ** 2; + } + + return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); + }, + ), +}); diff --git a/common/space_lua/stdlib/math_test.lua b/common/space_lua/stdlib/math_test.lua new file mode 100644 index 00000000..8996180a --- /dev/null +++ b/common/space_lua/stdlib/math_test.lua @@ -0,0 +1,48 @@ +local function assert_equal(a, b) + if a ~= b then + error("Assertion failed: " .. a .. " is not equal to " .. b) + end +end + +-- Trigonometric functions +assert_equal(math.cos(0), 1) +assert_equal(math.sin(0), 0) +assert_equal(math.tan(0), 0) +assert_equal(math.acos(1), 0) +assert_equal(math.asin(0), 0) +assert_equal(math.atan(0), 0) + +-- Hyperbolic functions +assert_equal(math.cosh(0), 1) +assert_equal(math.sinh(0), 0) +assert_equal(math.tanh(0), 0) + +-- Basic functions +assert_equal(math.abs(-5), 5) +assert_equal(math.ceil(3.3), 4) +assert_equal(math.floor(3.7), 3) +assert_equal(math.max(1, 2, 3, 4), 4) +assert_equal(math.min(1, 2, 3, 4), 1) + +-- Rounding and remainder +assert_equal(math.fmod(7, 3), 1) + +-- Power and logarithms +assert_equal(math.exp(0), 1) +assert_equal(math.log(math.exp(1)), 1) +assert_equal(math.log(8, 2), 3) -- log base 2 of 8 +assert_equal(math.pow(2, 3), 8) +assert_equal(math.sqrt(9), 3) + + +-- Random number tests (basic range checks) +local rand = math.random() +assert_equal(rand >= 0 and rand < 1, true) +local rand_n = math.random(10) +assert_equal(rand_n >= 1 and rand_n <= 10, true) +local rand_range = math.random(5, 10) +assert_equal(rand_range >= 5 and rand_range <= 10, true) + +-- Unsigned less than comparison +assert_equal(math.ult(1, 2), true) +assert_equal(math.ult(2, 1), false)