====== Lua Tips ==
===== Links ==
[[lua>|Programming in Lua (5.0) ]] [[http://lua-users.org/wiki/TutorialDirectory|Tutorial]] --- [[lua>manual/5.2/#contents|Reference Manual]] --- {{http://lua-users.org/files/wiki_insecure/users/thomasl/luarefv51.pdf|Short Reference}} --- IBM intro: [[http://www.ibm.com/developerworks/linux/library/l-lua.html|Embeddable scripting with Lua]] --- Linux Journal intro: [[http://www.linuxjournal.com/article/9605|A Look at Lua]]
===== Install ==
Use www.slackbuilds.org
External addons / Packages for Lua:
* [[http://luarocks.org/repositories/rocks/|Automated "Rocks"]]
* [[http://lua-users.org/wiki/LibrariesAndBindings|Manual]]
===== Implementations ==
==== Standalone lua ==
=== Shebang ==
''#!/usr/bin/lua''
=== Command Line Arguments ==
Use global table ''arg''
=== StdIo ==
local inp= io.stdin:read() -- Read a line from stdion
print() -- Print a line to stdout
io.stdout:write() -- Print a string to stdout
io.stderr:write() -- Print a string to stderr
==== Lua for Web Browsers ==
[[http://kripken.github.io/lua.vm.js/script_example.html|lua.vm.js]] thanks to asm.js
==== Lua for Java/Android ==
[[https://sourceforge.net/projects/luaj/|Luaj]], seen at [[https://github.com/M66B/XPrivacyLua/blob/master/README.md|XPrivacyLua]]
===== C-style ?-operator equivalent ==
This C code
d= a ? b : c
is in lua:
d= a and b or c
Note that this works only for //single// assignment. Use auxiliary tables for mulitiple assignment:
d1, d2 = table.unpack(a and {b1, b2} or {c1, c2})
Take care if b can become ''nil'' or ''false''. In this case the equivalent doesn't work. Instead use:
if a then d=b else d=c end
===== Default values for missing arguments ==
function foo(a)
a= a or "defaultvalue"
end
Take care, if ''false'' is a valid value, instead use:
function foo(a)
if a==nil then a="defaultvalue" end
end
===== Difference between nil and no value ==
function return_nil()
return nil
end
function return_nothing()
end
function count_args(...)
print(table.pack(...).n)
end
count_args(nil) --> 1
count_args() --> 0
--=> Passed nils increment argument count
count_args(return_nil()) --> 1
count_args(return_nothing()) --> 0
--=> Returned nils increment return value count
print(tonumber(nil)) --> nil
print(tonumber(return_nil())) --> nil
--=> tonumber() accepts nil as argument
print(tonumber()) --> bad argument #1 to 'tonumber' (value expected)
print(tonumber(return_nothing())) --> bad argument #1 to 'tonumber' (value expected)
--=> tonumber() does not accept nothing as argument
An example for a function returning nothing is the result of string.gmatch()
===== Lexical scoping ==
local strict= require"fs.strict"
local g,h,i
local function f()
g() -- ok
h() -- ok
--i() -- err, because i refers to old upvalue which is still nill
end
g= function() end -- existing upvalue g is assigned a function
function h() end -- same as above (mind the missing "local"!)
local function i() end -- a new upvalue is created which hides old i(assumption)
f()
i() -- ok
===== Numeric for Loop ==
for i = 0,24,4 do -- start, end (including), step
print(i)
end
===== Tables ==
==== Length ==
Use ''#'' operator
==== Iterate over ==
-- Iterate over array part:
for i,v in ipairs(now) do print(i,v) end
-- Iterate over complete table:
for k,v in pairs(now) do print(k,v) end
Oder is unspecified when iterating over a map with ''pairs()''
===== Copying Variables ==
#!/usr/bin/lua
-- Numbers:
a=3 -- a contains 3
b=a -- b contains 3
a=4 -- a contains 4, b still contains 3
print(a,b) -- 4 3
-- Booleans:
a=true; b=a; a=false
print(a,b) -- false true
-- Strings:
a="World" -- a points to the string "World"
b=a -- b points to the same string
a="Hello" -- a points to "World", b still points to "Hello"
print(a,b) --> Hello World
-- Functions:
a= function() print("World") end
b=a
-- a and b point to the same function:
print(a,b) --> function: 0x80500e8 function: 0x80500e8
a= function() print("Hello") end
-- a points to a new function and b still points to the old function:
print(a,b) --> function: 0x8050a48 function: 0x80500e8
a() -- Hello
b() -- World
A copy of a number, boolean, string or function can't modify the content of its origin or vice versa, they are independent of each other
#!/usr/bin/lua
-- Tables:
a={3}
b=a;
-- a and b point to the same table:
print(a,b) -- table: 0x80509e0 table: 0x80509e0
print(a[1],b[1]) -- 3 3
a[1]=4;
print(a[1],b[1]) -- 4 4
b[1]=5
print(a[1],b[1]) -- 5 5
a={6}
-- a points to a new table and b still points to the old table:
print(a,b) -- table: 0x8050a30 table: 0x80509e0
print(a[1],b[1]) -- 6 5
b[1]=7
print(a[1],b[1]) -- 6 7
A copy of a table can modify the value of the table entries of its origin and vice versa, because both table variables point to the same table object
===== Error Handling ==
==== pcall() differs from Lua Convention ==
''pcall()'':
success: true, value(s)
failure: false, error
Lua [[http://lua-users.org/wiki/FinalizedExceptions|convention]]:
success: value(s)
failure: nil, error
Error handling code after ''pcall()'' differs from error code after calling a function which complies to the convention!
==== Raise error or return error? ==
=== Converting between raise and return ==
''pcall()'' converts an error raising function into an error returning function.
''assert()'' or similar converts an error returning function into an error raising function.
=== Pro Error Raising ==
- A function which //returns// the error forces the user of that function to //always// check the return code (or convert to raise) , regadless if he wants to handle the error or just pass it upward.
- A function which //raises// the error can not get forgotten to check for errors.
- If the user wants to pass the error upward, checking and converting can be avaoided completely if both the called function and the caller use error raising. In any other case he has to check and/or convert:
^ caller⇓ callee⇒ ^ return ^ raise ^
^ return | check | pcall() + check |
^ raise | assert() | nothing |
=== Pro Error Returning ==
- If the user wants to handle the error, he simply checks the return value of an error returning function. If the function raises the error, the user must convert it to a error returning function by help of pcall() before.
- FIXME A typical ''try {...}''-block is done with a function in Lua. That means you end up having one function more for each try/catch usage
=== Rules: ==
- //Return// error if it can be expected, that the user needs to handle the error directly after calling the func in question.
- //Raise// the error if it can be expected, that the user does not care for errors directly after calling the func in question and simple passes it upward.
- If in doubt, raise the error FIXME What to do when in doubt?
===== Classes and Objects ==
See also projects/accounting/sbaccimport which has since rev 482:c85c255f1870 classes with inheritance where the constructor new() is a class method and can therefore be reused in child classes similar to PIL capter 21
==== Classes as Object templates ==
-- rectangle prototype:
rectangle_proto= {species="rectangle"}
function rectangle_proto:area() return self.width * self.height end
-- rectangle constructor:
function new_rectangle(width, height)
local rectangle= {width= width, height= height}
setmetatable(rectangle, {__index=rectangle_proto})
return rectangle
end
-- rectangle usage:
rectangle= new_rectangle(3,4)
print(rectangle:area()) --> 12
print(rectangle.species) --> rectangle
-- circle prototype:
circle_proto= {species="circle"}
function circle_proto:area() return self.radius^2 * math.pi end
-- circle constructor:
function new_circle(radius)
local circle= {radius= radius}
setmetatable(circle, {__index=circle_proto})
return circle
end
circle= new_circle(1)
print(circle:area()) --> 3.14
print(circle.species) --> circle
==== Classic Inheritance ==
Classic single inheritance chain:
-- Inheritance: shape prototype:
shape_proto= {family="shape"}
function shape_proto:get_species() return self.species end
function shape_proto:get_family() return self.family end
setmetatable(rectangle_proto, {__index=shape_proto})
setmetatable(circle_proto, {__index=shape_proto})
print(circle:get_family().."/"..circle:get_species()) --> shape/circle
print(rectangle:get_family().."/"..rectangle:get_species()) --> shape/rectangle
==== Implementing Interfaces ==
FIXME
* http://code.google.com/p/go-wiki/wiki/GoForCPPProgrammers#Interfaces
* Option 1: No metatables. Just implant all methods of the interface directly into the table which represents the object. Simple
* Option 2: One metatable for every type. No metatable chain. Each type hast a dedicated factory and metatable. All implemented interface methods are copied into the metatable of the type. Saves space.
==== Special Case: tostring() ==
Lua ''print()'' does not look for a ''tostring()'' method directly in the table, instead it looks for a method called ''%%__tostring()%%'' in its metatable. Thus, to enable both ''print(myobject:tostring())'' (explicit call) and ''print(myobject) '' (implicit call), provide the tostring() method in the prototype table of the object and set ''%%__tostring%%'' attribute of the metatable to that method:
-- rectangle prototype:
rectangle_proto= {}
function rectangle_proto:tostring() return "["..self.width.."|"..self.height.."]" end
-- rectangle constructor:
function new_rectangle(width, height)
rectangle= {width= width, height= height}
setmetatable(rectangle, {
__index=rectangle_proto,
__tostring=rectangle_proto.tostring
})
return rectangle
end
-- rectangle usage:
rectangle= new_rectangle(3,4)
print(rectangle:tostring()) --> [3|4]
print(rectangle) --> [3|4]
This works also if ''tostring()'' is defined in the base clase:
-- shape as base class:
shape_proto= {}
function shape_proto:tostring() return "["..self.width.."|"..self.height.."]" end
-- rectangle prototype inherits from shape:
rectangle_proto= {}
setmetatable(rectangle_proto, {
__index=shape_proto
})
-- rectangle constructor:
function new_rectangle(width, height)
rectangle= {width= width, height= height}
setmetatable(rectangle, {
__index=rectangle_proto,
__tostring=rectangle_proto.tostring
})
return rectangle
end
-- rectangle usage:
rectangle= new_rectangle(3,4)
print(rectangle:tostring()) --> [3|4]
print(rectangle) --> [3|4]
==== Privacy ==
=== Simple ==
local M= {}
local private= {} -- holds private data of all objecs in subtables
-- Class Point: --
local Point_proto= {} -- Class prototype
function Point_proto:set(x,y)
local priv= private[self]
priv.x= x
priv.y= y
end
function Point_proto:get()
local priv= private[self]
return priv.x, priv.y
end
function Point_proto:tostring()
local priv= private[self]
return "["..priv.x.."|"..priv.y.."]"
end
Point_proto.__index = Point_proto
Point_proto.__newindex= function() assert(false,"object is write-protected") end
setmetatable(Point_proto, { -- ensure read protection for Point and all childs
__index= function() assert(false, "object is read-protected") end
})
function M.new_Point(x,y) -- constructor
local instance= {}
setmetatable(instance, Point_proto)
private[instance]= {} -- create private part of object
instance:set(x,y)
return instance
end
return M
#!/usr/bin/lua
local point= require "point"
local print_r= function(table)
for key,value in pairs(table) do
io.write("[", key, "] = ", tostring(value), "\n")
end
end
local p= point.new_Point(3,4)
--p.x= 8 --> Write access throws an error
--print(p.y) --> Read access throws an error
print(p:tostring()) --> [3|4]
print_r(p) --> prints nothing
=== With inhteritance ==
local M= {}
local private= {} -- holds private data of all objecs in subtables
-- Class Point: --
-- ...
-- CLASS MovablePoint: --
local MovablePoint_proto= {} -- Class prototype
function MovablePoint_proto:move(dx, dy)
local priv= private[self]
priv.x= priv.x+dx
priv.y= priv.y+dy
end
MovablePoint_proto.__index = MovablePoint_proto
MovablePoint_proto.__newindex= Point_proto.__newindex
setmetatable(MovablePoint_proto, Point_proto) -- Inherit from Point
function M.new_MovablePoint(x,y) -- constructor
local instance= M.new_Point(x,y) -- call parent constructor
setmetatable(instance, MovablePoint_proto)
return instance
end
return M
#!/usr/bin/lua
local point= require "point"
-- ...
local mp= point.new_MovablePoint(5,6)
mp.x= 9 --> Write access throws an error
print(mp.y) --> Read access throws an error
print(mp:tostring()) --> [5|6]
mp:move(2,1)
print(mp:tostring()) --> [7|7]
print_r(mp) --> prints nothing
===== File access ==
Each of the following constructs is equivalent, it reads a file line by line:
local f= io.open"file.txt"
repeat
local l= f:read("l")
if l then print(l) end
until not l
f:close()
local f= io.open"file.txt"
for l in f:lines("l") do
print(l)
end
f:close()
for l in io.lines("file.txt", "l") do
print(l)
end
- ''"l"'' as arg for ''read()'' is the default, it can be omitted
- ''file:lines()'' and ''io.lines()'' accept the same format args as ''read()''. (Tested. Nowhere found in docu). Thus you could read a file eg chunk by chunk as well.
- io.lines() without arguments returns an iterater wich returns stdin line by line
==== Load CSV-like files ==
#!/usr/bin/lua
local separator=":"
for line in io.lines() do
print(line)
for cell in line:gmatch("[^"..separator.."]+") do
print("", cell)
end
end
===== Includes and Modules ==
==== Include ==
To run some code sitting in a different file, use ''dofile()''
To load global variables of an external (config) file into a table locally, you have to pass the table as environment:
a="Harry"
b="Hirsch"
#!/usr/bin/lua
local extenv={}
local chunk= loadfile("config.lua", "t" , extenv)
chunk()
for k,v in pairs(extenv) do
io.write("[", k, "] = ", tostring(v), "\n")
end
[a] = Harry
[b] = Hirsch
==== Modules ==
See [[http://lua-users.org/wiki/ModuleDefinition|ModuleDefinition]] and [[http://lua-users.org/wiki/LuaStyleGuide|LuaStyleGuide]] -> Modules
''module()'' is [[lua>manual/5.2/manual.html#8.2|deprecated]]!
===== Time and Date ==
==== Unix Timestamp ==
now= os.time()
==== Time Map / Time String ==
-- Return a table containing hour, min, wday, day, ... of current time:
now = os.date("*t")
-- Return a table containing hour, min, wday, day, ... of a certain time:
atime = os.date("*t", timestamp)
-- Return a formated string of current time:
now = os.date("Today is %A") -- Today is Thursday
[[http://www.lua.org/pil/22.1.html|Source]]
===== Performance ==
==== Using Locals ==
do
var= 0
for i=1,102000000 do
var= var+1
end
end
-- real 0m4.516s
do
local var= 0
for i=1,102000000 do
var= var+1
end
end
-- real 0m1.003s
local var= 0
do
for i=1,102000000 do
var= var+1
end
end
--real 0m0.970s
do
local var= 0
local closure= function()
for i=1,102000000 do
var= var+1
end
end
closure()
end
-- real 0m1.666s
do
local tab= {var= 0}
for i=1,102000000 do
tab.var= tab.var+1
end
end
-- real 0m4.248s
do
local tab= {0}
for i=1,102000000 do
tab[1]= tab[1]+1
end
end
-- real 0m4.767s
- Local variables (compiled indexed access) are much faster than global variables (table access).
- Chunk-local variables are as fast as function-local variables
- Upvalues (non-local variables of closures) are only a little slower than local variables
- Table access (array or map) is as slow as accessing global variables
Tested with Lua 5.2
==== Strings ==
Concatenating a string piecemeal in a loop is inefficient. Consider using a table as buffer and then ''table.concat()''
See also:
* http://www.lua.org/pil/11.6.html
* http://www.lua.org/gems/sample.pdf => About Strings
===== Coroutines ==
==== Basic Rules ==
A newly created coroutine is in state ''supended'':
co1= coroutine.create(function() end)
print(coroutine.status (co1)) --> suspended
A coroutine which resumes another coroutine goes from state ''running'' to ''normal'' while the resumed couroutine goes from state ''suspended'' to''running'':
co1= coroutine.create(function()
print("in co1: state main:", coroutine.status(main))
print("in co1: state co1", coroutine.status(co1))
end)
main= coroutine.running() -- get handle to main coroutine
print("in main: state main:", coroutine.status(main))
print("in main: state co1:", coroutine.status(co1))
coroutine.resume(co1)
Result:
in main: state main: running
in main: state co1: suspended
in co1: state main: normal
in co1: state co1 running
A ''running'' couroutine can yield() back to its resumer, which puts the resumer back to ''running'' and itself back to ''suspended'':
co1= coroutine.create(function()
print("in co1: state main:", coroutine.status(main))
print("in co1: state co1", coroutine.status(co1))
coroutine.yield()
end)
main= coroutine.running() -- get handle to main coroutine
coroutine.resume(co1)
print("in main: state main:", coroutine.status(main))
print("in main: state co1:", coroutine.status(co1))
Result:
in co1: state main: normal
in co1: state co1 running
in main: state main: running
in main: state co1: suspended
Alternatively a ''running'' couroutine can resume another coroutine (which is in state ''suspended''):
co1= coroutine.create(function()
coroutine.resume(co2)
end)
co2= coroutine.create(function()
print("in co2: state main:", coroutine.status(main))
print("in co2: state co1", coroutine.status(co1))
print("in co2: state co2", coroutine.status(co2))
end)
main= coroutine.running() -- get handle to main coroutine
coroutine.resume(co1)
Result:
in co2: state main: normal
in co2: state co1 normal
in co2: state co2 running
But a ''running'' couroutine cannot resume a non-suspended coroutine. Only coroutines in state ''suspended'' can be resumed:
co1= coroutine.create(function()
local res, err= coroutine.resume(main)
print(res, err)
end)
main= coroutine.running() -- get handle to main coroutine
coroutine.resume(co1)
Result:
false cannot resume non-suspended coroutine
==== Pipe-like Data exchange between Coroutines ==
#!/usr/bin/lua
co= coroutine.create(function()
while true do
io.stderr:write('co: waking up\n')
local inp= io.stdin:read()
io.stderr:write('co: read "'..inp..'", going to sleep\n')
coroutine.yield(inp)
end
end)
while true do
io.stderr:write('main: going to sleep\n')
local state, oup= coroutine.resume(co)
io.stderr:write('main: waking up\n')
print('> '..oup)
end
==== Misc ==
Coroutines can be used to convert a recursive data source/sink into an iterative one. See "Programming in Lua, 2nd editon, p.79"
===== Documentation Generation ==
[[http://stevedonovan.github.io/ldoc/topics/doc.md.html|Ldoc]] is preferred (over [[http://keplerproject.github.io/luadoc/manual.html#howto|LuaDoc]]).