all repos — min @ fde00ede82fa6aed1a26c58462ac455d38004bbc

A small but practical concatenative programming language.

Merge branch 'next'
h3rald h3rald@h3rald.com
Sat, 06 Feb 2021 08:58:02 +0000
commit

fde00ede82fa6aed1a26c58462ac455d38004bbc

parent

aa262a2a6616b52b9fc3a932f4ac3c4b3b54544a

M help.jsonhelp.json

@@ -722,7 +722,7 @@ "name": "exit",

"signature": "int ==> " }, "expect": { - "description": "Validates the first _n_ elements of the stack against the type descriptions specified in quot1 (_n_ is quot1's length) and if all the elements are valid returns them wrapped in quot2 (in reverse order). \n\n \n Tips\n \n * You can specify a typed dictionary by prepending the type name with dict:. Example: dict:socket\n * You can specify two or more matching types by separating the type names with a pipe: string|quot\n\n \n Example\n \n Assuming that the following elements are on the stack (from top to bottom): \n \n 1 \"test\" 3.4\n \n the following program evaluates to true:\n \n (int string num) expect (3.4 \"test\" 1) ==", + "description": "Validates the first _n_ elements of the stack against the type descriptions specified in quot1 (_n_ is quot1's length) and if all the elements are valid returns them wrapped in quot2 (in reverse order). \n\n \n Tips\n \n * You can specify a typed dictionary by prepending the type name with dict:. Example: dict:socket\n * You can specify two or more matching types by separating combined together in a logical type expression, e.g.: string|quot\n\n \n Example\n \n Assuming that the following elements are on the stack (from top to bottom): \n \n 1 \"test\" 3.4\n \n the following program evaluates to true:\n \n (int string num) expect (3.4 \"test\" 1) ==", "kind": "symbol", "name": "expect", "signature": "quot1 ==> quot2"

@@ -1290,6 +1290,12 @@ "description": "Pushes a copy of the second element on top of the stack.",

"kind": "symbol", "name": "over", "signature": "a1 a2 ==> a1 a2 a1" + }, + "parent-scope": { + "description": "Returns a dictionary dict2 holding a reference to the parent scope of dict1 or null if dict1 is ROOT.", + "kind": "symbol", + "name": "parent-scope", + "signature": "dict1 ==> dict2" }, "parse": { "description": "Parses str and returns a quoted program quot.",
M min.vimmin.vim

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

" Vim syntax file " Language: min " Maintainer: Fabio Cevasco -" Last Change: 30 Jan 2021 -" Version: 0.31.0 +" Last Change: 06 Feb 2021 +" Version: 0.32.0 if exists("b:current_syntax") finish

@@ -11,7 +11,7 @@

setl iskeyword=@,36-39,+,-,*,.,/,:,~,!,48-57,60-65,94-95,192-255 setl iskeyword+=^ -syntax keyword minDefaultSymbol ! != $ % & && ' * + - -> -inf . .. / : :: < <= =% =-= == ==> => =~ > >< >= >> ? @ ROOT ^ abs accept acos aes all? and any? append apply apply-interpolate args asin ask atan atime avg bind bitand bitnot bitor bitxor boolean boolean? capitalize case cd ceil chmod choose chr clear clear-stack cleave close column-print compiled? concat confirm connect cons cos cosh cp cpu crypto ctime d2r datetime ddel ddup debug decode decode-url define defined-sigil? defined-symbol? delete-sigil delete-symbol dequote dget dhas? dict dictionary? difference dip dir? dirname div dkeys download dpairs dpick drop dsdelete dset dsget dshas? dsinit dspost dsput dsquery dsread dstore dswrite dtype dup dvalues e encode encode-url env? error escape eval even? exists? exit expect expect-all expect-any expect-empty-stack fappend fatal file? filename filter find first flatten float float? floor foreach format-error fperms fread from-json from-semver from-yaml fs fsize fstats ftype fwrite get get-content get-env get-stack getchr gets hardlink harvest help hidden? http id if import in? indent indexof inf infix-dequote info insert integer integer? interpolate intersection invoke io join keep lambda last length line-info linrec listen lite? ln load load-symbol log10 log2 logic loglevel loglevel? lowercase ls ls-r map map-reduce mapkey match math md4 md5 med mini? mkdir mod mtime mv nan net newline nip not notice now null? num number? odd? one? operator opts or ord os over parse parse-url partition password pi pick pop pow pred prefix prefix-dequote prepend print product prompt publish put-env putchr puts quit quotation? quote quote-map r2g raise random randomize range raw-args recv recv-line reduce regex reject remove remove-symbol repeat replace replace-apply request require rest return reverse rm rmdir rolldown rollup round run save-symbol saved-symbols scope scope-sigils scope-symbols seal-sigil seal-symbol sealed-sigil? sealed-symbol? search search-all semver-inc-major semver-inc-minor semver-inc-patch semver? send seq set set-stack sha1 sha224 sha256 sha384 sha512 shl shorten shr sigil-help sigils sin sinh sip size sleep slice socket sort source split spread sqrt stack start-server stop-server str string string? stringlike? strip substr succ suffix sum swap swons symbol-help symbols symlink symlink? symmetric-difference sys system take tan tanh tap tau tformat time timeinfo times timestamp titleize to-json to-semver to-timestamp to-yaml trunc try type type? union unless unmapkey unseal-sigil unseal-symbol unzip uppercase version warn when which while with xor zip || +syntax keyword minDefaultSymbol ! != $ % & && ' * + - -> -inf . .. / : :: < <= =% =-= == ==> => =~ > >< >= >> ? @ ROOT ^ abs accept acos aes all? and any? append apply apply-interpolate args asin ask atan atime avg bind bitand bitnot bitor bitxor boolean boolean? capitalize case cd ceil chmod choose chr clear clear-stack cleave close column-print compiled? concat confirm connect cons cos cosh cp cpu crypto ctime d2r datetime ddel ddup debug decode decode-url define defined-sigil? defined-symbol? delete-sigil delete-symbol dequote dget dhas? dict dictionary? difference dip dir? dirname div dkeys download dpairs dpick drop dsdelete dset dsget dshas? dsinit dspost dsput dsquery dsread dstore dswrite dtype dup dvalues e encode encode-url env? error escape eval even? exists? exit expect expect-all expect-any expect-empty-stack fappend fatal file? filename filter find first flatten float float? floor foreach format-error fperms fread from-json from-semver from-yaml fs fsize fstats ftype fwrite get get-content get-env get-stack getchr gets hardlink harvest help hidden? http id if import in? indent indexof inf infix-dequote info insert integer integer? interpolate intersection invoke io join keep lambda lambda-bind last length line-info linrec listen lite? ln load load-symbol log10 log2 logic loglevel loglevel? lowercase ls ls-r map map-reduce mapkey match math md4 md5 med mini? mkdir mod mtime mv nan net newline nip not notice now null? num number? odd? one? operator opts or ord os over parent-scope parse parse-url partition password pi pick pop pow pred prefix prefix-dequote prepend print product prompt publish put-env putchr puts quit quotation? quote quote-map r2g raise random randomize range raw-args recv recv-line reduce regex reject remove remove-symbol repeat replace replace-apply request require rest return reverse rm rmdir rolldown rollup round run save-symbol saved-symbols scope scope-sigils scope-symbols seal-sigil seal-symbol sealed-sigil? sealed-symbol? search search-all semver-inc-major semver-inc-minor semver-inc-patch semver? send seq set set-stack sha1 sha224 sha256 sha384 sha512 shl shorten shr sigil-help sigils sin sinh sip size sleep slice socket sort source split spread sqrt stack start-server stop-server str string string? stringlike? strip substr succ suffix sum swap swons symbol-help symbols symlink symlink? symmetric-difference sys system take tan tanh tap tau tformat time timeinfo times timestamp titleize to-json to-semver to-timestamp to-yaml trunc try type type? typealias union unless unmapkey unseal-sigil unseal-symbol unzip uppercase version warn when which while with xor zip || ~ syntax match minDefaultSigil ;\<[/:@'~!?$%&=<>#^*#+]; contained syntax match minQuote ;\<['];
M min.ymlmin.yml

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

author: Fabio Cevasco description: A tiny concatenative programming language and shell. -id: 36890951 +id: 37148413 name: min -version: 0.31.0+version: 0.32.0
M minNotepad++.xmlminNotepad++.xml

@@ -24,7 +24,7 @@ <Keywords name="Folders in code2, close"></Keywords>

<Keywords name="Folders in comment, open"></Keywords> <Keywords name="Folders in comment, middle"></Keywords> <Keywords name="Folders in comment, close"></Keywords> - <Keywords name="Keywords1">puts import load apply args bind bool case define define-sigil delete-symbol delete-sigil dequote expect expect-empty-stack float foreach format-error from-json from-yaml gets if import infix-dequote int invoke linrec load load-symbol loglevel operator opts parse prefix-dequote prompt publish quit quote quote-bind quote-define raise read remove-symbol require reverse-expect-dequote return save-symbol scope saved-symbols scope-sigils scope-symbols seal-symbol seal-sigil sigils source string symbols tap times to-json to-yaml try unless unseal-symbol unseal-sigil when while with clear-stack cleave cons dip dup get-stack id keep nip over pick pop rolldown rollup set-stack sip spread swap swons append get concat drop filter find first flatten harvest insert last map map-reduce partition prepend quote-map reduce reject remove rest reverse set shorten size slice sort take ddup ddel dget dkeys dpick dpairs dset dtype dvalues dsdelete dsget dsinit dspost dsput dsquery dsread dswrite ask choose clear column-print confirm debug error fappend fatal fread fwrite getchr info mapkey newline notice password print putchr type unmapkey warning atime ctime fperms fsize fstats ftype mtime expect-all expect-any apply-interpolate capitalize chr escape from-semver indent indexof interpolate join length lowercase match ord parse-url prefix repeat replace regex search semver-inc-major semver-inc-minor semver-inc-patch split strip substr suffix titleize to-semver uppercase chmod cd cp cpu dirname filename get-env hardlink ls ls-r mkdir mv os put-env rm rmdir run sleep symlink system unzip which zip div mod pred random succ sum now timestamp timeinfo to-timestamp datetime tformat aes decode encode md4 md5 sha1 sha224 sha256 sha384 sha512 acos asin atan ceil cos cosh d2r floor ln log10 log2 pow r2d round sin sinh sqrt tan tanh tau trunc accept close connect listen port recv recv-line send socket download get-content request start-server stop-server abs lambda</Keywords> + <Keywords name="Keywords1">puts import load apply args bind bool case define define-sigil delete-symbol delete-sigil dequote expect expect-empty-stack float foreach format-error from-json from-yaml gets if import infix-dequote int invoke linrec load load-symbol loglevel operator opts parse prefix-dequote prompt publish quit quote quote-bind quote-define raise read remove-symbol require reverse-expect-dequote return save-symbol scope saved-symbols scope-sigils scope-symbols seal-symbol seal-sigil sigils source string symbols tap times to-json to-yaml try unless unseal-symbol unseal-sigil when while with clear-stack cleave cons dip dup get-stack id keep nip over pick pop rolldown rollup set-stack sip spread swap swons append get concat drop filter find first flatten harvest insert last map map-reduce partition prepend quote-map reduce reject remove rest reverse set shorten size slice sort take ddup ddel dget dkeys dpick dpairs dset dtype dvalues dsdelete dsget dsinit dspost dsput dsquery dsread dswrite ask choose clear column-print confirm debug error fappend fatal fread fwrite getchr info mapkey newline notice password print putchr type unmapkey warning atime ctime fperms fsize fstats ftype mtime expect-all expect-any apply-interpolate capitalize chr escape from-semver indent indexof interpolate join length lowercase match ord parse-url prefix repeat replace regex search semver-inc-major semver-inc-minor semver-inc-patch split strip substr suffix titleize to-semver uppercase chmod cd cp cpu dirname filename get-env hardlink ls ls-r mkdir mv os put-env rm rmdir run sleep symlink system unzip which zip div mod pred random succ sum now timestamp timeinfo to-timestamp datetime tformat aes decode encode md4 md5 sha1 sha224 sha256 sha384 sha512 acos asin atan ceil cos cosh d2r floor ln log10 log2 pow r2d round sin sinh sqrt tan tanh tau trunc accept close connect listen port recv recv-line send socket download get-content request start-server stop-server abs typealias lambda</Keywords> <Keywords name="Keywords2">== &gt;= &lt;= &gt; &lt; != and expect-all expect-any or not xor</Keywords> <Keywords name="Keywords3">: + @ = # ~ ^ * &gt; &lt; / ? % $ ! &amp;</Keywords> <Keywords name="Keywords4">eval exit ROOT version</Keywords>
M minpkg/core/scope.nimminpkg/core/scope.nim

@@ -42,13 +42,13 @@ result = false

# check if a symbol already exists in current scope if not scope.isNil and scope.symbols.hasKey(key): if not override and scope.symbols[key].sealed: - raiseInvalid("Symbol '$1' is sealed." % key) + 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) + result = scope.parent.setSymbol(key, value, override) proc getSigil*(scope: ref MinScope, key: string): MinOperator = if scope.sigils.hasKey(key):

@@ -93,4 +93,4 @@ proc previous*(scope: ref MinScope): ref MinScope =

if scope.parent.isNil: return scope else: - return scope.parent+ return scope.parent
M minpkg/core/utils.nimminpkg/core/utils.nim

@@ -171,6 +171,33 @@ return res.newVal

# Validators +proc validate*(i: In, value: MinValue, t: string, generics: var CritBitTree[string]): bool {.gcsafe.} + +proc validateValueType*(i: var MinInterpreter, element: string, value: MinValue, generics: var CritBitTree[string], vTypes: var seq[string], c: int): bool {.gcsafe.} = + vTypes.add value.typeName + let ors = element.split("|") + for to in ors: + let ands = to.split("&") + var andr = true + for ta in ands: + var t = ta + var neg = false + if t.len > 1 and t[0] == '!': + t = t[1..t.len-1] + neg = true + andr = i.validate(value, t, generics) + if neg: + andr = not andr + if not andr: + if neg: + vTypes[c] = t + else: + vTypes[c] = value.typeName + break + if andr: + result = true + break + proc basicValidate*(i: In, value: MinValue, t: string): bool = case t: of "bool":

@@ -196,6 +223,8 @@ return value.isString

of "a": return true else: + let tc = "typeclass:$#" % t + let ta = "typealias:$#" % t if t.contains(":"): var split = t.split(":") # Typed dictionaries

@@ -203,7 +232,14 @@ if split[0] == "dict":

if value.isTypedDictionary(split[1]): return true return false - elif i.scope.hasSymbol("typeclass:$#" % t): + elif i.scope.hasSymbol(ta): + # Custom type alias + let element = i.scope.getSymbol(ta).val.getString + var fakeGenerics: CritBitTree[string] + var vTypes = newSeq[string](0) + var c = 0 + return i.validateValueType(element, value, fakeGenerics, vTypes, c) + elif i.scope.hasSymbol(tc): # Custom type class var i2 = i.copy(i.filename) i2.withScope():

@@ -235,12 +271,23 @@ if ts.contains(s):

return true if i.scope.hasSymbol("typeclass:$#" % s): return true - for tt in s.split("|"): - if not ts.contains(tt) and not tt.startsWith("dict:") and not i.scope.hasSymbol("typeclass:$#" % tt): - return false + for ta in s.split("|"): + for to in ta.split("&"): + var tt = to + if to.len < 2: + return false + if to[0] == '!': + tt = to[1..to.len-1] + if not ts.contains(tt) and not tt.startsWith("dict:") and not i.scope.hasSymbol("typeclass:$#" % tt): + let ta = "typealias:$#" % tt + if i.scope.hasSymbol(ta): + return i.validType(i.scope.getSymbol(ta).val.getString) + return false return true + -proc expect*(i: var MinInterpreter, elements: varargs[string], generics: var CritBitTree[string]): seq[MinValue] = +# The following is used in operator signatures +proc expect*(i: var MinInterpreter, elements: varargs[string], generics: var CritBitTree[string]): seq[MinValue] {.gcsafe.}= let sym = i.currSym.getString var valid = newSeq[string](0) result = newSeq[MinValue](0)

@@ -258,54 +305,26 @@ var other = ""

if valid.len > 0: other = valid.reverse.join(" ") & " " result &= "- got: " & invalid & " " & other & sym + var res = false + var vTypes = newSeq[string](0) + var c = 0 for el in elements: - var element = el let value = i.pop result.add value - var split = element.split("|") - if split.len > 1: - var res = false - for t in split: - if i.validate(value, t, generics): - res = true - break - if not res: - raiseInvalid(message(value.typeName, elements, generics)) - elif not i.validate(value, element, generics): - raiseInvalid(message(value.typeName, elements, generics)) - if generics.hasKey(el): + res = i.validateValueType(el, value, generics, vTypes, c) + if res: + valid.add el + elif generics.hasKey(el): valid.add(generics[el]) else: - valid.add element - -proc expect*(i: var MinInterpreter, elements: varargs[string]): seq[MinValue] = - let stack = elements.reverse.join(" ") - let sym = i.currSym.getString - var valid = newSeq[string](0) - result = newSeq[MinValue](0) - let message = proc(invalid: string): string = - result = "Incorrect values found on the stack:\n" - result &= "- expected: " & stack & " $1\n" % sym - var other = "" - if valid.len > 0: - other = valid.reverse.join(" ") & " " - result &= "- got: " & invalid & " " & other & sym - for element in elements: - let value = i.pop - result.add value - var split = element.split("|") - if split.len > 1: - var res = false - for t in split: - if i.validate(value, t): - res = true - break - if not res: - raiseInvalid(message(value.typeName)) - elif not i.validate(value, element): - raiseInvalid(message(value.typeName)) - valid.add element + raiseInvalid(message(vTypes[c], elements, generics)) + c = c+1 +# The following is used in expect symbol and native symbol expectations. +proc expect*(i: var MinInterpreter, elements: varargs[string]): seq[MinValue] = + var c: CritBitTree[string] + return i.expect(elements, c) + proc reqQuotationOfQuotations*(i: var MinInterpreter, a: var MinValue) = a = i.pop if not a.isQuotation:
M minpkg/lib/min_lang.nimminpkg/lib/min_lang.nim

@@ -34,7 +34,7 @@ let HELP = HELPFILE.parseJson

when not defined(mini): - def.symbol("from-json") do (i: In): + def.symbol("from-json") do (i: In) {.gcsafe.}: let vals = i.expect("str") let s = vals[0] i.push i.fromJson(s.getString.parseJson)

@@ -198,6 +198,7 @@ var inExpects= newSeq[string](0)

var inVars = newSeq[string](0) var outExpects= newSeq[string](0) var outVars = newSeq[string](0) + var rawOutVars = newSeq[string](0) var generics: CritBitTree[string] var origGenerics: CritBitTree[string] var o = false

@@ -215,7 +216,7 @@ let t = vv.qVal[0].getString

let g = vv.qVal[1].getString if not i.validType(t): raiseInvalid("Invalid type '$#' in generic in signature at position $#" % [$t, $(c+1)]) - if g[0] != ':': + if g[0] != ':' and g[0] != '^': raiseInvalid("No mapping symbol specified in generic in signature at position $#" % $(c+1)) v = g[1..g.len-1] generics[v] = t

@@ -238,12 +239,17 @@ if tv.symVal == "typeclass" and inExpects.len > 0:

raiseInvalid("typeclasses can only have one input value") inExpects.add v else: - if v[0] != ':': - raiseInvalid("No mapping symbol specified in signature at position $#" % $(c+1)) + if v[0] != ':' and v[0] != '^': + raiseInvalid("No capturing symbol specified in signature at position $#" % $(c+1)) else: if o: + if v[0] == '^' and outExpects[outExpects.len-1] != "quot": + raiseInvalid("Only quotations can be captured to a lambda, found $# instead at position $#" % [outExpects[outExpects.len-1], $(c+1)]) + rawOutVars.add v outVars.add v[1..v.len-1] else: + if v[0] == '^': + raiseInvalid("A lambda capturing symbol was specified in signature at position $#. Lambda capturing symbols are only allowed for output values" % $(c+1)) inVars.add v[1..v.len-1] c.inc() if not o:

@@ -286,8 +292,9 @@ except MinReturnException:

discard # Validate output for k in 0..outVars.len-1: - i.pushSym outVars[k] - let x = i.peek + var x = i.scope.symbols[outVars[k]].val + if rawOutVars[k][0] == ':': + x = x.qVal[0] if t == "constructor": x.objType = n let o = outExpects[k]

@@ -301,12 +308,13 @@ break

else: r = i.validate(x, o, generics) if not r: - discard i.pop - var tp = t + var tp = o if generics.hasKey(o): tp = generics[o] generics = origGenerics - raiseInvalid("Invalid value for output symbol '$#'. Expected $#, found $#" % [outVars[k], tp, $x]) + raiseInvalid("Invalid value for output symbol '$#'. Expected $#, found $#" % [outVars[k], tp, $x]) + # Push output on stack + i.pushSym outVars[k] generics = origGenerics # Define symbol/sigil var doc = newJObject()

@@ -468,6 +476,21 @@ 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]) i.scope.symbols[symbol] = MinOperator(kind: minValOp, val: q1, sealed: false, quotation: isQuot) + + def.symbol("typealias") do (i: In): + let vals = i.expect("'sym", "'sym") + let sym = vals[0].getString + var s = vals[1].getString + if not i.validType(s): + raiseInvalid("Invalid type expression: $#" % s) + let symbol = "typealias:"&sym + when not defined(mini): + if not sym.match USER_SYMBOL_REGEX: + raiseInvalid("Symbol identifier '$1' contains invalid characters." % sym) + info "[typealias] $1 = $2" % [sym, s] + if i.scope.symbols.hasKey(symbol) and i.scope.symbols[symbol].sealed: + raiseUndefined("Attempting to redefine sealed symbol '$1'" % [symbol]) + i.scope.symbols[symbol] = MinOperator(kind: minValOp, val: s.newVal, sealed: false, quotation: false) def.symbol("lambda") do (i: In): let vals = i.expect("'sym", "quot")

@@ -478,7 +501,7 @@ symbol = sym.getString

when not defined(mini): if not symbol.match USER_SYMBOL_REGEX: raiseInvalid("Symbol identifier '$1' contains invalid characters." % symbol) - info "[define] $1 = $2" % [symbol, $q1] + info "[lambd] $1 = $2" % [symbol, $q1] if i.scope.symbols.hasKey(symbol) and i.scope.symbols[symbol].sealed: raiseUndefined("Attempting to redefine sealed symbol '$1'" % [symbol]) i.scope.symbols[symbol] = MinOperator(kind: minValOp, val: q1, sealed: false, quotation: true)

@@ -495,6 +518,17 @@ info "[bind] $1 = $2" % [symbol, $q1]

let res = i.scope.setSymbol(symbol, MinOperator(kind: minValOp, val: q1, quotation: isQuot)) if not res: raiseUndefined("Attempting to bind undefined symbol: " & symbol) + + def.symbol("lambda-bind") do (i: In): + let vals = i.expect("'sym", "quot") + let sym = vals[0] + var q1 = vals[1] + var symbol: string + symbol = sym.getString + info "[lambda-bind] $1 = $2" % [symbol, $q1] + let res = i.scope.setSymbol(symbol, MinOperator(kind: minValOp, val: q1, quotation: true)) + if not res: + raiseUndefined("Attempting to lambda-bind undefined symbol: " & symbol) def.symbol("delete-symbol") do (i: In): let vals = i.expect("'sym")

@@ -517,6 +551,18 @@ dict.filename = i.filename

dict.scope = i.scope i.push dict + def.symbol("parent-scope") do (i: In): + let vals = i.expect("dict") + let d = vals[0] + if d.scope.parent.isNil: + i.push newNull() + return + var dict = newDict(d.scope.parent) + dict.objType = "module" + dict.filename = i.filename + dict.scope = d.scope.parent + i.push dict + def.symbol("type") do (i: In): let vals = i.expect("a") i.push vals[0].typeName.newVal

@@ -573,6 +619,7 @@ let lines = desc.split("\n")

echo "" for l in lines: echo " " & l + echo "===" if i.scope.hasSymbol(s): found = true let sym = i.scope.getSymbol(s)

@@ -889,14 +936,14 @@ let vals = i.expect("'sym")

let sym = vals[0].getString var s = i.scope.getSymbol(sym) s.sealed = true - i.scope.setSymbol(sym, s) + i.scope.setSymbol(sym, s, true) def.symbol("seal-sigil") do (i: In): let vals = i.expect("'sym") let sym = vals[0].getString var s = i.scope.getSigil(sym) s.sealed = true - i.scope.setSigil(sym, s) + i.scope.setSigil(sym, s, true) def.symbol("unseal-symbol") do (i: In): let vals = i.expect("'sym")

@@ -939,12 +986,12 @@ opts = i.dset(opts, key.newVal, val.newVal)

else: discard i.push opts - + def.symbol("expect") do (i: In): var q: MinValue i.reqQuotationOfSymbols q i.push(i.expect(q.qVal.mapIt(it.getString())).reversed.newVal) - + def.symbol("infix-dequote") do (i: In): let vals = i.expect("quot") let q = vals[0]

@@ -1069,6 +1116,9 @@ i.pushSym("load-symbol")

def.sigil("^") do (i: In): i.pushSym("lambda") + + def.sigil("~") do (i: In): + i.pushSym("lambda-bind") # Shorthand symbol aliases

@@ -1086,6 +1136,9 @@ i.pushSym("bind")

def.symbol("^") do (i: In): i.pushSym("lambda") + + def.symbol("~") do (i: In): + i.pushSym("lambda-bind") def.symbol("'") do (i: In): i.pushSym("quote")
M next-release.mdnext-release.md

@@ -1,34 +1,100 @@

-* Implemented "auto-popping" by adding **!** at the end of any symbol (#104). -* Removed all symbols ending with **!** as auto-popping will work instead. -* Improved contrast and readability of the min web site (#107). -* Extended **operator** to support the creation of constructor symbols. -* Now using **dict:http-response** and **dict:http-response** for HTTP requests/responses. -* Now using **dict:timeinfo** for time info. -* Changed **parse-url** to push a **dict:url** on the stack. -* Fixed #115 and #118. -### Breaking changes +### Fixes -This release also introduces quite a lot of breaking changes aiming at addressing some language inconsistencies and making the language more stable overall (see #111 for more information). +* Added `===` at the end of integrated help descriptions (#127). +* Fixed override propagation when setting isymbols in upper scopes (#133). -**Read this carefully! It is most likely that your code will break when you upgrade.** +### Mew additions + +* New symbol: [parent-scope](https://min-lang.org/reference-lang/#op-parent-scope) (#117). +### Notable changes -* Removed **quote-define** (=) and **quote-bind** (#). -* **define** (:) now auto-quote quotations as well. -* To quickly bind a quotation to a symbol (and essentially create a symbol operator but with no validations or constraints), use the new **lambda** symbol or **^** (alias, sigil) -- addresses also #114. -* Removed **typeclass** and extended **operator** to create type classes as well. -* Renamed **string** and **float** type names (used in operator signatures) to **str** and **flt** respectively. -* Removed **define-sigil**, use **operator** instead. -* Removed **module** and **+** (sigil); use **require** to create modules. -* Removed **call**, **^** (sigil, alias -- reused for **lambda**, see above); use **invoke** to access module/dictionary symbols. -* Removed **set-type** symbol. -* Removed **~** sigil (rarely used). -* Renamed the following symbols: - * `int` -> `integer` - * `bool` -> `boolean` - * `delete` -> `delete-symbol` - * `defined?` -> `defined-symbol?` - * `seal` -> `seal-symbol` - * `sealed?` -> `sealed-symbol?` - * `unseal` -> `unseal-symbol` +#### Lambda capturing in operator output values + +You can now specify a lambda to be captured to an output value, like this: + + ( + symbol square + (==> quot ^o) + ( + (dup *) ~o + ) + ) :: + +Essentially, this allows you to push a lambda on the stack from an operator. + +Note that: + +* Lambdas must be captured using the `^` sigil in signatures and bound using `lambda-bind` in the operator body. +* Lambdas cannot be captured in input values (they have already been pushed on the stack). +* Requiring a lambda as an output value effectively bypasses stack pollution checks. While this can be useful at times, use with caution! + +#### Type Expressions + +When specifying types in operator signatures or through the `expect` operator, you can specify a logical expression containing types and type classes joined with one of the following operators: + +* `|` (or) +* `&` (and) +* `!` (not) + +Suppose for example you defined the following type classes: + +``` +(typeclass fiveplus + (int :n ==> bool :o) + ( + n 5 > @o + ) +) :: + +(typeclass tenminus + (int :n ==> bool :o) + ( + n 10 < @o + ) +) :: + +(typeclass even + (int :n ==> bool :o) + ( + n 2 mod 0 == @o + ) +) :: +``` + +You can combine them in a type expression as following: + +``` +(symbol test + (!even|tenminus&fiveplus :n ==> bool :o) + ( + true @o + ) +) :: +4 test ; error +6 test ; true +11 test ; true +``` + +### Type aliases + +You can now define *type aliases* using the `typealias` operator. + +For example, you can create an alias of part of the type expression used in the previous example, like this: + +``` +'tenminus&fiveplus 'five-to-ten typealias + +(symbol test + (!even|five-to-ten :n ==> bool :o) + ( + true @o + ) +) :: +``` + +Note that: +* Type aliases be used to create an alias for any type expression. +* Aliased type expressions can contain standard min types, dictionary types, type classes, and even other type aliases. +* The `typealias` operator actually creates lexically-scoped, `typealias:`-prefixed symbols that can be sealed, unsealed, and deleted exactly like other symbols.
M site/contents/learn-operators.mdsite/contents/learn-operators.md

@@ -48,6 +48,11 @@ * have no built-in validation of input and output values.

* do not support the `return` symbol to immediately end their execution. * have no built-in stack pollution checks. +> %tip +> Tip +> +> You can use {#link-operator||lang||lambda-bind#} to re-set a previously set lambda. + ## Sigils Besides symbols, you can also define sigils. min provides a set of predefined _sigils_ as abbreviations for for commonly-used symbols.

@@ -137,7 +142,7 @@

* One of the following shorthand symbols identifying a well-known {{m}} base type (see the {#link-page||reference||reference#} section for more information): `a`, `bool`, `null`, `str`, `int`, `num`, `flt`, `'sym`, `quot`, or `dict`. * A typed dictionary like `dict:module` or `dict:datastore`. * A type class (see below). -* a union of types/typed dictionaries/type classes, like `str|int`. +* a type expression like `str|int`. > %note% > Note

@@ -172,6 +177,96 @@ > %tip%

> Tip > > `typeclass:`-prefixed symbols are just like ordinary shmbols: they are lexically scoped, they can be sealed, unsealed and deleted. + +#### Capturing lambdas + +You can also specify a lambda to be captured to an output value, like this: + + ( + symbol square + (==> quot ^o) + ( + (dup *) ~o + ) + ) :: + +Essentially, this allows you to push a lambda on the stack from an operator. + +Note that: + +* Lambdas must be captured using the `^` sigil in signatures and bound using {#link-operator||lang||lambda-bind#} in the operator body. +* Lambdas cannot be captured in input values (they have already been pushed on the stack). +* Requiring a lambda as an output value effectively bypasses stack pollution checks. While this can be useful at times, use with caution! + +### Type expressions + +When specifying types in operator signatures or through the {#link-operator||lang||expect#} operator, you can specify a logical expression containing types and type classes joined with one of the following operators: + +* `|` (or) +* `&` (and) +* `!` (not) + +Suppose for example you defined the following type classes: + +``` +(typeclass fiveplus + (int :n ==> bool :o) + ( + n 5 > @o + ) +) :: + +(typeclass tenminus + (int :n ==> bool :o) + ( + n 10 < @o + ) +) :: + +(typeclass even + (int :n ==> bool :o) + ( + n 2 mod 0 == @o + ) +) :: +``` + +You can combine them in a type expression as following: + +``` +(symbol test + (!even|tenminus&fiveplus :n ==> bool :o) + ( + true @o + ) +) :: +4 test ; error +6 test ; true +11 test ; true +``` + +### Type aliases + +As you can see, type expressions can quickly become quite long and complex. To avoid this, you can define *type aliases* using the {#link-operator||lang||typealias#} operator. + +For example, you can create an alias of part of the type expression used in the previous example, like this: + +``` +'tenminus&fiveplus 'five-to-ten typealias + +(symbol test + (!even|five-to-ten :n ==> bool :o) + ( + true @o + ) +) :: +``` + +Note that: + +* Type aliases be used to create an alias for any type expression. +* Aliased type expressions can contain standard {{m}} types, dictionary types, type classes, and even other type aliases. +* The {#link-operator||lang||typealias#} operator actually creates lexically-scoped, `typealias:`-prefixed symbols that can be sealed, unsealed, and deleted exactly like other symbols. ### Generics
M site/contents/reference-lang.mdsite/contents/reference-lang.md

@@ -121,7 +121,7 @@ > > %tip%

> > Tips > > > > * You can specify a typed dictionary by prepending the type name with `dict:`. Example: `dict:socket` -> > * You can specify two or more matching types by separating the type names with a pipe: `string|quot` +> > * You can specify two or more matching types by separating combined together in a logical type expression, e.g.: `string|quot` > > %sidebar% > > Example

@@ -321,6 +321,9 @@

{#op||opts||{{none}}||{{d}}|| Returns a dictionary of all options passed to the current program, with their respective values.#} +{#op||parent-scope||{{d1}}||{{d2}}|| +Returns a dictionary {{d2}} holding a reference to the parent scope of {{d1}} or {{null}} if {{d1}} is ROOT.#} + {#op||parse||{{s}}||{{q}}|| Parses {{s}} and returns a quoted program {{q}}. #}

@@ -495,6 +498,9 @@ > > (pop)

> > (format-error puts) > > (0) > > ) try #} + +{#op||typealias||{{sl1}} {{sl2}}||{{none}}|| +Creates a type alias {{sl1}} for type expression {{sl2}}.#} {#op||unless||{{q1}} {{q2}}||{{a0p}}|| If {{1}} evaluates to {{f}} then evaluates {{2}}.#}
M site/settings.jsonsite/settings.json

@@ -6,5 +6,5 @@ "rules": "rules.min",

"temp": "temp", "templates": "templates", "title": "min language", - "version": "0.31.0" + "version": "0.32.0" }
M tasks/build.mintasks/build.min

@@ -44,7 +44,7 @@ (

"hastyscribe" required "Building - guide" notice! "hastyscribe Min_DeveloperGuide.md --field/version=$#" (version) =% !! - "cp Min_DeveloperGuide.htm site/output/guide.dev.html" !! + "cp Min_DeveloperGuide.htm site/output/guide.dev.html" !! ) ) :: ;; Builds the developer guide.
M tests/lang.mintests/lang.min

@@ -310,6 +310,40 @@ )

) :: ("a" "b" add "ab" ==) *test/assert ((1 2 3) (4 5) add (1 2 3 4 5) ==) *test/assert + + + (typeclass fiveplus + (int :n ==> bool :o) + ( + n 5 > @o + ) + ) :: + + (typeclass tenminus + (int :n ==> bool :o) + ( + n 10 < @o + ) + ) :: + + (typeclass even + (int :n ==> bool :o) + ( + n 2 mod 0 == @o + ) + ) :: + ( + 'tenminus&fiveplus 'five-to-ten typealias + (symbol test + (!even|five-to-ten :n ==> bool :o) + ( + true @o + ) + ) :: + 6 test + 11 test + and + ) *test/assert *test/report ; Tidy up