diff options
129 files changed, 3599 insertions, 887 deletions
diff --git a/.gitignore b/.gitignore index 1971a23cb..e4cec0ef6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ nimcache/ *.dylib *.zip *.iss +*.log mapping.txt tags @@ -42,3 +43,7 @@ xcuserdata/ /testresults.json testament.db /csources + +# Private directories and files (IDEs) +.*/ +~* diff --git a/compiler/ast.nim b/compiler/ast.nim index fecbefd7e..7275aceb1 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -938,7 +938,7 @@ const genericParamsPos* = 2 paramsPos* = 3 pragmasPos* = 4 - optimizedCodePos* = 5 # will be used for exception tracking + miscPos* = 5 # used for undocumented and hacky stuff bodyPos* = 6 # position of body; use rodread.getBody() instead! resultPos* = 7 dispatcherPos* = 8 # caution: if method has no 'result' it can be position 7! @@ -961,6 +961,9 @@ const skMethod, skConverter} var ggDebug* {.deprecated.}: bool ## convenience switch for trying out things +var + gMainPackageId*: int + gMainPackageNotes*: TNoteKinds proc isCallExpr*(n: PNode): bool = result = n.kind in nkCallKinds diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index f892a3128..9f4beda9e 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1235,7 +1235,7 @@ proc genOfHelper(p: BProc; dest: PType; a: Rope): Rope = # unfortunately 'genTypeInfo' sets tfObjHasKids as a side effect, so we # have to call it here first: let ti = genTypeInfo(p.module, dest) - if tfFinal in dest.flags or (p.module.objHasKidsValid and + if tfFinal in dest.flags or (objHasKidsValid in p.module.flags and tfObjHasKids notin dest.flags): result = "$1.m_type == $2" % [a, ti] else: diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim index 2a37257b6..2e77cd2a6 100644 --- a/compiler/ccgmerge.nim +++ b/compiler/ccgmerge.nim @@ -107,8 +107,8 @@ proc genMergeInfo*(m: BModule): Rope = writeIntSet(m.typeInfoMarker, s) s.add("labels:") encodeVInt(m.labels, s) - s.add(" hasframe:") - encodeVInt(ord(m.frameDeclared), s) + s.add(" flags:") + encodeVInt(cast[int](m.flags), s) s.add(tnl) s.add("*/") result = s.rope @@ -222,7 +222,8 @@ proc processMergeInfo(L: var TBaseLexer, m: BModule) = of "declared": readIntSet(L, m.declaredThings) of "typeInfo": readIntSet(L, m.typeInfoMarker) of "labels": m.labels = decodeVInt(L.buf, L.bufpos) - of "hasframe": m.frameDeclared = decodeVInt(L.buf, L.bufpos) != 0 + of "flags": + m.flags = cast[set[CodegenFlag]](decodeVInt(L.buf, L.bufpos) != 0) else: internalError("ccgmerge: unknown key: " & k) when not defined(nimhygiene): diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 988c923c8..61412ad67 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -16,7 +16,7 @@ const # above X strings a hash-switch for strings is generated proc registerGcRoot(p: BProc, v: PSym) = - if gSelectedGC in {gcMarkAndSweep, gcGenerational, gcV2} and + if gSelectedGC in {gcMarkAndSweep, gcGenerational, gcV2, gcRefc} and containsGarbageCollectedRef(v.loc.t): # we register a specialized marked proc here; this has the advantage # that it works out of the box for thread local storage then :-) diff --git a/compiler/ccgthreadvars.nim b/compiler/ccgthreadvars.nim index ab771d240..81af89249 100644 --- a/compiler/ccgthreadvars.nim +++ b/compiler/ccgthreadvars.nim @@ -18,7 +18,7 @@ proc emulatedThreadVars(): bool = proc accessThreadLocalVar(p: BProc, s: PSym) = if emulatedThreadVars() and not p.threadVarAccessed: p.threadVarAccessed = true - p.module.usesThreadVars = true + incl p.module.flags, usesThreadVars addf(p.procSec(cpsLocals), "\tNimThreadVars* NimTV;$n", []) add(p.procSec(cpsInit), ropecg(p.module, "\tNimTV = (NimThreadVars*) #GetThreadLocalVars();$n")) @@ -51,7 +51,7 @@ proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) = addf(m.s[cfsVars], " $1;$n", [s.loc.r]) proc generateThreadLocalStorage(m: BModule) = - if nimtv != nil and (m.usesThreadVars or sfMainModule in m.module.flags): + if nimtv != nil and (usesThreadVars in m.flags or sfMainModule in m.module.flags): for t in items(nimtvDeps): discard getTypeDesc(m, t) addf(m.s[cfsSeqTypes], "typedef struct {$1} NimThreadVars;$n", [nimtv]) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 700e6d148..6553deb66 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -752,7 +752,7 @@ proc genProcHeader(m: BModule, prc: PSym): Rope = genCLineDir(result, prc.info) # using static is needed for inline procs if lfExportLib in prc.loc.flags: - if m.isHeaderFile: + if isHeaderFile in m.flags: result.add "N_LIB_IMPORT " else: result.add "N_LIB_EXPORT " diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 0a8f89f2e..77be125b6 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -64,8 +64,8 @@ proc isSimpleConst(typ: PType): bool = (t.kind == tyProc and t.callConv == ccClosure) proc useStringh(m: BModule) = - if not m.includesStringh: - m.includesStringh = true + if includesStringh notin m.flags: + incl m.flags, includesStringh discard lists.includeStr(m.headerFiles, "<string.h>") proc useHeader(m: BModule, sym: PSym) = @@ -1011,11 +1011,11 @@ proc genInitCode(m: BModule) = add(prc, m.postInitProc.s(cpsLocals)) add(prc, genSectionEnd(cpsLocals)) - if optStackTrace in m.initProc.options and not m.frameDeclared: + if optStackTrace in m.initProc.options and frameDeclared notin m.flags: # BUT: the generated init code might depend on a current frame, so # declare it nevertheless: - m.frameDeclared = true - if not m.preventStackTrace: + incl m.flags, frameDeclared + if preventStackTrace notin m.flags: var procname = makeCString(m.module.name.s) add(prc, initFrame(m.initProc, procname, m.module.info.quotedFilename)) else: @@ -1032,7 +1032,7 @@ proc genInitCode(m: BModule) = add(prc, m.initProc.s(cpsStmts)) add(prc, m.postInitProc.s(cpsStmts)) add(prc, genSectionEnd(cpsStmts)) - if optStackTrace in m.initProc.options and not m.preventStackTrace: + if optStackTrace in m.initProc.options and preventStackTrace notin m.flags: add(prc, deinitFrame(m.initProc)) add(prc, deinitGCFrame(m.initProc)) addf(prc, "}$N$N", []) @@ -1105,7 +1105,7 @@ proc rawNewModule(module: PSym, filename: string): BModule = # no line tracing for the init sections of the system module so that we # don't generate a TFrame which can confuse the stack botton initialization: if sfSystemModule in module.flags: - result.preventStackTrace = true + incl result.flags, preventStackTrace excl(result.preInitProc.options, optStackTrace) excl(result.postInitProc.options, optStackTrace) @@ -1128,9 +1128,11 @@ proc resetModule*(m: BModule) = m.forwardedProcs = @[] m.typeNodesName = getTempName() m.nimTypesName = getTempName() - m.preventStackTrace = sfSystemModule in m.module.flags + if sfSystemModule in m.module.flags: + incl m.flags, preventStackTrace + else: + excl m.flags, preventStackTrace nullify m.s - m.usesThreadVars = false m.typeNodes = 0 m.nimTypes = 0 nullify m.extensionLoaders @@ -1175,7 +1177,7 @@ proc myOpen(module: PSym): PPassContext = let f = if headerFile.len > 0: headerFile else: gProjectFull generatedHeader = rawNewModule(module, changeFileExt(completeCFilePath(f), hExt)) - generatedHeader.isHeaderFile = true + incl generatedHeader.flags, isHeaderFile proc writeHeader(m: BModule) = var result = getCopyright(m.filename) @@ -1307,7 +1309,7 @@ proc myClose(b: PPassContext, n: PNode): PNode = registerModuleToMain(m.module) if sfMainModule in m.module.flags: - m.objHasKidsValid = true + incl m.flags, objHasKidsValid var disp = generateMethodDispatchers() for i in 0..sonsLen(disp)-1: genProcAux(m, disp.sons[i].sym) genMainProc(m) diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 187186373..c098902a6 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -92,17 +92,20 @@ type gcFrameType*: Rope # the struct {} we put the GC markers into TTypeSeq* = seq[PType] + + Codegenflag* = enum + preventStackTrace, # true if stack traces need to be prevented + usesThreadVars, # true if the module uses a thread var + frameDeclared, # hack for ROD support so that we don't declare + # a frame var twice in an init proc + isHeaderFile, # C source file is the header file + includesStringh, # C source file already includes ``<string.h>`` + objHasKidsValid # whether we can rely on tfObjHasKids TCGen = object of TPassContext # represents a C source file module*: PSym filename*: string s*: TCFileSections # sections of the C file - preventStackTrace*: bool # true if stack traces need to be prevented - usesThreadVars*: bool # true if the module uses a thread var - frameDeclared*: bool # hack for ROD support so that we don't declare - # a frame var twice in an init proc - isHeaderFile*: bool # C source file is the header file - includesStringh*: bool # C source file already includes ``<string.h>`` - objHasKidsValid*: bool # whether we can rely on tfObjHasKids + flags*: set[Codegenflag] cfilename*: string # filename of the module (including path, # without extension) typeCache*: TIdTable # cache the generated types diff --git a/compiler/commands.nim b/compiler/commands.nim index aa155ea88..3bc0b604a 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -213,6 +213,7 @@ proc testCompileOptionArg*(switch, arg: string, info: TLineInfo): bool = of "size": result = contains(gOptions, optOptimizeSize) of "none": result = gOptions * {optOptimizeSpeed, optOptimizeSize} == {} else: localError(info, errNoneSpeedOrSizeExpectedButXFound, arg) + of "verbosity": result = $gVerbosity == arg else: invalidCmdLineOption(passCmd1, switch, info) proc testCompileOption*(switch: string, info: TLineInfo): bool = @@ -261,13 +262,7 @@ proc processPath(path: string, info: TLineInfo, else: options.gProjectPath / path try: - result = unixToNativePath(p % ["nimrod", getPrefixDir(), - "nim", getPrefixDir(), - "lib", libpath, - "home", removeTrailingDirSep(os.getHomeDir()), - "config", info.toFullPath().splitFile().dir, - "projectname", options.gProjectName, - "projectpath", options.gProjectPath]) + result = pathSubs(p, info.toFullPath().splitFile().dir) except ValueError: localError(info, "invalid path: " & p) result = p @@ -321,6 +316,9 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = of "nonimblepath", "nobabelpath": expectNoArg(switch, arg, pass, info) options.gNoNimblePath = true + options.lazyPaths.head = nil + options.lazyPaths.tail = nil + options.lazyPaths.counter = 0 of "excludepath": expectArg(switch, arg, pass, info) let path = processPath(arg, info) diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim index a5a132005..e136265da 100644 --- a/compiler/evaltempl.nim +++ b/compiler/evaltempl.nim @@ -59,7 +59,7 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) = evalTemplateAux(templ.sons[i], actual, c, res) result.add res -proc evalTemplateArgs(n: PNode, s: PSym): PNode = +proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode = # if the template has zero arguments, it can be called without ``()`` # `n` is then a nkSym or something similar var totalParams = case n.kind @@ -75,7 +75,7 @@ proc evalTemplateArgs(n: PNode, s: PSym): PNode = # their bodies. We could try to fix this, but it may be # wiser to just deprecate immediate templates and macros # now that we have working untyped parameters. - genericParams = if sfImmediate in s.flags: 0 + genericParams = if sfImmediate in s.flags or fromHlo: 0 else: s.ast[genericParamsPos].len expectedRegularParams = <s.typ.len givenRegularParams = totalParams - genericParams @@ -104,14 +104,14 @@ proc evalTemplateArgs(n: PNode, s: PSym): PNode = var evalTemplateCounter* = 0 # to prevent endless recursion in templates instantiation -proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym): PNode = +proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode = inc(evalTemplateCounter) if evalTemplateCounter > 100: globalError(n.info, errTemplateInstantiationTooNested) result = n # replace each param by the corresponding node: - var args = evalTemplateArgs(n, tmpl) + var args = evalTemplateArgs(n, tmpl, fromHlo) var ctx: TemplCtx ctx.owner = tmpl ctx.genSymOwner = genSymOwner diff --git a/compiler/hlo.nim b/compiler/hlo.nim index 6cc9567af..de0fa6216 100644 --- a/compiler/hlo.nim +++ b/compiler/hlo.nim @@ -24,7 +24,7 @@ proc evalPattern(c: PContext, n, orig: PNode): PNode = of skMacro: result = semMacroExpr(c, n, orig, s) of skTemplate: - result = semTemplateExpr(c, n, s) + result = semTemplateExpr(c, n, s, {efFromHlo}) else: result = semDirectOp(c, n, {}) if optHints in gOptions and hintPattern in gNotes: diff --git a/compiler/importer.nim b/compiler/importer.nim index 86993358b..5ffe12728 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -22,7 +22,11 @@ proc getModuleName*(n: PNode): string = # The proc won't perform any checks that the path is actually valid case n.kind of nkStrLit, nkRStrLit, nkTripleStrLit: - result = unixToNativePath(n.strVal) + try: + result = pathSubs(n.strVal, n.info.toFullPath().splitFile().dir) + except ValueError: + localError(n.info, "invalid path: " & n.strVal) + result = n.strVal of nkIdent: result = n.ident.s of nkSym: diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 964752c16..124459306 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -71,7 +71,7 @@ type isLoop: bool # whether it's a 'block' or 'while' TGlobals = object - typeInfo, code: Rope + typeInfo, constants, code: Rope forwarded: seq[PSym] generatedSyms: IntSet typeInfoGenerated: IntSet @@ -237,7 +237,7 @@ proc useMagic(p: PProc, name: string) = internalAssert s.kind in {skProc, skMethod, skConverter} if not p.g.generatedSyms.containsOrIncl(s.id): let code = genProc(p, s) - add(p.g.code, code) + add(p.g.constants, code) else: # we used to exclude the system module from this check, but for DLL # generation support this sloppyness leads to hard to detect bugs, so @@ -1461,7 +1461,7 @@ proc genConstant(p: PProc, c: PSym) = p.body = nil #genLineDir(p, c.ast) genVarInit(p, c, c.ast) - add(p.g.code, p.body) + add(p.g.constants, p.body) p.body = oldBody proc genNew(p: PProc, n: PNode) = @@ -2137,7 +2137,7 @@ proc wholeCode*(m: BModule): Rope = var p = newProc(globals, m, nil, m.module.options) attachProc(p, prc) - result = globals.typeInfo & globals.code + result = globals.typeInfo & globals.constants & globals.code proc getClassName(t: PType): Rope = var s = t.sym diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 0a4c01ba8..8b201431e 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -138,6 +138,8 @@ proc getLineInfo*(L: TLexer, tok: TToken): TLineInfo {.inline.} = proc isKeyword*(kind: TTokType): bool = result = (kind >= tokKeywordLow) and (kind <= tokKeywordHigh) +template ones(n: expr): expr = ((1 shl n)-1) # for utf-8 conversion + proc isNimIdentifier*(s: string): bool = if s[0] in SymStartChars: var i = 1 @@ -589,12 +591,29 @@ proc getEscapedChar(L: var TLexer, tok: var TToken) = of '\\': add(tok.literal, '\\') inc(L.bufpos) - of 'x', 'X': + of 'x', 'X', 'u', 'U': + var tp = L.buf[L.bufpos] inc(L.bufpos) var xi = 0 handleHexChar(L, xi) handleHexChar(L, xi) - add(tok.literal, chr(xi)) + if tp in {'u', 'U'}: + handleHexChar(L, xi) + handleHexChar(L, xi) + # inlined toUTF-8 to avoid unicode and strutils dependencies. + if xi <=% 127: + add(tok.literal, xi.char ) + elif xi <=% 0x07FF: + add(tok.literal, ((xi shr 6) or 0b110_00000).char ) + add(tok.literal, ((xi and ones(6)) or 0b10_0000_00).char ) + elif xi <=% 0xFFFF: + add(tok.literal, (xi shr 12 or 0b1110_0000).char ) + add(tok.literal, (xi shr 6 and ones(6) or 0b10_0000_00).char ) + add(tok.literal, (xi and ones(6) or 0b10_0000_00).char ) + else: # value is 0xFFFF + add(tok.literal, "\xef\xbf\xbf" ) + else: + add(tok.literal, chr(xi)) of '0'..'9': if matchTwoChars(L, '0', {'0'..'9'}): lexMessage(L, warnOctalEscape) diff --git a/compiler/main.nim b/compiler/main.nim index ece21a817..b1b9006bd 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -41,6 +41,7 @@ proc commandGenDepend = proc commandCheck = msgs.gErrorMax = high(int) # do not stop after first error + defineSymbol("nimcheck") semanticPasses() # use an empty backend for semantic checking only rodPass() compileProject() @@ -241,7 +242,7 @@ proc mainCommand* = clearPasses() gLastCmdTime = epochTime() appendStr(searchPaths, options.libpath) - if gProjectFull.len != 0: + when false: # gProjectFull.len != 0: # current path is always looked first for modules prependStr(searchPaths, gProjectPath) setId(100) diff --git a/compiler/modules.nim b/compiler/modules.nim index ef727e200..8ac964321 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -156,6 +156,9 @@ proc compileModule*(fileIdx: int32, flags: TSymFlags): PSym = #var rd = handleSymbolFile(result) var rd: PRodReader result.flags = result.flags + flags + if sfMainModule in result.flags: + gMainPackageId = result.owner.id + if gCmd in {cmdCompileToC, cmdCompileToCpp, cmdCheck, cmdIdeTools}: rd = handleSymbolFile(result) if result.id < 0: @@ -183,6 +186,9 @@ proc importModule*(s: PSym, fileIdx: int32): PSym {.procvar.} = if optCaasEnabled in gGlobalOptions: addDep(s, fileIdx) if sfSystemModule in result.flags: localError(result.info, errAttemptToRedefine, result.name.s) + # restore the notes for outer module: + gNotes = if s.owner.id == gMainPackageId: gMainPackageNotes + else: ForeignPackageNotes proc includeModule*(s: PSym, fileIdx: int32): PNode {.procvar.} = result = syntaxes.parseFile(fileIdx) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index a556ad0c5..668d43bb3 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -500,12 +500,14 @@ type ESuggestDone* = object of Exception const + ForeignPackageNotes*: TNoteKinds = {hintProcessing, warnUnknownMagic, + hintQuitCalled} NotesVerbosity*: array[0..3, TNoteKinds] = [ {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit, warnProveField, warnProveIndex, warnGcUnsafe, hintSuccessX, hintPath, hintConf, - hintProcessing, + hintProcessing, hintPattern, hintDependency, hintExecuting, hintLinking, hintCodeBegin, hintCodeEnd, diff --git a/compiler/nim.cfg b/compiler/nim.cfg index 4f9962ea8..0ff128ba3 100644 --- a/compiler/nim.cfg +++ b/compiler/nim.cfg @@ -7,7 +7,7 @@ path:"$projectPath/.." path:"$lib/packages/docutils" define:booting -import:testability +#import:"$projectpath/testability" @if windows: cincludes: "$lib/wrappers/libffi/common" diff --git a/compiler/nim.nim b/compiler/nim.nim index b242052ec..a58afd593 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -56,12 +56,12 @@ proc handleCmdLine() = loadConfigs(DefaultConfig) # load all config files let scriptFile = gProjectFull.changeFileExt("nims") if fileExists(scriptFile): - runNimScript(scriptFile) + runNimScript(scriptFile, freshDefines=false) # 'nim foo.nims' means to just run the NimScript file and do nothing more: if scriptFile == gProjectFull: return elif fileExists(gProjectPath / "config.nims"): # directory wide NimScript file - runNimScript(gProjectPath / "config.nims") + runNimScript(gProjectPath / "config.nims", freshDefines=false) # now process command line arguments again, because some options in the # command line can overwite the config file's settings extccomp.initVars() diff --git a/compiler/options.nim b/compiler/options.nim index 2716a98d3..3ef6c6c46 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -242,6 +242,21 @@ proc getNimcacheDir*: string = result = if nimcacheDir.len > 0: nimcacheDir else: gProjectPath.shortenDir / genSubDir + +proc pathSubs*(p, config: string): string = + let home = removeTrailingDirSep(os.getHomeDir()) + result = unixToNativePath(p % [ + "nim", getPrefixDir(), + "lib", libpath, + "home", home, + "config", config, + "projectname", options.gProjectName, + "projectpath", options.gProjectPath, + "projectdir", options.gProjectPath, + "nimcache", getNimcacheDir()]) + if '~' in result: + result = result.replace("~", home) + template newPackageCache(): expr = newStringTable(when FileSystemCaseSensitive: modeCaseInsensitive diff --git a/compiler/parser.nim b/compiler/parser.nim index 1ba59b938..6132216e1 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -964,8 +964,9 @@ proc parseDoBlock(p: var TParser): PNode = proc parseDoBlocks(p: var TParser, call: PNode) = #| doBlocks = doBlock ^* IND{=} if p.tok.tokType == tkDo: - addSon(call, parseDoBlock(p)) - while sameInd(p) and p.tok.tokType == tkDo: + #withInd(p): + # addSon(call, parseDoBlock(p)) + while sameOrNoInd(p) and p.tok.tokType == tkDo: addSon(call, parseDoBlock(p)) proc parseProcExpr(p: var TParser, isExpr: bool): PNode = diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 38d17eb62..dc618d9aa 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -277,6 +277,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) = proc processNote(c: PContext, n: PNode) = if (n.kind == nkExprColonExpr) and (sonsLen(n) == 2) and (n.sons[0].kind == nkBracketExpr) and + (n.sons[0].sons.len == 2) and (n.sons[0].sons[1].kind == nkIdent) and (n.sons[0].sons[0].kind == nkIdent): #and (n.sons[1].kind == nkIdent): @@ -392,21 +393,23 @@ type TLinkFeature = enum linkNormal, linkSys -proc processCompile(c: PContext, n: PNode) = +proc relativeFile(c: PContext; n: PNode; ext=""): string = var s = expectStrLit(c, n) - var found = findFile(s) - if found == "": found = s - var trunc = changeFileExt(found, "") - if not isAbsolute(found): - found = parentDir(n.info.toFullPath) / found + if ext.len > 0 and splitFile(s).ext == "": + s = addFileExt(s, ext) + result = parentDir(n.info.toFullPath) / s + if not fileExists(result): + if isAbsolute(s): result = s + else: result = findFile(s) + +proc processCompile(c: PContext, n: PNode) = + let found = relativeFile(c, n) + let trunc = found.changeFileExt("") extccomp.addExternalFileToCompile(found) extccomp.addFileToLink(completeCFilePath(trunc, false)) proc processCommonLink(c: PContext, n: PNode, feature: TLinkFeature) = - var f = expectStrLit(c, n) - if splitFile(f).ext == "": f = addFileExt(f, CC[cCompiler].objExt) - var found = findFile(f) - if found == "": found = f # use the default + let found = relativeFile(c, n, CC[cCompiler].objExt) case feature of linkNormal: extccomp.addFileToLink(found) of linkSys: diff --git a/compiler/renderer.nim b/compiler/renderer.nim index f0ee137e9..e1db327f4 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -712,7 +712,11 @@ proc gproc(g: var TSrcGen, n: PNode) = gpattern(g, n.sons[patternPos]) let oldCheckAnon = g.checkAnon g.checkAnon = true - gsub(g, n.sons[genericParamsPos]) + if renderNoBody in g.flags and n[miscPos].kind != nkEmpty and + n[miscPos][1].kind != nkEmpty: + gsub(g, n[miscPos][1]) + else: + gsub(g, n.sons[genericParamsPos]) g.checkAnon = oldCheckAnon gsub(g, n.sons[paramsPos]) gsub(g, n.sons[pragmasPos]) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 22cd282fd..d04fd5231 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -118,10 +118,10 @@ proc setupVM*(module: PSym; scriptName: string): PEvalContext = processSwitch(a.getString 0, a.getString 1, passPP, unknownLineInfo()) -proc runNimScript*(scriptName: string) = +proc runNimScript*(scriptName: string; freshDefines=true) = passes.gIncludeFile = includeModule passes.gImportModule = importModule - initDefines() + if freshDefines: initDefines() defineSymbol("nimscript") defineSymbol("nimconfig") diff --git a/compiler/sem.nim b/compiler/sem.nim index c29cbe384..a8ec2229f 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -354,7 +354,6 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, #if c.evalContext == nil: # c.evalContext = c.createEvalContext(emStatic) - result = evalMacroCall(c.module, n, nOrig, sym) if efNoSemCheck notin flags: result = semAfterMacroCall(c, result, sym, flags) @@ -414,6 +413,12 @@ proc myOpen(module: PSym): PPassContext = c.importTable.addSym magicsys.systemModule # import the "System" identifier importAllSymbols(c, magicsys.systemModule) c.topLevelScope = openScope(c) + # don't be verbose unless the module belongs to the main package: + if module.owner.id == gMainPackageId: + gNotes = gMainPackageNotes + else: + if gMainPackageNotes == {}: gMainPackageNotes = gNotes + gNotes = ForeignPackageNotes result = c proc myOpenCached(module: PSym, rd: PRodReader): PPassContext = diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 17dd39595..65aa5fd58 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -129,7 +129,11 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = add(result, ')') var candidates = "" for err in errors: - add(candidates, err.getProcHeader(prefer)) + if err.kind in routineKinds and err.ast != nil: + add(candidates, renderTree(err.ast, + {renderNoBody, renderNoComments,renderNoPragmas})) + else: + add(candidates, err.getProcHeader(prefer)) add(candidates, "\n") if candidates != "": add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index a13f2c232..91a1ebf86 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -47,7 +47,7 @@ type efLValue, efWantIterator, efInTypeof, efWantStmt, efAllowStmt, efDetermineType, efAllowDestructor, efWantValue, efOperand, efNoSemCheck, - efNoProcvarCheck + efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo TExprFlags* = set[TExprFlag] TTypeAttachedOp* = enum diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 5aac1c2ac..57fb3ceed 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -15,7 +15,7 @@ proc semTemplateExpr(c: PContext, n: PNode, s: PSym, markUsed(n.info, s) styleCheckUse(n.info, s) pushInfoContext(n.info) - result = evalTemplate(n, s, getCurrOwner()) + result = evalTemplate(n, s, getCurrOwner(), efFromHlo in flags) if efNoSemCheck notin flags: result = semAfterMacroCall(c, result, s, flags) popInfoContext() @@ -717,6 +717,34 @@ proc resolveIndirectCall(c: PContext; n, nOrig: PNode; initCandidate(c, result, t) matches(c, n, nOrig, result) +proc bracketedMacro(n: PNode): PSym = + if n.len >= 1 and n[0].kind == nkSym: + result = n[0].sym + if result.kind notin {skMacro, skTemplate}: + result = nil + +proc semBracketedMacro(c: PContext; outer, inner: PNode; s: PSym; + flags: TExprFlags): PNode = + # We received untransformed bracket expression coming from macroOrTmpl[]. + # Transform it to macro or template call, where first come normal + # arguments, next come generic template arguments. + var sons = newSeq[PNode]() + sons.add inner.sons[0] + # Normal arguments: + for i in 1..<outer.len: + sons.add outer.sons[i] + # Generic template arguments from bracket expression: + for i in 1..<inner.len: + sons.add inner.sons[i] + shallowCopy(outer.sons, sons) + # FIXME: Shouldn't we check sfImmediate and call semDirectOp? + # However passing to semDirectOp doesn't work here. + case s.kind + of skMacro: result = semMacroExpr(c, outer, outer, s, flags) + of skTemplate: result = semTemplateExpr(c, outer, s, flags) + else: assert(false) + return + proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = result = nil checkMinSonsLen(n, 1) @@ -732,10 +760,15 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = for i in countup(1, sonsLen(n) - 1): addSon(result, n.sons[i]) return semExpr(c, result, flags) else: - n.sons[0] = semExpr(c, n.sons[0]) + n.sons[0] = semExpr(c, n.sons[0], {efInCall}) let t = n.sons[0].typ if t != nil and t.kind == tyVar: n.sons[0] = newDeref(n.sons[0]) + elif n.sons[0].kind == nkBracketExpr: + let s = bracketedMacro(n.sons[0]) + if s != nil: + return semBracketedMacro(c, n, n.sons[0], s, flags) + let nOrig = n.copyTree semOpAux(c, n) var t: PType = nil @@ -965,8 +998,20 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = else: result = newSymNode(s, n.info) else: result = newSymNode(s, n.info) - of skMacro: result = semMacroExpr(c, n, n, s, flags) - of skTemplate: result = semTemplateExpr(c, n, s, flags) + of skMacro: + if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0: + markUsed(n.info, s) + styleCheckUse(n.info, s) + result = newSymNode(s, n.info) + else: + result = semMacroExpr(c, n, n, s, flags) + of skTemplate: + if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0: + markUsed(n.info, s) + styleCheckUse(n.info, s) + result = newSymNode(s, n.info) + else: + result = semTemplateExpr(c, n, s, flags) of skParam: markUsed(n.info, s) styleCheckUse(n.info, s) @@ -1193,7 +1238,9 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = result.add(x[0]) return checkMinSonsLen(n, 2) - n.sons[0] = semExprWithType(c, n.sons[0], {efNoProcvarCheck}) + # make sure we don't evaluate generic macros/templates + n.sons[0] = semExprWithType(c, n.sons[0], + {efNoProcvarCheck, efNoEvaluateGeneric}) let arr = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyPtr, tyRef}) case arr.kind of tyArray, tyOpenArray, tyVarargs, tyArrayConstr, tySequence, tyString, @@ -1236,12 +1283,30 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = let s = if n.sons[0].kind == nkSym: n.sons[0].sym elif n[0].kind in nkSymChoices: n.sons[0][0].sym else: nil - if s != nil and s.kind in {skProc, skMethod, skConverter, skIterator}: - # type parameters: partial generic specialization - n.sons[0] = semSymGenericInstantiation(c, n.sons[0], s) - result = explicitGenericInstantiation(c, n, s) - elif s != nil and s.kind == skType: - result = symNodeFromType(c, semTypeNode(c, n, nil), n.info) + if s != nil: + case s.kind + of skProc, skMethod, skConverter, skIterator: + # type parameters: partial generic specialization + n.sons[0] = semSymGenericInstantiation(c, n.sons[0], s) + result = explicitGenericInstantiation(c, n, s) + of skMacro, skTemplate: + if efInCall in flags: + # We are processing macroOrTmpl[] in macroOrTmpl[](...) call. + # Return as is, so it can be transformed into complete macro or + # template call in semIndirectOp caller. + result = n + else: + # We are processing macroOrTmpl[] not in call. Transform it to the + # macro or template call with generic arguments here. + n.kind = nkCall + case s.kind + of skMacro: result = semMacroExpr(c, n, n, s, flags) + of skTemplate: result = semTemplateExpr(c, n, s, flags) + else: discard + of skType: + result = symNodeFromType(c, semTypeNode(c, n, nil), n.info) + else: + c.p.bracketExpr = n.sons[0] else: c.p.bracketExpr = n.sons[0] @@ -1818,7 +1883,7 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = result = nil template setResult(e: expr) = - if semCheck: result = semStmt(c, e) # do not open a new scope! + if semCheck: result = semExpr(c, e) # do not open a new scope! else: result = e # Check if the node is "when nimvm" @@ -1827,6 +1892,7 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = # else: # ... var whenNimvm = false + var typ = commonTypeBegin if n.sons.len == 2 and n.sons[0].kind == nkElifBranch and n.sons[1].kind == nkElse: let exprNode = n.sons[0].sons[0] @@ -1843,7 +1909,8 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = checkSonsLen(it, 2) if whenNimvm: if semCheck: - it.sons[1] = semStmt(c, it.sons[1]) + it.sons[1] = semExpr(c, it.sons[1]) + typ = commonType(typ, it.sons[1].typ) result = n # when nimvm is not elimited until codegen else: var e = semConstExpr(c, it.sons[0]) @@ -1857,12 +1924,14 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode = checkSonsLen(it, 1) if result == nil or whenNimvm: if semCheck: - it.sons[0] = semStmt(c, it.sons[0]) + it.sons[0] = semExpr(c, it.sons[0]) + typ = commonType(typ, it.sons[0].typ) if result == nil: result = it.sons[0] else: illFormedAst(n) if result == nil: result = newNodeI(nkEmpty, n.info) + if whenNimvm: result.typ = typ # The ``when`` statement implements the mechanism for platform dependent # code. Thus we try to ensure here consistent ID allocation after the # ``when`` statement. diff --git a/compiler/semfold.nim b/compiler/semfold.nim index c5a8cc2a2..feea6ce34 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -489,7 +489,7 @@ proc leValueConv(a, b: PNode): bool = of nkCharLit..nkUInt64Lit: case b.kind of nkCharLit..nkUInt64Lit: result = a.intVal <= b.intVal - of nkFloatLit..nkFloat128Lit: result = a.intVal <= round(b.floatVal) + of nkFloatLit..nkFloat128Lit: result = a.intVal <= round(b.floatVal).int else: internalError(a.info, "leValueConv") of nkFloatLit..nkFloat128Lit: case b.kind diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 14631a590..e4ac56cd6 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -43,9 +43,11 @@ proc rawHandleSelf(c: PContext; owner: PSym) = if arg.name.id == c.selfName.id: c.p.selfSym = arg arg.flags.incl sfIsSelf - let t = c.p.selfSym.typ.skipTypes(abstractPtrs) - if t.kind == tyObject: + var t = c.p.selfSym.typ.skipTypes(abstractPtrs) + while t.kind == tyObject: addObjFieldsToLocalScope(c, t.n) + if t.sons[0] == nil: break + t = t.sons[0].skipTypes(abstractPtrs) proc pushProcCon*(c: PContext; owner: PSym) = rawPushProcCon(c, owner) diff --git a/compiler/semmacrosanity.nim b/compiler/semmacrosanity.nim index 150680af7..6cd5c4a3c 100644 --- a/compiler/semmacrosanity.nim +++ b/compiler/semmacrosanity.nim @@ -12,25 +12,26 @@ import ast, astalgo, msgs, types -proc ithField(n: PNode, field: int): PSym = +proc ithField(n: PNode, field: var int): PSym = result = nil case n.kind of nkRecList: for i in countup(0, sonsLen(n) - 1): - result = ithField(n.sons[i], field-i) + result = ithField(n.sons[i], field) if result != nil: return of nkRecCase: if n.sons[0].kind != nkSym: internalError(n.info, "ithField") - result = ithField(n.sons[0], field-1) + result = ithField(n.sons[0], field) if result != nil: return for i in countup(1, sonsLen(n) - 1): case n.sons[i].kind of nkOfBranch, nkElse: - result = ithField(lastSon(n.sons[i]), field-1) + result = ithField(lastSon(n.sons[i]), field) if result != nil: return else: internalError(n.info, "ithField(record case branch)") of nkSym: if field == 0: result = n.sym + else: dec(field) else: discard proc annotateType*(n: PNode, t: PType) = @@ -39,10 +40,13 @@ proc annotateType*(n: PNode, t: PType) = # to not to skip tyGenericInst case n.kind of nkObjConstr: + let x = t.skipTypes(abstractPtrs) n.typ = t for i in 1 .. <n.len: - let field = x.n.ithField(i - 1) - if field.isNil: globalError n.info, "invalid field at index " & $i + var j = i-1 + let field = x.n.ithField(j) + if field.isNil: + globalError n.info, "invalid field at index " & $i else: internalAssert(n.sons[i].kind == nkExprColonExpr) annotateType(n.sons[i].sons[1], field.typ) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index f6bfca955..0b2343c10 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -17,7 +17,8 @@ proc semDiscard(c: PContext, n: PNode): PNode = checkSonsLen(n, 1) if n.sons[0].kind != nkEmpty: n.sons[0] = semExprWithType(c, n.sons[0]) - if isEmptyType(n.sons[0].typ): localError(n.info, errInvalidDiscard) + if isEmptyType(n.sons[0].typ) or n.sons[0].typ.kind == tyNone: + localError(n.info, errInvalidDiscard) proc semBreakOrContinue(c: PContext, n: PNode): PNode = result = n @@ -535,7 +536,7 @@ proc semConst(c: PContext, n: PNode): PNode = localError(a.sons[2].info, errConstExprExpected) continue if typeAllowed(typ, skConst) != nil and def.kind != nkNilLit: - localError(a.info, errXisNoType, typeToString(typ)) + localError(a.info, "invalid type for const: " & typeToString(typ)) continue v.typ = typ v.ast = def # no need to copy @@ -781,9 +782,9 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = var x = a[2] while x.kind in {nkStmtList, nkStmtListExpr} and x.len > 0: x = x.lastSon - if x.kind notin {nkObjectTy, nkDistinctTy, nkEnumTy, nkEmpty}: + if x.kind notin {nkObjectTy, nkDistinctTy, nkEnumTy, nkEmpty} and + s.typ.kind notin {tyObject, tyEnum}: # type aliases are hard: - #MessageOut('for type ' + typeToString(s.typ)); var t = semTypeNode(c, x, nil) assert t != nil if t.kind in {tyObject, tyEnum, tyDistinct}: @@ -929,6 +930,17 @@ proc semProcAnnotation(c: PContext, prc: PNode; pragma(c, result[namePos].sym, result[pragmasPos], validPragmas) return +proc setGenericParamsMisc(c: PContext; n: PNode): PNode = + let orig = n.sons[genericParamsPos] + # we keep the original params around for better error messages, see + # issue https://github.com/nim-lang/Nim/issues/1713 + result = semGenericParamList(c, orig) + if n.sons[miscPos].kind == nkEmpty: + n.sons[miscPos] = newTree(nkBracket, ast.emptyNode, orig) + else: + n.sons[miscPos].sons[1] = orig + n.sons[genericParamsPos] = result + proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = # XXX semProcAux should be good enough for this now, we will eventually # remove semLambda @@ -947,8 +959,7 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = openScope(c) var gp: PNode if n.sons[genericParamsPos].kind != nkEmpty: - n.sons[genericParamsPos] = semGenericParamList(c, n.sons[genericParamsPos]) - gp = n.sons[genericParamsPos] + gp = setGenericParamsMisc(c, n) else: gp = newNodeI(nkGenericParams, n.info) @@ -1170,8 +1181,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, openScope(c) var gp: PNode if n.sons[genericParamsPos].kind != nkEmpty: - n.sons[genericParamsPos] = semGenericParamList(c, n.sons[genericParamsPos]) - gp = n.sons[genericParamsPos] + gp = setGenericParamsMisc(c, n) else: gp = newNodeI(nkGenericParams, n.info) # process parameters: @@ -1250,7 +1260,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, c.p.wasForwarded = proto != nil maybeAddResult(c, s, n) - if sfImportc notin s.flags: + if lfDynamicLib notin s.loc.flags: # no semantic checking for importc: let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) # unfortunately we cannot skip this step when in 'system.compiles' @@ -1330,7 +1340,8 @@ proc finishMethod(c: PContext, s: PSym) = proc semMethod(c: PContext, n: PNode): PNode = if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "method") result = semProcAux(c, n, skMethod, methodPragmas) - + # macros can transform methods to nothing: + if namePos >= result.safeLen: return result var s = result.sons[namePos].sym if not isGenericRoutine(s): # why check for the body? bug #2400 has none. Checking for sfForward makes @@ -1345,6 +1356,8 @@ proc semConverterDef(c: PContext, n: PNode): PNode = if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "converter") checkSonsLen(n, bodyPos + 1) result = semProcAux(c, n, skConverter, converterPragmas) + # macros can transform converters to nothing: + if namePos >= result.safeLen: return result var s = result.sons[namePos].sym var t = s.typ if t.sons[0] == nil: localError(n.info, errXNeedsReturnType, "converter") @@ -1354,6 +1367,8 @@ proc semConverterDef(c: PContext, n: PNode): PNode = proc semMacroDef(c: PContext, n: PNode): PNode = checkSonsLen(n, bodyPos + 1) result = semProcAux(c, n, skMacro, macroPragmas) + # macros can transform macros to nothing: + if namePos >= result.safeLen: return result var s = result.sons[namePos].sym var t = s.typ if t.sons[0] == nil: localError(n.info, errXNeedsReturnType, "macro") diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index ba17cc307..13b283fe5 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -398,15 +398,18 @@ proc semIdentVis(c: PContext, kind: TSymKind, n: PNode, allowed: TSymFlags): PSym = # identifier with visibility if n.kind == nkPostfix: - if sonsLen(n) == 2 and n.sons[0].kind == nkIdent: + if sonsLen(n) == 2: # for gensym'ed identifiers the identifier may already have been # transformed to a symbol and we need to use that here: result = newSymG(kind, n.sons[1], c) - var v = n.sons[0].ident + var v = considerQuotedIdent(n.sons[0]) if sfExported in allowed and v.id == ord(wStar): incl(result.flags, sfExported) else: - localError(n.sons[0].info, errInvalidVisibilityX, v.s) + if not (sfExported in allowed): + localError(n.sons[0].info, errXOnlyAtModuleScope, "export") + else: + localError(n.sons[0].info, errInvalidVisibilityX, renderTree(n[0])) else: illFormedAst(n) else: @@ -1094,10 +1097,14 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = result = instGenericContainer(c, n.info, result, allowMetaTypes = false) -proc semTypeExpr(c: PContext, n: PNode): PType = +proc semTypeExpr(c: PContext, n: PNode; prev: PType): PType = var n = semExprWithType(c, n, {efDetermineType}) if n.typ.kind == tyTypeDesc: result = n.typ.base + # fix types constructed by macros: + if prev != nil and prev.sym != nil and result.sym.isNil: + result.sym = prev.sym + result.sym.typ = result else: localError(n.info, errTypeExpected, n.renderTree) result = errorType(c) @@ -1177,7 +1184,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = else: localError(n.info, errGenerated, "invalid type") elif n[0].kind notin nkIdentKinds: - result = semTypeExpr(c, n) + result = semTypeExpr(c, n, prev) else: let op = considerQuotedIdent(n.sons[0]) if op.id in {ord(wAnd), ord(wOr)} or op.s == "|": @@ -1218,7 +1225,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = let typExpr = semExprWithType(c, n.sons[1], {efInTypeof}) result = typExpr.typ else: - result = semTypeExpr(c, n) + result = semTypeExpr(c, n, prev) of nkWhenStmt: var whenResult = semWhen(c, n, false) if whenResult.kind == nkStmtList: whenResult.kind = nkStmtListType diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index ecef11d13..e72db45e7 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -242,6 +242,8 @@ proc argTypeToString(arg: PNode; prefer: TPreferedDesc): string = for i in 1 .. <arg.len: result.add(" | ") result.add typeToString(arg[i].typ, prefer) + elif arg.typ == nil: + result = "void" else: result = arg.typ.typeToString(prefer) @@ -253,15 +255,15 @@ proc describeArgs*(c: PContext, n: PNode, startIdx = 1; if n.sons[i].kind == nkExprEqExpr: add(result, renderTree(n.sons[i].sons[0])) add(result, ": ") - if arg.typ.isNil: + if arg.typ.isNil and arg.kind notin {nkStmtList, nkDo}: arg = c.semOperand(c, n.sons[i].sons[1]) n.sons[i].typ = arg.typ n.sons[i].sons[1] = arg else: - if arg.typ.isNil: + if arg.typ.isNil and arg.kind notin {nkStmtList, nkDo}: arg = c.semOperand(c, n.sons[i]) n.sons[i] = arg - if arg.typ.kind == tyError: return + if arg.typ != nil and arg.typ.kind == tyError: return add(result, argTypeToString(arg, prefer)) if i != sonsLen(n) - 1: add(result, ", ") diff --git a/compiler/vm.nim b/compiler/vm.nim index f799334d6..f275b7b9b 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -1185,19 +1185,35 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcNGetType: let rb = instr.regB let rc = instr.regC - if rc == 0: + case rc: + of 0: + # getType opcode: ensureKind(rkNode) if regs[rb].kind == rkNode and regs[rb].node.typ != nil: regs[ra].node = opMapTypeToAst(regs[rb].node.typ, c.debug[pc]) else: stackTrace(c, tos, pc, errGenerated, "node has no type") - else: + of 1: # typeKind opcode: ensureKind(rkInt) if regs[rb].kind == rkNode and regs[rb].node.typ != nil: regs[ra].intVal = ord(regs[rb].node.typ.kind) #else: # stackTrace(c, tos, pc, errGenerated, "node has no type") + of 2: + # getTypeInst opcode: + ensureKind(rkNode) + if regs[rb].kind == rkNode and regs[rb].node.typ != nil: + regs[ra].node = opMapTypeInstToAst(regs[rb].node.typ, c.debug[pc]) + else: + stackTrace(c, tos, pc, errGenerated, "node has no type") + else: + # getTypeImpl opcode: + ensureKind(rkNode) + if regs[rb].kind == rkNode and regs[rb].node.typ != nil: + regs[ra].node = opMapTypeImplToAst(regs[rb].node.typ, c.debug[pc]) + else: + stackTrace(c, tos, pc, errGenerated, "node has no type") of opcNStrVal: decodeB(rkNode) createStr regs[ra] diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index a4f02092d..678a765f4 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -import ast, types, msgs, osproc, streams, options, idents, securehash +import ast, types, msgs, os, osproc, streams, options, idents, securehash proc readOutput(p: Process): string = result = "" @@ -51,7 +51,9 @@ proc opGorge*(cmd, input, cache: string): string = proc opSlurp*(file: string, info: TLineInfo, module: PSym): string = try: - let filename = file.findFile + var filename = parentDir(info.toFullPath) / file + if not fileExists(filename): + filename = file.findFile result = readFile(filename) # we produce a fake include statement for every slurped filename, so that # the module dependencies are accurate: @@ -67,9 +69,11 @@ proc atomicTypeX(name: string; t: PType; info: TLineInfo): PNode = result = newSymNode(sym) result.typ = t -proc mapTypeToAst(t: PType, info: TLineInfo; allowRecursion=false): PNode +proc mapTypeToAstX(t: PType; info: TLineInfo; + inst=false; allowRecursionX=false): PNode -proc mapTypeToBracket(name: string; t: PType; info: TLineInfo): PNode = +proc mapTypeToBracketX(name: string; t: PType; info: TLineInfo; + inst=false): PNode = result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) result.add atomicTypeX(name, t, info) for i in 0 .. < t.len: @@ -78,10 +82,39 @@ proc mapTypeToBracket(name: string; t: PType; info: TLineInfo): PNode = void.typ = newType(tyEmpty, t.owner) result.add void else: - result.add mapTypeToAst(t.sons[i], info) + result.add mapTypeToAstX(t.sons[i], info, inst) -proc mapTypeToAst(t: PType, info: TLineInfo; allowRecursion=false): PNode = +proc mapTypeToAstX(t: PType; info: TLineInfo; + inst=false; allowRecursionX=false): PNode = + var allowRecursion = allowRecursionX template atomicType(name): expr = atomicTypeX(name, t, info) + template mapTypeToAst(t,info): expr = mapTypeToAstX(t, info, inst) + template mapTypeToAstR(t,info): expr = mapTypeToAstX(t, info, inst, true) + template mapTypeToAst(t,i,info): expr = + if i<t.len and t.sons[i]!=nil: mapTypeToAstX(t.sons[i], info, inst) + else: ast.emptyNode + template mapTypeToBracket(name,t,info): expr = + mapTypeToBracketX(name, t, info, inst) + template newNodeX(kind):expr = + newNodeIT(kind, if t.n.isNil: info else: t.n.info, t) + template newIdent(s):expr = + var r = newNodeX(nkIdent) + r.add !s + r + template newIdentDefs(n,t):expr = + var id = newNodeX(nkIdentDefs) + id.add n # name + id.add mapTypeToAst(t, info) # type + id.add ast.emptyNode # no assigned value + id + template newIdentDefs(s):expr = newIdentDefs(s, s.typ) + + if inst: + if t.sym != nil: # if this node has a symbol + if allowRecursion: # getTypeImpl behavior: turn off recursion + allowRecursion = false + else: # getTypeInst behavior: return symbol + return atomicType(t.sym.name.s) case t.kind of tyNone: result = atomicType("none") @@ -94,7 +127,14 @@ proc mapTypeToAst(t: PType, info: TLineInfo; allowRecursion=false): PNode = of tyArrayConstr, tyArray: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) result.add atomicType("array") - result.add mapTypeToAst(t.sons[0], info) + if inst and t.sons[0].kind == tyRange: + var rng = newNodeX(nkInfix) + rng.add newIdentNode(getIdent(".."), info) + rng.add t.sons[0].n.sons[0].copyTree + rng.add t.sons[0].n.sons[1].copyTree + result.add rng + else: + result.add mapTypeToAst(t.sons[0], info) result.add mapTypeToAst(t.sons[1], info) of tyTypeDesc: if t.base != nil: @@ -107,34 +147,95 @@ proc mapTypeToAst(t: PType, info: TLineInfo; allowRecursion=false): PNode = result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) for i in 0 .. < t.len: result.add mapTypeToAst(t.sons[i], info) - of tyGenericInst, tyGenericBody, tyOrdinal, tyUserTypeClassInst: + of tyGenericInst: + if inst: + if allowRecursion: + result = mapTypeToAstR(t.lastSon, info) + else: + result = newNodeX(nkBracketExpr) + result.add mapTypeToAst(t.lastSon, info) + for i in 1 .. < t.len-1: + result.add mapTypeToAst(t.sons[i], info) + else: + result = mapTypeToAst(t.lastSon, info) + of tyGenericBody, tyOrdinal, tyUserTypeClassInst: result = mapTypeToAst(t.lastSon, info) of tyDistinct: - if allowRecursion: - result = mapTypeToBracket("distinct", t, info) + if inst: + result = newNodeX(nkDistinctTy) + result.add mapTypeToAst(t.sons[0], info) else: - result = atomicType(t.sym.name.s) + if allowRecursion or t.sym==nil: + result = mapTypeToBracket("distinct", t, info) + else: + result = atomicType(t.sym.name.s) of tyGenericParam, tyForward: result = atomicType(t.sym.name.s) of tyObject: - if allowRecursion: - result = newNodeIT(nkObjectTy, if t.n.isNil: info else: t.n.info, t) - if t.sons[0] == nil: - result.add ast.emptyNode + if inst: + result = newNodeX(nkObjectTy) + result.add ast.emptyNode # pragmas not reconstructed yet + if t.sons[0]==nil: result.add ast.emptyNode # handle parent object + else: + var nn = newNodeX(nkOfInherit) + nn.add mapTypeToAst(t.sons[0], info) + result.add nn + if t.n.sons.len>0: + var rl = copyNode(t.n) # handle nkRecList + for s in t.n.sons: + rl.add newIdentDefs(s) + result.add rl else: - result.add mapTypeToAst(t.sons[0], info) - result.add copyTree(t.n) + result.add ast.emptyNode else: - result = atomicType(t.sym.name.s) + if allowRecursion or t.sym == nil: + result = newNodeIT(nkObjectTy, if t.n.isNil: info else: t.n.info, t) + result.add ast.emptyNode + if t.sons[0] == nil: + result.add ast.emptyNode + else: + result.add mapTypeToAst(t.sons[0], info) + result.add copyTree(t.n) + else: + result = atomicType(t.sym.name.s) of tyEnum: result = newNodeIT(nkEnumTy, if t.n.isNil: info else: t.n.info, t) result.add copyTree(t.n) - of tyTuple: result = mapTypeToBracket("tuple", t, info) + of tyTuple: + if inst: + result = newNodeX(nkTupleTy) + for s in t.n.sons: + result.add newIdentDefs(s) + else: + result = mapTypeToBracket("tuple", t, info) of tySet: result = mapTypeToBracket("set", t, info) - of tyPtr: result = mapTypeToBracket("ptr", t, info) - of tyRef: result = mapTypeToBracket("ref", t, info) + of tyPtr: + if inst: + result = newNodeX(nkPtrTy) + result.add mapTypeToAst(t.sons[0], info) + else: + result = mapTypeToBracket("ptr", t, info) + of tyRef: + if inst: + result = newNodeX(nkRefTy) + result.add mapTypeToAst(t.sons[0], info) + else: + result = mapTypeToBracket("ref", t, info) of tyVar: result = mapTypeToBracket("var", t, info) of tySequence: result = mapTypeToBracket("seq", t, info) - of tyProc: result = mapTypeToBracket("proc", t, info) + of tyProc: + if inst: + result = newNodeX(nkProcTy) + var fp = newNodeX(nkFormalParams) + if t.sons[0] == nil: + fp.add ast.emptyNode + else: + fp.add mapTypeToAst(t.sons[0], t.n[0].info) + for i in 1..<t.sons.len: + fp.add newIdentDefs(t.n[i], t.sons[i]) + result.add fp + result.add ast.emptyNode # pragmas aren't reconstructed yet + else: + result = mapTypeToBracket("proc", t, info) of tyOpenArray: result = mapTypeToBracket("openArray", t, info) of tyRange: result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) @@ -174,10 +275,24 @@ proc mapTypeToAst(t: PType, info: TLineInfo; allowRecursion=false): PNode = of tyNot: result = mapTypeToBracket("not", t, info) of tyAnything: result = atomicType"anything" of tyStatic, tyFromExpr, tyFieldAccessor: - result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) - result.add atomicType("static") - if t.n != nil: - result.add t.n.copyTree + if inst: + if t.n != nil: result = t.n.copyTree + else: result = atomicType "void" + else: + result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t) + result.add atomicType "static" + if t.n != nil: + result.add t.n.copyTree proc opMapTypeToAst*(t: PType; info: TLineInfo): PNode = - result = mapTypeToAst(t, info, true) + result = mapTypeToAstX(t, info, false, true) + +# the "Inst" version includes generic parameters in the resulting type tree +# and also tries to look like the corresponding Nim type declaration +proc opMapTypeInstToAst*(t: PType; info: TLineInfo): PNode = + result = mapTypeToAstX(t, info, true, false) + +# the "Impl" version includes generic parameters in the resulting type tree +# and also tries to look like the corresponding Nim type implementation +proc opMapTypeImplToAst*(t: PType; info: TLineInfo): PNode = + result = mapTypeToAstX(t, info, true, true) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index bd32ccc17..675fb2dc2 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -983,7 +983,12 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mNGetType: let tmp = c.genx(n.sons[1]) if dest < 0: dest = c.getTemp(n.typ) - c.gABC(n, opcNGetType, dest, tmp, if n[0].sym.name.s == "typeKind": 1 else: 0) + let rc = case n[0].sym.name.s: + of "getType": 0 + of "typeKind": 1 + of "getTypeInst": 2 + else: 3 # "getTypeImpl" + c.gABC(n, opcNGetType, dest, tmp, rc) c.freeTemp(tmp) #genUnaryABC(c, n, dest, opcNGetType) of mNStrVal: genUnaryABC(c, n, dest, opcNStrVal) @@ -1882,8 +1887,8 @@ proc optimizeJumps(c: PCtx; start: int) = else: discard proc genProc(c: PCtx; s: PSym): int = - let x = s.ast.sons[optimizedCodePos] - if x.kind == nkEmpty: + var x = s.ast.sons[miscPos] + if x.kind == nkEmpty or x[0].kind == nkEmpty: #if s.name.s == "outterMacro" or s.name.s == "innerProc": # echo "GENERATING CODE FOR ", s.name.s let last = c.code.len-1 @@ -1894,7 +1899,11 @@ proc genProc(c: PCtx; s: PSym): int = c.debug.setLen(last) #c.removeLastEof result = c.code.len+1 # skip the jump instruction - s.ast.sons[optimizedCodePos] = newIntNode(nkIntLit, result) + if x.kind == nkEmpty: + x = newTree(nkBracket, newIntNode(nkIntLit, result), ast.emptyNode) + else: + x.sons[0] = newIntNode(nkIntLit, result) + s.ast.sons[miscPos] = x # thanks to the jmp we can add top level statements easily and also nest # procs easily: let body = s.getBody @@ -1929,4 +1938,4 @@ proc genProc(c: PCtx; s: PSym): int = c.prc = oldPrc else: c.prc.maxSlots = s.offset - result = x.intVal.int + result = x[0].intVal.int diff --git a/compiler/vmops.nim b/compiler/vmops.nim index e40e05eff..d0b3119e2 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -24,22 +24,27 @@ template osop(op) {.immediate, dirty.} = template systemop(op) {.immediate, dirty.} = registerCallback(c, "stdlib.system." & astToStr(op), `op Wrapper`) -template wrap1f(op) {.immediate, dirty.} = +template wrap1f_math(op) {.immediate, dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = setResult(a, op(getFloat(a, 0))) mathop op -template wrap2f(op) {.immediate, dirty.} = +template wrap2f_math(op) {.immediate, dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = setResult(a, op(getFloat(a, 0), getFloat(a, 1))) mathop op -template wrap1s(op) {.immediate, dirty.} = +template wrap1s_os(op) {.immediate, dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = setResult(a, op(getString(a, 0))) osop op -template wrap2svoid(op) {.immediate, dirty.} = +template wrap1s_system(op) {.immediate, dirty.} = + proc `op Wrapper`(a: VmArgs) {.nimcall.} = + setResult(a, op(getString(a, 0))) + systemop op + +template wrap2svoid_system(op) {.immediate, dirty.} = proc `op Wrapper`(a: VmArgs) {.nimcall.} = op(getString(a, 0), getString(a, 1)) systemop op @@ -55,34 +60,35 @@ proc staticWalkDirImpl(path: string, relative: bool): PNode = newStrNode(nkStrLit, f)) proc registerAdditionalOps*(c: PCtx) = - wrap1f(sqrt) - wrap1f(ln) - wrap1f(log10) - wrap1f(log2) - wrap1f(exp) - wrap1f(round) - wrap1f(arccos) - wrap1f(arcsin) - wrap1f(arctan) - wrap2f(arctan2) - wrap1f(cos) - wrap1f(cosh) - wrap2f(hypot) - wrap1f(sinh) - wrap1f(sin) - wrap1f(tan) - wrap1f(tanh) - wrap2f(pow) - wrap1f(trunc) - wrap1f(floor) - wrap1f(ceil) - wrap2f(fmod) + wrap1f_math(sqrt) + wrap1f_math(ln) + wrap1f_math(log10) + wrap1f_math(log2) + wrap1f_math(exp) + wrap1f_math(round) + wrap1f_math(arccos) + wrap1f_math(arcsin) + wrap1f_math(arctan) + wrap2f_math(arctan2) + wrap1f_math(cos) + wrap1f_math(cosh) + wrap2f_math(hypot) + wrap1f_math(sinh) + wrap1f_math(sin) + wrap1f_math(tan) + wrap1f_math(tanh) + wrap2f_math(pow) + wrap1f_math(trunc) + wrap1f_math(floor) + wrap1f_math(ceil) + wrap2f_math(fmod) - wrap1s(getEnv) - wrap1s(existsEnv) - wrap1s(dirExists) - wrap1s(fileExists) - wrap2svoid(writeFile) + wrap1s_os(getEnv) + wrap1s_os(existsEnv) + wrap1s_os(dirExists) + wrap1s_os(fileExists) + wrap2svoid_system(writeFile) + wrap1s_system(readFile) systemop getCurrentExceptionMsg registerCallback c, "stdlib.*.staticWalkDir", proc (a: VmArgs) {.nimcall.} = setResult(a, staticWalkDirImpl(getString(a, 0), getBool(a, 1))) diff --git a/doc/filters.txt b/doc/filters.txt index 46bc6c3e5..1937b187c 100644 --- a/doc/filters.txt +++ b/doc/filters.txt @@ -23,6 +23,10 @@ just like an ordinary procedure call with named or positional arguments. The available parameters depend on the invoked filter. Before version 0.12.0 of the language ``#!`` was used instead of ``#?``. +**Hint:** With ``--hint[codeBegin]:on```or ``--verbosity:2`` +(or higher) Nim lists the processed code after each filter +application. + Pipe operator ============= @@ -41,9 +45,6 @@ Filters can be combined with the ``|`` pipe operator:: Available filters ================= -**Hint:** With ``--verbosity:2`` (or higher) Nim lists the processed code -after each filter application. - Replace filter -------------- diff --git a/doc/lib.txt b/doc/lib.txt index a789c6537..f749763a5 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -202,6 +202,9 @@ Math libraries * `mersenne <mersenne.html>`_ Mersenne twister random number generator. +* `random <random.html>`_ + Fast and tiny random number generator. + * `stats <stats.html>`_ Statistical analysis diff --git a/doc/manual/exceptions.txt b/doc/manual/exceptions.txt index e7af65386..d06c13df4 100644 --- a/doc/manual/exceptions.txt +++ b/doc/manual/exceptions.txt @@ -155,4 +155,4 @@ Exception hierarchy The exception tree is defined in the `system <system.html>`_ module: -.. include:: exception_hierarchy_fragment.txt +.. include:: ../exception_hierarchy_fragment.txt diff --git a/doc/manual/lexing.txt b/doc/manual/lexing.txt index 4187a60a4..4d03023c3 100644 --- a/doc/manual/lexing.txt +++ b/doc/manual/lexing.txt @@ -116,7 +116,7 @@ operator characters instead. The following keywords are reserved and cannot be used as identifiers: .. code-block:: nim - :file: keywords.txt + :file: ../keywords.txt Some keywords are unused; they are reserved for future developments of the language. diff --git a/doc/manual/syntax.txt b/doc/manual/syntax.txt index ca3b582ca..4f7483be8 100644 --- a/doc/manual/syntax.txt +++ b/doc/manual/syntax.txt @@ -119,6 +119,6 @@ Grammar The grammar's start symbol is ``module``. -.. include:: grammar.txt +.. include:: ../grammar.txt :literal: diff --git a/doc/manual/types.txt b/doc/manual/types.txt index a1596bcea..4c015ffb7 100644 --- a/doc/manual/types.txt +++ b/doc/manual/types.txt @@ -694,7 +694,7 @@ branch switch ``system.reset`` has to be used. Set type -------- -.. include:: sets_fragment.txt +.. include:: ../sets_fragment.txt Reference and pointer types --------------------------- diff --git a/doc/niminst.txt b/doc/niminst.txt index 7bd0f719e..bf5cb0f50 100644 --- a/doc/niminst.txt +++ b/doc/niminst.txt @@ -27,7 +27,7 @@ Configuration file niminst uses the Nim `parsecfg <parsecfg.html>`_ module to parse the configuration file. Here's an example of how the syntax looks like: -.. include:: doc/mytest.cfg +.. include:: mytest.cfg :literal: The value of a key-value pair can reference user-defined variables via @@ -190,6 +190,6 @@ Real world example The installers for the Nim compiler itself are generated by niminst. Have a look at its configuration file: -.. include:: compiler/installer.ini +.. include:: ../compiler/installer.ini :literal: diff --git a/examples/unix_socket/client.nim b/examples/unix_socket/client.nim new file mode 100644 index 000000000..f4283d64d --- /dev/null +++ b/examples/unix_socket/client.nim @@ -0,0 +1,6 @@ +import net + +let sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) + +sock.connectUnix("sock") +sock.send("hello\n") diff --git a/examples/unix_socket/server.nim b/examples/unix_socket/server.nim new file mode 100644 index 000000000..e798bbb48 --- /dev/null +++ b/examples/unix_socket/server.nim @@ -0,0 +1,14 @@ +import net + +let sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP) +sock.bindUnix("sock") +sock.listen() + +while true: + var client = new(Socket) + sock.accept(client) + var output = "" + output.setLen 32 + client.readLine(output) + echo "got ", output + client.close() diff --git a/koch.nim b/koch.nim index f7c0ae204..e8b08c5d2 100644 --- a/koch.nim +++ b/koch.nim @@ -336,10 +336,11 @@ template `|`(a, b): expr = (if a.len > 0: a else: b) proc tests(args: string) = # we compile the tester with taintMode:on to have a basic # taint mode test :-) - exec "nim cc --taintMode:on tests/testament/tester" + let nimexe = findNim() + exec nimexe & " cc --taintMode:on tests/testament/tester" # Since tests take a long time (on my machine), and we want to defy Murhpys # law - lets make sure the compiler really is freshly compiled! - exec "nim c --lib:lib -d:release --opt:speed compiler/nim.nim" + exec nimexe & " c --lib:lib -d:release --opt:speed compiler/nim.nim" let tester = quoteShell(getCurrentDir() / "tests/testament/tester".exe) let success = tryExec tester & " " & (args|"all") if not existsEnv("TRAVIS") and not existsEnv("APPVEYOR"): diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 2e3596d0f..16a954611 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -12,7 +12,7 @@ include "system/inclrtl" ## This module contains the interface to the compiler's abstract syntax ## tree (`AST`:idx:). Macros operate on this tree. -## .. include:: ../doc/astspec.txt +## .. include:: ../../doc/astspec.txt type NimNodeKind* = enum @@ -197,6 +197,18 @@ proc typeKind*(n: NimNode): NimTypeKind {.magic: "NGetType", noSideEffect.} ## Returns the type kind of the node 'n' that should represent a type, that ## means the node should have been obtained via `getType`. +proc getTypeInst*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} + ## Like getType except it includes generic parameters for a specific instance + +proc getTypeInst*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} + ## Like getType except it includes generic parameters for a specific instance + +proc getTypeImpl*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} + ## Like getType except it includes generic parameters for the implementation + +proc getTypeImpl*(n: typedesc): NimNode {.magic: "NGetType", noSideEffect.} + ## Like getType except it includes generic parameters for the implementation + proc strVal*(n: NimNode): string {.magic: "NStrVal", noSideEffect.} proc `intVal=`*(n: NimNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.} diff --git a/lib/impure/db_odbc.nim b/lib/impure/db_odbc.nim index 3a14e6304..d6343acc7 100644 --- a/lib/impure/db_odbc.nim +++ b/lib/impure/db_odbc.nim @@ -210,7 +210,7 @@ proc dbFormat(formatstr: SqlQuery, args: varargs[string]): string {. add(result, c) proc prepareFetch(db: var DbConn, query: SqlQuery, - args: varargs[string, `$`]) : TSqlSmallInt {. + args: varargs[string, `$`]): TSqlSmallInt {. tags: [ReadDbEffect, WriteDbEffect], raises: [DbError].} = # Prepare a statement, execute it and fetch the data to the driver # ready for retrieval of the data @@ -222,9 +222,8 @@ proc prepareFetch(db: var DbConn, query: SqlQuery, var q = dbFormat(query, args) db.sqlCheck(SQLPrepare(db.stmt, q.PSQLCHAR, q.len.TSqlSmallInt)) db.sqlCheck(SQLExecute(db.stmt)) - var retcode = SQLFetch(db.stmt) - db.sqlCheck(retcode) - result=retcode + result = SQLFetch(db.stmt) + db.sqlCheck(result) proc prepareFetchDirect(db: var DbConn, query: SqlQuery, args: varargs[string, `$`]) {. @@ -250,8 +249,8 @@ proc tryExec*(db: var DbConn, query: SqlQuery, args: varargs[string, `$`]): bool var rCnt = -1 res = SQLRowCount(db.stmt, rCnt) - if res != SQL_SUCCESS: dbError(db) properFreeResult(SQL_HANDLE_STMT, db.stmt) + if res != SQL_SUCCESS: dbError(db) except: discard return res == SQL_SUCCESS @@ -286,10 +285,10 @@ iterator fastRows*(db: var DbConn, query: SqlQuery, sz: TSqlSmallInt = 0 cCnt: TSqlSmallInt = 0.TSqlSmallInt res: TSqlSmallInt = 0.TSqlSmallInt - tempcCnt:TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled. - # tempcCnt,A field to store the number of temporary variables, for unknown reasons, + tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled. + # tempcCnt,A field to store the number of temporary variables, for unknown reasons, # after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0, - # so the values of the temporary variable to store the cCnt. + # so the values of the temporary variable to store the cCnt. # After every cycle and specified to cCnt. To ensure the traversal of all fields. res = db.prepareFetch(query, args) if res == SQL_NO_DATA: @@ -308,8 +307,8 @@ iterator fastRows*(db: var DbConn, query: SqlQuery, cCnt = tempcCnt yield rowRes res = SQLFetch(db.stmt) - db.sqlCheck(res) properFreeResult(SQL_HANDLE_STMT, db.stmt) + db.sqlCheck(res) iterator instantRows*(db: var DbConn, query: SqlQuery, args: varargs[string, `$`]): InstantRow @@ -317,14 +316,14 @@ iterator instantRows*(db: var DbConn, query: SqlQuery, ## Same as fastRows but returns a handle that can be used to get column text ## on demand using []. Returned handle is valid only within the interator body. var - rowRes: Row + rowRes: Row = @[] sz: TSqlSmallInt = 0 cCnt: TSqlSmallInt = 0.TSqlSmallInt res: TSqlSmallInt = 0.TSqlSmallInt - tempcCnt:TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled. - # tempcCnt,A field to store the number of temporary variables, for unknown reasons, + tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled. + # tempcCnt,A field to store the number of temporary variables, for unknown reasons, # after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0, - # so the values of the temporary variable to store the cCnt. + # so the values of the temporary variable to store the cCnt. # After every cycle and specified to cCnt. To ensure the traversal of all fields. res = db.prepareFetch(query, args) if res == SQL_NO_DATA: @@ -343,8 +342,8 @@ iterator instantRows*(db: var DbConn, query: SqlQuery, cCnt = tempcCnt yield (row: rowRes, len: cCnt.int) res = SQLFetch(db.stmt) - db.sqlCheck(res) properFreeResult(SQL_HANDLE_STMT, db.stmt) + db.sqlCheck(res) proc `[]`*(row: InstantRow, col: int): string {.inline.} = ## Returns text for given column of the row @@ -364,10 +363,10 @@ proc getRow*(db: var DbConn, query: SqlQuery, sz: TSqlSmallInt = 0.TSqlSmallInt cCnt: TSqlSmallInt = 0.TSqlSmallInt res: TSqlSmallInt = 0.TSqlSmallInt - tempcCnt:TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled. - ## tempcCnt,A field to store the number of temporary variables, for unknown reasons, + tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled. + ## tempcCnt,A field to store the number of temporary variables, for unknown reasons, ## after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0, - ## so the values of the temporary variable to store the cCnt. + ## so the values of the temporary variable to store the cCnt. ## After every cycle and specified to cCnt. To ensure the traversal of all fields. res = db.prepareFetch(query, args) if res == SQL_NO_DATA: @@ -385,8 +384,8 @@ proc getRow*(db: var DbConn, query: SqlQuery, cCnt = tempcCnt res = SQLFetch(db.stmt) result = rowRes - db.sqlCheck(res) properFreeResult(SQL_HANDLE_STMT, db.stmt) + db.sqlCheck(res) proc getAllRows*(db: var DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[Row] {. @@ -398,10 +397,10 @@ proc getAllRows*(db: var DbConn, query: SqlQuery, sz: TSqlSmallInt = 0 cCnt: TSqlSmallInt = 0.TSqlSmallInt res: TSqlSmallInt = 0.TSqlSmallInt - tempcCnt:TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled. - ## tempcCnt,A field to store the number of temporary variables, for unknown reasons, + tempcCnt: TSqlSmallInt # temporary cCnt,Fix the field values to be null when the release schema is compiled. + ## tempcCnt,A field to store the number of temporary variables, for unknown reasons, ## after performing a sqlgetdata function and circulating variables cCnt value will be changed to 0, - ## so the values of the temporary variable to store the cCnt. + ## so the values of the temporary variable to store the cCnt. ## After every cycle and specified to cCnt. To ensure the traversal of all fields. res = db.prepareFetch(query, args) if res == SQL_NO_DATA: @@ -421,8 +420,8 @@ proc getAllRows*(db: var DbConn, query: SqlQuery, rows.add(rowRes) res = SQLFetch(db.stmt) result = rows - db.sqlCheck(res) properFreeResult(SQL_HANDLE_STMT, db.stmt) + db.sqlCheck(res) iterator rows*(db: var DbConn, query: SqlQuery, args: varargs[string, `$`]): Row {. @@ -544,4 +543,4 @@ proc setEncoding*(connection: DbConn, encoding: string): bool {. ## Sets the encoding of a database connection, returns true for ## success, false for failure. ##result = set_character_set(connection, encoding) == 0 - dbError("setEncoding() is currently not implemented by the db_odbc module") \ No newline at end of file + dbError("setEncoding() is currently not implemented by the db_odbc module") diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index 1c2a39854..c636c5203 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -15,6 +15,8 @@ from math import ceil import options from unicode import runeLenAt +export options + ## What is NRE? ## ============ @@ -555,6 +557,16 @@ proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[st for match in str.findIter(pattern, start, endpos): result.add(match.match) +proc contains*(str: string, pattern: Regex, start = 0, endpos = int.high): bool = + ## Determine if the string contains the given pattern between the end and + ## start positions: + ## - "abc".contains(re"bc") == true + ## - "abc".contains(re"cd") == false + ## - "abc".contains(re"a", start = 1) == false + ## + ## Same as ``isSome(str.find(pattern, start, endpos))``. + return isSome(str.find(pattern, start, endpos)) + proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] = ## Splits the string with the given regex. This works according to the ## rules that Perl and Javascript use: diff --git a/lib/impure/re.nim b/lib/impure/re.nim index 60bb6c77f..d49c6d1c1 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -22,7 +22,7 @@ ## though. ## PRCE's licence follows: ## -## .. include:: ../doc/regexprs.txt +## .. include:: ../../doc/regexprs.txt ## import diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index e1d5f902e..ebe6fa19b 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -323,6 +323,11 @@ proc newSharedState(options: RstParseOptions, result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler result.findFile = if not isNil(findFile): findFile else: defaultFindFile +proc findRelativeFile(p: RstParser; filename: string): string = + result = p.filename.splitFile.dir / filename + if not existsFile(result): + result = p.s.findFile(filename) + proc rstMessage(p: RstParser, msgKind: MsgKind, arg: string) = p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, p.col + p.tok[p.idx].col, msgKind, arg) @@ -1500,7 +1505,7 @@ proc dirInclude(p: var RstParser): PRstNode = result = nil var n = parseDirective(p, {hasArg, argIsFile, hasOptions}, nil) var filename = strip(addNodes(n.sons[0])) - var path = p.s.findFile(filename) + var path = p.findRelativeFile(filename) if path == "": rstMessage(p, meCannotOpenFile, filename) else: @@ -1511,7 +1516,7 @@ proc dirInclude(p: var RstParser): PRstNode = else: var q: RstParser initParser(q, p.s) - q.filename = filename + q.filename = path q.col += getTokens(readFile(path), false, q.tok) # workaround a GCC bug; more like the interior pointer bug? #if find(q.tok[high(q.tok)].symbol, "\0\x01\x02") > 0: @@ -1538,7 +1543,7 @@ proc dirCodeBlock(p: var RstParser, nimExtension = false): PRstNode = result = parseDirective(p, {hasArg, hasOptions}, parseLiteralBlock) var filename = strip(getFieldValue(result, "file")) if filename != "": - var path = p.s.findFile(filename) + var path = p.findRelativeFile(filename) if path == "": rstMessage(p, meCannotOpenFile, filename) var n = newRstNode(rnLiteralBlock) add(n, newRstNode(rnLeaf, readFile(path))) @@ -1590,7 +1595,7 @@ proc dirRawAux(p: var RstParser, result: var PRstNode, kind: RstNodeKind, contentParser: SectionParser) = var filename = getFieldValue(result, "file") if filename.len > 0: - var path = p.s.findFile(filename) + var path = p.findRelativeFile(filename) if path.len == 0: rstMessage(p, meCannotOpenFile, filename) else: diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 19b068b60..bb42d6902 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -448,6 +448,14 @@ when hasSpawnH: Tposix_spawn_file_actions* {.importc: "posix_spawn_file_actions_t", header: "<spawn.h>", final, pure.} = object +when defined(linux): + # from sys/un.h + const Sockaddr_un_path_length* = 108 +else: + # according to http://pubs.opengroup.org/onlinepubs/009604499/basedefs/sys/un.h.html + # this is >=92 + const Sockaddr_un_path_length* = 92 + type Socklen* {.importc: "socklen_t", header: "<sys/socket.h>".} = cuint TSa_Family* {.importc: "sa_family_t", header: "<sys/socket.h>".} = cint @@ -457,6 +465,11 @@ type sa_family*: TSa_Family ## Address family. sa_data*: array [0..255, char] ## Socket address (variable-length data). + Sockaddr_un* {.importc: "struct sockaddr_un", header: "<sys/un.h>", + pure, final.} = object ## struct sockaddr_un + sun_family*: TSa_Family ## Address family. + sun_path*: array [0..Sockaddr_un_path_length-1, char] ## Socket path + Sockaddr_storage* {.importc: "struct sockaddr_storage", header: "<sys/socket.h>", pure, final.} = object ## struct sockaddr_storage @@ -1613,6 +1626,16 @@ else: var MAP_POPULATE*: cint = 0 +when defined(linux) or defined(nimdoc): + when defined(alpha) or defined(mips) or defined(parisc) or + defined(sparc) or defined(nimdoc): + const SO_REUSEPORT* = cint(0x0200) + ## Multiple binding: load balancing on incoming TCP connections + ## or UDP packets. (Requires Linux kernel > 3.9) + else: + const SO_REUSEPORT* = cint(15) +else: + var SO_REUSEPORT* {.importc, header: "<sys/socket.h>".}: cint when defined(macosx): # We can't use the NOSIGNAL flag in the ``send`` function, it has no effect @@ -1621,6 +1644,10 @@ when defined(macosx): MSG_NOSIGNAL* = 0'i32 var SO_NOSIGPIPE* {.importc, header: "<sys/socket.h>".}: cint +elif defined(solaris): + # Solaris dont have MSG_NOSIGNAL + const + MSG_NOSIGNAL* = 0'i32 else: var MSG_NOSIGNAL* {.importc, header: "<sys/socket.h>".}: cint @@ -2628,16 +2655,16 @@ proc utimes*(path: cstring, times: ptr array [2, Timeval]): int {. ## ## For more information read http://www.unix.com/man-page/posix/3/utimes/. -proc handle_signal(sig: cint, handler: proc (a: cint) {.noconv.}) {.importc: "signal", header: "<signal.h>".} - -template onSignal*(signals: varargs[cint], body: untyped): stmt = - ## Setup code to be executed when Unix signals are received. Example: - ## from posix import SIGINT, SIGTERM - ## onSignal(SIGINT, SIGTERM): - ## echo "bye" - - for s in signals: - handle_signal(s, - proc (sig: cint) {.noconv.} = - body +proc handle_signal(sig: cint, handler: proc (a: cint) {.noconv.}) {.importc: "signal", header: "<signal.h>".} + +template onSignal*(signals: varargs[cint], body: untyped): stmt = + ## Setup code to be executed when Unix signals are received. Example: + ## from posix import SIGINT, SIGTERM + ## onSignal(SIGINT, SIGTERM): + ## echo "bye" + + for s in signals: + handle_signal(s, + proc (sig: cint) {.noconv.} = + body ) diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 8345d43e5..2c7aaf2bf 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -11,7 +11,7 @@ include "system/inclrtl" import os, oids, tables, strutils, macros, times, heapqueue -import nativesockets, net +import nativesockets, net, queues export Port, SocketFlag @@ -155,6 +155,9 @@ type when not defined(release): var currentID = 0 + +proc callSoon*(cbproc: proc ()) {.gcsafe.} + proc newFuture*[T](fromProc: string = "unspecified"): Future[T] = ## Creates a new future. ## @@ -257,7 +260,7 @@ proc `callback=`*(future: FutureBase, cb: proc () {.closure,gcsafe.}) = ## passes ``future`` as a param to the callback. future.cb = cb if future.finished: - future.cb() + callSoon(future.cb) proc `callback=`*[T](future: Future[T], cb: proc (future: Future[T]) {.closure,gcsafe.}) = @@ -352,14 +355,42 @@ proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = fut2.callback = cb return retFuture +proc all*[A](futs: seq[Future[A]]): Future[seq[A]] = + ## Returns a future which will complete once all futures in ``futs`` + ## complete. + ## + ## The resulting future will hold the values of all awaited futures, + ## in the order they are passed. + + var + retFuture = newFuture[seq[A]]("asyncdispatch.all") + retValues = newSeq[A](len(futs)) + completedFutures = 0 + + for i, fut in futs: + fut.callback = proc(f: Future[A]) = + retValues[i] = f.read() + inc(completedFutures) + + if completedFutures == len(futs): + retFuture.complete(retValues) + + return retFuture + type PDispatcherBase = ref object of RootRef timers: HeapQueue[tuple[finishAt: float, fut: Future[void]]] + callbacks: Queue[proc ()] proc processTimers(p: PDispatcherBase) {.inline.} = while p.timers.len > 0 and epochTime() >= p.timers[0].finishAt: p.timers.pop().fut.complete() +proc processPendingCallbacks(p: PDispatcherBase) = + while p.callbacks.len > 0: + var cb = p.callbacks.dequeue() + cb() + proc adjustedTimeout(p: PDispatcherBase, timeout: int): int {.inline.} = # If dispatcher has active timers this proc returns the timeout # of the nearest timer. Returns `timeout` otherwise. @@ -403,6 +434,7 @@ when defined(windows) or defined(nimdoc): result.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1) result.handles = initSet[AsyncFD]() result.timers.newHeapQueue() + result.callbacks = initQueue[proc ()](64) var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -429,7 +461,7 @@ when defined(windows) or defined(nimdoc): proc poll*(timeout = 500) = ## Waits for completion events and processes them. let p = getGlobalDispatcher() - if p.handles.len == 0 and p.timers.len == 0: + if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0: raise newException(ValueError, "No handles or timers registered in dispatcher.") @@ -469,6 +501,8 @@ when defined(windows) or defined(nimdoc): # Timer processing. processTimers(p) + # Callback queue processing + processPendingCallbacks(p) var connectExPtr: pointer = nil var acceptExPtr: pointer = nil @@ -930,6 +964,7 @@ else: new result result.selector = newSelector() result.timers.newHeapQueue() + result.callbacks = initQueue[proc ()](64) var gDisp{.threadvar.}: PDispatcher ## Global dispatcher proc getGlobalDispatcher*(): PDispatcher = @@ -1025,7 +1060,10 @@ else: # (e.g. socket disconnected). discard + # Timer processing. processTimers(p) + # Callback queue processing + processPendingCallbacks(p) proc connect*(socket: AsyncFD, address: string, port: Port, domain = AF_INET): Future[void] = @@ -1604,6 +1642,11 @@ proc recvLine*(socket: AsyncFD): Future[string] {.async.} = return add(result, c) +proc callSoon*(cbproc: proc ()) = + ## Schedule `cbproc` to be called as soon as possible. + ## The callback is called when control returns to the event loop. + getGlobalDispatcher().callbacks.enqueue(cbproc) + proc runForever*() = ## Begins a never ending global dispatcher poll loop. while true: diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 590b52c1a..1eac4fc00 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -39,6 +39,7 @@ type AsyncHttpServer* = ref object socket: AsyncSocket reuseAddr: bool + reusePort: bool HttpCode* = enum Http100 = "100 Continue", @@ -99,10 +100,11 @@ proc `==`*(protocol: tuple[orig: string, major, minor: int], of HttpVer10: 0 result = protocol.major == major and protocol.minor == minor -proc newAsyncHttpServer*(reuseAddr = true): AsyncHttpServer = +proc newAsyncHttpServer*(reuseAddr = true, reusePort = false): AsyncHttpServer = ## Creates a new ``AsyncHttpServer`` instance. new result result.reuseAddr = reuseAddr + result.reusePort = reusePort proc addHeaders(msg: var string, headers: StringTableRef) = for k, v in headers: @@ -217,20 +219,20 @@ proc processClient(client: AsyncSocket, address: string, else: await client.sendStatus("417 Expectation Failed") - # Read the body - # - Check for Content-length header - if request.headers.hasKey("Content-Length"): - var contentLength = 0 - if parseInt(request.headers.getOrDefault("Content-Length"), - contentLength) == 0: - await request.respond(Http400, "Bad Request. Invalid Content-Length.") - continue - else: - request.body = await client.recv(contentLength) - assert request.body.len == contentLength - else: - await request.respond(Http400, "Bad Request. No Content-Length.") + # Read the body + # - Check for Content-length header + if request.headers.hasKey("Content-Length"): + var contentLength = 0 + if parseInt(request.headers.getOrDefault("Content-Length"), + contentLength) == 0: + await request.respond(Http400, "Bad Request. Invalid Content-Length.") continue + else: + request.body = await client.recv(contentLength) + assert request.body.len == contentLength + elif request.reqMethod == "post": + await request.respond(Http400, "Bad Request. No Content-Length.") + continue case request.reqMethod of "get", "post", "head", "put", "delete", "trace", "options", @@ -264,6 +266,8 @@ proc serve*(server: AsyncHttpServer, port: Port, server.socket = newAsyncSocket() if server.reuseAddr: server.socket.setSockOpt(OptReuseAddr, true) + if server.reusePort: + server.socket.setSockOpt(OptReusePort, true) server.socket.bindAddr(port, address) server.socket.listen() diff --git a/lib/pure/collections/LockFreeHash.nim b/lib/pure/collections/LockFreeHash.nim index bb77331df..954d62491 100644 --- a/lib/pure/collections/LockFreeHash.nim +++ b/lib/pure/collections/LockFreeHash.nim @@ -52,7 +52,7 @@ when sizeof(int) == 4: # 32bit {.deprecated: [TRaw: Raw].} elif sizeof(int) == 8: # 64bit type - Raw = range[0..4611686018427387903] + Raw = range[0'i64..4611686018427387903'i64] ## The range of uint values that can be stored directly in a value slot ## when on a 64 bit platform {.deprecated: [TRaw: Raw].} @@ -244,9 +244,9 @@ proc copySlot[K,V](idx: int, oldTbl: var PConcTable[K,V], newTbl: var PConcTable echo("oldVal is Tomb!!!, should not happen") if pop(oldVal) != 0: result = setVal(newTbl, pop(oldKey), pop(oldVal), 0, true) == 0 - if result: + #if result: #echo("Copied a Slot! idx= " & $idx & " key= " & $oldKey & " val= " & $oldVal) - else: + #else: #echo("copy slot failed") # Our copy is done so we disable the old slot while not ok: diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 0e3824a81..0817b38a3 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -657,7 +657,7 @@ template newSeqWith*(len: int, init: expr): expr = ## seq2D[1][0] = true ## seq2D[0][1] = true ## - ## import math + ## import random ## var seqRand = newSeqWith(20, random(10)) ## echo seqRand var result {.gensym.} = newSeq[type(init)](len) diff --git a/lib/pure/collections/sharedtables.nim b/lib/pure/collections/sharedtables.nim index 20e1bb7a9..17600b272 100644 --- a/lib/pure/collections/sharedtables.nim +++ b/lib/pure/collections/sharedtables.nim @@ -45,6 +45,59 @@ template withLock(t, x: untyped) = x release(t.lock) +template withValue*[A, B](t: var SharedTable[A, B], key: A, + value, body: untyped) = + ## retrieves the value at ``t[key]``. + ## `value` can be modified in the scope of the ``withValue`` call. + ## + ## .. code-block:: nim + ## + ## sharedTable.withValue(key, value) do: + ## # block is executed only if ``key`` in ``t`` + ## # value is threadsafe in block + ## value.name = "username" + ## value.uid = 1000 + ## + acquire(t.lock) + try: + var hc: Hash + var index = rawGet(t, key, hc) + let hasKey = index >= 0 + if hasKey: + var value {.inject.} = addr(t.data[index].val) + body + finally: + release(t.lock) + +template withValue*[A, B](t: var SharedTable[A, B], key: A, + value, body1, body2: untyped) = + ## retrieves the value at ``t[key]``. + ## `value` can be modified in the scope of the ``withValue`` call. + ## + ## .. code-block:: nim + ## + ## sharedTable.withValue(key, value) do: + ## # block is executed only if ``key`` in ``t`` + ## # value is threadsafe in block + ## value.name = "username" + ## value.uid = 1000 + ## do: + ## # block is executed when ``key`` not in ``t`` + ## raise newException(KeyError, "Key not found") + ## + acquire(t.lock) + try: + var hc: Hash + var index = rawGet(t, key, hc) + let hasKey = index >= 0 + if hasKey: + var value {.inject.} = addr(t.data[index].val) + body1 + else: + body2 + finally: + release(t.lock) + proc mget*[A, B](t: var SharedTable[A, B], key: A): var B = ## retrieves the value at ``t[key]``. The value can be modified. ## If `key` is not in `t`, the ``KeyError`` exception is raised. diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 58a789e76..b702f6248 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -136,6 +136,51 @@ proc mget*[A, B](t: var Table[A, B], key: A): var B {.deprecated.} = proc getOrDefault*[A, B](t: Table[A, B], key: A): B = getOrDefaultImpl(t, key) +template withValue*[A, B](t: var Table[A, B], key: A, + value, body: untyped) = + ## retrieves the value at ``t[key]``. + ## `value` can be modified in the scope of the ``withValue`` call. + ## + ## .. code-block:: nim + ## + ## sharedTable.withValue(key, value) do: + ## # block is executed only if ``key`` in ``t`` + ## value.name = "username" + ## value.uid = 1000 + ## + mixin rawGet + var hc: Hash + var index = rawGet(t, key, hc) + let hasKey = index >= 0 + if hasKey: + var value {.inject.} = addr(t.data[index].val) + body + +template withValue*[A, B](t: var Table[A, B], key: A, + value, body1, body2: untyped) = + ## retrieves the value at ``t[key]``. + ## `value` can be modified in the scope of the ``withValue`` call. + ## + ## .. code-block:: nim + ## + ## table.withValue(key, value) do: + ## # block is executed only if ``key`` in ``t`` + ## value.name = "username" + ## value.uid = 1000 + ## do: + ## # block is executed when ``key`` not in ``t`` + ## raise newException(KeyError, "Key not found") + ## + mixin rawGet + var hc: Hash + var index = rawGet(t, key, hc) + let hasKey = index >= 0 + if hasKey: + var value {.inject.} = addr(t.data[index].val) + body1 + else: + body2 + iterator allValues*[A, B](t: Table[A, B]; key: A): B = ## iterates over any value in the table `t` that belongs to the given `key`. var h: Hash = hash(key) and high(t.data) diff --git a/lib/pure/concurrency/cpuload.nim b/lib/pure/concurrency/cpuload.nim index 22598b5c9..b0fd002ed 100644 --- a/lib/pure/concurrency/cpuload.nim +++ b/lib/pure/concurrency/cpuload.nim @@ -79,6 +79,8 @@ proc advice*(s: var ThreadPoolState): ThreadPoolAdvice = inc s.calls when not defined(testing) and isMainModule: + import random + proc busyLoop() = while true: discard random(80) diff --git a/lib/pure/future.nim b/lib/pure/future.nim index 3793edc8b..4e2c1d893 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -29,21 +29,17 @@ proc createProcType(p, b: NimNode): NimNode {.compileTime.} = of nnkExprColonExpr: identDefs.add ident[0] identDefs.add ident[1] - of nnkIdent: + else: identDefs.add newIdentNode("i" & $i) identDefs.add(ident) - else: - error("Incorrect type list in proc type declaration.") identDefs.add newEmptyNode() formalParams.add identDefs - of nnkIdent: + else: var identDefs = newNimNode(nnkIdentDefs) identDefs.add newIdentNode("i0") identDefs.add(p) identDefs.add newEmptyNode() formalParams.add identDefs - else: - error("Incorrect type list in proc type declaration.") result.add formalParams result.add newEmptyNode() diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index f473e47d1..d59b8ecfe 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -79,10 +79,13 @@ ## constructor should be used for this purpose. However, ## currently only basic authentication is supported. -import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes, math +import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes, + math, random import asyncnet, asyncdispatch import nativesockets +export strtabs + type Response* = tuple[ version: string, diff --git a/lib/pure/json.nim b/lib/pure/json.nim index b9da8a0dd..cea7a1e90 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -49,6 +49,10 @@ ## "age": herAge ## } ## ] +## +## var j2 = %* {"name": "Isaac", "books": ["Robot Dreams"]} +## j2["details"] = %* {"age":35, "pi":3.1415} +## echo j2 import hashes, tables, strutils, lexbase, streams, unicode, macros @@ -707,17 +711,27 @@ proc `%`*(b: bool): JsonNode = proc `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode = ## Generic constructor for JSON data. Creates a new `JObject JsonNode` - new(result) - result.kind = JObject - result.fields = initTable[string, JsonNode](4) + result = newJObject() for key, val in items(keyVals): result.fields[key] = val -proc `%`*(elements: openArray[JsonNode]): JsonNode = +template `%`*(j: JsonNode): JsonNode = j + +proc `%`*[T](elements: openArray[T]): JsonNode = ## Generic constructor for JSON data. Creates a new `JArray JsonNode` - new(result) - result.kind = JArray - newSeq(result.elems, elements.len) - for i, p in pairs(elements): result.elems[i] = p + result = newJArray() + for elem in elements: result.add(%elem) + +proc `%`*(o: object): JsonNode = + ## Generic constructor for JSON data. Creates a new `JObject JsonNode` + result = newJObject() + for k, v in o.fieldPairs: result[k] = %v + +proc `%`*(o: ref object): JsonNode = + ## Generic constructor for JSON data. Creates a new `JObject JsonNode` + if o.isNil: + result = newJNull() + else: + result = %(o[]) proc toJson(x: NimNode): NimNode {.compiletime.} = case x.kind @@ -736,6 +750,9 @@ proc toJson(x: NimNode): NimNode {.compiletime.} = result = newNimNode(nnkTableConstr) x.expectLen(0) + of nnkNilLit: + result = newCall("newJNull") + else: result = x @@ -992,7 +1009,7 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true, result.add("null") proc pretty*(node: JsonNode, indent = 2): string = - ## Converts `node` to its JSON Representation, with indentation and + ## Returns a JSON Representation of `node`, with indentation and ## on multiple lines. result = "" toPretty(result, node, indent) @@ -1002,7 +1019,9 @@ proc toUgly*(result: var string, node: JsonNode) = ## regard for human readability. Meant to improve ``$`` string ## conversion performance. ## - ## This provides higher efficiency than the ``toPretty`` procedure as it + ## JSON representation is stored in the passed `result` + ## + ## This provides higher efficiency than the ``pretty`` procedure as it ## does **not** attempt to format the resulting JSON to make it human readable. var comma = false case node.kind: @@ -1325,10 +1344,26 @@ when isMainModule: var j4 = %*{"test": nil} doAssert j4 == %{"test": newJNull()} - echo("99% of tests finished. Going to try loading file.") + let seqOfNodes = @[%1, %2] + let jSeqOfNodes = %seqOfNodes + doAssert(jSeqOfNodes[1].num == 2) + + type MyObj = object + a, b: int + s: string + f32: float32 + f64: float64 + next: ref MyObj + var m: MyObj + m.s = "hi" + m.a = 5 + let jMyObj = %m + doAssert(jMyObj["a"].num == 5) + doAssert(jMyObj["s"].str == "hi") # Test loading of file. when not defined(js): + echo("99% of tests finished. Going to try loading file.") var parsed = parseFile("tests/testdata/jsontest.json") try: diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 84c8d3b11..2b903bedb 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -39,8 +39,6 @@ proc fac*(n: int): int {.noSideEffect.} = when defined(Posix) and not defined(haiku): {.passl: "-lm".} -when not defined(js) and not defined(nimscript): - import times const PI* = 3.1415926535897932384626433 ## the circle constant PI (Ludolph's number) @@ -119,192 +117,191 @@ proc sum*[T](x: openArray[T]): T {.noSideEffect.} = ## If `x` is empty, 0 is returned. for i in items(x): result = result + i -proc random*(max: int): int {.benign.} - ## Returns a random number in the range 0..max-1. The sequence of - ## random number is always the same, unless `randomize` is called - ## which initializes the random number generator with a "random" - ## number, i.e. a tickcount. - -proc random*(max: float): float {.benign.} - ## Returns a random number in the range 0..<max. The sequence of - ## random number is always the same, unless `randomize` is called - ## which initializes the random number generator with a "random" - ## number, i.e. a tickcount. This has a 16-bit resolution on windows - ## and a 48-bit resolution on other platforms. - -when not defined(nimscript): - proc randomize*() {.benign.} - ## Initializes the random number generator with a "random" - ## number, i.e. a tickcount. Note: Does nothing for the JavaScript target, - ## as JavaScript does not support this. Nor does it work for NimScript. - -proc randomize*(seed: int) {.benign.} - ## Initializes the random number generator with a specific seed. - ## Note: Does nothing for the JavaScript target, - ## as JavaScript does not support this. - {.push noSideEffect.} when not defined(JS): - proc sqrt*(x: float): float {.importc: "sqrt", header: "<math.h>".} + proc sqrt*(x: float32): float32 {.importc: "sqrtf", header: "<math.h>".} + proc sqrt*(x: float64): float64 {.importc: "sqrt", header: "<math.h>".} ## Computes the square root of `x`. - proc cbrt*(x: float): float {.importc: "cbrt", header: "<math.h>".} + proc cbrt*(x: float32): float32 {.importc: "cbrtf", header: "<math.h>".} + proc cbrt*(x: float64): float64 {.importc: "cbrt", header: "<math.h>".} ## Computes the cubic root of `x` - proc ln*(x: float): float {.importc: "log", header: "<math.h>".} + proc ln*(x: float32): float32 {.importc: "logf", header: "<math.h>".} + proc ln*(x: float64): float64 {.importc: "log", header: "<math.h>".} ## Computes the natural log of `x` - proc log10*(x: float): float {.importc: "log10", header: "<math.h>".} + proc log10*(x: float32): float32 {.importc: "log10f", header: "<math.h>".} + proc log10*(x: float64): float64 {.importc: "log10", header: "<math.h>".} ## Computes the common logarithm (base 10) of `x` - proc log2*(x: float): float = return ln(x) / ln(2.0) + proc log2*[T: float32|float64](x: T): T = return ln(x) / ln(2.0) ## Computes the binary logarithm (base 2) of `x` - proc exp*(x: float): float {.importc: "exp", header: "<math.h>".} + proc exp*(x: float32): float32 {.importc: "expf", header: "<math.h>".} + proc exp*(x: float64): float64 {.importc: "exp", header: "<math.h>".} ## Computes the exponential function of `x` (pow(E, x)) - proc frexp*(x: float, exponent: var int): float {. - importc: "frexp", header: "<math.h>".} - ## Split a number into mantissa and exponent. - ## `frexp` calculates the mantissa m (a float greater than or equal to 0.5 - ## and less than 1) and the integer value n such that `x` (the original - ## float value) equals m * 2**n. frexp stores n in `exponent` and returns - ## m. - - proc round*(x: float): int {.importc: "lrint", header: "<math.h>".} - ## Converts a float to an int by rounding. + proc round0(x: float32): float32 {.importc: "roundf", header: "<math.h>".} + proc round0(x: float64): float64 {.importc: "round", header: "<math.h>".} + ## Converts a float to an int by rounding. Used internally by the round + ## function when the specified number of places is 0. - proc arccos*(x: float): float {.importc: "acos", header: "<math.h>".} + proc arccos*(x: float32): float32 {.importc: "acosf", header: "<math.h>".} + proc arccos*(x: float64): float64 {.importc: "acos", header: "<math.h>".} ## Computes the arc cosine of `x` - proc arcsin*(x: float): float {.importc: "asin", header: "<math.h>".} + proc arcsin*(x: float32): float32 {.importc: "asinf", header: "<math.h>".} + proc arcsin*(x: float64): float64 {.importc: "asin", header: "<math.h>".} ## Computes the arc sine of `x` - proc arctan*(x: float): float {.importc: "atan", header: "<math.h>".} + proc arctan*(x: float32): float32 {.importc: "atanf", header: "<math.h>".} + proc arctan*(x: float64): float64 {.importc: "atan", header: "<math.h>".} ## Calculate the arc tangent of `y` / `x` - proc arctan2*(y, x: float): float {.importc: "atan2", header: "<math.h>".} + proc arctan2*(y, x: float32): float32 {.importc: "atan2f", header: "<math.h>".} + proc arctan2*(y, x: float64): float64 {.importc: "atan2", header: "<math.h>".} ## Calculate the arc tangent of `y` / `x`. ## `atan2` returns the arc tangent of `y` / `x`; it produces correct ## results even when the resulting angle is near pi/2 or -pi/2 ## (`x` near 0). - proc cos*(x: float): float {.importc: "cos", header: "<math.h>".} + proc cos*(x: float32): float32 {.importc: "cosf", header: "<math.h>".} + proc cos*(x: float64): float64 {.importc: "cos", header: "<math.h>".} ## Computes the cosine of `x` - proc cosh*(x: float): float {.importc: "cosh", header: "<math.h>".} + + proc cosh*(x: float32): float32 {.importc: "coshf", header: "<math.h>".} + proc cosh*(x: float64): float64 {.importc: "cosh", header: "<math.h>".} ## Computes the hyperbolic cosine of `x` - proc hypot*(x, y: float): float {.importc: "hypot", header: "<math.h>".} + + proc hypot*(x, y: float32): float32 {.importc: "hypotf", header: "<math.h>".} + proc hypot*(x, y: float64): float64 {.importc: "hypot", header: "<math.h>".} ## Computes the hypotenuse of a right-angle triangle with `x` and ## `y` as its base and height. Equivalent to ``sqrt(x*x + y*y)``. - proc sinh*(x: float): float {.importc: "sinh", header: "<math.h>".} + proc sinh*(x: float32): float32 {.importc: "sinhf", header: "<math.h>".} + proc sinh*(x: float64): float64 {.importc: "sinh", header: "<math.h>".} ## Computes the hyperbolic sine of `x` - proc sin*(x: float): float {.importc: "sin", header: "<math.h>".} + proc sin*(x: float32): float32 {.importc: "sinf", header: "<math.h>".} + proc sin*(x: float64): float64 {.importc: "sin", header: "<math.h>".} ## Computes the sine of `x` - proc tan*(x: float): float {.importc: "tan", header: "<math.h>".} + + proc tan*(x: float32): float32 {.importc: "tanf", header: "<math.h>".} + proc tan*(x: float64): float64 {.importc: "tan", header: "<math.h>".} ## Computes the tangent of `x` - proc tanh*(x: float): float {.importc: "tanh", header: "<math.h>".} + proc tanh*(x: float32): float32 {.importc: "tanhf", header: "<math.h>".} + proc tanh*(x: float64): float64 {.importc: "tanh", header: "<math.h>".} ## Computes the hyperbolic tangent of `x` - proc pow*(x, y: float): float {.importc: "pow", header: "<math.h>".} - ## Computes `x` to power of `y`. - proc erf*(x: float): float {.importc: "erf", header: "<math.h>".} + proc pow*(x, y: float32): float32 {.importc: "powf", header: "<math.h>".} + proc pow*(x, y: float64): float64 {.importc: "pow", header: "<math.h>".} + ## computes x to power raised of y. + + proc erf*(x: float32): float32 {.importc: "erff", header: "<math.h>".} + proc erf*(x: float64): float64 {.importc: "erf", header: "<math.h>".} ## The error function - proc erfc*(x: float): float {.importc: "erfc", header: "<math.h>".} + proc erfc*(x: float32): float32 {.importc: "erfcf", header: "<math.h>".} + proc erfc*(x: float64): float64 {.importc: "erfc", header: "<math.h>".} ## The complementary error function - proc lgamma*(x: float): float {.importc: "lgamma", header: "<math.h>".} + proc lgamma*(x: float32): float32 {.importc: "lgammaf", header: "<math.h>".} + proc lgamma*(x: float64): float64 {.importc: "lgamma", header: "<math.h>".} ## Natural log of the gamma function - proc tgamma*(x: float): float {.importc: "tgamma", header: "<math.h>".} + proc tgamma*(x: float32): float32 {.importc: "tgammaf", header: "<math.h>".} + proc tgamma*(x: float64): float64 {.importc: "tgamma", header: "<math.h>".} ## The gamma function - # C procs: - when defined(vcc) and false: - # The "secure" random, available from Windows XP - # https://msdn.microsoft.com/en-us/library/sxtz2fa8.aspx - # Present in some variants of MinGW but not enough to justify - # `when defined(windows)` yet - proc rand_s(val: var cuint) {.importc: "rand_s", header: "<stdlib.h>".} - # To behave like the normal version - proc rand(): cuint = rand_s(result) - else: - proc srand(seed: cint) {.importc: "srand", header: "<stdlib.h>".} - proc rand(): cint {.importc: "rand", header: "<stdlib.h>".} - - when not defined(windows): - proc srand48(seed: clong) {.importc: "srand48", header: "<stdlib.h>".} - proc drand48(): float {.importc: "drand48", header: "<stdlib.h>".} - proc random(max: float): float = - result = drand48() * max - else: - when defined(vcc): # Windows with Visual C - proc random(max: float): float = - # we are hardcoding this because - # importc-ing macros is extremely problematic - # and because the value is publicly documented - # on MSDN and very unlikely to change - # See https://msdn.microsoft.com/en-us/library/296az74e.aspx - const rand_max = 4294967295 # UINT_MAX - result = (float(rand()) / float(rand_max)) * max - proc randomize() = discard - proc randomize(seed: int) = discard - else: # Windows with another compiler - proc random(max: float): float = - # we are hardcoding this because - # importc-ing macros is extremely problematic - # and because the value is publicly documented - # on MSDN and very unlikely to change - const rand_max = 32767 - result = (float(rand()) / float(rand_max)) * max - - when not defined(vcc): # the above code for vcc uses `discard` instead - # this is either not Windows or is Windows without vcc - when not defined(nimscript): - proc randomize() = - randomize(cast[int](epochTime())) - proc randomize(seed: int) = - srand(cint(seed)) # rand_s doesn't use srand - when declared(srand48): srand48(seed) - - proc random(max: int): int = - result = int(rand()) mod max - - proc trunc*(x: float): float {.importc: "trunc", header: "<math.h>".} + proc trunc*(x: float32): float32 {.importc: "truncf", header: "<math.h>".} + proc trunc*(x: float64): float64 {.importc: "trunc", header: "<math.h>".} ## Truncates `x` to the decimal point ## ## .. code-block:: nim ## echo trunc(PI) # 3.0 - proc floor*(x: float): float {.importc: "floor", header: "<math.h>".} + + proc floor*(x: float32): float32 {.importc: "floorf", header: "<math.h>".} + proc floor*(x: float64): float64 {.importc: "floor", header: "<math.h>".} ## Computes the floor function (i.e., the largest integer not greater than `x`) ## ## .. code-block:: nim ## echo floor(-3.5) ## -4.0 - proc ceil*(x: float): float {.importc: "ceil", header: "<math.h>".} + + proc ceil*(x: float32): float32 {.importc: "ceilf", header: "<math.h>".} + proc ceil*(x: float64): float64 {.importc: "ceil", header: "<math.h>".} ## Computes the ceiling function (i.e., the smallest integer not less than `x`) ## ## .. code-block:: nim ## echo ceil(-2.1) ## -2.0 - proc fmod*(x, y: float): float {.importc: "fmod", header: "<math.h>".} + proc fmod*(x, y: float32): float32 {.importc: "fmodf", header: "<math.h>".} + proc fmod*(x, y: float64): float64 {.importc: "fmod", header: "<math.h>".} ## Computes the remainder of `x` divided by `y` ## ## .. code-block:: nim ## echo fmod(-2.5, 0.3) ## -0.1 else: - proc mathrandom(): float {.importc: "Math.random", nodecl.} - proc floor*(x: float): float {.importc: "Math.floor", nodecl.} - proc ceil*(x: float): float {.importc: "Math.ceil", nodecl.} - proc random(max: int): int = - result = int(floor(mathrandom() * float(max))) - proc random(max: float): float = - result = float(mathrandom() * float(max)) - proc randomize() = discard - proc randomize(seed: int) = discard - - proc sqrt*(x: float): float {.importc: "Math.sqrt", nodecl.} - proc ln*(x: float): float {.importc: "Math.log", nodecl.} - proc log10*(x: float): float = return ln(x) / ln(10.0) - proc log2*(x: float): float = return ln(x) / ln(2.0) - - proc exp*(x: float): float {.importc: "Math.exp", nodecl.} - proc round*(x: float): int {.importc: "Math.round", nodecl.} - proc pow*(x, y: float): float {.importc: "Math.pow", nodecl.} - - proc frexp*(x: float, exponent: var int): float = + proc floor*(x: float32): float32 {.importc: "Math.floor", nodecl.} + proc floor*(x: float64): float64 {.importc: "Math.floor", nodecl.} + proc ceil*(x: float32): float32 {.importc: "Math.ceil", nodecl.} + proc ceil*(x: float64): float64 {.importc: "Math.ceil", nodecl.} + + proc sqrt*(x: float32): float32 {.importc: "Math.sqrt", nodecl.} + proc sqrt*(x: float64): float64 {.importc: "Math.sqrt", nodecl.} + proc ln*(x: float32): float32 {.importc: "Math.log", nodecl.} + proc ln*(x: float64): float64 {.importc: "Math.log", nodecl.} + proc log10*[T: float32|float64](x: T): T = return ln(x) / ln(10.0) + proc log2*[T: float32|float64](x: T): T = return ln(x) / ln(2.0) + + proc exp*(x: float32): float32 {.importc: "Math.exp", nodecl.} + proc exp*(x: float64): float64 {.importc: "Math.exp", nodecl.} + proc round0(x: float): float {.importc: "Math.round", nodecl.} + + proc pow*(x, y: float32): float32 {.importC: "Math.pow", nodecl.} + proc pow*(x, y: float64): float64 {.importc: "Math.pow", nodecl.} + + proc arccos*(x: float32): float32 {.importc: "Math.acos", nodecl.} + proc arccos*(x: float64): float64 {.importc: "Math.acos", nodecl.} + proc arcsin*(x: float32): float32 {.importc: "Math.asin", nodecl.} + proc arcsin*(x: float64): float64 {.importc: "Math.asin", nodecl.} + proc arctan*(x: float32): float32 {.importc: "Math.atan", nodecl.} + proc arctan*(x: float64): float64 {.importc: "Math.atan", nodecl.} + proc arctan2*(y, x: float32): float32 {.importC: "Math.atan2", nodecl.} + proc arctan2*(y, x: float64): float64 {.importc: "Math.atan2", nodecl.} + + proc cos*(x: float32): float32 {.importc: "Math.cos", nodecl.} + proc cos*(x: float64): float64 {.importc: "Math.cos", nodecl.} + proc cosh*(x: float32): float32 = return (exp(x)+exp(-x))*0.5 + proc cosh*(x: float64): float64 = return (exp(x)+exp(-x))*0.5 + proc hypot*[T: float32|float64](x, y: T): T = return sqrt(x*x + y*y) + proc sinh*[T: float32|float64](x: T): T = return (exp(x)-exp(-x))*0.5 + proc sin*(x: float32): float32 {.importc: "Math.sin", nodecl.} + proc sin*(x: float64): float64 {.importc: "Math.sin", nodecl.} + proc tan*(x: float32): float32 {.importc: "Math.tan", nodecl.} + proc tan*(x: float64): float64 {.importc: "Math.tan", nodecl.} + proc tanh*[T: float32|float64](x: T): T = + var y = exp(2.0*x) + return (y-1.0)/(y+1.0) + +proc round*[T: float32|float64](x: T, places: int = 0): T = + ## Round a floating point number. + ## + ## If `places` is 0 (or omitted), round to the nearest integral value + ## following normal mathematical rounding rules (e.g. `round(54.5) -> 55.0`). + ## If `places` is greater than 0, round to the given number of decimal + ## places, e.g. `round(54.346, 2) -> 54.35`. + ## If `places` is negative, round to the left of the decimal place, e.g. + ## `round(537.345, -1) -> 540.0` + if places == 0: + result = round0(x) + else: + var mult = pow(10.0, places.T) + result = round0(x*mult)/mult + +when not defined(JS): + proc frexp*(x: float32, exponent: var int): float32 {. + importc: "frexp", header: "<math.h>".} + proc frexp*(x: float64, exponent: var int): float64 {. + importc: "frexp", header: "<math.h>".} + ## Split a number into mantissa and exponent. + ## `frexp` calculates the mantissa m (a float greater than or equal to 0.5 + ## and less than 1) and the integer value n such that `x` (the original + ## float value) equals m * 2**n. frexp stores n in `exponent` and returns + ## m. +else: + proc frexp*[T: float32|float64](x: T, exponent: var int): T = if x == 0.0: exponent = 0 result = 0.0 @@ -315,20 +312,22 @@ else: exponent = round(ex) result = x / pow(2.0, ex) - proc arccos*(x: float): float {.importc: "Math.acos", nodecl.} - proc arcsin*(x: float): float {.importc: "Math.asin", nodecl.} - proc arctan*(x: float): float {.importc: "Math.atan", nodecl.} - proc arctan2*(y, x: float): float {.importc: "Math.atan2", nodecl.} - - proc cos*(x: float): float {.importc: "Math.cos", nodecl.} - proc cosh*(x: float): float = return (exp(x)+exp(-x))*0.5 - proc hypot*(x, y: float): float = return sqrt(x*x + y*y) - proc sinh*(x: float): float = return (exp(x)-exp(-x))*0.5 - proc sin*(x: float): float {.importc: "Math.sin", nodecl.} - proc tan*(x: float): float {.importc: "Math.tan", nodecl.} - proc tanh*(x: float): float = - var y = exp(2.0*x) - return (y-1.0)/(y+1.0) +proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = + ## Breaks `x` into an integral and a fractional part. + ## + ## Returns a tuple containing intpart and floatpart representing + ## the integer part and the fractional part respectively. + ## + ## Both parts have the same sign as `x`. Analogous to the `modf` + ## function in C. + var + absolute: T + absolute = abs(x) + result.intpart = floor(absolute) + result.floatpart = absolute - result.intpart + if x < 0: + result.intpart = -result.intpart + result.floatpart = -result.floatpart {.pop.} @@ -340,7 +339,7 @@ proc radToDeg*[T: float32|float64](d: T): T {.inline.} = ## Convert from radians to degrees result = T(d) / RadPerDeg -proc `mod`*(x, y: float): float = +proc `mod`*[T: float32|float64](x, y: T): T = ## Computes the modulo operation for float operators. Equivalent ## to ``x - y * floor(x/y)``. Note that the remainder will always ## have the same sign as the divisor. @@ -349,14 +348,6 @@ proc `mod`*(x, y: float): float = ## echo (4.0 mod -3.1) # -2.2 result = if y == 0.0: x else: x - y * (x/y).floor -proc random*[T](x: Slice[T]): T = - ## For a slice `a .. b` returns a value in the range `a .. b-1`. - result = random(x.b - x.a) + x.a - -proc random*[T](a: openArray[T]): T = - ## returns a random element from the openarray `a`. - result = a[random(a.low..a.len)] - {.pop.} {.pop.} @@ -391,24 +382,6 @@ proc lcm*[T](x, y: T): T = x div gcd(x, y) * y when isMainModule and not defined(JS): - proc gettime(dummy: ptr cint): cint {.importc: "time", header: "<time.h>".} - - # Verifies random seed initialization. - let seed = gettime(nil) - randomize(seed) - const SIZE = 10 - var buf : array[0..SIZE, int] - # Fill the buffer with random values - for i in 0..SIZE-1: - buf[i] = random(high(int)) - # Check that the second random calls are the same for each position. - randomize(seed) - for i in 0..SIZE-1: - assert buf[i] == random(high(int)), "non deterministic random seeding" - - when not defined(testing): - echo "random values equal after reseeding" - # Check for no side effect annotation proc mySqrt(num: float): float {.noSideEffect.} = return sqrt(num) @@ -418,3 +391,36 @@ when isMainModule and not defined(JS): assert(lgamma(1.0) == 0.0) # ln(1.0) == 0.0 assert(erf(6.0) > erf(5.0)) assert(erfc(6.0) < erfc(5.0)) +when isMainModule: + # Function for approximate comparison of floats + proc `==~`(x, y: float): bool = (abs(x-y) < 1e-9) + + block: # round() tests + # Round to 0 decimal places + doAssert round(54.652) ==~ 55.0 + doAssert round(54.352) ==~ 54.0 + doAssert round(-54.652) ==~ -55.0 + doAssert round(-54.352) ==~ -54.0 + doAssert round(0.0) ==~ 0.0 + # Round to positive decimal places + doAssert round(-547.652, 1) ==~ -547.7 + doAssert round(547.652, 1) ==~ 547.7 + doAssert round(-547.652, 2) ==~ -547.65 + doAssert round(547.652, 2) ==~ 547.65 + # Round to negative decimal places + doAssert round(547.652, -1) ==~ 550.0 + doAssert round(547.652, -2) ==~ 500.0 + doAssert round(547.652, -3) ==~ 1000.0 + doAssert round(547.652, -4) ==~ 0.0 + doAssert round(-547.652, -1) ==~ -550.0 + doAssert round(-547.652, -2) ==~ -500.0 + doAssert round(-547.652, -3) ==~ -1000.0 + doAssert round(-547.652, -4) ==~ 0.0 + + block: # splitDecimal() tests + doAssert splitDecimal(54.674).intpart ==~ 54.0 + doAssert splitDecimal(54.674).floatpart ==~ 0.674 + doAssert splitDecimal(-693.4356).intpart ==~ -693.0 + doAssert splitDecimal(-693.4356).floatpart ==~ -0.4356 + doAssert splitDecimal(0.0).intpart ==~ 0.0 + doAssert splitDecimal(0.0).floatpart ==~ 0.0 diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index 043d6d80a..15828ff35 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -27,7 +27,7 @@ else: import posix export fcntl, F_GETFL, O_NONBLOCK, F_SETFL, EAGAIN, EWOULDBLOCK, MSG_NOSIGNAL, EINTR, EINPROGRESS, ECONNRESET, EPIPE, ENETRESET - export Sockaddr_storage + export Sockaddr_storage, Sockaddr_un, Sockaddr_un_path_length export SocketHandle, Sockaddr_in, Addrinfo, INADDR_ANY, SockAddr, SockLen, Sockaddr_in6, @@ -38,7 +38,7 @@ export SOL_SOCKET, SOMAXCONN, SO_ACCEPTCONN, SO_BROADCAST, SO_DEBUG, SO_DONTROUTE, - SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR, + SO_KEEPALIVE, SO_OOBINLINE, SO_REUSEADDR, SO_REUSEPORT, MSG_PEEK when defined(macosx) and not defined(nimdoc): diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 5de6667dd..cb8cea720 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -68,6 +68,7 @@ {.deadCodeElim: on.} import nativesockets, os, strutils, parseutils, times export Port, `$`, `==` +export Domain, SockType, Protocol const useWinVersion = defined(Windows) or defined(nimdoc) const defineSsl = defined(ssl) or defined(nimdoc) @@ -129,7 +130,7 @@ type SOBool* = enum ## Boolean socket options. OptAcceptConn, OptBroadcast, OptDebug, OptDontRoute, OptKeepAlive, - OptOOBInline, OptReuseAddr + OptOOBInline, OptReuseAddr, OptReusePort ReadLineResult* = enum ## result for readLineAsync ReadFullLine, ReadPartialLine, ReadDisconnected, ReadNone @@ -579,6 +580,7 @@ proc toCInt*(opt: SOBool): cint = of OptKeepAlive: SO_KEEPALIVE of OptOOBInline: SO_OOBINLINE of OptReuseAddr: SO_REUSEADDR + of OptReusePort: SO_REUSEPORT proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {. tags: [ReadIOEffect].} = @@ -604,9 +606,35 @@ proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) { var valuei = cint(if value: 1 else: 0) setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei) -when defineSsl: +when defined(posix) and not defined(nimdoc): + proc makeUnixAddr(path: string): Sockaddr_un = + result.sun_family = AF_UNIX.toInt + if path.len >= Sockaddr_un_path_length: + raise newException(ValueError, "socket path too long") + copyMem(addr result.sun_path, path.cstring, path.len + 1) + +when defined(posix): + proc connectUnix*(socket: Socket, path: string) = + ## Connects to Unix socket on `path`. + ## This only works on Unix-style systems: Mac OS X, BSD and Linux + when not defined(nimdoc): + var socketAddr = makeUnixAddr(path) + if socket.fd.connect(cast[ptr SockAddr](addr socketAddr), + sizeof(socketAddr).Socklen) != 0'i32: + raiseOSError(osLastError()) + + proc bindUnix*(socket: Socket, path: string) = + ## Binds Unix socket to `path`. + ## This only works on Unix-style systems: Mac OS X, BSD and Linux + when not defined(nimdoc): + var socketAddr = makeUnixAddr(path) + if socket.fd.bindAddr(cast[ptr SockAddr](addr socketAddr), + sizeof(socketAddr).Socklen) != 0'i32: + raiseOSError(osLastError()) + +when defined(ssl): proc handshake*(socket: Socket): bool - {.tags: [ReadIOEffect, WriteIOEffect], deprecated.} = + {.tags: [ReadIOEffect, WriteIOEffect], deprecated.} = ## This proc needs to be called on a socket after it connects. This is ## only applicable when using ``connectAsync``. ## This proc performs the SSL handshake. @@ -1374,7 +1402,7 @@ proc connect*(socket: Socket, address: string, port = Port(0), if selectWrite(s, timeout) != 1: raise newException(TimeoutError, "Call to 'connect' timed out.") else: - when defineSsl: + when defineSsl and not defined(nimdoc): if socket.isSSL: socket.fd.setBlocking(true) {.warning[Deprecated]: off.} diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 38b0ed4a3..6d528652a 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -875,8 +875,9 @@ elif not defined(useNimRtl): var error: cint let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error)) if sizeRead == sizeof(error): - raiseOSError("Could not find command: '$1'. OS error: $2" % - [$data.sysCommand, $strerror(error)]) + raiseOSError(osLastError(), + "Could not find command: '$1'. OS error: $2" % + [$data.sysCommand, $strerror(error)]) return pid @@ -966,17 +967,169 @@ elif not defined(useNimRtl): proc kill(p: Process) = if kill(p.id, SIGKILL) != 0'i32: raiseOsError(osLastError()) + + when defined(macosx) or defined(freebsd) or defined(netbsd) or + defined(openbsd): + import kqueue, times + + proc waitForExit(p: Process, timeout: int = -1): int = + if p.exitCode != -3: return p.exitCode + if timeout == -1: + if waitpid(p.id, p.exitCode, 0) < 0: + p.exitCode = -3 + raiseOSError(osLastError()) + else: + var kqFD = kqueue() + if kqFD == -1: + raiseOSError(osLastError()) + + var kevIn = KEvent(ident: p.id.uint, filter: EVFILT_PROC, + flags: EV_ADD, fflags: NOTE_EXIT) + var kevOut: KEvent + var tmspec: Timespec + + if timeout >= 1000: + tmspec.tv_sec = (timeout div 1_000).Time + tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000 + else: + tmspec.tv_sec = 0.Time + tmspec.tv_nsec = (timeout * 1_000_000) + + try: + while true: + var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1, + addr(tmspec)) + if count < 0: + let err = osLastError() + if err.cint != EINTR: + raiseOSError(osLastError()) + elif count == 0: + # timeout expired, so we trying to kill process + if posix.kill(p.id, SIGKILL) == -1: + raiseOSError(osLastError()) + if waitpid(p.id, p.exitCode, 0) < 0: + p.exitCode = -3 + raiseOSError(osLastError()) + break + else: + if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC: + if waitpid(p.id, p.exitCode, 0) < 0: + p.exitCode = -3 + raiseOSError(osLastError()) + break + else: + raiseOSError(osLastError()) + finally: + discard posix.close(kqFD) + + result = int(p.exitCode) shr 8 + else: + import times + + const + hasThreadSupport = compileOption("threads") and not defined(nimscript) + + proc waitForExit(p: Process, timeout: int = -1): int = + template adjustTimeout(t, s, e: Timespec) = + var diff: int + var b: Timespec + b.tv_sec = e.tv_sec + b.tv_nsec = e.tv_nsec + e.tv_sec = (e.tv_sec - s.tv_sec).Time + if e.tv_nsec >= s.tv_nsec: + e.tv_nsec -= s.tv_nsec + else: + if e.tv_sec == 0.Time: + raise newException(ValueError, "System time was modified") + else: + diff = s.tv_nsec - e.tv_nsec + e.tv_nsec = 1_000_000_000 - diff + t.tv_sec = (t.tv_sec - e.tv_sec).Time + if t.tv_nsec >= e.tv_nsec: + t.tv_nsec -= e.tv_nsec + else: + t.tv_sec = (int(t.tv_sec) - 1).Time + diff = e.tv_nsec - t.tv_nsec + t.tv_nsec = 1_000_000_000 - diff + s.tv_sec = b.tv_sec + s.tv_nsec = b.tv_nsec + + #if waitPid(p.id, p.exitCode, 0) == int(p.id): + # ``waitPid`` fails if the process is not running anymore. But then + # ``running`` probably set ``p.exitCode`` for us. Since ``p.exitCode`` is + # initialized with -3, wrong success exit codes are prevented. + if p.exitCode != -3: return p.exitCode + if timeout == -1: + if waitpid(p.id, p.exitCode, 0) < 0: + p.exitCode = -3 + raiseOSError(osLastError()) + else: + var nmask, omask: Sigset + var sinfo: SigInfo + var stspec, enspec, tmspec: Timespec - proc waitForExit(p: Process, timeout: int = -1): int = - #if waitPid(p.id, p.exitCode, 0) == int(p.id): - # ``waitPid`` fails if the process is not running anymore. But then - # ``running`` probably set ``p.exitCode`` for us. Since ``p.exitCode`` is - # initialized with -3, wrong success exit codes are prevented. - if p.exitCode != -3: return p.exitCode - if waitpid(p.id, p.exitCode, 0) < 0: - p.exitCode = -3 - raiseOSError(osLastError()) - result = int(p.exitCode) shr 8 + discard sigemptyset(nmask) + discard sigemptyset(omask) + discard sigaddset(nmask, SIGCHLD) + + when hasThreadSupport: + if pthread_sigmask(SIG_BLOCK, nmask, omask) == -1: + raiseOSError(osLastError()) + else: + if sigprocmask(SIG_BLOCK, nmask, omask) == -1: + raiseOSError(osLastError()) + + if timeout >= 1000: + tmspec.tv_sec = (timeout div 1_000).Time + tmspec.tv_nsec = (timeout %% 1_000) * 1_000_000 + else: + tmspec.tv_sec = 0.Time + tmspec.tv_nsec = (timeout * 1_000_000) + + try: + if clock_gettime(CLOCK_REALTIME, stspec) == -1: + raiseOSError(osLastError()) + while true: + let res = sigtimedwait(nmask, sinfo, tmspec) + if res == SIGCHLD: + if sinfo.si_pid == p.id: + if waitpid(p.id, p.exitCode, 0) < 0: + p.exitCode = -3 + raiseOSError(osLastError()) + break + else: + # we have SIGCHLD, but not for process we are waiting, + # so we need to adjust timeout value and continue + if clock_gettime(CLOCK_REALTIME, enspec) == -1: + raiseOSError(osLastError()) + adjustTimeout(tmspec, stspec, enspec) + elif res < 0: + let err = osLastError() + if err.cint == EINTR: + # we have received another signal, so we need to + # adjust timeout and continue + if clock_gettime(CLOCK_REALTIME, enspec) == -1: + raiseOSError(osLastError()) + adjustTimeout(tmspec, stspec, enspec) + elif err.cint == EAGAIN: + # timeout expired, so we trying to kill process + if posix.kill(p.id, SIGKILL) == -1: + raiseOSError(osLastError()) + if waitpid(p.id, p.exitCode, 0) < 0: + p.exitCode = -3 + raiseOSError(osLastError()) + break + else: + raiseOSError(err) + finally: + when hasThreadSupport: + if pthread_sigmask(SIG_UNBLOCK, nmask, omask) == -1: + raiseOSError(osLastError()) + else: + if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1: + raiseOSError(osLastError()) + + result = int(p.exitCode) shr 8 proc peekExitCode(p: Process): int = if p.exitCode != -3: return p.exitCode diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index 9bcac0a50..25879d2b7 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -15,17 +15,78 @@ ## This is an example of how a configuration file may look like: ## -## .. include:: doc/mytest.cfg +## .. include:: ../../doc/mytest.cfg ## :literal: ## The file ``examples/parsecfgex.nim`` demonstrates how to use the ## configuration file parser: ## ## .. code-block:: nim -## :file: examples/parsecfgex.nim - +## :file: ../../examples/parsecfgex.nim +## +## Examples +## -------- +## +## This is an example of a configuration file. +## +## :: +## +## charset = "utf-8" +## [Package] +## name = "hello" +## --threads:on +## [Author] +## name = "lihf8515" +## qq = "10214028" +## email = "lihaifeng@wxm.com" +## +## Creating a configuration file. +## ============================== +## .. code-block:: nim +## +## import parsecfg +## var dict=newConfig() +## dict.setSectionKey("","charset","utf-8") +## dict.setSectionKey("Package","name","hello") +## dict.setSectionKey("Package","--threads","on") +## dict.setSectionKey("Author","name","lihf8515") +## dict.setSectionKey("Author","qq","10214028") +## dict.setSectionKey("Author","email","lihaifeng@wxm.com") +## dict.writeConfig("config.ini") +## +## Reading a configuration file. +## ============================= +## .. code-block:: nim +## +## import parsecfg +## var dict = loadConfig("config.ini") +## var charset = dict.getSectionValue("","charset") +## var threads = dict.getSectionValue("Package","--threads") +## var pname = dict.getSectionValue("Package","name") +## var name = dict.getSectionValue("Author","name") +## var qq = dict.getSectionValue("Author","qq") +## var email = dict.getSectionValue("Author","email") +## echo pname & "\n" & name & "\n" & qq & "\n" & email +## +## Modifying a configuration file. +## =============================== +## .. code-block:: nim +## +## import parsecfg +## var dict = loadConfig("config.ini") +## dict.setSectionKey("Author","name","lhf") +## dict.writeConfig("config.ini") +## +## Deleting a section key in a configuration file. +## =============================================== +## .. code-block:: nim +## +## import parsecfg +## var dict = loadConfig("config.ini") +## dict.delSectionKey("Author","email") +## dict.writeConfig("config.ini") import - hashes, strutils, lexbase, streams + hashes, strutils, lexbase, streams, tables include "system/inclrtl" @@ -359,3 +420,138 @@ proc next*(c: var CfgParser): CfgEvent {.rtl, extern: "npc$1".} = result.kind = cfgError result.msg = errorStr(c, "invalid token: " & c.tok.literal) rawGetTok(c, c.tok) + +# ---------------- Configuration file related operations ---------------- +type + Config* = OrderedTableRef[string, OrderedTableRef[string, string]] + +proc newConfig*(): Config = + ## Create a new configuration table. + ## Useful when wanting to create a configuration file. + result = newOrderedTable[string, OrderedTableRef[string, string]]() + +proc loadConfig*(filename: string): Config = + ## Load the specified configuration file into a new Config instance. + var dict = newOrderedTable[string, OrderedTableRef[string, string]]() + var curSection = "" ## Current section, + ## the default value of the current section is "", + ## which means that the current section is a common + var p: CfgParser + var fileStream = newFileStream(filename, fmRead) + if fileStream != nil: + open(p, fileStream, filename) + while true: + var e = next(p) + case e.kind + of cfgEof: + break + of cfgSectionStart: # Only look for the first time the Section + curSection = e.section + of cfgKeyValuePair: + var t = newOrderedTable[string, string]() + if dict.hasKey(curSection): + t = dict[curSection] + t[e.key] = e.value + dict[curSection] = t + of cfgOption: + var c = newOrderedTable[string, string]() + if dict.hasKey(curSection): + c = dict[curSection] + c["--" & e.key] = e.value + dict[curSection] = c + of cfgError: + break + close(p) + result = dict + +proc replace(s: string): string = + var d = "" + var i = 0 + while i < s.len(): + if s[i] == '\\': + d.add(r"\\") + elif s[i] == '\c' and s[i+1] == '\L': + d.add(r"\n") + inc(i) + elif s[i] == '\c': + d.add(r"\n") + elif s[i] == '\L': + d.add(r"\n") + else: + d.add(s[i]) + inc(i) + result = d + +proc writeConfig*(dict: Config, filename: string) = + ## Writes the contents of the table to the specified configuration file. + ## Note: Comment statement will be ignored. + var file: File + if file.open(filename, fmWrite): + try: + var section, key, value, kv, segmentChar:string + for pair in dict.pairs(): + section = pair[0] + if section != "": ## Not general section + if not allCharsInSet(section, SymChars): ## Non system character + file.writeLine("[\"" & section & "\"]") + else: + file.writeLine("[" & section & "]") + for pair2 in pair[1].pairs(): + key = pair2[0] + value = pair2[1] + if key[0] == '-' and key[1] == '-': ## If it is a command key + segmentChar = ":" + if not allCharsInSet(key[2..key.len()-1], SymChars): + kv.add("--\"") + kv.add(key[2..key.len()-1]) + kv.add("\"") + else: + kv = key + else: + segmentChar = "=" + kv = key + if value != "": ## If the key is not empty + if not allCharsInSet(value, SymChars): + kv.add(segmentChar) + kv.add("\"") + kv.add(replace(value)) + kv.add("\"") + else: + kv.add(segmentChar) + kv.add(value) + file.writeLine(kv) + except: + raise + finally: + file.close() + +proc getSectionValue*(dict: Config, section, key: string): string = + ## Gets the Key value of the specified Section. + if dict.haskey(section): + if dict[section].hasKey(key): + result = dict[section][key] + else: + result = "" + else: + result = "" + +proc setSectionKey*(dict: var Config, section, key, value: string) = + ## Sets the Key value of the specified Section. + var t = newOrderedTable[string, string]() + if dict.hasKey(section): + t = dict[section] + t[key] = value + dict[section] = t + +proc delSection*(dict: var Config, section: string) = + ## Deletes the specified section and all of its sub keys. + dict.del(section) + +proc delSectionKey*(dict: var Config, section, key: string) = + ## Delete the key of the specified section. + if dict.haskey(section): + if dict[section].hasKey(key): + if dict[section].len() == 1: + dict.del(section) + else: + dict[section].del(key) diff --git a/lib/pure/parsecsv.nim b/lib/pure/parsecsv.nim index af51e1201..bb291bcbc 100644 --- a/lib/pure/parsecsv.nim +++ b/lib/pure/parsecsv.nim @@ -77,6 +77,15 @@ proc open*(my: var CsvParser, input: Stream, filename: string, my.row = @[] my.currRow = 0 +proc open*(my: var CsvParser, filename: string, + separator = ',', quote = '"', escape = '\0', + skipInitialSpace = false) = + ## same as the other `open` but creates the file stream for you. + var s = newFileStream(filename, fmRead) + if s == nil: my.error(0, "cannot open: " & filename) + open(my, s, filename, separator, + quote, escape, skipInitialSpace) + proc parseField(my: var CsvParser, a: var string) = var pos = my.bufpos var buf = my.buf @@ -131,6 +140,8 @@ proc readRow*(my: var CsvParser, columns = 0): bool = ## reads the next row; if `columns` > 0, it expects the row to have ## exactly this many columns. Returns false if the end of the file ## has been encountered else true. + ## + ## Blank lines are skipped. var col = 0 # current column var oldpos = my.bufpos while my.buf[my.bufpos] != '\0': diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim index f8b2c3d8d..06daa3782 100644 --- a/lib/pure/parsexml.nim +++ b/lib/pure/parsexml.nim @@ -34,7 +34,7 @@ ## document. ## ## .. code-block:: nim -## :file: examples/htmltitle.nim +## :file: ../../examples/htmltitle.nim ## ## ## Example 2: Retrieve all HTML links @@ -45,7 +45,7 @@ ## an HTML document contains. ## ## .. code-block:: nim -## :file: examples/htmlrefs.nim +## :file: ../../examples/htmlrefs.nim ## import diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index eea20a62c..7e1f50266 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -12,7 +12,7 @@ ## Matching performance is hopefully competitive with optimized regular ## expression engines. ## -## .. include:: ../doc/pegdocs.txt +## .. include:: ../../doc/pegdocs.txt ## include "system/inclrtl" diff --git a/lib/pure/random.nim b/lib/pure/random.nim new file mode 100644 index 000000000..c73f403eb --- /dev/null +++ b/lib/pure/random.nim @@ -0,0 +1,129 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2016 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +##[Nim's standard random number generator. Based on + +| `http://xoroshiro.di.unimi.it/`_ +| `http://xoroshiro.di.unimi.it/xoroshiro128plus.c`_ +]## + +include "system/inclrtl" +{.push debugger:off.} + +# XXX Expose RandomGenState +when defined(JS): + type ui = uint32 +else: + type ui = uint64 + +type + RandomGenState = object + a0, a1: ui + +when defined(JS): + var state = RandomGenState( + a0: 0x69B4C98Cu32, + a1: 0xFED1DD30u32) # global for backwards compatibility +else: + # racy for multi-threading but good enough for now: + var state = RandomGenState( + a0: 0x69B4C98CB8530805u64, + a1: 0xFED1DD3004688D67CAu64) # global for backwards compatibility + +proc rotl(x, k: ui): ui = + result = (x shl k) or (x shr (ui(64) - k)) + +proc next(s: var RandomGenState): uint64 = + let s0 = s.a0 + var s1 = s.a1 + result = s0 + s1 + s1 = s1 xor s0 + s.a0 = rotl(s0, 55) xor s1 xor (s1 shl 14) # a, b + s.a1 = rotl(s1, 36) # c + +proc skipRandomNumbers(s: var RandomGenState) = + ## This is the jump function for the generator. It is equivalent + ## to 2^64 calls to next(); it can be used to generate 2^64 + ## non-overlapping subsequences for parallel computations. + when defined(JS): + const helper = [0xbeac0467u32, 0xd86b048bu32] + else: + const helper = [0xbeac0467eba5facbu64, 0xd86b048b86aa9922u64] + var + s0 = ui 0 + s1 = ui 0 + for i in 0..high(helper): + for b in 0..< 64: + if (helper[i] and (ui(1) shl ui(b))) != 0: + s0 = s0 xor s.a0 + s1 = s1 xor s.a1 + discard next(s) + s.a0 = s0 + s.a1 = s1 + +proc random*(max: int): int {.benign.} = + ## Returns a random number in the range 0..max-1. The sequence of + ## random number is always the same, unless `randomize` is called + ## which initializes the random number generator with a "random" + ## number, i.e. a tickcount. + result = int(next(state) mod uint64(max)) + +proc random*(max: float): float {.benign.} = + ## Returns a random number in the range 0..<max. The sequence of + ## random number is always the same, unless `randomize` is called + ## which initializes the random number generator with a "random" + ## number, i.e. a tickcount. + let x = next(state) + when defined(JS): + result = (float(x) / float(high(uint32))) * max + else: + let u = (0x3FFu64 shl 52u64) or (x shr 12u64) + result = (cast[float](u) - 1.0) * max + +proc random*[T](x: Slice[T]): T = + ## For a slice `a .. b` returns a value in the range `a .. b-1`. + result = random(x.b - x.a) + x.a + +proc random*[T](a: openArray[T]): T = + ## returns a random element from the openarray `a`. + result = a[random(a.low..a.len)] + +proc randomize*(seed: int) {.benign.} = + ## Initializes the random number generator with a specific seed. + state.a0 = ui(seed shr 16) + state.a1 = ui(seed and 0xffff) + +when not defined(nimscript): + import times + + proc randomize*() {.benign.} = + ## Initializes the random number generator with a "random" + ## number, i.e. a tickcount. Note: Does not work for NimScript. + when defined(JS): + proc getMil(t: Time): int {.importcpp: "getTime", nodecl.} + randomize(getMil times.getTime()) + else: + randomize(int times.getTime()) + +{.pop.} + +when isMainModule: + proc main = + var occur: array[1000, int] + + var x = 8234 + for i in 0..100_000: + x = random(len(occur)) # myrand(x) + inc occur[x] + for i, oc in occur: + if oc < 69: + doAssert false, "too few occurances of " & $i + elif oc > 130: + doAssert false, "too many occurances of " & $i + main() diff --git a/lib/pure/rationals.nim b/lib/pure/rationals.nim index 6fd05dc4b..bf134f2ae 100644 --- a/lib/pure/rationals.nim +++ b/lib/pure/rationals.nim @@ -41,26 +41,26 @@ proc toRational*[T:SomeInteger](x: T): Rational[T] = proc toRationalSub(x: float, n: int): Rational[int] = var - a = 0 - b, c, d = 1 + a = 0'i64 + b, c, d = 1'i64 result = 0 // 1 # rational 0 while b <= n and d <= n: let ac = (a+c) let bd = (b+d) # scale by 1000 so not overflow for high precision - let mediant = (ac/1000) / (bd/1000) + let mediant = (ac.float/1000) / (bd.float/1000) if x == mediant: if bd <= n: - result.num = ac - result.den = bd + result.num = ac.int + result.den = bd.int return result elif d > b: - result.num = c - result.den = d + result.num = c.int + result.den = d.int return result else: - result.num = a - result.den = b + result.num = a.int + result.den = b.int return result elif x > mediant: a = ac @@ -69,8 +69,8 @@ proc toRationalSub(x: float, n: int): Rational[int] = c = ac d = bd if (b > n): - return initRational(c, d) - return initRational(a, b) + return initRational(c.int, d.int) + return initRational(a.int, b.int) proc toRational*(x: float, n: int = high(int)): Rational[int] = ## Calculate the best rational numerator and denominator diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index c3d6d75bd..be80685ab 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -14,6 +14,7 @@ ## <backends.html#the-javascript-target>`_. import parseutils +from math import pow, round, floor, log10 {.deadCodeElim: on.} @@ -324,7 +325,7 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = result[i] = chr(val mod 8 + ord('0')) val = val div 8 -iterator split*(s: string, seps: set[char] = Whitespace): string = +iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. ## ## Substrings are separated by a substring containing only `seps`. Note @@ -369,15 +370,19 @@ iterator split*(s: string, seps: set[char] = Whitespace): string = ## "08.398990" ## var last = 0 + var splits = maxsplit assert(not ('\0' in seps)) while last < len(s): while s[last] in seps: inc(last) var first = last while last < len(s) and s[last] notin seps: inc(last) # BUGFIX! if first <= last-1: + if splits == 0: last = len(s) yield substr(s, first, last-1) + if splits == 0: break + dec(splits) -iterator split*(s: string, sep: char): string = +iterator split*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a single separator. ## ## Substrings are separated by the character `sep`. @@ -404,26 +409,39 @@ iterator split*(s: string, sep: char): string = ## "" ## var last = 0 + var splits = maxsplit assert('\0' != sep) if len(s) > 0: # `<=` is correct here for the edge cases! while last <= len(s): var first = last while last < len(s) and s[last] != sep: inc(last) + if splits == 0: last = len(s) yield substr(s, first, last-1) + if splits == 0: break + dec(splits) inc(last) -iterator split*(s: string, sep: string): string = +proc substrEq(s: string, a, L: int, x: string): bool = + var i = 0 + while i < L and s[a+i] == x[i]: inc i + result = i == L + +iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a string separator. ## ## Substrings are separated by the string `sep`. var last = 0 + var splits = maxsplit if len(s) > 0: while last <= len(s): var first = last - while last < len(s) and s.substr(last, last + <sep.len) != sep: + while last < len(s) and not s.substrEq(last, sep.len, sep): inc(last) + if splits == 0: last = len(s) yield substr(s, first, last-1) + if splits == 0: break + dec(splits) inc(last, sep.len) iterator splitLines*(s: string): string = @@ -493,25 +511,25 @@ proc countLines*(s: string): int {.noSideEffect, else: discard inc i -proc split*(s: string, seps: set[char] = Whitespace): seq[string] {. +proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {. noSideEffect, rtl, extern: "nsuSplitCharSet".} = ## The same as the `split iterator <#split.i,string,set[char]>`_, but is a ## proc that returns a sequence of substrings. - accumulateResult(split(s, seps)) + accumulateResult(split(s, seps, maxsplit)) -proc split*(s: string, sep: char): seq[string] {.noSideEffect, +proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitChar".} = ## The same as the `split iterator <#split.i,string,char>`_, but is a proc ## that returns a sequence of substrings. - accumulateResult(split(s, sep)) + accumulateResult(split(s, sep, maxsplit)) -proc split*(s: string, sep: string): seq[string] {.noSideEffect, +proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitString".} = ## Splits the string `s` into substrings using a string separator. ## ## Substrings are separated by the string `sep`. This is a wrapper around the ## `split iterator <#split.i,string,string>`_. - accumulateResult(split(s, sep)) + accumulateResult(split(s, sep, maxsplit)) proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToHex".} = @@ -530,6 +548,10 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, # handle negative overflow if n == 0 and x < 0: n = -1 +proc toHex*[T](x: T): string = + ## Shortcut for ``toHex(x, T.sizeOf * 2)`` + toHex(x, T.sizeOf * 2) + proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, rtl, extern: "nsuIntToStr".} = ## Converts `x` to its decimal representation. @@ -1444,28 +1466,216 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``float`` type. result = formatBiggestFloat(f, format, precision, decimalSep) -proc formatSize*(bytes: BiggestInt, decimalSep = '.'): string = - ## Rounds and formats `bytes`. Examples: +proc trimZeros*(x: var string) {.noSideEffect.} = + ## Trim trailing zeros from a formatted floating point + ## value (`x`). Modifies the passed value. + var spl: seq[string] + if x.contains('.') or x.contains(','): + if x.contains('e'): + spl= x.split('e') + x = spl[0] + while x[x.high] == '0': + x.setLen(x.len-1) + if x[x.high] in [',', '.']: + x.setLen(x.len-1) + if spl.len > 0: + x &= "e" & spl[1] + +type + BinaryPrefixMode* = enum ## the different names for binary prefixes + bpIEC, # use the IEC/ISO standard prefixes such as kibi + bpColloquial # use the colloquial kilo, mega etc + +proc formatSize*(bytes: int64, + decimalSep = '.', + prefix = bpIEC, + includeSpace = false): string {.noSideEffect.} = + ## Rounds and formats `bytes`. + ## + ## By default, uses the IEC/ISO standard binary prefixes, so 1024 will be + ## formatted as 1KiB. Set prefix to `bpColloquial` to use the colloquial + ## names from the SI standard (e.g. k for 1000 being reused as 1024). + ## + ## `includeSpace` can be set to true to include the (SI preferred) space + ## between the number and the unit (e.g. 1 KiB). + ## + ## Examples: ## ## .. code-block:: nim ## - ## formatSize(1'i64 shl 31 + 300'i64) == "2.204GB" - ## formatSize(4096) == "4KB" - ## - template frmt(a, b, c: expr): expr = - let bs = $b - insertSep($a) & decimalSep & bs.substr(0, 2) & c - let gigabytes = bytes shr 30 - let megabytes = bytes shr 20 - let kilobytes = bytes shr 10 - if gigabytes != 0: - result = frmt(gigabytes, megabytes, "GB") - elif megabytes != 0: - result = frmt(megabytes, kilobytes, "MB") - elif kilobytes != 0: - result = frmt(kilobytes, bytes, "KB") + ## formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" + ## formatSize((2.234*1024*1024).int) == "2.234MiB" + ## formatSize(4096, includeSpace=true) == "4 KiB" + ## formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" + ## formatSize(4096) == "4KiB" + ## formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" + ## + const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"] + const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"] + var + xb: int64 = bytes + fbytes: float + last_xb: int64 = bytes + matchedIndex: int + prefixes: array[9, string] + if prefix == bpColloquial: + prefixes = collPrefixes else: - result = insertSep($bytes) & "B" + prefixes = iecPrefixes + + # Iterate through prefixes seeing if value will be greater than + # 0 in each case + for index in 1..<prefixes.len: + last_xb = xb + xb = bytes div (1'i64 shl (index*10)) + matchedIndex = index + if xb == 0: + xb = last_xb + matchedIndex = index - 1 + break + # xb has the integer number for the latest value; index should be correct + fbytes = bytes.float / (1'i64 shl (matchedIndex*10)).float + result = formatFloat(fbytes, format=ffDecimal, precision=3, decimalSep=decimalSep) + result.trimZeros() + if includeSpace: + result &= " " + result &= prefixes[matchedIndex] + result &= "B" + +proc formatEng*(f: BiggestFloat, + precision: range[0..32] = 10, + trim: bool = true, + siPrefix: bool = false, + unit: string = nil, + decimalSep = '.'): string {.noSideEffect.} = + ## Converts a floating point value `f` to a string using engineering notation. + ## + ## Numbers in of the range -1000.0<f<1000.0 will be formatted without an + ## exponent. Numbers outside of this range will be formatted as a + ## significand in the range -1000.0<f<1000.0 and an exponent that will always + ## be an integer multiple of 3, corresponding with the SI prefix scale k, M, + ## G, T etc for numbers with an absolute value greater than 1 and m, μ, n, p + ## etc for numbers with an absolute value less than 1. + ## + ## The default configuration (`trim=true` and `precision=10`) shows the + ## **shortest** form that precisely (up to a maximum of 10 decimal places) + ## displays the value. For example, 4.100000 will be displayed as 4.1 (which + ## is mathematically identical) whereas 4.1000003 will be displayed as + ## 4.1000003. + ## + ## If `trim` is set to true, trailing zeros will be removed; if false, the + ## number of digits specified by `precision` will always be shown. + ## + ## `precision` can be used to set the number of digits to be shown after the + ## decimal point or (if `trim` is true) the maximum number of digits to be + ## shown. + ## + ## .. code-block:: nim + ## + ## formatEng(0, 2, trim=false) == "0.00" + ## formatEng(0, 2) == "0" + ## formatEng(0.053, 0) == "53e-3" + ## formatEng(52731234, 2) == "52.73e6" + ## formatEng(-52731234, 2) == "-52.73e6" + ## + ## If `siPrefix` is set to true, the number will be displayed with the SI + ## prefix corresponding to the exponent. For example 4100 will be displayed + ## as "4.1 k" instead of "4.1e3". Note that `u` is used for micro- in place + ## of the greek letter mu (μ) as per ISO 2955. Numbers with an absolute + ## value outside of the range 1e-18<f<1000e18 (1a<f<1000E) will be displayed + ## with an exponent rather than an SI prefix, regardless of whether + ## `siPrefix` is true. + ## + ## If `unit` is not nil, the provided unit will be appended to the string + ## (with a space as required by the SI standard). This behaviour is slightly + ## different to appending the unit to the result as the location of the space + ## is altered depending on whether there is an exponent. + ## + ## .. code-block:: nim + ## + ## formatEng(4100, siPrefix=true, unit="V") == "4.1 kV" + ## formatEng(4.1, siPrefix=true, unit="V") == "4.1 V" + ## formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space + ## formatEng(4100, siPrefix=true) == "4.1 k" + ## formatEng(4.1, siPrefix=true, unit="") == "4.1 " # Space with unit="" + ## formatEng(4100, siPrefix=true, unit="") == "4.1 k" + ## formatEng(4100) == "4.1e3" + ## formatEng(4100, unit="V") == "4.1e3 V" + ## formatEng(4100, unit="") == "4.1e3 " # Space with unit="" + ## + ## `decimalSep` is used as the decimal separator + var + absolute: BiggestFloat + significand: BiggestFloat + fexponent: BiggestFloat + exponent: int + splitResult: seq[string] + suffix: string = "" + proc getPrefix(exp: int): char = + ## Get the SI prefix for a given exponent + ## + ## Assumes exponent is a multiple of 3; returns ' ' if no prefix found + const siPrefixes = ['a','f','p','n','u','m',' ','k','M','G','T','P','E'] + var index: int = (exp div 3) + 6 + result = ' ' + if index in low(siPrefixes)..high(siPrefixes): + result = siPrefixes[index] + + # Most of the work is done with the sign ignored, so get the absolute value + absolute = abs(f) + significand = f + + if absolute == 0.0: + # Simple case: just format it and force the exponent to 0 + exponent = 0 + result = significand.formatBiggestFloat(ffDecimal, precision, decimalSep='.') + else: + # Find the best exponent that's a multiple of 3 + fexponent = round(floor(log10(absolute))) + fexponent = 3.0 * round(floor(fexponent / 3.0)) + # Adjust the significand for the new exponent + significand /= pow(10.0, fexponent) + + # Round the significand and check whether it has affected + # the exponent + significand = round(significand, precision) + absolute = abs(significand) + if absolute >= 1000.0: + significand *= 0.001 + fexponent += 3 + # Components of the result: + result = significand.formatBiggestFloat(ffDecimal, precision, decimalSep='.') + exponent = fexponent.int() + + splitResult = result.split('.') + result = splitResult[0] + # result should have at most one decimal character + if splitResult.len() > 1: + # If trim is set, we get rid of trailing zeros. Don't use trimZeros here as + # we can be a bit more efficient through knowledge that there will never be + # an exponent in this part. + if trim: + while splitResult[1].endsWith("0"): + # Trim last character + splitResult[1].setLen(splitResult[1].len-1) + if splitResult[1].len() > 0: + result &= decimalSep & splitResult[1] + else: + result &= decimalSep & splitResult[1] + + # Combine the results accordingly + if siPrefix and exponent != 0: + var p = getPrefix(exponent) + if p != ' ': + suffix = " " & p + exponent = 0 # Exponent replaced by SI prefix + if suffix == "" and unit != nil: + suffix = " " + if unit != nil: + suffix &= unit + if exponent != 0: + result &= "e" & $exponent + result &= suffix proc findNormalized(x: string, inArray: openArray[string]): int = var i = 0 @@ -1661,9 +1871,14 @@ when isMainModule: ["1,0e-11", "1,0e-011"] doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" - when not defined(testing): - echo formatSize(1'i64 shl 31 + 300'i64) # == "4,GB" - echo formatSize(1'i64 shl 31) + + block: # formatSize tests + doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" + doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" + doAssert formatSize(4096) == "4KiB" + doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" + doAssert formatSize(4096, includeSpace=true) == "4 KiB" + doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] == "The cat eats fish." @@ -1743,6 +1958,7 @@ when isMainModule: doAssert isUpper("ABC") doAssert(not isUpper("AAcc")) doAssert(not isUpper("A#$")) + doAssert(unescape(r"\x013", "", "") == "\x013") doAssert join(["foo", "bar", "baz"]) == "foobarbaz" @@ -1778,4 +1994,39 @@ bar bar """.unindent() == "foo\nfoo\nbar\n" - echo("strutils tests passed") + let s = " this is an example " + doAssert s.split() == @["this", "is", "an", "example"] + doAssert s.split(maxsplit=4) == @["this", "is", "an", "example"] + doAssert s.split(' ', maxsplit=4) == @["", "this", "", "", "is an example "] + doAssert s.split(" ", maxsplit=4) == @["", "this", "", "", "is an example "] + + block: # formatEng tests + doAssert formatEng(0, 2, trim=false) == "0.00" + doAssert formatEng(0, 2) == "0" + doAssert formatEng(53, 2, trim=false) == "53.00" + doAssert formatEng(0.053, 2, trim=false) == "53.00e-3" + doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3" + doAssert formatEng(0.053, 4, trim=true) == "53e-3" + doAssert formatEng(0.053, 0) == "53e-3" + doAssert formatEng(52731234) == "52.731234e6" + doAssert formatEng(-52731234) == "-52.731234e6" + doAssert formatEng(52731234, 1) == "52.7e6" + doAssert formatEng(-52731234, 1) == "-52.7e6" + doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6" + doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6" + + doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV" + doAssert formatEng(4.1, siPrefix=true, unit="V") == "4.1 V" + doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space + doAssert formatEng(4100, siPrefix=true) == "4.1 k" + doAssert formatEng(4.1, siPrefix=true, unit="") == "4.1 " # Includes space + doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k" + doAssert formatEng(4100) == "4.1e3" + doAssert formatEng(4100, unit="V") == "4.1e3 V" + doAssert formatEng(4100, unit="") == "4.1e3 " # Space with unit="" + # Don't use SI prefix as number is too big + doAssert formatEng(3.1e22, siPrefix=true, unit="a") == "31e21 a" + # Don't use SI prefix as number is too small + doAssert formatEng(3.1e-25, siPrefix=true, unit="A") == "310e-27 A" + + #echo("strutils tests passed") diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim index 22f29b77c..351b3c086 100644 --- a/lib/pure/subexes.nim +++ b/lib/pure/subexes.nim @@ -9,7 +9,7 @@ ## Nim support for `substitution expressions`:idx: (`subex`:idx:). ## -## .. include:: ../doc/subexes.txt +## .. include:: ../../doc/subexes.txt ## {.push debugger:off .} # the user does not want to trace a part diff --git a/lib/pure/times.nim b/lib/pure/times.nim index e0ee884a8..ac8dc93ad 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -64,8 +64,9 @@ when defined(posix) and not defined(JS): proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {. importc: "gettimeofday", header: "<sys/time.h>".} - var - timezone {.importc, header: "<time.h>".}: int + when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): + var timezone {.importc, header: "<time.h>".}: int + var tzname {.importc, header: "<time.h>" .}: array[0..1, cstring] # we also need tzset() to make sure that tzname is initialized proc tzset() {.importc, header: "<time.h>".} @@ -94,7 +95,8 @@ elif defined(windows): elif defined(JS): type - Time* {.importc.} = object + Time* = ref TimeObj + TimeObj {.importc.} = object getDay: proc (): int {.tags: [], raises: [], benign.} getFullYear: proc (): int {.tags: [], raises: [], benign.} getHours: proc (): int {.tags: [], raises: [], benign.} @@ -416,18 +418,32 @@ when not defined(JS): when not defined(JS): # C wrapper: + when defined(freebsd) or defined(netbsd) or defined(openbsd): + type + StructTM {.importc: "struct tm", final.} = object + second {.importc: "tm_sec".}, + minute {.importc: "tm_min".}, + hour {.importc: "tm_hour".}, + monthday {.importc: "tm_mday".}, + month {.importc: "tm_mon".}, + year {.importc: "tm_year".}, + weekday {.importc: "tm_wday".}, + yearday {.importc: "tm_yday".}, + isdst {.importc: "tm_isdst".}: cint + gmtoff {.importc: "tm_gmtoff".}: clong + else: + type + StructTM {.importc: "struct tm", final.} = object + second {.importc: "tm_sec".}, + minute {.importc: "tm_min".}, + hour {.importc: "tm_hour".}, + monthday {.importc: "tm_mday".}, + month {.importc: "tm_mon".}, + year {.importc: "tm_year".}, + weekday {.importc: "tm_wday".}, + yearday {.importc: "tm_yday".}, + isdst {.importc: "tm_isdst".}: cint type - StructTM {.importc: "struct tm", final.} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - TimeInfoPtr = ptr StructTM Clock {.importc: "clock_t".} = distinct int @@ -457,24 +473,47 @@ when not defined(JS): const weekDays: array [0..6, WeekDay] = [ dSun, dMon, dTue, dWed, dThu, dFri, dSat] - TimeInfo(second: int(tm.second), - minute: int(tm.minute), - hour: int(tm.hour), - monthday: int(tm.monthday), - month: Month(tm.month), - year: tm.year + 1900'i32, - weekday: weekDays[int(tm.weekday)], - yearday: int(tm.yearday), - isDST: tm.isdst > 0, - tzname: if local: - if tm.isdst > 0: - getTzname().DST + when defined(freebsd) or defined(netbsd) or defined(openbsd): + TimeInfo(second: int(tm.second), + minute: int(tm.minute), + hour: int(tm.hour), + monthday: int(tm.monthday), + month: Month(tm.month), + year: tm.year + 1900'i32, + weekday: weekDays[int(tm.weekday)], + yearday: int(tm.yearday), + isDST: tm.isdst > 0, + tzname: if local: + if tm.isdst > 0: + getTzname().DST + else: + getTzname().nonDST else: - getTzname().nonDST - else: - "UTC", - timezone: if local: getTimezone() else: 0 - ) + "UTC", + # BSD stores in `gmtoff` offset east of UTC in seconds, + # but posix systems using west of UTC in seconds + timezone: if local: -(tm.gmtoff) else: 0 + ) + else: + TimeInfo(second: int(tm.second), + minute: int(tm.minute), + hour: int(tm.hour), + monthday: int(tm.monthday), + month: Month(tm.month), + year: tm.year + 1900'i32, + weekday: weekDays[int(tm.weekday)], + yearday: int(tm.yearday), + isDST: tm.isdst > 0, + tzname: if local: + if tm.isdst > 0: + getTzname().DST + else: + getTzname().nonDST + else: + "UTC", + timezone: if local: getTimezone() else: 0 + ) + proc timeInfoToTM(t: TimeInfo): StructTM = const @@ -564,7 +603,14 @@ when not defined(JS): return ($tzname[0], $tzname[1]) proc getTimezone(): int = - return timezone + when defined(freebsd) or defined(netbsd) or defined(openbsd): + var a = timec(nil) + let lt = localtime(addr(a)) + # BSD stores in `gmtoff` offset east of UTC in seconds, + # but posix systems using west of UTC in seconds + return -(lt.gmtoff) + else: + return timezone proc fromSeconds(since1970: float): Time = Time(since1970) diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 45f52eb7f..eeb1b607d 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -14,7 +14,7 @@ include "system/inclrtl" type - RuneImpl = int # underlying type of Rune + RuneImpl = int32 # underlying type of Rune Rune* = distinct RuneImpl ## type that can hold any Unicode character Rune16* = distinct int16 ## 16 bit Unicode character @@ -1148,7 +1148,7 @@ const 0x01f1, 501, # 0x01f3, 499] # -proc binarySearch(c: RuneImpl, tab: openArray[RuneImpl], len, stride: int): int = +proc binarySearch(c: RuneImpl, tab: openArray[int], len, stride: int): int = var n = len var t = 0 while n > 1: diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index aca9d51e2..b83ec44ca 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -310,7 +310,7 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} = ## ## .. code-block:: nim ## - ## import math + ## import math, random ## proc defectiveRobot() = ## randomize() ## case random(1..4) diff --git a/lib/system.nim b/lib/system.nim index f584f7590..3a5415b87 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -588,6 +588,9 @@ proc sizeof*[T](x: T): int {.magic: "SizeOf", noSideEffect.} ## that one never needs to know ``x``'s size. As a special semantic rule, ## ``x`` may also be a type identifier (``sizeof(int)`` is valid). ## + ## Limitations: If used within nim VM context ``sizeof`` will only work + ## for simple types. + ## ## .. code-block:: nim ## sizeof('A') #=> 1 ## sizeof(2) #=> 8 @@ -2293,12 +2296,15 @@ proc `$`*[T: tuple|object](x: T): string = if not firstElement: result.add(", ") result.add(name) result.add(": ") - when compiles(value.isNil): - if value.isNil: result.add "nil" - else: result.add($value) + when compiles($value): + when compiles(value.isNil): + if value.isNil: result.add "nil" + else: result.add($value) + else: + result.add($value) + firstElement = false else: - result.add($value) - firstElement = false + result.add("...") result.add(")") proc collectionToString[T: set | seq](x: T, b, e: string): string = @@ -2511,7 +2517,7 @@ template newException*(exceptn: typedesc, message: string): expr = e when hostOS == "standalone": - include panicoverride + include "$projectpath/panicoverride" when not declared(sysFatal): when hostOS == "standalone": @@ -2625,6 +2631,14 @@ when not defined(JS): #and not defined(nimscript): else: result = 0 when defined(nimscript): + proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.} + ## Opens a file named `filename` for reading. + ## + ## Then calls `readAll <#readAll>`_ and closes the file afterwards. + ## Returns the string. Raises an IO exception in case of an error. If + ## you need to call this inside a compile time macro you can use + ## `staticRead <#staticRead>`_. + proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.} ## Opens a file named `filename` for writing. Then writes the ## `content` completely to the file and closes the file afterwards. @@ -3031,34 +3045,6 @@ when not defined(JS): #and not defined(nimscript): {.pop.} # stacktrace when not defined(nimscript): - proc likely*(val: bool): bool {.importc: "likely", nodecl, nosideeffect.} - ## Hints the optimizer that `val` is likely going to be true. - ## - ## You can use this proc to decorate a branch condition. On certain - ## platforms this can help the processor predict better which branch is - ## going to be run. Example: - ## - ## .. code-block:: nim - ## for value in inputValues: - ## if likely(value <= 100): - ## process(value) - ## else: - ## echo "Value too big!" - - proc unlikely*(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.} - ## Hints the optimizer that `val` is likely going to be false. - ## - ## You can use this proc to decorate a branch condition. On certain - ## platforms this can help the processor predict better which branch is - ## going to be run. Example: - ## - ## .. code-block:: nim - ## for value in inputValues: - ## if unlikely(value > 100): - ## echo "Value too big!" - ## else: - ## process(value) - proc rawProc*[T: proc](x: T): pointer {.noSideEffect, inline.} = ## retrieves the raw proc pointer of the closure `x`. This is ## useful for interfacing closures with C. @@ -3126,6 +3112,58 @@ proc quit*(errormsg: string, errorcode = QuitFailure) {.noReturn.} = {.pop.} # checks {.pop.} # hints +when not defined(JS): + proc likely_proc(val: bool): bool {.importc: "likely", nodecl, nosideeffect.} + proc unlikely_proc(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.} + +template likely*(val: bool): bool = + ## Hints the optimizer that `val` is likely going to be true. + ## + ## You can use this template to decorate a branch condition. On certain + ## platforms this can help the processor predict better which branch is + ## going to be run. Example: + ## + ## .. code-block:: nim + ## for value in inputValues: + ## if likely(value <= 100): + ## process(value) + ## else: + ## echo "Value too big!" + ## + ## On backends without branch prediction (JS and the nimscript VM), this + ## template will not affect code execution. + when nimvm: + val + else: + when defined(JS): + val + else: + likely_proc(val) + +template unlikely*(val: bool): bool = + ## Hints the optimizer that `val` is likely going to be false. + ## + ## You can use this proc to decorate a branch condition. On certain + ## platforms this can help the processor predict better which branch is + ## going to be run. Example: + ## + ## .. code-block:: nim + ## for value in inputValues: + ## if unlikely(value > 100): + ## echo "Value too big!" + ## else: + ## process(value) + ## + ## On backends without branch prediction (JS and the nimscript VM), this + ## template will not affect code execution. + when nimvm: + val + else: + when defined(JS): + val + else: + unlikely_proc(val) + proc `/`*(x, y: int): float {.inline, noSideEffect.} = ## integer division that results in a float. result = toFloat(x) / toFloat(y) diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index e0fd53b7b..00a16e2bb 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -27,15 +27,14 @@ const type PTrunk = ptr Trunk - Trunk {.final.} = object + Trunk = object next: PTrunk # all nodes are connected with this pointer key: int # start address at bit 0 bits: array[0..IntsPerTrunk-1, int] # a bit vector TrunkBuckets = array[0..255, PTrunk] - IntSet {.final.} = object + IntSet = object data: TrunkBuckets -{.deprecated: [TIntSet: IntSet, TTrunk: Trunk, TTrunkBuckets: TrunkBuckets].} type AlignType = BiggestFloat @@ -64,8 +63,6 @@ type next, prev: PBigChunk # chunks of the same (or bigger) size align: int data: AlignType # start of usable memory -{.deprecated: [TAlignType: AlignType, TFreeCell: FreeCell, TBaseChunk: BaseChunk, - TBigChunk: BigChunk, TSmallChunk: SmallChunk].} template smallChunkOverhead(): expr = sizeof(SmallChunk)-sizeof(AlignType) template bigChunkOverhead(): expr = sizeof(BigChunk)-sizeof(AlignType) @@ -79,18 +76,18 @@ template bigChunkOverhead(): expr = sizeof(BigChunk)-sizeof(AlignType) type PLLChunk = ptr LLChunk - LLChunk {.pure.} = object ## *low-level* chunk + LLChunk = object ## *low-level* chunk size: int # remaining size acc: int # accumulator next: PLLChunk # next low-level chunk; only needed for dealloc PAvlNode = ptr AvlNode - AvlNode {.pure, final.} = object + AvlNode = object link: array[0..1, PAvlNode] # Left (0) and right (1) links key, upperBound: int level: int - MemRegion {.final, pure.} = object + MemRegion = object minLargeObj, maxLargeObj: int freeSmallChunks: array[0..SmallChunkSize div MemAlign-1, PSmallChunk] llmem: PLLChunk @@ -99,6 +96,7 @@ type freeChunksList: PBigChunk # XXX make this a datastructure with O(1) access chunkStarts: IntSet root, deleted, last, freeAvlNodes: PAvlNode + locked: bool # if locked, we cannot free pages. {.deprecated: [TLLChunk: LLChunk, TAvlNode: AvlNode, TMemRegion: MemRegion].} # shared: @@ -234,7 +232,8 @@ proc isSmallChunk(c: PChunk): bool {.inline.} = proc chunkUnused(c: PChunk): bool {.inline.} = result = not c.used -iterator allObjects(m: MemRegion): pointer {.inline.} = +iterator allObjects(m: var MemRegion): pointer {.inline.} = + m.locked = true for s in elements(m.chunkStarts): # we need to check here again as it could have been modified: if s in m.chunkStarts: @@ -252,6 +251,7 @@ iterator allObjects(m: MemRegion): pointer {.inline.} = else: let c = cast[PBigChunk](c) yield addr(c.data) + m.locked = false proc iterToProc*(iter: typed, envType: typedesc; procName: untyped) {. magic: "Plugin", compileTime.} @@ -385,7 +385,7 @@ proc freeBigChunk(a: var MemRegion, c: PBigChunk) = excl(a.chunkStarts, pageIndex(c)) c = cast[PBigChunk](le) - if c.size < ChunkOsReturn or doNotUnmap: + if c.size < ChunkOsReturn or doNotUnmap or a.locked: incl(a, a.chunkStarts, pageIndex(c)) updatePrevSize(a, c, c.size) listAdd(a.freeChunksList, c) @@ -442,26 +442,29 @@ proc getSmallChunk(a: var MemRegion): PSmallChunk = # ----------------------------------------------------------------------------- proc isAllocatedPtr(a: MemRegion, p: pointer): bool {.benign.} -proc allocInv(a: MemRegion): bool = - ## checks some (not all yet) invariants of the allocator's data structures. - for s in low(a.freeSmallChunks)..high(a.freeSmallChunks): - var c = a.freeSmallChunks[s] - while not (c == nil): - if c.next == c: - echo "[SYSASSERT] c.next == c" - return false - if not (c.size == s * MemAlign): - echo "[SYSASSERT] c.size != s * MemAlign" - return false - var it = c.freeList - while not (it == nil): - if not (it.zeroField == 0): - echo "[SYSASSERT] it.zeroField != 0" - c_printf("%ld %p\n", it.zeroField, it) +when true: + template allocInv(a: MemRegion): bool = true +else: + proc allocInv(a: MemRegion): bool = + ## checks some (not all yet) invariants of the allocator's data structures. + for s in low(a.freeSmallChunks)..high(a.freeSmallChunks): + var c = a.freeSmallChunks[s] + while not (c == nil): + if c.next == c: + echo "[SYSASSERT] c.next == c" return false - it = it.next - c = c.next - result = true + if not (c.size == s * MemAlign): + echo "[SYSASSERT] c.size != s * MemAlign" + return false + var it = c.freeList + while not (it == nil): + if not (it.zeroField == 0): + echo "[SYSASSERT] it.zeroField != 0" + c_printf("%ld %p\n", it.zeroField, it) + return false + it = it.next + c = c.next + result = true proc rawAlloc(a: var MemRegion, requestedSize: int): pointer = sysAssert(allocInv(a), "rawAlloc: begin") diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 5c2170a17..ee5eec30b 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -1,7 +1,7 @@ # # # Nim's Runtime Library -# (c) Copyright 2015 Andreas Rumpf +# (c) Copyright 2016 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -9,13 +9,8 @@ # Garbage Collector # -# The basic algorithm is *Deferred Reference Counting* with cycle detection. -# This is achieved by combining a Deutsch-Bobrow garbage collector -# together with Christoper's partial mark-sweep garbage collector. -# -# Special care has been taken to avoid recursion as far as possible to avoid -# stack overflows when traversing deep datastructures. It is well-suited -# for soft real time applications (like games). +# Refcounting + Mark&Sweep. Complex algorithms avoided. +# Been there, done that, didn't work. when defined(nimCoroutines): import arch @@ -30,7 +25,7 @@ const # this seems to be a good value withRealTime = defined(useRealtimeGC) useMarkForDebug = defined(gcGenerational) - useBackupGc = false # use a simple M&S GC to collect + useBackupGc = true # use a simple M&S GC to collect # cycles instead of the complex # algorithm @@ -55,8 +50,7 @@ type WalkOp = enum waMarkGlobal, # part of the backup/debug mark&sweep waMarkPrecise, # part of the backup/debug mark&sweep - waZctDecRef, waPush, waCycleDecRef, waMarkGray, waScan, waScanBlack, - waCollectWhite #, waDebug + waZctDecRef, waPush Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign.} # A ref type can have a finalizer that is called before the object's @@ -87,7 +81,6 @@ type idGenerator: int zct: CellSeq # the zero count table decStack: CellSeq # cells in the stack that are to decref again - cycleRoots: CellSet tempStack: CellSeq # temporary stack for recursion elimination recGcLock: int # prevent recursion via finalizers; no thread lock when withRealTime: @@ -96,6 +89,7 @@ type stat: GcStat when useMarkForDebug or useBackupGc: marked: CellSet + additionalRoots: CellSeq # dummy roots for GC_ref/unref when hasThreadSupport: toDispose: SharedList[pointer] @@ -136,9 +130,6 @@ proc usrToCell(usr: pointer): PCell {.inline.} = # convert pointer to userdata to object (=pointer to refcount) result = cast[PCell](cast[ByteAddress](usr)-%ByteAddress(sizeof(Cell))) -proc canBeCycleRoot(c: PCell): bool {.inline.} = - result = ntfAcyclic notin c.typ.flags - proc extGetCellType(c: pointer): PNimType {.compilerproc.} = # used for code generation concerning debugging result = usrToCell(c).typ @@ -200,14 +191,16 @@ proc prepareDealloc(cell: PCell) = (cast[Finalizer](cell.typ.finalizer))(cellToUsr(cell)) dec(gch.recGcLock) +template beforeDealloc(gch: var GcHeap; c: PCell; msg: typed) = + when false: + for i in 0..gch.decStack.len-1: + if gch.decStack.d[i] == c: + sysAssert(false, msg) + proc rtlAddCycleRoot(c: PCell) {.rtl, inl.} = # we MUST access gch as a global here, because this crosses DLL boundaries! when hasThreadSupport and hasSharedHeap: acquireSys(HeapLock) - when cycleGC: - if c.color != rcPurple: - c.setColor(rcPurple) - incl(gch.cycleRoots, c) when hasThreadSupport and hasSharedHeap: releaseSys(HeapLock) @@ -224,22 +217,30 @@ proc decRef(c: PCell) {.inline.} = gcAssert(c.refcount >=% rcIncrement, "decRef") if --c.refcount: rtlAddZCT(c) - elif canbeCycleRoot(c): - # unfortunately this is necessary here too, because a cycle might just - # have been broken up and we could recycle it. - rtlAddCycleRoot(c) - #writeCell("decRef", c) proc incRef(c: PCell) {.inline.} = gcAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr") c.refcount = c.refcount +% rcIncrement # and not colorMask #writeCell("incRef", c) - if canbeCycleRoot(c): - rtlAddCycleRoot(c) -proc nimGCref(p: pointer) {.compilerProc, inline.} = incRef(usrToCell(p)) -proc nimGCunref(p: pointer) {.compilerProc, inline.} = decRef(usrToCell(p)) +proc nimGCref(p: pointer) {.compilerProc.} = + # we keep it from being collected by pretending it's not even allocated: + add(gch.additionalRoots, usrToCell(p)) + incRef(usrToCell(p)) + +proc nimGCunref(p: pointer) {.compilerProc.} = + let cell = usrToCell(p) + var L = gch.additionalRoots.len-1 + var i = L + let d = gch.additionalRoots.d + while i >= 0: + if d[i] == cell: + d[i] = d[L] + dec gch.additionalRoots.len + break + dec(i) + decRef(usrToCell(p)) proc GC_addCycleRoot*[T](p: ref T) {.inline.} = ## adds 'p' to the cycle candidate set for the cycle collector. It is @@ -306,10 +307,10 @@ proc initGC() = # init the rt init(gch.zct) init(gch.tempStack) - init(gch.cycleRoots) init(gch.decStack) when useMarkForDebug or useBackupGc: init(gch.marked) + init(gch.additionalRoots) when hasThreadSupport: gch.toDispose = initSharedList[pointer]() @@ -563,7 +564,7 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = d[j] = res break dec(j) - if canbeCycleRoot(ol): excl(gch.cycleRoots, ol) + beforeDealloc(gch, ol, "growObj stack trash") rawDealloc(gch.region, ol) else: # we split the old refcount in 2 parts. XXX This is still not entirely @@ -597,54 +598,12 @@ proc freeCyclicCell(gch: var GcHeap, c: PCell) = when logGC: writeCell("cycle collector dealloc cell", c) when reallyDealloc: sysAssert(allocInv(gch.region), "free cyclic cell") + beforeDealloc(gch, c, "freeCyclicCell: stack trash") rawDealloc(gch.region, c) else: gcAssert(c.typ != nil, "freeCyclicCell") zeroMem(c, sizeof(Cell)) -proc markGray(s: PCell) = - if s.color != rcGray: - setColor(s, rcGray) - forAllChildren(s, waMarkGray) - -proc scanBlack(s: PCell) = - s.setColor(rcBlack) - forAllChildren(s, waScanBlack) - -proc scan(s: PCell) = - if s.color == rcGray: - if s.refcount >=% rcIncrement: - scanBlack(s) - else: - s.setColor(rcWhite) - forAllChildren(s, waScan) - -proc collectWhite(s: PCell) = - # This is a hacky way to deal with the following problem (bug #1796) - # Consider this content in cycleRoots: - # x -> a; y -> a where 'a' is an acyclic object so not included in - # cycleRoots itself. Then 'collectWhite' used to free 'a' twice. The - # 'isAllocatedPtr' check prevents this. This also means we do not need - # to query 's notin gch.cycleRoots' at all. - if isAllocatedPtr(gch.region, s) and s.color == rcWhite: - s.setColor(rcBlack) - forAllChildren(s, waCollectWhite) - freeCyclicCell(gch, s) - -proc markRoots(gch: var GcHeap) = - var tabSize = 0 - for s in elements(gch.cycleRoots): - #writeCell("markRoot", s) - inc tabSize - if s.color == rcPurple and s.refcount >=% rcIncrement: - markGray(s) - else: - excl(gch.cycleRoots, s) - # (s.color == rcBlack and rc == 0) as 1 condition: - if s.refcount == 0: - freeCyclicCell(gch, s) - gch.stat.cycleTableSize = max(gch.stat.cycleTableSize, tabSize) - when useBackupGc: proc sweep(gch: var GcHeap) = for x in allObjects(gch.region): @@ -666,16 +625,8 @@ when useMarkForDebug or useBackupGc: proc markGlobals(gch: var GcHeap) = for i in 0 .. < globalMarkersLen: globalMarkers[i]() - - proc stackMarkS(gch: var GcHeap, p: pointer) {.inline.} = - # the addresses are not as cells on the stack, so turn them to cells: - var cell = usrToCell(p) - var c = cast[ByteAddress](cell) - if c >% PageSize: - # fast check: does it look like a cell? - var objStart = cast[PCell](interiorAllocatedPtr(gch.region, cell)) - if objStart != nil: - markS(gch, objStart) + let d = gch.additionalRoots.d + for i in 0 .. < gch.additionalRoots.len: markS(gch, d[i]) when logGC: var @@ -717,19 +668,6 @@ proc doOperation(p: pointer, op: WalkOp) = #if c.refcount <% rcIncrement: addZCT(gch.zct, c) of waPush: add(gch.tempStack, c) - of waCycleDecRef: - gcAssert(c.refcount >=% rcIncrement, "doOperation 3") - c.refcount = c.refcount -% rcIncrement - of waMarkGray: - gcAssert(c.refcount >=% rcIncrement, "waMarkGray") - c.refcount = c.refcount -% rcIncrement - markGray(c) - of waScan: scan(c) - of waScanBlack: - c.refcount = c.refcount +% rcIncrement - if c.color != rcBlack: - scanBlack(c) - of waCollectWhite: collectWhite(c) of waMarkGlobal: when useMarkForDebug or useBackupGc: when hasThreadSupport: @@ -748,14 +686,6 @@ proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = proc collectZCT(gch: var GcHeap): bool {.benign.} -when useMarkForDebug or useBackupGc: - proc markStackAndRegistersForSweep(gch: var GcHeap) {.noinline, cdecl, - benign.} - -proc collectRoots(gch: var GcHeap) = - for s in elements(gch.cycleRoots): - collectWhite(s) - proc collectCycles(gch: var GcHeap) = when hasThreadSupport: for c in gch.toDispose: @@ -764,33 +694,12 @@ proc collectCycles(gch: var GcHeap) = while gch.zct.len > 0: discard collectZCT(gch) when useBackupGc: cellsetReset(gch.marked) - markStackAndRegistersForSweep(gch) - markGlobals(gch) - sweep(gch) - else: - markRoots(gch) - # scanRoots: - for s in elements(gch.cycleRoots): scan(s) - collectRoots(gch) - - cellsetReset(gch.cycleRoots) - # alive cycles need to be kept in 'cycleRoots' if they are referenced - # from the stack; otherwise the write barrier will add the cycle root again - # anyway: - when false: var d = gch.decStack.d - var cycleRootsLen = 0 for i in 0..gch.decStack.len-1: - var c = d[i] - gcAssert isAllocatedPtr(gch.region, c), "addBackStackRoots" - gcAssert c.refcount >=% rcIncrement, "addBackStackRoots: dead cell" - if canBeCycleRoot(c): - #if c notin gch.cycleRoots: - inc cycleRootsLen - incl(gch.cycleRoots, c) - gcAssert c.typ != nil, "addBackStackRoots 2" - if cycleRootsLen != 0: - cfprintf(cstdout, "cycle roots: %ld\n", cycleRootsLen) + sysAssert isAllocatedPtr(gch.region, d[i]), "collectCycles" + markS(gch, d[i]) + markGlobals(gch) + sweep(gch) proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = # the addresses are not as cells on the stack, so turn them to cells: @@ -812,31 +721,11 @@ proc gcMark(gch: var GcHeap, p: pointer) {.inline.} = add(gch.decStack, cell) sysAssert(allocInv(gch.region), "gcMark end") -proc markThreadStacks(gch: var GcHeap) = - when hasThreadSupport and hasSharedHeap: - {.error: "not fully implemented".} - var it = threadList - while it != nil: - # mark registers: - for i in 0 .. high(it.registers): gcMark(gch, it.registers[i]) - var sp = cast[ByteAddress](it.stackBottom) - var max = cast[ByteAddress](it.stackTop) - # XXX stack direction? - # XXX unroll this loop: - while sp <=% max: - gcMark(gch, cast[ppointer](sp)[]) - sp = sp +% sizeof(pointer) - it = it.next - include gc_common proc markStackAndRegisters(gch: var GcHeap) {.noinline, cdecl.} = forEachStackSlot(gch, gcMark) -when useMarkForDebug or useBackupGc: - proc markStackAndRegistersForSweep(gch: var GcHeap) = - forEachStackSlot(gch, stackMarkS) - proc collectZCT(gch: var GcHeap): bool = # Note: Freeing may add child objects to the ZCT! So essentially we do # deep freeing, which is bad for incremental operation. In order to @@ -866,8 +755,6 @@ proc collectZCT(gch: var GcHeap): bool = # as this might be too slow. # In any case, it should be removed from the ZCT. But not # freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!** - when cycleGC: - if canbeCycleRoot(c): excl(gch.cycleRoots, c) when logGC: writeCell("zct dealloc cell", c) gcTrace(c, csZctFreed) # We are about to free the object, call the finalizer BEFORE its @@ -877,6 +764,7 @@ proc collectZCT(gch: var GcHeap): bool = forAllChildren(c, waZctDecRef) when reallyDealloc: sysAssert(allocInv(gch.region), "collectZCT: rawDealloc") + beforeDealloc(gch, c, "collectZCT: stack trash") rawDealloc(gch.region, c) else: sysAssert(c.typ != nil, "collectZCT 2") @@ -915,7 +803,6 @@ proc collectCTBody(gch: var GcHeap) = sysAssert(gch.decStack.len == 0, "collectCT") prepareForInteriorPointerChecking(gch.region) markStackAndRegisters(gch) - markThreadStacks(gch) gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len) inc(gch.stat.stackScans) if collectZCT(gch): @@ -937,11 +824,6 @@ proc collectCTBody(gch: var GcHeap) = if gch.maxPause > 0 and duration > gch.maxPause: c_fprintf(c_stdout, "[GC] missed deadline: %ld\n", duration) -when useMarkForDebug or useBackupGc: - proc markForDebug(gch: var GcHeap) = - markStackAndRegistersForSweep(gch) - markGlobals(gch) - when defined(nimCoroutines): proc currentStackSizes(): int = for stack in items(gch.stack): @@ -1035,7 +917,7 @@ when not defined(useNimRtl): "[GC] max threshold: " & $gch.stat.maxThreshold & "\n" & "[GC] zct capacity: " & $gch.zct.cap & "\n" & "[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" & - "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) + "[GC] max pause time [ms]: " & $(gch.stat.maxPause div 1000_000) & "\n" when defined(nimCoroutines): result = result & "[GC] number of stacks: " & $gch.stack.len & "\n" for stack in items(gch.stack): diff --git a/lib/system/gc2.nim b/lib/system/gc2.nim index 7d54c07be..7f8e466b5 100644 --- a/lib/system/gc2.nim +++ b/lib/system/gc2.nim @@ -97,6 +97,8 @@ type additionalRoots: CellSeq # dummy roots for GC_ref/unref spaceIter: ObjectSpaceIter dumpHeapFile: File # File that is used for GC_dumpHeap + when hasThreadSupport: + toDispose: SharedList[pointer] var gch {.rtlThreadVar.}: GcHeap @@ -119,6 +121,8 @@ proc initGC() = init(gch.decStack) init(gch.additionalRoots) init(gch.greyStack) + when hasThreadSupport: + gch.toDispose = initSharedList[pointer]() # Which color to use for new objects is tricky: When we're marking, # they have to be *white* so that everything is marked that is only @@ -800,6 +804,10 @@ proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = proc collectZCT(gch: var GcHeap): bool {.benign.} proc collectCycles(gch: var GcHeap): bool = + when hasThreadSupport: + for c in gch.toDispose: + nimGCunref(c) + # ensure the ZCT 'color' is not used: while gch.zct.len > 0: discard collectZCT(gch) diff --git a/lib/system/gc_stack.nim b/lib/system/gc_stack.nim index c251a4d0b..e30d0a720 100644 --- a/lib/system/gc_stack.nim +++ b/lib/system/gc_stack.nim @@ -8,7 +8,23 @@ # "Stack GC" for embedded devices or ultra performance requirements. -include osalloc +when defined(nimphpext): + proc roundup(x, v: int): int {.inline.} = + result = (x + (v-1)) and not (v-1) + proc emalloc(size: int): pointer {.importc: "_emalloc".} + proc efree(mem: pointer) {.importc: "_efree".} + + proc osAllocPages(size: int): pointer {.inline.} = + emalloc(size) + + proc osTryAllocPages(size: int): pointer {.inline.} = + emalloc(size) + + proc osDeallocPages(p: pointer, size: int) {.inline.} = + efree(p) + +else: + include osalloc # We manage memory as a thread local stack. Since the allocation pointer # is detached from the control flow pointer, this model is vastly more @@ -100,6 +116,7 @@ proc allocSlowPath(r: var MemRegion; size: int) = fresh.size = s fresh.head = nil fresh.tail = nil + fresh.next = nil inc r.totalSize, s let old = r.tail if old == nil: @@ -127,11 +144,12 @@ proc runFinalizers(c: Chunk) = (cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader)) it = it.nextFinal -proc dealloc(r: var MemRegion; p: pointer) = - let it = cast[ptr ObjHeader](p-!sizeof(ObjHeader)) - if it.typ != nil and it.typ.finalizer != nil: - (cast[Finalizer](it.typ.finalizer))(p) - it.typ = nil +when false: + proc dealloc(r: var MemRegion; p: pointer) = + let it = cast[ptr ObjHeader](p-!sizeof(ObjHeader)) + if it.typ != nil and it.typ.finalizer != nil: + (cast[Finalizer](it.typ.finalizer))(p) + it.typ = nil proc deallocAll(r: var MemRegion; head: Chunk) = var it = head @@ -168,6 +186,9 @@ proc setObstackPtr*(r: var MemRegion; sp: StackPtr) = proc obstackPtr*(): StackPtr = tlRegion.obstackPtr() proc setObstackPtr*(sp: StackPtr) = tlRegion.setObstackPtr(sp) +proc deallocAll*() = tlRegion.deallocAll() + +proc deallocOsPages(r: var MemRegion) = r.deallocAll() proc joinRegion*(dest: var MemRegion; src: MemRegion) = # merging is not hard. @@ -406,6 +427,13 @@ proc realloc(p: pointer, newsize: Natural): pointer = if result == nil: raiseOutOfMem() proc dealloc(p: pointer) = cfree(p) +proc alloc0(r: var MemRegion; size: Natural): pointer = + # ignore the region. That is correct for the channels module + # but incorrect in general. XXX + result = alloc0(size) + +proc dealloc(r: var MemRegion; p: pointer) = dealloc(p) + proc allocShared(size: Natural): pointer = result = cmalloc(size) if result == nil: raiseOutOfMem() diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 99997efe6..ce67373bc 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -248,8 +248,12 @@ proc toJSStr(s: string): cstring {.asmNoStackFrame, compilerproc.} = for (var i = 0; i < len; ++i) { if (nonAsciiPart !== null) { var offset = (i - nonAsciiOffset) * 2; + var code = `s`[i].toString(16); + if (code.length == 1) { + code = "0"+code; + } nonAsciiPart[offset] = "%"; - nonAsciiPart[offset + 1] = `s`[i].toString(16); + nonAsciiPart[offset + 1] = code; } else if (`s`[i] < 128) asciiPart[i] = fcc(`s`[i]); diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 78410d716..8b83e194b 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -72,7 +72,7 @@ when defined(emscripten): proc osTryAllocPages(size: int): pointer = osAllocPages(size) - proc osDeallocPages(p: pointer, size: int) {.inline} = + proc osDeallocPages(p: pointer, size: int) {.inline.} = var mmapDescrPos = cast[ByteAddress](p) -% sizeof(EmscriptenMMapBlock) var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos) munmap(mmapDescr.realPointer, mmapDescr.realSize) @@ -107,7 +107,7 @@ elif defined(posix): MAP_PRIVATE or MAP_ANONYMOUS, -1, 0) if result == cast[pointer](-1): result = nil - proc osDeallocPages(p: pointer, size: int) {.inline} = + proc osDeallocPages(p: pointer, size: int) {.inline.} = when reallyOsDealloc: discard munmap(p, size) elif defined(windows): diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 3c34215ac..2fcfd69d8 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -236,8 +236,7 @@ when declared(stdout): # interface to the C procs: -when (defined(windows) and not defined(useWinAnsi)) or defined(nimdoc): - include "system/widestrs" +include "system/widestrs" when defined(windows) and not defined(useWinAnsi): when defined(cpp): diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index f8b93a2c3..74fcfd8c5 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -304,43 +304,39 @@ proc nimFloatToStr(f: float): string {.compilerproc.} = proc strtod(buf: cstring, endptr: ptr cstring): float64 {.importc, header: "<stdlib.h>", noSideEffect.} -var decimalPoint: char - -proc getDecimalPoint(): char = - result = decimalPoint - if result == '\0': - if strtod("0,5", nil) == 0.5: result = ',' - else: result = '.' - # yes this is threadsafe in practice, spare me: - decimalPoint = result - const IdentChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'} + powtens = [ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22] proc nimParseBiggestFloat(s: string, number: var BiggestFloat, start = 0): int {.compilerProc.} = - # This routine leverages `strtod()` for the non-trivial task of - # parsing floating point numbers correctly. Because `strtod()` is - # locale-dependent with respect to the radix character, we create - # a copy where the decimal point is replaced with the locale's - # radix character. + # This routine attempt to parse float that can parsed quickly. + # ie whose integer part can fit inside a 53bits integer. + # their real exponent must also be <= 22. If the float doesn't follow + # these restrictions, transform the float into this form: + # INTEGER * 10 ^ exponent and leave the work to standard `strtod()`. + # This avoid the problems of decimal character portability. + # see: http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ var i = start sign = 1.0 - t: array[500, char] # flaviu says: 325 is the longest reasonable literal - ti = 0 - hasdigits = false - - template addToBuf(c) = - if ti < t.high: - t[ti] = c; inc(ti) + kdigits, fdigits = 0 + exponent: int + integer: uint64 + fraction: uint64 + frac_exponent= 0 + exp_sign = 1 + first_digit = -1 + has_sign = false # Sign? if s[i] == '+' or s[i] == '-': + has_sign = true if s[i] == '-': sign = -1.0 - t[ti] = s[i] - inc(i); inc(ti) + inc(i) # NaN? if s[i] == 'N' or s[i] == 'n': @@ -360,40 +356,111 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, return i+3 - start return 0 + if s[i] in {'0'..'9'}: + first_digit = (s[i].ord - '0'.ord) # Integer part? while s[i] in {'0'..'9'}: - hasdigits = true - addToBuf(s[i]) - inc(i); + inc(kdigits) + integer = integer * 10'u64 + (s[i].ord - '0'.ord).uint64 + inc(i) while s[i] == '_': inc(i) # Fractional part? if s[i] == '.': - addToBuf(getDecimalPoint()) inc(i) + # if no integer part, Skip leading zeros + if kdigits <= 0: + while s[i] == '0': + inc(frac_exponent) + inc(i) + while s[i] == '_': inc(i) + + if first_digit == -1 and s[i] in {'0'..'9'}: + first_digit = (s[i].ord - '0'.ord) + # get fractional part while s[i] in {'0'..'9'}: - hasdigits = true - addToBuf(s[i]) + inc(fdigits) + inc(frac_exponent) + integer = integer * 10'u64 + (s[i].ord - '0'.ord).uint64 inc(i) while s[i] == '_': inc(i) - if not hasdigits: + + # if has no digits: return error + if kdigits + fdigits <= 0 and + (i == start or # no char consumed (empty string). + (i == start + 1 and has_sign)): # or only '+' or '- return 0 - # Exponent? if s[i] in {'e', 'E'}: - addToBuf(s[i]) inc(i) - if s[i] in {'+', '-'}: - addToBuf(s[i]) + if s[i] == '+' or s[i] == '-': + if s[i] == '-': + exp_sign = -1 + inc(i) if s[i] notin {'0'..'9'}: return 0 while s[i] in {'0'..'9'}: - addToBuf(s[i]) + exponent = exponent * 10 + (ord(s[i]) - ord('0')) inc(i) - while s[i] == '_': inc(i) - number = strtod(t, nil) + while s[i] == '_': inc(i) # underscores are allowed and ignored + + var real_exponent = exp_sign*exponent - frac_exponent + let exp_negative = real_exponent < 0 + var abs_exponent = abs(real_exponent) + + # if exponent greater than can be represented: +/- zero or infinity + if abs_exponent > 999: + if exp_negative: + number = 0.0*sign + else: + number = Inf*sign + return i - start + + # if integer is representable in 53 bits: fast path + # max fast path integer is 1<<53 - 1 or 8999999999999999 (16 digits) + if kdigits + fdigits <= 16 and first_digit <= 8: + # max float power of ten with set bits above the 53th bit is 10^22 + if abs_exponent <= 22: + if exp_negative: + number = sign * integer.float / powtens[abs_exponent] + else: + number = sign * integer.float * powtens[abs_exponent] + return i - start + + # if exponent is greater try to fit extra exponent above 22 by multiplying + # integer part is there is space left. + let slop = 15 - kdigits - fdigits + if abs_exponent <= 22 + slop and not exp_negative: + number = sign * integer.float * powtens[slop] * powtens[abs_exponent-slop] + return i - start + + # if failed: slow path with strtod. + var t: array[500, char] # flaviu says: 325 is the longest reasonable literal + var ti = 0 + let maxlen = t.high - "e+000".len # reserve enough space for exponent + result = i - start + i = start + # re-parse without error checking, any error should be handled by the code above. + while s[i] in {'0'..'9','+','-'}: + if ti < maxlen: + t[ti] = s[i]; inc(ti) + inc(i) + while s[i] in {'.', '_'}: # skip underscore and decimal point + inc(i) + + # insert exponent + t[ti] = 'E'; inc(ti) + t[ti] = if exp_negative: '-' else: '+'; inc(ti) + inc(ti, 3) + + # insert adjusted exponent + t[ti-1] = ('0'.ord + abs_exponent mod 10).char; abs_exponent = abs_exponent div 10 + t[ti-2] = ('0'.ord + abs_exponent mod 10).char; abs_exponent = abs_exponent div 10 + t[ti-3] = ('0'.ord + abs_exponent mod 10).char + + number = strtod(t, nil) proc nimInt64ToStr(x: int64): string {.compilerRtl.} = result = newString(sizeof(x)*4) diff --git a/lib/system/threads.nim b/lib/system/threads.nim index bdb737e35..99927fbac 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -301,7 +301,7 @@ type ## a pointer as a thread ID. {.deprecated: [TThread: Thread, TThreadId: ThreadId].} -when not defined(boehmgc) and not hasSharedHeap and not defined(gogc): +when not defined(boehmgc) and not hasSharedHeap and not defined(gogc) and not defined(gcstack): proc deallocOsPages() when defined(boehmgc): @@ -331,7 +331,7 @@ else: proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) = when defined(boehmgc): boehmGC_call_with_stack_base(threadProcWrapDispatch[TArg], thrd) - elif not defined(nogc) and not defined(gogc): + elif not defined(nogc) and not defined(gogc) and not defined(gcstack): var p {.volatile.}: proc(a: ptr Thread[TArg]) {.nimcall.} = threadProcWrapDispatch[TArg] when not hasSharedHeap: diff --git a/lib/system/timers.nim b/lib/system/timers.nim index ac8418824..8aa4505c4 100644 --- a/lib/system/timers.nim +++ b/lib/system/timers.nim @@ -61,7 +61,7 @@ elif defined(posixRealtime): final, pure.} = object ## struct timespec tv_sec: int ## Seconds. tv_nsec: int ## Nanoseconds. - {.deprecated: [TClockid: Clickid, TTimeSpec: TimeSpec].} + {.deprecated: [TClockid: Clockid, TTimeSpec: TimeSpec].} var CLOCK_REALTIME {.importc: "CLOCK_REALTIME", header: "<time.h>".}: Clockid diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 8ad3faf41..645ccd57b 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -94,7 +94,7 @@ type dwMinorVersion*: DWORD dwBuildNumber*: DWORD dwPlatformId*: DWORD - szCSDVersion*: array[0..127, WinChar]; + szCSDVersion*: array[0..127, WinChar] {.deprecated: [THandle: Handle, TSECURITY_ATTRIBUTES: SECURITY_ATTRIBUTES, TSTARTUPINFO: STARTUPINFO, TPROCESS_INFORMATION: PROCESS_INFORMATION, @@ -518,6 +518,8 @@ var SO_DEBUG* {.importc, header: "winsock2.h".}: cint ## turn on debugging info recording SO_ACCEPTCONN* {.importc, header: "winsock2.h".}: cint # socket has had listen() SO_REUSEADDR* {.importc, header: "winsock2.h".}: cint # allow local address reuse + SO_REUSEPORT* {.importc: "SO_REUSEADDR", header: "winsock2.h".}: cint # allow port reuse. Since Windows does not really support it, mapped to SO_REUSEADDR. This shouldn't cause problems. + SO_KEEPALIVE* {.importc, header: "winsock2.h".}: cint # keep connections alive SO_DONTROUTE* {.importc, header: "winsock2.h".}: cint # just use interface addresses SO_BROADCAST* {.importc, header: "winsock2.h".}: cint # permit sending of broadcast msgs diff --git a/tests/async/tasyncdiscard.nim b/tests/async/tasyncdiscard.nim index 71aba29e2..e7c87ad42 100644 --- a/tests/async/tasyncdiscard.nim +++ b/tests/async/tasyncdiscard.nim @@ -36,4 +36,4 @@ proc main {.async.} = discard await g() echo 6 -asyncCheck main() +waitFor(main()) diff --git a/tests/async/tasynctry.nim b/tests/async/tasynctry.nim index f77198e2e..5930f296f 100644 --- a/tests/async/tasynctry.nim +++ b/tests/async/tasynctry.nim @@ -48,7 +48,7 @@ proc catch() {.async.} = except OSError, EInvalidField: assert false -asyncCheck catch() +waitFor catch() proc test(): Future[bool] {.async.} = result = false @@ -92,13 +92,13 @@ proc test4(): Future[int] {.async.} = result = 2 var x = test() -assert x.read +assert x.waitFor() x = test2() -assert x.read +assert x.waitFor() var y = test3() -assert y.read == 2 +assert y.waitFor() == 2 y = test4() -assert y.read == 2 +assert y.waitFor() == 2 diff --git a/tests/ccgbugs/twrong_string_asgn.nim b/tests/ccgbugs/twrong_string_asgn.nim index b62e70e7c..669b7f8f5 100644 --- a/tests/ccgbugs/twrong_string_asgn.nim +++ b/tests/ccgbugs/twrong_string_asgn.nim @@ -16,4 +16,4 @@ x.callback = proc () = finished = true -while not finished: discard +while not finished: poll() diff --git a/tests/enum/tenum.nim b/tests/enum/tenum.nim index b081212e6..6d9bdd539 100644 --- a/tests/enum/tenum.nim +++ b/tests/enum/tenum.nim @@ -6,3 +6,9 @@ type var en: E en = a + +# Bug #4066 +import macros +macro genEnum(): untyped = newNimNode(nnkEnumTy).add(newEmptyNode(), newIdentNode("geItem1")) +type GeneratedEnum = genEnum() +doAssert(type(geItem1) is GeneratedEnum) diff --git a/tests/float/tfloat4.nim b/tests/float/tfloat4.nim index 960c4e5f7..006b4d88f 100644 --- a/tests/float/tfloat4.nim +++ b/tests/float/tfloat4.nim @@ -1,3 +1,8 @@ +discard """ + file: "tfloat4.nim" + output: "passed all tests." + exitcode: 0 +""" import math, strutils proc c_sprintf(buf, fmt: cstring) {.importc:"sprintf", header: "<stdio.h>", varargs.} @@ -11,8 +16,9 @@ proc floatToStr(f: float64): string = return add(result, ch) + let testFloats = [ - "0", "-1", "1", "1.", ".3", "3.3", "-.3", "-99.99", + "0", "-0", "0.", "0.0", "-0.", "-0.0", "-1", "1", "1.", ".3", "3.3", "-.3", "-99.99", "1.1e10", "-2e100", "1.234e-10", "1.234e+10", "-inf", "inf", "+inf", "3.14159265358979323846264338327950288", @@ -25,18 +31,20 @@ let testFloats = [ ] for num in testFloats: - assert num.parseFloat.floatToStr.parseFloat == num.parseFloat + doAssert num.parseFloat.floatToStr.parseFloat == num.parseFloat -assert "0".parseFloat == 0.0 -assert "-.1".parseFloat == -0.1 -assert "2.5e1".parseFloat == 25.0 -assert "1e10".parseFloat == 10_000_000_000.0 -assert "0.000_005".parseFloat == 5.000_000e-6 -assert "1.234_567e+2".parseFloat == 123.4567 -assert "1e1_00".parseFloat == "1e100".parseFloat -assert "3.1415926535897932384626433".parseFloat == +doAssert "0".parseFloat == 0.0 +doAssert "-.1".parseFloat == -0.1 +doAssert "2.5e1".parseFloat == 25.0 +doAssert "1e10".parseFloat == 10_000_000_000.0 +doAssert "0.000_005".parseFloat == 5.000_000e-6 +doAssert "1.234_567e+2".parseFloat == 123.4567 +doAssert "1e1_00".parseFloat == "1e100".parseFloat +doAssert "3.1415926535897932384626433".parseFloat == 3.1415926535897932384626433 -assert "2.71828182845904523536028747".parseFloat == +doAssert "2.71828182845904523536028747".parseFloat == 2.71828182845904523536028747 -assert 0.00097656250000000021684043449710088680149056017398834228515625 == +doAssert 0.00097656250000000021684043449710088680149056017398834228515625 == "0.00097656250000000021684043449710088680149056017398834228515625".parseFloat + +echo("passed all tests.") diff --git a/tests/float/tfloat5.nim b/tests/float/tfloat5.nim new file mode 100644 index 000000000..aa7dc6c53 --- /dev/null +++ b/tests/float/tfloat5.nim @@ -0,0 +1,15 @@ +discard """ + file: "tfloat5.nim" + output: '''0 : 0.0 +0 : 0.0 +0 : 0.0 +0 : 0.0''' +""" + +import parseutils + +var f: float +echo "*".parseFloat(f), " : ", f +echo "/".parseFloat(f), " : ", f +echo "+".parseFloat(f), " : ", f +echo "-".parseFloat(f), " : ", f diff --git a/tests/float/tfloat6.nim b/tests/float/tfloat6.nim new file mode 100644 index 000000000..721abd721 --- /dev/null +++ b/tests/float/tfloat6.nim @@ -0,0 +1,21 @@ +discard """ + file: "tfloat6.nim" + output: '''1e-06 : 1e-06 +1e-06 : 1e-06 +0.001 : 0.001 +1e-06 : 1e-06 +1e-06 : 1e-06 +10.000001 : 10.000001 +100.000001 : 100.000001''' +""" + +import strutils + +echo "0.00_0001".parseFloat(), " : ", 1E-6 +echo "0.00__00_01".parseFloat(), " : ", 1E-6 +echo "0.0_01".parseFloat(), " : ", 0.001 +echo "0.00_000_1".parseFloat(), " : ", 1E-6 +echo "0.00000_1".parseFloat(), " : ", 1E-6 + +echo "1_0.00_0001".parseFloat(), " : ", 10.000001 +echo "1__00.00_0001".parseFloat(), " : ", 1_00.000001 diff --git a/tests/float/tfloat7.nim b/tests/float/tfloat7.nim new file mode 100644 index 000000000..2337d1dd4 --- /dev/null +++ b/tests/float/tfloat7.nim @@ -0,0 +1,26 @@ +discard """ + file: "tfloat6.nim" + output: '''passed. +passed. +passed. +passed. +passed. +passed. +passed.''' +""" + +import strutils +template expect_fail(x: expr) = + try: + discard x + echo("expected to fail!") + except ValueError: + echo("passed.") + +expect_fail("1_0._00_0001".parseFloat()) +expect_fail("_1_0_00.0001".parseFloat()) +expect_fail("10.00.01".parseFloat()) +expect_fail("10.00E_01".parseFloat()) +expect_fail("10.00E_01".parseFloat()) +expect_fail("10.00E".parseFloat()) +expect_fail("10.00A".parseFloat()) diff --git a/tests/gc/thavlak.nim b/tests/gc/thavlak.nim new file mode 100644 index 000000000..efab49e36 --- /dev/null +++ b/tests/gc/thavlak.nim @@ -0,0 +1,457 @@ +discard """ + output: '''Welcome to LoopTesterApp, Nim edition +Constructing Simple CFG... +15000 dummy loops +Constructing CFG... +Performing Loop Recognition +1 Iteration +Another 50 iterations... +.................................................. +Found 1 loops (including artificial root node) (50)''' +""" + +# bug #3184 + +import tables +import sequtils +import sets + +type + BasicBlock = object + inEdges: seq[ref BasicBlock] + outEdges: seq[ref BasicBlock] + name: int + +proc newBasicBlock(name: int): ref BasicBlock = + new(result) + result.inEdges = newSeq[ref BasicBlock]() + result.outEdges = newSeq[ref BasicBlock]() + result.name = name + +proc hash(x: ref BasicBlock): int {.inline.} = + result = x.name + +type + BasicBlockEdge = object + fr: ref BasicBlock + to: ref BasicBlock + + Cfg = object + basicBlockMap: Table[int, ref BasicBlock] + edgeList: seq[BasicBlockEdge] + startNode: ref BasicBlock + +proc newCfg(): Cfg = + result.basicBlockMap = initTable[int, ref BasicBlock]() + result.edgeList = newSeq[BasicBlockEdge]() + +proc createNode(self: var Cfg, name: int): ref BasicBlock = + result = self.basicBlockMap.getOrDefault(name) + if result == nil: + result = newBasicBlock(name) + self.basicBlockMap.add name, result + + if self.startNode == nil: + self.startNode = result + +proc addEdge(self: var Cfg, edge: BasicBlockEdge) = + self.edgeList.add(edge) + +proc getNumNodes(self: Cfg): int = + self.basicBlockMap.len + +proc newBasicBlockEdge(cfg: var Cfg, fromName: int, toName: int): BasicBlockEdge = + result.fr = cfg.createNode(fromName) + result.to = cfg.createNode(toName) + result.fr.outEdges.add(result.to) + result.to.inEdges.add(result.fr) + cfg.addEdge(result) + +type + SimpleLoop = object + basicBlocks: seq[ref BasicBlock] # TODO: set here + children: seq[ref SimpleLoop] # TODO: set here + parent: ref SimpleLoop + header: ref BasicBlock + isRoot: bool + isReducible: bool + counter: int + nestingLevel: int + depthLevel: int + +proc newSimpleLoop(): ref SimpleLoop = + new(result) + result.basicBlocks = newSeq[ref BasicBlock]() + result.children = newSeq[ref SimpleLoop]() + result.parent = nil + result.header = nil + result.isRoot = false + result.isReducible = true + result.counter = 0 + result.nestingLevel = 0 + result.depthLevel = 0 + +proc addNode(self: ref SimpleLoop, bb: ref BasicBlock) = + self.basicBlocks.add bb + +proc addChildLoop(self: ref SimpleLoop, loop: ref SimpleLoop) = + self.children.add loop + +proc setParent(self: ref SimpleLoop, parent: ref SimpleLoop) = + self.parent = parent + self.parent.addChildLoop(self) + +proc setHeader(self: ref SimpleLoop, bb: ref BasicBlock) = + self.basicBlocks.add(bb) + self.header = bb + +proc setNestingLevel(self: ref SimpleLoop, level: int) = + self.nestingLevel = level + if level == 0: self.isRoot = true + +var loop_counter: int = 0 + +type + Lsg = object + loops: seq[ref SimpleLoop] + root: ref SimpleLoop + +proc createNewLoop(self: var Lsg): ref SimpleLoop = + result = newSimpleLoop() + loop_counter += 1 + result.counter = loop_counter + +proc addLoop(self: var Lsg, l: ref SimpleLoop) = + self.loops.add l + +proc newLsg(): Lsg = + result.loops = newSeq[ref SimpleLoop]() + result.root = result.createNewLoop() + result.root.setNestingLevel(0) + result.addLoop(result.root) + +proc getNumLoops(self: Lsg): int = + self.loops.len + +type + UnionFindNode = object + parent: ref UnionFindNode + bb: ref BasicBlock + l: ref SimpleLoop + dfsNumber: int + +proc newUnionFindNode(): ref UnionFindNode = + new(result) + when false: + result.parent = nil + result.bb = nil + result.l = nil + result.dfsNumber = 0 + +proc initNode(self: ref UnionFindNode, bb: ref BasicBlock, dfsNumber: int) = + self.parent = self + self.bb = bb + self.dfsNumber = dfsNumber + +proc findSet(self: ref UnionFindNode): ref UnionFindNode = + var nodeList = newSeq[ref UnionFindNode]() + result = self + + while result != result.parent: + var parent = result.parent + if parent != parent.parent: nodeList.add result + result = parent + + for iter in nodeList: iter.parent = result.parent + +proc union(self: ref UnionFindNode, unionFindNode: ref UnionFindNode) = + self.parent = unionFindNode + + +const + BB_TOP = 0 # uninitialized + BB_NONHEADER = 1 # a regular BB + BB_REDUCIBLE = 2 # reducible loop + BB_SELF = 3 # single BB loop + BB_IRREDUCIBLE = 4 # irreducible loop + BB_DEAD = 5 # a dead BB + BB_LAST = 6 # Sentinel + + # # Marker for uninitialized nodes. + UNVISITED = -1 + + # # Safeguard against pathologic algorithm behavior. + MAXNONBACKPREDS = (32 * 1024) + +type + HavlakLoopFinder = object + cfg: Cfg + lsg: Lsg + +proc newHavlakLoopFinder(cfg: Cfg, lsg: Lsg): HavlakLoopFinder = + result.cfg = cfg + result.lsg = lsg + +proc isAncestor(w: int, v: int, last: seq[int]): bool = + w <= v and v <= last[w] + +proc dfs(currentNode: ref BasicBlock, nodes: var seq[ref UnionFindNode], number: var Table[ref BasicBlock, int], last: var seq[int], current: int): int = + var stack = @[(currentNode, current)] + while stack.len > 0: + let (currentNode, current) = stack.pop() + nodes[current].initNode(currentNode, current) + number[currentNode] = current + + result = current + for target in currentNode.outEdges: + if number[target] == UNVISITED: + stack.add((target, result+1)) + #result = dfs(target, nodes, number, last, result + 1) + last[number[currentNode]] = result + +proc findLoops(self: var HavlakLoopFinder): int = + var startNode = self.cfg.startNode + if startNode == nil: return 0 + var size = self.cfg.getNumNodes + + var nonBackPreds = newSeq[HashSet[int]]() + var backPreds = newSeq[seq[int]]() + var number = initTable[ref BasicBlock, int]() + var header = newSeq[int](size) + var types = newSeq[int](size) + var last = newSeq[int](size) + var nodes = newSeq[ref UnionFindNode]() + + for i in 1..size: + nonBackPreds.add initSet[int](1) + backPreds.add newSeq[int]() + nodes.add newUnionFindNode() + + # Step a: + # - initialize all nodes as unvisited. + # - depth-first traversal and numbering. + # - unreached BB's are marked as dead. + # + for v in self.cfg.basicBlockMap.values: number[v] = UNVISITED + var res = dfs(startNode, nodes, number, last, 0) + + # Step b: + # - iterate over all nodes. + # + # A backedge comes from a descendant in the DFS tree, and non-backedges + # from non-descendants (following Tarjan). + # + # - check incoming edges 'v' and add them to either + # - the list of backedges (backPreds) or + # - the list of non-backedges (nonBackPreds) + # + for w in 0 .. <size: + header[w] = 0 + types[w] = BB_NONHEADER + + var nodeW = nodes[w].bb + if nodeW != nil: + for nodeV in nodeW.inEdges: + var v = number[nodeV] + if v != UNVISITED: + if isAncestor(w, v, last): + backPreds[w].add v + else: + nonBackPreds[w].incl v + else: + types[w] = BB_DEAD + + # Start node is root of all other loops. + header[0] = 0 + + # Step c: + # + # The outer loop, unchanged from Tarjan. It does nothing except + # for those nodes which are the destinations of backedges. + # For a header node w, we chase backward from the sources of the + # backedges adding nodes to the set P, representing the body of + # the loop headed by w. + # + # By running through the nodes in reverse of the DFST preorder, + # we ensure that inner loop headers will be processed before the + # headers for surrounding loops. + + for w in countdown(size - 1, 0): + # this is 'P' in Havlak's paper + var nodePool = newSeq[ref UnionFindNode]() + + var nodeW = nodes[w].bb + if nodeW != nil: # dead BB + # Step d: + for v in backPreds[w]: + if v != w: + nodePool.add nodes[v].findSet + else: + types[w] = BB_SELF + + # Copy nodePool to workList. + # + var workList = newSeq[ref UnionFindNode]() + for x in nodePool: workList.add x + + if nodePool.len != 0: types[w] = BB_REDUCIBLE + + # work the list... + # + while workList.len > 0: + var x = workList[0] + workList.del(0) + + # Step e: + # + # Step e represents the main difference from Tarjan's method. + # Chasing upwards from the sources of a node w's backedges. If + # there is a node y' that is not a descendant of w, w is marked + # the header of an irreducible loop, there is another entry + # into this loop that avoids w. + # + + # The algorithm has degenerated. Break and + # return in this case. + # + var nonBackSize = nonBackPreds[x.dfsNumber].len + if nonBackSize > MAXNONBACKPREDS: return 0 + + for iter in nonBackPreds[x.dfsNumber]: + var y = nodes[iter] + var ydash = y.findSet + + if not isAncestor(w, ydash.dfsNumber, last): + types[w] = BB_IRREDUCIBLE + nonBackPreds[w].incl ydash.dfsNumber + else: + if ydash.dfsNumber != w and not nodePool.contains(ydash): + workList.add ydash + nodePool.add ydash + + # Collapse/Unionize nodes in a SCC to a single node + # For every SCC found, create a loop descriptor and link it in. + # + if (nodePool.len > 0) or (types[w] == BB_SELF): + var l = self.lsg.createNewLoop + + l.setHeader(nodeW) + l.isReducible = types[w] != BB_IRREDUCIBLE + + # At this point, one can set attributes to the loop, such as: + # + # the bottom node: + # iter = backPreds(w).begin(); + # loop bottom is: nodes(iter).node; + # + # the number of backedges: + # backPreds(w).size() + # + # whether this loop is reducible: + # types(w) != BB_IRREDUCIBLE + # + nodes[w].l = l + + for node in nodePool: + # Add nodes to loop descriptor. + header[node.dfsNumber] = w + node.union(nodes[w]) + + # Nested loops are not added, but linked together. + var node_l = node.l + if node_l != nil: + node_l.setParent(l) + else: + l.addNode(node.bb) + + self.lsg.addLoop(l) + + result = self.lsg.getNumLoops + + +type + LoopTesterApp = object + cfg: Cfg + lsg: Lsg + +proc newLoopTesterApp(): LoopTesterApp = + result.cfg = newCfg() + result.lsg = newLsg() + +proc buildDiamond(self: var LoopTesterApp, start: int): int = + var bb0 = start + var x1 = newBasicBlockEdge(self.cfg, bb0, bb0 + 1) + var x2 = newBasicBlockEdge(self.cfg, bb0, bb0 + 2) + var x3 = newBasicBlockEdge(self.cfg, bb0 + 1, bb0 + 3) + var x4 = newBasicBlockEdge(self.cfg, bb0 + 2, bb0 + 3) + result = bb0 + 3 + +proc buildConnect(self: var LoopTesterApp, start1: int, end1: int) = + var x1 = newBasicBlockEdge(self.cfg, start1, end1) + +proc buildStraight(self: var LoopTesterApp, start: int, n: int): int = + for i in 0..n-1: + self.buildConnect(start + i, start + i + 1) + result = start + n + +proc buildBaseLoop(self: var LoopTesterApp, from1: int): int = + var header = self.buildStraight(from1, 1) + var diamond1 = self.buildDiamond(header) + var d11 = self.buildStraight(diamond1, 1) + var diamond2 = self.buildDiamond(d11) + var footer = self.buildStraight(diamond2, 1) + + self.buildConnect(diamond2, d11) + self.buildConnect(diamond1, header) + self.buildConnect(footer, from1) + result = self.buildStraight(footer, 1) + +proc run(self: var LoopTesterApp) = + echo "Welcome to LoopTesterApp, Nim edition" + echo "Constructing Simple CFG..." + + var x1 = self.cfg.createNode(0) + var x2 = self.buildBaseLoop(0) + var x3 = self.cfg.createNode(1) + self.buildConnect(0, 2) + + echo "15000 dummy loops" + + for i in 1..15000: + var h = newHavlakLoopFinder(self.cfg, newLsg()) + var res = h.findLoops + + echo "Constructing CFG..." + var n = 2 + + for parlooptrees in 1..10: + var x6 = self.cfg.createNode(n + 1) + self.buildConnect(2, n + 1) + n += 1 + for i in 1..100: + var top = n + n = self.buildStraight(n, 1) + for j in 1..25: n = self.buildBaseLoop(n) + var bottom = self.buildStraight(n, 1) + self.buildConnect n, top + n = bottom + self.buildConnect(n, 1) + + echo "Performing Loop Recognition\n1 Iteration" + + var h = newHavlakLoopFinder(self.cfg, newLsg()) + var loops = h.findLoops + + echo "Another 50 iterations..." + + var sum = 0 + for i in 1..50: + write stdout, "." + flushFile(stdout) + var hlf = newHavlakLoopFinder(self.cfg, newLsg()) + sum += hlf.findLoops + #echo getOccupiedMem() + echo "\nFound ", loops, " loops (including artificial root node) (", sum, ")" + +var l = newLoopTesterApp() +l.run diff --git a/tests/gc/tlists.nim b/tests/gc/tlists.nim new file mode 100644 index 000000000..26b32396c --- /dev/null +++ b/tests/gc/tlists.nim @@ -0,0 +1,37 @@ +discard """ + output: '''Success''' +""" + +# bug #3793 + +import os +import math +import lists +import strutils + +proc mkleak() = + # allocate 10 MB via linked lists + let numberOfLists = 100 + for i in countUp(1, numberOfLists): + var leakList = initDoublyLinkedList[string]() + let numberOfLeaks = 50000 + for j in countUp(1, numberOfLeaks): + let leakSize = 200 + let leaked = newString(leakSize) + leakList.append(leaked) + +proc mkManyLeaks() = + for i in 0..0: + when false: echo getOccupiedMem() + mkleak() + when false: echo getOccupiedMem() + # Force a full collection. This should free all of the + # lists and bring the memory usage down to a few MB's. + GC_fullCollect() + when false: echo getOccupiedMem() + if getOccupiedMem() > 8 * 200 * 50_000 * 2: + echo GC_getStatistics() + quit "leaking" + echo "Success" + +mkManyLeaks() diff --git a/tests/generics/tgenerictmpl2.nim b/tests/generics/tgenerictmpl2.nim new file mode 100644 index 000000000..0ecaf9ded --- /dev/null +++ b/tests/generics/tgenerictmpl2.nim @@ -0,0 +1,31 @@ +discard """ + output: '''1 +1 +1 +1 +999 +999 +999 +2''' +""" + +# test if we can pass explicit generic arguments to generic templates +# based on bug report #3496 + +proc tproc[T](t: T = 999) = echo t +template ttmpl[T](t: T = 999) = echo t + +tproc(1) +tproc[int](1) +ttmpl(1) +ttmpl[int](1) #<- crash case #1 + +tproc[int]() +discard tproc[int] +ttmpl[int]() #<- crash case #2 +ttmpl[int] #<- crash case #3 + +# but still allow normal use of [] on non-generic templates + +template tarr: expr = [1, 2, 3, 4] +echo tarr[1] diff --git a/tests/js/tclosures.nim b/tests/js/tclosures.nim index c0d93814c..0ec4f4743 100644 --- a/tests/js/tclosures.nim +++ b/tests/js/tclosures.nim @@ -2,7 +2,7 @@ discard """ action: run """ -import math, strutils +import math, random, strutils const consolePrefix = "jsCallbacks" asm """ diff --git a/tests/js/test2.nim b/tests/js/test2.nim index f6976d058..0f460d6f8 100644 --- a/tests/js/test2.nim +++ b/tests/js/test2.nim @@ -1,7 +1,8 @@ discard """ output: '''foo js 3.14 -7''' +7 +1''' """ # This file tests the JavaScript generator @@ -29,3 +30,13 @@ proc test(x: C, T: typedesc): T = cast[T](x) echo 7.test(int8) + +# #4222 +const someConst = [ "1"] + +proc procThatRefersToConst() # Forward decl +procThatRefersToConst() # Call bar before it is defined + +proc procThatRefersToConst() = + var i = 0 # Use a var index, otherwise nim will constfold foo[0] + echo someConst[i] # JS exception here: foo is still not initialized (undefined) diff --git a/tests/js/testtojsstr.nim b/tests/js/testtojsstr.nim new file mode 100644 index 000000000..03ac89e20 --- /dev/null +++ b/tests/js/testtojsstr.nim @@ -0,0 +1,8 @@ +discard """ + output = "И\n" +""" + +let s: string = "И\n" +let cs = s.cstring + +echo $s diff --git a/tests/lexer/tstrlits.nim b/tests/lexer/tstrlits.nim index f5b7ce937..cc8872f60 100644 --- a/tests/lexer/tstrlits.nim +++ b/tests/lexer/tstrlits.nim @@ -1,6 +1,6 @@ discard """ file: "tstrlits.nim" - output: "a\"\"long string\"\"\"\"\"abc\"def" + output: "a\"\"long string\"\"\"\"\"abc\"def_'2'●" """ # Test the new different string literals @@ -11,9 +11,13 @@ const raw = r"abc""def" + escaped = "\x5f'\50'\u25cf" + + stdout.write(rawQuote) stdout.write(tripleEmpty) stdout.write(raw) +stdout.write(escaped) #OUT a""long string"""""abc"def diff --git a/tests/macros/tgettypeinst.nim b/tests/macros/tgettypeinst.nim new file mode 100644 index 000000000..22e03a119 --- /dev/null +++ b/tests/macros/tgettypeinst.nim @@ -0,0 +1,122 @@ +discard """ +""" + +import macros, strUtils + +proc symToIdent(x: NimNode): NimNode = + case x.kind: + of nnkCharLit..nnkUInt64Lit: + result = newNimNode(x.kind) + result.intVal = x.intVal + of nnkFloatLit..nnkFloat64Lit: + result = newNimNode(x.kind) + result.floatVal = x.floatVal + of nnkStrLit..nnkTripleStrLit: + result = newNimNode(x.kind) + result.strVal = x.strVal + of nnkIdent, nnkSym: + result = newIdentNode($x) + else: + result = newNimNode(x.kind) + for c in x: + result.add symToIdent(c) + +macro testX(x,inst0: typed; recurse: static[bool]; implX: stmt): typed = + let inst = x.getTypeInst + let impl = x.getTypeImpl + let inst0r = inst0.symToIdent.treeRepr + let instr = inst.symToIdent.treeRepr + #echo inst0r + #echo instr + doAssert(instr == inst0r) + var impl0 = + if implX.kind == nnkNilLit: inst0 + else: implX[0][2] + let impl0r = impl0.symToIdent.treerepr + let implr = impl.symToIdent.treerepr + #echo impl0r + #echo implr + doAssert(implr == impl0r) + template echoString(s:string) = echo s.replace("\n","\n ") + result = newStmtList() + #result.add getAst(echoString(" " & inst0.repr)) + #result.add getAst(echoString(" " & inst.repr)) + #result.add getAst(echoString(" " & impl0.repr)) + #result.add getAst(echoString(" " & impl.repr)) + if recurse: + template testDecl(n, m :typed) = + testV(n, false): + type _ = m + result.add getAst(testDecl(inst.symToIdent, impl.symToIdent)) + +template testV(inst, recurse, impl) = + block: + #echo "testV(" & astToStr(inst) & ", " & $recurse & "):" & astToStr(impl) + var x: inst + testX(x, inst, recurse, impl) +template testT(inst, recurse) = + block: + type myType = inst + testV(myType, recurse): + type _ = inst + +template test(inst) = + testT(inst, false) + testV(inst, true, nil) +template test(inst, impl) = testV(inst, true, impl) + +type + Model = object of RootObj + User = object of Model + name : string + password : string + + Tree = object of RootObj + value : int + left,right : ref Tree + + MyEnum = enum + valueA, valueB, valueC + + MySet = set[MyEnum] + MySeq = seq[int] + MyIntPtr = ptr int + MyIntRef = ref int + + GenericObject[T] = object + value:T + Foo[N:static[int],T] = object + Bar[N:static[int],T] = object + #baz:Foo[N+1,GenericObject[T]] + baz:Foo[N,GenericObject[T]] + +test(bool) +test(char) +test(int) +test(float) +test(ptr int) +test(ref int) +test(array[1..10,Bar[2,Foo[3,float]]]) +test(distinct Bar[2,Foo[3,float]]) +test(tuple[a:int,b:Foo[-1,float]]) +#test(MyEnum): +# type _ = enum +# valueA, valueB, valueC +test(set[MyEnum]) +test(seq[int]) +test(Bar[2,Foo[3,float]]): + type _ = object + baz: Foo[2, GenericObject[Foo[3, float]]] +test(Model): + type _ = object of RootObj +test(User): + type _ = object of Model + name: string + password: string +test(Tree): + type _ = object of RootObj + value: int + left: ref Tree + right: ref Tree +test(proc (a: int, b: Foo[2,float])) +test(proc (a: int, b: Foo[2,float]): Bar[3,int]) diff --git a/tests/manyloc/keineschweine/lib/vehicles.nim b/tests/manyloc/keineschweine/lib/vehicles.nim index 94ebf9f57..ddfb43b38 100644 --- a/tests/manyloc/keineschweine/lib/vehicles.nim +++ b/tests/manyloc/keineschweine/lib/vehicles.nim @@ -1,6 +1,6 @@ import sfml, chipmunk, - sg_assets, sfml_stuff, keineschweine + sg_assets, sfml_stuff, "../keineschweine" proc accel*(obj: PVehicle, dt: float) = diff --git a/tests/manyloc/nake/nakefile.nim b/tests/manyloc/nake/nakefile.nim index e91b86986..2055d7834 100644 --- a/tests/manyloc/nake/nakefile.nim +++ b/tests/manyloc/nake/nakefile.nim @@ -1,5 +1,5 @@ import nake -import httpclient, zip/zipfiles, times, math, sequtils +import httpclient, zip/zipfiles, times, random, sequtils nakeImports randomize() @@ -145,7 +145,7 @@ task "download", "download game assets": echo "Extracted the libs dir. Copy the ones you need to this dir." task "zip-lib", "zip up the libs dir": - var z: TZipArchive + var z: ZipArchive if not z.open("libs-" & getDateStr() & ".zip", fmReadWrite): quit "Could not open zip" for file in walkDirRec("libs", {pcFile, pcDir}): diff --git a/tests/manyloc/named_argument_bug/main.nim.cfg b/tests/manyloc/named_argument_bug/main.nim.cfg index 27cf8e688..7df7a0e97 100644 --- a/tests/manyloc/named_argument_bug/main.nim.cfg +++ b/tests/manyloc/named_argument_bug/main.nim.cfg @@ -1,2 +1,3 @@ # this file only exists to mark 'main.nim' as the main file +--path:"$projectpath" diff --git a/tests/manyloc/named_argument_bug/tri_engine/math/circle.nim b/tests/manyloc/named_argument_bug/tri_engine/math/circle.nim index 7e7517998..b95cfa379 100644 --- a/tests/manyloc/named_argument_bug/tri_engine/math/circle.nim +++ b/tests/manyloc/named_argument_bug/tri_engine/math/circle.nim @@ -1,6 +1,6 @@ import - tri_engine/config, - tri_engine/math/vec + ../config, + vec type TCircle* = tuple[p: TV2[TR], r: TR] diff --git a/tests/manyloc/named_argument_bug/tri_engine/math/vec.nim b/tests/manyloc/named_argument_bug/tri_engine/math/vec.nim index 3b57acb8e..926958fe4 100644 --- a/tests/manyloc/named_argument_bug/tri_engine/math/vec.nim +++ b/tests/manyloc/named_argument_bug/tri_engine/math/vec.nim @@ -1,6 +1,6 @@ import macros, - tri_engine/config + "../config" type TV2*[T:SomeNumber=TR] = array[0..1, T] diff --git a/tests/overload/tstmtoverload.nim b/tests/overload/tstmtoverload.nim index f1944b637..75584bcab 100644 --- a/tests/overload/tstmtoverload.nim +++ b/tests/overload/tstmtoverload.nim @@ -10,7 +10,7 @@ template test(loopCount: int, extraI: int, testBody: stmt): stmt = template test(loopCount: int, extraF: float, testBody: stmt): stmt = block: - test(loopCount, round(extraF), testBody) + test(loopCount, round(extraF).int, testBody) template test(loopCount: int, testBody: stmt): stmt = block: diff --git a/tests/parallel/twrong_refcounts.nim b/tests/parallel/twrong_refcounts.nim index db32a96d8..57e0588a0 100644 --- a/tests/parallel/twrong_refcounts.nim +++ b/tests/parallel/twrong_refcounts.nim @@ -2,7 +2,7 @@ discard """ output: "Success" """ -import math, threadPool +import math, random, threadPool # --- diff --git a/tests/parser/tdo.nim b/tests/parser/tdo.nim new file mode 100644 index 000000000..7bd1f7411 --- /dev/null +++ b/tests/parser/tdo.nim @@ -0,0 +1,79 @@ +discard """ + output: '''true +true +true +true inner B''' +""" + +template withValue(a, b, c, d, e: untyped) = + if c: + d + else: + e + +template withValue(a, b, c, d: untyped) = + if c: + d + +const + EVENT_READ = 1 + EVENT_WRITE = 2 + FLAG_HANDLE = 3 + EVENT_MASK = 3 + +var s: string + +proc main = + var value = false + var fd = 8888 + var event = 0 + s.withValue(fd, value) do: + if value: + var oe = (EVENT_MASK) + if (oe xor event) != 0: + if (oe and EVENT_READ) != 0 and (event and EVENT_READ) == 0: + discard + if (oe and EVENT_WRITE) != 0 and (event and EVENT_WRITE) == 0: + discard + if (oe and EVENT_READ) == 0 and (event and EVENT_READ) != 0: + discard + if (oe and EVENT_WRITE) == 0 and (event and EVENT_WRITE) != 0: + discard + else: + raise newException(ValueError, "error") + do: + raise newException(ValueError, "Descriptor is not registered in queue") + +proc main2 = + var unused = 8 + # test 'then' branch: + s.withValue(unused, true) do: + echo "true" + do: + echo "false" + + # test overloading: + s.withValue(unused, false) do: + echo "cannot come here" + + # test 'else' branch: + s.withValue(unused, false) do: + echo "false" + do: + echo "true" + + # test proper nesting: + s.withValue(unused, false) do: + echo "false" + s.withValue(unused, false) do: + echo "false inner A" + do: + echo "true inner A" + do: + echo "true" + s.withValue(unused, false) do: + echo "false inner B" + do: + echo "true inner B" + +main2() diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim index 1ac9c8092..538582ba8 100644 --- a/tests/stdlib/tmath.nim +++ b/tests/stdlib/tmath.nim @@ -1,4 +1,4 @@ -import math +import math, random import unittest import sets diff --git a/tests/stdlib/tparscfg.nim b/tests/stdlib/tparscfg.nim index 4c11ccf61..7022d071b 100644 --- a/tests/stdlib/tparscfg.nim +++ b/tests/stdlib/tparscfg.nim @@ -1,25 +1,37 @@ +import parsecfg -import - os, parsecfg, strutils, streams +## Creating a configuration file. +var dict1=newConfig() +dict1.setSectionKey("","charset","utf-8") +dict1.setSectionKey("Package","name","hello") +dict1.setSectionKey("Package","--threads","on") +dict1.setSectionKey("Author","name","lihf8515") +dict1.setSectionKey("Author","qq","10214028") +dict1.setSectionKey("Author","email","lihaifeng@wxm.com") +dict1.writeConfig("config.ini") + +## Reading a configuration file. +var dict2 = loadConfig("config.ini") +var charset = dict2.getSectionValue("","charset") +var threads = dict2.getSectionValue("Package","--threads") +var pname = dict2.getSectionValue("Package","name") +var name = dict2.getSectionValue("Author","name") +var qq = dict2.getSectionValue("Author","qq") +var email = dict2.getSectionValue("Author","email") +echo charset +echo threads +echo pname +echo name +echo qq +echo email + +## Modifying a configuration file. +var dict3 = loadConfig("config.ini") +dict3.setSectionKey("Author","name","lhf") +dict3.writeConfig("config.ini") + +## Deleting a section key in a configuration file. +var dict4 = loadConfig("config.ini") +dict4.delSectionKey("Author","email") +dict4.writeConfig("config.ini") -var f = newFileStream(paramStr(1), fmRead) -if f != nil: - var p: TCfgParser - open(p, f, paramStr(1)) - while true: - var e = next(p) - case e.kind - of cfgEof: - echo("EOF!") - break - of cfgSectionStart: ## a ``[section]`` has been parsed - echo("new section: " & e.section) - of cfgKeyValuePair: - echo("key-value-pair: " & e.key & ": " & e.value) - of cfgOption: - echo("command: " & e.key & ": " & e.value) - of cfgError: - echo(e.msg) - close(p) -else: - echo("cannot open: " & paramStr(1)) diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index 4b210c23b..73113ac68 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -26,7 +26,7 @@ test "unittest multiple requires": require(true) -import math +import math, random from strutils import parseInt proc defectiveRobot() = randomize() diff --git a/tests/template/t2do.nim b/tests/template/t2do.nim index b87e3328c..ec364c5f3 100644 --- a/tests/template/t2do.nim +++ b/tests/template/t2do.nim @@ -15,8 +15,9 @@ template toFloatHelper(result: expr; tooSmall, tooLarge: stmt) {.immediate.} = tooLarge proc toFloat*(a: int): float = - toFloatHelper(result) - do: raise newException(ValueError, "number too small"): - raise newException(ValueError, "number too large") + toFloatHelper(result) do: + raise newException(ValueError, "number too small") + do: + raise newException(ValueError, "number too large") echo toFloat(8) diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index 150c55edc..125643d5a 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -152,6 +152,9 @@ proc gcTests(r: var TResults, cat: Category, options: string) = testWithoutBoehm "closureleak" testWithoutMs "refarrayleak" + testWithoutBoehm "tlists" + testWithoutBoehm "thavlak" + test "stackrefleak" test "cyclecollector" @@ -223,7 +226,7 @@ proc jsTests(r: var TResults, cat: Category, options: string) = "varres/tvartup", "misc/tints", "misc/tunsignedinc"]: test "tests/" & testfile & ".nim" - for testfile in ["pure/strutils", "pure/json"]: + for testfile in ["pure/strutils", "pure/json", "pure/random", "pure/times"]: test "lib/" & testfile & ".nim" # ------------------------- manyloc ------------------------------------------- diff --git a/tests/threads/ttryrecv.nim b/tests/threads/ttryrecv.nim index be79fadae..4a98e6c27 100644 --- a/tests/threads/ttryrecv.nim +++ b/tests/threads/ttryrecv.nim @@ -4,7 +4,7 @@ discard """ # bug #1816 -from math import random +from random import random from os import sleep type PComm = ptr Channel[int] diff --git a/tests/trmacros/tstatic_t_bug.nim b/tests/trmacros/tstatic_t_bug.nim new file mode 100644 index 000000000..cdfa53514 --- /dev/null +++ b/tests/trmacros/tstatic_t_bug.nim @@ -0,0 +1,24 @@ +discard """ + output: "optimized" +""" +# bug #4227 +type Vector64[N: static[int]] = array[N, int] + +proc `*`*[N: static[int]](a: Vector64[N]; b: float64): Vector64[N] = + result = a + +proc `+=`*[N: static[int]](a: var Vector64[N]; b: Vector64[N]) = + echo "regular" + +proc linearCombinationMut[N: static[int]](a: float64, v: var Vector64[N], w: Vector64[N]) {. inline .} = + echo "optimized" + +template rewriteLinearCombinationMut*{v += `*`(w, a)}(a: float64, v: var Vector64, w: Vector64): auto = + linearCombinationMut(a, v, w) + +proc main() = + const scaleVal = 9.0 + var a, b: Vector64[7] + a += b * scaleval + +main() diff --git a/tests/usingstmt/tthis.nim b/tests/usingstmt/tthis.nim new file mode 100644 index 000000000..83d75d08c --- /dev/null +++ b/tests/usingstmt/tthis.nim @@ -0,0 +1,15 @@ + +# bug #4177 + +type + Parent = object of RootObj + parentField: int + Child = object of Parent + childField: int + +{.this: self.} +proc sumFields(self: Child): int = + result = parentField + childField # Error: undeclared identifier: 'parentField' + +proc sumFieldsWorks(self: Child): int = + result = self.parentField + childField diff --git a/tests/vm/meta.nim b/tests/vm/meta.nim new file mode 100644 index 000000000..2aa01b5b3 --- /dev/null +++ b/tests/vm/meta.nim @@ -0,0 +1,240 @@ +# +# meta.nim +# + +import tables +import macros + +type + NodeSeq* = seq[NimNode] + Ident* = tuple[name: string, exported: bool] + Bracket* = seq[Ident] + Field* = tuple[identifier: Ident, type_name: string, default: string] + FieldSeq* = seq[Field] + TypeDef* = object + identifier*: Ident + fields*: FieldSeq + is_ref*: bool + object_type*: string + base_type*: string + TypeDefSeq* = seq[TypeDef] + Proc* = tuple[identifier: Ident, params: FieldSeq, + returns: Ident, generics: FieldSeq, body: NimNode] + ProcSeq* = seq[Proc] + +# Ident procs +proc newIdent*(name: string, exported = false): Ident = + result.name = name + result.exported = exported + +proc newIdent*(node: NimNode): Ident = + case node.kind: + of nnkPostfix: + result = newIdent(node[1]) + result.exported = true + of nnkIdent, nnkSym: + result.name = $(node) + else: + let msg = "newIdent cannot initialize from node kind: " & $(node.kind) + raise newException(ValueError, msg) + +proc render*(i: Ident): NimNode {.compileTime.} = + if i.name == nil: + return newNimNode(nnkEmpty) + + if i.exported: + result = newNimNode(nnkPostfix) + result.add(ident "*") + result.add(ident i.name) + else: + result = ident i.name + +proc `$`*(identifier: Ident): string = identifier.name + +converter toString*(x: Ident): string = x.name + +proc newBracket*(node: NimNode): Bracket = + result = @[] + case node.kind: + of nnkBracket: + for child in node: + if child.kind != nnkIdent: + let msg = "Bracket members can only be nnkIdent not kind: " & $(node.kind) + raise newException(ValueError, msg) + result.add(newIdent(child)) + else: + let msg = "newBracket must initialize from node kind nnkBracket not: " & $(node.kind) + raise newException(ValueError, msg) + +# Field procs +proc newField*(identifier: Ident, type_name: string, default: string = nil): Field = + result.identifier = identifier + result.type_name = type_name + result.default = default + +proc newField*(node: NimNode): Field = + case node.kind: + of nnkIdentDefs: + if node.len > 3: + let msg = "newField cannot initialize from nnkIdentDefs with multiple names" + raise newException(ValueError, msg) + result.identifier = newIdent(node[0]) + result.type_name = $(node[1]) + case node[2].kind: + of nnkIdent: + result.default = $(node[2]) + else: + result.default = nil + else: + let msg = "newField cannot initialize from node kind: " & $(node.kind) + raise newException(ValueError, msg) + +# FieldSeq procs +proc newFieldSeq*(node: NimNode): FieldSeq = + result = @[] + case node.kind: + of nnkIdentDefs: + let + type_name = $(node[node.len - 2]) + default_node = node[node.len - 1] + var default: string + case default_node.kind: + of nnkIdent: + default = $(default_node) + else: + default = nil + for i in 0..node.len - 3: + let name = newIdent(node[i]) + result.add(newField(name, type_name, default)) + of nnkRecList, nnkVarSection, nnkGenericParams: + for child in node: + result = result & newFieldSeq(child) + else: + let msg = "newFieldSeq cannot initialize from node kind: " & $(node.kind) + raise newException(ValueError, msg) + +proc render*(f: Field): NimNode {.compileTime.} = + let identifier = f.identifier.render() + let type_name = if f.type_name != nil: ident(f.type_name) else: newEmptyNode() + let default = if f.default != nil: ident(f.default) else: newEmptyNode() + newIdentDefs(identifier, type_name, default) + +proc render*(fs: FieldSeq): NimNode {.compileTime.} = + result = newNimNode(nnkRecList) + for field in fs: + result.add(field.render()) + +# TypeDef procs +proc newTypeDef*(identifier: Ident, is_ref = false, + object_type = "object", + base_type: string = nil): TypeDef {.compileTime.} = + result.identifier = identifier + result.fields = @[] + result.is_ref = is_ref + result.object_type = "object" + result.base_type = base_type + +proc newTypeDef*(node: NimNode): TypeDef {.compileTime.} = + case node.kind: + of nnkTypeDef: + result.identifier = newIdent($(node[0])) + var object_node: NimNode + case node[2].kind: + of nnkRefTy: + object_node = node[2][0] + result.is_ref = true + of nnkObjectTy: + object_node = node[2] + result.is_ref = false + else: + let msg = "newTypeDef could not parse RefTy/ObjectTy, found: " & $(node[2].kind) + raise newException(ValueError, msg) + case object_node[1].kind: + of nnkOfInherit: + result.base_type = $(object_node[1][0]) + else: + result.base_type = "object" + result.fields = newFieldSeq(object_node[2]) + else: + let msg = "newTypeDef cannot initialize from node kind: " & $(node.kind) + raise newException(ValueError, msg) + +proc render*(typedef: TypeDef): NimNode {.compileTime.} = + result = newNimNode(nnkTypeDef) + result.add(typedef.identifier.render) + result.add(newEmptyNode()) + let object_node = newNimNode(nnkObjectTy) + object_node.add(newEmptyNode()) + if typedef.base_type == nil: + object_node.add(newEmptyNode()) + else: + var base_type = newNimNode(nnkOfInherit) + base_type.add(ident(typedef.base_type)) + object_node.add(base_type) + let fields = typedef.fields.render() + object_node.add(fields) + if typedef.is_ref: + let ref_node = newNimNode(nnkRefTy) + ref_node.add(object_node) + result.add(ref_node) + else: + result.add(object_node) + +proc newTypeDefSeq*(node: NimNode): TypeDefSeq = + result = @[] + case node.kind: + of nnkTypeSection: + for child in node: + result.add(newTypeDef(child)) + else: + let msg = "newTypeSection could not parse TypeDef, found: " & $(node.kind) + raise newException(ValueError, msg) + +proc render*(typeseq: TypeDefSeq): NimNode {.compileTime.} = + result = newNimNode(nnkTypeSection) + for typedef in typeseq: + result.add(typedef.render()) + +proc newProc*(identifier: Ident, params: FieldSeq = nil, + returns: Ident, generics: FieldSeq = nil): Proc = + result.identifier = identifier + result.params = params + result.returns = returns + result.generics = generics + +proc newProc*(node: NimNode): Proc = + case node.kind: + of nnkProcDef, nnkMethodDef: + result.identifier = newIdent(node[0]) + case node[2].kind: + of nnkGenericParams: + result.generics = newFieldSeq(node[2]) + else: result.generics = nil + let formal_params = node[3] + case formal_params[0].kind: + of nnkIdent: + result.returns = newIdent(formal_params[0]) + else: discard + result.params = @[] + for i in 1..formal_params.len - 1: + let param = formal_params[i] + for field in newFieldSeq(param): + result.params.add(field) + result.body = node[6] + else: + let msg = "newProc cannot initialize from node kind: " & $(node.kind) + raise newException(ValueError, msg) + +proc render*(procdef: Proc): NimNode {.compileTime.} = + result = newNimNode(nnkProcDef) + result.add(procdef.identifier.render()) + result.add(newEmptyNode()) + result.add(newEmptyNode()) + let formal_params = newNimNode(nnkFormalParams) + formal_params.add(procdef.returns.render()) + for param in procdef.params: + formal_params.add(param.render()) + result.add(formal_params) + result.add(newEmptyNode()) + result.add(newEmptyNode()) + result.add(procdef.body) diff --git a/tests/vm/tcomponent.nim b/tests/vm/tcomponent.nim new file mode 100644 index 000000000..efeba2a6d --- /dev/null +++ b/tests/vm/tcomponent.nim @@ -0,0 +1,132 @@ +discard """ + output: '''`:)` @ 0,0 +FOO: blah''' +""" + +# +# magic.nim +# + +# bug #3729 + +import macros, sequtils, tables +import strutils +import future, meta + +type + Component = object + fields: FieldSeq + field_index: seq[string] + procs: ProcSeq + procs_index: seq[string] + + Registry = object + field_index: seq[string] + procs_index: seq[string] + components: Table[string, Component] + builtin: Component + +proc newRegistry(): Registry = + result.field_index = @[] + result.procs_index = @[] + result.components = initTable[string, Component]() + +var registry {.compileTime.} = newRegistry() + +proc validateComponent(r: var Registry, name: string, c: Component) = + if r.components.hasKey(name): + let msg = "`component` macro cannot consume duplicated identifier: " & name + raise newException(ValueError, msg) + + for field_name in c.field_index: + if r.field_index.contains(field_name): + let msg = "`component` macro cannot delcare duplicated field: " & field_name + raise newException(ValueError, msg) + r.field_index.add(field_name) + + for proc_name in c.procs_index: + if r.procs_index.contains(proc_name): + let msg = "`component` macro cannot delcare duplicated proc: " & proc_name + raise newException(ValueError, msg) + r.procs_index.add(proc_name) + +proc addComponent(r: var Registry, name: string, c: Component) = + r.validateComponent(name, c) + r.components.add(name, c) + +proc parse_component(body: NimNode): Component = + result.field_index = @[] + result.procs_index = @[] + for node in body: + case node.kind: + of nnkVarSection: + result.fields = newFieldSeq(node) + for field in result.fields: + result.field_index.add(field.identifier.name) + of nnkMethodDef, nnkProcDef: + let new_proc = meta.newProc(node) + result.procs = result.procs & @[new_proc] + for procdef in result.procs: + result.procs_index.add(procdef.identifier.name) + else: discard + +macro component*(name: expr, body: stmt): stmt {.immediate.} = + let component = parse_component(body) + registry.addComponent($name, component) + parseStmt("discard") + +macro component_builtins(body: stmt): stmt {.immediate.} = + let builtin = parse_component(body) + registry.field_index = builtin.field_index + registry.procs_index = builtin.procs_index + registry.builtin = builtin + +proc bind_methods*(component: var Component, identifier: Ident): seq[NimNode] = + result = @[] + for procdef in component.procs.mitems: + let this_field = newField(newIdent("this"), identifier) + procdef.params.insert(this_field, 0) + result.add(procdef.render()) + +macro bind_components*(type_name, component_names: expr): stmt {.immediate.} = + result = newStmtList() + let identifier = newIdent(type_name) + let components = newBracket(component_names) + var entity_type = newTypeDef(identifier, true, "object", "RootObj") + entity_type.fields = registry.builtin.fields + for component_name, component in registry.components: + if components.contains(newIdent(component_name)): + entity_type.fields = entity_type.fields & component.fields + # TODO why doesn't the following snippet work instead of the one above? + # for name in components: + # echo "Registering $1 to $2" % [name.name, identifier.name] + # let component = registry.components[name.name] + # entity_type.fields = entity_type.fields & component.fields + let type_section: TypeDefSeq = @[entity_type] + result.add type_section.render + var builtin = registry.builtin + let builtin_methods = bind_methods(builtin, identifier) + for builtin_proc in builtin_methods: + result.add(builtin_proc) + echo "SIGSEV here" + for component in registry.components.mvalues(): + for method_proc in bind_methods(component, identifier): + result.add(method_proc) + +component_builtins: + proc foo(msg: string) = + echo "FOO: $1" % msg + +component position: + var x*, y*: int + +component name: + var name*: string + proc render*(x, y: int) = echo "`$1` @ $2,$3" % [this.name, $x, $y] + +bind_components(Entity, [position, name]) + +var e = new(Entity) +e.name = ":)" +e.render(e.x, e.y) +e.foo("blah") diff --git a/todo.txt b/todo.txt index a6312468a..542f096fb 100644 --- a/todo.txt +++ b/todo.txt @@ -2,7 +2,7 @@ nim c --gc:v2 -r -d:useSysAssert -d:useGcAssert -d:smokeCycles -d:useRealtimeGc tests/gc/gctest - document ``this`` pragma -- document and stress test ``.partial`` object declarations +- https://github.com/nim-lang/Nim/issues/3898 essential for 1.0 ================= @@ -24,6 +24,7 @@ essential for 1.0 Not critical for 1.0 ==================== +- document and stress test ``.partial`` object declarations - add "all threads are blocked" detection to 'spawn' - figure out why C++ bootstrapping is so much slower - The bitwise 'not' operator cold be renamed to 'bnot' to @@ -62,7 +63,6 @@ Bugs GC == -- hybrid GC - use big blocks in the allocator - provide tool/API to track leaks/object counts - resizing of strings/sequences could take into account the memory that diff --git a/web/news.txt b/web/news.txt index b6ce533c8..12f08e5eb 100644 --- a/web/news.txt +++ b/web/news.txt @@ -2,7 +2,7 @@ News ==== -2016-XX-XX Version 0.13.1 released +2016-XX-XX Version 0.14.0 released ================================== Changes affecting backwards compatibility @@ -37,6 +37,24 @@ Changes affecting backwards compatibility by the language. With this change, ``alloc`` and ``dealloc`` are no longer aliases for ``malloc`` and ``free`` - use ``c_malloc`` and ``c_free`` if you need that. +- The ``json.%`` operator is now overloaded for ``object``, ``ref object`` and + ``openarray[T]``. +- The procs related to ``random`` number generation in ``math.nim`` have + been moved to its own ``random`` module and been reimplemented in pure + Nim. +- The path handling changed. The project directory is not added to the + search path automatically anymore. Add this line to your project's + config to get back the old behaviour: ``--path:"$projectdir"``. +- The ``round`` function in ``math.nim`` now returns a float and has been + corrected such that the C implementation always rounds up from .5 rather + than changing the operation for even and odd numbers. +- The ``round`` function now accepts a ``places`` argument to round to a + given number of places (e.g. round 4.35 to 4.4 if ``places`` is 1). +- In ``strutils.nim``, ``formatSize`` now returns a number representing the + size in conventional decimal format (e.g. 2.234GB meaning 2.234 GB rather + than meaning 2.285 GB as in the previous implementation). By default it + also uses IEC prefixes (KiB, MiB) etc and optionally uses colloquial names + (kB, MB etc) and the (SI-preferred) space. Library Additions @@ -48,6 +66,12 @@ Library Additions - Added ``strscans`` module that implements a ``scanf`` for easy input extraction. - Added a version of ``parseutils.parseUntil`` that can deal with a string ``until`` token. The other versions are for ``char`` and ``set[char]``. +- Added ``splitDecimal`` to ``math.nim`` to split a floating point value + into an integer part and a floating part (in the range -1<x<1). +- Added ``trimZeros`` to ```strutils.nim`` to trim trailing zeros in a + floating point number. +- Added ``formatEng`` to ``strutils.nim`` to format numbers using engineering + notation. Compiler Additions @@ -62,11 +86,13 @@ Language Additions - Nim now supports a ``.this`` pragma for more notational convenience. - Nim now supports a different ``using`` statement for more convenience. -- Nim now supports ``partial`` object declarations to mitigate the problems - that arise when types are mutually dependent and yet should be kept in - different modules. - ``include`` statements are not restricted to top level statements anymore. +.. + - Nim now supports ``partial`` object declarations to mitigate the problems + that arise when types are mutually dependent and yet should be kept in + different modules. + 2016-01-27 Nim in Action is now available! ========================================== diff --git a/web/website.ini b/web/website.ini index 5b3382820..58207a1c0 100644 --- a/web/website.ini +++ b/web/website.ini @@ -39,7 +39,7 @@ srcdoc2: "impure/re;impure/nre;pure/typetraits" srcdoc2: "pure/concurrency/threadpool.nim;pure/concurrency/cpuinfo.nim" srcdoc: "system/threads.nim;system/channels.nim;js/dom" srcdoc2: "pure/os;pure/strutils;pure/math;pure/matchers;pure/algorithm" -srcdoc2: "pure/stats;impure/nre;windows/winlean" +srcdoc2: "pure/stats;impure/nre;windows/winlean;pure/random" srcdoc2: "pure/complex;pure/times;pure/osproc;pure/pegs;pure/dynlib;pure/strscans" srcdoc2: "pure/parseopt;pure/parseopt2;pure/hashes;pure/strtabs;pure/lexbase" srcdoc2: "pure/parsecfg;pure/parsexml;pure/parsecsv;pure/parsesql" |