RuneScape:Editing/Lua guide

From the RuneScape Wiki, the wiki for all things RuneScape
Jump to: navigation, search
Crystal saw.png
This project page is currently under construction.
The information contained within should not be considered fully accurate and/or complete.
Information icon.svg This guide is written for use with the Source Mode editor.
If you have not already switched to the Source Mode editor you can find out how to here.

Lua is a scripting language which is able to be used on the wiki. It allows more complex templates to be added than normal wikitext, and is usually easier to read than wikitext templates. Lua is implemented via an extension called Scribunto.

Important[edit source]

The primary lua reference guide for this is Extension:Scribunto/Lua reference manual on MediaWiki which gives a short description of how to use every function in the standard libraries. While this guide will cover some of this, it will focus more on how to use lua as a whole rather than explaining the use of every available function. For some more explanation on the standard library functions see RuneScape:Lua/Library functions.

Debug console[edit source]


Every module page has a debug console, which you can see by scrolling down past the editor. The console has access to the module (including unsaved changes) as variable p. If possible, it is a good idea to test things here before saving. At the very least, you can input =p and press enter; if you get anything other than 'table' output, you have things to fix. It is also a good place to test things like patterns and refresh yourself on simple facts. You can use mw.log() to print strings and mw.logObject() to dump tables to the console, if necessary.

Most of the examples given in his guide can be tested by copying their code into the console. Use shift+enter to write multiline code before submitting.

Lua basics[edit source]

First module[edit source]

All lua modules have to be written in the Module namespace - they cannot work if elsewhere. You can write test modules as a subpage of Module:Sandbox, i.e. Module:Sandbox/User:USERNAME (for example Module:Sandbox/User:<insert name here>). You can have additional subpages of this if you want, like your userpage (e.g. Module:Sandbox/User:<insert name here>/example).

All modules follow a basic pattern: declare and initialise a master variable, assign functions to that variable, return the variable. By convention, the master variable is named p.

-- <nowiki>
local p = {}

function p.hello( frame )
    return 'Hello, world!'
end

return p
-- </nowiki>

This code first declares the master variable p using keyword local, and initialises it to an empty table {}. It then assigns a function named hello to p, with argument frame which simply returns the string Hello, world!. p is then returned. The <nowiki> comments prevent Mediawiki from parsing the module as wikicode and creating rogue links, it has no effect on the function of the module itself and it's considered best practice to add them.

If you save this module to Module:Sandbox/User:USERNAME, you can then put {{#invoke:Sandbox/User:USERNAME|hello}} on any non-module page (e.g. your user space) to see the result (i.e. print Hello, world!).

This should be a reasonable demonstration on a simple module; most parts of this will be covered more later.

Basic syntax[edit source]

Some lexical conventions[edit source]


Identifiers (or names) in Lua can be any string of letters, digits, and underscores, not beginning with a digit. (e.g. i, j, a10, _i, longerNames)

You should avoid identifiers starting with an underscore followed by one or more upper-case letters (e.g., _VERSION); they are reserved for special uses in Lua. Usually, the identifier _ (a single underscore) is used for dummy variables, and identifiers starting with a _ followed by something else (e.g. _hello) is used for internal use of a package.

The following words are reserved; we cannot use them as identifiers:

and break do else elseif
end false for function if
in local nil not or
repeat return then true until
while

Lua is case-sensitive: and is a reserved word, but And and AND are two different identifiers.

Lua needs no separator between consecutive statements, but we can use a semicolon if we wish. Line breaks play no role in Lua's syntax; for instance, the following four chunks are all valid and equivalent:

a = 1
b = a * 2

a = 1;
b = a * 2;

a = 1; b = a * 2

a = 1 b = a * 2 -- ugly, but valid

You can assign values to multiple variables in one line using a comma separated list, this will be useful later for functions who return more than one variable:

a, b, c = 1, 2, 3 -- a == 1, b == 2, c == 3

Assignment expressions are evaluated before assigning, so a, b = b, a swaps the variable values.

Comments[edit source]


Lua comments come in two types: single-line and multi-line. Anything written in a comment is not executed as code.

Single-line comments begin with -- and last until the end of the line.

Multi-line comments begin with --[=[ and end with ]=]. Any number of equals signs can be between the brackets, including none, but the start and end need to have the same amount of equal signs.

Data types[edit source]


To determine a data type of a variable, the type( var ) function can be used; it will return the type as a string (e.g. 'nil').

nil[edit source]


nil is the absence of value. You can't use it as a table key. When converted to a string, it is 'nil'; when converted to a number, it is still nil; when converted to a boolean, it is considered false - but remember, nil is not equal to false.

nil crops up in all sorts of places, so you'll need to remember to nil-check code. This is usually just a simple if statement:

if variable then
    -- code if not nil
else
    -- code if nil
end

or, if variable could legitimately be false:

if variable ~= nil then
    -- code if not nil
else
    -- code if nil
end

boolean[edit source]


Boolean values are true and false. Their string representations are predictably 'true' and 'false'.

Notably, unlike some other languages, only false and nil are false values; the number 0 and the empty string are considered true.

string[edit source]


Strings are a series of characters.

Literal strings - strings in the code directly - are enclosed by either ' (apostrophes/single quotes) or " (double quotes). There is no difference between the two, though ' is preferred, unless actual apostrophes are used in the string. String literals have some escape sequences, notably:

  • \t tab
  • \n newline
  • \' quote
  • \" double quote
  • \\ backslash

String literals can also be assigned as 'long strings' using the same bracket notation of comments (long strings do not interpret escape sequences):

-- This long string
foo = [=[
bar\tbaz
]=]

-- is equivalent to this quote-delimited string
bar = 'bar\\tbaz\n'

Strings have a few functions associated with them, covered later.

Any other data type can be converted to a string using the tostring( value ) function. However, functions and tables simply return 'function' or 'table'[1], respectively.

We can get the length of a string using the length operator (denoted by #):

local a = 'hello'
mw.log( #a ) --> 5
mw.log( #'good bye' ) --> 8

Strings are immutable values, any operation that would change the value of the string returns an entirely new string with is operands unchanged.

You can compare strings with the == operator:

mw.log( 'hello' == 'hello' ) --> true

number[edit source]


Numbers are any numerical object - there is no 'int' and 'float' types, just number. They're internally represented as 64-bit floating point numbers ('double' in many other languages). Integer and decimal parts are separated with a period - 1234.56. Alternatively, E-notation can be used - 123.45e10.

NaN and infinities are handled correctly, but there are no literals available for them. If necessary, 0/0 generates a NaN, 1/0 generates positive infinity, and -1/0 generates negative infinity.

Other data-types can be converted to a number using the tonumber( value ) function. However, this is only really meaningful for string values - other data-types, and strings that can't be converted to a number, return nil.

table[edit source]


Tables are associative arrays, similar to JavaScript objects, PHP arrays, Perl hashes and somewhat to Java HashMaps. This means items are stored as key-value pairs

Tables in Lua are neither values nor variables; they are objects. You may think of a table as a dynamically-allocated object; programs manipulate only references (or pointers) to them. Lua never does hidden copies or creation of new tables behind the scenes.

Tables are created with curly braces - the empty table is {}. Upon creation, tables can be filled using the following formats, separated by commas:

  1. [expression1] = expression2 - the result of expression1 is the key and the result of expression2 is the value
    • Expression1 can be almost anything - a string with any characters, a number, a function, true/false, another table, etc.
  2. name = expression - equivalent to ['name'] = expression
    • Note that this method only works if name is a simple string without characters that may otherwise be interpreted - essentially, if it contains anything other than alphanumeric characters (plus underscore), you'll need to use type 1
  3. expression - equivalent to [auto incrementing number] = expression
    • This notation is used to initialise tables with array like data in it. The auto incrementing number starts at 1.

Storing under a number is separate to storing under the string representation of that number. You cannot store under nil.

The value stored can be of any type. Storing the value nil is equivalent to removing the key from the table.

For example:

local t = {
    a = 'value for key a',
    ['b'] = 'value for key b',
    ['c'] = variable_used_elsewhere,
    ['9'] = 'value for key string 9',
    [9] = 'value for key number 9',
    [true] = {'a sub-table stored under the value for boolean true'},
    [tonumber] = 'value for function tonumber',
    [tostring] = function_stored_under_tostring,
}

You can also treat the table as a sequence of expressions (a traditional array):

local t = {
    'a',
    'b',
    'c',
    'd',
    'e',
}

-- equivalent to
local t2 = {
    [1] = 'a',
    [2] = 'b',
    [3] = 'c',
    [4] = 'd',
    [5] = 'e',
}

You can mix these two methods, but it is not advised for code clarity and the auto incrementing keys will overwrite manually defined keys. The length operator '#' will return the length of the array assuming there are not gaps in the number. If there is a gap its output is inconsistent; you will have to iterate over the entire table with pairs to check for the highest index:

local t = {
    [1] = 'a',
    'b'
}
mw.log( t[1] )  --> 'b'

local t2 = {
    'a',
    'b',
    'c',
    [5] = 'd'
}
mw.log( #t2 )  --> 3 or 5

After creation, you can retrieve and assign to table values in two ways:

  • If the key is a simple string, you can use dot notation: t.a
  • In all cases you can use bracket notation: t['a']

Bracket notation is how you'd access an index stored in a variable, e.g.:

local t = {
    a = 'value for key a',
    ['b'] = 'value for key b',
}

mw.log( t['a'] )  --> 'value for key a'
mw.log( t.a )  --> 'value for key a'

local index = 'a'
mw.log( t[index] )  --> 'value for key a'

t[index] = 'new value'
mw.log( t[index] )  --> 'new value'

You can mix array like keys with normal key-value pairs. The length operator '#' will only return the length of the array part.

local t = {
    a = 'value for key a',
    ['b'] = 'value for key b',
    ['c'] = variable_used_elsewhere,
    ['9'] = 'value for key string 9',
    'a',
    'b',
    'c'
}

mw.log( #t )  --> 3
mw.log( t['a'] )  --> 'value for key a'
mw.log( t[1] )  --> 'a'

You can append a value to the end of an array using table.insert:

local t = {
    'a',
    'b',
}

table.insert( t, 'c' )
mw.log( t[3] )  --> 'c'

While debugging, you can use mw.logObject( table ) to print out he whole table in a readable format.

local t = {
    a = 'value for key a',
    ['b'] = 'value for key b',
    ['9'] = 'value for key string 9',
    [true] = {'a sub-table stored under the value for boolean true'},
    'a',
    'b',
    'c'
}

mw.log( t )  --> 'table'
mw.logObject( t )  --[=[
                        table#1 {
                            'a',
                            'b',
                            'c',
                            [true] = table#2 {
                                'a sub-table stored under the value for boolean true',
                            },
                            ['9'] = 'value for key string 9',
                            ['a'] = 'value for key a',
                            ['b'] = 'value for key b',
                        }
                    ]=]

Tables are stored by reference in variables. This means that a variable only stores where in memory a table is located; it doesn't store all the data of the table. As a result multiple variables can point to the same table and a table is only really removed from memory when all variables linking to the table are destroyed. You can create a real copy of a table using the mw.clone() function.

local t1 = { 1 }
local t2 = t1
local t3 = mw.clone( t1 )
t1[1] = 2
mw.log( t1[1] ) --> 2
mw.log( t2[1] ) --> 2
mw.log( t3[1] ) --> 1

t1 = nil
mw.log( t2[1] ) --> 2
t2 = nil -- Now the table is really destroyed, the cloned table t3 still exists
Numbered or string keys[edit source]

Numbered keys, or sequences, are used when the order of your data is important but the exact location of it in the sequence is not. For example if you want to store a sentence word by word, it is important that the order of the words is maintained but we don't care which word is at a specific index:

local t = { 'This', 'is', 'a', 'sentence', '.' }
local t2 = { 'This', 'is', 'a', 'longer', 'sentence', '.' }

In the above example it is important that the word This is before is, but we don't care that the word sentence is at index 4 or 5.

String keys are used when the location of data is important. For example, when we want to store certain properties about an item:

local ashes = {
    tradeable = true,
    equipable = false,
    stackable = false,
    value = 2,
    ['buy limit'] = 10000
}

function[edit source]


Functions can be treated as normal variables - assigned, overwritten, passed as arguments, created anonymously, etc.

Functions are created with function, and are covered more later.

Structures[edit source]


Operators[edit source]


The following operators are supported:

Arithmetic[edit source]
  • + addition
  • - subtraction (or negation)
  • * multiplication
  • / division
  • % modulo
  • ^ exponentiation

When attempting to do arithmetic on a string, Lua will try to convert the strings to numbers with the tonumber() function; it is still advised to call the tonumber() function explicitly for code clarity.

mw.log( 5 + '10' ) --> 15
Relational[edit source]
  • == equality
  • ~= non-equality
  • > greater than
  • < less than
  • >= greater than or equals
  • <= less than or equals
Logical[edit source]
  • and
  • or
  • not

and and or are short-circuit - foo() or bar() will only call bar() if foo() is false or nil.

Lua supports a conventional set of logical operators: and, or, and not. Like control structures, all logical operators consider both the Boolean false and nil as false, and anything else as true. The result of the and operator is its first operand if that operand is false; otherwise, the result is its second operand. The result of the or operator is its first operand if it is not false; otherwise, the result is its second operand:

mw.log( true and true ) --> true
mw.log( true and false ) --> false
mw.log( true and nil ) --> nil
mw.log( nil and true ) --> nil
mw.log( true and 4 ) --> 4
mw.log( 5 and 4 ) --> 4
mw.log( nil and 4 ) --> nil
mw.log( nil or 4 ) --> 4
mw.log( 4 or nil ) --> 4
mw.log( 4 or 5 ) --> 4

This is very useful to set default values in case a variable is nil.

local var = arg or 0 -- var == arg if arg is not nil, otherwise var == 0

local var = arg and math.pow( arg ) or 0 -- prevents 'math.pow' from throwing an error and store 0 in case arg == nil
Concatenation[edit source]

To join two strings together use ... When trying to concatenate a number and a string Lua will automatically convert the number to a string with the tostring() function.

mw.log( 'Hello' .. 'World' ) --> 'HelloWorld'

local var1 = 'Hello'
local var2 = 'World'
mw.log( var1 .. var2 ) --> 'HelloWorld'

mw.log( 'Hello' .. 10 ) --> 'Hello10'

If multiple concatenations are required, it may be faster and easier to use string.format() or table.concat() (see later).

Length[edit source]

The length operator is #, used #a. If a is a string, the length of the string in bytes is returned. If a is a sequence, returns the number of entries in the array.

If a is table but not a strict sequence, it returns a value n such that a[n] is not nil and a[n+1] is nil - but this may not be consistent, so avoid using this operator on non-sequence tables.

Precedence[edit source]

Order of operations is:

  1. ^
  2. not # - (negation)
  3. * / %
  4. + - (subtraction)
  5. .. (concatenation)
  6. < > <= >= ~= ==
  7. and
  8. or

Functions[edit source]


The basic function is:

local function ( variable_list )
    --block of code
end

Functions can be given names in two ways:

  1. by assigning it to a variable: do_this_thing = function ( variables ) ...
  2. by putting the name just before the variable list: function do_this_thing( variables ) ...

These also apply to tables:

  1. t.do_this_thing = function ( variables ) ...
  2. function t.do_this_thing( variables ) ...

Newly declared variables in a function should always be local (more on that later). Variables declared outside the function can be used within it. Functions can be declared within other functions.

Values are returned from the function with the return keyword.

local function addOne( num )
    local numplus1 = num + 1
    return numplus1
end

mw.log( addOne( 10 ) ) --> 11

Functions can return comma-separated lists that aren't inside an array.

local function addOneTwoThree( num )
    return num + 1, num + 2, num + 3
end

local a, b, c = addOneTwoThree( 10 ) --> a == 11, b == 12, c == 13
local d = addOneTwoThree( 10 ) --> d == 11, other two values thrown away
local _, e = addOneTwoThree( 10 ) --> e == 12, other two values thrown away, using the _ as a placeholder

When used in control structures (e.g. if addOneTwoThree( 0 ) then ... end), only the first value is used.

When a function f returns the result of another function g which returns more than one result, but we are only interested in the first result of g we can wrap it in parenteses:

local function g()
    return 1, 2
end

local function f()
    return ( g() )
end

local a, b = f() --> a == 1, b == nil

It is allowed to supply more or less arguments than the function accepts:

local function add( num, num2 )
    num = num or 0
    num2 = num2 or 0
    return num + num2
end

mw.log( add( 5 ) ) --> 5; num2 == nil
mw.log( add( 1, 2, 4 ) ) --> 3; 4 is ignored

Functions are stored by reference and can be passed to other variables, tables and function arguments.

local function addOne( num )
    return num + 1
end

local a = addOne
mw.log( a( 10 ) ) --> 11

local t = {
    hello = addOne
}
mw.log( t.hello( 10 ) ) --> 11

local function exec( func, num )
    return func( num )
end
mw.log( exec( addOne, 10 ) ) --> 11

A Lua file is executed from top to bottom, therefore functions must declared before they are used:

mw.log( addOne( 10 ) ) --> Error

local function addOne( num )
    return num + 1
end

mw.log( addOne( 10 ) ) --> 11

It is possible to use a local functions inside another function before it is declared using forward declaration:

local f  -- Forward declaration

local function addOne( num )
    return num + f()
end

function f()  -- No local keyword here since 'f' is already declared local higher up
    return 1
end

mw.log( addOne( 10 ) ) --> 11

Global functions are implicitly forward declared.

You can create recursive local functions.

-- This
local function factorial( num )
    if num <= 1 then
        return 1
    else
        return num * factorial( num - 1 )
    end
end

-- Is a syntactic sugar for
local factorial
factorial = function( num )
    if num <= 1 then
        return 1
    else
        return num * factorial( num - 1 )
    end
end

-- This does not work
local factorial = function( num )
    if num <= 1 then
        return 1
    else
        return num * factorial( num - 1 )  -- The variable 'factorial' is not yet defined so it tries to call the global variable instead of the local one
    end
end

If a function is called with a single table as argument, it is possible to slightly shorten its notation by using {} instead of ():

local function sum( t )
    ...
end

sum( { name = value } )
sum{ name = value }

The output of one function can be used as the input of another. Take for example the following code:

local function hello( num )
    mw.log( 'hello' )
    return num
end

local function world( num )
    mw.log( 'world' )
    return num * 2
end

local result = world( hello( 10 ) ) --> result == 20
--[=[ Prints: 'hello'
              'world'
]=]

The mw.log statements allow us to track which function is executed first. Looking at the printed text, it is clear that first the function hello is executed, printing its name and returning the value 10. This value is then passed into function world which prints its own name and returns the value 20, which is then stored in the variable result. So result only gets to see the output of the very outer function and never gets to see anything that happened before the world function ended.

The standard libraries are just functions stored in tables, e.g. math.sin() means, in table math executed the function stored at key sin.

Lua also offers a special syntax for object-oriented calls, the colon operator. An expression like o:foo() calls the method foo in the object o. (more on this later)

Varargs[edit source]


Varargs (variable arguments) can be used by functions that should work on any number of passed arguments, but the number is not known ahead of time. They are represented by using ... in the function:

local function sum( ... )
    local t = { ... }
	local s = 0
	for i, v in ipairs( t ) do  -- More on this later
	  s = s + v
	end
	return s
end

mw.log( sum( 4, 5, 6, 7, 8, 9 ) ) --> 39

In the function declaration, ... has to be the last 'parameter'. Additional parameters can come first, though. Inside the function, the list of varargs is referenced by .... It is common, as in the example above, to turn the varargs into a sequence by wrapping it in curly braces. Then it can be iterated over by ipairs.

Control structures[edit source]


Control structures come in two main flavours: conditional and loops.

if[edit source]

If statements are of the generic form:

if expression1 then
    --block if expression1 is true
elseif expression2 then
    --block if expression1 is false and expression2 is true
else
    --block if all expressions are false
end

The elseif and else sections can be omitted if they are not needed.

Each expression should result in a boolean, but remember that all values can evaluate to a boolean: false and nil are false, and all other values are true (including 0, the empty string, and the empty table).

e.g.:

local var = 1
if var == 0 then
    mw.log( 'if' )
elseif var > 10 then
    mw.log( 'elseif' )
else
    mw.log( 'else' )
end
--> 'else'
while[edit source]

While loops repeat a given block until an expression returns false.

while expression do
    --block to evaluate while expression is true
end

e.g.:

local i = 10
local fib = 1
while i > 0 do
    fib = fib + fib
    i = i - 1  -- Very important, without this the loop would go on forever
end
mw.log( fib ) --> 1024
repeat[edit source]

Repeat is essentially the reverse of a while loop - it repeats the block until the expression is true.

repeat
    --block to evaluate while expression is false
until expression
for[edit source]

For loops repeat a number of times. There are two forms of for - the second of which is under the for-each heading below.

for name = start, stop, step do
    --block to evaluate
end

This version declares the local variable name, assigns it value start, then iterates the block, adding step to name each iteration until name exceeds stop. All three values can be expressions that evaluate to numbers.

step may be omitted to use the default value of 1.

For name the values i and j are commonly used.

e.g.:

local var = 0
for i = 10, 0, -1 do
    var = var + i
end
mw.log( var ) --> 55
for-each[edit source]

For each loops are primarily for table iteration. They require iterator functions, which are handily provided by the pairs and ipairs functions. You can use custom functions, but that is beyond the scope of this guide.

for k, v in pairs( t ) do
    --block to evaluate
end

This loop then iterates over the block, setting k to the key and v to the associated value for each entry in the table.

The pairs() function provides iteration over all values in the tables. The order is not reliable or consistent, so if order is important you may need another way.

The ipairs() function provides iteration over sequences. It will always iterate in the order of the sequence, if the sequence contains gaps it will stop at the first gap. All non number keys are ignored.

To order a non-sequence, you can use the opairs function of Module:Iterator. If you want a more controlled order, you can set a manual order in a sequence (or iterate over the table a few times, first to get the keys/values in a sequence and order them with table.sort, then to iterate in the order).

e.g.:

local t = {
    a = 'a',
    b = 'b',
    'c',
    'd'
}

for k, v in pairs( t ) do
    mw.log( 'key: ' .. k .. '; value: ' .. v )
end

for i, v in ipairs( t ) do
    mw.log( 'index: ' .. i .. '; value: ' .. v )
end

--[[
key: 1; value: c
key: 2; value: d
key: a; value: a
key: b; value: b
index: 1; value: c
index: 2; value: d
]]

Scoping[edit source]


In computer programming, the scope of a name binding—an association of a name to an entity, such as a variable—is the region of a computer program where the binding is valid: where the name can be used to refer to the entity. Such a region is referred to as a scope block. In other parts of the program the name may refer to a different entity (it may have a different binding), or to nothing at all (it may be unbound).[2]

In short, scope is the concept where only parts of your program have access to certain variables. e.g.:

for i = 1, 10 do
    ...
end

mw.log( i ) --> nil, we don't have access to the variable 'i' outside the for loop

With scoping it is possible to re-use the same variable name in different scope blocks; this is not recommended for code clarity but it explains the concept well.

local var = 'top level'

if true then
    mw.log( var ) --> 'top level', we can access variables defined in a higher level scope
    local var = 'level 1' -- This variable is restricted to the scope of the first if statement, the top level variable is unchanged but we can no longer access it from within this scope
    mw.log( var ) --> 'level 1'
    
    if true then
        mw.log( var ) --> 'level 1'
        local var = 'level 2'
        mw.log( var ) --> 'level 2'
    end
    
    mw.log( var ) --> 'level 1'
end

mw.log( var ) --> 'top level'

All control structures (e.g. if, for, ...) and functions create a new scope.

Global or local[edit source]


Global variables do not need declarations; we simply use them. It is not an error to access a non-initialized variable; we just get the value nil as the result:

mw.log( b ) --> nil
b = 10
mw.log( b ) --> 10

If we assign nil to a global variable, Lua behaves as if we have never used the variable:

b = nil
mw.log( b ) --> nil

Lua does not differentiate a non-initialized variable from one that we assigned nil. After the assignment, Lua can eventually reclaim the memory used by the variable.

Global variables are stored in the _G table. Global variables always have global scope; this means once it is created every scope has access to it until it is manually deleted:

mw.log( var ) --> nil

local function test()
    var = 'level 1'  -- Global variable
end

test()
mw.log( var ) --> 'level 1'

We can localise a variable to a given scope with the local keyword. Doing so has a few advantages:

  • Speed. It is faster to access a local variable.
  • Memory saving. Once we exit the scope in which the local variable was created it is automatically destroyed.
  • Preventing naming collisions or unexpected behaviour.
function main()
	a = 1  -- A global variable
    local b = 2  -- A local variable
	test()
	mw.log( a )  --> 10
    mw.log( b )  --> 2
end

function test()
	a = 10  -- Also a global variable
    local b = 20
end

The above example shows the problem with global variables. In both functions the variable a points to the same global variable, so the function test unintentionally changes the internal variable of the function main. For this reason you should always localise your variables.

It is also a good idea to localize your functions to prevent you from accidentally overwriting a function with the same name inside a module you require, demonstrated by the following example:

Module:Hello
-- <nowiki>
local p = {}

function printName()  -- Global function
    mw.log( 'Hello' )
end

local function printName2()  -- Local function, this means it can only be used inside this module
    mw.log( 'Hello' )
end

function p.name()
	printName()
end

function p.name2()
    printName2()
end

return p
-- </nowiki>
Module:World
-- <nowiki>
local hello = require( 'Module:Hello' )  -- More about this later
local p = {}

function printName()  -- Global function
    mw.log( 'World' )
end

local function printName2()  -- Local function
    mw.log( 'World' )
end

function p.main()
    printName() --> 'World'
    printName2() --> 'World'
    hello.name() --> 'World'
    hello.name2() --> 'Hello'
end

return p
-- </nowiki>

The function printName in Module:Hello is overwritten by the function in Module:World.

Scribunto[edit source]

In Scribunto modules are called using the #invoke parser function with the following syntax: {{#invoke:<module name>|<function to call>|<arg>}}

  • <module name> - name of the module without the Module: namespace prefix.
  • <function to call> - the function you want to call must be inside a table that is returned by the module; usually this table is called p. Functions outside this table can't be accessed by #invoke.
  • <args> - same style of argument you can pass to templates. This is optional.

Frames[edit source]


So far, the above has been about core lua. Now, we need to consider how this applies to the wiki - the most important of which is how modules are called.

Think of this as some sort of layer cake. Imagine the following example template and module are real and called from this page (you can test it yourself by copying the example to your userspace and sandbox module; see #First_module):

{{Mytemplate|text|3|separator=;}}: text;text;text;suffix

Template:Mytemplate

{{#invoke:Mymodule|main|last=suffix}}

Module:Mymodule
-- <nowiki>
local p = {}

function p.main( frame )
    local invokeArgs = frame.args
    local args = frame:getParent().args  -- Template args are customary stored in the 'args' variable

    mw.log( args[1] ) --> 'text'
    mw.log( args[2] ) --> '3'
    mw.log( args.separator ) --> ';'
    mw.log( invokeArgs.last ) --> 'suffix'

    local text = args[1] or ''  -- Set default value in case no parameters were passed to the template
    local multiplier = tonumber( args[2] ) or 0
    local separator = args.separator or ''
    local last = invokeArgs.last or ''

    local res = ''

    for i = 1, multiplier do
        res = res .. text .. separator
    end

    return res .. last
end

return p
-- </nowiki>
  1. Right here, this page calls Template:Mytemplate with parameter 1 set to text, parameter 2 set to 3 and parameter separator set to ;.
  2. This then loads up Template:Mytemplate and passes the parameters. In a normal wikicode template, this is where it does parsing - possibly with other templates helping - and returns up to the page. But, as Mytemplate uses #invoke, it moves down to the lua layer.
  3. The #invoke called Module:Mymodule's main function, with an additional parameter of last set to suffix. So, it executes the function p.main. Once it has the result, it passes it back up to the template, and the template passes it up to the page.

Now, how does Module:Mymodule get both sets of parameters? This is from the main variable passed to the function, the frame. Frames are tables with a few functions and values.

  1. The #invoke call generates a frame for itself, with the notable part being the args value. Since this #invoke specifies a parameter, it gets args = {last = 'suffix'}.
  2. The #invoke also generates a frame for the template call, which has the args value set to args = { [1] = 'text', [2] = '3' }. This frame is set as a parent to the previous frame.
  3. The main function in Module:Mymodule is passed the child frame. The parent can be accessed using the getParent() function.

Thus, in this example:

  • frame.args = { last = 'suffix' }
  • frame:getParent().args = { [1] = 'text', [2] = '3' }
Notes
  • Unnamed template parameters are passed as a sequence.
  • Argument values are always strings.
  • getParent() will return nil if there is no parent - e.g. if #invoke is directly used, or in the debug console.
Debug console

When we call the main function from the debug console, we would have to create our own dummy frame. To make it easier to use the debug console it is customary to split the bulk of the code from the frame using a _main function. e.g.:

-- <nowiki>
local p = {}

function p.main( frame )
    local invokeArgs = frame.args
    local args = frame:getParent().args
    
    return p._main( invokeArgs, args )
end

function p._main( invokeArgs, args )
    local text = args[1] or ''
    local multiplier = tonumber( args[2] ) or 0
    local separator = args.separator or ''
    local last = invokeArgs.last or ''

    local res = ''

    for i = 1, multiplier do
        res = res .. text .. separator
    end

    return res .. last
end

return p
-- </nowiki>

Importing[edit source]


There are a number of modules already written which provide functionality you may need. There are two ways of accessing this.

Requiring modules[edit source]


The core function require is the general-purpose module importer. To require a module, simply use the full page name of the module you need as the only parameter. This returns whatever the module returns (usually the p table, but some modules return a single function). You can then use the functions/data of the module as normal.

-- import [[Module:Paramtest]]
local paramtest = require( 'Module:Paramtest' )

-- use the has_content function of Paramtest
paramtest.has_content( args.foo )

If you only need to use one function from the module, you can assign that function to a local variable:

local hc = require( 'Module:Paramtest' ).has_content
hc( args.foo )

You can import any module, but ones designed with common functions to be imported are called 'helper modules'; you can see a list of these at RuneScape:Lua/Helper modules.

Loading data[edit source]


If you're loading data - not functions - it is more efficient to use the Scribunto function mw.loadData. This functions identically to require, except for a few conditions:

  • The module is evaluated once per page load, instead of once per #invoke (thus making it more efficient when repeatedly used on a page)
  • The module must return a table, and that table (and all subtables) can only contain numbers, strings, booleans, or more tables - notably, functions are not allowed. The keys of all tables must be booleans, numbers, or strings.
  • The table returned is read-only
local data = mw.loadData( 'Module:Experience/elitedata' )

Data is usually stored as a subpage of the primary module. Some examples of data pages:

If you're using data, you should absolutely try to use mw.loadData over require or putting it into the module directly. The only exception is when you need to acces this data a lot in a thight loop because a side effect of making the table read only is that its access time significantly increases (times 12 if reading a key that exists, times 6 otherwise).

Documentation[edit source]


Documentation of a module can be edited on the /doc subpage of the module. It is good practice to always add a doc page with at least {{Documentation}} or {{No documentation}} on it. Those templates will then generate a dependency list based on the content of your module which makes it a lot easier to find out which modules rely on each other.

See some examples of this:

Advanced lua[edit source]

Tail calls[edit source]

A feature of functions in Lua is that Lua does tail-call elimination. A tail call is a goto dressed as a call. A tail call happens when a function calls another as its last action, so it has nothing else to do. For instance, in the following code, the call to g is a tail call:

function f( x )
    x = x + 1
    return g( x )
end

After f calls g, it has nothing else to do. In such situations, the program does not need to return to the calling function when the called function ends. Therefore, after the tail call, the program does not need to keep any information about the calling function on the stack. When g returns, control can return directly to the point that called f. Some language implementations, such as the Lua interpreter, take advantage of this fact and actually do not use any extra stack space when doing a tail call. We say that these implementations do tail-call elimination.

Because tail calls use no stack space, the number of nested tail calls that a program can make is unlimited. For instance, we can call the following function passing any number as argument:

function foo( n )
    if n > 0 then 
        return foo( n - 1 ) 
    end
end

It will never overflow the stack.

A subtle point about tail-call elimination is what is a tail call. Some apparently obvious candidates fail the criterion that the calling function has nothing else to do after the call. For instance, in the following code, the call to g is not a tail call:

function f( x ) 
    g( x )
end

The problem in this example is that, after calling g, f still has to discard any results from g before returning. Similarly, all the following calls fail the criterion:

return g( x ) + 1 -- must do the addition
return x or g( x ) -- must adjust to 1 result
return ( g( x ) ) -- must adjust to 1 result

In Lua, only a call with the form return func( args ) is a tail call. However, both func and its arguments can be complex expressions, because Lua evaluates them before the call. For instance, the next call is a tail call:

return x[i].foo( x[j] + a*b, i + j )

Tail calls use no stack space, this means Lua does also not keep any information about the name of the function they call or at which line the call happened. Therefor the info Lua can give in a stack traceback is limited, in fact the only info it can give is that a tail call happened and nothing more. For example take the following code:

 1 local p = {}
 2 
 3 function p.foo()
 4     return p.bar() -- Tail call one
 5 end
 6 
 7 function p.bar()
 8     return p.buz() -- Tail call two
 9 end
10 
11 function p.buz()
12     return 1 + nil -- This will throw an error
13 end
14 
15 return p

If we call function foo using p.foo() in the console, we get:

Lua error at line 12: attempt to perform arithmetic on a nil value.

Backtrace:

    Module:Sandbox/User:USERNAME:12: ?
    (tail call): ?
    (tail call): ?
    console input:7: ?
    [C]: ?

In comparison when not using tail calls:

 1 local p = {}
 2 
 3 function p.foo()
 4     p.bar()
 5 end
 6 
 7 function p.bar()
 8     p.buz()
 9 end
10 
11 function p.buz()
12     return 1 + nil -- This will throw an error
13 end
14 
15 return p

If we call function foo using p.foo() in the console, we get:

Lua error at line 12: attempt to perform arithmetic on a nil value.

Backtrace:

    Module:Sandbox/User:USERNAME:12: in function "buz"
    Module:Sandbox/User:USERNAME:8: in function "bar"
    Module:Sandbox/User:USERNAME:4: in function "foo"
    console input:7: ?
    [C]: ?

Generic for[edit source]

Closures[edit source]

Lexical scoping[edit source]


Metatables[edit source]

Object oriented programming[edit source]

Performance optimizations[edit source]

Function profiling[edit source]


Semantic mediawiki[edit source]

See also[edit source]

Footnotes[edit source]

  1. ^ The behaviour of the tostring() function on tables can be changed using the __tostring metatable index. More on that later.
  2. ^ https://en.wikipedia.org/wiki/Scope_(computer_science)