Why Nebra
Lua is small, fast and embeddable, but large programs in it get hard to hold together. Nebra adds the structure without taking you off Lua.
Types are optional
Every valid Lua program is already a valid Nebra program. Add annotations where they pay off and leave the rest untyped. Nothing about the type system reaches the generated Lua.
Zero runtime overhead
Classes, generics, interfaces, pattern matching and async/await are all lowered at compile time. No runtime library is shipped and no metatable magic runs behind your back.
Targets 5.1 through 5.4 and LuaJIT
Pick your target in nebra.toml and the compiler emits idiomatic Lua for it, polyfilling newer operators such as floor division and bitwise ops on older runtimes.
A real module system
ES-style import and export lower to plain require calls. Cross-file types resolve, unused imports get stripped, and circular top-level imports are reported at compile time.
Nil safety when you want it
Optional chaining, nil coalescing, flow narrowing and a never type that marks diverging calls. Turn on strict-nil mode to make unchecked nil access a compile error.
One binary, whole toolchain
Compiler, REPL, test runner, package manager, docs generator, native bundler and language server all ship in a single self-contained executable.
What the compiler does
Types, classes and default parameters exist only while compiling. What lands on disk is Lua you would have been happy to write by hand. This is real compiler output, with [reflection] mode = "none"set so the optional metadata block is left out.
--- A counter that never leaves its range.
class Counter
count: number = 0
constructor(start: number = 0)
self.count = start
end
function bump(by: number = 1): number
self.count = clamp(self.count + by, 0, 100)
return self.count
end
end
function clamp(v: number, lo: number, hi: number): number
if v < lo then return lo end
if v > hi then return hi end
return v
end
local c = new Counter(5)
print(c:bump()) -- 6
print(c:bump(10)) -- 16
local Counter = {}
Counter.__index = Counter
Counter.__name = "Counter"
function Counter.new(start)
local self = setmetatable({}, Counter)
self.count = 0
self.count = start
return self
end
function Counter:bump(by)
if by == nil then by = 1 end
self.count = clamp(self.count + by, 0, 100)
return self.count
end
function clamp(v, lo, hi)
if v < lo then
return lo
end
if v > hi then
return hi
end
return v
end
local c = Counter.new(5)
print(c:bump())
print(c:bump(10))
Install in one line
The installer detects your platform, downloads the matching release archive and puts nebra on your PATH. No admin rights and no .NET or Lua installation required on the target machine.
- Linux / macOS
- fish
- Windows
curl -fsSL https://raw.githubusercontent.com/nebra-lang/nebra/master/scripts/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/nebra-lang/nebra/master/scripts/install.fish | fish
irm https://raw.githubusercontent.com/nebra-lang/nebra/master/scripts/install.ps1 | iex
Then open a new shell and run nebra version. See the installation guide for manual downloads, pinning a version and building from source.
Editor support
The language server ships inside the same binary. The VS Code extension launches it for you and gives you diagnostics, hover types, completion, go to definition, rename, signature help and semantic highlighting.
