Help:Lua debugging
This help-page, Help:Lua debugging, explains issues of writing Lua script and debugging the source code, to remove errors or improve performance. Because Lua is a "semi-compiled" interpreted language, it does not prescreen for all common syntax errors, nor detect misspelled variables, which are only found at runtime when seeing the "Script error" message. Also, unlike wp:templates which can be run interactively by edit-preview, the Lua script must be tested by show-preview (or run preview) of another page which uses a template which #invoke
s the Lua module being edited. In general, make one small change at a time, do a run show-preview, and save after a few good changes to have a version to restore, in case terrible errors occur after numerous later changes. Note: the saving of a working copy can be done to an offline text file, rather than saving each version into the module revisions.
Other pre-compiled, or semi-compiled, languages can pinpoint the line number, or code phrase, where a syntax error, or undefined variable name, has been detected; however, that processing can require extra time to perform. Instead, it might be possible to use a lexical analysis tool, specifically for Lua script, to better detect and pinpoint misspelled variables or logic errors in Lua source code.
Run preview with Show-preview
[edit]Live changes to Lua modules can be tested during edit, as a run preview, by using the new preview option, for templates/modules, to "Show preview" of a page which uses the template/module. Otherwise, Lua modules cannot be run interactively to see results, which is a feature only in markup-based templates. Instead, Lua modules are typically edited, saved, and run (by the invoking template) to see if any syntax errors can be spotted by the semi-compile during an edit-save. Meanwhile, although a run preview will not report simple syntax errors, it does allow editing the module live, then selecting "Show preview" of the caller page, to see the results or if "Script error" occurs during the run-preview, many times, before actually saving the edit. However, saving an edit gives the advantage of seeing some semi-compile messages to check for simple syntax errors, such as duplicate "then then" which would not be reported during a show-preview.
Causes of Script error
[edit]There can be many reasons why a Lua module triggers the message "Script error" when run in another page. Clicking the error text displays a popup that includes the actual error message generated by Lua, which can be used in a search engine to find a thorough description of what the message means and common remedies, as well as a stack trace to help determine the part of the code that actually triggered the error.
However, some errors are more common than others:
- Invalid concatenation of empty string:
my_str = my_str .. "xx"
- The string named 'my_str' must not be nil when used inside a string concatenation operation. - Invalid concatenation by "+" not ".." operator:
my_str = my_str + "z"
- The concatenation operator is double-dot ".." and trying to use a plus-sign "+" might pass during the pre-compile of edit-save but hit "Script error" when that portion of the Lua script is executed. - Invalid function name:
len = string.long(my_str)
- The string-length operator is hash mark '#' (as in:#my_str
), or use function 'string.len(my_str)
' but trying to use unknown function 'string.long(my_str)
' will successfully pass during the pre-compile of edit-save, yet it will hit "Script error" when that portion of the Lua script is executed.
- Invalid concatenation of empty string:
There are many other causes of "Script error" but always look for the potential to have an unset, uninitialized variable which might stop the program at that point.
Saving fall-back stable versions
[edit]To avoid facing too many errors at once, the general strategy is to make "one small change" at a time, do a run show-preview, and then save that tested version after a few good changes to have a stable version to restore, in case terrible errors occur after numerous later changes. Note: the saving of each working copy can be done to a set of offline text files, perhaps each with a specific version name (and internal comments), rather than saving each version into the Wikipedia module revisions.
How to debug
[edit]Simple review
[edit]To see the value of a variable in a single point of the module:
To see the value of a variable by modifying the code
[edit]The error()
function can be used to show the value of a variable at any point in the module. So to know the value of a variable var
at a certain point in the code, add the line error(var)
at that point.
In the case that the variable is a table (let's call the variable tab
), mw.dumpObject(tab)
can be used to show the table. If the variable does not have nested tables, table.concat(tab, ',')
can also be used as parameter in the error()
function, i.e. error(table.concat(tab, ','))
.
To see the value of a variable without changing the code
[edit]To obtain variables and values returned from functions (not local in both cases) the "Debug console" can be used. The "Debug console" appears below in the module page (when it is in edit mode). Then mw.log
, mw.logObject
, and =
can be used. Let's see its usefulness in the next example:
local p = {}
p.Hello = 'Hello'
function p.calc(num)
return num
end
function p.sum_mult(num)
return num + num, num * num
end
function p.mtable(num)
return {num, num+1}
end
return p
Requests to "Debug Console":
Request | Returned value |
---|---|
mw.log(p.Hello) |
"Hello" |
mw.log(p.calc(10/2)) |
5 |
mw.log(p.sum_mult(3)) |
6 9 |
mw.log(p.mtable(4)) |
table |
mw.logObject(p.mtable(4)) |
table#1 {
:4,
:5,
}
|
=p.sum_mult(10) |
20 100 |
=p.Hello .. ', World!' |
Hello, World! |
. The Debug console does not store the requests, then they will have to be copied or rewritten again in each module modification.
Review of the flow and the variable values in several points
[edit]The functions of the Module:SimpleDebug can be used for cases such as those mentioned above or for more complex cases:
- When variables or the returned value (or values) by a function are local.
- To see if the flow of the program goes through a point (to which you will label).
- To limit the number of values returned from a loop or set conditions to enable the registration of values.
Debugging older modules
[edit]In some situations, a Lua module might be an older effort, perhaps modified by various other editors along the way, with multiple unknown problems. A Lua module can contain numerous logic errors, and even misspelled variables, if the module was not properly tested, for all features, when initially developed. The message "Script error" only occurs when the test data triggers an invalid section of Lua code, so an untested module could contain many hidden bugs, only revealed when broader test data activates more areas of the source code, or uses more internal functions.
A common, and complex, problem is to try expanding an older module, for newer features, but unaware how some prior, unsolved bugs will only be triggered when the expanded functionality is added. Note how the errors are, often, not in the newly added Lua script, but rather waiting silently in older sections to be triggered (surprise) when the new code activates other parts of the Lua script. A hidden bug can be as simple as an undefined (or misspelled) variable name, which had been processed with a default value, but when used with newly added features, generates completely mysterious results, as though the new code had errors, when actually, the older misspelled variables, in other areas, completely trashed the operation of the new features.
A tactic, for dealing with older bugs, is to plan to run special extra test data to activate code areas, or show debug-display output, where new Lua script might be added, to provide a sanity check that the affected areas are functioning soundly, before adding too much new, detailed logic. In most cases, note: "proofreading is the fastest form of testing" if having the patience, or mental memory power, to review the details of older source code. However, another tactic is to reserve specific test-data values to trigger debug-display code which dumps the values of all related data to the screen, or provides a call-tree list to ensure the logic flows into various function sections as expected. In many cases, human memory cannot cope with details beyond 5-9 variables, and so debug-display becomes an easier option, despite the extra time needed to write the debug-print statements into the Lua module. Be careful to proceed slowly, because once extensive amounts of new Lua script are added, then it can become a puzzling guessing game whether the problems were caused by "all the new stuff" rather than by pre-existing bugs in the older Lua script.
Using personal sandbox for script development
[edit]The English Wikipedia has the Template Sandbox extension installed. There is a synergy between the "template sandbox" and "Scribunto" extensions, which allows developing and modifying Lua modules in private (i.e., in the user space) pages, before moving them to the global space.
Using "Template Sandbox" to develop Lua modules
[edit]Let's take a practical example. for this example, assume your user name is "Lua Developer". Let's say you want to test a bug-fix or enhancement to the String module. There are two reasons you can't do it directly: this module contains functions that are used by hundreds of templates, transcluded in millions of articles. Any bug will present a huge disruption to Wikipedia. The 2nd reason is more prosaic: because this module is so central, it is also protected, and our "Lua Developer" user does not have the required permissions to modify this module.
So, the first step in "sandboxing" is to copy Module:String to the private page User:Lua Developer/sandbox/Module:String. Now our developer can edit the module to her heart's content. At any given moment, she can open Special:TemplateSandbox, using the default "Sandbox prefix", which in her case, will be "User:Lua Developer/sandbox". This means that viewing any page from the sandbox page, whenever the parser encounters a Template T or Module M, it will look first to see if a page named "User:Lua Developer/sandbox/Template:T" or "User:Lua Developer/sandbox/Module:M" exists, and if so, the parser will use those for the parsed page, instead of the ones in the "real" Module and Template namespaces. Any template or module which do not exist under the "Sandbox prefix", will be pulled by the parser from the appropriate namespace.
From the Template sandbox, the developer can view any page, and can also enter raw wikitext, and ask the parser to parse it using the faux templates and modules she created under the "Sandbox prefix"
Small caveat: The case of the wrong case
[edit]Wikipedia has a small perversion regarding page name casing: it will convert the first character of a page name to uppercase, regardless of the way it was entered. To compensate, it also converts the first letter of a template (when transcluded) and module (when invoked) to uppercase. however, this does not extend to the modules stored in User: namespace viewed through the special Template sandbox, because in the sandbox "the first letter" is not the first letter any more. Here is an actual example: for real modules, let's say Module:String, mediawiki software will allow you to use {{#invoke:String|replace|Hara I am|a|e}}
to return "Here I em" (try it). It will also allow you to use {{#invoke:string|replace|Hara I am|a|e}}
to get the same result:
wikitext | result |
---|---|
{{#invoke:String |replace|Hara I am|a|e}} |
Here I em |
{{#invoke:string |replace|Hara I am|a|e}} |
Here I em |
However, if our developer would create a page named User:Lua Developer/sandbox/Module:string, she would not be able to invoke this module in any way, because the parser will look for a page named "Module:String" and not "Module:string" under the sandbox prefix, Wikipedia will not convert the name to "Module:String" (because it only converts the 1st character in the page name), and will not find it, so it will take the module from the main namespace (if such module exists), or will complain that the module does not exist.
Upper/lower case is correctly handled for all modules in normal use, which are housed in the Module: namespace.
Plan for extensive debug-display code
[edit]As another issue of strategy, adding debug-display sections does not slow Lua code as it might the speed of markup-based template execution, because Lua can process hundreds of if-conditions at rapid speed. Even the addition of an extra if-statement for every Lua variable has little drag on speed, compared to slowing a template by perhaps 50% if adding similar if-expressions inside a markup template. Feel free to have many sections of debug-display added into a Lua module, or add several parameter validation tests as extra if-statements to check the values of variables to detect typical bad data.
Occasional timeout errors
[edit]The Lua software was initially configured with a small, 10-second timeout limit, for the combined operation of all Lua code when formatting a page. By comparison, the markup-based templates have a 60-second timeout (6x higher time limit), and when the servers are slow, the markup-based templates could format perfectly, up through 59 seconds, but the Lua portions of templates will be stopped at the 10-second limit. The error message might be:
- Lua 'script error': "The time allocated for running scripts has expired."
Unlike markup-based templates, the formatted page will contain "script error" for each further invoked Lua module, as text to store in the page-cache copy. The danger of the timeout limit occurs during the rare periods of 3x-4x slower server delay, where a 3-second Lua operation might slow to 12-second duration with busy servers, and thus garble the processing to store "script error" into the formatted page, for thousands of readers to view.
The overall effort appears unstable, or unreliable, as if Lua suddenly "gets too tired" and starts complaining by storing "Script error" into the resultant page, where formatted text would have been expected instead. The timeout problem is somewhat rare, and when a formatted article contains "Script error" text inside the page, then perhaps edit the page slightly, to make a small change, to force the replacement of the page with a clean Lua run.
Exasperating bugs
[edit]Off-wiki tools
[edit]IDE
[edit]Running your code through an IDE is helpful for its text highlighting features, which can help you quickly spot syntax errors. Any IDE with the ability to install a Lua text highlighting plugin should work. This could be as simple as Notepad++ (with Lua selected from the language menu) or as full-featured as Visual Studio Code.
Step debugger
[edit]Step debugging is a powerful tool that allows you to step through code one line at a time, and hover over variables to see their contents at the time of execution. IDEs that easily support Lua step debugging include ZeroBrane Studio, and IntelliJ IDEA with various debugger plugins (Emmy Lua or Lunalysis).
RegEx
[edit]Lua uses a unique flavor of regular expressions that is not supported by top RegEx tools. One tool that can be used for checking Lua RegEx is GitSparTV's Lua Patterns.
Support
[edit]If you're still stuck and not sure how to proceed, please leave a note on Wikipedia:Lua requests requesting debugging help.
See also
[edit]- Wikipedia:Lua
- Template:Wikipedia help pages - large navbox linking to other topics