A tool to help with print debugging based on the Python package IceCream. There's already a Lua version of IceCream but it won't work with Pico-8/Picotron as far as I know. I've called it db
for debug rather than ic
but it's very similar in concept.
You can download the code here or copy/paste below:
db.lua
Summary
Displaying variables
The most basic thing you can do with db
is just display values and variables. When displaying variables with db
it displays both the variable name and its value.
x = 1 db(2) db(x) |
Output
db: 2 db: x=1 |
-
Note: Duplicate values
db
creates its output by searching the global environment by key/value pairs looking for a matching value and then displaying it with the corresponding key. This mostly works. But if more than one variable has the same valuedb
may log the wrong one. For example:x = 1 y = 1 db(x)
Because the ordering of keys in a table isn't guaranteed this will sometimes (correctly) display
db: x=1
but will also sometimes (incorrectly) displaydb: y=1
.db
automatically ignores variable/function names defined in the global environment before thedebug.lua
code—which includes all built-ins—to try to minimize these collisions.
Inline debug output
db
returns its input so you can debug variables where they're actually used.
x = 1 y = 2 z = db(x) + db(y) db(z) |
Output
db: x=1 db: y=2 db: z=3 |
User defined types
Since db
is essentially just a fancy print statement it should work properly with any object as long as it has a __tostring
meta-method defined.
do local vector_meta = { __tostring=function(_ENV) return 'vec('..x..','..y..')' end } function vector(x, y) return setmetatable({x=x, y=y}, vector_meta) end end v = vector(1, 2) db(v) |
Output
db: v=vec(1,2) |
Function calls
Since functions return values, by default db
displays a function call as a bare value:
function plus(a, b) return a + b end db(plus(1, 2)) |
Output
db: 3 |
You can use db.wrap
to enable functions to output their name, input parameters and output value, like so:
function plus(a, b) return a + b end db.wrap(plus) db(plus(1, 2)) |
Output
db: plus(1,2) --> 3 |
Selective debug output
The db.display
property can be used to turn debug output on and off wherever you want throughout your code. This lets you leave debug statements in place if you're not sure you're done with them yet but only display those parts you're interested in at the moment.
x = 1 y = 2 function plus(a, b) return db(x) + db(y) end function minus(a, b) return db(x) - db(y) end db.wrap(plus) db.wrap(minus) db.display = false p = db(plus(x, y)) db.display = true m = db(minus(x, y)) db(p) db(m) |
Output
db: x=1 db: y=1 db: minus(1,2) --> -1 db: p=3 db: m=-1 |
In the above example the debug output for plus
is not displayed. However, db.display
doesn't prevent the plus
function from being called or returning a value: You can see via db(p)
that p
has the correct value.
Debugging functions
The Problem
Consider the following example:
x = 1 y = 2 function plus(a, b) local c = 'who am i?' db(c) return db(a) + db(b) end db(plus(x, y)) |
Output
db: who am i? db: x=1 db: y=2 db: 3 |
First notice that the output of the function call is logged simply as db: 3
because the function hasn't been wrapped with db.wrap
. But more importantly, db(c)
is displaying the string but not the variable name while db(a)
and db(b)
are displaying as variables x
and y
respectively. That's becuase a
, b
and c
are all local variables and Lua doesn't give us direct access to those in the same way as globals. Standard Lua has debug tools which can get a hold of those values but we don't have access to those here in Pico-land.
The Solution
If you want/need to debug values within a function use db.local_env
and db.reset_env
like so:
x = 1 y = 2 function plus(a, b) --local _ENV = db.local_env() -- can be called with no arguments local _ENV = db.local_env({a=a, b=b}) -- note the 'local' keyword! c = 'who am i?' -- note the *lack* of 'local' db(c) return db(a) + db(b), db.reset_env() end db.wrap(plus) db(plus(x, y)) db(c) db(x) |
Output
db: c=who am i? db: a=1 db: b=2 db: plus(1,2) --> 3 db: [nil] db: x=1 |
db.local_env
creates a temporary enviroment and tells db
to use that environment for building its output. db.reset_env
just tells db
to go back to using the global environment. Including the call db.reset_env()
as a second return value let's us reset the environment "after" returning from the function.
- Note: The
local
keyword
Importantly_ENV
must be defined as local here or you'll overwrite the global environment. Equally importantly, local variables should not be defined with thelocal
keyword or they'll go back to outputting just values without names. These variables are still local though. As you can see in the outputdb(c)
, when called outside the function, displaysdb: [nil]
. When you're done debugging you'll probably want to remove that_ENV
: be sure to addlocal
where necessary or all your function local variables will suddenly be globals and you'll have whole new errors to hunt down!
Providing a table to db.local_env
is optional. It's mainly included to ensure that input parameters display correctly: Since input parameters are actually defined before our temporary environment, they don't exist in it unless we specifically put them there. You can still use those values but they won't display the variable name when logged.
function no_param(a) local _ENV = db.local_env() return db(a), db.reset_env() end no_param(1) |
Output
db: 1 |
[Please log in to post a comment]