Merge branch 'devel' of github.com:Araq/Nimrod into devel
This commit is contained in:
commit
5820093e58
12 changed files with 202 additions and 82 deletions
|
|
@ -81,7 +81,7 @@ proc hashSym(c: var MD5Context, s: PSym) =
|
||||||
|
|
||||||
proc hashTree(c: var MD5Context, n: PNode) =
|
proc hashTree(c: var MD5Context, n: PNode) =
|
||||||
if n == nil:
|
if n == nil:
|
||||||
c &= "noTreeKind"
|
c &= "\255"
|
||||||
return
|
return
|
||||||
var k = n.kind
|
var k = n.kind
|
||||||
md5Update(c, cast[cstring](addr(k)), 1)
|
md5Update(c, cast[cstring](addr(k)), 1)
|
||||||
|
|
@ -107,7 +107,7 @@ proc hashTree(c: var MD5Context, n: PNode) =
|
||||||
proc hashType(c: var MD5Context, t: PType) =
|
proc hashType(c: var MD5Context, t: PType) =
|
||||||
# modelled after 'typeToString'
|
# modelled after 'typeToString'
|
||||||
if t == nil:
|
if t == nil:
|
||||||
c &= "noTypeKind"
|
c &= "\254"
|
||||||
return
|
return
|
||||||
|
|
||||||
var k = t.kind
|
var k = t.kind
|
||||||
|
|
@ -168,7 +168,7 @@ proc canonConst(n: PNode): TUid =
|
||||||
c.hashType(n.typ)
|
c.hashType(n.typ)
|
||||||
md5Final(c, MD5Digest(result))
|
md5Final(c, MD5Digest(result))
|
||||||
|
|
||||||
proc canonSym(s: PSym): TUid
|
proc canonSym(s: PSym): TUid =
|
||||||
var c: MD5Context
|
var c: MD5Context
|
||||||
md5Init(c)
|
md5Init(c)
|
||||||
c.hashSym(s)
|
c.hashSym(s)
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,8 @@ type
|
||||||
fNumber*: BiggestFloat # the parsed floating point literal
|
fNumber*: BiggestFloat # the parsed floating point literal
|
||||||
base*: TNumericalBase # the numerical base; only valid for int
|
base*: TNumericalBase # the numerical base; only valid for int
|
||||||
# or float literals
|
# or float literals
|
||||||
|
strongSpaceA*: int8 # leading spaces of an operator
|
||||||
|
strongSpaceB*: int8 # trailing spaces of an operator
|
||||||
literal*: string # the parsed (string) literal; and
|
literal*: string # the parsed (string) literal; and
|
||||||
# documentation comments are here too
|
# documentation comments are here too
|
||||||
line*, col*: int
|
line*, col*: int
|
||||||
|
|
@ -119,6 +121,7 @@ type
|
||||||
indentAhead*: int # if > 0 an indendation has already been read
|
indentAhead*: int # if > 0 an indendation has already been read
|
||||||
# this is needed because scanning comments
|
# this is needed because scanning comments
|
||||||
# needs so much look-ahead
|
# needs so much look-ahead
|
||||||
|
strongSpaces*: bool
|
||||||
|
|
||||||
|
|
||||||
var gLinesCompiled*: int # all lines that have been compiled
|
var gLinesCompiled*: int # all lines that have been compiled
|
||||||
|
|
@ -183,6 +186,7 @@ proc initToken*(L: var TToken) =
|
||||||
L.tokType = tkInvalid
|
L.tokType = tkInvalid
|
||||||
L.iNumber = 0
|
L.iNumber = 0
|
||||||
L.indent = 0
|
L.indent = 0
|
||||||
|
L.strongSpaceA = 0
|
||||||
L.literal = ""
|
L.literal = ""
|
||||||
L.fNumber = 0.0
|
L.fNumber = 0.0
|
||||||
L.base = base10
|
L.base = base10
|
||||||
|
|
@ -192,6 +196,7 @@ proc fillToken(L: var TToken) =
|
||||||
L.tokType = tkInvalid
|
L.tokType = tkInvalid
|
||||||
L.iNumber = 0
|
L.iNumber = 0
|
||||||
L.indent = 0
|
L.indent = 0
|
||||||
|
L.strongSpaceA = 0
|
||||||
setLen(L.literal, 0)
|
setLen(L.literal, 0)
|
||||||
L.fNumber = 0.0
|
L.fNumber = 0.0
|
||||||
L.base = base10
|
L.base = base10
|
||||||
|
|
@ -634,6 +639,14 @@ proc getOperator(L: var TLexer, tok: var TToken) =
|
||||||
h = h !& ord(c)
|
h = h !& ord(c)
|
||||||
inc(pos)
|
inc(pos)
|
||||||
endOperator(L, tok, pos, h)
|
endOperator(L, tok, pos, h)
|
||||||
|
# advance pos but don't store it in L.bufpos so the next token (which might
|
||||||
|
# be an operator too) gets the preceeding spaces:
|
||||||
|
tok.strongSpaceB = 0
|
||||||
|
while buf[pos] == ' ':
|
||||||
|
inc pos
|
||||||
|
inc tok.strongSpaceB
|
||||||
|
if buf[pos] in {CR, LF, nimlexbase.EndOfFile}:
|
||||||
|
tok.strongSpaceB = -1
|
||||||
|
|
||||||
proc scanComment(L: var TLexer, tok: var TToken) =
|
proc scanComment(L: var TLexer, tok: var TToken) =
|
||||||
var pos = L.bufpos
|
var pos = L.bufpos
|
||||||
|
|
@ -677,10 +690,12 @@ proc scanComment(L: var TLexer, tok: var TToken) =
|
||||||
proc skip(L: var TLexer, tok: var TToken) =
|
proc skip(L: var TLexer, tok: var TToken) =
|
||||||
var pos = L.bufpos
|
var pos = L.bufpos
|
||||||
var buf = L.buf
|
var buf = L.buf
|
||||||
|
tok.strongSpaceA = 0
|
||||||
while true:
|
while true:
|
||||||
case buf[pos]
|
case buf[pos]
|
||||||
of ' ':
|
of ' ':
|
||||||
inc(pos)
|
inc(pos)
|
||||||
|
inc(tok.strongSpaceA)
|
||||||
of Tabulator:
|
of Tabulator:
|
||||||
lexMessagePos(L, errTabulatorsAreNotAllowed, pos)
|
lexMessagePos(L, errTabulatorsAreNotAllowed, pos)
|
||||||
inc(pos)
|
inc(pos)
|
||||||
|
|
@ -691,6 +706,7 @@ proc skip(L: var TLexer, tok: var TToken) =
|
||||||
while buf[pos] == ' ':
|
while buf[pos] == ' ':
|
||||||
inc(pos)
|
inc(pos)
|
||||||
inc(indent)
|
inc(indent)
|
||||||
|
tok.strongSpaceA = 0
|
||||||
if buf[pos] > ' ':
|
if buf[pos] > ' ':
|
||||||
tok.indent = indent
|
tok.indent = indent
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ type
|
||||||
inSemiStmtList: int
|
inSemiStmtList: int
|
||||||
|
|
||||||
proc parseAll*(p: var TParser): PNode
|
proc parseAll*(p: var TParser): PNode
|
||||||
proc openParser*(p: var TParser, filename: string, inputstream: PLLStream)
|
|
||||||
proc closeParser*(p: var TParser)
|
proc closeParser*(p: var TParser)
|
||||||
proc parseTopLevelStmt*(p: var TParser): PNode
|
proc parseTopLevelStmt*(p: var TParser): PNode
|
||||||
# implements an iterator. Returns the next top-level statement or
|
# implements an iterator. Returns the next top-level statement or
|
||||||
|
|
@ -50,7 +49,6 @@ proc parseString*(s: string, filename: string = "", line: int = 0): PNode
|
||||||
# correct error messages referring to the original source.
|
# correct error messages referring to the original source.
|
||||||
|
|
||||||
# helpers for the other parsers
|
# helpers for the other parsers
|
||||||
proc getPrecedence*(tok: TToken): int
|
|
||||||
proc isOperator*(tok: TToken): bool
|
proc isOperator*(tok: TToken): bool
|
||||||
proc getTok*(p: var TParser)
|
proc getTok*(p: var TParser)
|
||||||
proc parMessage*(p: TParser, msg: TMsgKind, arg: string = "")
|
proc parMessage*(p: TParser, msg: TMsgKind, arg: string = "")
|
||||||
|
|
@ -77,14 +75,17 @@ proc parseCase(p: var TParser): PNode
|
||||||
proc getTok(p: var TParser) =
|
proc getTok(p: var TParser) =
|
||||||
rawGetTok(p.lex, p.tok)
|
rawGetTok(p.lex, p.tok)
|
||||||
|
|
||||||
proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream) =
|
proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream,
|
||||||
|
strongSpaces=false) =
|
||||||
initToken(p.tok)
|
initToken(p.tok)
|
||||||
openLexer(p.lex, fileIdx, inputStream)
|
openLexer(p.lex, fileIdx, inputStream)
|
||||||
getTok(p) # read the first token
|
getTok(p) # read the first token
|
||||||
p.firstTok = true
|
p.firstTok = true
|
||||||
|
p.strongSpaces = strongSpaces
|
||||||
|
|
||||||
proc openParser*(p: var TParser, filename: string, inputStream: PLLStream) =
|
proc openParser*(p: var TParser, filename: string, inputStream: PLLStream,
|
||||||
openParser(p, filename.fileInfoIdx, inputstream)
|
strongSpaces=false) =
|
||||||
|
openParser(p, filename.fileInfoIdx, inputstream, strongSpaces)
|
||||||
|
|
||||||
proc closeParser(p: var TParser) =
|
proc closeParser(p: var TParser) =
|
||||||
closeLexer(p.lex)
|
closeLexer(p.lex)
|
||||||
|
|
@ -193,34 +194,52 @@ proc isSigilLike(tok: TToken): bool {.inline.} =
|
||||||
proc isLeftAssociative(tok: TToken): bool {.inline.} =
|
proc isLeftAssociative(tok: TToken): bool {.inline.} =
|
||||||
result = tok.tokType != tkOpr or relevantOprChar(tok.ident) != '^'
|
result = tok.tokType != tkOpr or relevantOprChar(tok.ident) != '^'
|
||||||
|
|
||||||
proc getPrecedence(tok: TToken): int =
|
proc getPrecedence(tok: TToken, strongSpaces: bool): int =
|
||||||
|
template considerStrongSpaces(x): expr =
|
||||||
|
x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0)
|
||||||
|
|
||||||
case tok.tokType
|
case tok.tokType
|
||||||
of tkOpr:
|
of tkOpr:
|
||||||
let L = tok.ident.s.len
|
let L = tok.ident.s.len
|
||||||
let relevantChar = relevantOprChar(tok.ident)
|
let relevantChar = relevantOprChar(tok.ident)
|
||||||
|
|
||||||
template considerAsgn(value: expr) =
|
template considerAsgn(value: expr) =
|
||||||
result = if tok.ident.s[L-1] == '=': 1 else: value
|
result = if tok.ident.s[L-1] == '=': 1 else: considerStrongSpaces(value)
|
||||||
|
|
||||||
case relevantChar
|
case relevantChar
|
||||||
of '$', '^': considerAsgn(10)
|
of '$', '^': considerAsgn(10)
|
||||||
of '*', '%', '/', '\\': considerAsgn(9)
|
of '*', '%', '/', '\\': considerAsgn(9)
|
||||||
of '~': result = 8
|
of '~': result = considerStrongSpaces(8)
|
||||||
of '+', '-', '|': considerAsgn(8)
|
of '+', '-', '|': considerAsgn(8)
|
||||||
of '&': considerAsgn(7)
|
of '&': considerAsgn(7)
|
||||||
of '=', '<', '>', '!': result = 5
|
of '=', '<', '>', '!': result = considerStrongSpaces(5)
|
||||||
of '.': considerAsgn(6)
|
of '.': considerAsgn(6)
|
||||||
of '?': result = 2
|
of '?': result = considerStrongSpaces(2)
|
||||||
else: considerAsgn(2)
|
else: considerAsgn(2)
|
||||||
of tkDiv, tkMod, tkShl, tkShr: result = 9
|
of tkDiv, tkMod, tkShl, tkShr: result = 9
|
||||||
of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5
|
of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5
|
||||||
of tkDotDot: result = 6
|
of tkDotDot: result = considerStrongSpaces(6)
|
||||||
of tkAnd: result = 4
|
of tkAnd: result = 4
|
||||||
of tkOr, tkXor: result = 3
|
of tkOr, tkXor: result = 3
|
||||||
else: result = -10
|
else: result = -10
|
||||||
|
|
||||||
proc isOperator(tok: TToken): bool =
|
proc isOperator(tok: TToken): bool =
|
||||||
result = getPrecedence(tok) >= 0
|
tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs,
|
||||||
|
tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor}
|
||||||
|
|
||||||
|
proc isUnary(p: TParser): bool =
|
||||||
|
p.strongSpaces and p.tok.tokType in {tkOpr, tkDotDot} and
|
||||||
|
p.tok.strongSpaceB == 0 and
|
||||||
|
p.tok.strongSpaceA > 0
|
||||||
|
|
||||||
|
proc checkBinary(p: TParser) {.inline.} =
|
||||||
|
# we don't check '..' here as that's too annoying
|
||||||
|
if p.strongSpaces and p.tok.tokType == tkOpr:
|
||||||
|
if p.tok.strongSpaceB > 0 and p.tok.strongSpaceA != p.tok.strongSpaceB:
|
||||||
|
parMessage(p, errGenerated, "number of spaces around '$#' not consistent"%
|
||||||
|
prettyTok(p.tok))
|
||||||
|
elif p.tok.strongSpaceA notin {0,1,2,4,8}:
|
||||||
|
parMessage(p, errGenerated, "number of spaces must be 0,1,2,4 or 8")
|
||||||
|
|
||||||
#| module = stmt ^* (';' / IND{=})
|
#| module = stmt ^* (';' / IND{=})
|
||||||
#|
|
#|
|
||||||
|
|
@ -650,6 +669,7 @@ proc primarySuffix(p: var TParser, r: PNode): PNode =
|
||||||
while p.tok.indent < 0:
|
while p.tok.indent < 0:
|
||||||
case p.tok.tokType
|
case p.tok.tokType
|
||||||
of tkParLe:
|
of tkParLe:
|
||||||
|
if p.strongSpaces and p.tok.strongSpaceA > 0: break
|
||||||
result = namedParams(p, result, nkCall, tkParRi)
|
result = namedParams(p, result, nkCall, tkParRi)
|
||||||
if result.len > 1 and result.sons[1].kind == nkExprColonExpr:
|
if result.len > 1 and result.sons[1].kind == nkExprColonExpr:
|
||||||
result.kind = nkObjConstr
|
result.kind = nkObjConstr
|
||||||
|
|
@ -664,8 +684,10 @@ proc primarySuffix(p: var TParser, r: PNode): PNode =
|
||||||
result = dotExpr(p, result)
|
result = dotExpr(p, result)
|
||||||
result = parseGStrLit(p, result)
|
result = parseGStrLit(p, result)
|
||||||
of tkBracketLe:
|
of tkBracketLe:
|
||||||
|
if p.strongSpaces and p.tok.strongSpaceA > 0: break
|
||||||
result = namedParams(p, result, nkBracketExpr, tkBracketRi)
|
result = namedParams(p, result, nkBracketExpr, tkBracketRi)
|
||||||
of tkCurlyLe:
|
of tkCurlyLe:
|
||||||
|
if p.strongSpaces and p.tok.strongSpaceA > 0: break
|
||||||
result = namedParams(p, result, nkCurlyExpr, tkCurlyRi)
|
result = namedParams(p, result, nkCurlyExpr, tkCurlyRi)
|
||||||
of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast:
|
of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast:
|
||||||
if p.inPragma == 0:
|
if p.inPragma == 0:
|
||||||
|
|
@ -695,10 +717,11 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode
|
||||||
proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
|
proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
|
||||||
result = primary(p, mode)
|
result = primary(p, mode)
|
||||||
# expand while operators have priorities higher than 'limit'
|
# expand while operators have priorities higher than 'limit'
|
||||||
var opPrec = getPrecedence(p.tok)
|
var opPrec = getPrecedence(p.tok, p.strongSpaces)
|
||||||
let modeB = if mode == pmTypeDef: pmTypeDesc else: mode
|
let modeB = if mode == pmTypeDef: pmTypeDesc else: mode
|
||||||
# the operator itself must not start on a new line:
|
# the operator itself must not start on a new line:
|
||||||
while opPrec >= limit and p.tok.indent < 0:
|
while opPrec >= limit and p.tok.indent < 0 and not isUnary(p):
|
||||||
|
checkBinary(p)
|
||||||
var leftAssoc = ord(isLeftAssociative(p.tok))
|
var leftAssoc = ord(isLeftAssociative(p.tok))
|
||||||
var a = newNodeP(nkInfix, p)
|
var a = newNodeP(nkInfix, p)
|
||||||
var opNode = newIdentNodeP(p.tok.ident, p) # skip operator:
|
var opNode = newIdentNodeP(p.tok.ident, p) # skip operator:
|
||||||
|
|
@ -710,7 +733,7 @@ proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
|
||||||
addSon(a, result)
|
addSon(a, result)
|
||||||
addSon(a, b)
|
addSon(a, b)
|
||||||
result = a
|
result = a
|
||||||
opPrec = getPrecedence(p.tok)
|
opPrec = getPrecedence(p.tok, p.strongSpaces)
|
||||||
|
|
||||||
proc simpleExpr(p: var TParser, mode = pmNormal): PNode =
|
proc simpleExpr(p: var TParser, mode = pmNormal): PNode =
|
||||||
result = simpleExprAux(p, -1, mode)
|
result = simpleExprAux(p, -1, mode)
|
||||||
|
|
@ -1933,7 +1956,9 @@ proc parseString(s: string, filename: string = "", line: int = 0): PNode =
|
||||||
stream.lineOffset = line
|
stream.lineOffset = line
|
||||||
|
|
||||||
var parser: TParser
|
var parser: TParser
|
||||||
openParser(parser, filename, stream)
|
# XXX for now the builtin 'parseStmt/Expr' functions do not know about strong
|
||||||
|
# spaces...
|
||||||
|
openParser(parser, filename, stream, false)
|
||||||
|
|
||||||
result = parser.parseAll
|
result = parser.parseAll
|
||||||
closeParser(parser)
|
closeParser(parser)
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,6 @@ proc makeExternImport(s: PSym, extname: string) =
|
||||||
incl(s.flags, sfImportc)
|
incl(s.flags, sfImportc)
|
||||||
excl(s.flags, sfForward)
|
excl(s.flags, sfForward)
|
||||||
|
|
||||||
const invalidIdentChars = AllChars - IdentChars
|
|
||||||
|
|
||||||
proc validateExternCName(s: PSym, info: TLineInfo) =
|
proc validateExternCName(s: PSym, info: TLineInfo) =
|
||||||
## Validates that the symbol name in s.loc.r is a valid C identifier.
|
## Validates that the symbol name in s.loc.r is a valid C identifier.
|
||||||
##
|
##
|
||||||
|
|
@ -106,16 +104,14 @@ proc validateExternCName(s: PSym, info: TLineInfo) =
|
||||||
## starting with a number. If the check fails, a generic error will be
|
## starting with a number. If the check fails, a generic error will be
|
||||||
## displayed to the user.
|
## displayed to the user.
|
||||||
let target = ropeToStr(s.loc.r)
|
let target = ropeToStr(s.loc.r)
|
||||||
if target.len < 1 or (not (target[0] in IdentStartChars)) or
|
if target.len < 1 or target[0] notin IdentStartChars or
|
||||||
(not target.allCharsInSet(IdentChars)):
|
not target.allCharsInSet(IdentChars):
|
||||||
localError(info, errGenerated, "invalid exported symbol")
|
localError(info, errGenerated, "invalid exported symbol")
|
||||||
|
|
||||||
proc makeExternExport(s: PSym, extname: string, info: TLineInfo) =
|
proc makeExternExport(s: PSym, extname: string, info: TLineInfo) =
|
||||||
setExternName(s, extname)
|
setExternName(s, extname)
|
||||||
case gCmd
|
if gCmd in {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC}:
|
||||||
of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC:
|
|
||||||
validateExternCName(s, info)
|
validateExternCName(s, info)
|
||||||
else: discard
|
|
||||||
incl(s.flags, sfExportc)
|
incl(s.flags, sfExportc)
|
||||||
|
|
||||||
proc processImportCompilerProc(s: PSym, extname: string) =
|
proc processImportCompilerProc(s: PSym, extname: string) =
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,11 @@ type
|
||||||
TFilterKind* = enum
|
TFilterKind* = enum
|
||||||
filtNone, filtTemplate, filtReplace, filtStrip
|
filtNone, filtTemplate, filtReplace, filtStrip
|
||||||
TParserKind* = enum
|
TParserKind* = enum
|
||||||
skinStandard, skinBraces, skinEndX
|
skinStandard, skinStrongSpaces, skinBraces, skinEndX
|
||||||
|
|
||||||
const
|
const
|
||||||
parserNames*: array[TParserKind, string] = ["standard", "braces", "endx"]
|
parserNames*: array[TParserKind, string] = ["standard", "strongspaces",
|
||||||
|
"braces", "endx"]
|
||||||
filterNames*: array[TFilterKind, string] = ["none", "stdtmpl", "replace",
|
filterNames*: array[TFilterKind, string] = ["none", "stdtmpl", "replace",
|
||||||
"strip"]
|
"strip"]
|
||||||
|
|
||||||
|
|
@ -54,7 +55,7 @@ proc parseFile(fileIdx: int32): PNode =
|
||||||
|
|
||||||
proc parseAll(p: var TParsers): PNode =
|
proc parseAll(p: var TParsers): PNode =
|
||||||
case p.skin
|
case p.skin
|
||||||
of skinStandard:
|
of skinStandard, skinStrongSpaces:
|
||||||
result = parser.parseAll(p.parser)
|
result = parser.parseAll(p.parser)
|
||||||
of skinBraces:
|
of skinBraces:
|
||||||
result = pbraces.parseAll(p.parser)
|
result = pbraces.parseAll(p.parser)
|
||||||
|
|
@ -65,7 +66,7 @@ proc parseAll(p: var TParsers): PNode =
|
||||||
|
|
||||||
proc parseTopLevelStmt(p: var TParsers): PNode =
|
proc parseTopLevelStmt(p: var TParsers): PNode =
|
||||||
case p.skin
|
case p.skin
|
||||||
of skinStandard:
|
of skinStandard, skinStrongSpaces:
|
||||||
result = parser.parseTopLevelStmt(p.parser)
|
result = parser.parseTopLevelStmt(p.parser)
|
||||||
of skinBraces:
|
of skinBraces:
|
||||||
result = pbraces.parseTopLevelStmt(p.parser)
|
result = pbraces.parseTopLevelStmt(p.parser)
|
||||||
|
|
@ -170,7 +171,9 @@ proc openParsers(p: var TParsers, fileIdx: int32, inputstream: PLLStream) =
|
||||||
else: s = inputstream
|
else: s = inputstream
|
||||||
case p.skin
|
case p.skin
|
||||||
of skinStandard, skinBraces, skinEndX:
|
of skinStandard, skinBraces, skinEndX:
|
||||||
parser.openParser(p.parser, fileIdx, s)
|
parser.openParser(p.parser, fileIdx, s, false)
|
||||||
|
of skinStrongSpaces:
|
||||||
|
parser.openParser(p.parser, fileIdx, s, true)
|
||||||
|
|
||||||
proc closeParsers(p: var TParsers) =
|
proc closeParsers(p: var TParsers) =
|
||||||
parser.closeParser(p.parser)
|
parser.closeParser(p.parser)
|
||||||
|
|
|
||||||
|
|
@ -480,8 +480,8 @@ precedence and associativity; this is useful for meta programming.
|
||||||
Associativity
|
Associativity
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
All binary operators are left-associative, except binary operators whose
|
Binary operators whose relevant character is ``^`` are right-associative, all
|
||||||
relevant char is ``^``.
|
other binary operators are left-associative.
|
||||||
|
|
||||||
Precedence
|
Precedence
|
||||||
----------
|
----------
|
||||||
|
|
@ -508,7 +508,7 @@ Precedence level Operators Relevant char
|
||||||
7 ``+ -`` ``+ ~ |`` OP7
|
7 ``+ -`` ``+ ~ |`` OP7
|
||||||
6 ``&`` ``&`` OP6
|
6 ``&`` ``&`` OP6
|
||||||
5 ``..`` ``.`` OP5
|
5 ``..`` ``.`` OP5
|
||||||
4 ``== <= < >= > != in not_in is isnot not of`` ``= < > !`` OP4
|
4 ``== <= < >= > != in notin is isnot not of`` ``= < > !`` OP4
|
||||||
3 ``and`` OP3
|
3 ``and`` OP3
|
||||||
2 ``or xor`` OP2
|
2 ``or xor`` OP2
|
||||||
1 ``@ : ?`` OP1
|
1 ``@ : ?`` OP1
|
||||||
|
|
@ -516,6 +516,46 @@ Precedence level Operators Relevant char
|
||||||
================ =============================================== ================== ===============
|
================ =============================================== ================== ===============
|
||||||
|
|
||||||
|
|
||||||
|
Strong spaces
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The number of spaces preceeding a non-keyword operator affects precedence
|
||||||
|
if the experimental parser directive ``#!strongSpaces`` is used. Indentation
|
||||||
|
is not used to determine the number of spaces. If 2 or more operators have the
|
||||||
|
same number of preceeding spaces the precedence table applies, so ``1 + 3 * 4``
|
||||||
|
is still parsed as ``1 + (3 * 4)``, but ``1+3 * 4`` is parsed as ``(1+3) * 4``:
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
#! strongSpaces
|
||||||
|
if foo+4 * 4 == 8 and b&c | 9 ++
|
||||||
|
bar:
|
||||||
|
echo ""
|
||||||
|
# is parsed as
|
||||||
|
if ((foo+4)*4 == 8) and (((b&c) | 9) ++ bar): echo ""
|
||||||
|
|
||||||
|
|
||||||
|
Furthermore whether an operator is used a prefix operator is affected by the
|
||||||
|
number of spaces:
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
#! strongSpaces
|
||||||
|
echo $foo
|
||||||
|
# is parsed as
|
||||||
|
echo($foo)
|
||||||
|
|
||||||
|
This also affects whether ``[]``, ``{}``, ``()`` are parsed as constructors
|
||||||
|
or as accessors:
|
||||||
|
|
||||||
|
.. code-block:: nimrod
|
||||||
|
#! strongSpaces
|
||||||
|
echo (1,2)
|
||||||
|
# is parsed as
|
||||||
|
echo((1,2))
|
||||||
|
|
||||||
|
|
||||||
|
Grammar
|
||||||
|
-------
|
||||||
|
|
||||||
The grammar's start symbol is ``module``.
|
The grammar's start symbol is ``module``.
|
||||||
|
|
||||||
.. include:: grammar.txt
|
.. include:: grammar.txt
|
||||||
|
|
|
||||||
|
|
@ -195,8 +195,14 @@ proc open*(connection, user, password, database: string): TDbConn {.
|
||||||
## be established.
|
## be established.
|
||||||
result = mysql.Init(nil)
|
result = mysql.Init(nil)
|
||||||
if result == nil: dbError("could not open database connection")
|
if result == nil: dbError("could not open database connection")
|
||||||
if mysql.RealConnect(result, "", user, password, database,
|
let
|
||||||
0'i32, nil, 0) == nil:
|
colonPos = connection.find(':')
|
||||||
|
host = if colonPos < 0: connection
|
||||||
|
else: substr(connection, 0, colonPos-1)
|
||||||
|
port: int32 = if colonPos < 0: 0'i32
|
||||||
|
else: substr(connection, colonPos+1).parseInt.int32
|
||||||
|
if mysql.RealConnect(result, host, user, password, database,
|
||||||
|
port, nil, 0) == nil:
|
||||||
var errmsg = $mysql.error(result)
|
var errmsg = $mysql.error(result)
|
||||||
db_mysql.Close(result)
|
db_mysql.Close(result)
|
||||||
dbError(errmsg)
|
dbError(errmsg)
|
||||||
|
|
|
||||||
|
|
@ -1164,13 +1164,13 @@ when not defined(nimrodVM):
|
||||||
## from it before writing to it is undefined behaviour!
|
## from it before writing to it is undefined behaviour!
|
||||||
## The allocated memory belongs to its allocating thread!
|
## The allocated memory belongs to its allocating thread!
|
||||||
## Use `allocShared` to allocate from a shared heap.
|
## Use `allocShared` to allocate from a shared heap.
|
||||||
proc alloc*(T: typedesc, size = 1): ptr T {.inline.} =
|
proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
|
||||||
## allocates a new memory block with at least ``T.sizeof * size``
|
## allocates a new memory block with at least ``T.sizeof * size``
|
||||||
## bytes. The block has to be freed with ``realloc(block, 0)`` or
|
## bytes. The block has to be freed with ``resize(block, 0)`` or
|
||||||
## ``dealloc(block)``. The block is not initialized, so reading
|
## ``free(block)``. The block is not initialized, so reading
|
||||||
## from it before writing to it is undefined behaviour!
|
## from it before writing to it is undefined behaviour!
|
||||||
## The allocated memory belongs to its allocating thread!
|
## The allocated memory belongs to its allocating thread!
|
||||||
## Use `allocShared` to allocate from a shared heap.
|
## Use `createSharedU` to allocate from a shared heap.
|
||||||
cast[ptr T](alloc(T.sizeof * size))
|
cast[ptr T](alloc(T.sizeof * size))
|
||||||
proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].}
|
proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].}
|
||||||
## allocates a new memory block with at least ``size`` bytes. The
|
## allocates a new memory block with at least ``size`` bytes. The
|
||||||
|
|
@ -1179,13 +1179,13 @@ when not defined(nimrodVM):
|
||||||
## containing zero, so it is somewhat safer than ``alloc``.
|
## containing zero, so it is somewhat safer than ``alloc``.
|
||||||
## The allocated memory belongs to its allocating thread!
|
## The allocated memory belongs to its allocating thread!
|
||||||
## Use `allocShared0` to allocate from a shared heap.
|
## Use `allocShared0` to allocate from a shared heap.
|
||||||
proc alloc0*(T: typedesc, size = 1): ptr T {.inline.} =
|
proc create*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
|
||||||
## allocates a new memory block with at least ``T.sizeof * size``
|
## allocates a new memory block with at least ``T.sizeof * size``
|
||||||
## bytes. The block has to be freed with ``realloc(block, 0)`` or
|
## bytes. The block has to be freed with ``resize(block, 0)`` or
|
||||||
## ``dealloc(block)``. The block is initialized with all bytes
|
## ``free(block)``. The block is initialized with all bytes
|
||||||
## containing zero, so it is somewhat safer than ``alloc``.
|
## containing zero, so it is somewhat safer than ``createU``.
|
||||||
## The allocated memory belongs to its allocating thread!
|
## The allocated memory belongs to its allocating thread!
|
||||||
## Use `allocShared0` to allocate from a shared heap.
|
## Use `createShared` to allocate from a shared heap.
|
||||||
cast[ptr T](alloc0(T.sizeof * size))
|
cast[ptr T](alloc0(T.sizeof * size))
|
||||||
proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [].}
|
proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [].}
|
||||||
## grows or shrinks a given memory block. If p is **nil** then a new
|
## grows or shrinks a given memory block. If p is **nil** then a new
|
||||||
|
|
@ -1195,14 +1195,14 @@ when not defined(nimrodVM):
|
||||||
## be freed with ``dealloc``.
|
## be freed with ``dealloc``.
|
||||||
## The allocated memory belongs to its allocating thread!
|
## The allocated memory belongs to its allocating thread!
|
||||||
## Use `reallocShared` to reallocate from a shared heap.
|
## Use `reallocShared` to reallocate from a shared heap.
|
||||||
proc reallocType*[T](p: ptr T, newSize: int): ptr T {.inline.} =
|
proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline.} =
|
||||||
## grows or shrinks a given memory block. If p is **nil** then a new
|
## grows or shrinks a given memory block. If p is **nil** then a new
|
||||||
## memory block is returned. In either way the block has at least
|
## memory block is returned. In either way the block has at least
|
||||||
## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not
|
## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not
|
||||||
## **nil** ``realloc`` calls ``dealloc(p)``. In other cases the block
|
## **nil** ``resize`` calls ``free(p)``. In other cases the block
|
||||||
## has to be freed with ``dealloc``. The allocated memory belongs to
|
## has to be freed with ``free``. The allocated memory belongs to
|
||||||
## its allocating thread!
|
## its allocating thread!
|
||||||
## Use `reallocShared` to reallocate from a shared heap.
|
## Use `resizeShared` to reallocate from a shared heap.
|
||||||
cast[ptr T](realloc(p, T.sizeof * newSize))
|
cast[ptr T](realloc(p, T.sizeof * newSize))
|
||||||
proc dealloc*(p: pointer) {.noconv, rtl, tags: [].}
|
proc dealloc*(p: pointer) {.noconv, rtl, tags: [].}
|
||||||
## frees the memory allocated with ``alloc``, ``alloc0`` or
|
## frees the memory allocated with ``alloc``, ``alloc0`` or
|
||||||
|
|
@ -1212,16 +1212,18 @@ when not defined(nimrodVM):
|
||||||
## or other memory may be corrupted.
|
## or other memory may be corrupted.
|
||||||
## The freed memory must belong to its allocating thread!
|
## The freed memory must belong to its allocating thread!
|
||||||
## Use `deallocShared` to deallocate from a shared heap.
|
## Use `deallocShared` to deallocate from a shared heap.
|
||||||
|
proc free*[T](p: ptr T) {.inline.} =
|
||||||
|
dealloc(p)
|
||||||
proc allocShared*(size: int): pointer {.noconv, rtl.}
|
proc allocShared*(size: int): pointer {.noconv, rtl.}
|
||||||
## allocates a new memory block on the shared heap with at
|
## allocates a new memory block on the shared heap with at
|
||||||
## least ``size`` bytes. The block has to be freed with
|
## least ``size`` bytes. The block has to be freed with
|
||||||
## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block
|
## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block
|
||||||
## is not initialized, so reading from it before writing to it is
|
## is not initialized, so reading from it before writing to it is
|
||||||
## undefined behaviour!
|
## undefined behaviour!
|
||||||
proc allocShared*(T: typedesc, size: int): ptr T {.inline.} =
|
proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
|
||||||
## allocates a new memory block on the shared heap with at
|
## allocates a new memory block on the shared heap with at
|
||||||
## least ``T.sizeof * size`` bytes. The block has to be freed with
|
## least ``T.sizeof * size`` bytes. The block has to be freed with
|
||||||
## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block
|
## ``resizeShared(block, 0)`` or ``freeShared(block)``. The block
|
||||||
## is not initialized, so reading from it before writing to it is
|
## is not initialized, so reading from it before writing to it is
|
||||||
## undefined behaviour!
|
## undefined behaviour!
|
||||||
cast[ptr T](allocShared(T.sizeof * size))
|
cast[ptr T](allocShared(T.sizeof * size))
|
||||||
|
|
@ -1231,25 +1233,25 @@ when not defined(nimrodVM):
|
||||||
## ``reallocShared(block, 0)`` or ``deallocShared(block)``.
|
## ``reallocShared(block, 0)`` or ``deallocShared(block)``.
|
||||||
## The block is initialized with all bytes
|
## The block is initialized with all bytes
|
||||||
## containing zero, so it is somewhat safer than ``allocShared``.
|
## containing zero, so it is somewhat safer than ``allocShared``.
|
||||||
proc allocShared0*(T: typedesc, size: int): ptr T {.inline.} =
|
proc createShared*(T: typedesc, size = 1.Positive): ptr T {.inline.} =
|
||||||
## allocates a new memory block on the shared heap with at
|
## allocates a new memory block on the shared heap with at
|
||||||
## least ``T.sizeof * size`` bytes. The block has to be freed with
|
## least ``T.sizeof * size`` bytes. The block has to be freed with
|
||||||
## ``reallocShared(block, 0)`` or ``deallocShared(block)``.
|
## ``resizeShared(block, 0)`` or ``freeShared(block)``.
|
||||||
## The block is initialized with all bytes
|
## The block is initialized with all bytes
|
||||||
## containing zero, so it is somewhat safer than ``allocShared``.
|
## containing zero, so it is somewhat safer than ``createSharedU``.
|
||||||
cast[ptr T](allocShared(T.sizeof * size))
|
cast[ptr T](allocShared0(T.sizeof * size))
|
||||||
proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl.}
|
proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl.}
|
||||||
## grows or shrinks a given memory block on the heap. If p is **nil**
|
## grows or shrinks a given memory block on the heap. If p is **nil**
|
||||||
## then a new memory block is returned. In either way the block has at
|
## then a new memory block is returned. In either way the block has at
|
||||||
## least ``newSize`` bytes. If ``newSize == 0`` and p is not **nil**
|
## least ``newSize`` bytes. If ``newSize == 0`` and p is not **nil**
|
||||||
## ``reallocShared`` calls ``deallocShared(p)``. In other cases the
|
## ``reallocShared`` calls ``deallocShared(p)``. In other cases the
|
||||||
## block has to be freed with ``deallocShared``.
|
## block has to be freed with ``deallocShared``.
|
||||||
proc reallocSharedType*[T](p: ptr T, newSize: int): ptr T {.inline.} =
|
proc resizeShared*[T](p: ptr T, newSize: Natural): ptr T {.inline.} =
|
||||||
## grows or shrinks a given memory block on the heap. If p is **nil**
|
## grows or shrinks a given memory block on the heap. If p is **nil**
|
||||||
## then a new memory block is returned. In either way the block has at
|
## then a new memory block is returned. In either way the block has at
|
||||||
## least ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is
|
## least ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is
|
||||||
## not **nil** ``reallocShared`` calls ``deallocShared(p)``. In other
|
## not **nil** ``resizeShared`` calls ``freeShared(p)``. In other
|
||||||
## cases the block has to be freed with ``deallocShared``.
|
## cases the block has to be freed with ``freeShared``.
|
||||||
cast[ptr T](reallocShared(p, T.sizeof * newSize))
|
cast[ptr T](reallocShared(p, T.sizeof * newSize))
|
||||||
proc deallocShared*(p: pointer) {.noconv, rtl.}
|
proc deallocShared*(p: pointer) {.noconv, rtl.}
|
||||||
## frees the memory allocated with ``allocShared``, ``allocShared0`` or
|
## frees the memory allocated with ``allocShared``, ``allocShared0`` or
|
||||||
|
|
@ -1257,6 +1259,13 @@ when not defined(nimrodVM):
|
||||||
## free the memory a leak occurs; if one tries to access freed
|
## free the memory a leak occurs; if one tries to access freed
|
||||||
## memory (or just freeing it twice!) a core dump may happen
|
## memory (or just freeing it twice!) a core dump may happen
|
||||||
## or other memory may be corrupted.
|
## or other memory may be corrupted.
|
||||||
|
proc freeShared*[T](p: ptr T) {.inline.} =
|
||||||
|
## frees the memory allocated with ``createShared``, ``createSharedU`` or
|
||||||
|
## ``resizeShared``. This procedure is dangerous! If one forgets to
|
||||||
|
## free the memory a leak occurs; if one tries to access freed
|
||||||
|
## memory (or just freeing it twice!) a core dump may happen
|
||||||
|
## or other memory may be corrupted.
|
||||||
|
deallocShared(p)
|
||||||
|
|
||||||
proc swap*[T](a, b: var T) {.magic: "Swap", noSideEffect.}
|
proc swap*[T](a, b: var T) {.magic: "Swap", noSideEffect.}
|
||||||
## swaps the values `a` and `b`. This is often more efficient than
|
## swaps the values `a` and `b`. This is often more efficient than
|
||||||
|
|
|
||||||
|
|
@ -2,44 +2,51 @@ var x: ptr int
|
||||||
|
|
||||||
x = cast[ptr int](alloc(7))
|
x = cast[ptr int](alloc(7))
|
||||||
assert x != nil
|
assert x != nil
|
||||||
|
x = cast[ptr int](x.realloc(2))
|
||||||
x = alloc(int, 3)
|
|
||||||
assert x != nil
|
assert x != nil
|
||||||
x.dealloc()
|
x.dealloc()
|
||||||
|
|
||||||
x = alloc0(int, 4)
|
x = createU(int, 3)
|
||||||
|
assert x != nil
|
||||||
|
x.free()
|
||||||
|
|
||||||
|
x = create(int, 4)
|
||||||
assert cast[ptr array[4, int]](x)[0] == 0
|
assert cast[ptr array[4, int]](x)[0] == 0
|
||||||
assert cast[ptr array[4, int]](x)[1] == 0
|
assert cast[ptr array[4, int]](x)[1] == 0
|
||||||
assert cast[ptr array[4, int]](x)[2] == 0
|
assert cast[ptr array[4, int]](x)[2] == 0
|
||||||
assert cast[ptr array[4, int]](x)[3] == 0
|
assert cast[ptr array[4, int]](x)[3] == 0
|
||||||
|
|
||||||
x = cast[ptr int](x.realloc(2))
|
x = x.resize(4)
|
||||||
assert x != nil
|
assert x != nil
|
||||||
|
x.free()
|
||||||
x = x.reallocType(4)
|
|
||||||
assert x != nil
|
|
||||||
x.dealloc()
|
|
||||||
|
|
||||||
x = cast[ptr int](allocShared(100))
|
x = cast[ptr int](allocShared(100))
|
||||||
assert x != nil
|
assert x != nil
|
||||||
deallocShared(x)
|
deallocShared(x)
|
||||||
|
|
||||||
x = allocShared(int, 3)
|
x = createSharedU(int, 3)
|
||||||
assert x != nil
|
assert x != nil
|
||||||
x.deallocShared()
|
x.freeShared()
|
||||||
|
|
||||||
x = allocShared0(int, 3)
|
x = createShared(int, 3)
|
||||||
assert x != nil
|
assert x != nil
|
||||||
assert cast[ptr array[3, int]](x)[0] == 0
|
assert cast[ptr array[3, int]](x)[0] == 0
|
||||||
assert cast[ptr array[3, int]](x)[1] == 0
|
assert cast[ptr array[3, int]](x)[1] == 0
|
||||||
assert cast[ptr array[3, int]](x)[2] == 0
|
assert cast[ptr array[3, int]](x)[2] == 0
|
||||||
|
|
||||||
x = cast[ptr int](reallocShared(x, 2))
|
|
||||||
assert x != nil
|
assert x != nil
|
||||||
|
x = cast[ptr int](x.resizeShared(2))
|
||||||
|
assert x != nil
|
||||||
|
x.freeShared()
|
||||||
|
|
||||||
x = reallocType(x, 12)
|
x = create(int, 10)
|
||||||
assert x != nil
|
assert x != nil
|
||||||
|
x = x.resize(12)
|
||||||
|
assert x != nil
|
||||||
|
x.dealloc()
|
||||||
|
|
||||||
x = reallocSharedType(x, 1)
|
x = createShared(int, 1)
|
||||||
assert x != nil
|
assert x != nil
|
||||||
x.deallocShared()
|
x = x.resizeShared(1)
|
||||||
|
assert x != nil
|
||||||
|
x.freeShared()
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,10 @@ proc generateJson*(filename: string, commit: int) =
|
||||||
on A.name = B.name and A.category = B.category
|
on A.name = B.name and A.category = B.category
|
||||||
where A.[commit] = ? and B.[commit] = ? and A.machine = ?
|
where A.[commit] = ? and B.[commit] = ? and A.machine = ?
|
||||||
and A.result != B.result"""
|
and A.result != B.result"""
|
||||||
|
selResults = """select
|
||||||
|
name, category, target, action, result, expected, given
|
||||||
|
from TestResult
|
||||||
|
where [commit] = ?"""
|
||||||
var db = open(connection="testament.db", user="testament", password="",
|
var db = open(connection="testament.db", user="testament", password="",
|
||||||
database="testament")
|
database="testament")
|
||||||
let lastCommit = db.getCommit(commit)
|
let lastCommit = db.getCommit(commit)
|
||||||
|
|
@ -189,6 +193,20 @@ proc generateJson*(filename: string, commit: int) =
|
||||||
|
|
||||||
outfile.writeln("""{"total": $#, "passed": $#, "skipped": $#""" % data)
|
outfile.writeln("""{"total": $#, "passed": $#, "skipped": $#""" % data)
|
||||||
|
|
||||||
|
let results = newJArray()
|
||||||
|
for row in db.rows(sql(selResults), lastCommit):
|
||||||
|
var obj = newJObject()
|
||||||
|
obj["name"] = %row[0]
|
||||||
|
obj["category"] = %row[1]
|
||||||
|
obj["target"] = %row[2]
|
||||||
|
obj["action"] = %row[3]
|
||||||
|
obj["result"] = %row[4]
|
||||||
|
obj["expected"] = %row[5]
|
||||||
|
obj["given"] = %row[6]
|
||||||
|
results.add(obj)
|
||||||
|
outfile.writeln(""", "results": """)
|
||||||
|
outfile.write(results.pretty)
|
||||||
|
|
||||||
if not previousCommit.isNil:
|
if not previousCommit.isNil:
|
||||||
let diff = newJArray()
|
let diff = newJArray()
|
||||||
|
|
||||||
|
|
|
||||||
1
todo.txt
1
todo.txt
|
|
@ -32,7 +32,6 @@ version 0.9.x
|
||||||
- ensure (ref T)(a, b) works as a type conversion and type constructor
|
- ensure (ref T)(a, b) works as a type conversion and type constructor
|
||||||
- optimize 'genericReset'; 'newException' leads to code bloat
|
- optimize 'genericReset'; 'newException' leads to code bloat
|
||||||
- stack-less GC
|
- stack-less GC
|
||||||
- implement strongSpaces:on
|
|
||||||
- make '--implicitStatic:on' the default
|
- make '--implicitStatic:on' the default
|
||||||
- implicit deref for parameter matching
|
- implicit deref for parameter matching
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ News
|
||||||
- The *command syntax* is supported in a lot more contexts.
|
- The *command syntax* is supported in a lot more contexts.
|
||||||
- Anonymous iterators are now supported and iterators can capture variables
|
- Anonymous iterators are now supported and iterators can capture variables
|
||||||
of an outer proc.
|
of an outer proc.
|
||||||
|
- The experimental ``strongSpaces`` parsing mode has been implemented.
|
||||||
|
|
||||||
|
|
||||||
Tools improvements
|
Tools improvements
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue