Adapted 70 symbols from min source code.
h3rald h3rald@h3rald.com
Tue, 23 Mar 2021 19:20:27 +0100
15 files changed,
2260 insertions(+),
0 deletions(-)
A
build.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash + +nim c -d:release --opt:size --gc:orc --deepcopy:on mn
A
mn.nim
@@ -0,0 +1,167 @@
+import + streams, + strutils, + mnpkg/baseutils, + mnpkg/parser, + mnpkg/value, + mnpkg/scope, + mnpkg/interpreter, + mnpkg/utils, + mnpkg/lang + +export + parser, + interpreter, + utils, + value, + scope, + lang + +proc stdLib*(i: In) = + i.lang_module + +proc interpret*(i: In, s: Stream) = + i.stdLib() + i.open(s, i.filename) + discard i.parser.getToken() + try: + i.interpret() + except: + i.error(getCurrentExceptionMsg()) + i.close() + +proc interpret*(i: In, s: string): MnValue = + i.open(newStringStream(s), i.filename) + discard i.parser.getToken() + try: + result = i.interpret() + except: + i.error(getCurrentExceptionMsg()) + i.close() + +proc mnFile*(filename: string, op = "interpret", main = true): seq[string] {.discardable.} + +proc mnStream(s: Stream, filename: string, op = "interpret", main = true): seq[string] {.discardable.}= + var i = newMinInterpreter(filename = filename) + i.pwd = filename.parentDirEx + i.interpret(s) + newSeq[string](0) + +proc mnStr*(buffer: string) = + mnStream(newStringStream(buffer), "input") + +proc mnFile*(filename: string, op = "interpret", main = true): seq[string] {.discardable.} = + var fn = filename + if not filename.endsWith(".mn"): + fn &= ".mn" + var fileLines = newSeq[string](0) + var contents = "" + try: + fileLines = fn.readFile().splitLines() + except: + stderr.writeLine("Cannot read from file: " & fn) + quit(3) + if fileLines[0].len >= 2 and fileLines[0][0..1] == "#!": + contents = ";;\n" & fileLines[1..fileLines.len-1].join("\n") + else: + contents = fileLines.join("\n") + mnStream(newStringStream(contents), fn, op, main) + +when isMainModule: + import + terminal, + parseopt, + mnpkg/meta + + var exeName = "mn" + + proc printResult(i: In, res: MnValue) = + if res.isNil: + return + if i.stack.len > 0: + let n = $i.stack.len + if res.isQuotation and res.qVal.len > 1: + echo " (" + for item in res.qVal: + echo " " & $item + echo " ".repeat(n.len) & ")" + elif res.isCommand: + echo " [" & res.cmdVal & "]" + else: + echo " $1" % [$i.stack[i.stack.len - 1]] + + proc mnSimpleRepl*(i: var MnInterpreter) = + i.stdLib() + var s = newStringStream("") + i.open(s, "<repl>") + var line: string + while true: + stdout.write("=| ") + stdout.flushFile() + line = stdin.readLine() + let r = i.interpret($line) + if $line != "": + i.printResult(r) + + proc mnSimpleRepl*() = + var i = newMinInterpreter(filename = "<repl>") + i.mnSimpleRepl() + + + let usage* = """ mn v$version - A truly minimal concatenative programming language + (c) 2021 Fabio Cevasco + + Usage: + $exe [options] [filename] + + Arguments: + filename A $exe file to interpret or compile + Options: + -e, --evaluate Evaluate a $exe program inline + -h, --help Print this help + -d, --debug Enable debug messages + -v, —-version Print the program version""" % [ + "exe", exeName, + "version", pkgVersion, + ] + + var file, s: string = "" + var args = newSeq[string](0) + var p = initOptParser() + + for kind, key, val in getopt(p): + case kind: + of cmdArgument: + args.add key + if file == "": + file = key + of cmdLongOption, cmdShortOption: + case key: + of "debug", "d": + DEBUG = true + of "evaluate", "e": + if file == "": + s = val + of "help", "h": + if file == "": + echo usage + quit(0) + of "version", "v": + if file == "": + echo pkgVersion + quit(0) + else: + discard + else: + discard + var op = "interpret" + if s != "": + mnStr(s) + elif file != "": + mnFile file, op + else: + if isatty(stdin): + mnSimpleRepl() + quit(0) + else: + mnStream newFileStream(stdin), "stdin", op
A
mn.nimble
@@ -0,0 +1,16 @@
+import + mnpkg/meta + +# Package + +version = pkgVersion +author = pkgAuthor +description = pkgDescription +license = "MIT" +bin = @[pkgName] +installFiles = @["mn.yml", "mn.nim"] +installDirs = @["mnpkg"] + +# Dependencies + +requires "nim >= 1.4.4"
A
mn.nims
@@ -0,0 +1,18 @@
+# https://blog.filippo.io/easy-windows-and-linux-cross-compilers-for-macos/ + +switch("amd64.windows.gcc.path", "/usr/local/bin") +switch("amd64.windows.gcc.exe", "x86_64-w64-mingw32-gcc") +switch("amd64.windows.gcc.linkerexe", "x86_64-w64-mingw32-gcc") + +switch("amd64.linux.gcc.path", "/usr/local/bin") +switch("amd64.linux.gcc.exe", "x86_64-linux-musl-gcc") +switch("amd64.linux.gcc.linkerexe", "x86_64-linux-musl-gcc") + +switch("opt", "size") + +when not defined(dev): + switch("define", "release") + +if findExe("musl-gcc") != "": + switch("gcc.exe", "musl-gcc") + switch("gcc.linkerexe", "musl-gcc")
A
mn.yml
@@ -0,0 +1,4 @@
+author: Fabio Cevasco +description: A truly minimal concatenative programming language. +name: mn +version: 0.1.0
A
mnpkg/baseutils.nim
@@ -0,0 +1,11 @@
+import + os + +proc reverse*[T](xs: openarray[T]): seq[T] = + result = newSeq[T](xs.len) + for i, x in xs: + result[result.len-i-1] = x + +proc parentDirEx*(s: string): string = + return s.parentDir +
A
mnpkg/interpreter.nim
@@ -0,0 +1,286 @@
+import + streams, + strutils, + os, + critbits, + algorithm +import + baseutils, + value, + scope, + parser + +type + MnTrappedException* = ref object of CatchableError + MnReturnException* = ref object of CatchableError + MnRuntimeError* = ref object of CatchableError + data*: MnValue + +var DEBUG* {. threadvar .} : bool +DEBUG = false + +proc diff*(a, b: seq[MnValue]): seq[MnValue] = + result = newSeq[MnValue](0) + for it in b: + if not a.contains it: + result.add it + +proc newSym*(i: In, s: string): MnValue = + return MnValue(kind: mnSymbol, symVal: s, filename: i.currSym.filename, line: i.currSym.line, column: i.currSym.column, outerSym: i.currSym.symVal) + +proc copySym*(i: In, sym: MnValue): MnValue = + return MnValue(kind: mnSymbol, symVal: sym.outerSym, filename: sym.filename, line: sym.line, column: sym.column, outerSym: "", docComment: sym.docComment) + +proc raiseRuntime*(msg: string) = + raise MnRuntimeError(msg: msg) + +proc dump*(i: MnInterpreter): string = + var s = "" + for item in i.stack: + s = s & $item & " " + return s + +template withScope*(i: In, res:ref MnScope, body: untyped): untyped = + let origScope = i.scope + try: + i.scope = newScopeRef(origScope) + body + res = i.scope + finally: + i.scope = origScope + +template withScope*(i: In, body: untyped): untyped = + let origScope = i.scope + try: + i.scope = newScopeRef(origScope) + body + finally: + i.scope = origScope + +proc newMinInterpreter*(filename = "input", pwd = ""): MnInterpreter = + var path = pwd + if not pwd.isAbsolute: + path = joinPath(getCurrentDir(), pwd) + var stack:MnStack = newSeq[MnValue](0) + var trace:MnStack = newSeq[MnValue](0) + var stackcopy:MnStack = newSeq[MnValue](0) + var pr:MnParser + var scope = newScopeRef(nil) + var i:MnInterpreter = MnInterpreter( + filename: filename, + pwd: path, + parser: pr, + stack: stack, + trace: trace, + stackcopy: stackcopy, + scope: scope, + currSym: MnValue(column: 1, line: 1, kind: mnSymbol, symVal: "") + ) + return i + +proc copy*(i: MnInterpreter, filename: string): MnInterpreter = + var path = filename + if not filename.isAbsolute: + path = joinPath(getCurrentDir(), filename) + result = newMinInterpreter() + result.filename = filename + result.pwd = path.parentDirEx + result.stack = i.stack + result.trace = i.trace + result.stackcopy = i.stackcopy + result.scope = i.scope + result.currSym = MnValue(column: 1, line: 1, kind: mnSymbol, symVal: "") + +proc formatError(sym: MnValue, message: string): string = + var name = sym.symVal + return "$1($2,$3) [$4]: $5" % [sym.filename, $sym.line, $sym.column, name, message] + +proc formatTrace(sym: MnValue): string = + var name = sym.symVal + if sym.filename == "": + return "<native> in symbol: $1" % [name] + else: + return "$1($2,$3) in symbol: $4" % [sym.filename, $sym.line, $sym.column, name] + +proc stackTrace*(i: In) = + var trace = i.trace + trace.reverse() + for sym in trace: + echo sym.formatTrace + +proc error*(i: In, message: string) = + stderr.writeLine(i.currSym.formatError(message)) + +proc open*(i: In, stream:Stream, filename: string) = + i.filename = filename + i.parser.open(stream, filename) + +proc close*(i: In) = + i.parser.close(); + +proc push*(i: In, val: MnValue) {.gcsafe.} + +proc call*(i: In, q: var MnValue): MnValue {.gcsafe.}= + var i2 = newMinInterpreter("<call>") + i2.trace = i.trace + i2.scope = i.scope + try: + i2.withScope(): + for v in q.qVal: + i2.push v + except: + i.currSym = i2.currSym + i.trace = i2.trace + raise + return i2.stack.newVal + +proc callValue*(i: In, v: var MnValue): MnValue {.gcsafe.}= + var i2 = newMinInterpreter("<call-value>") + i2.trace = i.trace + i2.scope = i.scope + try: + i2.withScope(): + i2.push v + except: + i.currSym = i2.currSym + i.trace = i2.trace + raise + return i2.stack[0] + +proc apply*(i: In, op: MnOperator, sym = "") {.gcsafe.}= + if op.kind == mnProcOp: + op.prc(i) + else: + if op.val.kind == mnQuotation: + var newscope = newScopeRef(i.scope) + i.withScope(newscope): + for e in op.val.qVal: + if e.isSymbol and e.symVal == sym: + raiseInvalid("Symbol '$#' evaluates to itself" % sym) + i.push e + else: + i.push(op.val) + +proc dequote*(i: In, q: var MnValue) = + if q.kind == mnQuotation: + i.withScope(): + let qqval = deepCopy(q.qVal) + for v in q.qVal: + i.push v + q.qVal = qqval + else: + i.push(q) + +proc apply*(i: In, q: var MnValue) {.gcsafe.}= + var i2 = newMinInterpreter("<apply>") + i2.trace = i.trace + i2.scope = i.scope + try: + i2.withScope(): + for v in q.qVal: + if (v.kind == mnQuotation): + var v2 = v + i2.dequote(v2) + else: + i2.push v + except: + i.currSym = i2.currSym + i.trace = i2.trace + raise + i.push i2.stack.newVal + +proc pop*(i: In): MnValue = + if i.stack.len > 0: + return i.stack.pop + else: + raiseEmptyStack() + +# Inherit file/line/column from current symbol +proc pushSym*(i: In, s: string) = + i.push MnValue( + kind: mnSymbol, + symVal: s, + filename: i.currSym.filename, + line: i.currSym.line, + column: i.currSym.column, + outerSym: i.currSym.symVal, + docComment: i.currSym.docComment) + +proc push*(i: In, val: MnValue) {.gcsafe.}= + if val.kind == mnSymbol: + if not i.evaluating: + if val.outerSym != "": + i.currSym = i.copySym(val) + else: + i.currSym = val + i.trace.add val + if DEBUG: + echo "-- push symbol: $#" % val.symVal + let symbol = val.symVal + if i.scope.hasSymbol(symbol): + i.apply i.scope.getSymbol(symbol), symbol + else: + raiseUndefined("Undefined symbol '$1'" % [val.symVal]) + discard i.trace.pop + elif val.kind == mnCommand: + if DEBUG: + echo "-- push command: $#" % val.cmdVal + i.push execShellCmd(val.cmdVal).newVal + else: + if DEBUG: + echo "-- push literal: $#" % $val + i.stack.add(val) + +proc peek*(i: MnInterpreter): MnValue = + if i.stack.len > 0: + return i.stack[i.stack.len-1] + else: + raiseEmptyStack() + +template handleErrors*(i: In, body: untyped) = + try: + body + except MnRuntimeError: + let msg = getCurrentExceptionMsg() + i.stack = i.stackcopy + stderr.writeLine("$1:$2,$3 $4" % [i.currSym.filename, $i.currSym.line, $i.currSym.column, msg]) + i.stackTrace() + i.trace = @[] + raise MnTrappedException(msg: msg) + except MnTrappedException: + raise + except: + let msg = getCurrentExceptionMsg() + i.stack = i.stackcopy + #i.stackTrace() + #i.trace = @[] + raise MnTrappedException(msg: msg) + +proc interpret*(i: In, parseOnly=false): MnValue {.discardable.} = + var val: MnValue + var q: MnValue + if parseOnly: + q = newSeq[MnValue](0).newVal + while i.parser.token != tkEof: + if i.trace.len == 0: + i.stackcopy = i.stack + handleErrors(i) do: + val = i.parser.parseMinValue(i) + if parseOnly: + q.qVal.add val + else: + i.push val + if parseOnly: + return q + if i.stack.len > 0: + return i.stack[i.stack.len - 1] + +proc eval*(i: In, s: string, name="<eval>", parseOnly=false): MnValue {.discardable.}= + var i2 = i.copy(name) + i2.open(newStringStream(s), name) + discard i2.parser.getToken() + result = i2.interpret(parseOnly) + i.trace = i2.trace + i.stackcopy = i2.stackcopy + i.stack = i2.stack + i.scope = i2.scope
A
mnpkg/lang.nim
@@ -0,0 +1,622 @@
+import + critbits, + strutils, + sequtils, + algorithm, + times, + os +import + parser, + value, + interpreter, + utils, + scope + +proc lang_module*(i: In) = + let def = i.scope + + def.symbol("apply") do (i: In): + let vals = i.expect("quot") + var prog = vals[0] + i.apply prog + + def.symbol("expect") do (i: In): + var q: MnValue + i.reqQuotationOfSymbols q + i.push(i.expect(q.qVal.mapIt(it.getString())).reversed.newVal) + + def.symbol("print") do (i: In): + let a = i.peek + a.print + + def.symbol("read") do (i: In): + let vals = i.expect("str") + let file = vals[0].strVal + var contents = file.readFile + i.push newVal(contents) + + def.symbol("write") do (i: In): + let vals = i.expect("str", "str") + let a = vals[0] + let b = vals[1] + a.strVal.writeFile(b.strVal) + + def.symbol("append") do (i: In): + let vals = i.expect("str", "str") + let a = vals[0] + let b = vals[1] + var f:File + discard f.open(a.strVal, fmAppend) + f.write(b.strVal) + f.close() + + def.symbol("args") do (i: In): + var args = newSeq[MnValue](0) + for par in commandLineParams(): + args.add par.newVal + i.push args.newVal + + def.symbol("exit") do (i: In): + let vals = i.expect("int") + quit(vals[0].intVal.int) + + def.symbol("puts") do (i: In): + let a = i.peek + echo $$a + + def.symbol("gets") do (i: In) {.gcsafe.}: + i.push stdin.readLine().newVal + + def.symbol("symbols") do (i: In): + var q = newSeq[MnValue](0) + var scope = i.scope + while not scope.isNil: + for s in scope.symbols.keys: + q.add s.newVal + scope = scope.parent + i.push q.newVal + + def.symbol("defined") do (i: In): + let vals = i.expect("'sym") + i.push(i.scope.hasSymbol(vals[0].getString).newVal) + + # Language constructs + + def.symbol("let") do (i: In): + let vals = i.expect("'sym", "a") + let sym = vals[0] + var q1 = vals[1] + var symbol: string + var isQuot = q1.isQuotation + q1 = @[q1].newVal + symbol = sym.getString + if not validUserSymbol(symbol): + raiseInvalid("User symbols must start with a letter and contain only letters, numbers, and underscores (_).") + i.scope.symbols[symbol] = MnOperator(kind: mnValOp, val: q1, sealed: false, quotation: isQuot) + + def.symbol("lambda") do (i: In): + let vals = i.expect("'sym", "quot") + let sym = vals[0] + var q1 = vals[1] + var symbol: string + symbol = sym.getString + if not validUserSymbol(symbol): + raiseInvalid("User symbols must start with a letter and contain only letters, numbers, and underscores (_).") + i.scope.symbols[symbol] = MnOperator(kind: mnValOp, val: q1, sealed: false, quotation: true) + + def.symbol("bind") do (i: In): + let vals = i.expect("'sym", "a") + let sym = vals[0] + var q1 = vals[1] + var symbol: string + var isQuot = q1.isQuotation + q1 = @[q1].newVal + symbol = sym.getString + let res = i.scope.setSymbol(symbol, MnOperator(kind: mnValOp, val: q1, quotation: isQuot)) + if not res: + raiseUndefined("Attempting to bind undefined symbol: " & symbol) + + def.symbol("lambda-bind") do (i: In): + let vals = i.expect("'sym", "quot") + let sym = vals[0] + var q1 = vals[1] + var symbol: string + symbol = sym.getString + let res = i.scope.setSymbol(symbol, MnOperator(kind: mnValOp, val: q1, quotation: true)) + if not res: + raiseUndefined("Attempting to lambda-bind undefined symbol: " & symbol) + + def.symbol("delete") do (i: In): + let vals = i.expect("'sym") + let sym = vals[0] + let res = i.scope.delSymbol(sym.getString) + if not res: + raiseUndefined("Attempting to delete undefined symbol: " & sym.getString) + + def.symbol("eval") do (i: In): + let vals = i.expect("str") + let s = vals[0] + i.eval s.strVal + + def.symbol("type") do (i: In): + let vals = i.expect("a") + i.push vals[0].typeName.newVal + + def.symbol("throw") do (i: In): + let vals = i.expect("str") + let err = vals[0] + raiseRuntime(err.getString) + + def.symbol("try") do (i: In): + let vals = i.expect("quot") + let prog = vals[0] + if prog.qVal.len == 0: + raiseInvalid("Quotation must contain at least one element") + var code = prog.qVal[0] + var final, catch: MnValue + 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 (hasCatch and not catch.isQuotation) or (hasFinally and not final.isQuotation): + raiseInvalid("Quotation must contain at least one quotation") + try: + i.dequote(code) + except MnRuntimeError: + if not hasCatch: + return + let e = (MnRuntimeError)getCurrentException() + i.push e.data + i.dequote(catch) + except: + if not hasCatch: + return + let e = getCurrentException() + i.push e.msg.newVal + i.dequote(catch) + finally: + if hasFinally: + i.dequote(final) + + def.symbol("quote") do (i: In): + let vals = i.expect("a") + let a = vals[0] + i.push @[a].newVal + + def.symbol("dequote") do (i: In): + let vals = i.expect("quot") + var q = vals[0] + i.dequote(q) + + # Conditionals + + def.symbol("when") do (i: In): + let vals = i.expect("quot", "quot") + var tpath = vals[0] + var check = vals[1] + var stack = i.stack + i.dequote(check) + let res = i.pop + i.stack = stack + if not res.isBool: + raiseInvalid("Result of check is not a boolean value") + if res.boolVal == true: + i.dequote(tpath) + + def.symbol("while") do (i: In): + let vals = i.expect("quot", "quot") + var d = vals[0] + var b = vals[1] + i.dequote(b) + var check = i.pop + while check.boolVal == true: + i.dequote(d) + i.dequote(b) + check = i.pop + + def.symbol("quotesym") do (i: In): + let vals = i.expect("str") + let s = vals[0] + i.push(@[i.newSym(s.strVal)].newVal) + + def.symbol("quotecmd") do (i: In): + let vals = i.expect("str") + let s = vals[0] + i.push(@[newCmd(s.strVal)].newVal) + + def.symbol("getenv") do (i: In): + let vals = i.expect("'sym") + let a = vals[0] + i.push a.getString.getEnv.newVal + + def.symbol("putenv") do (i: In): + let vals = i.expect("'sym", "'sym") + let key = vals[0] + let value = vals[1] + key.getString.putEnv value.getString + + def.symbol("timestamp") do (i: In): + i.push getTime().toUnix().newVal + + def.symbol("getstack") do (i: In): + i.push i.stack.newVal + + def.symbol("setstack") do (i: In): + let vals = i.expect("quot") + let q = vals[0] + i.stack = q.qVal + + def.symbol("pop") do (i: In): + discard i.pop + + def.symbol("dup") do (i: In): + i.push i.peek + + def.symbol("swap") do (i: In): + let vals = i.expect("a", "a") + let a = vals[0] + let b = vals[1] + i.push a + i.push b + + def.symbol("cons") do (i: In): + let vals = i.expect("quot", "a") + let q = vals[0] + let v = vals[1] + q.qVal = @[v] & q.qVal + i.push q + + def.symbol("interpolate") do (i: In): + var vals = i.expect("quot") + var prog = vals[0] + i.apply prog + vals = i.expect("quot", "str") + var q = vals[0] + let s = vals[1] + var strings = newSeq[string](0) + for el in q.qVal: + strings.add $$el + let res = s.strVal % strings + i.push res.newVal + + def.symbol("strip") do (i: In): + let vals = i.expect("'sym") + let s = vals[0] + i.push s.getString.strip.newVal + + def.symbol("substr") do (i: In): + let vals = i.expect("int", "int", "'sym") + let length = vals[0].intVal + let start = vals[1].intVal + let s = vals[2].getString + let index = min(start+length-1, s.len-1) + i.push s[start..index].newVal + + def.symbol("split") do (i: In): + let vals = i.expect("'sym", "'sym") + let sep = vals[0].getString + let s = vals[1].getString + var q = newSeq[MnValue](0) + for e in s.split(sep): + q.add e.newVal + i.push q.newVal + + def.symbol("join") do (i: In): + let vals = i.expect("'sym", "quot") + let s = vals[0] + let q = vals[1] + i.push q.qVal.mapIt($$it).join(s.getString).newVal + + def.symbol("length") do (i: In): + let vals = i.expect("'sym") + let s = vals[0] + i.push s.getString.len.newVal + + def.symbol("indexof") do (i: In): + let vals = i.expect("str", "str") + let reg = vals[0] + let str = vals[1] + let index = str.strVal.find(reg.strVal) + i.push index.newVal + + def.symbol("replace") do (i: In): + let vals = i.expect("str", "str", "str") + let s_replace = vals[0].strVal + let src = vals[1].strVal + let s_find = vals[2].strVal + i.push s_find.replace(src, s_replace).newVal + + def.symbol("concat") do (i: In): + let vals = i.expect("quot", "quot") + let q1 = vals[0] + let q2 = vals[1] + let q = q2.qVal & q1.qVal + i.push q.newVal + + def.symbol("get") do (i: In): + let vals = i.expect("int", "quot") + let index = vals[0] + let q = vals[1] + let ix = index.intVal + if q.qVal.len < ix or ix < 0: + raiseOutOfBounds("Index out of bounds") + i.push q.qVal[ix.int] + + def.symbol("set") do (i: In): + let vals = i.expect("int", "a", "quot") + let index = vals[0] + let val = vals[1] + let q = vals[2] + let ix = index.intVal + if q.qVal.len < ix or ix < 0: + raiseOutOfBounds("Index out of bounds") + q.qVal[ix.int] = val + i.push q + + def.symbol("remove") do (i: In): + let vals = i.expect("int", "quot") + let index = vals[0] + let q = vals[1] + let ix = index.intVal + if q.qVal.len < ix or ix < 0: + raiseOutOfBounds("Index out of bounds") + var res = newSeq[MnValue](0) + for x in 0..q.qVal.len-1: + if x == ix: + continue + res.add q.qVal[x] + i.push res.newVal + + def.symbol("size") do (i: In): + let vals = i.expect("quot") + let q = vals[0] + i.push q.qVal.len.newVal + + def.symbol("included") do (i: In): + let vals = i.expect("a", "quot") + let v = vals[0] + let q = vals[1] + i.push q.qVal.contains(v).newVal + + def.symbol("map") do (i: In): + let vals = i.expect("quot", "quot") + var prog = vals[0] + let list = vals[1] + var res = newSeq[MnValue](0) + for litem in list.qVal: + i.push litem + i.dequote(prog) + res.add i.pop + i.push res.newVal + + def.symbol("filter") do (i: In): + let vals = i.expect("quot", "quot") + var filter = vals[0] + let list = vals[1] + var res = newSeq[MnValue](0) + for e in list.qVal: + i.push e + i.dequote(filter) + var check = i.pop + if check.isBool and check.boolVal == true: + res.add e + i.push res.newVal + + def.symbol("reduce") do (i: In): + let vals = i.expect("quot", "a", "quot") + var q = vals[0] + var acc = vals[1] + let s = vals[2] + for el in s.qVal: + i.push acc + i.push el + i.dequote q + acc = i.pop + i.push acc + + def.symbol("slice") do (i: In): + let vals = i.expect("int", "int", "quot") + let finish = vals[0] + let start = vals[1] + let q = vals[2] + let st = start.intVal + let fn = finish.intVal + if st < 0 or fn > q.qVal.len-1: + raiseOutOfBounds("Index out of bounds") + elif fn < st: + raiseInvalid("End index must be greater than start index") + let rng = q.qVal[st.int..fn.int] + i.push rng.newVal + + def.symbol("nan") do (i: In): + i.push newVal(NaN) + + def.symbol("+inf") do (i: In): + i.push newVal(Inf) + + def.symbol("-inf") do (i: In): + i.push newVal(NegInf) + + def.symbol("+") do (i: In): + let vals = i.expect("num", "num") + let a = vals[0] + let b = vals[1] + if a.isInt: + if b.isInt: + i.push newVal(a.intVal + b.intVal) + else: + i.push newVal(a.intVal.float + b.floatVal) + else: + if b.isFloat: + i.push newVal(a.floatVal + b.floatVal) + else: + i.push newVal(a.floatVal + b.intVal.float) + + def.symbol("-") do (i: In): + let vals = i.expect("num", "num") + let a = vals[0] + let b = vals[1] + if a.isInt: + if b.isInt: + i.push newVal(b.intVal - a.intVal) + else: + i.push newVal(b.floatVal - a.intVal.float) + else: + if b.isFloat: + i.push newVal(b.floatVal - a.floatVal) + else: + i.push newVal(b.intVal.float - a.floatVal) + + def.symbol("*") do (i: In): + let vals = i.expect("num", "num") + let a = vals[0] + let b = vals[1] + if a.isInt: + if b.isInt: + i.push newVal(a.intVal * b.intVal) + else: + i.push newVal(a.intVal.float * b.floatVal) + else: + if b.isFloat: + i.push newVal(a.floatVal * b.floatVal) + else: + i.push newVal(a.floatVal * b.intVal.float) + + def.symbol("/") do (i: In): + let vals = i.expect("num", "num") + let a = vals[0] + let b = vals[1] + if a.isInt: + if b.isInt: + i.push newVal(b.intVal.int / a.intVal.int) + else: + i.push newVal(b.floatVal / a.intVal.float) + else: + if b.isFloat: + i.push newVal(b.floatVal / a.floatVal) + else: + i.push newVal(b.intVal.float / a.floatVal) + + def.symbol(">") do (i: In): + var n1, n2: MnValue + i.reqTwoNumbersOrStrings n2, n1 + if n1.isNumber and n2.isNumber: + if n1.isInt and n2.isInt: + i.push newVal(n1.intVal > n2.intVal) + elif n1.isInt and n2.isFloat: + i.push newVal(n1.intVal.float > n2.floatVal) + elif n1.isFloat and n2.isFloat: + i.push newVal(n1.floatVal > n2.floatVal) + elif n1.isFloat and n2.isInt: + i.push newVal(n1.floatVal > n2.intVal.float) + else: + i.push newVal(n1.strVal > n2.strVal) + + def.symbol(">=") do (i: In): + var n1, n2: MnValue + i.reqTwoNumbersOrStrings n2, n1 + if n1.isNumber and n2.isNumber: + if n1.isInt and n2.isInt: + i.push newVal(n1.intVal >= n2.intVal) + elif n1.isInt and n2.isFloat: + i.push newVal(n1.intVal.float > n2.floatVal or floatCompare(n1, n2)) + elif n1.isFloat and n2.isFloat: + i.push newVal(n1.floatVal > n2.floatVal or floatCompare(n1, n2)) + elif n1.isFloat and n2.isInt: + i.push newVal(n1.floatVal > n2.intVal.float or floatCompare(n1, n2)) + else: + i.push newVal(n1.strVal >= n2.strVal) + + def.symbol("<") do (i: In): + var n1, n2: MnValue + i.reqTwoNumbersOrStrings n1, n2 + if n1.isNumber and n2.isNumber: + if n1.isInt and n2.isInt: + i.push newVal(n1.intVal > n2.intVal) + elif n1.isInt and n2.isFloat: + i.push newVal(n1.intVal.float > n2.floatVal) + elif n1.isFloat and n2.isFloat: + i.push newVal(n1.floatVal > n2.floatVal) + elif n1.isFloat and n2.isInt: + i.push newVal(n1.floatVal > n2.intVal.float) + else: + i.push newVal(n1.strVal > n2.strVal) + + def.symbol("<=") do (i: In): + var n1, n2: MnValue + i.reqTwoNumbersOrStrings n1, n2 + if n1.isNumber and n2.isNumber: + if n1.isInt and n2.isInt: + i.push newVal(n1.intVal >= n2.intVal) + elif n1.isInt and n2.isFloat: + i.push newVal(n1.intVal.float > n2.floatVal or floatCompare(n1, n2)) + elif n1.isFloat and n2.isFloat: + i.push newVal(n1.floatVal > n2.floatVal or floatCompare(n1, n2)) + elif n1.isFloat and n2.isInt: + i.push newVal(n1.floatVal > n2.intVal.float or floatCompare(n1, n2)) + else: + i.push newVal(n1.strVal >= n2.strVal) + + def.symbol("==") do (i: In): + var n1, n2: MnValue + let vals = i.expect("a", "a") + n1 = vals[0] + n2 = vals[1] + if (n1.kind == mnFloat or n2.kind == mnFloat) and n1.isNumber and n2.isNumber: + i.push newVal(floatCompare(n1, n2)) + else: + i.push newVal(n1 == n2) + + def.symbol("!=") do (i: In): + var n1, n2: MnValue + let vals = i.expect("a", "a") + n1 = vals[0] + n2 = vals[1] + if (n1.kind == mnFloat or n2.kind == mnFloat) and n1.isNumber and n2.isNumber: + i.push newVal(not floatCompare(n1, n2)) + i.push newVal(not (n1 == n2)) + + def.symbol("!") do (i: In): + let vals = i.expect("bool") + let b = vals[0] + i.push newVal(not b.boolVal) + + def.symbol("&&") do (i: In): + let vals = i.expect("quot") + let q = vals[0] + var c = 0 + for v in q.qVal: + if not v.isQuotation: + raiseInvalid("A quotation of quotations is expected") + var vv = v + i.dequote vv + let r = i.pop + c.inc() + if not r.isBool: + raiseInvalid("Quotation #$# does not evaluate to a boolean value") + if not r.boolVal: + i.push r + return + i.push true.newVal + + def.symbol("||") do (i: In): + let vals = i.expect("quot") + let q = vals[0] + var c = 0 + for v in q.qVal: + if not v.isQuotation: + raiseInvalid("A quotation of quotations is expected") + var vv = v + i.dequote vv + let r = i.pop + c.inc() + if not r.isBool: + raiseInvalid("Quotation #$# does not evaluate to a boolean value") + if r.boolVal: + i.push r + return + i.push false.newVal
A
mnpkg/meta.nim
@@ -0,0 +1,20 @@
+import + strutils + +const ymlconfig = "../mn.yml".slurp + +var pkgName* {.threadvar.}: string +var pkgVersion* {.threadvar.}: string +var pkgAuthor* {.threadvar.}: string +var pkgDescription* {.threadvar.}: string + +for line in ymlconfig.split("\n"): + let pair = line.split(":") + if pair[0].strip == "name": + pkgName = pair[1].strip + if pair[0].strip == "version": + pkgVersion = pair[1].strip + if pair[0].strip == "author": + pkgAuthor = pair[1].strip + if pair[0].strip == "description": + pkgDescription = pair[1].strip
A
mnpkg/parser.nim
@@ -0,0 +1,725 @@
+# Adapted from: https://github.com/Araq/Nimrod/blob/v0.9.6/lib/pure/json.nim +import + lexbase, + strutils, + streams, + critbits + +import unicode except strip + +type + MnTokenKind* = enum + tkError, + tkEof, + tkString, + tkCommand, + tkInt, + tkFloat, + tkBracketLe, + tkBracketRi, + tkSqBracketLe, + tkSqBracketRi, + tkSymbol, + tkNull, + tkTrue, + tkFalse + MnKind* = enum + mnInt, + mnFloat, + mnQuotation, + mnCommand, + mnString, + mnSymbol, + mnNull, + mnBool + MnEventKind* = enum ## enumeration of all events that may occur when parsing + eMinError, ## an error ocurred during parsing + eMinEof, ## end of file reached + eMinString, ## a string literal + eMinInt, ## an integer literal + eMinFloat, ## a float literal + eMinQuotationStart, ## start of an array: the ``(`` token + eMinQuotationEnd, ## start of an array: the ``)`` token + MnParserError* = enum ## enumeration that lists all errors that can occur + errNone, ## no error + errInvalidToken, ## invalid token + errStringExpected, ## string expected + errBracketRiExpected, ## ``)`` expected + errQuoteExpected, ## ``"`` expected + errSqBracketRiExpected,## ``]`` expected + errEOC_Expected, ## ``*/`` expected + errEofExpected, ## EOF expected + errExprExpected + MnParserState* = enum + stateEof, + stateStart, + stateQuotation, + stateExpectValue + MnParser* = object of BaseLexer + a*: string + doc*: bool + currSym*: MnValue + token*: MnTokenKind + state*: seq[MnParserState] + kind*: MnEventKind + err*: MnParserError + filename*: string + MnValue* = ref MnValueObject + MnValueObject* = object + line*: int + column*: int + filename*: string + outerSym*: string + docComment*: string + case kind*: MnKind + of mnNull: discard + of mnInt: intVal*: BiggestInt + of mnFloat: floatVal*: BiggestFloat + of mnCommand: cmdVal*: string + of mnQuotation: + qVal*: seq[MnValue] + of mnString: strVal*: string + of mnSymbol: symVal*: string + of mnBool: boolVal*: bool + MnScopeKind* = enum + mnNativeScope, + mnLangScope + MnScope* = object + parent*: ref MnScope + symbols*: CritBitTree[MnOperator] + sigils*: CritBitTree[MnOperator] + kind*: MnScopeKind + MnOperatorProc* = proc (i: In) {.gcsafe.} + MnOperatorKind* = enum + mnProcOp + mnValOp + MnOperator* = object + sealed*: bool + case kind*: MnOperatorKind + of mnProcOp: + prc*: MnOperatorProc + of mnValOp: + quotation*: bool + val*: MnValue + MnStack* = seq[MnValue] + In* = var MnInterpreter + MnInterpreter* = object + stack*: MnStack + trace*: MnStack + stackcopy*: MnStack + pwd*: string + scope*: ref MnScope + parser*: MnParser + currSym*: MnValue + filename*: string + evaluating*: bool + MnParsingError* = ref object of ValueError + MnUndefinedError* = ref object of ValueError + MnEmptyStackError* = ref object of ValueError + MnInvalidError* = ref object of ValueError + MnOutOfBoundsError* = ref object of ValueError + + +# Helpers + +proc raiseInvalid*(msg: string) = + raise MnInvalidError(msg: msg) + +proc raiseUndefined*(msg: string) = + raise MnUndefinedError(msg: msg) + +proc raiseOutOfBounds*(msg: string) = + raise MnOutOfBoundsError(msg: msg) + +proc raiseEmptyStack*() = + raise MnEmptyStackError(msg: "Insufficient items on the stack") + +const + errorMessages: array[MnParserError, string] = [ + "no error", + "invalid token", + "string expected", + "')' expected", + "'\"' expected", + "']' expected", + "'*/' expected", + "EOF expected", + "expression expected" + ] + tokToStr: array[MnTokenKind, string] = [ + "invalid token", + "EOF", + "string literal", + "command literal", + "int literal", + "float literal", + "(", + ")", + "{", + "}", + "symbol", + "null", + "true", + "false" + ] + +proc newScope*(parent: ref MnScope, kind = mnLangScope): MnScope = + result = MnScope(parent: parent, kind: kind) + +proc newScopeRef*(parent: ref MnScope, kind = mnLangScope): ref MnScope = + new(result) + result[] = newScope(parent, kind) + +proc open*(my: var MnParser, input: Stream, filename: string) = + lexbase.open(my, input) + my.filename = filename + my.state = @[stateStart] + my.kind = eMinError + my.a = "" + +proc close*(my: var MnParser) = + lexbase.close(my) + +proc getInt*(my: MnParser): int = + assert(my.kind == eMinInt) + return parseint(my.a) + +proc getFloat*(my: MnParser): float = + assert(my.kind == eMinFloat) + return parseFloat(my.a) + +proc kind*(my: MnParser): MnEventKind = + return my.kind + +proc getColumn*(my: MnParser): int = + result = getColNumber(my, my.bufpos) + +proc getLine*(my: MnParser): int = + result = my.lineNumber + +proc getFilename*(my: MnParser): string = + result = my.filename + +proc errorMsg*(my: MnParser, msg: string): string = + assert(my.kind == eMinError) + result = "$1 [l:$2, c:$3] ERROR - $4" % [ + my.filename, $getLine(my), $getColumn(my), msg] + +proc errorMsg*(my: MnParser): string = + assert(my.kind == eMinError) + result = errorMsg(my, errorMessages[my.err]) + +proc errorMsgExpected*(my: MnParser, e: string): string = + result = errorMsg(my, e & " expected") + +proc raiseParsing*(p: MnParser, msg: string) = + raise MnParsingError(msg: errorMsgExpected(p, msg)) + +proc raiseUndefined*(p:MnParser, msg: string) = + raise MnUndefinedError(msg: errorMsg(p, msg)) + +proc parseNumber(my: var MnParser) = + var pos = my.bufpos + var buf = my.buf + if buf[pos] == '-': + add(my.a, '-') + inc(pos) + if buf[pos] == '.': + add(my.a, "0.") + inc(pos) + else: + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] == '.': + add(my.a, '.') + inc(pos) + # digits after the dot: + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] in {'E', 'e'}: + add(my.a, buf[pos]) + inc(pos) + if buf[pos] in {'+', '-'}: + add(my.a, buf[pos]) + inc(pos) + while buf[pos] in Digits: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos + +proc handleHexChar(c: char, x: var int): bool = + result = true # Success + case c + of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) + of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10) + of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10) + else: result = false # error + +proc parseString(my: var MnParser): MnTokenKind = + result = tkString + var pos = my.bufpos + 1 + var buf = my.buf + while true: + case buf[pos] + of '\0': + my.err = errQuoteExpected + result = tkError + break + of '"': + inc(pos) + break + of '\\': + case buf[pos+1] + of '\\', '"', '\'', '/': + add(my.a, buf[pos+1]) + inc(pos, 2) + of 'b': + add(my.a, '\b') + inc(pos, 2) + of 'f': + add(my.a, '\f') + inc(pos, 2) + of 'n': + add(my.a, '\L') + inc(pos, 2) + of 'r': + add(my.a, '\C') + inc(pos, 2) + of 't': + add(my.a, '\t') + inc(pos, 2) + of 'u': + inc(pos, 2) + var r: int + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + add(my.a, toUTF8(Rune(r))) + else: + # don't bother with the error + add(my.a, buf[pos]) + inc(pos) + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + add(my.a, '\c') + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + add(my.a, '\L') + else: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos # store back + +proc parseCommand(my: var MnParser): MnTokenKind = + result = tkCommand + var pos = my.bufpos + 1 + var buf = my.buf + while true: + case buf[pos] + of '\0': + my.err = errSqBracketRiExpected + result = tkError + break + of ']': + inc(pos) + break + of '\\': + case buf[pos+1] + of '\\', '"', '\'', '/': + add(my.a, buf[pos+1]) + inc(pos, 2) + of 'b': + add(my.a, '\b') + inc(pos, 2) + of 'f': + add(my.a, '\f') + inc(pos, 2) + of 'n': + add(my.a, '\L') + inc(pos, 2) + of 'r': + add(my.a, '\C') + inc(pos, 2) + of 't': + add(my.a, '\t') + inc(pos, 2) + of 'u': + inc(pos, 2) + var r: int + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + if handleHexChar(buf[pos], r): inc(pos) + add(my.a, toUTF8(Rune(r))) + else: + # don't bother with the error + add(my.a, buf[pos]) + inc(pos) + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + add(my.a, '\c') + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + add(my.a, '\L') + else: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos # store back + +proc parseSymbol(my: var MnParser): MnTokenKind = + result = tkSymbol + var pos = my.bufpos + var buf = my.buf + if not(buf[pos] in Whitespace): + while not(buf[pos] in WhiteSpace) and not(buf[pos] in ['\0', ')', '(', ']', '[']): + if buf[pos] == '"': + add(my.a, buf[pos]) + my.bufpos = pos + let r = parseString(my) + if r == tkError: + result = tkError + return + add(my.a, buf[pos]) + return + else: + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos + +proc addDoc(my: var MnParser, docComment: string, reset = true) = + if my.doc and not my.currSym.isNil and my.currSym.kind == mnSymbol: + if reset: + my.doc = false + if my.currSym.docComment.len == 0 or my.currSym.docComment.len > 0 and my.currSym.docComment[my.currSym.docComment.len-1] == '\n': + my.currSym.docComment &= docComment.strip(true, false) + else: + my.currSym.docComment &= docComment + +proc skip(my: var MnParser) = + var pos = my.bufpos + var buf = my.buf + while true: + case buf[pos] + of ';': + # skip line comment: + if buf[pos+1] == ';': + my.doc = true + inc(pos, 2) + while true: + case buf[pos] + of '\0': + break + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + my.addDoc "\n" + break + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + my.addDoc "\n" + break + else: + my.addDoc $my.buf[pos], false + inc(pos) + of '#': + if buf[pos+1] == '|': + # skip long comment: + if buf[pos+2] == '|': + inc(pos) + my.doc = true + inc(pos, 2) + while true: + case buf[pos] + of '\0': + my.err = errEOC_Expected + break + of '\c': + pos = lexbase.handleCR(my, pos) + my.addDoc "\n", false + buf = my.buf + of '\L': + pos = lexbase.handleLF(my, pos) + my.addDoc "\n", false + buf = my.buf + of '|': + inc(pos) + if buf[pos] == '|': + inc(pos) + if buf[pos] == '#': + inc(pos) + break + my.addDoc $buf[pos], false + else: + my.addDoc $my.buf[pos], false + inc(pos) + else: + break + of ' ', '\t': + inc(pos) + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + else: + break + my.bufpos = pos + +proc getToken*(my: var MnParser): MnTokenKind = + setLen(my.a, 0) + skip(my) + case my.buf[my.bufpos] + of '-', '.': + if my.bufpos+1 <= my.buf.len and my.buf[my.bufpos+1] in '0'..'9': + parseNumber(my) + if {'.', 'e', 'E'} in my.a: + result = tkFloat + else: + result = tkInt + else: + result = parseSymbol(my) + of '0'..'9': + parseNumber(my) + if {'.', 'e', 'E'} in my.a: + result = tkFloat + else: + result = tkInt + of '"': + result = parseString(my) + of '(': + inc(my.bufpos) + result = tkBracketLe + of ')': + inc(my.bufpos) + result = tkBracketRi + of '[': + result = parseCommand(my) + of '\0': + result = tkEof + else: + result = parseSymbol(my) + case my.a + of "null": result = tkNull + of "true": result = tkTrue + of "false": result = tkFalse + else: + discard + my.token = result + + +proc next*(my: var MnParser) = + var tk = getToken(my) + var i = my.state.len-1 + case my.state[i] + of stateEof: + if tk == tkEof: + my.kind = eMinEof + else: + my.kind = eMinError + my.err = errEofExpected + of stateStart: + case tk + of tkString, tkInt, tkFloat, tkTrue, tkFalse: + my.state[i] = stateEof # expect EOF next! + my.kind = MnEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateQuotation) # we expect any + my.kind = eMinQuotationStart + of tkEof: + my.kind = eMinEof + else: + my.kind = eMinError + my.err = errEofExpected + of stateQuotation: + case tk + of tkString, tkInt, tkFloat, tkTrue, tkFalse: + my.kind = MnEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateQuotation) + my.kind = eMinQuotationStart + of tkBracketRi: + my.kind = eMinQuotationEnd + discard my.state.pop() + else: + my.kind = eMinError + my.err = errBracketRiExpected + of stateExpectValue: + case tk + of tkString, tkInt, tkFloat, tkTrue, tkFalse: + my.kind = MnEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateQuotation) + my.kind = eMinQuotationStart + else: + my.kind = eMinError + my.err = errExprExpected + +proc eat(p: var MnParser, token: MnTokenKind) = + if p.token == token: discard getToken(p) + else: raiseParsing(p, tokToStr[token]) + +proc `$`*(a: MnValue): string = + case a.kind: + of mnNull: + return "null" + of mnBool: + return $a.boolVal + of mnSymbol: + return a.symVal + of mnString: + return "\"$1\"" % a.strVal.replace("\"", "\\\"") + of mnInt: + return $a.intVal + of mnFloat: + return $a.floatVal + of mnQuotation: + var q = "(" + for i in a.qVal: + q = q & $i & " " + q = q.strip & ")" + return q + of mnCommand: + return "[" & a.cmdVal & "]" + +proc `$$`*(a: MnValue): string = + case a.kind: + of mnNull: + return "null" + of mnBool: + return $a.boolVal + of mnSymbol: + return a.symVal + of mnString: + return a.strVal + of mnInt: + return $a.intVal + of mnFloat: + return $a.floatVal + of mnQuotation: + var q = "(" + for i in a.qVal: + q = q & $i & " " + q = q.strip & ")" + return q + of mnCommand: + return "[" & a.cmdVal & "]" + +proc parseMinValue*(p: var MnParser, i: In): MnValue = + case p.token + of tkNull: + result = MnValue(kind: mnNull) + discard getToken(p) + of tkTrue: + result = MnValue(kind: mnBool, boolVal: true) + discard getToken(p) + of tkFalse: + result = MnValue(kind: mnBool, boolVal: false) + discard getToken(p) + of tkString: + result = MnValue(kind: mnString, strVal: p.a) + p.a = "" + discard getToken(p) + of tkCommand: + result = MnValue(kind: mnCommand, cmdVal: p.a) + p.a = "" + discard getToken(p) + of tkInt: + result = MnValue(kind: mnInt, intVal: parseint(p.a)) + discard getToken(p) + of tkFloat: + result = MnValue(kind: mnFloat, floatVal: parseFloat(p.a)) + discard getToken(p) + of tkBracketLe: + var q = newSeq[MnValue](0) + discard getToken(p) + while p.token != tkBracketRi: + q.add p.parseMinValue(i) + eat(p, tkBracketRi) + result = MnValue(kind: mnQuotation, qVal: q) + of tkSymbol: + result = MnValue(kind: mnSymbol, symVal: p.a, column: p.getColumn, line: p.lineNumber, filename: p.filename) + p.a = "" + p.currSym = result + discard getToken(p) + else: + let err = "Undefined or invalid value: "&p.a + raiseUndefined(p, err) + result.filename = p.filename + +proc print*(a: MnValue) = + stdout.write($$a) + +# Predicates + +proc isNull*(s: MnValue): bool = + return s.kind == mnNull + +proc isSymbol*(s: MnValue): bool = + return s.kind == mnSymbol + +proc isQuotation*(s: MnValue): bool = + return s.kind == mnQuotation + +proc isCommand*(s: MnValue): bool = + return s.kind == mnCommand + +proc isString*(s: MnValue): bool = + return s.kind == mnString + +proc isFloat*(s: MnValue): bool = + return s.kind == mnFloat + +proc isInt*(s: MnValue): bool = + return s.kind == mnInt + +proc isNumber*(s: MnValue): bool = + return s.kind == mnInt or s.kind == mnFloat + +proc isBool*(s: MnValue): bool = + return s.kind == mnBool + +proc isStringLike*(s: MnValue): bool = + return s.isSymbol or s.isString or (s.isQuotation and s.qVal.len == 1 and s.qVal[0].isSymbol) + +proc `==`*(a: MnValue, b: MnValue): bool = + if not (a.kind == b.kind or (a.isNumber and b.isNumber)): + return false + if a.kind == mnSymbol and b.kind == mnSymbol: + return a.symVal == b.symVal + elif a.kind == mnInt and b.kind == mnInt: + return a.intVal == b.intVal + elif a.kind == mnInt and b.kind == mnFloat: + return a.intVal.float == b.floatVal.float + elif a.kind == mnFloat and b.kind == mnFloat: + return a.floatVal == b.floatVal + elif a.kind == mnFloat and b.kind == mnInt: + return a.floatVal == b.intVal.float + elif a.kind == b.kind: + if a.kind == mnString: + return a.strVal == b.strVal + elif a.kind == mnBool: + return a.boolVal == b.boolVal + elif a.kind == mnNull: + return true + elif a.kind == mnQuotation: + if a.qVal.len == b.qVal.len: + var c = 0 + for item in a.qVal: + if item == b.qVal[c]: + c.inc + else: + return false + return true + else: + return false + else: + return false
A
mnpkg/scope.nim
@@ -0,0 +1,96 @@
+import + strutils, + critbits +import + parser + +proc copy*(s: ref MnScope): ref MnScope = + var scope = newScope(s.parent) + scope.symbols = s.symbols + scope.sigils = s.sigils + new(result) + result[] = scope + +proc getSymbol*(scope: ref MnScope, key: string, acc=0): MnOperator = + if scope.symbols.hasKey(key): + return scope.symbols[key] + else: + if scope.parent.isNil: + raiseUndefined("Symbol '$1' not found." % key) + return scope.parent.getSymbol(key, acc + 1) + +proc hasSymbol*(scope: ref MnScope, key: string): bool = + if scope.isNil: + return false + elif scope.symbols.hasKey(key): + return true + elif not scope.parent.isNil: + return scope.parent.hasSymbol(key) + else: + return false + +proc delSymbol*(scope: ref MnScope, key: string): bool {.discardable.}= + if scope.symbols.hasKey(key): + if scope.symbols[key].sealed: + raiseInvalid("Symbol '$1' is sealed." % key) + scope.symbols.excl(key) + return true + return false + +proc setSymbol*(scope: ref MnScope, key: string, value: MnOperator, override = false): bool {.discardable.}= + result = false + # check if a symbol already exists in current scope + if not scope.isNil and scope.symbols.hasKey(key): + if not override and scope.symbols[key].sealed: + raiseInvalid("Symbol '$1' is sealed ." % key) + scope.symbols[key] = value + result = true + else: + # Go up the scope chain and attempt to find the symbol + if not scope.parent.isNil: + result = scope.parent.setSymbol(key, value, override) + +proc getSigil*(scope: ref MnScope, key: string): MnOperator = + if scope.sigils.hasKey(key): + return scope.sigils[key] + elif not scope.parent.isNil: + return scope.parent.getSigil(key) + else: + raiseUndefined("Sigil '$1' not found." % key) + +proc hasSigil*(scope: ref MnScope, key: string): bool = + if scope.isNil: + return false + elif scope.sigils.hasKey(key): + return true + elif not scope.parent.isNil: + return scope.parent.hasSigil(key) + else: + return false + +proc delSigil*(scope: ref MnScope, key: string): bool {.discardable.}= + if scope.sigils.hasKey(key): + if scope.sigils[key].sealed: + raiseInvalid("Sigil '$1' is sealed." % key) + scope.sigils.excl(key) + return true + return false + +proc setSigil*(scope: ref MnScope, key: string, value: MnOperator, override = false): bool {.discardable.}= + result = false + # check if a sigil already exists in current scope + if not scope.isNil and scope.sigils.hasKey(key): + if not override and scope.sigils[key].sealed: + raiseInvalid("Sigil '$1' is sealed." % key) + scope.sigils[key] = value + result = true + else: + # Go up the scope chain and attempt to find the sigil + if not scope.parent.isNil: + result = scope.parent.setSymbol(key, value) + +proc previous*(scope: ref MnScope): ref MnScope = + if scope.parent.isNil: + return scope + else: + return scope.parent
A
mnpkg/utils.nim
@@ -0,0 +1,196 @@
+import + strutils, + critbits, + math +import + baseutils, + parser, + value, + interpreter + +proc floatCompare*(n1, n2: MnValue): bool = + let + a:float = if n1.kind != mnFloat: n1.intVal.float else: n1.floatVal + b:float = if n2.kind != mnFloat: n2.intVal.float else: n2.floatVal + if a.classify == fcNan and b.classify == fcNan: + return true + else: + const + FLOAT_MIN_NORMAL = 2e-1022 + FLOAT_MAX_VALUE = (2-2e-52)*2e1023 + epsilon = 0.00001 + let + absA = abs(a) + absB = abs(b) + diff = abs(a - b) + + if a == b: + return true + elif a == 0 or b == 0 or diff < FLOAT_MIN_NORMAL: + return diff < (epsilon * FLOAT_MIN_NORMAL) + else: + return diff / min((absA + absB), FLOAT_MAX_VALUE) < epsilon + +# Library methods + +proc symbol*(scope: ref MnScope, sym: string, p: MnOperatorProc) = + scope.symbols[sym] = MnOperator(prc: p, kind: mnProcOp, sealed: true) + +proc symbol*(scope: ref MnScope, sym: string, v: MnValue) = + scope.symbols[sym] = MnOperator(val: v, kind: mnValOp, sealed: true) + +# Validators + +proc validUserSymbol*(s: string): bool = + for i in 0..<s.len: + case s[i]: + of 'a'..'z': + discard + of '0'..'9', '_': + if i > 0: + discard + else: + return false + else: + return false + return true + +proc validate*(i: In, value: MnValue, t: string): bool {.gcsafe.} + +proc validateValueType*(i: var MnInterpreter, element: string, value: MnValue, vTypes: var seq[string], c: int): bool {.gcsafe.} = + vTypes.add value.typeName + let ors = element.split("|") + for to in ors: + let ands = to.split("&") + var andr = true + for ta in ands: + var t = ta + var neg = false + if t.len > 1 and t[0] == '!': + t = t[1..t.len-1] + neg = true + andr = i.validate(value, t) + if neg: + andr = not andr + if not andr: + if neg: + vTypes[c] = t + else: + vTypes[c] = value.typeName + break + if andr: + result = true + break + +proc validateValueType*(i: var MnInterpreter, element: string, value: MnValue): bool {.gcsafe.} = + var s = newSeq[string](0) + var c = 0 + return i.validateValueType(element, value, s, c) + +proc validate*(i: In, value: MnValue, t: string): bool {.gcsafe.} = + case t: + of "bool": + return value.isBool + of "null": + return value.isNull + of "int": + return value.isInt + of "num": + return value.isNumber + of "quot": + return value.isQuotation + of "cmd": + return value.isCommand + of "'sym": + return value.isStringLike + of "sym": + return value.isSymbol + of "flt": + return value.isFloat + of "str": + return value.isString + of "a": + return true + else: + raiseInvalid("Unknown type '$#'" % t) + + +# The following is used in operator signatures +proc expect*(i: var MnInterpreter, elements: varargs[string]): seq[MnValue] {.gcsafe.}= + let sym = i.currSym.getString + var valid = newSeq[string](0) + result = newSeq[MnValue](0) + let message = proc(invalid: string, elements: varargs[string]): string = + var pelements = newSeq[string](0) + for e in elements.reverse: + pelements.add e + let stack = pelements.join(" ") + result = "Incorrect values found on the stack:\n" + result &= "- expected: " & stack & " $1\n" % sym + var other = "" + if valid.len > 0: + other = valid.reverse.join(" ") & " " + result &= "- got: " & invalid & " " & other & sym + var res = false + var vTypes = newSeq[string](0) + var c = 0 + for el in elements: + let value = i.pop + result.add value + res = i.validateValueType(el, value, vTypes, c) + if res: + valid.add el + else: + raiseInvalid(message(vTypes[c], elements)) + c = c+1 + + + +proc reqQuotationOfQuotations*(i: var MnInterpreter, a: var MnValue) = + a = i.pop + if not a.isQuotation: + raiseInvalid("A quotation is required on the stack") + for s in a.qVal: + if not s.isQuotation: + raiseInvalid("A quotation of quotations is required on the stack") + +proc reqQuotationOfNumbers*(i: var MnInterpreter, a: var MnValue) = + a = i.pop + if not a.isQuotation: + raiseInvalid("A quotation is required on the stack") + for s in a.qVal: + if not s.isNumber: + raiseInvalid("A quotation of numbers is required on the stack") + +proc reqQuotationOfIntegers*(i: var MnInterpreter, a: var MnValue) = + a = i.pop + if not a.isQuotation: + raiseInvalid("A quotation is required on the stack") + for s in a.qVal: + if not s.isInt: + raiseInvalid("A quotation of integers is required on the stack") + +proc reqQuotationOfSymbols*(i: var MnInterpreter, a: var MnValue) = + a = i.pop + if not a.isQuotation: + raiseInvalid("A quotation is required on the stack") + for s in a.qVal: + if not s.isSymbol: + raiseInvalid("A quotation of symbols is required on the stack") + +proc reqTwoNumbersOrStrings*(i: var MnInterpreter, a, b: var MnValue) = + a = i.pop + b = i.pop + if not (a.isString and b.isString or a.isNumber and b.isNumber): + raiseInvalid("Two numbers or two strings are required on the stack") + +proc reqStringOrQuotation*(i: var MnInterpreter, a: var MnValue) = + a = i.pop + if not a.isQuotation and not a.isString: + raiseInvalid("A quotation or a string is required on the stack") + +proc reqTwoQuotationsOrStrings*(i: var MnInterpreter, a, b: var MnValue) = + a = i.pop + b = i.pop + if not (a.isQuotation and b.isQuotation or a.isString and b.isString): + raiseInvalid("Two quotations or two strings are required on the stack")
A
mnpkg/value.nim
@@ -0,0 +1,80 @@
+import + parser, + hashes + +proc typeName*(v: MnValue): string = + case v.kind: + of mnInt: + return "int" + of mnFloat: + return "flt" + of mnCommand: + return "cmd" + of mnQuotation: + return "quot" + of mnString: + return "str" + of mnSymbol: + return "sym" + of mnNull: + return "null" + of mnBool: + return "bool" + +# Constructors + +proc newNull*(): MnValue = + return MnValue(kind: mnNull) + +proc newVal*(s: string): MnValue = + return MnValue(kind: mnString, strVal: s) + +proc newVal*(s: cstring): MnValue = + return MnValue(kind: mnString, strVal: $s) + +proc newVal*(q: seq[MnValue]): MnValue = + return MnValue(kind: mnQuotation, qVal: q) + +proc newVal*(i: BiggestInt): MnValue = + return MnValue(kind: mnInt, intVal: i) + +proc newVal*(f: BiggestFloat): MnValue = + return MnValue(kind: mnFloat, floatVal: f) + +proc newVal*(s: bool): MnValue = + return MnValue(kind: mnBool, boolVal: s) + +proc newSym*(s: string): MnValue = + return MnValue(kind: mnSymbol, symVal: s) + +proc newCmd*(s: string): MnValue = + return MnValue(kind: mnCommand, cmdVal: s) + +proc hash*(v: MnValue): Hash = + return hash($v) + +# Get string value from string or quoted symbol + +proc getFloat*(v: MnValue): float = + if v.isInt: + return v.intVal.float + elif v.isFloat: + return v.floatVal + else: + raiseInvalid("Value is not a number") + +proc getString*(v: MnValue): string = + if v.isSymbol: + return v.symVal + elif v.isString: + return v.strVal + elif v.isCommand: + return v.cmdVal + elif v.isQuotation: + if v.qVal.len != 1: + raiseInvalid("Quotation is not a quoted symbol") + let sym = v.qVal[0] + if sym.isSymbol: + return sym.symVal + else: + raiseInvalid("Quotation is not a quoted symbol")
A
mntool.mn
@@ -0,0 +1,15 @@
+#!/usr/bin/env mn + +; Validation +(args size 2 <) ("No task specified" puts 1 exit) when + +args 1 get ":" split "taskspec" let +taskspec 0 get "task" let +"default" "subtask" let +(taskspec size 1 >) (taskspec 1 get "subtask" bind) when + +"tasks/$#.mn" (task) interpolate "taskfile" let + +taskfile read eval + +"$#__$#" (task subtask) interpolate eval