Statically typed language built for Roblox that compiles to Luau.
Important
This language does not have a functional prototype yet. Check out the roadmap to see current progress. Check out examples to see some previews.
Before contributing please read up on how programming language front-ends work. Crafting interpreters is a good start. Try to adhere to the project's code style as much as possible, and follow general principles of writing clean and maintainable code. Refactoring guru is a good place to learn principles of writing clean code.
For any bugs please create an issue, describe it descriptively, and include how to reproduce it.
- Use a commit convention when writing commit names
- Add only one feature per PR, bloated PRs are a lot harder to review and accept.
- Unit testing
- Diagnostics
- Errors
- Warnings
- Colors
- Line info/code view
- Lexer
- Parser
- Skip semicolons
- Warn when
if a = borwhile a = bis detected - Warn if any statements come after a
returnstatement - Primitive literals
- Extended number literals (see examples)
- Array literals
- Object literals
- Tuple literals
- Range literals (see examples)
- Vector literals (see examples)
- Color literals (see examples)
- RGB
- HSV
- Identifiers
- Binary, unary, postfix unary, ternary, & assignment operations
- Null coalescing
- Optional member access
- Parenthesized expressions
- Member access & element access
- Invocation
- Variable declarations
- Event declarations
- Function declarations
- Instance constructors (see examples)
- Name declarator
- Tag declarator
- Attribute declarator
-
cloneclause
- Enum declarations
- Interface declarations
- Basic type parameters
- Defaults
- If statements
- For loops (see examples)
- While loops
- Repeat loops
- Match statements(see examples)
- Match expressions (see examples)
- Imports & exports (see examples)
- After statements (see examples)
- Every statements (see examples)
- While condition
- Destructuring (see examples)
- Async/await
- Breaks/continues
- Returns
- Blocks
- Primitive types, type names, & nullable types
- Union & intersection types
- Literal types
- Array types
- Tuple types
- Function types
- Type parameters
- Type arguments for calls and type names
- Type alias declarations
-
typeof -
nameof(see examples)- On member access
- String interpolation
- Shorthand attributes (see examples)
- Constant variables and fields
- Decorators (see examples)
- Type packs
- Interface computed properties (e.g.
[69]: true) - Namespaced types
- Resolver
- Report duplicate variables
- Report variables not found
- Report variables read in their own initializers
- Report break/continue outside of loops and return outside of functions
- Report duplicate interface/object/instance members
- Report
awaitoutside of async context - Report use of
NameandParentproperties in instance constructors - Warn shadowed variable declaration
- Intrinsics
-
printsymbol- Type pack arguments (e.g.
print<T...>(T...): void)
- Type pack arguments (e.g.
- Roblox data type symbols (
Vector3,Color3, etc.)
-
- Symbol binding
- Declaration symbols
- Named symbols inherited from declarations
- Type symbols
- Generic symbols
- Type solving
- Primitive literals
- Internal literals (array, tuple, range)
- Array
- Tuple
- Range
- Interfaces
- Functions
- Events
- Generics
- Type
- Function
- Event
- Roblox literals (rgb, hsv, vector)
- Identifiers
- Variable declarations
- Type checking
- Luau transpilation (Ion AST -> Luau AST)
- Luau rendering (Luau AST -> Luau source)
event data_changed<T>(T)
fn on_data_change<T>(new_data: T): void {
print("New data: %{new_data}")
}
data_changed += on_data_change
let data = 420
data_changed!(data)
data_changed -= on_data_changeThis is equivalent to the following in Luau:
local data_changed = Signal.new()
function on_data_change<T>(new_data: T): ()
print(`New data: {new_data}`)
end
local on_data_change_conn = data_changed:Connect(on_data_change)
local data = 420
data_changed:Fire(data)
on_data_change_conn:Disconnect()instance my_part: Part {
"MyPart"
Size: Vector3.one
Position: <(0, 10, 0)>
Color: rgb<(255, 0, 0)>
#Lava
@LavaKind: "very very hot"
} -> game.Workspacelocal my_part = Instance.new("Part")
my_part.Name = "MyPart"
my_part.Size = Vector3.one
my_part.Position = Vector3.new(0, 10, 0)
my_part.Color = Color3.fromRGB(255, 0, 0)
my_part:SetAttribute("LavaKind", "very very hot")
my_part.Parent = game.Workspace
my_part:AddTag("Lava")You can also clone instances using this syntax.
instance zombie_model: Model clone ReplicatedStorage.ZombieModel -> game.Workspacelocal zombie_model = ReplicatedStorage.ZombieModel:Clone()
my_part.Parent = game.Workspacelet const health = zombie_model@Health
print(health)
zombie_model@Health -= 10local health = zombie_model:GetAttribute("Health")
print(health)
zombie_model:SetAttribute("Health", zombie_model:GetAttribute("Health") - 10)enum Abc {
A
B
C
D = 69
E
}
print(Abc::A, Abc::B, Abc::C, Abc::D, Abc::E)print(0, 1, 2, 69, 70)export let const x = 69;local x = 69
return {
x = x
}For loops can be used in combination with range literals, and when used with range literals they emit a Luau for-i loop.
for i : 1..10
print(i)for i = 1, 10 do
print(i)
endlet const time = 10s
let const cooldown = 50ms
let const hour = 1h
let const update_rate = 20hz
let const transparency = 50%local time = 10
local cooldown = 0.05
local hour = 3600
local update_rate = 0.333333333
local transparency = 0.5after statements are a direct syntactic equivalent to task.delay()
after 100ms
print("delayed result")task.delay(0.1, function()
print("delayed result")
end)every statements are a direct syntactic equivalent to an async loop that waits in every iteration
every 1s
print("one second passed");task.spawn(function()
while true do
print("one second passed")
task.wait(1)
end
end)Using a conditional every statement:
let elapsed = 0;
every 1s while elapsed < 10 {
print("Elapsed time:", elapsed++);
}task.spawn(function()
while elapsed < 10 do
local _original = elapsed
elapsed += 1
print("Elapsed time:", _original)
task.wait(1)
end
end)enum Abc {
A
B
C
}
let abc = Abc.A;
match abc {
Abc.A -> print("got a"),
Abc.B -> print("got b"),
Abc.C -> print("got c"),
value -> print("wtf is this:", value)
}local abc = 0
if abc == 0 then
print("got a")
else if abc == 1 then
print("got b")
else if abc == 2 then
print("got c")
else
local value = abc
print("wtf is this:", value)
endenum Abc {
A
B
C
}
let abc = Abc.A;
let message = abc match {
Abc.A -> "got a",
Abc.B -> "got b",
Abc.C -> "got c",
value -> "wtf is this: " + value
}local abc = 0
local message
if abc == 0 then
message = "got a"
else if abc == 1 then
message = "got b"
else if abc == 2 then
message = "got c"
else
local value = abc
message = "wtf is this: " .. value
endnameof returns the text of the token it is given.
let abc = 69
fn foo -> null
interface FooBar {}
enum Abc {}
print(nameof(abc))
print(nameof(foo))
print(nameof(FooBar))
print(nameof(Abc))local abc = 69
local function foo()
return nil
end
print("abc")
print("foo")
print("FooBar")
print("Abc")On member access expressions
enum Abc { A B C }
print(nameof(Abc))
print(nameof(Abc::A))
print(nameof(Abc::B))
print(nameof(Abc::C))print("Abc")
print("A")
print("B")
print("C")Syntactic sugar for assigning variable names to properties/indexes of objects/arrays
let const {my_field} = my_obj
let const [one, two] = my_arr
let const (one, two) = my_tuplelocal my_field = my_obj.my_field
local one = my_arr[1]
local two = my_arr[2]
local one = my_tuple[1]
local two = my_tuple[2]Syntactic sugar for wrapping functions with other functions
fn log(name: string): void
-> print("Called #{name}")
@log
fn do_something {
print("Doing something...")
}
do_something()local function log(name, f)
return function(...)
print(`Called {name}`)
f(...)
end
end
local function do_something()
print("Doing something...")
end
do_something = log("do_something", do_something)
do_something() -- prints "Called do_something", then "Doing something..."