all repos — min @ 8b12c40913b7ce7ab4c1ec7aa2512817ac0d53d3

A small but practical concatenative programming language.

Started implementing bytecode interpreter.
h3rald h3rald@h3rald.com
Fri, 04 Aug 2023 15:48:49 +0200
commit

8b12c40913b7ce7ab4c1ec7aa2512817ac0d53d3

parent

8f1844499f38d9a5124a5441919a456f9c681340

6 files changed, 359 insertions(+), 193 deletions(-)

jump to
M min.nimmin.nim

@@ -1,7 +1,7 @@

-import - streams, - strutils, - sequtils, +import + streams, + strutils, + sequtils, json, os, algorithm,

@@ -10,16 +10,17 @@ logging,

minpkg/packages/niftylogger, minpkg/core/baseutils, minpkg/core/env, - minpkg/core/parser, - minpkg/core/value, + minpkg/core/parser, + minpkg/core/value, minpkg/core/scope, - minpkg/core/interpreter, - minpkg/core/utils -import - minpkg/lib/min_lang, - minpkg/lib/min_stack, - minpkg/lib/min_seq, - minpkg/lib/min_dict, + minpkg/core/interpreter, + minpkg/core/utils, + minpkg/core/vm +import + minpkg/lib/min_lang, + minpkg/lib/min_stack, + minpkg/lib/min_seq, + minpkg/lib/min_dict, minpkg/lib/min_num, minpkg/lib/min_str, minpkg/lib/min_logic,

@@ -34,7 +35,7 @@ minpkg/lib/min_net,

minpkg/lib/min_crypto, minpkg/lib/min_math -export +export env, parser, interpreter,

@@ -47,7 +48,7 @@

const PRELUDE* = "prelude.min".slurp.strip var NIMOPTIONS* = "" var MINMODULES* = newSeq[string](0) -var customPrelude {.threadvar.} : string +var customPrelude {.threadvar.}: string customPrelude = "" if logging.getHandlers().len == 0:

@@ -107,7 +108,7 @@ return getExecs().mapIt("&" & $it)

if word.startsWith("\""): var f = word[1..^1] if f == "": - f = getCurrentDir().replace("\\", "/") + f = getCurrentDir().replace("\\", "/") return toSeq(walkDir(f, true)).mapIt("\"$1" % it.path.replace("\\", "/")) elif f.dirExists: f = f.replace("\\", "/")

@@ -119,10 +120,14 @@ 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("\\", "/")]) + 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 toSeq(walkDir(dir, true)).filterIt( + it.path.toLowerAscii.startsWith(f.toLowerAscii)).mapIt("\"$1" % [ + it.path.replace("\\", "/")]) return symbols

@@ -166,31 +171,32 @@

proc interpret*(i: In, s: Stream) = i.stdLib() i.open(s, i.filename) - discard i.parser.getToken() + discard i.parser.getToken() try: i.interpret() except CatchableError: discard i.close() -proc interpret*(i: In, s: string): MinValue = +proc interpret*(i: In, s: string): MinValue = i.open(newStringStream(s), i.filename) - discard i.parser.getToken() + discard i.parser.getToken() try: result = i.interpret() except CatchableError: discard i.close() - -proc minFile*(filename: string, op = "interpret", main = true): seq[string] {.discardable.} -proc compile*(i: In, s: Stream, main = true): seq[string] = +proc minFile*(filename: string, op = "interpret", main = true): seq[ + string] {.discardable.} + +proc compile*(i: In, s: Stream, main = true): seq[string] = if "nim".findExe == "": logging.error "Nim compiler not found, unable to compile." quit(7) result = newSeq[string](0) i.open(s, i.filename) - discard i.parser.getToken() + discard i.parser.getToken() try: MINCOMPILED = true let dotindex = i.filename.rfind(".")

@@ -216,19 +222,25 @@ except CatchableError:

discard i.close() -proc minStream(s: Stream, filename: string, op = "interpret", main = true): seq[string] {.discardable.}= +proc minStream(s: Stream, filename: string, op = "interpret", main = true): seq[ + string] {.discardable.} = var i = newMinInterpreter(filename = filename) i.pwd = filename.parentDirEx if op == "interpret": i.interpret(s) - newSeq[string](0) + discard newSeq[string](0) + elif op == "bytecode-compile": + var vm = newVM() + let code = vm.compileToBytecode("") + vm.printBytecode(code) else: - i.compile(s, main) + discard i.compile(s, main) proc minStr*(buffer: string) = minStream(newStringStream(buffer), "input") -proc minFile*(filename: string, op = "interpret", main = true): seq[string] {.discardable.} = +proc minFile*(filename: string, op = "interpret", main = true): seq[ + string] {.discardable.} = var fn = filename if not filename.endsWith(".min"): fn &= ".min"

@@ -246,7 +258,7 @@ contents = fileLines.join("\n")

minStream(newStringStream(contents), fn, op, main) when isMainModule: - import + import terminal, parseopt, critbits,

@@ -255,6 +267,7 @@

var REPL = false var SIMPLEREPL = false var COMPILE = false + var BYTECODE = false var MODULEPATH = "" var exeName = "min" var iOpt = "\n -i, --interactive Start $1 shell (with advanced prompt, default if no file specidied)\n"

@@ -267,7 +280,7 @@ let n = $i.stack.len

if res.isQuotation and res.qVal.len > 1: echo " (" for item in res.qVal: - echo " " & $item + echo " " & $item echo " ".repeat(n.len) & ")" elif res.isCommand: echo " [" & res.cmdVal & "]"

@@ -279,7 +292,7 @@ if item.val.kind == minProcOp:

v = "<native>" else: v = $item.val.val - echo " " & v & " :" & $item.key + echo " " & v & " :" & $item.key if res.objType == "": echo " ".repeat(n.len) & "}" else:

@@ -296,7 +309,7 @@ var line: string

while true: i.push(i.newSym("prompt")) let vals = i.expect("str") - let v = vals[0] + let v = vals[0] let prompt = v.getString() stdout.write(prompt) stdout.flushFile()

@@ -319,21 +332,21 @@ return ed.getCompletions(symbols)

# evaluate prompt i.push(i.newSym("prompt")) let vals = i.expect("str") - let v = vals[0] + let v = vals[0] let prompt = v.getString() line = EDITOR.readLine(prompt) let r = i.interpret($line) if $line != "": i.printResult(r) - proc minRepl*() = + proc minRepl*() = var i = newMinInterpreter(filename = "<repl>") i.minRepl() - proc minSimpleRepl*() = + proc minSimpleRepl*() = var i = newMinInterpreter(filename = "<repl>") i.minSimpleRepl() - + let usage* = """ $exe v$version - a small but practical concatenative programming language (c) 2014-2021 Fabio Cevasco

@@ -358,8 +371,8 @@ compiled executable (if -c is set)

-n, --passN Pass options to the nim compiler (if -c is set) -p, --prelude:<file.min> If specified, it loads <file.min> instead of the default prelude code -v, —-version Print the program version""" % [ - "exe", exeName, - "version", pkgVersion, + "exe", exeName, + "version", pkgVersion, "iOpt", iOpt ]

@@ -367,15 +380,17 @@ var file, s: string = ""

var args = newSeq[string](0) logging.setLogFilter(logging.lvlNotice) var p = initOptParser() - + for kind, key, val in getopt(p): case kind: of cmdArgument: args.add key if file == "": - file = key + file = key of cmdLongOption, cmdShortOption: case key: + of "bytecode", "b": + BYTECODE = true of "compile", "c": COMPILE = true of "module-path", "m":

@@ -391,7 +406,7 @@ if file == "":

var val = val niftylogger.setLogLevel(val) of "passN", "n": - NIMOPTIONS = val + NIMOPTIONS = val of "evaluate", "e": if file == "": s = val

@@ -404,7 +419,7 @@ if file == "":

echo pkgVersion quit(0) of "interactive", "i": - if file == "": + if file == "": REPL = true of "interactive-simple", "j": if file == "":

@@ -415,7 +430,10 @@ else:

discard var op = "interpret" if COMPILE: - op = "compile" + if BYTECODE: + op = "bytecode-compile" + else: + op = "compile" if MODULEPATH.len > 0: for f in walkDirRec(MODULEPATH): if f.endsWith(".min"):
M minpkg/core/env.nimminpkg/core/env.nim

@@ -1,7 +1,7 @@

-import - os, +import + os, minline - + var HOME*: string if defined(windows): HOME = getenv("USERPROFILE")

@@ -9,8 +9,8 @@ if not defined(windows):

HOME = getenv("HOME") var MINRC* {.threadvar.}: string -MINRC = HOME / ".minrc" -var MINSYMBOLS* {.threadvar.}: string +MINRC = HOME / ".minrc" +var MINSYMBOLS* {.threadvar.}: string MINSYMBOLS = HOME / ".min_symbols" var MINHISTORY* {.threadvar.}: string MINHISTORY = HOME / ".min_history"
A minpkg/core/opcodes.nim

@@ -0,0 +1,42 @@

+ + +type MinOpCode* = enum + opUndef = 0x00 # Undefined + #### Header + opHead = 0x01 # Header + # 9 bytes in total + # language: 3 + # version: 3 + # bytecode: 1 + # undefined:2 + + #### Start + opStart = 0x02 # Program start + + #### Literal Values + opPushIn = 0x11 # Push integer value + # value: 8 + opPushFl = 0x12 # Push float value + # value: 8 + opPushNl = 0x13 # Push null value + opPushTr = 0x14 # Push true value + opPushFa = 0x15 # Push false value + opStrBeg = 0x16 # Begin string + OpStrEnd = 0x17 # End String + OpQuotBeg = 0x18 # Begin quotation + OpQuotEnd = 0x19 # End quotation + OpDictBeg = 0x1A # Begin dictionary + # typelength: 2 + # typevalue: ... + OpDictEnd = 0x1B # End dictionary + OptCmdBeg = 0x1C # Begin command + OptCmdEnd = 0x1D # End command + + #### Symbols + opSym = 0x20 # Push symbol + # length: 2 + # value: ... + #### Stop + opStop = 0xFF # Program stop + +
M minpkg/core/parser.nimminpkg/core/parser.nim

@@ -1,9 +1,9 @@

# Adapted from: https://github.com/Araq/Nimrod/blob/v0.9.6/lib/pure/json.nim -import - lexbase, - strutils, +import + lexbase, + strutils, sequtils, - streams, + streams, critbits, json, baseutils

@@ -38,32 +38,32 @@ minString,

minSymbol, minNull, 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 - eMinDictionaryStart, ## start of a dictionary: the ``{`` token - eMinDictionaryEnd ## start of a dictionary: the ``}`` token - MinParserError* = enum ## enumeration that lists all errors that can occur - errNone, ## no error - errInvalidToken, ## invalid token - errStringExpected, ## string expected - errBracketRiExpected, ## ``)`` expected - errBraceRiExpected, ## ``}`` expected - errQuoteExpected, ## ``"`` or ``'`` expected - errSqBracketRiExpected,## ``]`` expected - errEOC_Expected, ## ``*/`` expected - errEofExpected, ## EOF expected + 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 + eMinDictionaryStart, ## start of a dictionary: the ``{`` token + eMinDictionaryEnd ## start of a dictionary: the ``}`` token + MinParserError* = enum ## enumeration that lists all errors that can occur + errNone, ## no error + errInvalidToken, ## invalid token + errStringExpected, ## string expected + errBracketRiExpected, ## ``)`` expected + errBraceRiExpected, ## ``}`` expected + errQuoteExpected, ## ``"`` or ``'`` expected + errSqBracketRiExpected, ## ``]`` expected + errEOC_Expected, ## ``*/`` expected + errEofExpected, ## EOF expected errExprExpected - MinParserState* = enum - stateEof, - stateStart, - stateQuotation, - stateDictionary, + MinParserState* = enum + stateEof, + stateStart, + stateQuotation, + stateDictionary, stateExpectValue MinParser* = object of BaseLexer a*: string

@@ -74,7 +74,7 @@ state*: seq[MinParserState]

kind*: MinEventKind err*: MinParserError filename*: string - MinValue* = ref MinValueObject + MinValue* = ref MinValueObject MinValueObject* {.acyclic, final.} = object line*: int column*: int

@@ -87,9 +87,9 @@ of minInt: intVal*: BiggestInt

of minFloat: floatVal*: BiggestFloat of minCommand: cmdVal*: string of minDictionary: - scope*: ref MinScope - obj*: pointer - objType*: string + scope*: ref MinScope + obj*: pointer + objType*: string of minQuotation: qVal*: seq[MinValue] of minString: strVal*: string

@@ -103,7 +103,7 @@ parent*: ref MinScope

symbols*: CritBitTree[MinOperator] sigils*: CritBitTree[MinOperator] kind*: MinScopeKind - MinOperatorProc* = proc (i: In) {.closure.} + MinOperatorProc* = proc (i: In) {.closure.} MinOperatorKind* = enum minProcOp minValOp

@@ -127,13 +127,16 @@ scope*: ref MinScope

parser*: MinParser currSym*: MinValue filename*: string - evaluating*: bool - MinParsingError* = ref object of ValueError + evaluating*: bool + MinVM* = object + interpreter*: MinInterpreter + parser*: MinParser + 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 - + var CVARCOUNT = 0 # Helpers

@@ -150,7 +153,8 @@

proc raiseEmptyStack*() = raise MinEmptyStackError(msg: "Insufficient items on the stack") -proc dVal*(v: MinValue): CritBitTree[MinOperator] {.inline, extern:"min_exported_symbol_$1".}= +proc dVal*(v: MinValue): CritBitTree[MinOperator] {.inline, + extern: "min_exported_symbol_$1".} = if v.kind != minDictionary: raiseInvalid("dVal - Dictionary expected, got " & $v.kind) if v.scope.isNil:

@@ -177,7 +181,7 @@ "string literal",

"command literal", "int literal", "float literal", - "(", + "(", ")", "[", "]",

@@ -203,54 +207,60 @@ my.state = @[stateStart]

my.kind = eMinError my.a = "" -proc close*(my: var MinParser) {.inline, extern:"min_exported_symbol_$1".}= +proc close*(my: var MinParser) {.inline, extern: "min_exported_symbol_$1".} = lexbase.close(my) -proc getInt*(my: MinParser): int {.inline, extern:"min_exported_symbol_$1".}= +proc getInt*(my: MinParser): int {.inline, extern: "min_exported_symbol_$1".} = assert(my.kind == eMinInt) return parseint(my.a) -proc getFloat*(my: MinParser): float {.inline, extern:"min_exported_symbol_$1".}= +proc getFloat*(my: MinParser): float {.inline, + extern: "min_exported_symbol_$1".} = assert(my.kind == eMinFloat) return parseFloat(my.a) -proc kind*(my: MinParser): MinEventKind {.inline, extern:"min_exported_symbol_$1".}= +proc kind*(my: MinParser): MinEventKind {.inline, + extern: "min_exported_symbol_$1".} = return my.kind -proc getColumn*(my: MinParser): int {.inline, extern:"min_exported_symbol_$1".}= +proc getColumn*(my: MinParser): int {.inline, + extern: "min_exported_symbol_$1".} = result = getColNumber(my, my.bufpos) -proc getLine*(my: MinParser): int {.inline, extern:"min_exported_symbol_$1".}= +proc getLine*(my: MinParser): int {.inline, extern: "min_exported_symbol_$1".} = result = my.lineNumber -proc getFilename*(my: MinParser): string {.inline, extern:"min_exported_symbol_$1".}= +proc getFilename*(my: MinParser): string {.inline, + extern: "min_exported_symbol_$1".} = result = my.filename - -proc errorMsg*(my: MinParser, msg: string): string = + +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 = +proc errorMsg*(my: MinParser): string = assert(my.kind == eMinError) result = errorMsg(my, errorMessages[my.err]) - -proc errorMsgExpected*(my: MinParser, e: string): string = + +proc errorMsgExpected*(my: MinParser, e: string): string = result = errorMsg(my, e & " expected") -proc raiseParsing*(p: MinParser, msg: string) {.noinline, noreturn, extern:"min_exported_symbol_$1".}= +proc raiseParsing*(p: MinParser, msg: string) {.noinline, noreturn, + extern: "min_exported_symbol_$1".} = raise MinParsingError(msg: errorMsgExpected(p, msg)) -proc raiseUndefined*(p:MinParser, msg: string) {.noinline, noreturn, extern:"min_exported_symbol_$1_2".}= +proc raiseUndefined*(p: MinParser, msg: string) {.noinline, noreturn, + extern: "min_exported_symbol_$1_2".} = raise MinUndefinedError(msg: errorMsg(p, msg)) -proc parseNumber(my: var MinParser) = +proc parseNumber(my: var MinParser) = var pos = my.bufpos var buf = my.buf - if buf[pos] == '-': + if buf[pos] == '-': add(my.a, '-') inc(pos) - if buf[pos] == '.': + if buf[pos] == '.': add(my.a, "0.") inc(pos) else:

@@ -275,7 +285,7 @@ add(my.a, buf[pos])

inc(pos) my.bufpos = pos -proc handleHexChar(c: char, x: var int): bool = +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'))

@@ -288,8 +298,8 @@ result = tkString

var pos = my.bufpos + 1 var buf = my.buf while true: - case buf[pos] - of '\0': + case buf[pos] + of '\0': my.err = errQuoteExpected result = tkError break

@@ -298,21 +308,21 @@ inc(pos)

break of '\\': case buf[pos+1] - of '\\', '"', '\'', '/': + of '\\', '"', '\'', '/': add(my.a, buf[pos+1]) inc(pos, 2) of 'b': add(my.a, '\b') - inc(pos, 2) + inc(pos, 2) of 'f': add(my.a, '\f') - inc(pos, 2) + inc(pos, 2) of 'n': add(my.a, '\L') - inc(pos, 2) + inc(pos, 2) of 'r': add(my.a, '\C') - inc(pos, 2) + inc(pos, 2) of 't': add(my.a, '\t') inc(pos, 2)

@@ -324,15 +334,15 @@ 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: + else: # don't bother with the error add(my.a, buf[pos]) inc(pos) - of '\c': + of '\c': pos = lexbase.handleCR(my, pos) buf = my.buf add(my.a, '\c') - of '\L': + of '\L': pos = lexbase.handleLF(my, pos) buf = my.buf add(my.a, '\L')

@@ -346,8 +356,8 @@ result = tkCommand

var pos = my.bufpos + 1 var buf = my.buf while true: - case buf[pos] - of '\0': + case buf[pos] + of '\0': my.err = errSqBracketRiExpected result = tkError break

@@ -356,21 +366,21 @@ inc(pos)

break of '\\': case buf[pos+1] - of '\\', '"', '\'', '/': + of '\\', '"', '\'', '/': add(my.a, buf[pos+1]) inc(pos, 2) of 'b': add(my.a, '\b') - inc(pos, 2) + inc(pos, 2) of 'f': add(my.a, '\f') - inc(pos, 2) + inc(pos, 2) of 'n': add(my.a, '\L') - inc(pos, 2) + inc(pos, 2) of 'r': add(my.a, '\C') - inc(pos, 2) + inc(pos, 2) of 't': add(my.a, '\t') inc(pos, 2)

@@ -382,15 +392,15 @@ 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: + else: # don't bother with the error add(my.a, buf[pos]) inc(pos) - of '\c': + of '\c': pos = lexbase.handleCR(my, pos) buf = my.buf add(my.a, '\c') - of '\L': + of '\L': pos = lexbase.handleLF(my, pos) buf = my.buf add(my.a, '\L')

@@ -399,55 +409,57 @@ add(my.a, buf[pos])

inc(pos) my.bufpos = pos # store back -proc parseSymbol(my: var MinParser): MinTokenKind = +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', ')', '(', '}', '{', '[', ']']): - if buf[pos] == '"': - add(my.a, buf[pos]) - my.bufpos = pos - let r = parseString(my) - if r == tkError: - result = tkError - return - add(my.a, buf[pos]) + while not(buf[pos] in WhiteSpace) and not(buf[pos] in ['\0', ')', '(', '}', + '{', '[', ']']): + if buf[pos] == '"': + add(my.a, buf[pos]) + my.bufpos = pos + let r = parseString(my) + if r == tkError: + result = tkError return - else: - add(my.a, buf[pos]) - inc(pos) + add(my.a, buf[pos]) + return + else: + add(my.a, buf[pos]) + inc(pos) my.bufpos = pos proc addDoc(my: var MinParser, docComment: string, reset = true) = if my.doc and not my.currSym.isNil and my.currSym.kind == minSymbol: if reset: my.doc = false - if my.currSym.docComment.len == 0 or my.currSym.docComment.len > 0 and my.currSym.docComment[my.currSym.docComment.len-1] == '\n': + if my.currSym.docComment.len == 0 or my.currSym.docComment.len > 0 and + my.currSym.docComment[my.currSym.docComment.len-1] == '\n': my.currSym.docComment &= docComment.strip(true, false) else: my.currSym.docComment &= docComment -proc skip(my: var MinParser) = +proc skip(my: var MinParser) = var pos = my.bufpos var buf = my.buf - while true: + while true: case buf[pos] of ';': # skip line comment: - if buf[pos+1] == ';': - my.doc = true + if buf[pos+1] == ';': + my.doc = true inc(pos, 2) while true: - case buf[pos] - of '\0': + case buf[pos] + of '\0': break - of '\c': + of '\c': pos = lexbase.handleCR(my, pos) buf = my.buf my.addDoc "\n" break - of '\L': + of '\L': pos = lexbase.handleLF(my, pos) buf = my.buf my.addDoc "\n"

@@ -455,7 +467,7 @@ break

else: my.addDoc $my.buf[pos], false inc(pos) - of '#': + of '#': if buf[pos+1] == '|': # skip long comment: if buf[pos+2] == '|':

@@ -463,15 +475,15 @@ inc(pos)

my.doc = true inc(pos, 2) while true: - case buf[pos] - of '\0': + case buf[pos] + of '\0': my.err = errEOC_Expected break - of '\c': + of '\c': pos = lexbase.handleCR(my, pos) my.addDoc "\n", false buf = my.buf - of '\L': + of '\L': pos = lexbase.handleLF(my, pos) my.addDoc "\n", false buf = my.buf

@@ -479,21 +491,21 @@ of '|':

inc(pos) if buf[pos] == '|': inc(pos) - if buf[pos] == '#': + if buf[pos] == '#': inc(pos) break my.addDoc $buf[pos], false else: my.addDoc $my.buf[pos], false inc(pos) - else: + else: break - of ' ', '\t': + of ' ', '\t': inc(pos) - of '\c': + of '\c': pos = lexbase.handleCR(my, pos) buf = my.buf - of '\L': + of '\L': pos = lexbase.handleLF(my, pos) buf = my.buf else:

@@ -502,7 +514,7 @@ my.bufpos = pos

proc getToken*(my: var MinParser): MinTokenKind = setLen(my.a, 0) - skip(my) + skip(my) case my.buf[my.bufpos] of '-', '.': if my.bufpos+1 <= my.buf.len and my.buf[my.bufpos+1] in '0'..'9':

@@ -513,7 +525,7 @@ else:

result = tkInt else: result = parseSymbol(my) - of '0'..'9': + of '0'..'9': parseNumber(my) if {'.', 'e', 'E'} in my.a: result = tkFloat

@@ -539,16 +551,16 @@ of '\0':

result = tkEof else: result = parseSymbol(my) - case my.a + case my.a of "null": result = tkNull of "true": result = tkTrue of "false": result = tkFalse - else: + else: discard my.token = result -proc next*(my: var MinParser) = +proc next*(my: var MinParser) = var tk = getToken(my) var i = my.state.len-1 case my.state[i]

@@ -558,15 +570,15 @@ my.kind = eMinEof

else: my.kind = eMinError my.err = errEofExpected - of stateStart: + of stateStart: case tk of tkString, tkInt, tkFloat, tkTrue, tkFalse: my.state[i] = stateEof # expect EOF next! my.kind = MinEventKind(ord(tk)) - of tkBracketLe: + of tkBracketLe: my.state.add(stateQuotation) # we expect any my.kind = eMinQuotationStart - of tkBraceLe: + of tkBraceLe: my.state.add(stateDictionary) # we expect any my.kind = eMinDictionaryStart of tkEof:

@@ -578,10 +590,10 @@ of stateQuotation:

case tk of tkString, tkInt, tkFloat, tkTrue, tkFalse: my.kind = MinEventKind(ord(tk)) - of tkBracketLe: + of tkBracketLe: my.state.add(stateQuotation) my.kind = eMinQuotationStart - of tkBraceLe: + of tkBraceLe: my.state.add(stateDictionary) my.kind = eMinDictionaryStart of tkBracketRi:

@@ -597,10 +609,10 @@ of stateDictionary:

case tk of tkString, tkInt, tkFloat, tkTrue, tkFalse: my.kind = MinEventKind(ord(tk)) - of tkBracketLe: + of tkBracketLe: my.state.add(stateQuotation) my.kind = eMinQuotationStart - of tkBraceLe: + of tkBraceLe: my.state.add(stateDictionary) my.kind = eMinDictionaryStart of tkBracketRi:

@@ -616,21 +628,21 @@ of stateExpectValue:

case tk of tkString, tkInt, tkFloat, tkTrue, tkFalse: my.kind = MinEventKind(ord(tk)) - of tkBracketLe: + of tkBracketLe: my.state.add(stateQuotation) my.kind = eMinQuotationStart - of tkBraceLe: + of tkBraceLe: my.state.add(stateDictionary) my.kind = eMinDictionaryStart else: my.kind = eMinError my.err = errExprExpected -proc eat(p: var MinParser, token: MinTokenKind) = +proc eat(p: var MinParser, token: MinTokenKind) = if p.token == token: discard getToken(p) else: raiseParsing(p, tokToStr[token]) -proc `$`*(a: MinValue): string {.inline, extern:"min_exported_symbol_$1".}= +proc `$`*(a: MinValue): string {.inline, extern: "min_exported_symbol_$1".} = case a.kind: of minNull: return "null"

@@ -662,14 +674,14 @@ var k = $i.key

if k.contains(" "): k = "\"$1\"" % k d = d & v & " :" & k & " " - if a.objType != "": + if a.objType != "": d = d & ";" & a.objType d = d.strip & "}" return d of minCommand: return "[" & a.cmdVal & "]" -proc `$$`*(a: MinValue): string {.inline, extern:"min_exported_symbol_$1".}= +proc `$$`*(a: MinValue): string {.inline, extern: "min_exported_symbol_$1".} = case a.kind: of minNull: return "null"

@@ -703,7 +715,7 @@ var k = $i.key

if k.contains(" "): k = "\"$1\"" % k d = d & v & " :" & k & " " - if a.objType != "": + if a.objType != "": d = d & ";" & a.objType d = d.strip & "}" return d

@@ -736,7 +748,7 @@ discard getToken(p)

of tkBracketLe: var q = newSeq[MinValue](0) discard getToken(p) - while p.token != tkBracketRi: + while p.token != tkBracketRi: q.add p.parseMinValue(i) eat(p, tkBracketRi) result = MinValue(kind: minQuotation, qVal: q)

@@ -745,7 +757,7 @@ var scope = newScopeRef(nil)

var val: MinValue discard getToken(p) var c = 0 - while p.token != tkBraceRi: + while p.token != tkBraceRi: c = c+1 let v = p.parseMinValue(i) if val.isNil:

@@ -756,7 +768,8 @@ if key[0] == ':':

var offset = 0 if key[1] == '"': offset = 1 - scope.symbols[key[1+offset .. key.len-1-offset]] = MinOperator(kind: minValOp, val: val, sealed: false) + scope.symbols[key[1+offset .. key.len-1-offset]] = MinOperator( + kind: minValOp, val: val, sealed: false) val = nil else: raiseInvalid("Invalid dictionary key: " & key)

@@ -767,15 +780,16 @@ if c mod 2 != 0:

raiseInvalid("Invalid dictionary") result = MinValue(kind: minDictionary, scope: scope) of tkSymbol: - result = MinValue(kind: minSymbol, symVal: p.a, column: p.getColumn, line: p.lineNumber, filename: p.filename) + result = MinValue(kind: minSymbol, symVal: p.a, column: p.getColumn, + line: p.lineNumber, filename: p.filename) p.a = "" p.currSym = result discard getToken(p) else: - let err = "Undefined or invalid value: "&p.a - raiseUndefined(p, err) + let err = "Undefined or invalid value: "&p.a + raiseUndefined(p, err) result.filename = p.filename - + proc compileMinValue*(p: var MinParser, i: In, push = true, indent = ""): seq[string] = var op = indent if push:

@@ -806,13 +820,13 @@ CVARCOUNT.inc

var qvar = "q" & $CVARCOUNT result.add indent&"var "&qvar&" = newSeq[MinValue](0)" discard getToken(p) - while p.token != tkBracketRi: + while p.token != tkBracketRi: var instructions = p.compileMinValue(i, false, indent) let v = instructions.pop result = result.concat(instructions) result.add indent&qvar&".add "&v eat(p, tkBracketRi) - result.add op&"MinValue(kind: minQuotation, qVal: "&qvar&")" + result.add op&"MinValue(kind: minQuotation, qVal: "&qvar&")" of tkSqBracketLe, tkSqBracketRi: discard getToken(p) of tkCommand:

@@ -828,7 +842,7 @@ CVARCOUNT.inc

var scopevar = "scope" & $CVARCOUNT CVARCOUNT.inc var valvar = "val" & $CVARCOUNT - while p.token != tkBraceRi: + while p.token != tkBraceRi: c = c+1 var instructions = p.compileMinValue(i, false, indent) let v = p.parseMinValue(i)

@@ -842,7 +856,8 @@ result.add indent&valvar&" = "&vs

elif v.kind == minSymbol: let key = v.symVal if key[0] == ':': - result.add indent&scopevar&".symbols["&key[1 .. key.len-1]&"] = MinOperator(kind: minValOp, val: "&valvar&", sealed: false)" + result.add indent&scopevar&".symbols["&key[1 .. + key.len-1]&"] = MinOperator(kind: minValOp, val: "&valvar&", sealed: false)" val = nil else: raiseInvalid("Invalid dictionary key: " & key)

@@ -872,13 +887,13 @@

proc isSymbol*(s: MinValue): bool = return s.kind == minSymbol -proc isQuotation*(s: MinValue): bool = +proc isQuotation*(s: MinValue): bool = return s.kind == minQuotation -proc isCommand*(s: MinValue): bool = +proc isCommand*(s: MinValue): bool = return s.kind == minCommand -proc isString*(s: MinValue): bool = +proc isString*(s: MinValue): bool = return s.kind == minString proc isFloat*(s: MinValue): bool =

@@ -894,7 +909,8 @@ 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) + return s.isSymbol or s.isString or (s.isQuotation and s.qVal.len == 1 and + s.qVal[0].isSymbol) proc isDictionary*(q: MinValue): bool = return q.kind == minDictionary

@@ -909,7 +925,8 @@ if q.isTypedDictionary:

return q.objType == t return false -proc `==`*(a: MinValue, b: MinValue): bool {.inline, extern:"min_exported_symbol_eqeq".}= +proc `==`*(a: MinValue, b: MinValue): bool {.inline, + extern: "min_exported_symbol_eqeq".} = if not (a.kind == b.kind or (a.isNumber and b.isNumber)): return false if a.kind == minSymbol and b.kind == minSymbol:
A minpkg/core/vm.nim

@@ -0,0 +1,84 @@

+import + strutils, + sequtils + +import + meta, + baseutils, + parser, + interpreter, + opcodes + +proc newVM*(): MinVm = + result.interpreter = newMinInterpreter("<vm>") + +proc bytecode(s: string, symbol = false): seq[byte] = + result = newSeq[byte](0) + if symbol: + result.add opSym.byte + result.add s.len.byte + for c in s: + result.add c.ord.byte + else: + result.add opStrBeg.byte + for c in s: + result.add c.ord.byte + result.add OpStrEnd.byte + +proc bytecode(n: BiggestInt): seq[byte] = + result = newSeq[byte](0) + result.add opPushIn.byte + result = result.concat(cast[array[0..7, byte]](n).toSeq) + +proc bytecode(n: BiggestFloat): seq[byte] = + result = newSeq[byte](0) + result.add opPushFl.byte + result = result.concat(cast[array[0..7, byte]](n).toSeq) + +proc compileToBytecode*(vm: MinVm, s = ""): seq[byte] = + result = newSeq[byte](0) + result.add opHead.byte + for c in pkgName: + result.add c.ord.byte + let v = pkgVersion.split(".") + result.add v[0].parseInt.byte + result.add v[1].parseInt.byte + result.add v[2].parseInt.byte + result.add opUndef.byte + result.add opUndef.byte + var p = vm.parser + case p.token: + of tkNull: + result.add opPushNl.byte + discard p.getToken() + of tkTrue: + result.add opPushTr.byte + discard p.getToken() + of tkFalse: + result.add opPushFa.byte + discard p.getToken() + of tkInt: + result = result.concat(p.a.parseInt.bytecode) + p.a = "" + discard p.getToken() + of tkFloat: + result = result.concat(p.a.parseFloat.bytecode) + p.a = "" + discard p.getToken() + of tkString: + result = result.concat(p.a.escapeEx.bytecode) + p.a = "" + discard p.getToken() + of tkSymbol: + result = result.concat(p.a.escapeEx.bytecode(true)) + p.a = "" + discard p.getToken() + else: + raiseUndefined(p, "Undefined value: '"&p.a&"'") + + +proc printBytecode*(vm: MinVm, code: seq[byte]) = + for b in code: + echo b.toHex + +
A test.nim

@@ -0,0 +1,5 @@

+import min +MINCOMPILED = true +var i = newMinInterpreter("test.min") +i.stdLib() +### test.min (main)