all repos — min @ 41ef6b03007f894516fa6f06624da7fb848e210e

A small but practical concatenative programming language.

feat(modules) Implemented test module; fixed map implementation.
h3rald h3rald@h3rald.com
Sun, 22 May 2016 16:39:22 +0200
commit

41ef6b03007f894516fa6f06624da7fb848e210e

parent

b6be71e4892cb35d73d42f933402456c080ac6fd

7 files changed, 214 insertions(+), 66 deletions(-)

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

@@ -20,22 +20,79 @@ var ROOT*: ref MinScope = new MinScope

ROOT.name = "ROOT" +proc fullname*(scope: ref MinScope): string = + result = scope.name + if not scope.parent.isNil: + result = scope.parent.fullname & ":" & result + proc getSymbol*(scope: ref MinScope, key: string): MinOperator = if scope.symbols.hasKey(key): return scope.symbols[key] elif not scope.parent.isNil: return scope.parent.getSymbol(key) +proc setSymbol*(scope: ref MinScope, key: string, value: MinOperator): bool {.discardable.}= + result = false + # check if a symbol already exists in parent scope + if not scope.parent.isNil and scope.parent.symbols.hasKey(key): + scope.parent.symbols[key] = value + #echo "($1) SET EXISTING SYMBOL: $2" % [scope.parent.fullname, key] + return true + else: + # Go up the scope chain and attempt to find the symbol + if not scope.parent.isNil: + result = scope.parent.setSymbol(key, value) + if not result: + # Define local variable + #echo "($1) SET LOCAL SYMBOL: $2" % [scope.fullname, key] + scope.symbols[key] = value + return true + proc getSigil*(scope: ref MinScope, key: string): MinOperator = if scope.sigils.hasKey(key): return scope.sigils[key] elif not scope.parent.isNil: return scope.parent.getSigil(key) +proc dump*(i: MinInterpreter): string = + var s = "" + for item in i.stack: + s = s & $item & " " + return s + +proc debug*(i: var MinInterpreter, value: MinValue) = + if i.debugging: + stderr.writeLine("-- " & i.dump & $value) + +proc debug*(i: var MinInterpreter, value: string) = + if i.debugging: + stderr.writeLine("-- " & value) + +template newScope*(i: In, id: string, q: MinValue, body: stmt): stmt {.immediate.}= + q.scope = new MinScope + q.scope.name = id + q.scope.parent = i.scope + #i.debug "[scope] " & q.scope.fullname + let scope = i.scope + i.scope = q.scope + body + #i.debug "[scope] " & scope.fullname + i.scope = scope + +template newDisposableScope*(i: In, id: string, body: stmt): stmt {.immediate.}= + var q = MinValue(kind: minQuotation, qVal: newSeq[MinValue](0)) + q.scope = new MinScope + q.scope.name = id + q.scope.parent = i.scope + #i.debug "[scope] " & q.scope.fullname + let scope = i.scope + i.scope = q.scope + body + #i.debug "[scope] " & scope.fullname + i.scope = scope + proc newMinInterpreter*(debugging = false): MinInterpreter = var st:MinStack = newSeq[MinValue](0) - #var scope: ref MinScope = new MinScope - #scope.parent = ROOT var pr:MinParser var i:MinInterpreter = MinInterpreter( filename: "input",

@@ -62,16 +119,6 @@

proc close*(i: var MinInterpreter) = i.parser.close(); -proc dump*(i: MinInterpreter): string = - var s = "" - for item in i.stack: - s = s & $item & " " - return s - -proc debug(i: var MinInterpreter, value: MinValue) = - if i.debugging: - stderr.writeLine("-- " & i.dump & $value) - proc push*(i: var MinInterpreter, val: MinValue) = i.debug val if val.kind == minSymbol:

@@ -82,7 +129,8 @@ let sigil = "" & symbol[0]

let symbolProc = i.scope.getSymbol(symbol) if not symbolProc.isNil: try: - symbolProc(i) + i.newDisposableScope("<" & symbol & ">"): + symbolProc(i) except: i.error(errSystem, getCurrentExceptionMsg()) else:

@@ -147,7 +195,7 @@ finally:

i.filename = fn proc apply*(i: var MinInterpreter, symbol: string) = - i.scope.symbols[symbol](i) + i.scope.getSymbol(symbol)(i) proc copystack*(i: var MinInterpreter): MinStack = var s = newSeq[MinValue](0)
M core/parser.nimcore/parser.nim

@@ -1,4 +1,4 @@

-# Adapted from: https://github.com/Araq/Nimrod/blob/v0.9.6/lib/pure/min.nim +# Adapted from: https://github.com/Araq/Nimrod/blob/v0.9.6/lib/pure/json.nim import lexbase, strutils, streams, unicode, tables import types

@@ -379,14 +379,33 @@ return $a.intVal

of minFloat: return $a.floatVal of minQuotation: - var q = "( " + var q = "(" + for i in a.qVal: + q = q & $i & " " + q = q.strip & ")" + return q + +proc `$$`*(a: MinValue): string = + case a.kind: + of minBool: + return $a.boolVal + of minSymbol: + return a.symVal + of minString: + return a.strVal + of minInt: + return $a.intVal + of minFloat: + return $a.floatVal + of minQuotation: + var q = "(" for i in a.qVal: q = q & $i & " " - q = q & ")" + q = q.strip & ")" return q proc print*(a: MinValue) = - stdout.write($a) + stdout.write($$a) proc `==`*(a: MinValue, b: MinValue): bool = if a.kind == minInt and b.kind == minInt:
M core/utils.nimcore/utils.nim

@@ -64,8 +64,8 @@ return scope

proc symbol*(scope: ref MinScope, sym: string, p: MinOperator): ref MinScope = scope.symbols[sym] = p - if not scope.parent.isNil: - scope.parent.symbols[scope.name & ":" & sym] = p + #if not scope.parent.isNil: + # scope.parent.symbols[scope.name & ":" & sym] = p return scope proc sigil*(scope: ref MinScope, sym: string, p: MinOperator): ref MinScope =

@@ -79,3 +79,10 @@ mdl.scope.previous.symbols[scope.name] = proc(i: var MinInterpreter) =

i.evaluating = true i.push mdl i.evaluating = false + +template `<-`*[T](target, source: var T) = + shallowCopy target, source + +template alias*[T](varname: untyped, value: var T) = + var varname {.inject.}: type(value) + shallowCopy varname, value
M lib/io.nimlib/io.nim

@@ -10,11 +10,14 @@

define("io") + .symbol("newline") do (i: In): + echo "" + .symbol("puts") do (i: In): let a = i.peek - echo a + echo $$a - .symbol("puts") do (i: In): + .symbol("gets") do (i: In): i.push newVal(stdin.readLine()) .symbol("print") do (i: In):
M lib/lang.nimlib/lang.nim

@@ -12,13 +12,16 @@ quit(0)

.symbol("symbols") do (i: In): var q = newSeq[MinValue](0) - for s in i.scope.symbols.keys: - q.add s.newVal + var scope = i.scope.parent + while not scope.isNil: + for s in scope.symbols.keys: + q.add s.newVal + scope = scope.parent i.push q.newVal .symbol("sigils") do (i: In): var q = newSeq[MinValue](0) - for s in i.scope.sigils.keys: + for s in i.scope.parent.sigils.keys: q.add s.newVal i.push q.newVal

@@ -31,7 +34,7 @@ echo "Debugging: $1" % [$i.debugging]

# Language constructs - .symbol("set") do (i: In): + .symbol("let") 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

@@ -43,9 +46,26 @@ elif q2.isQuotation and q2.qVal.len == 1 and q2.qVal[0].kind == minSymbol:

symbol = q2.qVal[0].symVal else: i.error errIncorrect, "The top quotation must contain only one symbol value" - if not i.scope.getSymbol(symbol).isNil: - i.error errSystem, "Symbol '$1' already exists" % [symbol] - i.scope.symbols[symbol] = proc(i: var MinInterpreter) = + i.debug "[let] " & symbol & " = " & $q1 + i.scope.parent.symbols[symbol] = proc(i: var MinInterpreter) = + i.evaluating = true + i.push q1.qVal + i.evaluating = false + + .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) + var symbol: string + if not q1.isQuotation: + q1 = @[q1].newVal + if q2.isString: + symbol = q2.strVal + elif q2.isQuotation and q2.qVal.len == 1 and q2.qVal[0].kind == minSymbol: + symbol = q2.qVal[0].symVal + else: + i.error errIncorrect, "The top quotation must contain only one symbol value" + i.debug "[bind] " & symbol & " = " & $q1 + i.scope.setSymbol(symbol) do (i: In): i.evaluating = true i.push q1.qVal i.evaluating = false

@@ -54,11 +74,11 @@ .symbol("unset") do (i: In):

var q1 = i.pop if q1.qVal.len == 1 and q1.qVal[0].kind == minSymbol: var symbol = q1.qVal[0].symVal - i.scope.symbols.excl symbol + i.scope.parent.symbols.excl symbol else: i.error errIncorrect, "The top quotation must contain only one symbol value" - .symbol("define") do (i: In): + .symbol("module") do (i: In): let name = i.pop var code = i.pop if not name.isString or not code.isQuotation:

@@ -66,21 +86,14 @@ i.error(errIncorrect, "A string and a quotation are require on the stack")

let id = name.strVal let scope = i.scope let stack = i.copystack - i.scope = new MinScope - code.scope = i.scope - i.scope.parent = scope - for item in code.qVal: - i.push item - let p = proc(i: var MinInterpreter) = - i.evaluating = true - i.push code - i.evaluating = false - let symbols = i.scope.symbols - i.scope = scope - i.scope.symbols[id] = p - # Define symbols in parent scope as well - for sym, val in symbols.pairs: - i.scope.symbols[id & ":" & sym] = val + i.newScope(id, code): #<-- + for item in code.qVal: + i.push item + let p = proc(i: In) = + i.evaluating = true + i.push code + i.evaluating = false + i.scope.parent.symbols[id] = p i.stack = stack .symbol("import") do (i: In):

@@ -89,12 +102,14 @@ try:

i.scope.getSymbol(i.pop.strVal)(i) mdl = i.pop except: - discard + echo getCurrentExceptionMsg() if not mdl.isQuotation: i.error errNoQuotation if not mdl.scope.isNil: + #echo "MODULE SCOPE PARENT: ", mdl.scope.name for sym, val in mdl.scope.symbols.pairs: - i.scope.symbols[sym] = val + i.debug "[$1 - import] $2:$3" % [i.scope.parent.name, i.scope.name, sym] + i.scope.parent.symbols[sym] = val .sigil("'") do (i: In): i.push(@[MinValue(kind: minSymbol, symVal: i.pop.strVal)].newVal)

@@ -108,9 +123,9 @@ if q1.isQuotation and q2.isQuotation:

if q1.qVal.len == 1 and q1.qVal[0].kind == minSymbol: var symbol = q1.qVal[0].symVal if symbol.len == 1: - if not i.scope.getSigil(symbol).isNil: + if i.scope.parent.sigils.hasKey(symbol): i.error errSystem, "Sigil '$1' already exists" % [symbol] - i.scope.sigils[symbol] = proc(i: var MinInterpreter) = + i.scope.parent.sigils[symbol] = proc(i: var MinInterpreter) = i.evaluating = true i.push q2.qVal i.evaluating = false

@@ -185,17 +200,14 @@ let a = i.pop

i.push MinValue(kind: minQuotation, qVal: @[a]) .symbol("unquote") do (i: In): - let q = i.pop + var q = i.pop if not q.isQuotation: i.error errNoQuotation - let scope = i.scope - i.scope = new MinScope - i.scope.parent = scope - for item in q.qVal: - i.push item - i.scope = scope + i.newScope("<unquote-push>", q): + for item in q.qVal: + i.push item - .symbol("cons") do (i: In): + .symbol("append") do (i: In): var q = i.pop let v = i.pop if not q.isQuotation:

@@ -203,6 +215,14 @@ i.error errNoQuotation

q.qVal.add v i.push q + .symbol("cons") do (i: In): + var q = i.pop + let v = i.pop + if not q.isQuotation: + i.error errNoQuotation + q.qVal = @[v] & q.qVal + i.push q + .symbol("at") do (i: In): var index = i.pop var q = i.pop

@@ -221,7 +241,7 @@ i.push litem

for pitem in prog.qVal: i.push pitem i.apply("swap") - i.apply("cons") + i.apply("append") else: i.error(errIncorrect, "Two quotations are required on the stack")

@@ -251,6 +271,7 @@ i.push fpath.qVal

else: i.error(errIncorrect, "Three quotations are required on the stack") + # TODO test (add new scope?) .symbol("while") do (i: In): let d = i.pop let b = i.pop

@@ -264,6 +285,7 @@ check = i.pop

else: i.error(errIncorrect, "Two quotations are required on the stack") + # TODO test (add new scope?) .symbol("filter") do (i: In): let filter = i.pop let list = i.pop
M lib/prelude.minlib/prelude.min

@@ -10,16 +10,18 @@ #sys

#time // Common sigils -(set) (:) sigil +(bind) (:) sigil +(let) (.) sigil (getenv) ($) sigil (system) (!) sigil (run) (&) sigil -(define) (=) sigil +(module) (=) sigil (load) (@) sigil // Aliases -'set :: -'define := +'bind :: +'let :. +'module := 'import :# 'exit :quit 'concat :,

@@ -36,13 +38,14 @@ 'run :&

'getenv :$ 'pop :zap 'quote :unit +'quote :' 'unquote :i 'unquote :apply +'unquote :-> 'filter :select 'clear :empty +'cons :prepend 'match :~ -(".") :. -("..") :.. // Mathematical Operators (1 +) :succ

@@ -70,4 +73,5 @@ ((() cons cons) dip swap i) :bury2

((() cons cons cons) dip swap i) :bury3 // Other -(dup unset set) :reset +//(dup unset set) :reset +
A tests/test.min

@@ -0,0 +1,45 @@

+( + (("OK")) .ok + + ( + quote .check // save the check quotation to validate + quote .results // save the result symbol to update + check dup first swap rest + ( + pop + ok results -> append quote results bind + "." print pop + ) + ( + pop + check results -> append quote results bind + "x" print pop + ) + ifte + ) :assert + + ( + ( + quote .result + result + (ok !=) + ("FAILED: " print pop result puts) + () + ifte + ) + map + ) :report + +) =test + +#test + +// Sample unit test program +(()) :maths +'maths (2 3 ==) assert +'maths (2 1 >) assert +'maths (2 1 <) assert +'maths (4 4 ==) assert +'maths (7 7 ==) assert +newline +maths report