Skip to main content

Lua Targets

One Nebra codebase can compile for five different Lua runtimes. You pick the target once in nebra.toml and the compiler adapts its output, emitting native syntax where the runtime has it and a polyfill where it does not.

nebra.toml
target = "5.4" # 5.1, 5.2, 5.3, 5.4, jit

If you leave target out, the compiler defaults to 5.4.

Feature matrix

This is what each runtime provides on its own. Anything marked "polyfilled" still works in your Nebra source, it just compiles to different Lua.

FeatureLua 5.1Lua 5.2Lua 5.3Lua 5.4LuaJIT
goto and labelspolyfillednativenativenativenative
Floor division //polyfilledpolyfillednativenativepolyfilled
Bitwise operatorspolyfilledpolyfillednativenativebit library
Integer subtypenonoyesyesno
<const> localspolyfilledpolyfilledpolyfillednativepolyfilled
<close> localspolyfilledpolyfilledpolyfillednativepolyfilled
table.unpackunpacknativenativenativeunpack

continue does not exist in any Lua version. Nebra always lowers it, using goto on runtimes that have it and a restructured loop on Lua 5.1.

What polyfilling looks like

The compiler never asks you to write different code per target. It rewrites at codegen time.

Floor division on a runtime without //:

source
local half = total // 2
target 5.1
local half = math.floor(total / 2)

Bitwise operations on LuaJIT, which has the bit library rather than operators:

source
local masked = flags & 0xFF
target jit
local masked = bit.band(flags, 0xFF)

continue on Lua 5.1, which has neither continue nor goto:

source
for _, v in ipairs(items) do
if v == nil then continue end
process(v)
end
target 5.1
for _, v in ipairs(items) do
repeat
if v == nil then break end
process(v)
until true
end

Choosing a target

5.4 is the right default for new standalone projects. It has the most native syntax, so the output is the closest to what you wrote.

jit is what you want for performance-sensitive work or anything embedding LuaJIT. Note that LuaJIT tracks Lua 5.1 semantics with extensions, so it has goto and bitwise operations but no floor division operator and no integer subtype.

5.1 is the safest choice for maximum compatibility. Many embedded hosts, game engines and older frameworks are still on 5.1 or on a 5.1-compatible fork. Everything in Nebra works here, it just produces slightly more verbose output for the newer operators.

5.2 and 5.3 are for hosts that pin those specific versions.

If you are compiling a library that other people will consume on unknown runtimes, target 5.1. The output runs everywhere, including on 5.4.

Integer semantics

Lua 5.3 and 5.4 distinguish integers from floats. Lua 5.1, 5.2 and LuaJIT do not: every number is a double.

Nebra does not model this distinction in its type system. number is a single type covering both, and the runtime decides. This is deliberate, because it keeps the same source compiling for every target. If your program depends on integer overflow behaviour or on math.type, it is only portable across the runtimes that agree on it.

Verifying the output

The compiler will not stop you from reading what it produced, and you should:

nebra build
cat out/main.lua

To check a target actually runs on the real interpreter rather than the embedded one, compile and then invoke that interpreter directly:

nebra build
lua5.1 out/main.lua
luajit out/main.lua