diff options
79 files changed, 1268 insertions, 1395 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index e575f317d..97f48b253 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1311,6 +1311,10 @@ proc skipTypes*(t: PType, kinds: TTypeKinds): PType = result = t while result.kind in kinds: result = lastSon(result) +proc isGCedMem*(t: PType): bool {.inline.} = + result = t.kind in {tyString, tyRef, tySequence} or + t.kind == tyProc and t.callConv == ccClosure + proc propagateToOwner*(owner, elem: PType) = const HaveTheirOwnEmpty = {tySequence, tySet} owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta}) @@ -1331,9 +1335,7 @@ proc propagateToOwner*(owner, elem: PType) = owner.flags.incl tfHasMeta if owner.kind != tyProc: - if elem.kind in {tyString, tyRef, tySequence} or - elem.kind == tyProc and elem.callConv == ccClosure or - tfHasGCedMem in elem.flags: + if elem.isGCedMem or tfHasGCedMem in elem.flags: owner.flags.incl tfHasGCedMem proc rawAddSon*(father, son: PType) = diff --git a/compiler/cgen.nim b/compiler/cgen.nim index f64ebacfb..8d66d7a3b 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -730,7 +730,7 @@ proc retIsNotVoid(s: PSym): bool = result = (s.typ.sons[0] != nil) and not isInvalidReturnType(s.typ.sons[0]) proc initFrame(p: BProc, procname, filename: PRope): PRope = - discard cgsym(p.module, "pushFrame") + discard cgsym(p.module, "nimFrame") if p.maxFrameLen > 0: discard cgsym(p.module, "TVarSlot") result = rfmt(nil, "\tnimfrs($1, $2, $3, $4)$N", diff --git a/compiler/charsets.nim b/compiler/charsets.nim deleted file mode 100644 index d3d00b687..000000000 --- a/compiler/charsets.nim +++ /dev/null @@ -1,49 +0,0 @@ -# -# -# The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -const - CharSize* = SizeOf(Char) - Lrz* = ' ' - Apo* = '\'' - Tabulator* = '\x09' - ESC* = '\x1B' - CR* = '\x0D' - FF* = '\x0C' - LF* = '\x0A' - BEL* = '\x07' - BACKSPACE* = '\x08' - VT* = '\x0B' - -when defined(macos): - DirSep == ':' - "\n" == CR & "" - FirstNLchar == CR - PathSep == ';' # XXX: is this correct? -else: - when defined(unix): - DirSep == '/' - "\n" == LF & "" - FirstNLchar == LF - PathSep == ':' - else: - # windows, dos - DirSep == '\\' - "\n" == CR + LF - FirstNLchar == CR - DriveSeparator == ':' - PathSep == ';' -UpLetters == {'A'..'Z', '\xC0'..'\xDE'} -DownLetters == {'a'..'z', '\xDF'..'\xFF'} -Numbers == {'0'..'9'} -Letters == UpLetters + DownLetters -type - TCharSet* = set[Char] - PCharSet* = ref TCharSet - -# implementation diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 4117fc461..17bb5db55 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -49,6 +49,7 @@ proc initDefines*() = defineSymbol("nimcomputedgoto") defineSymbol("nimunion") defineSymbol("nimnewshared") + defineSymbol("nimrequiresnimframe") # add platform specific symbols: case targetCPU diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index 0ca07e828..1b9e5fe0f 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -12,7 +12,7 @@ const genPrefix* = ":tmp" # prefix for generated names -import ast, astalgo, types, idents, magicsys, msgs +import ast, astalgo, types, idents, magicsys, msgs, options proc newTupleAccess*(tup: PNode, i: int): PNode = result = newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes( @@ -151,8 +151,9 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode): PNode = if n.kind notin nkCallKinds or not n.typ.isEmptyType: localError(n.info, "'spawn' takes a call expression of type void") return - if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}: - localError(n.info, "'spawn' takes a GC safe call expression") + if optThreadAnalysis in gGlobalOptions: + if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}: + localError(n.info, "'spawn' takes a GC safe call expression") var threadParam = newSym(skParam, getIdent"thread", owner, n.info) argsParam = newSym(skParam, getIdent"args", owner, n.info) @@ -162,6 +163,7 @@ proc wrapProcForSpawn*(owner: PSym; n: PNode): PNode = argsParam.typ = ptrType argsParam.position = 1 var objType = createObj(owner, n.info) + incl(objType.flags, tfFinal) let castExpr = createCastExpr(argsParam, objType) var scratchObj = newSym(skVar, getIdent"scratch", owner, n.info) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 4d471bdac..8374c92a7 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -118,7 +118,7 @@ type warnNilStatement, warnAnalysisLoophole, warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure, warnEachIdentIsTuple, warnShadowIdent, - warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, + warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2, warnUninit, warnGcMem, warnUser, hintSuccess, hintSuccessX, hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, @@ -387,6 +387,7 @@ const warnProveField: "cannot prove that field '$1' is accessible [ProveField]", warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]", warnGcUnsafe: "not GC-safe: '$1' [GcUnsafe]", + warnGcUnsafe2: "cannot prove '$1' is GC-safe. This will become a compile time error in the future.", warnUninit: "'$1' might not have been initialized [Uninit]", warnGcMem: "'$1' uses GC'ed memory [GcMem]", warnUser: "$1 [User]", @@ -408,7 +409,7 @@ const hintUser: "$1 [User]"] const - WarningsToStr*: array[0..25, string] = ["CannotOpenFile", "OctalEscape", + WarningsToStr*: array[0..26, string] = ["CannotOpenFile", "OctalEscape", "XIsNeverRead", "XmightNotBeenInit", "Deprecated", "ConfigDeprecated", "SmallLshouldNotBeUsed", "UnknownMagic", @@ -416,7 +417,7 @@ const "CommentXIgnored", "NilStmt", "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", - "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "Uninit", + "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit", "GcMem", "User"] HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong", diff --git a/compiler/nversion.nim b/compiler/nversion.nim index db38354ce..b996d0b9b 100644 --- a/compiler/nversion.nim +++ b/compiler/nversion.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -15,8 +15,8 @@ const defaultAsmMarkerSymbol* = '!' VersionMajor* = 0 VersionMinor* = 9 - VersionPatch* = 3 + VersionPatch* = 4 VersionAsString* = $VersionMajor & "." & $VersionMinor & "." & $VersionPatch - RodFileVersion* = "1214" # modify this if the rod-format changes! + RodFileVersion* = "1215" # modify this if the rod-format changes! diff --git a/compiler/options.nim b/compiler/options.nim index 02719cacc..58a340d21 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -95,7 +95,7 @@ var optBoundsCheck, optOverflowCheck, optAssert, optWarns, optHints, optStackTrace, optLineTrace, optPatterns, optNilCheck} - gGlobalOptions*: TGlobalOptions = {optThreadAnalysis} + gGlobalOptions*: TGlobalOptions = {} gExitcode*: int8 gCmd*: TCommands = cmdNone # the command gSelectedGC* = gcRefc # the selected GC diff --git a/compiler/parsecfg.nim b/compiler/parsecfg.nim deleted file mode 100644 index e0d1afff1..000000000 --- a/compiler/parsecfg.nim +++ /dev/null @@ -1,346 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -# A HIGH-PERFORMANCE configuration file parser; -# the Nimrod version of this file is part of the -# standard library. - -import - llstream, nhashes, strutils, nimlexbase - -type - TCfgEventKind* = enum - cfgEof, # end of file reached - cfgSectionStart, # a ``[section]`` has been parsed - cfgKeyValuePair, # a ``key=value`` pair has been detected - cfgOption, # a ``--key=value`` command line option - cfgError # an error ocurred during parsing; msg contains the - # error message - TCfgEvent* = object of TObject - case kind*: TCfgEventKind - of cfgEof: - nil - - of cfgSectionStart: - section*: string - - of cfgKeyValuePair, cfgOption: - key*, value*: string - - of cfgError: - msg*: string - - - TTokKind* = enum - tkInvalid, tkEof, # order is important here! - tkSymbol, tkEquals, tkColon, tkBracketLe, tkBracketRi, tkDashDash - TToken*{.final.} = object # a token - kind*: TTokKind # the type of the token - literal*: string # the parsed (string) literal - - TParserState* = enum - startState, commaState - TCfgParser* = object of TBaseLexer - tok*: TToken - state*: TParserState - filename*: string - - -proc Open*(c: var TCfgParser, filename: string, inputStream: PLLStream) -proc Close*(c: var TCfgParser) -proc next*(c: var TCfgParser): TCfgEvent -proc getColumn*(c: TCfgParser): int -proc getLine*(c: TCfgParser): int -proc getFilename*(c: TCfgParser): string -proc errorStr*(c: TCfgParser, msg: string): string -# implementation - -const - SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'} # - # ---------------------------------------------------------------------------- - -proc rawGetTok(c: var TCfgParser, tok: var TToken) -proc open(c: var TCfgParser, filename: string, inputStream: PLLStream) = - openBaseLexer(c, inputStream) - c.filename = filename - c.state = startState - c.tok.kind = tkInvalid - c.tok.literal = "" - rawGetTok(c, c.tok) - -proc close(c: var TCfgParser) = - closeBaseLexer(c) - -proc getColumn(c: TCfgParser): int = - result = getColNumber(c, c.bufPos) - -proc getLine(c: TCfgParser): int = - result = c.linenumber - -proc getFilename(c: TCfgParser): string = - result = c.filename - -proc handleHexChar(c: var TCfgParser, xi: var int) = - case c.buf[c.bufpos] - of '0'..'9': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('0')) - inc(c.bufpos) - of 'a'..'f': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('a') + 10) - inc(c.bufpos) - of 'A'..'F': - xi = (xi shl 4) or (ord(c.buf[c.bufpos]) - ord('A') + 10) - inc(c.bufpos) - else: - nil - -proc handleDecChars(c: var TCfgParser, xi: var int) = - while c.buf[c.bufpos] in {'0'..'9'}: - xi = (xi * 10) + (ord(c.buf[c.bufpos]) - ord('0')) - inc(c.bufpos) - -proc getEscapedChar(c: var TCfgParser, tok: var TToken) = - var xi: int - inc(c.bufpos) # skip '\' - case c.buf[c.bufpos] - of 'n', 'N': - tok.literal = tok.literal & "\n" - Inc(c.bufpos) - of 'r', 'R', 'c', 'C': - add(tok.literal, CR) - Inc(c.bufpos) - of 'l', 'L': - add(tok.literal, LF) - Inc(c.bufpos) - of 'f', 'F': - add(tok.literal, FF) - inc(c.bufpos) - of 'e', 'E': - add(tok.literal, ESC) - Inc(c.bufpos) - of 'a', 'A': - add(tok.literal, BEL) - Inc(c.bufpos) - of 'b', 'B': - add(tok.literal, BACKSPACE) - Inc(c.bufpos) - of 'v', 'V': - add(tok.literal, VT) - Inc(c.bufpos) - of 't', 'T': - add(tok.literal, Tabulator) - Inc(c.bufpos) - of '\'', '\"': - add(tok.literal, c.buf[c.bufpos]) - Inc(c.bufpos) - of '\\': - add(tok.literal, '\\') - Inc(c.bufpos) - of 'x', 'X': - inc(c.bufpos) - xi = 0 - handleHexChar(c, xi) - handleHexChar(c, xi) - add(tok.literal, Chr(xi)) - of '0'..'9': - xi = 0 - handleDecChars(c, xi) - if (xi <= 255): add(tok.literal, Chr(xi)) - else: tok.kind = tkInvalid - else: tok.kind = tkInvalid - -proc HandleCRLF(c: var TCfgParser, pos: int): int = - case c.buf[pos] - of CR: result = lexbase.HandleCR(c, pos) - of LF: result = lexbase.HandleLF(c, pos) - else: result = pos - -proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) = - var - pos: int - ch: Char - buf: cstring - pos = c.bufPos + 1 # skip " - buf = c.buf # put `buf` in a register - tok.kind = tkSymbol - if (buf[pos] == '\"') and (buf[pos + 1] == '\"'): - # long string literal: - inc(pos, 2) # skip "" - # skip leading newline: - pos = HandleCRLF(c, pos) - buf = c.buf - while true: - case buf[pos] - of '\"': - if (buf[pos + 1] == '\"') and (buf[pos + 2] == '\"'): break - add(tok.literal, '\"') - Inc(pos) - of CR, LF: - pos = HandleCRLF(c, pos) - buf = c.buf - tok.literal = tok.literal & "\n" - of lexbase.EndOfFile: - tok.kind = tkInvalid - break - else: - add(tok.literal, buf[pos]) - Inc(pos) - c.bufpos = pos + - 3 # skip the three """ - else: - # ordinary string literal - while true: - ch = buf[pos] - if ch == '\"': - inc(pos) # skip '"' - break - if ch in {CR, LF, lexbase.EndOfFile}: - tok.kind = tkInvalid - break - if (ch == '\\') and not rawMode: - c.bufPos = pos - getEscapedChar(c, tok) - pos = c.bufPos - else: - add(tok.literal, ch) - Inc(pos) - c.bufpos = pos - -proc getSymbol(c: var TCfgParser, tok: var TToken) = - var - pos: int - buf: cstring - pos = c.bufpos - buf = c.buf - while true: - add(tok.literal, buf[pos]) - Inc(pos) - if not (buf[pos] in SymChars): break - c.bufpos = pos - tok.kind = tkSymbol - -proc skip(c: var TCfgParser) = - var - buf: cstring - pos: int - pos = c.bufpos - buf = c.buf - while true: - case buf[pos] - of ' ': - Inc(pos) - of Tabulator: - inc(pos) - of '#', ';': - while not (buf[pos] in {CR, LF, lexbase.EndOfFile}): inc(pos) - of CR, LF: - pos = HandleCRLF(c, pos) - buf = c.buf - else: - break # EndOfFile also leaves the loop - c.bufpos = pos - -proc rawGetTok(c: var TCfgParser, tok: var TToken) = - tok.kind = tkInvalid - setlen(tok.literal, 0) - skip(c) - case c.buf[c.bufpos] - of '=': - tok.kind = tkEquals - inc(c.bufpos) - tok.literal = "=" - of '-': - inc(c.bufPos) - if c.buf[c.bufPos] == '-': inc(c.bufPos) - tok.kind = tkDashDash - tok.literal = "--" - of ':': - tok.kind = tkColon - inc(c.bufpos) - tok.literal = ":" - of 'r', 'R': - if c.buf[c.bufPos + 1] == '\"': - Inc(c.bufPos) - getString(c, tok, true) - else: - getSymbol(c, tok) - of '[': - tok.kind = tkBracketLe - inc(c.bufpos) - tok.literal = "[" - of ']': - tok.kind = tkBracketRi - Inc(c.bufpos) - tok.literal = "]" - of '\"': - getString(c, tok, false) - of lexbase.EndOfFile: - tok.kind = tkEof - else: getSymbol(c, tok) - -proc errorStr(c: TCfgParser, msg: string): string = - result = `%`("$1($2, $3) Error: $4", - [c.filename, $(getLine(c)), $(getColumn(c)), msg]) - -proc getKeyValPair(c: var TCfgParser, kind: TCfgEventKind): TCfgEvent = - if c.tok.kind == tkSymbol: - result.kind = kind - result.key = c.tok.literal - result.value = "" - rawGetTok(c, c.tok) - while c.tok.literal == ".": - add(result.key, '.') - rawGetTok(c, c.tok) - if c.tok.kind == tkSymbol: - add(result.key, c.tok.literal) - rawGetTok(c, c.tok) - else: - result.kind = cfgError - result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal) - break - if c.tok.kind in {tkEquals, tkColon}: - rawGetTok(c, c.tok) - if c.tok.kind == tkSymbol: - result.value = c.tok.literal - else: - result.kind = cfgError - result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal) - rawGetTok(c, c.tok) - else: - result.kind = cfgError - result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal) - rawGetTok(c, c.tok) - -proc next(c: var TCfgParser): TCfgEvent = - case c.tok.kind - of tkEof: - result.kind = cfgEof - of tkDashDash: - rawGetTok(c, c.tok) - result = getKeyValPair(c, cfgOption) - of tkSymbol: - result = getKeyValPair(c, cfgKeyValuePair) - of tkBracketLe: - rawGetTok(c, c.tok) - if c.tok.kind == tkSymbol: - result.kind = cfgSectionStart - result.section = c.tok.literal - else: - result.kind = cfgError - result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal) - rawGetTok(c, c.tok) - if c.tok.kind == tkBracketRi: - rawGetTok(c, c.tok) - else: - result.kind = cfgError - result.msg = errorStr(c, "\']\' expected, but found: " & c.tok.literal) - of tkInvalid, tkBracketRi, tkEquals, tkColon: - result.kind = cfgError - result.msg = errorStr(c, "invalid token: " & c.tok.literal) - rawGetTok(c, c.tok) diff --git a/compiler/passes.nim b/compiler/passes.nim index 3dc31e7ac..66a1a4954 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -13,7 +13,7 @@ import strutils, lists, options, ast, astalgo, llstream, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, magicsys, nversion, - nimsets, syntaxes, times, rodread, semthreads, idgen + nimsets, syntaxes, times, rodread, idgen type TPassContext* = object of TObject # the pass's context @@ -74,7 +74,8 @@ proc astNeeded*(s: PSym): bool = ({sfCompilerProc, sfCompileTime} * s.flags == {}) and (s.typ.callConv != ccInline) and (s.ast.sons[genericParamsPos].kind == nkEmpty): - result = semthreads.needsGlobalAnalysis() + result = false + # XXX this doesn't really make sense with excessive CTFE else: result = true diff --git a/compiler/pendx.nim b/compiler/pendx.nim deleted file mode 100644 index 2942968a0..000000000 --- a/compiler/pendx.nim +++ /dev/null @@ -1,18 +0,0 @@ -# -# -# The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -import - llstream, lexer, parser, idents, strutils, ast, msgs - -proc ParseAll*(p: var TParser): PNode = - result = nil - -proc parseTopLevelStmt*(p: var TParser): PNode = - result = nil - diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 88fa516bf..db9fe7cbe 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -53,7 +53,7 @@ const wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked, - wBorrow} + wBorrow, wGcSafe} fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern, wImportCpp, wImportObjC, wError} varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl, @@ -689,10 +689,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, incl(sym.flags, sfProcvar) if sym.typ != nil: incl(sym.typ.flags, tfThread) of wGcSafe: - noVal(it) - incl(sym.flags, sfThread) - if sym.typ != nil: incl(sym.typ.flags, tfGcSafe) - else: invalidPragma(it) + if optThreadAnalysis in gGlobalOptions: + noVal(it) + if sym.kind != skType: incl(sym.flags, sfThread) + if sym.typ != nil: incl(sym.typ.flags, tfGcSafe) + else: invalidPragma(it) of wPacked: noVal(it) if sym.typ == nil: invalidPragma(it) diff --git a/compiler/sem.nim b/compiler/sem.nim index e7bff0665..7d129caf4 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -14,7 +14,7 @@ import wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math, magicsys, parser, nversion, nimsets, semfold, importer, procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch, - semthreads, intsets, transf, vmdef, vm, idgen, aliases, cgmeth, lambdalifting, + intsets, transf, vmdef, vm, idgen, aliases, cgmeth, lambdalifting, evaltempl, patterns, parampatterns, sempass2, pretty, semmacrosanity # implementation @@ -415,12 +415,7 @@ proc myProcess(context: PPassContext, n: PNode): PNode = if getCurrentException() of ESuggestDone: result = nil else: result = ast.emptyNode #if gCmd == cmdIdeTools: findSuggest(c, n) - -proc checkThreads(c: PContext) = - if not needsGlobalAnalysis(): return - for i in 0 .. c.threadEntries.len-1: - semthreads.analyseThreadProc(c.threadEntries[i]) - + proc myClose(context: PPassContext, n: PNode): PNode = var c = PContext(context) closeScope(c) # close module's scope @@ -431,7 +426,6 @@ proc myClose(context: PPassContext, n: PNode): PNode = addCodeForGenerics(c, result) if c.module.ast != nil: result.add(c.module.ast) - checkThreads(c) popOwner() popProcCon(c) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 4cb5ad38c..987a70a41 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -57,7 +57,6 @@ type # can access private object fields instCounter*: int # to prevent endless instantiations - threadEntries*: TSymSeq # list of thread entries to check ambiguousSymbols*: TIntSet # ids of all ambiguous symbols (cannot # store this info in the syms themselves!) inTypeClass*: int # > 0 if we are in a user-defined type class @@ -170,7 +169,6 @@ proc newContext(module: PSym): PContext = append(result.optionStack, newOptionEntry()) result.module = module result.friendModule = module - result.threadEntries = @[] result.converters = @[] result.patterns = @[] result.includedFiles = initIntSet() diff --git a/compiler/semfold.nim b/compiler/semfold.nim index caaab2291..79abfaf4d 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -404,7 +404,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = mExit, mInc, ast.mDec, mEcho, mSwap, mAppendStrCh, mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq, mParseExprToAst, mParseStmtToAst, mExpandToAst, mTypeTrait, - mNLen..mNError, mEqRef, mSlurp, mStaticExec, mNGenSym: + mNLen..mNError, mEqRef, mSlurp, mStaticExec, mNGenSym, mSpawn: discard of mRand: result = newIntNodeT(math.random(a.getInt.int), n) diff --git a/compiler/seminst.nim b/compiler/seminst.nim index a5149a842..f7d5fa6f8 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -127,9 +127,6 @@ proc sideEffectsCheck(c: PContext, s: PSym) = if {sfNoSideEffect, sfSideEffect} * s.flags == {sfNoSideEffect, sfSideEffect}: localError(s.info, errXhasSideEffects, s.name.s) - elif sfThread in s.flags and semthreads.needsGlobalAnalysis() and - s.ast.sons[genericParamsPos].kind == nkEmpty: - c.threadEntries.add(s) proc instGenericContainer(c: PContext, info: TLineInfo, header: PType, allowMetaTypes = false): PType = diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index b2b91490c..6235fb76a 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -71,7 +71,7 @@ type init: seq[int] # list of initialized variables guards: TModel # nested guards locked: seq[PNode] # locked locations - gcUnsafe: bool + gcUnsafe, isRecursive: bool PEffects = var TEffects proc isLocalVar(a: PEffects, s: PSym): bool = @@ -113,7 +113,8 @@ proc useVar(a: PEffects, n: PNode) = if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind == skVar: when trackGlobals: a.addUse(copyNode(n)) - if tfHasGCedMem in s.typ.flags: + if (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem) and + tfGcSafe notin s.typ.flags: message(n.info, warnGcUnsafe, renderTree(n)) a.gcUnsafe = true @@ -502,7 +503,9 @@ proc track(tracked: PEffects, n: PNode) = # are indistinguishable from normal procs (both have tyProc type) and # we can detect them only by checking for attached nkEffectList. if op != nil and op.kind == tyProc and op.n.sons[0].kind == nkEffectList: - if notGcSafe(op) and not importedFromC(a): + if a.kind == nkSym and a.sym == tracked.owner: + tracked.isRecursive = true + elif notGcSafe(op) and not importedFromC(a): message(n.info, warnGcUnsafe, renderTree(n)) tracked.gcUnsafe = true var effectList = op.n.sons[0] @@ -515,6 +518,7 @@ proc track(tracked: PEffects, n: PNode) = addEffect(tracked, createRaise(n)) addTag(tracked, createTag(n)) when trackGlobals: addUse(tracked, createAnyGlobal(n)) + # XXX handle 'gcsafe' properly for callbacks! else: mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) @@ -701,10 +705,12 @@ proc trackProc*(s: PSym, body: PNode) = checkRaisesSpec(usesSpec, t.uses, "uses an unlisted global variable: ", hints=on, symbolPredicate) effects.sons[usesEffects] = usesSpec - if sfThread in s.flags and t.gcUnsafe: - localError(s.info, "'$1' is not GC-safe" % s.name.s) - if not t.gcUnsafe: s.typ.flags.incl tfGcSafe - + if optThreadAnalysis in gGlobalOptions: + if sfThread in s.flags and t.gcUnsafe: + localError(s.info, warnGcUnsafe2, s.name.s) + #localError(s.info, "'$1' is not GC-safe" % s.name.s) + if not t.gcUnsafe: s.typ.flags.incl tfGcSafe + proc trackTopLevelStmt*(module: PSym; n: PNode) = if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}: diff --git a/compiler/semthreads.nim b/compiler/semthreads.nim deleted file mode 100644 index d3426ca3e..000000000 --- a/compiler/semthreads.nim +++ /dev/null @@ -1,390 +0,0 @@ -# -# -# The Nimrod Compiler -# (c) Copyright 2013 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Semantic analysis that deals with threads: Possible race conditions should -## be reported some day. -## -## -## ======================== -## No heap sharing analysis -## ======================== -## -## The only crucial operation that can violate the heap invariants is the -## write access. The analysis needs to distinguish between 'unknown', 'mine', -## and 'theirs' memory and pointers. Assignments 'whatever <- unknown' are -## invalid, and so are 'theirs <- whatever' but not 'mine <- theirs'. Since -## strings and sequences are heap allocated they are affected too: -## -## .. code-block:: nimrod -## proc p() = -## global = "alloc this string" # ugh! -## -## Thus the analysis is concerned with any type that contains a GC'ed -## reference... -## If the type system would distinguish between 'ref' and '!ref' and threads -## could not have '!ref' as input parameters the analysis could simply need to -## reject any write access to a global variable which contains GC'ed data. -## Thanks to the write barrier of the GC, this is exactly what needs to be -## done! Every write access to a global that contains GC'ed data needs to -## be prevented! Unfortunately '!ref' is not implemented yet... -## -## The assignment target is essential for the algorithm: only -## write access to heap locations and global variables are critical and need -## to be checked. Access via 'var' parameters is no problem to analyse since -## we need the arguments' locations in the analysis. -## -## However, this is tricky: -## -## var x = globalVar # 'x' points to 'theirs' -## while true: -## globalVar = x # NOT OK: 'theirs <- theirs' invalid due to -## # write barrier! -## x = "new string" # ugh: 'x is toUnknown'! -## -## --> Solution: toUnknown is never allowed anywhere! -## -## -## Beware that the same proc might need to be -## analysed multiple times! Oh and watch out for recursion! Recursion is handled -## by a stack of symbols that we are processing, if we come back to the same -## symbol, we have to skip this check (assume no error in the recursive case). -## However this is wrong. We need to check for the particular combination -## of (procsym, threadOwner(arg1), threadOwner(arg2), ...)! - -import - ast, astalgo, strutils, hashes, options, msgs, idents, types, os, - renderer, tables, rodread - -type - TThreadOwner = enum - toUndefined, # not computed yet - toVoid, # no return type - toNil, # cycle in computation or nil: can be overwritten - toTheirs, # some other heap - toMine # mine heap - - TCall = object {.pure.} - callee: PSym # what if callee is an indirect call? - args: seq[TThreadOwner] - - PProcCtx = ref TProcCtx - TProcCtx = object {.pure.} - nxt: PProcCtx # can be stacked - mapping: tables.TTable[int, TThreadOwner] # int = symbol ID - owner: PSym # current owner - -var - computed = tables.initTable[TCall, TThreadOwner]() - -proc hash(c: TCall): THash = - result = hash(c.callee.id) - for a in items(c.args): result = result !& hash(ord(a)) - result = !$result - -proc `==`(a, b: TCall): bool = - if a.callee != b.callee: return - if a.args.len != b.args.len: return - for i in 0..a.args.len-1: - if a.args[i] != b.args[i]: return - result = true - -proc newProcCtx(owner: PSym): PProcCtx = - assert owner != nil - new(result) - result.mapping = tables.initTable[int, TThreadOwner]() - result.owner = owner - -proc analyse(c: PProcCtx, n: PNode): TThreadOwner - -proc analyseSym(c: PProcCtx, n: PNode): TThreadOwner = - var v = n.sym - result = c.mapping[v.id] - if result != toUndefined: return - case v.kind - of skVar, skForVar, skLet, skResult: - result = toNil - if sfGlobal in v.flags: - if sfThread in v.flags: - result = toMine - elif containsGarbageCollectedRef(v.typ): - result = toTheirs - of skTemp: result = toNil - of skConst: result = toMine - of skParam: - result = c.mapping[v.id] - if result == toUndefined: - internalError(n.info, "param not set: " & v.name.s) - else: - result = toNil - c.mapping[v.id] = result - -proc lvalueSym(n: PNode): PNode = - result = n - while result.kind in {nkDotExpr, nkCheckedFieldExpr, - nkBracketExpr, nkDerefExpr, nkHiddenDeref}: - result = result.sons[0] - -proc writeAccess(c: PProcCtx, n: PNode, owner: TThreadOwner) = - if owner notin {toNil, toMine, toTheirs}: - internalError(n.info, "writeAccess: " & $owner) - var a = lvalueSym(n) - if a.kind == nkSym: - var v = a.sym - var lastOwner = analyseSym(c, a) - case lastOwner - of toNil: - # fine, toNil can be overwritten - var newOwner: TThreadOwner - if sfGlobal in v.flags: - newOwner = owner - elif containsTyRef(v.typ): - # ``var local = gNode`` --> ok, but ``local`` is theirs! - newOwner = owner - else: - # ``var local = gString`` --> string copy: ``local`` is mine! - newOwner = toMine - # XXX BUG what if the tuple contains both ``tyRef`` and ``tyString``? - c.mapping[v.id] = newOwner - of toVoid, toUndefined: internalError(n.info, "writeAccess") - of toTheirs: message(n.info, warnWriteToForeignHeap) - of toMine: - if lastOwner != owner and owner != toNil: - message(n.info, warnDifferentHeaps) - else: - # we could not backtrack to a concrete symbol, but that's fine: - var lastOwner = analyse(c, n) - case lastOwner - of toNil: discard # fine, toNil can be overwritten - of toVoid, toUndefined: internalError(n.info, "writeAccess") - of toTheirs: message(n.info, warnWriteToForeignHeap) - of toMine: - if lastOwner != owner and owner != toNil: - message(n.info, warnDifferentHeaps) - -proc analyseAssign(c: PProcCtx, le, ri: PNode) = - var y = analyse(c, ri) # read access; ok - writeAccess(c, le, y) - -proc analyseAssign(c: PProcCtx, n: PNode) = - analyseAssign(c, n.sons[0], n.sons[1]) - -proc analyseCall(c: PProcCtx, n: PNode): TThreadOwner = - var prc = n[0].sym - var newCtx = newProcCtx(prc) - var call: TCall - call.callee = prc - newSeq(call.args, n.len-1) - for i in 1..n.len-1: - call.args[i-1] = analyse(c, n[i]) - if not computed.hasKey(call): - computed[call] = toUndefined # we are computing it - let prctyp = skipTypes(prc.typ, abstractInst).n - for i in 1.. prctyp.len-1: - var formal = prctyp.sons[i].sym - newCtx.mapping[formal.id] = call.args[i-1] - pushInfoContext(n.info) - result = analyse(newCtx, prc.getBody) - if prc.ast.sons[bodyPos].kind == nkEmpty and - {sfNoSideEffect, sfThread, sfImportc} * prc.flags == {}: - message(n.info, warnAnalysisLoophole, renderTree(n)) - if result == toUndefined: result = toNil - if prc.typ.sons[0] != nil: - if prc.ast.len > resultPos: - result = newCtx.mapping[prc.ast.sons[resultPos].sym.id] - # if the proc body does not set 'result', nor 'return's something - # explicitely, it returns a binary zero, so 'toNil' is correct: - if result == toUndefined: result = toNil - else: - result = toNil - else: - result = toVoid - computed[call] = result - popInfoContext() - else: - result = computed[call] - if result == toUndefined: - # ugh, cycle! We are already computing it but don't know the - # outcome yet... - if prc.typ.sons[0] == nil: result = toVoid - else: result = toNil - -proc analyseVarTuple(c: PProcCtx, n: PNode) = - if n.kind != nkVarTuple: internalError(n.info, "analyseVarTuple") - var L = n.len - for i in countup(0, L-3): analyseAssign(c, n.sons[i], n.sons[L-1]) - -proc analyseSingleVar(c: PProcCtx, a: PNode) = - if a.sons[2].kind != nkEmpty: analyseAssign(c, a.sons[0], a.sons[2]) - -proc analyseVarSection(c: PProcCtx, n: PNode): TThreadOwner = - for i in countup(0, sonsLen(n) - 1): - var a = n.sons[i] - if a.kind == nkCommentStmt: continue - if a.kind == nkIdentDefs: - #assert(a.sons[0].kind == nkSym); also valid for after - # closure transformation: - analyseSingleVar(c, a) - else: - analyseVarTuple(c, a) - result = toVoid - -proc analyseConstSection(c: PProcCtx, t: PNode): TThreadOwner = - for i in countup(0, sonsLen(t) - 1): - var it = t.sons[i] - if it.kind == nkCommentStmt: continue - if it.kind != nkConstDef: internalError(t.info, "analyseConstSection") - if sfFakeConst in it.sons[0].sym.flags: analyseSingleVar(c, it) - result = toVoid - -template aggregateOwner(result, ana: expr) = - var a = ana # eval once - if result != a: - if result == toNil: result = a - elif a != toNil: message(n.info, warnDifferentHeaps) - -proc analyseArgs(c: PProcCtx, n: PNode, start = 1) = - for i in start..n.len-1: discard analyse(c, n[i]) - -proc analyseOp(c: PProcCtx, n: PNode): TThreadOwner = - if n[0].kind != nkSym or n[0].sym.kind != skProc: - if {tfNoSideEffect, tfThread} * n[0].typ.flags == {}: - message(n.info, warnAnalysisLoophole, renderTree(n)) - result = toNil - else: - var prc = n[0].sym - case prc.magic - of mNone: - if sfSystemModule in prc.owner.flags: - # System module proc does no harm :-) - analyseArgs(c, n) - if prc.typ.sons[0] == nil: result = toVoid - else: result = toNil - else: - result = analyseCall(c, n) - of mNew, mNewFinalize, mNewSeq, mSetLengthStr, mSetLengthSeq, - mAppendSeqElem, mReset, mAppendStrCh, mAppendStrStr: - writeAccess(c, n[1], toMine) - result = toVoid - of mSwap: - var a = analyse(c, n[2]) - writeAccess(c, n[1], a) - writeAccess(c, n[2], a) - result = toVoid - of mIntToStr, mInt64ToStr, mFloatToStr, mBoolToStr, mCharToStr, - mCStrToStr, mStrToStr, mEnumToStr, - mConStrStr, mConArrArr, mConArrT, - mConTArr, mConTT, mSlice, - mRepr, mArrToSeq, mCopyStr, mCopyStrLast, - mNewString, mNewStringOfCap: - analyseArgs(c, n) - result = toMine - else: - # don't recurse, but check args: - analyseArgs(c, n) - if prc.typ.sons[0] == nil: result = toVoid - else: result = toNil - -proc analyse(c: PProcCtx, n: PNode): TThreadOwner = - case n.kind - of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, - nkCallStrLit, nkHiddenCallConv: - result = analyseOp(c, n) - of nkAsgn, nkFastAsgn: - analyseAssign(c, n) - result = toVoid - of nkSym: result = analyseSym(c, n) - of nkEmpty, nkNone: result = toVoid - of nkNilLit, nkCharLit..nkFloat64Lit: result = toNil - of nkStrLit..nkTripleStrLit: result = toMine - of nkDotExpr, nkBracketExpr, nkDerefExpr, nkHiddenDeref: - # field access: - # pointer deref or array access: - result = analyse(c, n.sons[0]) - of nkBind: result = analyse(c, n.sons[0]) - of nkPar, nkCurly, nkBracket, nkRange: - # container construction: - result = toNil # nothing until later - for i in 0..n.len-1: aggregateOwner(result, analyse(c, n[i])) - of nkObjConstr: - if n.typ != nil and containsGarbageCollectedRef(n.typ): - result = toMine - else: - result = toNil # nothing until later - for i in 1..n.len-1: aggregateOwner(result, analyse(c, n[i])) - of nkAddr, nkHiddenAddr: - var a = lvalueSym(n) - if a.kind == nkSym: - result = analyseSym(c, a) - assert result in {toNil, toMine, toTheirs} - if result == toNil: - # assume toMine here for consistency: - c.mapping[a.sym.id] = toMine - result = toMine - else: - # should never really happen: - result = analyse(c, n.sons[0]) - of nkIfExpr: - result = toNil - for i in countup(0, sonsLen(n) - 1): - var it = n.sons[i] - if it.len == 2: - discard analyse(c, it.sons[0]) - aggregateOwner(result, analyse(c, it.sons[1])) - else: - aggregateOwner(result, analyse(c, it.sons[0])) - of nkStmtListExpr, nkBlockExpr: - var n = if n.kind == nkBlockExpr: n.sons[1] else: n - var L = sonsLen(n) - for i in countup(0, L-2): discard analyse(c, n.sons[i]) - if L > 0: result = analyse(c, n.sons[L-1]) - else: result = toVoid - of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkCast: - result = analyse(c, n.sons[1]) - of nkStringToCString, nkCStringToString, nkChckRangeF, nkChckRange64, - nkChckRange, nkCheckedFieldExpr, nkObjDownConv, - nkObjUpConv: - result = analyse(c, n.sons[0]) - of nkRaiseStmt: - var a = analyse(c, n.sons[0]) - if a != toMine: message(n.info, warnDifferentHeaps) - result = toVoid - of nkVarSection, nkLetSection: result = analyseVarSection(c, n) - of nkConstSection: result = analyseConstSection(c, n) - of nkTypeSection, nkCommentStmt: result = toVoid - of nkIfStmt, nkWhileStmt, nkTryStmt, nkCaseStmt, nkStmtList, nkBlockStmt, - nkElifBranch, nkElse, nkExceptBranch, nkOfBranch, nkFinally: - for i in 0 .. <n.len: discard analyse(c, n[i]) - result = toVoid - of nkBreakStmt, nkContinueStmt: result = toVoid - of nkReturnStmt, nkDiscardStmt: - if n.sons[0].kind != nkEmpty: result = analyse(c, n.sons[0]) - else: result = toVoid - of nkLambdaKinds, nkClosure: - result = toMine - of nkAsmStmt, nkPragma, nkIteratorDef, nkProcDef, nkMethodDef, - nkConverterDef, nkMacroDef, nkTemplateDef, - nkGotoState, nkState, nkBreakState, nkType, nkIdent: - result = toVoid - of nkExprColonExpr: - result = analyse(c, n.sons[1]) - else: internalError(n.info, "analysis not implemented for: " & $n.kind) - -proc analyseThreadProc*(prc: PSym) = - var c = newProcCtx(prc) - var formals = skipTypes(prc.typ, abstractInst).n - for i in 1 .. formals.len-1: - var formal = formals.sons[i].sym - # the input is copied and belongs to the thread: - c.mapping[formal.id] = toMine - discard analyse(c, prc.getBody) - -proc needsGlobalAnalysis*: bool = - result = gGlobalOptions * {optThreads, optThreadAnalysis} == - {optThreads, optThreadAnalysis} - diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 384bdc8a3..6289ecc85 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -194,10 +194,15 @@ proc semRange(c: PContext, n: PNode, prev: PType): PType = if isRange(n[1]): result = semRangeAux(c, n[1], prev) let n = result.n - if n.sons[0].kind in {nkCharLit..nkUInt64Lit}: - if n.sons[0].intVal > 0 or n.sons[1].intVal < 0: - incl(result.flags, tfNeedsInit) - elif n.sons[0].floatVal > 0.0 or n.sons[1].floatVal < 0.0: + if n.sons[0].kind in {nkCharLit..nkUInt64Lit} and n.sons[0].intVal > 0: + incl(result.flags, tfNeedsInit) + elif n.sons[1].kind in {nkCharLit..nkUInt64Lit} and n.sons[1].intVal < 0: + incl(result.flags, tfNeedsInit) + elif n.sons[0].kind in {nkFloatLit..nkFloat64Lit} and + n.sons[0].floatVal > 0.0: + incl(result.flags, tfNeedsInit) + elif n.sons[1].kind in {nkFloatLit..nkFloat64Lit} and + n.sons[1].floatVal < 0.0: incl(result.flags, tfNeedsInit) else: localError(n.sons[0].info, errRangeExpected) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 8b9b7b13b..33de40f34 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -19,7 +19,7 @@ proc sharedPtrCheck(info: TLineInfo, t: PType) = if t.sons[0].sym.magic in {mShared, mGuarded}: incl(t.flags, tfShared) if t.sons[0].sym.magic == mGuarded: incl(t.flags, tfGuarded) - if tfHasGCedMem in t.flags: + if tfHasGCedMem in t.flags or t.isGCedMem: localError(info, errGenerated, "shared memory may not refer to GC'ed thread local memory") @@ -352,8 +352,9 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = result = handleGenericInvokation(cl, t) of tyGenericBody: - internalError(cl.info, "ReplaceTypeVarsT: tyGenericBody" ) - result = replaceTypeVarsT(cl, lastSon(t)) + localError(cl.info, errCannotInstantiateX, typeToString(t)) + result = t + #result = replaceTypeVarsT(cl, lastSon(t)) of tyFromExpr: if cl.allowMetaTypes: return diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 5d3ed05f7..4b91a067e 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -148,6 +148,9 @@ proc sumGeneric(t: PType): int = result = ord(t.kind == tyGenericInvokation) for i in 0 .. <t.len: result += t.sons[i].sumGeneric break + of tyProc: + # proc matche proc better than 'stmt' to disambiguate 'spawn' + return 1 of tyGenericParam, tyExpr, tyStatic, tyStmt, tyTypeDesc: break else: return 0 @@ -851,7 +854,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyAnd: considerPreviousT: for branch in f.sons: - if typeRel(c, branch, aOrig) == isNone: + if typeRel(c, branch, aOrig) < isSubtype: return isNone bindingRet isGeneric @@ -859,7 +862,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyOr: considerPreviousT: for branch in f.sons: - if typeRel(c, branch, aOrig) != isNone: + if typeRel(c, branch, aOrig) >= isSubtype: bindingRet isGeneric return isNone diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 49611f649..fc6ba2f77 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -253,6 +253,7 @@ proc findUsages(node: PNode, s: PSym) = lastLineInfo = node.info proc findDefinition(node: PNode, s: PSym) = + if node.isNil or s.isNil: return if isTracked(node.info, s.name.s.len): suggestWriteln(symToStr(s, isLocal=false, sectionDef)) suggestQuit() diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim index 478c2a837..d5062544f 100644 --- a/compiler/syntaxes.nim +++ b/compiler/syntaxes.nim @@ -62,7 +62,6 @@ proc parseAll(p: var TParsers): PNode = of skinEndX: internalError("parser to implement") result = ast.emptyNode - # skinEndX: result := pendx.parseAll(p.parser); proc parseTopLevelStmt(p: var TParsers): PNode = case p.skin @@ -73,7 +72,6 @@ proc parseTopLevelStmt(p: var TParsers): PNode = of skinEndX: internalError("parser to implement") result = ast.emptyNode - #skinEndX: result := pendx.parseTopLevelStmt(p.parser); proc utf8Bom(s: string): int = if (s[0] == '\xEF') and (s[1] == '\xBB') and (s[2] == '\xBF'): diff --git a/compiler/types.nim b/compiler/types.nim index 1de0fc103..1f266d64f 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -342,9 +342,10 @@ proc canFormAcycleAux(marker: var TIntSet, typ: PType, startId: int): bool = result = t.id == startId # Inheritance can introduce cyclic types, however this is not relevant # as the type that is passed to 'new' is statically known! - #if t.kind == tyObject and tfFinal notin t.flags: - # # damn inheritance may introduce cycles: - # result = true + # er but we use it also for the write barrier ... + if t.kind == tyObject and tfFinal notin t.flags: + # damn inheritance may introduce cycles: + result = true of tyProc: result = typ.callConv == ccClosure else: discard @@ -537,7 +538,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = add(prag, "noSideEffect") if tfThread in t.flags: addSep(prag) - add(prag, "thread") + add(prag, "gcsafe") if len(prag) != 0: add(result, "{." & prag & ".}") of tyVarargs, tyIter: result = typeToStr[t.kind] % typeToString(t.sons[0]) diff --git a/compiler/typesrenderer.nim b/compiler/typesrenderer.nim index ebb9b7e15..790bd1047 100644 --- a/compiler/typesrenderer.nim +++ b/compiler/typesrenderer.nim @@ -69,7 +69,7 @@ proc renderType(n: PNode): string = for i in 1 .. <len(n): result.add(renderType(n[i]) & ',') result[<len(result)] = ']' else: result = "" - assert (not result.isNil) + assert(not result.isNil) proc renderParamTypes(found: var seq[string], n: PNode) = @@ -86,13 +86,11 @@ proc renderParamTypes(found: var seq[string], n: PNode) = let typePos = len(n) - 2 assert typePos > 0 var typeStr = renderType(n[typePos]) - if typeStr.len < 1: + if typeStr.len < 1 and n[typePos+1].kind != nkEmpty: # Try with the last node, maybe its a default value. - assert n[typePos+1].kind != nkEmpty let typ = n[typePos+1].typ if not typ.isNil: typeStr = typeToString(typ, preferExported) - if typeStr.len < 1: - return + if typeStr.len < 1: return for i in 0 .. <typePos: assert n[i].kind == nkIdent found.add(typeStr) diff --git a/compiler/vm.nim b/compiler/vm.nim index fb8749250..836f90967 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -354,6 +354,11 @@ template handleJmpBack() {.dirty.} = globalError(c.debug[pc], errTooManyIterations) dec(c.loopIterations) +proc skipColon(n: PNode): PNode = + result = n + if n.kind == nkExprColonExpr: + result = n.sons[1] + proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = var pc = start var tos = tos @@ -454,7 +459,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = decodeBC(rkNode) let src = regs[rb].node if src.kind notin {nkEmpty..nkNilLit}: - let n = src.sons[rc] + let n = src.sons[rc].skipColon regs[ra].node = n else: stackTrace(c, tos, pc, errNilAccess) @@ -969,7 +974,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcRepr: decodeB(rkNode) createStr regs[ra] - regs[ra].node.strVal = renderTree(regs[rb].node, {renderNoComments}) + regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments}) of opcQuit: if c.mode in {emRepl, emStaticExpr, emStaticStmt}: message(c.debug[pc], hintQuitCalled) @@ -1099,6 +1104,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = c.module) of opcGorge: decodeBC(rkNode) + createStr regs[ra] regs[ra].node.strVal = opGorge(regs[rb].node.strVal, regs[rc].node.strVal) of opcNError: diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 7c0c3d4f5..84577bb22 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -70,6 +70,9 @@ proc echoCode*(c: PCtx, start=0) {.deprecated.} = echo buf proc gABC(ctx: PCtx; n: PNode; opc: TOpcode; a, b, c: TRegister = 0) = + ## Takes the registers `b` and `c`, applies the operation `opc` to them, and + ## stores the result into register `a` + ## The node is needed for debug information assert opc.ord < 255 let ins = (opc.uint32 or (a.uint32 shl 8'u32) or (b.uint32 shl 16'u32) or @@ -78,6 +81,10 @@ proc gABC(ctx: PCtx; n: PNode; opc: TOpcode; a, b, c: TRegister = 0) = ctx.debug.add(n.info) proc gABI(c: PCtx; n: PNode; opc: TOpcode; a, b: TRegister; imm: BiggestInt) = + # Takes the `b` register and the immediate `imm`, appies the operation `opc`, + # and stores the output value into `a`. + # `imm` is signed and must be within [-127, 128] + assert(imm >= -127 and imm <= 128) let ins = (opc.uint32 or (a.uint32 shl 8'u32) or (b.uint32 shl 16'u32) or (imm+byteExcess).uint32 shl 24'u32).TInstr @@ -85,6 +92,9 @@ proc gABI(c: PCtx; n: PNode; opc: TOpcode; a, b: TRegister; imm: BiggestInt) = c.debug.add(n.info) proc gABx(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0; bx: int) = + # Applies `opc` to `bx` and stores it into register `a` + # `bx` must be signed and in the range [-32767, 32768] + assert(bx >= -32767 and bx <= 32768) let ins = (opc.uint32 or a.uint32 shl 8'u32 or (bx+wordExcess).uint32 shl 16'u32).TInstr c.code.add(ins) @@ -316,15 +326,7 @@ proc genAndOr(c: PCtx; n: PNode; opc: TOpcode; dest: var TDest) = c.patch(L1) proc canonValue*(n: PNode): PNode = - if n.kind == nkExprColonExpr: - result = n.sons[1] - elif n.hasSubnodeWith(nkExprColonExpr): - result = n.copyNode - newSeq(result.sons, n.len) - for i in 0.. <n.len: - result.sons[i] = canonValue(n.sons[i]) - else: - result = n + result = n proc rawGenLiteral(c: PCtx; n: PNode): int = result = c.constants.len diff --git a/doc/docs.txt b/doc/docs.txt index 2162d6564..8126da86c 100644 --- a/doc/docs.txt +++ b/doc/docs.txt @@ -6,19 +6,19 @@ The documentation consists of several documents: - | `Tutorial (part II) <tut2.html>`_ | The Nimrod tutorial part two deals with the advanced language constructs. +- | `Language Manual <manual.html>`_ + | The Nimrod manual is a draft that will evolve into a proper specification. + - | `Library documentation <lib.html>`_ | This document describes Nimrod's standard library. -- | `User guide <nimrodc.html>`_ +- | `Compiler user guide <nimrodc.html>`_ | The user guide lists command line arguments, special features of the compiler, etc. - | `Tools documentation <tools.html>`_ | Description of some tools that come with the standard distribution. -- | `Manual <manual.html>`_ - | The Nimrod manual is a draft that will evolve into a proper specification. - - | `GC <gc.html>`_ | Additional documentation about Nimrod's GC and how to operate it in a | realtime setting. diff --git a/doc/lib.txt b/doc/lib.txt index a209357f7..3ca519c9e 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -373,6 +373,9 @@ Miscellaneous * `logging <logging.html>`_ This module implements a simple logger. +* `future <future.html>`_ + This module implements new experimental features. Currently the syntax + sugar for anonymous procedures. Database support ---------------- diff --git a/doc/manual.txt b/doc/manual.txt index d6b9f296e..98af5aebc 100644 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -3575,6 +3575,8 @@ match anything without discrimination. User defined type classes ------------------------- +**Note**: User defined type classes are still in development. + The user-defined type classes are available in two flavours - declarative and imperative. Both are used to specify an arbitrary set of requirements that the matched type must satisfy. @@ -3599,13 +3601,13 @@ b) all statically evaluatable boolean expressions in the body must be true The identifiers following the `generic` keyword represent instances of the currently matched type. These instances can act both as variables of the type, -when used in contexts, where a value is expected, and as the type itself, when -used in a contexts, where a type is expected. +when used in contexts where a value is expected, and as the type itself when +used in contexts where a type is expected. Please note that the ``is`` operator allows one to easily verify the precise type signatures of the required operations, but since type inference and default parameters are still applied in the provided block, it's also possible -to encode usage protocols that doesn't reveal implementation details. +to encode usage protocols that do not reveal implementation details. As a special rule providing further convenience when writing type classes, any type value appearing in a callable expression will be treated as a variable of @@ -4118,6 +4120,8 @@ Special Types static[T] --------- +**Note**: static[T] is still in development. + As their name suggests, static params must be known at compile-time: .. code-block:: nimrod @@ -4255,6 +4259,7 @@ types that will match the typedesc param: The constraint can be a concrete type or a type class. + Special Operators ================= @@ -5600,10 +5605,8 @@ This is only useful if the program is compiled as a dynamic library via the Threads ======= -Even though Nimrod's `thread`:idx: support and semantics are preliminary, -they should be quite usable already. To enable thread support -the ``--threads:on`` command line switch needs to be used. The ``system`` -module then contains several threading primitives. +To enable thread support the ``--threads:on`` command line switch needs to +be used. The ``system`` module then contains several threading primitives. See the `threads <threads.html>`_ and `channels <channels.html>`_ modules for the thread API. @@ -5645,6 +5648,11 @@ contain a ``ref`` or ``closure`` type. This enforces the *no heap sharing restriction*. Routines that are imported from C are always assumed to be ``gcsafe``. +To enable the GC-safety checking the ``--threadAnalysis:on`` command line +switch must be used. This is a temporary workaround to ease the porting effort +from old code to the new threading model. In the future the thread analysis +will always be performed. + Future directions: @@ -5675,6 +5683,35 @@ in one thread cannot affect any other thread. However, an *unhandled* exception in one thread terminates the whole *process*! +Spawn +----- + +Nimrod has a builtin thread pool that can be used for CPU intensive tasks. For +IO intensive tasks the upcoming ``async`` and ``await`` features should be +used. `spawn`:idx: is used to pass a task to the thread pool: + +.. code-block:: nimrod + proc processLine(line: string) = + # do some heavy lifting here: + discard + + for x in lines("myinput.txt"): + spawn processLine(x) + sync() + +Currently the expression that ``spawn`` takes is however quite restricted: + +* It must be a call expresion ``f(a, ...)``. +* ``f`` must be ``gcsafe``. +* ``f`` must not have the calling convention ``closure``. +* ``f``'s parameters may not be of type ``var`` nor may they contain ``ref``. + This means you have to use raw ``ptr``'s for data passing reminding the + programmer to be careful. +* For *safe* data exchange between ``f`` and the caller a global ``TChannel`` + needs to be used. Other means will be provided soon. + + + Taint mode ========== diff --git a/doc/tut1.txt b/doc/tut1.txt index 5a20629a2..46eda7ae3 100644 --- a/doc/tut1.txt +++ b/doc/tut1.txt @@ -132,7 +132,7 @@ a backslash: TMyObject {.final, pure, acyclic.} = object # comment continues: \ # we have lots of space here to comment 'TMyObject'. # This line belongs to the comment as it's properly aligned. - + Comments are tokens; they are only allowed at certain places in the input file as they belong to the syntax tree! This feature enables perfect source-to-source @@ -150,6 +150,18 @@ the syntax, watch their indentation: **Note**: To comment out a large piece of code, it is often better to use a ``when false:`` statement. +.. code-block:: nimrod + when false: + brokenCode() + +Another option is to use the `discard`_ statement together with +*long string literals* to create block comments: + +.. code-block:: nimrod + discard """ You can have any nimrod code text commented + out inside this with no indentation restrictions. + yes("May I ask a pointless question?") """ + Numbers ------- @@ -575,27 +587,46 @@ Some terminology: in the example ``question`` is called a (formal) *parameter*, Result variable --------------- -A procedure that returns a value has an implicit ``result`` variable that -represents the return value. A ``return`` statement with no expression is a -shorthand for ``return result``. So all three code snippets are equivalent: - -.. code-block:: nimrod - return 42 - -.. code-block:: nimrod - result = 42 - return - -.. code-block:: nimrod - result = 42 - return result - - +A procedure that returns a value has an implicit ``result`` variable declared +that represents the return value. A ``return`` statement with no expression is a +shorthand for ``return result``. The ``result`` value is always returned +automatically at the end a procedure if there is no ``return`` statement at +the exit. + +.. code-block:: nimrod + proc sumTillNegative(x: varargs[int]): int = + for i in x: + if i < 0: + return + result = result + i + + echo sumTillNegative() # echos 0 + echo sumTillNegative(3, 4, 5) # echos 12 + echo sumTillNegative(3, 4 , -1 , 6) # echos 7 + +The ``result`` variable is already implicitly declared at the start of the +function, so declaring it again with 'var result', for example, would shadow it +with a normal variable of the same name. The result variable is also already +initialised with the type's default value. Note that referential data types will +be ``nil`` at the start of the procedure, and thus may require manual +initialisation. + + Parameters ---------- -Parameters are constant in the procedure body. Their value cannot be changed -because this allows the compiler to implement parameter passing in the most -efficient way. If the procedure needs to modify the argument for the +Parameters are constant in the procedure body. By default, their value cannot be +changed because this allows the compiler to implement parameter passing in the +most efficient way. If a mutable variable is needed inside the procedure, it has +to be declared with ``var`` in the procedure body. Shadowing the parameter name +is possible, and actually an idiom: + +.. code-block:: nimrod + proc printSeq(s: seq, nprinted: int = -1) = + var nprinted = if nprinted == -1: s.len else: min(nprinted, s.len) + for i in 0 .. <nprinted: + echo s[i] + +If the procedure needs to modify the argument for the caller, a ``var`` parameter can be used: .. code-block:: nimrod @@ -634,6 +665,9 @@ been declared with the ``discardable`` pragma: p(3, 4) # now valid +The discard statement can also be used to create block comments as described +in the `Comments`_. + Named arguments --------------- diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 79bf34b7c..334a4b8c7 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -7,6 +7,7 @@ # distribution, for details about the copyright. # +include "system/inclrtl" ## This module contains the interface to the compiler's abstract syntax ## tree (`AST`:idx:). Macros operate on this tree. @@ -109,19 +110,20 @@ const nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, nnkCallStrLit} -proc `[]`*(n: PNimrodNode, i: int): PNimrodNode {.magic: "NChild".} +proc `[]`*(n: PNimrodNode, i: int): PNimrodNode {.magic: "NChild", noSideEffect.} ## get `n`'s `i`'th child. -proc `[]=`*(n: PNimrodNode, i: int, child: PNimrodNode) {.magic: "NSetChild".} +proc `[]=`*(n: PNimrodNode, i: int, child: PNimrodNode) {.magic: "NSetChild", + noSideEffect.} ## set `n`'s `i`'th child to `child`. -proc `!`*(s: string): TNimrodIdent {.magic: "StrToIdent".} +proc `!`*(s: string): TNimrodIdent {.magic: "StrToIdent", noSideEffect.} ## constructs an identifier from the string `s` -proc `$`*(i: TNimrodIdent): string {.magic: "IdentToStr".} +proc `$`*(i: TNimrodIdent): string {.magic: "IdentToStr", noSideEffect.} ## converts a Nimrod identifier to a string -proc `$`*(s: PNimrodSymbol): string {.magic: "IdentToStr".} +proc `$`*(s: PNimrodSymbol): string {.magic: "IdentToStr", noSideEffect.} ## converts a Nimrod symbol to a string proc `==`*(a, b: TNimrodIdent): bool {.magic: "EqIdent", noSideEffect.} @@ -130,35 +132,36 @@ proc `==`*(a, b: TNimrodIdent): bool {.magic: "EqIdent", noSideEffect.} proc `==`*(a, b: PNimrodNode): bool {.magic: "EqNimrodNode", noSideEffect.} ## compares two Nimrod nodes -proc len*(n: PNimrodNode): int {.magic: "NLen".} +proc len*(n: PNimrodNode): int {.magic: "NLen", noSideEffect.} ## returns the number of children of `n`. -proc add*(father, child: PNimrodNode): PNimrodNode {.magic: "NAdd", discardable.} +proc add*(father, child: PNimrodNode): PNimrodNode {.magic: "NAdd", discardable, + noSideEffect.} ## Adds the `child` to the `father` node. Returns the ## father node so that calls can be nested. proc add*(father: PNimrodNode, children: varargs[PNimrodNode]): PNimrodNode {. - magic: "NAddMultiple", discardable.} + magic: "NAddMultiple", discardable, noSideEffect.} ## Adds each child of `children` to the `father` node. ## Returns the `father` node so that calls can be nested. -proc del*(father: PNimrodNode, idx = 0, n = 1) {.magic: "NDel".} +proc del*(father: PNimrodNode, idx = 0, n = 1) {.magic: "NDel", noSideEffect.} ## deletes `n` children of `father` starting at index `idx`. -proc kind*(n: PNimrodNode): TNimrodNodeKind {.magic: "NKind".} +proc kind*(n: PNimrodNode): TNimrodNodeKind {.magic: "NKind", noSideEffect.} ## returns the `kind` of the node `n`. -proc intVal*(n: PNimrodNode): BiggestInt {.magic: "NIntVal".} -proc floatVal*(n: PNimrodNode): BiggestFloat {.magic: "NFloatVal".} -proc symbol*(n: PNimrodNode): PNimrodSymbol {.magic: "NSymbol".} -proc ident*(n: PNimrodNode): TNimrodIdent {.magic: "NIdent".} -proc typ*(n: PNimrodNode): typedesc {.magic: "NGetType".} -proc strVal*(n: PNimrodNode): string {.magic: "NStrVal".} - -proc `intVal=`*(n: PNimrodNode, val: BiggestInt) {.magic: "NSetIntVal".} -proc `floatVal=`*(n: PNimrodNode, val: BiggestFloat) {.magic: "NSetFloatVal".} -proc `symbol=`*(n: PNimrodNode, val: PNimrodSymbol) {.magic: "NSetSymbol".} -proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent".} +proc intVal*(n: PNimrodNode): BiggestInt {.magic: "NIntVal", noSideEffect.} +proc floatVal*(n: PNimrodNode): BiggestFloat {.magic: "NFloatVal", noSideEffect.} +proc symbol*(n: PNimrodNode): PNimrodSymbol {.magic: "NSymbol", noSideEffect.} +proc ident*(n: PNimrodNode): TNimrodIdent {.magic: "NIdent", noSideEffect.} +proc typ*(n: PNimrodNode): typedesc {.magic: "NGetType", noSideEffect.} +proc strVal*(n: PNimrodNode): string {.magic: "NStrVal", noSideEffect.} + +proc `intVal=`*(n: PNimrodNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.} +proc `floatVal=`*(n: PNimrodNode, val: BiggestFloat) {.magic: "NSetFloatVal", noSideEffect.} +proc `symbol=`*(n: PNimrodNode, val: PNimrodSymbol) {.magic: "NSetSymbol", noSideEffect.} +proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent", noSideEffect.} #proc `typ=`*(n: PNimrodNode, typ: typedesc) {.magic: "NSetType".} # this is not sound! Unfortunately forbidding 'typ=' is not enough, as you # can easily do: @@ -166,24 +169,24 @@ proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent".} # let fake = semCheck(2.0) # bracket[0] = fake # constructs a mixed array with ints and floats! -proc `strVal=`*(n: PNimrodNode, val: string) {.magic: "NSetStrVal".} +proc `strVal=`*(n: PNimrodNode, val: string) {.magic: "NSetStrVal", noSideEffect.} proc newNimNode*(kind: TNimrodNodeKind, - n: PNimrodNode=nil): PNimrodNode {.magic: "NNewNimNode".} + n: PNimrodNode=nil): PNimrodNode {.magic: "NNewNimNode", noSideEffect.} -proc copyNimNode*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimNode".} -proc copyNimTree*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimTree".} +proc copyNimNode*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimNode", noSideEffect.} +proc copyNimTree*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimTree", noSideEffect.} -proc error*(msg: string) {.magic: "NError".} +proc error*(msg: string) {.magic: "NError", gcsafe.} ## writes an error message at compile time -proc warning*(msg: string) {.magic: "NWarning".} +proc warning*(msg: string) {.magic: "NWarning", gcsafe.} ## writes a warning message at compile time -proc hint*(msg: string) {.magic: "NHint".} +proc hint*(msg: string) {.magic: "NHint", gcsafe.} ## writes a hint message at compile time -proc newStrLitNode*(s: string): PNimrodNode {.compileTime.} = +proc newStrLitNode*(s: string): PNimrodNode {.compileTime, noSideEffect.} = ## creates a string literal node from `s` result = newNimNode(nnkStrLit) result.strVal = s @@ -219,7 +222,7 @@ type ## any other means in the language currently) proc bindSym*(ident: string, rule: TBindSymRule = brClosed): PNimrodNode {. - magic: "NBindSym".} + magic: "NBindSym", noSideEffect.} ## creates a node that binds `ident` to a symbol node. The bound symbol ## may be an overloaded symbol. ## If ``rule == brClosed`` either an ``nkClosedSymChoice`` tree is @@ -230,11 +233,11 @@ proc bindSym*(ident: string, rule: TBindSymRule = brClosed): PNimrodNode {. ## returned even if the symbol is not ambiguous. proc genSym*(kind: TNimrodSymKind = nskLet; ident = ""): PNimrodNode {. - magic: "NGenSym".} + magic: "NGenSym", noSideEffect.} ## generates a fresh symbol that is guaranteed to be unique. The symbol ## needs to occur in a declaration context. -proc callsite*(): PNimrodNode {.magic: "NCallSite".} +proc callsite*(): PNimrodNode {.magic: "NCallSite", gcsafe.} ## returns the AST if the invokation expression that invoked this macro. proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} = @@ -242,19 +245,19 @@ proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} = ## in a string literal node return newStrLitNode(repr(n)) -proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo".} +proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo", noSideEffect.} ## returns the position the node appears in the original source file ## in the form filename(line, col) -proc parseExpr*(s: string): PNimrodNode {.magic: "ParseExprToAst".} +proc parseExpr*(s: string): PNimrodNode {.magic: "ParseExprToAst", noSideEffect.} ## Compiles the passed string to its AST representation. ## Expects a single expression. -proc parseStmt*(s: string): PNimrodNode {.magic: "ParseStmtToAst".} +proc parseStmt*(s: string): PNimrodNode {.magic: "ParseStmtToAst", noSideEffect.} ## Compiles the passed string to its AST representation. ## Expects one or more statements. -proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst".} +proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst", noSideEffect.} ## Obtains the AST nodes returned from a macro or template invocation. ## Example: ## @@ -263,7 +266,7 @@ proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst".} ## macro FooMacro() = ## var ast = getAst(BarTemplate()) -proc quote*(bl: stmt, op = "``"): PNimrodNode {.magic: "QuoteAst".} +proc quote*(bl: stmt, op = "``"): PNimrodNode {.magic: "QuoteAst", noSideEffect.} ## Quasi-quoting operator. ## Accepts an expression or a block and returns the AST that represents it. ## Within the quoted AST, you are able to interpolate PNimrodNode expressions @@ -298,7 +301,7 @@ proc expectKind*(n: PNimrodNode, k: TNimrodNodeKind) {.compileTime.} = ## checks that `n` is of kind `k`. If this is not the case, ## compilation aborts with an error message. This is useful for writing ## macros that check the AST that is passed to them. - if n.kind != k: error("macro expects a node of kind: " & repr(k)) + if n.kind != k: error("macro expects a node of kind: " & $k) proc expectMinLen*(n: PNimrodNode, min: int) {.compileTime.} = ## checks that `n` has at least `min` children. If this is not the case, diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 5e638dc74..f50383038 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +include "system/inclrtl" + import os, oids, tables, strutils, macros import rawsockets @@ -31,7 +33,7 @@ export TPort type PFutureBase* = ref object of PObject - cb: proc () {.closure.} + cb: proc () {.closure,gcsafe.} finished: bool PFuture*[T] = ref object of PFutureBase @@ -68,7 +70,7 @@ proc fail*[T](future: PFuture[T], error: ref EBase) = if future.cb != nil: future.cb() -proc `callback=`*(future: PFutureBase, cb: proc () {.closure.}) = +proc `callback=`*(future: PFutureBase, cb: proc () {.closure,gcsafe.}) = ## Sets the callback proc to be called when the future completes. ## ## If future has already completed then ``cb`` will be called immediately. @@ -80,7 +82,7 @@ proc `callback=`*(future: PFutureBase, cb: proc () {.closure.}) = future.cb() proc `callback=`*[T](future: PFuture[T], - cb: proc (future: PFuture[T]) {.closure.}) = + cb: proc (future: PFuture[T]) {.closure,gcsafe.}) = ## Sets the callback proc to be called when the future completes. ## ## If future has already completed then ``cb`` will be called immediately. @@ -122,7 +124,7 @@ when defined(windows) or defined(nimdoc): TCompletionData* = object sock: TAsyncFD cb: proc (sock: TAsyncFD, bytesTransferred: DWORD, - errcode: TOSErrorCode) {.closure.} + errcode: TOSErrorCode) {.closure,gcsafe.} PDispatcher* = ref object ioPort: THandle @@ -237,7 +239,7 @@ when defined(windows) or defined(nimdoc): let func = cast[proc (s: TSocketHandle, name: ptr TSockAddr, namelen: cint, lpSendBuffer: pointer, dwSendDataLength: dword, - lpdwBytesSent: PDWORD, lpOverlapped: POverlapped): bool {.stdcall.}](connectExPtr) + lpdwBytesSent: PDWORD, lpOverlapped: POverlapped): bool {.stdcall,gcsafe.}](connectExPtr) result = func(s, name, namelen, lpSendBuffer, dwSendDataLength, lpdwBytesSent, lpOverlapped) @@ -251,7 +253,7 @@ when defined(windows) or defined(nimdoc): cast[proc (listenSock, acceptSock: TSocketHandle, lpOutputBuffer: pointer, dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: DWORD, lpdwBytesReceived: PDWORD, - lpOverlapped: POverlapped): bool {.stdcall.}](acceptExPtr) + lpOverlapped: POverlapped): bool {.stdcall,gcsafe.}](acceptExPtr) result = func(listenSock, acceptSock, lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength, lpdwBytesReceived, lpOverlapped) @@ -268,7 +270,7 @@ when defined(windows) or defined(nimdoc): dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: DWORD, LocalSockaddr: ptr ptr TSockAddr, LocalSockaddrLength: lpint, RemoteSockaddr: ptr ptr TSockAddr, - RemoteSockaddrLength: lpint) {.stdcall.}](getAcceptExSockAddrsPtr) + RemoteSockaddrLength: lpint) {.stdcall,gcsafe.}](getAcceptExSockAddrsPtr) func(lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength, @@ -537,7 +539,7 @@ else: from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK type TAsyncFD* = distinct cint - TCallback = proc (sock: TAsyncFD): bool {.closure.} + TCallback = proc (sock: TAsyncFD): bool {.closure,gcsafe.} PData* = ref object of PObject sock: TAsyncFD @@ -757,7 +759,7 @@ proc accept*(socket: TAsyncFD): PFuture[TAsyncFD] = template createCb*(retFutureSym, iteratorNameSym: expr): stmt {.immediate.} = var nameIterVar = iteratorNameSym - proc cb {.closure.} = + proc cb {.closure,gcsafe.} = if not nameIterVar.finished: var next = nameIterVar() if next == nil: diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index ab09dc860..c68ca4350 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -6,6 +6,8 @@ # distribution, for details about the copyright. # +include "system/inclrtl" + import sockets, os ## This module implements an asynchronous event loop together with asynchronous sockets @@ -98,13 +100,13 @@ type fd*: TSocketHandle deleVal*: PObject - handleRead*: proc (h: PObject) {.nimcall.} - handleWrite*: proc (h: PObject) {.nimcall.} - handleError*: proc (h: PObject) {.nimcall.} - hasDataBuffered*: proc (h: PObject): bool {.nimcall.} + handleRead*: proc (h: PObject) {.nimcall, gcsafe.} + handleWrite*: proc (h: PObject) {.nimcall, gcsafe.} + handleError*: proc (h: PObject) {.nimcall, gcsafe.} + hasDataBuffered*: proc (h: PObject): bool {.nimcall, gcsafe.} open*: bool - task*: proc (h: PObject) {.nimcall.} + task*: proc (h: PObject) {.nimcall, gcsafe.} mode*: TFileMode PDelegate* = ref TDelegate @@ -118,13 +120,13 @@ type socket: TSocket info: TInfo - handleRead*: proc (s: PAsyncSocket) {.closure.} - handleWrite: proc (s: PAsyncSocket) {.closure.} - handleConnect*: proc (s: PAsyncSocket) {.closure.} + handleRead*: proc (s: PAsyncSocket) {.closure, gcsafe.} + handleWrite: proc (s: PAsyncSocket) {.closure, gcsafe.} + handleConnect*: proc (s: PAsyncSocket) {.closure, gcsafe.} - handleAccept*: proc (s: PAsyncSocket) {.closure.} + handleAccept*: proc (s: PAsyncSocket) {.closure, gcsafe.} - handleTask*: proc (s: PAsyncSocket) {.closure.} + handleTask*: proc (s: PAsyncSocket) {.closure, gcsafe.} lineBuffer: TaintedString ## Temporary storage for ``readLine`` sendBuffer: string ## Temporary storage for ``send`` @@ -213,7 +215,7 @@ proc asyncSockHandleRead(h: PObject) = else: PAsyncSocket(h).handleAccept(PAsyncSocket(h)) -proc close*(sock: PAsyncSocket) +proc close*(sock: PAsyncSocket) {.gcsafe.} proc asyncSockHandleWrite(h: PObject) = when defined(ssl): if PAsyncSocket(h).socket.isSSL and not @@ -254,7 +256,7 @@ proc asyncSockHandleWrite(h: PObject) = PAsyncSocket(h).deleg.mode = fmRead when defined(ssl): - proc asyncSockDoHandshake(h: PObject) = + proc asyncSockDoHandshake(h: PObject) {.gcsafe.} = if PAsyncSocket(h).socket.isSSL and not PAsyncSocket(h).socket.gotHandshake: if PAsyncSocket(h).sslNeedAccept: @@ -437,7 +439,7 @@ proc isSendDataBuffered*(s: PAsyncSocket): bool = return s.sendBuffer.len != 0 proc setHandleWrite*(s: PAsyncSocket, - handleWrite: proc (s: PAsyncSocket) {.closure.}) = + handleWrite: proc (s: PAsyncSocket) {.closure, gcsafe.}) = ## Setter for the ``handleWrite`` event. ## ## To remove this event you should use the ``delHandleWrite`` function. diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 4e2b6f5f8..31fb24eef 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -377,7 +377,7 @@ proc setCookie*(name, value: string) = write(stdout, "Set-Cookie: ", name, "=", value, "\n") var - gcookies: PStringTable = nil + gcookies {.threadvar.}: PStringTable proc getCookie*(name: string): TaintedString = ## Gets a cookie. If no cookie of `name` exists, "" is returned. diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim index 3bb55239b..53f6688b9 100644 --- a/lib/pure/ftpclient.nim +++ b/lib/pure/ftpclient.nim @@ -6,6 +6,8 @@ # distribution, for details about the copyright. # +include "system/inclrtl" + import sockets, strutils, parseutils, times, os, asyncio ## This module **partially** implements an FTP client as specified @@ -41,7 +43,7 @@ type dummyA, dummyB: pointer # workaround a Nimrod API issue asyncCSock: PAsyncSocket asyncDSock: PAsyncSocket - handleEvent*: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} + handleEvent*: proc (ftp: PAsyncFTPClient, ev: TFTPEvent){.closure,gcsafe.} disp: PDispatcher asyncDSockID: PDelegate user, pass: string @@ -59,7 +61,7 @@ type JRetrText, JRetr, JStore TFTPJob = object - prc: proc (ftp: PFTPClient, async: bool): bool {.nimcall.} + prc: proc (ftp: PFTPClient, async: bool): bool {.nimcall, gcsafe.} case typ*: FTPJobType of JRetrText: lines: string @@ -148,7 +150,8 @@ proc assertReply(received: TaintedString, expected: varargs[string]) = [expected.join("' or '"), received.string]) proc createJob(ftp: PFTPClient, - prc: proc (ftp: PFTPClient, async: bool): bool {.nimcall.}, + prc: proc (ftp: PFTPClient, async: bool): bool {. + nimcall,gcsafe.}, cmd: FTPJobType) = if ftp.jobInProgress: raise newException(EFTP, "Unable to do two jobs at once.") @@ -558,7 +561,7 @@ proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = proc asyncFTPClient*(address: string, port = TPort(21), user, pass = "", - handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} = + handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure,gcsafe.} = (proc (ftp: PAsyncFTPClient, ev: TFTPEvent) = discard)): PAsyncFTPClient = ## Create a ``PAsyncFTPClient`` object. ## diff --git a/lib/pure/future.nim b/lib/pure/future.nim new file mode 100644 index 000000000..e0e4c4176 --- /dev/null +++ b/lib/pure/future.nim @@ -0,0 +1,118 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Dominik Picheta +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements experimental features which may soon be moved to +## the system module (or other more appropriate modules). + +import macros + +proc createProcType(p, b: PNimrodNode): PNimrodNode {.compileTime.} = + #echo treeRepr(p) + #echo treeRepr(b) + result = newNimNode(nnkProcTy) + var formalParams = newNimNode(nnkFormalParams) + + expectKind(b, nnkIdent) + formalParams.add b + + case p.kind + of nnkPar: + for i in 0 .. <p.len: + let ident = p[i] + var identDefs = newNimNode(nnkIdentDefs) + case ident.kind + of nnkExprColonExpr: + identDefs.add ident[0] + identDefs.add ident[1] + of nnkIdent: + 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: + 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() + #echo(treeRepr(result)) + #echo(result.toStrLit()) + +macro `=>`*(p, b: expr): expr {.immediate.} = + ## Syntax sugar for anonymous procedures. + ## + ## .. code-block:: nimrod + ## + ## proc passTwoAndTwo(f: (int, int) -> int): int = + ## f(2, 2) + ## + ## passTwoAndTwo((x, y) => x + y) # 4 + + #echo treeRepr(p) + #echo(treeRepr(b)) + var params: seq[PNimrodNode] = @[newIdentNode("auto")] + + case p.kind + of nnkPar: + for c in children(p): + var identDefs = newNimNode(nnkIdentDefs) + case c.kind + of nnkExprColonExpr: + identDefs.add(c[0]) + identDefs.add(c[1]) + identDefs.add(newEmptyNode()) + of nnkIdent: + identDefs.add(c) + identDefs.add(newEmptyNode()) + identDefs.add(newEmptyNode()) + else: + error("Incorrect procedure parameter list.") + params.add(identDefs) + of nnkIdent: + var identDefs = newNimNode(nnkIdentDefs) + identDefs.add(p) + identDefs.add(newEmptyNode()) + identDefs.add(newEmptyNode()) + params.add(identDefs) + of nnkInfix: + if p[0].kind == nnkIdent and p[0].ident == !"->": + var procTy = createProcType(p[1], p[2]) + params[0] = procTy[0][0] + for i in 1 .. <procTy[0].len: + params.add(procTy[0][i]) + else: + error("Expected proc type (->) got (" & $p[0].ident & ").") + else: + error("Incorrect procedure parameter list.") + result = newProc(params = params, body = b, procType = nnkLambda) + #echo(result.treeRepr) + #echo(result.toStrLit()) + #return result # TODO: Bug? + +macro `->`*(p, b: expr): expr {.immediate.} = + ## Syntax sugar for procedure types. + ## + ## .. code-block:: nimrod + ## + ## proc pass2(f: (float, float) -> float): float = + ## f(2, 2) + ## + ## # is the same as: + ## + ## proc pass2(f: proc (x, y: float): float): float = + ## f(2, 2) + + result = createProcType(p, b) diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim index 901fdc779..8de708c5d 100644 --- a/lib/pure/httpserver.nim +++ b/lib/pure/httpserver.nim @@ -477,7 +477,7 @@ proc nextAsync(s: PAsyncHTTPServer) = s.path = data.substr(i, last-1) proc asyncHTTPServer*(handleRequest: proc (server: PAsyncHTTPServer, client: TSocket, - path, query: string): bool {.closure.}, + path, query: string): bool {.closure, gcsafe.}, port = TPort(80), address = "", reuseAddr = false): PAsyncHTTPServer = ## Creates an Asynchronous HTTP server at ``port``. diff --git a/lib/pure/irc.nim b/lib/pure/irc.nim index 31a673210..49d9a9a34 100644 --- a/lib/pure/irc.nim +++ b/lib/pure/irc.nim @@ -32,6 +32,8 @@ ## **Warning:** The API of this module is unstable, and therefore is subject ## to change. +include "system/inclrtl" + import sockets, strutils, parseutils, times, asyncio, os type @@ -41,7 +43,7 @@ type nick, user, realname, serverPass: string case isAsync: bool of true: - handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.} + handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure, gcsafe.} asyncSock: PAsyncSocket myDispatcher: PDispatcher of false: @@ -445,7 +447,7 @@ proc asyncIRC*(address: string, port: TPort = 6667.TPort, realname = "NimrodBot", serverPass = "", joinChans: seq[string] = @[], msgLimit: bool = true, - ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.} + ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure,gcsafe.} ): PAsyncIRC = ## Use this function if you want to use asyncio's dispatcher. ## diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 3997b059f..e4aecd272 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -12,6 +12,8 @@ ## Basic math routines for Nimrod. ## This module is available for the JavaScript target. +include "system/inclrtl" + {.push debugger:off .} # the user does not want to trace a part # of the standard library! @@ -127,25 +129,25 @@ proc variance*(x: openArray[float]): float {.noSideEffect.} = result = result + diff*diff result = result / toFloat(len(x)) -proc random*(max: int): int +proc random*(max: int): int {.gcsafe.} ## 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. when not defined(windows): - proc random*(max: float): float + proc random*(max: float): float {.gcsafe.} ## 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 is currently not supported for windows. -proc randomize*() +proc randomize*() {.gcsafe.} ## 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. -proc randomize*(seed: int) +proc randomize*(seed: int) {.gcsafe.} ## initializes the random number generator with a specific seed. ## Note: Does nothing for the JavaScript target, ## as JavaScript does not support this. @@ -227,8 +229,8 @@ else: result = int(floor(mathrandom() * float(max))) proc random(max: float): float = result = float(mathrandom() * float(max)) - proc randomize() = nil - proc randomize(seed: int) = nil + 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.} diff --git a/lib/pure/os.nim b/lib/pure/os.nim index faca17e98..00a33db75 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -448,6 +448,8 @@ proc getLastAccessTime*(file: string): TTime {.rtl, extern: "nos$1".} = proc getCreationTime*(file: string): TTime {.rtl, extern: "nos$1".} = ## Returns the `file`'s creation time. + ## Note that under posix OS's, the returned time may actually be the time at + ## which the file's attribute's were last modified. when defined(posix): var res: TStat if stat(file, res) < 0'i32: osError(osLastError()) @@ -777,6 +779,25 @@ proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = elif defined(posix): result = path[0] == '/' +when defined(Windows): + proc openHandle(path: string, followSymlink=true): THandle = + var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL + if not followSymlink: + flags = flags or FILE_FLAG_OPEN_REPARSE_POINT + + when useWinUnicode: + result = createFileW( + newWideCString(path), 0'i32, + FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, + nil, OPEN_EXISTING, flags, 0 + ) + else: + result = createFileA( + path, 0'i32, + FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, + nil, OPEN_EXISTING, flags, 0 + ) + proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", tags: [FReadDir].} = ## Returns True if both pathname arguments refer to the same physical @@ -787,26 +808,8 @@ proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", ## sym-linked paths to the same file or directory. when defined(Windows): var success = true - - when useWinUnicode: - var p1 = newWideCString(path1) - var p2 = newWideCString(path2) - template openHandle(path: expr): expr = - createFileW(path, 0'i32, FILE_SHARE_DELETE or FILE_SHARE_READ or - FILE_SHARE_WRITE, nil, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL, 0) - - var f1 = openHandle(p1) - var f2 = openHandle(p2) - - else: - template openHandle(path: expr): expr = - createFileA(path, 0'i32, FILE_SHARE_DELETE or FILE_SHARE_READ or - FILE_SHARE_WRITE, nil, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL, 0) - - var f1 = openHandle(path1) - var f2 = openHandle(path2) + var f1 = openHandle(path1) + var f2 = openHandle(path2) var lastErr: TOSErrorCode if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: @@ -1564,7 +1567,7 @@ when defined(windows): # ourselves. This has the additional benefit that the program's behaviour # is always the same -- independent of the used C compiler. var - ownArgv: seq[string] + ownArgv {.threadvar.}: seq[string] proc paramCount*(): int {.rtl, extern: "nos$1", tags: [FReadIO].} = ## Returns the number of `command line arguments`:idx: given to the @@ -1747,4 +1750,140 @@ proc expandTilde*(path: string): string = else: result = path +when defined(Windows): + type + DeviceId = int32 + FileId = int64 +else: + type + DeviceId = TDev + FileId = TIno + +type + FileInfo = object + ## Contains information associated with a file object. + id: tuple[device: DeviceId, file: FileId] # Device and file id. + kind: TPathComponent # Kind of file object - directory, symlink, etc. + size: BiggestInt # Size of file. + permissions: set[TFilePermission] # File permissions + linkCount: BiggestInt # Number of hard links the file object has. + lastAccessTime: TTime # Time file was last accessed. + lastWriteTime: TTime # Time file was last modified/written to. + creationTime: TTime # Time file was created. Not supported on all systems! + +template rawToFormalFileInfo(rawInfo, formalInfo): expr = + ## Transforms the native file info structure into the one nimrod uses. + ## 'rawInfo' is either a 'TBY_HANDLE_FILE_INFORMATION' structure on Windows, + ## or a 'TStat' structure on posix + when defined(Windows): + template toTime(e): expr = winTimeToUnixTime(rdFileTime(e)) + template merge(a, b): expr = a or (b shl 32) + formalInfo.id.device = rawInfo.dwVolumeSerialNumber + formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh) + formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh) + formalInfo.linkCount = rawInfo.nNumberOfLinks + formalInfo.lastAccessTime = toTime(rawInfo.ftLastAccessTime) + formalInfo.lastWriteTime = toTime(rawInfo.ftLastWriteTime) + formalInfo.creationTime = toTime(rawInfo.ftCreationTime) + + # Retrieve basic permissions + if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32: + formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec, + fpGroupRead, fpOthersExec, fpOthersRead} + else: + result.permissions = {fpUserExec..fpOthersRead} + + # Retrieve basic file kind + result.kind = pcFile + if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32: + formalInfo.kind = pcDir + if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: + formalInfo.kind = succ(result.kind) + + else: + template checkAndIncludeMode(rawMode, formalMode: expr) = + if (rawInfo.st_mode and rawMode) != 0'i32: + formalInfo.permissions.incl(formalMode) + formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino) + formalInfo.size = rawInfo.st_size + formalInfo.linkCount = rawInfo.st_Nlink + formalInfo.lastAccessTime = rawInfo.st_atime + formalInfo.lastWriteTime = rawInfo.st_mtime + formalInfo.creationTime = rawInfo.st_ctime + + result.permissions = {} + checkAndIncludeMode(S_IRUSR, fpUserRead) + checkAndIncludeMode(S_IWUSR, fpUserWrite) + checkAndIncludeMode(S_IXUSR, fpUserExec) + + checkAndIncludeMode(S_IRGRP, fpGroupRead) + checkAndIncludeMode(S_IWGRP, fpGroupWrite) + checkAndIncludeMode(S_IXGRP, fpGroupExec) + + checkAndIncludeMode(S_IROTH, fpOthersRead) + checkAndIncludeMode(S_IWOTH, fpOthersWrite) + checkAndIncludeMode(S_IXOTH, fpOthersExec) + + formalInfo.kind = pcFile + if S_ISDIR(rawInfo.st_mode): formalInfo.kind = pcDir + if S_ISLNK(rawInfo.st_mode): formalInfo.kind.inc() + +proc getFileInfo*(handle: TFileHandle): FileInfo = + ## Retrieves file information for the file object represented by the given + ## handle. + ## + ## If the information cannot be retrieved, such as when the file handle + ## is invalid, an error will be thrown. + # Done: ID, Kind, Size, Permissions, Link Count + when defined(Windows): + var rawInfo: TBY_HANDLE_FILE_INFORMATION + # We have to use the super special '_get_osfhandle' call (wrapped above) + # To transform the C file descripter to a native file handle. + var realHandle = get_osfhandle(handle) + if getFileInformationByHandle(realHandle, addr rawInfo) == 0: + osError(osLastError()) + rawToFormalFileInfo(rawInfo, result) + else: + var rawInfo: TStat + if fstat(handle, rawInfo) < 0'i32: + osError(osLastError()) + rawToFormalFileInfo(rawInfo, result) + +proc getFileInfo*(file: TFile): FileInfo = + result = getFileInfo(file.fileHandle()) + +proc getFileInfo*(path: string, followSymlink = true): FileInfo = + ## Retrieves file information for the file object pointed to by `path`. + ## + ## Due to intrinsic differences between operating systems, the information + ## contained by the returned `FileInfo` structure will be slightly different + ## across platforms, and in some cases, incomplete or inaccurate. + ## + ## When `followSymlink` is true, symlinks are followed and the information + ## retrieved is information related to the symlink's target. Otherwise, + ## information on the symlink itself is retrieved. + ## + ## If the information cannot be retrieved, such as when the path doesn't + ## exist, or when permission restrictions prevent the program from retrieving + ## file information, an error will be thrown. + when defined(Windows): + var + handle = openHandle(path, followSymlink) + rawInfo: TBY_HANDLE_FILE_INFORMATION + if handle == INVALID_HANDLE_VALUE: + osError(osLastError()) + if getFileInformationByHandle(handle, addr rawInfo) == 0: + osError(osLastError()) + rawToFormalFileInfo(rawInfo, result) + discard closeHandle(handle) + else: + var rawInfo: TStat + if followSymlink: + if lstat(path, rawInfo) < 0'i32: + osError(osLastError()) + else: + if stat(path, rawInfo) < 0'i32: + osError(osLastError()) + rawToFormalFileInfo(rawInfo, result) + {.pop.} diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index ed83507d4..6e250f9d5 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -615,11 +615,13 @@ elif not defined(useNimRtl): optionPoStdErrToStdOut: bool when not defined(useFork): - proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} - proc startProcessAuxFork(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} + proc startProcessAuxSpawn(data: TStartProcessData): TPid {. + tags: [FExecIO, FReadEnv], gcsafe.} + proc startProcessAuxFork(data: TStartProcessData): TPid {. + tags: [FExecIO, FReadEnv], gcsafe.} {.push stacktrace: off, profiler: off.} proc startProcessAfterFork(data: ptr TStartProcessData) {. - tags: [FExecIO, FReadEnv], cdecl.} + tags: [FExecIO, FReadEnv], cdecl, gcsafe.} {.pop.} proc startProcess(command: string, @@ -946,7 +948,7 @@ elif not defined(useNimRtl): proc execCmdEx*(command: string, options: set[TProcessOption] = { poStdErrToStdOut, poUsePath}): tuple[ output: TaintedString, - exitCode: int] {.tags: [FExecIO, FReadIO].} = + exitCode: int] {.tags: [FExecIO, FReadIO], gcsafe.} = ## a convenience proc that runs the `command`, grabs all its output and ## exit code and returns both. var p = startCmd(command, options) diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index f3249b107..727a8efd8 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -70,7 +70,7 @@ const SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.', '/', '\\'} -proc rawGetTok(c: var TCfgParser, tok: var TToken) +proc rawGetTok(c: var TCfgParser, tok: var TToken) {.gcsafe.} proc open*(c: var TCfgParser, input: PStream, filename: string, lineOffset = 0) {. diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index f4c45b99c..959f5c6ef 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -19,10 +19,21 @@ import sockets, os, strutils, parseutils const redisNil* = "\0\0" +type + PPipeline = ref object + enabled: bool + buffer: string + expected: int ## number of replies expected if pipelined + +type + TSendMode = enum + normal, pipelined, multiple + type TRedis* {.pure, final.} = object socket: TSocket connected: bool + pipeline: PPipeline TRedisStatus* = string TRedisInteger* = biggestInt @@ -32,25 +43,42 @@ type EInvalidReply* = object of ESynch ## Invalid reply from redis ERedis* = object of ESynch ## Error in redis +proc newPipeline(): PPipeline = + new(result) + result.buffer = "" + result.enabled = false + result.expected = 0 + proc open*(host = "localhost", port = 6379.TPort): TRedis = ## Opens a connection to the redis server. result.socket = socket(buffered = false) if result.socket == InvalidSocket: OSError(OSLastError()) result.socket.connect(host, port) + result.pipeline = newPipeline() proc raiseInvalidReply(expected, got: char) = raise newException(EInvalidReply, "Expected '$1' at the beginning of a status reply got '$2'" % [$expected, $got]) -proc raiseNoOK(status: string) = - if status != "QUEUED" and status != "OK": +proc raiseNoOK(status: string, pipelineEnabled:bool) = + if pipelineEnabled and not (status == "QUEUED" or status == "PIPELINED"): + raise newException(EInvalidReply, "Expected \"QUEUED\" or \"PIPELINED\" got \"$1\"" % status) + elif not pipelineEnabled and status != "OK": raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) -proc parseStatus(r: TRedis): TRedisStatus = - var line = "" - r.socket.readLine(line) +template readSocket(r: TRedis, dummyVal:expr): stmt = + var line {.inject.} :TaintedString = "" + if r.pipeline.enabled: + return dummyVal + else: + readLine(r.socket, line) + +proc parseStatus(r: TRedis, line: string = ""): TRedisStatus = + if r.pipeline.enabled: + return "PIPELINED" + if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -60,13 +88,16 @@ proc parseStatus(r: TRedis): TRedisStatus = raiseInvalidReply('+', line[0]) return line.substr(1) # Strip '+' - -proc parseInteger(r: TRedis): TRedisInteger = - var line = "" - r.socket.readLine(line) - if line == "+QUEUED": # inside of multi - return -1 +proc readStatus(r:TRedis): TRedisStatus = + r.readSocket("PIPELINED") + return r.parseStatus(line) + +proc parseInteger(r: TRedis, line: string = ""): TRedisInteger = + if r.pipeline.enabled: return -1 + + #if line == "+QUEUED": # inside of multi + # return -1 if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -80,12 +111,18 @@ proc parseInteger(r: TRedis): TRedisInteger = if parseBiggestInt(line, result, 1) == 0: raise newException(EInvalidReply, "Unable to parse integer.") +proc readInteger(r: TRedis): TRedisInteger = + r.readSocket(-1) + return r.parseInteger(line) + proc recv(sock: TSocket, size: int): TaintedString = result = newString(size).TaintedString if sock.recv(cstring(result), size) != size: raise newException(EInvalidReply, "recv failed") -proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = +proc parseSingleString(r: TRedis, line:string, allowMBNil = False): TRedisString = + if r.pipeline.enabled: return "" + # Error. if line[0] == '-': raise newException(ERedis, strip(line)) @@ -95,9 +132,6 @@ proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = if line == "*-1": return RedisNil - if line == "+QUEUED" or line == "+OK" : # inside of a transaction (multi) - return nil - if line[0] != '$': raiseInvalidReply('$', line[0]) @@ -108,41 +142,84 @@ proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = var s = r.socket.recv(numBytes+2) result = strip(s.string) -proc parseMultiLines(r: TRedis, countLine:string): TRedisList = +proc readSingleString(r: TRedis): TRedisString = + r.readSocket("") + return r.parseSingleString(line) + +proc readNext(r: TRedis): TRedisList + +proc parseArrayLines(r: TRedis, countLine:string): TRedisList = if countLine.string[0] != '*': raiseInvalidReply('*', countLine.string[0]) var numElems = parseInt(countLine.string.substr(1)) if numElems == -1: return nil result = @[] + for i in 1..numElems: - var line = "" - r.socket.readLine(line.TaintedString) - if line[0] == '*': # after exec() may contain more multi-bulk replies - var parsed = r.parseMultiLines(line) + var parsed = r.readNext() + if not isNil(parsed): for item in parsed: result.add(item) - else: - result.add(r.parseSingle(line)) - -proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = - var line = "" - r.socket.readLine(line.TaintedString) - if line == "+QUEUED" or line == "+OK": # inside of a transaction (multi) - return nil - - return r.parseSingle(line, allowMBNil) +proc readArrayLines(r: TRedis): TRedisList = + r.readSocket(nil) + return r.parseArrayLines(line) + +proc parseBulkString(r: TRedis, allowMBNil = False, line:string = ""): TRedisString = + if r.pipeline.enabled: return "" + + return r.parseSingleString(line, allowMBNil) + +proc readBulkString(r: TRedis, allowMBNil = false): TRedisString = + r.readSocket("") + return r.parseBulkString(allowMBNil, line) + +proc readArray(r: TRedis): TRedisList = + r.readSocket(@[]) + return r.parseArrayLines(line) + +proc readNext(r: TRedis): TRedisList = + r.readSocket(@[]) + + var res = case line[0] + of '+', '-': @[r.parseStatus(line)] + of ':': @[$(r.parseInteger(line))] + of '$': @[r.parseBulkString(true,line)] + of '*': r.parseArrayLines(line) + else: + raise newException(EInvalidReply, "readNext failed on line: " & line) + nil + r.pipeline.expected -= 1 + return res + +proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = + ## Send buffered commands, clear buffer, return results + if r.pipeline.buffer.len > 0: + r.socket.send(r.pipeline.buffer) + r.pipeline.buffer = "" + + r.pipeline.enabled = false + result = @[] + + var tot = r.pipeline.expected -proc parseMultiBulk(r: TRedis): TRedisList = - var line = TaintedString"" - r.socket.readLine(line) + for i in 0..tot-1: + var ret = r.readNext() + for item in ret: + if not (item.contains("OK") or item.contains("QUEUED")): + result.add(item) - if line == "+QUEUED": # inside of a transaction (multi) - return nil - - return r.parseMultiLines(line) + r.pipeline.expected = 0 +proc startPipelining*(r: TRedis) = + ## Enable command pipelining (reduces network roundtrips). + ## Note that when enabled, you must call flushPipeline to actually send commands, except + ## for multi/exec() which enable and flush the pipeline automatically. + ## Commands return immediately with dummy values; actual results returned from + ## flushPipeline() or exec() + r.pipeline.expected = 0 + r.pipeline.enabled = true proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = var request = "*" & $(1 + args.len()) & "\c\L" @@ -151,7 +228,12 @@ proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = for i in items(args): request.add("$" & $i.len() & "\c\L") request.add(i & "\c\L") - r.socket.send(request) + + if r.pipeline.enabled: + r.pipeline.buffer.add(request) + r.pipeline.expected += 1 + else: + r.socket.send(request) proc sendCommand(r: TRedis, cmd: string, arg1: string, args: varargs[string]) = @@ -163,75 +245,80 @@ proc sendCommand(r: TRedis, cmd: string, arg1: string, for i in items(args): request.add("$" & $i.len() & "\c\L") request.add(i & "\c\L") - r.socket.send(request) + + if r.pipeline.enabled: + r.pipeline.expected += 1 + r.pipeline.buffer.add(request) + else: + r.socket.send(request) # Keys proc del*(r: TRedis, keys: varargs[string]): TRedisInteger = ## Delete a key or multiple keys r.sendCommand("DEL", keys) - return r.parseInteger() + return r.readInteger() proc exists*(r: TRedis, key: string): bool = ## Determine if a key exists r.sendCommand("EXISTS", key) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc expire*(r: TRedis, key: string, seconds: int): bool = ## Set a key's time to live in seconds. Returns `false` if the key could ## not be found or the timeout could not be set. r.sendCommand("EXPIRE", key, $seconds) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc expireAt*(r: TRedis, key: string, timestamp: int): bool = ## Set the expiration for a key as a UNIX timestamp. Returns `false` ## if the key could not be found or the timeout could not be set. r.sendCommand("EXPIREAT", key, $timestamp) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc keys*(r: TRedis, pattern: string): TRedisList = ## Find all keys matching the given pattern r.sendCommand("KEYS", pattern) - return r.parseMultiBulk() + return r.readArray() proc move*(r: TRedis, key: string, db: int): bool = ## Move a key to another database. Returns `true` on a successful move. r.sendCommand("MOVE", key, $db) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc persist*(r: TRedis, key: string): bool = ## Remove the expiration from a key. ## Returns `true` when the timeout was removed. r.sendCommand("PERSIST", key) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc randomKey*(r: TRedis): TRedisString = ## Return a random key from the keyspace r.sendCommand("RANDOMKEY") - return r.parseBulk() + return r.readBulkString() proc rename*(r: TRedis, key, newkey: string): TRedisStatus = ## Rename a key. ## ## **WARNING:** Overwrites `newkey` if it exists! r.sendCommand("RENAME", key, newkey) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc renameNX*(r: TRedis, key, newkey: string): bool = ## Same as ``rename`` but doesn't continue if `newkey` exists. ## Returns `true` if key was renamed. r.sendCommand("RENAMENX", key, newkey) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc ttl*(r: TRedis, key: string): TRedisInteger = ## Get the time to live for a key r.sendCommand("TTL", key) - return r.parseInteger() + return r.readInteger() proc keyType*(r: TRedis, key: string): TRedisStatus = ## Determine the type stored at key r.sendCommand("TYPE", key) - return r.parseStatus() + return r.readStatus() # Strings @@ -239,125 +326,125 @@ proc keyType*(r: TRedis, key: string): TRedisStatus = proc append*(r: TRedis, key, value: string): TRedisInteger = ## Append a value to a key r.sendCommand("APPEND", key, value) - return r.parseInteger() + return r.readInteger() proc decr*(r: TRedis, key: string): TRedisInteger = ## Decrement the integer value of a key by one r.sendCommand("DECR", key) - return r.parseInteger() + return r.readInteger() proc decrBy*(r: TRedis, key: string, decrement: int): TRedisInteger = ## Decrement the integer value of a key by the given number r.sendCommand("DECRBY", key, $decrement) - return r.parseInteger() + return r.readInteger() proc get*(r: TRedis, key: string): TRedisString = ## Get the value of a key. Returns `redisNil` when `key` doesn't exist. r.sendCommand("GET", key) - return r.parseBulk() + return r.readBulkString() proc getBit*(r: TRedis, key: string, offset: int): TRedisInteger = ## Returns the bit value at offset in the string value stored at key r.sendCommand("GETBIT", key, $offset) - return r.parseInteger() + return r.readInteger() proc getRange*(r: TRedis, key: string, start, stop: int): TRedisString = ## Get a substring of the string stored at a key r.sendCommand("GETRANGE", key, $start, $stop) - return r.parseBulk() + return r.readBulkString() proc getSet*(r: TRedis, key: string, value: string): TRedisString = ## Set the string value of a key and return its old value. Returns `redisNil` ## when key doesn't exist. r.sendCommand("GETSET", key, value) - return r.parseBulk() + return r.readBulkString() proc incr*(r: TRedis, key: string): TRedisInteger = ## Increment the integer value of a key by one. r.sendCommand("INCR", key) - return r.parseInteger() + return r.readInteger() proc incrBy*(r: TRedis, key: string, increment: int): TRedisInteger = ## Increment the integer value of a key by the given number r.sendCommand("INCRBY", key, $increment) - return r.parseInteger() + return r.readInteger() proc setk*(r: TRedis, key, value: string) = ## Set the string value of a key. ## ## NOTE: This function had to be renamed due to a clash with the `set` type. r.sendCommand("SET", key, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc setNX*(r: TRedis, key, value: string): bool = ## Set the value of a key, only if the key does not exist. Returns `true` ## if the key was set. r.sendCommand("SETNX", key, value) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc setBit*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Sets or clears the bit at offset in the string value stored at key r.sendCommand("SETBIT", key, $offset, value) - return r.parseInteger() + return r.readInteger() proc setEx*(r: TRedis, key: string, seconds: int, value: string): TRedisStatus = ## Set the value and expiration of a key r.sendCommand("SETEX", key, $seconds, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc setRange*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Overwrite part of a string at key starting at the specified offset r.sendCommand("SETRANGE", key, $offset, value) - return r.parseInteger() + return r.readInteger() proc strlen*(r: TRedis, key: string): TRedisInteger = ## Get the length of the value stored in a key. Returns 0 when key doesn't ## exist. r.sendCommand("STRLEN", key) - return r.parseInteger() + return r.readInteger() # Hashes proc hDel*(r: TRedis, key, field: string): bool = ## Delete a hash field at `key`. Returns `true` if the field was removed. r.sendCommand("HDEL", key, field) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc hExists*(r: TRedis, key, field: string): bool = ## Determine if a hash field exists. r.sendCommand("HEXISTS", key, field) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc hGet*(r: TRedis, key, field: string): TRedisString = ## Get the value of a hash field r.sendCommand("HGET", key, field) - return r.parseBulk() + return r.readBulkString() proc hGetAll*(r: TRedis, key: string): TRedisList = ## Get all the fields and values in a hash r.sendCommand("HGETALL", key) - return r.parseMultiBulk() + return r.readArray() proc hIncrBy*(r: TRedis, key, field: string, incr: int): TRedisInteger = ## Increment the integer value of a hash field by the given number r.sendCommand("HINCRBY", key, field, $incr) - return r.parseInteger() + return r.readInteger() proc hKeys*(r: TRedis, key: string): TRedisList = ## Get all the fields in a hash r.sendCommand("HKEYS", key) - return r.parseMultiBulk() + return r.readArray() proc hLen*(r: TRedis, key: string): TRedisInteger = ## Get the number of fields in a hash r.sendCommand("HLEN", key) - return r.parseInteger() + return r.readInteger() proc hMGet*(r: TRedis, key: string, fields: varargs[string]): TRedisList = ## Get the values of all the given hash fields r.sendCommand("HMGET", key, fields) - return r.parseMultiBulk() + return r.readArray() proc hMSet*(r: TRedis, key: string, fieldValues: openarray[tuple[field, value: string]]) = @@ -367,22 +454,22 @@ proc hMSet*(r: TRedis, key: string, args.add(field) args.add(value) r.sendCommand("HMSET", args) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc hSet*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the string value of a hash field r.sendCommand("HSET", key, field, value) - return r.parseInteger() + return r.readInteger() proc hSetNX*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the value of a hash field, only if the field does **not** exist r.sendCommand("HSETNX", key, field, value) - return r.parseInteger() + return r.readInteger() proc hVals*(r: TRedis, key: string): TRedisList = ## Get all the values in a hash r.sendCommand("HVALS", key) - return r.parseMultiBulk() + return r.readArray() # Lists @@ -393,7 +480,7 @@ proc bLPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = for i in items(keys): args.add(i) args.add($timeout) r.sendCommand("BLPOP", args) - return r.parseMultiBulk() + return r.readArray() proc bRPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = ## Remove and get the *last* element in a list, or block until one @@ -402,7 +489,7 @@ proc bRPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = for i in items(keys): args.add(i) args.add($timeout) r.sendCommand("BRPOP", args) - return r.parseMultiBulk() + return r.readArray() proc bRPopLPush*(r: TRedis, source, destination: string, timeout: int): TRedisString = @@ -411,29 +498,29 @@ proc bRPopLPush*(r: TRedis, source, destination: string, ## ## http://redis.io/commands/brpoplpush r.sendCommand("BRPOPLPUSH", source, destination, $timeout) - return r.parseBulk(true) # Multi-Bulk nil allowed. + return r.readBulkString(true) # Multi-Bulk nil allowed. proc lIndex*(r: TRedis, key: string, index: int): TRedisString = ## Get an element from a list by its index r.sendCommand("LINDEX", key, $index) - return r.parseBulk() + return r.readBulkString() proc lInsert*(r: TRedis, key: string, before: bool, pivot, value: string): TRedisInteger = ## Insert an element before or after another element in a list var pos = if before: "BEFORE" else: "AFTER" r.sendCommand("LINSERT", key, pos, pivot, value) - return r.parseInteger() + return r.readInteger() proc lLen*(r: TRedis, key: string): TRedisInteger = ## Get the length of a list r.sendCommand("LLEN", key) - return r.parseInteger() + return r.readInteger() proc lPop*(r: TRedis, key: string): TRedisString = ## Remove and get the first element in a list r.sendCommand("LPOP", key) - return r.parseBulk() + return r.readBulkString() proc lPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Prepend a value to a list. Returns the length of the list after the push. @@ -444,39 +531,39 @@ proc lPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = r.sendCommand("LPUSH", key, value) else: r.sendCommand("LPUSHX", key, value) - return r.parseInteger() + return r.readInteger() proc lRange*(r: TRedis, key: string, start, stop: int): TRedisList = ## Get a range of elements from a list. Returns `nil` when `key` ## doesn't exist. r.sendCommand("LRANGE", key, $start, $stop) - return r.parseMultiBulk() + return r.readArray() proc lRem*(r: TRedis, key: string, value: string, count: int = 0): TRedisInteger = ## Remove elements from a list. Returns the number of elements that have been ## removed. r.sendCommand("LREM", key, $count, value) - return r.parseInteger() + return r.readInteger() proc lSet*(r: TRedis, key: string, index: int, value: string) = ## Set the value of an element in a list by its index r.sendCommand("LSET", key, $index, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) -proc lTrim*(r: TRedis, key: string, start, stop: int) = +proc lTrim*(r: TRedis, key: string, start, stop: int) = ## Trim a list to the specified range r.sendCommand("LTRIM", key, $start, $stop) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc rPop*(r: TRedis, key: string): TRedisString = ## Remove and get the last element in a list r.sendCommand("RPOP", key) - return r.parseBulk() + return r.readBulkString() proc rPopLPush*(r: TRedis, source, destination: string): TRedisString = ## Remove the last element in a list, append it to another list and return it r.sendCommand("RPOPLPUSH", source, destination) - return r.parseBulk() + return r.readBulkString() proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Append a value to a list. Returns the length of the list after the push. @@ -487,106 +574,106 @@ proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = r.sendCommand("RPUSH", key, value) else: r.sendCommand("RPUSHX", key, value) - return r.parseInteger() + return r.readInteger() # Sets proc sadd*(r: TRedis, key: string, member: string): TRedisInteger = ## Add a member to a set r.sendCommand("SADD", key, member) - return r.parseInteger() + return r.readInteger() proc scard*(r: TRedis, key: string): TRedisInteger = ## Get the number of members in a set r.sendCommand("SCARD", key) - return r.parseInteger() + return r.readInteger() proc sdiff*(r: TRedis, keys: varargs[string]): TRedisList = ## Subtract multiple sets r.sendCommand("SDIFF", keys) - return r.parseMultiBulk() + return r.readArray() proc sdiffstore*(r: TRedis, destination: string, keys: varargs[string]): TRedisInteger = ## Subtract multiple sets and store the resulting set in a key r.sendCommand("SDIFFSTORE", destination, keys) - return r.parseInteger() + return r.readInteger() proc sinter*(r: TRedis, keys: varargs[string]): TRedisList = ## Intersect multiple sets r.sendCommand("SINTER", keys) - return r.parseMultiBulk() + return r.readArray() proc sinterstore*(r: TRedis, destination: string, keys: varargs[string]): TRedisInteger = ## Intersect multiple sets and store the resulting set in a key r.sendCommand("SINTERSTORE", destination, keys) - return r.parseInteger() + return r.readInteger() proc sismember*(r: TRedis, key: string, member: string): TRedisInteger = ## Determine if a given value is a member of a set r.sendCommand("SISMEMBER", key, member) - return r.parseInteger() + return r.readInteger() proc smembers*(r: TRedis, key: string): TRedisList = ## Get all the members in a set r.sendCommand("SMEMBERS", key) - return r.parseMultiBulk() + return r.readArray() proc smove*(r: TRedis, source: string, destination: string, member: string): TRedisInteger = ## Move a member from one set to another r.sendCommand("SMOVE", source, destination, member) - return r.parseInteger() + return r.readInteger() proc spop*(r: TRedis, key: string): TRedisString = ## Remove and return a random member from a set r.sendCommand("SPOP", key) - return r.parseBulk() + return r.readBulkString() proc srandmember*(r: TRedis, key: string): TRedisString = ## Get a random member from a set r.sendCommand("SRANDMEMBER", key) - return r.parseBulk() + return r.readBulkString() proc srem*(r: TRedis, key: string, member: string): TRedisInteger = ## Remove a member from a set r.sendCommand("SREM", key, member) - return r.parseInteger() + return r.readInteger() proc sunion*(r: TRedis, keys: varargs[string]): TRedisList = ## Add multiple sets r.sendCommand("SUNION", keys) - return r.parseMultiBulk() + return r.readArray() proc sunionstore*(r: TRedis, destination: string, key: varargs[string]): TRedisInteger = ## Add multiple sets and store the resulting set in a key r.sendCommand("SUNIONSTORE", destination, key) - return r.parseInteger() + return r.readInteger() # Sorted sets proc zadd*(r: TRedis, key: string, score: int, member: string): TRedisInteger = ## Add a member to a sorted set, or update its score if it already exists r.sendCommand("ZADD", key, $score, member) - return r.parseInteger() + return r.readInteger() proc zcard*(r: TRedis, key: string): TRedisInteger = ## Get the number of members in a sorted set r.sendCommand("ZCARD", key) - return r.parseInteger() + return r.readInteger() proc zcount*(r: TRedis, key: string, min: string, max: string): TRedisInteger = ## Count the members in a sorted set with scores within the given values r.sendCommand("ZCOUNT", key, min, max) - return r.parseInteger() + return r.readInteger() proc zincrby*(r: TRedis, key: string, increment: string, member: string): TRedisString = ## Increment the score of a member in a sorted set r.sendCommand("ZINCRBY", key, increment, member) - return r.parseBulk() + return r.readBulkString() proc zinterstore*(r: TRedis, destination: string, numkeys: string, keys: openarray[string], weights: openarray[string] = [], @@ -605,7 +692,7 @@ proc zinterstore*(r: TRedis, destination: string, numkeys: string, r.sendCommand("ZINTERSTORE", args) - return r.parseInteger() + return r.readInteger() proc zrange*(r: TRedis, key: string, start: string, stop: string, withScores: bool): TRedisList = @@ -614,7 +701,7 @@ proc zrange*(r: TRedis, key: string, start: string, stop: string, r.sendCommand("ZRANGE", key, start, stop) else: r.sendCommand("ZRANGE", "WITHSCORES", key, start, stop) - return r.parseMultiBulk() + return r.readArray() proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, @@ -629,29 +716,29 @@ proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, args.add($limitCount) r.sendCommand("ZRANGEBYSCORE", args) - return r.parseMultiBulk() + return r.readArray() proc zrank*(r: TRedis, key: string, member: string): TRedisString = ## Determine the index of a member in a sorted set r.sendCommand("ZRANK", key, member) - return r.parseBulk() + return r.readBulkString() proc zrem*(r: TRedis, key: string, member: string): TRedisInteger = ## Remove a member from a sorted set r.sendCommand("ZREM", key, member) - return r.parseInteger() + return r.readInteger() proc zremrangebyrank*(r: TRedis, key: string, start: string, stop: string): TRedisInteger = ## Remove all members in a sorted set within the given indexes r.sendCommand("ZREMRANGEBYRANK", key, start, stop) - return r.parseInteger() + return r.readInteger() proc zremrangebyscore*(r: TRedis, key: string, min: string, max: string): TRedisInteger = ## Remove all members in a sorted set within the given scores r.sendCommand("ZREMRANGEBYSCORE", key, min, max) - return r.parseInteger() + return r.readInteger() proc zrevrange*(r: TRedis, key: string, start: string, stop: string, withScore: bool): TRedisList = @@ -660,7 +747,7 @@ proc zrevrange*(r: TRedis, key: string, start: string, stop: string, if withScore: r.sendCommand("ZREVRANGE", "WITHSCORE", key, start, stop) else: r.sendCommand("ZREVRANGE", key, start, stop) - return r.parseMultiBulk() + return r.readArray() proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, @@ -676,18 +763,18 @@ proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, args.add($limitCount) r.sendCommand("ZREVRANGEBYSCORE", args) - return r.parseMultiBulk() + return r.readArray() proc zrevrank*(r: TRedis, key: string, member: string): TRedisString = ## Determine the index of a member in a sorted set, with ## scores ordered from high to low r.sendCommand("ZREVRANK", key, member) - return r.parseBulk() + return r.readBulkString() proc zscore*(r: TRedis, key: string, member: string): TRedisString = ## Get the score associated with the given member in a sorted set r.sendCommand("ZSCORE", key, member) - return r.parseBulk() + return r.readBulkString() proc zunionstore*(r: TRedis, destination: string, numkeys: string, keys: openarray[string], weights: openarray[string] = [], @@ -705,7 +792,7 @@ proc zunionstore*(r: TRedis, destination: string, numkeys: string, r.sendCommand("ZUNIONSTORE", args) - return r.parseInteger() + return r.readInteger() # Pub/Sub @@ -720,7 +807,7 @@ proc psubscribe*(r: TRedis, pattern: openarray[string]): ???? = proc publish*(r: TRedis, channel: string, message: string): TRedisInteger = ## Post a message to a channel r.socket.send("PUBLISH $# $#\c\L" % [channel, message]) - return r.parseInteger() + return r.readInteger() proc punsubscribe*(r: TRedis, [pattern: openarray[string], : string): ???? = ## Stop listening for messages posted to channels matching the given patterns @@ -744,92 +831,96 @@ proc unsubscribe*(r: TRedis, [channel: openarray[string], : string): ???? = proc discardMulti*(r: TRedis) = ## Discard all commands issued after MULTI r.sendCommand("DISCARD") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc exec*(r: TRedis): TRedisList = ## Execute all commands issued after MULTI - r.sendCommand("EXEC") - - return r.parseMultiBulk() + r.sendCommand("EXEC") + r.pipeline.enabled = false + # Will reply with +OK for MULTI/EXEC and +QUEUED for every command + # between, then with the results + return r.flushPipeline(true) + proc multi*(r: TRedis) = ## Mark the start of a transaction block + r.startPipelining() r.sendCommand("MULTI") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc unwatch*(r: TRedis) = ## Forget about all watched keys r.sendCommand("UNWATCH") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc watch*(r: TRedis, key: varargs[string]) = ## Watch the given keys to determine execution of the MULTI/EXEC block r.sendCommand("WATCH", key) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) # Connection proc auth*(r: TRedis, password: string) = ## Authenticate to the server r.sendCommand("AUTH", password) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc echoServ*(r: TRedis, message: string): TRedisString = ## Echo the given string r.sendCommand("ECHO", message) - return r.parseBulk() + return r.readBulkString() proc ping*(r: TRedis): TRedisStatus = ## Ping the server r.sendCommand("PING") - return r.parseStatus() + return r.readStatus() proc quit*(r: TRedis) = ## Close the connection r.sendCommand("QUIT") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc select*(r: TRedis, index: int): TRedisStatus = ## Change the selected database for the current connection r.sendCommand("SELECT", $index) - return r.parseStatus() + return r.readStatus() # Server proc bgrewriteaof*(r: TRedis) = ## Asynchronously rewrite the append-only file r.sendCommand("BGREWRITEAOF") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc bgsave*(r: TRedis) = ## Asynchronously save the dataset to disk r.sendCommand("BGSAVE") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc configGet*(r: TRedis, parameter: string): TRedisList = ## Get the value of a configuration parameter r.sendCommand("CONFIG", "GET", parameter) - return r.parseMultiBulk() + return r.readArray() proc configSet*(r: TRedis, parameter: string, value: string) = ## Set a configuration parameter to the given value r.sendCommand("CONFIG", "SET", parameter, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc configResetStat*(r: TRedis) = ## Reset the stats returned by INFO r.sendCommand("CONFIG", "RESETSTAT") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc dbsize*(r: TRedis): TRedisInteger = ## Return the number of keys in the selected database r.sendCommand("DBSIZE") - return r.parseInteger() + return r.readInteger() proc debugObject*(r: TRedis, key: string): TRedisStatus = ## Get debugging information about a key r.sendCommand("DEBUG", "OBJECT", key) - return r.parseStatus() + return r.readStatus() proc debugSegfault*(r: TRedis) = ## Make the server crash @@ -838,34 +929,34 @@ proc debugSegfault*(r: TRedis) = proc flushall*(r: TRedis): TRedisStatus = ## Remove all keys from all databases r.sendCommand("FLUSHALL") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc flushdb*(r: TRedis): TRedisStatus = ## Remove all keys from the current database r.sendCommand("FLUSHDB") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc info*(r: TRedis): TRedisString = ## Get information and statistics about the server r.sendCommand("INFO") - return r.parseBulk() + return r.readBulkString() proc lastsave*(r: TRedis): TRedisInteger = ## Get the UNIX time stamp of the last successful save to disk r.sendCommand("LASTSAVE") - return r.parseInteger() + return r.readInteger() discard """ proc monitor*(r: TRedis) = ## Listen for all requests received by the server in real time r.socket.send("MONITOR\c\L") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) """ proc save*(r: TRedis) = ## Synchronously save the dataset to disk r.sendCommand("SAVE") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc shutdown*(r: TRedis) = ## Synchronously save the dataset to disk and then shut down the server @@ -877,7 +968,7 @@ proc shutdown*(r: TRedis) = proc slaveof*(r: TRedis, host: string, port: string) = ## Make the server a slave of another instance, or promote it as master r.sendCommand("SLAVEOF", host, port) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = ## Iterator for keys and values in a hash. @@ -890,33 +981,72 @@ iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = else: yield (k, i) k = "" - -when false: - # sorry, deactivated for the test suite - var r = open() - r.auth("pass") +proc someTests(r: TRedis, how: TSendMode):seq[string] = + var list:seq[string] = @[] + if how == pipelined: + r.startPipelining() + elif how == multiple: + r.multi() + r.setk("nim:test", "Testing something.") r.setk("nim:utf8", "こんにちは") r.setk("nim:esc", "\\ths ągt\\") - - echo r.get("nim:esc") - echo r.incr("nim:int") - echo r.incr("nim:int") - echo r.get("nim:int") - echo r.get("nim:utf8") - echo repr(r.get("blahasha")) - echo r.randomKey() - + r.setk("nim:int", "1") + list.add(r.get("nim:esc")) + list.add($(r.incr("nim:int"))) + list.add(r.get("nim:int")) + list.add(r.get("nim:utf8")) + list.add($(r.hSet("test1", "name", "A Test"))) + var res = r.hGetAll("test1") + for r in res: + list.add(r) + list.add(r.get("invalid_key")) + list.add($(r.lpush("mylist","itema"))) + list.add($(r.lpush("mylist","itemb"))) + r.ltrim("mylist",0,1) var p = r.lrange("mylist", 0, -1) + for i in items(p): - echo(" ", i) + if not isNil(i): + list.add(i) - echo(r.debugObject("test")) + list.add(r.debugObject("mylist")) r.configSet("timeout", "299") - for i in items(r.configGet("timeout")): echo ">> ", i + var g = r.configGet("timeout") + for i in items(g): + list.add(i) + + list.add(r.echoServ("BLAH")) + + case how + of normal: + return list + of pipelined: + return r.flushPipeline() + of multiple: + return r.exec() + +proc assertListsIdentical(listA, listB: seq[string]) = + assert(listA.len == listB.len) + var i = 0 + for item in listA: + assert(item == listB[i]) + i = i + 1 + +when isMainModule: + when false: + var r = open() + + # Test with no pipelining + var listNormal = r.someTests(normal) - echo r.echoServ("BLAH") + # Test with pipelining enabled + var listPipelined = r.someTests(pipelined) + assertListsIdentical(listNormal, listPipelined) + # Test with multi/exec() (automatic pipelining) + var listMulti = r.someTests(multiple) + assertListsIdentical(listNormal, listMulti) diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index a6a0faabc..45f837833 100644 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -26,6 +26,8 @@ ## **Warning:** The API of this module is unstable, and therefore is subject ## to change. +include "system/inclrtl" + import sockets, strutils, os, strtabs, asyncio type @@ -82,7 +84,7 @@ type TAsyncScgiState = object handleRequest: proc (client: PAsyncSocket, - input: string, headers: PStringTable) {.closure.} + input: string, headers: PStringTable) {.closure,gcsafe.} asyncServer: PAsyncSocket disp: PDispatcher PAsyncScgiState* = ref TAsyncScgiState @@ -150,7 +152,7 @@ proc writeStatusOkTextContent*(c: TSocket, contentType = "text/html") = "Content-Type: $1\r\L\r\L" % contentType) proc run*(handleRequest: proc (client: TSocket, input: string, - headers: PStringTable): bool {.nimcall.}, + headers: PStringTable): bool {.nimcall,gcsafe.}, port = TPort(4000)) = ## encapsulates the SCGI object and main loop. var s: TScgiState @@ -246,7 +248,8 @@ proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) = s.disp.register(client) proc open*(handleRequest: proc (client: PAsyncSocket, - input: string, headers: PStringTable) {.closure.}, + input: string, headers: PStringTable) {. + closure, gcsafe.}, port = TPort(4000), address = "127.0.0.1", reuseAddr = false): PAsyncScgiState = ## Creates an ``PAsyncScgiState`` object which serves as a SCGI server. diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index b6acc329f..8d96cbaaf 100644 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -24,6 +24,8 @@ ## Asynchronous sockets are supported, however a better alternative is to use ## the `asyncio <asyncio.html>`_ module. +include "system/inclrtl" + {.deadCodeElim: on.} when hostOS == "solaris": @@ -544,7 +546,7 @@ proc acceptAddr*(server: TSocket, client: var TSocket, address: var string) {. else: SSLError("Unknown error") -proc setBlocking*(s: TSocket, blocking: bool) {.tags: [].} +proc setBlocking*(s: TSocket, blocking: bool) {.tags: [], gcsafe.} ## Sets blocking mode on socket when defined(ssl): diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 3b6dc87a5..63622a26c 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -12,6 +12,8 @@ ## interface for Nimrod file objects (`TFile`) and strings. Other modules ## may provide other implementations for this standard stream interface. +include "system/inclrtl" + proc newEIO(msg: string): ref EIO = new(result) result.msg = msg @@ -23,15 +25,15 @@ type ## here shouldn't be used directly. They are ## accessible so that a stream implementation ## can override them. - closeImpl*: proc (s: PStream) {.nimcall, tags: [].} - atEndImpl*: proc (s: PStream): bool {.nimcall, tags: [].} - setPositionImpl*: proc (s: PStream, pos: int) {.nimcall, tags: [].} - getPositionImpl*: proc (s: PStream): int {.nimcall, tags: [].} + closeImpl*: proc (s: PStream) {.nimcall, tags: [], gcsafe.} + atEndImpl*: proc (s: PStream): bool {.nimcall, tags: [], gcsafe.} + setPositionImpl*: proc (s: PStream, pos: int) {.nimcall, tags: [], gcsafe.} + getPositionImpl*: proc (s: PStream): int {.nimcall, tags: [], gcsafe.} readDataImpl*: proc (s: PStream, buffer: pointer, - bufLen: int): int {.nimcall, tags: [FReadIO].} + bufLen: int): int {.nimcall, tags: [FReadIO], gcsafe.} writeDataImpl*: proc (s: PStream, buffer: pointer, bufLen: int) {.nimcall, - tags: [FWriteIO].} - flushImpl*: proc (s: PStream) {.nimcall, tags: [FWriteIO].} + tags: [FWriteIO], gcsafe.} + flushImpl*: proc (s: PStream) {.nimcall, tags: [FWriteIO], gcsafe.} proc flush*(s: PStream) = ## flushes the buffers that the stream `s` might use. diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 2fce235e2..fdff06b2a 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -135,38 +135,38 @@ type months*: int ## The number of months years*: int ## The number of years -proc getTime*(): TTime {.tags: [FTime].} +proc getTime*(): TTime {.tags: [FTime], gcsafe.} ## gets the current calendar time as a UNIX epoch value (number of seconds ## elapsed since 1970) with integer precission. Use epochTime for higher ## resolution. -proc getLocalTime*(t: TTime): TTimeInfo {.tags: [FTime], raises: [].} +proc getLocalTime*(t: TTime): TTimeInfo {.tags: [FTime], raises: [], gcsafe.} ## converts the calendar time `t` to broken-time representation, ## expressed relative to the user's specified time zone. -proc getGMTime*(t: TTime): TTimeInfo {.tags: [FTime], raises: [].} +proc getGMTime*(t: TTime): TTimeInfo {.tags: [FTime], raises: [], gcsafe.} ## converts the calendar time `t` to broken-down time representation, ## expressed in Coordinated Universal Time (UTC). -proc timeInfoToTime*(timeInfo: TTimeInfo): TTime {.tags: [].} +proc timeInfoToTime*(timeInfo: TTimeInfo): TTime {.tags: [], gcsafe.} ## converts a broken-down time structure to ## calendar time representation. The function ignores the specified ## contents of the structure members `weekday` and `yearday` and recomputes ## them from the other information in the broken-down time structure. -proc fromSeconds*(since1970: float): TTime {.tags: [], raises: [].} +proc fromSeconds*(since1970: float): TTime {.tags: [], raises: [], gcsafe.} ## Takes a float which contains the number of seconds since the unix epoch and ## returns a time object. -proc fromSeconds*(since1970: int64): TTime {.tags: [], raises: [].} = +proc fromSeconds*(since1970: int64): TTime {.tags: [], raises: [], gcsafe.} = ## Takes an int which contains the number of seconds since the unix epoch and ## returns a time object. fromSeconds(float(since1970)) -proc toSeconds*(time: TTime): float {.tags: [], raises: [].} +proc toSeconds*(time: TTime): float {.tags: [], raises: [], gcsafe.} ## Returns the time in seconds since the unix epoch. -proc `$` *(timeInfo: TTimeInfo): string {.tags: [], raises: [].} +proc `$` *(timeInfo: TTimeInfo): string {.tags: [], raises: [], gcsafe.} ## converts a `TTimeInfo` object to a string representation. -proc `$` *(time: TTime): string {.tags: [], raises: [].} +proc `$` *(time: TTime): string {.tags: [], raises: [], gcsafe.} ## converts a calendar time to a string representation. proc `-`*(a, b: TTime): int64 {. @@ -189,14 +189,15 @@ proc `==`*(a, b: TTime): bool {. result = a - b == 0 when not defined(JS): - proc getTzname*(): tuple[nonDST, DST: string] {.tags: [FTime], raises: [].} + proc getTzname*(): tuple[nonDST, DST: string] {.tags: [FTime], raises: [], + gcsafe.} ## returns the local timezone; ``nonDST`` is the name of the local non-DST ## timezone, ``DST`` is the name of the local DST timezone. -proc getTimezone*(): int {.tags: [FTime], raises: [].} +proc getTimezone*(): int {.tags: [FTime], raises: [], gcsafe.} ## returns the offset of the local (non-DST) timezone in seconds west of UTC. -proc getStartMilsecs*(): int {.deprecated, tags: [FTime].} +proc getStartMilsecs*(): int {.deprecated, tags: [FTime], gcsafe.} ## get the miliseconds from the start of the program. **Deprecated since ## version 0.8.10.** Use ``epochTime`` or ``cpuTime`` instead. diff --git a/lib/system.nim b/lib/system.nim index 3c342cf9a..4a5d46a7f 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -192,9 +192,49 @@ when defined(nimNewShared): type `shared`* {.magic: "Shared".} guarded* {.magic: "Guarded".} -else: - {.pragma: gcsafe.} -#{.pragma: gcsafe.} + +# comparison operators: +proc `==` *[TEnum: enum](x, y: TEnum): bool {.magic: "EqEnum", noSideEffect.} +proc `==` *(x, y: pointer): bool {.magic: "EqRef", noSideEffect.} +proc `==` *(x, y: string): bool {.magic: "EqStr", noSideEffect.} +proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect.} +proc `==` *(x, y: char): bool {.magic: "EqCh", noSideEffect.} +proc `==` *(x, y: bool): bool {.magic: "EqB", noSideEffect.} +proc `==` *[T](x, y: set[T]): bool {.magic: "EqSet", noSideEffect.} +proc `==` *[T](x, y: ref T): bool {.magic: "EqRef", noSideEffect.} +proc `==` *[T](x, y: ptr T): bool {.magic: "EqRef", noSideEffect.} +proc `==` *[T: proc](x, y: T): bool {.magic: "EqProc", noSideEffect.} + +proc `<=` *[TEnum: enum](x, y: TEnum): bool {.magic: "LeEnum", noSideEffect.} +proc `<=` *(x, y: string): bool {.magic: "LeStr", noSideEffect.} +proc `<=` *(x, y: char): bool {.magic: "LeCh", noSideEffect.} +proc `<=` *[T](x, y: set[T]): bool {.magic: "LeSet", noSideEffect.} +proc `<=` *(x, y: bool): bool {.magic: "LeB", noSideEffect.} +proc `<=` *[T](x, y: ref T): bool {.magic: "LePtr", noSideEffect.} +proc `<=` *(x, y: pointer): bool {.magic: "LePtr", noSideEffect.} + +proc `<` *[TEnum: enum](x, y: TEnum): bool {.magic: "LtEnum", noSideEffect.} +proc `<` *(x, y: string): bool {.magic: "LtStr", noSideEffect.} +proc `<` *(x, y: char): bool {.magic: "LtCh", noSideEffect.} +proc `<` *[T](x, y: set[T]): bool {.magic: "LtSet", noSideEffect.} +proc `<` *(x, y: bool): bool {.magic: "LtB", noSideEffect.} +proc `<` *[T](x, y: ref T): bool {.magic: "LtPtr", noSideEffect.} +proc `<` *[T](x, y: ptr T): bool {.magic: "LtPtr", noSideEffect.} +proc `<` *(x, y: pointer): bool {.magic: "LtPtr", noSideEffect.} + +template `!=` * (x, y: expr): expr {.immediate.} = + ## unequals operator. This is a shorthand for ``not (x == y)``. + not (x == y) + +template `>=` * (x, y: expr): expr {.immediate.} = + ## "is greater or equals" operator. This is the same as ``y <= x``. + y <= x + +template `>` * (x, y: expr): expr {.immediate.} = + ## "is greater" operator. This is the same as ``y < x``. + y < x + +include "system/inclrtl" const NoFakeVars* = defined(NimrodVM) ## true if the backend doesn't support \ ## "fake variables" like 'var EBADF {.importc.}: cint'. @@ -696,47 +736,6 @@ proc `-+-` *[T](x, y: set[T]): set[T] {.magic: "SymDiffSet", noSideEffect.} ## computes the symmetric set difference. This is the same as ## ``(A - B) + (B - A)``, but more efficient. -# comparison operators: -proc `==` *[TEnum: enum](x, y: TEnum): bool {.magic: "EqEnum", noSideEffect.} -proc `==` *(x, y: pointer): bool {.magic: "EqRef", noSideEffect.} -proc `==` *(x, y: string): bool {.magic: "EqStr", noSideEffect.} -proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect.} -proc `==` *(x, y: char): bool {.magic: "EqCh", noSideEffect.} -proc `==` *(x, y: bool): bool {.magic: "EqB", noSideEffect.} -proc `==` *[T](x, y: set[T]): bool {.magic: "EqSet", noSideEffect.} -proc `==` *[T](x, y: ref T): bool {.magic: "EqRef", noSideEffect.} -proc `==` *[T](x, y: ptr T): bool {.magic: "EqRef", noSideEffect.} -proc `==` *[T: proc](x, y: T): bool {.magic: "EqProc", noSideEffect.} - -proc `<=` *[TEnum: enum](x, y: TEnum): bool {.magic: "LeEnum", noSideEffect.} -proc `<=` *(x, y: string): bool {.magic: "LeStr", noSideEffect.} -proc `<=` *(x, y: char): bool {.magic: "LeCh", noSideEffect.} -proc `<=` *[T](x, y: set[T]): bool {.magic: "LeSet", noSideEffect.} -proc `<=` *(x, y: bool): bool {.magic: "LeB", noSideEffect.} -proc `<=` *[T](x, y: ref T): bool {.magic: "LePtr", noSideEffect.} -proc `<=` *(x, y: pointer): bool {.magic: "LePtr", noSideEffect.} - -proc `<` *[TEnum: enum](x, y: TEnum): bool {.magic: "LtEnum", noSideEffect.} -proc `<` *(x, y: string): bool {.magic: "LtStr", noSideEffect.} -proc `<` *(x, y: char): bool {.magic: "LtCh", noSideEffect.} -proc `<` *[T](x, y: set[T]): bool {.magic: "LtSet", noSideEffect.} -proc `<` *(x, y: bool): bool {.magic: "LtB", noSideEffect.} -proc `<` *[T](x, y: ref T): bool {.magic: "LtPtr", noSideEffect.} -proc `<` *[T](x, y: ptr T): bool {.magic: "LtPtr", noSideEffect.} -proc `<` *(x, y: pointer): bool {.magic: "LtPtr", noSideEffect.} - -template `!=` * (x, y: expr): expr {.immediate.} = - ## unequals operator. This is a shorthand for ``not (x == y)``. - not (x == y) - -template `>=` * (x, y: expr): expr {.immediate.} = - ## "is greater or equals" operator. This is the same as ``y <= x``. - y <= x - -template `>` * (x, y: expr): expr {.immediate.} = - ## "is greater" operator. This is the same as ``y < x``. - y < x - proc contains*[T](x: set[T], y: T): bool {.magic: "InSet", noSideEffect.} ## One should overload this proc if one wants to overload the ``in`` operator. ## The parameters are in reverse order! ``a in b`` is a template for @@ -1030,8 +1029,6 @@ template sysAssert(cond: bool, msg: string) = echo "[SYSASSERT] ", msg quit 1 -include "system/inclrtl" - when not defined(JS) and not defined(nimrodVm) and hostOS != "standalone": include "system/cgprocs" @@ -2018,7 +2015,7 @@ when not defined(sysFatal): e.msg = message & arg raise e -proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo".} +proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo", gcsafe.} ## get type information for `x`. Ordinary code should not use this, but ## the `typeinfo` module instead. @@ -2209,10 +2206,14 @@ when not defined(JS): #and not defined(NimrodVM): ## Returns ``false`` if the end of the file has been reached, ``true`` ## otherwise. If ``false`` is returned `line` contains no new data. - proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, - tags: [FWriteIO].} - ## writes the values `x` to `f` and then writes "\n". - ## May throw an IO exception. + when not defined(booting): + proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, + tags: [FWriteIO], gcsafe.} + ## writes the values `x` to `f` and then writes "\n". + ## May throw an IO exception. + else: + proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, + tags: [FWriteIO].} proc getFileSize*(f: TFile): int64 {.tags: [FReadIO], gcsafe.} ## retrieves the file size (in bytes) of `f`. @@ -2935,3 +2936,6 @@ when not defined(booting): template isStatic*(x): expr = compiles(static(x)) # checks whether `x` is a value known at compile-time + +when hasThreadSupport: + when hostOS != "standalone": include "system/sysspawn" diff --git a/lib/system/assign.nim b/lib/system/assign.nim index bed8820be..75c749633 100644 --- a/lib/system/assign.nim +++ b/lib/system/assign.nim @@ -7,10 +7,11 @@ # distribution, for details about the copyright. # -proc genericResetAux(dest: pointer, n: ptr TNimNode) +proc genericResetAux(dest: pointer, n: ptr TNimNode) {.gcsafe.} -proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) -proc genericAssignAux(dest, src: pointer, n: ptr TNimNode, shallow: bool) = +proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) {.gcsafe.} +proc genericAssignAux(dest, src: pointer, n: ptr TNimNode, + shallow: bool) {.gcsafe.} = var d = cast[TAddress](dest) s = cast[TAddress](src) @@ -139,8 +140,8 @@ proc genericAssignOpenArray(dest, src: pointer, len: int, genericAssign(cast[pointer](d +% i*% mt.base.size), cast[pointer](s +% i*% mt.base.size), mt.base) -proc objectInit(dest: pointer, typ: PNimType) {.compilerProc.} -proc objectInitAux(dest: pointer, n: ptr TNimNode) = +proc objectInit(dest: pointer, typ: PNimType) {.compilerProc, gcsafe.} +proc objectInitAux(dest: pointer, n: ptr TNimNode) {.gcsafe.} = var d = cast[TAddress](dest) case n.kind of nkNone: sysAssert(false, "objectInitAux") @@ -184,7 +185,7 @@ else: mixin destroy for i in countup(0, r.len - 1): destroy(r[i]) -proc genericReset(dest: pointer, mt: PNimType) {.compilerProc.} +proc genericReset(dest: pointer, mt: PNimType) {.compilerProc, gcsafe.} proc genericResetAux(dest: pointer, n: ptr TNimNode) = var d = cast[TAddress](dest) case n.kind diff --git a/lib/system/channels.nim b/lib/system/channels.nim index bf949529b..e5535dbdc 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -29,7 +29,7 @@ type region: TMemRegion PRawChannel = ptr TRawChannel TLoadStoreMode = enum mStore, mLoad - TChannel*[TMsg] = TRawChannel ## a channel for thread communication + TChannel* {.gcsafe.}[TMsg] = TRawChannel ## a channel for thread communication const ChannelDeadMask = -2 @@ -49,9 +49,9 @@ proc deinitRawChannel(p: pointer) = deinitSysCond(c.cond) proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel, - mode: TLoadStoreMode) + mode: TLoadStoreMode) {.gcsafe.} proc storeAux(dest, src: pointer, n: ptr TNimNode, t: PRawChannel, - mode: TLoadStoreMode) = + mode: TLoadStoreMode) {.gcsafe.} = var d = cast[TAddress](dest) s = cast[TAddress](src) @@ -209,7 +209,6 @@ proc send*[TMsg](c: var TChannel[TMsg], msg: TMsg) = proc llRecv(q: PRawChannel, res: pointer, typ: PNimType) = # to save space, the generic is as small as possible - acquireSys(q.lock) q.ready = true while q.count <= 0: waitSysCond(q.cond, q.lock) @@ -218,17 +217,29 @@ proc llRecv(q: PRawChannel, res: pointer, typ: PNimType) = releaseSys(q.lock) sysFatal(EInvalidValue, "cannot receive message of wrong type") rawRecv(q, res, typ) - releaseSys(q.lock) proc recv*[TMsg](c: var TChannel[TMsg]): TMsg = ## receives a message from the channel `c`. This blocks until ## a message has arrived! You may use ``peek`` to avoid the blocking. var q = cast[PRawChannel](addr(c)) + acquireSys(q.lock) llRecv(q, addr(result), cast[PNimType](getTypeInfo(result))) + releaseSys(q.lock) + +proc tryRecv*[TMsg](c: var TChannel[TMsg]): tuple[dataAvaliable: bool, + msg: TMsg] = + ## try to receives a message from the channel `c` if available. Otherwise + ## it returns ``(false, default(msg))``. + var q = cast[PRawChannel](addr(c)) + if q.mask != ChannelDeadMask: + lockChannel(q): + llRecv(q, addr(result.msg), cast[PNimType](getTypeInfo(result.msg))) + result.dataAvaliable = true proc peek*[TMsg](c: var TChannel[TMsg]): int = ## returns the current number of messages in the channel `c`. Returns -1 - ## if the channel has been closed. + ## if the channel has been closed. **Note**: This is dangerous to use + ## as it encourages races. It's much better to use ``tryRecv`` instead. var q = cast[PRawChannel](addr(c)) if q.mask != ChannelDeadMask: lockChannel(q): diff --git a/lib/system/embedded.nim b/lib/system/embedded.nim index fad9722c2..661992e81 100644 --- a/lib/system/embedded.nim +++ b/lib/system/embedded.nim @@ -15,14 +15,14 @@ proc chckRange(i, a, b: int): int {.inline, compilerproc.} proc chckRangeF(x, a, b: float): float {.inline, compilerproc.} proc chckNil(p: pointer) {.inline, compilerproc.} -proc pushFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} = nil -proc popFrame {.compilerRtl, inl.} = nil +proc nimFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} = discard +proc popFrame {.compilerRtl, inl.} = discard -proc setFrame(s: PFrame) {.compilerRtl, inl.} = nil -proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} = nil -proc popSafePoint {.compilerRtl, inl.} = nil -proc pushCurrentException(e: ref E_Base) {.compilerRtl, inl.} = nil -proc popCurrentException {.compilerRtl, inl.} = nil +proc setFrame(s: PFrame) {.compilerRtl, inl.} = discard +proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} = discard +proc popSafePoint {.compilerRtl, inl.} = discard +proc pushCurrentException(e: ref E_Base) {.compilerRtl, inl.} = discard +proc popCurrentException {.compilerRtl, inl.} = discard # some platforms have native support for stack traces: const @@ -38,6 +38,6 @@ proc raiseException(e: ref E_Base, ename: CString) {.compilerRtl.} = proc reraiseException() {.compilerRtl.} = sysFatal(ENoExceptionToReraise, "no exception to reraise") -proc WriteStackTrace() = nil +proc writeStackTrace() = discard -proc setControlCHook(hook: proc () {.noconv.}) = nil +proc setControlCHook(hook: proc () {.noconv.}) = discard diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index e11a30e9b..2dc134eaf 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -44,9 +44,15 @@ var # a global variable for the root of all try blocks currException {.rtlThreadVar.}: ref E_Base -proc pushFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} = - s.prev = framePtr - framePtr = s +when defined(nimRequiresNimFrame): + proc nimFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} = + s.prev = framePtr + framePtr = s +else: + proc pushFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} = + # XXX only for backwards compatibility + s.prev = framePtr + framePtr = s proc popFrame {.compilerRtl, inl.} = framePtr = framePtr.prev diff --git a/lib/system/inclrtl.nim b/lib/system/inclrtl.nim index 9831130e2..12eb90162 100644 --- a/lib/system/inclrtl.nim +++ b/lib/system/inclrtl.nim @@ -16,6 +16,8 @@ # -> defined(useNimRtl) or appType == "lib" and not defined(createNimRtl) # 3) Exported into nimrtl. # -> appType == "lib" and defined(createNimRtl) +when not defined(nimNewShared): + {.pragma: gcsafe.} when defined(createNimRtl): when defined(useNimRtl): @@ -24,21 +26,21 @@ when defined(createNimRtl): {.error: "nimrtl must be built as a library!".} when defined(createNimRtl): - {.pragma: rtl, exportc: "nimrtl_$1", dynlib.} + {.pragma: rtl, exportc: "nimrtl_$1", dynlib, gcsafe.} {.pragma: inl.} {.pragma: compilerRtl, compilerproc, exportc: "nimrtl_$1", dynlib.} elif defined(useNimRtl): - when hostOS == "windows": + when defined(windows): const nimrtl* = "nimrtl.dll" - elif hostOS == "macosx": + elif defined(macosx): const nimrtl* = "nimrtl.dylib" else: const nimrtl* = "libnimrtl.so" - {.pragma: rtl, importc: "nimrtl_$1", dynlib: nimrtl.} + {.pragma: rtl, importc: "nimrtl_$1", dynlib: nimrtl, gcsafe.} {.pragma: inl.} {.pragma: compilerRtl, compilerproc, importc: "nimrtl_$1", dynlib: nimrtl.} else: - {.pragma: rtl.} + {.pragma: rtl, gcsafe.} {.pragma: inl, inline.} {.pragma: compilerRtl, compilerproc.} diff --git a/lib/system/repr.nim b/lib/system/repr.nim index 7c1a68bc7..487bac052 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -10,7 +10,7 @@ # The generic ``repr`` procedure. It is an invaluable debugging tool. when not defined(useNimRtl): - proc reprAny(p: pointer, typ: PNimType): string {.compilerRtl.} + proc reprAny(p: pointer, typ: PNimType): string {.compilerRtl, gcsafe.} proc reprInt(x: int64): string {.compilerproc.} = return $x proc reprFloat(x: float): string {.compilerproc.} = return $x @@ -78,7 +78,7 @@ proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} = type PByteArray = ptr array[0.. 0xffff, int8] -proc addSetElem(result: var string, elem: int, typ: PNimType) = +proc addSetElem(result: var string, elem: int, typ: PNimType) {.gcsafe.} = case typ.kind of tyEnum: add result, reprEnum(elem, typ) of tyBool: add result, reprBool(bool(elem)) @@ -147,7 +147,7 @@ when not defined(useNimRtl): for i in 0..cl.indent-1: add result, ' ' proc reprAux(result: var string, p: pointer, typ: PNimType, - cl: var TReprClosure) + cl: var TReprClosure) {.gcsafe.} proc reprArray(result: var string, p: pointer, typ: PNimType, cl: var TReprClosure) = @@ -172,7 +172,7 @@ when not defined(useNimRtl): add result, "]" proc reprRecordAux(result: var string, p: pointer, n: ptr TNimNode, - cl: var TReprClosure) = + cl: var TReprClosure) {.gcsafe.} = case n.kind of nkNone: sysAssert(false, "reprRecordAux") of nkSlot: diff --git a/lib/system/sysspawn.nim b/lib/system/sysspawn.nim index 3a641aba6..dabf35a3e 100644 --- a/lib/system/sysspawn.nim +++ b/lib/system/sysspawn.nim @@ -1,7 +1,18 @@ -# Implements Nimrod's 'spawn'. +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Implements Nimrod's 'spawn'. + +when not defined(NimString): + {.error: "You must not import this module explicitly".} {.push stackTrace:off.} -include system.syslocks when (defined(x86) or defined(amd64)) and defined(gcc): proc cpuRelax {.inline.} = @@ -10,12 +21,12 @@ elif (defined(x86) or defined(amd64)) and defined(vcc): proc cpuRelax {.importc: "YieldProcessor", header: "<windows.h>".} elif defined(intelc): proc cpuRelax {.importc: "_mm_pause", header: "xmmintrin.h".} -else: +elif false: from os import sleep proc cpuRelax {.inline.} = os.sleep(1) -when defined(windows): +when defined(windows) and not defined(gcc): proc interlockedCompareExchange(p: pointer; exchange, comparand: int32): int32 {.importc: "InterlockedCompareExchange", header: "<windows.h>", cdecl.} @@ -35,20 +46,30 @@ type c: TSysCond when defined(posix): stupidLock: TSysLock + counter: int proc createCondVar(): CondVar = initSysCond(result.c) when defined(posix): initSysLock(result.stupidLock) - acquireSys(result.stupidLock) + #acquireSys(result.stupidLock) proc await(cv: var CondVar) = when defined(posix): - waitSysCond(cv.c, cv.stupidLock) + acquireSys(cv.stupidLock) + while cv.counter <= 0: + waitSysCond(cv.c, cv.stupidLock) + dec cv.counter + releaseSys(cv.stupidLock) else: waitSysCondWindows(cv.c) -proc signal(cv: var CondVar) = signalSysCond(cv.c) +proc signal(cv: var CondVar) = + when defined(posix): + acquireSys(cv.stupidLock) + inc cv.counter + releaseSys(cv.stupidLock) + signalSysCond(cv.c) type FastCondVar = object @@ -59,7 +80,7 @@ proc createFastCondVar(): FastCondVar = initSysCond(result.slow.c) when defined(posix): initSysLock(result.slow.stupidLock) - acquireSys(result.slow.stupidLock) + #acquireSys(result.slow.stupidLock) result.event = false result.slowPath = false @@ -70,6 +91,7 @@ proc await(cv: var FastCondVar) = # return # cpuRelax() #cv.slowPath = true + # XXX For some reason this crashes some test programs await(cv.slow) cv.event = false @@ -109,6 +131,7 @@ proc slave(w: ptr Worker) {.thread.} = signal(gSomeReady) await(w.taskArrived) assert(not w.ready) + # shield against spurious wakeups: if w.data != nil: w.f(w, w.data) w.data = nil diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index eb9d2000b..4244bae4c 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -252,10 +252,8 @@ proc nimIntToStr(x: int): string {.compilerRtl.} = proc nimFloatToStr(x: float): string {.compilerproc.} = var buf: array [0..59, char] - c_sprintf(buf, "%#.f", x) - result = $buf - if result[len(result)-1] == '.': - result.add("0") + c_sprintf(buf, "%#.16e", x) + return $buf proc nimInt64ToStr(x: int64): string {.compilerRtl.} = result = newString(sizeof(x)*4) diff --git a/lib/system/threads.nim b/lib/system/threads.nim index ff9ab6cc0..0d52e4d09 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -256,9 +256,9 @@ type ## that **must not** be part of a message! Use ## a ``TThreadId`` for that. when TArg is void: - dataFn: proc () {.nimcall.} + dataFn: proc () {.nimcall, gcsafe.} else: - dataFn: proc (m: TArg) {.nimcall.} + dataFn: proc (m: TArg) {.nimcall, gcsafe.} data: TArg TThreadId*[TArg] = ptr TThread[TArg] ## the current implementation uses ## a pointer as a thread ID. diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 7024943b3..4ce2f11b4 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -581,6 +581,7 @@ const INVALID_FILE_SIZE* = -1'i32 FILE_FLAG_BACKUP_SEMANTICS* = 33554432'i32 + FILE_FLAG_OPEN_REPARSE_POINT* = 0x00200000'i32 # Error Constants const @@ -715,3 +716,6 @@ proc WSASend*(s: TSocketHandle, buf: ptr TWSABuf, bufCount: DWORD, bytesSent: PDWord, flags: DWORD, lpOverlapped: POverlapped, completionProc: POVERLAPPED_COMPLETION_ROUTINE): cint {. stdcall, importc: "WSASend", dynlib: "Ws2_32.dll".} + +proc get_osfhandle*(fd:TFileHandle): THandle {. + importc:"_get_osfhandle", header:"<io.h>".} diff --git a/lib/wrappers/sqlite3.nim b/lib/wrappers/sqlite3.nim index 586f763ae..7b7f0874e 100644 --- a/lib/wrappers/sqlite3.nim +++ b/lib/wrappers/sqlite3.nim @@ -106,15 +106,15 @@ type Pstmt* = ptr Tstmt Tvalue{.pure, final.} = object Pvalue* = ptr Tvalue - PPValue* = ptr Pvalue + PValueArg* = array[0..127, Pvalue] Tcallback* = proc (para1: pointer, para2: int32, para3, para4: cstringArray): int32{.cdecl.} Tbind_destructor_func* = proc (para1: pointer){.cdecl.} Tcreate_function_step_func* = proc (para1: Pcontext, para2: int32, - para3: PPValue){.cdecl.} + para3: PValueArg){.cdecl.} Tcreate_function_func_func* = proc (para1: Pcontext, para2: int32, - para3: PPValue){.cdecl.} + para3: PValueArg){.cdecl.} Tcreate_function_final_func* = proc (para1: Pcontext){.cdecl.} Tresult_func* = proc (para1: pointer){.cdecl.} Tcreate_collation_func* = proc (para1: pointer, para2: int32, para3: pointer, diff --git a/tests/actiontable/tactiontable2.nim b/tests/actiontable/tactiontable2.nim index 878356321..bfeb1c169 100644 --- a/tests/actiontable/tactiontable2.nim +++ b/tests/actiontable/tactiontable2.nim @@ -1,6 +1,6 @@ discard """ line: 21 - errormsg: "invalid type: 'TTable[string, proc (string)]'" + errormsg: "invalid type: 'TTable[string, proc (string){.gcsafe.}]'" """ import tables diff --git a/tests/bind/tnicerrorforsymchoice.nim b/tests/bind/tnicerrorforsymchoice.nim index bc271dcab..4e7a271b3 100644 --- a/tests/bind/tnicerrorforsymchoice.nim +++ b/tests/bind/tnicerrorforsymchoice.nim @@ -1,18 +1,18 @@ discard """ line: 18 - errormsg: "type mismatch: got (proc (TScgi) | proc (PAsyncSocket, PStringTable, string))" + errormsg: "type mismatch: got (proc (TScgi) | proc (PAsyncSocket, PStringTable, string){.gcsafe.})" """ #bug #442 import scgi, sockets, asyncio, strtabs proc handleSCGIRequest[TScgi: TScgiState | PAsyncScgiState](s: TScgi) = - nil + discard proc handleSCGIRequest(client: PAsyncSocket, headers: PStringTable, input: string) = - nil + discard proc test(handle: proc (client: PAsyncSocket, headers: PStringTable, input: string), b: int) = - nil + discard test(handleSCGIRequest) diff --git a/tests/ccgbugs/tcgbug.nim b/tests/ccgbugs/tcgbug.nim index 535424a27..3e4755f2f 100644 --- a/tests/ccgbugs/tcgbug.nim +++ b/tests/ccgbugs/tcgbug.nim @@ -20,7 +20,8 @@ new(a) q(a) # bug #914 -var x = newWideCString("Hello") +when defined(windows): + var x = newWideCString("Hello") echo "success" diff --git a/tests/closure/tclosuremacro.nim b/tests/closure/tclosuremacro.nim new file mode 100644 index 000000000..12e463316 --- /dev/null +++ b/tests/closure/tclosuremacro.nim @@ -0,0 +1,43 @@ +discard """ + output: '''10 +10 +10 +3 +3 +noReturn +6 +''' +""" + +import future + +proc twoParams(x: (int, int) -> int): int = + result = x(5, 5) + +proc oneParam(x: int -> int): int = + x(5) + +proc noParams(x: () -> int): int = + result = x() + +proc noReturn(x: () -> void) = + x() + +proc doWithOneAndTwo(f: (int, int) -> int): int = + f(1,2) + +echo twoParams(proc (a, b): auto = a + b) +echo twoParams((x, y) => x + y) + +echo oneParam(x => x+5) + +echo noParams(() => 3) + +echo doWithOneAndTwo((x, y) => x + y) + +noReturn(() -> void => echo("noReturn")) + +proc pass2(f: (int, int) -> int): (int) -> int = + (x: int) -> int => f(2, x) + +echo pass2((x, y) => x + y)(4) diff --git a/tests/closure/tinvalidclosure.nim b/tests/closure/tinvalidclosure.nim index c06270bfa..06e19df3c 100644 --- a/tests/closure/tinvalidclosure.nim +++ b/tests/closure/tinvalidclosure.nim @@ -1,6 +1,6 @@ discard """ line: 12 - errormsg: "type mismatch: got (proc (int){.closure.})" + errormsg: "type mismatch: got (proc (int){.closure, gcsafe.})" """ proc ugh[T](x: T) {.closure.} = diff --git a/tests/macros/texprcolonexpr.nim b/tests/macros/texprcolonexpr.nim new file mode 100644 index 000000000..3b2c86b77 --- /dev/null +++ b/tests/macros/texprcolonexpr.nim @@ -0,0 +1,19 @@ +discard """ + msg: ''' +Infix + Ident !"=>" + Call + Ident !"name" + Ident !"a" + ExprColonExpr + Ident !"b" + Ident !"cint" + NilLit nil +''' +""" +import macros + +macro def(x: stmt): stmt {.immediate.} = + echo treeRepr(x) + +def name(a, b:cint) => nil diff --git a/tests/manyloc/argument_parser/argument_parser.nim b/tests/manyloc/argument_parser/argument_parser.nim index 1edda4aa0..6f4fb650e 100644 --- a/tests/manyloc/argument_parser/argument_parser.nim +++ b/tests/manyloc/argument_parser/argument_parser.nim @@ -292,9 +292,9 @@ proc parse_parameter(quit_on_failure: bool, param, value: string, raise_or_quit(EInvalidValue, ("parameter $1 requires a " & "float, but $2 can't be parsed into one") % [param, escape(value)]) of PK_EMPTY: - nil + discard of PK_HELP: - nil + discard template build_specification_lookup(): diff --git a/tests/manyloc/keineschweine/lib/estreams.nim b/tests/manyloc/keineschweine/lib/estreams.nim index bd0a2c31a..ecafaed89 100644 --- a/tests/manyloc/keineschweine/lib/estreams.nim +++ b/tests/manyloc/keineschweine/lib/estreams.nim @@ -7,10 +7,6 @@ proc swapEndian16*(outp, inp: pointer) = var o = cast[cstring](outp) o[0] = i[1] o[1] = i[0] -when cpuEndian == bigEndian: - proc bigEndian16(outp, inp: pointer) {.inline.} = copyMem(outp, inp, 2) -else: - proc bigEndian16*(outp, inp: pointer) {.inline.} = swapEndian16(outp, inp) import enet diff --git a/tests/manyloc/nake/nake.nim b/tests/manyloc/nake/nake.nim index eade28c70..4b1b35662 100644 --- a/tests/manyloc/nake/nake.nim +++ b/tests/manyloc/nake/nake.nim @@ -74,7 +74,7 @@ else: echo "Unknown option: ", key, ": ", val of cmdArgument: task = key - else: nil + else: discard if printTaskList or task.isNil or not(tasks.hasKey(task)): echo "Available tasks:" for name, task in pairs(tasks): diff --git a/tests/stdlib/tgetfileinfo.nim b/tests/stdlib/tgetfileinfo.nim new file mode 100644 index 000000000..49a019061 --- /dev/null +++ b/tests/stdlib/tgetfileinfo.nim @@ -0,0 +1,93 @@ +discard """ + output: "" +""" + +import os, strutils +# Cases +# 1 - String : Existing File : Symlink true +# 2 - String : Existing File : Symlink false +# 3 - String : Non-existing File : Symlink true +# 4 - String : Non-existing File : Symlink false +# 5 - Handle : Valid File +# 6 - Handle : Invalid File +# 7 - Handle : Valid Handle +# 8 - Handle : Invalid Handle + +proc genBadFileName(limit = 100): string = + ## Generates a filename of a nonexistant file. + ## Returns "" if generation fails. + result = "a" + var hitLimit = true + + for i in 0..100: + if existsFile(result): + result.add("a") + else: + hitLimit = false + break + if hitLimit: + result = "" + +proc caseOneAndTwo(followLink: bool) = + try: + discard getFileInfo(getAppFilename(), followLink) + #echo("String : Existing File : Symlink $# : Success" % $followLink) + except EOS: + echo("String : Existing File : Symlink $# : Failure" % $followLink) + +proc caseThreeAndFour(followLink: bool) = + var invalidName = genBadFileName() + try: + discard getFileInfo(invalidName, true) + echo("String : Non-existing File : Symlink $# : Failure" % $followLink) + except EOS: + #echo("String : Non-existing File : Symlink $# : Success" % $followLink) + +proc testGetFileInfo = + # Case 1 + caseOneAndTwo(true) + + # Case 2 + caseOneAndTwo(false) + + # Case 3 + caseThreeAndFour(true) + + # Case 4 + caseThreeAndFour(false) + + # Case 5 and 7 + block: + let + testFile = open(getAppFilename()) + testHandle = fileHandle(testFile) + try: + discard getFileInfo(testFile) + #echo("Handle : Valid File : Success") + except EIO: + echo("Handle : Valid File : Failure") + + try: + discard getFileInfo(testHandle) + #echo("Handle : Valid File : Success") + except EIO: + echo("Handle : Valid File : Failure") + + # Case 6 and 8 + block: + let + testFile: TFile = nil + testHandle = TFileHandle(-1) + try: + discard getFileInfo(testFile) + echo("Handle : Invalid File : Failure") + except EIO, EOS: + #echo("Handle : Invalid File : Success") + + try: + discard getFileInfo(testHandle) + echo("Handle : Invalid File : Failure") + except EIO, EOS: + #echo("Handle : Invalid File : Success") + +testGetFileInfo() \ No newline at end of file diff --git a/tests/system/tsysspawnbadarg.nim b/tests/system/tsysspawnbadarg.nim new file mode 100644 index 000000000..ace074602 --- /dev/null +++ b/tests/system/tsysspawnbadarg.nim @@ -0,0 +1,7 @@ +discard """ + line: 7 + errormsg: "'spawn' takes a call expression of type void" + cmd: "nimrod $target --threads:on $options $file" +""" + +spawn(1) diff --git a/tests/testament/categories.nim b/tests/testament/categories.nim index faccfed57..bb9c90d2a 100644 --- a/tests/testament/categories.nim +++ b/tests/testament/categories.nim @@ -151,7 +151,7 @@ proc threadTests(r: var TResults, cat: Category, options: string) = #test "tthreadanalysis" #test "tthreadsort" test "tthreadanalysis2" - test "tthreadanalysis3" + #test "tthreadanalysis3" test "tthreadheapviolation1" # ------------------------- IO tests ------------------------------------------ diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index 757e54889..50d0e6eac 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -48,7 +48,7 @@ type let pegLineError = - peg"{[^(]*} '(' {\d+} ', ' \d+ ') ' ('Error'/'Warning') ':' \s* {.*}" + peg"{[^(]*} '(' {\d+} ', ' \d+ ') ' ('Error') ':' \s* {.*}" pegOtherError = peg"'Error:' \s* {.*}" pegSuccess = peg"'Hint: operation successful'.*" pegOfInterest = pegLineError / pegOtherError @@ -209,7 +209,7 @@ proc testNoSpec(r: var TResults, test: TTest) = # does not extract the spec because the file is not supposed to have any let tname = test.name.addFileExt(".nim") inc(r.total) - echo extractFilename(tname) + styledEcho "Processing ", fgCyan, extractFilename(tname) let given = callCompiler(cmdTemplate, test.name, test.options, test.target) r.addResult(test, "", given.msg, given.err) if given.err == reSuccess: inc(r.passed) diff --git a/tests/threads/tthreadanalysis2.nim b/tests/threads/tthreadanalysis2.nim index 697e2cb22..bcc09db98 100644 --- a/tests/threads/tthreadanalysis2.nim +++ b/tests/threads/tthreadanalysis2.nim @@ -1,7 +1,7 @@ discard """ file: "tthreadanalysis2.nim" - line: 42 - errormsg: "write to foreign heap" + line: 37 + errormsg: "'threadFunc' is not GC-safe" cmd: "nimrod $target --hints:on --threads:on $options $file" """ @@ -10,7 +10,7 @@ import os var thr: array [0..5, TThread[tuple[a, b: int]]] -proc doNothing() = nil +proc doNothing() = discard type PNode = ref TNode diff --git a/tests/threads/tthreadanalysis3.nim b/tests/threads/tthreadanalysis3.nim deleted file mode 100644 index 3c17fe7e2..000000000 --- a/tests/threads/tthreadanalysis3.nim +++ /dev/null @@ -1,51 +0,0 @@ -discard """ - file: "tthreadanalysis3.nim" - line: 35 - errormsg: "write to foreign heap" - cmd: "nimrod $target --hints:on --threads:on $options $file" -""" - -import os - -var - thr: array [0..5, TThread[tuple[a, b: int]]] - -proc doNothing() = nil - -type - PNode = ref TNode - TNode = object {.pure.} - le, ri: PNode - data: string - -var - root: PNode - -proc buildTree(depth: int): PNode = - if depth == 3: return nil - new(result) - result.le = buildTree(depth-1) - result.ri = buildTree(depth-1) - result.data = $depth - -proc echoLeTree(n: PNode) = - var it = n - while it != nil: - echo it.data - it = it.le - -proc threadFunc(interval: tuple[a, b: int]) {.thread.} = - doNothing() - for i in interval.a..interval.b: - var r = buildTree(i) - echoLeTree(r) # for local data - echoLeTree(root) # and the same for foreign data :-) - -proc main = - root = buildTree(5) - for i in 0..high(thr): - createThread(thr[i], threadFunc, (i*100, i*100+50)) - joinThreads(thr) - -main() - diff --git a/web/download.txt b/web/download.txt index f638a43b2..a306e516c 100644 --- a/web/download.txt +++ b/web/download.txt @@ -1,18 +1,12 @@ -Here you can download the latest version of the Nimrod Compiler. -Please choose your platform: -* source-based installation: `<download/nimrod_0.9.2.zip>`_ -* installer for Windows XP/Vista/7 (i386, 32bit): `<download/nimrod_0.9.2.exe>`_ - (includes GCC and everything else you need) -* minimal installer for Windows XP/Vista/7 (i386, 32bit): `<download/nimrod_gamera_0.9.2.exe>`_ - ("Gamera edition": includes only a minimal GCC) - -The source-based installation has been tested on these systems: -* Linux: i386, AMD64, PowerPC, ARM -* BSD: AMD64 -* Mac OS X: i386, AMD64 -* Solaris: AMD64 - -Other UNIX-based operating systems may work. - -.. include:: ../install.txt - +Starting with 0.9.4 we now advise people to build directly from the +github `master <https://github.com/Araq/Nimrod#compiling>`_ branch:: + + git clone git://github.com/Araq/Nimrod.git + cd Nimrod + git clone --depth 1 git://github.com/nimrod-code/csources + cd csources && sh build.sh + cd .. + bin/nimrod c koch + ./koch boot -d:release + +Prebuilt binaries will be available soon. diff --git a/web/news.txt b/web/news.txt index f7a9d05fa..fa1c532e8 100644 --- a/web/news.txt +++ b/web/news.txt @@ -4,17 +4,23 @@ News -2014-XX-XX Version 0.9.4 released +2014-04-21 Version 0.9.4 released ================================= The Nimrod development community is proud to announce the release of version -0.9.4 of the Nimrod compiler and tools. +0.9.4 of the Nimrod compiler and tools. **Note: This release has to be +considered beta quality! Lots of new features have been implemented but most +do not fullfill our quality standards.** + +This release can be downloaded from `github <https://github.com/Araq/Nimrod>`_. +Prebuilt binaries will be available soon. This release includes about 1300 changes in total including various bug fixes, new languages features and standard library additions and improvements. This release brings with it support for user-defined type classes, a brand new VM for executing Nimrod code at compile-time and new symbol binding rules for clean templates. + It also introduces support for the brand new `Babel package manager <https://github.com/nimrod-code/babel>`_ which has itself seen its first release recently. Many of the wrappers that were @@ -63,6 +69,16 @@ capabilities. Note that this feature has been implemented with Nimrod's macro system and so ``await`` and ``async`` are no keywords. +Syntactic sugar for anonymous procedures has also been introduced. It too has +been implemented as a macro. The following shows some simple usage of the new +syntax: + +.. code-block::nimrod + import future + + var s = @[1, 2, 3, 4, 5] + echo(s.map((x: int) => x * 5)) + Library Additions ----------------- @@ -77,6 +93,9 @@ Library Additions - Added module ``selectors``. - Added module ``asynchttpserver``. - Added support for the new asynchronous IO in the ``httpclient`` module. +- Added a Python-inspired ``future`` module that feature upcoming additions + to the ``system`` module. + Changes affecting backwards compatibility ----------------------------------------- @@ -102,6 +121,8 @@ Changes affecting backwards compatibility of sockets given to it. - The ``noStackFrame`` pragma has been renamed to ``asmNoStackFrame`` to ensure you only use it when you know what you're doing. +- Many of the wrappers that were present in the standard library have been + moved to separate repositories and should now be installed using Babel. Compiler Additions @@ -146,6 +167,7 @@ Language Additions of an outer proc. - The experimental ``strongSpaces`` parsing mode has been implemented. - You can annotate pointer types with regions for increased type safety. +- Added support for the builtin ``spawn`` for easy thread pool usage. Tools improvements diff --git a/web/nimrod.ini b/web/nimrod.ini index b29bcff30..14701ecea 100644 --- a/web/nimrod.ini +++ b/web/nimrod.ini @@ -63,7 +63,7 @@ srcdoc2: "pure/asyncio;pure/actors;core/locks;pure/oids;pure/endians;pure/uri" srcdoc2: "pure/nimprof;pure/unittest;packages/docutils/highlite" srcdoc2: "packages/docutils/rst;packages/docutils/rstast" srcdoc2: "packages/docutils/rstgen;pure/logging;pure/asyncdispatch;pure/asyncnet" -srcdoc2: "pure/rawsockets;pure/asynchttpserver;pure/net;pure/selectors" +srcdoc2: "pure/rawsockets;pure/asynchttpserver;pure/net;pure/selectors;pure/future" webdoc: "wrappers/libcurl;pure/md5;wrappers/mysql;wrappers/iup" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc" diff --git a/web/ticker.txt b/web/ticker.txt index a4ddddbbe..691a14575 100644 --- a/web/ticker.txt +++ b/web/ticker.txt @@ -1,5 +1,5 @@ <a class="news" href="news.html#Z2014-XX-XX-version-0-9-4-released"> - <h3>Apr 20, 2014</h3> + <h3>Apr 21, 2014</h3> <p>Nimrod version 0.9.4 has been released!</p> </a> |