NexusCS

MoonScript

Lua
Quick reference for MoonScript - a dynamic scripting language that compiles to Lua with classes, comprehensions, and CoffeeScript-inspired syntax.
featured

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