all repos — min @ f4d3e763ba1870a1ed61ae3b63f9723350dbe18d

A small but practical concatenative programming language.

Refactoring: scope now created for every quotation.
h3rald h3rald@h3rald.com
Fri, 28 Oct 2016 19:05:31 +0200
commit

f4d3e763ba1870a1ed61ae3b63f9723350dbe18d

parent

ac272839b6fed62f367d5a08c8c68cfe00b8ef61

M core/consts.nimcore/consts.nim

@@ -1,49 +1,49 @@

-import - os, - parsecfg, - streams, - strutils - -const - cfgfile = "../minim.nimble".slurp - -var - appname* = "MiNiM" - version*: string - f = newStringStream(cfgfile) - -if f != nil: - var p: CfgParser - open(p, f, "../minim.nimble") - while true: - var e = next(p) - case e.kind - of cfgEof: - break - of cfgKeyValuePair: - case e.key: - of "version": - version = e.value - else: - discard - of cfgError: - stderr.writeLine("Configuration error.") - quit(1) - else: - discard - close(p) -else: - stderr.writeLine("Cannot process configuration file.") - quit(2) - - -var HOME*: string -if defined(windows): - HOME = getenv("USERPROFILE") -if not defined(windows): - HOME = getenv("HOME") - -let MINIMRC* = HOME / ".minimrc" -let MINIMSYMBOLS* = HOME / ".minim_symbols" -let MINIMHISTORY* = HOME / ".minim_history" - +import + os, + parsecfg, + streams, + strutils + +const + cfgfile = "../minim.nimble".slurp + +var + appname* = "MiNiM" + version*: string + f = newStringStream(cfgfile) + +if f != nil: + var p: CfgParser + open(p, f, "../minim.nimble") + while true: + var e = next(p) + case e.kind + of cfgEof: + break + of cfgKeyValuePair: + case e.key: + of "version": + version = e.value + else: + discard + of cfgError: + stderr.writeLine("Configuration error.") + quit(1) + else: + discard + close(p) +else: + stderr.writeLine("Cannot process configuration file.") + quit(2) + + +var HOME*: string +if defined(windows): + HOME = getenv("USERPROFILE") +if not defined(windows): + HOME = getenv("HOME") + +let MINIMRC* = HOME / ".minimrc" +let MINIMSYMBOLS* = HOME / ".minim_symbols" +let MINIMHISTORY* = HOME / ".minim_history" +
M core/fileutils.nimcore/fileutils.nim

@@ -1,101 +1,101 @@

-import - os - -# Filetype and permissions - -proc filetype*(p: PathComponent): string = - case p - of pcFile: - return "file" - of pcLinkToFile: - return "filelink" - of pcDir: - return "dir" - of pcLinkToDir: - return "dirlink" - -proc unixPermissions*(s: set[FilePermission]): int = - result = 0 - for p in s: - case p: - of fpUserRead: - result += 400 - of fpUserWrite: - result += 200 - of fpUserExec: - result += 100 - of fpGroupRead: - result += 40 - of fpGroupWrite: - result += 20 - of fpGroupExec: - result += 10 - of fpOthersRead: - result += 4 - of fpOthersWrite: - result += 2 - of fpOthersExec: - result += 1 - -proc toFilePermissions*(p: BiggestInt): set[FilePermission] = - let user = ($p)[0].int - let group = ($p)[1].int - let others = ($p)[2].int - if user == 1: - result.incl fpUserExec - if user == 2: - result.incl fpUserWrite - if user == 3: - result.incl fpUserExec - result.incl fpUserWrite - if user == 4: - result.incl fpUserRead - if user == 5: - result.incl fpUserRead - result.incl fpUserExec - if user == 6: - result.incl fpUserRead - result.incl fpUserWrite - if user == 7: - result.incl fpUserRead - result.incl fpUserWrite - result.incl fpUserExec - if group == 1: - result.incl fpGroupExec - if group == 2: - result.incl fpGroupWrite - if group == 3: - result.incl fpGroupExec - result.incl fpGroupWrite - if group == 4: - result.incl fpGroupRead - if group == 5: - result.incl fpGroupRead - result.incl fpGroupExec - if group == 6: - result.incl fpGroupRead - result.incl fpGroupWrite - if group == 7: - result.incl fpGroupRead - result.incl fpGroupWrite - result.incl fpGroupExec - if others == 1: - result.incl fpOthersExec - if others == 2: - result.incl fpOthersWrite - if others == 3: - result.incl fpOthersExec - result.incl fpOthersWrite - if others == 4: - result.incl fpOthersRead - if others == 5: - result.incl fpOthersRead - result.incl fpOthersExec - if others == 6: - result.incl fpOthersRead - result.incl fpOthersWrite - if others == 7: - result.incl fpOthersRead - result.incl fpOthersWrite - result.incl fpOthersExec - +import + os + +# Filetype and permissions + +proc filetype*(p: PathComponent): string = + case p + of pcFile: + return "file" + of pcLinkToFile: + return "filelink" + of pcDir: + return "dir" + of pcLinkToDir: + return "dirlink" + +proc unixPermissions*(s: set[FilePermission]): int = + result = 0 + for p in s: + case p: + of fpUserRead: + result += 400 + of fpUserWrite: + result += 200 + of fpUserExec: + result += 100 + of fpGroupRead: + result += 40 + of fpGroupWrite: + result += 20 + of fpGroupExec: + result += 10 + of fpOthersRead: + result += 4 + of fpOthersWrite: + result += 2 + of fpOthersExec: + result += 1 + +proc toFilePermissions*(p: BiggestInt): set[FilePermission] = + let user = ($p)[0].int + let group = ($p)[1].int + let others = ($p)[2].int + if user == 1: + result.incl fpUserExec + if user == 2: + result.incl fpUserWrite + if user == 3: + result.incl fpUserExec + result.incl fpUserWrite + if user == 4: + result.incl fpUserRead + if user == 5: + result.incl fpUserRead + result.incl fpUserExec + if user == 6: + result.incl fpUserRead + result.incl fpUserWrite + if user == 7: + result.incl fpUserRead + result.incl fpUserWrite + result.incl fpUserExec + if group == 1: + result.incl fpGroupExec + if group == 2: + result.incl fpGroupWrite + if group == 3: + result.incl fpGroupExec + result.incl fpGroupWrite + if group == 4: + result.incl fpGroupRead + if group == 5: + result.incl fpGroupRead + result.incl fpGroupExec + if group == 6: + result.incl fpGroupRead + result.incl fpGroupWrite + if group == 7: + result.incl fpGroupRead + result.incl fpGroupWrite + result.incl fpGroupExec + if others == 1: + result.incl fpOthersExec + if others == 2: + result.incl fpOthersWrite + if others == 3: + result.incl fpOthersExec + result.incl fpOthersWrite + if others == 4: + result.incl fpOthersRead + if others == 5: + result.incl fpOthersRead + result.incl fpOthersExec + if others == 6: + result.incl fpOthersRead + result.incl fpOthersWrite + if others == 7: + result.incl fpOthersRead + result.incl fpOthersWrite + result.incl fpOthersExec +
M core/interpreter.nimcore/interpreter.nim

@@ -1,289 +1,245 @@

-import - streams, - strutils, - critbits, - os, - oids, - algorithm -import - value, - parser - -type - MinTrappedException* = ref object of SystemError - MinRuntimeError* = ref object of SystemError - qVal*: seq[MinValue] - -proc raiseRuntime*(msg: string, qVal: var seq[MinValue]) = - raise MinRuntimeError(msg: msg, qVal: qVal) - -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) - else: - raiseUndefined("Symbol '$1' not found." % key) - -proc hasSymbol*(scope: ref MinScope, key: string): bool = - if scope.symbols.hasKey(key): - return true - elif not scope.parent.isNil: - return scope.parent.hasSymbol(key) - else: - return false - -proc delSymbol*(scope: ref MinScope, 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 MinScope, key: string, value: MinOperator): bool {.discardable.}= - result = false - # check if a symbol already exists in current scope - if not scope.isNil and scope.symbols.hasKey(key): - if 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) - -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) - else: - raiseUndefined("Sigil '$1' not found." % key) - -proc hasSigil*(scope: ref MinScope, key: string): bool = - if scope.sigils.hasKey(key): - return true - elif not scope.parent.isNil: - return scope.parent.hasSigil(key) - else: - return false - -proc dump*(i: MinInterpreter): string = - var s = "" - for item in i.stack: - s = s & $item & " " - return s - -proc debug*(i: In, value: MinValue) = - if i.debugging: - echo $value - stderr.writeLine("-- " & i.dump & $value) - -proc debug*(i: In, value: string) = - if i.debugging: - stderr.writeLine("-- " & value) - -proc newScope*(i: In, id: string, q: var MinValue) = - q.scope = new MinScope - q.scope.name = id - q.scope.parent = i.scope - -template createScope*(i: In, id: string, q: MinValue, body: untyped): untyped = - q.scope = new MinScope - q.scope.name = id - q.scope.parent = i.scope - let scope = i.scope - i.scope = q.scope - body - i.scope = scope - -template withScope*(i: In, q: MinValue, body: untyped): untyped = - #i.debug "[scope] " & q.scope.fullname - let origScope = i.scope - i.scope = q.scope - body - #i.debug "[scope] " & scope.fullname - i.scope = origScope - -template addScope*(i: In, id: string, q: MinValue, body: untyped): untyped = - var added = new MinScope - added.name = id - if q.scope.isNil: - q.scope = i.scope - added.parent = q.scope - let scope = i.scope - i.scope = added - body - i.scope = scope - -proc newMinInterpreter*(debugging = false): MinInterpreter = - var stack:MinStack = newSeq[MinValue](0) - var trace:MinStack = newSeq[MinValue](0) - var stackcopy:MinStack = newSeq[MinValue](0) - var pr:MinParser - var scope = new MinScope - scope.name = "ROOT" - var i:MinInterpreter = MinInterpreter( - filename: "input", - pwd: "", - parser: pr, - stack: stack, - trace: trace, - stackcopy: stackcopy, - scope: scope, - debugging: debugging, - currSym: MinValue(column: 1, line: 1, kind: minSymbol, symVal: "") - ) - return i - -proc copy*(i: MinInterpreter, filename: string): MinInterpreter = - result = newMinInterpreter(debugging = i.debugging) - result.filename = filename - result.pwd = filename.parentDir - result.stack = i.stack - result.trace = i.trace - result.stackcopy = i.stackcopy - result.scope = i.scope - result.currSym = MinValue(column: 1, line: 1, kind: minSymbol, symVal: "") - -proc formatError(sym: MinValue, message: string): string = - if sym.filename.isNil or sym.filename == "": - return "(!) `$1`: $2" % [sym.symVal, message] - else: - return "(!) $1($2,$3) `$4`: $5" % [sym.filename, $sym.line, $sym.column, sym.symVal, message] - -proc formatTrace(sym: MinValue): string = - if sym.filename.isNil or sym.filename == "": - return " - [native] in symbol: $1" % [sym.symVal] - else: - return " - $1($2,$3) in symbol: $4" % [sym.filename, $sym.line, $sym.column, sym.symVal] - -proc stackTrace(i: In) = - var trace = i.trace - trace.reverse() - for sym in trace: - stderr.writeLine 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: MinValue) {.gcsafe.} - -proc apply*(i: In, op: MinOperator, name="apply") = - case op.kind - of minProcOp: - op.prc(i) - of minValOp: - if op.val.kind == minQuotation: - var q = op.val - i.addScope(name & "#" & $genOid(), q): - #echo "a1: ", i.scope.fullname - for e in q.qVal: - i.push e - else: - i.push(op.val) - -proc push*(i: In, val: MinValue) = - i.debug val - if val.kind == minSymbol: - i.trace.add val - if not i.evaluating: - i.currSym = val - let symbol = val.symVal - let sigil = "" & symbol[0] - let found = i.scope.hasSymbol(symbol) - if found: - let sym = i.scope.getSymbol(symbol) - i.apply(sym) - else: - let found = i.scope.hasSigil(sigil) - if symbol.len > 1 and found: - let sig = i.scope.getSigil(sigil) - let sym = symbol[1..symbol.len-1] - i.stack.add(MinValue(kind: minString, strVal: sym)) - i.apply(sig) - else: - raiseUndefined("Undefined symbol '$1' in scope '$2'" % [val.symVal, i.scope.fullname]) - discard i.trace.pop - else: - i.stack.add(val) - -proc push*(i: In, q: seq[MinValue]) = - for e in q: - i.push e - -proc pop*(i: In): MinValue = - if i.stack.len > 0: - return i.stack.pop - else: - raiseEmptyStack() - -proc peek*(i: MinInterpreter): MinValue = - if i.stack.len > 0: - return i.stack[i.stack.len-1] - else: - raiseEmptyStack() - -proc interpret*(i: In): MinValue {.gcsafe, discardable.} = - var val: MinValue - while i.parser.token != tkEof: - if i.trace.len == 0: - i.stackcopy = i.stack - try: - val = i.parser.parseMinValue - i.push val - except MinRuntimeError: - 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 - raise MinTrappedException(msg: msg) - except MinTrappedException: - raise - except: - let msg = getCurrentExceptionMsg() - i.stack = i.stackcopy - i.error(msg) - i.stackTrace - raise MinTrappedException(msg: msg) - if i.stack.len > 0: - return i.stack[i.stack.len - 1] - -proc unquote*(i: In, name: string, q: var MinValue) = - i.createScope(name, q): - for v in q.qVal: - i.push v - -proc eval*(i: In, s: string, name="<eval>") = - var i2 = i.copy(name) - i2.open(newStringStream(s), name) - discard i2.parser.getToken() - i2.interpret() - i.trace = i2.trace - i.stackcopy = i2.stackcopy - i.stack = i2.stack - i.scope = i2.scope - -proc load*(i: In, s: string) = - var i2 = i.copy(s) - i2.open(newStringStream(s.readFile), s) - discard i2.parser.getToken() - i2.interpret() - i.trace = i2.trace - i.stackcopy = i2.stackcopy - i.stack = i2.stack - i.scope = i2.scope +import + streams, + strutils, + critbits, + os, + oids, + algorithm +import + value, + scope, + parser + +type + MinTrappedException* = ref object of SystemError + MinRuntimeError* = ref object of SystemError + qVal*: seq[MinValue] + +proc raiseRuntime*(msg: string, qVal: var seq[MinValue]) = + raise MinRuntimeError(msg: msg, qVal: qVal) + +proc dump*(i: MinInterpreter): string = + var s = "" + for item in i.stack: + s = s & $item & " " + return s + +proc debug*(i: In, value: MinValue) = + if i.debugging: + echo $value + stderr.writeLine("-- " & i.dump & $value) + +proc debug*(i: In, value: string) = + if i.debugging: + stderr.writeLine("-- " & value) + +template withScope*(i: In, q: MinValue, body: untyped): untyped = + #i.debug "[scope] " & q.scope.fullname + let origScope = i.scope + # TODO verify + if q.scope.parent.isNil: + q.scope.parent = i.scope + i.scope = q.scope + body + #i.debug "[scope] " & scope.fullname + i.scope = origScope + +when false: + # TODO Remove + proc newScope*(i: In, id: string, q: var MinValue) = + q.scope = new MinScope + q.scope.name = id + q.scope.parent = i.scope + + template createScope*(i: In, id: string, q: MinValue, body: untyped): untyped = + q.scope = new MinScope + q.scope.name = id + q.scope.parent = i.scope + let scope = i.scope + i.scope = q.scope + body + i.scope = scope + + template withScope*(i: In, q: MinValue, body: untyped): untyped = + #i.debug "[scope] " & q.scope.fullname + let origScope = i.scope + i.scope = q.scope + body + #i.debug "[scope] " & scope.fullname + i.scope = origScope + + template addScope*(i: In, id: string, q: MinValue, body: untyped): untyped = + var added = new MinScope + added.name = id + if q.scope.isNil: + q.scope = i.scope + added.parent = q.scope + let scope = i.scope + i.scope = added + body + i.scope = scope + +proc newMinInterpreter*(debugging = false): MinInterpreter = + var stack:MinStack = newSeq[MinValue](0) + var trace:MinStack = newSeq[MinValue](0) + var stackcopy:MinStack = newSeq[MinValue](0) + var pr:MinParser + var scope = new MinScope + scope.name = "ROOT" + var i:MinInterpreter = MinInterpreter( + filename: "input", + pwd: "", + parser: pr, + stack: stack, + trace: trace, + stackcopy: stackcopy, + scope: scope, + debugging: debugging, + currSym: MinValue(column: 1, line: 1, kind: minSymbol, symVal: "") + ) + return i + +proc copy*(i: MinInterpreter, filename: string): MinInterpreter = + result = newMinInterpreter(debugging = i.debugging) + result.filename = filename + result.pwd = filename.parentDir + result.stack = i.stack + result.trace = i.trace + result.stackcopy = i.stackcopy + result.scope = i.scope + result.currSym = MinValue(column: 1, line: 1, kind: minSymbol, symVal: "") + +proc formatError(sym: MinValue, message: string): string = + if sym.filename.isNil or sym.filename == "": + return "(!) `$1`: $2" % [sym.symVal, message] + else: + return "(!) $1($2,$3) `$4`: $5" % [sym.filename, $sym.line, $sym.column, sym.symVal, message] + +proc formatTrace(sym: MinValue): string = + if sym.filename.isNil or sym.filename == "": + return " - [native] in symbol: $1" % [sym.symVal] + else: + return " - $1($2,$3) in symbol: $4" % [sym.filename, $sym.line, $sym.column, sym.symVal] + +proc stackTrace(i: In) = + var trace = i.trace + trace.reverse() + for sym in trace: + stderr.writeLine 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: MinValue) {.gcsafe.} + +proc apply*(i: In, op: MinOperator, name="apply") = + case op.kind + of minProcOp: + op.prc(i) + of minValOp: + if op.val.kind == minQuotation: + var q = op.val + i.withScope(q): + #echo "a1: ", i.scope.fullname + for e in q.qVal: + i.push e + else: + i.push(op.val) + +proc push*(i: In, val: MinValue) = + i.debug val + if val.kind == minSymbol: + i.trace.add val + if not i.evaluating: + i.currSym = val + let symbol = val.symVal + let sigil = "" & symbol[0] + let found = i.scope.hasSymbol(symbol) + if found: + let sym = i.scope.getSymbol(symbol) + i.apply(sym) + else: + let found = i.scope.hasSigil(sigil) + if symbol.len > 1 and found: + let sig = i.scope.getSigil(sigil) + let sym = symbol[1..symbol.len-1] + i.stack.add(MinValue(kind: minString, strVal: sym)) + i.apply(sig) + else: + raiseUndefined("Undefined symbol '$1' in scope '$2'" % [val.symVal, i.scope.fullname]) + discard i.trace.pop + else: + i.stack.add(val) + +proc push*(i: In, q: seq[MinValue]) = + for e in q: + i.push e + +proc pop*(i: In): MinValue = + if i.stack.len > 0: + return i.stack.pop + else: + raiseEmptyStack() + +proc peek*(i: MinInterpreter): MinValue = + if i.stack.len > 0: + return i.stack[i.stack.len-1] + else: + raiseEmptyStack() + +proc interpret*(i: In): MinValue {.gcsafe, discardable.} = + var val: MinValue + while i.parser.token != tkEof: + if i.trace.len == 0: + i.stackcopy = i.stack + try: + val = i.parser.parseMinValue(i.scope) + i.push val + except MinRuntimeError: + 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 + raise MinTrappedException(msg: msg) + except MinTrappedException: + raise + except: + let msg = getCurrentExceptionMsg() + i.stack = i.stackcopy + i.error(msg) + i.stackTrace + raise MinTrappedException(msg: msg) + if i.stack.len > 0: + return i.stack[i.stack.len - 1] + +proc unquote*(i: In, name: string, q: var MinValue) = + i.withScope(q): + for v in q.qVal: + i.push v + +proc eval*(i: In, s: string, name="<eval>") = + var i2 = i.copy(name) + i2.open(newStringStream(s), name) + discard i2.parser.getToken() + i2.interpret() + i.trace = i2.trace + i.stackcopy = i2.stackcopy + i.stack = i2.stack + i.scope = i2.scope + +proc load*(i: In, s: string) = + var i2 = i.copy(s) + i2.open(newStringStream(s.readFile), s) + discard i2.parser.getToken() + i2.interpret() + i.trace = i2.trace + i.stackcopy = i2.stackcopy + i.stack = i2.stack + i.scope = i2.scope
M core/linedit.nimcore/linedit.nim

@@ -1,500 +1,500 @@

-import - critbits, - terminal, - queues, - sequtils, - strutils, - os - -# getch/putch implementations -when defined(windows): - proc getchar*(): cint {.header: "<conio.h>", importc: "_getch".} - proc putchar*(c: cint): cint {.discardable, header: "<conio.h>", importc: "_putch".} - - proc termSetup*() = - discard - - proc termSave*(): string = - return "" - - proc termRestore*() = - discard -else: - import osproc - - proc termSetup*() = - discard execCmd "stty </dev/tty -icanon -echo -isig -iexten" - - proc termSave*(): string = - let res = execCmdEx "stty </dev/tty -g" - return res[0] - - let TERMSETTINGS* = termSave() - proc termRestore*() = - discard execCmd "stty </dev/tty " & TERMSETTINGS - - proc getchar*(): cint = - return stdin.readChar().ord.cint - - proc putchar*(c: cint) = - stdout.write(c.chr) - -# Types - -type - Key* = int - KeySeq* = seq[Key] - KeyCallback* = proc(ed: var LineEditor) - LineError* = ref Exception - LineEditorError* = ref Exception - LineEditorMode = enum - mdInsert - mdReplace - Line = object - text: string - position: int - LineHistory = object - file: string - tainted: bool - position: int - queue: Queue[string] - max: int - LineEditor* = object - completionCallback*: proc(ed: LineEditor): seq[string] - history: LineHistory - line: Line - mode: LineEditorMode - -# Internal Methods - -proc empty(line: Line): bool = - return line.text.len <= 0 - -proc full(line: Line): bool = - return line.position >= line.text.len - -proc first(line: Line): int = - if line.empty: - raise LineError(msg: "Line is empty!") - return 0 - -proc last(line: Line): int = - if line.empty: - raise LineError(msg: "Line is empty!") - return line.text.len-1 - -proc fromStart(line: Line): string = - if line.empty: - return "" - return line.text[line.first..line.position-1] - -proc toEnd(line: Line): string = - if line.empty: - return "" - return line.text[line.position..line.last] - -proc back*(ed: var LineEditor, n=1) = - if ed.line.position <= 0: - return - stdout.cursorBackward(n) - ed.line.position = ed.line.position - n - -proc forward*(ed: var LineEditor, n=1) = - if ed.line.full: - return - stdout.cursorForward(n) - ed.line.position += n - -proc `[]`( q: Queue[string], pos: int): string = - var c = 0 - for e in q.items: - if c == pos: - result = e - break - c.inc - -proc `[]=`( q: var Queue[string], pos: int, s: string) = - var c = 0 - for e in q.mitems: - if c == pos: - e = s - break - c.inc - -proc add(h: var LineHistory, s: string, force=false) = - if s == "" and not force: - return - if h.queue.len >= h.max: - discard h.queue.dequeue - if h.tainted: - h.queue[h.queue.len-1] = s - else: - h.queue.enqueue s - -proc previous(h: var LineHistory): string = - if h.queue.len == 0 or h.position <= 0: - return nil - h.position.dec - result = h.queue[h.position] - -proc next(h: var LineHistory): string = - if h.queue.len == 0 or h.position >= h.queue.len-1: - return nil - h.position.inc - result = h.queue[h.position] - -# Public API - -proc deletePrevious*(ed: var LineEditor) = - if ed.line.position <= 0: - return - if not ed.line.empty: - if ed.line.full: - stdout.cursorBackward - putchar(32) - stdout.cursorBackward - ed.line.position.dec - ed.line.text = ed.line.text[0..ed.line.last-1] - else: - let rest = ed.line.toEnd & " " - ed.back - for i in rest: - putchar i.ord - ed.line.text = ed.line.fromStart & ed.line.text[ed.line.position+1..ed.line.last] - stdout.cursorBackward(rest.len) - -proc deleteNext*(ed: var LineEditor) = - if not ed.line.empty: - if not ed.line.full: - let rest = ed.line.toEnd[1..^1] & " " - for c in rest: - putchar c.ord - stdout.cursorBackward(rest.len) - ed.line.text = ed.line.fromStart & ed.line.toEnd[1..^1] - -proc printChar*(ed: var LineEditor, c: int) = - if ed.line.full: - putchar(c.cint) - ed.line.text &= c.chr - ed.line.position += 1 - else: - if ed.mode == mdInsert: - putchar(c.cint) - let rest = ed.line.toEnd - ed.line.text.insert($c.chr, ed.line.position) - ed.line.position += 1 - for j in rest: - putchar(j.ord) - ed.line.position += 1 - ed.back(rest.len) - else: - putchar(c.cint) - ed.line.text[ed.line.position] = c.chr - ed.line.position += 1 - -proc changeLine*(ed: var LineEditor, s: string) = - let text = ed.line.text - let diff = text.len - s.len - let position = ed.line.position - if position > 0: - stdout.cursorBackward(position) - for c in s: - putchar(c.ord) - ed.line.position = s.len - ed.line.text = s - if diff > 0: - for i in 0.countup(diff-1): - putchar(32) - stdout.cursorBackward(diff) - -proc addToLineAtPosition(ed: var LineEditor, s: string) = - for c in s: - ed.printChar(c.ord) - -proc clearLine*(ed: var LineEditor) = - stdout.cursorBackward(ed.line.position+1) - for i in ed.line.text: - putchar(32) - putchar(32) - stdout.cursorBackward(ed.line.text.len) - ed.line.position = 0 - ed.line.text = "" - -proc goToStart*(ed: var LineEditor) = - stdout.cursorBackward(ed.line.position) - ed.line.position = 0 - -proc goToEnd*(ed: var LineEditor) = - let diff = ed.line.text.len - ed.line.position - stdout.cursorForward(diff) - ed.line.position = ed.line.text.len - -proc historyInit*(size = 256, historyFile: string = nil): LineHistory = - result.file = historyFile - result.queue = initQueue[string](size) - result.position = 0 - result.tainted = false - result.max = size - if historyFile.isNil: - return - if result.file.fileExists: - let lines = result.file.readFile.split("\n") - for line in lines: - if line != "": - result.add line - result.position = lines.len - else: - result.file.writeFile("") - -proc historyAdd*(ed: var LineEditor, force = false) = - ed.history.add ed.line.text, force - if ed.history.file.isNil: - return - ed.history.file.writeFile(toSeq(ed.history.queue.items).join("\n")) - -proc historyPrevious*(ed: var LineEditor) = - let s = ed.history.previous - if s.isNil: - return - let pos = ed.history.position - var current: int - if ed.history.tainted: - current = ed.history.queue.len-2 - else: - current = ed.history.queue.len-1 - if pos == current and ed.history.queue[current] != ed.line.text: - ed.historyAdd(force = true) - ed.history.tainted = true - if s != "": - ed.changeLine(s) - -proc historyNext*(ed: var LineEditor) = - let s = ed.history.next - if s.isNil: - return - ed.changeLine(s) - -proc historyFlush*(ed: var LineEditor) = - if ed.history.queue.len > 0: - ed.history.position = ed.history.queue.len - ed.history.tainted = false - -proc completeLine*(ed: var LineEditor): int = - if ed.completionCallback.isNil: - raise LineEditorError(msg: "Completion callback is not set") - let compl = ed.completionCallback(ed) - let position = ed.line.position - let words = ed.line.fromStart.split(" ") - var word: string - if words.len > 0: - word = words[words.len-1] - else: - word = ed.line.fromStart - var matches = compl.filterIt(it.toLowerAscii.startsWith(word.toLowerAscii)) - if ed.line.fromStart.len > 0 and matches.len > 0: - for i in 0..word.len-1: - ed.deletePrevious - var n = 0 - if matches.len > 0: - ed.addToLineAtPosition(matches[0]) - else: - return -1 - var ch = getchar() - while ch == 9: - n.inc - if n < matches.len: - let diff = ed.line.position - position - for i in 0.countup(diff-1 + word.len): - ed.deletePrevious - ed.addToLineAtPosition(matches[n]) - ch = getchar() - else: - n = -1 - return ch - -proc lineText*(ed: LineEditor): string = - return ed.line.text - -proc initEditor*(mode = mdInsert, historySize = 256, historyFile: string = nil): LineEditor = - termSetup() - result.mode = mode - result.history = historyInit(historySize, historyFile) - -# Character sets -const - CTRL* = {0 .. 31} - DIGIT* = {48 .. 57} - LETTER* = {65 .. 122} - UPPERLETTER* = {65 .. 90} - LOWERLETTER* = {97 .. 122} - PRINTABLE* = {32 .. 126} -when defined(windows): - const - ESCAPES* = {0, 22, 224} -else: - const - ESCAPES* = {27} - - -# Key Mappings -var KEYMAP*: CritBitTree[KeyCallBack] - -KEYMAP["backspace"] = proc(ed: var LineEditor) = - ed.deletePrevious() -KEYMAP["delete"] = proc(ed: var LineEditor) = - ed.deleteNext() -KEYMAP["insert"] = proc(ed: var LineEditor) = - if ed.mode == mdInsert: - ed.mode = mdReplace - else: - ed.mode = mdInsert -KEYMAP["down"] = proc(ed: var LineEditor) = - ed.historyNext() -KEYMAP["up"] = proc(ed: var LineEditor) = - ed.historyPrevious() -KEYMAP["left"] = proc(ed: var LineEditor) = - ed.back() -KEYMAP["right"] = proc(ed: var LineEditor) = - ed.forward() -KEYMAP["ctrl+c"] = proc(ed: var LineEditor) = - termRestore() - quit(0) -KEYMAP["ctrl+x"] = proc(ed: var LineEditor) = - ed.clearLine() -KEYMAP["ctrl+b"] = proc(ed: var LineEditor) = - ed.goToStart() -KEYMAP["ctrl+e"] = proc(ed: var LineEditor) = - ed.goToEnd() - -# Key Names -var KEYNAMES*: array[0..31, string] -KEYNAMES[1] = "ctrl+a" -KEYNAMES[2] = "ctrl+b" -KEYNAMES[3] = "ctrl+c" -KEYNAMES[4] = "ctrl+d" -KEYNAMES[5] = "ctrl+e" -KEYNAMES[6] = "ctrl+f" -KEYNAMES[7] = "ctrl+g" -KEYNAMES[8] = "ctrl+h" -KEYNAMES[9] = "ctrl+i" -KEYNAMES[9] = "tab" -KEYNAMES[10] = "ctrl+j" -KEYNAMES[11] = "ctrl+k" -KEYNAMES[12] = "ctrl+l" -KEYNAMES[13] = "ctrl+m" -KEYNAMES[14] = "ctrl+n" -KEYNAMES[15] = "ctrl+o" -KEYNAMES[16] = "ctrl+p" -KEYNAMES[17] = "ctrl+q" -KEYNAMES[18] = "ctrl+r" -KEYNAMES[19] = "ctrl+s" -KEYNAMES[20] = "ctrl+t" -KEYNAMES[21] = "ctrl+u" -KEYNAMES[22] = "ctrl+v" -KEYNAMES[23] = "ctrl+w" -KEYNAMES[24] = "ctrl+x" -KEYNAMES[25] = "ctrl+y" -KEYNAMES[26] = "ctrl+z" - -# Key Sequences -var KEYSEQS*: CritBitTree[KeySeq] - -when defined(windows): - KEYSEQS["up"] = @[224, 72] - KEYSEQS["down"] = @[224, 80] - KEYSEQS["right"] = @[224, 77] - KEYSEQS["left"] = @[224, 75] - KEYSEQS["insert"] = @[224, 82] - KEYSEQS["delete"] = @[224, 83] -else: - KEYSEQS["up"] = @[27, 91, 65] - KEYSEQS["down"] = @[27, 91, 66] - KEYSEQS["right"] = @[27, 91, 67] - KEYSEQS["left"] = @[27, 91, 68] - KEYSEQS["insert"] = @[27, 91, 50, 126] - KEYSEQS["delete"] = @[27, 91, 51, 126] - - -proc readLine*(ed: var LineEditor, prompt="", hidechars = false): string = - stdout.write(prompt) - ed.line = Line(text: "", position: 0) - var c = -1 # Used to manage completions - while true: - var c1: int - if c > 0: - c1 = c - c = -1 - else: - c1 = getchar() - if c1 in {10, 13}: - stdout.write("\n") - ed.historyAdd() - ed.historyFlush() - return ed.line.text - elif c1 in {8, 127}: - KEYMAP["backspace"](ed) - elif c1 in PRINTABLE: - if hidechars: - putchar('*'.ord) - ed.line.text &= c1.chr - ed.line.position.inc - else: - ed.printChar(c1) - elif c1 == 9: # TAB - c = ed.completeLine() - elif c1 in ESCAPES: - var s = newSeq[Key](0) - s.add(c1) - let c2 = getchar() - s.add(c2) - if s == KEYSEQS["left"]: - KEYMAP["left"](ed) - elif s == KEYSEQS["right"]: - KEYMAP["right"](ed) - elif s == KEYSEQS["up"]: - KEYMAP["up"](ed) - elif s == KEYSEQS["down"]: - KEYMAP["down"](ed) - elif s == KEYSEQS["delete"]: - KEYMAP["delete"](ed) - elif s == KEYSEQS["insert"]: - KEYMAP["insert"](ed) - elif c2 == 91: - let c3 = getchar() - s.add(c3) - if s == KEYSEQS["right"]: - KEYMAP["right"](ed) - elif s == KEYSEQS["left"]: - KEYMAP["left"](ed) - elif s == KEYSEQS["up"]: - KEYMAP["up"](ed) - elif s == KEYSEQS["down"]: - KEYMAP["down"](ed) - elif c3 in {50, 51}: - let c4 = getchar() - s.add(c4) - if c4 == 126 and c3 == 50: - KEYMAP["insert"](ed) - elif c4 == 126 and c3 == 51: - KEYMAP["delete"](ed) - elif KEYMAP.hasKey(KEYNAMES[c1]): - KEYMAP[KEYNAMES[c1]](ed) - -proc password*(ed: var LineEditor, prompt=""): string = - return ed.readLine(prompt, true) - -when isMainModule: - proc testChar() = - while true: - let a = getch().ord - echo "\n->", a - if a == 3: - termRestore() - quit(0) - proc testLineEditor() = - while true: - var ed = initEditor(historyFile = nil) - echo "---", ed.readLine("-> "), "---" - - testChar() - +import + critbits, + terminal, + queues, + sequtils, + strutils, + os + +# getch/putch implementations +when defined(windows): + proc getchar*(): cint {.header: "<conio.h>", importc: "_getch".} + proc putchar*(c: cint): cint {.discardable, header: "<conio.h>", importc: "_putch".} + + proc termSetup*() = + discard + + proc termSave*(): string = + return "" + + proc termRestore*() = + discard +else: + import osproc + + proc termSetup*() = + discard execCmd "stty </dev/tty -icanon -echo -isig -iexten" + + proc termSave*(): string = + let res = execCmdEx "stty </dev/tty -g" + return res[0] + + let TERMSETTINGS* = termSave() + proc termRestore*() = + discard execCmd "stty </dev/tty " & TERMSETTINGS + + proc getchar*(): cint = + return stdin.readChar().ord.cint + + proc putchar*(c: cint) = + stdout.write(c.chr) + +# Types + +type + Key* = int + KeySeq* = seq[Key] + KeyCallback* = proc(ed: var LineEditor) + LineError* = ref Exception + LineEditorError* = ref Exception + LineEditorMode = enum + mdInsert + mdReplace + Line = object + text: string + position: int + LineHistory = object + file: string + tainted: bool + position: int + queue: Queue[string] + max: int + LineEditor* = object + completionCallback*: proc(ed: LineEditor): seq[string] + history: LineHistory + line: Line + mode: LineEditorMode + +# Internal Methods + +proc empty(line: Line): bool = + return line.text.len <= 0 + +proc full(line: Line): bool = + return line.position >= line.text.len + +proc first(line: Line): int = + if line.empty: + raise LineError(msg: "Line is empty!") + return 0 + +proc last(line: Line): int = + if line.empty: + raise LineError(msg: "Line is empty!") + return line.text.len-1 + +proc fromStart(line: Line): string = + if line.empty: + return "" + return line.text[line.first..line.position-1] + +proc toEnd(line: Line): string = + if line.empty: + return "" + return line.text[line.position..line.last] + +proc back*(ed: var LineEditor, n=1) = + if ed.line.position <= 0: + return + stdout.cursorBackward(n) + ed.line.position = ed.line.position - n + +proc forward*(ed: var LineEditor, n=1) = + if ed.line.full: + return + stdout.cursorForward(n) + ed.line.position += n + +proc `[]`( q: Queue[string], pos: int): string = + var c = 0 + for e in q.items: + if c == pos: + result = e + break + c.inc + +proc `[]=`( q: var Queue[string], pos: int, s: string) = + var c = 0 + for e in q.mitems: + if c == pos: + e = s + break + c.inc + +proc add(h: var LineHistory, s: string, force=false) = + if s == "" and not force: + return + if h.queue.len >= h.max: + discard h.queue.dequeue + if h.tainted: + h.queue[h.queue.len-1] = s + else: + h.queue.enqueue s + +proc previous(h: var LineHistory): string = + if h.queue.len == 0 or h.position <= 0: + return nil + h.position.dec + result = h.queue[h.position] + +proc next(h: var LineHistory): string = + if h.queue.len == 0 or h.position >= h.queue.len-1: + return nil + h.position.inc + result = h.queue[h.position] + +# Public API + +proc deletePrevious*(ed: var LineEditor) = + if ed.line.position <= 0: + return + if not ed.line.empty: + if ed.line.full: + stdout.cursorBackward + putchar(32) + stdout.cursorBackward + ed.line.position.dec + ed.line.text = ed.line.text[0..ed.line.last-1] + else: + let rest = ed.line.toEnd & " " + ed.back + for i in rest: + putchar i.ord + ed.line.text = ed.line.fromStart & ed.line.text[ed.line.position+1..ed.line.last] + stdout.cursorBackward(rest.len) + +proc deleteNext*(ed: var LineEditor) = + if not ed.line.empty: + if not ed.line.full: + let rest = ed.line.toEnd[1..^1] & " " + for c in rest: + putchar c.ord + stdout.cursorBackward(rest.len) + ed.line.text = ed.line.fromStart & ed.line.toEnd[1..^1] + +proc printChar*(ed: var LineEditor, c: int) = + if ed.line.full: + putchar(c.cint) + ed.line.text &= c.chr + ed.line.position += 1 + else: + if ed.mode == mdInsert: + putchar(c.cint) + let rest = ed.line.toEnd + ed.line.text.insert($c.chr, ed.line.position) + ed.line.position += 1 + for j in rest: + putchar(j.ord) + ed.line.position += 1 + ed.back(rest.len) + else: + putchar(c.cint) + ed.line.text[ed.line.position] = c.chr + ed.line.position += 1 + +proc changeLine*(ed: var LineEditor, s: string) = + let text = ed.line.text + let diff = text.len - s.len + let position = ed.line.position + if position > 0: + stdout.cursorBackward(position) + for c in s: + putchar(c.ord) + ed.line.position = s.len + ed.line.text = s + if diff > 0: + for i in 0.countup(diff-1): + putchar(32) + stdout.cursorBackward(diff) + +proc addToLineAtPosition(ed: var LineEditor, s: string) = + for c in s: + ed.printChar(c.ord) + +proc clearLine*(ed: var LineEditor) = + stdout.cursorBackward(ed.line.position+1) + for i in ed.line.text: + putchar(32) + putchar(32) + stdout.cursorBackward(ed.line.text.len) + ed.line.position = 0 + ed.line.text = "" + +proc goToStart*(ed: var LineEditor) = + stdout.cursorBackward(ed.line.position) + ed.line.position = 0 + +proc goToEnd*(ed: var LineEditor) = + let diff = ed.line.text.len - ed.line.position + stdout.cursorForward(diff) + ed.line.position = ed.line.text.len + +proc historyInit*(size = 256, historyFile: string = nil): LineHistory = + result.file = historyFile + result.queue = initQueue[string](size) + result.position = 0 + result.tainted = false + result.max = size + if historyFile.isNil: + return + if result.file.fileExists: + let lines = result.file.readFile.split("\n") + for line in lines: + if line != "": + result.add line + result.position = lines.len + else: + result.file.writeFile("") + +proc historyAdd*(ed: var LineEditor, force = false) = + ed.history.add ed.line.text, force + if ed.history.file.isNil: + return + ed.history.file.writeFile(toSeq(ed.history.queue.items).join("\n")) + +proc historyPrevious*(ed: var LineEditor) = + let s = ed.history.previous + if s.isNil: + return + let pos = ed.history.position + var current: int + if ed.history.tainted: + current = ed.history.queue.len-2 + else: + current = ed.history.queue.len-1 + if pos == current and ed.history.queue[current] != ed.line.text: + ed.historyAdd(force = true) + ed.history.tainted = true + if s != "": + ed.changeLine(s) + +proc historyNext*(ed: var LineEditor) = + let s = ed.history.next + if s.isNil: + return + ed.changeLine(s) + +proc historyFlush*(ed: var LineEditor) = + if ed.history.queue.len > 0: + ed.history.position = ed.history.queue.len + ed.history.tainted = false + +proc completeLine*(ed: var LineEditor): int = + if ed.completionCallback.isNil: + raise LineEditorError(msg: "Completion callback is not set") + let compl = ed.completionCallback(ed) + let position = ed.line.position + let words = ed.line.fromStart.split(" ") + var word: string + if words.len > 0: + word = words[words.len-1] + else: + word = ed.line.fromStart + var matches = compl.filterIt(it.toLowerAscii.startsWith(word.toLowerAscii)) + if ed.line.fromStart.len > 0 and matches.len > 0: + for i in 0..word.len-1: + ed.deletePrevious + var n = 0 + if matches.len > 0: + ed.addToLineAtPosition(matches[0]) + else: + return -1 + var ch = getchar() + while ch == 9: + n.inc + if n < matches.len: + let diff = ed.line.position - position + for i in 0.countup(diff-1 + word.len): + ed.deletePrevious + ed.addToLineAtPosition(matches[n]) + ch = getchar() + else: + n = -1 + return ch + +proc lineText*(ed: LineEditor): string = + return ed.line.text + +proc initEditor*(mode = mdInsert, historySize = 256, historyFile: string = nil): LineEditor = + termSetup() + result.mode = mode + result.history = historyInit(historySize, historyFile) + +# Character sets +const + CTRL* = {0 .. 31} + DIGIT* = {48 .. 57} + LETTER* = {65 .. 122} + UPPERLETTER* = {65 .. 90} + LOWERLETTER* = {97 .. 122} + PRINTABLE* = {32 .. 126} +when defined(windows): + const + ESCAPES* = {0, 22, 224} +else: + const + ESCAPES* = {27} + + +# Key Mappings +var KEYMAP*: CritBitTree[KeyCallBack] + +KEYMAP["backspace"] = proc(ed: var LineEditor) = + ed.deletePrevious() +KEYMAP["delete"] = proc(ed: var LineEditor) = + ed.deleteNext() +KEYMAP["insert"] = proc(ed: var LineEditor) = + if ed.mode == mdInsert: + ed.mode = mdReplace + else: + ed.mode = mdInsert +KEYMAP["down"] = proc(ed: var LineEditor) = + ed.historyNext() +KEYMAP["up"] = proc(ed: var LineEditor) = + ed.historyPrevious() +KEYMAP["left"] = proc(ed: var LineEditor) = + ed.back() +KEYMAP["right"] = proc(ed: var LineEditor) = + ed.forward() +KEYMAP["ctrl+c"] = proc(ed: var LineEditor) = + termRestore() + quit(0) +KEYMAP["ctrl+x"] = proc(ed: var LineEditor) = + ed.clearLine() +KEYMAP["ctrl+b"] = proc(ed: var LineEditor) = + ed.goToStart() +KEYMAP["ctrl+e"] = proc(ed: var LineEditor) = + ed.goToEnd() + +# Key Names +var KEYNAMES*: array[0..31, string] +KEYNAMES[1] = "ctrl+a" +KEYNAMES[2] = "ctrl+b" +KEYNAMES[3] = "ctrl+c" +KEYNAMES[4] = "ctrl+d" +KEYNAMES[5] = "ctrl+e" +KEYNAMES[6] = "ctrl+f" +KEYNAMES[7] = "ctrl+g" +KEYNAMES[8] = "ctrl+h" +KEYNAMES[9] = "ctrl+i" +KEYNAMES[9] = "tab" +KEYNAMES[10] = "ctrl+j" +KEYNAMES[11] = "ctrl+k" +KEYNAMES[12] = "ctrl+l" +KEYNAMES[13] = "ctrl+m" +KEYNAMES[14] = "ctrl+n" +KEYNAMES[15] = "ctrl+o" +KEYNAMES[16] = "ctrl+p" +KEYNAMES[17] = "ctrl+q" +KEYNAMES[18] = "ctrl+r" +KEYNAMES[19] = "ctrl+s" +KEYNAMES[20] = "ctrl+t" +KEYNAMES[21] = "ctrl+u" +KEYNAMES[22] = "ctrl+v" +KEYNAMES[23] = "ctrl+w" +KEYNAMES[24] = "ctrl+x" +KEYNAMES[25] = "ctrl+y" +KEYNAMES[26] = "ctrl+z" + +# Key Sequences +var KEYSEQS*: CritBitTree[KeySeq] + +when defined(windows): + KEYSEQS["up"] = @[224, 72] + KEYSEQS["down"] = @[224, 80] + KEYSEQS["right"] = @[224, 77] + KEYSEQS["left"] = @[224, 75] + KEYSEQS["insert"] = @[224, 82] + KEYSEQS["delete"] = @[224, 83] +else: + KEYSEQS["up"] = @[27, 91, 65] + KEYSEQS["down"] = @[27, 91, 66] + KEYSEQS["right"] = @[27, 91, 67] + KEYSEQS["left"] = @[27, 91, 68] + KEYSEQS["insert"] = @[27, 91, 50, 126] + KEYSEQS["delete"] = @[27, 91, 51, 126] + + +proc readLine*(ed: var LineEditor, prompt="", hidechars = false): string = + stdout.write(prompt) + ed.line = Line(text: "", position: 0) + var c = -1 # Used to manage completions + while true: + var c1: int + if c > 0: + c1 = c + c = -1 + else: + c1 = getchar() + if c1 in {10, 13}: + stdout.write("\n") + ed.historyAdd() + ed.historyFlush() + return ed.line.text + elif c1 in {8, 127}: + KEYMAP["backspace"](ed) + elif c1 in PRINTABLE: + if hidechars: + putchar('*'.ord) + ed.line.text &= c1.chr + ed.line.position.inc + else: + ed.printChar(c1) + elif c1 == 9: # TAB + c = ed.completeLine() + elif c1 in ESCAPES: + var s = newSeq[Key](0) + s.add(c1) + let c2 = getchar() + s.add(c2) + if s == KEYSEQS["left"]: + KEYMAP["left"](ed) + elif s == KEYSEQS["right"]: + KEYMAP["right"](ed) + elif s == KEYSEQS["up"]: + KEYMAP["up"](ed) + elif s == KEYSEQS["down"]: + KEYMAP["down"](ed) + elif s == KEYSEQS["delete"]: + KEYMAP["delete"](ed) + elif s == KEYSEQS["insert"]: + KEYMAP["insert"](ed) + elif c2 == 91: + let c3 = getchar() + s.add(c3) + if s == KEYSEQS["right"]: + KEYMAP["right"](ed) + elif s == KEYSEQS["left"]: + KEYMAP["left"](ed) + elif s == KEYSEQS["up"]: + KEYMAP["up"](ed) + elif s == KEYSEQS["down"]: + KEYMAP["down"](ed) + elif c3 in {50, 51}: + let c4 = getchar() + s.add(c4) + if c4 == 126 and c3 == 50: + KEYMAP["insert"](ed) + elif c4 == 126 and c3 == 51: + KEYMAP["delete"](ed) + elif KEYMAP.hasKey(KEYNAMES[c1]): + KEYMAP[KEYNAMES[c1]](ed) + +proc password*(ed: var LineEditor, prompt=""): string = + return ed.readLine(prompt, true) + +when isMainModule: + proc testChar() = + while true: + let a = getch().ord + echo "\n->", a + if a == 3: + termRestore() + quit(0) + proc testLineEditor() = + while true: + var ed = initEditor(historyFile = nil) + echo "---", ed.readLine("-> "), "---" + + testChar() +
M core/parser.nimcore/parser.nim

@@ -1,570 +1,579 @@

-# Adapted from: https://github.com/Araq/Nimrod/blob/v0.9.6/lib/pure/json.nim -import - lexbase, - strutils, - streams, - unicode, - tables, - critbits - -type - MinTokenKind* = enum - tkError, - tkEof, - tkString, - tkInt, - tkFloat, - tkBracketLe, - tkBracketRi, - tkSymbol, - tkTrue, - tkFalse - MinKind* = enum - minInt, - minFloat, - minQuotation, - minString, - minSymbol, - minBool - MinEventKind* = 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 - MinParserError* = enum ## enumeration that lists all errors that can occur - errNone, ## no error - errInvalidToken, ## invalid token - errStringExpected, ## string expected - errBracketRiExpected, ## ``)`` expected - errQuoteExpected, ## ``"`` or ``'`` expected - errEOC_Expected, ## ``*/`` expected - errEofExpected, ## EOF expected - errExprExpected - MinParserState* = enum - stateEof, - stateStart, - stateQuotation, - stateExpectValue - MinParser* = object of BaseLexer - a*: string - token*: MinTokenKind - state*: seq[MinParserState] - kind*: MinEventKind - err*: MinParserError - filename*: string - MinValue* = ref MinValueObject - MinValueObject* = object - line*: int - column*: int - filename*: string - case kind*: MinKind - of minInt: intVal*: BiggestInt - of minFloat: floatVal*: BiggestFloat - of minQuotation: - qVal*: seq[MinValue] - scope*: ref MinScope - of minString: strVal*: string - of minSymbol: symVal*: string - of minBool: boolVal*: bool - MinScope* = object - symbols*: CritBitTree[MinOperator] - sigils*: CritBitTree[MinOperator] - parent*: ref MinScope - name*: string - stack*: MinStack - MinOperatorProc* = proc (i: In) {.closure.} - MinOperatorKind* = enum - minProcOp - minValOp - MinOperator* = object - sealed*: bool - case kind*: MinOperatorKind - of minProcOp: - prc*: MinOperatorProc - of minValOp: - val*: MinValue - MinStack* = seq[MinValue] - In* = var MinInterpreter - MinInterpreter* = object - stack*: MinStack - trace*: MinStack - stackcopy*: MinStack - pwd*: string - scope*: ref MinScope - parser*: MinParser - currSym*: MinValue - filename*: string - debugging*: bool - evaluating*: bool - MinParsingError* = ref object of ValueError - MinUndefinedError* = ref object of ValueError - MinEmptyStackError* = ref object of ValueError - MinInvalidError* = ref object of ValueError - MinOutOfBoundsError* = ref object of ValueError - -# Error Helpers - -proc raiseInvalid*(msg: string) = - raise MinInvalidError(msg: msg) - -proc raiseUndefined*(msg: string) = - raise MinUndefinedError(msg: msg) - -proc raiseOutOfBounds*(msg: string) = - raise MinOutOfBoundsError(msg: msg) - -proc raiseEmptyStack*() = - raise MinEmptyStackError(msg: "Insufficient items on the stack") - - -const - errorMessages: array[MinParserError, string] = [ - "no error", - "invalid token", - "string expected", - "')' expected", - "'\"' or \"'\" expected", - "'*/' expected", - "EOF expected", - "expression expected" - ] - tokToStr: array[MinTokenKind, string] = [ - "invalid token", - "EOF", - "string literal", - "int literal", - "float literal", - "(", - ")", - "symbol", - "true", - "false" - ] - -proc open*(my: var MinParser, input: Stream, filename: string) = - lexbase.open(my, input) - my.filename = filename - my.state = @[stateStart] - my.kind = eMinError - my.a = "" - -proc close*(my: var MinParser) {.inline.} = - lexbase.close(my) - -proc getInt*(my: MinParser): int {.inline.} = - assert(my.kind == eMinInt) - return parseint(my.a) - -proc getFloat*(my: MinParser): float {.inline.} = - assert(my.kind == eMinFloat) - return parseFloat(my.a) - -proc kind*(my: MinParser): MinEventKind {.inline.} = - return my.kind - -proc getColumn*(my: MinParser): int {.inline.} = - result = getColNumber(my, my.bufpos) - -proc getLine*(my: MinParser): int {.inline.} = - result = my.lineNumber - -proc getFilename*(my: MinParser): string {.inline.} = - result = my.filename - -proc errorMsg*(my: MinParser, 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: MinParser): string = - assert(my.kind == eMinError) - result = errorMsg(my, errorMessages[my.err]) - -proc errorMsgExpected*(my: MinParser, e: string): string = - result = errorMsg(my, e & " expected") - -proc raiseParsing*(p: MinParser, msg: string) {.noinline, noreturn.} = - raise MinParsingError(msg: errorMsgExpected(p, msg)) - -proc raiseUndefined*(p:MinParser, msg: string) {.noinline, noreturn.} = - raise MinUndefinedError(msg: errorMsg(p, msg)) - -proc parseNumber(my: var MinParser) = - 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 MinParser): MinTokenKind = - 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 parseSymbol(my: var MinParser): MinTokenKind = - 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', ')', '(']): - add(my.a, buf[pos]) - inc(pos) - my.bufpos = pos - -proc skip(my: var MinParser) = - var pos = my.bufpos - var buf = my.buf - while true: - case buf[pos] - of ';': - # skip line comment: - inc(pos, 2) - while true: - case buf[pos] - of '\0': - break - of '\c': - pos = lexbase.handleCR(my, pos) - buf = my.buf - break - of '\L': - pos = lexbase.handleLF(my, pos) - buf = my.buf - break - else: - inc(pos) - of '/': - if buf[pos+1] == '/': - # skip line comment: - inc(pos, 2) - while true: - case buf[pos] - of '\0': - break - of '\c': - pos = lexbase.handleCR(my, pos) - buf = my.buf - break - of '\L': - pos = lexbase.handleLF(my, pos) - buf = my.buf - break - else: - inc(pos) - elif buf[pos+1] == '*': - # skip long comment: - inc(pos, 2) - while true: - case buf[pos] - of '\0': - my.err = errEOC_Expected - break - of '\c': - pos = lexbase.handleCR(my, pos) - buf = my.buf - of '\L': - pos = lexbase.handleLF(my, pos) - buf = my.buf - of '*': - inc(pos) - if buf[pos] == '/': - inc(pos) - break - else: - 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 MinParser): MinTokenKind = - 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 '\0': - result = tkEof - else: - result = parseSymbol(my) - case my.a - of "true": result = tkTrue - of "false": result = tkFalse - else: - discard - my.token = result - - -proc next*(my: var MinParser) = - 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 = MinEventKind(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 = MinEventKind(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 = MinEventKind(ord(tk)) - of tkBracketLe: - my.state.add(stateQuotation) - my.kind = eMinQuotationStart - else: - my.kind = eMinError - my.err = errExprExpected - -proc eat(p: var MinParser, token: MinTokenKind) = - if p.token == token: discard getToken(p) - else: raiseParsing(p, tokToStr[token]) - -proc parseMinValue*(p: var MinParser): MinValue = - #echo p.a, " (", p.token, ")" - case p.token - of tkTrue: - result = MinValue(kind: minBool, boolVal: true, column: p.getColumn, line: p.lineNumber) - discard getToken(p) - of tkFalse: - result = MinValue(kind: minBool, boolVal: false, column: p.getColumn, line: p.lineNumber) - discard getToken(p) - of tkString: - result = MinValue(kind: minString, strVal: p.a, column: p.getColumn, line: p.lineNumber) - p.a = "" - discard getToken(p) - of tkInt: - result = MinValue(kind: minInt, intVal: parseint(p.a), column: p.getColumn, line: p.lineNumber) - discard getToken(p) - of tkFloat: - result = MinValue(kind: minFloat, floatVal: parseFloat(p.a), column: p.getColumn, line: p.lineNumber) - discard getToken(p) - of tkBracketLe: - var q = newSeq[MinValue](0) - discard getToken(p) - while p.token != tkBracketRi: - q.add parseMinValue(p) - eat(p, tkBracketRi) - result = MinValue(kind: minQuotation, qVal: q, column: p.getColumn, line: p.lineNumber) - of tkSymbol: - result = MinValue(kind: minSymbol, symVal: p.a, column: p.getColumn, line: p.lineNumber) - p.a = "" - discard getToken(p) - else: - raiseUndefined(p, "Undefined value: '"&p.a&"'") - result.filename = p.filename - -proc `$`*(a: MinValue): string = - case a.kind: - of minBool: - return $a.boolVal - of minSymbol: - return a.symVal - of minString: - return "\"$1\"" % a.strVal.replace("\"", "\\\"") - of minInt: - return $a.intVal - of minFloat: - return $a.floatVal - of minQuotation: - 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.strip & ")" - return q - -proc print*(a: MinValue) = - stdout.write($$a) - -proc `==`*(a: MinValue, b: MinValue): bool = - if a.kind == minSymbol and b.kind == minSymbol: - return a.symVal == b.symVal - elif a.kind == minInt and b.kind == minInt: - return a.intVal == b.intVal - elif a.kind == minInt and b.kind == minFloat: - return a.intVal.float == b.floatVal.float - elif a.kind == minFloat and b.kind == minFloat: - return a.floatVal == b.floatVal - elif a.kind == minFloat and b.kind == minInt: - return a.floatVal == b.intVal.float - elif a.kind == b.kind: - if a.kind == minString: - return a.strVal == b.strVal - elif a.kind == minBool: - return a.boolVal == b.boolVal - elif a.kind == minQuotation: - 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 +# Adapted from: https://github.com/Araq/Nimrod/blob/v0.9.6/lib/pure/json.nim +import + lexbase, + strutils, + streams, + unicode, + tables, + critbits, + oids + +type + MinTokenKind* = enum + tkError, + tkEof, + tkString, + tkInt, + tkFloat, + tkBracketLe, + tkBracketRi, + tkSymbol, + tkTrue, + tkFalse + MinKind* = enum + minInt, + minFloat, + minQuotation, + minString, + minSymbol, + minBool + MinEventKind* = 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 + MinParserError* = enum ## enumeration that lists all errors that can occur + errNone, ## no error + errInvalidToken, ## invalid token + errStringExpected, ## string expected + errBracketRiExpected, ## ``)`` expected + errQuoteExpected, ## ``"`` or ``'`` expected + errEOC_Expected, ## ``*/`` expected + errEofExpected, ## EOF expected + errExprExpected + MinParserState* = enum + stateEof, + stateStart, + stateQuotation, + stateExpectValue + MinParser* = object of BaseLexer + a*: string + token*: MinTokenKind + state*: seq[MinParserState] + kind*: MinEventKind + err*: MinParserError + filename*: string + MinValue* = ref MinValueObject + MinValueObject* = object + line*: int + column*: int + filename*: string + case kind*: MinKind + of minInt: intVal*: BiggestInt + of minFloat: floatVal*: BiggestFloat + of minQuotation: + qVal*: seq[MinValue] + scope*: ref MinScope + of minString: strVal*: string + of minSymbol: symVal*: string + of minBool: boolVal*: bool + MinScope* = object + symbols*: CritBitTree[MinOperator] + sigils*: CritBitTree[MinOperator] + parent*: ref MinScope + name*: string + stack*: MinStack + MinOperatorProc* = proc (i: In) {.closure.} + MinOperatorKind* = enum + minProcOp + minValOp + MinOperator* = object + sealed*: bool + case kind*: MinOperatorKind + of minProcOp: + prc*: MinOperatorProc + of minValOp: + val*: MinValue + MinStack* = seq[MinValue] + In* = var MinInterpreter + MinInterpreter* = object + stack*: MinStack + trace*: MinStack + stackcopy*: MinStack + pwd*: string + scope*: ref MinScope + parser*: MinParser + currSym*: MinValue + filename*: string + debugging*: bool + evaluating*: bool + MinParsingError* = ref object of ValueError + MinUndefinedError* = ref object of ValueError + MinEmptyStackError* = ref object of ValueError + MinInvalidError* = ref object of ValueError + MinOutOfBoundsError* = ref object of ValueError + +# Error Helpers + +proc raiseInvalid*(msg: string) = + raise MinInvalidError(msg: msg) + +proc raiseUndefined*(msg: string) = + raise MinUndefinedError(msg: msg) + +proc raiseOutOfBounds*(msg: string) = + raise MinOutOfBoundsError(msg: msg) + +proc raiseEmptyStack*() = + raise MinEmptyStackError(msg: "Insufficient items on the stack") + + +const + errorMessages: array[MinParserError, string] = [ + "no error", + "invalid token", + "string expected", + "')' expected", + "'\"' or \"'\" expected", + "'*/' expected", + "EOF expected", + "expression expected" + ] + tokToStr: array[MinTokenKind, string] = [ + "invalid token", + "EOF", + "string literal", + "int literal", + "float literal", + "(", + ")", + "symbol", + "true", + "false" + ] + +proc newScope*(parent: ref MinScope, name="scope"): MinScope = + result = MinScope(name: "<$1:$2>" % [name, $genOid()], parent: parent) + +proc newScopeRef*(parent: ref MinScope, name="scope"): ref MinScope = + new(result) + result[] = newScope(parent, name) + +proc open*(my: var MinParser, input: Stream, filename: string) = + lexbase.open(my, input) + my.filename = filename + my.state = @[stateStart] + my.kind = eMinError + my.a = "" + +proc close*(my: var MinParser) {.inline.} = + lexbase.close(my) + +proc getInt*(my: MinParser): int {.inline.} = + assert(my.kind == eMinInt) + return parseint(my.a) + +proc getFloat*(my: MinParser): float {.inline.} = + assert(my.kind == eMinFloat) + return parseFloat(my.a) + +proc kind*(my: MinParser): MinEventKind {.inline.} = + return my.kind + +proc getColumn*(my: MinParser): int {.inline.} = + result = getColNumber(my, my.bufpos) + +proc getLine*(my: MinParser): int {.inline.} = + result = my.lineNumber + +proc getFilename*(my: MinParser): string {.inline.} = + result = my.filename + +proc errorMsg*(my: MinParser, 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: MinParser): string = + assert(my.kind == eMinError) + result = errorMsg(my, errorMessages[my.err]) + +proc errorMsgExpected*(my: MinParser, e: string): string = + result = errorMsg(my, e & " expected") + +proc raiseParsing*(p: MinParser, msg: string) {.noinline, noreturn.} = + raise MinParsingError(msg: errorMsgExpected(p, msg)) + +proc raiseUndefined*(p:MinParser, msg: string) {.noinline, noreturn.} = + raise MinUndefinedError(msg: errorMsg(p, msg)) + +proc parseNumber(my: var MinParser) = + 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 MinParser): MinTokenKind = + 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 parseSymbol(my: var MinParser): MinTokenKind = + 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', ')', '(']): + add(my.a, buf[pos]) + inc(pos) + my.bufpos = pos + +proc skip(my: var MinParser) = + var pos = my.bufpos + var buf = my.buf + while true: + case buf[pos] + of ';': + # skip line comment: + inc(pos, 2) + while true: + case buf[pos] + of '\0': + break + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + break + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + break + else: + inc(pos) + of '/': + if buf[pos+1] == '/': + # skip line comment: + inc(pos, 2) + while true: + case buf[pos] + of '\0': + break + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + break + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + break + else: + inc(pos) + elif buf[pos+1] == '*': + # skip long comment: + inc(pos, 2) + while true: + case buf[pos] + of '\0': + my.err = errEOC_Expected + break + of '\c': + pos = lexbase.handleCR(my, pos) + buf = my.buf + of '\L': + pos = lexbase.handleLF(my, pos) + buf = my.buf + of '*': + inc(pos) + if buf[pos] == '/': + inc(pos) + break + else: + 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 MinParser): MinTokenKind = + 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 '\0': + result = tkEof + else: + result = parseSymbol(my) + case my.a + of "true": result = tkTrue + of "false": result = tkFalse + else: + discard + my.token = result + + +proc next*(my: var MinParser) = + 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 = MinEventKind(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 = MinEventKind(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 = MinEventKind(ord(tk)) + of tkBracketLe: + my.state.add(stateQuotation) + my.kind = eMinQuotationStart + else: + my.kind = eMinError + my.err = errExprExpected + +proc eat(p: var MinParser, token: MinTokenKind) = + if p.token == token: discard getToken(p) + else: raiseParsing(p, tokToStr[token]) + +proc parseMinValue*(p: var MinParser, parentScope: ref MinScope): MinValue = + #echo p.a, " (", p.token, ")" + case p.token + of tkTrue: + result = MinValue(kind: minBool, boolVal: true, column: p.getColumn, line: p.lineNumber) + discard getToken(p) + of tkFalse: + result = MinValue(kind: minBool, boolVal: false, column: p.getColumn, line: p.lineNumber) + discard getToken(p) + of tkString: + result = MinValue(kind: minString, strVal: p.a, column: p.getColumn, line: p.lineNumber) + p.a = "" + discard getToken(p) + of tkInt: + result = MinValue(kind: minInt, intVal: parseint(p.a), column: p.getColumn, line: p.lineNumber) + discard getToken(p) + of tkFloat: + result = MinValue(kind: minFloat, floatVal: parseFloat(p.a), column: p.getColumn, line: p.lineNumber) + discard getToken(p) + of tkBracketLe: + var q = newSeq[MinValue](0) + var scope = newScopeRef(parentScope, "quotation") + discard getToken(p) + while p.token != tkBracketRi: + q.add p.parseMinValue(scope) + eat(p, tkBracketRi) + result = MinValue(kind: minQuotation, qVal: q, column: p.getColumn, line: p.lineNumber, scope: scope) + of tkSymbol: + result = MinValue(kind: minSymbol, symVal: p.a, column: p.getColumn, line: p.lineNumber) + p.a = "" + discard getToken(p) + else: + raiseUndefined(p, "Undefined value: '"&p.a&"'") + result.filename = p.filename + +proc `$`*(a: MinValue): string = + case a.kind: + of minBool: + return $a.boolVal + of minSymbol: + return a.symVal + of minString: + return "\"$1\"" % a.strVal.replace("\"", "\\\"") + of minInt: + return $a.intVal + of minFloat: + return $a.floatVal + of minQuotation: + 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.strip & ")" + return q + +proc print*(a: MinValue) = + stdout.write($$a) + +proc `==`*(a: MinValue, b: MinValue): bool = + if a.kind == minSymbol and b.kind == minSymbol: + return a.symVal == b.symVal + elif a.kind == minInt and b.kind == minInt: + return a.intVal == b.intVal + elif a.kind == minInt and b.kind == minFloat: + return a.intVal.float == b.floatVal.float + elif a.kind == minFloat and b.kind == minFloat: + return a.floatVal == b.floatVal + elif a.kind == minFloat and b.kind == minInt: + return a.floatVal == b.intVal.float + elif a.kind == b.kind: + if a.kind == minString: + return a.strVal == b.strVal + elif a.kind == minBool: + return a.boolVal == b.boolVal + elif a.kind == minQuotation: + 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
M core/regex.nimcore/regex.nim

@@ -1,79 +1,79 @@

-import strutils -import ../vendor/sgregex - -type - InvalidRegexError = ref SystemError - -proc newRegex(pattern, mods: string): ptr srx_Context = - result = srx_Create(pattern, mods) - if result.isNil: - raise(InvalidRegexError(msg: "Invalid regular expression: \"$1\"" % pattern)) - -proc match*(str, pattern, mods: string): bool = - let r = newRegex(pattern, mods) - result = srx_Match(r, str, 0) == 1 - discard srx_Destroy(r) - -proc match*(str, pattern: string): bool = - return match(str, pattern, "") - -proc search*(str, pattern, mods: string): seq[string] = - let r = newRegex(pattern, mods) - discard srx_Match(r, str, 0) == 1 - let count = srx_GetCaptureCount(r) - result = newSeq[string](count) - for i in 0..count-1: - var first = 0 - var last = 0 - discard srx_GetCaptured(r, i, addr first, addr last) - result[i] = str.substr(first, last-1) - discard srx_Destroy(r) - -proc search*(str, pattern: string): seq[string] = - return search(str, pattern, "") - -proc replace*(str, pattern, repl, mods: string): string = - var r = newRegex(pattern, mods) - result = $srx_Replace(r, str, repl) - discard srx_Destroy(r) - -proc replace*(str, pattern, repl: string): string = - return replace(str, pattern, repl, "") - -proc `=~`*(str, r: string): seq[string] = - let m = r.search("(s)?/(.+?)/((.+?)/)?([mis]{0,3})?") - # full match, s, reg, replace/, replace, flags - if m[1] == "s" and m[3] != "": - return @[replace(str, m[2], m[4], m[5])] - else: - return search(str, m[2], m[5]) - -when isMainModule: - - proc tmatch(str, pattern: string) = - echo str, " =~ ", "/", pattern, "/", " -> ", str.match(pattern) - - proc tsearch(str, pattern: string) = - echo str, " =~ ", "/", pattern, "/", " -> ", str.search(pattern) - - proc tsearch(str, pattern, mods: string) = - echo str, " =~ ", "/", pattern, "/", mods, " -> ", str.search(pattern, mods) - - proc treplace(str, pattern, repl: string) = - echo str, " =~ ", "s/", pattern, "/", repl, "/", " -> ", str.replace(pattern, repl) - - proc toperator(str, pattern: string) = - echo str, " =~ ", pattern, " -> ", str =~ pattern - - "HELLO".tmatch("^H(.*)O$") - "HELLO".tmatch("^H(.*)S$") - "HELLO".tsearch("^H(E)(.*)O$") - "Hello, World!".treplace("[a-zA-Z]+,", "Goodbye,") - "127.0.0.1".tsearch("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$") - "127.0.0.1".treplace("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$", "$4.$3.$1.$2") - "127.0.0.1".treplace("[0-9]+", "255") - "Hello".tsearch("HELLO", "i") - "Hello\nWorld!".tsearch("HELLO.WORLD", "mis") - "Testing".toperator("s/test/eat/i") - - +import strutils +import ../vendor/sgregex + +type + InvalidRegexError = ref SystemError + +proc newRegex(pattern, mods: string): ptr srx_Context = + result = srx_Create(pattern, mods) + if result.isNil: + raise(InvalidRegexError(msg: "Invalid regular expression: \"$1\"" % pattern)) + +proc match*(str, pattern, mods: string): bool = + let r = newRegex(pattern, mods) + result = srx_Match(r, str, 0) == 1 + discard srx_Destroy(r) + +proc match*(str, pattern: string): bool = + return match(str, pattern, "") + +proc search*(str, pattern, mods: string): seq[string] = + let r = newRegex(pattern, mods) + discard srx_Match(r, str, 0) == 1 + let count = srx_GetCaptureCount(r) + result = newSeq[string](count) + for i in 0..count-1: + var first = 0 + var last = 0 + discard srx_GetCaptured(r, i, addr first, addr last) + result[i] = str.substr(first, last-1) + discard srx_Destroy(r) + +proc search*(str, pattern: string): seq[string] = + return search(str, pattern, "") + +proc replace*(str, pattern, repl, mods: string): string = + var r = newRegex(pattern, mods) + result = $srx_Replace(r, str, repl) + discard srx_Destroy(r) + +proc replace*(str, pattern, repl: string): string = + return replace(str, pattern, repl, "") + +proc `=~`*(str, r: string): seq[string] = + let m = r.search("(s)?/(.+?)/((.+?)/)?([mis]{0,3})?") + # full match, s, reg, replace/, replace, flags + if m[1] == "s" and m[3] != "": + return @[replace(str, m[2], m[4], m[5])] + else: + return search(str, m[2], m[5]) + +when isMainModule: + + proc tmatch(str, pattern: string) = + echo str, " =~ ", "/", pattern, "/", " -> ", str.match(pattern) + + proc tsearch(str, pattern: string) = + echo str, " =~ ", "/", pattern, "/", " -> ", str.search(pattern) + + proc tsearch(str, pattern, mods: string) = + echo str, " =~ ", "/", pattern, "/", mods, " -> ", str.search(pattern, mods) + + proc treplace(str, pattern, repl: string) = + echo str, " =~ ", "s/", pattern, "/", repl, "/", " -> ", str.replace(pattern, repl) + + proc toperator(str, pattern: string) = + echo str, " =~ ", pattern, " -> ", str =~ pattern + + "HELLO".tmatch("^H(.*)O$") + "HELLO".tmatch("^H(.*)S$") + "HELLO".tsearch("^H(E)(.*)O$") + "Hello, World!".treplace("[a-zA-Z]+,", "Goodbye,") + "127.0.0.1".tsearch("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$") + "127.0.0.1".treplace("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$", "$4.$3.$1.$2") + "127.0.0.1".treplace("[0-9]+", "255") + "Hello".tsearch("HELLO", "i") + "Hello\nWorld!".tsearch("HELLO.WORLD", "mis") + "Testing".toperator("s/test/eat/i") + +
A core/scope.nim

@@ -0,0 +1,73 @@

+import + strutils, + critbits +import + parser + +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) + else: + raiseUndefined("Symbol '$1' not found." % key) + +proc hasSymbol*(scope: ref MinScope, 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 MinScope, 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 MinScope, key: string, value: MinOperator): bool {.discardable.}= + result = false + # check if a symbol already exists in current scope + if not scope.isNil and scope.symbols.hasKey(key): + if 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) + +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) + else: + raiseUndefined("Sigil '$1' not found." % key) + +proc hasSigil*(scope: ref MinScope, 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 previous*(scope: ref MinScope): ref MinScope = + if scope.parent.isNil: + return scope + else: + return scope.parent
M core/utils.nimcore/utils.nim

@@ -1,194 +1,202 @@

-import - strutils, - critbits -import - parser, - value, - interpreter - -# Library methods - -proc previous(scope: ref MinScope): ref MinScope = - if scope.parent.isNil: - return scope - else: - return scope.parent - -proc define*(i: In, name: string): ref MinScope = - var scope = new MinScope - scope.name = name - scope.parent = i.scope - return scope - -proc symbol*(scope: ref MinScope, sym: string, p: MinOperatorProc): ref MinScope = - scope.symbols[sym] = MinOperator(prc: p, kind: minProcOp, sealed: true) - return scope - -proc symbol*(scope: ref MinScope, sym: string, v: MinValue): ref MinScope = - scope.symbols[sym] = MinOperator(val: v, kind: minValOp, sealed: true) - return scope - -proc sigil*(scope: ref MinScope, sym: string, p: MinOperatorProc): ref MinScope = - scope.previous.sigils[sym] = MinOperator(prc: p, kind: minProcOp, sealed: true) - return scope - -proc sigil*(scope: ref MinScope, sym: string, v: MinValue): ref MinScope = - scope.previous.sigils[sym] = MinOperator(val: v, kind: minValOp, sealed: true) - return scope - -proc finalize*(scope: ref MinScope) = - var mdl = newSeq[MinValue](0).newVal - mdl.scope = scope - let op = proc(i: In) {.gcsafe, closure.} = - i.evaluating = true - i.push mdl - i.evaluating = false - mdl.scope.previous.symbols[scope.name] = MinOperator(kind: minProcOp, prc: op) - -# Validators - -proc reqBool*(i: var MinInterpreter, a: var MinValue) = - a = i.pop - if not a.isBool: - 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: - raiseInvalid("Two bool values are required on the stack") - -proc reqInt*(i: var MinInterpreter, a: var MinValue) = - a = i.pop - if not a.isInt: - raiseInvalid("An integer is required on the stack") - -proc reqNumber*(i: var MinInterpreter, a: var MinValue) = - a = i.pop - if not a.isNumber: - raiseInvalid("A number 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: - raiseInvalid("Two integers are required on the stack") - -proc reqQuotation*(i: var MinInterpreter, a: var MinValue) = - a = i.pop - if not a.isQuotation: - 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): - 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): - 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): - raiseInvalid("Two numbers or two strings are required on the stack") - -proc reqIntAndString*(i: var MinInterpreter, b, a: var MinValue) = - b = i.pop - a = i.pop - if not (a.isString and b.isInt): - raiseInvalid("A string and a number are required on the stack") - -proc reqString*(i: var MinInterpreter, a: var MinValue) = - a = i.pop - if not a.isString: - raiseInvalid("A string is required on the stack") - -proc reqStringLikeAndQuotation*(i: var MinInterpreter, a, q: var MinValue) = - a = i.pop - q = i.pop - if not a.isStringLike or not q.isQuotation: - raiseInvalid("A string or symbol and a quotation are required on the stack") - -proc reqQuotationAndString*(i: var MinInterpreter, q, a: var MinValue) = - q = i.pop - a = i.pop - if not a.isString or not q.isQuotation: - raiseInvalid("A string and a quotation are required on the stack") - -proc reqQuotationAndStringLike*(i: var MinInterpreter, q, a: var MinValue) = - q = i.pop - a = i.pop - if not a.isStringLike or not q.isQuotation: - raiseInvalid("A quotation and a string or a symbol are required on the stack") - -proc reqStringOrQuotation*(i: var MinInterpreter, a: var MinValue) = - a = i.pop - if not a.isQuotation and not a.isString: - raiseInvalid("A quotation or a string is required on the stack") - -proc reqStringLike*(i: var MinInterpreter, a: var MinValue) = - a = i.pop - if not a.isStringLike: - raiseInvalid("A quoted 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: - raiseInvalid("Two strings are required on the stack") - -proc reqTwoStringLike*(i: var MinInterpreter, a, b: var MinValue) = - a = i.pop - b = i.pop - if not a.isStringLike or not b.isStringLike: - raiseInvalid("Two symbols or 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: - 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: - 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): - 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: - raiseInvalid("Three quotations are required on the stack") - -proc reqFourQuotations*(i: var MinInterpreter, a, b, c, d: var MinValue) = - a = i.pop - 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: - 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): - raiseInvalid("Two non-symbol values of similar type are required on the stack") - -proc reqDictionary*(i: In, q: var MinValue) = - q = i.pop - if not q.isDictionary: - raiseInvalid("An dictionary is required on the stack") +import + strutils, + critbits +import + parser, + value, + scope, + interpreter + +# Library methods + +proc printKeys(syms: CritBitTree[MinOperator]) = + for key, value in syms.pairs: + echo " - $1" % key + +proc define*(i: In, name: string): ref MinScope = + var scope = new MinScope + scope.name = name + scope.parent = i.scope + return scope + +proc symbol*(scope: ref MinScope, sym: string, p: MinOperatorProc): ref MinScope = + scope.symbols[sym] = MinOperator(prc: p, kind: minProcOp, sealed: true) + return scope + +proc symbol*(scope: ref MinScope, sym: string, v: MinValue): ref MinScope = + scope.symbols[sym] = MinOperator(val: v, kind: minValOp, sealed: true) + return scope + +proc sigil*(scope: ref MinScope, sym: string, p: MinOperatorProc): ref MinScope = + scope.sigils[sym] = MinOperator(prc: p, kind: minProcOp, sealed: true) + return scope + +proc sigil*(scope: ref MinScope, sym: string, v: MinValue): ref MinScope = + scope.sigils[sym] = MinOperator(val: v, kind: minValOp, sealed: true) + return scope + +proc finalize*(scope: ref MinScope) = + # TODO verify scope + var mdl = newSeq[MinValue](0).newVal(nil) + mdl.scope = scope + let op = proc(i: In) {.gcsafe, closure.} = + i.evaluating = true + i.push mdl + i.evaluating = false + #echo scope.previous.fullname, " - ", scope.name + #echo scope.previous.symbols.len, " - ", scope.symbols.len + #scope.previous.symbols[scope.name] = MinOperator(kind: minProcOp, prc: op) + # TODO echos + #echo "Finalizing: $1" % scope.name + #echo scope.previous.fullname, " - ", scope.previous.symbols.len + #scope.previous.symbols.printKeys + scope.previous.symbols[scope.name] = MinOperator(kind: minProcOp, prc: op) + #echo scope.previous.symbols.len, " - ", scope.symbols.len + +# Validators + +proc reqBool*(i: var MinInterpreter, a: var MinValue) = + a = i.pop + if not a.isBool: + 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: + raiseInvalid("Two bool values are required on the stack") + +proc reqInt*(i: var MinInterpreter, a: var MinValue) = + a = i.pop + if not a.isInt: + raiseInvalid("An integer is required on the stack") + +proc reqNumber*(i: var MinInterpreter, a: var MinValue) = + a = i.pop + if not a.isNumber: + raiseInvalid("A number 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: + raiseInvalid("Two integers are required on the stack") + +proc reqQuotation*(i: var MinInterpreter, a: var MinValue) = + a = i.pop + if not a.isQuotation: + 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): + 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): + 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): + raiseInvalid("Two numbers or two strings are required on the stack") + +proc reqIntAndString*(i: var MinInterpreter, b, a: var MinValue) = + b = i.pop + a = i.pop + if not (a.isString and b.isInt): + raiseInvalid("A string and a number are required on the stack") + +proc reqString*(i: var MinInterpreter, a: var MinValue) = + a = i.pop + if not a.isString: + raiseInvalid("A string is required on the stack") + +proc reqStringLikeAndQuotation*(i: var MinInterpreter, a, q: var MinValue) = + a = i.pop + q = i.pop + if not a.isStringLike or not q.isQuotation: + raiseInvalid("A string or symbol and a quotation are required on the stack") + +proc reqQuotationAndString*(i: var MinInterpreter, q, a: var MinValue) = + q = i.pop + a = i.pop + if not a.isString or not q.isQuotation: + raiseInvalid("A string and a quotation are required on the stack") + +proc reqQuotationAndStringLike*(i: var MinInterpreter, q, a: var MinValue) = + q = i.pop + a = i.pop + if not a.isStringLike or not q.isQuotation: + raiseInvalid("A quotation and a string or a symbol are required on the stack") + +proc reqStringOrQuotation*(i: var MinInterpreter, a: var MinValue) = + a = i.pop + if not a.isQuotation and not a.isString: + raiseInvalid("A quotation or a string is required on the stack") + +proc reqStringLike*(i: var MinInterpreter, a: var MinValue) = + a = i.pop + if not a.isStringLike: + raiseInvalid("A quoted 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: + raiseInvalid("Two strings are required on the stack") + +proc reqTwoStringLike*(i: var MinInterpreter, a, b: var MinValue) = + a = i.pop + b = i.pop + if not a.isStringLike or not b.isStringLike: + raiseInvalid("Two symbols or 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: + 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: + 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): + 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: + raiseInvalid("Three quotations are required on the stack") + +proc reqFourQuotations*(i: var MinInterpreter, a, b, c, d: var MinValue) = + a = i.pop + 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: + 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): + raiseInvalid("Two non-symbol values of similar type are required on the stack") + +proc reqDictionary*(i: In, q: var MinValue) = + q = i.pop + if not q.isDictionary: + raiseInvalid("An dictionary is required on the stack")
M core/value.nimcore/value.nim

@@ -1,83 +1,81 @@

-import - critbits -import - parser - -# Predicates - -proc isSymbol*(s: MinValue): bool = - return s.kind == minSymbol - -proc isQuotation*(s: MinValue): bool = - return s.kind == minQuotation - -proc isString*(s: MinValue): bool = - return s.kind == minString - -proc isFloat*(s: MinValue): bool = - return s.kind == minFloat - -proc isInt*(s: MinValue): bool = - return s.kind == minInt - -proc isNumber*(s: MinValue): bool = - return s.kind == minInt or s.kind == minFloat - -proc isBool*(s: MinValue): bool = - return s.kind == minBool - -proc isStringLike*(s: MinValue): bool = - return s.isSymbol or s.isString or (s.isQuotation and s.qVal.len == 1 and s.qVal[0].isSymbol) - -proc isDictionary*(q: MinValue): bool = - if not q.isQuotation: - return false - if q.qVal.len == 0: - return true - for val in q.qVal: - if not val.isQuotation or val.qVal.len != 2 or not (val.qVal[0].isSymbol or val.qVal[0].isString): - return false - return true - -# Constructors - -proc newVal*(s: string): MinValue = - return MinValue(kind: minString, strVal: s) - -proc newVal*(s: cstring): MinValue = - return MinValue(kind: minString, strVal: $s) - -proc newVal*(q: seq[MinValue]): MinValue = - return MinValue(kind: minQuotation, qVal: q) - -proc newVal*(s: BiggestInt): MinValue = - return MinValue(kind: minInt, intVal: s) - -proc newVal*(s: BiggestFloat): MinValue = - return MinValue(kind: minFloat, floatVal: s) - -proc newVal*(s: bool): MinValue = - return MinValue(kind: minBool, boolVal: s) - -proc newSym*(s: string): MinValue = - return MinValue(kind: minSymbol, symVal: s) - -proc newQuotation*(): MinValue = - return MinValue(kind: minQuotation, qVal: newSeq[MinValue](0)) - -# Get string value from string or quoted symbol - -proc getString*(v: MinValue): string = - if v.isSymbol: - return v.symVal - elif v.isString: - return v.strVal - 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") - +import + critbits +import + parser, + scope + +# Predicates + +proc isSymbol*(s: MinValue): bool = + return s.kind == minSymbol + +proc isQuotation*(s: MinValue): bool = + return s.kind == minQuotation + +proc isString*(s: MinValue): bool = + return s.kind == minString + +proc isFloat*(s: MinValue): bool = + return s.kind == minFloat + +proc isInt*(s: MinValue): bool = + return s.kind == minInt + +proc isNumber*(s: MinValue): bool = + return s.kind == minInt or s.kind == minFloat + +proc isBool*(s: MinValue): bool = + return s.kind == minBool + +proc isStringLike*(s: MinValue): bool = + return s.isSymbol or s.isString or (s.isQuotation and s.qVal.len == 1 and s.qVal[0].isSymbol) + +proc isDictionary*(q: MinValue): bool = + if not q.isQuotation: + return false + if q.qVal.len == 0: + return true + for val in q.qVal: + if not val.isQuotation or val.qVal.len != 2 or not (val.qVal[0].isSymbol or val.qVal[0].isString): + return false + return true + +# Constructors + +proc newVal*(s: string): MinValue = + return MinValue(kind: minString, strVal: s) + +proc newVal*(s: cstring): MinValue = + return MinValue(kind: minString, strVal: $s) + +proc newVal*(q: seq[MinValue], parentScope: ref MinScope): MinValue = + return MinValue(kind: minQuotation, qVal: q, scope: newScopeRef(parentScope)) + +proc newVal*(s: BiggestInt): MinValue = + return MinValue(kind: minInt, intVal: s) + +proc newVal*(s: BiggestFloat): MinValue = + return MinValue(kind: minFloat, floatVal: s) + +proc newVal*(s: bool): MinValue = + return MinValue(kind: minBool, boolVal: s) + +proc newSym*(s: string): MinValue = + return MinValue(kind: minSymbol, symVal: s) + +# Get string value from string or quoted symbol + +proc getString*(v: MinValue): string = + if v.isSymbol: + return v.symVal + elif v.isString: + return v.strVal + 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") +
M core/zip.nimcore/zip.nim

@@ -1,36 +1,36 @@

-import - os - -import - ../vendor/miniz - - -proc zip*(files: seq[string], filepath: string) = - var pZip: ptr mz_zip_archive = cast[ptr mz_zip_archive](alloc0(sizeof(mz_zip_archive))) - discard pZip.mz_zip_writer_init_file(filepath.cstring, 0) - var comment: pointer - for f in files: - discard pZip.mz_zip_writer_add_file(f.extractFileName.cstring, f.extractFileName.cstring, comment, 0, cast[mz_uint](MZ_DEFAULT_COMPRESSION)) - discard pZip.mz_zip_writer_finalize_archive() - discard pZip.mz_zip_writer_end() - dealloc(pZip) - -proc unzip*(src, dst: string) = - var pZip: ptr mz_zip_archive = cast[ptr mz_zip_archive](alloc0(sizeof(mz_zip_archive))) - discard pZip.mz_zip_reader_init_file(src.cstring, 0) - let total = pZip.mz_zip_reader_get_num_files() - if total == 0: - return - for i in 0.countup(total-1): - let isDir = pZip.mz_zip_reader_is_file_a_directory(i) - if isDir == 0: - # Extract file - let size = pZip.mz_zip_reader_get_filename(i, nil, 0) - var filename: cstring = cast[cstring](alloc(size)) - discard pZip.mz_zip_reader_get_filename(i, filename, size) - let dest = dst / $filename - dest.parentDir.createDir() - dest.writeFile("") - discard pZip.mz_zip_reader_extract_to_file(i, dest, 0) - discard pZip.mz_zip_reader_end() - dealloc(pZip) +import + os + +import + ../vendor/miniz + + +proc zip*(files: seq[string], filepath: string) = + var pZip: ptr mz_zip_archive = cast[ptr mz_zip_archive](alloc0(sizeof(mz_zip_archive))) + discard pZip.mz_zip_writer_init_file(filepath.cstring, 0) + var comment: pointer + for f in files: + discard pZip.mz_zip_writer_add_file(f.extractFileName.cstring, f.extractFileName.cstring, comment, 0, cast[mz_uint](MZ_DEFAULT_COMPRESSION)) + discard pZip.mz_zip_writer_finalize_archive() + discard pZip.mz_zip_writer_end() + dealloc(pZip) + +proc unzip*(src, dst: string) = + var pZip: ptr mz_zip_archive = cast[ptr mz_zip_archive](alloc0(sizeof(mz_zip_archive))) + discard pZip.mz_zip_reader_init_file(src.cstring, 0) + let total = pZip.mz_zip_reader_get_num_files() + if total == 0: + return + for i in 0.countup(total-1): + let isDir = pZip.mz_zip_reader_is_file_a_directory(i) + if isDir == 0: + # Extract file + let size = pZip.mz_zip_reader_get_filename(i, nil, 0) + var filename: cstring = cast[cstring](alloc(size)) + discard pZip.mz_zip_reader_get_filename(i, filename, size) + let dest = dst / $filename + dest.parentDir.createDir() + dest.writeFile("") + discard pZip.mz_zip_reader_extract_to_file(i, dest, 0) + discard pZip.mz_zip_reader_end() + dealloc(pZip)
M lib/min_crypto.nimlib/min_crypto.nim

@@ -1,73 +1,73 @@

-import - md5, - base64, - strutils, - times -import - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils -import - ../vendor/sha1, - ../vendor/nimSHA2, - ../vendor/nimAES - -proc crypto_module*(i: In)= - i.define("crypto") - - .symbol("md5") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.getMD5.newVal - - .symbol("sha1") do (i: In): - var s: MinValue - i.reqStringLike s - i.push compute(s.getString).toHex.newVal - - .symbol("sha224") do (i: In): - var s: MinValue - i.reqStringLike s - i.push computeSHA224(s.getString).hex.toLowerAscii.newVal - - .symbol("sha256") do (i: In): - var s: MinValue - i.reqStringLike s - i.push computeSHA256(s.getString).hex.toLowerAscii.newVal - - .symbol("sha384") do (i: In): - var s: MinValue - i.reqStringLike s - i.push computeSHA384(s.getString).hex.toLowerAscii.newVal - - .symbol("sha512") do (i: In): - var s: MinValue - i.reqStringLike s - i.push computeSHA512(s.getString).hex.toLowerAscii.newVal - - .symbol("encode") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.encode.newVal - - .symbol("decode") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.decode.newVal - - .symbol("aes") do (i: In): - var s, k: MinValue - i.reqTwoStrings k, s - var ctx: AESContext - var text = s.getString - var length = text.len - if length div 16 == 0: - text &= " ".repeat(16 - length) - elif length mod 16 != 0 and length div 16 >= 1: - text &= " ".repeat((length div 16 + 1) * 16 - length) - var key = k.getString.compute.toHex # SHA1 of key, to make sure it's long enough - var nonce = key[0..15] - i.push ctx.cryptOFB(nonce, text).newVal - - .finalize() +import + md5, + base64, + strutils, + times +import + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils +import + ../vendor/sha1, + ../vendor/nimSHA2, + ../vendor/nimAES + +proc crypto_module*(i: In)= + i.define("crypto") + + .symbol("md5") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.getMD5.newVal + + .symbol("sha1") do (i: In): + var s: MinValue + i.reqStringLike s + i.push compute(s.getString).toHex.newVal + + .symbol("sha224") do (i: In): + var s: MinValue + i.reqStringLike s + i.push computeSHA224(s.getString).hex.toLowerAscii.newVal + + .symbol("sha256") do (i: In): + var s: MinValue + i.reqStringLike s + i.push computeSHA256(s.getString).hex.toLowerAscii.newVal + + .symbol("sha384") do (i: In): + var s: MinValue + i.reqStringLike s + i.push computeSHA384(s.getString).hex.toLowerAscii.newVal + + .symbol("sha512") do (i: In): + var s: MinValue + i.reqStringLike s + i.push computeSHA512(s.getString).hex.toLowerAscii.newVal + + .symbol("encode") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.encode.newVal + + .symbol("decode") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.decode.newVal + + .symbol("aes") do (i: In): + var s, k: MinValue + i.reqTwoStrings k, s + var ctx: AESContext + var text = s.getString + var length = text.len + if length div 16 == 0: + text &= " ".repeat(16 - length) + elif length mod 16 != 0 and length div 16 >= 1: + text &= " ".repeat((length div 16 + 1) * 16 - length) + var key = k.getString.compute.toHex # SHA1 of key, to make sure it's long enough + var nonce = key[0..15] + i.push ctx.cryptOFB(nonce, text).newVal + + .finalize()
M lib/min_fs.nimlib/min_fs.nim

@@ -1,66 +1,66 @@

-import - strutils, - os, - times -import - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils, - ../core/fileutils - -proc fs_module*(i: In) = - i.define("fs") - .symbol("mtime") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.getLastModificationTime.toSeconds.newVal - - .symbol("atime") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.getLastAccessTime.toSeconds.newVal - - .symbol("ctime") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.getCreationTime.toSeconds.newVal - - .symbol("hidden?") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.isHidden.newVal - - .symbol("fsize") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.getFileSize.newVal - - .symbol("fstats") do (i: In): - var s: MinValue - i.reqStringLike s - let fi = s.getString.getFileInfo - var info = newSeq[MinValue](0).newVal - info.qVal.add @["name".newSym, s].newVal - info.qVal.add @["device".newSym, fi.id.device.newVal].newVal - info.qVal.add @["file".newSym, fi.id.file.newVal].newVal - info.qVal.add @["type".newSym, fi.kind.filetype.newVal].newVal - info.qVal.add @["size".newSym, fi.size.newVal].newVal - info.qVal.add @["permissions".newSym, fi.permissions.unixPermissions.newVal].newVal - info.qVal.add @["nlinks".newSym, fi.linkCount.newVal].newVal - info.qVal.add @["ctime".newSym, fi.creationTime.toSeconds.newVal].newVal - info.qVal.add @["atime".newSym, fi.lastAccessTime.toSeconds.newVal].newVal - info.qVal.add @["mtime".newSym, fi.lastWriteTime.toSeconds.newVal].newVal - i.push info - - .symbol("ftype") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.getFileInfo.kind.filetype.newVal - - .symbol("fperms") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.getFilePermissions.unixPermissions.newVal - - .finalize() +import + strutils, + os, + times +import + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils, + ../core/fileutils + +proc fs_module*(i: In) = + i.define("fs") + .symbol("mtime") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.getLastModificationTime.toSeconds.newVal + + .symbol("atime") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.getLastAccessTime.toSeconds.newVal + + .symbol("ctime") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.getCreationTime.toSeconds.newVal + + .symbol("hidden?") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.isHidden.newVal + + .symbol("fsize") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.getFileSize.newVal + + .symbol("fstats") do (i: In): + var s: MinValue + i.reqStringLike s + let fi = s.getString.getFileInfo + var info = newSeq[MinValue](0).newVal(i.scope) + info.qVal.add @["name".newSym, s].newVal(i.scope) + info.qVal.add @["device".newSym, fi.id.device.newVal].newVal(i.scope) + info.qVal.add @["file".newSym, fi.id.file.newVal].newVal(i.scope) + info.qVal.add @["type".newSym, fi.kind.filetype.newVal].newVal(i.scope) + info.qVal.add @["size".newSym, fi.size.newVal].newVal(i.scope) + info.qVal.add @["permissions".newSym, fi.permissions.unixPermissions.newVal].newVal(i.scope) + info.qVal.add @["nlinks".newSym, fi.linkCount.newVal].newVal(i.scope) + info.qVal.add @["ctime".newSym, fi.creationTime.toSeconds.newVal].newVal(i.scope) + info.qVal.add @["atime".newSym, fi.lastAccessTime.toSeconds.newVal].newVal(i.scope) + info.qVal.add @["mtime".newSym, fi.lastWriteTime.toSeconds.newVal].newVal(i.scope) + i.push info + + .symbol("ftype") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.getFileInfo.kind.filetype.newVal + + .symbol("fperms") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.getFilePermissions.unixPermissions.newVal + + .finalize()
M lib/min_io.nimlib/min_io.nim

@@ -1,115 +1,115 @@

-import - os, - strutils -import - ../core/linedit, - ../core/regex, - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils - -# I/O - - -proc io_module*(i: In) = - i.define("io") - - .symbol("newline") do (i: In): - echo "" - - .symbol("puts") do (i: In): - let a = i.peek - echo $$a - - .symbol("column-print") do (i: In): - var n, q: MinValue - i.reqIntAndQuotation n, q - var c = 0 - for s in q.qVal: - c.inc - stdout.write $$s & spaces(max(0, 15 - ($$s).len)) - if c mod n.intVal == 0: - echo "" - echo "" - - .symbol("gets") do (i: In): - var ed = initEditor() - i.push ed.readLine().newVal - - .symbol("password") do (i: In): - var ed = initEditor() - i.push ed.password("Enter Password: ").newVal - - .symbol("ask") do (i: In): - var s: MinValue - var ed = initEditor() - i.reqString s - i.push ed.readLine(s.getString & ": ").newVal - - .symbol("confirm") do (i: In): - var s: MinValue - var ed = initEditor() - i.reqString s - proc confirm(): bool = - let answer = ed.readLine(s.getString & " [yes/no]: ") - if answer.match("^y(es)?$", "i"): - return true - elif answer.match("^no?$", "i"): - return false - else: - stdout.write "Invalid answer. Please enter 'yes' or 'no': " - return confirm() - i.push confirm().newVal - - .symbol("choose") do (i: In): - var q, s: MinValue - var ed = initEditor() - i.reqStringLikeAndQuotation s, q - if q.qVal.len <= 0: - raiseInvalid("No choices to display") - stdout.writeLine(s.getString) - proc choose(): int = - var c = 0 - for item in q.qVal: - if not item.isQuotation or not item.qVal.len == 2 or not item.qVal[0].isString or not item.qVal[1].isQuotation: - raiseInvalid("Each item of the quotation must be a quotation containing a string and a quotation") - c.inc - echo "$1 - $2" % [$c, item.qVal[0].getString] - let answer = ed.readLine("Enter your choice ($1 - $2): " % ["1", $c]) - var choice: int - try: - choice = answer.parseInt - except: - choice = 0 - if choice <= 0 or choice > c: - echo "Invalid choice." - return choose() - else: - return choice - let choice = choose() - i.unquote("<choose>", q.qVal[choice-1].qVal[1]) - - .symbol("print") do (i: In): - let a = i.peek - a.print - - .symbol("fread") do (i: In): - var a: MinValue - i.reqString a - i.push newVal(a.strVal.readFile) - - .symbol("fwrite") do (i: In): - var a, b: MinValue - i.reqTwoStrings a, b - a.strVal.writeFile(b.strVal) - - .symbol("fappend") do (i: In): - var a, b: MinValue - i.reqTwoStrings a, b - var f:File - discard f.open(a.strVal, fmAppend) - f.write(b.strVal) - f.close() - - .finalize() +import + os, + strutils +import + ../core/linedit, + ../core/regex, + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils + +# I/O + + +proc io_module*(i: In) = + i.define("io") + + .symbol("newline") do (i: In): + echo "" + + .symbol("puts") do (i: In): + let a = i.peek + echo $$a + + .symbol("column-print") do (i: In): + var n, q: MinValue + i.reqIntAndQuotation n, q + var c = 0 + for s in q.qVal: + c.inc + stdout.write $$s & spaces(max(0, 15 - ($$s).len)) + if c mod n.intVal == 0: + echo "" + echo "" + + .symbol("gets") do (i: In): + var ed = initEditor() + i.push ed.readLine().newVal + + .symbol("password") do (i: In): + var ed = initEditor() + i.push ed.password("Enter Password: ").newVal + + .symbol("ask") do (i: In): + var s: MinValue + var ed = initEditor() + i.reqString s + i.push ed.readLine(s.getString & ": ").newVal + + .symbol("confirm") do (i: In): + var s: MinValue + var ed = initEditor() + i.reqString s + proc confirm(): bool = + let answer = ed.readLine(s.getString & " [yes/no]: ") + if answer.match("^y(es)?$", "i"): + return true + elif answer.match("^no?$", "i"): + return false + else: + stdout.write "Invalid answer. Please enter 'yes' or 'no': " + return confirm() + i.push confirm().newVal + + .symbol("choose") do (i: In): + var q, s: MinValue + var ed = initEditor() + i.reqStringLikeAndQuotation s, q + if q.qVal.len <= 0: + raiseInvalid("No choices to display") + stdout.writeLine(s.getString) + proc choose(): int = + var c = 0 + for item in q.qVal: + if not item.isQuotation or not item.qVal.len == 2 or not item.qVal[0].isString or not item.qVal[1].isQuotation: + raiseInvalid("Each item of the quotation must be a quotation containing a string and a quotation") + c.inc + echo "$1 - $2" % [$c, item.qVal[0].getString] + let answer = ed.readLine("Enter your choice ($1 - $2): " % ["1", $c]) + var choice: int + try: + choice = answer.parseInt + except: + choice = 0 + if choice <= 0 or choice > c: + echo "Invalid choice." + return choose() + else: + return choice + let choice = choose() + i.unquote("<choose>", q.qVal[choice-1].qVal[1]) + + .symbol("print") do (i: In): + let a = i.peek + a.print + + .symbol("fread") do (i: In): + var a: MinValue + i.reqString a + i.push newVal(a.strVal.readFile) + + .symbol("fwrite") do (i: In): + var a, b: MinValue + i.reqTwoStrings a, b + a.strVal.writeFile(b.strVal) + + .symbol("fappend") do (i: In): + var a, b: MinValue + i.reqTwoStrings a, b + var f:File + discard f.open(a.strVal, fmAppend) + f.write(b.strVal) + f.close() + + .finalize()
M lib/min_lang.nimlib/min_lang.nim

@@ -1,833 +1,841 @@

-import - critbits, - strutils, - os, - json, - algorithm, - oids -import - ../core/consts, - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils, - ../core/regex, - ../core/linedit - -# Dictionary Methods - -proc dget*(q: MinValue, s: MinValue): MinValue = - # Assumes q is a dictionary - for v in q.qVal: - if v.qVal[0].getString == s.getString: - return v.qVal[1] - raiseInvalid("Key '$1' not found" % s.getString) - -proc dhas*(q: MinValue, s: MinValue): bool = - # Assumes q is a dictionary - for v in q.qVal: - if v.qVal[0].getString == s.getString: - return true - return false - -proc ddel*(q: var MinValue, s: MinValue): MinValue = - # Assumes q is a dictionary - var found = false - var c = -1 - for v in q.qVal: - c.inc - if v.qVal[0].getString == s.getString: - found = true - break - if found: - q.qVal.delete(c) - return q - -proc dset*(q: var MinValue, s: MinValue, m: MinValue): MinValue {.discardable.}= - # Assumes q is a dictionary - var found = false - var c = -1 - for v in q.qVal: - c.inc - if v.qVal[0].getString == s.getString: - found = true - break - if found: - q.qVal.delete(c) - q.qVal.insert(@[s.getString.newSym, m].newVal, c) - return q - -proc keys*(q: MinValue): MinValue = - # Assumes q is a dictionary - result = newSeq[MinValue](0).newVal - for v in q.qVal: - result.qVal.add v.qVal[0] - -proc values*(q: MinValue): MinValue = - # Assumes q is a dictionary - result = newSeq[MinValue](0).newVal - for v in q.qVal: - result.qVal.add v.qVal[1] - -# JSON interop - -proc `%`*(a: MinValue): JsonNode = - case a.kind: - of minBool: - return %a.boolVal - of minSymbol: - return %(";sym:$1" % [a.symVal]) - of minString: - return %a.strVal - of minInt: - return %a.intVal - of minFloat: - return %a.floatVal - of minQuotation: - if a.isDictionary: - result = newJObject() - for i in a.qVal: - result[$i.qVal[0].symVal] = %i.qVal[1] - else: - result = newJArray() - for i in a.qVal: - result.add %i - -proc fromJson*(json: JsonNode): MinValue = - case json.kind: - of JNull: - result = newSeq[MinValue](0).newVal - of JBool: - result = json.getBVal.newVal - of JInt: - result = json.getNum.newVal - of JFloat: - result = json.getFNum.newVal - of JString: - let s = json.getStr - if s.match("^;sym:"): - result = regex.replace(s, "^;sym:", "").newSym - else: - result = json.getStr.newVal - of JObject: - var res = newSeq[MinValue](0) - for key, value in json.pairs: - res.add @[key.newSym, value.fromJson].newVal - return res.newVal - of JArray: - var res = newSeq[MinValue](0) - for value in json.items: - res.add value.fromJson - return res.newVal - -proc lang_module*(i: In) = - i.scope - .symbol("exit") do (i: In): - termRestore() - quit(0) - - .symbol("symbols") do (i: In): - var q = newSeq[MinValue](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 - - .symbol("sigils") do (i: In): - var q = newSeq[MinValue](0) - var scope = i.scope - while not scope.isNil: - for s in scope.sigils.keys: - q.add s.newVal - scope = scope.parent - i.push q.newVal - - .symbol("from-json") do (i: In): - var s: MinValue - i.reqString s - i.push s.getString.parseJson.fromJson - - .symbol("to-json") do (i: In): - var q: MinValue - i.reqQuotation q - i.push(($(%q)).newVal) - - .symbol("debug?") do (i: In): - i.push i.debugging.newVal - - .symbol("debug") do (i: In): - i.debugging = not i.debugging - echo "Debugging: $1" % [$i.debugging] - - # Language constructs - - .symbol("define") do (i: In): - var sym: MinValue - i.reqStringLike sym - var q1 = i.pop # existing (auto-quoted) - var symbol: string - if not q1.isQuotation: - q1 = @[q1].newVal - symbol = sym.getString - if not symbol.match "^[a-zA-Z0-9_][a-zA-Z0-9/!?+*._-]*$": - raiseInvalid("Symbol identifier '$1' contains invalid characters." % symbol) - i.debug "[define] (scope: $1) $2 = $3" % [i.scope.name, symbol, $q1] - if i.scope.symbols.hasKey(symbol) and i.scope.symbols[symbol].sealed: - raiseUndefined("Attempting to redefine sealed symbol '$1' on scope '$2'" % [symbol, i.scope.name]) - i.newScope("$1#$2" % [symbol, $genOid()], q1) - i.scope.symbols[symbol] = MinOperator(kind: minValOp, val: q1, sealed: false) - - .symbol("bind") do (i: In): - var sym: MinValue - i.reqStringLike sym - var q1 = i.pop # existing (auto-quoted) - var symbol: string - if not q1.isQuotation: - q1 = @[q1].newVal - symbol = sym.getString - i.debug "[bind] " & symbol & " = " & $q1 - let res = i.scope.setSymbol(symbol, MinOperator(kind: minValOp, val: q1)) - if not res: - raiseUndefined("Attempting to bind undefined symbol: " & symbol) - - .symbol("delete") do (i: In): - var sym: MinValue - i.reqStringLike sym - let res = i.scope.delSymbol(sym.getString) - if not res: - raiseUndefined("Attempting to delete undefined symbol: " & sym.getString) - - .symbol("scope") do (i: In): - var code: MinValue - i.reqQuotation code - code.filename = i.filename - i.unquote("<scope>", code) - i.push @[code].newVal - - .symbol("module") do (i: In): - var code, name: MinValue - i.reqStringLike name - i.reqQuotation code - code.filename = i.filename - i.unquote("<module>", code) - i.scope.symbols[name.getString] = MinOperator(kind: minValOp, val: @[code].newVal) - - .symbol("scope?") do (i: In): - var q: MinValue - i.reqQuotation q - if not q.scope.isNil: - i.push true.newVal - else: - i.push false.newVal - - .symbol("import") do (i: In): - var mdl, rawName: MinValue - var name: string - i.reqStringLike rawName - name = rawName.getString - i.apply(i.scope.getSymbol(name)) - i.reqQuotation mdl - if not mdl.scope.isNil: - for sym, val in mdl.scope.symbols.pairs: - i.debug "[import] $1:$2" % [i.scope.name, sym] - i.scope.symbols[sym] = val - - #.symbol("sigil") do (i: In): - # var q1, q2: MinValue - # i.reqTwoQuotations q1, q2 - # if q1.qVal.len == 1 and q1.qVal[0].kind == minSymbol: - # var symbol = q1.qVal[0].symVal - # if symbol.len == 1: - # if i.scope.hasSigil(symbol): - # raiseInvalid("Sigil '$1' already exists" % [symbol]) - # i.scope.sigils[symbol] = MinOperator(kind: minValOp, val: q2) - # else: - # raiseInvalid("A sigil can only have one character") - # else: - # raiseInvalid("The top quotation must contain only one symbol value") - - .symbol("eval") do (i: In): - var s: MinValue - i.reqString s - i.eval s.strVal - - .symbol("load") do (i: In): - var s: MinValue - i.reqStringLike s - var file = s.getString - if not file.endsWith(".min"): - file = file & ".min" - file = i.pwd.joinPath(file) - if not file.fileExists: - raiseInvalid("File '$1' does not exists." % file) - i.load file - - .symbol("with") do (i: In): - var qscope, qprog: MinValue - i.reqTwoQuotations qscope, qprog - if qscope.qVal.len > 0: - # System modules are empty quotes and don't need to be unquoted - i.unquote("<with-scope>", qscope) - let scope = i.scope - i.scope = qscope.scope - for v in qprog.qVal: - i.push v - i.scope = scope - - .symbol("publish") do (i: In): - var qscope, str: MinValue - i.reqQuotationAndStringLike qscope, str - let sym = str.getString - if qscope.scope.symbols.hasKey(sym) and qscope.scope.symbols[sym].sealed: - raiseUndefined("Attempting to redefine sealed symbol '$1' on scope '$2'" % [sym, qscope.scope.name]) - qscope.scope.symbols[sym] = i.scope.getSymbol(sym) - - .symbol("source") do (i: In): - var s: MinValue - i.reqStringLike s - let str = s.getString - let sym = i.scope.getSymbol(str) - if sym.kind == minValOp: - i.push sym.val - else: - raiseInvalid("No source available for native symbol '$1'." % str) - - .symbol("call") do (i: In): - var symbol, q: MinValue - i.reqStringLike symbol - i.reqQuotation q - let s = symbol.getString - let origScope = i.scope - i.scope = q.scope - let sym = i.scope.getSymbol(s) - i.apply(sym) - i.scope = origScope - - .symbol("inspect") do (i: In): - var scope: MinValue - i.reqQuotation scope - var symbols = newSeq[MinValue](0) - if scope.scope.isNil: - i.push symbols.newVal - else: - for s in scope.scope.symbols.keys: - symbols.add s.newVal - i.push symbols.newVal - - .symbol("raise") do (i: In): - var err: MinValue - i.reqDictionary err - if err.dhas("error".newSym) and err.dhas("message".newSym): - raiseRuntime("($1) $2" % [err.dget("error".newVal).getString, err.dget("message".newVal).getString], err.qVal) - else: - raiseInvalid("Invalid error dictionary") - - .symbol("format-error") do (i: In): - var err: MinValue - i.reqDictionary err - if err.dhas("error".newSym) and err.dhas("message".newSym): - var msg: string - var list = newSeq[MinValue]() - list.add err.dget("message".newVal) - if err.qVal.contains("symbol".newVal): - list.add err.dget("symbol".newVal) - if err.qVal.contains("filename".newVal): - list.add err.dget("filename".newVal) - if err.qVal.contains("line".newVal): - list.add err.dget("line".newVal) - if err.qVal.contains("column".newVal): - list.add err.dget("column".newVal) - if list.len <= 1: - msg = "(!) $1" % $$list[0] - else: - msg = "(!) $3($4,$5) `$2`: $1" % [$$list[0], $$list[1], $$list[2], $$list[3], $$list[4]] - i.push msg.newVal - else: - raiseInvalid("Invalid error dictionary") - - .symbol("try") do (i: In): - var prog: MinValue - i.reqQuotation prog - if prog.qVal.len == 0: - raiseInvalid("Quotation must contain at least one element") - var code = prog.qVal[0] - 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 (hasCatch and not catch.isQuotation) or (hasFinally and not final.isQuotation): - raiseInvalid("Quotation must contain at one quotation") - try: - i.unquote("<try-code>", code) - except MinRuntimeError: - if not hasCatch: - return - let e = (MinRuntimeError)getCurrentException() - i.push e.qVal.newVal - i.unquote("<try-catch>", catch) - except: - if not hasCatch: - return - let e = getCurrentException() - var res = newSeq[MinValue](0) - let err = regex.replace($e.name, ":.+$", "") - res.add @["error".newSym, err.newVal].newVal - res.add @["message".newSym, e.msg.newVal].newVal - res.add @["symbol".newSym, i.currSym].newVal - res.add @["filename".newSym, i.currSym.filename.newVal].newVal - res.add @["line".newSym, i.currSym.line.newVal].newVal - res.add @["column".newSym, i.currSym.column.newVal].newVal - i.push res.newVal - i.unquote("<try-catch>", catch) - finally: - if hasFinally: - i.unquote("<try-finally>", final) - - # Operations on the whole stack - - .symbol("id") do (i: In): - discard - - .symbol("pop") do (i: In): - if i.stack.len < 1: - raiseEmptyStack() - discard i.pop - - .symbol("dup") do (i: In): - i.push i.peek - - .symbol("dip") do (i: In): - var q: MinValue - i.reqQuotation q - let v = i.pop - i.unquote("<dip>", q) - i.push v - - .symbol("swap") do (i: In): - if i.stack.len < 2: - raiseEmptyStack() - let a = i.pop - let b = i.pop - i.push a - i.push b - - .symbol("sip") do (i: In): - var a, b: MinValue - i.reqTwoQuotations a, b - i.push b - i.unquote("<sip>", a) - i.push b - - .symbol("clear-stack") do (i: In): - while i.stack.len > 0: - discard i.pop - - .symbol("dump-stack") do (i: In): - echo i.dump - - .symbol("get-stack") do (i: In): - i.push i.stack.newVal - - .symbol("set-stack") do (i: In): - var q: MinValue - i.reqQuotation q - i.stack = q.qVal - - # Operations on quotations or strings - - .symbol("concat") do (i: In): - var q1, q2: MinValue - i.reqTwoQuotationsOrStrings q1, q2 - if q1.isString and q2.isString: - let s = q2.strVal & q1.strVal - i.push newVal(s) - else: - let q = q2.qVal & q1.qVal - i.push newVal(q) - - .symbol("first") do (i: In): - var q: MinValue - i.reqStringOrQuotation q - if q.isQuotation: - if q.qVal.len == 0: - raiseOutOfBounds("Quotation is empty") - i.push q.qVal[0] - elif q.isString: - if q.strVal.len == 0: - raiseOutOfBounds("String is empty") - i.push newVal($q.strVal[0]) - - .symbol("rest") do (i: In): - var q: MinValue - i.reqStringOrQuotation q - if q.isQuotation: - if q.qVal.len == 0: - raiseOutOfBounds("Quotation is empty") - i.push newVal(q.qVal[1..q.qVal.len-1]) - elif q.isString: - if q.strVal.len == 0: - raiseOutOfBounds("String is empty") - i.push newVal(q.strVal[1..q.strVal.len-1]) - - .symbol("quote") do (i: In): - let a = i.pop - i.push MinValue(kind: minQuotation, qVal: @[a]) - - .symbol("unquote") do (i: In): - var q: MinValue - i.reqQuotation q - i.unquote("<unquote>", q) - - .symbol("append") do (i: In): - var q: MinValue - i.reqQuotation q - let v = i.pop - q.qVal.add v - i.push q - - .symbol("cons") do (i: In): - var q: MinValue - i.reqQuotation q - let v = i.pop - q.qVal = @[v] & q.qVal - i.push q - - .symbol("at") do (i: In): - var index, q: MinValue - i.reqIntAndQuotation index, q - if q.qVal.len-1 < index.intVal: - raiseOutOfBounds("Insufficient items in quotation") - i.push q.qVal[index.intVal.int] - - .symbol("size") do (i: In): - var q: MinValue - i.reqStringOrQuotation q - if q.isQuotation: - i.push q.qVal.len.newVal - elif q.isString: - i.push q.strVal.len.newVal - - .symbol("contains") do (i: In): - let v = i.pop - var q: MinValue - i.reqQuotation q - i.push q.qVal.contains(v).newVal - - .symbol("map") do (i: In): - var prog, list: MinValue - i.reqTwoQuotations prog, list - #i.push newVal(newSeq[MinValue](0)) - var res = newSeq[MinValue](0) - for litem in list.qVal: - i.push litem - i.unquote("<map-quotation>", prog) - res.add i.pop - i.push res.newVal - - .symbol("foreach") do (i: In): - var prog, list: MinValue - i.reqTwoQuotations prog, list - for litem in list.qVal: - i.push litem - i.unquote("<foreach-quotation>", prog) - - .symbol("times") do (i: In): - var t, prog: MinValue - i.reqIntAndQuotation t, prog - if t.intVal < 1: - raiseInvalid("A non-zero natural number is required") - for c in 1..t.intVal: - i.unquote("<times-quotation>", prog) - - .symbol("ifte") do (i: In): - var fpath, tpath, check: MinValue - i.reqThreeQuotations fpath, tpath, check - var stack = i.stack - i.unquote("<ifte-check>", 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.unquote("<ifte-true>", tpath) - else: - i.unquote("<ifte-false>", fpath) - - # 4 ( - # ((> 3) ("Greater than 3" put!)) - # ((< 3) ("Smaller than 3" put!)) - # ('true ("Exactly 3" put!)) - # ) case - .symbol("case") do (i: In): - var cases: MinValue - i.reqQuotation cases - let last = cases.qVal.len-1 - if last == 0: - raiseInvalid("Empty case operator") - var k = 0 - let stack = i.stack - for c in cases.qVal: - i.stack = stack - if not c.isQuotation: - raiseInvalid("A quotation of quotations is required") - k.inc - if c.qVal.len != 2 or not c.qVal[0].isQuotation or not c.qVal[1].isQuotation: - raiseInvalid("Inner quotations in case operator must contain two quotations") - var q = c.qVal[0] - i.unquote("<case-$1-check>" % $k, q) - let res = i.pop - if not res.isBool(): - raiseInvalid("Result of case #$1 is not a boolean value" % $k) - if res.boolVal == true: - var t = c.qVal[1] - i.unquote("<case-$1-true>" % $k, t) - - - .symbol("while") do (i: In): - var d, b: MinValue - i.reqTwoQuotations d, b - i.push b.qVal - i.unquote("<while-check>", b) - var check = i.pop - while check.isBool and check.boolVal == true: - i.unquote("<while-quotation>", d) - i.unquote("<while-check>", b) - check = i.pop - - .symbol("filter") do (i: In): - var filter, list: MinValue - i.reqTwoQuotations filter, list - var res = newSeq[MinValue](0) - for e in list.qVal: - i.push e - i.unquote("<filter-check>", filter) - var check = i.pop - if check.isBool and check.boolVal == true: - res.add e - i.push res.newVal - - .symbol("sort") do (i: In): - var cmp, list: MinValue - i.reqTwoQuotations cmp, list - var i2 = i - var minCmp = proc(a, b: MinValue): int {.closure.}= - i2.push a - i2.push b - i2.unquote("<sort-cmp>", cmp) - let r = i2.pop - if r.isBool: - if r.boolVal == true: - return 1 - else: - return -1 - else: - raiseInvalid("Predicate quotation must return a boolean value") - var qList = list.qVal - sort[MinValue](qList, minCmp) - i.push qList.newVal - - .symbol("linrec") do (i: In): - var r2, r1, t, p: MinValue - i.reqFourQuotations r2, r1, t, p - proc linrec(i: In, p, t, r1, r2: var MinValue) = - i.unquote("<linrec-predicate>", p) - var check = i.pop - if check.isBool and check.boolVal == true: - i.unquote("<linrec-true>", t) - else: - i.unquote("<linrec-r1>", r1) - i.linrec(p, t, r1, r2) - i.unquote("<linrec-r2>", r2) - i.linrec(p, t, r1, r2) - - .symbol("dhas?") do (i: In): - var d, k: MinValue - i.reqStringLike k - i.reqDictionary d - i.push d.dhas(k).newVal - - .symbol("dget") do (i: In): - var d, k: MinValue - i.reqStringLike k - i.reqDictionary d - i.push d.dget(k) - - .symbol("dset") do (i: In): - var d, k: MinValue - let m = i.pop - i.reqStringLike k - i.reqDictionary d - i.push d.dset(k, m) - - .symbol("ddel") do (i: In): - var d, k: MinValue - i.reqStringLike k - i.reqDictionary d - i.push d.ddel(k) - - .symbol("dprint") do (i: In): - var d: MinValue - i.reqDictionary d - for v in d.qVal: - echo "$1: $2" % [$v.qVal[0], $v.qVal[1]] - #i.push d - - .symbol("keys") do (i: In): - var d: MinValue - i.reqDictionary d - #i.push d - i.push d.keys - - .symbol("values") do (i: In): - var d: MinValue - i.reqDictionary d - #i.push d - i.push d.values - - .symbol("interpolate") do (i: In): - var s, q: MinValue - i.reqQuotationAndString q, s - var strings = newSeq[string](0) - for el in q.qVal: - if el.isSymbol: - i.push el - strings.add $$i.pop - else: - strings.add $$el - let res = s.strVal % strings - i.push res.newVal - - .symbol("version") do (i: In): - i.push version.newVal - - # Save/load symbols - - .symbol("save-symbol") do (i: In): - var s:MinValue - i.reqStringLike s - let sym = s.getString - let op = i.scope.getSymbol(sym) - if op.kind == minProcOp: - raiseInvalid("Symbol '$1' cannot be serialized." % sym) - let json = MINIMSYMBOLS.readFile.parseJson - json[sym] = %op.val - MINIMSYMBOLS.writeFile(json.pretty) - - .symbol("load-symbol") do (i: In): - var s:MinValue - i.reqStringLike s - let sym = s.getString - let json = MINIMSYMBOLS.readFile.parseJson - if not json.hasKey(sym): - raiseUndefined("Symbol '$1' not found." % sym) - let val = json[sym].fromJson - i.scope.symbols[sym] = MinOperator(kind: minValOp, val: val) - - .symbol("stored-symbols") do (i: In): - var q = newSeq[MinValue](0) - let json = MINIMSYMBOLS.readFile.parseJson - for k,v in json.pairs: - q.add k.newVal - i.push q.newVal - - .symbol("remove-symbol") do (i: In): - var s:MinValue - i.reqStringLike s - let sym = s.getString - var json = MINIMSYMBOLS.readFile.parseJson - if not json.hasKey(sym): - raiseUndefined("Symbol '$1' not found." % sym) - json.delete(sym) - MINIMSYMBOLS.writeFile(json.pretty) - - .symbol("seal") do (i: In): - var sym: MinValue - i.reqStringLike sym - var s = i.scope.getSymbol(sym.getString) - s.sealed = true - i.scope.setSymbol(sym.getString, s) - - # Sigils - - .sigil("'") do (i: In): - var s: MinValue - i.reqString s - i.push(@[s.strVal.newSym].newVal) - - .sigil(":") do (i: In): - i.push("define".newSym) - - .sigil("~") do (i: In): - i.push("delete".newSym) - - .sigil("$") do (i: In): - i.push("getenv".newSym) - - .sigil("!") do (i: In): - i.push("system".newSym) - - .sigil("&") do (i: In): - i.push("run".newSym) - - .sigil("@") do (i: In): - i.push("bind".newSym) - - .sigil("=") do (i: In): - i.push("module".newSym) - - .sigil("^") do (i: In): - i.push("call".newSym) - - .sigil("/") do (i: In): - i.push("dget".newSym) - - .sigil(">") do (i: In): - i.push("save-symbol".newSym) - - .sigil("<") do (i: In): - i.push("load-symbol".newSym) - - .sigil("*") do (i: In): - i.push("seal".newSym) - - .symbol(":") do (i: In): - i.push("define".newSym) - - .symbol("@") do (i: In): - i.push("bind".newSym) - - .symbol("!") do (i: In): - i.push("system".newSym) - - .symbol("&") do (i: In): - i.push("run".newSym) - - .symbol("$") do (i: In): - i.push("getenv".newSym) - - .symbol("^") do (i: In): - i.push("call".newSym) - - .symbol("%") do (i: In): - i.push("interpolate".newSym) - - .symbol("'") do (i: In): - i.push("quote".newSym) - - .symbol("->") do (i: In): - i.push("unquote".newSym) - - .symbol("=>") do (i: In): - i.push("scope".newSym) - - .symbol("=~") do (i: In): - i.push("regex".newSym) - - .finalize() +import + critbits, + strutils, + os, + json, + algorithm, + oids +import + ../core/consts, + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils, + ../core/regex, + ../core/linedit, + ../core/scope + +# Dictionary Methods + +proc dget*(q: MinValue, s: MinValue): MinValue = + # Assumes q is a dictionary + for v in q.qVal: + if v.qVal[0].getString == s.getString: + return v.qVal[1] + raiseInvalid("Key '$1' not found" % s.getString) + +proc dhas*(q: MinValue, s: MinValue): bool = + # Assumes q is a dictionary + for v in q.qVal: + if v.qVal[0].getString == s.getString: + return true + return false + +proc ddel*(q: var MinValue, s: MinValue): MinValue = + # Assumes q is a dictionary + var found = false + var c = -1 + for v in q.qVal: + c.inc + if v.qVal[0].getString == s.getString: + found = true + break + if found: + q.qVal.delete(c) + return q + +proc dset*(i: In, q: var MinValue, s: MinValue, m: MinValue): MinValue {.discardable.}= + # Assumes q is a dictionary + var found = false + var c = -1 + for v in q.qVal: + c.inc + if v.qVal[0].getString == s.getString: + found = true + break + if found: + q.qVal.delete(c) + q.qVal.insert(@[s.getString.newSym, m].newVal(i.scope), c) + return q + +proc keys*(i: In, q: MinValue): MinValue = + # Assumes q is a dictionary + result = newSeq[MinValue](0).newVal(i.scope) + for v in q.qVal: + result.qVal.add v.qVal[0] + +proc values*(i: In, q: MinValue): MinValue = + # Assumes q is a dictionary + result = newSeq[MinValue](0).newVal(i.scope) + for v in q.qVal: + result.qVal.add v.qVal[1] + +# JSON interop + +proc `%`*(a: MinValue): JsonNode = + case a.kind: + of minBool: + return %a.boolVal + of minSymbol: + return %(";sym:$1" % [a.symVal]) + of minString: + return %a.strVal + of minInt: + return %a.intVal + of minFloat: + return %a.floatVal + of minQuotation: + if a.isDictionary: + result = newJObject() + for i in a.qVal: + result[$i.qVal[0].symVal] = %i.qVal[1] + else: + result = newJArray() + for i in a.qVal: + result.add %i + +proc fromJson*(i: In, json: JsonNode): MinValue = + case json.kind: + of JNull: + result = newSeq[MinValue](0).newVal(i.scope) + of JBool: + result = json.getBVal.newVal + of JInt: + result = json.getNum.newVal + of JFloat: + result = json.getFNum.newVal + of JString: + let s = json.getStr + if s.match("^;sym:"): + result = regex.replace(s, "^;sym:", "").newSym + else: + result = json.getStr.newVal + of JObject: + var res = newSeq[MinValue](0) + for key, value in json.pairs: + res.add @[key.newSym, i.fromJson(value)].newVal(i.scope) + return res.newVal(i.scope) + of JArray: + var res = newSeq[MinValue](0) + for value in json.items: + res.add i.fromJson(value) + return res.newVal(i.scope) + +proc lang_module*(i: In) = + i.scope + .symbol("exit") do (i: In): + termRestore() + quit(0) + + .symbol("symbols") do (i: In): + var q = newSeq[MinValue](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(i.scope) + + .symbol("sigils") do (i: In): + var q = newSeq[MinValue](0) + var scope = i.scope + while not scope.isNil: + for s in scope.sigils.keys: + q.add s.newVal + scope = scope.parent + i.push q.newVal(i.scope) + + .symbol("from-json") do (i: In): + var s: MinValue + i.reqString s + i.push i.fromJson(s.getString.parseJson) + + .symbol("to-json") do (i: In): + var q: MinValue + i.reqQuotation q + i.push(($(%q)).newVal) + + .symbol("debug?") do (i: In): + i.push i.debugging.newVal + + .symbol("debug") do (i: In): + i.debugging = not i.debugging + echo "Debugging: $1" % [$i.debugging] + + # Language constructs + + .symbol("define") do (i: In): + var sym: MinValue + i.reqStringLike sym + var q1 = i.pop # existing (auto-quoted) + var symbol: string + if not q1.isQuotation: + q1 = @[q1].newVal(i.scope) + symbol = sym.getString + if not symbol.match "^[a-zA-Z0-9_][a-zA-Z0-9/!?+*._-]*$": + raiseInvalid("Symbol identifier '$1' contains invalid characters." % symbol) + i.debug "[define] (scope: $1) $2 = $3" % [i.scope.name, symbol, $q1] + if i.scope.symbols.hasKey(symbol) and i.scope.symbols[symbol].sealed: + raiseUndefined("Attempting to redefine sealed symbol '$1' on scope '$2'" % [symbol, i.scope.name]) + #i.newScope("$1#$2" % [symbol, $genOid()], q1) + i.scope.symbols[symbol] = MinOperator(kind: minValOp, val: q1, sealed: false) + + .symbol("bind") do (i: In): + var sym: MinValue + i.reqStringLike sym + var q1 = i.pop # existing (auto-quoted) + var symbol: string + if not q1.isQuotation: + q1 = @[q1].newVal(i.scope) + symbol = sym.getString + i.debug "[bind] " & symbol & " = " & $q1 + let res = i.scope.setSymbol(symbol, MinOperator(kind: minValOp, val: q1)) + if not res: + raiseUndefined("Attempting to bind undefined symbol: " & symbol) + + .symbol("delete") do (i: In): + var sym: MinValue + i.reqStringLike sym + let res = i.scope.delSymbol(sym.getString) + if not res: + raiseUndefined("Attempting to delete undefined symbol: " & sym.getString) + + #.symbol("scope") do (i: In): + # var code: MinValue + # i.reqQuotation code + # code.filename = i.filename + # i.unquote("<scope>", code) + # i.push @[code].newVal(i.scope) + + .symbol("current-scope") do (i: In): + var code: MinValue + i.reqQuotation code + i.push code.scope.fullname.newVal + + .symbol("module") do (i: In): + var code, name: MinValue + i.reqStringLike name + i.reqQuotation code + code.filename = i.filename + i.unquote("<module>", code) + i.scope.symbols[name.getString] = MinOperator(kind: minValOp, val: @[code].newVal(i.scope)) + + #.symbol("scope?") do (i: In): + # var q: MinValue + # i.reqQuotation q + # if not q.scope.isNil: + # i.push true.newVal + # else: + # i.push false.newVal + + .symbol("import") do (i: In): + var mdl, rawName: MinValue + var name: string + i.reqStringLike rawName + name = rawName.getString + i.apply(i.scope.getSymbol(name)) + i.reqQuotation mdl + # TODO Remove echos + #echo name, " - ", mdl.scope.symbols.len + for sym, val in mdl.scope.symbols.pairs: + i.debug "[import] $1:$2" % [i.scope.name, sym] + #echo "[import] $1:$2" % [i.scope.name, sym] + i.scope.symbols[sym] = val + + #.symbol("sigil") do (i: In): + # var q1, q2: MinValue + # i.reqTwoQuotations q1, q2 + # if q1.qVal.len == 1 and q1.qVal[0].kind == minSymbol: + # var symbol = q1.qVal[0].symVal + # if symbol.len == 1: + # if i.scope.hasSigil(symbol): + # raiseInvalid("Sigil '$1' already exists" % [symbol]) + # i.scope.sigils[symbol] = MinOperator(kind: minValOp, val: q2) + # else: + # raiseInvalid("A sigil can only have one character") + # else: + # raiseInvalid("The top quotation must contain only one symbol value") + + .symbol("eval") do (i: In): + var s: MinValue + i.reqString s + i.eval s.strVal + + .symbol("load") do (i: In): + var s: MinValue + i.reqStringLike s + var file = s.getString + if not file.endsWith(".min"): + file = file & ".min" + file = i.pwd.joinPath(file) + if not file.fileExists: + raiseInvalid("File '$1' does not exists." % file) + i.load file + + .symbol("with") do (i: In): + var qscope, qprog: MinValue + i.reqTwoQuotations qscope, qprog + if qscope.qVal.len > 0: + # System modules are empty quotes and don't need to be unquoted + i.unquote("<with-scope>", qscope) + let scope = i.scope + i.scope = qscope.scope + for v in qprog.qVal: + i.push v + i.scope = scope + + .symbol("publish") do (i: In): + var qscope, str: MinValue + i.reqQuotationAndStringLike qscope, str + let sym = str.getString + if qscope.scope.symbols.hasKey(sym) and qscope.scope.symbols[sym].sealed: + raiseUndefined("Attempting to redefine sealed symbol '$1' on scope '$2'" % [sym, qscope.scope.name]) + qscope.scope.symbols[sym] = i.scope.getSymbol(sym) + + .symbol("source") do (i: In): + var s: MinValue + i.reqStringLike s + let str = s.getString + let sym = i.scope.getSymbol(str) + if sym.kind == minValOp: + i.push sym.val + else: + raiseInvalid("No source available for native symbol '$1'." % str) + + .symbol("call") do (i: In): + var symbol, q: MinValue + i.reqStringLike symbol + i.reqQuotation q + let s = symbol.getString + let origScope = i.scope + i.scope = q.scope + let sym = i.scope.getSymbol(s) + i.apply(sym) + i.scope = origScope + + .symbol("inspect") do (i: In): + var scope: MinValue + i.reqQuotation scope + var symbols = newSeq[MinValue](0) + if scope.scope.isNil: + i.push symbols.newVal(i.scope) + else: + for s in scope.scope.symbols.keys: + symbols.add s.newVal + i.push symbols.newVal(i.scope) + + .symbol("raise") do (i: In): + var err: MinValue + i.reqDictionary err + if err.dhas("error".newSym) and err.dhas("message".newSym): + raiseRuntime("($1) $2" % [err.dget("error".newVal).getString, err.dget("message".newVal).getString], err.qVal) + else: + raiseInvalid("Invalid error dictionary") + + .symbol("format-error") do (i: In): + var err: MinValue + i.reqDictionary err + if err.dhas("error".newSym) and err.dhas("message".newSym): + var msg: string + var list = newSeq[MinValue]() + list.add err.dget("message".newVal) + if err.qVal.contains("symbol".newVal): + list.add err.dget("symbol".newVal) + if err.qVal.contains("filename".newVal): + list.add err.dget("filename".newVal) + if err.qVal.contains("line".newVal): + list.add err.dget("line".newVal) + if err.qVal.contains("column".newVal): + list.add err.dget("column".newVal) + if list.len <= 1: + msg = "(!) $1" % $$list[0] + else: + msg = "(!) $3($4,$5) `$2`: $1" % [$$list[0], $$list[1], $$list[2], $$list[3], $$list[4]] + i.push msg.newVal + else: + raiseInvalid("Invalid error dictionary") + + .symbol("try") do (i: In): + var prog: MinValue + i.reqQuotation prog + if prog.qVal.len == 0: + raiseInvalid("Quotation must contain at least one element") + var code = prog.qVal[0] + 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 (hasCatch and not catch.isQuotation) or (hasFinally and not final.isQuotation): + raiseInvalid("Quotation must contain at one quotation") + try: + i.unquote("<try-code>", code) + except MinRuntimeError: + if not hasCatch: + return + let e = (MinRuntimeError)getCurrentException() + i.push e.qVal.newVal(i.scope) + i.unquote("<try-catch>", catch) + except: + if not hasCatch: + return + let e = getCurrentException() + var res = newSeq[MinValue](0) + let err = regex.replace($e.name, ":.+$", "") + res.add @["error".newSym, err.newVal].newVal(i.scope) + res.add @["message".newSym, e.msg.newVal].newVal(i.scope) + res.add @["symbol".newSym, i.currSym].newVal(i.scope) + res.add @["filename".newSym, i.currSym.filename.newVal].newVal(i.scope) + res.add @["line".newSym, i.currSym.line.newVal].newVal(i.scope) + res.add @["column".newSym, i.currSym.column.newVal].newVal(i.scope) + i.push res.newVal(i.scope) + i.unquote("<try-catch>", catch) + finally: + if hasFinally: + i.unquote("<try-finally>", final) + + # Operations on the whole stack + + .symbol("id") do (i: In): + discard + + .symbol("pop") do (i: In): + if i.stack.len < 1: + raiseEmptyStack() + discard i.pop + + .symbol("dup") do (i: In): + i.push i.peek + + .symbol("dip") do (i: In): + var q: MinValue + i.reqQuotation q + let v = i.pop + i.unquote("<dip>", q) + i.push v + + .symbol("swap") do (i: In): + if i.stack.len < 2: + raiseEmptyStack() + let a = i.pop + let b = i.pop + i.push a + i.push b + + .symbol("sip") do (i: In): + var a, b: MinValue + i.reqTwoQuotations a, b + i.push b + i.unquote("<sip>", a) + i.push b + + .symbol("clear-stack") do (i: In): + while i.stack.len > 0: + discard i.pop + + .symbol("dump-stack") do (i: In): + echo i.dump + + .symbol("get-stack") do (i: In): + i.push i.stack.newVal(i.scope) + + .symbol("set-stack") do (i: In): + var q: MinValue + i.reqQuotation q + i.stack = q.qVal + + # Operations on quotations or strings + + .symbol("concat") do (i: In): + var q1, q2: MinValue + i.reqTwoQuotationsOrStrings q1, q2 + if q1.isString and q2.isString: + let s = q2.strVal & q1.strVal + i.push newVal(s) + else: + let q = q2.qVal & q1.qVal + i.push q.newVal(i.scope) + + .symbol("first") do (i: In): + var q: MinValue + i.reqStringOrQuotation q + if q.isQuotation: + if q.qVal.len == 0: + raiseOutOfBounds("Quotation is empty") + i.push q.qVal[0] + elif q.isString: + if q.strVal.len == 0: + raiseOutOfBounds("String is empty") + i.push newVal($q.strVal[0]) + + .symbol("rest") do (i: In): + var q: MinValue + i.reqStringOrQuotation q + if q.isQuotation: + if q.qVal.len == 0: + raiseOutOfBounds("Quotation is empty") + i.push q.qVal[1..q.qVal.len-1].newVal(i.scope) + elif q.isString: + if q.strVal.len == 0: + raiseOutOfBounds("String is empty") + i.push newVal(q.strVal[1..q.strVal.len-1]) + + .symbol("quote") do (i: In): + let a = i.pop + i.push @[a].newVal(i.scope) + + .symbol("unquote") do (i: In): + var q: MinValue + i.reqQuotation q + i.unquote("<unquote>", q) + + .symbol("append") do (i: In): + var q: MinValue + i.reqQuotation q + let v = i.pop + q.qVal.add v + i.push q + + .symbol("cons") do (i: In): + var q: MinValue + i.reqQuotation q + let v = i.pop + q.qVal = @[v] & q.qVal + i.push q + + .symbol("at") do (i: In): + var index, q: MinValue + i.reqIntAndQuotation index, q + if q.qVal.len-1 < index.intVal: + raiseOutOfBounds("Insufficient items in quotation") + i.push q.qVal[index.intVal.int] + + .symbol("size") do (i: In): + var q: MinValue + i.reqStringOrQuotation q + if q.isQuotation: + i.push q.qVal.len.newVal + elif q.isString: + i.push q.strVal.len.newVal + + .symbol("contains") do (i: In): + let v = i.pop + var q: MinValue + i.reqQuotation q + i.push q.qVal.contains(v).newVal + + .symbol("map") do (i: In): + var prog, list: MinValue + i.reqTwoQuotations prog, list + #i.push newVal(newSeq[MinValue](0)) + var res = newSeq[MinValue](0) + for litem in list.qVal: + i.push litem + i.unquote("<map-quotation>", prog) + res.add i.pop + i.push res.newVal(i.scope) + + .symbol("foreach") do (i: In): + var prog, list: MinValue + i.reqTwoQuotations prog, list + for litem in list.qVal: + i.push litem + i.unquote("<foreach-quotation>", prog) + + .symbol("times") do (i: In): + var t, prog: MinValue + i.reqIntAndQuotation t, prog + if t.intVal < 1: + raiseInvalid("A non-zero natural number is required") + for c in 1..t.intVal: + i.unquote("<times-quotation>", prog) + + .symbol("ifte") do (i: In): + var fpath, tpath, check: MinValue + i.reqThreeQuotations fpath, tpath, check + var stack = i.stack + i.unquote("<ifte-check>", 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.unquote("<ifte-true>", tpath) + else: + i.unquote("<ifte-false>", fpath) + + # 4 ( + # ((> 3) ("Greater than 3" put!)) + # ((< 3) ("Smaller than 3" put!)) + # ('true ("Exactly 3" put!)) + # ) case + .symbol("case") do (i: In): + var cases: MinValue + i.reqQuotation cases + let last = cases.qVal.len-1 + if last == 0: + raiseInvalid("Empty case operator") + var k = 0 + let stack = i.stack + for c in cases.qVal: + i.stack = stack + if not c.isQuotation: + raiseInvalid("A quotation of quotations is required") + k.inc + if c.qVal.len != 2 or not c.qVal[0].isQuotation or not c.qVal[1].isQuotation: + raiseInvalid("Inner quotations in case operator must contain two quotations") + var q = c.qVal[0] + i.unquote("<case-$1-check>" % $k, q) + let res = i.pop + if not res.isBool(): + raiseInvalid("Result of case #$1 is not a boolean value" % $k) + if res.boolVal == true: + var t = c.qVal[1] + i.unquote("<case-$1-true>" % $k, t) + + + .symbol("while") do (i: In): + var d, b: MinValue + i.reqTwoQuotations d, b + i.push b.qVal + i.unquote("<while-check>", b) + var check = i.pop + while check.isBool and check.boolVal == true: + i.unquote("<while-quotation>", d) + i.unquote("<while-check>", b) + check = i.pop + + .symbol("filter") do (i: In): + var filter, list: MinValue + i.reqTwoQuotations filter, list + var res = newSeq[MinValue](0) + for e in list.qVal: + i.push e + i.unquote("<filter-check>", filter) + var check = i.pop + if check.isBool and check.boolVal == true: + res.add e + i.push res.newVal(i.scope) + + .symbol("sort") do (i: In): + var cmp, list: MinValue + i.reqTwoQuotations cmp, list + var i2 = i + var minCmp = proc(a, b: MinValue): int {.closure.}= + i2.push a + i2.push b + i2.unquote("<sort-cmp>", cmp) + let r = i2.pop + if r.isBool: + if r.boolVal == true: + return 1 + else: + return -1 + else: + raiseInvalid("Predicate quotation must return a boolean value") + var qList = list.qVal + sort[MinValue](qList, minCmp) + i.push qList.newVal(i.scope) + + .symbol("linrec") do (i: In): + var r2, r1, t, p: MinValue + i.reqFourQuotations r2, r1, t, p + proc linrec(i: In, p, t, r1, r2: var MinValue) = + i.unquote("<linrec-predicate>", p) + var check = i.pop + if check.isBool and check.boolVal == true: + i.unquote("<linrec-true>", t) + else: + i.unquote("<linrec-r1>", r1) + i.linrec(p, t, r1, r2) + i.unquote("<linrec-r2>", r2) + i.linrec(p, t, r1, r2) + + .symbol("dhas?") do (i: In): + var d, k: MinValue + i.reqStringLike k + i.reqDictionary d + i.push d.dhas(k).newVal + + .symbol("dget") do (i: In): + var d, k: MinValue + i.reqStringLike k + i.reqDictionary d + i.push d.dget(k) + + .symbol("dset") do (i: In): + var d, k: MinValue + let m = i.pop + i.reqStringLike k + i.reqDictionary d + i.push i.dset(d, k, m) + + .symbol("ddel") do (i: In): + var d, k: MinValue + i.reqStringLike k + i.reqDictionary d + i.push d.ddel(k) + + .symbol("dprint") do (i: In): + var d: MinValue + i.reqDictionary d + for v in d.qVal: + echo "$1: $2" % [$v.qVal[0], $v.qVal[1]] + #i.push d + + .symbol("keys") do (i: In): + var d: MinValue + i.reqDictionary d + #i.push d + i.push i.keys(d) + + .symbol("values") do (i: In): + var d: MinValue + i.reqDictionary d + #i.push d + i.push i.values(d) + + .symbol("interpolate") do (i: In): + var s, q: MinValue + i.reqQuotationAndString q, s + var strings = newSeq[string](0) + for el in q.qVal: + if el.isSymbol: + i.push el + strings.add $$i.pop + else: + strings.add $$el + let res = s.strVal % strings + i.push res.newVal + + .symbol("version") do (i: In): + i.push version.newVal + + # Save/load symbols + + .symbol("save-symbol") do (i: In): + var s:MinValue + i.reqStringLike s + let sym = s.getString + let op = i.scope.getSymbol(sym) + if op.kind == minProcOp: + raiseInvalid("Symbol '$1' cannot be serialized." % sym) + let json = MINIMSYMBOLS.readFile.parseJson + json[sym] = %op.val + MINIMSYMBOLS.writeFile(json.pretty) + + .symbol("load-symbol") do (i: In): + var s:MinValue + i.reqStringLike s + let sym = s.getString + let json = MINIMSYMBOLS.readFile.parseJson + if not json.hasKey(sym): + raiseUndefined("Symbol '$1' not found." % sym) + let val = i.fromJson(json[sym]) + i.scope.symbols[sym] = MinOperator(kind: minValOp, val: val) + + .symbol("stored-symbols") do (i: In): + var q = newSeq[MinValue](0) + let json = MINIMSYMBOLS.readFile.parseJson + for k,v in json.pairs: + q.add k.newVal + i.push q.newVal(i.scope) + + .symbol("remove-symbol") do (i: In): + var s:MinValue + i.reqStringLike s + let sym = s.getString + var json = MINIMSYMBOLS.readFile.parseJson + if not json.hasKey(sym): + raiseUndefined("Symbol '$1' not found." % sym) + json.delete(sym) + MINIMSYMBOLS.writeFile(json.pretty) + + .symbol("seal") do (i: In): + var sym: MinValue + i.reqStringLike sym + var s = i.scope.getSymbol(sym.getString) + s.sealed = true + i.scope.setSymbol(sym.getString, s) + + # Sigils + + .sigil("'") do (i: In): + var s: MinValue + i.reqString s + i.push(@[s.strVal.newSym].newVal(i.scope)) + + .sigil(":") do (i: In): + i.push("define".newSym) + + .sigil("~") do (i: In): + i.push("delete".newSym) + + .sigil("$") do (i: In): + i.push("getenv".newSym) + + .sigil("!") do (i: In): + i.push("system".newSym) + + .sigil("&") do (i: In): + i.push("run".newSym) + + .sigil("@") do (i: In): + i.push("bind".newSym) + + .sigil("=") do (i: In): + i.push("module".newSym) + + .sigil("^") do (i: In): + i.push("call".newSym) + + .sigil("/") do (i: In): + i.push("dget".newSym) + + .sigil(">") do (i: In): + i.push("save-symbol".newSym) + + .sigil("<") do (i: In): + i.push("load-symbol".newSym) + + .sigil("*") do (i: In): + i.push("seal".newSym) + + .symbol(":") do (i: In): + i.push("define".newSym) + + .symbol("@") do (i: In): + i.push("bind".newSym) + + .symbol("!") do (i: In): + i.push("system".newSym) + + .symbol("&") do (i: In): + i.push("run".newSym) + + .symbol("$") do (i: In): + i.push("getenv".newSym) + + .symbol("^") do (i: In): + i.push("call".newSym) + + .symbol("%") do (i: In): + i.push("interpolate".newSym) + + .symbol("'") do (i: In): + i.push("quote".newSym) + + .symbol("->") do (i: In): + i.push("unquote".newSym) + + #.symbol("=>") do (i: In): + # i.push("scope".newSym) + + .symbol("=~") do (i: In): + i.push("regex".newSym) + + .finalize()
M lib/min_logic.nimlib/min_logic.nim

@@ -1,149 +1,149 @@

-import - tables -import - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils - -# Comparison operators - - -proc logic_module*(i: In)= - i.define("logic") - - .symbol(">") do (i: In): - var n2, n1: MinValue - 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) - - .symbol(">=") do (i: In): - var n2, n1: MinValue - 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) - - .symbol("<") do (i: In): - var n2, n1: MinValue - 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) - - .symbol("<=") do (i: In): - var n2, n1: MinValue - 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) - - .symbol("==") do (i: In): - var n1, n2: MinValue - i.reqTwoSimilarTypesNonSymbol n2, n1 - i.push newVal(n1 == n2) - - .symbol("!=") do (i: In): - var n1, n2: MinValue - i.reqTwoSimilarTypesNonSymbol n2, n1 - i.push newVal(not (n1 == n2)) - - # Boolean Logic - - .symbol("not") do (i: In): - var b: MinValue - i.reqBool b - i.push newVal(not b.boolVal) - - .symbol("and") do (i: In): - var a, b: MinValue - i.reqTwoBools a, b - i.push newVal(a.boolVal and b.boolVal) - - .symbol("or") do (i: In): - var a, b: MinValue - i.reqTwoBools a, b - i.push newVal(a.boolVal or b.boolVal) - - .symbol("xor") do (i: In): - var a, b: MinValue - i.reqTwoBools a, b - i.push newVal(a.boolVal xor b.boolVal) - - .symbol("string?") do (i: In): - if i.peek.kind == minString: - i.push true.newVal - else: - i.push false.newVal - - .symbol("int?") do (i: In): - if i.peek.kind == minInt: - i.push true.newVal - else: - i.push false.newVal - - .symbol("float?") do (i: In): - if i.peek.kind == minFloat: - i.push true.newVal - else: - i.push false.newVal - - .symbol("number?") do (i: In): - if i.peek.kind == minFloat or i.peek.kind == minInt: - i.push true.newVal - else: - i.push false.newVal - - .symbol("bool?") do (i: In): - if i.peek.kind == minBool: - i.push true.newVal - else: - i.push false.newVal - - .symbol("quotation?") do (i: In): - if i.peek.kind == minQuotation: - i.push true.newVal - else: - i.push false.newVal - - .symbol("dictionary?") do (i: In): - if i.peek.isDictionary: - i.push true.newVal - else: - i.push false.newVal - - .finalize() +import + tables +import + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils + +# Comparison operators + + +proc logic_module*(i: In)= + i.define("logic") + + .symbol(">") do (i: In): + var n2, n1: MinValue + 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) + + .symbol(">=") do (i: In): + var n2, n1: MinValue + 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) + + .symbol("<") do (i: In): + var n2, n1: MinValue + 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) + + .symbol("<=") do (i: In): + var n2, n1: MinValue + 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) + + .symbol("==") do (i: In): + var n1, n2: MinValue + i.reqTwoSimilarTypesNonSymbol n2, n1 + i.push newVal(n1 == n2) + + .symbol("!=") do (i: In): + var n1, n2: MinValue + i.reqTwoSimilarTypesNonSymbol n2, n1 + i.push newVal(not (n1 == n2)) + + # Boolean Logic + + .symbol("not") do (i: In): + var b: MinValue + i.reqBool b + i.push newVal(not b.boolVal) + + .symbol("and") do (i: In): + var a, b: MinValue + i.reqTwoBools a, b + i.push newVal(a.boolVal and b.boolVal) + + .symbol("or") do (i: In): + var a, b: MinValue + i.reqTwoBools a, b + i.push newVal(a.boolVal or b.boolVal) + + .symbol("xor") do (i: In): + var a, b: MinValue + i.reqTwoBools a, b + i.push newVal(a.boolVal xor b.boolVal) + + .symbol("string?") do (i: In): + if i.peek.kind == minString: + i.push true.newVal + else: + i.push false.newVal + + .symbol("int?") do (i: In): + if i.peek.kind == minInt: + i.push true.newVal + else: + i.push false.newVal + + .symbol("float?") do (i: In): + if i.peek.kind == minFloat: + i.push true.newVal + else: + i.push false.newVal + + .symbol("number?") do (i: In): + if i.peek.kind == minFloat or i.peek.kind == minInt: + i.push true.newVal + else: + i.push false.newVal + + .symbol("bool?") do (i: In): + if i.peek.kind == minBool: + i.push true.newVal + else: + i.push false.newVal + + .symbol("quotation?") do (i: In): + if i.peek.kind == minQuotation: + i.push true.newVal + else: + i.push false.newVal + + .symbol("dictionary?") do (i: In): + if i.peek.isDictionary: + i.push true.newVal + else: + i.push false.newVal + + .finalize()
M lib/min_num.nimlib/min_num.nim

@@ -1,82 +1,82 @@

-import - tables, - random -import - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils - - # Arithmetic - -proc num_module*(i: In)= - - i.define("num") - - .symbol("+") do (i: In): - var a, b: MinValue - i.reqTwoNumbers a, b - 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) - - .symbol("-") do (i: In): - var a, b: MinValue - i.reqTwoNumbers a, b - 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) - - .symbol("*") do (i: In): - var a, b: MinValue - i.reqTwoNumbers a, b - 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) - - .symbol("/") do (i: In): - var a, b: MinValue - i.reqTwoNumbers a, b - 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) - - .symbol("div") do (i: In): - var a, b: MinValue - i.reqTwoInts b, a - i.push(newVal(a.intVal div b.intVal)) - - .symbol("mod") do (i: In): - var a, b: MinValue - i.reqTwoInts b, a - i.push(newVal(a.intVal mod b.intVal)) - - .finalize() +import + tables, + random +import + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils + + # Arithmetic + +proc num_module*(i: In)= + + i.define("num") + + .symbol("+") do (i: In): + var a, b: MinValue + i.reqTwoNumbers a, b + 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) + + .symbol("-") do (i: In): + var a, b: MinValue + i.reqTwoNumbers a, b + 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) + + .symbol("*") do (i: In): + var a, b: MinValue + i.reqTwoNumbers a, b + 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) + + .symbol("/") do (i: In): + var a, b: MinValue + i.reqTwoNumbers a, b + 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) + + .symbol("div") do (i: In): + var a, b: MinValue + i.reqTwoInts b, a + i.push(newVal(a.intVal div b.intVal)) + + .symbol("mod") do (i: In): + var a, b: MinValue + i.reqTwoInts b, a + i.push(newVal(a.intVal mod b.intVal)) + + .finalize()
M lib/min_str.nimlib/min_str.nim

@@ -1,142 +1,142 @@

-import - tables, - strutils, - sequtils -import - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils, - ../core/regex - -proc str_module*(i: In) = - i.define("str") - - .symbol("strip") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.strip.newVal - - .symbol("split") do (i: In): - var sep, s: MinValue - i.reqTwoStrings sep, s - var q = newSeq[MinValue](0) - for e in s.strVal.split(sep.strVal): - q.add e.newVal - i.push q.newVal - - .symbol("join") do (i: In): - var q, s: MinValue - i.reqStringLikeAndQuotation s, q - i.push q.qVal.mapIt($$it).join(s.getString).newVal - - .symbol("search") do (i: In): - var reg, str: MinValue - i.reqTwoStrings reg, str - var matches = str.strVal.search(reg.strVal) - var res = newSeq[MinValue](matches.len) - for i in 0..matches.len-1: - res[i] = matches[i].newVal - i.push res.newVal - - .symbol("match") do (i: In): - var reg, str: MinValue - i.reqTwoStrings reg, str - if str.strVal.match(reg.strVal): - i.push true.newVal - else: - i.push false.newVal - - .symbol("replace") do (i: In): - var s_replace, reg, s_find: MinValue - i.reqThreeStrings s_replace, reg, s_find - i.push regex.replace(s_find.strVal, reg.strVal, s_replace.strVal).newVal - - .symbol("regex") do (i: In): - var reg, str: MinValue - i.reqTwoStrings reg, str - let results = str.strVal =~ reg.strVal - var res = newSeq[MinValue](0) - for r in results: - res.add(r.newVal) - i.push res.newVal - - .symbol("lowercase") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.toLowerAscii.newVal - - .symbol("uppercase") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.toUpperAscii.newVal - - .symbol("capitalize") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.capitalizeAscii.newVal - - .symbol("titleize") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.split(" ").mapIt(it.capitalizeAscii).join(" ").newVal - - .symbol("repeat") do (i: In): - var s, n: MinValue - i.reqIntAndString n, s - i.push s.getString.repeat(n.intVal).newVal - - .symbol("indent") do (i: In): - var s, n: MinValue - i.reqIntAndString n, s - i.push s.getString.indent(n.intVal).newVal - - .symbol("string") do (i: In): - var s = i.pop - i.push(($$s).newVal) - - .symbol("bool") do (i: In): - var v = i.pop - let strcheck = (v.isString and (v.getString == "false" or v.getString == "")) - let intcheck = v.isInt and v.intVal == 0 - let floatcheck = v.isFloat and v.floatVal == 0 - let boolcheck = v.isBool and v.boolVal == false - let quotcheck = v.isQuotation and v.qVal.len == 0 - if strcheck or intcheck or floatcheck or boolcheck or quotcheck: - i.push false.newVal - else: - i.push true.newVal - - .symbol("int") do (i: In): - var s = i.pop - if s.isString: - i.push s.getString.parseInt.newVal - elif s.isFloat: - i.push s.floatVal.int.newVal - elif s.isInt: - i.push s - elif s.isBool: - if s.boolVal == true: - i.push 1.int.newVal - else: - i.push 0.int.newVal - else: - raiseInvalid("Cannot convert a quotation to an integer.") - - .symbol("float") do (i: In): - var s = i.pop - if s.isString: - i.push s.getString.parseFloat.newVal - elif s.isInt: - i.push s.intVal.float.newVal - elif s.isFloat: - i.push s - elif s.isBool: - if s.boolVal == true: - i.push 1.float.newVal - else: - i.push 0.float.newVal - else: - raiseInvalid("Cannot convert a quotation to float.") - - .finalize() +import + tables, + strutils, + sequtils +import + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils, + ../core/regex + +proc str_module*(i: In) = + i.define("str") + + .symbol("strip") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.strip.newVal + + .symbol("split") do (i: In): + var sep, s: MinValue + i.reqTwoStrings sep, s + var q = newSeq[MinValue](0) + for e in s.strVal.split(sep.strVal): + q.add e.newVal + i.push q.newVal(i.scope) + + .symbol("join") do (i: In): + var q, s: MinValue + i.reqStringLikeAndQuotation s, q + i.push q.qVal.mapIt($$it).join(s.getString).newVal + + .symbol("search") do (i: In): + var reg, str: MinValue + i.reqTwoStrings reg, str + var matches = str.strVal.search(reg.strVal) + var res = newSeq[MinValue](matches.len) + for i in 0..matches.len-1: + res[i] = matches[i].newVal + i.push res.newVal(i.scope) + + .symbol("match") do (i: In): + var reg, str: MinValue + i.reqTwoStrings reg, str + if str.strVal.match(reg.strVal): + i.push true.newVal + else: + i.push false.newVal + + .symbol("replace") do (i: In): + var s_replace, reg, s_find: MinValue + i.reqThreeStrings s_replace, reg, s_find + i.push regex.replace(s_find.strVal, reg.strVal, s_replace.strVal).newVal + + .symbol("regex") do (i: In): + var reg, str: MinValue + i.reqTwoStrings reg, str + let results = str.strVal =~ reg.strVal + var res = newSeq[MinValue](0) + for r in results: + res.add(r.newVal) + i.push res.newVal(i.scope) + + .symbol("lowercase") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.toLowerAscii.newVal + + .symbol("uppercase") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.toUpperAscii.newVal + + .symbol("capitalize") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.capitalizeAscii.newVal + + .symbol("titleize") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.split(" ").mapIt(it.capitalizeAscii).join(" ").newVal + + .symbol("repeat") do (i: In): + var s, n: MinValue + i.reqIntAndString n, s + i.push s.getString.repeat(n.intVal).newVal + + .symbol("indent") do (i: In): + var s, n: MinValue + i.reqIntAndString n, s + i.push s.getString.indent(n.intVal).newVal + + .symbol("string") do (i: In): + var s = i.pop + i.push(($$s).newVal) + + .symbol("bool") do (i: In): + var v = i.pop + let strcheck = (v.isString and (v.getString == "false" or v.getString == "")) + let intcheck = v.isInt and v.intVal == 0 + let floatcheck = v.isFloat and v.floatVal == 0 + let boolcheck = v.isBool and v.boolVal == false + let quotcheck = v.isQuotation and v.qVal.len == 0 + if strcheck or intcheck or floatcheck or boolcheck or quotcheck: + i.push false.newVal + else: + i.push true.newVal + + .symbol("int") do (i: In): + var s = i.pop + if s.isString: + i.push s.getString.parseInt.newVal + elif s.isFloat: + i.push s.floatVal.int.newVal + elif s.isInt: + i.push s + elif s.isBool: + if s.boolVal == true: + i.push 1.int.newVal + else: + i.push 0.int.newVal + else: + raiseInvalid("Cannot convert a quotation to an integer.") + + .symbol("float") do (i: In): + var s = i.pop + if s.isString: + i.push s.getString.parseFloat.newVal + elif s.isInt: + i.push s.intVal.float.newVal + elif s.isFloat: + i.push s + elif s.isBool: + if s.boolVal == true: + i.push 1.float.newVal + else: + i.push 0.float.newVal + else: + raiseInvalid("Cannot convert a quotation to float.") + + .finalize()
M lib/min_sys.nimlib/min_sys.nim

@@ -1,185 +1,185 @@

-import - tables, - os, - osproc, - strutils, - sequtils -import - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils, - ../core/fileutils, - ../core/zip - -proc unix(s: string): string = - return s.replace("\\", "/") - -proc sys_module*(i: In)= - i.define("sys") - - .symbol(".") do (i: In): - i.push newVal(getCurrentDir().unix) - - .symbol("..") do (i: In): - i.push newVal(getCurrentDir().parentDir.unix) - - .symbol("cd") do (i: In): - var f: MinValue - i.reqStringLike f - f.getString.setCurrentDir - - .symbol("ls") do (i: In): - var a: MinValue - i.reqStringLike a - var list = newSeq[MinValue](0) - for i in walkDir(a.getString): - list.add newVal(i.path.unix) - i.push list.newVal - - .symbol("ls-r") do (i: In): - var a: MinValue - i.reqStringLike a - var list = newSeq[MinValue](0) - for i in walkDirRec(a.getString): - list.add newVal(i.unix) - i.push list.newVal - - .symbol("system") do (i: In): - var a: MinValue - i.reqStringLike a - i.push execShellCmd(a.getString).newVal - - .symbol("run") do (i: In): - var cmd: MinValue - i.reqStringLike cmd - let res = execCmdEx(cmd.getString) - i.push @[@["output".newSym, res.output.newVal].newVal, @["code".newSym, res.exitCode.newVal].newVal].newVal - - .symbol("getenv") do (i: In): - var a: MinValue - i.reqStringLike a - i.push a.getString.getEnv.newVal - - .symbol("putenv") do (i: In): - var key, value: MinValue - i.reqTwoStringLike key, value - key.getString.putEnv value.getString - - .symbol("env?") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.existsEnv.newVal - - .symbol("which") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.findExe.newVal - - .symbol("os") do (i: In): - i.push hostOS.newVal - - .symbol("cpu") do (i: In): - i.push hostCPU.newVal - - .symbol("file?") do (i: In): - var f: MinValue - i.reqStringLike f - i.push f.getString.fileExists.newVal - - .symbol("dir?") do (i: In): - var f: MinValue - i.reqStringLike f - i.push f.getString.dirExists.newVal - - .symbol("rm") do (i: In): - var v: MinValue - i.reqStringLike v - let f = v.getString - if f.existsFile: - f.removeFile - elif f.existsDir: - f.removeDir - else: - raiseInvalid("File '$1' does not exist." % f) - - .symbol("cp") do (i: In): - var a, b: MinValue - i.reqTwoStringLike a, b - let src = b.getString - var dest = a.getString - if src.dirExists: - copyDirWithPermissions src, dest - elif dest.dirExists: - if src.dirExists: - copyDirWithPermissions src, dest - else: - copyFileWithPermissions src, dest / src.extractFilename - else: - copyFileWithPermissions src, dest - - .symbol("mv") do (i: In): - var a, b: MinValue - i.reqTwoStringLike a, b - let src = b.getString - var dest = a.getString - if dest.dirExists: - dest = dest / src.extractFilename - moveFile src, dest - - .symbol("rmdir") do (i: In): - var f: MinValue - i.reqStringLike f - f.getString.removeDir - - .symbol("mkdir") do (i: In): - var f: MinValue - i.reqStringLike f - f.getString.createDir - - .symbol("sleep") do (i: In): - var ms: MinValue - i.reqInt ms - sleep ms.intVal.int - - .symbol("chmod") do (i: In): - var s, perms: MinValue - i.reqIntAndString perms, s - s.getString.setFilePermissions(perms.intVal.toFilePermissions) - - .symbol("symlink?") do (i: In): - var s: MinValue - i.reqStringLike s - i.push s.getString.symlinkExists.newVal - - .symbol("symlink") do (i: In): - var src, dest: MinValue - i.reqTwoStringLike dest, src - src.getString.createSymlink dest.getString - - .symbol("hardlink") do (i: In): - var src, dest: MinValue - i.reqTwoStringLike dest, src - src.getString.createHardlink dest.getString - - .symbol("filename") do (i: In): - var f: MinValue - i.reqStringLike f - i.push f.getString.extractFilename.unix.newVal - - .symbol("dirname") do (i: In): - var f: MinValue - i.reqStringLike f - i.push f.getString.parentDir.unix.newVal - - .symbol("unzip") do (i: In): - var f, dir: MinValue - i.reqTwoStringLike dir, f - unzip(f.getString, dir.getString) - - .symbol("zip") do (i: In): - var files, file: MinValue - i.reqStringLikeAndQuotation file, files - zip(files.qVal.mapIt(it.getString), file.getString) - - .finalize() +import + tables, + os, + osproc, + strutils, + sequtils +import + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils, + ../core/fileutils, + ../core/zip + +proc unix(s: string): string = + return s.replace("\\", "/") + +proc sys_module*(i: In)= + i.define("sys") + + .symbol(".") do (i: In): + i.push newVal(getCurrentDir().unix) + + .symbol("..") do (i: In): + i.push newVal(getCurrentDir().parentDir.unix) + + .symbol("cd") do (i: In): + var f: MinValue + i.reqStringLike f + f.getString.setCurrentDir + + .symbol("ls") do (i: In): + var a: MinValue + i.reqStringLike a + var list = newSeq[MinValue](0) + for i in walkDir(a.getString): + list.add newVal(i.path.unix) + i.push list.newVal(i.scope) + + .symbol("ls-r") do (i: In): + var a: MinValue + i.reqStringLike a + var list = newSeq[MinValue](0) + for i in walkDirRec(a.getString): + list.add newVal(i.unix) + i.push list.newVal(i.scope) + + .symbol("system") do (i: In): + var a: MinValue + i.reqStringLike a + i.push execShellCmd(a.getString).newVal + + .symbol("run") do (i: In): + var cmd: MinValue + i.reqStringLike cmd + let res = execCmdEx(cmd.getString) + i.push @[@["output".newSym, res.output.newVal].newVal(i.scope), @["code".newSym, res.exitCode.newVal].newVal(i.scope)].newVal(i.scope) + + .symbol("getenv") do (i: In): + var a: MinValue + i.reqStringLike a + i.push a.getString.getEnv.newVal + + .symbol("putenv") do (i: In): + var key, value: MinValue + i.reqTwoStringLike key, value + key.getString.putEnv value.getString + + .symbol("env?") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.existsEnv.newVal + + .symbol("which") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.findExe.newVal + + .symbol("os") do (i: In): + i.push hostOS.newVal + + .symbol("cpu") do (i: In): + i.push hostCPU.newVal + + .symbol("file?") do (i: In): + var f: MinValue + i.reqStringLike f + i.push f.getString.fileExists.newVal + + .symbol("dir?") do (i: In): + var f: MinValue + i.reqStringLike f + i.push f.getString.dirExists.newVal + + .symbol("rm") do (i: In): + var v: MinValue + i.reqStringLike v + let f = v.getString + if f.existsFile: + f.removeFile + elif f.existsDir: + f.removeDir + else: + raiseInvalid("File '$1' does not exist." % f) + + .symbol("cp") do (i: In): + var a, b: MinValue + i.reqTwoStringLike a, b + let src = b.getString + var dest = a.getString + if src.dirExists: + copyDirWithPermissions src, dest + elif dest.dirExists: + if src.dirExists: + copyDirWithPermissions src, dest + else: + copyFileWithPermissions src, dest / src.extractFilename + else: + copyFileWithPermissions src, dest + + .symbol("mv") do (i: In): + var a, b: MinValue + i.reqTwoStringLike a, b + let src = b.getString + var dest = a.getString + if dest.dirExists: + dest = dest / src.extractFilename + moveFile src, dest + + .symbol("rmdir") do (i: In): + var f: MinValue + i.reqStringLike f + f.getString.removeDir + + .symbol("mkdir") do (i: In): + var f: MinValue + i.reqStringLike f + f.getString.createDir + + .symbol("sleep") do (i: In): + var ms: MinValue + i.reqInt ms + sleep ms.intVal.int + + .symbol("chmod") do (i: In): + var s, perms: MinValue + i.reqIntAndString perms, s + s.getString.setFilePermissions(perms.intVal.toFilePermissions) + + .symbol("symlink?") do (i: In): + var s: MinValue + i.reqStringLike s + i.push s.getString.symlinkExists.newVal + + .symbol("symlink") do (i: In): + var src, dest: MinValue + i.reqTwoStringLike dest, src + src.getString.createSymlink dest.getString + + .symbol("hardlink") do (i: In): + var src, dest: MinValue + i.reqTwoStringLike dest, src + src.getString.createHardlink dest.getString + + .symbol("filename") do (i: In): + var f: MinValue + i.reqStringLike f + i.push f.getString.extractFilename.unix.newVal + + .symbol("dirname") do (i: In): + var f: MinValue + i.reqStringLike f + i.push f.getString.parentDir.unix.newVal + + .symbol("unzip") do (i: In): + var f, dir: MinValue + i.reqTwoStringLike dir, f + unzip(f.getString, dir.getString) + + .symbol("zip") do (i: In): + var files, file: MinValue + i.reqStringLikeAndQuotation file, files + zip(files.qVal.mapIt(it.getString), file.getString) + + .finalize()
M lib/min_time.nimlib/min_time.nim

@@ -1,64 +1,64 @@

-import - times, - tables -import - ../core/parser, - ../core/value, - ../core/interpreter, - ../core/utils - -# Time - - -proc time_module*(i: In)= - i.define("time") - - .symbol("timestamp") do (i: In): - i.push getTime().int.newVal - - .symbol("now") do (i: In): - i.push epochTime().newVal - - .symbol("timeinfo") do (i: In): - var t: MinValue - i.reqNumber t - var time: Time - if t.kind == minInt: - time = t.intVal.fromSeconds - else: - time = t.floatVal.fromSeconds - let tinfo = time.getLocalTime - var info = newSeq[MinValue](0).newVal - info.qVal.add @["year".newSym, tinfo.year.newVal].newVal - info.qVal.add @["month".newSym, (tinfo.month.int+1).newVal].newVal - info.qVal.add @["day".newSym, tinfo.monthday.newVal].newVal - info.qVal.add @["weekday".newSym, (tinfo.weekday.int+1).newVal].newVal - info.qVal.add @["yearday".newSym, tinfo.yearday.newVal].newVal - info.qVal.add @["hour".newSym, tinfo.hour.newVal].newVal - info.qVal.add @["minute".newSym, tinfo.minute.newVal].newVal - info.qVal.add @["second".newSym, tinfo.second.newVal].newVal - i.push info - - .symbol("datetime") do (i: In): - var t: MinValue - i.reqNumber t - var time: Time - if t.kind == minInt: - time = t.intVal.fromSeconds - else: - time = t.floatVal.fromSeconds - i.push time.getLocalTime.format("yyyy-MM-dd'T'HH:mm:ss'Z'").newVal - - .symbol("tformat") do (i: In): - var t, s: MinValue - i.reqString s - i.reqNumber t - var time: Time - if t.kind == minInt: - time = t.intVal.fromSeconds - else: - time = t.floatVal.fromSeconds - i.push time.getLocalTime.format(s.getString).newVal - - .finalize() - +import + times, + tables +import + ../core/parser, + ../core/value, + ../core/interpreter, + ../core/utils + +# Time + + +proc time_module*(i: In)= + i.define("time") + + .symbol("timestamp") do (i: In): + i.push getTime().int.newVal + + .symbol("now") do (i: In): + i.push epochTime().newVal + + .symbol("timeinfo") do (i: In): + var t: MinValue + i.reqNumber t + var time: Time + if t.kind == minInt: + time = t.intVal.fromSeconds + else: + time = t.floatVal.fromSeconds + let tinfo = time.getLocalTime + var info = newSeq[MinValue](0).newVal(i.scope) + info.qVal.add @["year".newSym, tinfo.year.newVal].newVal(i.scope) + info.qVal.add @["month".newSym, (tinfo.month.int+1).newVal].newVal(i.scope) + info.qVal.add @["day".newSym, tinfo.monthday.newVal].newVal(i.scope) + info.qVal.add @["weekday".newSym, (tinfo.weekday.int+1).newVal].newVal(i.scope) + info.qVal.add @["yearday".newSym, tinfo.yearday.newVal].newVal(i.scope) + info.qVal.add @["hour".newSym, tinfo.hour.newVal].newVal(i.scope) + info.qVal.add @["minute".newSym, tinfo.minute.newVal].newVal(i.scope) + info.qVal.add @["second".newSym, tinfo.second.newVal].newVal(i.scope) + i.push info + + .symbol("datetime") do (i: In): + var t: MinValue + i.reqNumber t + var time: Time + if t.kind == minInt: + time = t.intVal.fromSeconds + else: + time = t.floatVal.fromSeconds + i.push time.getLocalTime.format("yyyy-MM-dd'T'HH:mm:ss'Z'").newVal + + .symbol("tformat") do (i: In): + var t, s: MinValue + i.reqString s + i.reqNumber t + var time: Time + if t.kind == minInt: + time = t.intVal.fromSeconds + else: + time = t.floatVal.fromSeconds + i.push time.getLocalTime.format(s.getString).newVal + + .finalize() +
M minim.nimminim.nim

@@ -1,235 +1,236 @@

-import - streams, - critbits, - parseopt2, - strutils, - os, - json, - sequtils, - algorithm -import - core/linedit, - core/consts, - core/parser, - core/value, - core/interpreter, - core/utils, - core/zip -import - lib/min_lang, - lib/min_num, - lib/min_str, - lib/min_logic, - lib/min_time, - lib/min_io, - lib/min_sys, - lib/min_crypto, - lib/min_fs - -var REPL = false -var DEBUGGING = false -const PRELUDE* = "prelude.min".slurp.strip -let usage* = """ $1 v$2 - a tiny concatenative shell and programming language - (c) 2014-2016 Fabio Cevasco - - Usage: - minim [options] [filename] - - Arguments: - filename A $1 file to interpret (default: STDIN). - Options: - -e, --evaluate Evaluate a $1 program inline - -h, --help Print this help - -v, --version Print the program version - -i, --interactive Start $1 shell""" % [appname, version] - - -proc getExecs(): seq[string] = - var res = newSeq[string](0) - let getFiles = proc(dir: string) = - for c, s in walkDir(dir, true): - if (c == pcFile or c == pcLinkToFile) and not res.contains(s): - res.add s - getFiles(getCurrentDir()) - for dir in "PATH".getEnv.split(PathSep): - getFiles(dir) - res.sort(system.cmp) - return res - -proc getCompletions(ed: LineEditor, symbols: seq[string]): seq[string] = - var words = ed.lineText.split(" ") - var word: string - if words.len == 0: - word = ed.lineText - else: - word = words[words.len-1] - if word.startsWith("'"): - return symbols.mapIt("'" & $it) - elif word.startsWith("~"): - return symbols.mapIt("~" & $it) - if word.startsWith("@"): - return symbols.mapIt("@" & $it) - if word.startsWith(">"): - return symbols.mapIt(">" & $it) - if word.startsWith("*"): - return symbols.mapIt("*" & $it) - if word.startsWith("("): - return symbols.mapIt("(" & $it) - if word.startsWith("<"): - return toSeq(MINIMSYMBOLS.readFile.parseJson.pairs).mapIt("<" & $it[0]) - if word.startsWith("$"): - return toSeq(envPairs()).mapIt("$" & $it[0]) - if word.startsWith("!"): - return getExecs().mapIt("!" & $it) - if word.startsWith("&"): - return getExecs().mapIt("&" & $it) - if word.startsWith("\""): - var f = word[1..^1] - if f == "": - f = getCurrentDir().replace("\\", "/") - return toSeq(walkDir(f, true)).mapIt("\"$1" % it.path.replace("\\", "/")) - elif f.dirExists: - f = f.replace("\\", "/") - if f[f.len-1] != '/': - f = f & "/" - return toSeq(walkDir(f, true)).mapIt("\"$1$2" % [f, it.path.replace("\\", "/")]) - else: - var dir: string - if f.contains("/") or dir.contains("\\"): - dir = f.parentDir - let file = f.extractFileName - return toSeq(walkDir(dir, true)).filterIt(it.path.toLowerAscii.startsWith(file.toLowerAscii)).mapIt("\"$1/$2" % [dir, it.path.replace("\\", "/")]) - else: - dir = getCurrentDir() - return toSeq(walkDir(dir, true)).filterIt(it.path.toLowerAscii.startsWith(f.toLowerAscii)).mapIt("\"$1" % [it.path.replace("\\", "/")]) - return symbols - -proc stdLib*(i: In) = - i.lang_module - i.io_module - i.logic_module - i.num_module - i.str_module - i.sys_module - i.time_module - i.fs_module - i.crypto_module - i.eval PRELUDE, "<prelude>" - if not MINIMSYMBOLS.fileExists: - MINIMSYMBOLS.writeFile("{}") - if not MINIMHISTORY.fileExists: - MINIMHISTORY.writeFile("") - if not MINIMRC.fileExists: - MINIMRC.writeFile("") - i.eval MINIMRC.readFile() - -proc minimStream(s: Stream, filename: string, debugging = false) = - var i = newMinInterpreter(debugging) - i.pwd = filename.parentDir - i.stdLib() - i.open(s, filename) - discard i.parser.getToken() - try: - i.interpret() - except: - discard - i.close() - -proc minimString*(buffer: string, debugging = false) = - minimStream(newStringStream(buffer), "input", debugging) - -proc minimFile*(filename: string, debugging = false) = - var stream = newFileStream(filename, fmRead) - if stream == nil: - stderr.writeLine("Error - Cannot read from file: "& filename) - stderr.flushFile() - minimStream(stream, filename, debugging) - -proc minimFile*(file: File, filename="stdin", debugging = false) = - var stream = newFileStream(stdin) - if stream == nil: - stderr.writeLine("Error - Cannot read from "& filename) - stderr.flushFile() - minimStream(stream, filename, debugging) - -proc printResult(i: In, res: MinValue) = - if res.isNil: - return - if i.stack.len > 0: - let n = $i.stack.len - if res.isQuotation and res.qVal.len > 1: - echo "{$1} -> (" % n - for item in res.qVal: - echo " " & $item - echo " ".repeat(n.len) & " )" - else: - echo "{$1} -> $2" % [$i.stack.len, $i.stack[i.stack.len - 1]] - -proc minimRepl*(i: var MinInterpreter) = - i.stdLib() - var s = newStringStream("") - i.open(s, "") - var line: string - #echo "$1 v$2" % [appname, version] - var ed = initEditor(historyFile = MINIMHISTORY) - while true: - let symbols = toSeq(i.scope.symbols.keys) - ed.completionCallback = proc(ed: LineEditor): seq[string] = - return ed.getCompletions(symbols) - # evaluate prompt - i.apply(i.scope.getSymbol("prompt")) - var v: MinValue - i.reqString(v) - let prompt = v.getString() - line = ed.readLine(prompt) - i.parser.buf = $i.parser.buf & $line - i.parser.bufLen = i.parser.buf.len - discard i.parser.getToken() - try: - i.printResult i.interpret() - except: - discard - -proc minimRepl*(debugging = false) = - var i = newMinInterpreter(debugging) - i.minimRepl - - -### - -when isMainModule: - - var file, s: string = "" - - for kind, key, val in getopt(): - case kind: - of cmdArgument: - file = key - of cmdLongOption, cmdShortOption: - case key: - of "debug", "d": - DEBUGGING = true - of "evaluate", "e": - s = val - of "help", "h": - echo usage - quit(0) - of "version", "v": - echo version - quit(0) - of "interactive", "i": - REPL = true - else: - discard - else: - discard - - if s != "": - minimString(s, DEBUGGING) - elif file != "": - minimFile file, DEBUGGING - elif REPL: - minimRepl DEBUGGING - quit(0) - else: - minimFile stdin, "stdin", DEBUGGING +import + streams, + critbits, + parseopt2, + strutils, + os, + json, + sequtils, + algorithm +import + core/linedit, + core/consts, + core/parser, + core/value, + core/scope, + core/interpreter, + core/utils, + core/zip +import + lib/min_lang, + lib/min_num, + lib/min_str, + lib/min_logic, + lib/min_time, + lib/min_io, + lib/min_sys, + lib/min_crypto, + lib/min_fs + +var REPL = false +var DEBUGGING = false +const PRELUDE* = "prelude.min".slurp.strip +let usage* = """ $1 v$2 - a tiny concatenative shell and programming language + (c) 2014-2016 Fabio Cevasco + + Usage: + minim [options] [filename] + + Arguments: + filename A $1 file to interpret (default: STDIN). + Options: + -e, --evaluate Evaluate a $1 program inline + -h, --help Print this help + -v, --version Print the program version + -i, --interactive Start $1 shell""" % [appname, version] + + +proc getExecs(): seq[string] = + var res = newSeq[string](0) + let getFiles = proc(dir: string) = + for c, s in walkDir(dir, true): + if (c == pcFile or c == pcLinkToFile) and not res.contains(s): + res.add s + getFiles(getCurrentDir()) + for dir in "PATH".getEnv.split(PathSep): + getFiles(dir) + res.sort(system.cmp) + return res + +proc getCompletions(ed: LineEditor, symbols: seq[string]): seq[string] = + var words = ed.lineText.split(" ") + var word: string + if words.len == 0: + word = ed.lineText + else: + word = words[words.len-1] + if word.startsWith("'"): + return symbols.mapIt("'" & $it) + elif word.startsWith("~"): + return symbols.mapIt("~" & $it) + if word.startsWith("@"): + return symbols.mapIt("@" & $it) + if word.startsWith(">"): + return symbols.mapIt(">" & $it) + if word.startsWith("*"): + return symbols.mapIt("*" & $it) + if word.startsWith("("): + return symbols.mapIt("(" & $it) + if word.startsWith("<"): + return toSeq(MINIMSYMBOLS.readFile.parseJson.pairs).mapIt("<" & $it[0]) + if word.startsWith("$"): + return toSeq(envPairs()).mapIt("$" & $it[0]) + if word.startsWith("!"): + return getExecs().mapIt("!" & $it) + if word.startsWith("&"): + return getExecs().mapIt("&" & $it) + if word.startsWith("\""): + var f = word[1..^1] + if f == "": + f = getCurrentDir().replace("\\", "/") + return toSeq(walkDir(f, true)).mapIt("\"$1" % it.path.replace("\\", "/")) + elif f.dirExists: + f = f.replace("\\", "/") + if f[f.len-1] != '/': + f = f & "/" + return toSeq(walkDir(f, true)).mapIt("\"$1$2" % [f, it.path.replace("\\", "/")]) + else: + var dir: string + if f.contains("/") or dir.contains("\\"): + dir = f.parentDir + let file = f.extractFileName + return toSeq(walkDir(dir, true)).filterIt(it.path.toLowerAscii.startsWith(file.toLowerAscii)).mapIt("\"$1/$2" % [dir, it.path.replace("\\", "/")]) + else: + dir = getCurrentDir() + return toSeq(walkDir(dir, true)).filterIt(it.path.toLowerAscii.startsWith(f.toLowerAscii)).mapIt("\"$1" % [it.path.replace("\\", "/")]) + return symbols + +proc stdLib*(i: In) = + i.lang_module + i.io_module + i.logic_module + i.num_module + i.str_module + i.sys_module + i.time_module + i.fs_module + i.crypto_module + i.eval PRELUDE, "<prelude>" + if not MINIMSYMBOLS.fileExists: + MINIMSYMBOLS.writeFile("{}") + if not MINIMHISTORY.fileExists: + MINIMHISTORY.writeFile("") + if not MINIMRC.fileExists: + MINIMRC.writeFile("") + i.eval MINIMRC.readFile() + +proc minimStream(s: Stream, filename: string, debugging = false) = + var i = newMinInterpreter(debugging) + i.pwd = filename.parentDir + i.stdLib() + i.open(s, filename) + discard i.parser.getToken() + try: + i.interpret() + except: + discard + i.close() + +proc minimString*(buffer: string, debugging = false) = + minimStream(newStringStream(buffer), "input", debugging) + +proc minimFile*(filename: string, debugging = false) = + var stream = newFileStream(filename, fmRead) + if stream == nil: + stderr.writeLine("Error - Cannot read from file: "& filename) + stderr.flushFile() + minimStream(stream, filename, debugging) + +proc minimFile*(file: File, filename="stdin", debugging = false) = + var stream = newFileStream(stdin) + if stream == nil: + stderr.writeLine("Error - Cannot read from "& filename) + stderr.flushFile() + minimStream(stream, filename, debugging) + +proc printResult(i: In, res: MinValue) = + if res.isNil: + return + if i.stack.len > 0: + let n = $i.stack.len + if res.isQuotation and res.qVal.len > 1: + echo "{$1} -> (" % n + for item in res.qVal: + echo " " & $item + echo " ".repeat(n.len) & " )" + else: + echo "{$1} -> $2" % [$i.stack.len, $i.stack[i.stack.len - 1]] + +proc minimRepl*(i: var MinInterpreter) = + i.stdLib() + var s = newStringStream("") + i.open(s, "") + var line: string + #echo "$1 v$2" % [appname, version] + var ed = initEditor(historyFile = MINIMHISTORY) + while true: + let symbols = toSeq(i.scope.symbols.keys) + ed.completionCallback = proc(ed: LineEditor): seq[string] = + return ed.getCompletions(symbols) + # evaluate prompt + i.apply(i.scope.getSymbol("prompt")) + var v: MinValue + i.reqString(v) + let prompt = v.getString() + line = ed.readLine(prompt) + i.parser.buf = $i.parser.buf & $line + i.parser.bufLen = i.parser.buf.len + discard i.parser.getToken() + try: + i.printResult i.interpret() + except: + discard + +proc minimRepl*(debugging = false) = + var i = newMinInterpreter(debugging) + i.minimRepl + + +### + +when isMainModule: + + var file, s: string = "" + + for kind, key, val in getopt(): + case kind: + of cmdArgument: + file = key + of cmdLongOption, cmdShortOption: + case key: + of "debug", "d": + DEBUGGING = true + of "evaluate", "e": + s = val + of "help", "h": + echo usage + quit(0) + of "version", "v": + echo version + quit(0) + of "interactive", "i": + REPL = true + else: + discard + else: + discard + + if s != "": + minimString(s, DEBUGGING) + elif file != "": + minimFile file, DEBUGGING + elif REPL: + minimRepl DEBUGGING + quit(0) + else: + minimFile stdin, "stdin", DEBUGGING
M tests/lang.mintests/lang.min

@@ -39,10 +39,6 @@

(symbols "mymath" contains) assert - (mymath scope?) assert - - (() scope? false ==) assert - (() inspect () ==) assert ('mymath import symbols "myplus" contains) assert

@@ -50,12 +46,12 @@

('mymath import 2 3 myplus 5 ==) assert ; Extend an existing scope - ( - ('mymath import - (-) :myminus) => @mymath - 5 2 mymath ^myminus 3 ==) assert - - (mymath inspect ("myminus" "myplus") ==) assert + ;( + ; ('mymath import + ; (-) :myminus) => @mymath + ; 5 2 mymath ^myminus 3 ==) assert + ; + ;(mymath inspect ("myminus" "myplus") ==) assert ;((":mysigil" concat) ', sigil ,test "test:mysigil" ==) assert