LuaGLM
A Lua 5.4.6 runtime providing vector, quaternion, and matrix basic types with an assortment of GLSL built-in functions.
Vectors and Quaternions
Vectors are a new basic type and are viewed as immutable tables of floats
accessible by keys 1, 2, 3, 4
, x, y, z, w
, and r, g, b, a
(case-insensitive).
-- Generic constructor
> v = vec(math.pi, math.exp(1), (1 + math.sqrt(5)) / 2)
-- Constructor with explicit length
> v3 = vec3(math.pi, math.exp(1), (1 + math.sqrt(5)) / 2)
-- Accessing vector fields
> v[3] + v.z
3.2360680103302
-- Vector swizzling
> v.xyzx
vec4(3.141593, 2.718282, 1.618034, 3.141593)
-- Test vectors for equality
> v == v3
true
-- Arithmetic operators on vector types
> ((v + v3) * v) - v3
vec3(16.597618, 12.059830, 3.618034)
-- OP_LEN returns the magnitude of the vector. The C API, e.g., lua_len, returns
-- the component count
> #v
4.4583287239075
-- Component count
> v.n
3
-- Iterate over each component with 'pairs'
> for k,v in pairs(v3) do print(k,v) end
1 3.1415927410126
2 2.7182817459106
3 1.6180340051651
-- Use vectors as table keys
> t = { }
> t[v // 1.0] = "Hello, World!"
> t[v // 1.0]
Hello, World!
Quaternions are a variant of vector:
-- Create a quaternion with {w,x,y,z} components
> quat(1,0,0,0)
quat(1.000000, {0.000000, 0.000000, 0.000000})
-- Create a quaternion by rotating an axis by 35 degrees
> quat(35.0, vec(1,0,0))
quat(0.953717, {0.300706, 0.000000, 0.000000})
-- Multiply a direction vector by the quaternion
> quat(35.0, vec(1,0,0)) * norm(vec(1))
vec3(0.577350, 0.141783, 0.804092)
-- Vectors and quaternions have explicit an type string even though they are
-- represented internally by the same LUA_TVECTOR type
> print(type(v), type(quat(1,0,0,0)))
vec3 quat
When a vector or quaternion value is accessed by an unknown field, some
additional rules exist prior to a __index
metamethod lookup:
- If a string key has less-than-or-equal-to four characters it is passed
through a swizzling filter. Returning a vector if all characters are valid
fields, e.g.,
v.zyx == vec3(v.z, v.y, v.x)
. - The component count of a vector (or quaternion) can be accessed by the
n
field (similar totable.pack
). The length operator returns the magnitude of the vector (or quaternion).
Vector API
Vector and quaternion values are represented by the LUA_TVECTOR
tag and are
internally represented using an array of floats (or half-precision floats, see
Configuration). On an API level they are effectively tables and accessing
their values can be done using the same C API functions:
lua_next
lua_geti
,lua_getfield
lua_rawget
,lua_rawgeti
lua_rawlen
,lua_len
: Returns the component count of the vec/quat.
For backwards compatibility the LUAGLM_COMPAT_5_4
build option can be used to
map LUA_TVECTOR
and LUA_TMATRIX
types to LUA_TTABLE
when interfacing with
the C API. The vector specific API can also be referenced in lua.h.
Vector Methods
Vector and quaternion values do not maintain an explicit metatable. The Lua
functions getmetatable
and debug.setmetatable
and C API functions
lua_setmetatable
and lua_getmetatable
can be used to define explicit
metatables for the LUA_TVECTOR
type.
For performance, all arithmetic and bitwise operations have internal
implementations that take precedence over any script-defined metamethod. All
other metamethods (e.g., __concat
, _call
, etc.) are allowed custom
implementations.
Matrices
Matrices are another basic type and represent mutable collections of
column(-major) vectors that are accessible by integer keys 1, 2, 3, 4
.
They are collectible objects and beholden to the garbage collector.
-- Create a matrix
> m = mat(vec(1.0, 0.0, 0.0), vec(0.0, 0.819152, 0.573576), vec(0.0, -0.573576, 0.819152))
-- Like vectors, matrices have an explicit type string even though they are
-- internally represented by the same LUA_TMATRIX tag
> type(m)
mat3x3
-- tostring for matrix and vector types
> tostring(m)
mat3x3((1.000000, 0.000000, 0.000000), (0.000000, 0.819152, 0.573576), (0.000000, -0.573576, 0.819152))
-- The length operator corresponds to the number of column vectors
> #m
3
-- Access a column component
> m[2]
vec3(0.000000, 0.819152, 0.573576)
-- Iterate over each matrix component
> for k,v in pairs(m) do print(k, v) end
1 vec3(1.000000, 0.000000, 0.000000)
2 vec3(0.000000, 0.819152, 0.573576)
3 vec3(0.000000, -0.573576, 0.819152)
-- Multiply a vector by the given matrix
> m * vec(0,1,0)
vec3(0.000000, 0.819152, 0.573576)
Matrix API
Matrix objects are represented by the LUA_TMATRIX
type. On an API level, they
are effectively tables (arrays) and accessing/modifying their components can be
done using the same C API functions:
lua_next
lua_gettable
,lua_settable
lua_geti
,lua_seti
lua_rawget
,lua_rawset
lua_rawgeti
,lua_rawseti
.lua_rawlen
,lua_len
The matrix specific C API can be referenced in lua.h.
Matrix Methods
Like vectors and quaternions, matrix objects do not maintain an explicit
metatable. See the Methods
section for vectors and quaternions.
Library Functions
The runtime mirrors, and extends, most built-in functions specified in the OpenGL Shading Language. Documentation for these functions is located in GLSL.md.
Power Patches
This runtime imports and extends many small/useful changes to the Lua parser, API, and runtime, all bound to preprocessor flags:
Compound Operators
Add "+=", "-=", "*=", "/=", "//=", "%=", "<<=", ">>=", "&=", "|=", "^=", and "..="
to the language. The increment and decrement operators (++, --
) have not been
implemented due to one of those operators being reserved.
Extended Literals
Allow binary numerals, underscores, and f/F/.f/.F suffixes in literals.
-- Separators
x = 1_234_456
-- Floating-point literal suffixes
x = 1f
-- Binary Numerals
verify_bf16(1.0, 0b0_01111111_000_0000)
Float16
Support for
half-precision
floating-points in string.pack
and string.unpack
using 'e'
as the
format specifier.
> string.unpack("e", string.pack("e", math.pi))
3.140625 3
Safe Navigation
An indexing operation that suppresses errors on accesses into undefined values (similar to the safe-navigation operators in C#, Kotlin, etc.), e.g.,
-- Indexing
t?.x?.y == nil
t.x?[1] == nil
-- Expression chain short circuiting
knees = head?.shoulders.knees
x,y,z = sigma?.tau.foot:gun("flower_power")
-- Functions
local x,y,z = call.exists?("Hello,", "World!", math.abs(-42))
if call.fix_bug?() then
error("Impossible")
end
-- Self syntax
local e,n,i,a,c = t.y?:z()
local linc,pdp = t.y:z?()
if t?:x(0xC00010FF, 0xFEEDC0DE) then
print(0xB105F00D)
end
If-Expressions (Ternary Operator)
The idiomatic approach to approximate ternary operators in Lua is syntax of the form:
local x = a and b or c
However, if a
evaluates to true while b
evaluates to false/nil, then the
expression will not behave exactly like a ternary operator. Instead, this patch
supports ternary-like operators using if COND then EXPR else EXPR
syntax:
local x = if a then b else c
local x = if a then b elseif c then d else e
print(if a then b else c)
Set Constructors
Syntactic sugar to improve the syntax for specifying sets, i.e.,
t = { .a, .b }
is functionally equivalent to:
t = { a = true, b = true }
Compile Time Jenkins' Hashes
String literals wrapped in back-ticks are Jenkins' one-at-a-time hashed when parsed.
> `Hello, World!`
1395890823
For runtime hashing, the joaat
function is included in the base library:
-- joaat(input [, ignore_case]): Compute the Jenkins hash of the input string.
-- If 'ignore_case' is true, the byte data is hashed as is. Otherwise, each
-- character is tolower'd prior to hashing.
> joaat("Hello, World!")
1395890823
> joaat("CPed")
2491553369
Lambda Expressions (Short Function Notation)
Syntactic sugar for writing concise anonymous functions of the form |a, b, ...| expr
.
Where expr
is any expression equivalent to function(a, b, ...) return expr end
. For example,
> f = |x| x^2 - 1 -- function(x) return x^2 - 1 end
> f(2)
3.0
> f(vec3(1, 2, 3))
vec3(0.000000, 3.000000, 8.000000)
-- 'hexadump' from lua-MessagePack.lua
> hexadump = |s| s:gsub('.', |c| string.format('%02X ', c:byte()))
> hexadump("\221\255\255\255\255Z")
DD FF FF FF FF 5A
Defer
Imports the defer statement from
Ravi into the
runtime. In addition, func2close
from ltests.h has been imported into the base
library.
-- LUA_EXT_DEFER:
defer
numopen = numopen - 1
end
-- LUA_EXT_DEFER_API: closing function. Can also be used to supply a
-- to-be-closed variable to a generic for loop.
local _ <close> = defer(function()
numopen = numopen - 1
end)
Optimized Iteration
A generic 'for' loop starts by evaluating its explist to produce four values: an iterator function, a state, an initial value for the control variable, and a closing (to-be-closed) value. However, the __pairs metamethod does not support the optional closing value.
This extension introduces optimizations to pairs/ipairs in for-loops by using the unused to-be-closed slot to cache an index variable (or marker) and is based on the loop optimization patch described in lua-l.
Extended API
Expose lua_createtable
and API functions common to other custom Lua runtimes.
-- Creates a new empty table.
-- narr: a hint for how many elements the table will have as a sequence.
-- nrec: a hint for how many other elements the table will have.
t = table.create(narr[, nrec])
table.new = table.create -- Deprecated alias
-- Create a new array filled w/ some value.
-- narr: number of elements to fill (i.e., size of array)
-- value: value to fill the table with
t = table.fill(narr[, value])
-- Restore the table to its initial value (removing its contents) while
-- retaining its internal pointer.
t = table.clear(t)
t = table.wipe(t) -- Deprecated alias
-- Request the removal of unused capacity in the given table (shrink_to_fit).
t = table.compact(t)
-- An efficient (implemented using memcpy) table shallow-copy implementations.
t2 = table.clone(t)
-- Debug: force an explicit rehash of the table.
t = table.rehash(t)
-- Return the type of table being used. Note, this function only measures the
-- size of the "array part" of a Lua table and the "root" node of its
-- "hash part". Once an "array" becomes "mixed", or if a table has all of
-- values nil'd out, the table.type will remain "mixed" or "hash".
label = table.type(t) -- "empty", "array", "hash", or "mixed"
-- Trim characters off the beginning and end of a string.
str = string.trim(input [, chars])
Readonly
Introduce the ability to make a table read-only and prohibit any modifications.
-- Mark a table as readonly.
--
-- This behavior is 'shallow', i.e., non-frozen tables stored within 't' are
-- still mutable.
--
-- Frozen tables respect the '__newindex' metamethod, however, any attempt to
-- modify the table by that function (e.g., __newindex = rawset) will lead to an
-- error being thrown.
--
-- Tables with 'protected' metatables, i.e., a '__metatable' field, cannot be
-- frozen.
t = table.freeze(t)
-- Return true if the provided table is configured as readonly; false otherwise.
bool = table.isfrozen(t)
This extension changes a few C API guarantees:
- lua_setmetatable:
@apii{1,0,-}
to@apii{1,0,v}
- lua_rawset:
@apii{2,0,m}
to@apii{2,0,v}
- lua_rawsetp:
@apii{1,0,m}
to@apii{1,0,v}
- lua_rawseti:
@apii{1,0,m}
to@apii{1,0,v}
With the error corresponding to a readonly violation (or out-of-memory).
Usertags (Tagged Userdata)
An alternative to
luaL_checkudata
that associates an integer tag with a userdata type (tname
).
/* Create a tagged user data with zero user values */
#define lua_newusertag(L, s, t) lua_newusertaguv(L, s, t, 0)
/* Create and push on the stack a new full userdata with tag 'tag' */
void *lua_newusertaguv(lua_State *L, size_t size, int tag, int nuvalue);
/*
* If the value at the given index is a userdata with tag 'tag', return its
* memory-block address (a pointer). Otherwise, NULL is returned.
*/
void *lua_tousertag(lua_State *L, int idx, int tag);
/*
* Check whether the value at the given index is a userdata with tag 'tag',
* returning the userdatas memory-block. Otherwise, an error is thrown.
*/
void *luaL_checkusertag(lua_State *L, int idx, int tag);
/*
* Associate a label with a usertag. If the tag is registered to another name,
* an error is thrown.
*
* This function exists to weakly enforce (or sanitize) tag conflicts between
* libraries. The current API does not require a tag to be named before use.
*/
int luaL_nameusertag(lua_State *L, int tag, const char *name);
Readline History
Keep a persistent list of commands that have been run on the Lua interpreter.
Uses the LUA_HISTORY
environment variable to declare location history.
Building
The Lua can be compiled as C or as C++. All functions required to integrate cglm into the runtime are defined in lglmcore.h while lglmaux.h maintains the functions to integrate cglm into the base Lua libraries.
Configuration
Defined in luaconf.h:
- LuaGLM Options
- LUAGLM_COMPAT_5_4: Lua 5.4 C-API compatibility (e.g., map
LUA_T{VECTOR,MATRIX}
toLUA_TTABLE
). - LUAGLM_HALF_TYPE: Use
Float16
as the vector storage type.
- LUAGLM_COMPAT_5_4: Lua 5.4 C-API compatibility (e.g., map
- Power Patches: See Lua Power Patches section.
- LUA_EXT_COMPOUND: Enable 'Compound Operators'.
- LUA_EXT_DEFER: Enable 'Defer'.
- LUA_EXT_IFEXPR: Enable 'If-Expressions'.
- LUA_EXT_JOAAT: Enable 'Compile Time Jenkins' Hashes'.
- LUA_EXT_LAMBDA: Enable 'Lambda Expressions'.
- LUA_EXT_LITERAL: Enable 'Extended Literals'.
- LUA_EXT_SAFENAV: Enable 'Safe Navigation'.
- LUA_EXT_SETINIT: Enable 'Set Constructors'.
- LUA_EXT_ITERATION: Enable 'Optimized Iteration'.
- LUA_EXT_API: Enable 'Extended API'.
- LUA_EXT_CHRONO: Enable nanosecond resolution timers and rdtsc sampling (
os.{usec,nsec,rdtsc}
). - LUA_EXT_HALF: Enable 'Float16'.
- LUA_EXT_READONLY: Enable 'Readonly'.
- LUA_EXT_USERTAG: Enable 'Usertags'.
- LUA_EXT_READLINE_HISTORY: Enable 'Readline History'.
Make
Uses a modified (GNU make required) version of the makefile bundled with releases of Lua.
# Rules: Default rule is "guess"
# clean - Clean build directory
# echo - Output build flags
# guess - Build (guesses platform)
# help - List platforms
#
# Options:
# UNITY - Unity/Jumbo build (onelua)
# DEBUG - Enable internal testing flags (and objects)
# UBSAN - Compile with and link UBSan
# ASAN - Compile with and link ASan
#
# Flags:
# EXTRA_CFLAGS - Additional C flags (e.g., -march=native)
# EXTRA_LDFLAGS - Additional Linker flags (e.g., -fuse-ld=mold)
# Ensure cglm is initialized
└> git submodule update --init
# Default Build (Add UNITY=1 for unity builds)
└> make
# Run Lua
└> ./lua
# WSL/Windows: clang.exe
└> make windows
└> ./lua.exe ...
# Emscripten
└> make emscripten
# Note: older node versions may require --experimental-wasm-eh
└> node lua.js ...
# Development: debug build (linux only)
└> make DEBUG=1 ...
Sources & Acknowledgments
- grit-lua: Original implementation and inspiration.
- cglm: Vector and Matrix functionality.
- OpenGL Shading Language
Developer Notes
- Improve SIGFPE testing (and more defensive programming in lglm.c where possible).
- Finalize test suite coverage.
- Cleanup test scripts/environment and publish.
- Parser/opcodes/builtins for vector and matrix types.
- String parsing/formatting optimizations.
- Deterministic glsl lib.
- Sandboxing improvements.
- Expand the complex number API.
License
Lua and cglm are distributed under the terms of the MIT license. See the Copyright Notice in lua.h.