-
Notifications
You must be signed in to change notification settings - Fork 196
Learn MoonScript in 15 Minutes
-- Two dashes start a comment. Comments can go until the end of the line. -- MoonScript transpiled to Lua does not keep comments.
-- As a note, MoonScript does not use 'do', 'then', or 'end' like Lua would and -- instead uses an indented syntax, much like Python.
hello = "world"
a, b, c = 1, 2, 3
hello = 123 -- Overwrites hello
from above.
x = 0 x += 10 -- x = x + 10
s = "hello " s ..= "world" -- s = s .. "world"
b = false b and= true or false -- b = b and (true or false)
-- Literals work almost exactly as they would in Lua. Strings can be broken in -- the middle of a line without requiring a .
some_string = "exa mple" -- local some_string = "exa\nple"
-- Strings can also have interpolated values, or values that are evaluated and -- then placed inside of a string.
some_string = "This is an #{some_string}" -- Becomes 'This is an ex\nample'
-- Functions are written using arrows:
my_function = -> -- compiles to function() end
my_function() -- calls an empty function
-- Functions can be called without using parenthesis. Parentheses may still be -- used to have priority over other functions.
func_a = -> print "Hello World!" func_b = -> value = 100 print "The value: #{value}"
-- If a function needs no parameters, it can be called with either ()
or !
.
func_a! func_b()
-- Functions can use arguments by preceding the arrow with a list of argument -- names bound by parentheses.
sum = (x, y)-> x + y -- The last expression is returned from the function. print sum(5, 10)
-- Lua has an idiom of sending the first argument to a function as the object,
-- like a 'self' object. Using a fat arrow (=>) instead of a skinny arrow (->)
-- automatically creates a self
variable. @x
is a shorthand for self.x
.
func = (num)=> @value + num
-- Default arguments can also be used with function literals:
a_function = (name = "something", height=100)-> print "Hello, I am #{name}.\nMy height is #{height}."
-- Because default arguments are calculated in the body of the function when -- transpiled to Lua, you can reference previous arguments.
some_args = (x = 100, y = x + 1000)-> print(x + y)
-- The minus sign plays two roles, a unary negation operator and a binary -- subtraction operator. It is recommended to always use spaces between binary -- operators to avoid the possible collision.
a = x - 10 -- a = x - 10 b = x-10 -- b = x - 10 c = x -y -- c = x(-y) d = x- z -- d = x - z
-- When there is no space between a variable and string literal, the function -- call takes priority over following expressions:
x = func"hello" + 100 -- func("hello") + 100 y = func "hello" + 100 -- func("hello" + 100)
-- Arguments to a function can span across multiple lines as long as the -- arguments are indented. The indentation can be nested as well.
my_func 5, -- called as my_func(5, 8, another_func(6, 7, 9, 1, 2), 5, 4) 8, another_func 6, 7, -- called as 9, 1, 2, -- another_func(6, 7, 9, 1, 2) 5, 4
-- If a function is used at the start of a block, the indentation can be -- different than the level of indentation used in a block:
if func 1, 2, 3, -- called as func(1, 2, 3, "hello", "world") "hello", "world" print "hello"
-- Tables are defined by curly braces, like Lua:
some_values = {1, 2, 3, 4}
-- Tables can use newlines instead of commas.
some_other_values = { 5, 6 7, 8 }
-- Assignment is done with :
instead of =
:
profile = { name: "Bill" age: 200 "favorite food": "rice" }
-- Curly braces can be left off for key: value
tables.
y = type: "dog", legs: 4, tails: 1
profile = height: "4 feet", shoe_size: 13, favorite_foods: -- nested table foo: "ice cream", bar: "donuts"
my_function dance: "Tango", partner: "none" -- :( forever alone
-- Tables constructed from variables can use the same name as the variables
-- by using :
as a prefix operator.
hair = "golden" height = 200 person = {:hair, :height}
-- Like in Lua, keys can be non-string or non-numeric values by using []
.
t =
[1 + 2]: "hello"
"hello world": true -- Can use string literals without []
.
-- List Comprehensions
-- Creates a copy of a list but with all items doubled. Using a star before a -- variable name or table can be used to iterate through the table's values.
items = {1, 2, 3, 4}
doubled = [item * 2 for item in *items]
-- Uses when
to determine if a value should be included.
slice = [item for item in *items when i > 1 and i < 3]
-- for
clauses inside of list comprehensions can be chained.
x_coords = {4, 5, 6, 7} y_coords = {9, 2, 3}
points = [{x,y} for x in *x_coords for y in *y_coords]
-- Numeric for loops can also be used in comprehensions:
evens = [i for i=1, 100 when i % 2 == 0]
-- Table Comprehensions are very similar but use {
and }
and take two
-- values for each iteration.
thing = color: "red", name: "thing", width: 123 thing_copy = {k, v for k, v in pairs thing}
-- Tables can be "flattened" from key-value pairs in an array by using unpack
-- to return both values, using the first as the key and the second as the
-- value.
tuples = {{"hello", "world"}, {"foo", "bar"}} table = {unpack tuple for tuple in *tuples}
-- Slicing can be done to iterate over only a certain section of an array. It
-- uses the *
notation for iterating but appends [start, end, step]
.
-- The next example also shows that this syntax can be used in a for
loop as
-- well as any comprehensions.
for item in *points[1, 10, 2] print unpack item
-- Any undesired values can be left off. The second comma is not required if -- the step is not included.
words = {"these", "are", "some", "words"} for word in *words[,3] print word
have_coins = false if have_coins print "Got coins" else print "No coins"
-- Use then
for single-line if
if have_coins then "Got coins" else "No coins"
-- unless
is the opposite of if
unless os.date("%A") == "Monday"
print "It is not Monday!"
-- if
and unless
can be used as expressions
is_tall = (name)-> if name == "Rob" then true else false
message = "I am #{if is_tall "Rob" then "very tall" else "not so tall"}"
print message -- "I am very tall"
-- if
, elseif
, and unless
can evaluate assignment as well as expressions.
if x = possibly_nil! -- sets x
to possibly_nil()
and evaluates x
print x
-- Conditionals can be used after a statement as well as before. This is -- called a "line decorator".
is_monday = os.date("%A") == "Monday" print("It IS Monday!") if isMonday print("It is not Monday..") unless isMonday --print("It IS Monday!" if isMonday) -- Not a statement, does not work
for i = 1, 10 print i
for i = 10, 1, -1 do print i -- Use do
for single-line loops.
i = 0 while i < 10 continue if i % 2 == 0 -- Continue statement; skip the rest of the loop. print i
-- Loops can be used as a line decorator, just like conditionals print "item: #{item}" for item in *items
-- Using loops as an expression generates an array table. The last statement -- in the block is coerced into an expression and added to the table. my_numbers = for i = 1, 6 do i -- {1, 2, 3, 4, 5, 6}
-- use continue
to filter out values
odds = for i in *my_numbers
continue if i % 2 == 0 -- acts opposite to when
in comprehensions!
i -- Only added to return table if odd
-- A for
loop returns nil
when it is the last statement of a function
-- Use an explicit return
to generate a table.
print_squared = (t) -> for x in t do xx -- returns nil
squared = (t) -> return for x in t do xx -- returns new table of squares
-- The following does the same as (t) -> [i for i in *t when i % 2 == 0]
-- But list comprehension generates better code and is more readable!
filter_odds = (t) -> return for x in *t if x % 2 == 0 then x else continue evens = filter_odds(my_numbers) -- {2, 4, 6}
-- Switch statements are a shorthand way of writing multiple if
statements
-- checking against the same value. The value is only evaluated once.
name = "Dan"
switch name when "Dave" print "You are Dave." when "Dan" print "You are not Dave, but Dan." else print "You are neither Dave nor Dan."
-- Switches can also be used as expressions, as well as compare multiple
-- values. The values can be on the same line as the when
clause if they
-- are only one expression.
b = 4 next_even = switch b when 1 then 2 when 2, 3 then 4 when 4, 5 then 6 else error "I can't count that high! D:"
-- Classes are created using the class
keyword followed by an identifier,
-- typically written using CamelCase. Values specific to a class can use @ as
-- the identifier instead of self.value
.
class Inventory new: => @items = {} add_item: (name)=> -- note the use of fat arrow for classes! @items[name] = 0 unless @items[name] @items[name] += 1
-- The new
function inside of a class is special because it is called when
-- an instance of the class is created.
-- Creating an instance of the class is as simple as calling the class as a -- function. Calling functions inside of the class uses \ to separate the -- instance from the function it is calling.
inv = Inventory! inv\add_item "t-shirt" inv\add_item "pants"
-- Values defined in the class - not the new() function - will be shared across -- all instances of the class.
class Person clothes: {} give_item: (name)=> table.insert @clothes name
a = Person! b = Person!
a\give_item "pants" b\give_item "shirt"
-- prints out both "pants" and "shirt"
print item for item in *a.clothes
-- Class instances have a value .__class
that are equal to the class object
-- that created the instance.
assert(b.__class == Person)
-- Variables declared in the body of the class (using the =
operator) can act
-- as "private" variables that are only accessible to the class and everything
-- else in the current scope.
class SomeThings secret = 123 log = (msg)-> print "LOG: #{msg}"
some_method: => log "Hello World: #{secret}"
-- The extends
keyword can be used to inherit properties and methods from
-- another class.
class Backpack extends Inventory
size: 10
add_item: (name)=>
error "backpack is full" if #@items > @size
super name -- calls Inventory.add_item with name
.
-- Because a new
method was not added, the new
method from Inventory
will
-- be used instead. If we did want to use a constructor while still using the
-- constructor from Inventory
, we could use the magical super
function
-- during new()
.
-- When a class extends another, it calls the method __inherited
on the
-- parent class (if it exists). It is always called with the parent and the
-- child object.
class ParentClass @__inherited: (child)=> print "#{@__name} was inherited by #{child.__name}" a_method: (a, b) => print a .. ' ' .. b
-- Will print 'ParentClass was inherited by MyClass'
class MyClass extends ParentClass a_method: => super "hello world", "from MyClass!" assert super == ParentClass
-- All values are local by default. The export
keyword can be used to
-- declare the variable as a global value.
export var_1, var_2 var_1, var_3 = "hello", "world" -- var_3 is local, var_1 is not.
export this_is_global_assignment = "Hi!"
-- Classes can also be prefixed with export
to make them global classes.
-- Alternatively, all CamelCase variables can be exported automatically using
-- export ^
, and all values can be exported using export *
.
-- do
lets you manually create a scope, for when you need local variables.
do x = 5 print x -- nil
-- Here we use do
as an expression to create a closure.
counter = do i = 0 -> i += 1 return i
print counter! -- 1 print counter! -- 2
-- The local
keyword can be used to define variables before they are assigned.
local var_4
if something
var_4 = 1
print var_4 -- works because var_4
was set in this scope, not the if
scope.
-- The local
keyword can also be used to shadow an existing variable.
x = 10 if false local x x = 12 print x -- 10
-- Use local *
to forward-declare all variables.
-- Alternatively, use local ^
to forward-declare all CamelCase values.
local *
first = -> second!
second = -> print data
data = {}
-- Values from a table can be brought to the current scope using the import
-- and from
keyword. Names in the import
list can be preceded by \
if
-- they are a module function.
import insert from table -- local insert = table.insert import \add from state: 100, add: (value)=> @state + value print add 22
-- Like tables, commas can be excluded from import
lists to allow for longer
-- lists of imported items.
import
asdf, gh, jkl
antidisestablishmentarianism
from {}
-- The with
statement can be used to quickly call and assign values in an
-- instance of a class or object.
file = with File "lmsi15m.moon" -- file
is the value of set_encoding()
.
\set_encoding "utf8"
create_person = (name, relatives)-> with Person! .name = name \add_relative relative for relative in *relatives me = create_person "Ryan", {"sister", "sister", "brother", "dad", "mother"}
with str = "Hello" -- assignment as expression! :D print "original: #{str}" print "upper: #{\upper!}"
-- Destructuring can take arrays, tables, and nested tables and convert them -- into local variables.
obj2 =
numbers: {1, 2, 3, 4}
properties:
color: "green"
height: 13.5
{numbers: {first, second}, properties: {:color}} = obj2
-- first
and second
return [1] and [2] because they are as an array, but
-- color
is like color: color
so it sets itself to the color
value.
print first, second, color
-- Destructuring can be used in place of import
.
{:max, :min, random: rand} = math -- rename math.random to rand
-- Destructuring can be done anywhere assignment can be done. for {left, right} in *{{"hello", "world"}, {"egg", "head"}} print left, right