diff options
86 files changed, 3308 insertions, 2906 deletions
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 93a9dd65d..05a3602d1 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -964,8 +964,11 @@ proc genEcho(p: BProc, n: PNode) = var args: Rope = nil var a: TLoc for i in countup(0, n.len-1): - initLocExpr(p, n.sons[i], a) - addf(args, ", $1? ($1)->data:\"nil\"", [rdLoc(a)]) + if n.sons[i].skipConv.kind == nkNilLit: + add(args, ", \"nil\"") + else: + initLocExpr(p, n.sons[i], a) + addf(args, ", $1? ($1)->data:\"nil\"", [rdLoc(a)]) linefmt(p, cpsStmts, "printf($1$2);$n", makeCString(repeat("%s", n.len) & tnl), args) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index b6572d960..6d29b1684 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -551,12 +551,7 @@ proc genBreakStmt(p: BProc, t: PNode) = lineF(p, cpsStmts, "goto $1;$n", [label]) proc getRaiseFrmt(p: BProc): string = - if p.module.compileToCpp: - result = "throw NimException($1, $2);$n" - elif getCompilerProc("Exception") != nil: - result = "#raiseException((#Exception*)$1, $2);$n" - else: - result = "#raiseException((#E_Base*)$1, $2);$n" + result = "#raiseException((#Exception*)$1, $2);$n" proc genRaiseStmt(p: BProc, t: PNode) = if p.inExceptBlock > 0: @@ -797,11 +792,8 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = # finallyPart(); if not isEmptyType(t.typ) and d.k == locNone: getTemp(p, t.typ, d) - var - exc: Rope - i, length, blen: int genLineDir(p, t) - exc = getTempName() + let exc = getTempName() if getCompilerProc("Exception") != nil: discard cgsym(p.module, "Exception") else: @@ -809,20 +801,23 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = add(p.nestedTryStmts, t) startBlock(p, "try {$n") expr(p, t.sons[0], d) - length = sonsLen(t) + let length = sonsLen(t) endBlock(p, ropecg(p.module, "} catch (NimException& $1) {$n", [exc])) if optStackTrace in p.options: - linefmt(p, cpsStmts, "#setFrame((TFrame*)&F);$n") + linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR);$n") inc p.inExceptBlock - i = 1 + var i = 1 var catchAllPresent = false while (i < length) and (t.sons[i].kind == nkExceptBranch): - blen = sonsLen(t.sons[i]) + let blen = sonsLen(t.sons[i]) if i > 1: addf(p.s(cpsStmts), "else ", []) if blen == 1: # general except section: catchAllPresent = true - exprBlock(p, t.sons[i].sons[0], d) + startBlock(p) + expr(p, t.sons[i].sons[0], d) + linefmt(p, cpsStmts, "#popCurrentException();$n") + endBlock(p) else: var orExpr: Rope = nil for j in countup(0, blen - 2): @@ -832,7 +827,10 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = "#isObj($1.exp->m_type, $2)", [exc, genTypeInfo(p.module, t.sons[i].sons[j].typ)]) lineF(p, cpsStmts, "if ($1) ", [orExpr]) - exprBlock(p, t.sons[i].sons[blen-1], d) + startBlock(p) + expr(p, t.sons[i].sons[blen-1], d) + linefmt(p, cpsStmts, "#popCurrentException();$n") + endBlock(p) inc(i) # reraise the exception if there was no catch all @@ -912,7 +910,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = startBlock(p, "else {$n") linefmt(p, cpsStmts, "#popSafePoint();$n") if optStackTrace in p.options: - linefmt(p, cpsStmts, "#setFrame((TFrame*)&F);$n") + linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR);$n") inc p.inExceptBlock var i = 1 while (i < length) and (t.sons[i].kind == nkExceptBranch): diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 60ebf591b..3742fd2fd 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -11,20 +11,21 @@ # ------------------------- Name Mangling -------------------------------- -proc mangleField(name: string): string = - result = mangle(name) - result[0] = result[0].toUpper # Mangling makes everything lowercase, - # but some identifiers are C keywords - proc isKeyword(w: PIdent): bool = - # nimrod and C++ share some keywords - # it's more efficient to test the whole nimrod keywords range + # Nim and C++ share some keywords + # it's more efficient to test the whole Nim keywords range case w.id of ccgKeywordsLow..ccgKeywordsHigh, nimKeywordsLow..nimKeywordsHigh, ord(wInline): return true else: return false +proc mangleField(name: PIdent): string = + result = mangle(name.s) + if isKeyword(name): + result[0] = result[0].toUpper # Mangling makes everything lowercase, + # but some identifiers are C keywords + proc mangleName(s: PSym): Rope = result = s.loc.r if result == nil: @@ -110,7 +111,7 @@ proc mapSetType(typ: PType): TCTypeKind = else: result = ctArray proc mapType(typ: PType): TCTypeKind = - ## Maps a nimrod type to a C type + ## Maps a Nim type to a C type case typ.kind of tyNone, tyStmt: result = ctVoid of tyBool: result = ctBool @@ -379,7 +380,7 @@ proc mangleRecFieldName(field: PSym, rectype: PType): Rope = ({sfImportc, sfExportc} * rectype.sym.flags != {}): result = field.loc.r else: - result = rope(mangleField(field.name.s)) + result = rope(mangleField(field.name)) if result == nil: internalError(field.info, "mangleRecFieldName") proc genRecordFieldsAux(m: BModule, n: PNode, @@ -642,7 +643,7 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope = result.add getTypeDescAux(m, typeInSlot, check) else: inc i - + if chunkStart != 0: result.add cppName.data.substr(chunkStart) else: diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index 4e94c1867..4ba6643ec 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -176,7 +176,7 @@ proc mangle*(name: string): string = result = newStringOfCap(name.len) case name[0] of Letters: - result.add(name[0].toLower) + result.add(name[0]) of Digits: result.add("N" & name[0]) else: diff --git a/compiler/guards.nim b/compiler/guards.nim index dc2b24add..df2c1dd75 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -123,7 +123,7 @@ proc neg(n: PNode): PNode = let eAsNode = newIntNode(nkIntLit, e.sym.position) if not inSet(n.sons[1], eAsNode): s.add eAsNode result.sons[1] = s - elif lengthOrd(t) < 1000: + elif t.kind notin {tyString, tySequence} and lengthOrd(t) < 1000: result.sons[1] = complement(n.sons[1]) else: # not ({2, 3, 4}.contains(x)) x != 2 and x != 3 and x != 4 @@ -908,5 +908,5 @@ proc buildProperFieldCheck(access, check: PNode): PNode = proc checkFieldAccess*(m: TModel, n: PNode) = for i in 1..n.len-1: let check = buildProperFieldCheck(n.sons[0], n.sons[i]) - if m.doesImply(check) != impYes: + if check != nil and m.doesImply(check) != impYes: message(n.info, warnProveField, renderTree(n.sons[0])); break diff --git a/compiler/installer.ini b/compiler/installer.ini index 12a8e702d..fff82cb5b 100644 --- a/compiler/installer.ini +++ b/compiler/installer.ini @@ -47,7 +47,7 @@ Start: "doc/overview.html" [Other] Files: "readme.txt;install.txt;contributors.txt;copying.txt" -Files: "configure;makefile" +Files: "makefile" Files: "*.ini" Files: "koch.nim" @@ -70,6 +70,10 @@ Files: "doc/*.nim" Files: "doc/*.cfg" Files: "compiler/nimfix/*.nim" Files: "compiler/nimfix/*.cfg" +Files: "compiler/nimsuggest/*.nim" +Files: "compiler/nimsuggest/*.cfg" +Files: "compiler/plugins/locals/*.nim" +Files: "compiler/plugins/active.nim" Files: "tools/*.nim" Files: "tools/*.cfg" Files: "tools/*.tmpl" @@ -97,13 +101,8 @@ Files: "lib/pure/concurrency/*.cfg" Files: "lib/impure/*.nim" Files: "lib/wrappers/*.nim" -Files: "lib/wrappers/cairo/*.nim" -Files: "lib/wrappers/gtk/*.nim" -Files: "lib/wrappers/lua/*.nim" -Files: "lib/wrappers/opengl/*.nim" Files: "lib/wrappers/readline/*.nim" Files: "lib/wrappers/sdl/*.nim" -Files: "lib/wrappers/x11/*.nim" Files: "lib/wrappers/zip/*.nim" Files: "lib/wrappers/zip/libzip_all.c" @@ -115,8 +114,6 @@ Files: "lib/packages/docutils/*.nim" [Other] Files: "examples/*.nim" -Files: "examples/gtk/*.nim" -Files: "examples/0mq/*.nim" Files: "examples/c++iface/*.nim" Files: "examples/objciface/*.nim" Files: "examples/cross_calculator/" @@ -126,12 +123,109 @@ Files: "examples/*.txt" Files: "examples/*.cfg" Files: "examples/*.tmpl" +Files: "tests/actiontable/*.nim" +Files: "tests/alias/*.nim" +Files: "tests/ambsym/*.nim" +Files: "tests/array/*.nim" +Files: "tests/assign/*.nim" +Files: "tests/astoverload/*.nim" +Files: "tests/async/*.nim" +Files: "tests/benchmarks/*.nim" +Files: "tests/bind/*.nim" +Files: "tests/borrow/*.nim" +Files: "tests/casestmt/*.nim" +Files: "tests/ccgbugs/*.nim" +Files: "tests/clearmsg/*.nim" +Files: "tests/closure/*.nim" +Files: "tests/cnstseq/*.nim" +Files: "tests/collections/*.nim" +Files: "tests/compiles/*.nim" +Files: "tests/concat/*.nim" +Files: "tests/concepts/*.nim" +Files: "tests/constr/*.nim" +Files: "tests/constraints/*.nim" +Files: "tests/controlflow/*.nim" +Files: "tests/converter/*.nim" +Files: "tests/cpp/*.nim" +Files: "tests/defaultprocparam/*.nim" +Files: "tests/deprecated/*.nim" +Files: "tests/destructor/*.nim" +Files: "tests/dir with space/*.nim" +Files: "tests/discard/*.nim" +Files: "tests/distinct/*.nim" +Files: "tests/dll/*.nim" +Files: "tests/effects/*.nim" +Files: "tests/enum/*.nim" +Files: "tests/exception/*.nim" +Files: "tests/exprs/*.nim" +Files: "tests/fields/*.nim" +Files: "tests/float/*.nim" +Files: "tests/friends/*.nim" +Files: "tests/gc/*.nim" +Files: "tests/generics/*.nim" +Files: "tests/gensym/*.nim" +Files: "tests/global/*.nim" +Files: "tests/implicit/*.nim" +Files: "tests/init/*.nim" +Files: "tests/iter/*.nim" +Files: "tests/js/*.nim" +Files: "tests/js/*.cfg" +Files: "tests/let/*.nim" +Files: "tests/lexer/*.nim" +Files: "tests/lookups/*.nim" +Files: "tests/macros/*.nim" +Files: "tests/magics/*.nim" +Files: "tests/metatype/*.nim" +Files: "tests/method/*.nim" +Files: "tests/misc/*.nim" +Files: "tests/modules/*.nim" +Files: "tests/namedparams/*.nim" +Files: "tests/notnil/*.nim" +Files: "tests/objects/*.nim" +Files: "tests/objvariant/*.nim" +Files: "tests/openarray/*.nim" +Files: "tests/osproc/*.nim" +Files: "tests/overflw/*.nim" +Files: "tests/overload/*.nim" +Files: "tests/parallel/*.nim" +Files: "tests/parallel/*.cfg" +Files: "tests/parser/*.nim" +Files: "tests/pragmas/*.nim" +Files: "tests/proc/*.nim" +Files: "tests/procvar/*.nim" +Files: "tests/range/*.nim" +Files: "tests/rodfiles/*.nim" +Files: "tests/seq/*.nim" +Files: "tests/sets/*.nim" +Files: "tests/showoff/*.nim" +Files: "tests/specialops/*.nim" +Files: "tests/stdlib/*.nim" +Files: "tests/system/*.nim" +Files: "tests/template/*.nim" +Files: "tests/testament/*.nim" +Files: "tests/testdata/*.nim" +Files: "tests/threads/*.nim" +Files: "tests/threads/*.cfg" +Files: "tests/trmacros/*.nim" +Files: "tests/tuples/*.nim" +Files: "tests/typerel/*.nim" +Files: "tests/types/*.nim" +Files: "tests/usingstmt/*.nim" +Files: "tests/varres/*.nim" +Files: "tests/varstmt/*.nim" +Files: "tests/vm/*.nim" +Files: "tests/readme.txt" +Files: "tests/testament/css/*.css" +Files: "tests/testament/*.cfg" +Files: "lib/pure/unidecode/unidecode.dat" [Windows] Files: "bin/nim.exe" -Files: "bin/nim_debug.exe" Files: "bin/c2nim.exe" Files: "bin/nimgrep.exe" +Files: "bin/nimsuggest.exe" +Files: "bin/nimble.exe" +Files: "bin/*.dll" Files: "dist/*.dll" Files: "koch.exe" @@ -142,7 +236,7 @@ BinPath: r"bin;dist\mingw\bin;dist" ; Section | dir | zipFile | size hint (in KB) | url | exe start menu entry Download: r"Documentation|doc|docs.zip|13824|http://nim-lang.org/download/docs-${version}.zip|overview.html" Download: r"C Compiler (MingW)|dist|mingw.zip|82944|http://nim-lang.org/download/${mingw}.zip" -Download: r"Aporia IDE|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.1.3.zip|aporia\bin\aporia.exe" +Download: r"Aporia IDE|dist|aporia.zip|97997|http://nim-lang.org/download/aporia-0.3.0.zip|aporia-0.3.0\bin\aporia.exe" ; for now only NSIS supports optional downloads [UnixBin] diff --git a/compiler/nimfix/nimfix.nim.cfg b/compiler/nimfix/nimfix.nim.cfg index 533563a98..73219d6f8 100644 --- a/compiler/nimfix/nimfix.nim.cfg +++ b/compiler/nimfix/nimfix.nim.cfg @@ -5,7 +5,7 @@ hint[XDeclaredButNotUsed]:off path:"$projectPath/.." path:"$lib/packages/docutils" -path:"$nim/compiler" +path:"../../compiler" define:useStdoutAsStdmsg symbol:nimfix diff --git a/compiler/nimsuggest/nimsuggest.nim b/compiler/nimsuggest/nimsuggest.nim index b45ca475c..8285d81d9 100644 --- a/compiler/nimsuggest/nimsuggest.nim +++ b/compiler/nimsuggest/nimsuggest.nim @@ -82,7 +82,8 @@ proc action(cmd: string) = if cmd[i] == ';': i = parseQuoted(cmd, dirtyfile, i+1) i += skipWhile(cmd, seps, i) - var line, col = -1 + var line = -1 + var col = 0 i += parseInt(cmd, line, i) i += skipWhile(cmd, seps, i) i += parseInt(cmd, col, i) @@ -97,7 +98,7 @@ proc action(cmd: string) = resetModule dirtyIdx if dirtyIdx != gProjectMainIdx: resetModule gProjectMainIdx - gTrackPos = newLineInfo(dirtyIdx, line, col) + gTrackPos = newLineInfo(dirtyIdx, line, col-1) #echo dirtyfile, gDirtyBufferIdx, " project ", gProjectMainIdx gErrorCounter = 0 if not isKnownFile: @@ -150,11 +151,11 @@ proc mainCommand = proc processCmdLine*(pass: TCmdLinePass, cmd: string) = var p = parseopt.initOptParser(cmd) - while true: + while true: parseopt.next(p) case p.kind - of cmdEnd: break - of cmdLongoption, cmdShortOption: + of cmdEnd: break + of cmdLongoption, cmdShortOption: case p.key.normalize of "port": gPort = parseInt(p.val).Port of "address": gAddress = p.val diff --git a/compiler/nimsuggest/nimsuggest.nim.cfg b/compiler/nimsuggest/nimsuggest.nim.cfg index 062092f16..acca17396 100644 --- a/compiler/nimsuggest/nimsuggest.nim.cfg +++ b/compiler/nimsuggest/nimsuggest.nim.cfg @@ -6,7 +6,7 @@ hint[XDeclaredButNotUsed]:off path:"$projectPath/../.." path:"$lib/packages/docutils" -path:"$nim/compiler" +path:"../../compiler" define:useStdoutAsStdmsg define:nimsuggest diff --git a/compiler/options.nim b/compiler/options.nim index 65250f519..998ab7781 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -1,7 +1,7 @@ # # # The Nim Compiler -# (c) Copyright 2013 Andreas Rumpf +# (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. diff --git a/compiler/sem.nim b/compiler/sem.nim index 2e13c88c3..346a17df1 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -89,6 +89,10 @@ proc fitNode(c: PContext, formal: PType, arg: PNode): PNode = let x = result.skipConv if x.kind == nkPar and formal.kind != tyExpr: changeType(x, formal, check=true) + else: + result = skipHiddenSubConv(result) + #result.typ = takeType(formal, arg.typ) + #echo arg.info, " picked ", result.typ.typeToString proc inferWithMetatype(c: PContext, formal: PType, arg: PNode, coerceDistincts = false): PNode diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index eb8d0c561..cd6ba3753 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -24,7 +24,7 @@ proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # same as 'semExprWithType' but doesn't check for proc vars result = semExpr(c, n, flags + {efOperand}) - if result.kind == nkEmpty: + if result.kind == nkEmpty and result.typ.isNil: # do not produce another redundant error message: #raiseRecoverableError("") result = errorNode(c, n) @@ -447,6 +447,7 @@ proc changeType(n: PNode, newType: PType, check: bool) = of nkPar: let tup = newType.skipTypes({tyGenericInst}) if tup.kind != tyTuple: + if tup.kind == tyObject: return internalError(n.info, "changeType: no tuple type for constructor") elif sonsLen(n) > 0 and n.sons[0].kind == nkExprColonExpr: # named tuple? @@ -535,43 +536,55 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = result.typ.sons[0] = makeRangeType(c, 0, sonsLen(result) - 1, n.info) proc fixAbstractType(c: PContext, n: PNode) = - # XXX finally rewrite that crap! - for i in countup(1, sonsLen(n) - 1): - var it = n.sons[i] - case it.kind - of nkHiddenStdConv, nkHiddenSubConv: - if it.sons[1].kind == nkBracket: - it.sons[1].typ = arrayConstrType(c, it.sons[1]) - #it.sons[1] = semArrayConstr(c, it.sons[1]) - if skipTypes(it.typ, abstractVar).kind in {tyOpenArray, tyVarargs}: - #if n.sons[0].kind == nkSym and IdentEq(n.sons[0].sym.name, "[]="): - # debug(n) - - var s = skipTypes(it.sons[1].typ, abstractVar) - if s.kind == tyArrayConstr and s.sons[1].kind == tyEmpty: - s = copyType(s, getCurrOwner(), false) - skipTypes(s, abstractVar).sons[1] = elemType( - skipTypes(it.typ, abstractVar)) - it.sons[1].typ = s - elif s.kind == tySequence and s.sons[0].kind == tyEmpty: - s = copyType(s, getCurrOwner(), false) - skipTypes(s, abstractVar).sons[0] = elemType( - skipTypes(it.typ, abstractVar)) - it.sons[1].typ = s - - elif skipTypes(it.sons[1].typ, abstractVar).kind in - {tyNil, tyArrayConstr, tyTuple, tySet}: + for i in 1 .. < n.len: + let it = n.sons[i] + # do not get rid of nkHiddenSubConv for OpenArrays, the codegen needs it: + if it.kind == nkHiddenSubConv and + skipTypes(it.typ, abstractVar).kind notin {tyOpenArray, tyVarargs}: + if skipTypes(it.sons[1].typ, abstractVar).kind in + {tyNil, tyArrayConstr, tyTuple, tySet}: var s = skipTypes(it.typ, abstractVar) if s.kind != tyExpr: changeType(it.sons[1], s, check=true) n.sons[i] = it.sons[1] - of nkBracket: - # an implicitly constructed array (passed to an open array): - n.sons[i] = semArrayConstr(c, it, {}) - else: - discard - #if (it.typ == nil): - # InternalError(it.info, "fixAbstractType: " & renderTree(it)) + when false: + # XXX finally rewrite that crap! + for i in countup(1, sonsLen(n) - 1): + var it = n.sons[i] + case it.kind + of nkHiddenStdConv, nkHiddenSubConv: + if it.sons[1].kind == nkBracket: + it.sons[1].typ = arrayConstrType(c, it.sons[1]) + #it.sons[1] = semArrayConstr(c, it.sons[1]) + if skipTypes(it.typ, abstractVar).kind in {tyOpenArray, tyVarargs}: + #if n.sons[0].kind == nkSym and IdentEq(n.sons[0].sym.name, "[]="): + # debug(n) + + var s = skipTypes(it.sons[1].typ, abstractVar) + if s.kind == tyArrayConstr and s.sons[1].kind == tyEmpty: + s = copyType(s, getCurrOwner(), false) + skipTypes(s, abstractVar).sons[1] = elemType( + skipTypes(it.typ, abstractVar)) + it.sons[1].typ = s + elif s.kind == tySequence and s.sons[0].kind == tyEmpty: + s = copyType(s, getCurrOwner(), false) + skipTypes(s, abstractVar).sons[0] = elemType( + skipTypes(it.typ, abstractVar)) + it.sons[1].typ = s + + elif skipTypes(it.sons[1].typ, abstractVar).kind in + {tyNil, tyArrayConstr, tyTuple, tySet}: + var s = skipTypes(it.typ, abstractVar) + if s.kind != tyExpr: + changeType(it.sons[1], s, check=true) + n.sons[i] = it.sons[1] + of nkBracket: + # an implicitly constructed array (passed to an open array): + n.sons[i] = semArrayConstr(c, it, {}) + else: + discard + #if (it.typ == nil): + # InternalError(it.info, "fixAbstractType: " & renderTree(it)) proc skipObjConv(n: PNode): PNode = case n.kind @@ -2040,7 +2053,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkEmpty, nkNone, nkCommentStmt: discard of nkNilLit: - result.typ = getSysType(tyNil) + if result.typ == nil: result.typ = getSysType(tyNil) of nkIntLit: if result.typ == nil: setIntLitType(result) of nkInt8Lit: diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 796dde9a6..da24005c2 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -286,10 +286,13 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = of mNot: result = newIntNodeT(1 - getInt(a), n) of mCard: result = newIntNodeT(nimsets.cardSet(a), n) of mBitnotI, mBitnotI64: result = newIntNodeT(not getInt(a), n) - of mLengthStr, mXLenStr: result = newIntNodeT(len(getStr(a)), n) + of mLengthStr, mXLenStr: + if a.kind == nkNilLit: result = newIntNodeT(0, n) + else: result = newIntNodeT(len(getStr(a)), n) of mLengthArray: result = newIntNodeT(lengthOrd(a.typ), n) of mLengthSeq, mLengthOpenArray, mXLenSeq: - result = newIntNodeT(sonsLen(a), n) # BUGFIX + if a.kind == nkNilLit: result = newIntNodeT(0, n) + else: result = newIntNodeT(sonsLen(a), n) # BUGFIX of mUnaryPlusI, mUnaryPlusF64: result = a # throw `+` away of mToFloat, mToBiggestFloat: result = newFloatNodeT(toFloat(int(getInt(a))), n) @@ -431,7 +434,8 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = mExit, mInc, ast.mDec, mEcho, mSwap, mAppendStrCh, mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq, mParseExprToAst, mParseStmtToAst, mExpandToAst, mTypeTrait, mDotDot, - mNLen..mNError, mEqRef, mSlurp, mStaticExec, mNGenSym, mSpawn, mParallel: + mNLen..mNError, mEqRef, mSlurp, mStaticExec, mNGenSym, mSpawn, + mParallel, mPlugin: discard else: internalError(a.info, "evalOp(" & $m & ')') @@ -651,7 +655,7 @@ proc getConstExpr(m: PSym, n: PNode): PNode = result = copyNode(n) of nkIfExpr: result = getConstIfExpr(m, n) - of nkCall, nkCommand, nkCallStrLit, nkPrefix, nkInfix: + of nkCallKinds: if n.sons[0].kind != nkSym: return var s = n.sons[0].sym if s.kind != skProc: return diff --git a/compiler/seminst.nim b/compiler/seminst.nim index f72e2dc5b..b2aef63a8 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -187,7 +187,9 @@ proc instantiateProcType(c: PContext, pt: TIdTable, let param = copySym(oldParam) param.owner = prc param.typ = result.sons[i] - param.ast = oldParam.ast.copyTree + if oldParam.ast != nil: + param.ast = fitNode(c, param.typ, oldParam.ast) + # don't be lazy here and call replaceTypeVarsN(cl, originalParams[i])! result.n.sons[i] = newSymNode(param) addDecl(c, param) diff --git a/compiler/semmacrosanity.nim b/compiler/semmacrosanity.nim index 2ef7a54e7..bb9814a16 100644 --- a/compiler/semmacrosanity.nim +++ b/compiler/semmacrosanity.nim @@ -16,9 +16,9 @@ proc ithField(n: PNode, field: int): PSym = result = nil case n.kind of nkRecList: - for i in countup(0, sonsLen(n) - 1): + for i in countup(0, sonsLen(n) - 1): result = ithField(n.sons[i], field-i) - if result != nil: return + if result != nil: return of nkRecCase: if n.sons[0].kind != nkSym: internalError(n.info, "ithField") result = ithField(n.sons[0], field-1) @@ -34,7 +34,7 @@ proc ithField(n: PNode, field: int): PSym = else: discard proc annotateType*(n: PNode, t: PType) = - let x = t.skipTypes(abstractInst) + let x = t.skipTypes(abstractInst+{tyRange}) # Note: x can be unequal to t and we need to be careful to use 't' # to not to skip tyGenericInst case n.kind @@ -80,7 +80,7 @@ proc annotateType*(n: PNode, t: PType) = if x.kind in {tyString, tyCString}: n.typ = t else: - globalError(n.info, "string literal must be of some string type") + globalError(n.info, "string literal must be of some string type") of nkNilLit: if x.kind in NilableTypes: n.typ = t diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 6bc0fa32c..adf03be64 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -560,7 +560,10 @@ proc trackCase(tracked: PEffects, n: PNode) = track(tracked, n.sons[0]) let oldState = tracked.init.len let oldFacts = tracked.guards.len - let interesting = interestingCaseExpr(n.sons[0]) and warnProveField in gNotes + let stringCase = skipTypes(n.sons[0].typ, + abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} + let interesting = not stringCase and interestingCaseExpr(n.sons[0]) and + warnProveField in gNotes var inter: TIntersection = @[] var toCover = 0 for i in 1.. <n.len: @@ -575,13 +578,8 @@ proc trackCase(tracked: PEffects, n: PNode) = for i in oldState.. <tracked.init.len: addToIntersection(inter, tracked.init[i]) - let exh = case skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}).kind - of tyFloat..tyFloat128, tyString: - lastSon(n).kind == nkElse - else: - true setLen(tracked.init, oldState) - if exh: + if not stringCase or lastSon(n).kind == nkElse: for id, count in items(inter): if count >= toCover: tracked.init.add id # else we can't merge diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 304fe6d14..8c7bd7243 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -629,7 +629,7 @@ proc skipGenericInvocation(t: PType): PType {.inline.} = result = t if result.kind == tyGenericInvocation: result = result.sons[0] - if result.kind == tyGenericBody: + while result.kind in {tyGenericInst, tyGenericBody}: result = lastSon(result) proc addInheritedFields(c: PContext, check: var IntSet, pos: var int, diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 5c8a3bc58..2a9d15b5a 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -147,6 +147,7 @@ proc copyCandidate(a: var TCandidate, b: TCandidate) = proc sumGeneric(t: PType): int = var t = t + var isvar = 1 while true: case t.kind of tyGenericInst, tyArray, tyRef, tyPtr, tyDistinct, tyArrayConstr, @@ -154,18 +155,20 @@ proc sumGeneric(t: PType): int = t = t.lastSon inc result of tyVar: - # but do not make 'var T' more specific than 'T'! t = t.sons[0] + inc result + inc isvar of tyGenericInvocation, tyTuple: - result = ord(t.kind == tyGenericInvocation) + result += ord(t.kind == tyGenericInvocation) for i in 0 .. <t.len: result += t.sons[i].sumGeneric break of tyGenericParam, tyExpr, tyStatic, tyStmt, tyTypeDesc: break of tyBool, tyChar, tyEnum, tyObject, tyProc, tyPointer, tyString, tyCString, tyInt..tyInt64, tyFloat..tyFloat128, tyUInt..tyUInt64: - return 1 - else: return 0 + return isvar + else: + return 0 #var ggDebug: bool @@ -919,6 +922,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyAnd: considerPreviousT: + result = isEqual for branch in f.sons: let x = typeRel(c, branch, aOrig) if x < isSubtype: return isNone @@ -1196,15 +1200,6 @@ proc isInlineIterator*(t: PType): bool = result = t.kind == tyIter or (t.kind == tyBuiltInTypeClass and t.base.kind == tyIter) -proc isEmptyContainer*(t: PType): bool = - case t.kind - of tyExpr, tyNil: result = true - of tyArray, tyArrayConstr: result = t.sons[1].kind == tyEmpty - of tySet, tySequence, tyOpenArray, tyVarargs: - result = t.sons[0].kind == tyEmpty - of tyGenericInst: result = isEmptyContainer(t.lastSon) - else: result = false - proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) = case r of isConvertible, isIntConv: inc(m.convMatches, convMatch) @@ -1309,7 +1304,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, if arg.typ == nil: result = arg elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple: - result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c) + result = implicitConv(nkHiddenSubConv, f, arg, m, c) elif arg.typ.isEmptyContainer: result = arg.copyTree result.typ = getInstantiatedType(c, arg, m, f) @@ -1324,7 +1319,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, inc(m.exactMatches) result = arg if skipTypes(f, abstractVar-{tyTypeDesc}).kind in {tyTuple}: - result = implicitConv(nkHiddenStdConv, f, arg, m, c) + result = implicitConv(nkHiddenSubConv, f, arg, m, c) of isNone: # do not do this in ``typeRel`` as it then can't infere T in ``ref T``: if a.kind in {tyProxy, tyUnknown}: @@ -1470,9 +1465,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, m.state = csNoMatch return if formal.typ.kind == tyVar: - if n.isLValue: - inc(m.genericMatches, 100) - else: + if not n.isLValue: m.state = csNoMatch return @@ -1578,6 +1571,8 @@ proc matchesAux(c: PContext, n, nOrig: PNode, #assert(container == nil) if container.isNil: container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, arg)) + else: + incrIndexType(container.typ) addSon(container, arg) setSon(m.call, formal.position + 1, implicitConv(nkHiddenStdConv, formal.typ, container, m, c)) diff --git a/compiler/transf.nim b/compiler/transf.nim index 2143b6bec..57547b682 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -379,6 +379,9 @@ proc transformConv(c: PTransf, n: PNode): PTransNode = result = transformSons(c, n) of tyOpenArray, tyVarargs: result = transform(c, n.sons[1]) + PNode(result).typ = takeType(n.typ, n.sons[1].typ) + #echo n.info, " came here and produced ", typeToString(PNode(result).typ), + # " from ", typeToString(n.typ), " and ", typeToString(n.sons[1].typ) of tyCString: if source.kind == tyString: result = newTransNode(nkStringToCString, n, 1) @@ -713,8 +716,7 @@ proc transform(c: PTransf, n: PNode): PTransNode = add(result, PTransNode(newSymNode(labl))) of nkBreakStmt: result = transformBreak(c, n) of nkWhileStmt: result = transformWhile(c, n) - of nkCall, nkHiddenCallConv, nkCommand, nkInfix, nkPrefix, nkPostfix, - nkCallStrLit: + of nkCallKinds: result = transformCall(c, n) of nkAddr, nkHiddenAddr: result = transformAddrDeref(c, n, nkDerefExpr, nkHiddenDeref) diff --git a/compiler/types.nim b/compiler/types.nim index 7f05e7051..e205f5722 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1439,3 +1439,45 @@ proc skipConv*(n: PNode): PNode = proc skipConvTakeType*(n: PNode): PNode = result = n.skipConv result.typ = n.typ + +proc isEmptyContainer*(t: PType): bool = + case t.kind + of tyExpr, tyNil: result = true + of tyArray, tyArrayConstr: result = t.sons[1].kind == tyEmpty + of tySet, tySequence, tyOpenArray, tyVarargs: + result = t.sons[0].kind == tyEmpty + of tyGenericInst: result = isEmptyContainer(t.lastSon) + else: result = false + +proc takeType*(formal, arg: PType): PType = + # param: openArray[string] = [] + # [] is an array constructor of length 0 of type string! + if arg.kind == tyNil: + # and not (formal.kind == tyProc and formal.callConv == ccClosure): + result = formal + elif formal.kind in {tyOpenArray, tyVarargs, tySequence} and + arg.isEmptyContainer: + let a = copyType(arg.skipTypes({tyGenericInst}), arg.owner, keepId=false) + a.sons[ord(arg.kind in {tyArray, tyArrayConstr})] = formal.sons[0] + result = a + elif formal.kind in {tyTuple, tySet} and arg.kind == formal.kind: + result = formal + else: + result = arg + +proc skipHiddenSubConv*(n: PNode): PNode = + if n.kind == nkHiddenSubConv: + # param: openArray[string] = [] + # [] is an array constructor of length 0 of type string! + let formal = n.typ + result = n.sons[1] + let arg = result.typ + let dest = takeType(formal, arg) + if dest == arg and formal.kind != tyExpr: + #echo n.info, " came here for ", formal.typeToString + result = n + else: + result = copyTree(result) + result.typ = dest + else: + result = n diff --git a/compiler/vm.nim b/compiler/vm.nim index 6fae5a8b7..1c6c9a30b 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -16,7 +16,8 @@ import ast except getstr import strutils, astalgo, msgs, vmdef, vmgen, nimsets, types, passes, unsigned, - parser, vmdeps, idents, trees, renderer, options, transf, parseutils + parser, vmdeps, idents, trees, renderer, options, transf, parseutils, + vmmarshal from semfold import leValueConv, ordinalValToString from evaltempl import evalTemplate @@ -371,11 +372,6 @@ template handleJmpBack() {.dirty.} = globalError(c.debug[pc], errTooManyIterations) dec(c.loopIterations) -proc skipColon(n: PNode): PNode = - result = n - if n.kind == nkExprColonExpr: - result = n.sons[1] - proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = var pc = start var tos = tos @@ -1369,6 +1365,19 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = while typ.kind == tyTypeDesc and typ.len > 0: typ = typ.sons[0] createStr regs[ra] regs[ra].node.strVal = typ.typeToString(preferExported) + of opcMarshalLoad: + let ra = instr.regA + let rb = instr.regB + inc pc + let typ = c.types[c.code[pc].regBx - wordExcess] + putIntoReg(regs[ra], loadAny(regs[rb].node.strVal, typ)) + of opcMarshalStore: + decodeB(rkNode) + inc pc + let typ = c.types[c.code[pc].regBx - wordExcess] + createStrKeepNode(regs[ra]) + if regs[ra].node.strVal.isNil: regs[ra].node.strVal = newStringOfCap(1000) + storeAny(regs[ra].node.strVal, typ, regs[rb].regToNode) inc pc proc execute(c: PCtx, start: int): PNode = diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index b4892d010..047009f01 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -66,7 +66,8 @@ type opcMulSet, opcPlusSet, opcMinusSet, opcSymdiffSet, opcConcatStr, opcContainsSet, opcRepr, opcSetLenStr, opcSetLenSeq, opcSwap, opcIsNil, opcOf, opcIs, - opcSubStr, opcParseFloat, opcConv, opcCast, opcQuit, opcReset, + opcSubStr, opcParseFloat, opcConv, opcCast, + opcQuit, opcReset, opcNarrowS, opcNarrowU, opcAddStrCh, @@ -132,7 +133,8 @@ type opcLdImmInt, # dest = immediate value opcNBindSym, opcSetType, # dest.typ = types[Bx] - opcTypeTrait + opcTypeTrait, + opcMarshalLoad, opcMarshalStore TBlock* = object label*: PSym @@ -221,7 +223,8 @@ proc registerCallback*(c: PCtx; name: string; callback: VmCallback) = const firstABxInstr* = opcTJmp largeInstrs* = { # instructions which use 2 int32s instead of 1: - opcSubStr, opcConv, opcCast, opcNewSeq, opcOf} + opcSubStr, opcConv, opcCast, opcNewSeq, opcOf, + opcMarshalLoad, opcMarshalStore} slotSomeTemp* = slotTempUnknown relativeJumps* = {opcTJmp, opcFJmp, opcJmp, opcJmpBack} diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index b354061a9..0743a4502 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -76,6 +76,11 @@ proc codeListing(c: PCtx, result: var string, start=0; last = -1) = elif opc in {opcLdConst, opcAsgnConst}: result.addf("\t$#\tr$#, $#", ($opc).substr(3), x.regA, c.constants[x.regBx-wordExcess].renderTree) + elif opc in {opcMarshalLoad, opcMarshalStore}: + let y = c.code[i+1] + result.addf("\t$#\tr$#, r$#, $#", ($opc).substr(3), x.regA, x.regB, + c.types[y.regBx-wordExcess].typeToString) + inc i else: result.addf("\t$#\tr$#, $#", ($opc).substr(3), x.regA, x.regBx-wordExcess) result.add("\t#") @@ -696,8 +701,7 @@ proc genCard(c: PCtx; n: PNode; dest: var TDest) = c.gABC(n, opcCard, dest, tmp) c.freeTemp(tmp) -proc genMagic(c: PCtx; n: PNode; dest: var TDest) = - let m = n.sons[0].sym.magic +proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = case m of mAnd: c.genAndOr(n, opcFJmp, dest) of mOr: c.genAndOr(n, opcTJmp, dest) @@ -1028,6 +1032,22 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest) = # mGCref, mGCunref, internalError(n.info, "cannot generate code for: " & $m) +proc genMarshalLoad(c: PCtx, n: PNode, dest: var TDest) = + ## Signature: proc to*[T](data: string): T + if dest < 0: dest = c.getTemp(n.typ) + var tmp = c.genx(n.sons[1]) + c.gABC(n, opcMarshalLoad, dest, tmp) + c.gABx(n, opcMarshalLoad, 0, c.genType(n.typ)) + c.freeTemp(tmp) + +proc genMarshalStore(c: PCtx, n: PNode, dest: var TDest) = + ## Signature: proc `$$`*[T](x: T): string + if dest < 0: dest = c.getTemp(n.typ) + var tmp = c.genx(n.sons[1]) + c.gABC(n, opcMarshalStore, dest, tmp) + c.gABx(n, opcMarshalStore, 0, c.genType(n.sons[1].typ)) + c.freeTemp(tmp) + const atomicTypes = {tyBool, tyChar, tyExpr, tyStmt, tyTypeDesc, tyStatic, @@ -1533,6 +1553,15 @@ proc matches(s: PSym; x: string): bool = dec L result = true +proc matches(s: PSym; y: varargs[string]): bool = + var s = s + var L = y.len-1 + while L >= 0: + if s == nil or y[L].cmpIgnoreStyle(s.name.s) != 0: return false + s = if sfFromGeneric in s.flags: s.owner.owner else: s.owner + dec L + result = true + proc procIsCallback(c: PCtx; s: PSym): bool = if s.offset < -1: return true var i = -2 @@ -1570,8 +1599,17 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = else: internalError(n.info, "cannot generate code for: " & s.name.s) of nkCallKinds: - if n.sons[0].kind == nkSym and n.sons[0].sym.magic != mNone: - genMagic(c, n, dest) + if n.sons[0].kind == nkSym: + let s = n.sons[0].sym + if s.magic != mNone: + genMagic(c, n, dest, s.magic) + elif matches(s, "stdlib", "marshal", "to"): + genMarshalLoad(c, n, dest) + elif matches(s, "stdlib", "marshal", "$$"): + genMarshalStore(c, n, dest) + else: + genCall(c, n, dest) + clearDest(c, n, dest) else: genCall(c, n, dest) clearDest(c, n, dest) diff --git a/compiler/vmmarshal.nim b/compiler/vmmarshal.nim new file mode 100644 index 000000000..293d0d949 --- /dev/null +++ b/compiler/vmmarshal.nim @@ -0,0 +1,283 @@ +# +# +# The Nim Compiler +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Implements marshaling for the VM. + +import streams, json, intsets, tables, ast, astalgo, idents, types, msgs + +proc ptrToInt(x: PNode): int {.inline.} = + result = cast[int](x) # don't skip alignment + +proc getField(n: PNode; position: int): PSym = + case n.kind + of nkRecList: + for i in countup(0, sonsLen(n) - 1): + result = getField(n.sons[i], position) + if result != nil: return + of nkRecCase: + result = getField(n.sons[0], position) + if result != nil: return + for i in countup(1, sonsLen(n) - 1): + case n.sons[i].kind + of nkOfBranch, nkElse: + result = getField(lastSon(n.sons[i]), position) + if result != nil: return + else: internalError(n.info, "getField(record case branch)") + of nkSym: + if n.sym.position == position: result = n.sym + else: discard + +proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) + +proc storeObj(s: var string; typ: PType; x: PNode; stored: var IntSet) = + internalAssert x.kind in {nkObjConstr, nkPar} + let start = ord(x.kind == nkObjConstr) + for i in countup(start, sonsLen(x) - 1): + if i > start: s.add(", ") + var it = x.sons[i] + if it.kind == nkExprColonExpr: + internalAssert it.sons[0].kind == nkSym + let field = it.sons[0].sym + s.add(escapeJson(field.name.s)) + s.add(": ") + storeAny(s, field.typ, it.sons[1], stored) + elif typ.n != nil: + let field = getField(typ.n, i) + s.add(escapeJson(field.name.s)) + s.add(": ") + storeAny(s, field.typ, it, stored) + +proc skipColon*(n: PNode): PNode = + result = n + if n.kind == nkExprColonExpr: + result = n.sons[1] + +proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) = + case t.kind + of tyNone: assert false + of tyBool: s.add($(a.intVal != 0)) + of tyChar: + let ch = char(a.intVal) + if ch < '\128': + s.add(escapeJson($ch)) + else: + s.add($int(ch)) + of tyArray, tySequence: + if t.kind == tySequence and a.kind == nkNilLit: s.add("null") + else: + s.add("[") + for i in 0 .. a.len-1: + if i > 0: s.add(", ") + storeAny(s, t.elemType, a[i], stored) + s.add("]") + of tyTuple: + s.add("{") + for i in 0.. <t.len: + if i > 0: s.add(", ") + s.add("\"Field" & $i) + s.add("\": ") + storeAny(s, t.sons[i], a[i].skipColon, stored) + s.add("}") + of tyObject: + s.add("{") + storeObj(s, t, a, stored) + s.add("}") + of tySet: + s.add("[") + for i in 0.. <a.len: + if i > 0: s.add(", ") + if a[i].kind == nkRange: + var x = copyNode(a[i][0]) + storeAny(s, t.lastSon, x, stored) + while x.intVal+1 <= a[i][1].intVal: + s.add(", ") + storeAny(s, t.lastSon, x, stored) + inc x.intVal + else: + storeAny(s, t.lastSon, a[i], stored) + s.add("]") + of tyRange, tyGenericInst: storeAny(s, t.lastSon, a, stored) + of tyEnum: + # we need a slow linear search because of enums with holes: + for e in items(t.n): + if e.sym.position == a.intVal: + s.add e.sym.name.s.escapeJson + break + of tyPtr, tyRef: + var x = a + if isNil(x) or x.kind == nkNilLit: s.add("null") + elif stored.containsOrIncl(x.ptrToInt): + # already stored, so we simply write out the pointer as an int: + s.add($x.ptrToInt) + else: + # else as a [value, key] pair: + # (reversed order for convenient x[0] access!) + s.add("[") + s.add($x.ptrToInt) + s.add(", ") + storeAny(s, t.lastSon, a, stored) + s.add("]") + of tyString, tyCString: + if a.kind == nkNilLit or a.strVal.isNil: s.add("null") + else: s.add(escapeJson(a.strVal)) + of tyInt..tyInt64, tyUInt..tyUInt64: s.add($a.intVal) + of tyFloat..tyFloat128: s.add($a.floatVal) + else: + internalError a.info, "cannot marshal at compile-time " & t.typeToString + +proc storeAny*(s: var string; t: PType; a: PNode) = + var stored = initIntSet() + storeAny(s, t, a, stored) + +proc loadAny(p: var JsonParser, t: PType, + tab: var Table[BiggestInt, PNode]): PNode = + case t.kind + of tyNone: assert false + of tyBool: + case p.kind + of jsonFalse: result = newIntNode(nkIntLit, 0) + of jsonTrue: result = newIntNode(nkIntLit, 1) + else: raiseParseErr(p, "'true' or 'false' expected for a bool") + next(p) + of tyChar: + if p.kind == jsonString: + var x = p.str + if x.len == 1: + result = newIntNode(nkIntLit, ord(x[0])) + next(p) + return + elif p.kind == jsonInt: + result = newIntNode(nkIntLit, getInt(p)) + next(p) + return + raiseParseErr(p, "string of length 1 expected for a char") + of tyEnum: + if p.kind == jsonString: + for e in items(t.n): + if e.sym.name.s == p.str: + result = newIntNode(nkIntLit, e.sym.position) + next(p) + return + raiseParseErr(p, "string expected for an enum") + of tyArray: + if p.kind != jsonArrayStart: raiseParseErr(p, "'[' expected for an array") + next(p) + result = newNode(nkBracket) + while p.kind != jsonArrayEnd and p.kind != jsonEof: + result.add loadAny(p, t.elemType, tab) + if p.kind == jsonArrayEnd: next(p) + else: raiseParseErr(p, "']' end of array expected") + of tySequence: + case p.kind + of jsonNull: + result = newNode(nkNilLit) + next(p) + of jsonArrayStart: + next(p) + result = newNode(nkBracket) + while p.kind != jsonArrayEnd and p.kind != jsonEof: + result.add loadAny(p, t.elemType, tab) + if p.kind == jsonArrayEnd: next(p) + else: raiseParseErr(p, "") + else: + raiseParseErr(p, "'[' expected for a seq") + of tyTuple: + if p.kind != jsonObjectStart: raiseParseErr(p, "'{' expected for an object") + next(p) + result = newNode(nkPar) + var i = 0 + while p.kind != jsonObjectEnd and p.kind != jsonEof: + if p.kind != jsonString: + raiseParseErr(p, "string expected for a field name") + next(p) + if i >= t.len: + raiseParseErr(p, "too many fields to tuple type " & typeToString(t)) + result.add loadAny(p, t.sons[i], tab) + inc i + if p.kind == jsonObjectEnd: next(p) + else: raiseParseErr(p, "'}' end of object expected") + of tyObject: + if p.kind != jsonObjectStart: raiseParseErr(p, "'{' expected for an object") + next(p) + result = newNode(nkPar) + result.sons = @[] + while p.kind != jsonObjectEnd and p.kind != jsonEof: + if p.kind != jsonString: + raiseParseErr(p, "string expected for a field name") + let field = lookupInRecord(t.n, getIdent(p.str)) + if field.isNil: + raiseParseErr(p, "unknown field for object of type " & typeToString(t)) + next(p) + if field.position >= result.sons.len: + setLen(result.sons, field.position+1) + result.sons[field.position] = loadAny(p, field.typ, tab) + if p.kind == jsonObjectEnd: next(p) + else: raiseParseErr(p, "'}' end of object expected") + of tySet: + if p.kind != jsonArrayStart: raiseParseErr(p, "'[' expected for a set") + next(p) + result = newNode(nkCurly) + while p.kind != jsonArrayEnd and p.kind != jsonEof: + result.add loadAny(p, t.lastSon, tab) + next(p) + if p.kind == jsonArrayEnd: next(p) + else: raiseParseErr(p, "']' end of array expected") + of tyPtr, tyRef: + case p.kind + of jsonNull: + result = newNode(nkNilLit) + next(p) + of jsonInt: + result = tab[p.getInt] + if result.isNil: + raiseParseErr(p, "cannot load object with address " & $p.getInt) + next(p) + of jsonArrayStart: + next(p) + if p.kind == jsonInt: + let idx = p.getInt + next(p) + result = loadAny(p, t.lastSon, tab) + tab[idx] = result + else: raiseParseErr(p, "index for ref type expected") + if p.kind == jsonArrayEnd: next(p) + else: raiseParseErr(p, "']' end of ref-address pair expected") + else: raiseParseErr(p, "int for pointer type expected") + of tyString, tyCString: + case p.kind + of jsonNull: + result = newNode(nkNilLit) + next(p) + of jsonString: + result = newStrNode(nkStrLit, p.str) + next(p) + else: raiseParseErr(p, "string expected") + of tyInt..tyInt64, tyUInt..tyUInt64: + if p.kind == jsonInt: + result = newIntNode(nkIntLit, getInt(p)) + next(p) + return + raiseParseErr(p, "int expected") + of tyFloat..tyFloat128: + if p.kind == jsonFloat: + result = newFloatNode(nkFloatLit, getFloat(p)) + next(p) + return + raiseParseErr(p, "float expected") + of tyRange, tyGenericInst: result = loadAny(p, t.lastSon, tab) + else: + internalError "cannot marshal at compile-time " & t.typeToString + +proc loadAny*(s: string; t: PType): PNode = + var tab = initTable[BiggestInt, PNode]() + var p: JsonParser + open(p, newStringStream(s), "unknown file") + next(p) + result = loadAny(p, t, tab) + close(p) diff --git a/config/nim.cfg b/config/nim.cfg index ccb9977db..fef7df79e 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -96,7 +96,7 @@ path="$lib/pure/unidecode" # Configuration for the GNU C/C++ compiler: @if windows: - #gcc.path = r"$nimrod\dist\mingw\bin" + #gcc.path = r"$nim\dist\mingw\bin" @if gcc: tlsEmulation:on @end diff --git a/doc/lib.txt b/doc/lib.txt index 385e7a91a..1c0278068 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -37,7 +37,7 @@ Core * `unsigned <unsigned.html>`_ This module implements basic arithmetic operators for unsigned integers. - To discourage users from using unsigned integers, it's not part + To discourage users from using unsigned integers, it's not part of ``system``, but an extra import. * `threads <threads.html>`_ @@ -45,7 +45,7 @@ Core import it explicitly. * `channels <channels.html>`_ - Nim message passing support for threads. **Note**: This is part of the + Nim message passing support for threads. **Note**: This is part of the system module. Do not import it explicitly. * `locks <locks.html>`_ @@ -55,7 +55,7 @@ Core Contains the AST API and documentation of Nim for writing macros. * `typeinfo <typeinfo.html>`_ - Provides (unsafe) access to Nim's run time type information. + Provides (unsafe) access to Nim's run time type information. * `typetraits <typetraits.html>`_ This module defines compile-time reflection procs for working with types. @@ -110,9 +110,9 @@ String handling * `unicode <unicode.html>`_ This module provides support to handle the Unicode UTF-8 encoding. - + * `encodings <encodings.html>`_ - Converts between different character encodings. On UNIX, this uses + Converts between different character encodings. On UNIX, this uses the ``iconv`` library, on Windows the Windows API. * `pegs <pegs.html>`_ @@ -159,7 +159,7 @@ Generic Operating System Services may provide other implementations for this standard stream interface. * `marshal <marshal.html>`_ - Contains procs for serialization and deseralization of arbitrary Nim + Contains procs for serialization and deseralization of arbitrary Nim data structures. * `terminal <terminal.html>`_ @@ -168,7 +168,7 @@ Generic Operating System Services sequences and does not depend on any other module. * `memfiles <memfiles.html>`_ - This module provides support for memory mapped files (Posix's ``mmap``) + This module provides support for memory mapped files (Posix's ``mmap``) on the different operating systems. * `fsmonitor <fsmonitor.html>`_ @@ -228,7 +228,7 @@ Internet Protocols and Support This module implements a simple HTTP client. * `smtp <smtp.html>`_ - This module implement a simple SMTP client. + This module implement a simple SMTP client. * `ftpclient <ftpclient.html>`_ This module implements an FTP client. @@ -346,7 +346,7 @@ XML Processing This module parses an HTML document and creates its XML tree representation. * `htmlgen <htmlgen.html>`_ - This module implements a simple XML and HTML code + This module implements a simple XML and HTML code generator. Each commonly used HTML tag has a corresponding macro that generates a string with its HTML representation. @@ -381,7 +381,7 @@ Miscellaneous * `oids <oids.html>`_ An OID is a global ID that consists of a timestamp, - a unique counter and a random value. This combination should suffice to + a unique counter and a random value. This combination should suffice to produce a globally distributed unique ID. This implementation was extracted from the Mongodb interface and it thus binary compatible with a Mongo OID. @@ -453,12 +453,8 @@ Other * `zipfiles <zipfiles.html>`_ This module implements a zip archive creator/reader/modifier. -* `web <web.html>`_ - This module contains simple high-level procedures for dealing with the - Web like loading the contents of a Web page from an URL. - * `ssl <ssl.html>`_ - This module provides an easy to use sockets-style + This module provides an easy to use sockets-style Nim interface to the OpenSSL library. * `rdstdin <rdstdin.html>`_ @@ -513,25 +509,6 @@ Regular expressions Wrapper for the TRE library. -Graphics libraries ------------------- - -* `sdl <sdl.html>`_ - Part of the wrapper for SDL. -* `sdl_gfx <sdl_gfx.html>`_ - Part of the wrapper for SDL. -* `sdl_image <sdl_image.html>`_ - Part of the wrapper for SDL. -* `sdl_mixer <sdl_mixer.html>`_ - Part of the wrapper for SDL. -* `sdl_net <sdl_net.html>`_ - Part of the wrapper for SDL. -* `sdl_ttf <sdl_ttf.html>`_ - Part of the wrapper for SDL. -* `smpeg <smpeg.html>`_ - Part of the wrapper for SDL. - - GUI libraries ------------- @@ -591,7 +568,7 @@ Data Compression and Archiving Scientific computing -------------------- -* `libsvm <libsvm.html>`_ +* `libsvm <libsvm.html>`_ Low level wrapper for `lib svm <http://www.csie.ntu.edu.tw/~cjlin/libsvm/>`_. Nimble diff --git a/doc/nimsuggest.txt b/doc/nimsuggest.txt new file mode 100644 index 000000000..2b52196b9 --- /dev/null +++ b/doc/nimsuggest.txt @@ -0,0 +1,174 @@ +================================ + Nim IDE Integration Guide +================================ + +:Author: Unknown +:Version: |nimversion| + +.. contents:: + + +Nim differs from many other compilers in that it is really fast, +and being so fast makes it suited to provide external queries for +text editors about the source code being written. Through the +``nimsuggest`` tool, any IDE +can query a ``.nim`` source file and obtain useful information like +definition of symbols or suggestions for completion. + +This document will guide you through the available options. If you +want to look at practical examples of nimsuggest support you can look +at the +`various editor integrations <https://github.com/Araq/Nim/wiki/Editor-Support>`_ +already available. + + +Installation +============ + +Nimsuggest is available as a Nimble package but currently does not install +properly via Nimble. As nimsuggest is part of the compiler it also doesn't make +too much sense as a Nimble package. Instead we will do the building manually:: + + cd compiler/nimsuggest + nim c -d:release nimsuggest + cp nimsuggest ../../bin + # OR: copy the nimsuggest binary to where your 'nim' binary is + cd ../.. + + + +Nimsuggest invocation +===================== + +Run it via ``nimsuggest --stdin myproject.nim``. Nimsuggest is a server that +takes queries that are related to ``myproject``. There is some support so that +you can throw random ``.nim`` files which are not part of ``myproject`` at +Nimsuggest too, but usually the query refer to modules/files that are part of +``myproject``. + +``--stdin`` means that Nimsuggest reads the query from ``stdin``. This is great +for testing things out and playing with it but for an editor communication +via sockets is more reasonable so that is the default. It listens to port 6000 +by default. + + +Specifying the location of the query +------------------------------------ + +Nimsuggest than waits for queries to process. A query consists of a +cryptic 3 letter "command" ``def`` or ``con`` or ``sug`` or ``use`` followed by +a location. A query location consists of: + + +``file.nim`` + This is the name of the module or include file the query refers to. + +``dirtyfile.nim`` + This is optional. + + The ``file`` paramater is enough for static analysis, but IDEs + tend to have *unsaved buffers* where the user may still be in + the middle of typing a line. In such situations the IDE can + save the current contents to a temporary file and then use the + ``dirtyfile.nim`` option to tell Nimsuggest that ``foobar.nim`` should + be taken from ``temporary/foobar.nim``. + + +``line`` + An integer with the line you are going to query. For the compiler + lines start at **1**. + +``col`` + An integer with the column you are going to query. For the + compiler columns start at **1**. + + +Definitions +----------- + +The ``def`` Nimsuggest command performs a query about the definition +of a specific symbol. If available, Nimsuggest will answer with the +type, source file, line/column information and other accessory data +if available like a docstring. With this information an IDE can +provide the typical *Jump to definition* where a user puts the +cursor on a symbol or uses the mouse to select it and is redirected +to the place where the symbol is located. + +Since Nim is implemented in Nim, one of the nice things of +this feature is that any user with an IDE supporting it can quickly +jump around the standard library implementation and see exactly +what a proc does, learning about the language and seeing real life +examples of how to write/implement specific features. + +Nimsuggest will always answer with a single definition or none if it +can't find any valid symbol matching the position of the query. + + +Suggestions +----------- + +The ``sug`` Nimsuggest command performs a query about possible +completion symbols at some point in the file. + +The typical usage scenario for this option is to call it after the +user has typed the dot character for `the object oriented call +syntax <tut2.html#method-call-syntax>`_. Nimsuggest will try to return +the suggestions sorted first by scope (from innermost to outermost) +and then by item name. + + +Invocation context +------------------ + +The ``con`` Nimsuggest command is very similar to the suggestions +command, but instead of being used after the user has typed a dot +character, this one is meant to be used after the user has typed +an opening brace to start typing parameters. + + +Symbol usages +------------- + +The ``use`` Nimsuggest command lists all usages of the symbol at +a position. IDEs can use this to find all the places in the file +where the symbol is used and offer the user to rename it in all +places at the same time. + +For this kind of query the IDE will most likely ignore all the +type/signature info provided by Nimsuggest and concentrate on the +filename, line and column position of the multiple returned answers. + + + +Parsing nimsuggest output +========================= + +Nimsuggest output is always returned on single lines separated by +tab characters (``\t``). The values of each column are: + +1. Three characters indicating the type of returned answer (e.g. + ``def`` for definition, ``sug`` for suggestion, etc). +2. Type of the symbol. This can be ``skProc``, ``skLet``, and just + about any of the enums defined in the module ``compiler/ast.nim``. +3. Full qualitifed path of the symbol. If you are querying a symbol + defined in the ``proj.nim`` file, this would have the form + ``proj.symbolName``. +4. Type/signature. For variables and enums this will contain the + type of the symbol, for procs, methods and templates this will + contain the full unique signature (e.g. ``proc (File)``). +5. Full path to the file containing the symbol. +6. Line where the symbol is located in the file. Lines start to + count at **1**. +7. Column where the symbol is located in the file. Columns start + to count at **1**. +8. Docstring for the symbol if available or the empty string. To + differentiate the docstring from end of answer, + the docstring is always provided enclosed in double quotes, and + if the docstring spans multiple lines, all following lines of the + docstring will start with a blank space to align visually with + the starting quote. + + Also, you won't find raw ``\n`` characters breaking the one + answer per line format. Instead you will need to parse sequences + in the form ``\xHH``, where *HH* is a hexadecimal value (e.g. + newlines generate the sequence ``\x0A``). diff --git a/doc/tools.txt b/doc/tools.txt index 7f2830879..b0a45c575 100644 --- a/doc/tools.txt +++ b/doc/tools.txt @@ -4,6 +4,11 @@ Tools available with Nim The standard distribution ships with the following tools: +- | `Nimsuggest for IDE support <nimsuggest.html>`_ + | Through the ``nimsuggest`` tool, any IDE can query a ``.nim`` source file + and obtain useful information like definition of symbols or suggestions for + completion. + - | `Nim Installation Generator <niminst.html>`_ | How to generate a nice installer for your Nim program. diff --git a/doc/tut1.txt b/doc/tut1.txt index cb5a0c8dd..58ace1dbe 100644 --- a/doc/tut1.txt +++ b/doc/tut1.txt @@ -16,7 +16,7 @@ Introduction </p></blockquote> -This document is a tutorial for the programming language *Nim*. +This document is a tutorial for the programming language *Nim*. This tutorial assumes that you are familiar with basic programming concepts like variables, types or statements but is kept very basic. The `manual <manual.html>`_ contains many more examples of the advanced language features. @@ -50,7 +50,7 @@ Commonly used commands and switches have abbreviations, so you can also use:: nim c -r greetings.nim To compile a release version use:: - + nim c -d:release greetings.nim By default the Nim compiler generates a large amount of runtime checks @@ -116,7 +116,7 @@ hash character ``#``. Documentation comments start with ``##``: .. code-block:: nim # A comment. - + var myVariable: int ## a documentation comment @@ -200,7 +200,7 @@ constant declaration at compile time: .. code-block:: nim const x = "abc" # the constant x contains the string "abc" - + Indentation can be used after the ``const`` keyword to list a whole section of constants: @@ -214,7 +214,7 @@ constants: The let statement ================= -The ``let`` statement works like the ``var`` statement but the declared +The ``let`` statement works like the ``var`` statement but the declared symbols are *single assignment* variables: After the initialization their value cannot change: @@ -228,7 +228,7 @@ and put it into a data section": .. code-block:: const input = readLine(stdin) # Error: constant expression expected - + .. code-block:: let input = readLine(stdin) # works @@ -310,8 +310,8 @@ the compiler that for every other value nothing should be done: else: discard The empty `discard statement`_ is a *do nothing* statement. The compiler knows -that a case statement with an else part cannot fail and thus the error -disappears. Note that it is impossible to cover all possible string values: +that a case statement with an else part cannot fail and thus the error +disappears. Note that it is impossible to cover all possible string values: that is why string cases always need an ``else`` branch. In general the case statement is used for subrange types or enumerations where @@ -406,7 +406,7 @@ The block's *label* (``myblock`` in the example) is optional. Break statement --------------- A block can be left prematurely with a ``break`` statement. The break statement -can leave a ``while``, ``for``, or a ``block`` statement. It leaves the +can leave a ``while``, ``for``, or a ``block`` statement. It leaves the innermost construct, unless a label of a block is given: .. code-block:: nim @@ -461,7 +461,7 @@ differences: * The statements within a branch do not open a new scope. * The compiler checks the semantics and produces code *only* for the statements that belong to the first condition that evaluates to ``true``. - + The ``when`` statement is useful for writing platform specific code, similar to the ``#ifdef`` construct in the C programming language. @@ -486,14 +486,14 @@ to be indented, but single simple statements do not: .. code-block:: nim # no indentation needed for single assignment statement: if x: x = false - + # indentation needed for nested if statement: if x: if y: y = false else: y = true - + # indentation needed, because two statements follow the condition: if x: x = false @@ -514,7 +514,7 @@ contain indentation at certain places for better readability: As a rule of thumb, indentation within expressions is allowed after operators, an open parenthesis and after commas. -With parenthesis and semicolons ``(;)`` you can use statements where only +With parenthesis and semicolons ``(;)`` you can use statements where only an expression is allowed: .. code-block:: nim @@ -560,45 +560,45 @@ Some terminology: in the example ``question`` is called a (formal) *parameter*, Result variable --------------- -A procedure that returns a value has an implicit ``result`` variable declared +A procedure that returns a value has an implicit ``result`` variable declared that represents the return value. A ``return`` statement with no expression is a -shorthand for ``return result``. The ``result`` value is always returned +shorthand for ``return result``. The ``result`` value is always returned automatically at the end a procedure if there is no ``return`` statement at the exit. .. code-block:: nim - proc sumTillNegative(x: varargs[int]): int = + proc sumTillNegative(x: varargs[int]): int = for i in x: if i < 0: return - result = result + i - + result = result + i + echo sumTillNegative() # echos 0 echo sumTillNegative(3, 4, 5) # echos 12 echo sumTillNegative(3, 4 , -1 , 6) # echos 7 -The ``result`` variable is already implicitly declared at the start of the +The ``result`` variable is already implicitly declared at the start of the function, so declaring it again with 'var result', for example, would shadow it with a normal variable of the same name. The result variable is also already initialised with the type's default value. Note that referential data types will be ``nil`` at the start of the procedure, and thus may require manual initialisation. - + Parameters ---------- Parameters are constant in the procedure body. By default, their value cannot be -changed because this allows the compiler to implement parameter passing in the +changed because this allows the compiler to implement parameter passing in the most efficient way. If a mutable variable is needed inside the procedure, it has to be declared with ``var`` in the procedure body. Shadowing the parameter name -is possible, and actually an idiom: +is possible, and actually an idiom: .. code-block:: nim proc printSeq(s: seq, nprinted: int = -1) = var nprinted = if nprinted == -1: s.len else: min(nprinted, s.len) for i in 0 .. <nprinted: echo s[i] - + If the procedure needs to modify the argument for the caller, a ``var`` parameter can be used: @@ -630,12 +630,12 @@ allow to silently throw away a return value: The return value can be ignored implicitly if the called proc/iterator has -been declared with the ``discardable`` pragma: +been declared with the ``discardable`` pragma: .. code-block:: nim - proc p(x, y: int): int {.discardable.} = + proc p(x, y: int): int {.discardable.} = return x + y - + p(3, 4) # now valid The ``discard`` statement can also be used to create block comments as @@ -899,7 +899,7 @@ object on the heap, so there is a trade-off to be made here. Integers -------- -Nim has these integer types built-in: +Nim has these integer types built-in: ``int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64``. The default integer type is ``int``. Integer literals can have a *type suffix* @@ -1114,7 +1114,7 @@ Arrays An array is a simple fixed length container. Each element in the array has the same type. The array's index type can be any ordinal type. -Arrays can be constructed via ``[]``: +Arrays can be constructed via ``[]``: .. code-block:: nim @@ -1370,12 +1370,12 @@ integer. var building: tuple[street: string, number: int] building = ("Rue del Percebe", 13) echo(building.street) - + # The following line does not compile, they are different tuples! #person = building # --> Error: type mismatch: got (tuple[street: string, number: int]) # but expected 'Person' - + # The following works because the field names and types are the same. var teacher: tuple[name: string, age: int] = ("Mark", 42) person = teacher @@ -1450,13 +1450,13 @@ operators perform implicit dereferencing operations for reference types: type Node = ref NodeObj - NodeObj = object - le, ri: PNode + NodeObj = object + le, ri: Node data: int var n: Node new(n) - n.data = 9 + n.data = 9 # no need to write n[].data; in fact n[].data is highly discouraged! To allocate a new traced object, the built-in procedure ``new`` has to be used. @@ -1559,9 +1559,9 @@ This is best illustrated by an example: A symbol of a module *can* be *qualified* with the ``module.symbol`` syntax. If -the symbol is ambiguous, it even *has* to be qualified. A symbol is ambiguous -if it is defined in two (or more) different modules and both modules are -imported by a third one: +the symbol is ambiguous, it even *has* to be qualified. A symbol is ambiguous +if it is defined in two (or more) different modules and both modules are +imported by a third one: .. code-block:: nim # Module A diff --git a/koch.nim b/koch.nim index 3ebfb6655..55019b544 100644 --- a/koch.nim +++ b/koch.nim @@ -106,6 +106,12 @@ proc zip(args: string) = exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim zip compiler/installer.ini" % ["tools/niminst/niminst".exe, VersionAsString]) +proc targz(args: string) = + exec("$3 cc -r $2 --var:version=$1 --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" % + [VersionAsString, compileNimInst, findNim()]) + exec("$# --var:version=$# --var:mingw=none --main:compiler/nim.nim targz compiler/installer.ini" % + ["tools" / "niminst" / "niminst".exe, VersionAsString]) + proc buildTool(toolname, args: string) = exec("$# cc $# $#" % [findNim(), args, toolname]) copyFile(dest="bin"/ splitFile(toolname).name.exe, source=toolname.exe) @@ -113,12 +119,12 @@ proc buildTool(toolname, args: string) = proc nsis(args: string) = # make sure we have generated the niminst executables: buildTool("tools/niminst/niminst", args) - buildTool("tools/nimgrep", args) + #buildTool("tools/nimgrep", args) # produce 'nim_debug.exe': - exec "nim c compiler" / "nim.nim" - copyExe("compiler/nim".exe, "bin/nim_debug".exe) + #exec "nim c compiler" / "nim.nim" + #copyExe("compiler/nim".exe, "bin/nim_debug".exe) exec(("tools" / "niminst" / "niminst --var:version=$# --var:mingw=mingw$#" & - " nsis compiler/nim") % [VersionAsString, $(sizeof(pointer)*8)]) + " nsis compiler/installer.ini") % [VersionAsString, $(sizeof(pointer)*8)]) proc install(args: string) = exec("$# cc -r $# --var:version=$# --var:mingw=none --main:compiler/nim.nim scripts compiler/installer.ini" % @@ -308,12 +314,14 @@ proc winRelease() = #buildTool("tools/niminst/niminst", " -d:release") buildTool("tools/nimgrep", " -d:release") buildTool("compiler/nimfix/nimfix", " -d:release") + buildTool("compiler/nimsuggest/nimsuggest", " -d:release") + + #run7z("win32", "bin/nim.exe", "bin/c2nim.exe", "bin/nimgrep.exe", + # "bin/nimfix.exe", + # "bin/nimble.exe", "bin/*.dll", + # "config", "dist/*.dll", "examples", "lib", + # "readme.txt", "contributors.txt", "copying.txt") - run7z("win32", "bin/nim.exe", "bin/c2nim.exe", "bin/nimgrep.exe", - "bin/nimfix.exe", - "bin/nimble.exe", "bin/*.dll", - "config", "dist/*.dll", "examples", "lib", - "readme.txt", "contributors.txt", "copying.txt") # second step: XXX build 64 bit version # -------------- tests -------------------------------------------------------- @@ -357,6 +365,7 @@ of cmdArgument: of "pdf": pdf() of "csource", "csources": csource(op.cmdLineRest) of "zip": zip(op.cmdLineRest) + of "targz": targz(op.cmdLineRest) of "nsis": nsis(op.cmdLineRest) of "install": install(op.cmdLineRest) of "test", "tests": tests(op.cmdLineRest) diff --git a/lib/impure/rdstdin.nim b/lib/impure/rdstdin.nim index 55f8c5d32..f4d00979c 100644 --- a/lib/impure/rdstdin.nim +++ b/lib/impure/rdstdin.nim @@ -135,7 +135,7 @@ else: var cur, old: Termios discard fd.tcgetattr(cur.addr) old = cur - cur.lflag = cur.lflag and not Tcflag(ECHO) + cur.c_lflag = cur.c_lflag and not Tcflag(ECHO) discard fd.tcsetattr(TCSADRAIN, cur.addr) stdout.write prompt result = stdin.readLine(password) diff --git a/lib/impure/re.nim b/lib/impure/re.nim index fb95610f6..279f8aadd 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -146,8 +146,8 @@ proc findBounds*(s: string, pattern: Regex, proc findBounds*(s: string, pattern: Regex, start = 0): tuple[first, last: int] = - ## returns the starting position of `pattern` in `s`. If it does not - ## match, ``(-1,0)`` is returned. + ## returns the starting position and end position of ``pattern`` in ``s``. + ## If it does not match, ``(-1,0)`` is returned. var rtarray = initRtArray[cint](3) rawMatches = rtarray.getRawData diff --git a/lib/impure/zipfiles.nim b/lib/impure/zipfiles.nim index c22294061..b41ca1e4b 100644 --- a/lib/impure/zipfiles.nim +++ b/lib/impure/zipfiles.nim @@ -9,8 +9,8 @@ ## This module implements a zip archive creator/reader/modifier. -import - streams, libzip, times, os +import + streams, libzip, times, os, strutils type TZipArchive* = object of RootObj ## represents a zip archive @@ -18,14 +18,14 @@ type w: PZip -proc zipError(z: var TZipArchive) = +proc zipError(z: var TZipArchive) = var e: ref IOError new(e) e.msg = $zip_strerror(z.w) raise e - + proc open*(z: var TZipArchive, filename: string, mode: FileMode = fmRead): bool = - ## Opens a zip file for reading, writing or appending. All file modes are + ## Opens a zip file for reading, writing or appending. All file modes are ## supported. Returns true iff successful, false otherwise. var err, flags: int32 case mode @@ -41,21 +41,21 @@ proc open*(z: var TZipArchive, filename: string, mode: FileMode = fmRead): bool proc close*(z: var TZipArchive) = ## Closes a zip file. zip_close(z.w) - -proc createDir*(z: var TZipArchive, dir: string) = + +proc createDir*(z: var TZipArchive, dir: string) = ## Creates a directory within the `z` archive. This does not fail if the - ## directory already exists. Note that for adding a file like + ## directory already exists. Note that for adding a file like ## ``"path1/path2/filename"`` it is not necessary - ## to create the ``"path/path2"`` subdirectories - it will be done - ## automatically by ``addFile``. - assert(z.mode != fmRead) + ## to create the ``"path/path2"`` subdirectories - it will be done + ## automatically by ``addFile``. + assert(z.mode != fmRead) discard zip_add_dir(z.w, dir) zip_error_clear(z.w) -proc addFile*(z: var TZipArchive, dest, src: string) = +proc addFile*(z: var TZipArchive, dest, src: string) = ## Adds the file `src` to the archive `z` with the name `dest`. `dest` - ## may contain a path that will be created. - assert(z.mode != fmRead) + ## may contain a path that will be created. + assert(z.mode != fmRead) if not fileExists(src): raise newException(IOError, "File '" & src & "' does not exist") var zipsrc = zip_source_file(z.w, src, 0, -1) @@ -67,21 +67,21 @@ proc addFile*(z: var TZipArchive, dest, src: string) = zip_source_free(zipsrc) zipError(z) -proc addFile*(z: var TZipArchive, file: string) = +proc addFile*(z: var TZipArchive, file: string) = ## A shortcut for ``addFile(z, file, file)``, i.e. the name of the source is ## the name of the destination. addFile(z, file, file) - -proc mySourceCallback(state, data: pointer, len: int, - cmd: TZipSourceCmd): int {.cdecl.} = + +proc mySourceCallback(state, data: pointer, len: int, + cmd: TZipSourceCmd): int {.cdecl.} = var src = cast[Stream](state) case cmd - of ZIP_SOURCE_OPEN: + of ZIP_SOURCE_OPEN: if src.setPositionImpl != nil: setPosition(src, 0) # reset of ZIP_SOURCE_READ: result = readData(src, data, len) of ZIP_SOURCE_CLOSE: close(src) - of ZIP_SOURCE_STAT: + of ZIP_SOURCE_STAT: var stat = cast[PZipStat](data) zip_stat_init(stat) stat.size = high(int32)-1 # we don't know the size @@ -94,8 +94,8 @@ proc mySourceCallback(state, data: pointer, len: int, result = 2*sizeof(cint) of constZIP_SOURCE_FREE: GC_unref(src) else: assert(false) - -proc addFile*(z: var TZipArchive, dest: string, src: Stream) = + +proc addFile*(z: var TZipArchive, dest: string, src: Stream) = ## Adds a file named with `dest` to the archive `z`. `dest` ## may contain a path. The file's content is read from the `src` stream. assert(z.mode != fmRead) @@ -105,39 +105,45 @@ proc addFile*(z: var TZipArchive, dest: string, src: Stream) = if zip_add(z.w, dest, zipsrc) < 0'i32: zip_source_free(zipsrc) zipError(z) - + # -------------- zip file stream --------------------------------------------- type TZipFileStream = object of StreamObj f: PZipFile + atEnd: bool - PZipFileStream* = - ref TZipFileStream ## a reader stream of a file within a zip archive + PZipFileStream* = + ref TZipFileStream ## a reader stream of a file within a zip archive proc fsClose(s: Stream) = zip_fclose(PZipFileStream(s).f) -proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int = +proc fsAtEnd(s: Stream): bool = PZipFileStream(s).atEnd +proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int = result = zip_fread(PZipFileStream(s).f, buffer, bufLen) + if result == 0: + PZipFileStream(s).atEnd = true -proc newZipFileStream(f: PZipFile): PZipFileStream = +proc newZipFileStream(f: PZipFile): PZipFileStream = new(result) result.f = f + result.atEnd = false result.closeImpl = fsClose result.readDataImpl = fsReadData + result.atEndImpl = fsAtEnd # other methods are nil! # ---------------------------------------------------------------------------- - -proc getStream*(z: var TZipArchive, filename: string): PZipFileStream = + +proc getStream*(z: var TZipArchive, filename: string): PZipFileStream = ## returns a stream that can be used to read the file named `filename` ## from the archive `z`. Returns nil in case of an error. - ## The returned stream does not support the `setPosition`, `getPosition`, + ## The returned stream does not support the `setPosition`, `getPosition`, ## `writeData` or `atEnd` methods. var x = zip_fopen(z.w, filename, 0'i32) if x != nil: result = newZipFileStream(x) - -iterator walkFiles*(z: var TZipArchive): string = - ## walks over all files in the archive `z` and returns the filename + +iterator walkFiles*(z: var TZipArchive): string = + ## walks over all files in the archive `z` and returns the filename ## (including the path). var i = 0'i32 var num = zip_get_num_files(z.w) @@ -158,12 +164,20 @@ proc extractFile*(z: var TZipArchive, srcFile: string, dest: Stream) = proc extractFile*(z: var TZipArchive, srcFile: string, dest: string) = ## extracts a file from the zip archive `z` to the destination filename. - var file = newFileStream(dest, fmReadWrite) + var file = newFileStream(dest, fmWrite) extractFile(z, srcFile, file) file.close() proc extractAll*(z: var TZipArchive, dest: string) = ## extracts all files from archive `z` to the destination directory. for file in walkFiles(z): - extractFile(z, file, dest / extractFilename(file)) - + if file.endsWith("/"): + createDir(dest / file) + else: + extractFile(z, file, dest / file) + +when not defined(testing) and isMainModule: + var zip: TZipArchive + if not zip.open("nim-0.11.0.zip"): + raise newException(IOError, "opening zip failed") + zip.extractAll("test") diff --git a/lib/js/dom.nim b/lib/js/dom.nim index 94f4fa29c..b063fa838 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -152,10 +152,12 @@ type DocumentObj {.importc.} = object of NodeObj alinkColor*: cstring bgColor*: cstring + body*: Element charset*: cstring cookie*: cstring defaultCharset*: cstring fgColor*: cstring + head*: Element lastModified*: cstring linkColor*: cstring referrer*: cstring diff --git a/lib/nimbase.h b/lib/nimbase.h index e9dad0bb7..eea618bac 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -343,15 +343,15 @@ struct TFrame { }; #define nimfr(proc, file) \ - TFrame F; \ - F.procname = proc; F.filename = file; F.line = 0; F.len = 0; nimFrame(&F); + TFrame FR; \ + FR.procname = proc; FR.filename = file; FR.line = 0; FR.len = 0; nimFrame(&FR); #define nimfrs(proc, file, slots, length) \ - struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; TVarSlot s[slots];} F; \ - F.procname = proc; F.filename = file; F.line = 0; F.len = length; nimFrame((TFrame*)&F); + struct {TFrame* prev;NCSTRING procname;NI line;NCSTRING filename; NI len; TVarSlot s[slots];} FR; \ + FR.procname = proc; FR.filename = file; FR.line = 0; FR.len = length; nimFrame((TFrame*)&FR); #define nimln(n, file) \ - F.line = n; F.filename = file; + FR.line = n; FR.filename = file; #define NIM_POSIX_INIT __attribute__((constructor)) diff --git a/lib/posix/termios.nim b/lib/posix/termios.nim index 0917ab514..710b2fa6b 100644 --- a/lib/posix/termios.nim +++ b/lib/posix/termios.nim @@ -19,15 +19,12 @@ const type Termios* {.importc: "struct termios", header: "<termios.h>".} = object - iflag*: Tcflag # input mode flags - oflag*: Tcflag # output mode flags - cflag*: Tcflag # control mode flags - lflag*: Tcflag # local mode flags - line*: cuchar # line discipline - cc*: array[NCCS, cuchar] # control characters - ispeed*: Speed # input speed - ospeed*: Speed # output speed - + c_iflag*: Tcflag # input mode flags + c_oflag*: Tcflag # output mode flags + c_cflag*: Tcflag # control mode flags + c_lflag*: Tcflag # local mode flags + c_line*: cuchar # line discipline + c_cc*: array[NCCS, cuchar] # control characters # cc characters diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 27f77cef2..a4d7a1632 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -634,6 +634,93 @@ when defined(windows) or defined(nimdoc): # free ``ol``. return retFuture + proc recvInto*(socket: TAsyncFD, buf: cstring, size: int, + flags = {SocketFlag.SafeDisconn}): Future[int] = + ## Reads **up to** ``size`` bytes from ``socket`` into ``buf``, which must + ## at least be of that size. Returned future will complete once all the + ## data requested is read, a part of the data has been read, or the socket + ## has disconnected in which case the future will complete with a value of + ## ``0``. + ## + ## **Warning**: The ``Peek`` socket flag is not supported on Windows. + + + # Things to note: + # * When WSARecv completes immediately then ``bytesReceived`` is very + # unreliable. + # * Still need to implement message-oriented socket disconnection, + # '\0' in the message currently signifies a socket disconnect. Who + # knows what will happen when someone sends that to our socket. + verifyPresence(socket) + assert SocketFlag.Peek notin flags, "Peek not supported on Windows." + + var retFuture = newFuture[int]("recvInto") + + #buf[] = '\0' + var dataBuf: TWSABuf + dataBuf.buf = buf + dataBuf.len = size + + var bytesReceived: Dword + var flagsio = flags.toOSFlags().Dword + var ol = PCustomOverlapped() + GC_ref(ol) + ol.data = TCompletionData(fd: socket, cb: + proc (fd: TAsyncFD, bytesCount: Dword, errcode: OSErrorCode) = + if not retFuture.finished: + if errcode == OSErrorCode(-1): + if bytesCount == 0 and dataBuf.buf[0] == '\0': + retFuture.complete(0) + else: + retFuture.complete(bytesCount) + else: + if flags.isDisconnectionError(errcode): + retFuture.complete(0) + else: + retFuture.fail(newException(OSError, osErrorMsg(errcode))) + if dataBuf.buf != nil: + dataBuf.buf = nil + ) + + let ret = WSARecv(socket.SocketHandle, addr dataBuf, 1, addr bytesReceived, + addr flagsio, cast[POVERLAPPED](ol), nil) + if ret == -1: + let err = osLastError() + if err.int32 != ERROR_IO_PENDING: + if dataBuf.buf != nil: + dataBuf.buf = nil + GC_unref(ol) + if flags.isDisconnectionError(err): + retFuture.complete(0) + else: + retFuture.fail(newException(OSError, osErrorMsg(err))) + elif ret == 0 and bytesReceived == 0 and dataBuf.buf[0] == '\0': + # We have to ensure that the buffer is empty because WSARecv will tell + # us immediately when it was disconnected, even when there is still + # data in the buffer. + # We want to give the user as much data as we can. So we only return + # the empty string (which signals a disconnection) when there is + # nothing left to read. + retFuture.complete(0) + # TODO: "For message-oriented sockets, where a zero byte message is often + # allowable, a failure with an error code of WSAEDISCON is used to + # indicate graceful closure." + # ~ http://msdn.microsoft.com/en-us/library/ms741688%28v=vs.85%29.aspx + else: + # Request to read completed immediately. + # From my tests bytesReceived isn't reliable. + let realSize = + if bytesReceived == 0: + size + else: + bytesReceived + assert realSize <= size + retFuture.complete(realSize) + # We don't deallocate ``ol`` here because even though this completed + # immediately poll will still be notified about its completion and it will + # free ``ol``. + return retFuture + proc send*(socket: TAsyncFD, data: string, flags = {SocketFlag.SafeDisconn}): Future[void] = ## Sends ``data`` to ``socket``. The returned future will complete once all @@ -983,6 +1070,30 @@ else: addRead(socket, cb) return retFuture + proc recvInto*(socket: TAsyncFD, buf: cstring, size: int, + flags = {SocketFlag.SafeDisconn}): Future[int] = + var retFuture = newFuture[int]("recvInto") + + proc cb(sock: TAsyncFD): bool = + result = true + let res = recv(sock.SocketHandle, buf, size.cint, + flags.toOSFlags()) + if res < 0: + let lastError = osLastError() + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + if flags.isDisconnectionError(lastError): + retFuture.complete(0) + else: + retFuture.fail(newException(OSError, osErrorMsg(lastError))) + else: + result = false # We still want this callback to be called. + else: + retFuture.complete(res) + # TODO: The following causes a massive slowdown. + #if not cb(socket): + addRead(socket, cb) + return retFuture + proc send*(socket: TAsyncFD, data: string, flags = {SocketFlag.SafeDisconn}): Future[void] = var retFuture = newFuture[void]("send") diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index dc5a55dcc..279cedb5d 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -23,8 +23,7 @@ ## proc cb(req: Request) {.async.} = ## await req.respond(Http200, "Hello World") ## -## asyncCheck server.serve(Port(8080), cb) -## runForever() +## waitFor server.serve(Port(8080), cb) import strtabs, asyncnet, asyncdispatch, parseutils, uri, strutils type @@ -109,22 +108,19 @@ proc sendHeaders*(req: Request, headers: StringTableRef): Future[void] = addHeaders(msg, headers) return req.client.send(msg) -proc respond*(req: Request, code: HttpCode, - content: string, headers = newStringTable()) {.async.} = +proc respond*(req: Request, code: HttpCode, content: string, + headers: StringTableRef = nil): Future[void] = ## Responds to the request with the specified ``HttpCode``, headers and ## content. ## ## This procedure will **not** close the client socket. - var customHeaders = headers - customHeaders["Content-Length"] = $content.len var msg = "HTTP/1.1 " & $code & "\c\L" - msg.addHeaders(customHeaders) - await req.client.send(msg & "\c\L" & content) -proc newRequest(): Request = - result.headers = newStringTable(modeCaseInsensitive) - result.hostname = "" - result.body = "" + if headers != nil: + msg.addHeaders(headers) + msg.add("Content-Length: " & $content.len & "\c\L\c\L") + msg.add(content) + result = req.client.send(msg) proc parseHeader(line: string): tuple[key, value: string] = var i = 0 @@ -149,59 +145,65 @@ proc sendStatus(client: AsyncSocket, status: string): Future[void] = proc processClient(client: AsyncSocket, address: string, callback: proc (request: Request): Future[void] {.closure, gcsafe.}) {.async.} = + var request: Request + request.url = initUri() + request.headers = newStringTable(modeCaseInsensitive) + var line = newStringOfCap(80) + var key, value = "" + while not client.isClosed: # GET /path HTTP/1.1 # Header: val # \n - var request = newRequest() - request.hostname = address + request.headers.clear(modeCaseInsensitive) + request.hostname.shallowCopy(address) assert client != nil request.client = client # First line - GET /path HTTP/1.1 - let line = await client.recvLine() # TODO: Timeouts. + line.setLen(0) + await client.recvLineInto(addr line) # TODO: Timeouts. if line == "": client.close() return - let lineParts = line.split(' ') - if lineParts.len != 3: - await request.respond(Http400, "Invalid request. Got: " & line) - continue - let reqMethod = lineParts[0] - let path = lineParts[1] - let protocol = lineParts[2] + var i = 0 + for linePart in line.split(' '): + case i + of 0: request.reqMethod.shallowCopy(linePart.normalize) + of 1: parseUri(linePart, request.url) + of 2: + try: + request.protocol = parseProtocol(linePart) + except ValueError: + asyncCheck request.respond(Http400, + "Invalid request protocol. Got: " & linePart) + continue + else: + await request.respond(Http400, "Invalid request. Got: " & line) + continue + inc i # Headers - var i = 0 while true: i = 0 - let headerLine = await client.recvLine() - if headerLine == "": - client.close(); return - if headerLine == "\c\L": break - # TODO: Compiler crash - #let (key, value) = parseHeader(headerLine) - let kv = parseHeader(headerLine) - request.headers[kv.key] = kv.value + line.setLen(0) + await client.recvLineInto(addr line) - request.reqMethod = reqMethod - request.url = parseUri(path) - try: - request.protocol = protocol.parseProtocol() - except ValueError: - asyncCheck request.respond(Http400, "Invalid request protocol. Got: " & - protocol) - continue + if line == "": + client.close(); return + if line == "\c\L": break + let (key, value) = parseHeader(line) + request.headers[key] = value - if reqMethod.normalize == "post": + if request.reqMethod == "post": # Check for Expect header if request.headers.hasKey("Expect"): if request.headers["Expect"].toLower == "100-continue": await client.sendStatus("100 Continue") else: await client.sendStatus("417 Expectation Failed") - + # Read the body # - Check for Content-length header if request.headers.hasKey("Content-Length"): @@ -215,11 +217,11 @@ proc processClient(client: AsyncSocket, address: string, await request.respond(Http400, "Bad Request. No Content-Length.") continue - case reqMethod.normalize + case request.reqMethod of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch": await callback(request) else: - await request.respond(Http400, "Invalid request method. Got: " & reqMethod) + await request.respond(Http400, "Invalid request method. Got: " & request.reqMethod) # Persistent connections if (request.protocol == HttpVer11 and @@ -247,7 +249,7 @@ proc serve*(server: AsyncHttpServer, port: Port, server.socket.setSockOpt(OptReuseAddr, true) server.socket.bindAddr(port, address) server.socket.listen() - + while true: # TODO: Causes compiler crash. #var (address, client) = await server.socket.acceptAddr() diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 39d05d36b..aadbde824 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -24,7 +24,7 @@ ## ## Chat server ## ^^^^^^^^^^^ -## +## ## The following example demonstrates a simple chat server. ## ## .. code-block::nim @@ -182,26 +182,30 @@ proc connect*(socket: AsyncSocket, address: string, port: Port, sslSetConnectState(socket.sslHandle) sslLoop(socket, flags, sslDoHandshake(socket.sslHandle)) -proc readInto(buf: cstring, size: int, socket: AsyncSocket, - flags: set[SocketFlag]): Future[int] {.async.} = +template readInto(buf: cstring, size: int, socket: AsyncSocket, + flags: set[SocketFlag]): int = + ## Reads **up to** ``size`` bytes from ``socket`` into ``buf``. Note that + ## this is a template and not a proc. + var res = 0 if socket.isSsl: when defined(ssl): # SSL mode. sslLoop(socket, flags, sslRead(socket.sslHandle, buf, size.cint)) - result = opResult + res = opResult else: - var data = await recv(socket.fd.TAsyncFD, size, flags) - if data.len != 0: - copyMem(buf, addr data[0], data.len) + var recvIntoFut = recvInto(socket.fd.TAsyncFD, buf, size, flags) + yield recvIntoFut # Not in SSL mode. - result = data.len + res = recvIntoFut.read() + res -proc readIntoBuf(socket: AsyncSocket, - flags: set[SocketFlag]): Future[int] {.async.} = - result = await readInto(addr socket.buffer[0], BufferSize, socket, flags) +template readIntoBuf(socket: AsyncSocket, + flags: set[SocketFlag]): int = + var size = readInto(addr socket.buffer[0], BufferSize, socket, flags) socket.currPos = 0 - socket.bufLen = result + socket.bufLen = size + size proc recv*(socket: AsyncSocket, size: int, flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} = @@ -222,10 +226,11 @@ proc recv*(socket: AsyncSocket, size: int, ## to be read then the future will complete with a value of ``""``. if socket.isBuffered: result = newString(size) + shallow(result) let originalBufPos = socket.currPos if socket.bufLen == 0: - let res = await socket.readIntoBuf(flags - {SocketFlag.Peek}) + let res = socket.readIntoBuf(flags - {SocketFlag.Peek}) if res == 0: result.setLen(0) return @@ -236,7 +241,7 @@ proc recv*(socket: AsyncSocket, size: int, if SocketFlag.Peek in flags: # We don't want to get another buffer if we're peeking. break - let res = await socket.readIntoBuf(flags - {SocketFlag.Peek}) + let res = socket.readIntoBuf(flags - {SocketFlag.Peek}) if res == 0: break @@ -251,7 +256,7 @@ proc recv*(socket: AsyncSocket, size: int, result.setLen(read) else: result = newString(size) - let read = await readInto(addr result[0], size, socket, flags) + let read = readInto(addr result[0], size, socket, flags) result.setLen(read) proc send*(socket: AsyncSocket, data: string, @@ -302,15 +307,14 @@ proc accept*(socket: AsyncSocket, retFut.complete(future.read.client) return retFut -proc recvLine*(socket: AsyncSocket, - flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} = - ## Reads a line of data from ``socket``. Returned future will complete once - ## a full line is read or an error occurs. +proc recvLineInto*(socket: AsyncSocket, resString: ptr string, + flags = {SocketFlag.SafeDisconn}) {.async.} = + ## Reads a line of data from ``socket`` into ``resString``. ## ## If a full line is read ``\r\L`` is not ## added to ``line``, however if solely ``\r\L`` is read then ``line`` ## will be set to it. - ## + ## ## If the socket is disconnected, ``line`` will be set to ``""``. ## ## If the socket is disconnected in the middle of a line (before ``\r\L`` @@ -318,27 +322,32 @@ proc recvLine*(socket: AsyncSocket, ## The partial line **will be lost**. ## ## **Warning**: The ``Peek`` flag is not yet implemented. - ## - ## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol - ## uses ``\r\L`` to delimit a new line. - template addNLIfEmpty(): stmt = - if result.len == 0: - result.add("\c\L") + ## + ## **Warning**: ``recvLineInto`` on unbuffered sockets assumes that the + ## protocol uses ``\r\L`` to delimit a new line. + ## + ## **Warning**: ``recvLineInto`` currently uses a raw pointer to a string for + ## performance reasons. This will likely change soon to use FutureVars. assert SocketFlag.Peek notin flags ## TODO: + result = newFuture[void]("asyncnet.recvLineInto") + + template addNLIfEmpty(): stmt = + if resString[].len == 0: + resString[].add("\c\L") + if socket.isBuffered: - result = "" if socket.bufLen == 0: - let res = await socket.readIntoBuf(flags) + let res = socket.readIntoBuf(flags) if res == 0: return var lastR = false while true: if socket.currPos >= socket.bufLen: - let res = await socket.readIntoBuf(flags) + let res = socket.readIntoBuf(flags) if res == 0: - result = "" - break + resString[].setLen(0) + return case socket.buffer[socket.currPos] of '\r': @@ -353,24 +362,53 @@ proc recvLine*(socket: AsyncSocket, socket.currPos.inc() return else: - result.add socket.buffer[socket.currPos] + resString[].add socket.buffer[socket.currPos] socket.currPos.inc() else: - result = "" var c = "" while true: - c = await recv(socket, 1, flags) + let recvFut = recv(socket, 1, flags) + c = recvFut.read() if c.len == 0: - return "" + resString[].setLen(0) + return if c == "\r": - c = await recv(socket, 1, flags) # Skip \L + let recvFut = recv(socket, 1, flags) # Skip \L + c = recvFut.read() assert c == "\L" addNLIfEmpty() return elif c == "\L": addNLIfEmpty() return - add(result.string, c) + resString[].add c + +proc recvLine*(socket: AsyncSocket, + flags = {SocketFlag.SafeDisconn}): Future[string] {.async.} = + ## Reads a line of data from ``socket``. Returned future will complete once + ## a full line is read or an error occurs. + ## + ## If a full line is read ``\r\L`` is not + ## added to ``line``, however if solely ``\r\L`` is read then ``line`` + ## will be set to it. + ## + ## If the socket is disconnected, ``line`` will be set to ``""``. + ## + ## If the socket is disconnected in the middle of a line (before ``\r\L`` + ## is read) then line will be set to ``""``. + ## The partial line **will be lost**. + ## + ## **Warning**: The ``Peek`` flag is not yet implemented. + ## + ## **Warning**: ``recvLine`` on unbuffered sockets assumes that the protocol + ## uses ``\r\L`` to delimit a new line. + template addNLIfEmpty(): stmt = + if result.len == 0: + result.add("\c\L") + assert SocketFlag.Peek notin flags ## TODO: + + result = "" + await socket.recvLineInto(addr result, flags) proc listen*(socket: AsyncSocket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} = ## Marks ``socket`` as accepting connections. @@ -500,11 +538,11 @@ when not defined(testing) and isMainModule: proc (future: Future[void]) = echo("Send") client.close() - + var f = accept(sock) f.callback = onAccept - + var f = accept(sock) f.callback = onAccept runForever() - + diff --git a/lib/pure/basic3d.nim b/lib/pure/basic3d.nim index 18ebed67b..5a943dd05 100644 --- a/lib/pure/basic3d.nim +++ b/lib/pure/basic3d.nim @@ -16,33 +16,33 @@ import times ## Vectors are implemented as direction vectors, ie. when transformed with a matrix ## the translation part of matrix is ignored. The coordinate system used is ## right handed, because its compatible with 2d coordinate system (rotation around -## zaxis equals 2d rotation). +## zaxis equals 2d rotation). ## Operators `+` , `-` , `*` , `/` , `+=` , `-=` , `*=` and `/=` are implemented ## for vectors and scalars. ## ## ## Quick start example: -## +## ## # Create a matrix which first rotates, then scales and at last translates -## +## ## var m:TMatrix3d=rotate(PI,vector3d(1,1,2.5)) & scale(2.0) & move(100.0,200.0,300.0) -## +## ## # Create a 3d point at (100,150,200) and a vector (5,2,3) -## -## var pt:TPoint3d=point3d(100.0,150.0,200.0) -## +## +## var pt:TPoint3d=point3d(100.0,150.0,200.0) +## ## var vec:TVector3d=vector3d(5.0,2.0,3.0) -## -## +## +## ## pt &= m # transforms pt in place -## +## ## var pt2:TPoint3d=pt & m #concatenates pt with m and returns a new point -## +## ## var vec2:TVector3d=vec & m #concatenates vec with m and returns a new vector -type +type TMatrix3d* =object ## Implements a row major 3d matrix, which means ## transformations are applied the order they are concatenated. @@ -53,12 +53,12 @@ type ## [ tx ty tz tw ] ax*,ay*,az*,aw*, bx*,by*,bz*,bw*, cx*,cy*,cz*,cw*, tx*,ty*,tz*,tw*:float TPoint3d* = object - ## Implements a non-homegeneous 2d point stored as + ## Implements a non-homegeneous 2d point stored as ## an `x` , `y` and `z` coordinate. x*,y*,z*:float - TVector3d* = object - ## Implements a 3d **direction vector** stored as - ## an `x` , `y` and `z` coordinate. Direction vector means, + TVector3d* = object + ## Implements a 3d **direction vector** stored as + ## an `x` , `y` and `z` coordinate. Direction vector means, ## that when transforming a vector with a matrix, the translational ## part of the matrix is ignored. x*,y*,z*:float @@ -67,7 +67,7 @@ type # Some forward declarations proc matrix3d*(ax,ay,az,aw,bx,by,bz,bw,cx,cy,cz,cw,tx,ty,tz,tw:float):TMatrix3d {.noInit.} - ## Creates a new 4x4 3d transformation matrix. + ## Creates a new 4x4 3d transformation matrix. ## `ax` , `ay` , `az` is the local x axis. ## `bx` , `by` , `bz` is the local y axis. ## `cx` , `cy` , `cz` is the local z axis. @@ -76,7 +76,7 @@ proc vector3d*(x,y,z:float):TVector3d {.noInit,inline.} ## Returns a new 3d vector (`x`,`y`,`z`) proc point3d*(x,y,z:float):TPoint3d {.noInit,inline.} ## Returns a new 4d point (`x`,`y`,`z`) -proc tryNormalize*(v:var TVector3d):bool +proc tryNormalize*(v:var TVector3d):bool ## Modifies `v` to have a length of 1.0, keeping its angle. ## If `v` has zero length (and thus no angle), it is left unmodified and false is ## returned, otherwise true is returned. @@ -85,7 +85,7 @@ proc tryNormalize*(v:var TVector3d):bool let IDMATRIX*:TMatrix3d=matrix3d( - 1.0,0.0,0.0,0.0, + 1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,0.0,0.0,1.0) @@ -114,20 +114,20 @@ proc safeArccos(v:float):float= ## due to rounding issues return arccos(clamp(v,-1.0,1.0)) -template makeBinOpVector(s:expr)= +template makeBinOpVector(s:expr)= ## implements binary operators + , - , * and / for vectors - proc s*(a,b:TVector3d):TVector3d {.inline,noInit.} = + proc s*(a,b:TVector3d):TVector3d {.inline,noInit.} = vector3d(s(a.x,b.x),s(a.y,b.y),s(a.z,b.z)) - proc s*(a:TVector3d,b:float):TVector3d {.inline,noInit.} = + proc s*(a:TVector3d,b:float):TVector3d {.inline,noInit.} = vector3d(s(a.x,b),s(a.y,b),s(a.z,b)) - proc s*(a:float,b:TVector3d):TVector3d {.inline,noInit.} = + proc s*(a:float,b:TVector3d):TVector3d {.inline,noInit.} = vector3d(s(a,b.x),s(a,b.y),s(a,b.z)) - -template makeBinOpAssignVector(s:expr)= + +template makeBinOpAssignVector(s:expr)= ## implements inplace binary operators += , -= , /= and *= for vectors - proc s*(a:var TVector3d,b:TVector3d) {.inline.} = + proc s*(a:var TVector3d,b:TVector3d) {.inline.} = s(a.x,b.x) ; s(a.y,b.y) ; s(a.z,b.z) - proc s*(a:var TVector3d,b:float) {.inline.} = + proc s*(a:var TVector3d,b:float) {.inline.} = s(a.x,b) ; s(a.y,b) ; s(a.z,b) @@ -188,20 +188,20 @@ proc scale*(s:float):TMatrix3d {.noInit.} = proc scale*(s:float,org:TPoint3d):TMatrix3d {.noInit.} = ## Returns a new scaling matrix using, `org` as scale origin. - result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0, + result.setElements(s,0,0,0, 0,s,0,0, 0,0,s,0, org.x-s*org.x,org.y-s*org.y,org.z-s*org.z,1.0) proc stretch*(sx,sy,sz:float):TMatrix3d {.noInit.} = ## Returns new a stretch matrix, which is a ## scale matrix with non uniform scale in x,y and z. result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, 0,0,0,1) - + proc stretch*(sx,sy,sz:float,org:TPoint3d):TMatrix3d {.noInit.} = ## Returns a new stretch matrix, which is a ## scale matrix with non uniform scale in x,y and z. ## `org` is used as stretch origin. result.setElements(sx,0,0,0, 0,sy,0,0, 0,0,sz,0, org.x-sx*org.x,org.y-sy*org.y,org.z-sz*org.z,1) - + proc move*(dx,dy,dz:float):TMatrix3d {.noInit.} = ## Returns a new translation matrix. result.setElements(1,0,0,0, 0,1,0,0, 0,0,1,0, dx,dy,dz,1) @@ -235,7 +235,7 @@ proc rotate*(angle:float,axis:TVector3d):TMatrix3d {.noInit.}= uvomc=normax.x*normax.y*omc uwomc=normax.x*normax.z*omc vwomc=normax.y*normax.z*omc - + result.setElements( u2+(1.0-u2)*cs, uvomc+wsi, uwomc-vsi, 0.0, uvomc-wsi, v2+(1.0-v2)*cs, vwomc+usi, 0.0, @@ -248,11 +248,11 @@ proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}= # see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf # for how this is computed - + var normax=axis if not normax.tryNormalize: #simplifies matrix computation below a lot raise newException(DivByZeroError,"Cannot rotate around zero length axis") - + let u=normax.x v=normax.y @@ -272,7 +272,7 @@ proc rotate*(angle:float,org:TPoint3d,axis:TVector3d):TMatrix3d {.noInit.}= uvomc=normax.x*normax.y*omc uwomc=normax.x*normax.z*omc vwomc=normax.y*normax.z*omc - + result.setElements( u2+(v2+w2)*cs, uvomc+wsi, uwomc-vsi, 0.0, uvomc-wsi, v2+(u2+w2)*cs, vwomc+usi, 0.0, @@ -305,7 +305,7 @@ proc rotateY*(angle:float):TMatrix3d {.noInit.}= 0,1,0,0, s,0,c,0, 0,0,0,1) - + proc rotateZ*(angle:float):TMatrix3d {.noInit.}= ## Creates a matrix that rotates around the z-axis with `angle` radians, ## which is also called a 'yaw' matrix. @@ -317,19 +317,19 @@ proc rotateZ*(angle:float):TMatrix3d {.noInit.}= -s,c,0,0, 0,0,1,0, 0,0,0,1) - + proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool= - ## Checks if the transform is uniform, that is + ## Checks if the transform is uniform, that is ## perpendicular axes of equal length, which means (for example) ## it cannot transform a sphere into an ellipsoid. - ## `tol` is used as tolerance for both equal length comparison + ## `tol` is used as tolerance for both equal length comparison ## and perpendicular comparison. - + #dot product=0 means perpendicular coord. system, check xaxis vs yaxis and xaxis vs zaxis if abs(m.ax*m.bx+m.ay*m.by+m.az*m.bz)<=tol and # x vs y abs(m.ax*m.cx+m.ay*m.cy+m.az*m.cz)<=tol and #x vs z abs(m.bx*m.cx+m.by*m.cy+m.bz*m.cz)<=tol: #y vs z - + #subtract squared lengths of axes to check if uniform scaling: let sqxlen=(m.ax*m.ax+m.ay*m.ay+m.az*m.az) @@ -340,16 +340,16 @@ proc isUniform*(m:TMatrix3d,tol=1.0e-6):bool= return false - + proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}= ## Creates a matrix that mirrors over the plane that has `planeperp` as normal, ## and passes through origo. `planeperp` does not need to be normalized. - + # https://en.wikipedia.org/wiki/Transformation_matrix var n=planeperp if not n.tryNormalize: raise newException(DivByZeroError,"Cannot mirror over a plane with a zero length normal") - + let a=n.x b=n.y @@ -357,7 +357,7 @@ proc mirror*(planeperp:TVector3d):TMatrix3d {.noInit.}= ab=a*b ac=a*c bc=b*c - + result.setElements( 1-2*a*a , -2*ab,-2*ac,0, -2*ab , 1-2*b*b, -2*bc, 0, @@ -376,7 +376,7 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}= var n=planeperp if not n.tryNormalize: raise newException(DivByZeroError,"Cannot mirror over a plane with a zero length normal") - + let a=n.x b=n.y @@ -390,7 +390,7 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}= tx=org.x ty=org.y tz=org.z - + result.setElements( 1-2*aa , -2*ab,-2*ac,0, -2*ab , 1-2*bb, -2*bc, 0, @@ -402,8 +402,8 @@ proc mirror*(org:TPoint3d,planeperp:TVector3d):TMatrix3d {.noInit.}= proc determinant*(m:TMatrix3d):float= ## Computes the determinant of matrix `m`. - - # This computation is gotten from ratsimp(optimize(determinant(m))) + + # This computation is gotten from ratsimp(optimize(determinant(m))) # in maxima CAS let O1=m.cx*m.tw-m.cw*m.tx @@ -423,10 +423,10 @@ proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}= ## Computes the inverse of matrix `m`. If the matrix ## determinant is zero, thus not invertible, a EDivByZero ## will be raised. - + # this computation comes from optimize(invert(m)) in maxima CAS - - let + + let det=m.determinant O2=m.cy*m.tw-m.cw*m.ty O3=m.cz*m.tw-m.cw*m.tz @@ -464,7 +464,7 @@ proc inverse*(m:TMatrix3d):TMatrix3d {.noInit.}= proc equals*(m1:TMatrix3d,m2:TMatrix3d,tol=1.0e-6):bool= ## Checks if all elements of `m1`and `m2` is equal within ## a given tolerance `tol`. - return + return abs(m1.ax-m2.ax)<=tol and abs(m1.ay-m2.ay)<=tol and abs(m1.az-m2.az)<=tol and @@ -486,11 +486,11 @@ proc `=~`*(m1,m2:TMatrix3d):bool= ## Checks if `m1` and `m2` is approximately equal, using a ## tolerance of 1e-6. equals(m1,m2) - + proc transpose*(m:TMatrix3d):TMatrix3d {.noInit.}= ## Returns the transpose of `m` result.setElements(m.ax,m.bx,m.cx,m.tx,m.ay,m.by,m.cy,m.ty,m.az,m.bz,m.cz,m.tz,m.aw,m.bw,m.cw,m.tw) - + proc getXAxis*(m:TMatrix3d):TVector3d {.noInit.}= ## Gets the local x axis of `m` result.x=m.ax @@ -509,26 +509,26 @@ proc getZAxis*(m:TMatrix3d):TVector3d {.noInit.}= result.y=m.cy result.z=m.cz - + proc `$`*(m:TMatrix3d):string= ## String representation of `m` - return rtos(m.ax) & "," & rtos(m.ay) & "," &rtos(m.az) & "," & rtos(m.aw) & - "\n" & rtos(m.bx) & "," & rtos(m.by) & "," &rtos(m.bz) & "," & rtos(m.bw) & - "\n" & rtos(m.cx) & "," & rtos(m.cy) & "," &rtos(m.cz) & "," & rtos(m.cw) & - "\n" & rtos(m.tx) & "," & rtos(m.ty) & "," &rtos(m.tz) & "," & rtos(m.tw) - + return rtos(m.ax) & "," & rtos(m.ay) & "," & rtos(m.az) & "," & rtos(m.aw) & + "\n" & rtos(m.bx) & "," & rtos(m.by) & "," & rtos(m.bz) & "," & rtos(m.bw) & + "\n" & rtos(m.cx) & "," & rtos(m.cy) & "," & rtos(m.cz) & "," & rtos(m.cw) & + "\n" & rtos(m.tx) & "," & rtos(m.ty) & "," & rtos(m.tz) & "," & rtos(m.tw) + proc apply*(m:TMatrix3d, x,y,z:var float, translate=false)= ## Applies transformation `m` onto `x` , `y` , `z` , optionally ## using the translation part of the matrix. - let + let oldx=x oldy=y oldz=z - + x=m.cx*oldz+m.bx*oldy+m.ax*oldx y=m.cy*oldz+m.by*oldy+m.ay*oldx z=m.cz*oldz+m.bz*oldy+m.az*oldx - + if translate: x+=m.tx y+=m.ty @@ -552,13 +552,13 @@ proc `len=`*(v:var TVector3d,newlen:float) {.noInit.} = ## an arbitrary vector of the requested length is returned. let fac=newlen/v.len - + if newlen==0.0: v.x=0.0 v.y=0.0 v.z=0.0 return - + if fac==Inf or fac==NegInf: #to short for float accuracy #do as good as possible: @@ -588,7 +588,7 @@ proc `&` *(v:TVector3d,m:TMatrix3d):TVector3d {.noInit.} = ## Concatenate vector `v` with a transformation matrix. ## Transforming a vector ignores the translational part ## of the matrix. - + # | AX AY AZ AW | # | X Y Z 1 | * | BX BY BZ BW | # | CX CY CZ CW | @@ -605,12 +605,12 @@ proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} = ## Applies transformation `m` onto `v` in place. ## Transforming a vector ignores the translational part ## of the matrix. - + # | AX AY AZ AW | # | X Y Z 1 | * | BX BY BZ BW | # | CX CY CZ CW | # | 0 0 0 1 | - + let newx=m.cx*v.z+m.bx*v.y+m.ax*v.x newy=m.cy*v.z+m.by*v.y+m.ay*v.x @@ -620,38 +620,38 @@ proc `&=` *(v:var TVector3d,m:TMatrix3d) {.noInit.} = proc transformNorm*(v:var TVector3d,m:TMatrix3d)= ## Applies a normal direction transformation `m` onto `v` in place. - ## The resulting vector is *not* normalized. Transforming a vector ignores the - ## translational part of the matrix. If the matrix is not invertible + ## The resulting vector is *not* normalized. Transforming a vector ignores the + ## translational part of the matrix. If the matrix is not invertible ## (determinant=0), an EDivByZero will be raised. # transforming a normal is done by transforming # by the transpose of the inverse of the original matrix - + # Major reason this simple function is here is that this function can be optimized in the future, # (possibly by hardware) as well as having a consistent API with the 2d version. v&=transpose(inverse(m)) - + proc transformInv*(v:var TVector3d,m:TMatrix3d)= - ## Applies the inverse of `m` on vector `v`. Transforming a vector ignores - ## the translational part of the matrix. Transforming a vector ignores the + ## Applies the inverse of `m` on vector `v`. Transforming a vector ignores + ## the translational part of the matrix. Transforming a vector ignores the ## translational part of the matrix. ## If the matrix is not invertible (determinant=0), an EDivByZero ## will be raised. - + # Major reason this simple function is here is that this function can be optimized in the future, # (possibly by hardware) as well as having a consistent API with the 2d version. v&=m.inverse - + proc transformNormInv*(vec:var TVector3d,m:TMatrix3d)= ## Applies an inverse normal direction transformation `m` onto `v` in place. - ## This is faster than creating an inverse - ## matrix and transformNorm(...) it. Transforming a vector ignores the + ## This is faster than creating an inverse + ## matrix and transformNorm(...) it. Transforming a vector ignores the ## translational part of the matrix. - + # see vector2d:s equivalent for a deeper look how/why this works vec&=m.transpose -proc tryNormalize*(v:var TVector3d):bool= +proc tryNormalize*(v:var TVector3d):bool= ## Modifies `v` to have a length of 1.0, keeping its angle. ## If `v` has zero length (and thus no angle), it is left unmodified and false is ## returned, otherwise true is returned. @@ -663,26 +663,26 @@ proc tryNormalize*(v:var TVector3d):bool= v.x/=mag v.y/=mag v.z/=mag - + return true -proc normalize*(v:var TVector3d) {.inline.}= +proc normalize*(v:var TVector3d) {.inline.}= ## Modifies `v` to have a length of 1.0, keeping its angle. ## If `v` has zero length, an EDivByZero will be raised. if not tryNormalize(v): raise newException(DivByZeroError,"Cannot normalize zero length vector") proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)= - ## Rotates `vec` in place, with `angle` radians over `axis`, which passes + ## Rotates `vec` in place, with `angle` radians over `axis`, which passes ## through origo. # see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf # for how this is computed - + var normax=axis if not normax.tryNormalize: raise newException(DivByZeroError,"Cannot rotate around zero length axis") - + let cs=cos(angle) si=sin(angle) @@ -694,11 +694,11 @@ proc rotate*(vec:var TVector3d,angle:float,axis:TVector3d)= y=vec.y z=vec.z uxyzomc=(u*x+v*y+w*z)*omc - + vec.x=u*uxyzomc+x*cs+(v*z-w*y)*si vec.y=v*uxyzomc+y*cs+(w*x-u*z)*si vec.z=w*uxyzomc+z*cs+(u*y-v*x)*si - + proc scale*(v:var TVector3d,s:float)= ## Scales the vector in place with factor `s` v.x*=s @@ -713,12 +713,12 @@ proc stretch*(v:var TVector3d,sx,sy,sz:float)= proc mirror*(v:var TVector3d,planeperp:TVector3d)= ## Computes the mirrored vector of `v` over the plane - ## that has `planeperp` as normal direction. + ## that has `planeperp` as normal direction. ## `planeperp` does not need to be normalized. - + var n=planeperp n.normalize - + let x=v.x y=v.y @@ -729,7 +729,7 @@ proc mirror*(v:var TVector3d,planeperp:TVector3d)= ac=a*c ab=a*b bc=b*c - + v.x= -2*(ac*z+ab*y+a*a*x)+x v.y= -2*(bc*z+b*b*y+ab*x)+y v.z= -2*(c*c*z+bc*y+ac*x)+z @@ -740,7 +740,7 @@ proc `-` *(v:TVector3d):TVector3d= result.x= -v.x result.y= -v.y result.z= -v.z - + # declare templated binary operators makeBinOpVector(`+`) makeBinOpVector(`-`) @@ -752,7 +752,7 @@ makeBinOpAssignVector(`*=`) makeBinOpAssignVector(`/=`) proc dot*(v1,v2:TVector3d):float {.inline.}= - ## Computes the dot product of two vectors. + ## Computes the dot product of two vectors. ## Returns 0.0 if the vectors are perpendicular. return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z @@ -769,12 +769,12 @@ proc cross*(v1,v2:TVector3d):TVector3d {.inline.}= proc equals*(v1,v2:TVector3d,tol=1.0e-6):bool= ## Checks if two vectors approximately equals with a tolerance. return abs(v2.x-v1.x)<=tol and abs(v2.y-v1.y)<=tol and abs(v2.z-v1.z)<=tol - + proc `=~` *(v1,v2:TVector3d):bool= - ## Checks if two vectors approximately equals with a + ## Checks if two vectors approximately equals with a ## hardcoded tolerance 1e-6 equals(v1,v2) - + proc angleTo*(v1,v2:TVector3d):float= ## Returns the smallest angle between v1 and v2, ## which is in range 0-PI @@ -801,7 +801,7 @@ proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}= ay=cross(norm,ax) ay.normalize() az=cross(ax,ay) - + result.setElements( ax.x,ax.y,ax.z,0.0, ay.x,ay.y,ay.z,0.0, @@ -811,20 +811,20 @@ proc arbitraryAxis*(norm:TVector3d):TMatrix3d {.noInit.}= proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}= ## Computes the bisector between v1 and v2 as a normalized vector. ## If one of the input vectors has zero length, a normalized version - ## of the other is returned. If both input vectors has zero length, + ## of the other is returned. If both input vectors has zero length, ## an arbitrary normalized vector `v1` is returned. var vmag1=v1.len vmag2=v2.len - - # zero length vector equals arbitrary vector, just change + + # zero length vector equals arbitrary vector, just change # magnitude to one to avoid zero division - if vmag1==0.0: + if vmag1==0.0: if vmag2==0: #both are zero length return any normalized vector return XAXIS vmag1=1.0 - if vmag2==0.0: vmag2=1.0 - + if vmag2==0.0: vmag2=1.0 + let x1=v1.x/vmag1 y1=v1.y/vmag1 @@ -832,14 +832,14 @@ proc bisect*(v1,v2:TVector3d):TVector3d {.noInit.}= x2=v2.x/vmag2 y2=v2.y/vmag2 z2=v2.z/vmag2 - + result.x=(x1 + x2) * 0.5 result.y=(y1 + y2) * 0.5 result.z=(z1 + z2) * 0.5 - + if not result.tryNormalize(): # This can happen if vectors are colinear. In this special case - # there are actually inifinitely many bisectors, we select just + # there are actually inifinitely many bisectors, we select just # one of them. result=v1.cross(XAXIS) if result.sqrLen<1.0e-9: @@ -857,14 +857,14 @@ proc point3d*(x,y,z:float):TPoint3d= result.x=x result.y=y result.z=z - + proc sqrDist*(a,b:TPoint3d):float= ## Computes the squared distance between `a`and `b` let dx=b.x-a.x let dy=b.y-a.y let dz=b.z-a.z result=dx*dx+dy*dy+dz*dz - + proc dist*(a,b:TPoint3d):float {.inline.}= ## Computes the absolute distance between `a`and `b` result=sqrt(sqrDist(a,b)) @@ -876,7 +876,7 @@ proc `$` *(p:TPoint3d):string= result.add(rtos(p.y)) result.add(",") result.add(rtos(p.z)) - + proc `&`*(p:TPoint3d,m:TMatrix3d):TPoint3d= ## Concatenates a point `p` with a transform `m`, ## resulting in a new, transformed point. @@ -893,18 +893,18 @@ proc `&=` *(p:var TPoint3d,m:TMatrix3d)= p.x=m.cx*z+m.bx*y+m.ax*x+m.tx p.y=m.cy*z+m.by*y+m.ay*x+m.ty p.z=m.cz*z+m.bz*y+m.az*x+m.tz - + proc transformInv*(p:var TPoint3d,m:TMatrix3d)= ## Applies the inverse of transformation `m` onto `p` in place. ## If the matrix is not invertable (determinant=0) , EDivByZero will ## be raised. - + # can possibly be more optimized in the future so use this function when possible p&=inverse(m) proc `+`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} = - ## Adds a vector `v` to a point `p`, resulting + ## Adds a vector `v` to a point `p`, resulting ## in a new point. result.x=p.x+v.x result.y=p.y+v.y @@ -917,7 +917,7 @@ proc `+=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} = p.z+=v.z proc `-`*(p:TPoint3d,v:TVector3d):TPoint3d {.noInit,inline.} = - ## Subtracts a vector `v` from a point `p`, resulting + ## Subtracts a vector `v` from a point `p`, resulting ## in a new point. result.x=p.x-v.x result.y=p.y-v.y @@ -933,37 +933,37 @@ proc `-=`*(p:var TPoint3d,v:TVector3d) {.noInit,inline.} = ## Subtracts a vector `v` from a point `p` in place. p.x-=v.x p.y-=v.y - p.z-=v.z + p.z-=v.z proc equals(p1,p2:TPoint3d,tol=1.0e-6):bool {.inline.}= ## Checks if two points approximately equals with a tolerance. return abs(p2.x-p1.x)<=tol and abs(p2.y-p1.y)<=tol and abs(p2.z-p1.z)<=tol proc `=~`*(p1,p2:TPoint3d):bool {.inline.}= - ## Checks if two vectors approximately equals with a + ## Checks if two vectors approximately equals with a ## hardcoded tolerance 1e-6 equals(p1,p2) proc rotate*(p:var TPoint3d,rad:float,axis:TVector3d)= - ## Rotates point `p` in place `rad` radians about an axis + ## Rotates point `p` in place `rad` radians about an axis ## passing through origo. - + var v=vector3d(p.x,p.y,p.z) v.rotate(rad,axis) # reuse this code here since doing the same thing and quite complicated p.x=v.x p.y=v.y p.z=v.z - + proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)= - ## Rotates point `p` in place `rad` radians about an axis + ## Rotates point `p` in place `rad` radians about an axis ## passing through `org` - + # see PDF document http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.pdf # for how this is computed - + var normax=axis normax.normalize - + let cs=cos(angle) omc=1.0-cs @@ -987,17 +987,17 @@ proc rotate*(p:var TPoint3d,angle:float,org:TPoint3d,axis:TVector3d)= bv=b*v cw=c*w uxmvymwz=ux-vy-wz - + p.x=(a*(vv+ww)-u*(bv+cw-uxmvymwz))*omc + x*cs + (b*w+v*z-c*v-w*y)*si p.y=(b*(uu+ww)-v*(au+cw-uxmvymwz))*omc + y*cs + (c*u-a*w+w*x-u*z)*si p.z=(c*(uu+vv)-w*(au+bv-uxmvymwz))*omc + z*cs + (a*v+u*y-b*u-v*x)*si - + proc scale*(p:var TPoint3d,fac:float) {.inline.}= ## Scales a point in place `fac` times with world origo as origin. p.x*=fac p.y*=fac p.z*=fac - + proc scale*(p:var TPoint3d,fac:float,org:TPoint3d){.inline.}= ## Scales the point in place `fac` times with `org` as origin. p.x=(p.x - org.x) * fac + org.x @@ -1005,7 +1005,7 @@ proc scale*(p:var TPoint3d,fac:float,org:TPoint3d){.inline.}= p.z=(p.z - org.z) * fac + org.z proc stretch*(p:var TPoint3d,facx,facy,facz:float){.inline.}= - ## Scales a point in place non uniformly `facx` , `facy` , `facz` times + ## Scales a point in place non uniformly `facx` , `facy` , `facz` times ## with world origo as origin. p.x*=facx p.y*=facy @@ -1017,7 +1017,7 @@ proc stretch*(p:var TPoint3d,facx,facy,facz:float,org:TPoint3d){.inline.}= p.x=(p.x - org.x) * facx + org.x p.y=(p.y - org.y) * facy + org.y p.z=(p.z - org.z) * facz + org.z - + proc move*(p:var TPoint3d,dx,dy,dz:float){.inline.}= ## Translates a point `dx` , `dy` , `dz` in place. @@ -1033,7 +1033,7 @@ proc move*(p:var TPoint3d,v:TVector3d){.inline.}= proc area*(a,b,c:TPoint3d):float {.inline.}= ## Computes the area of the triangle thru points `a` , `b` and `c` - + # The area of a planar 3d quadliteral is the magnitude of the cross # product of two edge vectors. Taking this time 0.5 gives the triangle area. return cross(b-a,c-a).len*0.5 diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 232e52c89..a9357ce67 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -819,15 +819,18 @@ proc enlarge[A](t: var CountTable[A]) = swap(t.data, n) proc `[]=`*[A](t: var CountTable[A], key: A, val: int) = - ## puts a (key, value)-pair into `t`. `val` has to be positive. + ## puts a (key, value)-pair into `t`. assert val > 0 var h = rawGet(t, key) if h >= 0: t.data[h].val = val else: - h = -1 - h - t.data[h].key = key - t.data[h].val = val + if mustRehash(len(t.data), t.counter): enlarge(t) + rawInsert(t, t.data, key, val) + inc(t.counter) + #h = -1 - h + #t.data[h].key = key + #t.data[h].val = val proc initCountTable*[A](initialSize=64): CountTable[A] = ## creates a new count table that is empty. diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim index b46f8343c..dd141eb01 100644 --- a/lib/pure/ftpclient.nim +++ b/lib/pure/ftpclient.nim @@ -593,7 +593,7 @@ proc register*(d: Dispatcher, ftp: AsyncFTPClient): Delegate {.discardable.} = ftp.disp = d return ftp.disp.register(ftp.csock) -when isMainModule: +when not defined(testing) and isMainModule: proc main = var d = newDispatcher() let hev = diff --git a/lib/pure/gentabs.nim b/lib/pure/gentabs.nim index 8c89a0ac3..84d0a44de 100644 --- a/lib/pure/gentabs.nim +++ b/lib/pure/gentabs.nim @@ -9,7 +9,7 @@ ## The ``gentabs`` module implements an efficient hash table that is a ## key-value mapping. The keys are required to be strings, but the values -## may be any Nim or user defined type. This module supports matching +## may be any Nim or user defined type. This module supports matching ## of keys in case-sensitive, case-insensitive and style-insensitive modes. {.deprecated.} @@ -22,7 +22,7 @@ type modeCaseSensitive, ## case sensitive matching of keys modeCaseInsensitive, ## case insensitive matching of keys modeStyleInsensitive ## style sensitive matching of keys - + TGenKeyValuePair[T] = tuple[key: string, val: T] TGenKeyValuePairSeq[T] = seq[TGenKeyValuePair[T]] TGenTable*[T] = object of RootObj @@ -83,7 +83,7 @@ proc rawGet[T](tbl: PGenTable[T], key: string): int = h = nextTry(h, high(tbl.data)) result = - 1 -proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T], +proc rawInsert[T](tbl: PGenTable[T], data: var TGenKeyValuePairSeq[T], key: string, val: T) = var h: THash h = myhash(tbl, key) and high(data) @@ -96,7 +96,7 @@ proc enlarge[T](tbl: PGenTable[T]) = var n: TGenKeyValuePairSeq[T] newSeq(n, len(tbl.data) * growthFactor) for i in countup(0, high(tbl.data)): - if not isNil(tbl.data[i].key): + if not isNil(tbl.data[i].key): rawInsert[T](tbl, n, tbl.data[i].key, tbl.data[i].val) swap(tbl.data, n) @@ -141,20 +141,20 @@ when isMainModule: assert(not x.hasKey("NOPE")) # ...but key "NOPE" is not in the table. for k,v in pairs(x): # make sure the 'pairs' iterator works assert(x[k]==v) - + # # Verify a table of user-defined types # type TMyType = tuple[first, second: string] # a pair of strings - + var y = newGenTable[TMyType](modeCaseInsensitive) # hash table where each # value is TMyType tuple - + #var junk: TMyType = ("OK", "Here") - + #echo junk.first, " ", junk.second - + y["Hello"] = ("Hello", "World") y["Goodbye"] = ("Goodbye", "Everyone") #y["Hello"] = TMyType( ("Hello", "World") ) @@ -163,42 +163,44 @@ when isMainModule: assert( not isNil(y["Hello"].first) ) assert( y["Hello"].first == "Hello" ) assert( y["Hello"].second == "World" ) - + # # Verify table of tables # - var z: PGenTable[ PGenTable[int] ] # hash table where each value is + var z: PGenTable[ PGenTable[int] ] # hash table where each value is # a hash table of ints - + z = newGenTable[PGenTable[int]](modeCaseInsensitive) z["first"] = newGenTable[int](modeCaseInsensitive) z["first"]["one"] = 1 z["first"]["two"] = 2 z["first"]["three"] = 3 - + z["second"] = newGenTable[int](modeCaseInsensitive) z["second"]["red"] = 10 z["second"]["blue"] = 20 - + assert(len(z) == 2) # length of outer table assert(len(z["first"]) == 3) # length of "first" table assert(len(z["second"]) == 2) # length of "second" table assert( z["first"]["one"] == 1) # retrieve from first inner table assert( z["second"]["red"] == 10) # retrieve from second inner table - - var output = "" - for k, v in pairs(z): - output.add( "$# ($#) ->\n" % [k,$len(v)] ) - for k2,v2 in pairs(v): - output.add( " $# <-> $#\n" % [k2,$v2] ) - - let expected = unindent """ - first (3) -> - two <-> 2 - three <-> 3 - one <-> 1 - second (2) -> - red <-> 10 - blue <-> 20 - """ - assert output == expected + + when false: + # disabled: depends on hash order: + var output = "" + for k, v in pairs(z): + output.add( "$# ($#) ->\L" % [k,$len(v)] ) + for k2,v2 in pairs(v): + output.add( " $# <-> $#\L" % [k2,$v2] ) + + let expected = unindent """ + first (3) -> + two <-> 2 + three <-> 3 + one <-> 1 + second (2) -> + red <-> 10 + blue <-> 20 + """ + assert output == expected diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index bf9e33296..e0092f314 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -15,8 +15,8 @@ ## type than its compiletime type: ## ## .. code-block:: nim -## -## type +## +## type ## TA = object ## TB = object of TA ## f: int @@ -28,6 +28,8 @@ ## new(b) ## a = b ## echo($$a[]) # produces "{}", not "{f: 0}" +## +## **Note**: The ``to`` and ``$$`` operations are available at compile-time! import streams, typeinfo, json, intsets, tables @@ -38,7 +40,12 @@ proc storeAny(s: Stream, a: TAny, stored: var IntSet) = case a.kind of akNone: assert false of akBool: s.write($getBool(a)) - of akChar: s.write(escapeJson($getChar(a))) + of akChar: + let ch = getChar(a) + if ch < '\128': + s.write(escapeJson($ch)) + else: + s.write($int(ch)) of akArray, akSequence: if a.kind == akSequence and isNil(a): s.write("null") else: @@ -92,7 +99,7 @@ proc storeAny(s: Stream, a: TAny, stored: var IntSet) = proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = case a.kind of akNone: assert false - of akBool: + of akBool: case p.kind of jsonFalse: setBiggestInt(a, 0) of jsonTrue: setBiggestInt(a, 1) @@ -105,8 +112,12 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = setBiggestInt(a, ord(x[0])) next(p) return + elif p.kind == jsonInt: + setBiggestInt(a, getInt(p)) + next(p) + return raiseParseErr(p, "string of length 1 expected for a char") - of akEnum: + of akEnum: if p.kind == jsonString: setBiggestInt(a, getEnumOrdinal(a, p.str)) next(p) @@ -122,7 +133,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = if p.kind == jsonArrayEnd: next(p) else: raiseParseErr(p, "']' end of array expected") of akSequence: - case p.kind + case p.kind of jsonNull: setPointer(a, nil) next(p) @@ -143,7 +154,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = if p.kind != jsonObjectStart: raiseParseErr(p, "'{' expected for an object") next(p) while p.kind != jsonObjectEnd and p.kind != jsonEof: - if p.kind != jsonString: + if p.kind != jsonString: raiseParseErr(p, "string expected for a field name") var fieldName = p.str next(p) @@ -160,7 +171,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = if p.kind == jsonArrayEnd: next(p) else: raiseParseErr(p, "']' end of array expected") of akPtr, akRef: - case p.kind + case p.kind of jsonNull: setPointer(a, nil) next(p) @@ -170,7 +181,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = of jsonArrayStart: next(p) if a.kind == akRef: invokeNew(a) - else: setPointer(a, alloc0(a.baseTypeSize)) + else: setPointer(a, alloc0(a.baseTypeSize)) if p.kind == jsonInt: t[p.getInt] = getPointer(a) next(p) @@ -179,8 +190,8 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = if p.kind == jsonArrayEnd: next(p) else: raiseParseErr(p, "']' end of ref-address pair expected") else: raiseParseErr(p, "int for pointer type expected") - of akProc, akPointer, akCString: - case p.kind + of akProc, akPointer, akCString: + case p.kind of jsonNull: setPointer(a, nil) next(p) @@ -189,7 +200,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = next(p) else: raiseParseErr(p, "int for pointer type expected") of akString: - case p.kind + case p.kind of jsonNull: setPointer(a, nil) next(p) @@ -197,7 +208,7 @@ proc loadAny(p: var JsonParser, a: TAny, t: var Table[BiggestInt, pointer]) = setString(a, p.str) next(p) else: raiseParseErr(p, "string expected") - of akInt..akInt64, akUInt..akUInt64: + of akInt..akInt64, akUInt..akUInt64: if p.kind == jsonInt: setBiggestInt(a, getInt(p)) next(p) @@ -243,22 +254,22 @@ proc to*[T](data: string): T = ## reads data and transforms it to a ``T``. var tab = initTable[BiggestInt, pointer]() loadAny(newStringStream(data), toAny(result), tab) - + when not defined(testing) and isMainModule: template testit(x: expr) = echo($$to[type(x)]($$x)) var x: array[0..4, array[0..4, string]] = [ - ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], - ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], + ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], + ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"]] testit(x) var test2: tuple[name: string, s: uint] = ("tuple test", 56u) testit(test2) - + type TE = enum blah, blah2 - + TestObj = object test, asd: int case test2: TE @@ -266,7 +277,7 @@ when not defined(testing) and isMainModule: help: string else: nil - + PNode = ref TNode TNode = object next, prev: PNode @@ -294,7 +305,7 @@ when not defined(testing) and isMainModule: test4.a = "ref string test: A" test4.b = "ref string test: B" testit(test4) - + var test5 = @[(0,1),(2,3),(4,5)] testit(test5) @@ -305,7 +316,7 @@ when not defined(testing) and isMainModule: echo($$test7) testit(test7) - type + type TA {.inheritable.} = object TB = object of TA f: int diff --git a/lib/pure/math.nim b/lib/pure/math.nim index daa108460..cb58ea39b 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -85,7 +85,7 @@ proc fac*(n: int): int {.noSideEffect.} = proc isPowerOfTwo*(x: int): bool {.noSideEffect.} = ## returns true, if `x` is a power of two, false otherwise. ## Zero and negative numbers are not a power of two. - return (x != 0) and ((x and (x - 1)) == 0) + return (x > 0) and ((x and (x - 1)) == 0) proc nextPowerOfTwo*(x: int): int {.noSideEffect.} = ## returns `x` rounded up to the nearest power of two. @@ -114,18 +114,23 @@ proc sum*[T](x: openArray[T]): T {.noSideEffect.} = ## If `x` is empty, 0 is returned. for i in items(x): result = result + i -proc mean*(x: openArray[float]): float {.noSideEffect.} = - ## computes the mean of the elements in `x`. +template toFloat(f: float): float = f + +proc mean*[T](x: openArray[T]): float {.noSideEffect.} = + ## computes the mean of the elements in `x`, which are first converted to floats. ## If `x` is empty, NaN is returned. - result = sum(x) / toFloat(len(x)) + ## ``toFloat(x: T): float`` must be defined. + for i in items(x): result = result + toFloat(i) + result = result / toFloat(len(x)) -proc variance*(x: openArray[float]): float {.noSideEffect.} = +proc variance*[T](x: openArray[T]): float {.noSideEffect.} = ## computes the variance of the elements in `x`. ## If `x` is empty, NaN is returned. + ## ``toFloat(x: T): float`` must be defined. result = 0.0 var m = mean(x) - for i in 0 .. high(x): - var diff = x[i] - m + for i in items(x): + var diff = toFloat(i) - m result = result + diff*diff result = result / toFloat(len(x)) diff --git a/lib/pure/pegs.nimfix b/lib/pure/pegs.nimfix deleted file mode 100644 index 15bc95351..000000000 --- a/lib/pure/pegs.nimfix +++ /dev/null @@ -1,1770 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Simple PEG (Parsing expression grammar) matching. Uses no memorization, but -## uses superoperators and symbol inlining to improve performance. Note: -## Matching performance is hopefully competitive with optimized regular -## expression engines. -## -## .. include:: ../doc/pegdocs.txt -## - -include "system/inclrtl" - -const - useUnicode = true ## change this to deactivate proper UTF-8 support - -import - strutils - -when useUnicode: - import unicode - -const - InlineThreshold = 5 ## number of leaves; -1 to disable inlining - MaxSubpatterns* = 10 ## defines the maximum number of subpatterns that - ## can be captured. More subpatterns cannot be captured! - -type - PegKind = enum - pkEmpty, - pkAny, ## any character (.) - pkAnyRune, ## any Unicode character (_) - pkNewLine, ## CR-LF, LF, CR - pkLetter, ## Unicode letter - pkLower, ## Unicode lower case letter - pkUpper, ## Unicode upper case letter - pkTitle, ## Unicode title character - pkWhitespace, ## Unicode whitespace character - pkTerminal, - pkTerminalIgnoreCase, - pkTerminalIgnoreStyle, - pkChar, ## single character to match - pkCharChoice, - pkNonTerminal, - pkSequence, ## a b c ... --> Internal DSL: peg(a, b, c) - pkOrderedChoice, ## a / b / ... --> Internal DSL: a / b or /[a, b, c] - pkGreedyRep, ## a* --> Internal DSL: *a - ## a+ --> (a a*) - pkGreedyRepChar, ## x* where x is a single character (superop) - pkGreedyRepSet, ## [set]* (superop) - pkGreedyAny, ## .* or _* (superop) - pkOption, ## a? --> Internal DSL: ?a - pkAndPredicate, ## &a --> Internal DSL: &a - pkNotPredicate, ## !a --> Internal DSL: !a - pkCapture, ## {a} --> Internal DSL: capture(a) - pkBackRef, ## $i --> Internal DSL: backref(i) - pkBackRefIgnoreCase, - pkBackRefIgnoreStyle, - pkSearch, ## @a --> Internal DSL: !*a - pkCapturedSearch, ## {@} a --> Internal DSL: !*\a - pkRule, ## a <- b - pkList, ## a, b - pkStartAnchor ## ^ --> Internal DSL: startAnchor() - NonTerminalFlag = enum - ntDeclared, ntUsed - NonTerminalObj = object ## represents a non terminal symbol - name: string ## the name of the symbol - line: int ## line the symbol has been declared/used in - col: int ## column the symbol has been declared/used in - flags: set[NonTerminalFlag] ## the nonterminal's flags - rule: TNode ## the rule that the symbol refers to - TNode {.shallow.} = object - case kind: PegKind - of pkEmpty..pkWhitespace: nil - of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: term: string - of pkChar, pkGreedyRepChar: ch: char - of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char] - of pkNonTerminal: nt: PNonTerminal - of pkBackRef..pkBackRefIgnoreStyle: index: range[0..MaxSubpatterns] - else: sons: seq[TNode] - PNonTerminal* = ref NonTerminalObj - TPeg* = TNode - -block: - type - Peg = TNode - NonTerminal = PNonTerminal - {.deprecated: [TPeg: Peg, PNonTerminal: NonTerminal].} - -proc term*(t: string): TPeg {.nosideEffect, rtl, extern: "npegs$1Str".} = - ## constructs a PEG from a terminal string - if t.len != 1: - result.kind = pkTerminal - result.term = t - else: - result.kind = pkChar - result.ch = t[0] - -proc termIgnoreCase*(t: string): TPeg {.nosideEffect, rtl, extern: "npegs$1".} = - ## constructs a PEG from a terminal string; ignore case for matching - result.kind = pkTerminalIgnoreCase - result.term = t - -proc termIgnoreStyle*(t: string): TPeg {.nosideEffect, rtl, extern: "npegs$1".} = - ## constructs a PEG from a terminal string; ignore style for matching - result.kind = pkTerminalIgnoreStyle - result.term = t - -proc term*(t: char): TPeg {.nosideEffect, rtl, extern: "npegs$1Char".} = - ## constructs a PEG from a terminal char - assert t != '\0' - result.kind = pkChar - result.ch = t - -proc charSet*(s: set[char]): TPeg {.nosideEffect, rtl, extern: "npegs$1".} = - ## constructs a PEG from a character set `s` - assert '\0' notin s - result.kind = pkCharChoice - new(result.charChoice) - result.charChoice[] = s - -proc len(a: TPeg): int {.inline.} = return a.sons.len -proc add(d: var TPeg, s: TPeg) {.inline.} = add(d.sons, s) - -proc addChoice(dest: var TPeg, elem: TPeg) = - var L = dest.len-1 - if L >= 0 and dest.sons[L].kind == pkCharChoice: - # caution! Do not introduce false aliasing here! - case elem.kind - of pkCharChoice: - dest.sons[L] = charSet(dest.sons[L].charChoice[] + elem.charChoice[]) - of pkChar: - dest.sons[L] = charSet(dest.sons[L].charChoice[] + {elem.ch}) - else: add(dest, elem) - else: add(dest, elem) - -template multipleOp(k: PegKind, localOpt: expr) = - result.kind = k - result.sons = @[] - for x in items(a): - if x.kind == k: - for y in items(x.sons): - localOpt(result, y) - else: - localOpt(result, x) - if result.len == 1: - result = result.sons[0] - -proc `/`*(a: varargs[TPeg]): TPeg {. - nosideEffect, rtl, extern: "npegsOrderedChoice".} = - ## constructs an ordered choice with the PEGs in `a` - multipleOp(pkOrderedChoice, addChoice) - -proc addSequence(dest: var TPeg, elem: TPeg) = - var L = dest.len-1 - if L >= 0 and dest.sons[L].kind == pkTerminal: - # caution! Do not introduce false aliasing here! - case elem.kind - of pkTerminal: - dest.sons[L] = term(dest.sons[L].term & elem.term) - of pkChar: - dest.sons[L] = term(dest.sons[L].term & elem.ch) - else: add(dest, elem) - else: add(dest, elem) - -proc sequence*(a: varargs[TPeg]): TPeg {. - nosideEffect, rtl, extern: "npegs$1".} = - ## constructs a sequence with all the PEGs from `a` - multipleOp(pkSequence, addSequence) - -proc `?`*(a: TPeg): TPeg {.nosideEffect, rtl, extern: "npegsOptional".} = - ## constructs an optional for the PEG `a` - if a.kind in {pkOption, pkGreedyRep, pkGreedyAny, pkGreedyRepChar, - pkGreedyRepSet}: - # a* ? --> a* - # a? ? --> a? - result = a - else: - result.kind = pkOption - result.sons = @[a] - -proc `*`*(a: TPeg): TPeg {.nosideEffect, rtl, extern: "npegsGreedyRep".} = - ## constructs a "greedy repetition" for the PEG `a` - case a.kind - of pkGreedyRep, pkGreedyRepChar, pkGreedyRepSet, pkGreedyAny, pkOption: - assert false - # produces endless loop! - of pkChar: - result.kind = pkGreedyRepChar - result.ch = a.ch - of pkCharChoice: - result.kind = pkGreedyRepSet - result.charChoice = a.charChoice # copying a reference suffices! - of pkAny, pkAnyRune: - result.kind = pkGreedyAny - else: - result.kind = pkGreedyRep - result.sons = @[a] - -proc `!*`*(a: TPeg): TPeg {.nosideEffect, rtl, extern: "npegsSearch".} = - ## constructs a "search" for the PEG `a` - result.kind = pkSearch - result.sons = @[a] - -proc `!*\`*(a: TPeg): TPeg {.noSideEffect, rtl, - extern: "npgegsCapturedSearch".} = - ## constructs a "captured search" for the PEG `a` - result.kind = pkCapturedSearch - result.sons = @[a] - -proc `+`*(a: TPeg): TPeg {.nosideEffect, rtl, extern: "npegsGreedyPosRep".} = - ## constructs a "greedy positive repetition" with the PEG `a` - return sequence(a, *a) - -proc `&`*(a: TPeg): TPeg {.nosideEffect, rtl, extern: "npegsAndPredicate".} = - ## constructs an "and predicate" with the PEG `a` - result.kind = pkAndPredicate - result.sons = @[a] - -proc `!`*(a: TPeg): TPeg {.nosideEffect, rtl, extern: "npegsNotPredicate".} = - ## constructs a "not predicate" with the PEG `a` - result.kind = pkNotPredicate - result.sons = @[a] - -proc any*: TPeg {.inline.} = - ## constructs the PEG `any character`:idx: (``.``) - result.kind = pkAny - -proc anyRune*: TPeg {.inline.} = - ## constructs the PEG `any rune`:idx: (``_``) - result.kind = pkAnyRune - -proc newLine*: TPeg {.inline.} = - ## constructs the PEG `newline`:idx: (``\n``) - result.kind = pkNewLine - -proc unicodeLetter*: TPeg {.inline.} = - ## constructs the PEG ``\letter`` which matches any Unicode letter. - result.kind = pkLetter - -proc unicodeLower*: TPeg {.inline.} = - ## constructs the PEG ``\lower`` which matches any Unicode lowercase letter. - result.kind = pkLower - -proc unicodeUpper*: TPeg {.inline.} = - ## constructs the PEG ``\upper`` which matches any Unicode uppercase letter. - result.kind = pkUpper - -proc unicodeTitle*: TPeg {.inline.} = - ## constructs the PEG ``\title`` which matches any Unicode title letter. - result.kind = pkTitle - -proc unicodeWhitespace*: TPeg {.inline.} = - ## constructs the PEG ``\white`` which matches any Unicode - ## whitespace character. - result.kind = pkWhitespace - -proc startAnchor*: TPeg {.inline.} = - ## constructs the PEG ``^`` which matches the start of the input. - result.kind = pkStartAnchor - -proc endAnchor*: TPeg {.inline.} = - ## constructs the PEG ``$`` which matches the end of the input. - result = !any() - -proc capture*(a: TPeg): TPeg {.nosideEffect, rtl, extern: "npegsCapture".} = - ## constructs a capture with the PEG `a` - result.kind = pkCapture - result.sons = @[a] - -proc backref*(index: range[1..MaxSubpatterns]): TPeg {. - nosideEffect, rtl, extern: "npegs$1".} = - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. - result.kind = pkBackRef - result.index = index-1 - -proc backrefIgnoreCase*(index: range[1..MaxSubpatterns]): TPeg {. - nosideEffect, rtl, extern: "npegs$1".} = - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. Ignores case for matching. - result.kind = pkBackRefIgnoreCase - result.index = index-1 - -proc backrefIgnoreStyle*(index: range[1..MaxSubpatterns]): TPeg {. - nosideEffect, rtl, extern: "npegs$1".}= - ## constructs a back reference of the given `index`. `index` starts counting - ## from 1. Ignores style for matching. - result.kind = pkBackRefIgnoreStyle - result.index = index-1 - -proc spaceCost(n: TPeg): int = - case n.kind - of pkEmpty: discard - of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle, pkChar, - pkGreedyRepChar, pkCharChoice, pkGreedyRepSet, - pkAny..pkWhitespace, pkGreedyAny: - result = 1 - of pkNonTerminal: - # we cannot inline a rule with a non-terminal - result = InlineThreshold+1 - else: - for i in 0..n.len-1: - inc(result, spaceCost(n.sons[i])) - if result >= InlineThreshold: break - -proc nonterminal*(n: PNonTerminal): TPeg {. - nosideEffect, rtl, extern: "npegs$1".} = - ## constructs a PEG that consists of the nonterminal symbol - assert n != nil - if ntDeclared in n.flags and spaceCost(n.rule) < InlineThreshold: - when false: echo "inlining symbol: ", n.name - result = n.rule # inlining of rule enables better optimizations - else: - result.kind = pkNonTerminal - result.nt = n - -proc newNonTerminal*(name: string, line, column: int): PNonTerminal {. - nosideEffect, rtl, extern: "npegs$1".} = - ## constructs a nonterminal symbol - new(result) - result.name = name - result.line = line - result.col = column - -template letters*: expr = - ## expands to ``charset({'A'..'Z', 'a'..'z'})`` - charSet({'A'..'Z', 'a'..'z'}) - -template digits*: expr = - ## expands to ``charset({'0'..'9'})`` - charSet({'0'..'9'}) - -template whitespace*: expr = - ## expands to ``charset({' ', '\9'..'\13'})`` - charSet({' ', '\9'..'\13'}) - -template identChars*: expr = - ## expands to ``charset({'a'..'z', 'A'..'Z', '0'..'9', '_'})`` - charSet({'a'..'z', 'A'..'Z', '0'..'9', '_'}) - -template identStartChars*: expr = - ## expands to ``charset({'A'..'Z', 'a'..'z', '_'})`` - charSet({'a'..'z', 'A'..'Z', '_'}) - -template ident*: expr = - ## same as ``[a-zA-Z_][a-zA-z_0-9]*``; standard identifier - sequence(charSet({'a'..'z', 'A'..'Z', '_'}), - *charSet({'a'..'z', 'A'..'Z', '0'..'9', '_'})) - -template natural*: expr = - ## same as ``\d+`` - +digits - -# ------------------------- debugging ----------------------------------------- - -proc esc(c: char, reserved = {'\0'..'\255'}): string = - case c - of '\b': result = "\\b" - of '\t': result = "\\t" - of '\c': result = "\\c" - of '\L': result = "\\l" - of '\v': result = "\\v" - of '\f': result = "\\f" - of '\e': result = "\\e" - of '\a': result = "\\a" - of '\\': result = "\\\\" - of 'a'..'z', 'A'..'Z', '0'..'9', '_': result = $c - elif c < ' ' or c >= '\128': result = '\\' & $ord(c) - elif c in reserved: result = '\\' & c - else: result = $c - -proc singleQuoteEsc(c: char): string = return "'" & esc(c, {'\''}) & "'" - -proc singleQuoteEsc(str: string): string = - result = "'" - for c in items(str): add result, esc(c, {'\''}) - add result, '\'' - -proc charSetEscAux(cc: set[char]): string = - const reserved = {'^', '-', ']'} - result = "" - var c1 = 0 - while c1 <= 0xff: - if chr(c1) in cc: - var c2 = c1 - while c2 < 0xff and chr(succ(c2)) in cc: inc(c2) - if c1 == c2: - add result, esc(chr(c1), reserved) - elif c2 == succ(c1): - add result, esc(chr(c1), reserved) & esc(chr(c2), reserved) - else: - add result, esc(chr(c1), reserved) & '-' & esc(chr(c2), reserved) - c1 = c2 - inc(c1) - -proc charSetEsc(cc: set[char]): string = - if card(cc) >= 128+64: - result = "[^" & charSetEscAux({'\1'..'\xFF'} - cc) & ']' - else: - result = '[' & charSetEscAux(cc) & ']' - -proc toStrAux(r: TPeg, res: var string) = - case r.kind - of pkEmpty: add(res, "()") - of pkAny: add(res, '.') - of pkAnyRune: add(res, '_') - of pkLetter: add(res, "\\letter") - of pkLower: add(res, "\\lower") - of pkUpper: add(res, "\\upper") - of pkTitle: add(res, "\\title") - of pkWhitespace: add(res, "\\white") - - of pkNewLine: add(res, "\\n") - of pkTerminal: add(res, singleQuoteEsc(r.term)) - of pkTerminalIgnoreCase: - add(res, 'i') - add(res, singleQuoteEsc(r.term)) - of pkTerminalIgnoreStyle: - add(res, 'y') - add(res, singleQuoteEsc(r.term)) - of pkChar: add(res, singleQuoteEsc(r.ch)) - of pkCharChoice: add(res, charSetEsc(r.charChoice[])) - of pkNonTerminal: add(res, r.nt.name) - of pkSequence: - add(res, '(') - toStrAux(r.sons[0], res) - for i in 1 .. high(r.sons): - add(res, ' ') - toStrAux(r.sons[i], res) - add(res, ')') - of pkOrderedChoice: - add(res, '(') - toStrAux(r.sons[0], res) - for i in 1 .. high(r.sons): - add(res, " / ") - toStrAux(r.sons[i], res) - add(res, ')') - of pkGreedyRep: - toStrAux(r.sons[0], res) - add(res, '*') - of pkGreedyRepChar: - add(res, singleQuoteEsc(r.ch)) - add(res, '*') - of pkGreedyRepSet: - add(res, charSetEsc(r.charChoice[])) - add(res, '*') - of pkGreedyAny: - add(res, ".*") - of pkOption: - toStrAux(r.sons[0], res) - add(res, '?') - of pkAndPredicate: - add(res, '&') - toStrAux(r.sons[0], res) - of pkNotPredicate: - add(res, '!') - toStrAux(r.sons[0], res) - of pkSearch: - add(res, '@') - toStrAux(r.sons[0], res) - of pkCapturedSearch: - add(res, "{@}") - toStrAux(r.sons[0], res) - of pkCapture: - add(res, '{') - toStrAux(r.sons[0], res) - add(res, '}') - of pkBackRef: - add(res, '$') - add(res, $r.index) - of pkBackRefIgnoreCase: - add(res, "i$") - add(res, $r.index) - of pkBackRefIgnoreStyle: - add(res, "y$") - add(res, $r.index) - of pkRule: - toStrAux(r.sons[0], res) - add(res, " <- ") - toStrAux(r.sons[1], res) - of pkList: - for i in 0 .. high(r.sons): - toStrAux(r.sons[i], res) - add(res, "\n") - of pkStartAnchor: - add(res, '^') - -proc `$` *(r: TPeg): string {.nosideEffect, rtl, extern: "npegsToString".} = - ## converts a PEG to its string representation - result = "" - toStrAux(r, result) - -# --------------------- core engine ------------------------------------------- - -type - Captures* = object ## contains the captured substrings. - matches: array[0..MaxSubpatterns-1, tuple[first, last: int]] - ml: int - origStart: int - -{.deprecated: [TCaptures: Captures].} - -proc bounds*(c: Captures, - i: range[0..MaxSubpatterns-1]): tuple[first, last: int] = - ## returns the bounds ``[first..last]`` of the `i`'th capture. - result = c.matches[i] - -when not useUnicode: - type - Rune = char - template fastRuneAt(s, i, ch: expr) = - ch = s[i] - inc(i) - template runeLenAt(s, i: expr): expr = 1 - - proc isAlpha(a: char): bool {.inline.} = return a in {'a'..'z','A'..'Z'} - proc isUpper(a: char): bool {.inline.} = return a in {'A'..'Z'} - proc isLower(a: char): bool {.inline.} = return a in {'a'..'z'} - proc isTitle(a: char): bool {.inline.} = return false - proc isWhiteSpace(a: char): bool {.inline.} = return a in {' ', '\9'..'\13'} - -proc rawMatch*(s: string, p: TPeg, start: int, c: var Captures): int {. - nosideEffect, rtl, extern: "npegs$1".} = - ## low-level matching proc that implements the PEG interpreter. Use this - ## for maximum efficiency (every other PEG operation ends up calling this - ## proc). - ## Returns -1 if it does not match, else the length of the match - case p.kind - of pkEmpty: result = 0 # match of length 0 - of pkAny: - if s[start] != '\0': result = 1 - else: result = -1 - of pkAnyRune: - if s[start] != '\0': - result = runeLenAt(s, start) - else: - result = -1 - of pkLetter: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isAlpha(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkLower: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isLower(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkUpper: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isUpper(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkTitle: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isTitle(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkWhitespace: - if s[start] != '\0': - var a: Rune - result = start - fastRuneAt(s, result, a) - if isWhiteSpace(a): dec(result, start) - else: result = -1 - else: - result = -1 - of pkGreedyAny: - result = len(s) - start - of pkNewLine: - if s[start] == '\L': result = 1 - elif s[start] == '\C': - if s[start+1] == '\L': result = 2 - else: result = 1 - else: result = -1 - of pkTerminal: - result = len(p.term) - for i in 0..result-1: - if p.term[i] != s[start+i]: - result = -1 - break - of pkTerminalIgnoreCase: - var - i = 0 - a, b: Rune - result = start - while i < len(p.term): - fastRuneAt(p.term, i, a) - fastRuneAt(s, result, b) - if toLower(a) != toLower(b): - result = -1 - break - dec(result, start) - of pkTerminalIgnoreStyle: - var - i = 0 - a, b: Rune - result = start - while i < len(p.term): - while true: - fastRuneAt(p.term, i, a) - if a != Rune('_'): break - while true: - fastRuneAt(s, result, b) - if b != Rune('_'): break - if toLower(a) != toLower(b): - result = -1 - break - dec(result, start) - of pkChar: - if p.ch == s[start]: result = 1 - else: result = -1 - of pkCharChoice: - if contains(p.charChoice[], s[start]): result = 1 - else: result = -1 - of pkNonTerminal: - var oldMl = c.ml - when false: echo "enter: ", p.nt.name - result = rawMatch(s, p.nt.rule, start, c) - when false: echo "leave: ", p.nt.name - if result < 0: c.ml = oldMl - of pkSequence: - var oldMl = c.ml - result = 0 - for i in 0..high(p.sons): - var x = rawMatch(s, p.sons[i], start+result, c) - if x < 0: - c.ml = oldMl - result = -1 - break - else: inc(result, x) - of pkOrderedChoice: - var oldMl = c.ml - for i in 0..high(p.sons): - result = rawMatch(s, p.sons[i], start, c) - if result >= 0: break - c.ml = oldMl - of pkSearch: - var oldMl = c.ml - result = 0 - while start+result < s.len: - var x = rawMatch(s, p.sons[0], start+result, c) - if x >= 0: - inc(result, x) - return - inc(result) - result = -1 - c.ml = oldMl - of pkCapturedSearch: - var idx = c.ml # reserve a slot for the subpattern - inc(c.ml) - result = 0 - while start+result < s.len: - var x = rawMatch(s, p.sons[0], start+result, c) - if x >= 0: - if idx < MaxSubpatterns: - c.matches[idx] = (start, start+result-1) - #else: silently ignore the capture - inc(result, x) - return - inc(result) - result = -1 - c.ml = idx - of pkGreedyRep: - result = 0 - while true: - var x = rawMatch(s, p.sons[0], start+result, c) - # if x == 0, we have an endless loop; so the correct behaviour would be - # not to break. But endless loops can be easily introduced: - # ``(comment / \w*)*`` is such an example. Breaking for x == 0 does the - # expected thing in this case. - if x <= 0: break - inc(result, x) - of pkGreedyRepChar: - result = 0 - var ch = p.ch - while ch == s[start+result]: inc(result) - of pkGreedyRepSet: - result = 0 - while contains(p.charChoice[], s[start+result]): inc(result) - of pkOption: - result = max(0, rawMatch(s, p.sons[0], start, c)) - of pkAndPredicate: - var oldMl = c.ml - result = rawMatch(s, p.sons[0], start, c) - if result >= 0: result = 0 # do not consume anything - else: c.ml = oldMl - of pkNotPredicate: - var oldMl = c.ml - result = rawMatch(s, p.sons[0], start, c) - if result < 0: result = 0 - else: - c.ml = oldMl - result = -1 - of pkCapture: - var idx = c.ml # reserve a slot for the subpattern - inc(c.ml) - result = rawMatch(s, p.sons[0], start, c) - if result >= 0: - if idx < MaxSubpatterns: - c.matches[idx] = (start, start+result-1) - #else: silently ignore the capture - else: - c.ml = idx - of pkBackRef..pkBackRefIgnoreStyle: - if p.index >= c.ml: return -1 - var (a, b) = c.matches[p.index] - var n: TPeg - n.kind = succ(pkTerminal, ord(p.kind)-ord(pkBackRef)) - n.term = s.substr(a, b) - result = rawMatch(s, n, start, c) - of pkStartAnchor: - if c.origStart == start: result = 0 - else: result = -1 - of pkRule, pkList: assert false - -template fillMatches(s, caps, c: expr) = - for k in 0..c.ml-1: - caps[k] = substr(s, c.matches[k][0], c.matches[k][1]) - -proc match*(s: string, pattern: TPeg, matches: var openArray[string], - start = 0): bool {.nosideEffect, rtl, extern: "npegs$1Capture".} = - ## returns ``true`` if ``s[start..]`` matches the ``pattern`` and - ## the captured substrings in the array ``matches``. If it does not - ## match, nothing is written into ``matches`` and ``false`` is - ## returned. - var c: Captures - c.origStart = start - result = rawMatch(s, pattern, start, c) == len(s) - start - if result: fillMatches(s, matches, c) - -proc match*(s: string, pattern: TPeg, - start = 0): bool {.nosideEffect, rtl, extern: "npegs$1".} = - ## returns ``true`` if ``s`` matches the ``pattern`` beginning from ``start``. - var c: Captures - c.origStart = start - result = rawMatch(s, pattern, start, c) == len(s)-start - -proc matchLen*(s: string, pattern: TPeg, matches: var openArray[string], - start = 0): int {.nosideEffect, rtl, extern: "npegs$1Capture".} = - ## the same as ``match``, but it returns the length of the match, - ## if there is no match, -1 is returned. Note that a match length - ## of zero can happen. It's possible that a suffix of `s` remains - ## that does not belong to the match. - var c: Captures - c.origStart = start - result = rawMatch(s, pattern, start, c) - if result >= 0: fillMatches(s, matches, c) - -proc matchLen*(s: string, pattern: TPeg, - start = 0): int {.nosideEffect, rtl, extern: "npegs$1".} = - ## the same as ``match``, but it returns the length of the match, - ## if there is no match, -1 is returned. Note that a match length - ## of zero can happen. It's possible that a suffix of `s` remains - ## that does not belong to the match. - var c: Captures - c.origStart = start - result = rawMatch(s, pattern, start, c) - -proc find*(s: string, pattern: TPeg, matches: var openArray[string], - start = 0): int {.nosideEffect, rtl, extern: "npegs$1Capture".} = - ## returns the starting position of ``pattern`` in ``s`` and the captured - ## substrings in the array ``matches``. If it does not match, nothing - ## is written into ``matches`` and -1 is returned. - var c: Captures - c.origStart = start - for i in start .. s.len-1: - c.ml = 0 - if rawMatch(s, pattern, i, c) >= 0: - fillMatches(s, matches, c) - return i - return -1 - # could also use the pattern here: (!P .)* P - -proc findBounds*(s: string, pattern: TPeg, matches: var openArray[string], - start = 0): tuple[first, last: int] {. - nosideEffect, rtl, extern: "npegs$1Capture".} = - ## returns the starting position and end position of ``pattern`` in ``s`` - ## and the captured - ## substrings in the array ``matches``. If it does not match, nothing - ## is written into ``matches`` and (-1,0) is returned. - var c: Captures - c.origStart = start - for i in start .. s.len-1: - c.ml = 0 - var L = rawMatch(s, pattern, i, c) - if L >= 0: - fillMatches(s, matches, c) - return (i, i+L-1) - return (-1, 0) - -proc find*(s: string, pattern: TPeg, - start = 0): int {.nosideEffect, rtl, extern: "npegs$1".} = - ## returns the starting position of ``pattern`` in ``s``. If it does not - ## match, -1 is returned. - var c: Captures - c.origStart = start - for i in start .. s.len-1: - if rawMatch(s, pattern, i, c) >= 0: return i - return -1 - -iterator findAll*(s: string, pattern: TPeg, start = 0): string = - ## yields all matching *substrings* of `s` that match `pattern`. - var c: Captures - c.origStart = start - var i = start - while i < s.len: - c.ml = 0 - var L = rawMatch(s, pattern, i, c) - if L < 0: - inc(i, 1) - else: - yield substr(s, i, i+L-1) - inc(i, L) - -proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {. - nosideEffect, rtl, extern: "npegs$1".} = - ## returns all matching *substrings* of `s` that match `pattern`. - ## If it does not match, @[] is returned. - accumulateResult(findAll(s, pattern, start)) - -when not defined(nimhygiene): - {.pragma: inject.} - -template `=~`*(s: string, pattern: TPeg): bool = - ## This calls ``match`` with an implicit declared ``matches`` array that - ## can be used in the scope of the ``=~`` call: - ## - ## .. code-block:: nim - ## - ## if line =~ peg"\s* {\w+} \s* '=' \s* {\w+}": - ## # matches a key=value pair: - ## echo("Key: ", matches[0]) - ## echo("Value: ", matches[1]) - ## elif line =~ peg"\s*{'#'.*}": - ## # matches a comment - ## # note that the implicit ``matches`` array is different from the - ## # ``matches`` array of the first branch - ## echo("comment: ", matches[0]) - ## else: - ## echo("syntax error") - ## - bind MaxSubpatterns - when not declaredInScope(matches): - var matches {.inject.}: array[0..MaxSubpatterns-1, string] - match(s, pattern, matches) - -# ------------------------- more string handling ------------------------------ - -proc contains*(s: string, pattern: TPeg, start = 0): bool {. - nosideEffect, rtl, extern: "npegs$1".} = - ## same as ``find(s, pattern, start) >= 0`` - return find(s, pattern, start) >= 0 - -proc contains*(s: string, pattern: TPeg, matches: var openArray[string], - start = 0): bool {.nosideEffect, rtl, extern: "npegs$1Capture".} = - ## same as ``find(s, pattern, matches, start) >= 0`` - return find(s, pattern, matches, start) >= 0 - -proc startsWith*(s: string, prefix: TPeg, start = 0): bool {. - nosideEffect, rtl, extern: "npegs$1".} = - ## returns true if `s` starts with the pattern `prefix` - result = matchLen(s, prefix, start) >= 0 - -proc endsWith*(s: string, suffix: TPeg, start = 0): bool {. - nosideEffect, rtl, extern: "npegs$1".} = - ## returns true if `s` ends with the pattern `prefix` - var c: Captures - c.origStart = start - for i in start .. s.len-1: - if rawMatch(s, suffix, i, c) == s.len - i: return true - -proc replacef*(s: string, sub: TPeg, by: string): string {. - nosideEffect, rtl, extern: "npegs$1".} = - ## Replaces `sub` in `s` by the string `by`. Captures can be accessed in `by` - ## with the notation ``$i`` and ``$#`` (see strutils.`%`). Examples: - ## - ## .. code-block:: nim - ## "var1=key; var2=key2".replace(peg"{\ident}'='{\ident}", "$1<-$2$2") - ## - ## Results in: - ## - ## .. code-block:: nim - ## - ## "var1<-keykey; val2<-key2key2" - result = "" - var i = 0 - var caps: array[0..MaxSubpatterns-1, string] - var c: Captures - while i < s.len: - c.ml = 0 - var x = rawMatch(s, sub, i, c) - if x <= 0: - add(result, s[i]) - inc(i) - else: - fillMatches(s, caps, c) - addf(result, by, caps) - inc(i, x) - add(result, substr(s, i)) - -proc replace*(s: string, sub: TPeg, by = ""): string {. - nosideEffect, rtl, extern: "npegs$1".} = - ## Replaces `sub` in `s` by the string `by`. Captures cannot be accessed - ## in `by`. - result = "" - var i = 0 - var c: Captures - while i < s.len: - var x = rawMatch(s, sub, i, c) - if x <= 0: - add(result, s[i]) - inc(i) - else: - add(result, by) - inc(i, x) - add(result, substr(s, i)) - -proc parallelReplace*(s: string, subs: varargs[ - tuple[pattern: TPeg, repl: string]]): string {. - nosideEffect, rtl, extern: "npegs$1".} = - ## Returns a modified copy of `s` with the substitutions in `subs` - ## applied in parallel. - result = "" - var i = 0 - var c: Captures - var caps: array[0..MaxSubpatterns-1, string] - while i < s.len: - block searchSubs: - for j in 0..high(subs): - c.ml = 0 - var x = rawMatch(s, subs[j][0], i, c) - if x > 0: - fillMatches(s, caps, c) - addf(result, subs[j][1], caps) - inc(i, x) - break searchSubs - add(result, s[i]) - inc(i) - # copy the rest: - add(result, substr(s, i)) - -proc transformFile*(infile, outfile: string, - subs: varargs[tuple[pattern: TPeg, repl: string]]) {. - rtl, extern: "npegs$1".} = - ## reads in the file `infile`, performs a parallel replacement (calls - ## `parallelReplace`) and writes back to `outfile`. Raises ``EIO`` if an - ## error occurs. This is supposed to be used for quick scripting. - var x = readFile(infile).string - writeFile(outfile, x.parallelReplace(subs)) - -iterator split*(s: string, sep: TPeg): string = - ## Splits the string `s` into substrings. - ## - ## Substrings are separated by the PEG `sep`. - ## Examples: - ## - ## .. code-block:: nim - ## for word in split("00232this02939is39an22example111", peg"\d+"): - ## writeln(stdout, word) - ## - ## Results in: - ## - ## .. code-block:: nim - ## "this" - ## "is" - ## "an" - ## "example" - ## - var c: Captures - var - first = 0 - last = 0 - while last < len(s): - c.ml = 0 - var x = rawMatch(s, sep, last, c) - if x > 0: inc(last, x) - first = last - while last < len(s): - inc(last) - c.ml = 0 - x = rawMatch(s, sep, last, c) - if x > 0: break - if first < last: - yield substr(s, first, last-1) - -proc split*(s: string, sep: TPeg): seq[string] {. - nosideEffect, rtl, extern: "npegs$1".} = - ## Splits the string `s` into substrings. - accumulateResult(split(s, sep)) - -# ------------------- scanner ------------------------------------------------- - -type - TModifier = enum - modNone, - modVerbatim, - modIgnoreCase, - modIgnoreStyle - TTokKind = enum ## enumeration of all tokens - tkInvalid, ## invalid token - tkEof, ## end of file reached - tkAny, ## . - tkAnyRune, ## _ - tkIdentifier, ## abc - tkStringLit, ## "abc" or 'abc' - tkCharSet, ## [^A-Z] - tkParLe, ## '(' - tkParRi, ## ')' - tkCurlyLe, ## '{' - tkCurlyRi, ## '}' - tkCurlyAt, ## '{@}' - tkArrow, ## '<-' - tkBar, ## '/' - tkStar, ## '*' - tkPlus, ## '+' - tkAmp, ## '&' - tkNot, ## '!' - tkOption, ## '?' - tkAt, ## '@' - tkBuiltin, ## \identifier - tkEscaped, ## \\ - tkBackref, ## '$' - tkDollar, ## '$' - tkHat ## '^' - - TToken {.final.} = object ## a token - kind: TTokKind ## the type of the token - modifier: TModifier - literal: string ## the parsed (string) literal - charset: set[char] ## if kind == tkCharSet - index: int ## if kind == tkBackref - - PegLexer {.inheritable.} = object ## the lexer object. - bufpos: int ## the current position within the buffer - buf: cstring ## the buffer itself - lineNumber: int ## the current line number - lineStart: int ## index of last line start in buffer - colOffset: int ## column to add - filename: string - -const - tokKindToStr: array[TTokKind, string] = [ - "invalid", "[EOF]", ".", "_", "identifier", "string literal", - "character set", "(", ")", "{", "}", "{@}", - "<-", "/", "*", "+", "&", "!", "?", - "@", "built-in", "escaped", "$", "$", "^" - ] - -proc handleCR(L: var PegLexer, pos: int): int = - assert(L.buf[pos] == '\c') - inc(L.lineNumber) - result = pos+1 - if L.buf[result] == '\L': inc(result) - L.lineStart = result - -proc handleLF(L: var PegLexer, pos: int): int = - assert(L.buf[pos] == '\L') - inc(L.lineNumber) - result = pos+1 - L.lineStart = result - -proc init(L: var PegLexer, input, filename: string, line = 1, col = 0) = - L.buf = input - L.bufpos = 0 - L.lineNumber = line - L.colOffset = col - L.lineStart = 0 - L.filename = filename - -proc getColumn(L: PegLexer): int {.inline.} = - result = abs(L.bufpos - L.lineStart) + L.colOffset - -proc getLine(L: PegLexer): int {.inline.} = - result = L.lineNumber - -proc errorStr(L: PegLexer, msg: string, line = -1, col = -1): string = - var line = if line < 0: getLine(L) else: line - var col = if col < 0: getColumn(L) else: col - result = "$1($2, $3) Error: $4" % [L.filename, $line, $col, msg] - -proc handleHexChar(c: var PegLexer, xi: var int) = - case c.buf[c.bufpos] - of '0'..'9': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('0')) - inc(c.bufpos) - of 'a'..'f': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('a') + 10) - inc(c.bufpos) - of 'A'..'F': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10) - inc(c.bufpos) - else: discard - -proc getEscapedChar(c: var PegLexer, tok: var TToken) = - inc(c.bufpos) - case c.buf[c.bufpos] - of 'r', 'R', 'c', 'C': - add(tok.literal, '\c') - inc(c.bufpos) - of 'l', 'L': - add(tok.literal, '\L') - inc(c.bufpos) - of 'f', 'F': - add(tok.literal, '\f') - inc(c.bufpos) - of 'e', 'E': - add(tok.literal, '\e') - inc(c.bufpos) - of 'a', 'A': - add(tok.literal, '\a') - inc(c.bufpos) - of 'b', 'B': - add(tok.literal, '\b') - inc(c.bufpos) - of 'v', 'V': - add(tok.literal, '\v') - inc(c.bufpos) - of 't', 'T': - add(tok.literal, '\t') - inc(c.bufpos) - of 'x', 'X': - inc(c.bufpos) - var xi = 0 - handleHexChar(c, xi) - handleHexChar(c, xi) - if xi == 0: tok.kind = tkInvalid - else: add(tok.literal, chr(xi)) - of '0'..'9': - var val = ord(c.buf[c.bufpos]) - ord('0') - inc(c.bufpos) - var i = 1 - while (i <= 3) and (c.buf[c.bufpos] in {'0'..'9'}): - val = val * 10 + ord(c.buf[c.bufpos]) - ord('0') - inc(c.bufpos) - inc(i) - if val > 0 and val <= 255: add(tok.literal, chr(val)) - else: tok.kind = tkInvalid - of '\0'..'\31': - tok.kind = tkInvalid - elif c.buf[c.bufpos] in strutils.Letters: - tok.kind = tkInvalid - else: - add(tok.literal, c.buf[c.bufpos]) - inc(c.bufpos) - -proc skip(c: var PegLexer) = - var pos = c.bufpos - var buf = c.buf - while true: - case buf[pos] - of ' ', '\t': - inc(pos) - of '#': - while not (buf[pos] in {'\c', '\L', '\0'}): inc(pos) - of '\c': - pos = handleCR(c, pos) - buf = c.buf - of '\L': - pos = handleLF(c, pos) - buf = c.buf - else: - break # EndOfFile also leaves the loop - c.bufpos = pos - -proc getString(c: var PegLexer, tok: var TToken) = - tok.kind = tkStringLit - var pos = c.bufpos + 1 - var buf = c.buf - var quote = buf[pos-1] - while true: - case buf[pos] - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - of '\c', '\L', '\0': - tok.kind = tkInvalid - break - elif buf[pos] == quote: - inc(pos) - break - else: - add(tok.literal, buf[pos]) - inc(pos) - c.bufpos = pos - -proc getDollar(c: var PegLexer, tok: var TToken) = - var pos = c.bufpos + 1 - var buf = c.buf - if buf[pos] in {'0'..'9'}: - tok.kind = tkBackref - tok.index = 0 - while buf[pos] in {'0'..'9'}: - tok.index = tok.index * 10 + ord(buf[pos]) - ord('0') - inc(pos) - else: - tok.kind = tkDollar - c.bufpos = pos - -proc getCharSet(c: var PegLexer, tok: var TToken) = - tok.kind = tkCharSet - tok.charset = {} - var pos = c.bufpos + 1 - var buf = c.buf - var caret = false - if buf[pos] == '^': - inc(pos) - caret = true - while true: - var ch: char - case buf[pos] - of ']': - inc(pos) - break - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - ch = tok.literal[tok.literal.len-1] - of '\C', '\L', '\0': - tok.kind = tkInvalid - break - else: - ch = buf[pos] - inc(pos) - incl(tok.charset, ch) - if buf[pos] == '-': - if buf[pos+1] == ']': - incl(tok.charset, '-') - inc(pos) - else: - inc(pos) - var ch2: char - case buf[pos] - of '\\': - c.bufpos = pos - getEscapedChar(c, tok) - pos = c.bufpos - ch2 = tok.literal[tok.literal.len-1] - of '\C', '\L', '\0': - tok.kind = tkInvalid - break - else: - ch2 = buf[pos] - inc(pos) - for i in ord(ch)+1 .. ord(ch2): - incl(tok.charset, chr(i)) - c.bufpos = pos - if caret: tok.charset = {'\1'..'\xFF'} - tok.charset - -proc getSymbol(c: var PegLexer, tok: var TToken) = - var pos = c.bufpos - var buf = c.buf - while true: - add(tok.literal, buf[pos]) - inc(pos) - if buf[pos] notin strutils.IdentChars: break - c.bufpos = pos - tok.kind = tkIdentifier - -proc getBuiltin(c: var PegLexer, tok: var TToken) = - if c.buf[c.bufpos+1] in strutils.Letters: - inc(c.bufpos) - getSymbol(c, tok) - tok.kind = tkBuiltin - else: - tok.kind = tkEscaped - getEscapedChar(c, tok) # may set tok.kind to tkInvalid - -proc getTok(c: var PegLexer, tok: var TToken) = - tok.kind = tkInvalid - tok.modifier = modNone - setLen(tok.literal, 0) - skip(c) - case c.buf[c.bufpos] - of '{': - inc(c.bufpos) - if c.buf[c.bufpos] == '@' and c.buf[c.bufpos+1] == '}': - tok.kind = tkCurlyAt - inc(c.bufpos, 2) - add(tok.literal, "{@}") - else: - tok.kind = tkCurlyLe - add(tok.literal, '{') - of '}': - tok.kind = tkCurlyRi - inc(c.bufpos) - add(tok.literal, '}') - of '[': - getCharSet(c, tok) - of '(': - tok.kind = tkParLe - inc(c.bufpos) - add(tok.literal, '(') - of ')': - tok.kind = tkParRi - inc(c.bufpos) - add(tok.literal, ')') - of '.': - tok.kind = tkAny - inc(c.bufpos) - add(tok.literal, '.') - of '_': - tok.kind = tkAnyRune - inc(c.bufpos) - add(tok.literal, '_') - of '\\': - getBuiltin(c, tok) - of '\'', '"': getString(c, tok) - of '$': getDollar(c, tok) - of '\0': - tok.kind = tkEof - tok.literal = "[EOF]" - of 'a'..'z', 'A'..'Z', '\128'..'\255': - getSymbol(c, tok) - if c.buf[c.bufpos] in {'\'', '"'} or - c.buf[c.bufpos] == '$' and c.buf[c.bufpos+1] in {'0'..'9'}: - case tok.literal - of "i": tok.modifier = modIgnoreCase - of "y": tok.modifier = modIgnoreStyle - of "v": tok.modifier = modVerbatim - else: discard - setLen(tok.literal, 0) - if c.buf[c.bufpos] == '$': - getDollar(c, tok) - else: - getString(c, tok) - if tok.modifier == modNone: tok.kind = tkInvalid - of '+': - tok.kind = tkPlus - inc(c.bufpos) - add(tok.literal, '+') - of '*': - tok.kind = tkStar - inc(c.bufpos) - add(tok.literal, '+') - of '<': - if c.buf[c.bufpos+1] == '-': - inc(c.bufpos, 2) - tok.kind = tkArrow - add(tok.literal, "<-") - else: - add(tok.literal, '<') - of '/': - tok.kind = tkBar - inc(c.bufpos) - add(tok.literal, '/') - of '?': - tok.kind = tkOption - inc(c.bufpos) - add(tok.literal, '?') - of '!': - tok.kind = tkNot - inc(c.bufpos) - add(tok.literal, '!') - of '&': - tok.kind = tkAmp - inc(c.bufpos) - add(tok.literal, '!') - of '@': - tok.kind = tkAt - inc(c.bufpos) - add(tok.literal, '@') - if c.buf[c.bufpos] == '@': - tok.kind = tkCurlyAt - inc(c.bufpos) - add(tok.literal, '@') - of '^': - tok.kind = tkHat - inc(c.bufpos) - add(tok.literal, '^') - else: - add(tok.literal, c.buf[c.bufpos]) - inc(c.bufpos) - -proc arrowIsNextTok(c: PegLexer): bool = - # the only look ahead we need - var pos = c.bufpos - while c.buf[pos] in {'\t', ' '}: inc(pos) - result = c.buf[pos] == '<' and c.buf[pos+1] == '-' - -# ----------------------------- parser ---------------------------------------- - -type - EInvalidPeg* = object of ValueError ## raised if an invalid - ## PEG has been detected - PegParser = object of PegLexer ## the PEG parser object - tok: TToken - nonterms: seq[PNonTerminal] - modifier: TModifier - captures: int - identIsVerbatim: bool - skip: TPeg - -proc pegError(p: PegParser, msg: string, line = -1, col = -1) = - var e: ref EInvalidPeg - new(e) - e.msg = errorStr(p, msg, line, col) - raise e - -proc getTok(p: var PegParser) = - getTok(p, p.tok) - if p.tok.kind == tkInvalid: pegError(p, "invalid token") - -proc eat(p: var PegParser, kind: TTokKind) = - if p.tok.kind == kind: getTok(p) - else: pegError(p, tokKindToStr[kind] & " expected") - -proc parseExpr(p: var PegParser): TPeg - -proc getNonTerminal(p: var PegParser, name: string): PNonTerminal = - for i in 0..high(p.nonterms): - result = p.nonterms[i] - if cmpIgnoreStyle(result.name, name) == 0: return - # forward reference: - result = newNonTerminal(name, getLine(p), getColumn(p)) - add(p.nonterms, result) - -proc modifiedTerm(s: string, m: TModifier): TPeg = - case m - of modNone, modVerbatim: result = term(s) - of modIgnoreCase: result = termIgnoreCase(s) - of modIgnoreStyle: result = termIgnoreStyle(s) - -proc modifiedBackref(s: int, m: TModifier): TPeg = - case m - of modNone, modVerbatim: result = backref(s) - of modIgnoreCase: result = backrefIgnoreCase(s) - of modIgnoreStyle: result = backrefIgnoreStyle(s) - -proc builtin(p: var PegParser): TPeg = - # do not use "y", "skip" or "i" as these would be ambiguous - case p.tok.literal - of "n": result = newLine() - of "d": result = charSet({'0'..'9'}) - of "D": result = charSet({'\1'..'\xff'} - {'0'..'9'}) - of "s": result = charSet({' ', '\9'..'\13'}) - of "S": result = charSet({'\1'..'\xff'} - {' ', '\9'..'\13'}) - of "w": result = charSet({'a'..'z', 'A'..'Z', '_', '0'..'9'}) - of "W": result = charSet({'\1'..'\xff'} - {'a'..'z','A'..'Z','_','0'..'9'}) - of "a": result = charSet({'a'..'z', 'A'..'Z'}) - of "A": result = charSet({'\1'..'\xff'} - {'a'..'z', 'A'..'Z'}) - of "ident": result = pegs.ident - of "letter": result = unicodeLetter() - of "upper": result = unicodeUpper() - of "lower": result = unicodeLower() - of "title": result = unicodeTitle() - of "white": result = unicodeWhitespace() - else: pegError(p, "unknown built-in: " & p.tok.literal) - -proc token(terminal: TPeg, p: PegParser): TPeg = - if p.skip.kind == pkEmpty: result = terminal - else: result = sequence(p.skip, terminal) - -proc primary(p: var PegParser): TPeg = - case p.tok.kind - of tkAmp: - getTok(p) - return &primary(p) - of tkNot: - getTok(p) - return !primary(p) - of tkAt: - getTok(p) - return !*primary(p) - of tkCurlyAt: - getTok(p) - return !*\primary(p).token(p) - else: discard - case p.tok.kind - of tkIdentifier: - if p.identIsVerbatim: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedTerm(p.tok.literal, m).token(p) - getTok(p) - elif not arrowIsNextTok(p): - var nt = getNonTerminal(p, p.tok.literal) - incl(nt.flags, ntUsed) - result = nonterminal(nt).token(p) - getTok(p) - else: - pegError(p, "expression expected, but found: " & p.tok.literal) - of tkStringLit: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedTerm(p.tok.literal, m).token(p) - getTok(p) - of tkCharSet: - if '\0' in p.tok.charset: - pegError(p, "binary zero ('\\0') not allowed in character class") - result = charSet(p.tok.charset).token(p) - getTok(p) - of tkParLe: - getTok(p) - result = parseExpr(p) - eat(p, tkParRi) - of tkCurlyLe: - getTok(p) - result = capture(parseExpr(p)).token(p) - eat(p, tkCurlyRi) - inc(p.captures) - of tkAny: - result = any().token(p) - getTok(p) - of tkAnyRune: - result = anyRune().token(p) - getTok(p) - of tkBuiltin: - result = builtin(p).token(p) - getTok(p) - of tkEscaped: - result = term(p.tok.literal[0]).token(p) - getTok(p) - of tkDollar: - result = endAnchor() - getTok(p) - of tkHat: - result = startAnchor() - getTok(p) - of tkBackref: - var m = p.tok.modifier - if m == modNone: m = p.modifier - result = modifiedBackref(p.tok.index, m).token(p) - if p.tok.index < 0 or p.tok.index > p.captures: - pegError(p, "invalid back reference index: " & $p.tok.index) - getTok(p) - else: - pegError(p, "expression expected, but found: " & p.tok.literal) - getTok(p) # we must consume a token here to prevent endless loops! - while true: - case p.tok.kind - of tkOption: - result = ?result - getTok(p) - of tkStar: - result = *result - getTok(p) - of tkPlus: - result = +result - getTok(p) - else: break - -proc seqExpr(p: var PegParser): TPeg = - result = primary(p) - while true: - case p.tok.kind - of tkAmp, tkNot, tkAt, tkStringLit, tkCharSet, tkParLe, tkCurlyLe, - tkAny, tkAnyRune, tkBuiltin, tkEscaped, tkDollar, tkBackref, - tkHat, tkCurlyAt: - result = sequence(result, primary(p)) - of tkIdentifier: - if not arrowIsNextTok(p): - result = sequence(result, primary(p)) - else: break - else: break - -proc parseExpr(p: var PegParser): TPeg = - result = seqExpr(p) - while p.tok.kind == tkBar: - getTok(p) - result = result / seqExpr(p) - -proc parseRule(p: var PegParser): PNonTerminal = - if p.tok.kind == tkIdentifier and arrowIsNextTok(p): - result = getNonTerminal(p, p.tok.literal) - if ntDeclared in result.flags: - pegError(p, "attempt to redefine: " & result.name) - result.line = getLine(p) - result.col = getColumn(p) - getTok(p) - eat(p, tkArrow) - result.rule = parseExpr(p) - incl(result.flags, ntDeclared) # NOW inlining may be attempted - else: - pegError(p, "rule expected, but found: " & p.tok.literal) - -proc rawParse(p: var PegParser): TPeg = - ## parses a rule or a PEG expression - while p.tok.kind == tkBuiltin: - case p.tok.literal - of "i": - p.modifier = modIgnoreCase - getTok(p) - of "y": - p.modifier = modIgnoreStyle - getTok(p) - of "skip": - getTok(p) - p.skip = ?primary(p) - else: break - if p.tok.kind == tkIdentifier and arrowIsNextTok(p): - result = parseRule(p).rule - while p.tok.kind != tkEof: - discard parseRule(p) - else: - p.identIsVerbatim = true - result = parseExpr(p) - if p.tok.kind != tkEof: - pegError(p, "EOF expected, but found: " & p.tok.literal) - for i in 0..high(p.nonterms): - var nt = p.nonterms[i] - if ntDeclared notin nt.flags: - pegError(p, "undeclared identifier: " & nt.name, nt.line, nt.col) - elif ntUsed notin nt.flags and i > 0: - pegError(p, "unused rule: " & nt.name, nt.line, nt.col) - -proc parsePeg*(pattern: string, filename = "pattern", line = 1, col = 0): TPeg = - ## constructs a Peg object from `pattern`. `filename`, `line`, `col` are - ## used for error messages, but they only provide start offsets. `parsePeg` - ## keeps track of line and column numbers within `pattern`. - var p: PegParser - init(PegLexer(p), pattern, filename, line, col) - p.tok.kind = tkInvalid - p.tok.modifier = modNone - p.tok.literal = "" - p.tok.charset = {} - p.nonterms = @[] - p.identIsVerbatim = false - getTok(p) - result = rawParse(p) - -proc peg*(pattern: string): TPeg = - ## constructs a Peg object from the `pattern`. The short name has been - ## chosen to encourage its use as a raw string modifier:: - ## - ## peg"{\ident} \s* '=' \s* {.*}" - result = parsePeg(pattern, "pattern") - -proc escapePeg*(s: string): string = - ## escapes `s` so that it is matched verbatim when used as a peg. - result = "" - var inQuote = false - for c in items(s): - case c - of '\0'..'\31', '\'', '"', '\\': - if inQuote: - result.add('\'') - inQuote = false - result.add("\\x") - result.add(toHex(ord(c), 2)) - else: - if not inQuote: - result.add('\'') - inQuote = true - result.add(c) - if inQuote: result.add('\'') - -when isMainModule: - assert escapePeg("abc''def'") == r"'abc'\x27\x27'def'\x27" - assert match("(a b c)", peg"'(' @ ')'") - assert match("W_HI_Le", peg"\y 'while'") - assert(not match("W_HI_L", peg"\y 'while'")) - assert(not match("W_HI_Le", peg"\y v'while'")) - assert match("W_HI_Le", peg"y'while'") - - assert($ +digits == $peg"\d+") - assert "0158787".match(peg"\d+") - assert "ABC 0232".match(peg"\w+\s+\d+") - assert "ABC".match(peg"\d+ / \w+") - - for word in split("00232this02939is39an22example111", peg"\d+"): - writeln(stdout, word) - - assert matchLen("key", ident) == 3 - - var pattern = sequence(ident, *whitespace, term('='), *whitespace, ident) - assert matchLen("key1= cal9", pattern) == 11 - - var ws = newNonTerminal("ws", 1, 1) - ws.rule = *whitespace - - var expr = newNonTerminal("expr", 1, 1) - expr.rule = sequence(capture(ident), *sequence( - nonterminal(ws), term('+'), nonterminal(ws), nonterminal(expr))) - - var c: Captures - var s = "a+b + c +d+e+f" - assert rawMatch(s, expr.rule, 0, c) == len(s) - var a = "" - for i in 0..c.ml-1: - a.add(substr(s, c.matches[i][0], c.matches[i][1])) - assert a == "abcdef" - #echo expr.rule - - #const filename = "lib/devel/peg/grammar.txt" - #var grammar = parsePeg(newFileStream(filename, fmRead), filename) - #echo "a <- [abc]*?".match(grammar) - assert find("_____abc_______", term("abc"), 2) == 5 - assert match("_______ana", peg"A <- 'ana' / . A") - assert match("abcs%%%", peg"A <- ..A / .A / '%'") - - var matches: array[0..MaxSubpatterns-1, string] - if "abc" =~ peg"{'a'}'bc' 'xyz' / {\ident}": - assert matches[0] == "abc" - else: - assert false - - var g2 = peg"""S <- A B / C D - A <- 'a'+ - B <- 'b'+ - C <- 'c'+ - D <- 'd'+ - """ - assert($g2 == "((A B) / (C D))") - assert match("cccccdddddd", g2) - assert("var1=key; var2=key2".replacef(peg"{\ident}'='{\ident}", "$1<-$2$2") == - "var1<-keykey; var2<-key2key2") - assert("var1=key; var2=key2".replace(peg"{\ident}'='{\ident}", "$1<-$2$2") == - "$1<-$2$2; $1<-$2$2") - assert "var1=key; var2=key2".endsWith(peg"{\ident}'='{\ident}") - - if "aaaaaa" =~ peg"'aa' !. / ({'a'})+": - assert matches[0] == "a" - else: - assert false - - if match("abcdefg", peg"c {d} ef {g}", matches, 2): - assert matches[0] == "d" - assert matches[1] == "g" - else: - assert false - - for x in findAll("abcdef", peg".", 3): - echo x - - for x in findAll("abcdef", peg"^{.}", 3): - assert x == "d" - - if "f(a, b)" =~ peg"{[0-9]+} / ({\ident} '(' {@} ')')": - assert matches[0] == "f" - assert matches[1] == "a, b" - else: - assert false - - assert match("eine übersicht und außerdem", peg"(\letter \white*)+") - # ß is not a lower cased letter?! - assert match("eine übersicht und auerdem", peg"(\lower \white*)+") - assert match("EINE ÜBERSICHT UND AUSSERDEM", peg"(\upper \white*)+") - assert(not match("456678", peg"(\letter)+")) - - assert("var1 = key; var2 = key2".replacef( - peg"\skip(\s*) {\ident}'='{\ident}", "$1<-$2$2") == - "var1<-keykey;var2<-key2key2") - - assert match("prefix/start", peg"^start$", 7) - diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index 727d5a386..7fdd994f2 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -168,6 +168,12 @@ proc newStringTable*(mode: StringTableMode): StringTableRef {. result.counter = 0 newSeq(result.data, startSize) +proc clear*(s: StringTableRef, mode: StringTableMode) = + ## resets a string table to be empty again. + s.mode = mode + s.counter = 0 + s.data.setLen(startSize) + proc newStringTable*(keyValuePairs: varargs[string], mode: StringTableMode): StringTableRef {. rtl, extern: "nst$1WithPairs".} = @@ -227,7 +233,7 @@ proc `$`*(t: StringTableRef): string {.rtl, extern: "nstDollar".} = result = "{:}" else: result = "{" - for key, val in pairs(t): + for key, val in pairs(t): if result.len > 1: result.add(", ") result.add(key) result.add(": ") diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 1b248126b..59cebf7fa 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1410,7 +1410,8 @@ when isMainModule: doAssert wordWrap(inp, 10, false) == outp doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001" - doAssert formatBiggestFloat(0.00000000001, ffScientific, 1) == "1.0e-11" + doAssert formatBiggestFloat(0.00000000001, ffScientific, 1) in + ["1.0e-11", "1.0e-011"] doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" when not defined(testing): diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index a3c5cdcfb..29f700db5 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -51,12 +51,13 @@ else: proc setRaw(fd: FileHandle, time: cint = TCSAFLUSH) = var mode: Termios discard fd.tcgetattr(addr mode) - mode.iflag = mode.iflag and not Tcflag(BRKINT or ICRNL or INPCK or ISTRIP or IXON) - mode.oflag = mode.oflag and not Tcflag(OPOST) - mode.cflag = (mode.cflag and not Tcflag(CSIZE or PARENB)) or CS8 - mode.lflag = mode.lflag and not Tcflag(ECHO or ICANON or IEXTEN or ISIG) - mode.cc[VMIN] = 1.cuchar - mode.cc[VTIME] = 0.cuchar + mode.c_iflag = mode.c_iflag and not Tcflag(BRKINT or ICRNL or INPCK or + ISTRIP or IXON) + mode.c_oflag = mode.c_oflag and not Tcflag(OPOST) + mode.c_cflag = (mode.c_cflag and not Tcflag(CSIZE or PARENB)) or CS8 + mode.c_lflag = mode.c_lflag and not Tcflag(ECHO or ICANON or IEXTEN or ISIG) + mode.c_cc[VMIN] = 1.cuchar + mode.c_cc[VTIME] = 0.cuchar discard fd.tcsetattr(time, addr mode) proc setCursorPos*(x, y: int) = diff --git a/lib/pure/times.nim b/lib/pure/times.nim index c275ede69..5f8835c6a 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -764,7 +764,7 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = info.monthday = value[j..j+1].parseInt() j += 2 of "ddd": - case value[j..j+2].toLower(): + case value[j..j+2].toLower() of "sun": info.weekday = dSun of "mon": @@ -1068,45 +1068,45 @@ when isMainModule: var f = "dddd at hh:mmtt on MMM d, yyyy" assert($s.parse(f) == "Tue Dec 15 09:04:00 2015") # ANSIC = "Mon Jan _2 15:04:05 2006" - s = "Mon Jan 2 15:04:05 2006" - f = "ddd MMM d HH:mm:ss yyyy" - assert($s.parse(f) == "Mon Jan 2 15:04:05 2006") + s = "Thu Jan 12 15:04:05 2006" + f = "ddd MMM dd HH:mm:ss yyyy" + assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") # UnixDate = "Mon Jan _2 15:04:05 MST 2006" - s = "Mon Jan 2 15:04:05 MST 2006" - f = "ddd MMM d HH:mm:ss ZZZ yyyy" - assert($s.parse(f) == "Mon Jan 2 15:04:05 2006") + s = "Thu Jan 12 15:04:05 MST 2006" + f = "ddd MMM dd HH:mm:ss ZZZ yyyy" + assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") # RubyDate = "Mon Jan 02 15:04:05 -0700 2006" - s = "Mon Jan 02 15:04:05 -07:00 2006" + s = "Thu Jan 12 15:04:05 -07:00 2006" f = "ddd MMM dd HH:mm:ss zzz yyyy" - assert($s.parse(f) == "Mon Jan 2 15:04:05 2006") + assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") # RFC822 = "02 Jan 06 15:04 MST" - s = "02 Jan 06 15:04 MST" + s = "12 Jan 16 15:04 MST" f = "dd MMM yy HH:mm ZZZ" - assert($s.parse(f) == "Mon Jan 2 15:04:00 2006") + assert($s.parse(f) == "Tue Jan 12 15:04:00 2016") # RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone - s = "02 Jan 06 15:04 -07:00" + s = "12 Jan 16 15:04 -07:00" f = "dd MMM yy HH:mm zzz" - assert($s.parse(f) == "Mon Jan 2 15:04:00 2006") + assert($s.parse(f) == "Tue Jan 12 15:04:00 2016") # RFC850 = "Monday, 02-Jan-06 15:04:05 MST" - s = "Monday, 02-Jan-06 15:04:05 MST" + s = "Monday, 12-Jan-06 15:04:05 MST" f = "dddd, dd-MMM-yy HH:mm:ss ZZZ" - assert($s.parse(f) == "Mon Jan 2 15:04:05 2006") + assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") # RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" - s = "Mon, 02 Jan 2006 15:04:05 MST" + s = "Thu, 12 Jan 2006 15:04:05 MST" f = "ddd, dd MMM yyyy HH:mm:ss ZZZ" - assert($s.parse(f) == "Mon Jan 2 15:04:05 2006") + assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") # RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone - s = "Mon, 02 Jan 2006 15:04:05 -07:00" + s = "Thu, 12 Jan 2006 15:04:05 -07:00" f = "ddd, dd MMM yyyy HH:mm:ss zzz" - assert($s.parse(f) == "Mon Jan 2 15:04:05 2006") + assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") # RFC3339 = "2006-01-02T15:04:05Z07:00" - s = "2006-01-02T15:04:05Z-07:00" + s = "2006-01-12T15:04:05Z-07:00" f = "yyyy-MM-ddTHH:mm:ssZzzz" - assert($s.parse(f) == "Mon Jan 2 15:04:05 2006") + assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" - s = "2006-01-02T15:04:05.999999999Z-07:00" + s = "2006-01-12T15:04:05.999999999Z-07:00" f = "yyyy-MM-ddTHH:mm:ss.999999999Zzzz" - assert($s.parse(f) == "Mon Jan 2 15:04:05 2006") + assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") # Kitchen = "3:04PM" s = "3:04PM" f = "h:mmtt" diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 4a9f4631d..5fd3c2418 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -105,6 +105,31 @@ template fastRuneAt*(s: string, i: int, result: expr, doInc = true) = result = Rune(ord(s[i])) when doInc: inc(i) +proc validateUtf8*(s: string): int = + ## returns the position of the invalid byte in ``s`` if the string ``s`` does + ## not hold valid UTF-8 data. Otherwise -1 is returned. + var i = 0 + let L = s.len + while i < L: + if ord(s[i]) <=% 127: + inc(i) + elif ord(s[i]) shr 5 == 0b110: + if i+1 < L and ord(s[i+1]) shr 6 == 0b10: inc(i, 2) + else: return i + elif ord(s[i]) shr 4 == 0b1110: + if i+2 < L and ord(s[i+1]) shr 6 == 0b10 and ord(s[i+2]) shr 6 == 0b10: + inc i, 3 + else: return i + elif ord(s[i]) shr 3 == 0b11110: + if i+3 < L and ord(s[i+1]) shr 6 == 0b10 and + ord(s[i+2]) shr 6 == 0b10 and + ord(s[i+3]) shr 6 == 0b10: + inc i, 4 + else: return i + else: + return i + return -1 + proc runeAt*(s: string, i: Natural): Rune = ## returns the unicode character in `s` at byte index `i` fastRuneAt(s, i, result, false) diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index b0afb75f9..1890a9bf4 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -53,10 +53,10 @@ proc parseAuthority(authority: string, result: var Uri) = while true: case authority[i] of '@': - result.password = result.port - result.port = "" - result.username = result.hostname - result.hostname = "" + swap result.password, result.port + result.port.setLen(0) + swap result.username, result.hostname + result.hostname.setLen(0) inPort = false of ':': inPort = true @@ -75,7 +75,7 @@ proc parsePath(uri: string, i: var int, result: var Uri) = # The 'mailto' scheme's PATH actually contains the hostname/username if result.scheme.toLower == "mailto": parseAuthority(result.path, result) - result.path = "" + result.path.setLen(0) if uri[i] == '?': i.inc # Skip '?' @@ -85,13 +85,21 @@ proc parsePath(uri: string, i: var int, result: var Uri) = i.inc # Skip '#' i.inc parseUntil(uri, result.anchor, {}, i) -proc initUri(): Uri = +proc initUri*(): Uri = + ## Initializes a URI. result = Uri(scheme: "", username: "", password: "", hostname: "", port: "", path: "", query: "", anchor: "") -proc parseUri*(uri: string): Uri = - ## Parses a URI. - result = initUri() +proc resetUri(uri: var Uri) = + for f in uri.fields: + when f is string: + f.setLen(0) + else: + f = false + +proc parseUri*(uri: string, result: var Uri) = + ## Parses a URI. The `result` variable will be cleared before. + resetUri(result) var i = 0 @@ -105,7 +113,7 @@ proc parseUri*(uri: string): Uri = if uri[i] != ':': # Assume this is a reference URI (relative URI) i = 0 - result.scheme = "" + result.scheme.setLen(0) parsePath(uri, i, result) return i.inc # Skip ':' @@ -124,6 +132,11 @@ proc parseUri*(uri: string): Uri = # Path parsePath(uri, i, result) +proc parseUri*(uri: string): Uri = + ## Parses a URI and returns it. + result = initUri() + parseUri(uri, result) + proc removeDotSegments(path: string): string = var collection: seq[string] = @[] let endsWithSlash = path[path.len-1] == '/' diff --git a/lib/system.nim b/lib/system.nim index 85f1350d7..ca4c81411 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1532,10 +1532,10 @@ const NimMajor*: int = 0 ## is the major number of Nim's version. - NimMinor*: int = 10 + NimMinor*: int = 11 ## is the minor number of Nim's version. - NimPatch*: int = 3 + NimPatch*: int = 2 ## is the patch number of Nim's version. NimVersion*: string = $NimMajor & "." & $NimMinor & "." & $NimPatch diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 1b3471978..189d52f57 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -58,7 +58,7 @@ proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} = proc popSafePoint {.compilerRtl, inl.} = excHandler = excHandler.prev -proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} = +proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} = e.parent = currException currException = e @@ -69,12 +69,12 @@ proc popCurrentException {.compilerRtl, inl.} = const nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and not NimStackTrace - hasSomeStackTrace = NimStackTrace or + hasSomeStackTrace = NimStackTrace or defined(nativeStackTrace) and nativeStackTraceSupported when defined(nativeStacktrace) and nativeStackTraceSupported: type - TDl_info {.importc: "Dl_info", header: "<dlfcn.h>", + TDl_info {.importc: "Dl_info", header: "<dlfcn.h>", final, pure.} = object dli_fname: cstring dli_fbase: pointer @@ -98,7 +98,7 @@ when defined(nativeStacktrace) and nativeStackTraceSupported: tempDlInfo: TDl_info # This is allowed to be expensive since it only happens during crashes # (but this way you don't need manual stack tracing) - var size = backtrace(cast[ptr pointer](addr(tempAddresses)), + var size = backtrace(cast[ptr pointer](addr(tempAddresses)), len(tempAddresses)) var enabled = false for i in 0..size-1: @@ -123,7 +123,7 @@ when defined(nativeStacktrace) and nativeStackTraceSupported: when not hasThreadSupport: var tempFrames: array [0..127, PFrame] # should not be alloc'd on stack - + proc auxWriteStackTrace(f: PFrame, s: var string) = when hasThreadSupport: var @@ -160,7 +160,7 @@ proc auxWriteStackTrace(f: PFrame, s: var string) = inc(i) b = b.prev for j in countdown(i-1, 0): - if tempFrames[j] == nil: + if tempFrames[j] == nil: add(s, "(") add(s, $skipped) add(s, " calls omitted) ...") @@ -214,41 +214,49 @@ proc raiseExceptionAux(e: ref Exception) = if not localRaiseHook(e): return if globalRaiseHook != nil: if not globalRaiseHook(e): return - if excHandler != nil: - if not excHandler.hasRaiseAction or excHandler.raiseAction(e): + when defined(cpp): + if e[] of OutOfMemError: + showErrorMessage(e.name) + quitOrDebug() + else: pushCurrentException(e) - c_longjmp(excHandler.context, 1) - elif e[] of OutOfMemError: - showErrorMessage(e.name) - quitOrDebug() + {.emit: "throw NimException(`e`, `e`->name);".} else: - when hasSomeStackTrace: - var buf = newStringOfCap(2000) - if isNil(e.trace): rawWriteStackTrace(buf) - else: add(buf, e.trace) - add(buf, "Error: unhandled exception: ") - if not isNil(e.msg): add(buf, e.msg) - add(buf, " [") - add(buf, $e.name) - add(buf, "]\n") - showErrorMessage(buf) + if excHandler != nil: + if not excHandler.hasRaiseAction or excHandler.raiseAction(e): + pushCurrentException(e) + c_longjmp(excHandler.context, 1) + elif e[] of OutOfMemError: + showErrorMessage(e.name) + quitOrDebug() else: - # ugly, but avoids heap allocations :-) - template xadd(buf, s, slen: expr) = - if L + slen < high(buf): - copyMem(addr(buf[L]), cstring(s), slen) - inc L, slen - template add(buf, s: expr) = - xadd(buf, s, s.len) - var buf: array [0..2000, char] - var L = 0 - add(buf, "Error: unhandled exception: ") - if not isNil(e.msg): add(buf, e.msg) - add(buf, " [") - xadd(buf, e.name, c_strlen(e.name)) - add(buf, "]\n") - showErrorMessage(buf) - quitOrDebug() + when hasSomeStackTrace: + var buf = newStringOfCap(2000) + if isNil(e.trace): rawWriteStackTrace(buf) + else: add(buf, e.trace) + add(buf, "Error: unhandled exception: ") + if not isNil(e.msg): add(buf, e.msg) + add(buf, " [") + add(buf, $e.name) + add(buf, "]\n") + showErrorMessage(buf) + else: + # ugly, but avoids heap allocations :-) + template xadd(buf, s, slen: expr) = + if L + slen < high(buf): + copyMem(addr(buf[L]), cstring(s), slen) + inc L, slen + template add(buf, s: expr) = + xadd(buf, s, s.len) + var buf: array [0..2000, char] + var L = 0 + add(buf, "Error: unhandled exception: ") + if not isNil(e.msg): add(buf, e.msg) + add(buf, " [") + xadd(buf, e.name, c_strlen(e.name)) + add(buf, "]\n") + showErrorMessage(buf) + quitOrDebug() proc raiseException(e: ref Exception, ename: cstring) {.compilerRtl.} = e.name = ename @@ -309,7 +317,7 @@ when not defined(noSignalHandler): proc signalHandler(sig: cint) {.exportc: "signalHandler", noconv.} = template processSignal(s, action: expr) {.immediate, dirty.} = if s == SIGINT: action("SIGINT: Interrupted by Ctrl-C.\n") - elif s == SIGSEGV: + elif s == SIGSEGV: action("SIGSEGV: Illegal storage access. (Attempt to read from nil?)\n") elif s == SIGABRT: when defined(endb): diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 584f7cf48..57b79c7de 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -10,6 +10,8 @@ ## This module implements a small wrapper for some needed Win API procedures, ## so that the Nim compiler does not depend on the huge Windows module. +{.deadCodeElim:on.} + const useWinUnicode* = not defined(useWinAnsi) @@ -29,7 +31,7 @@ type nLength*: int32 lpSecurityDescriptor*: pointer bInheritHandle*: WINBOOL - + TSTARTUPINFO* {.final, pure.} = object cb*: int32 lpReserved*: cstring @@ -59,7 +61,7 @@ type TFILETIME* {.final, pure.} = object ## CANNOT BE int64 BECAUSE OF ALIGNMENT dwLowDateTime*: DWORD dwHighDateTime*: DWORD - + TBY_HANDLE_FILE_INFORMATION* {.final, pure.} = object dwFileAttributes*: DWORD ftCreationTime*: TFILETIME @@ -94,26 +96,26 @@ const STD_ERROR_HANDLE* = -12'i32 DETACHED_PROCESS* = 8'i32 - + SW_SHOWNORMAL* = 1'i32 INVALID_HANDLE_VALUE* = THandle(-1) - + CREATE_UNICODE_ENVIRONMENT* = 1024'i32 proc closeHandle*(hObject: THandle): WINBOOL {.stdcall, dynlib: "kernel32", importc: "CloseHandle".} - + proc readFile*(hFile: THandle, Buffer: pointer, nNumberOfBytesToRead: int32, lpNumberOfBytesRead: ptr int32, lpOverlapped: pointer): WINBOOL{. stdcall, dynlib: "kernel32", importc: "ReadFile".} - + proc writeFile*(hFile: THandle, Buffer: pointer, nNumberOfBytesToWrite: int32, - lpNumberOfBytesWritten: ptr int32, + lpNumberOfBytesWritten: ptr int32, lpOverlapped: pointer): WINBOOL{. stdcall, dynlib: "kernel32", importc: "WriteFile".} proc createPipe*(hReadPipe, hWritePipe: var THandle, - lpPipeAttributes: var TSECURITY_ATTRIBUTES, + lpPipeAttributes: var TSECURITY_ATTRIBUTES, nSize: int32): WINBOOL{. stdcall, dynlib: "kernel32", importc: "CreatePipe".} @@ -159,7 +161,7 @@ proc setStdHandle*(nStdHandle: int32, hHandle: THandle): WINBOOL {.stdcall, proc flushFileBuffers*(hFile: THandle): WINBOOL {.stdcall, dynlib: "kernel32", importc: "FlushFileBuffers".} -proc getLastError*(): int32 {.importc: "GetLastError", +proc getLastError*(): int32 {.importc: "GetLastError", stdcall, dynlib: "kernel32".} when useWinUnicode: @@ -179,7 +181,7 @@ proc localFree*(p: pointer) {. importc: "LocalFree", stdcall, dynlib: "kernel32".} when useWinUnicode: - proc getCurrentDirectoryW*(nBufferLength: int32, + proc getCurrentDirectoryW*(nBufferLength: int32, lpBuffer: WideCString): int32 {. importc: "GetCurrentDirectoryW", dynlib: "kernel32", stdcall.} proc setCurrentDirectoryW*(lpPathName: WideCString): int32 {. @@ -191,8 +193,8 @@ when useWinUnicode: proc setEnvironmentVariableW*(lpName, lpValue: WideCString): int32 {. stdcall, dynlib: "kernel32", importc: "SetEnvironmentVariableW".} - proc getModuleFileNameW*(handle: THandle, buf: WideCString, - size: int32): int32 {.importc: "GetModuleFileNameW", + proc getModuleFileNameW*(handle: THandle, buf: WideCString, + size: int32): int32 {.importc: "GetModuleFileNameW", dynlib: "kernel32", stdcall.} else: proc getCurrentDirectoryA*(nBufferLength: int32, lpBuffer: cstring): int32 {. @@ -269,14 +271,14 @@ proc findClose*(hFindFile: THandle) {.stdcall, dynlib: "kernel32", when useWinUnicode: proc getFullPathNameW*(lpFileName: WideCString, nBufferLength: int32, - lpBuffer: WideCString, + lpBuffer: WideCString, lpFilePart: var WideCString): int32 {. - stdcall, dynlib: "kernel32", + stdcall, dynlib: "kernel32", importc: "GetFullPathNameW".} proc getFileAttributesW*(lpFileName: WideCString): int32 {. - stdcall, dynlib: "kernel32", + stdcall, dynlib: "kernel32", importc: "GetFileAttributesW".} - proc setFileAttributesW*(lpFileName: WideCString, + proc setFileAttributesW*(lpFileName: WideCString, dwFileAttributes: int32): WINBOOL {. stdcall, dynlib: "kernel32", importc: "SetFileAttributesW".} @@ -299,12 +301,12 @@ when useWinUnicode: else: proc getFullPathNameA*(lpFileName: cstring, nBufferLength: int32, lpBuffer: cstring, lpFilePart: var cstring): int32 {. - stdcall, dynlib: "kernel32", + stdcall, dynlib: "kernel32", importc: "GetFullPathNameA".} proc getFileAttributesA*(lpFileName: cstring): int32 {. - stdcall, dynlib: "kernel32", + stdcall, dynlib: "kernel32", importc: "GetFileAttributesA".} - proc setFileAttributesA*(lpFileName: cstring, + proc setFileAttributesA*(lpFileName: cstring, dwFileAttributes: int32): WINBOOL {. stdcall, dynlib: "kernel32", importc: "SetFileAttributesA".} @@ -324,10 +326,10 @@ else: proc getCommandLineA*(): cstring {. importc: "GetCommandLineA", stdcall, dynlib: "kernel32".} -proc rdFileTime*(f: TFILETIME): int64 = +proc rdFileTime*(f: TFILETIME): int64 = result = ze64(f.dwLowDateTime) or (ze64(f.dwHighDateTime) shl 32) -proc rdFileSize*(f: TWIN32_FIND_DATA): int64 = +proc rdFileSize*(f: TWIN32_FIND_DATA): int64 = result = ze64(f.nFileSizeLow) or (ze64(f.nFileSizeHigh) shl 32) proc getSystemTimeAsFileTime*(lpSystemTimeAsFileTime: var TFILETIME) {. @@ -347,7 +349,7 @@ else: lpParameters, lpDirectory: cstring, nShowCmd: int32): THandle{. stdcall, dynlib: "shell32.dll", importc: "ShellExecuteA".} - + proc getFileInformationByHandle*(hFile: THandle, lpFileInformation: ptr TBY_HANDLE_FILE_INFORMATION): WINBOOL{. stdcall, dynlib: "kernel32", importc: "GetFileInformationByHandle".} @@ -357,12 +359,12 @@ const WSASYS_STATUS_LEN* = 128 FD_SETSIZE* = 64 MSG_PEEK* = 2 - + INADDR_ANY* = 0 INADDR_LOOPBACK* = 0x7F000001 INADDR_BROADCAST* = -1 INADDR_NONE* = -1 - + ws2dll = "Ws2_32.dll" WSAEWOULDBLOCK* = 10035 @@ -376,31 +378,31 @@ type {.deprecated: [TSocketHandle: SocketHandle].} type - WSAData* {.importc: "WSADATA", header: "winsock2.h".} = object + WSAData* {.importc: "WSADATA", header: "winsock2.h".} = object wVersion, wHighVersion: int16 szDescription: array[0..WSADESCRIPTION_LEN, char] szSystemStatus: array[0..WSASYS_STATUS_LEN, char] iMaxSockets, iMaxUdpDg: int16 lpVendorInfo: cstring - - SockAddr* {.importc: "SOCKADDR", header: "winsock2.h".} = object + + SockAddr* {.importc: "SOCKADDR", header: "winsock2.h".} = object sa_family*: int16 # unsigned sa_data: array[0..13, char] InAddr* {.importc: "IN_ADDR", header: "winsock2.h".} = object s_addr*: int32 # IP address - - Sockaddr_in* {.importc: "SOCKADDR_IN", + + Sockaddr_in* {.importc: "SOCKADDR_IN", header: "winsock2.h".} = object sin_family*: int16 sin_port*: int16 # unsigned sin_addr*: InAddr sin_zero*: array[0..7, char] - In6_addr* {.importc: "IN6_ADDR", header: "winsock2.h".} = object + In6_addr* {.importc: "IN6_ADDR", header: "winsock2.h".} = object bytes*: array[0..15, char] - Sockaddr_in6* {.importc: "SOCKADDR_IN6", + Sockaddr_in6* {.importc: "SOCKADDR_IN6", header: "winsock2.h".} = object sin6_family*: int16 sin6_port*: int16 # unsigned @@ -430,23 +432,23 @@ type h_addrtype*: int16 h_length*: int16 h_addr_list*: cstringArray - + TFdSet* = object fd_count*: cint # unsigned fd_array*: array[0..FD_SETSIZE-1, SocketHandle] - + Timeval* = object tv_sec*, tv_usec*: int32 - + AddrInfo* = object - ai_flags*: cint ## Input flags. - ai_family*: cint ## Address family of socket. - ai_socktype*: cint ## Socket type. - ai_protocol*: cint ## Protocol of socket. - ai_addrlen*: int ## Length of socket address. + ai_flags*: cint ## Input flags. + ai_family*: cint ## Address family of socket. + ai_socktype*: cint ## Socket type. + ai_protocol*: cint ## Protocol of socket. + ai_addrlen*: int ## Length of socket address. ai_canonname*: cstring ## Canonical name of service location. - ai_addr*: ptr SockAddr ## Socket address of socket. - ai_next*: ptr AddrInfo ## Pointer to next in list. + ai_addr*: ptr SockAddr ## Socket address of socket. + ai_next*: ptr AddrInfo ## Pointer to next in list. SockLen* = cuint @@ -501,7 +503,7 @@ proc bindSocket*(s: SocketHandle, name: ptr SockAddr, namelen: SockLen): cint {. stdcall, importc: "bind", dynlib: ws2dll.} proc connect*(s: SocketHandle, name: ptr SockAddr, namelen: SockLen): cint {. stdcall, importc: "connect", dynlib: ws2dll.} -proc getsockname*(s: SocketHandle, name: ptr SockAddr, +proc getsockname*(s: SocketHandle, name: ptr SockAddr, namelen: ptr SockLen): cint {. stdcall, importc: "getsockname", dynlib: ws2dll.} proc getsockopt*(s: SocketHandle, level, optname: cint, optval: pointer, @@ -515,7 +517,7 @@ proc listen*(s: SocketHandle, backlog: cint): cint {. stdcall, importc: "listen", dynlib: ws2dll.} proc recv*(s: SocketHandle, buf: pointer, len, flags: cint): cint {. stdcall, importc: "recv", dynlib: ws2dll.} -proc recvfrom*(s: SocketHandle, buf: cstring, len, flags: cint, +proc recvfrom*(s: SocketHandle, buf: cstring, len, flags: cint, fromm: ptr SockAddr, fromlen: ptr SockLen): cint {. stdcall, importc: "recvfrom", dynlib: ws2dll.} proc select*(nfds: cint, readfds, writefds, exceptfds: ptr TFdSet, @@ -529,22 +531,22 @@ proc sendto*(s: SocketHandle, buf: pointer, len, flags: cint, proc shutdown*(s: SocketHandle, how: cint): cint {. stdcall, importc: "shutdown", dynlib: ws2dll.} - + proc getnameinfo*(a1: ptr SockAddr, a2: SockLen, a3: cstring, a4: SockLen, a5: cstring, a6: SockLen, a7: cint): cint {. stdcall, importc: "getnameinfo", dynlib: ws2dll.} - + proc inet_addr*(cp: cstring): int32 {. - stdcall, importc: "inet_addr", dynlib: ws2dll.} + stdcall, importc: "inet_addr", dynlib: ws2dll.} proc WSAFDIsSet(s: SocketHandle, set: var TFdSet): bool {. stdcall, importc: "__WSAFDIsSet", dynlib: ws2dll, noSideEffect.} -proc FD_ISSET*(socket: SocketHandle, set: var TFdSet): cint = +proc FD_ISSET*(socket: SocketHandle, set: var TFdSet): cint = result = if WSAFDIsSet(socket, set): 1'i32 else: 0'i32 -proc FD_SET*(socket: SocketHandle, s: var TFdSet) = +proc FD_SET*(socket: SocketHandle, s: var TFdSet) = if s.fd_count < FD_SETSIZE: s.fd_array[int(s.fd_count)] = socket inc(s.fd_count) @@ -575,8 +577,8 @@ type proc waitForMultipleObjects*(nCount: DWORD, lpHandles: PWOHandleArray, bWaitAll: WINBOOL, dwMilliseconds: DWORD): DWORD{. stdcall, dynlib: "kernel32", importc: "WaitForMultipleObjects".} - - + + # for memfiles.nim: const @@ -586,7 +588,7 @@ const FILE_SHARE_READ* = 1'i32 FILE_SHARE_DELETE* = 4'i32 FILE_SHARE_WRITE* = 2'i32 - + CREATE_ALWAYS* = 2'i32 CREATE_NEW* = 1'i32 OPEN_EXISTING* = 3'i32 @@ -628,7 +630,7 @@ proc setEndOfFile*(hFile: THandle): WINBOOL {.stdcall, dynlib: "kernel32", importc: "SetEndOfFile".} proc setFilePointer*(hFile: THandle, lDistanceToMove: LONG, - lpDistanceToMoveHigh: ptr LONG, + lpDistanceToMoveHigh: ptr LONG, dwMoveMethod: DWORD): DWORD {. stdcall, dynlib: "kernel32", importc: "SetFilePointer".} @@ -637,14 +639,14 @@ proc getFileSize*(hFile: THandle, lpFileSizeHigh: ptr DWORD): DWORD{.stdcall, proc mapViewOfFileEx*(hFileMappingObject: THandle, dwDesiredAccess: DWORD, dwFileOffsetHigh, dwFileOffsetLow: DWORD, - dwNumberOfBytesToMap: DWORD, + dwNumberOfBytesToMap: DWORD, lpBaseAddress: pointer): pointer{. stdcall, dynlib: "kernel32", importc: "MapViewOfFileEx".} proc createFileMappingW*(hFile: THandle, lpFileMappingAttributes: pointer, flProtect, dwMaximumSizeHigh: DWORD, - dwMaximumSizeLow: DWORD, + dwMaximumSizeLow: DWORD, lpName: pointer): THandle {. stdcall, dynlib: "kernel32", importc: "CreateFileMappingW".} @@ -702,7 +704,7 @@ proc getOverlappedResult*(hFile: THandle, lpOverlapped: TOVERLAPPED, lpNumberOfBytesTransferred: var DWORD, bWait: WINBOOL): WINBOOL{. stdcall, dynlib: "kernel32", importc: "GetOverlappedResult".} -const +const IOC_OUT* = 0x40000000 IOC_IN* = 0x80000000 IOC_WS2* = 0x08000000 @@ -725,7 +727,7 @@ var proc WSAIoctl*(s: SocketHandle, dwIoControlCode: DWORD, lpvInBuffer: pointer, cbInBuffer: DWORD, lpvOutBuffer: pointer, cbOutBuffer: DWORD, lpcbBytesReturned: PDWORD, lpOverlapped: POVERLAPPED, - lpCompletionRoutine: POVERLAPPED_COMPLETION_ROUTINE): cint + lpCompletionRoutine: POVERLAPPED_COMPLETION_ROUTINE): cint {.stdcall, importc: "WSAIoctl", dynlib: "Ws2_32.dll".} type @@ -746,7 +748,7 @@ proc WSASend*(s: SocketHandle, buf: ptr TWSABuf, bufCount: DWORD, proc get_osfhandle*(fd:FileHandle): THandle {. importc: "_get_osfhandle", header:"<io.h>".} -proc getSystemTimes*(lpIdleTime, lpKernelTime, +proc getSystemTimes*(lpIdleTime, lpKernelTime, lpUserTime: var TFILETIME): WINBOOL {.stdcall, dynlib: "kernel32", importc: "GetSystemTimes".} diff --git a/tests/ccgbugs/tpartialcs.nim b/tests/ccgbugs/tpartialcs.nim new file mode 100644 index 000000000..12ff65c37 --- /dev/null +++ b/tests/ccgbugs/tpartialcs.nim @@ -0,0 +1,20 @@ + +# bug #2551 + +type Tup = tuple + A, a: int + +type Obj = object + A, a: int + +var x: Tup # This works. +var y: Obj # This doesn't. + +# bug #2212 + +proc f() = + let + p = 1.0 + P = 0.25 + 0.5 + +f() diff --git a/tests/collections/tcounttable.nim b/tests/collections/tcounttable.nim new file mode 100644 index 000000000..ebbb1c8e5 --- /dev/null +++ b/tests/collections/tcounttable.nim @@ -0,0 +1,19 @@ +discard """ + output: "And we get here" +""" + +# bug #2625 + +const s_len = 32 + +import tables +var substr_counts: CountTable[string] = initCountTable[string]() +var my_string = "Hello, this is sadly broken for strings over 64 characters. Note that it *does* appear to work for short strings." +for i in 0..(my_string.len - s_len): + let s = my_string[i..i+s_len-1] + substr_counts[s] = 1 + # substr_counts[s] = substr_counts[s] + 1 # Also breaks, + 2 as well, etc. + # substr_counts.inc(s) # This works + #echo "Iteration ", i + +echo "And we get here" diff --git a/tests/cpp/tcppraise.nim b/tests/cpp/tcppraise.nim new file mode 100644 index 000000000..a9ea8e6ce --- /dev/null +++ b/tests/cpp/tcppraise.nim @@ -0,0 +1,17 @@ +discard """ + cmd: "nim cpp $file" + output: '''foo +bar +Need odd and >= 3 digits## +baz''' +""" + +# bug #1888 +echo "foo" +try: + echo "bar" + raise newException(ValueError, "Need odd and >= 3 digits") +# echo "baz" +except ValueError: + echo getCurrentExceptionMsg(), "##" +echo "baz" diff --git a/tests/distinct/tdistinct_consts.nim b/tests/distinct/tdistinct_consts.nim new file mode 100644 index 000000000..4f6ced2d2 --- /dev/null +++ b/tests/distinct/tdistinct_consts.nim @@ -0,0 +1,20 @@ + +# bug #2641 + +type MyChar = distinct char +const c:MyChar = MyChar('a') + +type MyBool = distinct bool +const b:MyBool = MyBool(true) + +type MyBoolSet = distinct set[bool] +const bs:MyBoolSet = MyBoolSet({true}) + +type MyCharSet= distinct set[char] +const cs:MyCharSet = MyCharSet({'a'}) + +type MyBoolSeq = distinct seq[bool] +const bseq:MyBoolSeq = MyBoolSeq(@[true, false]) + +type MyBoolArr = distinct array[3, bool] +const barr:MyBoolArr = MyBoolArr([true, false, true]) diff --git a/tests/exception/texceptionbreak.nim b/tests/exception/texceptionbreak.nim index 76e986787..00dd8ed9f 100644 --- a/tests/exception/texceptionbreak.nim +++ b/tests/exception/texceptionbreak.nim @@ -5,20 +5,20 @@ discard """ # First variety try: - raise newException(EOS, "Problem") -except EOS: + raise newException(OSError, "Problem") +except OSError: for y in [1, 2, 3]: discard try: discard - except EOS: + except OSError: discard echo "1" # Second Variety try: - raise newException(EOS, "Problem") -except EOS: + raise newException(OSError, "Problem") +except OSError: for y in [1, 2, 3]: discard for y in [1, 2, 3]: @@ -28,8 +28,8 @@ echo "2" # Third Variety try: - raise newException(EOS, "Problem") -except EOS: + raise newException(OSError, "Problem") +except OSError: block: break @@ -38,8 +38,8 @@ echo "3" # Fourth Variety block: try: - raise newException(EOS, "Problem") - except EOS: + raise newException(OSError, "Problem") + except OSError: break -echo "4" \ No newline at end of file +echo "4" diff --git a/tests/exception/texcsub.nim b/tests/exception/texcsub.nim index 3dba357f9..02125d2c0 100644 --- a/tests/exception/texcsub.nim +++ b/tests/exception/texcsub.nim @@ -5,12 +5,12 @@ discard """ # Test inheritance for exception matching: try: - raise newException(EOS, "dummy message") -except E_Base: + raise newException(OSError, "dummy message") +except Exception: echo "caught!" -except: +except: echo "wtf!?" - + #OUT caught! diff --git a/tests/exception/tfinally4.nim b/tests/exception/tfinally4.nim index 05c57c4f5..3aa707ff6 100644 --- a/tests/exception/tfinally4.nim +++ b/tests/exception/tfinally4.nim @@ -8,19 +8,19 @@ discard """ var raiseEx = true var returnA = true var returnB = false - -proc main: int = + +proc main: int = try: #A try: #B if raiseEx: - raise newException(EOS, "") + raise newException(OSError, "") return 3 finally: #B echo "B1" if returnB: return 2 echo "B2" - except EOS: #A + except OSError: #A echo "catch" finally: #A echo "A1" diff --git a/tests/exception/tnestedreturn.nim b/tests/exception/tnestedreturn.nim index 591638f0e..1480764f1 100644 --- a/tests/exception/tnestedreturn.nim +++ b/tests/exception/tnestedreturn.nim @@ -7,7 +7,7 @@ discard """ proc test1() = - finally: echo "A" + defer: echo "A" try: raise newException(OSError, "Problem") @@ -19,7 +19,7 @@ test1() proc test2() = - finally: echo "B" + defer: echo "B" try: return diff --git a/tests/exception/tonraise.nim b/tests/exception/tonraise.nim index 1a555dd94..a155f0b8e 100644 --- a/tests/exception/tonraise.nim +++ b/tests/exception/tonraise.nim @@ -4,8 +4,8 @@ success''' """ type - ESomething = object of E_Base - ESomeOtherErr = object of E_Base + ESomething = object of Exception + ESomeOtherErr = object of Exception proc genErrors(s: string) = if s == "error!": @@ -17,14 +17,14 @@ proc foo() = var i = 0 try: inc i - onRaise(proc (e: ref E_Base): bool = + onRaise(proc (e: ref Exception): bool = echo "i: ", i) genErrors("errssor!") except ESomething: echo("ESomething happened") except: echo("Some other error happened") - + # test that raise handler is gone: try: genErrors("error!") diff --git a/tests/macros/treturnsempty.nim b/tests/macros/treturnsempty.nim new file mode 100644 index 000000000..7af26a747 --- /dev/null +++ b/tests/macros/treturnsempty.nim @@ -0,0 +1,12 @@ +discard """ + errormsg: "type mismatch" + line: 11 +""" +# bug #2372 +macro foo(dummy: int): stmt = + discard + +proc takeStr(s: string) = echo s + +takeStr foo(12) + diff --git a/tests/overload/tspec.nim b/tests/overload/tspec.nim index 685df503a..f2002a390 100644 --- a/tests/overload/tspec.nim +++ b/tests/overload/tspec.nim @@ -11,7 +11,10 @@ ref T 123 2 1 -@[123, 2, 1]''' +@[123, 2, 1] +Called! +merge with var +merge no var''' """ # Things that's even in the spec now! @@ -79,3 +82,37 @@ proc takeV[T](a: varargs[T]) = takeV([123, 2, 1]) # takeV's T is "int", not "array of int" echo(@[123, 2, 1]) + +# bug #2600 + +type + FutureBase* = ref object of RootObj ## Untyped future. + + Future*[T] = ref object of FutureBase ## Typed future. + value: T ## Stored value + + FutureVar*[T] = distinct Future[T] + +proc newFuture*[T](): Future[T] = + new(result) + +proc newFutureVar*[T](): FutureVar[T] = + result = FutureVar[T](newFuture[T]()) + +proc mget*[T](future: FutureVar[T]): var T = + Future[T](future).value + +proc reset*[T](future: FutureVar[T]) = + echo "Called!" + +proc merge[T](x: Future[T]) = echo "merge no var" +proc merge[T](x: var Future[T]) = echo "merge with var" + +when true: + var foo = newFutureVar[string]() + foo.mget() = "" + foo.mget.add("Foobar") + foo.reset() + var bar = newFuture[int]() + bar.merge # merge with var + merge(newFuture[int]()) # merge no var diff --git a/tests/sets/tsets.nim b/tests/sets/tsets.nim index e370209ed..646175329 100644 --- a/tests/sets/tsets.nim +++ b/tests/sets/tsets.nim @@ -1,6 +1,7 @@ discard """ file: "tsets.nim" - output: "Ha ein F ist in s!" + output: '''Ha ein F ist in s! +false''' """ # Test the handling of sets @@ -15,30 +16,30 @@ type TAZ = range['a'..'z'] TAZset = set[TAZ] - TTokType* = enum + TTokType* = enum tkInvalid, tkEof, tkSymbol, - tkAddr, tkAnd, tkAs, tkAsm, tkBlock, tkBreak, tkCase, tkCast, tkConst, - tkContinue, tkConverter, tkDiscard, tkDiv, tkElif, tkElse, tkEnd, tkEnum, - tkExcept, tkException, tkFinally, tkFor, tkFrom, tkGeneric, tkIf, tkImplies, - tkImport, tkIn, tkInclude, tkIs, tkIsnot, tkIterator, tkLambda, tkMacro, - tkMethod, tkMod, tkNil, tkNot, tkNotin, tkObject, tkOf, tkOr, tkOut, tkProc, - tkPtr, tkRaise, tkRecord, tkRef, tkReturn, tkShl, tkShr, tkTemplate, tkTry, + tkAddr, tkAnd, tkAs, tkAsm, tkBlock, tkBreak, tkCase, tkCast, tkConst, + tkContinue, tkConverter, tkDiscard, tkDiv, tkElif, tkElse, tkEnd, tkEnum, + tkExcept, tkException, tkFinally, tkFor, tkFrom, tkGeneric, tkIf, tkImplies, + tkImport, tkIn, tkInclude, tkIs, tkIsnot, tkIterator, tkLambda, tkMacro, + tkMethod, tkMod, tkNil, tkNot, tkNotin, tkObject, tkOf, tkOr, tkOut, tkProc, + tkPtr, tkRaise, tkRecord, tkRef, tkReturn, tkShl, tkShr, tkTemplate, tkTry, tkType, tkVar, tkWhen, tkWhere, tkWhile, tkWith, tkWithout, tkXor, tkYield, - tkIntLit, tkInt8Lit, tkInt16Lit, tkInt32Lit, tkInt64Lit, tkFloatLit, - tkFloat32Lit, tkFloat64Lit, tkStrLit, tkRStrLit, tkTripleStrLit, tkCharLit, - tkRCharLit, tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, - tkCurlyRi, tkBracketDotLe, tkBracketDotRi, - tkCurlyDotLe, tkCurlyDotRi, + tkIntLit, tkInt8Lit, tkInt16Lit, tkInt32Lit, tkInt64Lit, tkFloatLit, + tkFloat32Lit, tkFloat64Lit, tkStrLit, tkRStrLit, tkTripleStrLit, tkCharLit, + tkRCharLit, tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, + tkCurlyRi, tkBracketDotLe, tkBracketDotRi, + tkCurlyDotLe, tkCurlyDotRi, tkParDotLe, tkParDotRi, - tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot, tkHat, tkOpr, + tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot, tkHat, tkOpr, tkComment, tkAccent, tkInd, tkSad, tkDed, tkSpaces, tkInfixOpr, tkPrefixOpr, tkPostfixOpr TTokTypeRange = range[tkSymbol..tkDed] TTokTypes* = set[TTokTypeRange] const - toktypes: TTokTypes = {TTokTypeRange(tkSymbol)..pred(tkIntLit), + toktypes: TTokTypes = {TTokTypeRange(tkSymbol)..pred(tkIntLit), tkStrLit..tkTripleStrLit} var @@ -62,3 +63,142 @@ for x in low(TTokTypeRange) .. high(TTokTypeRange): #OUT Ha ein F ist in s! +type + TMsgKind* = enum + errUnknown, errIllFormedAstX, errInternal, errCannotOpenFile, errGenerated, + errXCompilerDoesNotSupportCpp, errStringLiteralExpected, + errIntLiteralExpected, errInvalidCharacterConstant, + errClosingTripleQuoteExpected, errClosingQuoteExpected, + errTabulatorsAreNotAllowed, errInvalidToken, errLineTooLong, + errInvalidNumber, errNumberOutOfRange, errNnotAllowedInCharacter, + errClosingBracketExpected, errMissingFinalQuote, errIdentifierExpected, + errNewlineExpected, + errInvalidModuleName, + errOperatorExpected, errTokenExpected, errStringAfterIncludeExpected, + errRecursiveDependencyX, errOnOrOffExpected, errNoneSpeedOrSizeExpected, + errInvalidPragma, errUnknownPragma, errInvalidDirectiveX, + errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation, + errExceptionExpected, errExceptionAlreadyHandled, + errYieldNotAllowedHere, errYieldNotAllowedInTryStmt, + errInvalidNumberOfYieldExpr, errCannotReturnExpr, errAttemptToRedefine, + errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel, + errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected, + errInvalidVarSubstitution, errUnknownVar, errUnknownCcompiler, + errOnOrOffExpectedButXFound, errNoneBoehmRefcExpectedButXFound, + errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound, + errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound, + errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected, + errExprExpected, errUndeclaredIdentifier, errUseQualifier, errTypeExpected, + errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable, + errInvalidArgForX, errStmtHasNoEffect, errXExpectsTypeOrValue, + errXExpectsArrayType, errIteratorCannotBeInstantiated, errExprXAmbiguous, + errConstantDivisionByZero, errOrdinalTypeExpected, + errOrdinalOrFloatTypeExpected, errOverOrUnderflow, + errCannotEvalXBecauseIncompletelyDefined, errChrExpectsRange0_255, + errDynlibRequiresExportc, errUndeclaredFieldX, errNilAccess, + errIndexOutOfBounds, errIndexTypesDoNotMatch, errBracketsInvalidForType, + errValueOutOfSetBounds, errFieldInitTwice, errFieldNotInit, + errExprXCannotBeCalled, errExprHasNoType, errExprXHasNoType, + errCastNotInSafeMode, errExprCannotBeCastedToX, errCommaOrParRiExpected, + errCurlyLeOrParLeExpected, errSectionExpected, errRangeExpected, + errMagicOnlyInSystem, errPowerOfTwoExpected, + errStringMayNotBeEmpty, errCallConvExpected, errProcOnlyOneCallConv, + errSymbolMustBeImported, errExprMustBeBool, errConstExprExpected, + errDuplicateCaseLabel, errRangeIsEmpty, errSelectorMustBeOfCertainTypes, + errSelectorMustBeOrdinal, errOrdXMustNotBeNegative, errLenXinvalid, + errWrongNumberOfVariables, errExprCannotBeRaised, errBreakOnlyInLoop, + errTypeXhasUnknownSize, errConstNeedsConstExpr, errConstNeedsValue, + errResultCannotBeOpenArray, errSizeTooBig, errSetTooBig, + errBaseTypeMustBeOrdinal, errInheritanceOnlyWithNonFinalObjects, + errInheritanceOnlyWithEnums, errIllegalRecursionInTypeX, + errCannotInstantiateX, errExprHasNoAddress, errXStackEscape, + errVarForOutParamNeeded, + errPureTypeMismatch, errTypeMismatch, errButExpected, errButExpectedX, + errAmbiguousCallXYZ, errWrongNumberOfArguments, + errXCannotBePassedToProcVar, + errXCannotBeInParamDecl, errPragmaOnlyInHeaderOfProc, errImplOfXNotAllowed, + errImplOfXexpected, errNoSymbolToBorrowFromFound, errDiscardValueX, + errInvalidDiscard, errIllegalConvFromXtoY, errCannotBindXTwice, + errInvalidOrderInArrayConstructor, + errInvalidOrderInEnumX, errEnumXHasHoles, errExceptExpected, errInvalidTry, + errOptionExpected, errXisNoLabel, errNotAllCasesCovered, + errUnknownSubstitionVar, errComplexStmtRequiresInd, errXisNotCallable, + errNoPragmasAllowedForX, errNoGenericParamsAllowedForX, + errInvalidParamKindX, errDefaultArgumentInvalid, errNamedParamHasToBeIdent, + errNoReturnTypeForX, errConvNeedsOneArg, errInvalidPragmaX, + errXNotAllowedHere, errInvalidControlFlowX, + errXisNoType, errCircumNeedsPointer, errInvalidExpression, + errInvalidExpressionX, errEnumHasNoValueX, errNamedExprExpected, + errNamedExprNotAllowed, errXExpectsOneTypeParam, + errArrayExpectsTwoTypeParams, errInvalidVisibilityX, errInitHereNotAllowed, + errXCannotBeAssignedTo, errIteratorNotAllowed, errXNeedsReturnType, + errNoReturnTypeDeclared, + errInvalidCommandX, errXOnlyAtModuleScope, + errXNeedsParamObjectType, + errTemplateInstantiationTooNested, errInstantiationFrom, + errInvalidIndexValueForTuple, errCommandExpectsFilename, + errMainModuleMustBeSpecified, + errXExpected, + errTIsNotAConcreteType, + errInvalidSectionStart, errGridTableNotImplemented, errGeneralParseError, + errNewSectionExpected, errWhitespaceExpected, errXisNoValidIndexFile, + errCannotRenderX, errVarVarTypeNotAllowed, errInstantiateXExplicitly, + errOnlyACallOpCanBeDelegator, errUsingNoSymbol, + errMacroBodyDependsOnGenericTypes, + errDestructorNotGenericEnough, + errInlineIteratorsAsProcParams, + errXExpectsTwoArguments, + errXExpectsObjectTypes, errXcanNeverBeOfThisSubtype, errTooManyIterations, + errCannotInterpretNodeX, errFieldXNotFound, errInvalidConversionFromTypeX, + errAssertionFailed, errCannotGenerateCodeForX, errXRequiresOneArgument, + errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate, + errXhasSideEffects, errIteratorExpected, errLetNeedsInit, + errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, + errXCannotBeClosure, errXMustBeCompileTime, + errCannotInferTypeOfTheLiteral, + errCannotInferReturnType, + errGenericLambdaNotAllowed, + errCompilerDoesntSupportTarget, + errUser, + warnCannotOpenFile, + warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, + warnDeprecated, warnConfigDeprecated, + warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel, + warnUnknownSubstitutionX, warnLanguageXNotSupported, + warnFieldXNotSupported, warnCommentXIgnored, + warnNilStatement, warnTypelessParam, + warnDifferentHeaps, warnWriteToForeignHeap, warnUnsafeCode, + warnEachIdentIsTuple, warnShadowIdent, + warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2, + warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed, + warnUser, + hintSuccess, hintSuccessX, + hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, + hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, + hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath, + hintConditionAlwaysTrue, hintName, hintPattern, + hintUser + +const + fatalMin* = errUnknown + fatalMax* = errInternal + errMin* = errUnknown + errMax* = errUser + warnMin* = warnCannotOpenFile + warnMax* = pred(hintSuccess) + hintMin* = hintSuccess + hintMax* = high(TMsgKind) + +type + TNoteKind* = range[warnMin..hintMax] # "notes" are warnings or hints + TNoteKinds* = set[TNoteKind] + +var + gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - + {warnShadowIdent, warnUninit, + warnProveField, warnProveIndex, warnGcUnsafe} + + +#import compiler.msgs + +echo warnUninit in gNotes diff --git a/tests/stdlib/tdialogs.nim b/tests/stdlib/tdialogs.nim deleted file mode 100644 index f0203d319..000000000 --- a/tests/stdlib/tdialogs.nim +++ /dev/null @@ -1,17 +0,0 @@ -# Test the dialogs module - -import dialogs, gtk2 - -gtk2.nimrod_init() - -var x = chooseFilesToOpen(nil) -for a in items(x): - writeln(stdout, a) - -info(nil, "start with an info box") -warning(nil, "now a warning ...") -error(nil, "... and an error!") - -writeln(stdout, chooseFileToOpen(nil)) -writeln(stdout, chooseFileToSave(nil)) -writeln(stdout, chooseDir(nil)) diff --git a/tests/template/tdefault_nil.nim b/tests/template/tdefault_nil.nim new file mode 100644 index 000000000..891166306 --- /dev/null +++ b/tests/template/tdefault_nil.nim @@ -0,0 +1,14 @@ + +# bug #2629 +import sequtils, os + +template glob_rst(basedir: string = nil): expr = + if baseDir.isNil: + to_seq(walk_files("*.rst")) + else: + to_seq(walk_files(basedir/"*.rst")) + +let + rst_files = concat(glob_rst(), glob_rst("docs")) + +when isMainModule: echo rst_files diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index 4476fccf2..07a2421a6 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -226,7 +226,7 @@ proc testStdlib(r: var TResults, pattern, options: string, cat: Category) = for test in os.walkFiles(pattern): let contents = readFile(test).string if contents.contains("when isMainModule"): - testSpec r, makeTest(test, options, cat, actionRun) + testSpec r, makeTest(test, options, cat, actionRunNoSpec) else: testNoSpec r, makeTest(test, options, cat, actionCompile) diff --git a/tests/testament/specs.nim b/tests/testament/specs.nim index 8bf1a4ad7..9306bf025 100644 --- a/tests/testament/specs.nim +++ b/tests/testament/specs.nim @@ -10,13 +10,14 @@ import parseutils, strutils, os, osproc, streams, parsecfg const - cmdTemplate* = r"nim $target --hints:on $options $file" + cmdTemplate* = r"nim $target --hints:on -d:testing $options $file" type TTestAction* = enum actionCompile = "compile" actionRun = "run" actionReject = "reject" + actionRunNoSpec = "runNoSpec" TResultEnum* = enum reNimcCrash, # nim compiler seems to have crashed reMsgsDiffer, # error messages differ @@ -78,7 +79,7 @@ proc extractSpec(filename: string): string = when not defined(nimhygiene): {.pragma: inject.} -template parseSpecAux(fillResult: stmt) {.immediate.} = +template parseSpecAux(fillResult: untyped) = var ss = newStringStream(extractSpec(filename)) var p {.inject.}: CfgParser open(p, ss, filename, 1) @@ -92,8 +93,7 @@ template parseSpecAux(fillResult: stmt) {.immediate.} = fillResult close(p) -proc parseSpec*(filename: string): TSpec = - result.file = filename +proc specDefaults*(result: var TSpec) = result.msg = "" result.outp = "" result.nimout = "" @@ -101,6 +101,10 @@ proc parseSpec*(filename: string): TSpec = result.cmd = cmdTemplate result.line = 0 result.column = 0 + +proc parseSpec*(filename: string): TSpec = + specDefaults(result) + result.file = filename parseSpecAux: case normalize(e.key) of "action": @@ -112,7 +116,7 @@ proc parseSpec*(filename: string): TSpec = of "file": result.file = e.value of "line": discard parseInt(e.value, result.line) of "column": discard parseInt(e.value, result.column) - of "output": + of "output": result.action = actionRun result.outp = e.value of "outputsub": @@ -121,7 +125,7 @@ proc parseSpec*(filename: string): TSpec = result.substr = true of "sortoutput": result.sortoutput = parseCfgBool(e.value) - of "exitcode": + of "exitcode": discard parseInt(e.value, result.exitCode) of "msg": result.msg = e.value diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index ed39109ad..b3e65959a 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -196,7 +196,12 @@ proc testSpec(r: var TResults, test: TTest) = let tname = test.name.addFileExt(".nim") inc(r.total) styledEcho "Processing ", fgCyan, extractFilename(tname) - var expected = parseSpec(tname) + var expected: TSpec + if test.action != actionRunNoSpec: + expected = parseSpec(tname) + else: + specDefaults expected + expected.action = actionRunNoSpec if expected.err == reIgnored: r.addResult(test, "", "", reIgnored) inc(r.skipped) @@ -206,7 +211,7 @@ proc testSpec(r: var TResults, test: TTest) = var given = callCompiler(expected.cmd, test.name, test.options & " --hint[Path]:off --hint[Processing]:off", test.target) compilerOutputTests(test, given, expected, r) - of actionRun: + of actionRun, actionRunNoSpec: var given = callCompiler(expected.cmd, test.name, test.options, test.target) if given.err != reSuccess: diff --git a/tests/tuples/tuple_with_nil.nim b/tests/tuples/tuple_with_nil.nim new file mode 100644 index 000000000..26e4ae85e --- /dev/null +++ b/tests/tuples/tuple_with_nil.nim @@ -0,0 +1,766 @@ +import macros +from strutils import IdentStartChars +import parseutils +import unicode +import math +import fenv +import unsigned +import pegs +import streams + +type + FormatError = object of Exception ## Error in the format string. + + Writer = concept W + ## Writer to output a character `c`. + when (NimMajor, NimMinor, NimPatch) > (0, 10, 2): + write(W, 'c') + else: + block: + var x: W + write(x, char) + + FmtAlign = enum ## Format alignment + faDefault ## default for given format type + faLeft ## left aligned + faRight ## right aligned + faCenter ## centered + faPadding ## right aligned, fill characters after sign (numbers only) + + FmtSign = enum ## Format sign + fsMinus ## only unary minus, no reservered sign space for positive numbers + fsPlus ## unary minus and unary plus + fsSpace ## unary minus and reserved space for positive numbers + + FmtType = enum ## Format type + ftDefault ## default format for given parameter type + ftStr ## string + ftChar ## character + ftDec ## decimal integer + ftBin ## binary integer + ftOct ## octal integer + ftHex ## hexadecimal integer + ftFix ## real number in fixed point notation + ftSci ## real number in scientific notation + ftGen ## real number in generic form (either fixed point or scientific) + ftPercent ## real number multiplied by 100 and % added + + Format = tuple ## Formatting information. + typ: FmtType ## format type + precision: int ## floating point precision + width: int ## minimal width + fill: string ## the fill character, UTF8 + align: FmtAlign ## aligment + sign: FmtSign ## sign notation + baseprefix: bool ## whether binary, octal, hex should be prefixed by 0b, 0x, 0o + upcase: bool ## upper case letters in hex or exponential formats + comma: bool ## + arysep: string ## separator for array elements + + PartKind = enum pkStr, pkFmt + + Part = object + ## Information of a part of the target string. + case kind: PartKind ## type of the part + of pkStr: + str: string ## literal string + of pkFmt: + arg: int ## position argument + fmt: string ## format string + field: string ## field of argument to be accessed + index: int ## array index of argument to be accessed + nested: bool ## true if the argument contains nested formats + +const + DefaultPrec = 6 ## Default precision for floating point numbers. + DefaultFmt: Format = (ftDefault, -1, -1, nil, faDefault, fsMinus, false, false, false, nil) + ## Default format corresponding to the empty format string, i.e. + ## `x.format("") == x.format(DefaultFmt)`. + round_nums = [0.5, 0.05, 0.005, 0.0005, 0.00005, 0.000005, 0.0000005, 0.00000005] + ## Rounding offset for floating point numbers up to precision 8. + +proc write(s: var string; c: char) = + s.add(c) + +proc has(c: Captures; i: range[0..pegs.MaxSubpatterns-1]): bool {.nosideeffect, inline.} = + ## Tests whether `c` contains a non-empty capture `i`. + let b = c.bounds(i) + result = b.first <= b.last + +proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: char): char {.nosideeffect, inline.} = + ## If capture `i` is non-empty return that portion of `str` casted + ## to `char`, otherwise return `def`. + result = if c.has(i): str[c.bounds(i).first] else: def + +proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: string; begoff: int = 0): string {.nosideeffect, inline.} = + ## If capture `i` is non-empty return that portion of `str` as + ## string, otherwise return `def`. + let b = c.bounds(i) + result = if c.has(i): str.substr(b.first + begoff, b.last) else: def + +proc get(str: string; c: Captures; i: range[0..MaxSubpatterns-1]; def: int; begoff: int = 0): int {.nosideeffect, inline.} = + ## If capture `i` is non-empty return that portion of `str` + ## converted to int, otherwise return `def`. + if c.has(i): + discard str.parseInt(result, c.bounds(i).first + begoff) + else: + result = def + +proc parse(fmt: string): Format {.nosideeffect.} = + # Converts the format string `fmt` into a `Format` structure. + let p = + sequence(capture(?sequence(anyRune(), &charSet({'<', '>', '=', '^'}))), + capture(?charSet({'<', '>', '=', '^'})), + capture(?charSet({'-', '+', ' '})), + capture(?charSet({'#'})), + capture(?(+digits())), + capture(?charSet({','})), + capture(?sequence(charSet({'.'}), +digits())), + capture(?charSet({'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 's', 'x', 'X', '%'})), + capture(?sequence(charSet({'a'}), *pegs.any()))) + # let p=peg"{(_&[<>=^])?}{[<>=^]?}{[-+ ]?}{[#]?}{[0-9]+?}{[,]?}{([.][0-9]+)?}{[bcdeEfFgGnosxX%]?}{(a.*)?}" + + var caps: Captures + if fmt.rawmatch(p, 0, caps) < 0: + raise newException(FormatError, "Invalid format string") + + result.fill = fmt.get(caps, 0, nil) + + case fmt.get(caps, 1, 0.char) + of '<': result.align = faLeft + of '>': result.align = faRight + of '^': result.align = faCenter + of '=': result.align = faPadding + else: result.align = faDefault + + case fmt.get(caps, 2, '-') + of '-': result.sign = fsMinus + of '+': result.sign = fsPlus + of ' ': result.sign = fsSpace + else: result.sign = fsMinus + + result.baseprefix = caps.has(3) + + result.width = fmt.get(caps, 4, -1) + + if caps.has(4) and fmt[caps.bounds(4).first] == '0': + if result.fill != nil: + raise newException(FormatError, "Leading 0 in with not allowed with explicit fill character") + if result.align != faDefault: + raise newException(FormatError, "Leading 0 in with not allowed with explicit alignment") + result.fill = "0" + result.align = faPadding + + result.comma = caps.has(5) + + result.precision = fmt.get(caps, 6, -1, 1) + + case fmt.get(caps, 7, 0.char) + of 's': result.typ = ftStr + of 'c': result.typ = ftChar + of 'd', 'n': result.typ = ftDec + of 'b': result.typ = ftBin + of 'o': result.typ = ftOct + of 'x': result.typ = ftHex + of 'X': result.typ = ftHex; result.upcase = true + of 'f', 'F': result.typ = ftFix + of 'e': result.typ = ftSci + of 'E': result.typ = ftSci; result.upcase = true + of 'g': result.typ = ftGen + of 'G': result.typ = ftGen; result.upcase = true + of '%': result.typ = ftPercent + else: result.typ = ftDefault + + result.arysep = fmt.get(caps, 8, nil, 1) + +proc getalign(fmt: Format; defalign: FmtAlign; slen: int) : tuple[left, right:int] {.nosideeffect.} = + ## Returns the number of left and right padding characters for a + ## given format alignment and width of the object to be printed. + ## + ## `fmt` + ## the format data + ## `default` + ## if `fmt.align == faDefault`, then this alignment is used + ## `slen` + ## the width of the object to be printed. + ## + ## The returned values `(left, right)` will be as minimal as possible + ## so that `left + slen + right >= fmt.width`. + result.left = 0 + result.right = 0 + if (fmt.width >= 0) and (slen < fmt.width): + let alg = if fmt.align == faDefault: defalign else: fmt.align + case alg: + of faLeft: result.right = fmt.width - slen + of faRight, faPadding: result.left = fmt.width - slen + of faCenter: + result.left = (fmt.width - slen) div 2 + result.right = fmt.width - slen - result.left + else: discard + +proc writefill(o: var Writer; fmt: Format; n: int; signum: int = 0) = + ## Write characters for filling. This function also writes the sign + ## of a numeric format and handles the padding alignment + ## accordingly. + ## + ## `o` + ## output object + ## `add` + ## output function + ## `fmt` + ## format to be used (important for padding aligment) + ## `n` + ## the number of filling characters to be written + ## `signum` + ## the sign of the number to be written, < 0 negative, > 0 positive, = 0 zero + if fmt.align == faPadding and signum != 0: + if signum < 0: write(o, '-') + elif fmt.sign == fsPlus: write(o, '+') + elif fmt.sign == fsSpace: write(o, ' ') + + if fmt.fill == nil: + for i in 1..n: write(o, ' ') + else: + for i in 1..n: + for c in fmt.fill: + write(o, c) + + if fmt.align != faPadding and signum != 0: + if signum < 0: write(o, '-') + elif fmt.sign == fsPlus: write(o, '+') + elif fmt.sign == fsSpace: write(o, ' ') + +proc writeformat(o: var Writer; s: string; fmt: Format) = + ## Write string `s` according to format `fmt` using output object + ## `o` and output function `add`. + if fmt.typ notin {ftDefault, ftStr}: + raise newException(FormatError, "String variable must have 's' format type") + + # compute alignment + let len = if fmt.precision < 0: runelen(s) else: min(runelen(s), fmt.precision) + var alg = getalign(fmt, faLeft, len) + writefill(o, fmt, alg.left) + var pos = 0 + for i in 0..len-1: + let rlen = runeLenAt(s, pos) + for j in pos..pos+rlen-1: write(o, s[j]) + pos += rlen + writefill(o, fmt, alg.right) + +proc writeformat(o: var Writer; c: char; fmt: Format) = + ## Write character `c` according to format `fmt` using output object + ## `o` and output function `add`. + if not (fmt.typ in {ftChar, ftDefault}): + raise newException(FormatError, "Character variable must have 'c' format type") + + # compute alignment + var alg = getalign(fmt, faLeft, 1) + writefill(o, fmt, alg.left) + write(o, c) + writefill(o, fmt, alg.right) + +proc writeformat(o: var Writer; c: Rune; fmt: Format) = + ## Write rune `c` according to format `fmt` using output object + ## `o` and output function `add`. + if not (fmt.typ in {ftChar, ftDefault}): + raise newException(FormatError, "Character variable must have 'c' format type") + + # compute alignment + var alg = getalign(fmt, faLeft, 1) + writefill(o, fmt, alg.left) + let s = c.toUTF8 + for c in s: write(o, c) + writefill(o, fmt, alg.right) + +proc abs(x: SomeUnsignedInt): SomeUnsignedInt {.inline.} = x + ## Return the absolute value of the unsigned int `x`. + +proc writeformat(o: var Writer; i: SomeInteger; fmt: Format) = + ## Write integer `i` according to format `fmt` using output object + ## `o` and output function `add`. + var fmt = fmt + if fmt.typ == ftDefault: + fmt.typ = ftDec + if not (fmt.typ in {ftBin, ftOct, ftHex, ftDec}): + raise newException(FormatError, "Integer variable must of one of the following types: b,o,x,X,d,n") + + var base: type(i) + var len = 0 + case fmt.typ: + of ftDec: + base = 10 + of ftBin: + base = 2 + if fmt.baseprefix: len += 2 + of ftOct: + base = 8 + if fmt.baseprefix: len += 2 + of ftHex: + base = 16 + if fmt.baseprefix: len += 2 + else: assert(false) + + if fmt.sign != fsMinus or i < 0: len.inc + + var x: type(i) = abs(i) + var irev: type(i) = 0 + var ilen = 0 + while x > 0.SomeInteger: + len.inc + ilen.inc + irev = irev * base + x mod base + x = x div base + if ilen == 0: + ilen.inc + len.inc + + var alg = getalign(fmt, faRight, len) + writefill(o, fmt, alg.left, if i >= 0.SomeInteger: 1 else: -1) + if fmt.baseprefix: + case fmt.typ + of ftBin: + write(o, '0') + write(o, 'b') + of ftOct: + write(o, '0') + write(o, 'o') + of ftHex: + write(o, '0') + write(o, 'x') + else: + raise newException(FormatError, "# only allowed with b, o, x or X") + while ilen > 0: + ilen.dec + let c = irev mod base + irev = irev div base + if c < 10: + write(o, ('0'.int + c.int).char) + elif fmt.upcase: + write(o, ('A'.int + c.int - 10).char) + else: + write(o, ('a'.int + c.int - 10).char) + writefill(o, fmt, alg.right) + +proc writeformat(o: var Writer; p: pointer; fmt: Format) = + ## Write pointer `i` according to format `fmt` using output object + ## `o` and output function `add`. + ## + ## Pointers are casted to unsigned int and formated as hexadecimal + ## with prefix unless specified otherwise. + var f = fmt + if f.typ == 0.char: + f.typ = 'x' + f.baseprefix = true + writeformat(o, add, cast[uint](p), f) + +proc writeformat(o: var Writer; x: SomeReal; fmt: Format) = + ## Write real number `x` according to format `fmt` using output + ## object `o` and output function `add`. + var fmt = fmt + # handle default format + if fmt.typ == ftDefault: + fmt.typ = ftGen + if fmt.precision < 0: fmt.precision = DefaultPrec + if not (fmt.typ in {ftFix, ftSci, ftGen, ftPercent}): + raise newException(FormatError, "Integer variable must of one of the following types: f,F,e,E,g,G,%") + + let positive = x >= 0 and classify(x) != fcNegZero + var len = 0 + + if fmt.sign != fsMinus or not positive: len.inc + + var prec = if fmt.precision < 0: DefaultPrec else: fmt.precision + var y = abs(x) + var exp = 0 + var numstr, frstr: array[0..31, char] + var numlen, frbeg, frlen = 0 + + if fmt.typ == ftPercent: y *= 100 + + case classify(x): + of fcNan: + numstr[0..2] = ['n', 'a', 'n'] + numlen = 3 + of fcInf, fcNegInf: + numstr[0..2] = ['f', 'n', 'i'] + numlen = 3 + of fcZero, fcNegZero: + numstr[0] = '0' + numlen = 1 + else: # a usual fractional number + if not (fmt.typ in {ftFix, ftPercent}): # not fixed point + exp = int(floor(log10(y))) + if fmt.typ == ftGen: + if prec == 0: prec = 1 + if -4 <= exp and exp < prec: + prec = prec-1-exp + exp = 0 + else: + prec = prec - 1 + len += 4 # exponent + else: + len += 4 # exponent + # shift y so that 1 <= abs(y) < 2 + if exp > 0: y /= pow(10.SomeReal, abs(exp).SomeReal) + elif exp < 0: y *= pow(10.SomeReal, abs(exp).SomeReal) + elif fmt.typ == ftPercent: + len += 1 # percent sign + + # handle rounding by adding +0.5 * LSB + if prec < len(round_nums): y += round_nums[prec] + + # split into integer and fractional part + var mult = 1'i64 + for i in 1..prec: mult *= 10 + var num = y.int64 + var fr = ((y - num.SomeReal) * mult.SomeReal).int64 + # build integer part string + while num != 0: + numstr[numlen] = ('0'.int + (num mod 10)).char + numlen.inc + num = num div 10 + if numlen == 0: + numstr[0] = '0' + numlen.inc + # build fractional part string + while fr != 0: + frstr[frlen] = ('0'.int + (fr mod 10)).char + frlen.inc + fr = fr div 10 + while frlen < prec: + frstr[frlen] = '0' + frlen.inc + # possible remove trailing 0 + if fmt.typ == ftGen: + while frbeg < frlen and frstr[frbeg] == '0': frbeg.inc + # update length of string + len += numlen; + if frbeg < frlen: + len += 1 + frlen - frbeg # decimal point and fractional string + + let alg = getalign(fmt, faRight, len) + writefill(o, fmt, alg.left, if positive: 1 else: -1) + for i in (numlen-1).countdown(0): write(o, numstr[i]) + if frbeg < frlen: + write(o, '.') + for i in (frlen-1).countdown(frbeg): write(o, frstr[i]) + if fmt.typ == ftSci or (fmt.typ == ftGen and exp != 0): + write(o, if fmt.upcase: 'E' else: 'e') + if exp >= 0: + write(o, '+') + else: + write(o, '-') + exp = -exp + if exp < 10: + write(o, '0') + write(o, ('0'.int + exp).char) + else: + var i=0 + while exp > 0: + numstr[i] = ('0'.int + exp mod 10).char + i+=1 + exp = exp div 10 + while i>0: + i-=1 + write(o, numstr[i]) + if fmt.typ == ftPercent: write(o, '%') + writefill(o, fmt, alg.right) + +proc writeformat(o: var Writer; b: bool; fmt: Format) = + ## Write boolean value `b` according to format `fmt` using output + ## object `o`. A boolean may be formatted numerically or as string. + ## In the former case true is written as 1 and false as 0, in the + ## latter the strings "true" and "false" are shown, respectively. + ## The default is string format. + if fmt.typ in {ftStr, ftDefault}: + writeformat(o, + if b: "true" + else: "false", + fmt) + elif fmt.typ in {ftBin, ftOct, ftHex, ftDec}: + writeformat(o, + if b: 1 + else: 0, + fmt) + else: + raise newException(FormatError, "Boolean values must of one of the following types: s,b,o,x,X,d,n") + +proc writeformat(o: var Writer; ary: openarray[any]; fmt: Format) = + ## Write array `ary` according to format `fmt` using output object + ## `o` and output function `add`. + if ary.len == 0: return + + var sep: string + var nxtfmt = fmt + if fmt.arysep == nil: + sep = "\t" + elif fmt.arysep.len == 0: + sep = "" + else: + let sepch = fmt.arysep[0] + let nxt = 1 + skipUntil(fmt.arysep, sepch, 1) + if nxt >= 1: + nxtfmt.arysep = fmt.arysep.substr(nxt) + sep = fmt.arysep.substr(1, nxt-1) + else: + nxtfmt.arysep = "" + sep = fmt.arysep.substr(1) + writeformat(o, ary[0], nxtfmt) + for i in 1..ary.len-1: + for c in sep: write(o, c) + writeformat(o, ary[i], nxtfmt) + +proc addformat[T](o: var Writer; x: T; fmt: Format = DefaultFmt) {.inline.} = + ## Write `x` formatted with `fmt` to `o`. + writeformat(o, x, fmt) + +proc addformat[T](o: var Writer; x: T; fmt: string) {.inline.} = + ## The same as `addformat(o, x, parse(fmt))`. + addformat(o, x, fmt.parse) + +proc addformat(s: var string; x: string) {.inline.} = + ## Write `x` to `s`. This is a fast specialized version for + ## appending unformatted strings. + add(s, x) + +proc addformat(f: File; x: string) {.inline.} = + ## Write `x` to `f`. This is a fast specialized version for + ## writing unformatted strings to a file. + write(f, x) + +proc addformat[T](f: File; x: T; fmt: Format = DefaultFmt) {.inline.} = + ## Write `x` to file `f` using format `fmt`. + var g = f + writeformat(g, x, fmt) + +proc addformat[T](f: File; x: T; fmt: string) {.inline.} = + ## Write `x` to file `f` using format string `fmt`. This is the same + ## as `addformat(f, x, parse(fmt))` + addformat(f, x, parse(fmt)) + +proc addformat(s: Stream; x: string) {.inline.} = + ## Write `x` to `s`. This is a fast specialized version for + ## writing unformatted strings to a stream. + write(s, x) + +proc addformat[T](s: Stream; x: T; fmt: Format = DefaultFmt) {.inline.} = + ## Write `x` to stream `s` using format `fmt`. + var g = s + writeformat(g, x, fmt) + +proc addformat[T](s: Stream; x: T; fmt: string) {.inline.} = + ## Write `x` to stream `s` using format string `fmt`. This is the same + ## as `addformat(s, x, parse(fmt))` + addformat(s, x, parse(fmt)) + +proc format[T](x: T; fmt: Format): string = + ## Return `x` formatted as a string according to format `fmt`. + result = "" + addformat(result, x, fmt) + +proc format[T](x: T; fmt: string): string = + ## Return `x` formatted as a string according to format string `fmt`. + result = format(x, fmt.parse) + +proc format[T](x: T): string {.inline.} = + ## Return `x` formatted as a string according to the default format. + ## The default format corresponds to an empty format string. + var fmt {.global.} : Format = DefaultFmt + result = format(x, fmt) + +proc unquoted(s: string): string {.compileTime.} = + ## Return `s` {{ and }} by single { and }, respectively. + result = "" + var pos = 0 + while pos < s.len: + let nxt = pos + skipUntil(s, {'{', '}'}) + result.add(s.substr(pos, nxt)) + pos = nxt + 2 + +proc splitfmt(s: string): seq[Part] {.compiletime, nosideeffect.} = + ## Split format string `s` into a sequence of "parts". + ## + + ## Each part is either a literal string or a format specification. A + ## format specification is a substring of the form + ## "{[arg][:format]}" where `arg` is either empty or a number + ## refering to the arg-th argument and an additional field or array + ## index. The format string is a string accepted by `parse`. + let subpeg = sequence(capture(digits()), + capture(?sequence(charSet({'.'}), *pegs.identStartChars(), *identChars())), + capture(?sequence(charSet({'['}), +digits(), charSet({']'}))), + capture(?sequence(charSet({':'}), *pegs.any()))) + result = @[] + var pos = 0 + while true: + let oppos = pos + skipUntil(s, {'{', '}'}, pos) + # reached the end + if oppos >= s.len: + if pos < s.len: + result.add(Part(kind: pkStr, str: s.substr(pos).unquoted)) + return + # skip double + if oppos + 1 < s.len and s[oppos] == s[oppos+1]: + result.add(Part(kind: pkStr, str: s.substr(pos, oppos))) + pos = oppos + 2 + continue + if s[oppos] == '}': + error("Single '}' encountered in format string") + if oppos > pos: + result.add(Part(kind: pkStr, str: s.substr(pos, oppos-1).unquoted)) + # find matching closing } + var lvl = 1 + var nested = false + pos = oppos + while lvl > 0: + pos.inc + pos = pos + skipUntil(s, {'{', '}'}, pos) + if pos >= s.len: + error("Single '{' encountered in format string") + if s[pos] == '{': + lvl.inc + if lvl == 2: + nested = true + if lvl > 2: + error("Too many nested format levels") + else: + lvl.dec + let clpos = pos + var fmtpart = Part(kind: pkFmt, arg: -1, fmt: s.substr(oppos+1, clpos-1), field: nil, index: int.high, nested: nested) + if fmtpart.fmt.len > 0: + var m: array[0..3, string] + if not fmtpart.fmt.match(subpeg, m): + error("invalid format string") + + if m[1] != nil and m[1].len > 0: + fmtpart.field = m[1].substr(1) + if m[2] != nil and m[2].len > 0: + discard parseInt(m[2].substr(1, m[2].len-2), fmtpart.index) + + if m[0].len > 0: discard parseInt(m[0], fmtpart.arg) + if m[3] == nil or m[3].len == 0: + fmtpart.fmt = "" + elif m[3][0] == ':': + fmtpart.fmt = m[3].substr(1) + else: + fmtpart.fmt = m[3] + result.add(fmtpart) + pos = clpos + 1 + +proc literal(s: string): NimNode {.compiletime, nosideeffect.} = + ## Return the nim literal of string `s`. This handles the case if + ## `s` is nil. + result = if s == nil: newNilLit() else: newLit(s) + +proc literal(b: bool): NimNode {.compiletime, nosideeffect.} = + ## Return the nim literal of boolean `b`. This is either `true` + ## or `false` symbol. + result = if b: "true".ident else: "false".ident + +proc literal[T](x: T): NimNode {.compiletime, nosideeffect.} = + ## Return the nim literal of value `x`. + when type(x) is enum: + result = ($x).ident + else: + result = newLit(x) + +proc generatefmt(fmtstr: string; + args: var openarray[tuple[arg:NimNode, cnt:int]]; + arg: var int;): seq[tuple[val, fmt:NimNode]] {.compiletime.} = + ## fmtstr + ## the format string + ## args + ## array of expressions for the arguments + ## arg + ## the number of the next argument for automatic parsing + ## + ## If arg is < 0 then the functions assumes that explicit numbering + ## must be used, otherwise automatic numbering is used starting at + ## `arg`. The value of arg is updated according to the number of + ## arguments being used. If arg == 0 then automatic and manual + ## numbering is not decided (because no explicit manual numbering is + ## fixed und no automatically numbered argument has been used so + ## far). + ## + ## The function returns a list of pairs `(val, fmt)` where `val` is + ## an expression to be formatted and `fmt` is the format string (or + ## Format). Therefore, the resulting string can be generated by + ## concatenating expressions `val.format(fmt)`. If `fmt` is `nil` + ## then `val` is a (literal) string expression. + try: + result = @[] + for part in splitfmt(fmtstr): + case part.kind + of pkStr: result.add((newLit(part.str), nil)) + of pkFmt: + # first compute the argument expression + # start with the correct index + var argexpr : NimNode + if part.arg >= 0: + if arg > 0: + error("Cannot switch from automatic field numbering to manual field specification") + if part.arg >= args.len: + error("Invalid explicit argument index: " & $part.arg) + argexpr = args[part.arg].arg + args[part.arg].cnt = args[part.arg].cnt + 1 + arg = -1 + else: + if arg < 0: + error("Cannot switch from manual field specification to automatic field numbering") + if arg >= args.len: + error("Too few arguments for format string") + argexpr = args[arg].arg + args[arg].cnt = args[arg].cnt + 1 + arg.inc + # possible field access + if part.field != nil and part.field.len > 0: + argexpr = newDotExpr(argexpr, part.field.ident) + # possible array access + if part.index < int.high: + argexpr = newNimNode(nnkBracketExpr).add(argexpr, newLit(part.index)) + # now the expression for the format data + var fmtexpr: NimNode + if part.nested: + # nested format string. Compute the format string by + # concatenating the parts of the substring. + for e in generatefmt(part.fmt, args, arg): + var newexpr = if part.fmt == nil: e.val else: newCall(bindsym"format", e.val, e.fmt) + if fmtexpr != nil and fmtexpr.kind != nnkNilLit: + fmtexpr = infix(fmtexpr, "&", newexpr) + else: + fmtexpr = newexpr + else: + # literal format string, precompute the format data + fmtexpr = newNimNode(nnkPar) + for field, val in part.fmt.parse.fieldPairs: + fmtexpr.add(newNimNode(nnkExprColonExpr).add(field.ident, literal(val))) + # add argument + result.add((argexpr, fmtexpr)) + finally: + discard + +proc addfmtfmt(fmtstr: string; args: NimNode; retvar: NimNode): NimNode {.compileTime.} = + var argexprs = newseq[tuple[arg:NimNode; cnt:int]](args.len) + result = newNimNode(nnkStmtListExpr) + # generate let bindings for arguments + for i in 0..args.len-1: + let argsym = gensym(nskLet, "arg" & $i) + result.add(newLetStmt(argsym, args[i])) + argexprs[i].arg = argsym + # add result values + var arg = 0 + for e in generatefmt(fmtstr, argexprs, arg): + if e.fmt == nil or e.fmt.kind == nnkNilLit: + result.add(newCall(bindsym"addformat", retvar, e.val)) + else: + result.add(newCall(bindsym"addformat", retvar, e.val, e.fmt)) + for i, arg in argexprs: + if arg.cnt == 0: + warning("Argument " & $(i+1) & " `" & args[i].repr & "` is not used in format string") + +macro addfmt(s: var string, fmtstr: string{lit}, args: varargs[expr]): expr = + ## The same as `s.add(fmtstr.fmt(args...))` but faster. + result = addfmtfmt($fmtstr, args, s) + +var s: string = "" +s.addfmt("a:{}", 42) diff --git a/tests/tuples/tuple_with_seq.nim b/tests/tuples/tuple_with_seq.nim new file mode 100644 index 000000000..39edb500f --- /dev/null +++ b/tests/tuples/tuple_with_seq.nim @@ -0,0 +1,46 @@ +discard """ + output: '''it's nil +@[1, 2, 3]''' +""" + +template foo(s: string = nil) = + if isNil(s): + echo "it's nil" + else: + echo s + +foo + + +# bug #2632 + +proc takeTup(x: tuple[s: string;x: seq[int]]) = + discard + +takeTup(("foo", @[])) + + +#proc foobar(): () = + +proc f(xs: seq[int]) = + discard + +proc g(t: tuple[n:int, xs:seq[int]]) = + discard + +when isMainModule: + f(@[]) # OK + g((1,@[1])) # OK + g((0,@[])) # NG + + +# bug #2630 +type T = tuple[a: seq[int], b: int] + +var t: T = (@[1,2,3], 7) + +proc test(s: seq[int]): T = + echo s + (s, 7) + +t = test(t.a) diff --git a/tests/types/temptyseqs.nim b/tests/types/temptyseqs.nim index 2b07ba679..834f63729 100644 --- a/tests/types/temptyseqs.nim +++ b/tests/types/temptyseqs.nim @@ -1,5 +1,13 @@ discard """ - output: "1" + output: '''1 +foo +bar +baz +foo +bar +baz +yes +no''' """ # bug #1708 @@ -24,3 +32,59 @@ when true: const foo2: seq[string] = @[] echo foo[0][0][0] + +proc takeEmpty(x: openArray[string] = []) = discard +takeEmpty() +takeEmpty([]) + +proc takeEmpty2(x: openArray[string] = @[]) = discard +takeEmpty2() +takeEmpty2([]) +takeEmpty2(@[]) + +#takeEmpty2([nil]) + +#rawMessage(errExecutionOfProgramFailed, []) + +# bug #2470 +const + stuff: seq[string] = @[] + +for str in stuff: + echo "str=", str + +# bug #1354 +proc foo4[T](more: seq[T] = @[]) = + var more2 = more + +foo4[int]() + +proc maino: int = + var wd: cstring = nil + inc result + +discard maino() + +proc varargso(a: varargs[string]) = + for x in a: + echo x + +varargso(["foo", "bar", "baz"]) +varargso("foo", "bar", "baz") + + +type + Flago = enum + tfNeedsInit, tfNotNil + +var s: set[Flago] = {tfNeedsInit} + +if {tfNeedsInit, tfNotNil} * s != {}: + echo "yes" +else: + echo "no" + +if {tfNeedsInit, tfNotNil} * s <= {tfNotNil}: + echo "yes" +else: + echo "no" diff --git a/todo.txt b/todo.txt index 12e82331f..4972015a7 100644 --- a/todo.txt +++ b/todo.txt @@ -1,20 +1,20 @@ -version 0.10.4 +version 0.11.2 ============== -- make 'nil' work for 'add': - - resizeString - - incrSeq - - addChar - - -version 0.10.6 (RC1?) -===================== - +- The remaining bugs of the lambda lifting pass that is responsible to enable + closures and closure iterators need to be fixed. +- ``concept`` needs to be refined, a nice name for the feature is not enough. +- Destructors need to be refined. - make '--implicitStatic:on' the default; then we can also clean up the 'static[T]' mess in the compiler! -- finish 'parallel' or mark as experimental + +- Finish the implementation of the 'parallel' statement. - Deprecate ``immediate`` for templates and macros - special case varargs[untyped] and varargs[typed] +- make 'nil' work for 'add': + - resizeString + - incrSeq + - addChar version 1.0 diff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim index e50b251d3..f0ae45484 100644 --- a/tools/niminst/niminst.nim +++ b/tools/niminst/niminst.nim @@ -35,6 +35,7 @@ type actionNsis, # action: create NSIS installer actionScripts # action: create install and deinstall scripts actionZip, # action: create zip file + actionTargz, # action: create targz file actionDeb # action: prepare deb package FileCategory = enum @@ -171,6 +172,7 @@ proc parseCmdLine(c: var ConfigData) = of "csource": incl(c.actions, actionCSource) of "scripts": incl(c.actions, actionScripts) of "zip": incl(c.actions, actionZip) + of "targz": incl(c.actions, actionTargz) of "inno": incl(c.actions, actionInno) of "nsis": incl(c.actions, actionNsis) of "deb": incl(c.actions, actionDeb) @@ -181,10 +183,10 @@ proc parseCmdLine(c: var ConfigData) = break of cmdLongoption, cmdShortOption: case normalize(key.string) - of "help", "h": + of "help", "h": stdout.write(Usage) quit(0) - of "version", "v": + of "version", "v": stdout.write(Version & "\n") quit(0) of "o", "output": c.outdir = val @@ -240,7 +242,7 @@ proc incl(s: var seq[string], x: string): int = for i in 0.. <s.len: if cmpIgnoreStyle(s[i], x) == 0: return i s.add(x) - result = s.len-1 + result = s.len-1 proc platforms(c: var ConfigData, v: string) = for line in splitLines(v): @@ -277,17 +279,17 @@ proc parseIniFile(c: var ConfigData) = of "name": c.name = v of "displayname": c.displayName = v of "version": c.version = v - of "os": + of "os": c.oses = split(v, {';'}) hasCpuOs = true if c.explicitPlatforms: quit(errorStr(p, "you cannot have both 'platforms' and 'os'")) - of "cpu": + of "cpu": c.cpus = split(v, {';'}) hasCpuOs = true if c.explicitPlatforms: quit(errorStr(p, "you cannot have both 'platforms' and 'cpu'")) - of "platforms": + of "platforms": platforms(c, v) c.explicitPlatforms = true if hasCpuOs: @@ -389,7 +391,7 @@ proc readCFiles(c: var ConfigData, osA, cpuA: int) = of cfgKeyValuePair: case section of "ccompiler": pathFlags(p, k.key, k.value, c.ccompiler) - of "linker": + of "linker": pathFlags(p, k.key, k.value, c.linker) # HACK: we conditionally add ``-lm -ldl``, so remove them from the # linker flags: @@ -558,28 +560,68 @@ when haveZipLib: else: quit("Cannot open for writing: " & n) +proc targzDist(c: var ConfigData) = + let proj = toLower(c.name) & "-" & c.version + var n = "$#.tar.gz" % proj + let tmpDir = if c.outdir.len == 0: "build" else: c.outdir + + template processFile(z, dest, src) = + let s = src + let d = dest + echo "Copying ", s, " to ", tmpDir / d + let destdir = tmpdir / d.splitFile.dir + if not dirExists(destdir): createDir(destdir) + copyFile(s, tmpDir / d) + + processFile(z, proj / buildBatFile32, "build" / buildBatFile32) + processFile(z, proj / buildBatFile64, "build" / buildBatFile64) + processFile(z, proj / buildShFile, "build" / buildShFile) + processFile(z, proj / makeFile, "build" / makeFile) + processFile(z, proj / installShFile, installShFile) + processFile(z, proj / deinstallShFile, deinstallShFile) + for f in walkFiles(c.libpath / "lib/*.h"): + processFile(z, proj / "c_code" / extractFilename(f), f) + for osA in 1..c.oses.len: + for cpuA in 1..c.cpus.len: + var dir = buildDir(osA, cpuA) + for k, f in walkDir("build" / dir): + if k == pcFile: processFile(z, proj / dir / extractFilename(f), f) + + for cat in items({fcConfig..fcOther, fcUnix}): + for f in items(c.cat[cat]): processFile(z, proj / f, f) + + let oldDir = getCurrentDir() + setCurrentDir(tmpDir) + try: + #if execShellCmd("7z a -ttar $1.tar $1" % proj) != 0 or + # execShellCmd("7z a -tgzip $1.tar.gz $1.tar" % proj) != 0 or + if execShellCmd("7z a -tzip $1.zip $1" % proj) != 0: + echo("External program failed") + finally: + setCurrentDir(oldDir) + # -- prepare build files for .deb creation proc debDist(c: var ConfigData) = if not existsFile(getOutputDir(c) / "build.sh"): quit("No build.sh found.") if not existsFile(getOutputDir(c) / "install.sh"): quit("No install.sh found.") - + if c.debOpts.shortDesc == "": quit("shortDesc must be set in the .ini file.") if c.debOpts.licenses.len == 0: echo("[Warning] No licenses specified for .deb creation.") - + # -- Copy files into /tmp/.. echo("Copying source to tmp/niminst/deb/") var currentSource = getCurrentDir() var workingDir = getTempDir() / "niminst" / "deb" var upstreamSource = (c.name.toLower() & "-" & c.version) - + createDir(workingDir / upstreamSource) - + template copyNimDist(f, dest: string): stmt = createDir((workingDir / upstreamSource / dest).splitFile.dir) copyFile(currentSource / f, workingDir / upstreamSource / dest) - + # Don't copy all files, only the ones specified in the config: copyNimDist(buildShFile, buildShFile) copyNimDist(makeFile, makeFile) @@ -624,5 +666,7 @@ if actionZip in c.actions: zipDist(c) else: quit("libzip is not installed") +if actionTargz in c.actions: + targzDist(c) if actionDeb in c.actions: debDist(c) diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl index c21bfb9d5..843a8cf44 100644 --- a/tools/niminst/nsis.tmpl +++ b/tools/niminst/nsis.tmpl @@ -1,5 +1,5 @@ #! stdtmpl(subsChar='?') | standard -#proc generateNsisSetup(c: ConfigData): string = +#proc generateNsisSetup(c: ConfigData): string = # result = "; NSIS script generated by niminst\n" & # "; To regenerate run ``niminst nsis`` or ``koch nsis``\n" @@ -35,8 +35,8 @@ ; Default installation folder ; This is changed later (in .onInit) to the root directory, if possible. - InstallDir "$LOCALAPPDATA\?{c.name}" - + InstallDir "$LOCALAPPDATA\?{c.name}-?{c.version}" + ; Get installation folder from registry if available InstallDirRegKey HKCU "Software\c.name\c.version" "" @@ -86,14 +86,14 @@ !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH - + ; Setup the uninstaller pages !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES - + ;-------------------------------- ;Languages - + !insertmacro MUI_LANGUAGE "English" ;-------------------------------- @@ -104,7 +104,7 @@ ; Nim binary. Section "Core Files" CoreSection ; This is a mandotory section - SectionIn RO + SectionIn RO ; Output files to the base installation directory SetOutPath "$INSTDIR" @@ -164,7 +164,7 @@ ; The downloadable sections. These sections are automatically generated by ; niminst and the template filters. - #var i = 0 + #var i = 0 #for download in c.downloads: # inc i # let d = download.split('|') @@ -207,7 +207,7 @@ CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{e}.lnk" "$INSTDIR\?dir\?{startMenuEntry.toWin}" !insertmacro MUI_STARTMENU_WRITE_END # end if - + ignore: SectionEnd #end diff --git a/web/assets/style.css b/web/assets/style.css index b74cbc486..17541a118 100644 --- a/web/assets/style.css +++ b/web/assets/style.css @@ -125,8 +125,8 @@ pre .end { background:url("images/tabEnd.png") no-repeat left bottom; } opacity 1s ease-in-out; } #slideshow > div.active { visibility:visible; opacity:1; transition-delay:0s; } #slideshow > div.init { transition-delay:0s; } - #slideshow-nav { z-index:3; position:absolute; top:110px;; right:-12px; } - #slideshow-nav > div { margin:5px 0; width:23px; height:23px; background:url("images/slideshow-nav.png") no-repeat; } + #slideshow-nav { z-index:3; position:absolute; top:341px; left:18px; } + #slideshow-nav > div { display:inline-block; margin:5px 0; width:23px; height:23px; background:url("images/slideshow-nav.png") no-repeat; } #slideshow-nav > div:hover { background-image:url("images/slideshow-nav_active.png"); opacity:0.5; } #slideshow-nav > div.active { background-image:url("images/slideshow-nav_active.png"); opacity:1; } diff --git a/web/documentation.txt b/web/documentation.txt index dbb737cd9..67f8b4070 100644 --- a/web/documentation.txt +++ b/web/documentation.txt @@ -8,13 +8,13 @@ Nim's Documentation .. container:: libraries - - | `Standard Library <lib.html>`_ + - | `Standard Library <docs/lib.html>`_ | This document describes Nim's standard library. - - | `Language Manual <manual.html>`_ + - | `Language Manual <docs/manual.html>`_ | The Nim manual is a draft that will evolve into a proper specification. - - | `Compiler User Guide <nimc.html>`_ + - | `Compiler User Guide <docs/nimc.html>`_ | The user guide lists command line arguments, special features of the compiler, etc. @@ -26,11 +26,11 @@ Nim's Documentation .. container:: tools - - | `Source Code Filters <filters.html>`_ + - | `Source Code Filters <docs/filters.html>`_ | The Nim compiler supports source code filters as a simple yet powerful builtin templating system. - - | `Tools Documentation <tools.html>`_ + - | `Tools Documentation <docs/tools.html>`_ | Description of some tools that come with the standard distribution. @@ -41,16 +41,17 @@ Nim's Documentation .. container:: internals - - | `Garbage Collector <gc.html>`_ + - | `Garbage Collector <docs/gc.html>`_ | Additional documentation about Nim's GC and how to operate it in a realtime setting. - - | `Internal Documentation <intern.html>`_ - | The internal documentation describes how the compiler is implemented. Read - this if you want to hack the compiler. + - | `Internal Documentation <docs/intern.html>`_ + | The internal documentation describes how the compiler is implemented. + Read this if you want to hack the compiler. Search Options -------------- -`Documentation Index <theindex.html>`_ - The generated index. **Index + (Ctrl+F) == Joy** +`Documentation Index <docs/theindex.html>`_ - The generated +index. **Index + (Ctrl+F) == Joy** diff --git a/web/download.txt b/web/download.txt index 3d47467f2..6acc80b53 100644 --- a/web/download.txt +++ b/web/download.txt @@ -13,8 +13,8 @@ Binaries -------- Unfortunately for now we only provide builds for Windows. -* 32 bit: `nim-0.10.2_x32.exe <download/nim-0.10.2_x32.exe>`_ -* 64 bit: `nim-0.10.2_x64.exe <download/nim-0.10.2_x64.exe>`_ +* 32 bit: `nim-0.11.2_x32.exe <download/nim-0.11.2_x32.exe>`_ +* 64 bit: `nim-0.11.2_x64.exe <download/nim-0.11.2_x64.exe>`_ Installation based on generated C code @@ -24,8 +24,12 @@ This installation method is the preferred way for Linux, Mac OS X, and other Uni like systems. Binary packages may be provided later. -Download `nim-0.10.2.zip <download/nim-0.10.2.zip>`_, extract it and follow -these instructions: +Download one of these: + +* `nim-0.11.2.zip (28 MB) <download/nim-0.11.2.zip>`_ +* `nim-0.11.2.tar.xz (2.6MB) <download/nim-0.11.2.tar.xz>`_ + +Extract the file and follow these instructions: * sh build.sh * Add ``$your_install_dir/bin`` to your PATH. diff --git a/web/learn.txt b/web/learn.txt index 7a9600e57..bf0cc43ef 100644 --- a/web/learn.txt +++ b/web/learn.txt @@ -8,10 +8,10 @@ Learning Nim .. container:: tutorials - - | `Tutorial (part I) <tut1.html>`_ + - | `Tutorial (part I) <docs/tut1.html>`_ | Learn the basics of Nim's types, variables, procedures, control flow, etc... - - | `Tutorial (part II) <tut2.html>`_ + - | `Tutorial (part II) <docs/tut2.html>`_ | Learn Nim's more advanced features such as OOP, generics, macros, etc... @@ -52,5 +52,5 @@ Learning Nim Documentation ------------- -More examples of Nim code can be found in the `Nim Language Documentation <manual.html>`_. +More examples of Nim code can be found in the `Nim Language Documentation <docs/manual.html>`_. diff --git a/web/news.txt b/web/news.txt index b3453feaf..9719fb8d7 100644 --- a/web/news.txt +++ b/web/news.txt @@ -3,344 +3,423 @@ News ==== .. - 2015-03-01 Version 0.10.4 released + 2015-05-05 Version 0.11.2 released ================================== - Changes affecting backwards compatibility ----------------------------------------- - - Parameter names are finally properly ``gensym``'ed. This can break - templates though that used to rely on the fact that they are not. - (Bug #1915.) This means this doesn't compile anymore: - - .. code-block:: nim - - template doIt(body: stmt) {.immediate.} = - # this used to inject the 'str' parameter: - proc res(str: string) = - body - - doIt: - echo str # Error: undeclared identifier: 'str' - - Declare the ``doIt`` template as ``immediate, dirty`` to get the old - behaviour. - - Tuple field names are not ignored anymore, this caused too many problems - in practice so now the behaviour as it was for version 0.9.6: If field - names exist for the tuple type, they are checked. - - ``logging.level`` and ``logging.handlers`` are no longer exported. - ``addHandler``, ``getHandlers``, ``setLogFilter`` and ``getLogFilter`` - should be used instead. - - ``nim idetools`` has been replaced by a separate tool `nimsuggest`_. - - *arrow like* operators are not right associative anymore. - - *arrow like* operators are now required to end with either ``->``, ``~>`` or - ``=>``, not just ``>``. Examples of operators still considered arrow like: - ``->``, ``==>``, ``+=>``. On the other hand, the following operators are now - considered regular operators again: ``|>``, ``-+>``, etc. - - Typeless parameters are now only allowed in templates and macros. The old - way turned out to be too error-prone. - - The 'addr' and 'type' operators are now parsed as unary function - application. This means ``type(x).name`` is now parsed as ``(type(x)).name`` - and not as ``type((x).name)``. Note that this also affects the AST - structure; for immediate macro parameters ``nkCall('addr', 'x')`` is - produced instead of ``nkAddr('x')``. - - ``concept`` is now a keyword and is used instead of ``generic``. - - The ``inc``, ``dec``, ``+=``, ``-=`` builtins now produce OverflowError - exceptions. This means code like the following: - - .. code-block:: nim - var x = low(T) - while x <= high(T): - echo x - inc x - - Needs to be replaced by something like this: - - .. code-block:: nim - var x = low(T).int - while x <= high(T).int: - echo x.T - inc x - - - **Negative indexing for slicing does not work anymore!** Instead - of ``a[0.. -1]`` you can - use ``a[0.. ^1]``. This also works with accessing a single - element ``a[^1]``. Note that we cannot detect this reliably as it is - determined at **runtime** whether negative indexing is used! - ``a[0.. -1]`` now produces the empty string/sequence. - - The compiler now warns about code like ``foo +=1`` which uses inconsistent - spacing around binary operators. Later versions of the language will parse - these as unary operators instead so that ``echo $foo`` finally can do what - people expect it to do. - - ``system.untyped`` and ``system.typed`` have been introduced as aliases - for ``expr`` and ``stmt``. The new names capture the semantics much better - and most likely ``expr`` and ``stmt`` will be deprecated in favor of the - new names. - - The ``split`` method in module ``re`` has changed. It now handles the case - of matches having a length of 0, and empty strings being yielded from the - iterator. A notable change might be that a pattern being matched at the - beginning and end of a string, will result in an empty string being produced - at the start and the end of the iterator. Language Additions ------------------ - - For empty ``case object`` branches ``discard`` can finally be used instead - of ``nil``. - - Automatic dereferencing is now done for the first argument of a routine - call if overloading resolution produces no match otherwise. This feature - has to be enabled with the `experimental`_ pragma. - - Objects that do not use inheritance nor ``case`` can be put into ``const`` - sections. This means that finally this is possible and produces rather - nice code: - .. code-block:: nim - import tables + Bugfixes + -------- + - const - foo = {"ah": "finally", "this": "is", "possible.": "nice!"}.toTable() +2015-05-04 Version 0.11.2 released +================================== +This is just a bugfix release that fixes the most pressing regressions we +introduced with version 0.11.0. The way types are computed was +changed significantly causing all sort of problems. Sorry for the +inconvenience; we grew overconfident our large test suite would prevent these +things. - - Ordinary parameters can follow after a varargs parameter. This means the - following is finally accepted by the compiler: - .. code-block:: nim - template takesBlock(a, b: int, x: varargs[expr]; blck: stmt) = - blck - echo a, b +2015-04-30 Version 0.11.0 released +================================== - takesBlock 1, 2, "some", 0.90, "random stuff": - echo "yay" +With this release we are one step closer to reaching version 1.0 and by +extension the persistence of the Nim specification. As mentioned in the +previous release notes, starting with version 1.0, we will not be introducing +any more breaking changes to Nim. - - Overloading by 'var T' is now finally possible: +The *language* itself is very close to 1.0, the primary area that requires +more work is the standard library. - .. code-block:: nim - proc varOrConst(x: var int) = echo "var" - proc varOrConst(x: int) = echo "const" +Take a look at the `download <download.html>`_ page for binaries (Windows-only) +and 0.11.0 snapshots of the source code. The Windows installer now also +includes `Aporia <https://github.com/nim-lang/aporia>`_, +`Nimble <https://github.com/nim-lang/nimble>`_ and other useful tools to get +you started with Nim. - var x: int - varOrConst(x) # "var" - varOrConst(45) # "const" +What's left to be done +~~~~~~~~~~~~~~~~~~~~~~ - - Array and seq indexing can now use the builtin ``^`` operator to access - things from backwards: ``a[^1]`` is like Python's ``a[-1]``. - - A first version of the specification and implementation of the overloading - of the assignment operator has arrived! - - ``system.len`` for strings and sequences now returns 0 for nil. +The 1.0 release is expected by the end of this year. Rumors say it will be in +summer 2015. What's left: - - A single underscore can now be used to discard values when unpacking tuples. +* Bug fixes, bug fixes, bug fixes, in particular: + - The remaining bugs of the lambda lifting pass that is responsible to enable + closures and closure iterators need to be fixed. + - ``concept`` needs to be refined, a nice name for the feature is not enough. + - Destructors need to be refined. + - ``static[T]`` needs to be fixed. + - Finish the implementation of the 'parallel' statement. +* ``immediate`` templates and macros will be deprecated as these will soon be + completely unnecessary, instead the ``typed`` or ``untyped`` metatypes can + be used. +* More of the standard library should be moved to Nimble packages and what's + left should use the features we have for concurrency and parallelism. - Library additions - ----------------- - - ``reversed`` proc added to the ``unicode`` module. - - Added multipart param to httpclient's ``post`` and ``postContent`` together - with a ``newMultipartData`` proc. - - Added `%*` operator for JSON. - - The compiler is now available as Nimble package for c2nim. - - Added ``..^`` and ``..<`` templates to system so that the rather annoying - space between ``.. <`` and ``.. ^`` is not necessary anymore. - - Added ``system.xlen`` for strings and sequences to get back the old ``len`` - operation that doesn't check for ``nil`` for efficiency. +Changes affecting backwards compatibility +----------------------------------------- +- Parameter names are finally properly ``gensym``'ed. This can break + templates though that used to rely on the fact that they are not. + (Bug #1915.) This means this doesn't compile anymore: - Bugfixes - -------- +.. code-block:: nim + + template doIt(body: stmt) {.immediate.} = + # this used to inject the 'str' parameter: + proc res(str: string) = + body + + doIt: + echo str # Error: undeclared identifier: 'str' +.. + + This used to inject the ``str`` parameter into the scope of the body. + Declare the ``doIt`` template as ``immediate, dirty`` to get the old + behaviour. +- Tuple field names are not ignored anymore, this caused too many problems + in practice so now the behaviour is as it was for version 0.9.6: If field + names exist for the tuple type, they are checked. +- ``logging.level`` and ``logging.handlers`` are no longer exported. + ``addHandler``, ``getHandlers``, ``setLogFilter`` and ``getLogFilter`` + should be used instead. +- ``nim idetools`` has been replaced by a separate + tool `nimsuggest <0.11.0/nimsuggest.html>`_. +- *arrow like* operators are not right associative anymore and are required + to end with either ``->``, ``~>`` or + ``=>``, not just ``>``. Examples of operators still considered arrow like: + ``->``, ``==>``, ``+=>``. On the other hand, the following operators are now + considered regular operators again: ``|>``, ``-+>``, etc. +- Typeless parameters are now only allowed in templates and macros. The old + way turned out to be too error-prone. +- The 'addr' and 'type' operators are now parsed as unary function + application. This means ``type(x).name`` is now parsed as ``(type(x)).name`` + and not as ``type((x).name)``. Note that this also affects the AST + structure; for immediate macro parameters ``nkCall('addr', 'x')`` is + produced instead of ``nkAddr('x')``. +- ``concept`` is now a keyword and is used instead of ``generic``. +- The ``inc``, ``dec``, ``+=``, ``-=`` builtins now produce OverflowError + exceptions. This means code like the following: + +.. code-block:: nim + var x = low(T) + while x <= high(T): + echo x + inc x + +Needs to be replaced by something like this: + +.. code-block:: nim + var x = low(T).int + while x <= high(T).int: + echo x.T + inc x + +- **Negative indexing for slicing does not work anymore!** Instead + of ``a[0.. -1]`` you can + use ``a[0.. ^1]``. This also works with accessing a single + element ``a[^1]``. Note that we cannot detect this reliably as it is + determined at **runtime** whether negative indexing is used! + ``a[0.. -1]`` now produces the empty string/sequence. +- The compiler now warns about code like ``foo +=1`` which uses inconsistent + spacing around binary operators. Later versions of the language will parse + these as unary operators instead so that ``echo $foo`` finally can do what + people expect it to do. +- ``system.untyped`` and ``system.typed`` have been introduced as aliases + for ``expr`` and ``stmt``. The new names capture the semantics much better + and most likely ``expr`` and ``stmt`` will be deprecated in favor of the + new names. +- The ``split`` method in module ``re`` has changed. It now handles the case + of matches having a length of 0, and empty strings being yielded from the + iterator. A notable change might be that a pattern being matched at the + beginning and end of a string, will result in an empty string being produced + at the start and the end of the iterator. +- The compiler and nimsuggest now count columns starting with 1, not 0 for + consistency with the rest of the world. + + +Language Additions +------------------ + +- For empty ``case object`` branches ``discard`` can finally be used instead + of ``nil``. +- Automatic dereferencing is now done for the first argument of a routine + call if overloading resolution produces no match otherwise. This feature + has to be enabled with + the `experimental <0.11.0/manual.html#pragmas-experimental-pragma>`_ pragma. +- Objects that do not use inheritance nor ``case`` can be put into ``const`` + sections. This means that finally this is possible and produces rather + nice code: + +.. code-block:: nim + import tables + + const + foo = {"ah": "finally", "this": "is", "possible.": "nice!"}.toTable() + + +- Ordinary parameters can follow after a varargs parameter. This means the + following is finally accepted by the compiler: + +.. code-block:: nim + template takesBlock(a, b: int, x: varargs[expr]; blck: stmt) = + blck + echo a, b + + takesBlock 1, 2, "some", 0.90, "random stuff": + echo "yay" + +- Overloading by 'var T' is now finally possible: + +.. code-block:: nim + proc varOrConst(x: var int) = echo "var" + proc varOrConst(x: int) = echo "const" + + var x: int + varOrConst(x) # "var" + varOrConst(45) # "const" + +- Array and seq indexing can now use the builtin ``^`` operator to access + things from backwards: ``a[^1]`` is like Python's ``a[-1]``. +- A first version of the specification and implementation of the overloading + of the assignment operator has arrived! +- ``system.len`` for strings and sequences now returns 0 for nil. + +- A single underscore can now be used to discard values when unpacking tuples: + +.. code-block:: nim + let (path, _, _) = os.splitFile("path/file.ext") + + +- ``marshal.$$`` and ``marshal.to`` can be executed at compile-time. +- Interoperability with C++ improved tremendously; C++'s templates and + operators can be wrapped directly. See + `this <0.11.0/nimc.html#additional-features-importcpp-pragma>`_ + for more information. +- ``macros.getType`` can be used to query an AST's type at compile-time. This + enables more powerful macros, for instance *currying* can now be done with + a macro. + + +Library additions +----------------- + +- ``reversed`` proc added to the ``unicode`` module. +- Added multipart param to httpclient's ``post`` and ``postContent`` together + with a ``newMultipartData`` proc. +- Added `%*` operator for JSON. +- The compiler is now available as Nimble package for c2nim. +- Added ``..^`` and ``..<`` templates to system so that the rather annoying + space between ``.. <`` and ``.. ^`` is not necessary anymore. +- Added ``system.xlen`` for strings and sequences to get back the old ``len`` + operation that doesn't check for ``nil`` for efficiency. + + +Bugfixes +-------- + +- Fixed internal compiler error when using ``char()`` in an echo call + (`#1788 <https://github.com/Araq/Nim/issues/1788>`_). +- Fixed Windows cross-compilation on Linux. +- Overload resolution now works for types distinguished only by a + ``static[int]`` param + (`#1056 <https://github.com/Araq/Nim/issues/1056>`_). +- Other fixes relating to generic types and static params. +- Fixed some compiler crashes with unnamed tuples + (`#1774 <https://github.com/Araq/Nim/issues/1774>`_). +- Fixed ``channels.tryRecv`` blocking + (`#1816 <https://github.com/Araq/Nim/issues/1816>`_). +- Fixed generic instantiation errors with ``typedesc`` + (`#419 <https://github.com/Araq/Nim/issues/419>`_). +- Fixed generic regression where the compiler no longer detected constant + expressions properly (`#544 <https://github.com/Araq/Nim/issues/544>`_). +- Fixed internal error with generic proc using ``static[T]`` in a specific + way (`#1049 <https://github.com/Araq/Nim/issues/1049>`_). +- More fixes relating to generics (`#1820 <https://github.com/Araq/Nim/issues/1820>`_, + `#1050 <https://github.com/Araq/Nim/issues/1050>`_, + `#1859 <https://github.com/Araq/Nim/issues/1859>`_, + `#1858 <https://github.com/Araq/Nim/issues/1858>`_). +- Fixed httpclient to properly encode queries. +- Many fixes to the ``uri`` module. +- Async sockets are now closed on error. +- Fixes to httpclient's handling of multipart data. +- Fixed GC segfaults with asynchronous sockets + (`#1796 <https://github.com/Araq/Nim/issues/1796>`_). +- Added more versions to openssl's DLL version list + (`076f993 <https://github.com/Araq/Nim/commit/076f993>`_). +- Fixed shallow copy in iterators being broken + (`#1803 <https://github.com/Araq/Nim/issues/1803>`_). +- ``nil`` can now be inserted into tables with the ``db_sqlite`` module + (`#1866 <https://github.com/Araq/Nim/issues/1866>`_). +- Fixed "Incorrect assembler generated" + (`#1907 <https://github.com/Araq/Nim/issues/1907>`_) +- Fixed "Expression templates that define macros are unusable in some contexts" + (`#1903 <https://github.com/Araq/Nim/issues/1903>`_) +- Fixed "a second level generic subclass causes the compiler to crash" + (`#1919 <https://github.com/Araq/Nim/issues/1919>`_) +- Fixed "nim 0.10.2 generates invalid AsyncHttpClient C code for MSVC " + (`#1901 <https://github.com/Araq/Nim/issues/1901>`_) +- Fixed "1 shl n produces wrong C code" + (`#1928 <https://github.com/Araq/Nim/issues/1928>`_) +- Fixed "Internal error on tuple yield" + (`#1838 <https://github.com/Araq/Nim/issues/1838>`_) +- Fixed "ICE with template" + (`#1915 <https://github.com/Araq/Nim/issues/1915>`_) +- Fixed "include the tool directory in the installer as it is required by koch" + (`#1947 <https://github.com/Araq/Nim/issues/1947>`_) +- Fixed "Can't compile if file location contains spaces on Windows" + (`#1955 <https://github.com/Araq/Nim/issues/1955>`_) +- Fixed "List comprehension macro only supports infix checks as guards" + (`#1920 <https://github.com/Araq/Nim/issues/1920>`_) +- Fixed "wrong field names of compatible tuples in generic types" + (`#1910 <https://github.com/Araq/Nim/issues/1910>`_) +- Fixed "Macros within templates no longer work as expected" + (`#1944 <https://github.com/Araq/Nim/issues/1944>`_) +- Fixed "Compiling for Standalone AVR broken in 0.10.2" + (`#1964 <https://github.com/Araq/Nim/issues/1964>`_) +- Fixed "Compiling for Standalone AVR broken in 0.10.2" + (`#1964 <https://github.com/Araq/Nim/issues/1964>`_) +- Fixed "Code generation for mitems with tuple elements" + (`#1833 <https://github.com/Araq/Nim/issues/1833>`_) +- Fixed "httpclient.HttpMethod should not be an enum" + (`#1962 <https://github.com/Araq/Nim/issues/1962>`_) +- Fixed "terminal / eraseScreen() throws an OverflowError" + (`#1906 <https://github.com/Araq/Nim/issues/1906>`_) +- Fixed "setControlCHook(nil) disables registered quit procs" + (`#1546 <https://github.com/Araq/Nim/issues/1546>`_) +- Fixed "Unexpected idetools behaviour" + (`#325 <https://github.com/Araq/Nim/issues/325>`_) +- Fixed "Unused lifted lambda does not compile" + (`#1642 <https://github.com/Araq/Nim/issues/1642>`_) +- Fixed "'low' and 'high' don't work with cstring asguments" + (`#2030 <https://github.com/Araq/Nim/issues/2030>`_) +- Fixed "Converting to int does not round in JS backend" + (`#1959 <https://github.com/Araq/Nim/issues/1959>`_) +- Fixed "Internal error genRecordField 2 when adding region to pointer." + (`#2039 <https://github.com/Araq/Nim/issues/2039>`_) +- Fixed "Macros fail to compile when compiled with --os:standalone" + (`#2041 <https://github.com/Araq/Nim/issues/2041>`_) +- Fixed "Reading from {.compileTime.} variables can cause code generation to fail" + (`#2022 <https://github.com/Araq/Nim/issues/2022>`_) +- Fixed "Passing overloaded symbols to templates fails inside generic procedures" + (`#1988 <https://github.com/Araq/Nim/issues/1988>`_) +- Fixed "Compiling iterator with object assignment in release mode causes "var not init"" + (`#2023 <https://github.com/Araq/Nim/issues/2023>`_) +- Fixed "calling a large number of macros doing some computation fails" + (`#1989 <https://github.com/Araq/Nim/issues/1989>`_) +- Fixed "Can't get Koch to install nim under Windows" + (`#2061 <https://github.com/Araq/Nim/issues/2061>`_) +- Fixed "Template with two stmt parameters segfaults compiler" + (`#2057 <https://github.com/Araq/Nim/issues/2057>`_) +- Fixed "`noSideEffect` not affected by `echo`" + (`#2011 <https://github.com/Araq/Nim/issues/2011>`_) +- Fixed "Compiling with the cpp backend ignores --passc" + (`#1601 <https://github.com/Araq/Nim/issues/1601>`_) +- Fixed "Put untyped procedure parameters behind the experimental pragma" + (`#1956 <https://github.com/Araq/Nim/issues/1956>`_) +- Fixed "generic regression" + (`#2073 <https://github.com/Araq/Nim/issues/2073>`_) +- Fixed "generic regression" + (`#2073 <https://github.com/Araq/Nim/issues/2073>`_) +- Fixed "Regression in template lookup with generics" + (`#2004 <https://github.com/Araq/Nim/issues/2004>`_) +- Fixed "GC's growObj is wrong for edge cases" + (`#2070 <https://github.com/Araq/Nim/issues/2070>`_) +- Fixed "Compiler internal error when creating an array out of a typeclass" + (`#1131 <https://github.com/Araq/Nim/issues/1131>`_) +- Fixed "GC's growObj is wrong for edge cases" + (`#2070 <https://github.com/Araq/Nim/issues/2070>`_) +- Fixed "Invalid Objective-C code generated when calling class method" + (`#2068 <https://github.com/Araq/Nim/issues/2068>`_) +- Fixed "walkDirRec Error" + (`#2116 <https://github.com/Araq/Nim/issues/2116>`_) +- Fixed "Typo in code causes compiler SIGSEGV in evalAtCompileTime" + (`#2113 <https://github.com/Araq/Nim/issues/2113>`_) +- Fixed "Regression on exportc" + (`#2118 <https://github.com/Araq/Nim/issues/2118>`_) +- Fixed "Error message" + (`#2102 <https://github.com/Araq/Nim/issues/2102>`_) +- Fixed "hint[path] = off not working in nim.cfg" + (`#2103 <https://github.com/Araq/Nim/issues/2103>`_) +- Fixed "compiler crashes when getting a tuple from a sequence of generic tuples" + (`#2121 <https://github.com/Araq/Nim/issues/2121>`_) +- Fixed "nim check hangs with when" + (`#2123 <https://github.com/Araq/Nim/issues/2123>`_) +- Fixed "static[T] param in nested type resolve/caching issue" + (`#2125 <https://github.com/Araq/Nim/issues/2125>`_) +- Fixed "repr should display ``\0``" + (`#2124 <https://github.com/Araq/Nim/issues/2124>`_) +- Fixed "'nim check' never ends in case of recursive dependency " + (`#2051 <https://github.com/Araq/Nim/issues/2051>`_) +- Fixed "From macros: Error: unhandled exception: sons is not accessible" + (`#2167 <https://github.com/Araq/Nim/issues/2167>`_) +- Fixed "`fieldPairs` doesn't work inside templates" + (`#1902 <https://github.com/Araq/Nim/issues/1902>`_) +- Fixed "fields iterator misbehavior on break statement" + (`#2134 <https://github.com/Araq/Nim/issues/2134>`_) +- Fixed "Fix for compiler not building anymore since #c3244ef1ff" + (`#2193 <https://github.com/Araq/Nim/issues/2193>`_) +- Fixed "JSON parser fails in cpp output mode" + (`#2199 <https://github.com/Araq/Nim/issues/2199>`_) +- Fixed "macros.getType mishandles void return" + (`#2211 <https://github.com/Araq/Nim/issues/2211>`_) +- Fixed "Regression involving templates instantiated within generics" + (`#2215 <https://github.com/Araq/Nim/issues/2215>`_) +- Fixed ""Error: invalid type" for 'not nil' on generic type." + (`#2216 <https://github.com/Araq/Nim/issues/2216>`_) +- Fixed "--threads:on breaks async" + (`#2074 <https://github.com/Araq/Nim/issues/2074>`_) +- Fixed "Type mismatch not always caught, can generate bad code for C backend." + (`#2169 <https://github.com/Araq/Nim/issues/2169>`_) +- Fixed "Failed C compilation when storing proc to own type in object" + (`#2233 <https://github.com/Araq/Nim/issues/2233>`_) +- Fixed "Unknown line/column number in constant declaration type conversion error" + (`#2252 <https://github.com/Araq/Nim/issues/2252>`_) +- Fixed "Adding {.compile.} fails if nimcache already exists." + (`#2247 <https://github.com/Araq/Nim/issues/2247>`_) +- Fixed "Two different type names generated for a single type (C backend)" + (`#2250 <https://github.com/Araq/Nim/issues/2250>`_) +- Fixed "Ambigous call when it should not be" + (`#2229 <https://github.com/Araq/Nim/issues/2229>`_) +- Fixed "Make sure we can load root urls" + (`#2227 <https://github.com/Araq/Nim/issues/2227>`_) +- Fixed "Failure to slice a string with an int subrange type" + (`#794 <https://github.com/Araq/Nim/issues/794>`_) +- Fixed "documentation error" + (`#2205 <https://github.com/Araq/Nim/issues/2205>`_) +- Fixed "Code growth when using `const`" + (`#1940 <https://github.com/Araq/Nim/issues/1940>`_) +- Fixed "Instances of generic types confuse overload resolution" + (`#2220 <https://github.com/Araq/Nim/issues/2220>`_) +- Fixed "Compiler error when initializing sdl2's EventType" + (`#2316 <https://github.com/Araq/Nim/issues/2316>`_) +- Fixed "Parallel disjoint checking can't handle `<`, `items`, or arrays" + (`#2287 <https://github.com/Araq/Nim/issues/2287>`_) +- Fixed "Strings aren't copied in parallel loop" + (`#2286 <https://github.com/Araq/Nim/issues/2286>`_) +- Fixed "JavaScript compiler crash with tables" + (`#2298 <https://github.com/Araq/Nim/issues/2298>`_) +- Fixed "Range checker too restrictive" + (`#1845 <https://github.com/Araq/Nim/issues/1845>`_) +- Fixed "Failure to slice a string with an int subrange type" + (`#794 <https://github.com/Araq/Nim/issues/794>`_) +- Fixed "Remind user when compiling in debug mode" + (`#1868 <https://github.com/Araq/Nim/issues/1868>`_) +- Fixed "Compiler user guide has jumbled options/commands." + (`#1819 <https://github.com/Araq/Nim/issues/1819>`_) +- Fixed "using `method`: 1 in a objects constructor fails when compiling" + (`#1791 <https://github.com/Araq/Nim/issues/1791>`_) - - Fixed internal compiler error when using ``char()`` in an echo call - (`#1788 <https://github.com/Araq/Nim/issues/1788>`_). - - Fixed Windows cross-compilation on Linux. - - Overload resolution now works for types distinguished only by a - ``static[int]`` param - (`#1056 <https://github.com/Araq/Nim/issues/1056>`_). - - Other fixes relating to generic types and static params. - - Fixed some compiler crashes with unnamed tuples - (`#1774 <https://github.com/Araq/Nim/issues/1774>`_). - - Fixed ``channels.tryRecv`` blocking - (`#1816 <https://github.com/Araq/Nim/issues/1816>`_). - - Fixed generic instantiation errors with ``typedesc`` - (`#419 <https://github.com/Araq/Nim/issues/419>`_). - - Fixed generic regression where the compiler no longer detected constant - expressions properly (`#544 <https://github.com/Araq/Nim/issues/544>`_). - - Fixed internal error with generic proc using ``static[T]`` in a specific - way (`#1049 <https://github.com/Araq/Nim/issues/1049>`_). - - More fixes relating to generics - (`#1820 <https://github.com/Araq/Nim/issues/1820>`_, - `#1050 <https://github.com/Araq/Nim/issues/1050>`_, - `#1859 <https://github.com/Araq/Nim/issues/1859>`_, - `#1858 <https://github.com/Araq/Nim/issues/1858>`_). - - Fixed httpclient to properly encode queries. - - Many fixes to the ``uri`` module. - - Async sockets are now closed on error. - - Fixes to httpclient's handling of multipart data. - - Fixed GC segfaults with asynchronous sockets - (`#1796 <https://github.com/Araq/Nim/issues/1796>`_). - - Added more versions to openssl's DLL version list - (`076f993 <https://github.com/Araq/Nim/commit/076f993>`_). - - Fixed shallow copy in iterators being broken - (`#1803 <https://github.com/Araq/Nim/issues/1803>`_). - - ``nil`` can now be inserted into tables with the ``db_sqlite`` module - (`#1866 <https://github.com/Araq/Nim/issues/1866>`_). - - Fixed "Incorrect assembler generated" - (`#1907 <https://github.com/Araq/Nim/issues/1907>`_) - - Fixed "Expression templates that define macros are unusable in some contexts" - (`#1903 <https://github.com/Araq/Nim/issues/1903>`_) - - Fixed "a second level generic subclass causes the compiler to crash" - (`#1919 <https://github.com/Araq/Nim/issues/1919>`_) - - Fixed "nim 0.10.2 generates invalid AsyncHttpClient C code for MSVC " - (`#1901 <https://github.com/Araq/Nim/issues/1901>`_) - - Fixed "1 shl n produces wrong C code" - (`#1928 <https://github.com/Araq/Nim/issues/1928>`_) - - Fixed "Internal error on tuple yield" - (`#1838 <https://github.com/Araq/Nim/issues/1838>`_) - - Fixed "ICE with template" - (`#1915 <https://github.com/Araq/Nim/issues/1915>`_) - - Fixed "include the tool directory in the installer as it is required by koch" - (`#1947 <https://github.com/Araq/Nim/issues/1947>`_) - - Fixed "Can't compile if file location contains spaces on Windows" - (`#1955 <https://github.com/Araq/Nim/issues/1955>`_) - - Fixed "List comprehension macro only supports infix checks as guards" - (`#1920 <https://github.com/Araq/Nim/issues/1920>`_) - - Fixed "wrong field names of compatible tuples in generic types" - (`#1910 <https://github.com/Araq/Nim/issues/1910>`_) - - Fixed "Macros within templates no longer work as expected" - (`#1944 <https://github.com/Araq/Nim/issues/1944>`_) - - Fixed "Compiling for Standalone AVR broken in 0.10.2" - (`#1964 <https://github.com/Araq/Nim/issues/1964>`_) - - Fixed "Compiling for Standalone AVR broken in 0.10.2" - (`#1964 <https://github.com/Araq/Nim/issues/1964>`_) - - Fixed "Code generation for mitems with tuple elements" - (`#1833 <https://github.com/Araq/Nim/issues/1833>`_) - - Fixed "httpclient.HttpMethod should not be an enum" - (`#1962 <https://github.com/Araq/Nim/issues/1962>`_) - - Fixed "terminal / eraseScreen() throws an OverflowError" - (`#1906 <https://github.com/Araq/Nim/issues/1906>`_) - - Fixed "setControlCHook(nil) disables registered quit procs" - (`#1546 <https://github.com/Araq/Nim/issues/1546>`_) - - Fixed "Unexpected idetools behaviour" - (`#325 <https://github.com/Araq/Nim/issues/325>`_) - - Fixed "Unused lifted lambda does not compile" - (`#1642 <https://github.com/Araq/Nim/issues/1642>`_) - - Fixed "'low' and 'high' don't work with cstring asguments" - (`#2030 <https://github.com/Araq/Nim/issues/2030>`_) - - Fixed "Converting to int does not round in JS backend" - (`#1959 <https://github.com/Araq/Nim/issues/1959>`_) - - Fixed "Internal error genRecordField 2 when adding region to pointer." - (`#2039 <https://github.com/Araq/Nim/issues/2039>`_) - - Fixed "Macros fail to compile when compiled with --os:standalone" - (`#2041 <https://github.com/Araq/Nim/issues/2041>`_) - - Fixed "Reading from {.compileTime.} variables can cause code generation to fail" - (`#2022 <https://github.com/Araq/Nim/issues/2022>`_) - - Fixed "Passing overloaded symbols to templates fails inside generic procedures" - (`#1988 <https://github.com/Araq/Nim/issues/1988>`_) - - Fixed "Compiling iterator with object assignment in release mode causes "var not init"" - (`#2023 <https://github.com/Araq/Nim/issues/2023>`_) - - Fixed "calling a large number of macros doing some computation fails" - (`#1989 <https://github.com/Araq/Nim/issues/1989>`_) - - Fixed "Can't get Koch to install nim under Windows" - (`#2061 <https://github.com/Araq/Nim/issues/2061>`_) - - Fixed "Template with two stmt parameters segfaults compiler" - (`#2057 <https://github.com/Araq/Nim/issues/2057>`_) - - Fixed "`noSideEffect` not affected by `echo`" - (`#2011 <https://github.com/Araq/Nim/issues/2011>`_) - - Fixed "Compiling with the cpp backend ignores --passc" - (`#1601 <https://github.com/Araq/Nim/issues/1601>`_) - - Fixed "Put untyped procedure parameters behind the experimental pragma" - (`#1956 <https://github.com/Araq/Nim/issues/1956>`_) - - Fixed "generic regression" - (`#2073 <https://github.com/Araq/Nim/issues/2073>`_) - - Fixed "generic regression" - (`#2073 <https://github.com/Araq/Nim/issues/2073>`_) - - Fixed "Regression in template lookup with generics" - (`#2004 <https://github.com/Araq/Nim/issues/2004>`_) - - Fixed "GC's growObj is wrong for edge cases" - (`#2070 <https://github.com/Araq/Nim/issues/2070>`_) - - Fixed "Compiler internal error when creating an array out of a typeclass" - (`#1131 <https://github.com/Araq/Nim/issues/1131>`_) - - Fixed "GC's growObj is wrong for edge cases" - (`#2070 <https://github.com/Araq/Nim/issues/2070>`_) - - Fixed "Invalid Objective-C code generated when calling class method" - (`#2068 <https://github.com/Araq/Nim/issues/2068>`_) - - Fixed "walkDirRec Error" - (`#2116 <https://github.com/Araq/Nim/issues/2116>`_) - - Fixed "Typo in code causes compiler SIGSEGV in evalAtCompileTime" - (`#2113 <https://github.com/Araq/Nim/issues/2113>`_) - - Fixed "Regression on exportc" - (`#2118 <https://github.com/Araq/Nim/issues/2118>`_) - - Fixed "Error message" - (`#2102 <https://github.com/Araq/Nim/issues/2102>`_) - - Fixed "hint[path] = off not working in nim.cfg" - (`#2103 <https://github.com/Araq/Nim/issues/2103>`_) - - Fixed "compiler crashes when getting a tuple from a sequence of generic tuples" - (`#2121 <https://github.com/Araq/Nim/issues/2121>`_) - - Fixed "nim check hangs with when" - (`#2123 <https://github.com/Araq/Nim/issues/2123>`_) - - Fixed "static[T] param in nested type resolve/caching issue" - (`#2125 <https://github.com/Araq/Nim/issues/2125>`_) - - Fixed "repr should display ``\0``" - (`#2124 <https://github.com/Araq/Nim/issues/2124>`_) - - Fixed "'nim check' never ends in case of recursive dependency " - (`#2051 <https://github.com/Araq/Nim/issues/2051>`_) - - Fixed "From macros: Error: unhandled exception: sons is not accessible" - (`#2167 <https://github.com/Araq/Nim/issues/2167>`_) - - Fixed "`fieldPairs` doesn't work inside templates" - (`#1902 <https://github.com/Araq/Nim/issues/1902>`_) - - Fixed "fields iterator misbehavior on break statement" - (`#2134 <https://github.com/Araq/Nim/issues/2134>`_) - - Fixed "Fix for compiler not building anymore since #c3244ef1ff" - (`#2193 <https://github.com/Araq/Nim/issues/2193>`_) - - Fixed "JSON parser fails in cpp output mode" - (`#2199 <https://github.com/Araq/Nim/issues/2199>`_) - - Fixed "macros.getType mishandles void return" - (`#2211 <https://github.com/Araq/Nim/issues/2211>`_) - - Fixed "Regression involving templates instantiated within generics" - (`#2215 <https://github.com/Araq/Nim/issues/2215>`_) - - Fixed ""Error: invalid type" for 'not nil' on generic type." - (`#2216 <https://github.com/Araq/Nim/issues/2216>`_) - - Fixed "--threads:on breaks async" - (`#2074 <https://github.com/Araq/Nim/issues/2074>`_) - - Fixed "Type mismatch not always caught, can generate bad code for C backend." - (`#2169 <https://github.com/Araq/Nim/issues/2169>`_) - - Fixed "Failed C compilation when storing proc to own type in object" - (`#2233 <https://github.com/Araq/Nim/issues/2233>`_) - - Fixed "Unknown line/column number in constant declaration type conversion error" - (`#2252 <https://github.com/Araq/Nim/issues/2252>`_) - - Fixed "Adding {.compile.} fails if nimcache already exists." - (`#2247 <https://github.com/Araq/Nim/issues/2247>`_) - - Fixed "Two different type names generated for a single type (C backend)" - (`#2250 <https://github.com/Araq/Nim/issues/2250>`_) - - Fixed "Ambigous call when it should not be" - (`#2229 <https://github.com/Araq/Nim/issues/2229>`_) - - Fixed "Make sure we can load root urls" - (`#2227 <https://github.com/Araq/Nim/issues/2227>`_) - - Fixed "Failure to slice a string with an int subrange type" - (`#794 <https://github.com/Araq/Nim/issues/794>`_) - - Fixed "documentation error" - (`#2205 <https://github.com/Araq/Nim/issues/2205>`_) - - Fixed "Code growth when using `const`" - (`#1940 <https://github.com/Araq/Nim/issues/1940>`_) - - Fixed "Instances of generic types confuse overload resolution" - (`#2220 <https://github.com/Araq/Nim/issues/2220>`_) - - Fixed "Compiler error when initializing sdl2's EventType" - (`#2316 <https://github.com/Araq/Nim/issues/2316>`_) - - Fixed "Parallel disjoint checking can't handle `<`, `items`, or arrays" - (`#2287 <https://github.com/Araq/Nim/issues/2287>`_) - - Fixed "Strings aren't copied in parallel loop" - (`#2286 <https://github.com/Araq/Nim/issues/2286>`_) - - Fixed "JavaScript compiler crash with tables" - (`#2298 <https://github.com/Araq/Nim/issues/2298>`_) - - Fixed "Range checker too restrictive" - (`#1845 <https://github.com/Araq/Nim/issues/1845>`_) - - Fixed "Failure to slice a string with an int subrange type" - (`#794 <https://github.com/Araq/Nim/issues/794>`_) - - Fixed "Remind user when compiling in debug mode" - (`#1868 <https://github.com/Araq/Nim/issues/1868>`_) - - Fixed "Compiler user guide has jumbled options/commands." - (`#1819 <https://github.com/Araq/Nim/issues/1819>`_) - - Fixed "using `method`: 1 in a objects constructor fails when compiling" - (`#1791 <https://github.com/Araq/Nim/issues/1791>`_) 2014-12-29 Version 0.10.2 released ================================== diff --git a/web/question.txt b/web/question.txt index 0733a2455..c4d997922 100644 --- a/web/question.txt +++ b/web/question.txt @@ -16,11 +16,11 @@ General FAQ language that tries to give the programmer ultimate power without compromises on runtime efficiency. This means it focuses on compile-time mechanisms in all their - various forms. Beneath a nice infix/indentation based syntax with a - powerful (AST based, hygienic) macro system lies a semantic model that supports - a soft realtime GC on thread local heaps. Asynchronous message passing is used - between threads, so no "stop the world" mechanism is necessary. An unsafe - shared memory heap is also provided for the increased efficiency that results + various forms. Beneath a nice infix/indentation based syntax with a + powerful (AST based, hygienic) macro system lies a semantic model that supports + a soft realtime GC on thread local heaps. Asynchronous message passing is used + between threads, so no "stop the world" mechanism is necessary. An unsafe + shared memory heap is also provided for the increased efficiency that results from that model. @@ -29,8 +29,8 @@ General FAQ Why yet another programming language? ------------------------------------- - Nim is one of the very few *programmable* statically typed languages, and - one of the even fewer that produces native binaries that require no + Nim is one of the very few *programmable* statically typed languages, and + one of the even fewer that produces native binaries that require no runtime or interpreter. @@ -48,12 +48,12 @@ General FAQ What is Nim's take on concurrency? ---------------------------------- - Nim primarily focusses on thread local (and garbage collected) heaps and - message passing between threads. Each thread has its own GC, so no + Nim primarily focusses on thread local (and garbage collected) heaps and + message passing between threads. Each thread has its own GC, so no "stop the world" mechanism is necessary. An unsafe shared memory heap is also provided. - Future versions will additionally include a GC "per thread group" + Future versions will additionally include a GC "per thread group" and Nim's type system will be enhanced to accurately model this shared memory heap. @@ -74,7 +74,7 @@ General FAQ ------------------ The compiler is in development and some important features are still missing. - However, the compiler is quite stable already: It is able to compile itself + However, the compiler is quite stable already: It is able to compile itself and a substantial body of other code. Until version 1.0.0 is released, minor incompatibilities with older versions of the compiler will be introduced. @@ -83,9 +83,9 @@ General FAQ How fast is Nim? ---------------- - Benchmarks show it to be comparable to C. Some language features (methods, + Benchmarks show it to be comparable to C. Some language features (methods, closures, message passing) are not yet as optimized as they could and will be. - The only overhead Nim has over C is the GC which has been tuned + The only overhead Nim has over C is the GC which has been tuned for years but still needs some work. @@ -96,7 +96,7 @@ General FAQ A JVM backend is almost impossible. The JVM is not expressive enough. It has never been designed as a general purpose VM anyway. A CLR backend is possible - but would require much work. + but would require much work. .. container:: standout @@ -112,6 +112,7 @@ General FAQ - jEdit: https://github.com/exhu/nimrod-misc/tree/master/jedit - TextMate: Available in bundle installer (`Repository <https://github.com/textmate/nim.tmbundle>`_) - Sublime Text: Available via Package Control (`Repository <https://github.com/Varriount/NimLime>`_) + - LiClipse: http://www.liclipse.com/ (Eclipse based plugin) .. container:: standout diff --git a/web/ticker.txt b/web/ticker.txt index 724d29231..4840e4039 100644 --- a/web/ticker.txt +++ b/web/ticker.txt @@ -1,13 +1,13 @@ +<a class="news" href="news.html#Z2015-05-04-version-0-11-2-released"> + <h4>May 4, 2015</h4> + <p>Nim version 0.11.2 has been released!</p> +</a> + <a class="news" href="news.html#Z2014-12-29-version-0-10-2-released"> <h4>Dec 29, 2014</h4> <p>Nim version 0.10.2 has been released!</p> </a> -<a class="news" href="news.html#Z2014-12-09-new-website-design"> - <h4>Dec 9, 2014</h4> - <p>The new website design and forum are now online!</p> -</a> - <a class="news" href="news.html#Z2014-02-11-nimrod-featured-in-dr-dobb-s-journal"> <h4>Feb 11, 2014</h4> <p>Nimrod featured in Dr. Dobb's Journal</p> diff --git a/web/website.ini b/web/website.ini index 6266f05bb..bb9f1b204 100644 --- a/web/website.ini +++ b/web/website.ini @@ -31,7 +31,7 @@ file: ticker.txt [Documentation] doc: "endb;intern;apis;lib;manual.txt;tut1;tut2;nimc;overview;filters" doc: "tools;niminst;nimgrep;gc;estp;idetools;docgen;koch;backends.txt" -doc: "nimfix.txt" +doc: "nimfix.txt;nimsuggest.txt" pdf: "manual.txt;lib;tut1;tut2;nimc;niminst;gc" srcdoc2: "system.nim" srcdoc2: "core/macros;pure/marshal;core/typeinfo;core/unsigned" @@ -49,7 +49,7 @@ srcdoc2: "pure/httpserver;pure/httpclient;pure/smtp;impure/ssl;pure/fsmonitor" srcdoc2: "pure/ropes;pure/unidecode/unidecode;pure/xmldom;pure/xmldomparser" srcdoc2: "pure/xmlparser;pure/htmlparser;pure/xmltree;pure/colors;pure/mimetypes" srcdoc2: "pure/json;pure/base64;pure/scgi;pure/redis;impure/graphics" -srcdoc2: "impure/rdstdin" +srcdoc2: "impure/rdstdin;impure/dialogs" srcdoc2: "pure/collections/tables;pure/collections/sets;pure/collections/lists" srcdoc2: "pure/collections/intsets;pure/collections/queues;pure/encodings" srcdoc2: "pure/events;pure/collections/sequtils;pure/cookies" @@ -60,7 +60,7 @@ srcdoc2: "packages/docutils/rst;packages/docutils/rstast" srcdoc2: "packages/docutils/rstgen;pure/logging;pure/asyncdispatch;pure/asyncnet" srcdoc2: "pure/rawsockets;pure/asynchttpserver;pure/net;pure/selectors;pure/future" srcdoc2: "pure/asyncfile" -srcdoc2: "pure/md5" +srcdoc2: "pure/md5;pure/rationals" srcdoc2: "posix/posix" srcdoc2: "pure/fenv" srcdoc2: "pure/basic2d;pure/basic3d" |