all repos — min @ 8ad3286035c2d9707f5a4260d25f879624946e1c

A small but practical concatenative programming language.

Added require, invoke, other fixes and improvements.
h3rald h3rald@h3rald.com
Mon, 21 Dec 2020 20:14:34 +0100
commit

8ad3286035c2d9707f5a4260d25f879624946e1c

parent

9a4390d0d79a3daa0f005fd0137e89463d190bb5

M core/env.nimcore/env.nim

@@ -22,4 +22,6 @@ var EDITOR* {.threadvar.}: LineEditor

EDITOR = initEditor(historyFile = MINHISTORY) var MINCOMPILED* {.threadvar.}: bool -MINCOMPILED = false+MINCOMPILED = false +var BUNDLEDASSETS* {.threadvar.}: bool +BUNDLEDASSETS = false
M core/interpreter.nimcore/interpreter.nim

@@ -22,7 +22,8 @@ MinTrappedException* = ref object of CatchableError

MinRuntimeError* = ref object of CatchableError data*: MinValue -var MINCOMPILEDFILES* {.threadvar.}: CritBitTree[MinOperatorProc] +var COMPILEDMINFILES* {.threadvar.}: CritBitTree[MinOperatorProc] +var COMPILEDASSETS* {.threadvar.}: CritBitTree[MinOperatorProc] const USER_SYMBOL_REGEX* = "^[a-zA-Z_][a-zA-Z0-9/!?+*._-]*$"

@@ -115,7 +116,7 @@ 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) = +proc stackTrace*(i: In) = var trace = i.trace trace.reverse() for sym in trace:

@@ -265,6 +266,27 @@ return i.stack[i.stack.len-1]

else: raiseEmptyStack() +template handleErrors*(i: In, body: untyped) = + try: + body + except MinRuntimeError: + let msg = getCurrentExceptionMsg() + i.stack = i.stackcopy + error("$1:$2,$3 $4" % [i.currSym.filename, $i.currSym.line, $i.currSym.column, msg]) + i.stackTrace() + i.trace = @[] + raise MinTrappedException(msg: msg) + except MinTrappedException: + raise + except: + let msg = getCurrentExceptionMsg() + i.stack = i.stackcopy + i.error(msg) + i.stackTrace() + i.trace = @[] + raise MinTrappedException(msg: msg) + + proc interpret*(i: In, parseOnly=false): MinValue {.discardable, extern:"min_exported_symbol_$1".} = var val: MinValue var q: MinValue

@@ -326,7 +348,7 @@

proc compileFile*(i: In, main: bool): seq[string] {.discardable, extern:"min_exported_symbol_$1".} = result = newSeq[string](0) if not main: - result.add "MINCOMPILEDFILES[\"$#\"] = proc(i: In) {.gcsafe.}=" % i.filename + result.add "COMPILEDMINFILES[\"$#\"] = proc(i: In) {.gcsafe.}=" % i.filename result = result.concat(i.rawCompile(" ")) else: result = i.rawCompile("")

@@ -369,6 +391,26 @@ i.trace = i2.trace

i.stackcopy = i2.stackcopy i.stack = i2.stack i.scope = i2.scope + +proc require*(i: In, s: string, parseOnly=false): MinValue {.discardable, extern:"min_exported_symbol_$1".}= + var fileLines = newSeq[string](0) + var contents = "" + try: + fileLines = s.readFile().splitLines() + except: + fatal("Cannot read from file: " & s) + if fileLines[0].len >= 2 and fileLines[0][0..1] == "#!": + contents = ";;\n" & fileLines[1..fileLines.len-1].join("\n") + else: + contents = fileLines.join("\n") + var i2 = i.copy(s) + i2.open(newStringStream(contents), s) + discard i2.parser.getToken() + discard i2.interpret(parseOnly) + result = newDict(i2.scope) + result.objType = "module" + for key, value in i2.scope.symbols.pairs: + result.scope.symbols[key] = value proc parse*(i: In, s: string, name="<parse>"): MinValue {.extern:"min_exported_symbol_$1".}= return i.eval(s, name, true)
M lib/min_dstore.nimlib/min_dstore.nim

@@ -48,12 +48,13 @@ let collection = parts[0]

let id = parts[1] let data = i.dget(ds, "data".newVal) if not dhas(data, collection): - raiseInvalid("Collection '$#' does not exist" % collection) - let cll = i.dget(data, collection.newVal) - if dhas(cll, id.newVal): - i.push true.newVal + i.push false.newVal else: - i.push false.newVal + let cll = i.dget(data, collection.newVal) + if dhas(cll, id.newVal): + i.push true.newVal + else: + i.push false.newVal def.symbol("dsget") do (i: In): let vals = i.expect("'sym", "dict:datastore")

@@ -74,10 +75,11 @@ var filter = vals[0]

let collection = vals[1] let ds = vals[2] let data = i.dget(ds, "data".newVal) + var res = newSeq[MinValue](0) if not dhas(data, collection): - raiseInvalid("Collection '$#' does not exist" % collection.getString) + i.push res.newVal + return let cll = i.dget(data, collection) - var res = newSeq[MinValue](0) for e in i.values(cll).qVal: i.push e try:
M lib/min_http.nimlib/min_http.nim

@@ -93,7 +93,7 @@ if not port.isInt:

raiseInvalid("Port is not an integer.") var server = newAsyncHttpServer() var i {.threadvar.}: MinInterpreter - i = ii + i = ii.copy(ii.filename) proc handler(req: Request) {.async, gcsafe.} = var qreq = newDict(i.scope) qreq = i.dset(qreq, "url", newVal($req.url))

@@ -102,8 +102,9 @@ qreq = i.dset(qreq, "method", newVal($req.reqMethod))

qreq = i.dset(qreq, "hostname", newVal($req.hostname)) qreq = i.dset(qreq, "version", newVal("$1.$2" % [$req.protocol.major, $req.protocol.minor])) qreq = i.dset(qreq, "body", newVal($req.body)) - i.push qreq - i.dequote qhandler + i.handleErrors do: + i.push qreq + i.dequote qhandler let qres = i.pop var body = "".newVal var rawHeaders = newDict(i.scope)
M lib/min_lang.nimlib/min_lang.nim

@@ -89,13 +89,36 @@ file = file & ".min"

info("[load] File: ", file) if MINCOMPILED: var compiledFile = strutils.replace(strutils.replace(file, "\\", "/"), "./", "") - if MINCOMPILEDFILES.hasKey(compiledFile): - MINCOMPILEDFILES[compiledFile](i) + if COMPILEDMINFILES.hasKey(compiledFile): + COMPILEDMINFILES[compiledFile](i) + return + var f = i.pwd.joinPath(file) + if not f.fileExists: + raiseInvalid("File '$1' does not exist." % file) + i.load f + + def.symbol("require") do (i: In): + let vals = i.expect("'sym") + let s = vals[0] + var file = s.getString + if not file.endsWith(".min"): + file = file & ".min" + info("[require] File: ", file) + if MINCOMPILED: + var compiledFile = strutils.replace(strutils.replace(file, "\\", "/"), "./", "") + if COMPILEDMINFILES.hasKey(compiledFile): + var i2 = i.copy(file) + COMPILEDMINFILES[compiledFile](i2) + var mdl = newDict(i2.scope) + mdl.objType = "module" + for key, value in i2.scope.symbols.pairs: + mdl.scope.symbols[key] = value + i.push(mdl) return - file = i.pwd.joinPath(file) - if not file.fileExists: + let f = i.pwd.joinPath(file) + if not f.fileExists: raiseInvalid("File '$1' does not exist." % file) - i.load file + i.push i.require(f) def.symbol("read") do (i: In): let vals = i.expect("'sym")

@@ -406,6 +429,25 @@ i.scope.parent = origScope

let sym = i.scope.getSymbol(s) i.apply(sym) i.scope = origScope + + def.symbol("invoke") do (i: In): + let vals = i.expect("'sym") + let s = vals[0].getString + let parts = s.split("/") + if parts.len < 2: + raiseInvalid("Dictionary identifier not specified") + let mdlId = parts[0] + let symId = parts[1] + i.apply i.scope.getSymbol(mdlId) + let mdl = i.pop + if mdl.kind != minDictionary: + raiseInvalid("'$#' is not a dictionary" % mdlId) + var sym: MinValue + try: + sym = i.dget(mdl, symId) + except: + raiseInvalid("Symbol '$#' not found in dictionary '$#'" % [symId, mdlId]) + i.dequote sym def.symbol("set-type") do (i: In): let vals = i.expect("'sym", "dict")

@@ -837,6 +879,9 @@ i.push("module".newSym)

def.sigil("^") do (i: In): i.push("call".newSym) + + def.sigil("*") do (i: In): + i.push("invoke".newSym) def.sigil(">") do (i: In): i.push("save-symbol".newSym)
M min.nimblemin.nimble

@@ -8,7 +8,8 @@ author = pkgAuthor

description = pkgDescription license = "MIT" bin = @[pkgName] -installFiles = @["core/meta.nim"] +installFiles = @["min.yml", "min.nim", "mindyn.nim", "prelude.min"] +installDirs = @["vendor", "lib", "core", "packages"] # Dependencies
M next-release.mdnext-release.md

@@ -1,2 +1,6 @@

* Added **encode-url**, **decode-url**, **parse-url** symbols. * Added **dstore** module providing support for simple, persistent, in-memory JSON stores. +* Added **require** symbol to read a min file and automatically create a module containing all symbols defined in the file. +* Added **invoke** symbol (and **\*** sigil) to easily call a symbol defined in a module or dictionary, e.g. `*mymodule/mymethod`. +* Fixed library installation via nimble +* Fixed error handling and stack trace for **start-server** symbol.
M site/assets/styles/min-lang.csssite/assets/styles/min-lang.css

@@ -56,6 +56,7 @@ }

header .luxbar-menu i { vertical-align: text-bottom; + line-height: 18px; } .luxbar-menu-light {
M site/contents/_defs_.mdsite/contents/_defs_.md

@@ -13,7 +13,7 @@ {{dstore => [dict:datastore](class:kwd)}}

{{d => [dict](class:kwd)}} {{d1 => [dict<sub>1</sub>](class:kwd)}} {{d2 => [dict<sub>2</sub>](class:kwd)}} -{{s0p => [dict<sub>\*</sub>](class:kwd)}} +{{d0p => [dict<sub>\*</sub>](class:kwd)}} {{flt => [float](class:kwd)}} {{i => [int](class:kwd)}} {{i1 => [int<sub>1</sub>](class:kwd)}}
M site/contents/get-started.mdsite/contents/get-started.md

@@ -72,6 +72,7 @@ * The {#link-module||fs#}

* The {#link-module||sys#} * The following operators: * {#link-operator||lang||load#} + * {#link-operator||lang||require#} * {#link-operator||lang||read#} * {#link-operator||lang||to-json#} * {#link-operator||lang||from-json#}
M site/contents/learn-operators.mdsite/contents/learn-operators.md

@@ -43,6 +43,8 @@ \:

: Alias for {#link-operator||lang||define#}. ^ : Alias for {#link-operator||lang||call#}. +* +: Alias for {#link-operator||lang||invoke#}. @ : Alias for {#link-operator||lang||bind#}. >
M site/contents/reference-dstore.mdsite/contents/reference-dstore.md

@@ -4,7 +4,7 @@ title: "dstore Module"

----- {@ _defs_.md || 0 @} -{#op||dsdelete||{{datoee}} {{sl}}||{{dstore}}|| +{#op||dsdelete||{{dstore}} {{sl}}||{{dstore}}|| Removes an item from the datastore {{dstore}}. The item is uniquely identified by {{sl}}, which contains the collection containing the item and the item id, separated by a forward slash (/). Puts the reference to the modified datastore back on tbe stack. #}

@@ -30,6 +30,7 @@ > > %sidebar%

> > Example > > > > Assuming that **ds** is a datastore, the following program retrieves all elements of teh collection **posts** whose author field is set to "h3rald": +> > > > ds "posts" (/author "h3rald" ==) dsquery #}
M site/contents/reference-lang.mdsite/contents/reference-lang.md

@@ -20,6 +20,8 @@ {#sig||^||call#}

{#alias||^||call#} +{#sig||*||invoke#} + {#sig||@||bind#} {#alias||@||bind#}

@@ -206,6 +208,9 @@ > * If {{any}} is an integer, no conversion is performed.

> * If {{any}} is a float, it is converted to an integer value by truncating its decimal part. > * If {{any}} is a string, it is parsed as an integer value.#} +{#op||invoke||{{sl}}||{{a0p}}|| +Assming that {{sl}} is a formatted like *dictionary*/*symbol*, calls *symbol* defined in *dictionary*. #} + {#op||linrec||{{q1}} {{q2}} {{q3}} {{q4}}||{{a0p}}|| > Implements linear recursions as follows: >

@@ -226,7 +231,7 @@ {#op||lite?||{{null}}||{{b}}||

Returns {{t}} if min was built in _lite_ mode. #} {#op||load||{{sl}}||{{a0p}}|| -Parses and interprets the specified {{m}} file, adding [.min](class:ext) if not specified. #} +Parses and interprets the specified {{m}} file {{sl}}, adding [.min](class:ext) if not specified. #} {#op||load-symbol||{{sl}}||{{a0p}}|| Loads the contents of symbol {{sl}} from the [.min\_symbols](class:file) file. #}

@@ -311,6 +316,9 @@ Reads and parses the specified {{m}} file {{sl}} and returns a quoted program {{q}}. #}

{#op||remove-symbol||{{sl}}||{{null}}|| Removes the symbol {{sl}} from the [.min\_symbols](class:file) file. #} + +{#op||require||{{sl}}||{{d}}|| +Parses and interprets (in a separater interpreter) the specified {{m}} file {{sl}}, adding [.min](class:ext) if not specified, and returns a module dictionary {{d}} containing all the symbols defined in {{sl}}. #} {#op||ROOT||{{null}}||{{d}}|| Returns a module holding a reference to the [ROOT](class:kwd) scope.
M tests/lang.mintests/lang.min

@@ -50,6 +50,9 @@

("2 2 +" "tests/testload.min" fwrite 'testload load 4 ==) assert "tests/testload.min" rm + ("2 :two 3 :three" "tests/testrequire.min" fwrite 'testrequire require :tm *tm/two *tm/three + 5 ==) assert + "tests/testrequire.min" rm + (2 2 mymath ^myplus 4 ==) assert