all repos — min @ dfd5dd21d2ad8c467b3d90bbc4d001698667f669

A small but practical concatenative programming language.

Implemnted -m, -d:mini and updated docs.
h3rald h3rald@h3rald.com
Thu, 10 Dec 2020 20:19:17 +0100
commit

dfd5dd21d2ad8c467b3d90bbc4d001698667f669

parent

ea3610c1ab01e4bf724af1f9f64f80438e008902

M .gitignore.gitignore

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

nimcache/ build/ -min.exe +*.exe min +litemin +minimin nakefile Min_DeveloperGuide.htm nimsuggest.log

@@ -14,4 +16,5 @@ site/temp/

dynlibs/ .env.yml response.json -_* +run +run.nim
M core/interpreter.nimcore/interpreter.nim

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

import streams, strutils, - critbits, + sequtils, + critbits, os, algorithm, logging

@@ -14,6 +15,8 @@ type

MinTrappedException* = ref object of CatchableError MinRuntimeError* = ref object of CatchableError data*: MinValue + +var MINCOMPILEDFILES* {.threadvar.}: CritBitTree[MinOperatorProc] proc raiseRuntime*(msg: string, data: MinValue) {.extern:"min_exported_symbol_$1".}= data.objType = "error"

@@ -278,18 +281,13 @@ if parseOnly:

return q if i.stack.len > 0: return i.stack[i.stack.len - 1] - -proc compile*(i: In): seq[string] {.discardable, extern:"min_exported_symbol_$1".} = - result = newSeq[string](0) - result.add "import min" - result.add "MINCOMPILED = true" - result.add "var i = newMinInterpreter(\"$#\")" % i.filename - result.add "i.stdLib()" + +proc rawCompile*(i: In, indent = ""): seq[string] {.discardable, extern:"min_exported_symbol_$1".} = while i.parser.token != tkEof: if i.trace.len == 0: i.stackcopy = i.stack try: - result.add i.parser.compileMinValue(i) + result.add i.parser.compileMinValue(i, push = true, indent) except MinRuntimeError: let msg = getCurrentExceptionMsg() i.stack = i.stackcopy

@@ -306,6 +304,22 @@ i.error(msg)

i.stackTrace i.trace = @[] raise MinTrappedException(msg: msg) + +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 = result.concat(i.rawCompile(" ")) + else: + result = i.rawCompile("") + +proc initCompiledFile*(i: In, files: seq[string]): seq[string] {.discardable, extern:"min_exported_symbol_$1".} = + result = newSeq[string](0) + result.add "import min, critbits" + result.add "MINCOMPILED = true" + if files.len > 0: + result.add "var i = newMinInterpreter(\"$#\")" % i.filename + result.add "i.stdLib()" proc eval*(i: In, s: string, name="<eval>", parseOnly=false): MinValue {.discardable, extern:"min_exported_symbol_$1".}= var i2 = i.copy(name)
M core/parser.nimcore/parser.nim

@@ -652,10 +652,10 @@ else:

raiseUndefined(p, "Undefined value: '"&p.a&"'") result.filename = p.filename -proc compileMinValue*(p: var MinParser, i: In, push = true): seq[string] {.extern:"min_exported_symbol_$1".}= - var op = "" +proc compileMinValue*(p: var MinParser, i: In, push = true, indent = ""): seq[string] {.extern:"min_exported_symbol_$1".}= + var op = indent if push: - op = "i.push " + op = indent&"i.push " result = newSeq[string](0) case p.token of tkTrue:

@@ -676,13 +676,13 @@ result = @[op&"MinValue(kind: minFloat, floatVal: "&p.a&")"]

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

@@ -695,19 +695,19 @@ var scopevar = "scope" & $genOid()

var valvar = "val" & $genOid() while p.token != tkBraceRi: c = c+1 - var instructions = p.compileMinValue(i, false) + var instructions = p.compileMinValue(i, false, indent) let v = p.parseMinValue(i) let vs = instructions.pop result = result.concat(instructions) if val.isNil: if not valInitialized: - result.add "var "&valvar&": MinValue" + result.add indent&"var "&valvar&": MinValue" valInitialized = true - result.add valvar&" = "&vs + result.add indent&valvar&" = "&vs elif v.kind == minSymbol: let key = v.symVal if key[0] == ':': - result.add 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)

@@ -716,7 +716,7 @@ raiseInvalid("Invalid dictionary key: " & $v)

eat(p, tkBraceRi) if c mod 2 != 0: raiseInvalid("Invalid dictionary") - result.add "var "&scopevar&" = newScopeRef(nil)" + result.add indent&"var "&scopevar&" = newScopeRef(nil)" result.add op&"MinValue(kind: minDictionary, scope: "&scopevar&")" of tkSymbol: result = @[op&"MinValue(kind: minSymbol, symVal: "&p.a.escapeJson&", column: " & $p.getColumn & ", line: " & $p.lineNumber & ", filename: "&p.filename.escapeJson&")"]
M core/utils.nimcore/utils.nim

@@ -3,7 +3,6 @@ strutils,

critbits, json import - ../packages/nim-sgregex/sgregex, parser, value, scope,

@@ -159,8 +158,8 @@ of JFloat:

result = json.getFloat.newVal of JString: let s = json.getStr - if s.match("^;sym:"): - result = sgregex.replace(s, "^;sym:", "").newSym + if s.startsWith(";sym:"): + result = s.replace(";sym:", "").newSym else: result = json.getStr.newVal of JObject:

@@ -170,8 +169,8 @@ var first = $key[0]

var rest = "" if key.len > 1: rest = key[1..key.len-1] - first = sgregex.replace(first, "[^a-zA-Z0-9_]", "_") - rest = sgregex.replace(rest, "[^a-zA-Z0-9/!?+*._-]", "_") + #first = sgregex.replace(first, peg"[^a-zA-Z0-9_]", "_") + #rest = sgregex.replace(rest, peg"[^a-zA-Z0-9/!?+*._-]", "_") discard i.dset(res, first&rest, i.fromJson(value)) return res of JArray:
M lib/min_http.nimlib/min_http.nim

@@ -6,6 +6,15 @@ ../core/value,

../core/interpreter, ../core/utils +when defined(amd64): + when defined(windows): + {.passL: "-static -Lvendor/openssl/windows -lssl -lcrypto -lws2_32".} + else: + if defined(linux): + {.passL: "-static -Lvendor/openssl/linux -lssl -lcrypto".} + elif defined(macosx): + {.passL: "-Bstatic -Lvendor/openssl/macosx -lssl -lcrypto -Bdynamic".} + var minUserAgent {.threadvar.} : string minUserAgent = "$1 http-module/$2" % [pkgName, pkgVersion]
M lib/min_lang.nimlib/min_lang.nim

@@ -4,9 +4,12 @@ strutils,

sequtils, parseopt, algorithm, - json, - os, - logging + logging, + json +when not defined(mini): + import + os, + ../packages/nim-sgregex/sgregex import ../core/env, ../core/consts,

@@ -14,7 +17,6 @@ ../core/parser,

../core/value, ../core/interpreter, ../core/utils, - ../packages/nim-sgregex/sgregex, ../packages/niftylogger, ../core/scope

@@ -73,6 +75,9 @@

def.symbol("lite?") do (i: In): i.push defined(lite).newVal + def.symbol("mini?") do (i: In): + i.push defined(mini).newVal + def.symbol("from-yaml") do (i: In): let vals = i.expect("string") let s = vals[0]

@@ -136,8 +141,9 @@ if not q1.isQuotation:

q1 = @[q1].newVal isQuot = false symbol = sym.getString - if not symbol.match "^[a-zA-Z_][a-zA-Z0-9/!?+*._-]*$": - raiseInvalid("Symbol identifier '$1' contains invalid characters." % symbol) + when not defined(mini): + if not symbol.match "^[a-zA-Z_][a-zA-Z0-9/!?+*._-]*$": + raiseInvalid("Symbol identifier '$1' contains invalid characters." % symbol) info "[define] $1 = $2" % [symbol, $q1] if i.scope.symbols.hasKey(symbol) and i.scope.symbols[symbol].sealed: raiseUndefined("Attempting to redefine sealed symbol '$1'" % [symbol])

@@ -220,28 +226,40 @@ let vals = i.expect("string")

let s = vals[0] i.push i.parse s.strVal - def.symbol("load") do (i: In): - let vals = i.expect("'sym") - let s = vals[0] - var file = s.getString - if not file.endsWith(".min"): - file = file & ".min" - file = i.pwd.joinPath(file) - info("[load] File: ", file) - if not file.fileExists: - raiseInvalid("File '$1' does not exist." % file) - i.load file + when not defined(mini): + def.symbol("load") 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("[load] File: ", file) + if MINCOMPILED: + var compiledFile = strutils.replace(strutils.replace(file, "\\", "/"), "./", "") + if MINCOMPILEDFILES.hasKey(compiledFile): + MINCOMPILEDFILES[compiledFile](i) + return + file = i.pwd.joinPath(file) + if not file.fileExists: + raiseInvalid("File '$1' does not exist." % file) + i.load file - def.symbol("read") 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("[read] File: ", file) - if not file.fileExists: - raiseInvalid("File '$1' does not exist." % file) - i.push i.read file + def.symbol("read") 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("[read] File: ", file) + if not file.fileExists: + raiseInvalid("File '$1' does not exist." % file) + i.push i.read file + + def.symbol("raw-args") do (i: In): + var args = newSeq[MinValue](0) + for par in commandLineParams(): + args.add par.newVal + i.push args.newVal def.symbol("with") do (i: In): let vals = i.expect("dict", "quot")

@@ -359,7 +377,10 @@ if not hasCatch:

return let e = getCurrentException() var res = newDict(i.scope) - let err = sgregex.replace($e.name, ":.+$", "") + var err = $e.name + let col = err.find(":") + if col >= 0: + err = err[0..col-1] res.objType = "error" i.dset(res, "error", err.newVal) i.dset(res, "message", e.msg.newVal)

@@ -627,12 +648,6 @@ else:

discard i.push opts - def.symbol("raw-args") do (i: In): - var args = newSeq[MinValue](0) - for par in commandLineParams(): - args.add par.newVal - i.push args.newVal - def.symbol("expect") do (i: In): var q: MinValue i.reqQuotationOfSymbols q

@@ -719,7 +734,10 @@ else:

raiseInvalid("Cannot convert a quotation to float.") def.symbol("prompt") do (i: In): - i.eval(""""[$1]\n$$ " (.) => %""") + when defined(mini): + i.push "$ ".newVal + else: + i.eval(""""[$1]\n$$ " (.) => %""") # Sigils
M lib/min_str.nimlib/min_str.nim

@@ -6,8 +6,11 @@ import

../core/parser, ../core/value, ../core/interpreter, - ../core/utils, - ../packages/nim-sgregex/sgregex + ../core/utils + +when not defined(mini): + import + ../packages/nim-sgregex/sgregex proc str_module*(i: In) =

@@ -116,58 +119,59 @@ let str = vals[1]

let index = str.strVal.find(reg.strVal) i.push index.newVal - def.symbol("search") do (i: In): - let vals = i.expect("string", "string") - let reg = vals[0] - let str = vals[1] - 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 + when not defined(mini): + def.symbol("search") do (i: In): + let vals = i.expect("string", "string") + let reg = vals[0] + let str = vals[1] + 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 - def.symbol("match") do (i: In): - let vals = i.expect("string", "string") - let reg = vals[0] - let str = vals[1] - if str.strVal.match(reg.strVal): - i.push true.newVal - else: - i.push false.newVal + def.symbol("match") do (i: In): + let vals = i.expect("string", "string") + let reg = vals[0] + let str = vals[1] + if str.strVal.match(reg.strVal): + i.push true.newVal + else: + i.push false.newVal - def.symbol("replace") do (i: In): - let vals = i.expect("string", "string", "string") - let s_replace = vals[0] - let reg = vals[1] - let s_find = vals[2] - i.push sgregex.replace(s_find.strVal, reg.strVal, s_replace.strVal).newVal + def.symbol("replace") do (i: In): + let vals = i.expect("string", "string", "string") + let s_replace = vals[0] + let reg = vals[1] + let s_find = vals[2] + i.push sgregex.replace(s_find.strVal, reg.strVal, s_replace.strVal).newVal - def.symbol("regex") do (i: In): - let vals = i.expect("string", "string") - let reg = vals[0] - let str = vals[1] - let results = str.strVal =~ reg.strVal - var res = newSeq[MinValue](0) - for r in results: - res.add(r.newVal) - i.push res.newVal + def.symbol("regex") do (i: In): + let vals = i.expect("string", "string") + let reg = vals[0] + let str = vals[1] + let results = str.strVal =~ reg.strVal + var res = newSeq[MinValue](0) + for r in results: + res.add(r.newVal) + i.push res.newVal - def.symbol("semver?") do (i: In): - let vals = i.expect("string") - let v = vals[0].strVal - i.push v.match("^\\d+\\.\\d+\\.\\d+$").newVal - - def.symbol("from-semver") do (i: In): - let vals = i.expect("string") - let v = vals[0].strVal - let parts = v.search("^(\\d+)\\.(\\d+)\\.(\\d+)$") - if parts[0].len == 0: - raiseInvalid("String '$1' is not a basic semver" % v) - var d = newDict(i.scope) - i.dset(d, "major", parts[1].parseInt.newVal) - i.dset(d, "minor", parts[2].parseInt.newVal) - i.dset(d, "patch", parts[3].parseInt.newVal) - i.push d + def.symbol("semver?") do (i: In): + let vals = i.expect("string") + let v = vals[0].strVal + i.push v.match("^\\d+\\.\\d+\\.\\d+$").newVal + + def.symbol("from-semver") do (i: In): + let vals = i.expect("string") + let v = vals[0].strVal + let parts = v.search("^(\\d+)\\.(\\d+)\\.(\\d+)$") + if parts[0].len == 0: + raiseInvalid("String '$1' is not a basic semver" % v) + var d = newDict(i.scope) + i.dset(d, "major", parts[1].parseInt.newVal) + i.dset(d, "minor", parts[2].parseInt.newVal) + i.dset(d, "patch", parts[3].parseInt.newVal) + i.push d def.symbol("to-semver") do (i: In): let vals = i.expect("dict")
M min.nimmin.nim

@@ -1,18 +1,18 @@

-when not defined(windows): - {.passL: "-rdynamic".} import streams, critbits, strutils, os, - json, sequtils, - algorithm, - logging, - dynlib + logging + +when not defined(mini): + import + json, + algorithm, + dynlib import - packages/nimline/nimline, packages/niftylogger, core/env, core/parser,

@@ -28,16 +28,21 @@ lib/min_dict,

lib/min_num, lib/min_str, lib/min_logic, - lib/min_time, - lib/min_io, - lib/min_sys, - lib/min_fs + lib/min_time -when not defined(lite): - import lib/min_http - import lib/min_net - import lib/min_crypto - import lib/min_math +when not defined(mini): + import + packages/nimline/nimline, + lib/min_io, + lib/min_sys, + lib/min_fs + +when not defined(lite) and not defined(mini): + import + lib/min_http, + lib/min_net, + lib/min_crypto, + lib/min_math export env,

@@ -52,72 +57,75 @@

const PRELUDE* = "prelude.min".slurp.strip var NIMOPTIONS* = "" +var MINMODULES* = newSeq[string](0) var customPrelude = "" if logging.getHandlers().len == 0: newNiftyLogger().addHandler() -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 +when not defined(mini): + 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 symbols.mapIt("(" & $it) - if word.startsWith("<"): - return toSeq(MINSYMBOLS.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("\\", "/")]) +when not defined(mini): + 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: - 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("\\", "/")]) + 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 symbols.mapIt("(" & $it) + if word.startsWith("<"): + return toSeq(MINSYMBOLS.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: - dir = getCurrentDir() - return toSeq(walkDir(dir, true)).filterIt(it.path.toLowerAscii.startsWith(f.toLowerAscii)).mapIt("\"$1" % [it.path.replace("\\", "/")]) - return symbols + 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) = setLogFilter(lvlNotice)

@@ -131,14 +139,15 @@ i.lang_module

i.stack_module i.seq_module i.dict_module - i.io_module i.logic_module i.num_module i.str_module - i.sys_module i.time_module - i.fs_module - when not defined(lite): + when not defined(mini): + i.sys_module + i.fs_module + i.io_module + when not defined(lite) and not defined(mini): i.crypto_module i.net_module i.math_module

@@ -152,31 +161,33 @@ except:

warn("Unable to process custom prelude code in $1" % customPrelude) i.eval MINRC.readFile() -type - LibProc = proc(i: In) {.nimcall.} +when not defined(mini): + type + LibProc = proc(i: In) {.nimcall.} -proc dynLib*(i: In) = - discard MINLIBS.existsOrCreateDir - for library in walkFiles(MINLIBS & "/*"): - var modname = library.splitFile.name - var libfile = library.splitFile.name & library.splitFile.ext - if modname.len > 3 and modname[0..2] == "lib": - modname = modname[3..modname.len-1] - let dll = library.loadLib() - if dll != nil: - let modsym = dll.symAddr(modname) - if modsym != nil: - let modproc = cast[LibProc](dll.symAddr(modname)) - i.modproc() - info("[$1] Dynamic module loaded successfully: $2" % [libfile, modname]) + proc dynLib*(i: In) = + discard MINLIBS.existsOrCreateDir + for library in walkFiles(MINLIBS & "/*"): + var modname = library.splitFile.name + var libfile = library.splitFile.name & library.splitFile.ext + if modname.len > 3 and modname[0..2] == "lib": + modname = modname[3..modname.len-1] + let dll = library.loadLib() + if dll != nil: + let modsym = dll.symAddr(modname) + if modsym != nil: + let modproc = cast[LibProc](dll.symAddr(modname)) + i.modproc() + info("[$1] Dynamic module loaded successfully: $2" % [libfile, modname]) + else: + warn("[$1] Library does not contain symbol $2" % [libfile, modname]) else: - warn("[$1] Library does not contain symbol $2" % [libfile, modname]) - else: - warn("Unable to load dynamic library: " & libfile) + warn("Unable to load dynamic library: " & libfile) proc interpret*(i: In, s: Stream) = i.stdLib() - i.dynLib() + when not defined(mini): + i.dynLib() i.open(s, i.filename) discard i.parser.getToken() try:

@@ -194,38 +205,52 @@ except:

discard i.close() -proc compile*(i: In, s: Stream) = +proc minFile*(filename: string, op = "interpret", main = true): seq[string] {.discardable.} + +proc compile*(i: In, s: Stream, main = true): seq[string] = if "nim".findExe == "": error "Nim compiler not found, unable to compile." quit(7) + result = newSeq[string](0) i.open(s, i.filename) discard i.parser.getToken() try: MINCOMPILED = true let nimFile = i.filename.changeFileExt("nim") - notice("Generating $#..." % nimFile) - let r = i.compile() - writeFile(nimFile, r.join("\n")) - let cmd = "nim c $#$#" % [NIMOPTIONS&" ", nimFile] - notice("Calling Nim compiler:") - notice(cmd) - discard execShellCmd(cmd) + if main: + notice("Generating $#..." % nimFile) + result = i.initCompiledFile(MINMODULES) + for m in MINMODULES: + let f = m.replace("\\", "/") + result.add "### $#" % f + notice("- Including: $#" % f) + result = result.concat(minFile(f, "compile", main = false)) + result.add "### $# (main)" % i.filename + result = result.concat(i.compileFile(main)) + writeFile(nimFile, result.join("\n")) + let cmd = "nim c $#$#" % [NIMOPTIONS&" ", nimFile] + notice("Calling Nim compiler:") + notice(cmd) + discard execShellCmd(cmd) + else: + result = result.concat(i.compileFile(main)) except: discard - i.close() + i.close() -proc minStream(s: Stream, filename: string, op = "interpret") = +proc minStream(s: Stream, filename: string, op = "interpret", main = true): seq[string] {.discardable.}= var i = newMinInterpreter(filename = filename) i.pwd = filename.parentDir if op == "interpret": i.interpret(s) + newSeq[string](0) else: - i.compile(s) + i.compile(s, main) proc minStr*(buffer: string) = minStream(newStringStream(buffer), "input") -proc minFile*(filename: string, op = "interpret") = +proc minFile*(filename: string, op = "interpret", main = true): seq[string] {.discardable.} = var fn = filename if not filename.endsWith(".min"): fn &= ".min"

@@ -240,7 +265,7 @@ if fileLines[0].len >= 2 and fileLines[0][0..1] == "#!":

contents = ";;\n" & fileLines[1..fileLines.len-1].join("\n") else: contents = fileLines.join("\n") - minStream(newStringStream(contents), fn, op) + minStream(newStringStream(contents), fn, op, main) proc minFile*(file: File, filename="stdin", op = "interpret") = var stream = newFileStream(filename)

@@ -276,26 +301,33 @@ echo " ".repeat(n.len) & "}"

else: echo " $1" % [$i.stack[i.stack.len - 1]] -proc minRepl*(i: var MinInterpreter, simple = false) = +proc minSimpleRepl*(i: var MinInterpreter) = i.stdLib() - i.dynLib() + when not defined(mini): + i.dynLib() var s = newStringStream("") i.open(s, "<repl>") var line: string - var ed = initEditor(historyFile = MINHISTORY) - if simple: - while true: - i.push("prompt".newSym) - let vals = i.expect("string") - let v = vals[0] - let prompt = v.getString() - stdout.write(prompt) - stdout.flushFile() - line = stdin.readLine() - let r = i.interpret($line) - if $line != "": - i.printResult(r) - else: + while true: + i.push("prompt".newSym) + let vals = i.expect("string") + let v = vals[0] + let prompt = v.getString() + stdout.write(prompt) + stdout.flushFile() + line = stdin.readLine() + let r = i.interpret($line) + if $line != "": + i.printResult(r) + +when not defined(mini): + proc minRepl*(i: var MinInterpreter) = + i.stdLib() + i.dynLib() + var s = newStringStream("") + i.open(s, "<repl>") + var line: string + var ed = initEditor(historyFile = MINHISTORY) while true: let symbols = toSeq(i.scope.symbols.keys) ed.completionCallback = proc(ed: LineEditor): seq[string] =

@@ -310,9 +342,13 @@ let r = i.interpret($line)

if $line != "": i.printResult(r) -proc minRepl*(simple = false) = + proc minRepl*() = + var i = newMinInterpreter(filename = "<repl>") + i.minRepl() + +proc minSimpleRepl*() = var i = newMinInterpreter(filename = "<repl>") - i.minRepl(simple) + i.minSimpleRepl() when isMainModule:

@@ -326,28 +362,44 @@ var INSTALL = false

var UNINSTALL = false var COMPILE = false var libfile = "" + var exeName = "min" + var installOpt = "\n -—install:<lib> Install dynamic library file <lib>\n" + var uninstallOpt = "\n —-uninstall:<lib> Uninstall dynamic library file <lib>\n" + var iOpt = "\n -i, --interactive Start $1 shell (with advanced prompt)\n" + when defined(lite): + exeName = "litemin" + when defined(mini): + installOpt = "" + uninstallOpt = "" + iOpt = "" + exeName = "minimin" - let usage* = """ $1 v$2 - a tiny concatenative shell and programming language + let usage* = """ $exe v$version - a tiny concatenative programming language (c) 2014-2020 Fabio Cevasco Usage: - min [options] [filename] + $exe [options] [filename] Arguments: - filename A $1 file to interpret or compile (default: STDIN). - Options: - -—install:<lib> Install dynamic library file <lib> - —-uninstall:<lib> Uninstall dynamic library file <lib> + filename A $exe file to interpret or compile (default: STDIN). + Options:$installOpt$uninstallOpt -c, --compile Compile the specified file - -e, --evaluate Evaluate a $1 program inline - -h, --help Print this help - -i, --interactive Start $1 shell (with advanced prompt) - -j, --interactive-simple Start $1 shell (without advanced prompt) + -e, --evaluate Evaluate a $exe program inline + -h, --help Print this help$iOpt + -j, --interactive-simple Start $exe shell (without advanced prompt) -l, --log Set log level (debug|info|notice|warn|error|fatal) Default: notice + -m, --module-path Specify a directory containing the .min files to include in the + 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""" % [pkgName, pkgVersion] + -v, —-version Print the program version""" % [ + "exe", exeName, + "version", pkgVersion, + "installOpt", installOpt, + "uninstallOpt", uninstallOpt, + "iOpt", iOpt + ] var file, s: string = "" var args = newSeq[string](0)

@@ -363,6 +415,10 @@ of cmdLongOption, cmdShortOption:

case key: of "compile", "c": COMPILE = true + of "module-path", "m": + for f in walkDirRec(val): + if f.endsWith(".min"): + MINMODULES.add f of "prelude", "p": customPrelude = val of "log", "l":

@@ -429,8 +485,14 @@ fatal("Unable to uninstall library file: " & libfile)

quit(6) notice("Dynamic linbrary uninstalled successfully: " & libfile.extractFilename) quit(0) - elif REPL or SIMPLEREPL: - minRepl(SIMPLEREPL) + elif REPL: + when defined(mini): + minSimpleRepl() + else: + minRepl() + quit(0) + elif SIMPLEREPL: + minSimpleRepl() quit(0) else: minFile stdin, "stdin", op
M min.nimsmin.nims

@@ -14,29 +14,12 @@ switch("define", "ssl")

switch("opt", "size") switch("threads", "on") - when defined(windows): - switch("passL","-static") # TODO", change once issue nim#15220 is resolved switch("define", "noOpenSSLHacks") switch("dynlibOverride", "ssl-") switch("dynlibOverride", "crypto-") - switch("passL","-Lvendor/openssl/windows") - switch("passL","-lssl") - switch("passL","-lcrypto") - switch("passL","-lws2_32") switch("define", "sslVersion:(") else: switch("dynlibOverride", "ssl") - switch("dynlibOverride", "crypto") - if defined(linux): - switch("passL","-static") - switch("passL","-Lvendor/openssl/linux") - switch("passL","-lssl") - switch("passL","-lcrypto") - elif defined(macosx): - switch("passL","-Bstatic") - switch("passL","-Lvendor/openssl/macosx") - switch("passL","-lssl") - switch("passL","-lcrypto") - switch("passL","-Bdynamic") + switch("dynlibOverride", "crypto")
M next-release.mdnext-release.md

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

* Added the possibility to "compile" min files into sibgle executables. This is achieved by converting the specified min file to its raw nim code equivalent and then calling the Nim compiler (which in turns calls the C compiler). * Added **compiled?** symbol which returns true if the program has been compiled. +* Added the possibility of including a path containing additional **.min** files to compile along with the main file (**-m**, **--module-path**). +* Added the possibility to compile a bare-bones version of min specifying the **-d:mini** compilation flag. +* Added **mini?** symbol which returns true if min was compiled specifying **-d:mini**. +* Now distributing precompiled **litemin** and **minimin** executables as well.
M prelude.minprelude.min

@@ -1,20 +1,24 @@

; Imports 'str import -'io import 'logic import 'num import -'sys import 'stack import 'seq import 'dict import 'time import -'fs import -'lite? ( +(mini?) ( ( - 'crypto import - 'math import - 'net import - 'http import + 'io import + 'fs import + 'sys import + ) ROOT with +) unless +(lite? mini? or) ( + ( + 'crypto import + 'math import + 'net import + 'http import ) ROOT with ) unless ; Unseal prompt symbol
M run.minrun.min

@@ -23,8 +23,6 @@ (taskspec size 1 >) (taskspec 1 get @subtask) when

"./tasks/$#.min" (task) =% :task-file -(task-file exists? not) ("Task '$1' does not exist" (task) =% error 2 exit) when - ; Load task module task-file load
M site/assets/styles/min-lang.csssite/assets/styles/min-lang.css

@@ -114,11 +114,11 @@ b, strong, dt {

font-weight: 400; } -code, pre, .kwd, .cmd, .file, .dir, kbd, .kbd, .min-terminal { +code, pre, .kwd, .cmd, .file, .dir, kbd, .kbd, .ext, .min-terminal { font-family: 'Inconsolata', monospace; } -.kwd, .kwd, .cmd, .file, .dir, kbd, .kbd { +.kwd, .kwd, .cmd, .file, .dir, .ext, kbd, .kbd { font-weight: bold; }
M site/contents/download.mdsite/contents/download.md

@@ -38,13 +38,40 @@ ### Additional build options

#### -d:lite -If the **d:lite** flag is specified, an even more minimal executable file will be generated, however the following functionalities will not be available: +If the **d:lite** flag is specified, a more minimal executable file ("litemin") will be generated, however the following functionalities will not be available: * The {#link-module||crypto#} * The {#link-module||net#} * The {#link-module||http#} * The {#link-module||math#} * The {#link-operator||sys||zip#} and {#link-operator||sys||unzip#} operators. + +#### -d:mini + +If the **d:mini** flag is specified, an even more minimal executable file ("minimin") will be generated, however the following functionalities will not be available: + +* The {#link-module||io#} +* The {#link-module||fs#} +* The {#link-module||sys#} +* The {#link-module||net#} +* The {#link-module||http#} +* The {#link-module||math#} +* The following operators: + * {#link-operator||lang||load#} + * {#link-operator||lang||read#} + * {#link-operator||lang||raw-args#} + * {#link-operator||str||search#} + * {#link-operator||str||match#} + * {#link-operator||str||replace#} + * {#link-operator||str||regex#} + * {#link-operator||str||semver?#} + * {#link-operator||str||from-semver#} + +Additionally: + +* No checks will be performed when defining symbols +* Only the simple REPL will be available +* There will be no support for dynamic libraries ## Running then min Shell

@@ -105,10 +132,18 @@ > %min-terminal%

> > [$](class:prompt) min -c myfile.min -n:-d:release +Additionally, you can also use `-m:<path>` (or `--module-path`) to specify one path containing [.min](class:ext) files which will be compiled as well (but not executed) along with the specified file. Whenever a {#link-operator||lang||load#} symbol is used to load an external [.min](class:ext) file, it will attempt to load from the pre-loaded files first before searching the filesystem. + +For example, the following command executed in the root folder of the min project will compile [run.min](class:file) along with all [.min](class:ext) files included in the [task](class:dir) and its subfolders: + +> %min-terminal% +> +> [$](class:prompt) min -c run.min -m:tasks + > %note% > Note > -> In order to successfully compile min files, Nim must be installed on your system and min must be installed via nimble. +> In order to successfully compile [.min](class.ext) files, Nim must be installed on your system and min must be installed via nimble. ## Syntax Highlighting
M tasks/build.mintasks/build.min

@@ -8,19 +8,27 @@ :prog (prog which "" ==) ("$# is not available" (prog) =% error 1 exit) when

) :required ( :target-os + :variant + " " :d-variant + "min" :o-variant + (variant length 0 >) ( + "-d:$# " (variant) =% @d-variant + "$#min" (variant) =% @o-variant + ) when "nim" required - "Building - $# (x64)" (target-os) =% notice - "nim c -d:release --cpu:amd64 --os:$# min" (target-os) =% ! + "Building $# - $# (x64)" (o-variant target-os) =% notice + "nim c -d:release --cpu:amd64 --os:$# $#-o:$# min" (target-os d-variant o-variant) =% puts ! {} target-os %os config /version %version + o-variant %exe pack ) :cz ( :vdata - "min" :exe - (vdata /os "windows" ==) ("min.exe" @exe) when - "min_v$version:_$os:_x64.zip" :fn + vdata /exe :exe + (vdata /os "windows" ==) ("$#.exe" (exe) =% @exe) when + "$exe:_v$version:_$os:_x64.zip" :fn fn vdata dpairs % ":" "" replace @fn "Compressing: $#" (fn) =% notice (exe) => fn zip

@@ -49,17 +57,41 @@

; Define module {} ( - os cz + "lite" os cz +) %lite +( + "mini" os cz +) %mini +( + "" os cz ) %default ( - "linux" cz + "" "linux" cz ) %linux ( - "macosx" cz + "lite" "linux" cz +) %linux-lite +( + "mini" "linux" cz +) %linux-mini +( + "" "macosx" cz ) %macosx ( - "windows" cz + "lite" "macosx" cz +) %macosx-lite +( + "mini" "macosx" cz +) %macosx-min +( + "" "windows" cz ) %windows +( + "lite" "windows" cz +) %windows-lite +( + "mini" "windows" cz +) %windows-mini 'build-guide %guide 'build-site %site (
M tasks/release.mintasks/release.min

@@ -10,8 +10,14 @@ build-tasks ^guide

build-tasks ^site build-tasks ^vim build-tasks ^windows + build-tasks ^windows-lite + build-tasks ^windows-mini build-tasks ^linux + build-tasks ^linux-lite + build-tasks ^linux-mini build-tasks ^macosx + build-tasks ^macosx-lite + build-tasks ^macosx-mini github-tasks ^update github-tasks ^upload ssh-tasks ^build