diff options
-rw-r--r-- | compiler/ccgcalls.nim | 38 | ||||
-rw-r--r-- | compiler/cgen.nim | 2 | ||||
-rw-r--r-- | compiler/installer.ini | 2 | ||||
-rw-r--r-- | compiler/jsgen.nim | 11 | ||||
-rw-r--r-- | compiler/platform.nim | 7 | ||||
-rw-r--r-- | compiler/pragmas.nim | 544 | ||||
-rw-r--r-- | compiler/sem.nim | 6 | ||||
-rw-r--r-- | compiler/semtypes.nim | 3 | ||||
-rw-r--r-- | compiler/vm.nim | 15 | ||||
-rw-r--r-- | compiler/vmgen.nim | 27 | ||||
-rw-r--r-- | lib/core/macros.nim | 18 | ||||
-rw-r--r-- | lib/pure/asyncdispatch.nim | 20 | ||||
-rw-r--r-- | lib/pure/asyncnet.nim | 2 | ||||
-rw-r--r-- | lib/pure/httpclient.nim | 2 | ||||
-rw-r--r-- | lib/pure/marshal.nim | 2 | ||||
-rw-r--r-- | lib/pure/unittest.nim | 144 | ||||
-rw-r--r-- | lib/system.nim | 19 | ||||
-rw-r--r-- | lib/system/platforms.nim | 6 | ||||
-rw-r--r-- | lib/system/repr.nim | 5 | ||||
-rw-r--r-- | lib/system/sets.nim | 6 | ||||
-rw-r--r-- | tests/async/tasyncconnect.nim | 33 | ||||
-rw-r--r-- | tests/async/tasynceverror.nim | 65 | ||||
-rw-r--r-- | tests/metatype/tstatic_ones.nim | 28 | ||||
-rw-r--r-- | tests/pragmas/tsym_as_pragma.nim | 8 | ||||
-rw-r--r-- | tests/stdlib/tunittest.nim | 14 | ||||
-rw-r--r-- | tests/template/t_otemplates.nim | 678 | ||||
-rw-r--r-- | tests/template/twrongsymkind.nim | 20 | ||||
-rw-r--r-- | web/news.txt | 2 |
28 files changed, 1037 insertions, 690 deletions
diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 2dacc25e9..86ecc9db8 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -159,6 +159,17 @@ proc genArgNoParam(p: BProc, n: PNode): Rope = initLocExprSingleUse(p, n, a) result = rdLoc(a) +template genParamLoop(params) {.dirty.} = + if i < sonsLen(typ): + assert(typ.n.sons[i].kind == nkSym) + let paramType = typ.n.sons[i] + if not paramType.typ.isCompileTimeOnly: + if params != nil: add(params, ~", ") + add(params, genArg(p, ri.sons[i], paramType.sym, ri)) + else: + if params != nil: add(params, ~", ") + add(params, genArgNoParam(p, ri.sons[i])) + proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) = var op: TLoc # this is a hotspot in the compiler @@ -170,13 +181,7 @@ proc genPrefixCall(p: BProc, le, ri: PNode, d: var TLoc) = assert(sonsLen(typ) == sonsLen(typ.n)) var length = sonsLen(ri) for i in countup(1, length - 1): - if ri.sons[i].typ.isCompileTimeOnly: continue - if params != nil: add(params, ~", ") - if i < sonsLen(typ): - assert(typ.n.sons[i].kind == nkSym) - add(params, genArg(p, ri.sons[i], typ.n.sons[i].sym, ri)) - else: - add(params, genArgNoParam(p, ri.sons[i])) + genParamLoop(params) fixupCall(p, le, ri, d, op.r, params) proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = @@ -198,13 +203,7 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = var length = sonsLen(ri) for i in countup(1, length - 1): assert(sonsLen(typ) == sonsLen(typ.n)) - if ri.sons[i].typ.isCompileTimeOnly: continue - if i < sonsLen(typ): - assert(typ.n.sons[i].kind == nkSym) - add(pl, genArg(p, ri.sons[i], typ.n.sons[i].sym, ri)) - else: - add(pl, genArgNoParam(p, ri.sons[i])) - if i < length - 1: add(pl, ~", ") + genParamLoop(pl) template genCallPattern {.dirty.} = lineF(p, cpsStmts, callPattern & ";$n", [op.r, pl, pl.addComma, rawProc]) @@ -241,13 +240,14 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = genCallPattern() proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope = - if ri.sons[i].typ.isCompileTimeOnly: - result = nil - elif i < sonsLen(typ): + if i < sonsLen(typ): # 'var T' is 'T&' in C++. This means we ignore the request of # any nkHiddenAddr when it's a 'var T'. - assert(typ.n.sons[i].kind == nkSym) - if typ.sons[i].kind == tyVar and ri.sons[i].kind == nkHiddenAddr: + let paramType = typ.n.sons[i] + assert(paramType.kind == nkSym) + if paramType.typ.isCompileTimeOnly: + result = nil + elif typ.sons[i].kind == tyVar and ri.sons[i].kind == nkHiddenAddr: result = genArgNoParam(p, ri.sons[i][0]) else: result = genArgNoParam(p, ri.sons[i]) #, typ.n.sons[i].sym) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index bfc80b2a1..2e95918cc 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -950,7 +950,7 @@ proc genMainProc(m: BModule) = gBreakpoints.add(m.genFilenames) let initStackBottomCall = - if platform.targetOS == osStandalone: "".rope + if platform.targetOS == osStandalone or gSelectedGC == gcNone: "".rope else: ropecg(m, "\t#initStackBottomWith((void *)&inner);$N") inc(m.labels) appcg(m, m.s[cfsProcs], PreMainBody, [ diff --git a/compiler/installer.ini b/compiler/installer.ini index 52a3d0886..c8af38886 100644 --- a/compiler/installer.ini +++ b/compiler/installer.ini @@ -6,7 +6,7 @@ Name: "Nim" Version: "$version" Platforms: """ windows: i386;amd64 - linux: i386;amd64;powerpc64;arm;sparc;mips;powerpc + linux: i386;amd64;powerpc64;arm;sparc;mips;mipsel;powerpc;powerpc64el;arm64 macosx: i386;amd64;powerpc64 solaris: i386;amd64;sparc freebsd: i386;amd64 diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index f32441d98..89a1b84a2 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -1045,9 +1045,18 @@ proc genArg(p: PProc, n: PNode, r: var TCompRes) = proc genArgs(p: PProc, n: PNode, r: var TCompRes) = add(r.res, "(") var hasArgs = false + + var typ = skipTypes(n.sons[0].typ, abstractInst) + assert(typ.kind == tyProc) + assert(sonsLen(typ) == sonsLen(typ.n)) + for i in countup(1, sonsLen(n) - 1): let it = n.sons[i] - if it.typ.isCompileTimeOnly: continue + if i < sonsLen(typ): + assert(typ.n.sons[i].kind == nkSym) + let paramType = typ.n.sons[i] + if paramType.typ.isCompileTimeOnly: continue + if hasArgs: add(r.res, ", ") genArg(p, it, r) hasArgs = true diff --git a/compiler/platform.nim b/compiler/platform.nim index 4dd5d8836..c4e7d453a 100644 --- a/compiler/platform.nim +++ b/compiler/platform.nim @@ -158,8 +158,8 @@ type TSystemCPU* = enum # Also add CPU for in initialization section and # alias conditionals to condsyms (end of module). cpuNone, cpuI386, cpuM68k, cpuAlpha, cpuPowerpc, cpuPowerpc64, - cpuSparc, cpuVm, cpuIa64, cpuAmd64, cpuMips, cpuArm, - cpuJS, cpuNimrodVM, cpuAVR + cpuPowerpc64el, cpuSparc, cpuVm, cpuIa64, cpuAmd64, cpuMips, cpuMipsel, + cpuArm, cpuArm64, cpuJS, cpuNimrodVM, cpuAVR type TEndian* = enum @@ -175,12 +175,15 @@ const (name: "alpha", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64), (name: "powerpc", intSize: 32, endian: bigEndian, floatSize: 64, bit: 32), (name: "powerpc64", intSize: 64, endian: bigEndian, floatSize: 64,bit: 64), + (name: "powerpc64el", intSize: 64, endian: littleEndian, floatSize: 64,bit: 64), (name: "sparc", intSize: 32, endian: bigEndian, floatSize: 64, bit: 32), (name: "vm", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32), (name: "ia64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64), (name: "amd64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64), (name: "mips", intSize: 32, endian: bigEndian, floatSize: 64, bit: 32), + (name: "mipsel", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32), (name: "arm", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32), + (name: "arm64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64), (name: "js", intSize: 32, endian: bigEndian,floatSize: 64,bit: 32), (name: "nimrodvm", intSize: 32, endian: bigEndian, floatSize: 64, bit: 32), (name: "avr", intSize: 16, endian: littleEndian, floatSize: 32, bit: 16)] diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index c771155af..128a90138 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -591,280 +591,282 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, validPragmas: TSpecialWords): bool = var it = n.sons[i] var key = if it.kind == nkExprColonExpr: it.sons[0] else: it - if key.kind == nkIdent: - var userPragma = strTableGet(c.userPragmas, key.ident) - if userPragma != nil: - inc c.instCounter - if c.instCounter > 100: - globalError(it.info, errRecursiveDependencyX, userPragma.name.s) - pragma(c, sym, userPragma.ast, validPragmas) - # ensure the pragma is also remember for generic instantiations in other - # modules: - n.sons[i] = userPragma.ast - dec c.instCounter - else: - var k = whichKeyword(key.ident) - if k in validPragmas: - case k - of wExportc: - makeExternExport(sym, getOptionalStr(c, it, "$1"), it.info) - incl(sym.flags, sfUsed) # avoid wrong hints - of wImportc: makeExternImport(sym, getOptionalStr(c, it, "$1")) - of wImportCompilerProc: - processImportCompilerProc(sym, getOptionalStr(c, it, "$1")) - of wExtern: setExternName(sym, expectStrLit(c, it)) - of wImmediate: - if sym.kind in {skTemplate, skMacro}: incl(sym.flags, sfImmediate) - else: invalidPragma(it) - of wDirty: - if sym.kind == skTemplate: incl(sym.flags, sfDirty) - else: invalidPragma(it) - of wImportCpp: - processImportCpp(sym, getOptionalStr(c, it, "$1")) - of wImportObjC: - processImportObjC(sym, getOptionalStr(c, it, "$1")) - of wAlign: - if sym.typ == nil: invalidPragma(it) - var align = expectIntLit(c, it) - if (not isPowerOfTwo(align) and align != 0) or align >% high(int16): - localError(it.info, errPowerOfTwoExpected) - else: - sym.typ.align = align.int16 - of wSize: - if sym.typ == nil: invalidPragma(it) - var size = expectIntLit(c, it) - if not isPowerOfTwo(size) or size <= 0 or size > 8: - localError(it.info, errPowerOfTwoExpected) - else: - sym.typ.size = size - of wNodecl: - noVal(it) - incl(sym.loc.flags, lfNoDecl) - of wPure, wAsmNoStackFrame: - noVal(it) - if sym != nil: - if k == wPure and sym.kind in routineKinds: invalidPragma(it) - else: incl(sym.flags, sfPure) - of wVolatile: - noVal(it) - incl(sym.flags, sfVolatile) - of wRegister: - noVal(it) - incl(sym.flags, sfRegister) - of wThreadVar: - noVal(it) - incl(sym.flags, sfThread) - of wDeadCodeElim: pragmaDeadCodeElim(c, it) - of wNoForward: pragmaNoForward(c, it) - of wMagic: processMagic(c, it, sym) - of wCompileTime: - noVal(it) - incl(sym.flags, sfCompileTime) - incl(sym.loc.flags, lfNoDecl) - of wGlobal: - noVal(it) - incl(sym.flags, sfGlobal) - incl(sym.flags, sfPure) - of wMerge: - # only supported for backwards compat, doesn't do anything anymore - noVal(it) - of wConstructor: - noVal(it) - incl(sym.flags, sfConstructor) - of wHeader: - var lib = getLib(c, libHeader, getStrLitNode(c, it)) - addToLib(lib, sym) - incl(sym.flags, sfImportc) - incl(sym.loc.flags, lfHeader) - incl(sym.loc.flags, lfNoDecl) - # implies nodecl, because otherwise header would not make sense - if sym.loc.r == nil: sym.loc.r = rope(sym.name.s) - of wDestructor: - sym.flags.incl sfOverriden - if sym.name.s.normalize != "destroy": - localError(n.info, errGenerated, "destructor has to be named 'destroy'") - of wOverride: - sym.flags.incl sfOverriden - of wNosideeffect: - noVal(it) - incl(sym.flags, sfNoSideEffect) - if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect) - of wSideeffect: - noVal(it) - incl(sym.flags, sfSideEffect) - of wNoreturn: - noVal(it) - incl(sym.flags, sfNoReturn) - of wDynlib: - processDynLib(c, it, sym) - of wCompilerproc: - noVal(it) # compilerproc may not get a string! - if sfFromGeneric notin sym.flags: markCompilerProc(sym) - of wProcVar: - noVal(it) - incl(sym.flags, sfProcvar) - of wDeprecated: - if it.kind == nkExprColonExpr: deprecatedStmt(c, it) - elif sym != nil: incl(sym.flags, sfDeprecated) - else: incl(c.module.flags, sfDeprecated) - of wVarargs: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfVarargs) - of wBorrow: - if sym.kind == skType: - typeBorrow(sym, it) - else: - noVal(it) - incl(sym.flags, sfBorrow) - of wFinal: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfFinal) - of wInheritable: - noVal(it) - if sym.typ == nil or tfFinal in sym.typ.flags: invalidPragma(it) - else: incl(sym.typ.flags, tfInheritable) - of wAcyclic: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfAcyclic) - of wShallow: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfShallow) - of wThread: - noVal(it) - incl(sym.flags, sfThread) - incl(sym.flags, sfProcvar) - if sym.typ != nil: incl(sym.typ.flags, tfThread) - of wGcSafe: - noVal(it) - if sym.kind != skType: incl(sym.flags, sfThread) - if sym.typ != nil: incl(sym.typ.flags, tfGcSafe) - else: invalidPragma(it) - of wPacked: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfPacked) - of wHint: message(it.info, hintUser, expectStrLit(c, it)) - of wWarning: message(it.info, warnUser, expectStrLit(c, it)) - of wError: - if sym != nil and sym.isRoutine: - # This is subtle but correct: the error *statement* is only - # allowed for top level statements. Seems to be easier than - # distinguishing properly between - # ``proc p() {.error}`` and ``proc p() = {.error: "msg".}`` - noVal(it) - incl(sym.flags, sfError) - else: - localError(it.info, errUser, expectStrLit(c, it)) - of wFatal: fatal(it.info, errUser, expectStrLit(c, it)) - of wDefine: processDefine(c, it) - of wUndef: processUndef(c, it) - of wCompile: processCompile(c, it) - of wLink: processCommonLink(c, it, linkNormal) - of wLinksys: processCommonLink(c, it, linkSys) - of wPassl: extccomp.addLinkOption(expectStrLit(c, it)) - of wPassc: extccomp.addCompileOption(expectStrLit(c, it)) - of wBreakpoint: pragmaBreakpoint(c, it) - of wWatchPoint: pragmaWatchpoint(c, it) - of wPush: - processPush(c, n, i + 1) - result = true - of wPop: processPop(c, it) - of wPragma: - processPragma(c, n, i) - result = true - of wDiscardable: - noVal(it) - if sym != nil: incl(sym.flags, sfDiscardable) - of wNoInit: - noVal(it) - if sym != nil: incl(sym.flags, sfNoInit) - of wCodegenDecl: processCodegenDecl(c, it, sym) - of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, - wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, - wLinedir, wStacktrace, wLinetrace, wOptimization, - wCallconv, - wDebugger, wProfiler, wFloatchecks, wNanChecks, wInfChecks, - wPatterns: - if processOption(c, it): - # calling conventions (boring...): - localError(it.info, errOptionExpected) - of FirstCallConv..LastCallConv: - assert(sym != nil) - if sym.typ == nil: invalidPragma(it) - else: sym.typ.callConv = wordToCallConv(k) - of wEmit: pragmaEmit(c, it) - of wUnroll: pragmaUnroll(c, it) - of wLinearScanEnd, wComputedGoto: noVal(it) - of wEffects: - # is later processed in effect analysis: - noVal(it) - of wIncompleteStruct: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfIncompleteStruct) - of wUnchecked: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfUncheckedArray) - of wUnion: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfUnion) - of wRequiresInit: - noVal(it) - if sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfNeedsInit) - of wByRef: - noVal(it) - if sym == nil or sym.typ == nil: - if processOption(c, it): localError(it.info, errOptionExpected) - else: - incl(sym.typ.flags, tfByRef) - of wByCopy: - noVal(it) - if sym.kind != skType or sym.typ == nil: invalidPragma(it) - else: incl(sym.typ.flags, tfByCopy) - of wInject, wGensym: - # We check for errors, but do nothing with these pragmas otherwise - # as they are handled directly in 'evalTemplate'. - noVal(it) - if sym == nil: invalidPragma(it) - of wLine: pragmaLine(c, it) - of wRaises, wTags: pragmaRaisesOrTags(c, it) - of wLocks: - if sym == nil: pragmaLockStmt(c, it) - elif sym.typ == nil: invalidPragma(it) - else: sym.typ.lockLevel = pragmaLocks(c, it) - of wGuard: - if sym == nil or sym.kind notin {skVar, skLet, skField}: - invalidPragma(it) - else: - sym.guard = pragmaGuard(c, it, sym.kind) - of wGoto: - if sym == nil or sym.kind notin {skVar, skLet}: - invalidPragma(it) - else: - sym.flags.incl sfGoto - of wInjectStmt: - if it.kind != nkExprColonExpr: - localError(it.info, errExprExpected) - else: - it.sons[1] = c.semExpr(c, it.sons[1]) - of wExperimental: - noVal(it) - if isTopLevel(c): - c.module.flags.incl sfExperimental - else: - localError(it.info, "'experimental' pragma only valid as toplevel statement") - of wNoRewrite: + if key.kind == nkBracketExpr: + processNote(c, it) + return + let ident = considerQuotedIdent(key) + var userPragma = strTableGet(c.userPragmas, ident) + if userPragma != nil: + inc c.instCounter + if c.instCounter > 100: + globalError(it.info, errRecursiveDependencyX, userPragma.name.s) + pragma(c, sym, userPragma.ast, validPragmas) + # ensure the pragma is also remember for generic instantiations in other + # modules: + n.sons[i] = userPragma.ast + dec c.instCounter + else: + var k = whichKeyword(ident) + if k in validPragmas: + case k + of wExportc: + makeExternExport(sym, getOptionalStr(c, it, "$1"), it.info) + incl(sym.flags, sfUsed) # avoid wrong hints + of wImportc: makeExternImport(sym, getOptionalStr(c, it, "$1")) + of wImportCompilerProc: + processImportCompilerProc(sym, getOptionalStr(c, it, "$1")) + of wExtern: setExternName(sym, expectStrLit(c, it)) + of wImmediate: + if sym.kind in {skTemplate, skMacro}: incl(sym.flags, sfImmediate) + else: invalidPragma(it) + of wDirty: + if sym.kind == skTemplate: incl(sym.flags, sfDirty) + else: invalidPragma(it) + of wImportCpp: + processImportCpp(sym, getOptionalStr(c, it, "$1")) + of wImportObjC: + processImportObjC(sym, getOptionalStr(c, it, "$1")) + of wAlign: + if sym.typ == nil: invalidPragma(it) + var align = expectIntLit(c, it) + if (not isPowerOfTwo(align) and align != 0) or align >% high(int16): + localError(it.info, errPowerOfTwoExpected) + else: + sym.typ.align = align.int16 + of wSize: + if sym.typ == nil: invalidPragma(it) + var size = expectIntLit(c, it) + if not isPowerOfTwo(size) or size <= 0 or size > 8: + localError(it.info, errPowerOfTwoExpected) + else: + sym.typ.size = size + of wNodecl: + noVal(it) + incl(sym.loc.flags, lfNoDecl) + of wPure, wAsmNoStackFrame: + noVal(it) + if sym != nil: + if k == wPure and sym.kind in routineKinds: invalidPragma(it) + else: incl(sym.flags, sfPure) + of wVolatile: + noVal(it) + incl(sym.flags, sfVolatile) + of wRegister: + noVal(it) + incl(sym.flags, sfRegister) + of wThreadVar: + noVal(it) + incl(sym.flags, sfThread) + of wDeadCodeElim: pragmaDeadCodeElim(c, it) + of wNoForward: pragmaNoForward(c, it) + of wMagic: processMagic(c, it, sym) + of wCompileTime: + noVal(it) + incl(sym.flags, sfCompileTime) + incl(sym.loc.flags, lfNoDecl) + of wGlobal: + noVal(it) + incl(sym.flags, sfGlobal) + incl(sym.flags, sfPure) + of wMerge: + # only supported for backwards compat, doesn't do anything anymore + noVal(it) + of wConstructor: + noVal(it) + incl(sym.flags, sfConstructor) + of wHeader: + var lib = getLib(c, libHeader, getStrLitNode(c, it)) + addToLib(lib, sym) + incl(sym.flags, sfImportc) + incl(sym.loc.flags, lfHeader) + incl(sym.loc.flags, lfNoDecl) + # implies nodecl, because otherwise header would not make sense + if sym.loc.r == nil: sym.loc.r = rope(sym.name.s) + of wDestructor: + sym.flags.incl sfOverriden + if sym.name.s.normalize != "destroy": + localError(n.info, errGenerated, "destructor has to be named 'destroy'") + of wOverride: + sym.flags.incl sfOverriden + of wNosideeffect: + noVal(it) + incl(sym.flags, sfNoSideEffect) + if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect) + of wSideeffect: + noVal(it) + incl(sym.flags, sfSideEffect) + of wNoreturn: + noVal(it) + incl(sym.flags, sfNoReturn) + of wDynlib: + processDynLib(c, it, sym) + of wCompilerproc: + noVal(it) # compilerproc may not get a string! + if sfFromGeneric notin sym.flags: markCompilerProc(sym) + of wProcVar: + noVal(it) + incl(sym.flags, sfProcvar) + of wDeprecated: + if it.kind == nkExprColonExpr: deprecatedStmt(c, it) + elif sym != nil: incl(sym.flags, sfDeprecated) + else: incl(c.module.flags, sfDeprecated) + of wVarargs: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfVarargs) + of wBorrow: + if sym.kind == skType: + typeBorrow(sym, it) + else: noVal(it) + incl(sym.flags, sfBorrow) + of wFinal: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfFinal) + of wInheritable: + noVal(it) + if sym.typ == nil or tfFinal in sym.typ.flags: invalidPragma(it) + else: incl(sym.typ.flags, tfInheritable) + of wAcyclic: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfAcyclic) + of wShallow: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfShallow) + of wThread: + noVal(it) + incl(sym.flags, sfThread) + incl(sym.flags, sfProcvar) + if sym.typ != nil: incl(sym.typ.flags, tfThread) + of wGcSafe: + noVal(it) + if sym.kind != skType: incl(sym.flags, sfThread) + if sym.typ != nil: incl(sym.typ.flags, tfGcSafe) else: invalidPragma(it) + of wPacked: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfPacked) + of wHint: message(it.info, hintUser, expectStrLit(c, it)) + of wWarning: message(it.info, warnUser, expectStrLit(c, it)) + of wError: + if sym != nil and sym.isRoutine: + # This is subtle but correct: the error *statement* is only + # allowed for top level statements. Seems to be easier than + # distinguishing properly between + # ``proc p() {.error}`` and ``proc p() = {.error: "msg".}`` + noVal(it) + incl(sym.flags, sfError) + else: + localError(it.info, errUser, expectStrLit(c, it)) + of wFatal: fatal(it.info, errUser, expectStrLit(c, it)) + of wDefine: processDefine(c, it) + of wUndef: processUndef(c, it) + of wCompile: processCompile(c, it) + of wLink: processCommonLink(c, it, linkNormal) + of wLinksys: processCommonLink(c, it, linkSys) + of wPassl: extccomp.addLinkOption(expectStrLit(c, it)) + of wPassc: extccomp.addCompileOption(expectStrLit(c, it)) + of wBreakpoint: pragmaBreakpoint(c, it) + of wWatchPoint: pragmaWatchpoint(c, it) + of wPush: + processPush(c, n, i + 1) + result = true + of wPop: processPop(c, it) + of wPragma: + processPragma(c, n, i) + result = true + of wDiscardable: + noVal(it) + if sym != nil: incl(sym.flags, sfDiscardable) + of wNoInit: + noVal(it) + if sym != nil: incl(sym.flags, sfNoInit) + of wCodegenDecl: processCodegenDecl(c, it, sym) + of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks, + wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints, + wLinedir, wStacktrace, wLinetrace, wOptimization, + wCallconv, + wDebugger, wProfiler, wFloatchecks, wNanChecks, wInfChecks, + wPatterns: + if processOption(c, it): + # calling conventions (boring...): + localError(it.info, errOptionExpected) + of FirstCallConv..LastCallConv: + assert(sym != nil) + if sym.typ == nil: invalidPragma(it) + else: sym.typ.callConv = wordToCallConv(k) + of wEmit: pragmaEmit(c, it) + of wUnroll: pragmaUnroll(c, it) + of wLinearScanEnd, wComputedGoto: noVal(it) + of wEffects: + # is later processed in effect analysis: + noVal(it) + of wIncompleteStruct: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfIncompleteStruct) + of wUnchecked: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfUncheckedArray) + of wUnion: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfUnion) + of wRequiresInit: + noVal(it) + if sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfNeedsInit) + of wByRef: + noVal(it) + if sym == nil or sym.typ == nil: + if processOption(c, it): localError(it.info, errOptionExpected) + else: + incl(sym.typ.flags, tfByRef) + of wByCopy: + noVal(it) + if sym.kind != skType or sym.typ == nil: invalidPragma(it) + else: incl(sym.typ.flags, tfByCopy) + of wInject, wGensym: + # We check for errors, but do nothing with these pragmas otherwise + # as they are handled directly in 'evalTemplate'. + noVal(it) + if sym == nil: invalidPragma(it) + of wLine: pragmaLine(c, it) + of wRaises, wTags: pragmaRaisesOrTags(c, it) + of wLocks: + if sym == nil: pragmaLockStmt(c, it) + elif sym.typ == nil: invalidPragma(it) + else: sym.typ.lockLevel = pragmaLocks(c, it) + of wGuard: + if sym == nil or sym.kind notin {skVar, skLet, skField}: + invalidPragma(it) + else: + sym.guard = pragmaGuard(c, it, sym.kind) + of wGoto: + if sym == nil or sym.kind notin {skVar, skLet}: + invalidPragma(it) + else: + sym.flags.incl sfGoto + of wInjectStmt: + if it.kind != nkExprColonExpr: + localError(it.info, errExprExpected) + else: + it.sons[1] = c.semExpr(c, it.sons[1]) + of wExperimental: + noVal(it) + if isTopLevel(c): + c.module.flags.incl sfExperimental + else: + localError(it.info, "'experimental' pragma only valid as toplevel statement") + of wNoRewrite: + noVal(it) else: invalidPragma(it) - else: processNote(c, it) + else: invalidPragma(it) proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = diff --git a/compiler/sem.nim b/compiler/sem.nim index d23dd1543..041524f84 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -171,11 +171,15 @@ proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym = result = newSym(kind, considerQuotedIdent(n), getCurrOwner(), n.info) proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = + proc `$`(kind: TSymKind): string = substr(system.`$`(kind), 2).toLower + # like newSymS, but considers gensym'ed symbols if n.kind == nkSym: # and sfGenSym in n.sym.flags: result = n.sym - internalAssert result.kind == kind + if result.kind != kind: + localError(n.info, "cannot use symbol of kind '" & + $result.kind & "' as a '" & $kind & "'") # when there is a nested proc inside a template, semtmpl # will assign a wrong owner during the first pass over the # template; we must fix it here: see #909 diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index b518f0fb9..3bb1284fc 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -23,6 +23,9 @@ proc newConstraint(c: PContext, k: TTypeKind): PType = proc semEnum(c: PContext, n: PNode, prev: PType): PType = if n.sonsLen == 0: return newConstraint(c, tyEnum) + elif n.sonsLen == 1: + # don't create an empty tyEnum; fixes #3052 + return errorType(c) var counter, x: BiggestInt e: PSym diff --git a/compiler/vm.nim b/compiler/vm.nim index 40d273ceb..d7495d77f 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -120,10 +120,10 @@ template decodeBx(k: expr) {.immediate, dirty.} = template move(a, b: expr) {.immediate, dirty.} = system.shallowCopy(a, b) # XXX fix minor 'shallowCopy' overloading bug in compiler -proc createStrKeepNode(x: var TFullReg) = +proc createStrKeepNode(x: var TFullReg; keepNode=true) = if x.node.isNil: x.node = newNode(nkStrLit) - elif x.node.kind == nkNilLit: + elif x.node.kind == nkNilLit and keepNode: when defined(useNodeIds): let id = x.node.id system.reset(x.node[]) @@ -385,6 +385,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = #if c.traceActive: # echo "PC ", pc, " ", c.code[pc].opcode, " ra ", ra # message(c.debug[pc], warnUser, "Trace") + case instr.opcode of opcEof: return regs[ra] of opcRet: @@ -407,8 +408,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = decodeB(rkInt) regs[ra].intVal = regs[rb].intVal of opcAsgnStr: - decodeB(rkNode) - createStrKeepNode regs[ra] + decodeBC(rkNode) + createStrKeepNode regs[ra], rc != 0 regs[ra].node.strVal = regs[rb].node.strVal of opcAsgnFloat: decodeB(rkFloat) @@ -509,8 +510,10 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of rkNode: if regs[rb].node.kind == nkNilLit: stackTrace(c, tos, pc, errNilAccess) - assert regs[rb].node.kind == nkRefTy - regs[ra].node = regs[rb].node.sons[0] + if regs[rb].node.kind == nkRefTy: + regs[ra].node = regs[rb].node.sons[0] + else: + stackTrace(c, tos, pc, errGenerated, "limited VM support for 'ref'") else: stackTrace(c, tos, pc, errNilAccess) of opcWrDeref: diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 7abcbdb92..1bee9788a 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -74,8 +74,9 @@ proc codeListing(c: PCtx, result: var string, start=0; last = -1) = result.addf("\t$#\tr$#, L$#", ($opc).substr(3), x.regA, i+x.regBx-wordExcess) elif opc in {opcLdConst, opcAsgnConst}: - result.addf("\t$#\tr$#, $#", ($opc).substr(3), x.regA, - c.constants[x.regBx-wordExcess].renderTree) + let idx = x.regBx-wordExcess + result.addf("\t$#\tr$#, $# ($#)", ($opc).substr(3), x.regA, + c.constants[idx].renderTree, $idx) elif opc in {opcMarshalLoad, opcMarshalStore}: let y = c.code[i+1] result.addf("\t$#\tr$#, r$#, $#", ($opc).substr(3), x.regA, x.regB, @@ -184,7 +185,7 @@ proc getTemp(cc: PCtx; typ: PType): TRegister = return TRegister(i) # if register pressure is high, we re-use more aggressively: - if c.maxSlots >= HighRegisterPressure: + if c.maxSlots >= HighRegisterPressure and false: for i in 0 .. c.maxSlots-1: if not c.slots[i].inUse: c.slots[i] = (inUse: true, kind: k) @@ -1073,6 +1074,7 @@ const tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64} proc fitsRegister*(t: PType): bool = + assert t != nil t.skipTypes(abstractInst-{tyTypeDesc}).kind in { tyRange, tyEnum, tyBool, tyInt..tyUInt64, tyChar} @@ -1110,6 +1112,7 @@ proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; if dest < 0: dest = c.getTemp(n.typ) if not isAddr: gABC(c, n, opc, dest, tmp) + assert n.typ != nil if gfAddrOf notin flags and fitsRegister(n.typ): c.gABC(n, opcNodeToReg, dest, dest) elif c.prc.slots[tmp].kind >= slotTempUnknown: @@ -1143,7 +1146,7 @@ proc whichAsgnOpc(n: PNode; opc: TOpcode): TOpcode = opc proc genAsgn(c: PCtx; dest: TDest; ri: PNode; requiresCopy: bool) = let tmp = c.genx(ri) assert dest >= 0 - gABC(c, ri, whichAsgnOpc(ri), dest, tmp) + gABC(c, ri, whichAsgnOpc(ri), dest, tmp, 1-ord(requiresCopy)) c.freeTemp(tmp) proc setSlot(c: PCtx; v: PSym) = @@ -1194,9 +1197,10 @@ proc preventFalseAlias(c: PCtx; n: PNode; opc: TOpcode; # opcLdObj et al really means "load address". We sometimes have to create a # copy in order to not introduce false aliasing: # mylocal = a.b # needs a copy of the data! + assert n.typ != nil if needsAdditionalCopy(n): var cc = c.getTemp(n.typ) - c.gABC(n, whichAsgnOpc(n), cc, value) + c.gABC(n, whichAsgnOpc(n), cc, value, 0) c.gABC(n, opc, dest, idx, cc) c.freeTemp(cc) else: @@ -1241,10 +1245,11 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) = internalAssert s.position > 0 or (s.position == 0 and s.kind in {skParam,skResult}) var dest: TRegister = s.position + ord(s.kind == skParam) + assert le.typ != nil if needsAdditionalCopy(le) and s.kind in {skResult, skVar, skParam}: var cc = c.getTemp(le.typ) gen(c, ri, cc) - c.gABC(le, whichAsgnOpc(le), dest, cc) + c.gABC(le, whichAsgnOpc(le), dest, cc, 1) c.freeTemp(cc) else: gen(c, ri, dest) @@ -1303,6 +1308,7 @@ proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = if sfImportc in s.flags: c.importcSym(n.info, s) else: genGlobalInit(c, n, s) if dest < 0: dest = c.getTemp(n.typ) + assert s.typ != nil if gfAddrOf notin flags and fitsRegister(s.typ): var cc = c.getTemp(n.typ) c.gABx(n, opcLdGlobal, cc, s.position) @@ -1426,6 +1432,7 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode = globalError(info, "cannot create null element for: " & $t.kind) proc ldNullOpcode(t: PType): TOpcode = + assert t != nil if fitsRegister(t): opcLdNullReg else: opcLdNull proc genVarSection(c: PCtx; n: PNode) = @@ -1453,7 +1460,7 @@ proc genVarSection(c: PCtx; n: PNode) = if a.sons[2].kind != nkEmpty: let tmp = c.genx(a.sons[0], {gfAddrOf}) let val = c.genx(a.sons[2]) - c.preventFalseAlias(a, opcWrDeref, tmp, 0, val) + c.preventFalseAlias(a.sons[2], opcWrDeref, tmp, 0, val) c.freeTemp(val) c.freeTemp(tmp) else: @@ -1461,13 +1468,15 @@ proc genVarSection(c: PCtx; n: PNode) = if a.sons[2].kind == nkEmpty: c.gABx(a, ldNullOpcode(s.typ), s.position, c.genType(s.typ)) else: + assert s.typ != nil if not fitsRegister(s.typ): c.gABx(a, ldNullOpcode(s.typ), s.position, c.genType(s.typ)) let le = a.sons[0] + assert le.typ != nil if not fitsRegister(le.typ) and s.kind in {skResult, skVar, skParam}: var cc = c.getTemp(le.typ) gen(c, a.sons[2], cc) - c.gABC(le, whichAsgnOpc(le), s.position.TRegister, cc) + c.gABC(le, whichAsgnOpc(le), s.position.TRegister, cc, 1) c.freeTemp(cc) else: gen(c, a.sons[2], s.position.TRegister) @@ -1694,7 +1703,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = c.freeTemp(tmp1) c.freeTemp(tmp2) if dest >= 0: - gABC(c, n, whichAsgnOpc(n), dest, tmp0) + gABC(c, n, whichAsgnOpc(n), dest, tmp0, 1) c.freeTemp(tmp0) else: dest = tmp0 diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 7d1c15610..c89fa354a 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -592,10 +592,8 @@ proc newNilLit*(): NimNode {.compileTime.} = ## New nil literal shortcut result = newNimNode(nnkNilLit) -proc high*(node: NimNode): int {.compileTime.} = len(node) - 1 - ## Return the highest index available for a node -proc last*(node: NimNode): NimNode {.compileTime.} = node[node.high] - ## Return the last item in nodes children. Same as `node[node.high()]` +proc last*(node: NimNode): NimNode {.compileTime.} = node[<node.len] + ## Return the last item in nodes children. Same as `node[^1]` const @@ -695,7 +693,7 @@ proc `body=`*(someProc: NimNode, val: NimNode) {.compileTime.} = of nnkBlockStmt, nnkWhileStmt: someProc[1] = val of nnkForStmt: - someProc[high(someProc)] = val + someProc[len(someProc)-1] = val else: badNodeKind someProc.kind, "body=" @@ -722,7 +720,7 @@ proc ident*(name: string): NimNode {.compileTime,inline.} = newIdentNode(name) ## Create a new ident node from a string iterator children*(n: NimNode): NimNode {.inline.}= - for i in 0 .. high(n): + for i in 0 ..< n.len: yield n[i] template findChild*(n: NimNode; cond: expr): NimNode {. @@ -742,16 +740,16 @@ template findChild*(n: NimNode; cond: expr): NimNode {. proc insert*(a: NimNode; pos: int; b: NimNode) {.compileTime.} = ## Insert node B into A at pos - if high(a) < pos: + if len(a)-1 < pos: ## add some empty nodes first - for i in high(a)..pos-2: + for i in len(a)-1..pos-2: a.add newEmptyNode() a.add b else: ## push the last item onto the list again ## and shift each item down to pos up one - a.add(a[a.high]) - for i in countdown(high(a) - 2, pos): + a.add(a[a.len-1]) + for i in countdown(len(a) - 3, pos): a[i + 1] = a[i] a[pos] = b diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 2ce31b4e8..7523b29d5 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -972,9 +972,9 @@ else: let data = PData(info.key.data) assert data.fd == info.key.fd.AsyncFD #echo("In poll ", data.fd.cint) - if EvError in info.events: - closeSocket(data.fd) - continue + # There may be EvError here, but we handle them in callbacks, + # so that exceptions can be raised from `send(...)` and + # `recv(...)` routines. if EvRead in info.events: # Callback may add items to ``data.readCBs`` which causes issues if @@ -1013,9 +1013,17 @@ else: var retFuture = newFuture[void]("connect") proc cb(fd: AsyncFD): bool = - # We have connected. - retFuture.complete() - return true + var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR)) + if ret == 0: + # We have connected. + retFuture.complete() + return true + elif ret == EINTR: + # interrupted, keep waiting + return false + else: + retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret)))) + return true assert getSockDomain(socket.SocketHandle) == domain var aiList = getAddrInfo(address, port, domain) diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index cfd3d7666..9139200f3 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -453,6 +453,7 @@ proc close*(socket: AsyncSocket) = when defined(ssl): if socket.isSSL: let res = SslShutdown(socket.sslHandle) + SSLFree(socket.sslHandle) if res == 0: discard elif res != 1: @@ -567,4 +568,3 @@ when not defined(testing) and isMainModule: var f = accept(sock) f.callback = onAccept runForever() - diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index e6b8088c5..98687359b 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -402,7 +402,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", headers.add(" HTTP/1.1\c\L") - add(headers, "Host: " & r.hostname & "\c\L") + add(headers, "Host: " & parseUri(url).hostname & "\c\L") if userAgent != "": add(headers, "User-Agent: " & userAgent & "\c\L") if proxy != nil and proxy.auth != "": diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index 49f049e46..173cd1e81 100644 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -17,7 +17,7 @@ ## .. code-block:: nim ## ## type -## A = object +## A = object of RootObj ## B = object of A ## f: int ## diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index de52cbbd1..c459023a9 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -9,7 +9,7 @@ ## :Author: Zahary Karadjov ## -## This module implements boilerplate to make testing easy. +## This module implements boilerplate to make unit testing easy. ## ## Example: ## @@ -41,27 +41,69 @@ when not defined(ECMAScript): import terminal type - TestStatus* = enum OK, FAILED - OutputLevel* = enum PRINT_ALL, PRINT_FAILURES, PRINT_NONE + TestStatus* = enum OK, FAILED ## The status of a test when it is done. + OutputLevel* = enum ## The output verbosity of the tests. + PRINT_ALL, ## Print as much as possible. + PRINT_FAILURES, ## Print only the failed tests. + PRINT_NONE ## Print nothing. {.deprecated: [TTestStatus: TestStatus, TOutputLevel: OutputLevel]} -var - abortOnError* {.threadvar.}: bool - outputLevel* {.threadvar.}: OutputLevel - colorOutput* {.threadvar.}: bool +var ## Global unittest settings! + + abortOnError* {.threadvar.}: bool ## Set to true in order to quit + ## immediately on fail. Default is false, + ## unless the ``NIMTEST_ABORT_ON_ERROR`` + ## environment variable is set for + ## the non-js target. + outputLevel* {.threadvar.}: OutputLevel ## Set the verbosity of test results. + ## Default is ``PRINT_ALL``, unless + ## the ``NIMTEST_OUTPUT_LVL`` environment + ## variable is set for the non-js target. + + colorOutput* {.threadvar.}: bool ## Have test results printed in color. + ## Default is true for the non-js target + ## unless, the environment variable + ## ``NIMTEST_NO_COLOR`` is set. checkpoints {.threadvar.}: seq[string] checkpoints = @[] -template testSetupIMPL*: stmt {.immediate, dirty.} = discard +template testSetupIMPL*: stmt {.immediate, dirty.} = discard #Should this be public or even exist? template testTeardownIMPL*: stmt {.immediate, dirty.} = discard proc shouldRun(testName: string): bool = result = true template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} = + ## Declare a test suite identified by `name` with optional ``setup`` + ## and/or ``teardown`` section. + ## + ## A test suite is a series of one or more related tests sharing a + ## common fixture (``setup``, ``teardown``). The fixture is executed + ## for EACH test. + ## + ## .. code-block:: nim + ## suite "test suite for addition": + ## setup: + ## let result = 4 + ## + ## test "2 + 2 = 4": + ## check(2+2 == result) + ## + ## test "2 + -2 != 4": + ## check(2+2 != result) + ## + ## # No teardown needed + ## + ## The suite will run the individual test cases in the order in which + ## they were listed. With default global settings the above code prints: + ## + ## .. code-block:: + ## + ## [OK] 2 + 2 = 4 + ## [OK] (2 + -2) != 4 block: template setup(setupBody: stmt): stmt {.immediate, dirty.} = template testSetupIMPL: stmt {.immediate, dirty.} = setupBody @@ -87,6 +129,19 @@ proc testDone(name: string, s: TestStatus) = rawPrint() template test*(name: expr, body: stmt): stmt {.immediate, dirty.} = + ## Define a single test case identified by `name`. + ## + ## .. code-block:: nim + ## + ## test "roses are red": + ## let roses = "red" + ## check(roses == "red") + ## + ## The above code outputs: + ## + ## .. code-block:: + ## + ## [OK] roses are red bind shouldRun, checkpoints, testDone if shouldRun(name): @@ -108,10 +163,32 @@ template test*(name: expr, body: stmt): stmt {.immediate, dirty.} = testDone name, testStatusIMPL proc checkpoint*(msg: string) = + ## Set a checkpoint identified by `msg`. Upon test failure all + ## checkpoints encountered so far are printed out. Example: + ## + ## .. code-block:: nim + ## + ## checkpoint("Checkpoint A") + ## check((42, "the Answer to life and everything") == (1, "a")) + ## checkpoint("Checkpoint B") + ## + ## outputs "Checkpoint A" once it fails. checkpoints.add(msg) # TODO: add support for something like SCOPED_TRACE from Google Test template fail* = + ## Print out the checkpoints encountered so far and quit if ``abortOnError`` + ## is true. Otherwise, erase the checkpoints and indicate the test has + ## failed (change exit code and test status). This template is useful + ## for debugging, but is otherwise mostly used internally. Example: + ## + ## .. code-block:: nim + ## + ## checkpoint("Checkpoint A") + ## complicatedProcInThread() + ## fail() + ## + ## outputs "Checkpoint A" before quitting. bind checkpoints for msg in items(checkpoints): echo msg @@ -127,8 +204,23 @@ template fail* = checkpoints = @[] macro check*(conditions: stmt): stmt {.immediate.} = + ## Verify if a statement or a list of statements is true. + ## A helpful error message and set checkpoints are printed out on + ## failure (if ``outputLevel`` is not ``PRINT_NONE``). + ## Example: + ## + ## .. code-block:: nim + ## + ## import strutils + ## + ## check("AKB48".toLower() == "akb48") + ## + ## let teams = {'A', 'K', 'B', '4', '8'} + ## + ## check: + ## "AKB48".toLower() == "akb48" + ## 'C' in teams let checked = callsite()[1] - var argsAsgns = newNimNode(nnkStmtList) argsPrintOuts = newNimNode(nnkStmtList) @@ -143,7 +235,7 @@ macro check*(conditions: stmt): stmt {.immediate.} = checkpoint(name & " was " & $value) proc inspectArgs(exp: NimNode) = - for i in 1 .. <exp.len: + for i in countup(1, exp.len - 1): if exp[i].kind notin nnkLiterals: inc counter var arg = newIdentNode(":p" & $counter) @@ -194,11 +286,34 @@ macro check*(conditions: stmt): stmt {.immediate.} = result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit)) template require*(conditions: stmt): stmt {.immediate, dirty.} = + ## Same as `check` except any failed test causes the program to quit + ## immediately. Any teardown statements are not executed and the failed + ## test output is not generated. + let savedAbortOnError = abortOnError block: - const AbortOnError {.inject.} = true + abortOnError = true check conditions + abortOnError = savedAbortOnError macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} = + ## Test if `body` raises an exception found in the passed `exceptions`. + ## The test passes if the raised exception is part of the acceptable + ## exceptions. Otherwise, it fails. + ## Example: + ## + ## .. code-block:: nim + ## + ## import math + ## proc defectiveRobot() = + ## randomize() + ## case random(1..4) + ## of 1: raise newException(OSError, "CANNOT COMPUTE!") + ## of 2: discard parseInt("Hello World!") + ## of 3: raise newException(IOError, "I can't do that Dave.") + ## else: assert 2 + 2 == 5 + ## + ## expect IOError, OSError, ValueError, AssertionError: + ## defectiveRobot() let exp = callsite() template expectBody(errorTypes, lineInfoLit: expr, body: stmt): NimNode {.dirty.} = @@ -208,6 +323,9 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} = fail() except errorTypes: discard + except: + checkpoint(lineInfoLit & ": Expect Failed, unexpected exception was thrown.") + fail() var body = exp[exp.len - 1] @@ -219,9 +337,9 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} = when declared(stdout): - ## Reading settings + # Reading settings + # On a terminal this branch is executed var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string - abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR") colorOutput = not existsEnv("NIMTEST_NO_COLOR") diff --git a/lib/system.nim b/lib/system.nim index c5b0e0cc7..dd1a0721a 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -176,10 +176,16 @@ proc new*[T](a: var ref T) {.magic: "New", noSideEffect.} ## creates a new object of type ``T`` and returns a safe (traced) ## reference to it in ``a``. -proc new*(T: typedesc): ref T = +proc new*(T: typedesc): auto = ## creates a new object of type ``T`` and returns a safe (traced) ## reference to it as result value - new(result) + when (T is ref): + var r: T + else: + var r: ref T + new(r) + return r + proc internalNew*[T](a: var ref T) {.magic: "New", noSideEffect.} ## leaked implementation detail. Do not use. @@ -1150,7 +1156,8 @@ const hostCPU* {.magic: "HostCPU".}: string = "" ## a string that describes the host CPU. Possible values: - ## "i386", "alpha", "powerpc", "powerpc64", "sparc", "amd64", "mips", "arm". + ## "i386", "alpha", "powerpc", "powerpc64", "powerpc64el", "sparc", + ## "amd64", "mips", "mipsel", "arm", "arm64". seqShallowFlag = low(int) @@ -2173,7 +2180,11 @@ proc `$`*[T: tuple|object](x: T): string = if not firstElement: result.add(", ") result.add(name) result.add(": ") - result.add($value) + when compiles(value.isNil): + if value.isNil: result.add "nil" + else: result.add($value) + else: + result.add($value) firstElement = false result.add(")") diff --git a/lib/system/platforms.nim b/lib/system/platforms.nim index 47a01d5fe..0a7045fae 100644 --- a/lib/system/platforms.nim +++ b/lib/system/platforms.nim @@ -18,11 +18,14 @@ type alpha, ## Alpha processor powerpc, ## 32 bit PowerPC powerpc64, ## 64 bit PowerPC + powerpc64el, ## Little Endian 64 bit PowerPC sparc, ## Sparc based processor ia64, ## Intel Itanium amd64, ## x86_64 (AMD64); 64 bit x86 compatible CPU mips, ## Mips based processor + mipsel, ## Little Endian Mips based processor arm, ## ARM based processor + arm64, ## ARM64 based processor vm, ## Some Virtual machine: Nim's VM or JavaScript avr ## AVR based processor @@ -63,11 +66,14 @@ const elif defined(alpha): CpuPlatform.alpha elif defined(powerpc): CpuPlatform.powerpc elif defined(powerpc64): CpuPlatform.powerpc64 + elif defined(powerpc64el): CpuPlatform.powerpc64el elif defined(sparc): CpuPlatform.sparc elif defined(ia64): CpuPlatform.ia64 elif defined(amd64): CpuPlatform.amd64 elif defined(mips): CpuPlatform.mips + elif defined(mipsel): CpuPlatform.mipsel elif defined(arm): CpuPlatform.arm + elif defined(arm64): CpuPlatform.arm64 elif defined(vm): CpuPlatform.vm elif defined(avr): CpuPlatform.avr else: CpuPlatform.none diff --git a/lib/system/repr.nim b/lib/system/repr.nim index 74396f424..b4188527f 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -256,7 +256,10 @@ when not defined(useNimRtl): of tyBool: add result, reprBool(cast[ptr bool](p)[]) of tyChar: add result, reprChar(cast[ptr char](p)[]) of tyString: reprStrAux(result, cast[ptr string](p)[]) - of tyCString: reprStrAux(result, $(cast[ptr cstring](p)[])) + of tyCString: + let cs = cast[ptr cstring](p)[] + if cs.isNil: add result, "nil" + else: reprStrAux(result, $cs) of tyRange: reprAux(result, p, typ.base, cl) of tyProc, tyPointer: if cast[PPointer](p)[] == nil: add result, "nil" diff --git a/lib/system/sets.nim b/lib/system/sets.nim index 22d6d57c0..66877de30 100644 --- a/lib/system/sets.nim +++ b/lib/system/sets.nim @@ -19,9 +19,9 @@ proc countBits32(n: int32): int {.compilerproc.} = v = (v and 0x33333333'i32) +% ((v shr 2'i32) and 0x33333333'i32) result = ((v +% (v shr 4'i32) and 0xF0F0F0F'i32) *% 0x1010101'i32) shr 24'i32 -proc countBits64(n: int64): int {.compilerproc.} = - result = countBits32(toU32(n and 0xffff'i64)) + - countBits32(toU32(n shr 16'i64)) +proc countBits64(n: int64): int {.compilerproc.} = + result = countBits32(toU32(n and 0xffffffff'i64)) + + countBits32(toU32(n shr 32'i64)) proc cardSet(s: NimSet, len: int): int {.compilerproc.} = result = 0 diff --git a/tests/async/tasyncconnect.nim b/tests/async/tasyncconnect.nim new file mode 100644 index 000000000..bc63b8e82 --- /dev/null +++ b/tests/async/tasyncconnect.nim @@ -0,0 +1,33 @@ +discard """ + file: "tasyncconnect.nim" + exitcode: 1 + outputsub: "Error: unhandled exception: Connection refused [Exception]" +""" + +import + asyncdispatch, + posix + + +const + testHost = "127.0.0.1" + testPort = Port(17357) + + +when defined(windows) or defined(nimdoc): + discard +else: + proc testAsyncConnect() {.async.} = + var s = newAsyncRawSocket() + + await s.connect(testHost, testPort) + + var peerAddr: SockAddr + var addrSize = Socklen(sizeof(peerAddr)) + var ret = SocketHandle(s).getpeername(addr(peerAddr), addr(addrSize)) + + if ret < 0: + echo("`connect(...)` failed but no exception was raised.") + quit(2) + + waitFor(testAsyncConnect()) diff --git a/tests/async/tasynceverror.nim b/tests/async/tasynceverror.nim new file mode 100644 index 000000000..3b81680cb --- /dev/null +++ b/tests/async/tasynceverror.nim @@ -0,0 +1,65 @@ +discard """ + file: "tasynceverror.nim" + exitcode: 1 + outputsub: "Error: unhandled exception: Connection reset by peer [Exception]" +""" + +import + asyncdispatch, + asyncnet, + rawsockets, + os + + +const + testHost = "127.0.0.1" + testPort = Port(17357) + + +when defined(windows) or defined(nimdoc): + discard +else: + proc createListenSocket(host: string, port: Port): TAsyncFD = + result = newAsyncRawSocket() + + SocketHandle(result).setSockOptInt(SOL_SOCKET, SO_REUSEADDR, 1) + + var aiList = getAddrInfo(host, port, AF_INET) + if SocketHandle(result).bindAddr(aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32: + dealloc(aiList) + raiseOSError(osLastError()) + dealloc(aiList) + + if SocketHandle(result).listen(1) < 0'i32: + raiseOSError(osLastError()) + + + proc testAsyncSend() {.async.} = + var + ls = createListenSocket(testHost, testPort) + s = newAsyncSocket() + + await s.connect(testHost, testPort) + + var ps = await ls.accept() + SocketHandle(ls).close() + + await ps.send("test 1", flags={}) + s.close() + # This send should raise EPIPE + await ps.send("test 2", flags={}) + SocketHandle(ps).close() + + + # The bug was, when the poll function handled EvError for us, + # our callbacks may never get executed, thus making the event + # loop block indefinitely. This is a timer to keep everything + # rolling. 400 ms is an arbitrary value, should be enough though. + proc timer() {.async.} = + await sleepAsync(400) + echo("Timer expired.") + quit(2) + + + asyncCheck(testAsyncSend()) + waitFor(timer()) diff --git a/tests/metatype/tstatic_ones.nim b/tests/metatype/tstatic_ones.nim new file mode 100644 index 000000000..73a88447d --- /dev/null +++ b/tests/metatype/tstatic_ones.nim @@ -0,0 +1,28 @@ +discard """ + output: "@[2, 2, 2, 2, 2]" +""" + +# bug #3144 + +type IntArray[N: static[int]] = array[N, int] + +proc `$`(a: IntArray): string = $(@(a)) + +proc `+=`[N: static[int]](a: var IntArray[N], b: IntArray[N]) = + for i in 0 .. < N: + a[i] += b[i] + +proc zeros(N: static[int]): IntArray[N] = + for i in 0 .. < N: + result[i] = 0 + +proc ones(N: static[int]): IntArray[N] = + for i in 0 .. < N: + result[i] = 1 + +proc sum[N: static[int]](vs: seq[IntArray[N]]): IntArray[N] = + result = zeros(N) + for v in vs: + result += v + +echo sum(@[ones(5), ones(5)]) diff --git a/tests/pragmas/tsym_as_pragma.nim b/tests/pragmas/tsym_as_pragma.nim new file mode 100644 index 000000000..f6ba44dc9 --- /dev/null +++ b/tests/pragmas/tsym_as_pragma.nim @@ -0,0 +1,8 @@ + +# bug #3171 + +template newDataWindow(): stmt = + let eventClosure = proc (closure: pointer): bool {.closure, cdecl.} = + discard + +newDataWindow() diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index b23b3cdab..fb9b02243 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -19,3 +19,17 @@ import options test "unittest typedescs": check(none(int) == none(int)) check(none(int) != some(1)) + + +import math +from strutils import parseInt +proc defectiveRobot() = + randomize() + case random(1..4) + of 1: raise newException(OSError, "CANNOT COMPUTE!") + of 2: discard parseInt("Hello World!") + of 3: raise newException(IOError, "I can't do that Dave.") + else: assert 2 + 2 == 5 +test "unittest expect": + expect IOError, OSError, ValueError, AssertionError: + defectiveRobot() diff --git a/tests/template/t_otemplates.nim b/tests/template/t_otemplates.nim index db535d818..6c419f72f 100644 --- a/tests/template/t_otemplates.nim +++ b/tests/template/t_otemplates.nim @@ -2,344 +2,344 @@ discard """ output: "Success" """ -# Ref: -# http://nim-lang.org/macros.html -# http://nim-lang.org/parseutils.html - - -# Imports -import tables, parseutils, macros, strutils -import annotate -export annotate - - -# Fields -const identChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'} - - -# Procedure Declarations -proc parse_template(node: NimNode, value: string) {.compiletime.} - - -# Procedure Definitions -proc substring(value: string, index: int, length = -1): string {.compiletime.} = - ## Returns a string at most `length` characters long, starting at `index`. - return if length < 0: value.substr(index) - elif length == 0: "" - else: value.substr(index, index + length-1) - - -proc parse_thru_eol(value: string, index: int): int {.compiletime.} = - ## Reads until and past the end of the current line, unless - ## a non-whitespace character is encountered first - var remainder: string - var read = value.parseUntil(remainder, {0x0A.char}, index) - if remainder.skipWhitespace() == read: - return read + 1 - - -proc trim_after_eol(value: var string) {.compiletime.} = - ## Trims any whitespace at end after \n - var toTrim = 0 - for i in countdown(value.len-1, 0): - # If \n, return - if value[i] in [' ', '\t']: inc(toTrim) - else: break - - if toTrim > 0: - value = value.substring(0, value.len - toTrim) - - -proc trim_eol(value: var string) {.compiletime.} = - ## Removes everything after the last line if it contains nothing but whitespace - for i in countdown(value.len - 1, 0): - # If \n, trim and return - if value[i] == 0x0A.char: - value = value.substr(0, i) - break - - # This is the first character - if i == 0: - value = "" - break - - # Skip change - if not (value[i] in [' ', '\t']): break - - -proc detect_indent(value: string, index: int): int {.compiletime.} = - ## Detects how indented the line at `index` is. - # Seek to the beginning of the line. - var lastChar = index - for i in countdown(index, 0): - if value[i] == 0x0A.char: - # if \n, return the indentation level - return lastChar - i - elif not (value[i] in [' ', '\t']): - # if non-whitespace char, decrement lastChar - dec(lastChar) - - -proc parse_thru_string(value: string, i: var int, strType = '"') {.compiletime.} = - ## Parses until ending " or ' is reached. - inc(i) - if i < value.len-1: - inc(i, value.skipUntil({'\\', strType}, i)) - - -proc parse_to_close(value: string, index: int, open='(', close=')', opened=0): int {.compiletime.} = - ## Reads until all opened braces are closed - ## ignoring any strings "" or '' - var remainder = value.substring(index) - var open_braces = opened - result = 0 - - while result < remainder.len: - var c = remainder[result] - - if c == open: inc(open_braces) - elif c == close: dec(open_braces) - elif c == '"': remainder.parse_thru_string(result) - elif c == '\'': remainder.parse_thru_string(result, '\'') - - if open_braces == 0: break - else: inc(result) - - -iterator parse_stmt_list(value: string, index: var int): string = - ## Parses unguided ${..} block - var read = value.parse_to_close(index, open='{', close='}') - var expressions = value.substring(index + 1, read - 1).split({ ';', 0x0A.char }) - - for expression in expressions: - let value = expression.strip - if value.len > 0: - yield value - - #Increment index & parse thru EOL - inc(index, read + 1) - inc(index, value.parse_thru_eol(index)) - - -iterator parse_compound_statements(value, identifier: string, index: int): string = - ## Parses through several statements, i.e. if {} elif {} else {} - ## and returns the initialization of each as an empty statement - ## i.e. if x == 5 { ... } becomes if x == 5: nil. - - template get_next_ident(expected): stmt = - var nextIdent: string - discard value.parseWhile(nextIdent, {'$'} + identChars, i) - - var next: string - var read: int - - if nextIdent == "case": - # We have to handle case a bit differently - read = value.parseUntil(next, '$', i) - inc(i, read) - yield next.strip(leading=false) & "\n" - - else: - read = value.parseUntil(next, '{', i) - - if nextIdent in expected: - inc(i, read) - # Parse until closing }, then skip whitespace afterwards - read = value.parse_to_close(i, open='{', close='}') - inc(i, read + 1) - inc(i, value.skipWhitespace(i)) - yield next & ": nil\n" - - else: break - - - var i = index - while true: - # Check if next statement would be valid, given the identifier - if identifier in ["if", "when"]: - get_next_ident([identifier, "$elif", "$else"]) - - elif identifier == "case": - get_next_ident(["case", "$of", "$elif", "$else"]) - - elif identifier == "try": - get_next_ident(["try", "$except", "$finally"]) - - -proc parse_complex_stmt(value, identifier: string, index: var int): NimNode {.compiletime.} = - ## Parses if/when/try /elif /else /except /finally statements - - # Build up complex statement string - var stmtString = newString(0) - var numStatements = 0 - for statement in value.parse_compound_statements(identifier, index): - if statement[0] == '$': stmtString.add(statement.substr(1)) - else: stmtString.add(statement) - inc(numStatements) - - # Parse stmt string - result = parseExpr(stmtString) - - var resultIndex = 0 - - # Fast forward a bit if this is a case statement - if identifier == "case": - inc(resultIndex) - - while resultIndex < numStatements: - - # Detect indentation - let indent = detect_indent(value, index) - - # Parse until an open brace `{` - var read = value.skipUntil('{', index) - inc(index, read + 1) - - # Parse through EOL - inc(index, value.parse_thru_eol(index)) - - # Parse through { .. } - read = value.parse_to_close(index, open='{', close='}', opened=1) - - # Add parsed sub-expression into body - var body = newStmtList() - var stmtString = value.substring(index, read) - trim_after_eol(stmtString) - stmtString = reindent(stmtString, indent) - parse_template(body, stmtString) - inc(index, read + 1) - - # Insert body into result - var stmtIndex = macros.high(result[resultIndex]) - result[resultIndex][stmtIndex] = body - - # Parse through EOL again & increment result index - inc(index, value.parse_thru_eol(index)) - inc(resultIndex) - - -proc parse_simple_statement(value: string, index: var int): NimNode {.compiletime.} = - ## Parses for/while - - # Detect indentation - let indent = detect_indent(value, index) - - # Parse until an open brace `{` - var splitValue: string - var read = value.parseUntil(splitValue, '{', index) - result = parseExpr(splitValue & ":nil") - inc(index, read + 1) - - # Parse through EOL - inc(index, value.parse_thru_eol(index)) - - # Parse through { .. } - read = value.parse_to_close(index, open='{', close='}', opened=1) - - # Add parsed sub-expression into body - var body = newStmtList() - var stmtString = value.substring(index, read) - trim_after_eol(stmtString) - stmtString = reindent(stmtString, indent) - parse_template(body, stmtString) - inc(index, read + 1) - - # Insert body into result - var stmtIndex = macros.high(result) - result[stmtIndex] = body - - # Parse through EOL again - inc(index, value.parse_thru_eol(index)) - - -proc parse_until_symbol(node: NimNode, value: string, index: var int): bool {.compiletime.} = - ## Parses a string until a $ symbol is encountered, if - ## two $$'s are encountered in a row, a split will happen - ## removing one of the $'s from the resulting output - var splitValue: string - var read = value.parseUntil(splitValue, '$', index) - var insertionPoint = node.len - - inc(index, read + 1) - if index < value.len: - - case value[index] - of '$': - # Check for duplicate `$`, meaning this is an escaped $ - node.add newCall("add", ident("result"), newStrLitNode("$")) - inc(index) - - of '(': - # Check for open `(`, which means parse as simple single-line expression. - trim_eol(splitValue) - read = value.parse_to_close(index) + 1 - node.add newCall("add", ident("result"), - newCall(bindSym"strip", parseExpr("$" & value.substring(index, read))) - ) - inc(index, read) - - of '{': - # Check for open `{`, which means open statement list - trim_eol(splitValue) - for s in value.parse_stmt_list(index): - node.add parseExpr(s) - - else: - # Otherwise parse while valid `identChars` and make expression w/ $ - var identifier: string - read = value.parseWhile(identifier, identChars, index) - - if identifier in ["for", "while"]: - ## for/while means open simple statement - trim_eol(splitValue) - node.add value.parse_simple_statement(index) - - elif identifier in ["if", "when", "case", "try"]: - ## if/when/case/try means complex statement - trim_eol(splitValue) - node.add value.parse_complex_stmt(identifier, index) - - elif identifier.len > 0: - ## Treat as simple variable - node.add newCall("add", ident("result"), newCall("$", ident(identifier))) - inc(index, read) - - result = true - - # Insert - if splitValue.len > 0: - node.insert insertionPoint, newCall("add", ident("result"), newStrLitNode(splitValue)) - - -proc parse_template(node: NimNode, value: string) = - ## Parses through entire template, outputing valid - ## Nim code into the input `node` AST. - var index = 0 - while index < value.len and - parse_until_symbol(node, value, index): nil - - -macro tmpli*(body: expr): stmt = - result = newStmtList() - - result.add parseExpr("result = \"\"") - - var value = if body.kind in nnkStrLit..nnkTripleStrLit: body.strVal - else: body[1].strVal - - parse_template(result, reindent(value)) - - -macro tmpl*(body: expr): stmt = - result = newStmtList() - - var value = if body.kind in nnkStrLit..nnkTripleStrLit: body.strVal - else: body[1].strVal - - parse_template(result, reindent(value)) - - -# Run tests -when isMainModule: +# Ref: +# http://nim-lang.org/macros.html +# http://nim-lang.org/parseutils.html + + +# Imports +import tables, parseutils, macros, strutils +import annotate +export annotate + + +# Fields +const identChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'} + + +# Procedure Declarations +proc parse_template(node: NimNode, value: string) {.compiletime.} + + +# Procedure Definitions +proc substring(value: string, index: int, length = -1): string {.compiletime.} = + ## Returns a string at most `length` characters long, starting at `index`. + return if length < 0: value.substr(index) + elif length == 0: "" + else: value.substr(index, index + length-1) + + +proc parse_thru_eol(value: string, index: int): int {.compiletime.} = + ## Reads until and past the end of the current line, unless + ## a non-whitespace character is encountered first + var remainder: string + var read = value.parseUntil(remainder, {0x0A.char}, index) + if remainder.skipWhitespace() == read: + return read + 1 + + +proc trim_after_eol(value: var string) {.compiletime.} = + ## Trims any whitespace at end after \n + var toTrim = 0 + for i in countdown(value.len-1, 0): + # If \n, return + if value[i] in [' ', '\t']: inc(toTrim) + else: break + + if toTrim > 0: + value = value.substring(0, value.len - toTrim) + + +proc trim_eol(value: var string) {.compiletime.} = + ## Removes everything after the last line if it contains nothing but whitespace + for i in countdown(value.len - 1, 0): + # If \n, trim and return + if value[i] == 0x0A.char: + value = value.substr(0, i) + break + + # This is the first character + if i == 0: + value = "" + break + + # Skip change + if not (value[i] in [' ', '\t']): break + + +proc detect_indent(value: string, index: int): int {.compiletime.} = + ## Detects how indented the line at `index` is. + # Seek to the beginning of the line. + var lastChar = index + for i in countdown(index, 0): + if value[i] == 0x0A.char: + # if \n, return the indentation level + return lastChar - i + elif not (value[i] in [' ', '\t']): + # if non-whitespace char, decrement lastChar + dec(lastChar) + + +proc parse_thru_string(value: string, i: var int, strType = '"') {.compiletime.} = + ## Parses until ending " or ' is reached. + inc(i) + if i < value.len-1: + inc(i, value.skipUntil({'\\', strType}, i)) + + +proc parse_to_close(value: string, index: int, open='(', close=')', opened=0): int {.compiletime.} = + ## Reads until all opened braces are closed + ## ignoring any strings "" or '' + var remainder = value.substring(index) + var open_braces = opened + result = 0 + + while result < remainder.len: + var c = remainder[result] + + if c == open: inc(open_braces) + elif c == close: dec(open_braces) + elif c == '"': remainder.parse_thru_string(result) + elif c == '\'': remainder.parse_thru_string(result, '\'') + + if open_braces == 0: break + else: inc(result) + + +iterator parse_stmt_list(value: string, index: var int): string = + ## Parses unguided ${..} block + var read = value.parse_to_close(index, open='{', close='}') + var expressions = value.substring(index + 1, read - 1).split({ ';', 0x0A.char }) + + for expression in expressions: + let value = expression.strip + if value.len > 0: + yield value + + #Increment index & parse thru EOL + inc(index, read + 1) + inc(index, value.parse_thru_eol(index)) + + +iterator parse_compound_statements(value, identifier: string, index: int): string = + ## Parses through several statements, i.e. if {} elif {} else {} + ## and returns the initialization of each as an empty statement + ## i.e. if x == 5 { ... } becomes if x == 5: nil. + + template get_next_ident(expected): stmt = + var nextIdent: string + discard value.parseWhile(nextIdent, {'$'} + identChars, i) + + var next: string + var read: int + + if nextIdent == "case": + # We have to handle case a bit differently + read = value.parseUntil(next, '$', i) + inc(i, read) + yield next.strip(leading=false) & "\n" + + else: + read = value.parseUntil(next, '{', i) + + if nextIdent in expected: + inc(i, read) + # Parse until closing }, then skip whitespace afterwards + read = value.parse_to_close(i, open='{', close='}') + inc(i, read + 1) + inc(i, value.skipWhitespace(i)) + yield next & ": nil\n" + + else: break + + + var i = index + while true: + # Check if next statement would be valid, given the identifier + if identifier in ["if", "when"]: + get_next_ident([identifier, "$elif", "$else"]) + + elif identifier == "case": + get_next_ident(["case", "$of", "$elif", "$else"]) + + elif identifier == "try": + get_next_ident(["try", "$except", "$finally"]) + + +proc parse_complex_stmt(value, identifier: string, index: var int): NimNode {.compiletime.} = + ## Parses if/when/try /elif /else /except /finally statements + + # Build up complex statement string + var stmtString = newString(0) + var numStatements = 0 + for statement in value.parse_compound_statements(identifier, index): + if statement[0] == '$': stmtString.add(statement.substr(1)) + else: stmtString.add(statement) + inc(numStatements) + + # Parse stmt string + result = parseExpr(stmtString) + + var resultIndex = 0 + + # Fast forward a bit if this is a case statement + if identifier == "case": + inc(resultIndex) + + while resultIndex < numStatements: + + # Detect indentation + let indent = detect_indent(value, index) + + # Parse until an open brace `{` + var read = value.skipUntil('{', index) + inc(index, read + 1) + + # Parse through EOL + inc(index, value.parse_thru_eol(index)) + + # Parse through { .. } + read = value.parse_to_close(index, open='{', close='}', opened=1) + + # Add parsed sub-expression into body + var body = newStmtList() + var stmtString = value.substring(index, read) + trim_after_eol(stmtString) + stmtString = reindent(stmtString, indent) + parse_template(body, stmtString) + inc(index, read + 1) + + # Insert body into result + var stmtIndex = result[resultIndex].len-1 + result[resultIndex][stmtIndex] = body + + # Parse through EOL again & increment result index + inc(index, value.parse_thru_eol(index)) + inc(resultIndex) + + +proc parse_simple_statement(value: string, index: var int): NimNode {.compiletime.} = + ## Parses for/while + + # Detect indentation + let indent = detect_indent(value, index) + + # Parse until an open brace `{` + var splitValue: string + var read = value.parseUntil(splitValue, '{', index) + result = parseExpr(splitValue & ":nil") + inc(index, read + 1) + + # Parse through EOL + inc(index, value.parse_thru_eol(index)) + + # Parse through { .. } + read = value.parse_to_close(index, open='{', close='}', opened=1) + + # Add parsed sub-expression into body + var body = newStmtList() + var stmtString = value.substring(index, read) + trim_after_eol(stmtString) + stmtString = reindent(stmtString, indent) + parse_template(body, stmtString) + inc(index, read + 1) + + # Insert body into result + var stmtIndex = result.len-1 + result[stmtIndex] = body + + # Parse through EOL again + inc(index, value.parse_thru_eol(index)) + + +proc parse_until_symbol(node: NimNode, value: string, index: var int): bool {.compiletime.} = + ## Parses a string until a $ symbol is encountered, if + ## two $$'s are encountered in a row, a split will happen + ## removing one of the $'s from the resulting output + var splitValue: string + var read = value.parseUntil(splitValue, '$', index) + var insertionPoint = node.len + + inc(index, read + 1) + if index < value.len: + + case value[index] + of '$': + # Check for duplicate `$`, meaning this is an escaped $ + node.add newCall("add", ident("result"), newStrLitNode("$")) + inc(index) + + of '(': + # Check for open `(`, which means parse as simple single-line expression. + trim_eol(splitValue) + read = value.parse_to_close(index) + 1 + node.add newCall("add", ident("result"), + newCall(bindSym"strip", parseExpr("$" & value.substring(index, read))) + ) + inc(index, read) + + of '{': + # Check for open `{`, which means open statement list + trim_eol(splitValue) + for s in value.parse_stmt_list(index): + node.add parseExpr(s) + + else: + # Otherwise parse while valid `identChars` and make expression w/ $ + var identifier: string + read = value.parseWhile(identifier, identChars, index) + + if identifier in ["for", "while"]: + ## for/while means open simple statement + trim_eol(splitValue) + node.add value.parse_simple_statement(index) + + elif identifier in ["if", "when", "case", "try"]: + ## if/when/case/try means complex statement + trim_eol(splitValue) + node.add value.parse_complex_stmt(identifier, index) + + elif identifier.len > 0: + ## Treat as simple variable + node.add newCall("add", ident("result"), newCall("$", ident(identifier))) + inc(index, read) + + result = true + + # Insert + if splitValue.len > 0: + node.insert insertionPoint, newCall("add", ident("result"), newStrLitNode(splitValue)) + + +proc parse_template(node: NimNode, value: string) = + ## Parses through entire template, outputing valid + ## Nim code into the input `node` AST. + var index = 0 + while index < value.len and + parse_until_symbol(node, value, index): nil + + +macro tmpli*(body: expr): stmt = + result = newStmtList() + + result.add parseExpr("result = \"\"") + + var value = if body.kind in nnkStrLit..nnkTripleStrLit: body.strVal + else: body[1].strVal + + parse_template(result, reindent(value)) + + +macro tmpl*(body: expr): stmt = + result = newStmtList() + + var value = if body.kind in nnkStrLit..nnkTripleStrLit: body.strVal + else: body[1].strVal + + parse_template(result, reindent(value)) + + +# Run tests +when isMainModule: include otests echo "Success" diff --git a/tests/template/twrongsymkind.nim b/tests/template/twrongsymkind.nim new file mode 100644 index 000000000..be3d8c652 --- /dev/null +++ b/tests/template/twrongsymkind.nim @@ -0,0 +1,20 @@ +discard """ + errormsg: "cannot use symbol of kind 'var' as a 'param'" + line: 20 +""" + +# bug #3158 + +type + MyData = object + x: int + +template newDataWindow(data: ref MyData): stmt = + proc testProc(data: ref MyData) = + echo "Hello, ", data.x + testProc(data) + +var d: ref MyData +new(d) +d.x = 10 +newDataWindow(d) diff --git a/web/news.txt b/web/news.txt index a5bc600b9..b72ae56d2 100644 --- a/web/news.txt +++ b/web/news.txt @@ -42,6 +42,8 @@ News - ``sequtils.delete`` doesn't take confusing default arguments anymore. - ``system.free`` was an error-prone alias to ``system.dealloc`` and has been removed. + - ``macros.high`` never worked and the manual says ``high`` cannot be + overloaded, so we removed it with no deprecation cycle. Library additions |