Getting started
Introduction
MoonScript is a dynamic scripting language that compiles to Lua. It adds significant syntax improvements including classes, list comprehensions, arrow functions, and more.
-- MoonScript
print "Hello, World!"
x = 10
y = x + 5
Installation
# Install via LuaRocks
luarocks install moonscript
# Verify installation
moon -v
moonc -v
Basic compilation
# Compile to Lua
moonc input.moon
# Run directly
moon script.moon
# Watch and auto-compile
moonc -w .
# Compile to specific directory
moonc -t output/ input.moon
Quick example
-- MoonScript
class Thing
name: "unknown"
thing = Thing!
thing.name = "MoonScript"
print thing.name
Compiles to:
-- Lua
local Thing
do
local _class_0
local _base_0 = {
name = "unknown"
}
_class_0 = setmetatable({
__init = function(self) end,
__base = _base_0
}, { __call = function(cls, ...) ... end })
Thing = _class_0
end
local thing = Thing()
thing.name = "MoonScript"
print(thing.name)
Basic syntax
Variables
x = 10 -- Local by default
y = x + 5
name = "MoonScript"
export global_var = 42 -- Explicit global
-- Compiles to Lua
local x = 10
local y = x + 5
local name = "MoonScript"
global_var = 42
Function calls
print "Hello" -- Parentheses optional
print "Hello", "World" -- Multiple args
my_func 5, 10 -- Call with args
func(x).method! -- Chained calls
String interpolation
name = "World"
msg = "Hello, #{name}!" -- String interpolation
x = 10
y = 20
print "#{x} + #{y} = #{x + y}" -- Expressions
-- Compiles to
local name = "World"
local msg = "Hello, " .. name .. "!"
local x, y = 10, 20
print(x .. " + " .. y .. " = " .. (x + y))
Comments
-- Single line comment
--[[
Multi-line
comment block
]]
Line decorators
-- Implicit returns
func = -> 42 -- Returns 42
-- @ is self
method = => @value -- self.value
-- with blocks auto-return
with obj
.x = 10 -- obj.x = 10
Functions
Function definitions
-- Named functions
square = (x) -> x * x
-- Multiple parameters
add = (a, b) -> a + b
-- No parameters
greet = -> print "Hello"
-- Default arguments
multiply = (x, y=2) -> x * y
-- Variadic functions
sum = (...) ->
total = 0
for v in *{...}
total += v
total
Arrow functions
-- Fat arrow (=>) binds self
class Counter
value: 0
increment: => -- Method (has self)
@value += 1
get: => @value -- Access via @
-- Thin arrow (->) no self binding
transform = (x) -> x * 2
Function calls
-- Parentheses optional
print "Hello"
func 1, 2, 3
-- With parentheses
func(x, y)
-- Chained calls
obj\method(arg)\another! -- ! means no args
Implicit returns
-- Last expression is returned
double = (x) -> x * 2 -- Returns x * 2
-- Multiple statements
compute = (x) ->
temp = x * 2
temp + 10 -- Returned
-- Explicit return
early_exit = (x) ->
return nil if x < 0
x * 2
Tables and arrays
Table literals
-- Array-style
items = {1, 2, 3, 4}
-- Hash-style
person = {
name: "Alice"
age: 30
city: "NYC"
}
-- Mixed
config = {
"option1"
"option2"
debug: true
level: 5
}
Table access
-- Dot notation
person.name -- "Alice"
-- Bracket notation
person["age"] -- 30
-- Method calls
str\upper() -- string:upper()
obj\method arg -- obj:method(arg)
Table comprehensions
-- Array comprehension
squares = [x * x for x in *{1,2,3,4,5}]
-- Result: {1, 4, 9, 16, 25}
-- With condition
evens = [x for x in *{1,2,3,4,5,6} when x % 2 == 0]
-- Result: {2, 4, 6}
-- Hash comprehension
doubles = {x, x*2 for x in *{1,2,3}}
-- Result: {1=2, 2=4, 3=6}
Slicing
items = {1, 2, 3, 4, 5}
-- Slice notation
items[2,4] -- {2, 3, 4}
items[2,] -- {2, 3, 4, 5}
items[,3] -- {1, 2, 3}
Classes and OOP
Class definition
class Animal
new: (@name, @age) => -- Constructor
@alive = true
speak: => -- Method
print "#{@name} makes a sound"
describe: =>
print "#{@name} is #{@age} years old"
-- Compiles to metatable-based OOP
local Animal
do
local _class_0
local _base_0 = {
speak = function(self)
return print(self.name .. " makes a sound")
end,
describe = function(self)
return print(self.name .. " is " .. self.age .. " years old")
end
}
_base_0.__index = _base_0
_class_0 = setmetatable({
__init = function(self, name, age)
self.name, self.age = name, age
self.alive = true
end,
__base = _base_0,
__name = "Animal"
}, {
__index = _base_0,
__call = function(cls, ...)
local _self_0 = setmetatable({}, _base_0)
cls.__init(_self_0, ...)
return _self_0
end
})
Animal = _class_0
end
Creating instances
-- Call class as function
cat = Animal "Whiskers", 3
cat\speak() -- Call method
-- Or with explicit new
dog = Animal "Buddy", 5
print dog.name -- "Buddy"
Inheritance
class Dog extends Animal
new: (name, age, @breed) =>
super name, age -- Call parent constructor
speak: => -- Override method
print "#{@name} barks!"
fetch: => -- New method
print "#{@name} fetches the ball"
-- Usage
dog = Dog "Buddy", 5, "Golden Retriever"
dog\speak() -- "Buddy barks!"
dog\fetch() -- "Buddy fetches the ball"
Class properties
class Counter
count: 0 -- Class variable (shared)
increment: =>
@@count += 1 -- @@ accesses class
@reset: => -- Class method
@@count = 0
-- Usage
c1 = Counter!
c2 = Counter!
c1\increment()
print Counter.count -- 1 (shared state)
Super calls
class Parent
value: 10
get_value: => @value
class Child extends Parent
value: 20
get_value: =>
parent_val = super! -- Call parent method
parent_val + @value
c = Child!
print c\get_value() -- 30
Control flow
Conditionals
-- If statement
if condition
print "true"
elseif other_condition
print "also true"
else
print "false"
-- Postfix if
print "positive" if x > 0
-- Unless (inverted if)
unless x == 0
print "not zero"
-- Postfix unless
return unless valid -- Return if not valid
Ternary operator
-- Condition and/or chains
result = condition and "yes" or "no"
-- If-else expression
value = if x > 0 then "positive" else "negative"
Switch statements
-- Switch with cases
name = switch value
when "a" then "alpha"
when "b" then "beta"
when "c" then "gamma"
else "unknown"
-- Switch with expressions
result = switch
when x > 0 then "positive"
when x < 0 then "negative"
else "zero"
For loops
-- Numeric for
for i = 1, 10
print i
-- With step
for i = 1, 10, 2
print i -- 1, 3, 5, 7, 9
-- Iterate array
items = {1, 2, 3, 4, 5}
for item in *items
print item
-- Iterate with index
for i, item in ipairs items
print i, item
While loops
-- While loop
i = 0
while i < 10
print i
i += 1
-- Until loop (inverted while)
i = 0
until i >= 10
print i
i += 1
Comprehensions
List comprehensions
-- Basic comprehension
squares = [x * x for x in *{1,2,3,4,5}]
-- Result: {1, 4, 9, 16, 25}
-- With filter (when)
evens = [x for x in *{1,2,3,4,5,6} when x % 2 == 0]
-- Result: {2, 4, 6}
-- Multiple conditions
filtered = [x for x in *items when x > 5 when x < 15]
-- Nested iteration
pairs = [
{x, y}
for x in *{1,2,3}
for y in *{4,5,6}
]
Table comprehensions
-- Create hash table
doubles = {x, x*2 for x in *{1,2,3,4,5}}
-- Result: {1=2, 2=4, 3=6, 4=8, 5=10}
-- From pairs
dict = {name, value for name, value in pairs(original)}
-- With transformation
squared = {k, v*v for k, v in pairs(numbers)}
For expressions
-- Collect results
results = for i = 1, 10
i * i
-- With filter
positive = for x in *numbers
continue if x <= 0 -- Skip negatives
x
-- Transform and filter
names = for person in *people when person.age >= 18
person.name
Accumulator comprehensions
-- Sum with for
total = 0
for x in *{1,2,3,4,5}
total += x
-- Can be written as:
total = do
sum = 0
for x in *{1,2,3,4,5}
sum += x
sum
Special syntax
With statement
-- Implicit self calls
with obj
.x = 10 -- obj.x = 10
.y = 20 -- obj.y = 20
\method! -- obj:method()
-- Returns the object
result = with {}
.name = "Alice"
.age = 30
-- result is {name: "Alice", age: 30}
Do blocks
-- Create scope
x = do
temp = 10
temp * 2
-- x = 20, temp is not accessible
-- Useful for complex initialization
config = do
base = load_defaults()
apply_overrides base
base
Destructuring
-- Array destructuring
{a, b, c} = {1, 2, 3} -- a=1, b=2, c=3
-- Object destructuring
{:name, :age} = person -- name=person.name, age=person.age
-- Function parameters
func = ({:x, :y}) ->
print x, y
func {x: 10, y: 20} -- Prints: 10 20
-- Nested destructuring
{player: {:name, :score}} = game_state
Operators
-- Arithmetic
x += 1 -- Increment
y -= 2 -- Decrement
z *= 3 -- Multiply assign
w /= 4 -- Divide assign
-- Logical
result = a and b -- Logical and
result = a or b -- Logical or
result = not a -- Logical not
-- Comparison
x == y -- Equality
x != y -- Inequality (also ~=)
x < y, x <= y -- Less than
x > y, x >= y -- Greater than
Exports and imports
-- Export variables
export my_func = -> print "Hello"
export {something, another}
-- Import modules
import "module"
import "thing" from "module"
import * as mylib from "lib"
-- Relative imports
require "relative.path.module"
Compilation
moonc commands
# Compile single file
moonc script.moon
# Compile multiple files
moonc *.moon
# Output to directory
moonc -t output/ input.moon
# Watch and auto-compile
moonc -w . # Watch current directory
moonc -w src/ # Watch specific directory
# Print compiled Lua to stdout
moonc -p script.moon
# Include line numbers in output
moonc -l script.moon
moon commands
# Run script directly
moon script.moon
# Pass arguments
moon script.moon arg1 arg2
# REPL mode
moon # Interactive mode
# Execute string
moon -e "print 'Hello'"
# Load libraries
moon -l lib script.moon
Require paths
-- MoonScript adds .moon to package path
require "mymodule" -- Looks for mymodule.moon
-- Explicit extension
require "mymodule.moon"
-- Nested modules
require "lib.utils.helper"
-- Looks in lib/utils/helper.moon
Compile-time options
# Custom Lua version target
moonc --lua-version=5.1 script.moon
moonc --lua-version=5.2 script.moon
# Minify output
moonc --minify script.moon
# Keep line numbers (debug)
moonc -l script.moon
MoonScript vs Lua
Key differences
| Feature | MoonScript | Lua |
|---|---|---|
| Variables | Local by default | Global by default |
| Classes | Built-in class |
Manual metatables |
| String interpolation | "Hello #{name}" |
"Hello " .. name |
| List comprehensions | [x*2 for x in *items] |
Manual loops |
| Arrow functions | (x) -> x * 2 |
function(x) return x*2 end |
| Method calls | obj\method() |
obj:method() |
| Self reference | @property |
self.property |
| Conditionals | Postfix: do_it() if condition |
Prefix only |
Function syntax
-- MoonScript
greet = (name) -> "Hello, #{name}"
method = => @value * 2
-- Lua equivalent
local greet = function(name)
return "Hello, " .. name
end
local method = function(self)
return self.value * 2
end
Class syntax
-- MoonScript
class Dog
new: (@name) =>
bark: => print "#{@name} barks!"
dog = Dog "Buddy"
dog\bark()
-- Lua equivalent
local Dog
do
local _class_0
local _base_0 = {
bark = function(self)
return print(self.name .. " barks!")
end
}
_base_0.__index = _base_0
_class_0 = setmetatable({
__init = function(self, name)
self.name = name
end,
__base = _base_0
}, {
__index = _base_0,
__call = function(cls, ...)
local _self_0 = setmetatable({}, _base_0)
cls.__init(_self_0, ...)
return _self_0
end
})
Dog = _class_0
end
local dog = Dog("Buddy")
dog:bark()
Comprehensions
-- MoonScript
squares = [x*x for x in *{1,2,3,4,5}]
-- Lua equivalent
local squares = {}
for _, x in ipairs({1, 2, 3, 4, 5}) do
squares[#squares + 1] = x * x
end
Common patterns
Module pattern
-- mymodule.moon
class MyClass
value: 0
increment: => @value += 1
helper = (x) -> x * 2
{
:MyClass
:helper
version: "1.0.0"
}
-- Usage
{:MyClass, :helper} = require "mymodule"
obj = MyClass!
obj\increment()
print helper 5
Error handling
-- pcall pattern
success, result = pcall ->
risky_operation()
unless success
print "Error: #{result}"
-- Error function
error "Something went wrong" unless valid
-- Assert
assert condition, "Condition must be true"
Callback pattern
-- Define callback
process_items = (items, callback) ->
for item in *items
callback item
-- Use callback
process_items data, (item) ->
print "Processing: #{item}"
-- With fat arrow for context
class Processor
process_all: (items) =>
process_items items, (item) =>
@handle_item item -- @ works here
Mixin pattern
-- Mixin module
Serializable =
to_json: =>
require("cjson").encode @
from_json: (str) =>
require("cjson").decode str
-- Use in class
class User
new: (@name, @email) =>
-- Mix in methods
for k, v in pairs Serializable
@@__base[k] = v
Gotchas
Variable scoping
-- ⚠️ Variables are local by default
x = 10 -- Local to scope
export y = 20 -- Make global
-- ⚠️ No implicit globals
function_call() -- Must be defined earlier
-- or use export for globals
Self binding
-- ⚠️ @ only works with fat arrow (=>)
class Wrong
method: -> -- Thin arrow
print @value -- @ is nil!
class Right
method: => -- Fat arrow
print @value -- @ is self ✓
-- ⚠️ Callbacks lose context
class Handler
callback: =>
some_func (x) -> -- Thin arrow
@handle(x) -- @ is nil!
some_func (x) => -- Fat arrow
@handle(x) -- @ works ✓
Implicit returns
-- ⚠️ Last expression is always returned
get_config = ->
config = load_defaults()
apply_overrides config
config -- Returned ✓
-- ⚠️ Unwanted return
set_value = (x) ->
@value = x -- Returns x (may not want)
nil -- Explicit nil return
-- ✓ Or use explicit return
set_value = (x) ->
@value = x
return -- Return nil
Table keys
-- ⚠️ Colon syntax creates string keys
table = {
name: "Alice" -- key is "name"
age: 30 -- key is "age"
}
-- ⚠️ Bracket syntax for computed keys
key = "dynamic"
table = {
[key]: "value" -- Use brackets for variables
["literal"]: "value" -- Or literals with special chars
}
Method calls
-- ⚠️ Backslash (\) vs dot (.)
obj.method() -- Calls without self
obj\method() -- Calls with self (same as obj:method())
-- ⚠️ Exclamation (!) means no args
obj\method! -- Same as obj:method()
obj\method arg -- Same as obj:method(arg)
obj\method(arg1, arg2) -- Multiple args need parens
Whitespace sensitivity
-- ⚠️ Indentation matters
if condition
do_something()
do_another() -- Part of if block
-- ⚠️ Inconsistent indentation breaks code
if condition
line1()
line2() -- ERROR: inconsistent indent
-- ✓ Use consistent spaces (2 or 4)
if condition
line1()
line2()
Also see
- MoonScript official site (moonscript.org)
- MoonScript reference (moonscript.org)
- MoonScript GitHub (github.com)
- Lua cheatsheet (devhints.io)
- Try MoonScript online (moonscript.org)
- MoonScript cookbook (moonscript.org)