Now while this method of having a BSOD is good, it is NOT a replacement for testing your code. ALWAYS test your code. Also the method of implementing a BSOD is not perfect, there are some errors which will not appear in the BSOD, for example syntax errors will not since syntax is checked before running the program.
What normally causes a Blue Screen of Death (BSoD)
In Windows a BSoD happens when a system failure or error occurs, whether its caused by hardware, software, memory problems, etc. Normally when a BSoD occurs it is very helpful and tells you why it happened and sometimes how to fix it. Find more information on a BSOD here: http://en.wikipedia....Screen_of_Death
For Unix (thats you too Mac people) this BSoD is normally refered to as a 'kernal panic'. This kernal panic is normally caused by that same things as a BSoD, although it occurs less in Unix due to the fact that, well, Unix is awesome. Find more information on a kernal panic here: http://en.wikipedia....ki/Kernel_panic
Errors and You
Getting errors are a big part of debugging programs in any programming language. They quite often provide very useful information about what went wrong and how to fix it. An example of an error in ComputerCraft is
startup:4: attempt to call nil
this error tells us that we are trying to call a function that does not exist in the program 'startup' on line 4. Now these error messages can vary, but the basic format should always tell you the program, line number and the error message. Sometimes they can be bad, like this errorString expected
which comes up from time to time, off the top of my head I think its to do with the rednet api. Knowing how to read and interpret an error can make you a very powerful debugger and make fixing your programs very easy. However I am not here to tell you more about debugging and such, I am here to help you with error handling.Error Handling (how the BSoD works)
Now in programming languages when an error occurs it needs to be handled. Now just a bit of a deviation… When a program is executing there is a data type, known as a stack, that contains where the program is up to, each time you call a function it is pushed on the top of the stack, and when the function finishes it is pushed off the stack and the previous stack item continues from where it left off. Now back to topic… When an error is raised it will continue down the stack until something handles it. If you don not handle the error in your code it will go back to whatever has called your program, if that program does not handle the error it will continue does that programs stack until it is handled. In the case of ComputerCraft the shell handles our errors and prints them to the screen.
Now as stated we can handle the errors ourself, just like we can create errors intentionally. Now in most high level programming languages to handle an error we can use a try/catch statement. For example in Java we would use this syntax:
try
{
// what we want to do that can throw an error
}
catch(SomePreciseException e)
{
// print out what we want for this specific exception
}
catch(Exception e)
{
// generically catch any other errors that we did not expect or such
}
Now sadly in Lua we do not have a try catch statement. However in Lua we have a function called pcall, which means protected call. Now pcall can only run functions, so this means that we must surround our code that could cause errors in a function.
Now what pcall does is it calls its first argument, which is the function, in protected mode so that it catches any errors while it is running. If there are no errors pcall will return a true, plus any values that are returned by the call, otherwise it will return false and the error message. Now the error message does not need to be a string, infact it could be anything, including a table, but I shall leave those bits up to you to figure out completely.
So really for all intensive purposes using the error function is a throw and the pcall is a catch.
Making Errors
In Lua, just like other languages, we can also create errors. The most common method is by using the 'error' function but we can also use the assert function. Now say we have the following example;
local number = tonumber(read())
if not number then error("invalid input, not a number") end
print(number)
now while this is a completely valid example it can be simplified with assert like so
local number = assert(tonumber(read()), "invalid input, not a number")
print(number)
So assert will attempt the first parameter and if it returns false or nil then it will raise an error with the message in the second parameter.Now as detailed previously when an error occurs there is location information that comes with it, it includes the file and line number that the error occurs. This is the same for assert as it raises an error. Sometimes however we don not wish the error to show as ours, for example, what if we are creating an api that needs to be called with a specific parameter. This is when we use the second parameter of the error function. This second parameter is the levelthat the error should be reported, that way we can blame someone else not us. So with this example
function this(str)
if type(str) ~= "string" then error("Expected string, got "..type(str)) end
end
and this example
function this(str)
assert(type(str) ~= "string", "Expected string, got "..type(str))
end
these would both point the finger at us, saying that our function is the one to have caused an error. Now unfortunately the assert function does not allow the use of the level argument. So if we wish to use the level we must do it with the error function like so
function this(str)
if type(str) ~= "string" then error("Expected string, got "..type(str), 2) end
end
This will now point the finger at the function that calls ours. Now that we all know this, onto the BSoDEDIT: After reading some of the code made by the CC devs, I have discovered that if you do a level of 0 on the error function it will not print the error message with the file name and line number. It is essentially
printError( 'Some Error' )
error()
this is a very handy trick to quickly exit the program, print a red message, but now show it as an error in the code. as such I have updated the assert function in the next section to also do the same.
A Custom Assert
Firstly I would just like to say thank you to user: Stary2001 on the #computercraft IRC channel for helping me with this code, wasn't in the right mind of thinking and he helped me.
Now as stated above unlike error assert does not have a level. Now this is essentially how the assert works
if not [someCondition] then
error("Some message", 1)
end
Now while this is all well and good, as you can see it provides the default level 1, pointing the blame for the error to our function. Now as stated above, we can just use error(<msg>, 2) but consider this scenario:
local function someFunc( p1, p2, p3, p4, p5, p6, p7, p8 )
if type(p1) ~= "string" then error("Arg1 expected string",2) end
if type(p2) ~= "string" and type(p2) ~= "nil" then error("Arg2 expected string or nil",2) end
if type(p3) ~= "boolean" then error("Arg3 expected boolean",2) end
if type(p4) ~= "table" then error("Arg4 expected table",2) end
if type(p5) == "number" then error("Arg5 numbers not number",2) end
if type(p6) ~= "boolean" then error("Arg6 expected boolean",2) end
if type(p7) == "nil" then error("Arg7 nil not allowed",2) end
if not p8 then error("Arg8 nil not allowed",2) end
end
now thats a lot of code there that is not overly unreadable and if you're anything like me, you may have missed one or 2 end to the if statements as well, just to check the parametersNow this is where a custom assert can come in handy. Here is a version of assert that has, optional, levels:
local function assert( condition, message, level )
level = tonumber( level ) or 1
level = level == 0 and 0 or (level + 1)
if not condition then
error( message or "assertion failed!", level )
end
return condition
end
Now if you don't provide a level, it will default to pointing finger at your code causing the error, just like the default assert, however now we can provide a level to the assert. so the example we used before now becomes this
local function someFunc( p1, p2, p3, p4, p5, p6, p7, p8 )
assert( type(p1) == "string", "Arg1 expected string", 2 )
assert( type(p2) == "string" or type(p2) == "nil", "Arg2 expected string or nil", 2 )
assert( type(p3) == "boolean", "Arg3 expected boolean", 2 )
assert( type(p4) == "table", "Arg4 expected table", 2 )
assert( type(p5) ~= "number", "Arg5 numbers not allowed", 2 )
assert( type(p6) == "boolean", "Arg6 expected boolean", 2 )
assert( type(p7) ~= "nil", "Arg7 nil not allowed", 2 )
assert( p8, "Arg8 nil not allowed", 2 )
end
Now while this isn't any shorter in lines, it is easier to read, and there is no worry about forgetting an end ;)/>The Code
This is the basics behind the code
local function main()
-- main code body goes here
return true -- this is needed so that when the program ends normally the BSOD will not appear
end
local ok, err = pcall(main)
if not ok then
-- an error has occurred so print the BSOD here
end
Now you don not need to have all your code inside the 'main' function, you can have functions outside if it, because as stated before the error will go until something handles it, which is our pcall on 'main'. So this code would work as well
local function doSomething()
error("Some bad error! :P/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>",2)
end
local function main()
doSomething()
return true -- this is needed so that when the program ends normally the BSOD will not appear
end
local ok, err = pcall(main)
if not ok then
print(err)
end
Now there are ways for us to print our own errors and exit so the BSOD does not appear
local running = false
local function main()
if not running then
print("You silly person!")
return true
end
return true
end
local ok, err = pcall(main)
if not ok then
print(err)
end
By using return true we can stop the bsod from showing up, while still exiting the program and telling the person the error message that is not bad enough to require a BSOD.We can also pass parameters to functions with pcall, there will be an example in the next code snippet.
Lets say that we have areas in code that we want to thown an error and handle it in our own code, lets say an invalid file or something, we can do this
local function openFile(path)
local handle = assert(io.open(path, "r"), "File does not exist! :(/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>") -- now if there is a problem with this it will throw an error
handle.close()
end
local function main()
write("Please input a file: ")
local input = read()
local ok, err = pcall(openFile, input)
if not ok then
print(err)
else
print("That file exists! :)/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>")
end
end
local ok, err = pcall(main)
if not ok then
print(err)
end
Now in the above example we are handling the potential error in the code meaning that it does not go back to the BSOD, but instead to our pcall in the code.When programming now, with the BSOD, I like to use a C-style main function like so
local function main(argc, argv)
return true
end
local ok, err = pcall(main, #{...}, {...})
if not ok then
print(err)
end
This code passes the runtime argument count and arguments to the main function. Very C-style programming hey ;)/> We can then validate the runtime arguments inside the main, obviously its just as easy to do this outside the main, but either way, does not make a huge difference.Now this is an example that I made and send to someone else, read the comments for some information
-- functions can be out here, thats fine
local function someFunction()
error("Input error")
end
-- this is a very C-style language feel to program entry
local function main(argc, argv)
-- main program body goes in here
-- you can do argument checking in here, or before the pcall if you wish
-- either way, but there is support for it here, argc is the count, argv is the arguments
-- functions can also be in our main function
local function anotherFunction()
error("Bad runtime error!")
end
-- you can even handle errors yourself if you do not want a certain error to go to the bsod
local ok, err = pcall(someFunction)
if not ok then
print("We got an error from someFunction and 'caught' it here, it gave us "..err)
end
if math.random(1,4) == 4 then
anotherFunction()
end
return true -- absolutely needed or else if will show BSOD when quitting without error!
end
local ok, err = pcall(main, #({...}), {...}) -- pass the argument count and the arguments to main
if not ok then
-- errors, except syntax errors, if not caught in the main program body will come here.
-- Errors when they occur will go down the stack returning each function until something handles it,
-- normally this would go to the shell, but since we are using pcall we are catching this error and
-- then printing it here
-- draw the BSOD here however you wish
print("BSOD: Really bad runtime error "..err)
end
A full working example, BSOD and all!
Now I'm not going to go into detail about how this works here, have a read through and see if you can understand it, and if not comment on this thread and ask
Spoiler
local isDev = true -- set this for your own development environments, it will make the BSOD print a line number.
local function main(argc, argv)
if argc ~= 0 then
if argc == 1 then
if argv[1] == "-help" then
print("Valid arguments -help -test")
return true -- we want to exit but not trigger the BSOD for something this trivial
elseif argv[1] == "-test" then
print("Tested, now running")
else
print("Invalid argument, "..argv[1].." run with -help to see valid arguments")
return true -- we want to exit but not trigger the BSOD for something this trivial
end
else
print("This program only supports 1 argument not "..argc)
return true -- we want to exit but not trigger the BSOD for something this trivial
end
end
-- function calls and main code body here
return true
end
local ok, err = pcall(main, #({...}), {...})
if not ok then
-- for the following 2 functions are from my Extended String Library...
local split = function(str, pat) local t = {} local fpat = "(.-)"..pat local last_end = 1local s, e, cap = str:find(fpat, 1) while s do if s ~= 1 or cap ~= "" then table.insert(t,cap) end last_end = e + 1 s, e, cap = str:find(fpat, last_end) end if last_end <= #str then cap = str:sub(last_end) table.insert(t, cap) end return t end
local trim = function(str) return (str:gsub("^%s*(.-)%s*$", "%1")) end
-- localize these as they are only used here
local setColors = function(bg, txt) if not (term.isColor and term.isColor()) then return false end if txt then term.setTextColor(txt) end if bg then term.setBackgroundColor(bg) end end
local clear = function(col) if col then term.setBackgroundColor(col) end term.clear() end
local cwrite = function(msg, y, offset) local sw,sh = term.getSize() term.setCursorPos(sw/2-#msg/2, (y or (sh/2)) + (offset or 0)) write(msg) end
local errHeader = function(text, y, centre) setColors(colors.white, colors.blue) if centre then cwrite(text, y) else term.setCursorPos(1,y) print(text) end setColors(colors.blue, colors.white) end
local printMsg = function(text, y) term.setCursorPos(1,y) print(text) end
local waitForKey = function() while true do local e,k=os.pullEventRaw("key") if k == 28 or k == 156 then break end end end
local err = split(err,":")
local sw,sh = term.getSize()
clear(colors.blue)
-- the error headers
errHeader(" ERROR : "..( err[#err-2] and string.upper(err[#err-2]) or "<UNKNOWN>").." ", 2, true)
errHeader(" Error Description ", 7, false)
errHeader(" Reporting the Issue ", sh-7, false)
-- BSOD content
printMsg("An error has occurred during runtime with \'"..(err[#err-2] or "<unknown>").."\' and the program has ceased working.", 4)
printMsg(trim((isDev and "Line "..(err[#err-1] and err[#err-1] or "<unknown>")..":" or "")..err[#err]) or "<No error message supplied>",9)
printMsg("If this problem persists please try updating the program with '-update' and if the problem still persists report the issue with '-report'",sh-5)
cwrite("Press enter to quit...",sh-1)
waitForKey()
term.clear(colors.black)
term.setCursorPos(1,1)
end
I hope this tutorial was helpful in learning about error handling and controlled error creation, oh and a making a BSOD. Please feel free to leave any suggestions or comments :)/>