User:Gaz Lloyd/lua

From the RuneScape Wiki, the wiki for all things RuneScape
Jump to: navigation, search
Crystal saw.png
This 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 | edit source]

The primary lua reference guide for this is Extension:Scribunto/Lua reference manual on MediaWiki. While this guide will cover some of this, it is highly recommended to refer to that guide in addition to this one.

Debug console[edit | 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 Module:Logger to dump tables to the console, if necessary.

Lua basics[edit | edit source]

First module[edit | 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:Gaz Lloyd). You can have additional subpages of this if you want, like your userpage (e.g. Module:Sandbox/User:Gaz Lloyd/inv).

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.

local p = {}

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

return p

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 (more on that later) which simply returns the string Hello, world!. p is then returned.

If you save this module to Module:Sandbox/User:USERNAME, you can then put {{#invoke:Sandbox/User:USERNAME|hello}} on any non-module page 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 | edit source]

Comments[edit | 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.

Data types[edit | edit source]

To determine a data type of a variable, the type(var) function can be used.

nil

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

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

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", respectively.

number

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 datatypes can be converted to a number using the tonumber(value) function. However, this is only really meaningful for string values - other datatypes, and strings that can't be converted to a number, return nil.

table

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 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

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:

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 subtable 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):

t = {
    "a",
    "b",
    "c",
    "d",
    "e",
}

-- equivalent to
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.

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.:

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

index = "a"
print(t[index]) -- prints "value for key a"

t[index] = "new value"
print(t[index]) -- prints "new value"

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

t = {
    "a",
    "b",
}

table.insert(t, "c")
print(t[3]) -- prints "c"
function

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 | edit source]

Operators[edit | edit source]

The following operators are supported:

Arithmetic
  • + addition
  • - subtraction (or negation)
  • * multiplication
  • / division
  • % modulo
  • ^ exponentiation
Relational
  • == equality
  • ~= non-equality
  • > greater than
  • < less than
  • >= greater than or equals
  • <= less than or equals
Logical
  • and
  • or
  • not

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

Concatenation

To join two strings together use ...

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

Length

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

Order of operations is:

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

Functions[edit | edit source]

The basic function is:

function (variable_list)
    --block of code
end

Functions can be given names in two ways:

  1. by assigning it to a variable: local 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) ...

Inside a function, newly declared variables are local to the function - they cannot be accessed from outside the function, and are thrown away when the function is finished. 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.

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

addOne(10) -- = 11

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

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.

Variable declaration and assignment[edit | edit source]

Variables are declared with the local keyword. They can be declared with a specified initial value, or without (where they have nil value).

As you may have already gathered, assignment is performed by =. Assignments can be done as a list, e.g. a, b, c = 1, 2, 3.

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

Varargs[edit | 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:

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

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 | edit source]

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

if

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).

while

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

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

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

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 = init, limit, step do
    --block to evaluate
end

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

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

for-each

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 = pairs(t) do
    --block to evaluate
end

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

The pairs() function provides iteration over all 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, but won't work properly on non-sequences.

To order a non-sequence, you can use the opairs function of Module:Utils. 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).

Scribunto[edit | edit source]

Frames[edit | 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. For this, we'll use the example of this page.

  1. Right here, this page is going to use {{GEP|Abyssal whip|5}}: 529680. That's a call to Template:GEP with parameter 1 set to Abyssal whip and parameter 2 set to 5
  2. This then loads up Template:GEP and passes the parameter. In a normal wikicode template, this is where it does parsing - possibly with other templates helping - and returns up to the page. But, as GEP uses #invoke ({{#invoke:Exchange|price|format=no}}, to be exact), it moves down to the lua layer
  3. The #invoke called Module:Exchange's price function, with an additional parameter of format set to no. So, it executes the function p.price, using other dependencies - how it works doesn't matter. Once it has the value, it passes it back up to the template, and the template passes it up to the page.

Now, how does Module:Exchange get both sets of parameters? This is from the main variable passes 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={format='no'}.
  2. The #invoke also generates a frame for the template call, which has the args value set to args={['1']='Abyssal whip', ['2'] = '5'}. This frame is set as a parent to the previous frame.
  3. The price function in Module:Exchange is passed the child frame. The parent can be accessed using the getParent() function.

Thus, in this example:

  • frame.args = {format='no'}
  • frame:getParent().args = {['1']='Abyssal whip', ['2'] = '5'}
Notes
  • Argument keys are always strings - remember that args[1] and args['1'] are two different keys! Remember this when using unnamed parameters.
  • Argument values are also always strings.
  • getParent() will return nil if there is no parent - e.g. if #invoke is directly used, or in the debug console.

Importing[edit | 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 | 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 | 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.

Library functions[edit | edit source]

The core lua library and the Scribunto library have a large amount of functions to use. Here, we'll only cover notable uses of them. See the documentation for full details.

In all below descriptions, optional arguments have square brackets around them with default values after a pipe, if appropriate. i.e. foo(x, [y|1]) means function foo has one required argument, and the second argument is optional and will default to 1.

Lua library functions[edit | edit source]

The lua library is the core library of the lua language itself. You can read more about each function in the documentation; this is just a quick summary and does not include all of the functions.

Global functions[edit | edit source]

Functions that are so core that they are part of the global package.

Function Description Example
nil]) Used to iterate over tables. Returns the next key after key, or the first if key is nil. Returns nil at the end of the table. Order is not specified - use ipairs for an order.
local k = next(t)
while k do
  local v = t[k]
  -- block
  k = next(t, k)
end
pairs(table) Returns an iterator function for use in a for-each loop to iterate over all the keys in the array. Order is not specified.
local t = {a=1, b=3, hello="world"}
for i,v in pairs(t) do
  -- block
end
ipairs(table) Returns an iterator function for use in a for-each loop to iterate over all the items in a sequence, in numerical order.
local t = {"a", "b", "hello", "world"}
for i,v in ipairs(t) do
  -- block
end
pcall(f, ...) Runs function f in protected mode. If f results in an error, returns false and the error message; otherwise returns true and the normal return values of the function.

Typically used to check the GE price of something safely, if needed.

-- returns true, [GE price of an abyssal whip]
pcall(exg.price, "Abyssal whip")

-- returns false, "package.lua:80: module `Module:Exchange/Foobarbaz' not found"
pcall(exg.price, "Foobarbaz")
xpcall(f, errhandler) The same as pcall, except if there is an error, the error message is passed to the errhandler function first. It also does not pass any values to f.
type(val) Returns the type of val, as a string.
type({})
type(1)
typr("1")
type(type)
10]) Attempts to convert val into a number with the specified base (2 to 36).
tonumber("2")
tostring(val) Converts val into its string representation.
tostring(2)
tostring({})
tostring(tostring)
1], [j|#table]) Returns the values from the table (sequence) as a comma-separated list suitable for function calls or assignment. i.e., it removes the curly braces from the sequence. Non-sequence behaviour not defined.
local t = {"1001", 2}
tonumber(unpack(t))

Math functions[edit | edit source]

Functions associated with mathematical operations.

Function Description Example
math.abs(x) Absolute value of x.

math.pi The number pi.
math.pi * radius ^ 2
math.sin(x)
math.cos(x)
math.tan(x)
math.asin(x)
math.acos(x)
math.atan(x)
Trigonometric functions of x, in radians.
math.sin(math.pi)
math.sinh(x)
math.cosh(x)
math.tanh(x)
Hyperbolic functions of x.
math.sinh(1)
math.deg(x) Converts x from radians to degrees.
math.deg(math.pi)
math.rad(x) Converts x from degrees to radians.
math.rad(90)
math.floor(x) Rounds x down to the next integer (always toward negativity)
math.floor(3.5) -- 3
math.floor(-3.5) -- -4
math.ceil(x) Rounds x up to the next integer (always away from negativity)
math.ceil(3.5) -- 4
math.ceil(-3.5) -- -3
math.exp(x)
math.exp(10)
math.log(x) Natural logarithm of x.
math.log(10)
math.log10(x) Base-10 logarithm of x.
math.log10(10)
math.max(x, ...) Returns the maximum value of all the supplied arguments. NaNs are ignored, except for x.
math.max(1,4,6,10,99)
math.min(x, ...) Returns the minimum value of all the supplied arguments. NaNs are ignored, except for x.
math.min(1,4,6,10,99)
math.modf(x) Returns two values: the integer part of x and the fractional part of x.

math.random([x], [y]) Generates a psuedorandom number: a real number between 0 (inclusive) and 1 (exclusive); an integer 1 to x (both inclusive); or an integer between x and y (both inclusive) - with 0, 1, or 2 parameters specified respectively.
math.random(1,10)
math.randomseed(x) Sets the random seed to x.
math.randomseed(1337)

Operating system library[edit | edit source]

Functions to do with the operating system's time.

Function Description Example
os.clock() Approximate number of seconds of CPU time used by the program so far.
local t1 = os.clock()
-- code
local t2 = os.clock()
mw.log('Started at '..t1..', finished at '..t2)
'%c'], [time]) Formats the given time (default now) with the specified format. Format is either '*t' for a table, or follows strftime.
os.date('%H:%M:%S %e %B %Y')
os.time([t]) Returns the unix timestamp for the specified table, or now.
os.time()
os.difftime(t2, t1) Returns the number of seconds from t1 and t2.
os.difftime()


Global functions[edit | edit source]

Function Description Example

Scribunto library[edit | edit source]

The Scribunto library are the functions provided by the extension, mostly related to wiki editing.









math[edit | edit source]

Almost entirely common mathematical functions.

  • math.abs(x) - absolute value
  • math.cos(x), math.sin(x), math.tan(x),
math.acos(x), math.asin(x), math.atan(x) - trigonometric functions (x in radians)
    • math.rad, math.deg - degrees to radians, radians to degrees
  • math.cosh(x), math.sinh(x), math.tanh(x) - hyperbolic functions
  • math.log, math.log10 - natural and base-10 logarithms
  • math.exp(x) - e^x
  • math.huge - positive infinity
  • math.pi - pi
  • math.floor(x) - smallest integer >= x
  • math.ceil(x) - largest integer <= x
  • math.max(x, ...) - the largest value of the passed arguments
  • math.min(x, ...) - the smallest value of the passed arguments
  • math.modf(x) - returns 2 numbers - the integer part and the fractional part of x
  • math.sqrt(x) - square root
  • math.random([x],[y]) - random number with value:
    • if no arguments used, returns a real number between 0 (inclusive) and 1 (exclusive).
    • if one argument, returns an integer between 1 and x (both inclusive)
    • if two arguments, returns an integer between x and y (both inclusive)
    • the seed can be changed using math.randomseed(x)

os[edit | edit source]

  • os.clock() - CPU time used by the program (in seconds)
  • os.date([format|'%c'], [time]) - Formats time (unix timestamp - defaults to now) according to format. Format is either *t, to return a table (with fields year, month, day, hour, min, sec, wday, yday, and isdst), or specified by strftime.
  • os.time([date]) - return the unix timestamp for the specified table (same fields as above), or now if not specified.

string[edit | edit source]

Almost all string functions can be done using syntactic sugar:

local str = '%s'
-- normal usage
string.format(str, 'foo')

-- alternative
str:format('foo')

-- can also be done with literals, if they're surrounded in brackets
('%s'):format('foo')

Several functions have versions for unicode strings in the mw.ustring library. These won't be covered here.

  • string.lower(str) - returns of a lowercase copy of the string
  • string.upper(str) - returns of an uppercase copy of the string
  • string.rep(str, n) - returns n copies of str (as one string)
  • string.reverse(str) - (bytewise) reverses the string
  • string.sub(str, i, [j|-1]) - returns a substring of str from i to j. Both can be negative - where -1 is the end of the string (and the default value of j).
Patterns

Patterns are lua's equivalent of regular expressions. They're more limited, but they do the job well enough. See documentation for full information. Lua's base patterns operate on ASCII strings - use ustring functions and patterns for unicode strings.

If you are familiar with Perl-compatible regular expressions (PCRE), the primary differences are:

  • % is the escape characters
  • . always matches all characters including newlines (single-line mode always on)
  • No case-insensitivity
  • No alternation (|)
  • Cannot use * + ? - on capture groups
  • No generalised finite quantifier ({m,n})
  • All quantifiers are greedy, except - (equivalent to *?)
  • No equivalent to \b or lookaheads/lookbehinds/non-capture groups

Lua pattern character classes represent a group of characters:

  • . - all characters
  • %a - all letters
  • %c - all control characters
  • %d - all digits
  • %l - all lowercase letters
  • %p - all punctuation characters
  • %s - all whitespace characters
  • %u - all uppercase letters
  • %w - all alphanumeric characters
  • %x - all hexadecimal digits
  • %z - ASCII NUL character
  • All of the above, if the uppercase version is used (e.g. %A) means all characters not in that class.
  • [set] represents all characters in the set, e.g. [abc] matches a or b or c. Sets can contain character classes.
  • [^set] represents the characters not in the set.


The following string functions use patterns:

  • string.find(str, pattern, [init|1], [plain|false]) - find the first match of pattern in str. If a match is found, the start and end offsets where it is found in str are returned; if not found, nil returned. If the pattern has captures, these are also returned after the offsets.
    init specifies where to start looking, and can be negative to count backwards from the end of the string.
    If plain is true, patten matching is disabled (init must be specified).
  • string.gmatch(str, pattern) - returns an iterator function that returns the captures present in pattern (or the whole match if there are no captures). ^ is literal in this function.
  • string.gsub(str, pattern, repl, [n]) - returns a copy of the string where all (or the first n, if specified) occurences of pattern are replaced by repl. The second return value is the total number of matches.
    If repl is a string, its value is used for replacement. %n, where n is 1 to 9 can be used to refer to captures in pattern. %0 is the entire string and %% is a single %.
    If repl is a table, the first capture (or the whole match if there's no capture) is used as the key. A nil value (no matching key) means no replacement.
    If repl is a function, the function is called for every match with all captures passed in order as its arguments (or the entire match as its only argument, if no captures). Returning nil means no replacement.
  • string.match(str, pattern, [init|1]) - finds the first match of pattern in string. If found, returns the captures from the pattern (the whole match if no patterns specified). If not found, returns nil.
    • This differs from string.find as find returns the offsets of the match and captures, while string.match returns the captures/match itself.