TLC AST Node Specification
May 17, 2026 ยท View on GitHub
The parser produces an Abstract Syntax Tree (AST) made of nested Lua tables. Each node has a kind field that identifies its type. This document specifies every node type TLC can produce.
Program Structure
Program
The root node of every AST.
{ kind = "Program", body = <Block> }
Block
A sequence of statements. Every scope boundary (function body, if-clause, loop body) produces a Block.
{ kind = "Block", statements = <list_of_statements> }
Literals and Identifiers
NumericLiteral
A number constant. value is the numeric value, raw is the original source text.
{ kind = "NumericLiteral", value = <number>, raw = <string> }
-- Example: 42, 3.14, 0xFF, 1e10
StringLiteral
A string constant (single-quoted, double-quoted, or long string).
{ kind = "StringLiteral", value = <string> }
-- Example: "hello", 'world', [[long string]]
BooleanLiteral
{ kind = "BooleanLiteral", value = <boolean> }
-- value is true or false
NilLiteral
{ kind = "NilLiteral" }
Identifier
A variable or function name reference.
{ kind = "Identifier", value = <string> }
-- Example: x, myVar, _G
VarargExpression
The ... expression inside a vararg function.
{ kind = "VarargExpression" }
Expressions
FunctionExpression
An anonymous function definition. Also used as the body field in LocalFunctionDeclaration and as the value in desugared function declaration assignments.
{
kind = "FunctionExpression",
body = <Block>,
parameters = <list_of_strings>,
isVararg = <boolean>
}
UnaryOperator
A unary operation: -x, not x, or #x.
{
kind = "UnaryOperator",
operator = <string>, -- "-", "not", "#"
operand = <node>
}
BinaryOperator
A binary operation.
{
kind = "BinaryOperator",
operator = <string>, -- "+", "-", "*", "/", "%", "^", "..",
-- "==", "~=", "<", ">", "<=", ">=",
-- "and", "or"
left = <node>,
right = <node>
}
FunctionCall
A function or method call.
{
kind = "FunctionCall",
callee = <node>,
arguments = <list_of_nodes>,
isMethodCall = <boolean>? -- nil by default, true for obj:method() syntax
}
When isMethodCall is true, callee is an IndexExpression where the method name is the index, and the object is implicitly passed as the first argument by the VM.
IndexExpression
Table field access via table.key or table[expr].
{
kind = "IndexExpression",
base = <node>, -- the table
index = <node>, -- the key
}
For dot syntax (t.key), index is a StringLiteral node. For bracket syntax (t[expr]), index is an arbitrary expression.
TableConstructor
A table literal { ... }.
{
kind = "TableConstructor",
elements = <list_of_TableElement>
}
TableElement
A single entry in a table constructor.
{
kind = "TableElement",
key = <node>,
value = <node>,
isImplicit = <boolean> -- true for positional entries like {1, 2, 3}
}
For implicit keys ({expr1, expr2}), key is a NumericLiteral with sequential indices starting at 1.
ParenthesizedExpression
An expression wrapped in parentheses. This is not purely cosmetic -- in Lua, parentheses force a multi-return expression to adjust to a single value. (f()) returns one value, f() may return many.
{
kind = "ParenthesizedExpression",
expression = <node>
}
Statements
LocalDeclarationStatement
local var1, var2 = expr1, expr2
{
kind = "LocalDeclarationStatement",
variables = <list_of_strings>,
initializers = <list_of_nodes> -- may be empty
}
LocalFunctionDeclaration
local function name(...) ... end
{
kind = "LocalFunctionDeclaration",
name = <string>,
body = <FunctionExpression>
}
AssignmentStatement
lvalue1, lvalue2 = expr1, expr2
Each lvalue is either an Identifier or IndexExpression node.
{
kind = "AssignmentStatement",
lvalues = <list_of_nodes>,
expressions = <list_of_nodes>
}
CallStatement
A function call used as a statement (the return value is discarded).
{
kind = "CallStatement",
expression = <FunctionCall>
}
IfStatement
{
kind = "IfStatement",
clauses = <list_of_IfClauses>,
elseClause = <Block> or nil
}
IfClause
A single if or elseif branch.
{
kind = "IfClause",
condition = <node>,
body = <Block>
}
WhileStatement
while condition do ... end
{
kind = "WhileStatement",
condition = <node>,
body = <Block>
}
RepeatStatement
repeat ... until condition
{
kind = "RepeatStatement",
body = <Block>,
condition = <node>
}
Note: In Lua, locals declared inside the repeat block are visible in the until condition. The parser handles this scoping correctly.
ForNumericStatement
for var = start, limit[, step] do ... end
{
kind = "ForNumericStatement",
variable = <string>,
start = <node>,
limit = <node>,
step = <node> or nil, -- nil means implicit step of 1
body = <Block>
}
ForGenericStatement
for iter1, iter2 in expr1, expr2 do ... end
{
kind = "ForGenericStatement",
iterators = <list_of_strings>,
expressions = <list_of_nodes>,
body = <Block>
}
DoStatement
do ... end
{
kind = "DoStatement",
body = <Block>
}
ReturnStatement
return expr1, expr2, ...
{
kind = "ReturnStatement",
expressions = <list_of_nodes> -- may be empty
}
BreakStatement
{ kind = "BreakStatement" }
Example
Given the source code:
local x = 1 + 2
print(x)
TLC produces:
{
kind = "Program",
body = {
kind = "Block",
statements = {
{
kind = "LocalDeclarationStatement",
variables = {"x"},
initializers = {
{
kind = "BinaryOperator",
operator = "+",
left = {kind = "NumericLiteral", value = 1, raw = "1"},
right = {kind = "NumericLiteral", value = 2, raw = "2"}
}
}
},
{
kind = "CallStatement",
expression = {
kind = "FunctionCall",
callee = {kind = "Identifier", value = "print"},
arguments = {
{kind = "Identifier", value = "x"}
}
}
}
}
}
}