MoonScript 0.8.0 - Changelog

On This Page

All Pages

MoonScript v0.8.0 (2026-10-22)

New parser

The LPeg parser has been replaced. The grammar is defined in moonscript/parse/grammar.moon and compiled by pgen into a native C module, moonscript.parse.native. A pure Lua version of the same grammar is included as moonscript.parse.slow, a drop-in replacement for the native module.

Parsing is around 5x faster:

InterpreterLPeg parserNew parserSpeedup
Lua 5.1.50.947s0.180s5.3x
LuaJIT 2.10.817s0.141s5.8x

(Parse time for a 405 file, 58,296 line corpus, best of 5 passes.)

The parser produces the same syntax trees as the LPeg grammar. Other changes:

New Features

Changes

Bug Fixes

Binaries

MoonScript v0.7.0 (2026-07-26)

This update includes a bunch of syntax bug fixes and cleanups, with a few new features and an enhanced linter. This will be the last release using LPeg, future versions will use the new parser.

Destructuring updates

Function arguments can be destructured

A function argument can now be written as a table literal to destructure the value passed in. The destructuring patterns are the same ones supported by assignment, including nested patterns, numeric keys, and @field targets:

dist = ({x: x1, y: y1}, {x: x2, y: y2}) ->
  math.sqrt (x2 - x1)^2 + (y2 - y1)^2

class Point
  new: ({x: @x, y: @y}) =>

render = (name, {:width, :height}, scale=1, ...) ->
  print name, width, height, scale, ...

The argument becomes an internal name and the fields are unpacked at the top of the function body:

render = function(name, _arg_0, scale, ...)
  local width, height
  width, height = _arg_0.width, _arg_0.height
  if scale == nil then
    scale = 1
  end
  return print(name, width, height, scale, ...)
end

Destructured arguments always declare fresh locals, so a name in the pattern shadows an enclosing local of the same name instead of assigning to it.

Argument initialization now happens in the order the arguments are written, so a default value can reference a name that an earlier argument destructured:

scaled = ({:base}, amount = base * 2) -> base + amount

Because the destructured names are bound at the top of the body, a name in a pattern can not collide with another argument. ({:a}, a) -> and ({:self}) => are now compile errors.

Destructuring in multiple assignment

Destructuring can now be used mixed in with other names in multiple assignment (#367, #304):

num, {:message} = two_values!
head, {:content}, tail = mix!
{value: built}, {:status} = builder\build!

Previously the assignment was split into separate statements, which broke destructuring output.

Better validation of destructuring targets

The names extracted by a destructuring pattern are now checked with the same rule the parser uses for assignment targets. @@class_field, index expressions, and slices are now allowed:

{x: obj.a, y: obj["b"], z: @prop, w: @@cls_prop} = t

Destructuring into something that can never be assigned to is now a compile error instead of generating broken code:

Linter updates

Lint stages

The linter’s checks are now organized into named stages: global_access, unused, constant_assign, and import_overwrite. Every stage runs by default. moonc --lint-stage limits reporting to the named stage, and --exclude-lint-stage reports everything but the named stage. Both options can be repeated, and they can not be combined.

moonc -l --lint-stage global_access --lint-stage unused .
moonc -l --exclude-lint-stage import_overwrite .

New checks

import_overwrite reports an import that clobbers a name that is already bound in scope:

insert = "hello"
f = ->
  import insert from table -- import overwrites existing binding `insert`

constant_assign reports writing to a name that is tagged as constant. In this version, only the names created by import are tagged as constant. For this release, constant names are only checked in the linter. In the future we will make constant violation a compiler error.

import insert from table
insert = 5 -- assigning to constant `insert`

Compact lint output

moonc --lint-format compact writes one line per issue in the familiar file:line:column: message shape, with the stage name appended, for editors and tools that parse compiler output (#465):

$ moonc -l --lint-format compact lint_example.moon
lint_example.moon:7:5: accessing global `my_nmuber` [global_access]

Standard library updates

New class introspection functions

The moon module has four new functions, documented in the standard library reference:

Unlike the old is_object, these can tell classes, instances, and __base tables apart.

is_object is now deprecated. It returns truthy for instances, classes, and __base tables, so it can not be used to distinguish them.

BREAKING: moon.type no longer returns the class object for a class

moon.type now returns the string "class" when given a class, and it only returns the class object for actual instances. Previously a class returned itself, and a __base table returned the class it belonged to, which made it impossible to tell an instance from the class it came from.

class MyClass

type MyClass!       -- MyClass (unchanged)
type MyClass        -- "class" (was MyClass)
type MyClass.__base -- "table" (was MyClass)

BREAKING: changes to moonscript.util

These are internal helpers that were never documented, but they were reachable:

Code Generation

Interpolated strings and nested expressions are parenthesized correctly

The compiler now knows Lua’s operator precedence when writing out an expression, and it adds parentheses when a nested expression would otherwise be regrouped by the surrounding operators. String interpolation is the most common way to get a nested expression, and it was silently generating the wrong math (#320):

x = 10 / "#{b}.5"

Before:

local x = 10 / tostring(b) .. ".5"

After:

local x = 10 / (tostring(b) .. ".5")

Because .. is right associative in Lua, an interpolated string on the left hand side of a .. is grouped as well. This is observable through the __concat metamethod:

prefix = "id: #{id}, " .. rest

Before:

local prefix = "id: " .. tostring(id) .. ", " .. rest

After:

local prefix = ("id: " .. tostring(id) .. ", ") .. rest

Prefix operators are grouped in update assignments and switches

The check for whether a value needs to be wrapped in parentheses did not account for the prefix operators #, -, not, and ~, which parse greedily. An update assignment or a switch case with one of them on the left would compile to the wrong grouping (#457):

count -= #datum + 1
total *= -x + 1
flag and= not a or b

Before:

local count = count - #datum + 1
local total = total * -x + 1
local flag = flag and not a or b

After:

local count = count - (#datum + 1)
local total = total * (-x + 1)
local flag = flag and (not a or b)

Expression lines no longer use _

When an expression appears where a statement is expected, the compiler assigns it to a throwaway variable. It used to use the name _, which would clobber a variable of that name in the user’s code (#309). It now uses an autogenerated _scrap_N name.

_ = 5
tbl.field
print _

Before:

local _ = 5
_ = tbl.field
return print(_)

After:

local _ = 5
local _scrap_0 = tbl.field
return print(_)

local in a class body is hoisted

A local declaration inside a class body was written out near the bottom of the generated do block, after the methods had already been compiled. Methods referencing the name would close over the outer or global name instead of the class local (#459). The declaration is now hoisted to the top of the class body, placed after the reference to the parent class so a body local can shadow the parent’s name.

class Counter
  local cache

  -- previously `cache` local would not be lexically available in the method
  update: => cache or= "something..."

Bug Fixes

Binaries

` $ moon --version MoonScript version 0.7.0 (static build) Runtime: LuaJIT 2.1.1748459687 Commit: v0.7.0 Built: 2026-07-24T09:07:45-07:00

New Parser in Testing

The next version of MoonScript will likely include the new C parser generated by pgen, replacing LPeg. Early testing shows the new parser is be about 5x faster.

Setting the MOONSCRIPT_PARSER=pgen environment variable will make the compiler parse with the moonscript_parser module instead of building the LPeg grammar at runtime. This is opt in and experimental, the LPeg grammar is still the default

MoonScript v0.6.0 (2026-01-10)

New Features

Command Line Improvements

moonc Option Renames

New Tools

Utility Improvements

Compiler Enhancements

Bug Fixes

Internal Changes

MoonScript v0.5.0 (2016-9-25)

Syntax updates

Function calls

Function calls with parentheses can now have free whitespace around the arguments. Additionally, a line break may be used in place of a comma:

my_func(
  "first arg"
  =>
    print "some func"

  "third arg", "fourth arg"
)

Function argument definitions

Just like the function all update, function argument definitions have no whitespace restrictions between arguments, and line breaks can be used to separate arguments:

some_func = (
  name
  type
  action="print"
) =>
  print name, type, action

Additions

Bug Fixes

MoonScript v0.4.0 (2015-12-06)

Changes to super

super now looks up the parent method via the class reference, instead of a (fixed) closure to the parent class.

Given the following code:

class MyThing extends OtherThing
  the_method: =>
    super!

In the past super would compile to something like this:

_parent_0.the_method(self)

Where _parent_0 was an internal local variable that contains a reference to the parent class. Because the reference to parent is an internal local variable, you could never swap out the parent unless resorting to the debug library.

This version will compile to:

_class_0.__parent.__base.the_method(self)

Where _class_0 is an internal local variable that contains the current class (MyThing).

Another difference is that the instance method is looked up on __base instead of the class. The old variation would trigger the metamethod for looking up on the instance, but a class method of the same name could conflict, take precedence, and be retuned instead. By referencing __base directly we avoid this issue.

Super on class methods

super can now be used on class methods. It works exactly as you would expect.

class MyThing extends OtherThing
  @static_method: =>
    print super!

Calling super will compile to:

_class_0.__parent.static_method(self)

Improved scoping for super

The scoping of super is more intelligent. You can warp your methods in other code and super will still generate correctly. For example, syntax like this will now work as expected:

class Sub extends Base
  value: if debugging
    => super! + 100
  else
    => super! + 10

  other_value: some_decorator {
    the_func: =>
      super!
  }

super will refer to the lexically closest class declaration to find the name of the method it should call on the parent.

Bug Fixes

Other

MoonScript v0.3.2 (2015-6-01)

Bug Fixes

MoonScript v0.3.1 (2015-3-07)

Bug Fixes

MoonScript v0.3.0 (2015-2-28)

New Features

Parsing Updates

Whitespace parsing has been relaxed in a handful of locations:

x = really_long_function! +
  2304

big_math = 123 /
  12 -
  43 * 17


bool_exp = nice_shirt and cool_shoes or
  skateboard and shades
hello = 100 + (
  var * 0.23
) - 15


funcall(
  "height", "age", "weight"
)


takes_two_functions (->
  print "hello"
), ->
  print "world"
x = {
  hello:
    call_a_function "football", "hut"
}

Code Generation

Bug Fixes

Misc

MoonScript v0.2.6 (2014-6-18)

Bug Fixes

MoonScript v0.2.5 (2014-3-5)

New Things

Bug Fixes

Code Generation

Bound functions will avoid creating an anonymous function unless necessary.

x = hello\world

Before:

local x = (function()
  local _base_0 = hello
  local _fn_0 = _base_0.world
  return function(...)
    return _fn_0(_base_0, ...)
  end
end)()

After:

local x
do
  local _base_0 = hello
  local _fn_0 = _base_0.world
  x = function(...)
    return _fn_0(_base_0, ...)
  end
end

Explicit return statement now avoids creating anonymous function for statements where return can be cascaded into the body.

->
  if test1
    return [x for x in *y]

  if test2
    return if true
      "yes"
    else
      "no"

  false

Before:

local _
_ = function()
  if test1 then
    return (function()
      local _accum_0 = { }
      local _len_0 = 1
      local _list_0 = y
      for _index_0 = 1, #_list_0 do
        local x = _list_0[_index_0]
        _accum_0[_len_0] = x
        _len_0 = _len_0 + 1
      end
      return _accum_0
    end)()
  end
  if test2 then
    return (function()
      if true then
        return "yes"
      else
        return "no"
      end
    end)()
  end
  return false
end

After:

local _
_ = function()
  if test1 then
    local _accum_0 = { }
    local _len_0 = 1
    local _list_0 = y
    for _index_0 = 1, #_list_0 do
      local x = _list_0[_index_0]
      _accum_0[_len_0] = x
      _len_0 = _len_0 + 1
    end
    return _accum_0
  end
  if test2 then
    if true then
      return "yes"
    else
      return "no"
    end
  end
  return false
end

MoonScript v0.2.4 (2013-07-02)

Changes

moon = require "moon"

New Things

x = "hello
world"
import a, b
  c, d from z

Bug Fixes

MoonScript v0.2.3-2 (2013-01-29)

Fixed bug with moonloader not loading anything

MoonScript v0.2.3 (2013-01-24)

Changes

moonscript = require "moonscript"

New Things

Bug Fixes

Other Stuff

MoonScript has made its way into GitHub. .moon files should start to be recognized in the near future.

MoonScript v0.2.2 (2012-11-03)

Changes

New Things

The Language

The API

The Tools

Bug Fixes

MoonScript v0.2.0 (2011-12-12)

Changes

New Things

The Language

The API

The Tools

Standard Library

I'm now including a small set of useful functions in a single module called moon:

require "moon"

Documentation is available here.

Bug Fixes

Other Stuff

Since the first release, I've written one other project in MoonScript (other than the compiler). It’s a static site generator called sitegen. It’s what I now use to generate all of my project pages and this blog.

MoonScript 0.1.0 (2011-08-12)

Initial release