Skip to content
/ ion Public

Statically typed language built for Roblox that compiles to Luau

R-unic/ion

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ion

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.

Contributing

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.

When contributing code

  • Use a commit convention when writing commit names
  • Add only one feature per PR, bloated PRs are a lot harder to review and accept.

Roadmap

  • Unit testing
  • Diagnostics
    • Errors
    • Warnings
    • Colors
    • Line info/code view
  • Lexer
  • Parser
    • Skip semicolons
    • Warn when if a = b or while a = b is detected
    • Warn if any statements come after a return statement
    • 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
      • clone clause
    • 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 await outside of async context
    • Report use of Name and Parent properties in instance constructors
    • Warn shadowed variable declaration
  • Intrinsics
    • print symbol
      • Type pack arguments (e.g. print<T...>(T...): void)
    • 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)

Examples

Events

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_change

This 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 construction

instance my_part: Part {
  "MyPart"
  Size: Vector3.one
  Position: <(0, 10, 0)>
  Color: rgb<(255, 0, 0)>
  
  #Lava
  @LavaKind: "very very hot"
} -> game.Workspace
local 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.Workspace
local zombie_model = ReplicatedStorage.ZombieModel:Clone()
my_part.Parent = game.Workspace

Shorthand attributes

let const health = zombie_model@Health
print(health)
zombie_model@Health -= 10
local health = zombie_model:GetAttribute("Health")
print(health)
zombie_model:SetAttribute("Health", zombie_model:GetAttribute("Health") - 10)

Enums

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)

Imports/exports

export let const x = 69;
local x = 69

return {
  x = x
}

Improved for loops

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)
end

Extended number literals

let 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.5

after statements

after 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

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)

match statements

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)
end

match expressions

enum 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
end

nameof expression

nameof 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")

Destructuring

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_tuple
local 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]

Decorators

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..."

About

Statically typed language built for Roblox that compiles to Luau

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Contributors 2

  •  
  •