Errors
MaSH treats an error as a fact to record, not as an event that unwinds the script. This is different from most languages, and everything on this page follows from it.
The rule
Section titled “The rule”When a statement fails, MaSH records the error and goes on to the next statement. The script does not stop. There is no try and no catch.
set x to 5 / 0printline "still running"set y to nosuch()printline "still running again"still runningstill running againx = 5 / 0printline("still running")y = nosuch()printline("still running again")still runningstill running againBoth failures were recorded. Neither was shown, because nothing asked.
A statement that fails does as much as it can. A failed assignment gives the variable the value null (or NAN for a division by zero). A failed call gives null. A failed printline prints nothing, not even the line ending.
set z to 1set z to z + nosuch.thingprintline mash.type(z)printline "before"printline {a: 1}.missingprintline "after"NULLbeforeafterz = 1z = z + nosuch.thingprintline(mash.type(z))printline("before")printline({a: 1}.missing)printline("after")NULLbeforeafterThe things that cause a recorded error and carry on include:
- Dividing or taking a remainder by zero.
- Calling a block that does not exist (
Undefined block or function: name). - Calling a method that does not exist on a value (
Unable to call method "upper" of non-object "string".). - Reading a missing key of a dictionary with a dot (
Item "k" does not exist.). - Adding text to a number with
+. - Anything an object refuses: a missing file, a failed web request, a permission.
throw and raise are different. They record an error and stop the block they are in. See throw and raise.
mash.error
Section titled “mash.error”The last recorded error is available as mash.error.
| Property | Value |
|---|---|
mash.error.state | true when an error is recorded and not yet handled |
mash.error.message | the message |
mash.error.line | the line of the failed statement, or empty |
mash.error.expression | the text of that line, or empty |
set x to 5 / 0printline mash.error.stateprintline mash.error.messageprintline mash.error.lineprintline mash.error.expressiontrueDivision by zero1set x to 5 / 0x = 5 / 0printline(mash.error.state)printline(mash.error.message)printline(mash.error.line)printline(mash.error.expression)trueDivision by zero1x = 5 / 0Only the last error is kept. A second error replaces the first.
The state is sticky. It stays true through later statements that succeed, until an error block handles it. Reading mash.error.state does not clear it.
error blocks
Section titled “error blocks”An error block runs when an error is recorded and not yet handled. It is checked where you put it: after the statements you want to watch.
set data to 10 / 0error printline "caught: {{mash.error.message}}"endprintline "carrying on"caught: Division by zerocarrying ondata = 10 / 0error { printline("caught: {{mash.error.message}}")}printline("carrying on")caught: Division by zerocarrying onAn error block is if mash.error.state under another name. Both forms mean the same:
throw "first"if mash.error.state printline "explicit check: {{mash.error.message}}"endexplicit check: firstthrow "first"if (mash.error.state) { printline("explicit check: {{mash.error.message}}")}explicit check: firstWhen an error block begins, the error counts as handled, and when the block ends the error state is cleared. A second error block straight after finds nothing to do:
set x to 10 / 0printline "step 1"printline "step 2"error printline "handled: {{mash.error.message}}"endprintline "state now: {{mash.error.state}}"error printline "never runs"endstep 1step 2handled: Division by zerostate now: falsex = 10 / 0printline("step 1")printline("step 2")error { printline("handled: {{mash.error.message}}")}printline("state now: {{mash.error.state}}")error { printline("never runs")}step 1step 2handled: Division by zerostate now: falseNotice that the block caught an error from two statements earlier. It does not look only at the line above. It looks at whatever was recorded since the last time one ran. To tell which statement failed, read mash.error.line and mash.error.expression.
An empty error block is the way to clear the state and ignore an error:
throw "ignore me"errorendprintline mash.error.statefalsethrow "ignore me"error {}printline(mash.error.state)falseInside the block you can use exit, next, break, return, fail and throw as anywhere else. exit ends the script. A throw inside the block records a new error, which is then the current one, and it behaves as a throw anywhere else: it ends the block the error block is in (the run, at top level).
set x to 1 / 0error printline "handling first" throw "second"endprintline "never printed"x = 1 / 0error { printline("handling first") throw "second"}printline("never printed")An error in a condition
Section titled “An error in a condition”If an if, while, until or ensure condition fails to compute, the error is recorded and the condition is read as false. The else branch runs.
set d to {a: 1}if d.missing printline "yes"else printline "no"endprintline mash.error.messagenoItem "missing" does not exist.d = {a: 1}if (d.missing) { printline("yes")} else { printline("no")}printline(mash.error.message)noItem "missing" does not exist.A handler for the whole script
Section titled “A handler for the whole script”Name a block as the global error block, and it runs after every statement that records an error, without you placing an error block each time.
block onError printline "global handler: {{mash.error.message}}"endset errorblock to "onError"set x to 5 % 0printline "no error here"set y to 1 / 0global handler: Modulo by zerono error hereglobal handler: Division by zeroblock onError() { printline("global handler: {{mash.error.message}}")}errorblock = "onError"x = 5 % 0printline("no error here")y = 1 / 0global handler: Modulo by zerono error hereglobal handler: Division by zeroerrorblock = "name" and mash.error.block = "name" do the same. The handler counts the error as handled. If the handler itself fails or calls fail, the script stops.
The handler runs after incidental errors such as the division above, and the script carries on. After a throw it behaves differently: see below.
There is no way to switch it off again; errorblock = null does not.
throw and raise
Section titled “throw and raise”throw "message" records an error and stops the block it is in. raise is the same word.
If an error block comes straight after the throw, it handles the error and the script carries on after the block.
throw "something odd"error printline "caught: {{mash.error.message}}"endprintline "carrying on"caught: something oddcarrying onthrow "something odd"error { printline("caught: {{mash.error.message}}")}printline("carrying on")caught: something oddcarrying onIf no error block is straight after, the error is recorded and the global error block runs if there is one. Then the block the throw is in ends at once, however deep in if, each or while it was. A later error block in that same block does not run. The code that called the block then skips to its own next error block, which can be after the call or after a loop or if that holds the call. The statements in between do not run. At the top level of the script there is no caller, so a throw there, not caught straight after, ends the run.
block check throw "bad input" printline "never printed"endcheck()printline "never printed either"error printline "caught: {{mash.error.message}}"endprintline "end of script"caught: bad inputend of scriptblock check() { throw "bad input" printline("never printed")}check()printline("never printed either")error { printline("caught: {{mash.error.message}}")}printline("end of script")caught: bad inputend of scriptIf no caller catches it, the run ends and the run’s error is the message. This is so even when the global error block handled it.
printline "before"throw "no handler"printline "never printed"printline("before")throw "no handler"printline("never printed")throw with no message records an empty error. mash.error.line and mash.error.expression give the line of the throw.
fail "message" stops the script at once. The run is reported as failed, with the message, the source line and the line number. An error block cannot catch it, and nothing after it runs.
printline "before"fail "input file missing"printline "never printed"printline("before")fail "input file missing"printline("never printed")A common shape is to handle the error, then decide:
set data to mash.files.get("/Reports/today.csv")error fail "Could not read today's report: {{mash.error.message}}"enddata = mash.files.get("/Reports/today.csv")error { fail "Could not read today's report: {{mash.error.message}}"}exit ends the whole script and reports success, including when it is inside a block. See Statements.
stoponerror
Section titled “stoponerror”stoponerror = true turns the language round. From that line on, any recorded error stops the script, the way an exception would elsewhere. The run is reported as failed with that error.
set stoponerror to trueprintline "a"throw "boom"printline "b"stoponerror = trueprintline("a")throw "boom"printline("b")Put stoponerror = true at the top of a script where a silent partial run would do harm. It is the single most useful safety setting in the language. stoponerror = false returns to the default.
debug = true is accepted and has no effect on the script’s behaviour.
Errors the script cannot recover from
Section titled “Errors the script cannot recover from”Some errors end the run whatever the script does. They are not recorded and carried on; they stop everything.
- A syntax error. The script does not start. Nothing is run and nothing is printed.
- Reading or calling a name on
mashthat does not exist, such asmash.nosuchthing. - Running out of time or memory. See Limits.
fail, afailor an error inside the global error block, andstoponerror.- A
throwwith noerrorblock after it.
Syntax errors
Section titled “Syntax errors”A syntax error is reported with its line and column, and the script does not run.
| Message | Meaning |
|---|---|
Parse error at line 2, column 1: Unexpected token end (end) | a word or symbol turned up where the language did not expect it |
Syntax error at line 1, column 11: Unterminated string | a quote was never closed |
Syntax error at line 1, column 13: Expected ), got as (as) | one thing was required and another was found |
`mash` is reserved and cannot be assigned. Rename the local. | an assignment to a name the language owns |
ensure must return, next, break, fail or exit | an ensure with no way out |
Unexpected character '&' — did you mean '&&'? | a single & or | |
A missing end at the very end of the script is accepted, so a script that forgets the last end runs as if it were there.
Where you see errors
Section titled “Where you see errors”An error that nothing handles is still recorded, and the run keeps a list of them. For each distinct failing line the list holds the line, the text of the line, the message and a count. A loop that fails ten thousand times is one entry with a count of 10,000. At most 50 distinct entries are kept, and the total is always counted.
An error counts as handled, and leaves the list, when an error block or the global error block deals with it. So the list holds the errors that were swallowed.
Where it shows:
- In the Mashup execution panel, as Unhandled errors (n), under the output. Each entry shows its line number, which you can click to jump to the line, the message, a count when it happened more than once, and the line of code.
- In the run log on Settings > MaSH, as a warning count on the run, and as the full list when you open the run.
This was read from the interface code and is not a promise about its wording.
A run that stopped shows its error as the run’s error, in the form:
Message: input file missingSource: fail "input file missing"Line: 2The two are different things. A run with unhandled errors finished all its statements. A run with an error stopped before the end. A run that finished is not reported as an error, so look at the unhandled list as well as the status.
Putting it together
Section titled “Putting it together”A script that fetches data and checks the result at each step:
block loadRows set rows to [1, 2, 3] return rowsend
set rows to loadRows()error fail "Could not load rows: {{mash.error.message}}"end
set total to 0each rows as r increment total by rendprintline "total is {{total}}"total is 6block loadRows() { rows = [1, 2, 3] return rows}
rows = loadRows()error { fail "Could not load rows: {{mash.error.message}}"}
total = 0each (rows) as r { total += r}printline("total is {{total}}")total is 6