lua
2021-12-03 ยท 7 min read
Syntax #
function foo(x)
-- <body>
end
-- local to the scope
local function bar()
-- <body>
end
-- anonymous function (all fns are technically anonymous)
local max = function (x, y)
return (a > b) and a or b
end
-- simulate "named" args
function rename(args) do
os.rename(args.old, args.new)
end
-- can call w/o parens
rename{ new = "perm.lua", old = "temp.lua" }
if x then
-- <case 1>
elseif y then
-- <case 2>
else
-- <case 3>
end
while x do
-- <block>
end
repeat
-- <block>
until x
-- note end is _inclusive_
for i = 1, 3 do
print(i)
end
-- output:
-- > 1
-- > 2
-- > 3
for i = 3, 1, -1 do
print(i)
end
-- output:
-- > 3
-- > 2
-- > 1
-- iterate over an _array_
local a = { 1, 3, 5 }
for i, x in ipairs(a) do
print(i, x)
end
-- output:
-- > 1 1
-- > 2 3
-- > 3 5
-- iterate over a table (order not guaranteed)
local a = { 1, 3, 5 }
for i, x in ipairs(a) do
print(i, x)
end
-- output:
-- > 1 10
-- > 2 foo
-- > x 5
-- > y 3
-- creates a new scope
do
-- <block>
end
-- multiple assignment
a, b = 10, 20
-- this is a block comment
--[[
print(x)
--]]
-- new table
x = {}
x["foo"] = 5
-- array (note 1-indexed)
local a = {}
for i = 1, 5 do
a[i] = math.random(10)
end
Semantics #
- undefined variables default to
nil
- _ALL_CAPS variables are reserved for lua
- types:
nil
,boolean
,number
(double),string
(immutable, just dumb byte vec),table
(map type),function
,userdata
(opaque blobs from external libs),thread
(but actually coroutine, not OS thread) - all values are
true
exceptfalse
andnil
- arrays are just tables with
number
keys - variables are global by default, need to declare with
local
to restrict to lexical scoping - arrays cannot have holes, but can be partially filled until used
Idioms #
-- default value
x = x or "default"
Strings #
-- string concat
"foo" .. " bar"
-- string length
#"foo"
-- multiline strings
escapes = [[
\f (form feed)
\n (newline)
\r (carriage return)
]]
print("|" .. escapes .. "|")
-- output:
-- > | \f (form feed)
-- > \n (newline)
-- > \r (carriage return)
-- > |
Arrays #
-- remove last entry
a[#a] = nil
-- or
table.remove(a, #a)
-- append entry
a[#a + 1] = "foo"
-- or
table.insert(a, #a + 1, "foo")
-- insert (shifting other elts)
table.insert(a, 1, "foo")
-- sort
table.sort({ 5, 3, 9, 1, 7})
-- > { 1, 3, 5, 7, 9 }
-- concat
table.concat({ 1, 2, 3 }, " ")
-- > "1 2 3"
Structs #
point = { x = 10, y = 20 }
print(point.x)
Iterators #
- an iterator is just a function with some local state. each time you call the function it returns the next value and
nil
when finished.
example:
-- returning a closure that modifies the outer fn args each call
function fromto(a, b)
return function ()
if a > b then
return nil
else
a = a + 1
return a - 1
end
end
end
for x in fromto(2, 5) do
print(x)
end
-- > 2
-- > 3
-- > 4
-- > 5
- there are also stateless iterators
function fromto(a, b)
return function (state, seed)
if seed >= state then
return nil
else
return seed + 1
end
end, b, a - 1 -- notice the state, seed returned here
end
- and "seedless" iterators
function fromto(a, b)
return function(state)
if state[1] > state[2] then
return nil
else
state[1] = state[1] + 1
return state[1] - 1
end
end, { a, b } -- this is the initial state
end
Errors #
expressing errors:
- can return
out, err
, whereout
isnil
if there was an error anderr
is an error message - can raise an exception with e.g.
assert(cond)
orerror("error message")
+error
has an optional 2nd arg that specifies the "error-level", whereerror("..", 1)
means "this function is to blame",error("..", 2)
means "the calling function is to blame",error("..", 3)
means the caller's caller, etc...
catching errors:
pcall(function, arg1, arg2, ..)
will return ok, result
where ok: boolean
and result
is the succesful output or the error message.
also
xpcall(
function ()
-- do fallible thing
end,
function (err)
-- handle error
end
)
debug.traceback()
returns the traceback as a string
Modules #
- modules are just tables
- some standard modules:
table
,io
,string
,math
,os
,coroutine
,package
,debug
, all included in the prelude - "import" a module with
mymodule = require "mymodule"
require
searchespackage.path
to find modules- can (usually) modify the path with
LUA_PATH
env var - lua replaces ";;" in the env var with the pre-compiled default
- the format is a series of ";" separated template strings, where "?" is replaced with the module name
- e.g.,
"/usr/local/share/lua/5.2/?.lua;/usr/local/share/lua/5.2/?/init.lua;./?.lua"
- lua replaces "." in the module name with platform path separator. used to import from packages with many modules
- can (usually) modify the path with
defining a module
-- file: complex.lua
local complex = {}
function complex.new(r, i)
return { real = r or 0, im = i or 0 }
end
complex.i = complex.new(0, 1)
function complex.add(c1, c2)
return complex.new(c1.real + c2.real, c1.im + c2.im)
end
function complex.tostring(c)
return tostring(c.real) .. "+" .. tostring(c.im) .. "i"
end
return complex
Metatables #
- can set metatable for a table to override built-in behaviours, e.g., arithmetic, tostring, equality, iteration, call table as fn, etc...
- use
setmetatable
andgetmetatable
functions - metamethods:
__add
,__sub
,__mul
,__div
,__mod
,__pow
,__unm
,__concat
,__len
,__eq
,__lt
,__le
,__index
,__newindex
,__call
,__tostring
,__ipairs
,__pairs
,__gc
__index
and__newindex
can be tables, used for single-inheritance OO
augmenting complex.lua defined above with metatables
local complex = {}
local mt = {}
function complex.new(r, i)
return setmetatable({ real = r or 0, im = i or 0 }, mt)
end
function complex.is_complex(c)
return getmetatable(c) == mt
end
complex.i = complex.new(0, 1)
function complex.add(c1, c2)
return complex.new(c1.real + c2.real, c1.im + c2.im)
end
mt.__add = complex.add
function complex.eq(c1, c2)
return (c1.real == c2.real) and (c1.im == c2.im)
end
mt.__eq = complex.eq
function complex.norm(c)
return math.sqrt(c.real * c.real + c.im * c.im)
end
mt.__len = complex.norm
function complex.tostring(c)
return tostring(c.real) .. "+" .. tostring(c.im) .. "i"
end
mt.__tostring = complex.tostring
return complex
OO #
- using dot-notation doesn't add implicit
self
to call. you need to include it manually likeobj.method(obj, args)
- to avoid this duplication, lua has colon-notation which adds this implicit
self
as the first arg:obj:method(args)
. - declaring functions using a
obj:method
adds implicitself
parameter
function point:norm()
return math.sqrt(self.x * self.x + self.y * self.y)
end
can define prototype-style classes
local Complex = {}
Complex.__index = Complex
function Complex:new(r, i)
return setmetatable({ real = r or 0, im = i or 0 }, self)
end
function Complex:norm()
return math.sqrt(self.real * self.real + self.im * self.im)
end
-- etc...
return Complex
adding other fields to Complex
makes them default values for new instances
local Complex = { real = 0, im = 0 }
single inheritance (Point extends Shape)
local Shape = {}
Shape.__index = Shape
function Shape:new(x, y)
return setmetatable({ x = x, y = y }, self)
end
function Shape:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
return Shape
local Shape = require "shape"
local Point = setmetatable({}, Shape)
Point.__index = Point
function Point:area()
return 0
end
return Point
local Point = require "point"
p = Point:new(5, 10)
print(p:area())
-- > 0
p:move(-3, 7)
print(p.x, p.y)
-- > 2 17
this is a simple version of OO in lua (there are more sophisticated versions). some disadvantages of this approach: 1. class methods (e.g. new) visible in instance namespace, 2. metamethods are not automatically inherited (need to be explicitly set)