diff options
69 files changed, 1714 insertions, 536 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 9c9dfce9a..1dff21503 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -382,6 +382,10 @@ type # sons[0]: type of containing object or tuple # sons[1]: field type # .n: nkDotExpr storing the field name + +static: + # remind us when TTypeKind stops to fit in a single 64-bit word + assert TTypeKind.high.ord <= 63 const tyPureObject* = tyTuple @@ -394,7 +398,7 @@ const tyUserTypeClass, tyUserTypeClassInst, tyAnd, tyOr, tyNot, tyAnything} - tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyStatic, tyExpr} + tyTypeClasses + tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyExpr} + tyTypeClasses type TTypeKinds* = set[TTypeKind] @@ -428,8 +432,9 @@ type tfFromGeneric, # type is an instantiation of a generic; this is needed # because for instantiations of objects, structural # type equality has to be used - tfUnresolved, # marks unresolved typedesc params: e.g. + tfUnresolved, # marks unresolved typedesc/static params: e.g. # proc foo(T: typedesc, list: seq[T]): var T + # proc foo(L: static[int]): array[L, int] tfRetType, # marks return types in proc (used to detect type classes # used as return types for return type inference) tfCapturesEnv, # whether proc really captures some environment @@ -448,6 +453,10 @@ type tfHasStatic tfGenericTypeParam tfImplicitTypeParam + tfWildcard # consider a proc like foo[T, I](x: Type[T, I]) + # T and I here can bind to both typedesc and static types + # before this is determined, we'll consider them to be a + # wildcard type. TTypeFlags* = set[TTypeFlag] @@ -693,7 +702,7 @@ type TSym* {.acyclic.} = object of TIdObj # proc and type instantiations are cached in the generic symbol case kind*: TSymKind - of skType: + of skType, skGenericParam: typeInstCache*: seq[PType] typScope*: PScope of routineKinds: @@ -818,6 +827,9 @@ type counter*: int data*: TObjectSeq + TImplication* = enum + impUnknown, impNo, impYes + # BUGFIX: a module is overloadable so that a proc can have the # same name as an imported module. This is necessary because of # the poor naming choices in the standard library. @@ -865,6 +877,7 @@ const nkCallKinds* = {nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit, nkHiddenCallConv} + nkLiterals* = {nkCharLit..nkTripleStrLit} nkLambdaKinds* = {nkLambda, nkDo} declarativeDefs* = {nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef} procDefs* = nkLambdaKinds + declarativeDefs @@ -957,7 +970,9 @@ var emptyNode* = newNode(nkEmpty) # There is a single empty node that is shared! Do not overwrite it! proc isMetaType*(t: PType): bool = - return t.kind in tyMetaTypes or tfHasMeta in t.flags + return t.kind in tyMetaTypes or + (t.kind == tyStatic and t.n == nil) or + tfHasMeta in t.flags proc linkTo*(t: PType, s: PSym): PType {.discardable.} = t.sym = s @@ -1287,7 +1302,7 @@ proc skipTypes*(t: PType, kinds: TTypeKinds): PType = proc propagateToOwner*(owner, elem: PType) = const HaveTheirOwnEmpty = {tySequence, tySet} owner.flags = owner.flags + (elem.flags * {tfHasShared, tfHasMeta, - tfHasStatic, tfHasGCedMem}) + tfHasGCedMem}) if tfNotNil in elem.flags: if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvokation}: owner.flags.incl tfNotNil @@ -1301,12 +1316,9 @@ proc propagateToOwner*(owner, elem: PType) = if tfShared in elem.flags: owner.flags.incl tfHasShared - if elem.kind in tyMetaTypes: + if elem.isMetaType: owner.flags.incl tfHasMeta - if elem.kind == tyStatic: - owner.flags.incl tfHasStatic - if elem.kind in {tyString, tyRef, tySequence} or elem.kind == tyProc and elem.callConv == ccClosure: owner.flags.incl tfHasGCedMem @@ -1494,6 +1506,9 @@ proc hasPattern*(s: PSym): bool {.inline.} = iterator items*(n: PNode): PNode = for i in 0.. <n.len: yield n.sons[i] +iterator pairs*(n: PNode): tuple[i: int, n: PNode] = + for i in 0.. <n.len: yield (i, n.sons[i]) + proc isAtom*(n: PNode): bool {.inline.} = result = n.kind >= nkNone and n.kind <= nkNilLit diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index cce2cc215..36dd7f562 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -50,7 +50,7 @@ proc strTableGet*(t: TStrTable, name: PIdent): PSym type TTabIter*{.final.} = object # consider all fields here private h*: THash # current hash - + proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym proc nextIter*(ti: var TTabIter, tab: TStrTable): PSym # usage: @@ -157,6 +157,12 @@ proc leValue*(a, b: PNode): bool = #InternalError(a.info, "leValue") discard +proc weakLeValue*(a, b: PNode): TImplication = + if a.kind notin nkLiterals or b.kind notin nkLiterals: + result = impUnknown + else: + result = if leValue(a, b): impYes else: impNo + proc lookupInRecord(n: PNode, field: PIdent): PSym = result = nil case n.kind diff --git a/compiler/canonicalizer.nim b/compiler/canonicalizer.nim index 07e932b28..3bc4eb029 100644 --- a/compiler/canonicalizer.nim +++ b/compiler/canonicalizer.nim @@ -81,7 +81,7 @@ proc hashSym(c: var MD5Context, s: PSym) = proc hashTree(c: var MD5Context, n: PNode) = if n == nil: - c &= "noTreeKind" + c &= "\255" return var k = n.kind md5Update(c, cast[cstring](addr(k)), 1) @@ -107,7 +107,7 @@ proc hashTree(c: var MD5Context, n: PNode) = proc hashType(c: var MD5Context, t: PType) = # modelled after 'typeToString' if t == nil: - c &= "noTypeKind" + c &= "\254" return var k = t.kind @@ -168,7 +168,7 @@ proc canonConst(n: PNode): TUid = c.hashType(n.typ) md5Final(c, MD5Digest(result)) -proc canonSym(s: PSym): TUid +proc canonSym(s: PSym): TUid = var c: MD5Context md5Init(c) c.hashSym(s) diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index da1673ca4..1d8f0158b 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -87,9 +87,10 @@ proc getUniqueType*(key: PType): PType = gCanonicalTypes[k] = key result = key of tyTypeDesc, tyTypeClasses, tyGenericParam, - tyFromExpr, tyStatic, tyFieldAccessor: + tyFromExpr, tyFieldAccessor: internalError("GetUniqueType") - of tyGenericInst, tyDistinct, tyOrdinal, tyMutable, tyConst, tyIter: + of tyGenericInst, tyDistinct, tyOrdinal, tyMutable, + tyConst, tyIter, tyStatic: result = getUniqueType(lastSon(key)) of tyArrayConstr, tyGenericInvokation, tyGenericBody, tyOpenArray, tyArray, tySet, tyRange, tyTuple, diff --git a/compiler/evalffi.nim b/compiler/evalffi.nim index 54be0ccb2..db78da714 100644 --- a/compiler/evalffi.nim +++ b/compiler/evalffi.nim @@ -151,7 +151,7 @@ proc getField(n: PNode; position: int): PSym = else: discard proc packObject(x: PNode, typ: PType, res: pointer) = - InternalAssert x.kind in {nkObjConstr, nkPar} + internalAssert x.kind in {nkObjConstr, nkPar} # compute the field's offsets: discard typ.getSize for i in countup(ord(x.kind == nkObjConstr), sonsLen(x) - 1): diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 12761f1d4..a7ee7f7cc 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -16,7 +16,7 @@ import type TSystemCC* = enum ccNone, ccGcc, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc, - ccTcc, ccPcc, ccUcc, ccIcl, ccGpp + ccTcc, ccPcc, ccUcc, ccIcl TInfoCCProp* = enum # properties of the C compiler: hasSwitchRange, # CC allows ranges in switch statements (GNU C) hasComputedGoto, # CC has computed goto (GNU C extension) @@ -33,11 +33,12 @@ type optSpeed: string, # the options for optimization for speed optSize: string, # the options for optimization for size compilerExe: string, # the compiler's executable + cppCompiler: string, # name of the C++ compiler's executable (if supported) compileTmpl: string, # the compile command template buildGui: string, # command to build a GUI application buildDll: string, # command to build a shared library buildLib: string, # command to build a static library - linkerExe: string, # the linker's executable + linkerExe: string, # the linker's executable (if not matching compiler's) linkTmpl: string, # command to link files to produce an exe includeCmd: string, # command to add an include dir linkDirCmd: string, # command to add a lib dir @@ -63,11 +64,12 @@ compiler gcc: optSpeed: " -O3 -ffast-math ", optSize: " -Os -ffast-math ", compilerExe: "gcc", + cppCompiler: "g++", compileTmpl: "-c $options $include -o $objfile $file", buildGui: " -mwindows", buildDll: " -shared", buildLib: "ar rcs $libfile $objfiles", - linkerExe: "gcc", + linkerExe: "", linkTmpl: "$buildgui $builddll -o $exefile $objfiles $options", includeCmd: " -I", linkDirCmd: " -L", @@ -77,32 +79,21 @@ compiler gcc: asmStmtFrmt: "asm($1);$n", props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm, hasNakedAttribute}) - -compiler gpp: - result = gcc() - - result.name = "gpp" - result.compilerExe = "g++" - result.linkerExe = "g++" - - result.buildDll = " -mdll" - # XXX: Hmm, I'm keeping this from the previos version, - # but my gcc doesn't even have such an option (is this mingw?) compiler llvmGcc: result = gcc() result.name = "llvm_gcc" result.compilerExe = "llvm-gcc" + result.cppCompiler = "llvm-g++" result.buildLib = "llvm-ar rcs $libfile $objfiles" - result.linkerExe = "llvm-gcc" compiler clang: result = llvmGcc() result.name = "clang" result.compilerExe = "clang" - result.linkerExe = "clang" + result.cppCompiler = "clang++" compiler vcc: result = ( @@ -111,6 +102,7 @@ compiler vcc: optSpeed: " /Ogityb2 /G7 /arch:SSE2 ", optSize: " /O1 /G7 ", compilerExe: "cl", + cppCompiler: "cl", compileTmpl: "/c $options $include /Fo$objfile $file", buildGui: " /link /SUBSYSTEM:WINDOWS ", buildDll: " /LD", @@ -131,7 +123,7 @@ compiler icl: result = vcc() else: result = gcc() - + result.name = "icl" result.compilerExe = "icl" result.linkerExe = "icl" @@ -143,6 +135,7 @@ compiler lcc: optSpeed: " -O -p6 ", optSize: " -O -p6 ", compilerExe: "lcc", + cppCompiler: "", compileTmpl: "$options $include -Fo$objfile $file", buildGui: " -subsystem windows", buildDll: " -dll", @@ -164,6 +157,7 @@ compiler bcc: optSpeed: " -O2 -6 ", optSize: " -O1 -6 ", compilerExe: "bcc32", + cppCompiler: "", compileTmpl: "-c $options $include -o$objfile $file", buildGui: " -tW", buildDll: " -tWD", @@ -185,6 +179,7 @@ compiler dmc: optSpeed: " -ff -o -6 ", optSize: " -ff -o -6 ", compilerExe: "dmc", + cppCompiler: "", compileTmpl: "-c $options $include -o$objfile $file", buildGui: " -L/exet:nt/su:windows", buildDll: " -WD", @@ -206,6 +201,7 @@ compiler wcc: optSpeed: " -ox -on -6 -d0 -fp6 -zW ", optSize: "", compilerExe: "wcl386", + cppCompiler: "", compileTmpl: "-c $options $include -fo=$objfile $file", buildGui: " -bw", buildDll: " -bd", @@ -227,6 +223,7 @@ compiler tcc: optSpeed: "", optSize: "", compilerExe: "tcc", + cppCompiler: "", compileTmpl: "-c $options $include -o $objfile $file", buildGui: "UNAVAILABLE!", buildDll: " -shared", @@ -249,6 +246,7 @@ compiler pcc: optSpeed: " -Ox ", optSize: " -Os ", compilerExe: "cc", + cppCompiler: "", compileTmpl: "-c $options $include -Fo$objfile $file", buildGui: " -SUBSYSTEM:WINDOWS", buildDll: " -DLL", @@ -270,6 +268,7 @@ compiler ucc: optSpeed: " -O3 ", optSize: " -O1 ", compilerExe: "cc", + cppCompiler: "", compileTmpl: "-c $options $include -o $objfile $file", buildGui: "", buildDll: " -shared ", @@ -297,8 +296,7 @@ const tcc(), pcc(), ucc(), - icl(), - gpp()] + icl()] const hExt* = ".h" @@ -471,11 +469,21 @@ proc needsExeExt(): bool {.inline.} = result = (optGenScript in gGlobalOptions and targetOS == osWindows) or (platform.hostOS == osWindows) +proc getCompilerExe(compiler: TSystemCC): string = + result = if gCmd == cmdCompileToCpp: CC[compiler].cppCompiler + else: CC[compiler].compilerExe + if result.len == 0: + rawMessage(errCompilerDoesntSupportTarget, CC[compiler].name) + +proc getLinkerExe(compiler: TSystemCC): string = + result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe + else: compiler.getCompilerExe + proc getCompileCFileCmd*(cfilename: string, isExternal = false): string = var c = cCompiler var options = cFileSpecificOptions(cfilename) var exe = getConfigVar(c, ".exe") - if exe.len == 0: exe = CC[c].compilerExe + if exe.len == 0: exe = c.getCompilerExe if needsExeExt(): exe = addFileExt(exe, "exe") if optGenDynLib in gGlobalOptions and @@ -493,7 +501,7 @@ proc getCompileCFileCmd*(cfilename: string, isExternal = false): string = compilePattern = joinPath(ccompilerpath, exe) else: includeCmd = "" - compilePattern = CC[c].compilerExe + compilePattern = c.getCompilerExe var cfile = if noAbsolutePaths(): extractFilename(cfilename) else: cfilename @@ -600,7 +608,7 @@ proc callCCompiler*(projectfile: string) = if optCompileOnly notin gGlobalOptions: execExternalProgram(linkCmd) else: var linkerExe = getConfigVar(c, ".linkerexe") - if len(linkerExe) == 0: linkerExe = CC[c].linkerExe + if len(linkerExe) == 0: linkerExe = c.getLinkerExe if needsExeExt(): linkerExe = addFileExt(linkerExe, "exe") if noAbsolutePaths(): linkCmd = quoteShell(linkerExe) else: linkCmd = quoteShell(joinPath(ccompilerpath, linkerExe)) diff --git a/compiler/guards.nim b/compiler/guards.nim index fe868054f..f475f5068 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -274,10 +274,6 @@ proc pred(n: PNode): PNode = else: result = n -type - TImplication* = enum - impUnknown, impNo, impYes - proc impliesEq(fact, eq: PNode): TImplication = let (loc, val) = if isLocation(eq.sons[1]): (1, 2) else: (2, 1) diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 67cd364dc..4bc8eff86 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -518,7 +518,7 @@ proc searchForInnerProcs(o: POuterContext, n: PNode) = else: internalError(it.info, "transformOuter") of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef, - nkClosure: + nkClosure, nkTypeSection: # don't recurse here: # XXX recurse here and setup 'up' pointers discard diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 4285a090d..217e33675 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -101,6 +101,7 @@ type base10, # base10 is listed as the first element, # so that it is the correct default value base2, base8, base16 + TToken* = object # a Nimrod token tokType*: TTokType # the type of the token indent*: int # the indentation; != -1 if the token has been @@ -110,6 +111,8 @@ type fNumber*: BiggestFloat # the parsed floating point literal base*: TNumericalBase # the numerical base; only valid for int # or float literals + strongSpaceA*: int8 # leading spaces of an operator + strongSpaceB*: int8 # trailing spaces of an operator literal*: string # the parsed (string) literal; and # documentation comments are here too line*, col*: int @@ -119,7 +122,9 @@ type indentAhead*: int # if > 0 an indendation has already been read # this is needed because scanning comments # needs so much look-ahead - + currLineIndent*: int + strongSpaces*: bool + var gLinesCompiled*: int # all lines that have been compiled @@ -184,6 +189,7 @@ proc initToken*(L: var TToken) = L.tokType = tkInvalid L.iNumber = 0 L.indent = 0 + L.strongSpaceA = 0 L.literal = "" L.fNumber = 0.0 L.base = base10 @@ -193,6 +199,7 @@ proc fillToken(L: var TToken) = L.tokType = tkInvalid L.iNumber = 0 L.indent = 0 + L.strongSpaceA = 0 setLen(L.literal, 0) L.fNumber = 0.0 L.base = base10 @@ -202,6 +209,7 @@ proc openLexer(lex: var TLexer, fileIdx: int32, inputstream: PLLStream) = openBaseLexer(lex, inputstream) lex.fileIdx = fileidx lex.indentAhead = - 1 + lex.currLineIndent = 0 inc(lex.lineNumber, inputstream.lineOffset) proc closeLexer(lex: var TLexer) = @@ -635,6 +643,14 @@ proc getOperator(L: var TLexer, tok: var TToken) = h = h !& ord(c) inc(pos) endOperator(L, tok, pos, h) + # advance pos but don't store it in L.bufpos so the next token (which might + # be an operator too) gets the preceeding spaces: + tok.strongSpaceB = 0 + while buf[pos] == ' ': + inc pos + inc tok.strongSpaceB + if buf[pos] in {CR, LF, nimlexbase.EndOfFile}: + tok.strongSpaceB = -1 proc scanComment(L: var TLexer, tok: var TToken) = var pos = L.bufpos @@ -678,10 +694,12 @@ proc scanComment(L: var TLexer, tok: var TToken) = proc skip(L: var TLexer, tok: var TToken) = var pos = L.bufpos var buf = L.buf + tok.strongSpaceA = 0 while true: case buf[pos] of ' ': inc(pos) + inc(tok.strongSpaceA) of Tabulator: lexMessagePos(L, errTabulatorsAreNotAllowed, pos) inc(pos) @@ -692,8 +710,10 @@ proc skip(L: var TLexer, tok: var TToken) = while buf[pos] == ' ': inc(pos) inc(indent) + tok.strongSpaceA = 0 if buf[pos] > ' ': tok.indent = indent + L.currLineIndent = indent break else: break # EndOfFile also leaves the loop @@ -703,6 +723,7 @@ proc rawGetTok(L: var TLexer, tok: var TToken) = fillToken(tok) if L.indentAhead >= 0: tok.indent = L.indentAhead + L.currLineIndent = L.indentAhead L.indentAhead = -1 else: tok.indent = -1 @@ -812,5 +833,5 @@ proc rawGetTok(L: var TLexer, tok: var TToken) = tok.tokType = tkInvalid lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')') inc(L.bufpos) - + dummyIdent = getIdent("") diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 8239f2a47..60125177c 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -127,7 +127,10 @@ proc ensureNoMissingOrUnusedSymbols(scope: PScope) = elif {sfUsed, sfExported} * s.flags == {} and optHints in s.options: # BUGFIX: check options in s! if s.kind notin {skForVar, skParam, skMethod, skUnknown, skGenericParam}: - message(s.info, hintXDeclaredButNotUsed, getSymRepr(s)) + # XXX: implicit type params are currently skTypes + # maybe they can be made skGenericParam as well. + if s.typ != nil and tfImplicitTypeParam notin s.typ.flags: + message(s.info, hintXDeclaredButNotUsed, getSymRepr(s)) s = nextIter(it, scope.symbols) proc wrongRedefinition*(info: TLineInfo, s: string) = diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 66763e7f5..cdafc997b 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -96,8 +96,8 @@ type errOnlyACallOpCanBeDelegator, errUsingNoSymbol, errMacroBodyDependsOnGenericTypes, errDestructorNotGenericEnough, - - errXExpectsTwoArguments, + errInlineIteratorsAsProcParams, + errXExpectsTwoArguments, errXExpectsObjectTypes, errXcanNeverBeOfThisSubtype, errTooManyIterations, errCannotInterpretNodeX, errFieldXNotFound, errInvalidConversionFromTypeX, errAssertionFailed, errCannotGenerateCodeForX, errXRequiresOneArgument, @@ -106,6 +106,9 @@ type errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, errXCannotBeClosure, errXMustBeCompileTime, errCannotInferTypeOfTheLiteral, + errCannotInferReturnType, + errGenericLambdaNotAllowed, + errCompilerDoesntSupportTarget, errUser, warnCannotOpenFile, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, @@ -331,6 +334,8 @@ const "because the parameter '$1' has a generic type", errDestructorNotGenericEnough: "Destructor signarue is too specific. " & "A destructor must be associated will all instantiations of a generic type", + errInlineIteratorsAsProcParams: "inline iterators can be used as parameters only for " & + "templates, macros and other inline iterators", errXExpectsTwoArguments: "\'$1\' expects two arguments", errXExpectsObjectTypes: "\'$1\' expects object types", errXcanNeverBeOfThisSubtype: "\'$1\' can never be of this subtype", @@ -353,6 +358,11 @@ const errXCannotBeClosure: "'$1' cannot have 'closure' calling convention", errXMustBeCompileTime: "'$1' can only be used in compile-time context", errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1", + errCannotInferReturnType: "cannot infer the return type of the proc", + errGenericLambdaNotAllowed: "A nested proc can have generic parameters only when " & + "it is used as an operand to another routine and the types " & + "of the generic paramers can be infered from the expected signature.", + errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target", errUser: "$1", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", @@ -717,19 +727,19 @@ proc handleError(msg: TMsgKind, eh: TErrorHandling, s: string) = writeStackTrace() quit 1 - if msg >= fatalMin and msg <= fatalMax: + if msg >= fatalMin and msg <= fatalMax: quit() - if msg >= errMin and msg <= errMax: + if msg >= errMin and msg <= errMax: inc(gErrorCounter) options.gExitcode = 1'i8 - if gErrorCounter >= gErrorMax: + if gErrorCounter >= gErrorMax: quit() elif eh == doAbort and gCmd != cmdIdeTools: quit() elif eh == doRaise: raiseRecoverableError(s) -proc `==`*(a, b: TLineInfo): bool = +proc `==`*(a, b: TLineInfo): bool = result = a.line == b.line and a.fileIndex == b.fileIndex proc writeContext(lastinfo: TLineInfo) = diff --git a/compiler/parser.nim b/compiler/parser.nim index 5a5bfb574..060629518 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -38,7 +38,6 @@ type inSemiStmtList: int proc parseAll*(p: var TParser): PNode -proc openParser*(p: var TParser, filename: string, inputstream: PLLStream) proc closeParser*(p: var TParser) proc parseTopLevelStmt*(p: var TParser): PNode # implements an iterator. Returns the next top-level statement or @@ -50,7 +49,6 @@ proc parseString*(s: string, filename: string = "", line: int = 0): PNode # correct error messages referring to the original source. # helpers for the other parsers -proc getPrecedence*(tok: TToken): int proc isOperator*(tok: TToken): bool proc getTok*(p: var TParser) proc parMessage*(p: TParser, msg: TMsgKind, arg: string = "") @@ -77,14 +75,17 @@ proc parseCase(p: var TParser): PNode proc getTok(p: var TParser) = rawGetTok(p.lex, p.tok) -proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream) = +proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream, + strongSpaces=false) = initToken(p.tok) openLexer(p.lex, fileIdx, inputStream) getTok(p) # read the first token p.firstTok = true + p.strongSpaces = strongSpaces -proc openParser*(p: var TParser, filename: string, inputStream: PLLStream) = - openParser(p, filename.fileInfoIdx, inputstream) +proc openParser*(p: var TParser, filename: string, inputStream: PLLStream, + strongSpaces=false) = + openParser(p, filename.fileInfoIdx, inputstream, strongSpaces) proc closeParser(p: var TParser) = closeLexer(p.lex) @@ -193,34 +194,52 @@ proc isSigilLike(tok: TToken): bool {.inline.} = proc isLeftAssociative(tok: TToken): bool {.inline.} = result = tok.tokType != tkOpr or relevantOprChar(tok.ident) != '^' -proc getPrecedence(tok: TToken): int = +proc getPrecedence(tok: TToken, strongSpaces: bool): int = + template considerStrongSpaces(x): expr = + x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0) + case tok.tokType of tkOpr: let L = tok.ident.s.len let relevantChar = relevantOprChar(tok.ident) - template considerAsgn(value: expr) = - result = if tok.ident.s[L-1] == '=': 1 else: value + template considerAsgn(value: expr) = + result = if tok.ident.s[L-1] == '=': 1 else: considerStrongSpaces(value) case relevantChar of '$', '^': considerAsgn(10) of '*', '%', '/', '\\': considerAsgn(9) - of '~': result = 8 + of '~': result = considerStrongSpaces(8) of '+', '-', '|': considerAsgn(8) of '&': considerAsgn(7) - of '=', '<', '>', '!': result = 5 + of '=', '<', '>', '!': result = considerStrongSpaces(5) of '.': considerAsgn(6) - of '?': result = 2 + of '?': result = considerStrongSpaces(2) else: considerAsgn(2) of tkDiv, tkMod, tkShl, tkShr: result = 9 of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 - of tkDotDot: result = 6 + of tkDotDot: result = considerStrongSpaces(6) of tkAnd: result = 4 of tkOr, tkXor: result = 3 - else: result = - 10 - -proc isOperator(tok: TToken): bool = - result = getPrecedence(tok) >= 0 + else: result = -10 + +proc isOperator(tok: TToken): bool = + tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, + tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor} + +proc isUnary(p: TParser): bool = + p.strongSpaces and p.tok.tokType in {tkOpr, tkDotDot} and + p.tok.strongSpaceB == 0 and + p.tok.strongSpaceA > 0 + +proc checkBinary(p: TParser) {.inline.} = + # we don't check '..' here as that's too annoying + if p.strongSpaces and p.tok.tokType == tkOpr: + if p.tok.strongSpaceB > 0 and p.tok.strongSpaceA != p.tok.strongSpaceB: + parMessage(p, errGenerated, "number of spaces around '$#' not consistent"% + prettyTok(p.tok)) + elif p.tok.strongSpaceA notin {0,1,2,4,8}: + parMessage(p, errGenerated, "number of spaces must be 0,1,2,4 or 8") #| module = stmt ^* (';' / IND{=}) #| @@ -639,7 +658,7 @@ proc namedParams(p: var TParser, callee: PNode, exprColonEqExprListAux(p, endTok, result) proc parseMacroColon(p: var TParser, x: PNode): PNode -proc primarySuffix(p: var TParser, r: PNode): PNode = +proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode = #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks? #| | doBlocks #| | '.' optInd ('type' | 'addr' | symbol) generalizedLit? @@ -647,9 +666,11 @@ proc primarySuffix(p: var TParser, r: PNode): PNode = #| | '{' optInd indexExprList optPar '}' #| | &( '`'|IDENT|literal|'cast') expr # command syntax result = r - while p.tok.indent < 0: + while p.tok.indent < 0 or + (p.tok.tokType == tkDot and p.tok.indent >= baseIndent): case p.tok.tokType of tkParLe: + if p.strongSpaces and p.tok.strongSpaceA > 0: break result = namedParams(p, result, nkCall, tkParRi) if result.len > 1 and result.sons[1].kind == nkExprColonExpr: result.kind = nkObjConstr @@ -664,8 +685,10 @@ proc primarySuffix(p: var TParser, r: PNode): PNode = result = dotExpr(p, result) result = parseGStrLit(p, result) of tkBracketLe: + if p.strongSpaces and p.tok.strongSpaceA > 0: break result = namedParams(p, result, nkBracketExpr, tkBracketRi) of tkCurlyLe: + if p.strongSpaces and p.tok.strongSpaceA > 0: break result = namedParams(p, result, nkCurlyExpr, tkCurlyRi) of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast: if p.inPragma == 0: @@ -691,14 +714,17 @@ proc primarySuffix(p: var TParser, r: PNode): PNode = break proc primary(p: var TParser, mode: TPrimaryMode): PNode +proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode -proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = - result = primary(p, mode) +proc parseOperators(p: var TParser, headNode: PNode, + limit: int, mode: TPrimaryMode): PNode = + result = headNode # expand while operators have priorities higher than 'limit' - var opPrec = getPrecedence(p.tok) + var opPrec = getPrecedence(p.tok, p.strongSpaces) let modeB = if mode == pmTypeDef: pmTypeDesc else: mode # the operator itself must not start on a new line: - while opPrec >= limit and p.tok.indent < 0: + while opPrec >= limit and p.tok.indent < 0 and not isUnary(p): + checkBinary(p) var leftAssoc = ord(isLeftAssociative(p.tok)) var a = newNodeP(nkInfix, p) var opNode = newIdentNodeP(p.tok.ident, p) # skip operator: @@ -710,7 +736,11 @@ proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = addSon(a, result) addSon(a, b) result = a - opPrec = getPrecedence(p.tok) + opPrec = getPrecedence(p.tok, p.strongSpaces) + +proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode = + result = primary(p, mode) + result = parseOperators(p, result, limit, mode) proc simpleExpr(p: var TParser, mode = pmNormal): PNode = result = simpleExprAux(p, -1, mode) @@ -978,8 +1008,9 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = optInd(p, a) if isSigil: #XXX prefix operators + let baseInd = p.lex.currLineIndent addSon(result, primary(p, pmSkipSuffix)) - result = primarySuffix(p, result) + result = primarySuffix(p, result, baseInd) else: addSon(result, primary(p, pmNormal)) return @@ -1042,9 +1073,10 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode = optInd(p, result) addSon(result, primary(p, pmNormal)) else: + let baseInd = p.lex.currLineIndent result = identOrLiteral(p, mode) if mode != pmSkipSuffix: - result = primarySuffix(p, result) + result = primarySuffix(p, result, baseInd) proc parseTypeDesc(p: var TParser): PNode = #| typeDesc = simpleExpr @@ -1478,7 +1510,7 @@ proc parseSection(p: var TParser, kind: TNodeKind, defparser: TDefParser): PNode = #| section(p) = COMMENT? p / (IND{>} (p / COMMENT)^+IND{=} DED) result = newNodeP(kind, p) - getTok(p) + if kind != nkTypeSection: getTok(p) skipComment(p, result) if realInd(p): withInd(p): @@ -1839,7 +1871,16 @@ proc complexOrSimpleStmt(p: var TParser): PNode = of tkMacro: result = parseRoutine(p, nkMacroDef) of tkTemplate: result = parseRoutine(p, nkTemplateDef) of tkConverter: result = parseRoutine(p, nkConverterDef) - of tkType: result = parseSection(p, nkTypeSection, parseTypeDef) + of tkType: + getTok(p) + if p.tok.tokType == tkParLe: + getTok(p) + result = newNodeP(nkTypeOfExpr, p) + result.addSon(primary(p, pmTypeDesc)) + eat(p, tkParRi) + result = parseOperators(p, result, -1, pmNormal) + else: + result = parseSection(p, nkTypeSection, parseTypeDef) of tkConst: result = parseSection(p, nkConstSection, parseConstant) of tkLet: result = parseSection(p, nkLetSection, parseVariable) of tkWhen: result = parseIfOrWhen(p, nkWhenStmt) @@ -1863,7 +1904,7 @@ proc parseStmt(p: var TParser): PNode = if p.tok.indent < 0 or p.tok.indent == p.currInd: discard else: break else: - if p.tok.indent > p.currInd: + if p.tok.indent > p.currInd and p.tok.tokType != tkDot: parMessage(p, errInvalidIndentation) break if p.tok.tokType in {tkCurlyRi, tkParRi, tkCurlyDotRi, tkBracketRi}: @@ -1890,7 +1931,8 @@ proc parseStmt(p: var TParser): PNode = else: result = newNodeP(nkStmtList, p) while true: - if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) + if p.tok.indent >= 0: + parMessage(p, errInvalidIndentation) let a = simpleStmt(p) if a.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) result.add(a) @@ -1933,7 +1975,9 @@ proc parseString(s: string, filename: string = "", line: int = 0): PNode = stream.lineOffset = line var parser: TParser - openParser(parser, filename, stream) + # XXX for now the builtin 'parseStmt/Expr' functions do not know about strong + # spaces... + openParser(parser, filename, stream, false) result = parser.parseAll closeParser(parser) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 076e99924..fba4e7657 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -97,8 +97,6 @@ proc makeExternImport(s: PSym, extname: string) = incl(s.flags, sfImportc) excl(s.flags, sfForward) -const invalidIdentChars = AllChars - IdentChars - proc validateExternCName(s: PSym, info: TLineInfo) = ## Validates that the symbol name in s.loc.r is a valid C identifier. ## @@ -106,16 +104,14 @@ proc validateExternCName(s: PSym, info: TLineInfo) = ## starting with a number. If the check fails, a generic error will be ## displayed to the user. let target = ropeToStr(s.loc.r) - if target.len < 1 or (not (target[0] in IdentStartChars)) or - (not target.allCharsInSet(IdentChars)): + if target.len < 1 or target[0] notin IdentStartChars or + not target.allCharsInSet(IdentChars): localError(info, errGenerated, "invalid exported symbol") proc makeExternExport(s: PSym, extname: string, info: TLineInfo) = setExternName(s, extname) - case gCmd - of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC: + if gCmd in {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC}: validateExternCName(s, info) - else: discard incl(s.flags, sfExportc) proc processImportCompilerProc(s: PSym, extname: string) = diff --git a/compiler/sem.nim b/compiler/sem.nim index 5ee46654e..093fc9452 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -139,6 +139,10 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = result = n.sym internalAssert sfGenSym in result.flags internalAssert result.kind == kind + # when there is a nested proc inside a template, semtmpl + # will assign a wrong owner during the first pass over the + # template; we must fix it here: see #909 + result.owner = getCurrOwner() else: result = newSym(kind, considerAcc(n), getCurrOwner(), n.info) @@ -194,7 +198,8 @@ proc fixupTypeAfterEval(c: PContext, evaluated, eOrig: PNode): PNode = result = semExprWithType(c, evaluated) else: result = evaluated - semmacrosanity.annotateType(result, eOrig.typ) + let expectedType = eOrig.typ.skipTypes({tyStatic}) + semmacrosanity.annotateType(result, expectedType) else: result = semExprWithType(c, evaluated) #result = fitNode(c, e.typ, result) inlined with special case: @@ -214,14 +219,26 @@ proc tryConstExpr(c: PContext, n: PNode): PNode = result = getConstExpr(c.module, e) if result != nil: return + let oldErrorCount = msgs.gErrorCounter + let oldErrorMax = msgs.gErrorMax + let oldErrorOutputs = errorOutputs + + errorOutputs = {} + msgs.gErrorMax = high(int) + try: result = evalConstExpr(c.module, e) if result == nil or result.kind == nkEmpty: - return nil + result = nil + else: + result = fixupTypeAfterEval(c, result, e) - result = fixupTypeAfterEval(c, result, e) except ERecoverableError: - return nil + result = nil + + msgs.gErrorCounter = oldErrorCount + msgs.gErrorMax = oldErrorMax + errorOutputs = oldErrorOutputs proc semConstExpr(c: PContext, n: PNode): PNode = var e = semExprWithType(c, n) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 2e5def75a..5d480bc98 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -82,7 +82,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: seq[string]) = # fail fast: globalError(n.info, errTypeMismatch, "") var result = msgKindToString(errTypeMismatch) - add(result, describeArgs(c, n, 1 + ord(nfDotField in n.flags))) + add(result, describeArgs(c, n, 1)) add(result, ')') var candidates = "" @@ -114,7 +114,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode, var errors: seq[string] var usedSyms: seq[PNode] - + template pickBest(headSymbol: expr) = pickBestCandidate(c, headSymbol, n, orig, initialBinding, filter, result, alt, errors) @@ -166,7 +166,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode, n.sons[0..1] = [callOp, n[1], calleeName] orig.sons[0..1] = [callOp, orig[1], calleeName] pickBest(callOp) - + if overloadsState == csEmpty and result.state == csEmpty: localError(n.info, errUndeclaredIdentifier, considerAcc(f).s) return @@ -175,9 +175,15 @@ proc resolveOverloads(c: PContext, n, orig: PNode, localError(n.info, errExprXCannotBeCalled, renderTree(n, {renderNoComments})) else: + if {nfDotField, nfDotSetter} * n.flags != {}: + # clean up the inserted ops + n.sons.delete(2) + n.sons[0] = f + errors = @[] pickBest(f) notFoundError(c, n, errors) + return if alt.state == csMatch and cmpCandidates(result, alt) == 0 and diff --git a/compiler/semdata.nim b/compiler/semdata.nim index d942aa41e..088b93fae 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -42,7 +42,7 @@ type TExprFlag* = enum efLValue, efWantIterator, efInTypeof, efWantStmt, efDetermineType, - efAllowDestructor, efWantValue + efAllowDestructor, efWantValue, efOperand TExprFlags* = set[TExprFlag] PContext* = ref TContext @@ -236,17 +236,20 @@ proc makeAndType*(c: PContext, t1, t2: PType): PType = result.sons = @[t1, t2] propagateToOwner(result, t1) propagateToOwner(result, t2) + result.flags.incl((t1.flags + t2.flags) * {tfHasStatic}) proc makeOrType*(c: PContext, t1, t2: PType): PType = result = newTypeS(tyOr, c) result.sons = @[t1, t2] propagateToOwner(result, t1) propagateToOwner(result, t2) + result.flags.incl((t1.flags + t2.flags) * {tfHasStatic}) proc makeNotType*(c: PContext, t1: PType): PType = result = newTypeS(tyNot, c) result.sons = @[t1] propagateToOwner(result, t1) + result.flags.incl(t1.flags * {tfHasStatic}) proc newTypeS(kind: TTypeKind, c: PContext): PType = result = newType(kind, getCurrOwner()) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 203a51816..7e141cb24 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -21,7 +21,7 @@ proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # same as 'semExprWithType' but doesn't check for proc vars - result = semExpr(c, n, flags) + result = semExpr(c, n, flags + {efOperand}) if result.kind == nkEmpty: # do not produce another redundant error message: #raiseRecoverableError("") @@ -117,8 +117,8 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = elif s.ast != nil: result = semExpr(c, s.ast) else: - internalError(n.info, "no default for") - result = emptyNode + n.typ = s.typ + return n of skType: markUsed(n, s) result = newSymNode(s, n.info) @@ -325,8 +325,13 @@ proc isOpImpl(c: PContext, n: PNode): PNode = tfIterator notin t.flags)) else: var t2 = n[2].typ.skipTypes({tyTypeDesc}) + # XXX: liftParamType started to perform addDecl + # we could do that instead in semTypeNode by snooping for added + # gnrc. params, then it won't be necessary to open a new scope here + openScope(c) let lifted = liftParamType(c, skType, newNodeI(nkArgList, n.info), t2, ":anon", n.info) + closeScope(c) if lifted != nil: t2 = lifted var m: TCandidate initCandidate(c, m, t2) @@ -612,7 +617,19 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode = if result.isNil: result = n else: return result result.typ = semfold.getIntervalType(callee.magic, call) - + + block maybeLabelAsStatic: + # XXX: temporary work-around needed for tlateboundstatic. + # This is certainly not correct, but it will get the job + # done until we have a more robust infrastructure for + # implicit statics. + if n.len > 1: + for i in 1 .. <n.len: + if n[i].typ.kind != tyStatic or tfUnresolved notin n[i].typ.flags: + break maybeLabelAsStatic + n.typ = newTypeWithSons(c, tyStatic, @[n.typ]) + n.typ.flags.incl tfUnresolved + # optimization pass: not necessary for correctness of the semantic pass if {sfNoSideEffect, sfCompileTime} * callee.flags != {} and {sfForward, sfImportc} * callee.flags == {}: @@ -885,7 +902,7 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent, of nkSym: if r.sym.name.id == field.id: result = r.sym else: illFormedAst(n) - + proc makeDeref(n: PNode): PNode = var t = skipTypes(n.typ, {tyGenericInst}) result = n @@ -899,6 +916,24 @@ proc makeDeref(n: PNode): PNode = addSon(result, a) t = skipTypes(t.sons[0], {tyGenericInst}) +const tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass} + +proc readTypeParameter(c: PContext, typ: PType, + paramName: PIdent, info: TLineInfo): PNode = + let ty = if typ.kind == tyGenericInst: typ.skipGenericAlias + else: (internalAssert typ.kind == tyCompositeTypeClass; typ.sons[1]) + + let tbody = ty.sons[0] + for s in countup(0, tbody.len-2): + let tParam = tbody.sons[s] + if tParam.sym.name == paramName: + let rawTyp = ty.sons[s + 1] + if rawTyp.kind == tyStatic: + return rawTyp.n + else: + let foundTyp = makeTypeDesc(c, rawTyp) + return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info) + proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = ## returns nil if it's not a built-in field access checkSonsLen(n, 2) @@ -916,7 +951,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = var ty = n.sons[0].typ var f: PSym = nil result = nil - if isTypeExpr(n.sons[0]) or ty.kind == tyTypeDesc and ty.base.kind != tyNone: + if isTypeExpr(n.sons[0]) or (ty.kind == tyTypeDesc and ty.base.kind != tyNone): if ty.kind == tyTypeDesc: ty = ty.base case ty.kind of tyEnum: @@ -925,28 +960,17 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = f = getSymFromList(ty.n, i) if f != nil: break ty = ty.sons[0] # enum inheritance - if f != nil: + if f != nil: result = newSymNode(f) result.info = n.info result.typ = ty markUsed(n, f) return - of tyGenericInst: - assert ty.sons[0].kind == tyGenericBody - let tbody = ty.sons[0] - for s in countup(0, tbody.len-2): - let tParam = tbody.sons[s] - if tParam.sym.name == i: - let rawTyp = ty.sons[s + 1] - if rawTyp.kind == tyStatic: - return rawTyp.n - else: - let foundTyp = makeTypeDesc(c, rawTyp) - return newSymNode(copySym(tParam.sym).linkTo(foundTyp), n.info) - return + of tyTypeParamsHolders: + return readTypeParameter(c, ty, i, n.info) of tyObject, tyTuple: if ty.n.kind == nkRecList: - for field in ty.n.sons: + for field in ty.n: if field.sym.name == i: n.typ = newTypeWithSons(c, tyFieldAccessor, @[ty, field.sym.typ]) n.typ.n = copyTree(n) @@ -958,6 +982,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = # XXX: This is probably not relevant any more # reset to prevent 'nil' bug: see "tests/reject/tenumitems.nim": ty = n.sons[0].typ + return nil ty = skipTypes(ty, {tyGenericInst, tyVar, tyPtr, tyRef}) var check: PNode = nil @@ -990,6 +1015,10 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = n.typ = f.typ result = n + # we didn't find any field, let's look for a generic param + if result == nil and n.sons[0].typ.kind in tyTypeParamsHolders: + result = readTypeParameter(c, n.sons[0].typ, i, n.info) + proc dotTransformation(c: PContext, n: PNode): PNode = if isSymChoice(n.sons[1]): result = newNodeI(nkDotCall, n.info) @@ -1119,6 +1148,9 @@ proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} = n.sons[0] = x # 'result[]' --> 'result' n.sons[1] = takeImplicitAddr(c, ri) +template resultTypeIsInferrable(typ: PType): expr = + typ.isMetaType and typ.kind != tyTypeDesc + proc semAsgn(c: PContext, n: PNode): PNode = checkSonsLen(n, 2) var a = n.sons[0] @@ -1170,7 +1202,7 @@ proc semAsgn(c: PContext, n: PNode): PNode = if lhsIsResult: {efAllowDestructor} else: {}) if lhsIsResult: n.typ = enforceVoidContext - if lhs.sym.typ.isMetaType and lhs.sym.typ.kind != tyTypeDesc: + if resultTypeIsInferrable(lhs.sym.typ): if cmpTypes(c, lhs.typ, rhs.typ) == isGeneric: internalAssert c.p.resultSym != nil lhs.typ = rhs.typ @@ -1206,6 +1238,7 @@ proc semReturn(c: PContext, n: PNode): PNode = proc semProcBody(c: PContext, n: PNode): PNode = openScope(c) + result = semExpr(c, n) if c.p.resultSym != nil and not isEmptyType(result.typ): # transform ``expr`` to ``result = expr``, but not if the expr is already @@ -1229,6 +1262,11 @@ proc semProcBody(c: PContext, n: PNode): PNode = result = semAsgn(c, a) else: discardCheck(c, result) + + if c.p.owner.kind notin {skMacro, skTemplate} and + c.p.resultSym != nil and c.p.resultSym.typ.isMetaType: + localError(c.p.resultSym.info, errCannotInferReturnType) + closeScope(c) proc semYieldVarResult(c: PContext, n: PNode, restype: PType) = @@ -1259,12 +1297,21 @@ proc semYield(c: PContext, n: PNode): PNode = localError(n.info, errYieldNotAllowedInTryStmt) elif n.sons[0].kind != nkEmpty: n.sons[0] = semExprWithType(c, n.sons[0]) # check for type compatibility: - var restype = c.p.owner.typ.sons[0] + var iterType = c.p.owner.typ + var restype = iterType.sons[0] if restype != nil: let adjustedRes = if c.p.owner.kind == skIterator: restype.base else: restype n.sons[0] = fitNode(c, adjustedRes, n.sons[0]) if n.sons[0].typ == nil: internalError(n.info, "semYield") + + if resultTypeIsInferrable(adjustedRes): + let inferred = n.sons[0].typ + if c.p.owner.kind == skIterator: + iterType.sons[0].sons[0] = inferred + else: + iterType.sons[0] = inferred + semYieldVarResult(c, n, adjustedRes) else: localError(n.info, errCannotReturnExpr) @@ -1346,7 +1393,7 @@ proc expectString(c: PContext, n: PNode): string = localError(n.info, errStringLiteralExpected) proc getMagicSym(magic: TMagic): PSym = - result = newSym(skProc, getIdent($magic), getCurrOwner(), gCodegenLineInfo) + result = newSym(skProc, getIdent($magic), systemModule, gCodegenLineInfo) result.magic = magic proc newAnonSym(kind: TSymKind, info: TLineInfo, @@ -1939,7 +1986,8 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkBracketExpr: checkMinSonsLen(n, 1) var s = qualifiedLookUp(c, n.sons[0], {checkUndeclared}) - if s != nil and s.kind in {skProc, skMethod, skConverter}+skIterators: + if (s != nil and s.kind in {skProc, skMethod, skConverter}+skIterators) or + n[0].kind in nkSymChoices: # type parameters: partial generic specialization n.sons[0] = semSymGenericInstantiation(c, n.sons[0], s) result = explicitGenericInstantiation(c, n, s) diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 8faf1d21a..a5149a842 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -15,12 +15,11 @@ proc instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable, if n.kind != nkGenericParams: internalError(n.info, "instantiateGenericParamList; no generic params") newSeq(entry.concreteTypes, n.len) - for i in countup(0, n.len - 1): - var a = n.sons[i] + for i, a in n.pairs: if a.kind != nkSym: internalError(a.info, "instantiateGenericParamList; no symbol") var q = a.sym - if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic}+tyTypeClasses: + if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic, tyIter}+tyTypeClasses: continue var s = newSym(skType, q.name, getCurrOwner(), q.info) s.flags = s.flags + {sfUsed, sfFromGeneric} @@ -86,19 +85,21 @@ proc freshGenSyms(n: PNode, owner: PSym, symMap: var TIdTable) = proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) +proc addProcDecls(c: PContext, fn: PSym) = + # get the proc itself in scope (e.g. for recursion) + addDecl(c, fn) + + for i in 1 .. <fn.typ.n.len: + var param = fn.typ.n.sons[i].sym + param.owner = fn + addParamOrResult(c, param, fn.kind) + + maybeAddResult(c, fn, fn.ast) + proc instantiateBody(c: PContext, n: PNode, result: PSym) = if n.sons[bodyPos].kind != nkEmpty: inc c.inGenericInst # add it here, so that recursive generic procs are possible: - addDecl(c, result) - pushProcCon(c, result) - # add params to scope - for i in 1 .. <result.typ.n.len: - var param = result.typ.n.sons[i].sym - param.owner = result - addParamOrResult(c, param, result.kind) - # debug result.typ.n - maybeAddResult(c, result, n) var b = n.sons[bodyPos] var symMap: TIdTable initIdTable symMap @@ -108,7 +109,6 @@ proc instantiateBody(c: PContext, n: PNode, result: PSym) = n.sons[bodyPos] = transformBody(c.module, b, result) #echo "code instantiated ", result.name.s excl(result.flags, sfForward) - popProcCon(c) dec c.inGenericInst proc fixupInstantiatedSymbols(c: PContext, s: PSym) = @@ -145,11 +145,56 @@ proc instGenericContainer(c: PContext, info: TLineInfo, header: PType, proc instGenericContainer(c: PContext, n: PNode, header: PType): PType = result = instGenericContainer(c, n.info, header) +proc instantiateProcType(c: PContext, pt: TIdTable, + prc: PSym, info: TLineInfo) = + # XXX: Instantiates a generic proc signature, while at the same + # time adding the instantiated proc params into the current scope. + # This is necessary, because the instantiation process may refer to + # these params in situations like this: + # proc foo[Container](a: Container, b: a.type.Item): type(b.x) + # + # Alas, doing this here is probably not enough, because another + # proc signature could appear in the params: + # proc foo[T](a: proc (x: T, b: type(x.y)) + # + # The solution would be to move this logic into semtypinst, but + # at this point semtypinst have to become part of sem, because it + # will need to use openScope, addDecl, etc + # + addDecl(c, prc) + + pushInfoContext(info) + var cl = initTypeVars(c, pt, info) + var result = instCopyType(cl, prc.typ) + let originalParams = result.n + result.n = originalParams.shallowCopy + + for i in 1 .. <result.len: + result.sons[i] = replaceTypeVarsT(cl, result.sons[i]) + propagateToOwner(result, result.sons[i]) + let param = replaceTypeVarsN(cl, originalParams[i]) + result.n.sons[i] = param + if param.kind == nkSym: + # XXX: this won't be true for void params + # implement pass-through of void params and + # the "sort by distance to point" container + param.sym.owner = prc + addDecl(c, param.sym) + + result.sons[0] = replaceTypeVarsT(cl, result.sons[0]) + result.n.sons[0] = originalParams[0].copyTree + + eraseVoidParams(result) + skipIntLiteralParams(result) + + prc.typ = result + maybeAddResult(c, prc, prc.ast) + popInfoContext() + proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, info: TLineInfo): PSym = # no need to instantiate generic templates/macros: if fn.kind in {skTemplate, skMacro}: return fn - # generates an instantiated proc if c.instCounter > 1000: internalError(fn.ast.info, "nesting too deep") inc(c.instCounter) @@ -173,7 +218,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, var entry = TInstantiation.new entry.sym = result instantiateGenericParamList(c, n.sons[genericParamsPos], pt, entry[]) - result.typ = generateTypeInstance(c, pt, info, fn.typ) + pushProcCon(c, result) + instantiateProcType(c, pt, result, info) n.sons[genericParamsPos] = ast.emptyNode var oldPrc = genericCacheGet(fn, entry[]) if oldPrc == nil: @@ -183,12 +229,12 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, pragma(c, result, n.sons[pragmasPos], allRoutinePragmas) if isNil(n.sons[bodyPos]): n.sons[bodyPos] = copyTree(fn.getBody) - if fn.kind != skTemplate: - instantiateBody(c, n, result) - sideEffectsCheck(c, result) + instantiateBody(c, n, result) + sideEffectsCheck(c, result) paramsTypeCheck(c, result.typ) else: result = oldPrc + popProcCon(c) popInfoContext() closeScope(c) # close scope for parameters popOwner() diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index a11386966..15bfaab10 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -661,10 +661,18 @@ proc semFor(c: PContext, n: PNode): PNode = openScope(c) n.sons[length-2] = semExprNoDeref(c, n.sons[length-2], {efWantIterator}) var call = n.sons[length-2] - if call.kind in nkCallKinds and call.sons[0].typ.callConv == ccClosure: + let isCallExpr = call.kind in nkCallKinds + if isCallExpr and call.sons[0].sym.magic != mNone: + if call.sons[0].sym.magic == mOmpParFor: + result = semForVars(c, n) + result.kind = nkParForStmt + else: + result = semForFields(c, n, call.sons[0].sym.magic) + elif (isCallExpr and call.sons[0].typ.callConv == ccClosure) or + call.typ.kind == tyIter: # first class iterator: result = semForVars(c, n) - elif call.kind notin nkCallKinds or call.sons[0].kind != nkSym or + elif not isCallExpr or call.sons[0].kind != nkSym or call.sons[0].sym.kind notin skIterators: if length == 3: n.sons[length-2] = implicitIterator(c, "items", n.sons[length-2]) @@ -673,12 +681,6 @@ proc semFor(c: PContext, n: PNode): PNode = else: localError(n.sons[length-2].info, errIteratorExpected) result = semForVars(c, n) - elif call.sons[0].sym.magic != mNone: - if call.sons[0].sym.magic == mOmpParFor: - result = semForVars(c, n) - result.kind = nkParForStmt - else: - result = semForFields(c, n, call.sons[0].sym.magic) else: result = semForVars(c, n) # propagate any enforced VoidContext: @@ -942,13 +944,15 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s) #if efDetermineType notin flags: # XXX not good enough; see tnamedparamanonproc.nim - if n.sons[genericParamsPos].kind == nkEmpty: + if gp.len == 0 or (gp.len == 1 and tfRetType in gp[0].typ.flags): pushProcCon(c, s) addResult(c, s.typ.sons[0], n.info, skProc) let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) n.sons[bodyPos] = transformBody(c.module, semBody, s) addResultNode(c, n) popProcCon(c) + elif efOperand notin flags: + localError(n.info, errGenericLambdaNotAllowed) sideEffectsCheck(c, s) else: localError(n.info, errImplOfXexpected, s.name.s) diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 31624a97f..363c5246f 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -155,7 +155,11 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) = of nkPragmaExpr: x = x[0] of nkIdent: break else: illFormedAst(x) - c.toInject.incl(x.ident.id) + let ident = getIdentNode(c, x) + if not isTemplParam(c, ident): + c.toInject.incl(x.ident.id) + else: + replaceIdentBySym(n, ident) else: let ident = getIdentNode(c, n) if not isTemplParam(c, ident): @@ -348,7 +352,9 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = of nkMethodDef: result = semRoutineInTemplBody(c, n, skMethod) of nkIteratorDef: - result = semRoutineInTemplBody(c, n, n[namePos].sym.kind) + let kind = if hasPragma(n[pragmasPos], wClosure): skClosureIterator + else: skIterator + result = semRoutineInTemplBody(c, n, kind) of nkTemplateDef: result = semRoutineInTemplBody(c, n, skTemplate) of nkMacroDef: @@ -357,6 +363,8 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = result = semRoutineInTemplBody(c, n, skConverter) of nkPragmaExpr: result.sons[0] = semTemplBody(c, n.sons[0]) + of nkPostfix: + result.sons[1] = semTemplBody(c, n.sons[1]) of nkPragma: discard else: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index a619de7ff..2f1532e6a 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -147,26 +147,38 @@ proc semDistinct(c: PContext, n: PNode, prev: PType): PType = else: result = newConstraint(c, tyDistinct) -proc semRangeAux(c: PContext, n: PNode, prev: PType): PType = +proc semRangeAux(c: PContext, n: PNode, prev: PType): PType = assert isRange(n) checkSonsLen(n, 3) result = newOrPrevType(tyRange, prev, c) result.n = newNodeI(nkRange, n.info) - if (n[1].kind == nkEmpty) or (n[2].kind == nkEmpty): + if (n[1].kind == nkEmpty) or (n[2].kind == nkEmpty): localError(n.info, errRangeIsEmpty) - var a = semConstExpr(c, n[1]) - var b = semConstExpr(c, n[2]) - if not sameType(a.typ, b.typ): + + var range: array[2, PNode] + range[0] = semExprWithType(c, n[1], {efDetermineType}) + range[1] = semExprWithType(c, n[2], {efDetermineType}) + + var rangeT: array[2, PType] + for i in 0..1: rangeT[i] = range[i].typ.skipTypes({tyStatic}).skipIntLit + + if not sameType(rangeT[0], rangeT[1]): localError(n.info, errPureTypeMismatch) - elif a.typ.kind notin {tyInt..tyInt64,tyEnum,tyBool,tyChar, - tyFloat..tyFloat128,tyUInt8..tyUInt32}: + elif not rangeT[0].isOrdinalType: localError(n.info, errOrdinalTypeExpected) - elif enumHasHoles(a.typ): - localError(n.info, errEnumXHasHoles, a.typ.sym.name.s) - elif not leValue(a, b): localError(n.info, errRangeIsEmpty) - addSon(result.n, a) - addSon(result.n, b) - addSonSkipIntLit(result, b.typ) + elif enumHasHoles(rangeT[0]): + localError(n.info, errEnumXHasHoles, rangeT[0].sym.name.s) + + for i in 0..1: + if hasGenericArguments(range[i]): + result.n.addSon makeStaticExpr(c, range[i]) + else: + result.n.addSon semConstExpr(c, range[i]) + + if weakLeValue(result.n[0], result.n[1]) == impNo: + localError(n.info, errRangeIsEmpty) + + addSonSkipIntLit(result, rangeT[0]) proc semRange(c: PContext, n: PNode, prev: PType): PType = result = nil @@ -191,6 +203,14 @@ proc nMinusOne(n: PNode): PNode = newSymNode(getSysMagic("<", mUnaryLt)), n]) +proc makeRangeWithStaticExpr(c: PContext, n: PNode): PType = + let intType = getSysType tyInt + result = newTypeS(tyRange, c) + result.sons = @[intType] + result.n = newNode(nkRange, n.info, @[ + newIntTypeNode(nkIntLit, 0, intType), + makeStaticExpr(c, n.nMinusOne)]) + proc semArray(c: PContext, n: PNode, prev: PType): PType = var indx, base: PType result = newOrPrevType(tyArray, prev, c) @@ -200,7 +220,7 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = else: let e = semExprWithType(c, n.sons[1], {efDetermineType}) if e.typ.kind == tyFromExpr: - indx = e.typ + indx = makeRangeWithStaticExpr(c, e.typ.n) elif e.kind in {nkIntLit..nkUInt64Lit}: indx = makeRangeType(c, 0, e.intVal-1, n.info, e.typ) elif e.kind == nkSym and e.typ.kind == tyStatic: @@ -208,7 +228,7 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = internalAssert c.inGenericContext > 0 if not isOrdinalType(e.typ.lastSon): localError(n[1].info, errOrdinalTypeExpected) - indx = e.typ + indx = makeRangeWithStaticExpr(c, e) elif e.kind in nkCallKinds and hasGenericArguments(e): if not isOrdinalType(e.typ): localError(n[1].info, errOrdinalTypeExpected) @@ -217,12 +237,7 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType = # We are going to construct a range type that will be # properly filled-out in semtypinst (see how tyStaticExpr # is handled there). - let intType = getSysType(tyInt) - indx = newTypeS(tyRange, c) - indx.sons = @[intType] - indx.n = newNode(nkRange, n.info, @[ - newIntTypeNode(nkIntLit, 0, intType), - makeStaticExpr(c, e.nMinusOne)]) + indx = makeRangeWithStaticExpr(c, e) else: indx = e.typ.skipTypes({tyTypeDesc}) addSonSkipIntLit(result, indx) @@ -271,6 +286,18 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = result = result.typ.sym.copySym result.typ = copyType(result.typ, result.typ.owner, true) result.typ.flags.incl tfUnresolved + + if result.kind == skGenericParam: + if result.typ.kind == tyGenericParam and result.typ.len == 0 and + tfWildcard in result.typ.flags: + # collapse the wild-card param to a type + result.kind = skType + result.typ.flags.excl tfWildcard + return + else: + localError(n.info, errTypeExpected) + return errorSym(c, n) + if result.kind != skType: # this implements the wanted ``var v: V, x: V`` feature ... var ov: TOverloadIter @@ -602,15 +629,24 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType = incl(result.flags, tfFinal) proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) = - if kind == skMacro and param.typ.kind notin {tyTypeDesc, tyStatic}: - # within a macro, every param has the type PNimrodNode! - # and param.typ.kind in {tyTypeDesc, tyExpr, tyStmt}: - let nn = getSysSym"PNimrodNode" - var a = copySym(param) - a.typ = nn.typ - if sfGenSym notin a.flags: addDecl(c, a) + template addDecl(x) = + if sfGenSym notin x.flags: addDecl(c, x) + + if kind == skMacro: + if param.typ.kind == tyTypeDesc: + addDecl(param) + elif param.typ.kind == tyStatic: + var a = copySym(param) + a.typ = param.typ.base + addDecl(a) + else: + # within a macro, every param has the type PNimrodNode! + let nn = getSysSym"PNimrodNode" + var a = copySym(param) + a.typ = nn.typ + addDecl(a) else: - if sfGenSym notin param.flags: addDecl(c, param) + addDecl(param) let typedescId = getIdent"typedesc" @@ -644,6 +680,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, s.position = genericParams.len genericParams.addSon(newSymNode(s)) result = typeClass + addDecl(c, s) # XXX: There are codegen errors if this is turned into a nested proc template liftingWalk(typ: PType, anonFlag = false): expr = @@ -667,11 +704,13 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, of tyStatic: # proc(a: expr{string}, b: expr{nkLambda}) # overload on compile time values and AST trees + if paramType.n != nil: return # this is a concrete type + if tfUnresolved in paramType.flags: return # already lifted let base = paramType.base.maybeLift if base.isMetaType and procKind == skMacro: localError(info, errMacroBodyDependsOnGenericTypes, paramName) result = addImplicitGeneric(c.newTypeWithSons(tyStatic, @[base])) - result.flags.incl tfHasStatic + result.flags.incl({tfHasStatic, tfUnresolved}) of tyTypeDesc: if tfUnresolved notin paramType.flags: @@ -716,12 +755,21 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result.kind = tyUserTypeClassInst result.rawAddSon paramType.lastSon return addImplicitGeneric(result) - + result = instGenericContainer(c, paramType.sym.info, result, allowMetaTypes = true) result = newTypeWithSons(c, tyCompositeTypeClass, @[paramType, result]) result = addImplicitGeneric(result) - + + of tyIter: + if paramType.callConv == ccInline: + if procKind notin {skTemplate, skMacro, skIterator}: + localError(info, errInlineIteratorsAsProcParams) + if paramType.len == 1: + let lifted = liftingWalk(paramType.base) + if lifted != nil: paramType.sons[0] = lifted + result = addImplicitGeneric(paramType) + of tyGenericInst: if paramType.lastSon.kind == tyUserTypeClass: var cp = copyType(paramType, getCurrOwner(), false) @@ -735,7 +783,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = paramType result.lastSon.shouldHaveMeta - let liftBody = liftingWalk(paramType.lastSon) + let liftBody = liftingWalk(paramType.lastSon, true) if liftBody != nil: result = liftBody result.shouldHaveMeta @@ -747,7 +795,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, let expanded = instGenericContainer(c, info, paramType, allowMetaTypes = true) - result = liftingWalk(expanded) + result = liftingWalk(expanded, true) of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot: result = addImplicitGeneric(copyType(paramType, getCurrOwner(), true)) @@ -757,12 +805,11 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = addImplicitGeneric(newTypeS(tyAnything, c)) of tyGenericParam: - if tfGenericTypeParam in paramType.flags and false: - if paramType.sonsLen > 0: - result = liftingWalk(paramType.lastSon) - else: - result = addImplicitGeneric(newTypeS(tyAnything, c)) - + markUsed(genericParams, paramType.sym) + if tfWildcard in paramType.flags: + paramType.flags.excl tfWildcard + paramType.sym.kind = skType + else: discard # result = liftingWalk(paramType) @@ -774,8 +821,8 @@ proc semParamType(c: PContext, n: PNode, constraint: var PNode): PType = else: result = semTypeNode(c, n, nil) -proc semProcTypeNode(c: PContext, n, genericParams: PNode, - prev: PType, kind: TSymKind): PType = +proc semProcTypeNode(c: PContext, n, genericParams: PNode, + prev: PType, kind: TSymKind): PType = var res: PNode cl: TIntSet @@ -840,9 +887,13 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, addParamOrResult(c, arg, kind) if gCmd == cmdPretty: checkDef(a.sons[j], arg) - + var r: PType if n.sons[0].kind != nkEmpty: - var r = semTypeNode(c, n.sons[0], nil) + r = semTypeNode(c, n.sons[0], nil) + elif kind == skIterator: + r = newTypeS(tyAnything, c) + + if r != nil: # turn explicit 'void' return type into 'nil' because the rest of the # compiler only checks for 'nil': if skipTypes(r, {tyGenericInst}).kind != tyEmpty: @@ -852,10 +903,20 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if lifted != nil: r = lifted r.flags.incl tfRetType r = skipIntLit(r) - if kind == skIterator: r = newTypeWithSons(c, tyIter, @[r]) + if kind == skIterator: + # see tchainediterators + # in cases like iterator foo(it: iterator): type(it) + # we don't need to change the return type to iter[T] + if not r.isInlineIterator: r = newTypeWithSons(c, tyIter, @[r]) result.sons[0] = r res.typ = r + if genericParams != nil: + for n in genericParams: + if tfWildcard in n.sym.typ.flags: + n.sym.kind = skType + n.sym.typ.flags.excl tfWildcard + proc semStmtListType(c: PContext, n: PNode, prev: PType): PType = checkMinSonsLen(n, 1) var length = sonsLen(n) @@ -884,6 +945,11 @@ proc semGenericParamInInvokation(c: PContext, n: PNode): PType = result = semTypeNode(c, n, nil) proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = + if s.typ == nil: + localError(n.info, "cannot instantiate the '$1' $2" % + [s.name.s, ($s.kind).substr(2).toLower]) + return newOrPrevType(tyError, prev, c) + result = newOrPrevType(tyGenericInvokation, prev, c) addSonSkipIntLit(result, s.typ) @@ -893,10 +959,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = rawAddSon(result, typ) else: addSonSkipIntLit(result, typ) - if s.typ == nil: - localError(n.info, errCannotInstantiateX, s.name.s) - return newOrPrevType(tyError, prev, c) - elif s.typ.kind == tyForward: + if s.typ.kind == tyForward: for i in countup(1, sonsLen(n)-1): var elem = semGenericParamInInvokation(c, n.sons[i]) addToResult(elem) @@ -906,7 +969,6 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = localError(n.info, errNoGenericParamsAllowedForX, s.name.s) return newOrPrevType(tyError, prev, c) else: - var m = newCandidate(c, s, n) matches(c, n, copyTree(n), m) @@ -923,7 +985,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = let typ = m.call[i].typ.skipTypes({tyTypeDesc}) if containsGenericType(typ): isConcrete = false addToResult(typ) - + if isConcrete: if s.ast == nil: localError(n.info, errCannotInstantiateX, s.name.s) @@ -984,7 +1046,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkTypeOfExpr: # for ``type(countup(1,3))``, see ``tests/ttoseq``. checkSonsLen(n, 1) - result = semExprWithType(c, n.sons[0], {efInTypeof}).typ.skipTypes({tyIter}) + let typExpr = semExprWithType(c, n.sons[0], {efInTypeof}) + result = typExpr.typ.skipTypes({tyIter}) of nkPar: if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev) else: @@ -1051,13 +1114,17 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = for i in countup(1, n.len - 1): result.rawAddSon(semTypeNode(c, n.sons[i], nil)) else: result = semGeneric(c, n, s, prev) - of nkIdent, nkDotExpr, nkAccQuoted: - if n.kind == nkDotExpr: - let head = qualifiedLookUp(c, n[0], {checkAmbiguity, checkUndeclared}) - if head.kind in {skType}: - var toBind = initIntSet() - var preprocessed = semGenericStmt(c, n, {}, toBind) - return makeTypeFromExpr(c, preprocessed) + of nkDotExpr: + var typeExpr = semExpr(c, n) + if typeExpr.typ.kind != tyTypeDesc: + localError(n.info, errTypeExpected) + return errorType(c) + result = typeExpr.typ.base + if result.isMetaType: + var toBind = initIntSet() + var preprocessed = semGenericStmt(c, n, {}, toBind) + return makeTypeFromExpr(c, preprocessed) + of nkIdent, nkAccQuoted: var s = semTypeIdent(c, n) if s.typ == nil: if s.kind != skError: localError(n.info, errTypeExpected) @@ -1103,8 +1170,12 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = newConstraint(c, tyIter) else: result = semProcTypeWithScope(c, n, prev, skClosureIterator) - result.flags.incl(tfIterator) - result.callConv = ccClosure + if n.lastSon.kind == nkPragma and hasPragma(n.lastSon, wInline): + result.kind = tyIter + result.callConv = ccInline + else: + result.flags.incl(tfIterator) + result.callConv = ccClosure of nkProcTy: if n.sonsLen == 0: result = newConstraint(c, tyProc) @@ -1219,6 +1290,7 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode = if typ == nil: typ = newTypeS(tyGenericParam, c) + if father == nil: typ.flags.incl tfWildcard typ.flags.incl tfGenericTypeParam @@ -1229,8 +1301,7 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode = # type for each generic param. the index # of the parameter will be stored in the # attached symbol. - var s = case finalType.kind - of tyStatic: + var s = if finalType.kind == tyStatic or tfWildcard in typ.flags: newSymG(skGenericParam, a.sons[j], c).linkTo(finalType) else: newSymG(skType, a.sons[j], c).linkTo(finalType) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 22edc6e32..4a8a463f5 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -80,7 +80,7 @@ type proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym -proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode): PNode +proc replaceTypeVarsN*(cl: var TReplTypeVars, n: PNode): PNode template checkMetaInvariants(cl: TReplTypeVars, t: PType) = when false: @@ -96,8 +96,11 @@ proc replaceTypeVarsT*(cl: var TReplTypeVars, t: PType): PType = checkMetaInvariants(cl, result) proc prepareNode(cl: var TReplTypeVars, n: PNode): PNode = + let t = replaceTypeVarsT(cl, n.typ) + if t != nil and t.kind == tyStatic and t.n != nil: + return t.n result = copyNode(n) - result.typ = replaceTypeVarsT(cl, n.typ) + result.typ = t if result.kind == nkSym: result.sym = replaceTypeVarsS(cl, n.sym) let isCall = result.kind in nkCallKinds for i in 0 .. <n.safeLen: @@ -181,7 +184,8 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode): PNode = of nkStaticExpr: var n = prepareNode(cl, n) n = reResolveCallsWithTypedescParams(cl, n) - result = cl.c.semExpr(cl.c, n) + result = if cl.allowMetaTypes: n + else: cl.c.semExpr(cl.c, n) else: var length = sonsLen(n) if length > 0: @@ -196,10 +200,10 @@ proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym = result = copySym(s, false) incl(result.flags, sfFromGeneric) idTablePut(cl.symMap, s, result) - result.typ = replaceTypeVarsT(cl, s.typ) result.owner = s.owner + result.typ = replaceTypeVarsT(cl, s.typ) result.ast = replaceTypeVarsN(cl, s.ast) - + proc lookupTypeVar(cl: TReplTypeVars, t: PType): PType = result = PType(idTableGet(cl.typeMap, t)) if result == nil: @@ -209,7 +213,7 @@ proc lookupTypeVar(cl: TReplTypeVars, t: PType): PType = elif result.kind == tyGenericParam and not cl.allowMetaTypes: internalError(cl.info, "substitution with generic parameter") -proc instCopyType(cl: var TReplTypeVars, t: PType): PType = +proc instCopyType*(cl: var TReplTypeVars, t: PType): PType = # XXX: relying on allowMetaTypes is a kludge result = copyType(t, t.owner, cl.allowMetaTypes) result.flags.incl tfFromGeneric @@ -280,7 +284,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType = rawAddSon(result, newbody) checkPartialConstructedType(cl.info, newbody) -proc eraseVoidParams(t: PType) = +proc eraseVoidParams*(t: PType) = if t.sons[0] != nil and t.sons[0].kind == tyEmpty: t.sons[0] = nil @@ -297,7 +301,7 @@ proc eraseVoidParams(t: PType) = setLen t.n.sons, pos return -proc skipIntLiteralParams(t: PType) = +proc skipIntLiteralParams*(t: PType) = for i in 0 .. <t.sonsLen: let p = t.sons[i] if p == nil: continue @@ -305,6 +309,11 @@ proc skipIntLiteralParams(t: PType) = if skipped != p: t.sons[i] = skipped if i > 0: t.n.sons[i].sym.typ = skipped + + # when the typeof operator is used on a static input + # param, the results gets infected with static as well: + if t.sons[0] != nil and t.sons[0].kind == tyStatic: + t.sons[0] = t.sons[0].base proc propagateFieldFlags(t: PType, n: PNode) = # This is meant for objects and tuples @@ -314,16 +323,15 @@ proc propagateFieldFlags(t: PType, n: PNode) = of nkSym: propagateToOwner(t, n.sym.typ) of nkRecList, nkRecCase, nkOfBranch, nkElse: - if n.sons != nil: - for son in n.sons: - propagateFieldFlags(t, son) + for son in n: + propagateFieldFlags(t, son) else: discard proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = result = t if t == nil: return - if t.kind in {tyStatic, tyGenericParam} + tyTypeClasses: + if t.kind in {tyStatic, tyGenericParam, tyIter} + tyTypeClasses: let lookup = PType(idTableGet(cl.typeMap, t)) if lookup != nil: return lookup @@ -336,6 +344,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = result = replaceTypeVarsT(cl, lastSon(t)) of tyFromExpr: + if cl.allowMetaTypes: return var n = prepareNode(cl, t.n) n = cl.c.semConstExpr(cl.c, n) if n.typ.kind == tyTypeDesc: @@ -388,21 +397,11 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = propagateToOwner(result, result.sons[i]) result.n = replaceTypeVarsN(cl, result.n) - - # XXX: This is not really needed? - # if result.kind in GenericTypes: - # localError(cl.info, errCannotInstantiateX, typeToString(t, preferName)) - + case result.kind of tyArray: let idx = result.sons[0] - if idx.kind == tyStatic: - if idx.n == nil: - let lookup = lookupTypeVar(cl, idx) - internalAssert lookup != nil - idx.n = lookup.n - - result.sons[0] = makeRangeType(cl.c, 0, idx.n.intVal - 1, idx.n.info) + internalAssert idx.kind != tyStatic of tyObject, tyTuple: propagateFieldFlags(result, result.n) @@ -413,7 +412,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType = else: discard -proc initTypeVars(p: PContext, pt: TIdTable, info: TLineInfo): TReplTypeVars = +proc initTypeVars*(p: PContext, pt: TIdTable, info: TLineInfo): TReplTypeVars = initIdTable(result.symMap) copyIdTable(result.typeMap, pt) initIdTable(result.localCache) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index c0898ef26..662268380 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -522,8 +522,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = template bindingRet(res) = when res == isGeneric: - let bound = aOrig.skipTypes({tyRange}).skipIntLit - put(c.bindings, f, bound) + if doBind: + let bound = aOrig.skipTypes({tyRange}).skipIntLit + if doBind: put(c.bindings, f, bound) return res template considerPreviousT(body: stmt) {.immediate.} = @@ -620,8 +621,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = else: fRange = prev result = typeRel(c, f.sons[1], a.sons[1]) - if result < isGeneric: result = isNone - elif lengthOrd(fRange) != lengthOrd(a): result = isNone + if result < isGeneric: + result = isNone + elif lengthOrd(fRange) != lengthOrd(a): + result = isNone else: discard of tyOpenArray, tyVarargs: case a.kind @@ -867,7 +870,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = # any value" and what we need is "match any type", which can be encoded # by a tyTypeDesc params. Unfortunately, this requires more substantial # changes in semtypinst and elsewhere. - if a.kind == tyTypeDesc: + if tfWildcard in a.flags: + result = isGeneric + elif a.kind == tyTypeDesc: if f.sonsLen == 0: result = isGeneric else: @@ -883,11 +888,16 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = result = isGeneric if result == isGeneric: - var concrete = concreteType(c, a) - if concrete == nil: - result = isNone + var concrete = a + if tfWildcard in a.flags: + a.sym.kind = skType + a.flags.excl tfWildcard else: - if doBind: put(c.bindings, f, concrete) + concrete = concreteType(c, a) + if concrete == nil: + return isNone + if doBind: + put(c.bindings, f, concrete) elif a.kind == tyEmpty: result = isGeneric elif x.kind == tyGenericParam: @@ -937,8 +947,19 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = of tyProxy: result = isEqual + + of tyFromExpr: + # fix the expression, so it contains the already instantiated types + let instantiated = replaceTypesInBody(c.c, c.bindings, f.n) + let reevaluted = c.c.semExpr(c.c, instantiated) + if reevaluted.typ.kind != tyTypeDesc: + localError(f.n.info, errTypeExpected) + result = isNone + else: + result = typeRel(c, a, reevaluted.typ.base) - else: internalAssert false + else: + internalAssert false proc cmpTypes*(c: PContext, f, a: PType): TTypeRelation = var m: TCandidate @@ -1014,6 +1035,10 @@ proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType, result.typ = getInstantiatedType(c, arg, m, base(f)) m.baseTypeMatch = true +proc isInlineIterator*(t: PType): bool = + result = t.kind == tyIter or + (t.kind == tyBuiltInTypeClass and t.base.kind == tyIter) + proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, argSemantized, argOrig: PNode): PNode = var @@ -1021,17 +1046,31 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, arg = argSemantized argType = argType c = m.c - + if tfHasStatic in fMaybeStatic.flags: # XXX: When implicit statics are the default # this will be done earlier - we just have to # make sure that static types enter here - var evaluated = c.semTryConstExpr(c, arg) - if evaluated != nil: - arg.typ = newTypeS(tyStatic, c) - arg.typ.sons = @[evaluated.typ] - arg.typ.n = evaluated - argType = arg.typ + + # XXX: weaken tyGenericParam and call it tyGenericPlaceholder + # and finally start using tyTypedesc for generic types properly. + if argType.kind == tyGenericParam and tfWildcard in argType.flags: + argType.assignType(f) + # put(m.bindings, f, argType) + return argSemantized + + if argType.kind == tyStatic: + if m.calleeSym.kind == skType: + result = newNodeI(nkType, argOrig.info) + result.typ = makeTypeFromExpr(c, arg) + return + else: + var evaluated = c.semTryConstExpr(c, arg) + if evaluated != nil: + arg.typ = newTypeS(tyStatic, c) + arg.typ.sons = @[evaluated.typ] + arg.typ.n = evaluated + argType = arg.typ var a = if c.inTypeClass > 0: argType.skipTypes({tyTypeDesc, tyFieldAccessor}) @@ -1060,7 +1099,14 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, return arg.typ.n else: return argOrig - + + if r != isNone and f.isInlineIterator: + var inlined = newTypeS(tyStatic, c) + inlined.sons = @[argType] + inlined.n = argSemantized + put(m.bindings, f, inlined) + return argSemantized + case r of isConvertible: inc(m.convMatches) @@ -1188,7 +1234,9 @@ proc prepareOperand(c: PContext; formal: PType; a: PNode): PNode = # a.typ == nil is valid result = a elif a.typ.isNil: - result = c.semOperand(c, a, {efDetermineType}) + let flags = if formal.kind == tyIter: {efDetermineType, efWantIterator} + else: {efDetermineType} + result = c.semOperand(c, a, flags) else: result = a diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim index 7c44ec0b4..478c2a837 100644 --- a/compiler/syntaxes.nim +++ b/compiler/syntaxes.nim @@ -17,14 +17,15 @@ type TFilterKind* = enum filtNone, filtTemplate, filtReplace, filtStrip TParserKind* = enum - skinStandard, skinBraces, skinEndX + skinStandard, skinStrongSpaces, skinBraces, skinEndX const - parserNames*: array[TParserKind, string] = ["standard", "braces", "endx"] - filterNames*: array[TFilterKind, string] = ["none", "stdtmpl", "replace", - "strip"] + parserNames*: array[TParserKind, string] = ["standard", "strongspaces", + "braces", "endx"] + filterNames*: array[TFilterKind, string] = ["none", "stdtmpl", "replace", + "strip"] -type +type TParsers*{.final.} = object skin*: TParserKind parser*: TParser @@ -54,7 +55,7 @@ proc parseFile(fileIdx: int32): PNode = proc parseAll(p: var TParsers): PNode = case p.skin - of skinStandard: + of skinStandard, skinStrongSpaces: result = parser.parseAll(p.parser) of skinBraces: result = pbraces.parseAll(p.parser) @@ -65,7 +66,7 @@ proc parseAll(p: var TParsers): PNode = proc parseTopLevelStmt(p: var TParsers): PNode = case p.skin - of skinStandard: + of skinStandard, skinStrongSpaces: result = parser.parseTopLevelStmt(p.parser) of skinBraces: result = pbraces.parseTopLevelStmt(p.parser) @@ -170,7 +171,9 @@ proc openParsers(p: var TParsers, fileIdx: int32, inputstream: PLLStream) = else: s = inputstream case p.skin of skinStandard, skinBraces, skinEndX: - parser.openParser(p.parser, fileIdx, s) + parser.openParser(p.parser, fileIdx, s, false) + of skinStrongSpaces: + parser.openParser(p.parser, fileIdx, s, true) -proc closeParsers(p: var TParsers) = +proc closeParsers(p: var TParsers) = parser.closeParser(p.parser) diff --git a/compiler/transf.nim b/compiler/transf.nim index f4b716c5b..9586398c9 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -425,7 +425,7 @@ proc findWrongOwners(c: PTransf, n: PNode) = x.sym.owner.name.s & " " & getCurrOwner(c).name.s) else: for i in 0 .. <safeLen(n): findWrongOwners(c, n.sons[i]) - + proc transformFor(c: PTransf, n: PNode): PTransNode = # generate access statements for the parameters (unless they are constant) # put mapping from formal parameters to actual parameters @@ -433,12 +433,13 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = var length = sonsLen(n) var call = n.sons[length - 2] - if call.kind notin nkCallKinds or call.sons[0].kind != nkSym or - call.sons[0].sym.kind != skIterator: + if call.typ.kind != tyIter and + (call.kind notin nkCallKinds or call.sons[0].kind != nkSym or + call.sons[0].sym.kind != skIterator): n.sons[length-1] = transformLoopBody(c, n.sons[length-1]).PNode return lambdalifting.liftForLoop(n).PTransNode #InternalError(call.info, "transformFor") - + #echo "transforming: ", renderTree(n) result = newTransNode(nkStmtList, n.info, 0) var loopBody = transformLoopBody(c, n.sons[length-1]) @@ -459,6 +460,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = for i in countup(1, sonsLen(call) - 1): var arg = transform(c, call.sons[i]).PNode var formal = skipTypes(iter.typ, abstractInst).n.sons[i].sym + if arg.typ.kind == tyIter: continue case putArgInto(arg, formal.typ) of paDirectMapping: idNodeTablePut(newC.mapping, formal, arg) @@ -480,7 +482,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode = dec(c.inlining) popInfoContext() popTransCon(c) - #echo "transformed: ", renderTree(n) + # echo "transformed: ", result.PNode.renderTree proc getMagicOp(call: PNode): TMagic = if call.sons[0].kind == nkSym and diff --git a/compiler/types.nim b/compiler/types.nim index 8cf91da8b..89b15c4a8 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1255,15 +1255,18 @@ proc getSize(typ: PType): BiggestInt = if result < 0: internalError("getSize: " & $typ.kind) proc containsGenericTypeIter(t: PType, closure: PObject): bool = - if t.kind in GenericTypes + tyTypeClasses + {tyFromExpr}: - return true + if t.kind == tyStatic: + return t.n == nil if t.kind == tyTypeDesc: if t.base.kind == tyNone: return true if containsGenericTypeIter(t.base, closure): return true return false - - return t.kind == tyStatic and t.n == nil + + if t.kind in GenericTypes + tyTypeClasses + {tyFromExpr}: + return true + + return false proc containsGenericType*(t: PType): bool = result = iterOverType(t, containsGenericTypeIter, nil) diff --git a/compiler/vm.nim b/compiler/vm.nim index f9b143bce..5c8e533f1 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -425,7 +425,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = decodeBC(rkNode) let src = regs[rb].node if src.kind notin {nkEmpty..nkNilLit}: - regs[ra].node = src.sons[rc] + let n = src.sons[rc] + regs[ra].node = n else: stackTrace(c, tos, pc, errIndexOutOfBounds) of opcWrObj: diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index b1a751723..d3eda5db3 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -308,12 +308,20 @@ proc genAndOr(c: PCtx; n: PNode; opc: TOpcode; dest: var TDest) = c.gen(n.sons[2], dest) c.patch(L1) -proc nilLiteral(n: PNode): PNode = - result = n +proc canonConst(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] = canonConst(n.sons[i]) + else: + result = n proc rawGenLiteral(c: PCtx; n: PNode): int = result = c.constants.len - c.constants.add n.nilLiteral + c.constants.add n.canonConst internalAssert result < 0x7fff proc sameConstant*(a, b: PNode): bool = @@ -329,10 +337,10 @@ proc sameConstant*(a, b: PNode): bool = of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal of nkType: result = a.typ == b.typ of nkEmpty, nkNilLit: result = true - else: - if sonsLen(a) == sonsLen(b): - for i in countup(0, sonsLen(a) - 1): - if not sameConstant(a.sons[i], b.sons[i]): return + else: + if sonsLen(a) == sonsLen(b): + for i in countup(0, sonsLen(a) - 1): + if not sameConstant(a.sons[i], b.sons[i]): return result = true proc genLiteral(c: PCtx; n: PNode): int = @@ -1561,7 +1569,7 @@ proc genProc(c: PCtx; s: PSym): int = c.gABC(body, opcEof, eofInstr.regA) c.optimizeJumps(result) s.offset = c.prc.maxSlots - #if s.name.s == "xmlConstructor": + #if s.name.s == "foo": # echo renderTree(body) # c.echoCode(result) c.prc = oldPrc diff --git a/doc/manual.txt b/doc/manual.txt index 3c1f7b651..ab1badaf3 100644 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -79,8 +79,21 @@ program execution. Unless explicitly classified, an error is a static error. A `checked runtime error`:idx: is an error that the implementation detects and reports at runtime. The method for reporting such errors is via *raising -exceptions*. However, the implementation provides a means to disable these -runtime checks. See the section pragmas_ for details. +exceptions* or *dying with a fatal error*. However, the implementation +provides a means to disable these runtime checks. See the section pragmas_ +for details. + +Wether a checked runtime error results in an exception or in a fatal error at +runtime is implementation specific. Thus the following program is always +invalid: + +.. code-block:: nimrod + var a: array[0..1, char] + let i = 5 + try: + a[i] = 'N' + except EInvalidIndex: + echo "invalid index" An `unchecked runtime error`:idx: is an error that is not guaranteed to be detected, and can cause the subsequent behavior of the computation to @@ -480,8 +493,8 @@ precedence and associativity; this is useful for meta programming. Associativity ------------- -All binary operators are left-associative, except binary operators whose -relevant char is ``^``. +Binary operators whose relevant character is ``^`` are right-associative, all +other binary operators are left-associative. Precedence ---------- @@ -508,7 +521,7 @@ Precedence level Operators Relevant char 7 ``+ -`` ``+ ~ |`` OP7 6 ``&`` ``&`` OP6 5 ``..`` ``.`` OP5 - 4 ``== <= < >= > != in not_in is isnot not of`` ``= < > !`` OP4 + 4 ``== <= < >= > != in notin is isnot not of`` ``= < > !`` OP4 3 ``and`` OP3 2 ``or xor`` OP2 1 ``@ : ?`` OP1 @@ -516,6 +529,46 @@ Precedence level Operators Relevant char ================ =============================================== ================== =============== +Strong spaces +------------- + +The number of spaces preceeding a non-keyword operator affects precedence +if the experimental parser directive ``#!strongSpaces`` is used. Indentation +is not used to determine the number of spaces. If 2 or more operators have the +same number of preceding spaces the precedence table applies, so ``1 + 3 * 4`` +is still parsed as ``1 + (3 * 4)``, but ``1+3 * 4`` is parsed as ``(1+3) * 4``: + +.. code-block:: nimrod + #! strongSpaces + if foo+4 * 4 == 8 and b&c | 9 ++ + bar: + echo "" + # is parsed as + if ((foo+4)*4 == 8) and (((b&c) | 9) ++ bar): echo "" + + +Furthermore whether an operator is used a prefix operator is affected by the +number of spaces: + +.. code-block:: nimrod + #! strongSpaces + echo $foo + # is parsed as + echo($foo) + +This also affects whether ``[]``, ``{}``, ``()`` are parsed as constructors +or as accessors: + +.. code-block:: nimrod + #! strongSpaces + echo (1,2) + # is parsed as + echo((1,2)) + + +Grammar +------- + The grammar's start symbol is ``module``. .. include:: grammar.txt @@ -2822,7 +2875,6 @@ as there are components in the tuple. The i'th iteration variable's type is the type of the i'th component. In other words, implicit tuple unpacking in a for loop context is supported. - Implict items/pairs invocations ------------------------------- @@ -2847,10 +2899,11 @@ First class iterators There are 2 kinds of iterators in Nimrod: *inline* and *closure* iterators. An `inline iterator`:idx: is an iterator that's always inlined by the compiler leading to zero overhead for the abstraction, but may result in a heavy -increase in code size. Inline iterators are second class -citizens; one cannot pass them around like first class procs. +increase in code size. Inline iterators are second class citizens; +They can be passed as parameters only to other inlining code facilities like +templates, macros and other inline iterators. -In contrast to that, a `closure iterator`:idx: can be passed around: +In contrast to that, a `closure iterator`:idx: can be passed around more freely: .. code-block:: nimrod iterator count0(): int {.closure.} = @@ -2873,9 +2926,7 @@ Closure iterators have other restrictions than inline iterators: 1. ``yield`` in a closure iterator can not occur in a ``try`` statement. 2. For now, a closure iterator cannot be evaluated at compile time. 3. ``return`` is allowed in a closure iterator (but rarely useful). -4. Since closure iterators can be used as a collaborative tasking - system, ``void`` is a valid return type for them. -5. Both inline and closure iterators cannot be recursive. +4. Both inline and closure iterators cannot be recursive. Iterators that are neither marked ``{.closure.}`` nor ``{.inline.}`` explicitly default to being inline, but that this may change in future versions of the @@ -2937,6 +2988,14 @@ parameters of an outer factory proc: for f in foo(): echo f +Implicit return type +-------------------- + +Since inline interators must always produce values that will be consumed in +a for loop, the compiler will implicity use the ``auto`` return type if no +type is given by the user. In contrast, since closure iterators can be used +as a collaborative tasking system, ``void`` is a valid return type for them. + Type sections ============= @@ -3441,7 +3500,7 @@ Declarative type classes are written in the following form: c.len is ordinal items(c) is iterator for value in c: - value.type is T + type(value) is T The type class will be matched if: @@ -4016,8 +4075,8 @@ Static params can also appear in the signatures of generic types: AffineTransform2D[T] = Matrix[3, 3, T] AffineTransform3D[T] = Matrix[4, 4, T] - AffineTransform3D[float] # OK - AffineTransform2D[string] # Error, `string` is not a `Number` + var m1: AffineTransform3D[float] # OK + var m2: AffineTransform2D[string] # Error, `string` is not a `Number` typedesc diff --git a/lib/impure/db_mysql.nim b/lib/impure/db_mysql.nim index 74aaa1a59..32cda3e4d 100644 --- a/lib/impure/db_mysql.nim +++ b/lib/impure/db_mysql.nim @@ -195,8 +195,14 @@ proc open*(connection, user, password, database: string): TDbConn {. ## be established. result = mysql.Init(nil) if result == nil: dbError("could not open database connection") - if mysql.RealConnect(result, "", user, password, database, - 0'i32, nil, 0) == nil: + let + colonPos = connection.find(':') + host = if colonPos < 0: connection + else: substr(connection, 0, colonPos-1) + port: int32 = if colonPos < 0: 0'i32 + else: substr(connection, colonPos+1).parseInt.int32 + if mysql.RealConnect(result, host, user, password, database, + port, nil, 0) == nil: var errmsg = $mysql.error(result) db_mysql.Close(result) dbError(errmsg) diff --git a/lib/nimbase.h b/lib/nimbase.h index 1100e084b..b16b27b2c 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -314,6 +314,9 @@ static unsigned long nimNaN[2]={0xffffffff, 0x7fffffff}; # define INF INFINITY # elif defined(HUGE_VAL) # define INF HUGE_VAL +# elif defined(_MSC_VER) +# include <float.h> +# define INF (DBL_MAX+DBL_MAX) # else # define INF (1.0 / 0.0) # endif diff --git a/lib/posix/epoll.nim b/lib/posix/epoll.nim index 366521551..57a2f001f 100644 --- a/lib/posix/epoll.nim +++ b/lib/posix/epoll.nim @@ -36,7 +36,7 @@ type epoll_data* {.importc: "union epoll_data", header: "<sys/epoll.h>", pure, final.} = object # TODO: This is actually a union. #thePtr* {.importc: "ptr".}: pointer - fd*: cint # \ + fd* {.importc: "fd".}: cint # \ #u32*: uint32 #u64*: uint64 diff --git a/lib/pure/asyncio2.nim b/lib/pure/asyncio2.nim index 12d4cb5a3..eb31eca13 100644 --- a/lib/pure/asyncio2.nim +++ b/lib/pure/asyncio2.nim @@ -43,6 +43,14 @@ proc complete*[T](future: PFuture[T], val: T) = if future.cb != nil: future.cb() +proc complete*(future: PFuture[void]) = + ## Completes a void ``future``. + assert(not future.finished, "Future already finished, cannot finish twice.") + assert(future.error == nil) + future.finished = true + if future.cb != nil: + future.cb() + proc fail*[T](future: PFuture[T], error: ref EBase) = ## Completes ``future`` with ``error``. assert(not future.finished, "Future already finished, cannot finish twice.") @@ -76,7 +84,8 @@ proc read*[T](future: PFuture[T]): T = ## If the result of the future is an error then that error will be raised. if future.finished: if future.error != nil: raise future.error - return future.value + when T isnot void: + return future.value else: # TODO: Make a custom exception type for this? raise newException(EInvalidValue, "Future still in progress.") @@ -94,7 +103,8 @@ proc failed*[T](future: PFuture[T]): bool = # TODO: Get rid of register. Do it implicitly. when defined(windows) or defined(nimdoc): - import winlean + import winlean, sets, hashes + #from hashes import THash type TCompletionKey = dword @@ -105,7 +115,7 @@ when defined(windows) or defined(nimdoc): PDispatcher* = ref object ioPort: THandle - hasHandles: bool + handles: TSet[TSocketHandle] TCustomOverlapped = object Internal*: DWORD @@ -117,21 +127,31 @@ when defined(windows) or defined(nimdoc): PCustomOverlapped = ptr TCustomOverlapped + proc hash(x: TSocketHandle): THash {.borrow.} + proc newDispatcher*(): PDispatcher = ## Creates a new Dispatcher instance. new result result.ioPort = CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1) + result.handles = initSet[TSocketHandle]() proc register*(p: PDispatcher, sock: TSocketHandle) = ## Registers ``sock`` with the dispatcher ``p``. if CreateIOCompletionPort(sock.THandle, p.ioPort, cast[TCompletionKey](sock), 1) == 0: OSError(OSLastError()) - p.hasHandles = true + p.handles.incl(sock) + + proc verifyPresence(p: PDispatcher, sock: TSocketHandle) = + ## Ensures that socket has been registered with the dispatcher. + if sock notin p.handles: + raise newException(EInvalidValue, + "Operation performed on a socket which has not been registered with" & + " the dispatcher yet.") proc poll*(p: PDispatcher, timeout = 500) = ## Waits for completion events and processes them. - if not p.hasHandles: + if p.handles.len == 0: raise newException(EInvalidValue, "No handles registered in dispatcher.") let llTimeout = @@ -232,13 +252,13 @@ when defined(windows) or defined(nimdoc): RemoteSockaddr, RemoteSockaddrLength) proc connect*(p: PDispatcher, socket: TSocketHandle, address: string, port: TPort, - af = AF_INET): PFuture[int] = + af = AF_INET): PFuture[void] = ## Connects ``socket`` to server at ``address:port``. ## ## Returns a ``PFuture`` which will complete when the connection succeeds ## or an error occurs. - - var retFuture = newFuture[int]()# TODO: Change to void when that regression is fixed. + verifyPresence(p, socket) + var retFuture = newFuture[void]() # Apparently ``ConnectEx`` expects the socket to be initially bound: var saddr: Tsockaddr_in saddr.sin_family = int16(toInt(af)) @@ -260,7 +280,7 @@ when defined(windows) or defined(nimdoc): proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) = if not retFuture.finished: if errcode == TOSErrorCode(-1): - retFuture.complete(0) + retFuture.complete() else: retFuture.fail(newException(EOS, osErrorMsg(errcode))) ) @@ -270,7 +290,7 @@ when defined(windows) or defined(nimdoc): if ret: # Request to connect completed immediately. success = true - retFuture.complete(0) + retFuture.complete() # We don't deallocate ``ol`` here because even though this completed # immediately poll will still be notified about its completion and it will # free ``ol``. @@ -298,7 +318,7 @@ when defined(windows) or defined(nimdoc): ## recv operation then the future may complete with only a part of the ## requested data read. If socket is disconnected and no data is available ## to be read then the future will complete with a value of ``""``. - + verifyPresence(p, socket) var retFuture = newFuture[string]() var dataBuf: TWSABuf @@ -351,10 +371,11 @@ when defined(windows) or defined(nimdoc): # free ``ol``. return retFuture - proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] = + proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[void] = ## Sends ``data`` to ``socket``. The returned future will complete once all ## data has been sent. - var retFuture = newFuture[int]() + verifyPresence(p, socket) + var retFuture = newFuture[void]() var dataBuf: TWSABuf dataBuf.buf = data @@ -366,7 +387,7 @@ when defined(windows) or defined(nimdoc): proc (sock: TSocketHandle, bytesCount: DWord, errcode: TOSErrorCode) = if not retFuture.finished: if errcode == TOSErrorCode(-1): - retFuture.complete(0) + retFuture.complete() else: retFuture.fail(newException(EOS, osErrorMsg(errcode))) ) @@ -379,7 +400,7 @@ when defined(windows) or defined(nimdoc): retFuture.fail(newException(EOS, osErrorMsg(err))) dealloc(ol) else: - retFuture.complete(0) + retFuture.complete() # We don't deallocate ``ol`` here because even though this completed # immediately poll will still be notified about its completion and it will # free ``ol``. @@ -390,7 +411,9 @@ when defined(windows) or defined(nimdoc): ## Accepts a new connection. Returns a future containing the client socket ## corresponding to that connection and the remote address of the client. ## The future will complete when the connection is successfully accepted. - + ## + ## The resulting client socket is automatically registered to dispatcher. + verifyPresence(p, socket) var retFuture = newFuture[tuple[address: string, client: TSocketHandle]]() var clientSock = socket() @@ -416,6 +439,7 @@ when defined(windows) or defined(nimdoc): dwLocalAddressLength, dwRemoteAddressLength, addr LocalSockaddr, addr localLen, addr RemoteSockaddr, addr remoteLen) + p.register(clientSock) # TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186 retFuture.complete( (address: $inet_ntoa(cast[ptr Tsockaddr_in](remoteSockAddr).sin_addr), @@ -452,6 +476,18 @@ when defined(windows) or defined(nimdoc): return retFuture + proc socket*(disp: PDispatcher, domain: TDomain = AF_INET, + typ: TType = SOCK_STREAM, + protocol: TProtocol = IPPROTO_TCP): TSocketHandle = + ## Creates a new socket and registers it with the dispatcher implicitly. + result = socket(domain, typ, protocol) + disp.register(result) + + proc close*(disp: PDispatcher, socket: TSocketHandle) = + ## Closes a socket and ensures that it is unregistered. + socket.close() + disp.handles.excl(socket) + initAll() else: import selectors @@ -473,62 +509,76 @@ else: proc update(p: PDispatcher, sock: TSocketHandle, events: set[TEvent]) = assert sock in p.selector - echo("Update: ", events) - if events == {}: - discard p.selector.unregister(sock) - else: - discard p.selector.update(sock, events) + discard p.selector.update(sock, events) + + proc register(p: PDispatcher, sock: TSocketHandle) = + var data = PData(sock: sock, readCBs: @[], writeCBs: @[]) + p.selector.register(sock, {}, data.PObject) + + proc socket*(disp: PDispatcher, domain: TDomain = AF_INET, + typ: TType = SOCK_STREAM, + protocol: TProtocol = IPPROTO_TCP): TSocketHandle = + result = socket(domain, typ, protocol) + disp.register(result) + proc close*(disp: PDispatcher, sock: TSocketHandle) = + sock.close() + disp.selector.unregister(sock) + proc addRead(p: PDispatcher, sock: TSocketHandle, cb: TCallback) = if sock notin p.selector: - var data = PData(sock: sock, readCBs: @[cb], writeCBs: @[]) - p.selector.register(sock, {EvRead}, data.PObject) - else: - p.selector[sock].data.PData.readCBs.add(cb) - p.update(sock, p.selector[sock].events + {EvRead}) + raise newException(EInvalidValue, "File descriptor not registered.") + p.selector[sock].data.PData.readCBs.add(cb) + p.update(sock, p.selector[sock].events + {EvRead}) proc addWrite(p: PDispatcher, sock: TSocketHandle, cb: TCallback) = if sock notin p.selector: - var data = PData(sock: sock, readCBs: @[], writeCBs: @[cb]) - p.selector.register(sock, {EvWrite}, data.PObject) - else: - p.selector[sock].data.PData.writeCBs.add(cb) - p.update(sock, p.selector[sock].events + {EvWrite}) + raise newException(EInvalidValue, "File descriptor not registered.") + p.selector[sock].data.PData.writeCBs.add(cb) + p.update(sock, p.selector[sock].events + {EvWrite}) proc poll*(p: PDispatcher, timeout = 500) = for info in p.selector.select(timeout): let data = PData(info.key.data) assert data.sock == info.key.fd - echo("R: ", data.readCBs.len, " W: ", data.writeCBs.len, ". ", info.events) - + #echo("In poll ", data.sock.cint) if EvRead in info.events: - var newReadCBs: seq[TCallback] = @[] - for cb in data.readCBs: + # Callback may add items to ``data.readCBs`` which causes issues if + # we are iterating over ``data.readCBs`` at the same time. We therefore + # make a copy to iterate over. + let currentCBs = data.readCBs + data.readCBs = @[] + for cb in currentCBs: if not cb(data.sock): # Callback wants to be called again. - newReadCBs.add(cb) - data.readCBs = newReadCBs + data.readCBs.add(cb) if EvWrite in info.events: - var newWriteCBs: seq[TCallback] = @[] - for cb in data.writeCBs: + let currentCBs = data.writeCBs + data.writeCBs = @[] + for cb in currentCBs: if not cb(data.sock): # Callback wants to be called again. - newWriteCBs.add(cb) - data.writeCBs = newWriteCBs - - var newEvents: set[TEvent] - if data.readCBs.len != 0: newEvents = {EvRead} - if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite} - p.update(data.sock, newEvents) + data.writeCBs.add(cb) + + if info.key in p.selector: + var newEvents: set[TEvent] + if data.readCBs.len != 0: newEvents = {EvRead} + if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite} + if newEvents != info.key.events: + echo(info.key.events, " -> ", newEvents) + p.update(data.sock, newEvents) + else: + # FD no longer a part of the selector. Likely been closed + # (e.g. socket disconnected). proc connect*(p: PDispatcher, socket: TSocketHandle, address: string, port: TPort, - af = AF_INET): PFuture[int] = - var retFuture = newFuture[int]() + af = AF_INET): PFuture[void] = + var retFuture = newFuture[void]() proc cb(sock: TSocketHandle): bool = # We have connected. - retFuture.complete(0) + retFuture.complete() return true var aiList = getAddrInfo(address, port, af) @@ -540,7 +590,7 @@ else: if ret == 0: # Request to connect completed immediately. success = true - retFuture.complete(0) + retFuture.complete() break else: lastError = osLastError() @@ -568,6 +618,7 @@ else: result = true let netSize = size - sizeRead let res = recv(sock, addr readBuffer[sizeRead], netSize, flags.cint) + #echo("recv cb res: ", res) if res < 0: let lastError = osLastError() if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: @@ -575,6 +626,7 @@ else: else: result = false # We still want this callback to be called. elif res == 0: + #echo("Disconnected recv: ", sizeRead) # Disconnected if sizeRead == 0: retFuture.complete("") @@ -587,12 +639,13 @@ else: result = false # We want to read all the data requested. else: retFuture.complete(readBuffer) + #echo("Recv cb result: ", result) addRead(p, socket, cb) return retFuture - proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] = - var retFuture = newFuture[int]() + proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[void] = + var retFuture = newFuture[void]() var written = 0 @@ -612,10 +665,9 @@ else: if res != netSize: result = false # We still have data to send. else: - retFuture.complete(0) + retFuture.complete() addWrite(p, socket, cb) return retFuture - proc acceptAddr*(p: PDispatcher, socket: TSocketHandle): PFuture[tuple[address: string, client: TSocketHandle]] = @@ -634,6 +686,7 @@ else: else: retFuture.fail(newException(EOS, osErrorMsg(lastError))) else: + p.register(client) retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client)) addRead(p, socket, cb) return retFuture @@ -745,12 +798,17 @@ macro async*(prc: stmt): stmt {.immediate.} = hint("Processing " & prc[0].getName & " as an async proc.") + let returnType = prc[3][0] + var subtypeName = "" # Verify that the return type is a PFuture[T] - if prc[3][0].kind == nnkIdent: - error("Expected return type of 'PFuture' got '" & $prc[3][0] & "'") - elif prc[3][0].kind == nnkBracketExpr: - if $prc[3][0][0] != "PFuture": - error("Expected return type of 'PFuture' got '" & $prc[3][0][0] & "'") + if returnType.kind == nnkIdent: + error("Expected return type of 'PFuture' got '" & $returnType & "'") + elif returnType.kind == nnkBracketExpr: + if $returnType[0] != "PFuture": + error("Expected return type of 'PFuture' got '" & $returnType[0] & "'") + subtypeName = $returnType[1].ident + elif returnType.kind == nnkEmpty: + subtypeName = "void" # TODO: Why can't I use genSym? I get illegal capture errors for Syms. # TODO: It seems genSym is broken. Change all usages back to genSym when fixed @@ -763,20 +821,24 @@ macro async*(prc: stmt): stmt {.immediate.} = newVarStmt(retFutureSym, newCall( newNimNode(nnkBracketExpr).add( - newIdentNode("newFuture"), - prc[3][0][1])))) # Get type from return type of this proc. - + newIdentNode(!"newFuture"), # TODO: Strange bug here? Remove the `!`. + newIdentNode(subtypeName))))) # Get type from return type of this proc + echo(treeRepr(outerProcBody)) # -> iterator nameIter(): PFutureBase {.closure.} = # -> var result: T # -> <proc_body> # -> complete(retFuture, result) var iteratorNameSym = newIdentNode($prc[0].getName & "Iter") #genSym(nskIterator, $prc[0].ident & "Iter") var procBody = prc[6].processBody(retFutureSym) - procBody.insert(0, newNimNode(nnkVarSection).add( - newIdentDefs(newIdentNode("result"), prc[3][0][1]))) # -> var result: T - procBody.add( - newCall(newIdentNode("complete"), - retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result) + if subtypeName != "void": + procBody.insert(0, newNimNode(nnkVarSection).add( + newIdentDefs(newIdentNode("result"), returnType[1]))) # -> var result: T + procBody.add( + newCall(newIdentNode("complete"), + retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result) + else: + # -> complete(retFuture) + procBody.add(newCall(newIdentNode("complete"), retFutureSym)) var closureIterator = newProc(iteratorNameSym, [newIdentNode("PFutureBase")], procBody, nnkIteratorDef) @@ -811,6 +873,12 @@ macro async*(prc: stmt): stmt {.immediate.} = for i in 0 .. <result[4].len: if result[4][i].ident == !"async": result[4].del(i) + if subtypeName == "void": + # Add discardable pragma. + result[4].add(newIdentNode("discardable")) + if returnType.kind == nnkEmpty: + # Add PFuture[void] + result[3][0] = parseExpr("PFuture[void]") result[6] = outerProcBody @@ -833,9 +901,13 @@ proc recvLine*(p: PDispatcher, socket: TSocketHandle): PFuture[string] {.async.} result = "" var c = "" while true: + #echo("1") c = await p.recv(socket, 1) + #echo("Received ", c.len) if c.len == 0: + #echo("returning") return + #echo("2") if c == "\r": c = await p.recv(socket, 1, MSG_PEEK) if c.len > 0 and c == "\L": @@ -845,12 +917,14 @@ proc recvLine*(p: PDispatcher, socket: TSocketHandle): PFuture[string] {.async.} elif c == "\L": addNLIfEmpty() return + #echo("3") add(result.string, c) + #echo("4") when isMainModule: var p = newDispatcher() - var sock = socket() + var sock = p.socket() sock.setBlocking false @@ -859,6 +933,7 @@ when isMainModule: proc main(p: PDispatcher): PFuture[int] {.async.} = discard await p.connect(sock, "irc.freenode.net", TPort(6667)) while true: + echo("recvLine") var line = await p.recvLine(sock) echo("Line is: ", line.repr) if line == "": @@ -882,7 +957,7 @@ when isMainModule: else: when false: - var f = p.connect(sock, "irc.freenode.org", TPort(6667)) + var f = p.connect(sock, "irc.poop.nl", TPort(6667)) f.callback = proc (future: PFuture[int]) = echo("Connected in future!") @@ -898,11 +973,13 @@ when isMainModule: sock.bindAddr(TPort(6667)) sock.listen() proc onAccept(future: PFuture[TSocketHandle]) = - echo "Accepted" - var t = p.send(future.read, "test\c\L") + let client = future.read + echo "Accepted ", client.cint + var t = p.send(client, "test\c\L") t.callback = proc (future: PFuture[int]) = - echo(future.read) + echo("Send: ", future.read) + client.close() var f = p.accept(sock) f.callback = onAccept @@ -919,4 +996,4 @@ when isMainModule: - \ No newline at end of file + diff --git a/lib/pure/dynlib.nim b/lib/pure/dynlib.nim index 889912052..54a553173 100644 --- a/lib/pure/dynlib.nim +++ b/lib/pure/dynlib.nim @@ -92,7 +92,7 @@ elif defined(windows) or defined(dos): proc unloadLib(lib: TLibHandle) = FreeLibrary(cast[THINSTANCE](lib)) proc symAddr(lib: TLibHandle, name: cstring): pointer = - result = GetProcAddress(cast[THINSTANCE](lib), name) + result = getProcAddress(cast[THINSTANCE](lib), name) else: {.error: "no implementation for dynlib".} diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 0ec007009..9ee98cbe6 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -9,7 +9,264 @@ ## This module implements a high-level cross-platform sockets interface. -import sockets2, os +import sockets2, os, strutils, unsigned + +type + IpAddressFamily* {.pure.} = enum ## Describes the type of an IP address + IPv6, ## IPv6 address + IPv4 ## IPv4 address + + TIpAddress* = object ## stores an arbitrary IP address + case family*: IpAddressFamily ## the type of the IP address (IPv4 or IPv6) + of IpAddressFamily.IPv6: + address_v6*: array[0..15, uint8] ## Contains the IP address in bytes in case of IPv6 + of IpAddressFamily.IPv4: + address_v4*: array[0..3, uint8] ## Contains the IP address in bytes in case of IPv4 + +proc IPv4_any*(): TIpAddress = + ## Returns the IPv4 any address, which can be used to listen on all available + ## network adapters + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [0'u8, 0'u8, 0'u8, 0'u8]) + +proc IPv4_loopback*(): TIpAddress = + ## Returns the IPv4 loopback address (127.0.0.1) + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [127'u8, 0'u8, 0'u8, 1'u8]) + +proc IPv4_broadcast*(): TIpAddress = + ## Returns the IPv4 broadcast address (255.255.255.255) + result = TIpAddress( + family: IpAddressFamily.IPv4, + address_v4: [255'u8, 255'u8, 255'u8, 255'u8]) + +proc IPv6_any*(): TIpAddress = + ## Returns the IPv6 any address (::0), which can be used + ## to listen on all available network adapters + result = TIpAddress( + family: IpAddressFamily.IPv6, + address_v6: [0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8]) + +proc IPv6_loopback*(): TIpAddress = + ## Returns the IPv6 loopback address (::1) + result = TIpAddress( + family: IpAddressFamily.IPv6, + address_v6: [0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,0'u8,1'u8]) + +proc `==`*(lhs, rhs: TIpAddress): bool = + ## Compares two IpAddresses for Equality. Returns two if the addresses are equal + if lhs.family != rhs.family: return false + if lhs.family == IpAddressFamily.IPv4: + for i in low(lhs.address_v4) .. high(lhs.address_v4): + if lhs.address_v4[i] != rhs.address_v4[i]: return false + else: # IPv6 + for i in low(lhs.address_v6) .. high(lhs.address_v6): + if lhs.address_v6[i] != rhs.address_v6[i]: return false + return true + +proc `$`*(address: TIpAddress): string = + ## Converts an TIpAddress into the textual representation + result = "" + case address.family + of IpAddressFamily.IPv4: + for i in 0 .. 3: + if i != 0: + result.add('.') + result.add($address.address_v4[i]) + of IpAddressFamily.IPv6: + var + currentZeroStart = -1 + currentZeroCount = 0 + biggestZeroStart = -1 + biggestZeroCount = 0 + # Look for the largest block of zeros + for i in 0..7: + var isZero = address.address_v6[i*2] == 0 and address.address_v6[i*2+1] == 0 + if isZero: + if currentZeroStart == -1: + currentZeroStart = i + currentZeroCount = 1 + else: + currentZeroCount.inc() + if currentZeroCount > biggestZeroCount: + biggestZeroCount = currentZeroCount + biggestZeroStart = currentZeroStart + else: + currentZeroStart = -1 + + if biggestZeroCount == 8: # Special case ::0 + result.add("::") + else: # Print address + var printedLastGroup = false + for i in 0..7: + var word:uint16 = (cast[uint16](address.address_v6[i*2])) shl 8 + word = word or cast[uint16](address.address_v6[i*2+1]) + + if biggestZeroCount != 0 and # Check if group is in skip group + (i >= biggestZeroStart and i < (biggestZeroStart + biggestZeroCount)): + if i == biggestZeroStart: # skip start + result.add("::") + printedLastGroup = false + else: + if printedLastGroup: + result.add(':') + var + afterLeadingZeros = false + mask = 0xF000'u16 + for j in 0'u16..3'u16: + var val = (mask and word) shr (4'u16*(3'u16-j)) + if val != 0 or afterLeadingZeros: + if val < 0xA: + result.add(chr(uint16(ord('0'))+val)) + else: # val >= 0xA + result.add(chr(uint16(ord('a'))+val-0xA)) + afterLeadingZeros = true + mask = mask shr 4 + printedLastGroup = true + +proc parseIPv4Address(address_str: string): TIpAddress = + ## Parses IPv4 adresses + ## Raises EInvalidValue on errors + var + byteCount = 0 + currentByte:uint16 = 0 + seperatorValid = false + + result.family = IpAddressFamily.IPv4 + + for i in 0 .. high(address_str): + if address_str[i] in strutils.Digits: # Character is a number + currentByte = currentByte * 10 + cast[uint16](ord(address_str[i]) - ord('0')) + if currentByte > 255'u16: + raise newException(EInvalidValue, "Invalid IP Address. Value is out of range") + seperatorValid = true + elif address_str[i] == '.': # IPv4 address separator + if not seperatorValid or byteCount >= 3: + raise newException(EInvalidValue, "Invalid IP Address. The address consists of too many groups") + result.address_v4[byteCount] = cast[uint8](currentByte) + currentByte = 0 + byteCount.inc + seperatorValid = false + else: + raise newException(EInvalidValue, "Invalid IP Address. Address contains an invalid character") + + if byteCount != 3 or not seperatorValid: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v4[byteCount] = cast[uint8](currentByte) + +proc parseIPv6Address(address_str: string): TIpAddress = + ## Parses IPv6 adresses + ## Raises EInvalidValue on errors + result.family = IpAddressFamily.IPv6 + if address_str.len < 2: raise newException(EInvalidValue, "Invalid IP Address") + + var + groupCount = 0 + currentGroupStart = 0 + currentShort:uint32 = 0 + seperatorValid = true + dualColonGroup = -1 + lastWasColon = false + v4StartPos = -1 + byteCount = 0 + + for i,c in address_str: + if c == ':': + if not seperatorValid: raise newException(EInvalidValue, "Invalid IP Address. Address contains an invalid seperator") + if lastWasColon: + if dualColonGroup != -1: raise newException(EInvalidValue, "Invalid IP Address. Address contains more than one \"::\" seperator") + dualColonGroup = groupCount + seperatorValid = false + elif i != 0 and i != high(address_str): + if groupCount >= 8: raise newException(EInvalidValue, "Invalid IP Address. The address consists of too many groups") + result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) + result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) + currentShort = 0 + groupCount.inc() + if dualColonGroup != -1: seperatorValid = false + elif i == 0: # only valid if address starts with :: + if address_str[1] != ':': + raise newException(EInvalidValue, "Invalid IP Address. Address may not start with \":\"") + else: # i == high(address_str) - only valid if address ends with :: + if address_str[high(address_str)-1] != ':': + raise newException(EInvalidValue, "Invalid IP Address. Address may not end with \":\"") + lastWasColon = true + currentGroupStart = i + 1 + elif c == '.': # Switch to parse IPv4 mode + if i < 3 or not seperatorValid or groupCount >= 7: raise newException(EInvalidValue, "Invalid IP Address") + v4StartPos = currentGroupStart + currentShort = 0 + seperatorValid = false + break + elif c in strutils.HexDigits: + if c in strutils.Digits: # Normal digit + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('0')) + elif c >= 'a' and c <= 'f': # Lower case hex + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('a')) + 10 + else: # Upper case hex + currentShort = (currentShort shl 4) + cast[uint32](ord(c) - ord('A')) + 10 + if currentShort > 65535'u32: + raise newException(EInvalidValue, "Invalid IP Address. Value is out of range") + lastWasColon = false + seperatorValid = true + else: + raise newException(EInvalidValue, "Invalid IP Address. Address contains an invalid character") + + + if v4StartPos == -1: # Don't parse v4. Copy the remaining v6 stuff + if seperatorValid: # Copy remaining data + if groupCount >= 8: raise newException(EInvalidValue, "Invalid IP Address. The address consists of too many groups") + result.address_v6[groupCount*2] = cast[uint8](currentShort shr 8) + result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) + groupCount.inc() + else: # Must parse IPv4 address + for i,c in address_str[v4StartPos..high(address_str)]: + if c in strutils.Digits: # Character is a number + currentShort = currentShort * 10 + cast[uint32](ord(c) - ord('0')) + if currentShort > 255'u32: + raise newException(EInvalidValue, "Invalid IP Address. Value is out of range") + seperatorValid = true + elif c == '.': # IPv4 address separator + if not seperatorValid or byteCount >= 3: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) + currentShort = 0 + byteCount.inc() + seperatorValid = false + else: # Invalid character + raise newException(EInvalidValue, "Invalid IP Address. Address contains an invalid character") + + if byteCount != 3 or not seperatorValid: + raise newException(EInvalidValue, "Invalid IP Address") + result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) + groupCount += 2 + + # Shift and fill zeros in case of :: + if groupCount > 8: + raise newException(EInvalidValue, "Invalid IP Address. The address consists of too many groups") + elif groupCount < 8: # must fill + if dualColonGroup == -1: raise newException(EInvalidValue, "Invalid IP Address. The address consists of too few groups") + var toFill = 8 - groupCount # The number of groups to fill + var toShift = groupCount - dualColonGroup # Nr of known groups after :: + for i in 0..2*toShift-1: # shift + result.address_v6[15-i] = result.address_v6[groupCount*2-i-1] + for i in 0..2*toFill-1: # fill with 0s + result.address_v6[dualColonGroup*2+i] = 0 + elif dualColonGroup != -1: raise newException(EInvalidValue, "Invalid IP Address. The address consists of too many groups") + + +proc parseIpAddress*(address_str: string): TIpAddress = + ## Parses an IP address + ## Raises EInvalidValue on error + if address_str == nil: + raise newException(EInvalidValue, "IP Address string is nil") + if address_str.contains(':'): + return parseIPv6Address(address_str) + else: + return parseIPv4Address(address_str) + type TSocket* = TSocketHandle @@ -52,4 +309,4 @@ proc setBlocking*(s: TSocket, blocking: bool) {.tags: [].} = else: var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK if fcntl(s, F_SETFL, mode) == -1: - osError(osLastError()) \ No newline at end of file + osError(osLastError()) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index bfecc569a..faca17e98 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -260,11 +260,12 @@ proc osError*(errorCode: TOSErrorCode) = ## ## If the error code is ``0`` or an error message could not be retrieved, ## the message ``unknown OS error`` will be used. - let msg = osErrorMsg(errorCode) - if msg == "": - raise newException(EOS, "unknown OS error") - else: - raise newException(EOS, msg) + var e: ref EOS; new(e) + e.errorCode = errorCode.int32 + e.msg = osErrorMsg(errorCode) + if e.msg == "": + e.msg = "unknown OS error" + raise e {.push stackTrace:off.} proc osLastError*(): TOSErrorCode = diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 582b3c960..5d6848565 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -606,7 +606,8 @@ elif not defined(useNimRtl): optionPoParentStreams: bool optionPoStdErrToStdOut: bool - proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} + when not defined(useFork): + proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} proc startProcessAuxFork(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} {.push stacktrace: off, profiler: off.} proc startProcessAfterFork(data: ptr TStartProcessData) {. @@ -664,7 +665,8 @@ elif not defined(useNimRtl): data.workingDir = workingDir - when defined(posix_spawn) and not defined(useFork) and not defined(useClone) and not defined(linux): + when defined(posix_spawn) and not defined(useFork) and + not defined(useClone) and not defined(linux): pid = startProcessAuxSpawn(data) else: pid = startProcessAuxFork(data) @@ -694,55 +696,56 @@ elif not defined(useNimRtl): discard close(pStdin[readIdx]) discard close(pStdout[writeIdx]) - proc startProcessAuxSpawn(data: TStartProcessData): TPid = - var attr: Tposix_spawnattr - var fops: Tposix_spawn_file_actions - - template chck(e: expr) = - if e != 0'i32: osError(osLastError()) - - chck posix_spawn_file_actions_init(fops) - chck posix_spawnattr_init(attr) - - var mask: Tsigset - chck sigemptyset(mask) - chck posix_spawnattr_setsigmask(attr, mask) - chck posix_spawnattr_setpgroup(attr, 0'i32) - - chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or - POSIX_SPAWN_SETSIGMASK or - POSIX_SPAWN_SETPGROUP) - - if not data.optionPoParentStreams: - chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx]) - chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx) - chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) - chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx) - chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) - if data.optionPoStdErrToStdOut: - chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) + when not defined(useFork): + proc startProcessAuxSpawn(data: TStartProcessData): TPid = + var attr: Tposix_spawnattr + var fops: Tposix_spawn_file_actions + + template chck(e: expr) = + if e != 0'i32: osError(osLastError()) + + chck posix_spawn_file_actions_init(fops) + chck posix_spawnattr_init(attr) + + var mask: Tsigset + chck sigemptyset(mask) + chck posix_spawnattr_setsigmask(attr, mask) + chck posix_spawnattr_setpgroup(attr, 0'i32) + + chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or + POSIX_SPAWN_SETSIGMASK or + POSIX_SPAWN_SETPGROUP) + + if not data.optionPoParentStreams: + chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx]) + chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx) + chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) + chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx) + chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) + if data.optionPoStdErrToStdOut: + chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) + else: + chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) + + var res: cint + # FIXME: chdir is global to process + if data.workingDir.len > 0: + setCurrentDir($data.workingDir) + var pid: TPid + + if data.optionPoUsePath: + res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) else: - chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) - - var res: cint - # FIXME: chdir is global to process - if data.workingDir.len > 0: - setCurrentDir($data.workingDir) - var pid: TPid - - if data.optionPoUsePath: - res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) - else: - res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) + res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) - discard posix_spawn_file_actions_destroy(fops) - discard posix_spawnattr_destroy(attr) - chck res - return pid + discard posix_spawn_file_actions_destroy(fops) + discard posix_spawnattr_destroy(attr) + chck res + return pid proc startProcessAuxFork(data: TStartProcessData): TPid = if pipe(data.pErrorPipe) != 0: - osError(osLastError()) + osError(osLastError()) finally: discard close(data.pErrorPipe[readIdx]) diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index 3f9686e1e..bd8836f7c 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -267,7 +267,7 @@ proc getSymbol(c: var TSqlLexer, tok: var TToken) = while true: add(tok.literal, buf[pos]) Inc(pos) - if not (buf[pos] in {'a'..'z','A'..'Z','0'..'9','_','$', '\128'..'\255'}): + if buf[pos] notin {'a'..'z','A'..'Z','0'..'9','_','$', '\128'..'\255'}: break c.bufpos = pos tok.kind = tkIdentifier diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index 6482a01a6..085344e3e 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -15,6 +15,7 @@ when defined(linux): import posix, epoll elif defined(windows): import winlean proc hash*(x: TSocketHandle): THash {.borrow.} +proc `$`*(x: TSocketHandle): string {.borrow.} type TEvent* = enum @@ -31,7 +32,7 @@ when defined(linux) or defined(nimdoc): type PSelector* = ref object epollFD: cint - events: array[64, ptr epoll_event] + events: array[64, epoll_event] fds: TTable[TSocketHandle, PSelectorKey] proc createEventStruct(events: set[TEvent], fd: TSocketHandle): epoll_event = @@ -39,17 +40,14 @@ when defined(linux) or defined(nimdoc): result.events = EPOLLIN if EvWrite in events: result.events = result.events or EPOLLOUT + result.events = result.events or EPOLLRDHUP result.data.fd = fd.cint proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent], data: PObject): PSelectorKey {.discardable.} = ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent ## ``events``. - if s.fds.hasKey(fd): - raise newException(EInvalidValue, "File descriptor already exists.") - var event = createEventStruct(events, fd) - if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: OSError(OSLastError()) @@ -61,22 +59,19 @@ when defined(linux) or defined(nimdoc): proc update*(s: PSelector, fd: TSocketHandle, events: set[TEvent]): PSelectorKey {.discardable.} = ## Updates the events which ``fd`` wants notifications for. - if not s.fds.hasKey(fd): - raise newException(EInvalidValue, "File descriptor not found.") - var event = createEventStruct(events, fd) - - s.fds[fd].events = events - echo("About to update") - if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: - OSError(OSLastError()) - echo("finished updating") - result = s.fds[fd] + if s.fds[fd].events != events: + var event = createEventStruct(events, fd) + + s.fds[fd].events = events + if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: + OSError(OSLastError()) + result = s.fds[fd] proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} = - if not s.fds.hasKey(fd): - raise newException(EInvalidValue, "File descriptor not found.") if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: - OSError(OSLastError()) + let err = OSLastError() + if err.cint notin {ENOENT, EBADF}: # TODO: Why do we sometimes get an EBADF? Is this normal? + OSError(err) result = s.fds[fd] s.fds.del(fd) @@ -84,6 +79,15 @@ when defined(linux) or defined(nimdoc): if s.epollFD.close() != 0: OSError(OSLastError()) dealloc(addr s.events) # TODO: Test this + proc epollHasFd(s: PSelector, fd: TSocketHandle): bool = + result = true + var event = createEventStruct(s.fds[fd].events, fd) + if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: + let err = osLastError() + if err.cint in {ENOENT, EBADF}: + return false + OSError(OSLastError()) + proc select*(s: PSelector, timeout: int): seq[TReadyInfo] = ## ## The ``events`` field of the returned ``key`` contains the original events @@ -91,29 +95,44 @@ when defined(linux) or defined(nimdoc): ## of the ``TReadyInfo`` tuple which determines which events are ready ## on the ``fd``. result = @[] - - let evNum = epoll_wait(s.epollFD, s.events[0], 64.cint, timeout.cint) + let evNum = epoll_wait(s.epollFD, addr s.events[0], 64.cint, timeout.cint) if evNum < 0: OSError(OSLastError()) if evNum == 0: return @[] for i in 0 .. <evNum: + let fd = s.events[i].data.fd.TSocketHandle + var evSet: set[TEvent] = {} if (s.events[i].events and EPOLLIN) != 0: evSet = evSet + {EvRead} if (s.events[i].events and EPOLLOUT) != 0: evSet = evSet + {EvWrite} - - let selectorKey = s.fds[s.events[i].data.fd.TSocketHandle] + let selectorKey = s.fds[fd] + assert selectorKey != nil result.add((selectorKey, evSet)) + + #echo("Epoll: ", result[i].key.fd, " ", result[i].events, " ", result[i].key.events) proc newSelector*(): PSelector = new result result.epollFD = epoll_create(64) - result.events = cast[array[64, ptr epoll_event]](alloc0(sizeof(epoll_event)*64)) + result.events = cast[array[64, epoll_event]](alloc0(sizeof(epoll_event)*64)) result.fds = initTable[TSocketHandle, PSelectorKey]() if result.epollFD < 0: OSError(OSLastError()) proc contains*(s: PSelector, fd: TSocketHandle): bool = ## Determines whether selector contains a file descriptor. - return s.fds.hasKey(fd) + if s.fds.hasKey(fd): + # Ensure the underlying epoll instance still contains this fd. + result = epollHasFd(s, fd) + else: + return false + + proc contains*(s: PSelector, key: PSelectorKey): bool = + ## Determines whether selector contains this selector key. More accurate + ## than checking if the file descriptor is in the selector because it + ## ensures that the keys are equal. File descriptors may not always be + ## unique especially when an fd is closed and then a new one is opened, + ## the new one may have the same value. + return key.fd in s and s.fds[key.fd] == key proc `[]`*(s: PSelector, fd: TSocketHandle): PSelectorKey = ## Retrieves the selector key for ``fd``. @@ -247,4 +266,4 @@ when isMainModule: - \ No newline at end of file + diff --git a/lib/pure/sockets2.nim b/lib/pure/sockets2.nim index 3542a0694..290f414b4 100644 --- a/lib/pure/sockets2.nim +++ b/lib/pure/sockets2.nim @@ -24,6 +24,10 @@ else: export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen, inet_ntoa, recv, `==`, connect, send, accept +export + SO_ERROR, + SOL_SOCKET + type TPort* = distinct uint16 ## port type @@ -208,6 +212,24 @@ proc htons*(x: int16): int16 = ## order, this is a no-op; otherwise, it performs a 2-byte swap operation. result = sockets2.ntohs(x) +proc getSockOptInt*(socket: TSocketHandle, level, optname: int): int {. + tags: [FReadIO].} = + ## getsockopt for integer options. + var res: cint + var size = sizeof(res).TSocklen + if getsockopt(socket, cint(level), cint(optname), + addr(res), addr(size)) < 0'i32: + osError(osLastError()) + result = int(res) + +proc setSockOptInt*(socket: TSocketHandle, level, optname, optval: int) {. + tags: [FWriteIO].} = + ## setsockopt for integer options. + var value = cint(optval) + if setsockopt(socket, cint(level), cint(optname), addr(value), + sizeof(value).TSocklen) < 0'i32: + osError(osLastError()) + when defined(Windows): var wsa: TWSADATA if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) diff --git a/lib/system.nim b/lib/system.nim index 171c7b6b8..41624bb05 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -260,6 +260,7 @@ type ## system raises. EIO* = object of ESystem ## raised if an IO error occured. EOS* = object of ESystem ## raised if an operating system service failed. + errorCode*: int32 ## OS-defined error code describing this error. EInvalidLibrary* = object of EOS ## raised if a dynamic library ## could not be loaded. EResourceExhausted* = object of ESystem ## raised if a resource request @@ -1164,13 +1165,13 @@ when not defined(nimrodVM): ## from it before writing to it is undefined behaviour! ## The allocated memory belongs to its allocating thread! ## Use `allocShared` to allocate from a shared heap. - proc alloc*(T: typedesc, size = 1): ptr T {.inline.} = + proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline.} = ## allocates a new memory block with at least ``T.sizeof * size`` - ## bytes. The block has to be freed with ``realloc(block, 0)`` or - ## ``dealloc(block)``. The block is not initialized, so reading + ## bytes. The block has to be freed with ``resize(block, 0)`` or + ## ``free(block)``. The block is not initialized, so reading ## from it before writing to it is undefined behaviour! ## The allocated memory belongs to its allocating thread! - ## Use `allocShared` to allocate from a shared heap. + ## Use `createSharedU` to allocate from a shared heap. cast[ptr T](alloc(T.sizeof * size)) proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].} ## allocates a new memory block with at least ``size`` bytes. The @@ -1179,13 +1180,13 @@ when not defined(nimrodVM): ## containing zero, so it is somewhat safer than ``alloc``. ## The allocated memory belongs to its allocating thread! ## Use `allocShared0` to allocate from a shared heap. - proc alloc0*(T: typedesc, size = 1): ptr T {.inline.} = + proc create*(T: typedesc, size = 1.Positive): ptr T {.inline.} = ## allocates a new memory block with at least ``T.sizeof * size`` - ## bytes. The block has to be freed with ``realloc(block, 0)`` or - ## ``dealloc(block)``. The block is initialized with all bytes - ## containing zero, so it is somewhat safer than ``alloc``. + ## bytes. The block has to be freed with ``resize(block, 0)`` or + ## ``free(block)``. The block is initialized with all bytes + ## containing zero, so it is somewhat safer than ``createU``. ## The allocated memory belongs to its allocating thread! - ## Use `allocShared0` to allocate from a shared heap. + ## Use `createShared` to allocate from a shared heap. cast[ptr T](alloc0(T.sizeof * size)) proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [].} ## grows or shrinks a given memory block. If p is **nil** then a new @@ -1195,14 +1196,14 @@ when not defined(nimrodVM): ## be freed with ``dealloc``. ## The allocated memory belongs to its allocating thread! ## Use `reallocShared` to reallocate from a shared heap. - proc reallocType*[T](p: ptr T, newSize: int): ptr T {.inline.} = + proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline.} = ## grows or shrinks a given memory block. If p is **nil** then a new ## memory block is returned. In either way the block has at least ## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not - ## **nil** ``realloc`` calls ``dealloc(p)``. In other cases the block - ## has to be freed with ``dealloc``. The allocated memory belongs to + ## **nil** ``resize`` calls ``free(p)``. In other cases the block + ## has to be freed with ``free``. The allocated memory belongs to ## its allocating thread! - ## Use `reallocShared` to reallocate from a shared heap. + ## Use `resizeShared` to reallocate from a shared heap. cast[ptr T](realloc(p, T.sizeof * newSize)) proc dealloc*(p: pointer) {.noconv, rtl, tags: [].} ## frees the memory allocated with ``alloc``, ``alloc0`` or @@ -1212,16 +1213,18 @@ when not defined(nimrodVM): ## or other memory may be corrupted. ## The freed memory must belong to its allocating thread! ## Use `deallocShared` to deallocate from a shared heap. + proc free*[T](p: ptr T) {.inline.} = + dealloc(p) proc allocShared*(size: int): pointer {.noconv, rtl.} ## allocates a new memory block on the shared heap with at ## least ``size`` bytes. The block has to be freed with ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block ## is not initialized, so reading from it before writing to it is ## undefined behaviour! - proc allocShared*(T: typedesc, size: int): ptr T {.inline.} = + proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline.} = ## allocates a new memory block on the shared heap with at ## least ``T.sizeof * size`` bytes. The block has to be freed with - ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block + ## ``resizeShared(block, 0)`` or ``freeShared(block)``. The block ## is not initialized, so reading from it before writing to it is ## undefined behaviour! cast[ptr T](allocShared(T.sizeof * size)) @@ -1231,25 +1234,25 @@ when not defined(nimrodVM): ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. ## The block is initialized with all bytes ## containing zero, so it is somewhat safer than ``allocShared``. - proc allocShared0*(T: typedesc, size: int): ptr T {.inline.} = + proc createShared*(T: typedesc, size = 1.Positive): ptr T {.inline.} = ## allocates a new memory block on the shared heap with at ## least ``T.sizeof * size`` bytes. The block has to be freed with - ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. + ## ``resizeShared(block, 0)`` or ``freeShared(block)``. ## The block is initialized with all bytes - ## containing zero, so it is somewhat safer than ``allocShared``. - cast[ptr T](allocShared(T.sizeof * size)) + ## containing zero, so it is somewhat safer than ``createSharedU``. + cast[ptr T](allocShared0(T.sizeof * size)) proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl.} ## grows or shrinks a given memory block on the heap. If p is **nil** ## then a new memory block is returned. In either way the block has at ## least ``newSize`` bytes. If ``newSize == 0`` and p is not **nil** ## ``reallocShared`` calls ``deallocShared(p)``. In other cases the ## block has to be freed with ``deallocShared``. - proc reallocSharedType*[T](p: ptr T, newSize: int): ptr T {.inline.} = + proc resizeShared*[T](p: ptr T, newSize: Natural): ptr T {.inline.} = ## grows or shrinks a given memory block on the heap. If p is **nil** ## then a new memory block is returned. In either way the block has at ## least ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is - ## not **nil** ``reallocShared`` calls ``deallocShared(p)``. In other - ## cases the block has to be freed with ``deallocShared``. + ## not **nil** ``resizeShared`` calls ``freeShared(p)``. In other + ## cases the block has to be freed with ``freeShared``. cast[ptr T](reallocShared(p, T.sizeof * newSize)) proc deallocShared*(p: pointer) {.noconv, rtl.} ## frees the memory allocated with ``allocShared``, ``allocShared0`` or @@ -1257,6 +1260,13 @@ when not defined(nimrodVM): ## free the memory a leak occurs; if one tries to access freed ## memory (or just freeing it twice!) a core dump may happen ## or other memory may be corrupted. + proc freeShared*[T](p: ptr T) {.inline.} = + ## frees the memory allocated with ``createShared``, ``createSharedU`` or + ## ``resizeShared``. This procedure is dangerous! If one forgets to + ## free the memory a leak occurs; if one tries to access freed + ## memory (or just freeing it twice!) a core dump may happen + ## or other memory may be corrupted. + deallocShared(p) proc swap*[T](a, b: var T) {.magic: "Swap", noSideEffect.} ## swaps the values `a` and `b`. This is often more efficient than diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 74ef9c9ec..4d87cf4b2 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -456,6 +456,7 @@ var SO_DONTLINGER* {.importc, header: "Winsock2.h".}: cint SO_EXCLUSIVEADDRUSE* {.importc, header: "Winsock2.h".}: cint # disallow local address reuse + SO_ERROR* {.importc, header: "Winsock2.h".}: cint proc `==`*(x, y: TSocketHandle): bool {.borrow.} diff --git a/tests/async/tasyncawait.nim b/tests/async/tasyncawait.nim index bde5bf8c8..91dfb7932 100644 --- a/tests/async/tasyncawait.nim +++ b/tests/async/tasyncawait.nim @@ -14,30 +14,31 @@ const var clientCount = 0 -proc sendMessages(disp: PDispatcher, client: TSocketHandle): PFuture[int] {.async.} = +proc sendMessages(disp: PDispatcher, client: TSocketHandle) {.async.} = for i in 0 .. <messagesToSend: - discard await disp.send(client, "Message " & $i & "\c\L") + await disp.send(client, "Message " & $i & "\c\L") -proc launchSwarm(disp: PDispatcher, port: TPort): PFuture[int] {.async.} = +proc launchSwarm(disp: PDispatcher, port: TPort) {.async.} = for i in 0 .. <swarmSize: - var sock = socket() + var sock = disp.socket() + #disp.register(sock) - discard await disp.connect(sock, "localhost", port) + await disp.connect(sock, "localhost", port) when true: - discard await sendMessages(disp, sock) - sock.close() + await sendMessages(disp, sock) + disp.close(sock) else: # Issue #932: https://github.com/Araq/Nimrod/issues/932 var msgFut = sendMessages(disp, sock) msgFut.callback = proc () = - sock.close() + disp.close(sock) -proc readMessages(disp: PDispatcher, client: TSocketHandle): PFuture[int] {.async.} = +proc readMessages(disp: PDispatcher, client: TSocketHandle) {.async.} = while true: var line = await disp.recvLine(client) if line == "": - client.close() + disp.close(client) clientCount.inc break else: @@ -46,16 +47,18 @@ proc readMessages(disp: PDispatcher, client: TSocketHandle): PFuture[int] {.asyn else: doAssert false -proc createServer(disp: PDispatcher, port: TPort): PFuture[int] {.async.} = - var server = socket() +proc createServer(disp: PDispatcher, port: TPort) {.async.} = + var server = disp.socket() #disp.register(server) server.bindAddr(port) server.listen() while true: - discard readMessages(disp, await disp.accept(server)) + var client = await disp.accept(server) + readMessages(disp, client) + # TODO: Test: readMessages(disp, await disp.accept(server)) -discard disp.createServer(TPort(10335)) -discard disp.launchSwarm(TPort(10335)) +disp.createServer(TPort(10335)) +disp.launchSwarm(TPort(10335)) while true: disp.poll() if clientCount == swarmSize: break diff --git a/tests/bind/tbindtypedesc.nim b/tests/bind/tinvalidbindtypedesc.nim index d6fbae537..7d97d2e0d 100644 --- a/tests/bind/tbindtypedesc.nim +++ b/tests/bind/tinvalidbindtypedesc.nim @@ -1,6 +1,5 @@ discard """ line: 11 - file: "tbindtypedesc.nim" errormsg: "type mismatch: got (typedesc[float], string)" """ diff --git a/tests/effects/teffects1.nim b/tests/effects/teffects1.nim index b72e8b00c..0014cff46 100644 --- a/tests/effects/teffects1.nim +++ b/tests/effects/teffects1.nim @@ -1,5 +1,5 @@ discard """ - line: 1855 + line: 1913 file: "system.nim" errormsg: "can raise an unlisted exception: ref EIO" """ diff --git a/tests/generics/tbadgenericlambda.nim b/tests/generics/tbadgenericlambda.nim new file mode 100644 index 000000000..5e406cacc --- /dev/null +++ b/tests/generics/tbadgenericlambda.nim @@ -0,0 +1,7 @@ +discard """ + msg: "nested proc can have generic parameters only when" + line: 6 +""" + +let x = proc (x, y): auto = x + y + diff --git a/tests/generics/tgenericlambda.nim b/tests/generics/tgenericlambda.nim index f7aafe1d9..eb6ada3e5 100644 --- a/tests/generics/tgenericlambda.nim +++ b/tests/generics/tgenericlambda.nim @@ -1,5 +1,5 @@ discard """ - output: "10\n10\n1\n2\n3" + output: "10\n10\n1\n2\n3\n15" """ proc test(x: proc (a, b: int): int) = @@ -16,3 +16,8 @@ proc foreach[T](s: seq[T], body: proc(x: T)) = foreach(@[1,2,3]) do (x): echo x +proc foo = + let x = proc (a, b: int): auto = a + b + echo x(5, 10) + +foo() diff --git a/tests/generics/tgenericshardcases.nim b/tests/generics/tgenericshardcases.nim index 2ef63bc20..e3b805db6 100644 --- a/tests/generics/tgenericshardcases.nim +++ b/tests/generics/tgenericshardcases.nim @@ -14,7 +14,8 @@ macro selectType(a, b: typedesc): typedesc = type Foo[T] = object data1: array[T.high, int] - data2: array[typeNameLen(T), float] # data3: array[0..T.typeNameLen, selectType(float, int)] + data2: array[typeNameLen(T), float] + data3: array[0..T.typeNameLen, selectType(float, int)] MyEnum = enum A, B, C, D @@ -27,10 +28,15 @@ echo high(f1.data2) # (MyEnum.len = 6) - 1 == 5 echo high(f2.data1) # 127 - 1 == 126 echo high(f2.data2) # int8.len - 1 == 3 -#static: -# assert high(f1.data1) == ord(D) -# assert high(f1.data2) == 6 # length of MyEnum +static: + assert high(f1.data1) == ord(C) + assert high(f1.data2) == 5 # length of MyEnum minus one, because we used T.high -# assert high(f2.data1) == 127 -# assert high(f2.data2) == 4 # length of int8 + assert high(f2.data1) == 126 + assert high(f2.data2) == 3 + + assert high(f1.data3) == 6 # length of MyEnum + assert high(f2.data3) == 4 # length of int8 + + assert f2.data3[0] is float diff --git a/tests/generics/tlateboundstatic.nim b/tests/generics/tlateboundstatic.nim new file mode 100644 index 000000000..f68f95f8d --- /dev/null +++ b/tests/generics/tlateboundstatic.nim @@ -0,0 +1,16 @@ +discard """ + msg: "array[0..3, int]" +""" + +type + KK[I: static[int]] = object + x: array[I, int] + +proc foo(a: static[string]): KK[a.len] = + result.x[0] = 12 + +var x = foo "test" + +import typetraits +static: echo x.x.type.name + diff --git a/tests/generics/tsigtypeop.nim b/tests/generics/tsigtypeop.nim new file mode 100644 index 000000000..4c863cba1 --- /dev/null +++ b/tests/generics/tsigtypeop.nim @@ -0,0 +1,9 @@ +type + Vec3[T] = array[3, T] + +proc foo(x: Vec3, y: Vec3.T, z: x.T): x.type.T = + return 10 + +var y: Vec3[int] = [1, 2, 3] +var z: int = foo(y, 3, 4) + diff --git a/tests/iter/tchainediterators.nim b/tests/iter/tchainediterators.nim new file mode 100644 index 000000000..18d096761 --- /dev/null +++ b/tests/iter/tchainediterators.nim @@ -0,0 +1,38 @@ +discard """ + output: '''16 +32 +48 +64 +128 +192 +''' +""" + +iterator gaz(it: iterator{.inline.}): type(it) = + for x in it: + yield x*2 + +iterator baz(it: iterator{.inline.}) = + for x in gaz(it): + yield x*2 + +type T1 = auto + +iterator bar(it: iterator: T1{.inline.}): T1 = + for x in baz(it): + yield x*2 + +iterator foo[T](x: iterator: T{.inline.}): T = + for e in bar(x): + yield e*2 + +var s = @[1, 2, 3] + +# pass an interator several levels deep: +for x in s.items.foo: + echo x + +# use some complex iterator as an input for another one: +for x in s.items.baz.foo: + echo x + diff --git a/tests/iter/titerable.nim b/tests/iter/titerable.nim new file mode 100644 index 000000000..3ec79f68d --- /dev/null +++ b/tests/iter/titerable.nim @@ -0,0 +1,26 @@ +discard """ + output: '''2 +4 +6 +4 +8 +12 +''' +""" + +iterator map[T, U](s: iterator:T{.inline.}, f: proc(x: T): U): U = + for e in s: yield f(e) + +template toSeq(s: expr): expr = + var res = newSeq[type(s)](0) + for e in s: res.add(e) + res + +var s1 = @[1, 2, 3] +for x in map(s1.items, proc (a:int): int = a*2): + echo x + +var s2 = toSeq(map(s1.items, proc (a:int): int = a*4)) +for x in s2: + echo x + diff --git a/tests/metatype/tstaticparams.nim b/tests/metatype/tstaticparams.nim index b1377443b..6d7c569e0 100644 --- a/tests/metatype/tstaticparams.nim +++ b/tests/metatype/tstaticparams.nim @@ -1,6 +1,6 @@ discard """ file: "tstaticparams.nim" - output: "abracadabra\ntest\n3" + output: "abracadabra\ntest\n3\n15\n4\n2" """ type @@ -11,8 +11,11 @@ type data: array[I, T] TA1[T; I: static[int]] = array[I, T] - # TA2[T; I: static[int]] = array[0..I, T] - # TA3[T; I: static[int]] = array[I-1, T] + TA2[T; I: static[int]] = array[0..I, T] + TA3[T; I: static[int]] = array[I-1, T] + + TObj = object + x: TA3[int, 3] proc takeFoo(x: TFoo) = echo "abracadabra" @@ -26,6 +29,30 @@ echo high(y.data) var t1: TA1[float, 1] - # t2: TA2[string, 4] - # t3: TA3[int, 10] + t2: TA2[string, 4] + t3: TA3[int, 10] + t4: TObj + +# example from the manual: +type + Matrix[M,N: static[int]; T] = array[0..(M*N - 1), T] + # Note how `Number` is just a type constraint here, while + # `static[int]` requires us to supply a compile-time int value + + AffineTransform2D[T] = Matrix[3, 3, T] + AffineTransform3D[T] = Matrix[4, 4, T] + +var m: AffineTransform3D[float] +echo high(m) + +proc getRows(mtx: Matrix): int = + result = mtx.M + +echo getRows(m) + +# issue 997 +type TTest[T: static[int], U: static[int]] = array[0..T*U, int] +type TTestSub[N: static[int]] = TTest[1, N] +var z: TTestSub[2] +echo z.high diff --git a/tests/metatype/tusertypeclasses.nim b/tests/metatype/tusertypeclasses.nim index 5b04c490f..a5d575dbf 100644 --- a/tests/metatype/tusertypeclasses.nim +++ b/tests/metatype/tusertypeclasses.nim @@ -26,7 +26,7 @@ foo 10 foo "test" foo(@[TObj(x: 10), TObj(x: 20)]) -proc intval(x: int) = discard +proc intval(x: int): int = 10 # check real and virtual fields type @@ -34,7 +34,8 @@ type T.x y(T) intval T.y - + let z = intval(T.y) + proc y(x: TObj): int = 10 proc testFoo(x: TFoo) = discard diff --git a/tests/metatype/udtcmanual.nim b/tests/metatype/udtcmanual.nim index f22bd6ac6..dd44298dc 100644 --- a/tests/metatype/udtcmanual.nim +++ b/tests/metatype/udtcmanual.nim @@ -25,7 +25,7 @@ type C.len is Ordinal items(c) is iterator for value in C: - value.type is T + type(value) is T proc takesIntContainer(c: Container[int]) = for e in c: echo e diff --git a/tests/overload/tissue966.nim b/tests/overload/tissue966.nim new file mode 100644 index 000000000..2911348cf --- /dev/null +++ b/tests/overload/tissue966.nim @@ -0,0 +1,12 @@ +discard """ + errormsg: "type mismatch: got (PTest)" +""" + +type + PTest = ref object + +proc test(x: PTest, y: int) = nil + +var buf: PTest +buf.test() + diff --git a/tests/parser/tstrongspaces.nim b/tests/parser/tstrongspaces.nim new file mode 100644 index 000000000..91506daf0 --- /dev/null +++ b/tests/parser/tstrongspaces.nim @@ -0,0 +1,52 @@ +#! strongSpaces + +discard """ + output: '''35 +77 +(Field0: 1, Field1: 2, Field2: 2) +ha +true +tester args +all +all args +''' +""" + +echo 2+5 * 5 + +let foo = 77 +echo $foo + +echo (1, 2, 2) + +template `&`(a, b: int): expr = a and b +template `|`(a, b: int): expr = a - b +template `++`(a, b: int): expr = a + b == 8009 + +when true: + let b = 66 + let c = 90 + let bar = 8000 + if foo+4 * 4 == 8 and b&c | 9 ++ + bar: + echo "ho" + else: + echo "ha" + + let booA = foo+4 * 4 - b&c | 9 + + bar + # is parsed as + let booB = ((foo+4)*4) - ((b&c) | 9) + bar + + echo booA == booB + + +template `|`(a, b): expr = (if a.len > 0: a else: b) + +const + tester = "tester" + args = "args" + +echo tester & " " & args|"all" +echo "all" | tester & " " & args +echo "all"|tester & " " & args diff --git a/tests/static/tstaticparammacro.nim b/tests/static/tstaticparammacro.nim new file mode 100644 index 000000000..7fb9e2014 --- /dev/null +++ b/tests/static/tstaticparammacro.nim @@ -0,0 +1,52 @@ +discard """ + msg: '''letters +aa +bb +numbers +11 +22 +AST a +[(11, 22), (33, 44)] +AST b +(e: [55, 66], f: [77, 88]) +55 +''' +""" + +import macros + +type + TConfig = tuple + letters: seq[string] + numbers:seq[int] + +const data: Tconfig = (@["aa", "bb"], @[11, 22]) + +macro mymacro(data: static[TConfig]): stmt = + echo "letters" + for s in items(data.letters): + echo s + echo "numbers" + for n in items(data.numbers): + echo n + +mymacro(data) + +type + Ta = seq[tuple[c:int, d:int]] + Tb = tuple[e:seq[int], f:seq[int]] + +const + a : Ta = @[(11, 22), (33, 44)] + b : Tb = (@[55,66], @[77, 88]) + +macro mA(data: static[Ta]): stmt = + echo "AST a \n", repr(data) + +macro mB(data: static[Tb]): stmt = + echo "AST b \n", repr(data) + echo data.e[0] + +mA(a) +mB(b) + diff --git a/tests/stdlib/tpegs.nim b/tests/stdlib/tpegs.nim index 7775091a1..6e488bab4 100644 --- a/tests/stdlib/tpegs.nim +++ b/tests/stdlib/tpegs.nim @@ -72,7 +72,7 @@ type rule: TNode ## the rule that the symbol refers to TNode {.final, shallow.} = object case kind: TPegKind - of pkEmpty..pkWhitespace: discard + of pkEmpty..pkWhitespace: nil of pkTerminal, pkTerminalIgnoreCase, pkTerminalIgnoreStyle: term: string of pkChar, pkGreedyRepChar: ch: char of pkCharChoice, pkGreedyRepSet: charChoice: ref set[char] diff --git a/tests/system/alloc.nim b/tests/system/alloc.nim index 665b448ac..7abefec2a 100644 --- a/tests/system/alloc.nim +++ b/tests/system/alloc.nim @@ -2,44 +2,51 @@ var x: ptr int x = cast[ptr int](alloc(7)) assert x != nil - -x = alloc(int, 3) +x = cast[ptr int](x.realloc(2)) assert x != nil x.dealloc() -x = alloc0(int, 4) +x = createU(int, 3) +assert x != nil +x.free() + +x = create(int, 4) assert cast[ptr array[4, int]](x)[0] == 0 assert cast[ptr array[4, int]](x)[1] == 0 assert cast[ptr array[4, int]](x)[2] == 0 assert cast[ptr array[4, int]](x)[3] == 0 -x = cast[ptr int](x.realloc(2)) -assert x != nil - -x = x.reallocType(4) +x = x.resize(4) assert x != nil -x.dealloc() +x.free() x = cast[ptr int](allocShared(100)) assert x != nil deallocShared(x) -x = allocShared(int, 3) +x = createSharedU(int, 3) assert x != nil -x.deallocShared() +x.freeShared() -x = allocShared0(int, 3) +x = createShared(int, 3) assert x != nil assert cast[ptr array[3, int]](x)[0] == 0 assert cast[ptr array[3, int]](x)[1] == 0 assert cast[ptr array[3, int]](x)[2] == 0 -x = cast[ptr int](reallocShared(x, 2)) assert x != nil +x = cast[ptr int](x.resizeShared(2)) +assert x != nil +x.freeShared() -x = reallocType(x, 12) +x = create(int, 10) assert x != nil +x = x.resize(12) +assert x != nil +x.dealloc() -x = reallocSharedType(x, 1) +x = createShared(int, 1) +assert x != nil +x = x.resizeShared(1) assert x != nil -x.deallocShared() +x.freeShared() diff --git a/tests/template/tissue909.nim b/tests/template/tissue909.nim new file mode 100644 index 000000000..5b57a3558 --- /dev/null +++ b/tests/template/tissue909.nim @@ -0,0 +1,16 @@ +import macros + +template baz() = + proc bar() = + var x = 5 + iterator foo(): int {.closure.} = + echo x + var y = foo + discard y() + +macro test(): stmt = + result = getAst(baz()) + echo(treeRepr(result)) + +test() +bar() diff --git a/tests/template/tissue993.nim b/tests/template/tissue993.nim new file mode 100644 index 000000000..d39f43942 --- /dev/null +++ b/tests/template/tissue993.nim @@ -0,0 +1,21 @@ + +type pnode* = ref object of tobject + +template litNode (name, ty): stmt = + type name* = ref object of PNode + val*: ty +litNode PIntNode, int + +import json + +template withKey*(j: PJsonNode; key: string; varname: expr; + body:stmt): stmt {.immediate.} = + if j.hasKey(key): + let varname{.inject.}= j[key] + block: + body + +var j = parsejson("{\"zzz\":1}") +withkey(j, "foo", x): + echo(x) + diff --git a/tests/testament/htmlgen.nim b/tests/testament/htmlgen.nim index 74d8811b8..89d56c693 100644 --- a/tests/testament/htmlgen.nim +++ b/tests/testament/htmlgen.nim @@ -174,6 +174,10 @@ proc generateJson*(filename: string, commit: int) = on A.name = B.name and A.category = B.category where A.[commit] = ? and B.[commit] = ? and A.machine = ? and A.result != B.result""" + selResults = """select + name, category, target, action, result, expected, given + from TestResult + where [commit] = ?""" var db = open(connection="testament.db", user="testament", password="", database="testament") let lastCommit = db.getCommit(commit) @@ -189,6 +193,20 @@ proc generateJson*(filename: string, commit: int) = outfile.writeln("""{"total": $#, "passed": $#, "skipped": $#""" % data) + let results = newJArray() + for row in db.rows(sql(selResults), lastCommit): + var obj = newJObject() + obj["name"] = %row[0] + obj["category"] = %row[1] + obj["target"] = %row[2] + obj["action"] = %row[3] + obj["result"] = %row[4] + obj["expected"] = %row[5] + obj["given"] = %row[6] + results.add(obj) + outfile.writeln(""", "results": """) + outfile.write(results.pretty) + if not previousCommit.isNil: let diff = newJArray() diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim index fac97cf2a..923cd7518 100644 --- a/tests/testament/tester.nim +++ b/tests/testament/tester.nim @@ -11,7 +11,7 @@ import parseutils, strutils, pegs, os, osproc, streams, parsecfg, json, - marshal, backend, parseopt, specs, htmlgen, browsers + marshal, backend, parseopt, specs, htmlgen, browsers, terminal const resultsFile = "testresults.html" @@ -109,6 +109,12 @@ proc addResult(r: var TResults, test: TTest, expected = expected, given = given) r.data.addf("$#\t$#\t$#\t$#", name, expected, given, $success) + if success notin {reSuccess, reIgnored}: + styledEcho styleBright, fgRed, "^^^ [", $success, "]" + styledEcho styleDim, "EXPECTED:" + echo expected + styledEcho styleDim, "GIVEN:" + echo given proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest) = if strip(expected.msg) notin strip(given.msg): diff --git a/tests/vm/tstaticprintseq.nim b/tests/vm/tstaticprintseq.nim index 99a56d161..4575f3af1 100644 --- a/tests/vm/tstaticprintseq.nim +++ b/tests/vm/tstaticprintseq.nim @@ -4,7 +4,17 @@ discard """ 3 1 2 -3''' +3 +1 +2 +3 +1 +2 +3 +aa +bb +aa +bb''' """ const s = @[1,2,3] @@ -19,3 +29,27 @@ static: for e in s: echo e +macro bar(x: static[seq[int]]): stmt = + for e in x: + echo e + +bar s +bar(@[1, 2, 3]) + +type + TData = tuple + letters: seq[string] + numbers: seq[int] + +const data: TData = (@["aa", "bb"], @[11, 22]) + +static: + var m = data + for x in m.letters: + echo x + +macro ff(d: static[TData]): stmt = + for x in d.letters: + echo x + +ff(data) diff --git a/tests/vm/twrongconst.nim b/tests/vm/twrongconst.nim index e5b8a15bd..5c0c80f9f 100644 --- a/tests/vm/twrongconst.nim +++ b/tests/vm/twrongconst.nim @@ -1,10 +1,9 @@ discard """ - output: "Error: cannot evaluate at compile time: x" - line: 10 + errormsg: "cannot evaluate at compile time: x" + line: 9 """ -var x: array[100, char] +var x: array[100, char] template Foo : expr = x[42] - const myConst = foo diff --git a/tests/vm/twrongwhen.nim b/tests/vm/twrongwhen.nim index 085bb6fb6..d67e42883 100644 --- a/tests/vm/twrongwhen.nim +++ b/tests/vm/twrongwhen.nim @@ -1,9 +1,9 @@ discard """ - output: "Error: cannot evaluate at compile time: x" + errormsg: "cannot evaluate at compile time: x" line: 7 """ -proc bla(x:int) = +proc bla(x:int) = when x == 0: echo "oops" else: diff --git a/todo.txt b/todo.txt index a67ff5172..3b085ac91 100644 --- a/todo.txt +++ b/todo.txt @@ -1,7 +1,6 @@ version 0.9.4 ============= -- make testament produce full JSON information - fix gensym capture bug - vm - at least try to get the basic type zoo ops right @@ -32,7 +31,6 @@ version 0.9.x - ensure (ref T)(a, b) works as a type conversion and type constructor - optimize 'genericReset'; 'newException' leads to code bloat - stack-less GC -- implement strongSpaces:on - make '--implicitStatic:on' the default - implicit deref for parameter matching diff --git a/web/news.txt b/web/news.txt index 0001cece7..83863cdd9 100644 --- a/web/news.txt +++ b/web/news.txt @@ -82,6 +82,7 @@ News - The *command syntax* is supported in a lot more contexts. - Anonymous iterators are now supported and iterators can capture variables of an outer proc. + - The experimental ``strongSpaces`` parsing mode has been implemented. Tools improvements |