refactor(errors) Improved error management.
@@ -4,23 +4,16 @@ types,
parser, ../vendor/linenoise -const ERRORS: array [MinError, string] = [ - "A system error occurred", - "A parsing error occurred", - "A generic error occurred", - "Insufficient items on the stack", - "Quotation not found on the stack", - "Symbol undefined", - "Incorrect items on the stack", - "Runtime error", - "Two numbers are required on the stack", - "Division by zero", - "Two quotations are required on the stack" -] - var ROOT*: ref MinScope = new MinScope ROOT.name = "ROOT" + + +proc raiseUndefined(msg: string) = + raise MinUndefinedError(msg: msg) + +proc raiseEmptyStack() = + raise MinEmptyStackError(msg:"Insufficient items on the stack") proc fullname*(scope: ref MinScope): string = result = scope.name@@ -110,13 +103,12 @@ result.stack = i.copystack
result.scope = i.scope result.currSym = MinValue(column: 1, line: 1, kind: minSymbol, symVal: "") -proc error*(i: MinInterpreter, status: MinError, message = "") = - var msg = if message == "": ERRORS[status] else: message +proc error(i: MinInterpreter, message: string) = if i.currSym.filename == "": - stderr.writeLine("`$1`: Error - $2" % [i.currSym.symVal, msg]) + stderr.writeLine("`$1`: Error - $2" % [i.currSym.symVal, message]) else: - stderr.writeLine("$1 [$2,$3] `$4`: Error - $5" % [i.currSym.filename, $i.currSym.line, $i.currSym.column, i.currSym.symVal, msg]) - quit(int(status)) + stderr.writeLine("$1 [$2,$3] `$4`: Error - $5" % [i.currSym.filename, $i.currSym.line, $i.currSym.column, i.currSym.symVal, message]) + quit(100) template execute(i: In, body: stmt) {.immediate.}= try:@@ -124,7 +116,7 @@ body
except MinRuntimeError: stderr.writeLine("$1 [$2,$3]: $4" % [i.currSym.filename, $i.currSym.line, $i.currSym.column, getCurrentExceptionMsg()]) except: - i.error(errSystem, getCurrentExceptionMsg()) + i.error(getCurrentExceptionMsg()) proc open*(i: In, stream:Stream, filename: string) = i.filename = filename@@ -158,8 +150,7 @@ else:
i.execute: i.sigilProc else: - i.error(errUndefined, "Undefined symbol: '"&val.symVal&"'") - return + raiseUndefined("Undefined symbol: '"&val.symVal&"'") else: i.stack.add(val)@@ -171,13 +162,13 @@ proc pop*(i: In): MinValue =
if i.stack.len > 0: return i.stack.pop else: - i.error(errEmptyStack) + raiseEmptyStack() proc peek*(i: MinInterpreter): MinValue = if i.stack.len > 0: return i.stack[i.stack.len-1] else: - i.error(errEmptyStack) + raiseEmptyStack() proc interpret*(i: In) = var val: MinValue
@@ -68,15 +68,11 @@
proc errorMsgExpected*(my: MinParser, e: string): string = result = errorMsg(my, e & " expected") -proc raiseParseError*(p: MinParser, msg: string) {.noinline, noreturn.} = +proc raiseParsing*(p: MinParser, msg: string) {.noinline, noreturn.} = raise MinParsingError(msg: errorMsgExpected(p, msg)) -proc raiseUndefinedError*(p:MinParser, msg: string) {.noinline, noreturn.} = +proc raiseUndefined*(p:MinParser, msg: string) {.noinline, noreturn.} = raise MinUndefinedError(msg: errorMsg(p, msg)) - -#proc error(p: MinParser, msg: string) = -# writeln(stderr, p.errorMsg(msg)) -# flushFile(stderr) proc parseNumber(my: var MinParser) = var pos = my.bufpos@@ -348,7 +344,7 @@ my.err = errExprExpected
proc eat(p: var MinParser, token: MinTokenKind) = if p.token == token: discard getToken(p) - else: raiseParseError(p, tokToStr[token]) + else: raiseParsing(p, tokToStr[token]) proc parseMinValue*(p: var MinParser): MinValue = #echo p.a, " (", p.token, ")"@@ -381,7 +377,7 @@ result = MinValue(kind: minSymbol, symVal: p.a, column: p.getColumn, line: p.lineNumber)
p.a = "" discard getToken(p) else: - raiseUndefinedError(p, "Undefined value: '"&p.a&"'") + raiseUndefined(p, "Undefined value: '"&p.a&"'") result.filename = p.filename proc `$`*(a: MinValue): string =
@@ -81,21 +81,10 @@ unsafe*: bool
In* = var MinInterpreter MinOperator* = proc (i: In) MinSigil* = proc (i: In, sym: string) - MinError* = enum - errSystem, - errParser, - errGeneric, - errEmptyStack, - errNoQuotation, - errUndefined, - errIncorrect, - errRuntime, - errTwoNumbersRequired, - errDivisionByZero, - errTwoQuotationsRequired MinParsingError* = ref object of ValueError MinUndefinedError* = ref object of ValueError MinInvalidError* = ref object of ValueError + MinEmptyStackError* = ref object of ValueError MinRuntimeError* = ref object of SystemError qVal*: seq[MinValue]
@@ -89,99 +89,110 @@ template alias*[T](varname: untyped, value: var T) =
var varname {.inject.}: type(value) shallowCopy varname, value +# Error Helpers + +proc raiseInvalid*(msg: string) = + raise MinInvalidError(msg: msg) + +proc raiseUndefined*(msg: string) = + raise MinUndefinedError(msg: msg) + +proc raiseRuntime*(msg: string, qVal: var seq[MinValue]) = + raise MinRuntimeError(msg: msg, qVal: qVal) + # Validators proc reqBool*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isBool: - raise MinInvalidError(msg: "A bool value is required on the stack") + raiseInvalid("A bool value is required on the stack") proc reqTwoBools*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not a.isBool or not b.isBool: - raise MinInvalidError(msg: "Two bool values are required on the stack") + raiseInvalid("Two bool values are required on the stack") proc reqInt*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isInt: - raise MinInvalidError(msg: "An integer is required on the stack") + raiseInvalid("An integer is required on the stack") proc reqTwoInts*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not a.isInt or not b.isInt: - raise MinInvalidError(msg: "Two integers are required on the stack") + raiseInvalid("Two integers are required on the stack") proc reqQuotation*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isQuotation: - raise MinInvalidError(msg: "A quotation is required on the stack") + raiseInvalid("A quotation is required on the stack") proc reqIntAndQuotation*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not (a.isInt and b.isQuotation): - raise MinInvalidError(msg: "An integer and a quotation are required on the stack") + raiseInvalid("An integer and a quotation are required on the stack") proc reqTwoNumbers*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not (a.isNumber and b.isNumber): - raise MinInvalidError(msg: "Two numbers are required on the stack") + raiseInvalid("Two numbers are required on the stack") proc reqTwoNumbersOrStrings*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not (a.isString and b.isString or a.isNumber and b.isNumber): - raise MinInvalidError(msg: "Two numbers or two strings are required on the stack") + raiseInvalid("Two numbers or two strings are required on the stack") proc reqString*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isString: - raise MinInvalidError(msg: "A string is required on the stack") + raiseInvalid("A string is required on the stack") proc reqStringOrQuotation*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isQuotation and not a.isString: - raise MinInvalidError(msg: "A quotation or a string is required on the stack") + raiseInvalid("A quotation or a string is required on the stack") proc reqStringOrSymbol*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isStringLike: - raise MinInvalidError(msg: "A symbol or a string is required on the stack") + raiseInvalid("A symbol or a string is required on the stack") proc reqTwoStrings*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not a.isString or not b.isString: - raise MinInvalidError(msg: "Two strings are required on the stack") + raiseInvalid("Two strings are required on the stack") proc reqThreeStrings*(i: var MinInterpreter, a, b, c: var MinValue) = a = i.pop b = i.pop c = i.pop if not a.isString or not b.isString or not c.isString: - raise MinInvalidError(msg: "Three strings are required on the stack") + raiseInvalid("Three strings are required on the stack") proc reqTwoQuotations*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not a.isQuotation or not b.isQuotation: - raise MinInvalidError(msg: "Two quotations are required on the stack") + raiseInvalid("Two quotations are required on the stack") proc reqTwoQuotationsOrStrings*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not (a.isQuotation and b.isQuotation or a.isString and b.isString): - raise MinInvalidError(msg: "Two quotations or two strings are required on the stack") + raiseInvalid("Two quotations or two strings are required on the stack") proc reqThreeQuotations*(i: var MinInterpreter, a, b, c: var MinValue) = a = i.pop b = i.pop c = i.pop if not a.isQuotation or not b.isQuotation or not c.isQuotation: - raise MinInvalidError(msg: "Three quotations are required on the stack") + raiseInvalid("Three quotations are required on the stack") proc reqFourQuotations*(i: var MinInterpreter, a, b, c, d: var MinValue) = a = i.pop@@ -189,10 +200,10 @@ b = i.pop
c = i.pop d = i.pop if not a.isQuotation or not b.isQuotation or not c.isQuotation or not d.isQuotation: - raise MinInvalidError(msg: "Four quotations are required on the stack") + raiseInvalid("Four quotations are required on the stack") proc reqTwoSimilarTypesNonSymbol*(i: var MinInterpreter, a, b: var MinValue) = a = i.pop b = i.pop if not ((a.kind == a.kind or (a.isNumber and a.isNumber)) and not a.isSymbol): - raise MinInvalidError(msg: "Two non-symbol values of similar type are required on the stack") + raiseInvalid("Two non-symbol values of similar type are required on the stack")
@@ -50,7 +50,7 @@ symbol = q2.strVal
elif q2.isQuotation and q2.qVal.len == 1 and q2.qVal[0].kind == minSymbol: symbol = q2.qVal[0].symVal else: - raise MinInvalidError(msg:"The top quotation must contain only one symbol value") + raiseInvalid("The top quotation must contain only one symbol value") i.debug "[define] " & symbol & " = " & $q1 i.scope.symbols[symbol] = proc(i: In) = i.push q1.qVal@@ -66,19 +66,19 @@ symbol = q2.getString
elif q2.isQuotation and q2.qVal.len == 1 and q2.qVal[0].kind == minSymbol: symbol = q2.qVal[0].symVal else: - raise MinInvalidError(msg:"The top quotation must contain only one symbol value") + raiseInvalid("The top quotation must contain only one symbol value") i.debug "[bind] " & symbol & " = " & $q1 let res = i.scope.setSymbol(symbol) do (i: In): i.push q1.qVal if not res: - raise MinUndefinedError(msg:"Attempting to bind undefined symbol: " & symbol) + raiseUndefined("Attempting to bind undefined symbol: " & symbol) .symbol("delete") do (i: In): var sym: MinValue i.reqStringOrSymbol sym let res = i.scope.delSymbol(sym.getString) if not res: - raise MinUndefinedError(msg:"Attempting to delete undefined symbol: " & sym.getString) + raiseUndefined("Attempting to delete undefined symbol: " & sym.getString) .symbol("scope") do (i: In): var code: MinValue@@ -90,14 +90,11 @@
.symbol("import") do (i: In): var mdl: MinValue var name: string - try: - name = i.pop.strVal - i.scope.getSymbol(name)(i) - mdl = i.pop - except: - echo getCurrentExceptionMsg() + name = i.pop.strVal + i.scope.getSymbol(name)(i) + mdl = i.pop if not mdl.isQuotation: - i.error errNoQuotation + raiseInvalid("No quotation was found on the stack") if mdl.scope.isNotNil: #echo "MODULE SCOPE PARENT: ", mdl.scope.name for sym, val in mdl.scope.symbols.pairs:@@ -114,15 +111,15 @@ if q1.qVal.len == 1 and q1.qVal[0].kind == minSymbol:
var symbol = q1.qVal[0].symVal if symbol.len == 1: if i.scope.getSigil(symbol).isNotNil: - raise MinRuntimeError(msg:"Sigil '$1' already exists" % [symbol]) + raiseInvalid("Sigil '$1' already exists" % [symbol]) i.scope.sigils[symbol] = proc(i: In) = i.evaluating = true i.push q2.qVal i.evaluating = false else: - raise MinInvalidError(msg:"A sigil can only have one character") + raiseInvalid("A sigil can only have one character") else: - raise MinInvalidError(msg:"The top quotation must contain only one symbol value") + raiseInvalid("The top quotation must contain only one symbol value") .symbol("eval") do (i: In): var s: MinValue@@ -143,21 +140,21 @@ i.reqTwoQuotations symbols, target
let vals = symbols.qVal var q: MinValue if vals.len == 0: - raise MinRuntimeError(msg:"No symbol to call") + raiseInvalid("No symbol to call") let origScope = i.scope i.scope = target.scope for c in 0..vals.len-1: if not vals[c].isStringLike: - raise MinInvalidError(msg:"Quotation must contain only symbols or strings") + raiseInvalid("Quotation must contain only symbols or strings") let symbol = vals[c].getString let qProc = i.scope.getSymbol(symbol) if qProc.isNil: - raise MinUndefinedError(msg:"Symbol '$1' not found in scope '$2'" % [symbol, i.scope.fullname]) + raiseUndefined("Symbol '$1' not found in scope '$2'" % [symbol, i.scope.fullname]) qProc(i) if vals.len > 1 and c < vals.len-1: q = i.pop if not q.isQuotation: - raise MinRuntimeError(msg:"Unable to evaluate symbol '$1'" % [symbol]) + raiseInvalid("Unable to evaluate symbol '$1'" % [symbol]) i.scope = q.scope i.scope = origScope@@ -172,13 +169,13 @@
.symbol("raise") do (i: In): var err: MinValue i.reqQuotation err - raise MinRuntimeError(msg:"($1) $2" % [err.qVal[0].getString, err.qVal[1].getString], qVal: err.qVal) + raiseRuntime("($1) $2" % [err.qVal[0].getString, err.qVal[1].getString], err.qVal) .symbol("try") do (i: In): var prog: MinValue i.reqQuotation prog if prog.qVal.len < 2: - raise MinInvalidError(msg:"Quotation must contain at least two elements") + raiseInvalid("Quotation must contain at least two elements") var code = prog.qVal[0] var catch = prog.qVal[1] var final: MinValue@@ -187,7 +184,7 @@ if prog.qVal.len > 2:
final = prog.qVal[2] hasFinally = true if (not code.isQuotation or not catch.isQuotation) or (hasFinally and not final.isQuotation): - raise MinInvalidError(msg:"Quotation must contain at least two quotations") + raiseInvalid("Quotation must contain at least two quotations") i.unsafe = true try: i.unquote("<try-code>", code)@@ -199,7 +196,7 @@ i.unquote("<try-catch>", catch)
except: i.unsafe = false let e = getCurrentException() - i.push @[e.name.newVal, e.msg.newVal].newVal + i.push @[regex.replace($e.name, ":.+$", "").newVal, e.msg.newVal].newVal i.unquote("<try-catch>", catch) finally: if hasFinally:@@ -215,7 +212,7 @@ results[][c] = i.stack[0]
var results = newSeq[MinValue](q.qVal.len) for c in 0..q.qVal.high: if not q.qVal[c].isQuotation: - raise MinInvalidError(msg: "Item #$1 is not a quotation" % [$(c+1)]) + raiseInvalid("Item #$1 is not a quotation" % [$(c+1)]) var i2 = i.copy(i.filename) var res: MinStack = newSeq[MinValue](0) pRun coroutine, (i2, c, results.addr)
@@ -89,10 +89,10 @@ (5 (dup 0 ==) (1 +) (dup 1 -) ( * ) linrec 120 ==) assert ;factorial of 5
( ( - (() 1 at) + (pop) (first) - ("Caught an " swap concat) - ) try "Caught an IndexError" ==) assert + ("Caught a " swap concat) + ) try "Caught a MinEmptyStackError" ==) assert ( (