On Wed, Jun 5, 2013 at 8:22 PM, MZMcBride <z...@mzmcbride.com> wrote:

 The debug console seems finicky. Its line numbers never seem to make
> sense. For example, I input this into a fresh console:
>

The console isn't designed to handle blocks of code. You should compose
blocks of code into the module itself and use the debug console for simple
poking and prodding. You don't have to save the page: unsaved modifications
to the page are reflected to the console.

For example, go edit a module page, and paste this snippet into the code
editor:

--
local p = {}

function p.greet(name)
    return "Hello, " .. name
end

return p
--

Then in the debug console, type:

=p.greet("Lua")
Hello, Lua

Lua error in console input at line 6: attempt to index local 'frame' (a
> nil value).
>

That's because you wrote a function that accesses a "frame" parameter, but
you don't provide a value for it. You'll get a similar error if you omit
the name from the invocation of the 'greet' function we wrote earlier:

=p.greet()
Lua error at line 4: attempt to concatenate local 'name' (a nil value).

Note that the error cites the correct line number: it's indeed the 'return'
statement in p.greet that is causing the error. In some programming
languages, like Python, arguments without defaults are required, and it is
an error to omit them from the invocation. In Lua and some other languages
the value of any parameter that is absent from the invocation is set to
nil, so p.greet() is equivalent to p.greet(nil).

I have no idea what line 6 refers to. Earlier when I was testing it was
> saying line 14. The line numbers never seem to match up to what I think
> I'm testing.
>

I think your confusion resulted from trying to work with blocks of code in
the debug console.

"frame" can be a bit tricky to grasp and trickier still to work with, but
once you get it, it's not so bad.

The way Scribunto handles {{#invoke}} is this: first, it makes a table.
This is the 'frame' table. Then it makes an 'args' table inside that table.
Then it puts the arguments into the 'args' table. Then it calls your
function, passing the frame table as the sole parameter.

When the parser invokes a Lua function from an {{#invoke}}, it *always*
passes exactly one parameter: frame. It doesn't matter what arguments (if
any) were present in {{#invoke}} or what your function thought it was
getting. It's getting a frame and it's going to like it.

As an example, here's Lua code that is (very roughly) equivalent to how
Scribunto would handle {{#invoke:Sort|asc|3|1|2}}

--
frame = {}
frame.args = { 3, 1, 2 }
sort = require('Module:Sort')
sort.asc(frame)
--

This is a bit annoying, because at first glance "invoke" looks like it's
just an alternate syntax for a Lua function invocation, so you expect
{{#invoke:Sort|asc|3|1|2}} to be equivalent to sort.asc(3, 1, 2), but
that's not the case.

So, how do we work with this? I can recommend two approaches. For the
purpose of demonstrating them, let's imagine we want to write a Scribunto
math module that has a function, 'sum', that takes two numbers and adds
them. One approach is work with a frame parameter from the get-go.

--
local p = {}

function p.sum(frame)
    return frame.args[1] + frame.args[2]
end

return p
--

This works if we save it and then try to invoke it from somewhere:
{{#invoke:Math|sum|4|5}} should evaluate to 9. But how do we test it in the
debug console? We'll pretend we're the parser and make our own frame table.
Paste the code block above into a module, and type this into the debug
console, hitting <return> after each line:

frame = {}
frame.args = { 4, 5 }
=p.sum(frame)

With any luck, you'll get '9'. You can make this shorter by creating both
tables at once:

frame = { args = { 4, 5 } }
=p.sum(frame)

Or even:

=p.sum({ args = {4, 5} })

Because calling a function with a table as the parameter is very common,
Lua has a special syntax for it, which lets you omit the parentheses:

=p.sum{ args = { 4, 5 } }

All of the variants above are equivalent.

The second approach is to not deal with frames until you have to. This
workflow works especially well when you're developing on your own computer,
using the standard Lua interpreter. Just write a normal Lua function and
verify that it works.

--
local p = {}
function p.sum(a, b)
    return a + b
end
return p
--

You can test that it works with =p.sum(4, 5). When you're ready to save
your module, write a helper function that takes a frame table and uses it
to call your actual function.

--
local p = {}

function p.sum_real(a, b)
    return a + b
end

function p.sum(frame)
    return p.sum_real(frame.args[1], frame.args[2])
end

return p
--

This lets you test your function easily from the console, using 'sum_real':

=p.sum_real(4,5)
9

But 'sum' works from wikitext as expected: {{#invoke:Math|sum|4|5}} will
evaluate to 9.

There's a note on the edit screen about using mw.log(), but with various
> inputs, I still can't seem to get anything working properly with the debug
> console.


Works for me. Try this example:

--
local p = {}
function p.greet(name)
    mw.log("I got: " .. name)
    return "Hello!"
end
return p
--

In the console:
=p.greet("Lua")
I got: Lua
Hello!



> Lua seems to be very focused on tables for storage, but dumping
> what's currently in a table at particular points in the code feels
> excruciating. I feel like I must be missing something. This is related to
> <https://bugzilla.wikimedia.org/show_bug.cgi?id=48173>.
>

You're not missing anything, except maybe a plane ticket to Brazil so you
can throw a glass of dirty water at the creators of Lua. There's no single,
simple, straightforward way to print a table in Lua. You have to write it
yourself or use a library. The mind reels.

There's probably a helper in the mw library somewhere.

Is there a better way to write/debug Lua modules? Any help writing these
> modules (or simply getting a working sort module on Meta-Wiki) would be
> appreciated.
>

If you're writing something that doesn't need to talk with MediaWiki via
the Scribunto API, it's sometimes easier to develop and test locally and
then refactor it to use frame.args as a last step before saving it as a
module. I also often use my tiny Scribunto REPL (
https://gist.github.com/atdt/5382043). Just save it as "scrib" somewhere in
your $PATH.

--
Ori Livneh
_______________________________________________
Wikitech-l mailing list
Wikitech-l@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/wikitech-l

Reply via email to