The pcall command
The pcall command wraps a function call and catches any error that occurs during the function. The return values of pcall are either true, followed by the return values of the function, or false, followed by an error message. The arguments to pcall are the function name, followed by each argument that would normally be passed to the function.
In this example, we use a short (and error-prone) function to band a segment to free space. (For a correct version of this function, see band.Add().)
function BandSegment(segment, length) local bandID = band.Add(segment,segment-1,segment+1,length,0.0,0.0) band.SetGoalLength(bandID, length) return bandID end print(pcall(BandSegment,2,1.0)) -- okay print(pcall(BandSegment,2,0.0)) -- error, zero length print(pcall(BandSegment,1,1.0)) -- error, needs a segment before and after true 1 false [string "..."]:7: bad argument #4 to 'Add' (rho must be > 0 and <= 10000) false [string "..."]:7: bad argument #2 to 'Add' (segment index out of bounds)
Anonymous functions (closures)
An actual function definition can stand in for the function name. This can be convenient for simple functions. The function has no name, hence it is "anonymous". Here, we create a band and set its strength and goal length in one step, without checking for success.
function AddBandSafe(segment) return_code, bandID = pcall( function(segment) local bandID = BandSegment(segment,0.001) band.SetStrength(bandID,10.0) return bandID end, segment) if return_code then return bandID else return 0 end end print(AddBandSafe(2)) -- okay print(AddBandSafe(1)) -- error, needs segments on both sides 1 (success, band is # 1) 0 (failure, no band number)
The error command
The error command allows you to create your own errors, which can be caught by the error handler or appear in the output window. You can use the error command to resend an error that you have caught. This allows your error handler to perform some cleanup before continuing the error-handling process upwards. The optional second argument will cause Lua to report the line number of one of the calling functions instead of the actual line number of the error. This is useful if the error was caused by a bad argument passed in by the calling function.
In this example, we create two bands, but delete the first band if the second band fails. We also report the error at the calling location (one step above, arg = 2) instead of at band.Add (one step below) or at the error statement (arg = 1 or omitted). We also check for the possibility of band.Add returning 0 (some band functions do this).
function BandTwoSegments(seg1, seg2, length) local rc, bandID1, bandID2 rc, bandID1 = pcall(BandSegment,seg1,length) if rc == false then error(bandID1,2) -- bandID1 contains the error elseif bandID1 == 0 then error("Failed to create band 1",2) -- custom error message end -- don't know what went wrong exactly rc, bandID2 = pcall(BandSegment,seg2,length) if rc == false then band.Delete(bandID1) -- delete the first band error(bandID2,2) -- bandID2 contains the error elseif bandID2 == 0 then band.Delete(bandID1) -- delete first band error("Failed to create band 2",2) -- custom error message end return bandID1, bandID2 end BandTwoSegments(2,3,1.0) -- okay BandTwoSegments(2,1,1.0) -- error, should create then delete first band
The xpcall command and catching Cancel
The pcall command lets you catch errors that would normally terminate the script, and continue executing. However, pcall cannot catch the "Cancelled" error, because the script stops before the return code can be checked. To catch "Cancelled" you must use the xpcall command. Xpcall calls an error handling function instead of simply returning an error code. Cancel will still stop the program, but only after the error handler has returned.
Xpcall has the same return values as pcall. It is called differently, however. The first argument is the name of the function you want to protect. This function cannot accept any arguments, so you may want to wrap your function inside an anonymous function. The second argument is the name of the error handling function. The error handler accepts one argument, the error message, and returns one value, a possibly different error message.
You should print the error message either in the message handler or after checking the return code; otherwise, it won't appear in the output window at all.
You should take care to ensure your error handler and cleanup routines are reliable. If they have an error, the program will exit with an "Error in error handler" message.
This is how a typical script that catches cancel might look:
-- Various User Function -- Save best result in slot 3 _best_score = -math.huge function SaveBest() if current.GetScore() > _best_score then _best_score = current.GetScore() save.Quicksave(3) end end -- Change to loops, wiggle n times, saving best function MainLoop(n) -- these routines need to be cleaned up after selection.SelectAll() structure.SetSecondaryStructureSelected("L") while n > 0 do print(n,"left") structure.WiggleAll(2) n = n - 1 SaveBest() end end -- Delete any bands the script added, leaving originals untouched NOriginalBands = band.GetCount() function DeleteAddedBands() if NOriginalBands == 0 then band.DeleteAll() end while band.GetCount() > NOriginalBands do band.DeleteBand(band.GetCount()) end end -- Do this when things end normally or user cancels function CleanUp() save.LoadSecondaryStructure() selection.DeselectAll() DeleteAddedBands() end -- This is the function that handles the error message function ErrorHandler(errmsg) if string.find(errmsg,"Cancelled") then print("User cancel.") -- print this instead of error message save.Quickload(3) -- best stable solution in slot 3 CleanUp() else -- it's a real error, not a cancel -- You may also want to do cleaning up stuff here print(errmsg) -- one place to print the error end return errmsg end -- Main User Code -- This code does not need to be cleaned up after print(puzzle.GetName()) save.SaveSecondaryStructure() NLoops = 5 SaveBest() -- This code needs to be cleaned up after rc, err = xpcall( function() MainLoop(NLoops) end, ErrorHandler) -- Check if everything was ok (optional) if rc == false then -- you may want to do cleaning up stuff here print(err) -- this is the other place to print the error else CleanUp() print("Done.") end