readability
Presets and switches change how the code reads, never what it does. Some of them cost something you might need, like matching line numbers.
which preset
Use balanced if you don't want to think about it.
Use faithful if you want source that compiles to the same bytes, or stack traces that point into the right function. Just know it can be quite verbose.
Use readable if you want the cleanest result. It turns every switch on, so names, types and line numbers can differ from the original.
The site, the executor and the Discord bot start on balanced. The API and the MCP server start on faithful.
presets
A preset sets every switch at once.
| faithfulexact bytes | balancedeasy to read | readableeverything on | |
|---|---|---|---|
| Readable layout | off | on | on |
| Function line numbers | off | on | on |
| Inferred types | off | on | on |
| Fold constants | off | off | on |
| Model names | off | on | on |
| Drop dead code | off | off | on |
| Upvalue notes | off | on | on |
| Upvalue notes on own line | off | on | on |
| Compound assignment | off | on | on |
| Positive conditions | off | on | on |
| Early returns | off | on | on |
| Paragraph breaks | off | on | on |
| Inline small calls | off | off | on |
| Roll up loops | off | on | on |
| Math constants | off | off | on |
| Const locals | off | on | on |
| Field functions | off | on | on |
| Mutable globals | off | on | on |
Want readable without inferred types? Pick readable and turn that one switch off. On the site, it's under options. Over the API, send {"preset": "readable", "inferredTypes": false}.
what a switch costs
| cost | what changes |
|---|---|
| bytes | The source no longer compiles to the same bytecode. |
| lines | Code moves off its original lines, so line numbers in errors no longer match. |
| names | Values get names the file didn't have, or lose names it did. |
| types | Type annotations appear that the file didn't have. |
| frames | A call to a small helper becomes the helper's body, so stack traces have one frame fewer. |
| version | The source uses syntax only newer Luau understands, like const, so older tools reject it. |
The API lists what a result's switches cost in setting.spent. faithful costs nothing.
faithful layout
Faithful keeps every function on its original line, so you'll see runs of blank lines. Turn on Readable layout (freeLines) to get rid of them.
switches
Every switch is off in faithful. Each example shows the same bytecode decompiled without the switch, then with it.
freeLineson in balancedreadableLays functions out as reads best, not padded or packed onto their original lines. costs: bytes, lines
without
print(1) print(2) function done() print(3) end done()
with
print(1) print(2) function done() print(3) end done()
lineNoteson in balancedreadableUnder the readable layout, ends each function's first line in the line it was defined on.
without
print(1) print(2) function done() print(3) end done()
with
print(1) print(2) function done() -- line 2 print(3) end done()
inferredTypeson in balancedreadableAdds the type annotations inference concludes, beside the input's own. costs: bytes, types
without
local function label(n) return "item " .. tostring(n) end return label(3)
with
local function label(n: number): string return "item " .. tostring(n) end return label(3)
foldConstantson in readableWrites a computation on literals as its value: 7 for math.max(4, 7). costs: bytes
without
local width = math.max(4, 7) print(width)
with
local width = 7 print(width)
modelNameson in balancedreadableNames the values the input left unnamed, never with a name that could change the program. costs: names
without
local function v0(arg0)
local v1 = 0
for i = 1, #arg0 do
v1 = v1 + arg0[i]
end
return v1
end
return v0({ 1, 2, 3 })
with
local function sum(values)
local sum1 = 0
for i = 1, #values do
sum1 = sum1 + values[i]
end
return sum1
end
return sum({ 1, 2, 3 })
dropDeadCodeon in readableLeaves out values nothing reads and branches that never run. costs: bytes, names
without
local unused = 21
print("ready")
with
print("ready")
upvalueNoteson in balancedreadableSays on each function's first line which outer variables it captures, and whether it sees their later changes.
without
local function counter(k) local n = 0 return function() n += k return n end end print(counter(2)())
with
local function counter(k) local n = 0 return function() -- upvalues: n (ref), k (copy) n += k return n end end print(counter(2)())
upvalueNotesOwnLineon in balancedreadablePuts the upvalue note on a line of its own under the function's first line. costs: lines
without
local function counter(k) local n = 0 return function() -- upvalues: n (ref), k (copy) n += k return n end end print(counter(2)())
with
local function counter(k) local n = 0 return function() -- upvalues: n (ref), k (copy) n += k return n end end print(counter(2)())
compoundEverywhereon in balancedreadableWrites x op= e wherever it means the same thing. costs: bytes
without
local t = { k = 1 }
t.k = t.k + 2
print(t.k)
with
local t = { k = 1 }
t.k += 2
print(t.k)
positiveBrancheson in balancedreadableTests an if without not, and puts a short exit arm first. costs: bytes, lines
without
local function check(ok)
if not ok then
print("no")
else
print("yes")
end
end
check(true)
with
local function check(ok)
if ok then
print("yes")
else
print("no")
end
end
check(true)
earlyExitson in balancedreadableA function that ends in one big if returns early instead. costs: bytes
without
local function save(s)
if s then
print(s)
print(#s)
end
end
save("x")
with
local function save(s)
if not s then return end
print(s)
print(#s)
end
save("x")
paragraphBreakson in balancedreadableAdds a blank line between the steps of the code. costs: bytes, lines
without
local squares = {}
for i = 1, 3 do
squares[i] = i * i
end
return squares
with
local squares = {}
for i = 1, 3 do
squares[i] = i * i
end
return squares
inlineCallson in readableWrites a call of a small local helper as the helper's body. costs: bytes, names, frames
without
local function key(name, n)
return name .. "_" .. n
end
local k = key("hp", 3)
print(k)
with
local k = "hp" .. "_" .. 3 print(k)
rerollLoopson in balancedreadableWrites lines repeated with 1, 2, 3 in them as the for loop that runs them. costs: bytes
without
local t = { 5, 6, 7 }
print(t[1])
print(t[2])
print(t[3])
with
local t = { 5, 6, 7 }
for i = 1, 3 do
print(t[i])
end
unfoldMathon in readableWrites a number the math library computes as that expression: math.pi / 2 for 1.5707963267948966. costs: bytes
without
local quarter = 1.5707963267948966 print(math.sin(quarter))
with
local quarter = math.pi / 2 print(math.sin(quarter))
constLocalson in balancedreadableDeclares a local nothing assigns again as const. costs: version
without
local limit = tonumber("8")
local count = 0
count = count + (limit :: any)
print(count)
with
const limit = tonumber("8")
local count = 0
count = count + (limit :: any)
print(count)
fieldFunctionson in balancedreadableWrites a table of keyed functions as local t = {} and a function t.k() per function. costs: bytes, names
without
return { ["clamp"] = function(arg0)
return math.max(0, math.min(arg0, 1))
end,
["twice"] = function(arg0)
return arg0 * 2
end }
with
local module = {}
function module.clamp(arg0)
return math.max(0, math.min(arg0, 1))
end
function module.twice(arg0)
return arg0 * 2
end
return module
mutableGlobalson in balancedreadableWrites game.Players for (game).Players, as scripts compiled with those globals mutable read them. costs: bytes
without
local camera = (workspace).CurrentCamera print(camera)
with
local camera = workspace.CurrentCamera print(camera)