MediaWiki Lua for non-Lua programmers

, .

This is a blog post to “onboard” people to Lua programming for MediaWiki (e.g. on Wikipedias or Wikimedia Commons). The target audience is people who already know some programming, but aren’t very familiar with Lua specifically. The goal is not that you’ll be a professional Lua programmer, but that you’ll be aware of some of the more important aspects, and be able to write some useful Lua code with a good portion of trial-and-error and some googling. I’ll be comparing Lua to several other programming languages, in the hope that you’re familiar enough with at least some of them to understand what I mean.

Some syntax

No semicolons. No braces – blocks are usually introduced by a relevant keyword (e.g. function name(args), if cond then) and always end with the keyword end. Strings concatenate with .., not . like in PHP/Perl or + like in Java(Script)/Python. The not-equal operator is ~=, not != like in most other languages. Comments begin with --, like in Haskell; # (PHP, Perl, Python) is the length operator instead, // and /* */ (C, C++, Java, JS) are syntax errors.

Local variables are declared with the keyword local, otherwise all variables are global (even inside a function!) – in other words, you’ll want to write local a lot of the time. (If you test your module using ScribuntoUnit, it will yell at you if you forgot a local somewhere and accidentally leaked a global variable.)

Tables

Tables are a fairly fundamental data structure in Lua. They’re similar to arrays in PHP, in that they’re a single data structure that fulfills two purposes which many other languages have as separate data structures: lists/arrays/vectors, and maps/dictionaries/hashes. In Lua as in PHP, a list is just a map with sequential integer keys; unlike in PHP and most other languages, though, list indexes start from 1, not 0. (Nothing stops you from using 0 as an index manually, but it will make everything more confusing. The syntax { "first", "last" } creates a table using indexes 1 and 2, not 0 and 1.)

Unlike in JavaScript and PHP, and more like in Python and some other languages, table keys (indexes) are not limited to strings or numbers: any other value, including a table, can be used as a key. That said, numbers and strings are more common, and strings have shortcut syntax: someTable.someKey is equivalent to someTable["someKey"].

Tables also fulfill the role of objects, including methods, which we’ll talk about later. There’s no special syntax to define classes or create instances of a class. There are “metatables”, which can customize various behavior of a table, kind of like the prototype of an object in JavaScript, but I’ll just mention that here and let you look it up if you think you need it, since I don’t think it usually comes up in normal module programming.

Here’s an example table:

local someTable = {
    -- normalKey: "normalValue" in JavaScript,
    -- i.e. uses the string "normalKey" as the key
    normalKey = "normalValue",
    -- "otherKey": "otherValue" in Python
    -- [ "otherKey" ]: "otherValue" in JavaScript
    -- ("otherKey" can be any expression)
    [ "otherKey" ] = "otherValue",
    -- [ "a", "b", "c" ] in Python, JavaScript, etc.
    "a", "b", "c", -- implicit indexes 1, 2, 3
}

For more information on tables, including a longer syntax example (scroll down a bit), see the Scribunto Lua reference manual § table.

nil

nil is Lua’s version of “null/none/nothing”. (Like in Lisp!) For example, it’s what you get when accessing a table key that doesn’t exist. (Incidentally, there’s actually no syntax to delete a table entry – you just set it to nil instead: someTable.someKey = nil.)

Functions

Unlike in most other languages, functions can return multiple values. A function can return "first", "second", "third", and a caller can assign local a, b, c = someFunction(), and a will be "first", b will be "second", and c will be "third". (Yes, you can emulate this in other languages by returning a list, don’t @ me.) If you’re just writing your own code, you can of course ignore this, but you should be aware of it when interacting with other people’s code, and you may also find that it’s useful for you.

If a function returns more values than a caller is interested in, the extra values are dropped. This means that you can use this feature to return additional / auxiliary information, and callers who aren’t interested in it can just write local mainResult = yourFunction() and ignore whatever else it happens to return. If a function returns fewer values than a caller asked for – local first, second, third = functionReturningOneValue() – then the other values are set to nil.

You can also define functions in a table, in a way that I haven’t seen in a lot of other languages, but actually find very natural and convenient:

function someTable.someFunction() ... end

This is exactly the same as:

someTable.someFunction = function() ... end

But nicer to read, since someTable.someFunction() matches how the function will be called. This also leads us nicely into our next topic.

Methods

As we’ve just seen, you can have functions inside a table, and call those functions using the . syntax familiar from many other languages. However, these functions don’t automatically have access to the surrounding table. For that, Lua has a mechanism kind of reminiscent of Python, in that you’ll use the name self to identify that surrounding table (what many other languages call this). However, you don’t define self as the first parameter of the method, like you would in Python (def some_method(self, other_param)); instead, you define and call the method with a colon instead of a period:

function someTable:someMethod(param1, param2)
    return self.something + param1 + param2
end

local result = someTable:someMethod(arg1, arg2)

This is exactly equivalent to:

-- do not write code like this, this is for demonstration only
function someTable.someMethod(self, param1, param2)
    return self.something + param1 + param2
end

local result = someTable.someMethod(someTable, arg1, arg2)

When defining a method, the colon is syntactic sugar for a first self parameter; when calling it, it’s syntactic sugar for passing in the table as the first argument. (In theory, you can mix and match this, and colon-call non-colon-defined methods or vice versa, but that will probably just result in confusion.) When writing Lua, it’s important to get into a habit of using table members the right way, which is usually indicated in the relevant documentation. For instance, frame objects have frame.args but frame:getParent() – the arguments are a normal table member, the parent frame is a method. frame:getParent().args correctly gets the parent frame args (assuming there is a parent frame!); frame.getParent() is wrong, and frame:getParent():args is a syntax error.

Named arguments

When calling a function with a single table literal argument, you can omit the surrounding parentheses: func{...} means the same as func({...}). Some methods in MediaWiki are intended to be called like this, emulating named arguments in some other languages:

local wikitext = frame:expandTemplate{
    title = 'Some template',
    args = {
        'first',
        'second',
        other = 'named',
    },
}

This would be equivalent to {{Some template |first |second |other=named}}.

Conclusion

This was just an overview of some more noteworthy aspects of Lua. For a more thorough introduction, including more details on using Lua in MediaWiki specifically, see the Scribunto Lua reference manual: it has a lot of information, and is also available in several other languages.