MicroLua

MicroLua is a tiny Lua runtime for embedded systems and other places where portability, a small footprint, and predictable memory use matter.

In a local macOS size build with similar build flags, MicroLua's compiler/parser-less runtime artifacts total 218 KiB, or 259 KiB with the parser. That is about 2.8x smaller than Lua 5.5.0's runtime artifacts at 612 KiB.

Download

Clone the source repository:

git clone https://github.com/lus-lang/microlua.git
cd microlua

You can also download a source archive from the repository host and unpack it locally. The build instructions below assume that the current directory is the MicroLua source tree.

Compile

MicroLua uses Meson and Ninja. A debug build allows libc in the core and is the usual build for development:

meson setup builddir
ninja -C builddir

A release build compiles the core static library in freestanding mode:

meson setup builddir-release --buildtype=release
ninja -C builddir-release

In release mode, the core library is compiled with freestanding flags. The command-line executable still uses libc for file and console I/O.

For embedded systems that should not carry the source parser, build a bytecode-only runtime:

meson setup build-bytecode -Dcompiler=false --buildtype=release
ninja -C build-bytecode

The bytecode-only library omits the lexer and parser. It accepts precompiled MicroLua bytecode through MLuaLoadBytecode, MLuaDoBytecode, MLuaLoadBuffer, or MLuaDoBuffer.

Port Configuration

Port settings live in src/MLuaConfig.h. Use -Dport=generic64, generic32, cortex-m, riscv32, or ti84ce for bundled presets, or provide a board-specific header:

meson setup build-board -Dport_header=path/to/my_board_mlua.h

A port header can override pointer size, heap alignment, default stack and frame sizes, GC threshold, fixed-width type source, native float subtype and width, math hooks, compiler support, bytecode dumping, the string.pack format engine, VM dispatch style, and opcode profiling. MicroLua supports 64-bit NaN-boxing and a 32-bit alignment-tagging representation with boxed full-width integers.

Build Outputs

builddir/mlua
The command-line interpreter and REPL.
builddir/libmicrolua.a
The embeddable MicroLua static library.
builddir-release/libmicrolua.a
The freestanding release static library.
build-bytecode/mlua
The bytecode-only command-line runner.
build-bytecode/libmicrolua.a
The embeddable bytecode-only static library without the lexer or parser.

Use

Command Line

./builddir/mlua script.lua
./builddir/mlua
./builddir/mlua -e 'print(1 + 2 * 3)'
./builddir/mlua -o chunk.mlu script.lua
./build-bytecode/mlua chunk.mlu

Running mlua with no file starts the interactive REPL. The REPL evaluates one line as one chunk, so local variables do not persist across lines. Put multi-line programs in a file.

Use -o on a compiler-enabled host build to create a portable .mlu bytecode file. Bytecode has a versioned header with an explicit endianness byte and fixed-width serialized fields, so it can be moved across supported endian and pointer-size targets with a compatible MicroLua bytecode version. Numeric constants are stored as canonical IEEE-754 binary64 values and are narrowed or widened at the boundary for targets using a narrower native MLUA_FLOAT. The current bytecode format is v4, which adds fused global and table-indexing opcodes.

Options

Option Meaning
-h, --help Show command-line help.
-e EXPR, --eval EXPR Evaluate a source chunk supplied on the command line. Requires a compiler-enabled build.
-i, --interactive Enter interactive mode. Requires a compiler-enabled build.
-I FILE, --include FILE Include a file through the REPL's require support. Requires a compiler-enabled build.
-d, --dump Print memory usage statistics.
--memory-limit N Run with a heap limited to N bytes.
--no-column Omit column information from debug output.
-o FILE Compile a source file or -e chunk and write MicroLua bytecode to FILE. Requires a compiler-enabled build.
-v, --version Print version information.

Small Example

local total = 0
for i = 1, 10 do
  total = total + i
end
print(total)

Embed MicroLua

Link your application with libmicrolua.a and include the headers from src/. The core can run on a caller-provided heap and does not require libc in the freestanding release build.

The embedder controls host integration. Output and module loading are available only when the embedder installs callbacks.

MLuaSetOutput
Installs the callback used by functions such as print and error.
MLuaSetRequirer
Installs the callback used by require.

Optional io, os, dofile, and loadfile support lives in src/extensions/MLuaStdLib.c and requires libc.

A common embedded workflow is to compile source on a development host with mlua -o app.mlu app.lua, include the resulting bytes in firmware or external storage, then call MLuaDoBytecode or MLuaLoadBytecode on the target. A bytecode-only build rejects source-only workflows such as -e, the REPL, load, and loadfile.

Special ports

Special ports are complete platform integrations that go beyond a Meson port preset. They may include board-specific build systems, host bindings, packaging tools, install notes, benchmarks, or emulator test flows.

TI-84 Plus CE

platform/ti84ce/ is a complete board port for the TI-84 Plus CE: an eZ80 target with 24-bit int and pointers, binary32 double, a small C stack, and a tight RAM budget. It is built with the external CE C/C++ Toolchain makefiles, not Meson.

Program Directory Contents
MLUA.8xp (~57 KB) platform/ti84ce/repl/ Full build: runs source or bytecode appvars and includes an on-calc REPL.
MLUAR.8xp (~44 KB) platform/ti84ce/runner/ Bytecode-only runner without the lexer, parser, or compiler.
cd platform/ti84ce/repl && make
cd platform/ti84ce/runner && make

The CE preset disables string.dump and the string.pack family (MLUA_ENABLE_DUMP=0, MLUA_ENABLE_PACK=0) and enables GNU-C computed-goto dispatch (MLUA_VM_COMPUTED_GOTO=1). The full REPL is close to the calculator's practical decompressed image ceiling, so image size should be checked in bin/MLUA.map when core code grows.

MicroLua vs TI-BASIC in CEmu on OS 5.7
Benchmark Workload TI-BASIC MicroLua Winner
bench_int Scalar integer loop, 20k iterations 161 s 19.9 s MicroLua 8.1x
mandel 32x24 Mandelbrot floats, compute 121 s 52.7 s MicroLua 2.3x
mandel Draw phase, one pixel per cell 5 s 1.7 s MicroLua 2.9x
bench_list 60 element-wise passes over 500-element lists 20 s 19.8 s MicroLua 1.01x
bench_str Build 1000-char string by 500 appends and scan 11 s 6.8 s MicroLua 1.6x

The matched benchmark sources, calculator bindings, installation notes, and CEmu autotester workflow are documented in platform/ti84ce/README.md.

Documentation

Language

MicroLua follows Lua syntax where that syntax fits its footprint. Supported statements include local declarations, assignments, function definitions, if, while, repeat, numeric for, generic for, do blocks, break, and return.

Supported values include nil, booleans, numbers, strings, tables, functions, C functions, and coroutines. Strings and source files are UTF-8. Numeric literals keep the Lua 5.3 integer/float distinction: math.type(1) is "integer" and math.type(1.0) is "float".

Supported operators include arithmetic +, -, *, /, %, exponentiation ^, length #, concatenation .., comparisons, and, or, and not.

Functions support lexical closures, upvalues, varargs, multiple return values, and tail-call frame reuse for plain fixed-arity return f(args) calls. Vararg and multiple-return call forms have the same language result but may allocate a fresh frame.

Tables

Tables have an array part and a hash part. Array keys must remain contiguous: assigning a value past #t + 1 is a runtime error because it would create a hole. Assigning nil past the end is a no-op.

MicroLua does not have metatables. It provides table.forward as a small table-delegation mechanism for lookup forwarding.

Base Globals

Function Arguments Returns Description
assertv: any [, message: string]All original arguments, or raises an error.Checks that v is not false or nil. If the check fails, raises message or "assertion failed!".
errormessage: stringDoes not return.Raises a runtime error using the string value of message, or "error" when no string is supplied.
loadchunk: string [, chunkname: string [, mode: string [, env: any]]]Compiled function, or nil, message.Compiles a string chunk. MicroLua accepts the extra Lua-style parameters but only uses the chunk name.
loadstringchunk: string [, chunkname: string [, mode: string [, env: any]]]Compiled function, or nil, message.Alias of load.
nexttable: table [, key: any]next_key, value, or nil.Returns the next key and value after key. Pass nil or omit key to start iteration.
pairstable: tableiterator, table, nil.Returns a generic-for iterator triple using next.
ipairstable: tableiterator, table, 0.Returns a generic-for iterator triple over array indices starting at 1.
pcallfunc: function, ...: anytrue, ... on success; false, message on error.Calls func in protected mode with the remaining arguments.
selectindex: integer|string, ...: anySelected values, a count, or no values.If index is "#", returns the number of extra arguments. Otherwise returns arguments from the numeric index onward; negative indices count from the end.
tonumbervalue: number|string [, base: integer]Number, or nil.Converts numbers or numeric strings. Base conversion is supported for integer strings.
tostringvalue: anyString.Converts a value to its string representation.
typevalue: anyString.Returns the MicroLua type name.
xpcallfunc: function, handler: functiontrue, ... on success; false, handled_error on error.Protected call that invokes handler with the error value when func fails.
print...: anyNo values.Writes tab-separated values through the embedder output callback. Available when the embedder installs output support; the REPL does.
requirename: stringModule result.Loads a module through the embedder requirer callback. Available only when that callback is installed; the REPL installs one.
unpacktable: table [, i: integer [, j: integer]]Values table[i] through table[j].Global compatibility alias of table.unpack.

math

Function Arguments Returns Description
math.absx: numberNumber.Absolute value.
math.acosx: numberNumber.Arc cosine in radians.
math.asinx: numberNumber.Arc sine in radians.
math.atany: number [, x: number]Number.Arc tangent. With two arguments, uses atan2(y, x).
math.atan2y: number [, x: number]Number.Alias of math.atan in MicroLua.
math.ceilx: numberNumber.Smallest integer not less than x.
math.cosx: numberNumber.Cosine of x radians.
math.coshx: numberNumber.Hyperbolic cosine.
math.degx: numberNumber.Converts radians to degrees.
math.expx: numberNumber.Returns e raised to x.
math.floorx: numberNumber.Largest integer not greater than x.
math.fmodx: number, y: numberNumber.C-style floating remainder of x / y.
math.frexpx: numbermantissa, exponent.Splits x into a normalized fraction and exponent.
math.ldexpmantissa: number, exponent: integerNumber.Returns mantissa * 2^exponent.
math.logx: number [, base: number]Number.Natural logarithm, or logarithm in the supplied base.
math.log10x: numberNumber.Base-10 logarithm.
math.max...: numberNumber, or nil when called without arguments.Maximum numeric argument.
math.min...: numberNumber, or nil when called without arguments.Minimum numeric argument.
math.modx: number, y: numberNumber.Alias of math.fmod.
math.modfx: numberinteger_part, fractional_part.Splits a number into integer and fractional parts.
math.powx: number, y: numberNumber.Returns x raised to y.
math.radx: numberNumber.Converts degrees to radians.
math.random[m: integer [, n: integer]]Number or integer.With no arguments, returns a float in [0, 1). With one argument, returns an integer in [1, m]. With two, returns an integer in [m, n].
math.randomseedseed: integerNo values.Seeds MicroLua's xorshift64 pseudo-random generator. Seed zero is changed to one.
math.sinx: numberNumber.Sine of x radians.
math.sinhx: numberNumber.Hyperbolic sine.
math.sqrtx: numberNumber.Square root.
math.tanx: numberNumber.Tangent of x radians.
math.tanhx: numberNumber.Hyperbolic tangent.
math.tointegerx: numberInteger, or nil.Returns x as an integer if it is exactly representable as a MicroLua integer.
math.typex: any"integer", "float", or nil.Reports whether a number is stored as an integer or float.
math.ulta: integer, b: integerBoolean.Unsigned less-than comparison of two integer values.

Available constants: pi, huge, maxinteger, and mininteger.

string

string.len, string.sub, string.byte, string.char, and string.reverse operate on codepoints. Pattern matching is byte-based, and case conversion is ASCII-only. string.dump is present when MLUA_ENABLE_DUMP is enabled. The string.pack, string.packsize, and string.unpack functions are present when MLUA_ENABLE_PACK is enabled; both knobs default on.

Function Arguments Returns Description
string.bytes: string [, i: integer [, j: integer]]Codepoints from i through j.Returns UTF-8 codepoints at codepoint positions. Negative indices count from the end.
string.char...: integerString.Encodes up to 256 numeric codepoints as UTF-8.
string.dumpfunc: functionBytecode string, or nil, message.Serializes a Lua closure in MicroLua bytecode format. C functions cannot be dumped.
string.finds: string, pattern: string [, init: integer [, plain: boolean]]start, end [, captures...], or nil.Finds a byte-position match. With plain truthy, searches for literal text. Otherwise uses MicroLua patterns.
string.formatformat: string, ...: anyString.Formats values into a bounded buffer using MicroLua's formatter.
string.lens: stringInteger.Returns the number of UTF-8 codepoints.
string.lowers: stringString.ASCII-only lowercase conversion. Non-ASCII bytes are preserved.
string.matchs: string, pattern: string [, init: integer]Captures, whole match, or nil.Matches a MicroLua pattern. Captures are returned when present; otherwise the matched substring is returned.
string.packformat: string, ...: integer|stringBinary string.Packs values using supported format codes: b, B, h, H, i, I, l, L, n, N, cN, z, and ignored endian markers.
string.packsizeformat: stringInteger, or nil, message.Returns the static packed size for fixed-size formats. Variable-length s and z formats return nil, message.
string.reps: string, n: integerString.Repeats s n times. Non-positive counts return an empty string.
string.reverses: stringString.Reverses the sequence of UTF-8 codepoints.
string.subs: string [, i: integer [, j: integer]]String.Returns a codepoint-indexed substring. Negative indices count from the end.
string.unpackformat: string, data: string [, pos: integer]Unpacked values followed by next position.Unpacks values from data starting at 1-based byte position pos.
string.uppers: stringString.ASCII-only uppercase conversion. Non-ASCII bytes are preserved.

Supported pattern features include anchors ^ and $, classes such as %a, %d, %s, %w, %l, %u, %p, %c, and %x, character sets, captures, and the *, +, -, and ? quantifiers.

table

Function Arguments Returns Description
table.concattable: table [, sep: string [, i: integer [, j: integer]]]String.Concatenates array entries from i through j, placing sep between entries.
table.forwardtable: table, forward: table|nilNo values.Sets MicroLua's table lookup forwarding target. This is the small substitute for metatable __index.
table.inserttable: table, value: any or table: table, pos: integer, value: anyNo values.Inserts at the end or at pos. Positions outside 1..#table+1 are errors because they would create holes.
table.maxntable: tableNumber.Returns the largest positive numeric key present in the table, or zero for non-tables/no numeric keys.
table.pack...: anyTable.Returns a table containing the arguments at integer keys and an n field with the argument count.
table.removetable: table [, pos: integer]Removed value, or nil.Removes and returns the entry at pos, defaulting to the last array entry, then shifts later entries down.
table.sorttable: table [, comp: function]No values.Sorts the array part in place. comp(a, b), when supplied, should return true when a comes before b.
table.unpacktable: table [, i: integer [, j: integer]]Values table[i] through table[j].Returns array entries from i through j. Negative or zero starts are clamped to one; inverted ranges return no values.

table.pack and table.unpack follow the Lua 5.2 naming. unpack is also available as a global compatibility alias.

coroutine

Function Arguments Returns Description
coroutine.closeco: coroutinetrue, or false, message.Closes a suspended or dead coroutine and drops its saved state. Closing a running coroutine is an error.
coroutine.createfunc: functionCoroutine.Creates a suspended coroutine that will run func.
coroutine.isyieldableNo arguments.Boolean.Returns true inside a coroutine when not crossing a C-call boundary.
coroutine.resumeco: coroutine, ...: anytrue, ... on success/yield; false, message on error.Starts or resumes co, passing the remaining arguments into the coroutine.
coroutine.runningNo arguments.co, false inside a coroutine; nil, true on the main thread.Reports the currently running coroutine and whether execution is on the main thread.
coroutine.statusco: coroutineString.Returns "suspended", "running", "normal", or "dead".
coroutine.yield...: anyResume arguments when the coroutine is resumed again.Suspends the current coroutine and transfers the arguments to its resumer.

Coroutines can yield from nested Lua calls. Yielding across a C-call boundary is an error.

Optional io and os

These are not part of the freestanding core. They are available in the REPL build through src/extensions/MLuaStdLib.c.

Function Arguments Returns Description
io.closefile: fileBoolean.Closes a file handle created by io.open.
io.linesfile: fileiterator, file, nil.Returns an iterator triple that reads lines from a file handle.
io.openpath: string [, mode: string]File handle, or nil, message.Opens a file with the supplied C fopen mode, defaulting to read mode.
io.read[format: string]Read value, or no value at EOF.Reads from standard input. Supported formats include line, *l, *L, *n, and *a.
io.write...: anyNo values.Writes values to standard output.
file:read[format: string]Read value, or no value at EOF.Reads from an opened file handle using the same formats as io.read.
file:write...: anyThe file handle.Writes values to the file and returns the handle for chaining.
file:closeself: fileBoolean.Closes the file handle.
file:linesself: fileiterator, file, nil.Returns an iterator triple over lines in the file.
os.clockNo arguments.Number.Returns CPU time in seconds according to the host C library.
os.date[format: string]String or table.Formats the current local time. "*t" returns a table with date/time fields.
os.exit[code: integer]Does not return.Terminates the host process with the integer exit code, or zero.
os.getenvname: stringString, or nil.Returns the value of an environment variable.
os.timeNo arguments.Number.Returns the current time as a host timestamp.
dofilepath: stringAll chunk return values.Loads and executes a file.
loadfilepath: stringCompiled function, or nil, message.Loads and compiles a file without executing it.

Limitations

Tests and Benchmarks

Run all tests in a debug build:

meson test -C builddir

Run the freestanding release suite, including the libc-free symbol guard:

meson test -C builddir-release

Run only interpreter, smoke, guard, CLI, or security suites:

meson test -C builddir --suite interpreter
meson test -C builddir --suite smoke
meson test -C builddir --suite bytecode
meson test -C builddir --suite guard
meson test -C builddir --suite cli
meson test -C builddir --suite security

Compare MicroLua against a local Lua 5.5 binary:

python3 bench/bench.py
python3 bench/bench.py --quick

The benchmark checks byte-identical output before reporting timing, native heap, and binary-size results. It skips cleanly when Lua 5.5 is not available.

The memory comparison uses small standalone ports of relevant Lua examples, scaled down for local runs and adjusted to avoid unsupported MicroLua features such as metatables and generated load. Each row passed a byte-identical stdout correctness gate.

python3 bench/benchmarksgame/bench.py
python3 bench/benchmarksgame/bench.py --mlua builddir-release/mlua --lua /path/to/lua5.5
Memory benchmarks
Workload Source example Memory-pressure focus Lua exact peak MicroLua exact peak mlua/lua memory
binarytrees binarytrees-lua-4 Allocation churn 1.19 MiB 546.1 KiB 0.45x
knucleotide knucleotide-lua-2 Substring table pressure 76.9 KiB 71.8 KiB 0.93x
revcomp revcomp-lua-5 String builder pressure 115.4 KiB 104.5 KiB 0.91x
spectralnorm spectralnorm-lua-1 Numeric arrays 25.9 KiB 25.6 KiB 0.99x

Lua memory is exact peak Lua heap from a tracking lua_newstate allocator. MicroLua memory is exact constrained-heap high-water from a high-limit --dump run.

Compatibility Reference

MicroLua intentionally selects a small subset and a few later additions from Lua. For background on standard Lua behavior, see the official manuals at lua.org/manual. This page documents only the features implemented by MicroLua.

License

BSD Zero Clause License

Copyright (c) 2026 Louka Ménard Blondin <hello@louka.sh>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.