all repos — min @ edf32d56aee2790df0584d1a8099b1c1e454a4d2

A small but practical concatenative programming language.

Now rolling back stack changes on errors.
h3rald h3rald@h3rald.com
Sat, 18 Jun 2016 13:40:28 +0200
commit

edf32d56aee2790df0584d1a8099b1c1e454a4d2

parent

2ac8f37b62655e812cc5ec544e27d42a1ddad6da

4 files changed, 93 insertions(+), 12 deletions(-)

jump to
M core/utils.nimcore/utils.nim

@@ -114,67 +114,85 @@

proc reqBool*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isBool: + i.push a 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: + i.push b + i.push a raiseInvalid("Two bool values are required on the stack") proc reqInt*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isInt: + i.push a 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: + i.push b + i.push a raiseInvalid("Two integers are required on the stack") proc reqQuotation*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isQuotation: + i.push a 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): + i.push b + i.push a 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): + i.push b + i.push a 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): + i.push b + i.push a 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: + i.push a 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: + i.push a 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: + i.push a 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: + i.push b + i.push a raiseInvalid("Two strings are required on the stack") proc reqThreeStrings*(i: var MinInterpreter, a, b, c: var MinValue) =

@@ -182,18 +200,25 @@ a = i.pop

b = i.pop c = i.pop if not a.isString or not b.isString or not c.isString: + i.push c + i.push b + i.push a 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: + i.push b + i.push a 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): + i.push b + i.push a raiseInvalid("Two quotations or two strings are required on the stack") proc reqThreeQuotations*(i: var MinInterpreter, a, b, c: var MinValue) =

@@ -201,6 +226,9 @@ a = i.pop

b = i.pop c = i.pop if not a.isQuotation or not b.isQuotation or not c.isQuotation: + i.push c + i.push b + i.push a raiseInvalid("Three quotations are required on the stack") proc reqFourQuotations*(i: var MinInterpreter, a, b, c, d: var MinValue) =

@@ -209,15 +237,22 @@ 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: + i.push d + i.push c + i.push b + i.push a 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): + i.push b + i.push a raiseInvalid("Two non-symbol values of similar type are required on the stack") proc reqObject*(i: var MinInterpreter, t: string, a: var MinValue) = a = i.pop if not a.isQuotation or a.objType.isNil or a.objType != t: + i.push a raiseInvalid("A $1 object is required" % [t])
M lib/min_lang.nimlib/min_lang.nim

@@ -43,6 +43,7 @@ .symbol("define") do (i: In):

var q2 = i.pop # new (can be a quoted symbol or a string) var q1 = i.pop # existing (auto-quoted) var symbol: string + let rawQ1 = q1 if not q1.isQuotation: q1 = @[q1].newVal if q2.isString:

@@ -50,6 +51,8 @@ symbol = q2.strVal

elif q2.isQuotation and q2.qVal.len == 1 and q2.qVal[0].kind == minSymbol: symbol = q2.qVal[0].symVal else: + i.push rawQ1 + i.push q2 raiseInvalid("The top quotation must contain only one symbol value") i.debug "[define] " & symbol & " = " & $q1 i.scope.symbols[symbol] = proc(i: In) =

@@ -58,6 +61,7 @@

.symbol("bind") do (i: In): var q2 = i.pop # new (can be a quoted symbol or a string) var q1 = i.pop # existing (auto-quoted) + let rawQ1 = q1 var symbol: string if not q1.isQuotation: q1 = @[q1].newVal

@@ -66,11 +70,15 @@ symbol = q2.getString

elif q2.isQuotation and q2.qVal.len == 1 and q2.qVal[0].kind == minSymbol: symbol = q2.qVal[0].symVal else: + i.push rawQ1 + i.push q2 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: + i.push rawQ1 + i.push q2 raiseUndefined("Attempting to bind undefined symbol: " & symbol) .symbol("delete") do (i: In):

@@ -78,6 +86,7 @@ var sym: MinValue

i.reqStringOrSymbol sym let res = i.scope.delSymbol(sym.getString) if not res: + i.push sym raiseUndefined("Attempting to delete undefined symbol: " & sym.getString) .symbol("scope") do (i: In):

@@ -88,13 +97,12 @@ i.unquote("<scope>", code)

i.push @[code].newVal .symbol("import") do (i: In): - var mdl: MinValue + var mdl, rawName: MinValue var name: string - name = i.pop.strVal + i.reqString rawName + name = rawName.strVal i.scope.getSymbol(name)(i) - mdl = i.pop - if not mdl.isQuotation: - raiseInvalid("No quotation was found on the stack") + i.reqQuotation mdl if mdl.scope.isNotNil: #echo "MODULE SCOPE PARENT: ", mdl.scope.name for sym, val in mdl.scope.symbols.pairs:

@@ -111,14 +119,20 @@ 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: + i.push q2 + i.push q1 raiseInvalid("Sigil '$1' already exists" % [symbol]) i.scope.sigils[symbol] = proc(i: In) = i.evaluating = true i.push q2.qVal i.evaluating = false else: + i.push q2 + i.push q1 raiseInvalid("A sigil can only have one character") else: + i.push q2 + i.push q1 raiseInvalid("The top quotation must contain only one symbol value") .symbol("eval") do (i: In):

@@ -140,20 +154,27 @@ i.reqTwoQuotations symbols, target

let vals = symbols.qVal var q: MinValue if vals.len == 0: + i.push target + i.push symbols 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: + i.push target + i.push symbols raiseInvalid("Quotation must contain only symbols or strings") let symbol = vals[c].getString let qProc = i.scope.getSymbol(symbol) if qProc.isNil: + i.push target + i.push symbols 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: + i.push q raiseInvalid("Unable to evaluate symbol '$1'" % [symbol]) i.scope = q.scope i.scope = origScope

@@ -174,26 +195,35 @@

.symbol("try") do (i: In): var prog: MinValue i.reqQuotation prog - if prog.qVal.len < 2: - raiseInvalid("Quotation must contain at least two elements") + if prog.qVal.len == 0: + i.push prog + raiseInvalid("Quotation must contain at least one element") var code = prog.qVal[0] - var catch = prog.qVal[1] - var final: MinValue + var final, catch: MinValue var hasFinally = false + var hasCatch = false + if prog.qVal.len > 1: + catch = prog.qVal[1] + hasCatch = true 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): - raiseInvalid("Quotation must contain at least two quotations") + if (not code.isQuotation) or (hasCatch and not catch.isQuotation) or (hasFinally and not final.isQuotation): + i.push prog + raiseInvalid("Quotation must contain at one quotation") i.unsafe = true try: i.unquote("<try-code>", code) except MinRuntimeError: + if not hasCatch: + return i.unsafe = false let e = (MinRuntimeError)getCurrentException() i.push e.qVal.newVal i.unquote("<try-catch>", catch) except: + if not hasCatch: + return i.unsafe = false let e = getCurrentException() i.push @[regex.replace($e.name, ":.+$", "").newVal, e.msg.newVal].newVal

@@ -212,6 +242,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: + i.push q raiseInvalid("Item #$1 is not a quotation" % [$(c+1)]) var i2 = i.copy(i.filename) var res: MinStack = newSeq[MinValue](0)

@@ -254,10 +285,12 @@ var q: MinValue

i.reqStringOrQuotation q if q.isQuotation: if q.qVal.len == 0: + i.push q raiseOutOfBounds("Quotation is empty") i.push q.qVal[0] elif q.isString: if q.strVal.len == 0: + i.push q raiseOutOfBounds("String is empty") i.push newVal($q.strVal[0])

@@ -266,10 +299,12 @@ var q: MinValue

i.reqStringOrQuotation q if q.isQuotation: if q.qVal.len == 0: + i.push q raiseOutOfBounds("Quotation is empty") i.push newVal(q.qVal[1..q.qVal.len-1]) elif q.isString: if q.strVal.len == 0: + i.push q raiseOutOfBounds("String is empty") i.push newVal(q.strVal[1..q.strVal.len-1])

@@ -300,6 +335,8 @@ .symbol("at") do (i: In):

var index, q: MinValue i.reqIntAndQuotation index, q if q.qVal.len-1 < index.intVal: + i.push q + i.push index raiseOutOfBounds("Insufficient items in quotation") i.push q.qVal[index.intVal.int]

@@ -331,6 +368,8 @@ .symbol("times") do (i: In):

var t, prog: MinValue i.reqIntAndQuotation t, prog if t.intVal < 1: + i.push prog + i.push t raiseInvalid("A non-zero natural number is required") for c in 1..t.intVal: i.unquote("<times-quotation>", prog)
M lib/min_net.nimlib/min_net.nim

@@ -14,13 +14,17 @@ var q: MinValue

i.reqQuotation q # (ipv4 stream tcp) if q.qVal.len < 3 or not (q.qVal[0].isSymbol and q.qVal[1].isSymbol and q.qVal[2].isSymbol): + i.push q raiseInvalid("Quotation must contain three symbols for <domain> <type> <protocol>") let vals = q.qVal if not ["ipv4", "ipv6"].contains(vals[0].symVal): + i.push q raiseInvalid("Domain symbol must be 'ipv4' or 'ipv6'") if not ["stream", "dgram"].contains(vals[1].symVal): + i.push q raiseInvalid("Type symbol must be 'stream' or 'dgram'") if not ["tcp", "udp"].contains(vals[2].symVal): + i.push q raiseInvalid("Protocol symbol must be 'tcp' or 'udp'") var domain: Domain
M tests/lang.mintests/lang.min

@@ -3,7 +3,7 @@ #test

"lang" describe - (symbols size put 178 ==) assert + (symbols size 178 ==) assert (sigils size 10 ==) assert

@@ -99,6 +99,9 @@ (

(("TestError" "Test Message") raise) (1 at) ) try "Test Message" ==) assert + + ( + (("test" (1 2) :)) try getstack ("test" (1 2)) ==) assert ( (