diff options
-rw-r--r-- | compiler/ccgstmts.nim | 2 | ||||
-rw-r--r-- | compiler/ccgtypes.nim | 169 | ||||
-rw-r--r-- | compiler/cgen.nim | 6 | ||||
-rw-r--r-- | compiler/debuginfo.nim | 81 | ||||
-rw-r--r-- | compiler/extccomp.nim | 4 | ||||
-rw-r--r-- | compiler/lambdalifting.nim | 1 | ||||
-rw-r--r-- | compiler/lexer.nim | 4 | ||||
-rw-r--r-- | compiler/lookups.nim | 19 | ||||
-rw-r--r-- | compiler/semexprs.nim | 6 | ||||
-rw-r--r-- | compiler/semfold.nim | 9 | ||||
-rw-r--r-- | compiler/sigmatch.nim | 49 | ||||
-rw-r--r-- | compiler/transf.nim | 2 | ||||
-rw-r--r-- | compiler/vmgen.nim | 9 | ||||
-rw-r--r-- | doc/lib.txt | 5 | ||||
-rw-r--r-- | lib/core/macros.nim | 18 | ||||
-rw-r--r-- | lib/js/dom.nim | 5 | ||||
-rw-r--r-- | lib/pure/logging.nim | 28 | ||||
-rw-r--r-- | lib/pure/parseutils.nim | 30 | ||||
-rw-r--r-- | lib/pure/strscans.nim | 521 | ||||
-rw-r--r-- | lib/pure/strutils.nim | 2 | ||||
-rw-r--r-- | tests/ccgbugs/tmissingvolatile.nim | 2 | ||||
-rw-r--r-- | tests/closure/tdeeplynested.nim | 20 | ||||
-rw-r--r-- | web/news.txt | 4 | ||||
-rw-r--r-- | web/website.ini | 2 |
24 files changed, 871 insertions, 127 deletions
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index f2ceadcce..988c923c8 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -961,6 +961,8 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope = var a: TLoc initLocExpr(p, t.sons[i], a) res.add($rdLoc(a)) + elif sym.kind == skType: + res.add($getTypeDesc(p.module, sym.typ)) else: var r = sym.loc.r if r == nil: diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index ebc3225f2..195417342 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -1,7 +1,7 @@ # # # The Nim Compiler -# (c) Copyright 2013 Andreas Rumpf +# (c) Copyright 2016 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -11,6 +11,8 @@ # ------------------------- Name Mangling -------------------------------- +import debuginfo + proc isKeyword(w: PIdent): bool = # Nim and C++ share some keywords # it's more efficient to test the whole Nim keywords range @@ -26,67 +28,66 @@ proc mangleField(name: PIdent): string = result[0] = result[0].toUpper # Mangling makes everything lowercase, # but some identifiers are C keywords +proc hashOwner(s: PSym): FilenameHash = + var m = s + while m.kind != skModule: m = m.owner + let p = m.owner + assert p.kind == skPackage + result = gDebugInfo.register(p.name.s, m.name.s) + proc mangleName(s: PSym): Rope = result = s.loc.r if result == nil: - when oKeepVariableNames: - let keepOrigName = s.kind in skLocalVars - {skForVar} and - {sfFromGeneric, sfGlobal, sfShadowed, sfGenSym} * s.flags == {} and - not isKeyword(s.name) - # XXX: This is still very experimental - # - # Even with all these inefficient checks, the bootstrap - # time is actually improved. This is probably because so many - # rope concatenations are now eliminated. - # - # Future notes: - # sfFromGeneric seems to be needed in order to avoid multiple - # definitions of certain variables generated in transf with - # names such as: - # `r`, `res` - # I need to study where these come from. - # - # about sfShadowed: - # consider the following Nim code: - # var x = 10 - # block: - # var x = something(x) - # The generated C code will be: - # NI x; - # x = 10; - # { - # NI x; - # x = something(x); // Oops, x is already shadowed here - # } - # Right now, we work-around by not keeping the original name - # of the shadowed variable, but we can do better - we can - # create an alternative reference to it in the outer scope and - # use that in the inner scope. - # - # about isCKeyword: - # Nim variable names can be C keywords. - # We need to avoid such names in the generated code. - # XXX: Study whether mangleName is called just once per variable. - # Otherwise, there might be better place to do this. - # - # about sfGlobal: - # This seems to be harder - a top level extern variable from - # another modules can have the same name as a local one. - # Maybe we should just implement sfShadowed for them too. - # - # about skForVar: - # These are not properly scoped now - we need to add blocks - # around for loops in transf - if keepOrigName: - result = s.name.s.mangle.rope - else: - add(result, rope(mangle(s.name.s))) - add(result, ~"_") - add(result, rope(s.id)) + let keepOrigName = s.kind in skLocalVars - {skForVar} and + {sfFromGeneric, sfGlobal, sfShadowed, sfGenSym} * s.flags == {} and + not isKeyword(s.name) + # Even with all these inefficient checks, the bootstrap + # time is actually improved. This is probably because so many + # rope concatenations are now eliminated. + # + # sfFromGeneric is needed in order to avoid multiple + # definitions of certain variables generated in transf with + # names such as: + # `r`, `res` + # I need to study where these come from. + # + # about sfShadowed: + # consider the following Nim code: + # var x = 10 + # block: + # var x = something(x) + # The generated C code will be: + # NI x; + # x = 10; + # { + # NI x; + # x = something(x); // Oops, x is already shadowed here + # } + # Right now, we work-around by not keeping the original name + # of the shadowed variable, but we can do better - we can + # create an alternative reference to it in the outer scope and + # use that in the inner scope. + # + # about isCKeyword: + # Nim variable names can be C keywords. + # We need to avoid such names in the generated code. + # + # about sfGlobal: + # This seems to be harder - a top level extern variable from + # another modules can have the same name as a local one. + # Maybe we should just implement sfShadowed for them too. + # + # about skForVar: + # These are not properly scoped now - we need to add blocks + # around for loops in transf + result = s.name.s.mangle.rope + if keepOrigName: + result.add "0" else: - add(result, rope(mangle(s.name.s))) add(result, ~"_") add(result, rope(s.id)) + add(result, ~"_") + add(result, rope(hashOwner(s).BiggestInt)) s.loc.r = result proc typeName(typ: PType): Rope = @@ -242,18 +243,6 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope = case typ.kind of tyPointer: result = typeNameOrLiteral(typ, "void*") - of tyEnum: - if firstOrd(typ) < 0: - result = typeNameOrLiteral(typ, "NI32") - else: - case int(getSize(typ)) - of 1: result = typeNameOrLiteral(typ, "NU8") - of 2: result = typeNameOrLiteral(typ, "NU16") - of 4: result = typeNameOrLiteral(typ, "NI32") - of 8: result = typeNameOrLiteral(typ, "NI64") - else: - internalError(typ.sym.info, "getSimpleTypeDesc: " & $(getSize(typ))) - result = nil of tyString: discard cgsym(m, "NimStringDesc") result = typeNameOrLiteral(typ, "NimStringDesc*") @@ -578,6 +567,33 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope = of tyOpenArray, tyVarargs: result = getTypeDescWeak(m, t.sons[0], check) & "*" idTablePut(m.typeCache, t, result) + of tyRange, tyEnum: + let t = if t.kind == tyRange: t.lastSon else: t + result = getTypeName(t) + if isImportedCppType(t) or + (sfImportc in t.sym.flags and t.sym.magic == mNone): return + idTablePut(m.typeCache, t, result) + var size: int + if firstOrd(t) < 0: + addf(m.s[cfsTypes], "typedef NI32 $1;$n", [result]) + size = 4 + else: + size = int(getSize(t)) + case size + of 1: addf(m.s[cfsTypes], "typedef NU8 $1;$n", [result]) + of 2: addf(m.s[cfsTypes], "typedef NU16 $1;$n", [result]) + of 4: addf(m.s[cfsTypes], "typedef NI32 $1;$n", [result]) + of 8: addf(m.s[cfsTypes], "typedef NI64 $1;$n", [result]) + else: internalError(t.sym.info, "getTypeDescAux: enum") + let owner = hashOwner(t.sym) + if not gDebugInfo.hasEnum(t.sym.name.s, t.sym.info.line, owner): + var vals: seq[(string, int)] = @[] + for i in countup(0, t.n.len - 1): + assert(t.n.sons[i].kind == nkSym) + let field = t.n.sons[i].sym + vals.add((field.name.s, field.position.int)) + gDebugInfo.registerEnum(EnumDesc(size: size, owner: owner, id: t.sym.id, + name: t.sym.name.s, values: vals)) of tyProc: result = getTypeName(t) idTablePut(m.typeCache, t, result) @@ -673,16 +689,13 @@ proc getTypeDescAux(m: BModule, typ: PType, check: var IntSet): Rope = else: getTupleDesc(m, t, result, check) if not isImportedType(t): add(m.s[cfsTypes], recdesc) of tySet: - case int(getSize(t)) - of 1: result = rope("NU8") - of 2: result = rope("NU16") - of 4: result = rope("NU32") - of 8: result = rope("NU64") - else: - result = getTypeName(t) - idTablePut(m.typeCache, t, result) - if not isImportedType(t): - addf(m.s[cfsTypes], "typedef NU8 $1[$2];$n", + result = getTypeName(t.lastSon) & "Set" + idTablePut(m.typeCache, t, result) + if not isImportedType(t): + let s = int(getSize(t)) + case s + of 1, 2, 4, 8: addf(m.s[cfsTypes], "typedef NU$2 $1;$n", [result, rope(s*8)]) + else: addf(m.s[cfsTypes], "typedef NU8 $1[$2];$n", [result, rope(getSize(t))]) of tyGenericInst, tyDistinct, tyOrdinal, tyConst, tyMutable, tyIter, tyTypeDesc: diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 4f27a5a46..0a8f89f2e 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -301,7 +301,8 @@ proc resetLoc(p: BProc, loc: var TLoc) = proc constructLoc(p: BProc, loc: TLoc, isTemp = false) = let typ = skipTypes(loc.t, abstractRange) if not isComplexValueType(typ): - linefmt(p, cpsStmts, "$1 = 0;$n", rdLoc(loc)) + linefmt(p, cpsStmts, "$1 = ($2)0;$n", rdLoc(loc), + getTypeDesc(p.module, typ)) else: if not isTemp or containsGarbageCollectedRef(loc.t): # don't use memset for temporary values for performance if we can @@ -330,7 +331,8 @@ proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false) = linefmt(p, cpsLocals, "$1 $2;$n", getTypeDesc(p.module, t), result.r) result.k = locTemp #result.a = - 1 - result.t = getUniqueType(t) + result.t = t + #result.t = getUniqueType(t) result.s = OnStack result.flags = {} constructLoc(p, result, not needsInit) diff --git a/compiler/debuginfo.nim b/compiler/debuginfo.nim new file mode 100644 index 000000000..8589730b9 --- /dev/null +++ b/compiler/debuginfo.nim @@ -0,0 +1,81 @@ +# +# +# The Nim Compiler +# (c) Copyright 2016 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## The compiler can generate debuginfo to help debuggers in translating back from C/C++/JS code +## to Nim. The data structure has been designed to produce something useful with Nim's marshal +## module. + +type + FilenameHash* = uint32 + FilenameMapping* = object + package*, file*: string + mangled*: FilenameHash + EnumDesc* = object + size*: int + owner*: FilenameHash + id*: int + name*: string + values*: seq[(string, int)] + DebugInfo* = object + version*: int + files*: seq[FilenameMapping] + enums*: seq[EnumDesc] + conflicts*: bool + +proc sdbmHash(hash: FilenameHash, c: char): FilenameHash {.inline.} = + return FilenameHash(c) + (hash shl 6) + (hash shl 16) - hash + +proc sdbmHash(package, file: string): FilenameHash = + template `&=`(x, c) = x = sdbmHash(x, c) + result = 0 + for i in 0..<package.len: + result &= package[i] + result &= '.' + for i in 0..<file.len: + result &= file[i] + +proc register*(self: var DebugInfo; package, file: string): FilenameHash = + result = sdbmHash(package, file) + for f in self.files: + if f.mangled == result: + if f.package == package and f.file == file: return + self.conflicts = true + break + self.files.add(FilenameMapping(package: package, file: file, mangled: result)) + +proc hasEnum*(self: DebugInfo; ename: string; id: int; owner: FilenameHash): bool = + for en in self.enums: + if en.owner == owner and en.name == ename and en.id == id: return true + +proc registerEnum*(self: var DebugInfo; ed: EnumDesc) = + self.enums.add ed + +proc init*(self: var DebugInfo) = + self.version = 1 + self.files = @[] + self.enums = @[] + +var gDebugInfo*: DebugInfo +debuginfo.init gDebugInfo + +import marshal, streams + +proc writeDebugInfo*(self: var DebugInfo; file: string) = + let s = newFileStream(file, fmWrite) + store(s, self) + s.close + +proc writeDebugInfo*(file: string) = writeDebugInfo(gDebugInfo, file) + +proc loadDebugInfo*(self: var DebugInfo; file: string) = + let s = newFileStream(file, fmRead) + load(s, self) + s.close + +proc loadDebugInfo*(file: string) = loadDebugInfo(gDebugInfo, file) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 16a8b8bd4..b2ee9c7f1 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -16,6 +16,8 @@ import lists, ropes, os, strutils, osproc, platform, condsyms, options, msgs, securehash, streams +from debuginfo import writeDebugInfo + type TSystemCC* = enum ccNone, ccGcc, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc, @@ -736,6 +738,8 @@ proc callCCompiler*(projectfile: string) = if not noAbsolutePaths(): if not exefile.isAbsolute(): exefile = joinPath(splitFile(projectfile).dir, exefile) + if optCDebug in gGlobalOptions: + writeDebugInfo(exefile.changeFileExt("ndb")) exefile = quoteShell(exefile) let linkOptions = getLinkOptions() & " " & getConfigVar(cCompiler, ".options.linker") diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 959632bab..753602c80 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -716,6 +716,7 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass; #localError(n.info, "internal error: closure to closure created") # now we know better, so patch it: n.sons[0] = x.sons[0] + n.sons[1] = x.sons[1] of nkLambdaKinds, nkIteratorDef: if n.typ != nil and n[namePos].kind == nkSym: let m = newSymNode(n[namePos].sym) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 0032b97df..0a4c01ba8 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -811,10 +811,6 @@ proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int; break dec nesting inc pos - of '\t': - lexMessagePos(L, errTabulatorsAreNotAllowed, pos) - inc(pos) - if isDoc: tok.literal.add '\t' of CR, LF: pos = handleCRLF(L, pos) buf = L.buf diff --git a/compiler/lookups.nim b/compiler/lookups.nim index a337bf0f3..962c28613 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -221,6 +221,19 @@ when defined(nimfix): else: template fixSpelling(n: PNode; ident: PIdent; op: expr) = discard +proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) = + var err = "Error: ambiguous identifier: '" & s.name.s & "'" + var ti: TIdentIter + var candidate = initIdentIter(ti, c.importTable.symbols, s.name) + var i = 0 + while candidate != nil: + if i == 0: err.add " --use " + else: err.add " or " + err.add candidate.owner.name.s & "." & candidate.name.s + candidate = nextIdentIter(ti, c.importTable.symbols) + inc i + localError(info, errGenerated, err) + proc lookUp*(c: PContext, n: PNode): PSym = # Looks up a symbol. Generates an error in case of nil. case n.kind @@ -243,7 +256,7 @@ proc lookUp*(c: PContext, n: PNode): PSym = internalError(n.info, "lookUp") return if contains(c.ambiguousSymbols, result.id): - localError(n.info, errUseQualifier, result.name.s) + errorUseQualifier(c, n.info, result) if result.kind == skStub: loadStub(result) type @@ -261,11 +274,11 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags = {checkUndeclared}): PSym = result = errorSym(c, n) elif checkAmbiguity in flags and result != nil and contains(c.ambiguousSymbols, result.id): - localError(n.info, errUseQualifier, ident.s) + errorUseQualifier(c, n.info, result) of nkSym: result = n.sym if checkAmbiguity in flags and contains(c.ambiguousSymbols, result.id): - localError(n.info, errUseQualifier, n.sym.name.s) + errorUseQualifier(c, n.info, n.sym) of nkDotExpr: result = nil var m = qualifiedLookUp(c, n.sons[0], flags*{checkUndeclared}) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 105667aab..052098864 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -213,7 +213,7 @@ proc semConv(c: PContext, n: PNode): PNode = styleCheckUse(n.info, it.sym) markIndirect(c, it.sym) return it - localError(n.info, errUseQualifier, op.sons[0].sym.name.s) + errorUseQualifier(c, n.info, op.sons[0].sym) proc semCast(c: PContext, n: PNode): PNode = ## Semantically analyze a casting ("cast[type](param)") @@ -2227,7 +2227,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = elif n.len == 1: result = semObjConstr(c, n, flags) elif contains(c.ambiguousSymbols, s.id): - localError(n.info, errUseQualifier, s.name.s) + errorUseQualifier(c, n.info, s) elif s.magic == mNone: result = semDirectOp(c, n, flags) else: result = semMagic(c, n, s, flags) of skProc, skMethod, skConverter, skIterator: @@ -2343,7 +2343,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "from") result = evalFrom(c, n) of nkIncludeStmt: - if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "include") + #if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "include") result = evalInclude(c, n) of nkExportStmt, nkExportExceptStmt: if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "export") diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 5fe4e3299..c5a8cc2a2 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -419,7 +419,14 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = result = newStrNodeT(substr(getStr(a), int(getOrdValue(b)), int(getOrdValue(c))), n) of mFloatToStr: result = newStrNodeT($getFloat(a), n) - of mCStrToStr, mCharToStr: result = newStrNodeT(getStrOrChar(a), n) + of mCStrToStr, mCharToStr: + if a.kind == nkBracket: + var s = "" + for b in a.sons: + s.add b.getStrOrChar + result = newStrNodeT(s, n) + else: + result = newStrNodeT(getStrOrChar(a), n) of mStrToStr: result = a of mEnumToStr: result = newStrNodeT(ordinalValToString(a), n) of mArrToSeq: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 09758e05d..ecef11d13 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1659,30 +1659,39 @@ proc matchesAux(c: PContext, n, nOrig: PNode, when false: localError(n.sons[a].info, errCannotBindXTwice, formal.name.s) m.state = csNoMatch return - m.baseTypeMatch = false - n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) - var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, - n.sons[a], nOrig.sons[a]) - if arg == nil: - m.state = csNoMatch - return - if m.baseTypeMatch: - #assert(container == nil) + + if formal.typ.isVarargsUntyped: if container.isNil: - container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, arg)) + container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, n.info)) + setSon(m.call, formal.position + 1, container) else: incrIndexType(container.typ) - addSon(container, arg) - setSon(m.call, formal.position + 1, - implicitConv(nkHiddenStdConv, formal.typ, container, m, c)) - #if f != formalLen - 1: container = nil - - # pick the formal from the end, so that 'x, y, varargs, z' works: - f = max(f, formalLen - n.len + a + 1) + addSon(container, n.sons[a]) else: - setSon(m.call, formal.position + 1, arg) - inc(f) - container = nil + m.baseTypeMatch = false + n.sons[a] = prepareOperand(c, formal.typ, n.sons[a]) + var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ, + n.sons[a], nOrig.sons[a]) + if arg == nil: + m.state = csNoMatch + return + if m.baseTypeMatch: + #assert(container == nil) + if container.isNil: + container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, arg)) + else: + incrIndexType(container.typ) + addSon(container, arg) + setSon(m.call, formal.position + 1, + implicitConv(nkHiddenStdConv, formal.typ, container, m, c)) + #if f != formalLen - 1: container = nil + + # pick the formal from the end, so that 'x, y, varargs, z' works: + f = max(f, formalLen - n.len + a + 1) + else: + setSon(m.call, formal.position + 1, arg) + inc(f) + container = nil checkConstraint(n.sons[a]) inc(a) diff --git a/compiler/transf.nim b/compiler/transf.nim index 0647553c6..25988fb8c 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -95,7 +95,7 @@ proc getCurrOwner(c: PTransf): PSym = proc newTemp(c: PTransf, typ: PType, info: TLineInfo): PNode = let r = newSym(skTemp, getIdent(genPrefix), getCurrOwner(c), info) - r.typ = skipTypes(typ, {tyGenericInst}) + r.typ = typ #skipTypes(typ, {tyGenericInst}) incl(r.flags, sfFromGeneric) let owner = getCurrOwner(c) if owner.isIterator and not c.tooEarly: diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 019c79eb3..bd32ccc17 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1337,10 +1337,11 @@ proc genGlobalInit(c: PCtx; n: PNode; s: PSym) = # var decls{.compileTime.}: seq[NimNode] = @[] let dest = c.getTemp(s.typ) c.gABx(n, opcLdGlobal, dest, s.position) - let tmp = c.genx(s.ast) - c.preventFalseAlias(n, opcWrDeref, dest, 0, tmp) - c.freeTemp(dest) - c.freeTemp(tmp) + if s.ast != nil: + let tmp = c.genx(s.ast) + c.preventFalseAlias(n, opcWrDeref, dest, 0, tmp) + c.freeTemp(dest) + c.freeTemp(tmp) proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = let s = n.sym diff --git a/doc/lib.txt b/doc/lib.txt index 5ff6de7fd..e24db97e0 100644 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -87,7 +87,7 @@ Collections and algorithms * `sequtils <sequtils.html>`_ This module implements operations for the built-in seq type which were inspired by functional programming languages. - + String handling --------------- @@ -100,6 +100,9 @@ String handling * `parseutils <parseutils.html>`_ This module contains helpers for parsing tokens, numbers, identifiers, etc. +* `strscans <strscans.html>`_ + This module contains a ``scanf`` macro for convenient parsing of mini languages. + * `strtabs <strtabs.html>`_ The ``strtabs`` module implements an efficient hash table that is a mapping from strings to strings. Supports a case-sensitive, case-insensitive and diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 678982a52..2e3596d0f 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -818,6 +818,8 @@ proc cmpIgnoreStyle(a, b: cstring): int {.noSideEffect.} = else: result = c var i = 0 var j = 0 + # first char is case sensitive + if a[0] != b[0]: return 1 while true: while a[i] == '_': inc(i) while b[j] == '_': inc(j) # BUGFIX: typo @@ -828,9 +830,23 @@ proc cmpIgnoreStyle(a, b: cstring): int {.noSideEffect.} = inc(i) inc(j) -proc eqIdent* (a, b: string): bool = cmpIgnoreStyle(a, b) == 0 +proc eqIdent*(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 ## Check if two idents are identical. +proc eqIdent*(node: NimNode; s: string): bool {.compileTime.} = + ## Check if node is some identifier node (``nnkIdent``, ``nnkSym``, etc.) + ## is the same as ``s``. Note that this is the preferred way to check! Most + ## other ways like ``node.ident`` are much more error-prone, unfortunately. + case node.kind + of nnkIdent: + result = node.ident == !s + of nnkSym: + result = eqIdent($node.symbol, s) + of nnkOpenSymChoice, nnkClosedSymChoice: + result = eqIdent($node[0], s) + else: + result = false + proc hasArgOfName* (params: NimNode; name: string): bool {.compiletime.}= ## Search nnkFormalParams for an argument. assert params.kind == nnkFormalParams diff --git a/lib/js/dom.nim b/lib/js/dom.nim index 11df959d7..5104712e8 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -402,7 +402,9 @@ proc routeEvent*(w: Window, event: Event) proc scrollBy*(w: Window, x, y: int) proc scrollTo*(w: Window, x, y: int) proc setInterval*(w: Window, code: cstring, pause: int): ref TInterval +proc setInterval*(w: Window, function: proc (), pause: int): ref TInterval proc setTimeout*(w: Window, code: cstring, pause: int): ref TTimeOut +proc setTimeout*(w: Window, function: proc (), pause: int): ref TInterval proc stop*(w: Window) # Node "methods" @@ -481,6 +483,9 @@ proc getAttribute*(s: Style, attr: cstring, caseSensitive=false): cstring proc removeAttribute*(s: Style, attr: cstring, caseSensitive=false) proc setAttribute*(s: Style, attr, value: cstring, caseSensitive=false) +# Event "methods" +proc preventDefault*(ev: Event) + {.pop.} var diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index f602ce31d..8a0d2fedd 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -54,6 +54,7 @@ type lvlAll, ## all levels active lvlDebug, ## debug level (and any above) active lvlInfo, ## info level (and any above) active + lvlNotice, ## info notice (and any above) active lvlWarn, ## warn level (and any above) active lvlError, ## error level (and any above) active lvlFatal, ## fatal level (and any above) active @@ -61,7 +62,7 @@ type const LevelNames*: array [Level, string] = [ - "DEBUG", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE" + "DEBUG", "DEBUG", "INFO", "NOTICE", "WARN", "ERROR", "FATAL", "NONE" ] defaultFmtStr* = "$levelname " ## default format string @@ -258,22 +259,47 @@ template log*(level: Level, args: varargs[string, `$`]) = template debug*(args: varargs[string, `$`]) = ## Logs a debug message to all registered handlers. + ## + ## Messages that are useful to the application developer only and are usually + ## turned off in release. log(lvlDebug, args) template info*(args: varargs[string, `$`]) = ## Logs an info message to all registered handlers. + ## + ## Messages that are generated during the normal operation of an application + ## and are of no particular importance. Useful to aggregate for potential + ## later analysis. log(lvlInfo, args) +template notice*(args: varargs[string, `$`]) = + ## Logs an notice message to all registered handlers. + ## + ## Semantically very similar to `info`, but meant to be messages you want to + ## be actively notified about (depending on your application). + ## These could be, for example, grouped by hour and mailed out. + log(lvlNotice, args) + template warn*(args: varargs[string, `$`]) = ## Logs a warning message to all registered handlers. + ## + ## A non-error message that may indicate a potential problem rising or + ## impacted performance. log(lvlWarn, args) template error*(args: varargs[string, `$`]) = ## Logs an error message to all registered handlers. + ## + ## A application-level error condition. For example, some user input generated + ## an exception. The application will continue to run, but functionality or + ## data was impacted, possibly visible to users. log(lvlError, args) template fatal*(args: varargs[string, `$`]) = ## Logs a fatal error message to all registered handlers. + ## + ## A application-level fatal condition. FATAL usually means that the application + ## cannot go on and will exit (but this logging event will not do that for you). log(lvlFatal, args) proc addHandler*(handler: Logger) = diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index 3e25eba22..fb7d72182 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -173,6 +173,22 @@ proc parseUntil*(s: string, token: var string, until: char, result = i-start token = substr(s, start, i-1) +proc parseUntil*(s: string, token: var string, until: string, + start = 0): int {.inline.} = + ## parses a token and stores it in ``token``. Returns + ## the number of the parsed characters or 0 in case of an error. A token + ## consists of any character that comes before the `until` token. + var i = start + while i < s.len: + if s[i] == until[0]: + var u = 1 + while i+u < s.len and u < until.len and s[i+u] == until[u]: + inc u + if u >= until.len: break + inc(i) + result = i-start + token = substr(s, start, i-1) + proc parseWhile*(s: string, token: var string, validChars: set[char], start = 0): int {.inline.} = ## parses a token and stores it in ``token``. Returns @@ -240,7 +256,7 @@ proc rawParseUInt(s: string, b: var uint64, start = 0): int = res = 0'u64 prev = 0'u64 i = start - if s[i] == '+': inc(i) # Allow + if s[i] == '+': inc(i) # Allow if s[i] in {'0'..'9'}: b = 0 while s[i] in {'0'..'9'}: @@ -255,8 +271,10 @@ proc rawParseUInt(s: string, b: var uint64, start = 0): int = proc parseBiggestUInt*(s: string, number: var uint64, start = 0): int {. rtl, extern: "npuParseBiggestUInt", noSideEffect.} = - ## parses an unsigned integer starting at `start` and stores the value into `number`. - ## Result is the number of processed chars or 0 if there is no integer or overflow detected. + ## parses an unsigned integer starting at `start` and stores the value + ## into `number`. + ## Result is the number of processed chars or 0 if there is no integer + ## or overflow detected. var res: uint64 # use 'res' for exception safety (don't write to 'number' in case of an # overflow exception): @@ -265,8 +283,10 @@ proc parseBiggestUInt*(s: string, number: var uint64, start = 0): int {. proc parseUInt*(s: string, number: var uint, start = 0): int {. rtl, extern: "npuParseUInt", noSideEffect.} = - ## parses an unsigned integer starting at `start` and stores the value into `number`. - ## Result is the number of processed chars or 0 if there is no integer or overflow detected. + ## parses an unsigned integer starting at `start` and stores the value + ## into `number`. + ## Result is the number of processed chars or 0 if there is no integer or + ## overflow detected. var res: uint64 result = parseBiggestUInt(s, res, start) if (sizeof(uint) <= 4) and diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim new file mode 100644 index 000000000..f695c3e2a --- /dev/null +++ b/lib/pure/strscans.nim @@ -0,0 +1,521 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2016 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +##[ +This module contains a `scanf`:idx: macro that can be used for extracting +substrings from an input string. This is often easier than regular expressions. +Some examples as an apetizer: + +.. code-block:: nim + # check if input string matches a triple of integers: + const input = "(1,2,4)" + var x, y, z: int + if scanf(input, "($i,$i,$i)", x, y, z): + echo "matches and x is ", x, " y is ", y, " z is ", z + + # check if input string matches an ISO date followed by an identifier followed + # by whitespace and a floating point number: + var year, month, day: int + var identifier: string + var myfloat: float + if scanf(input, "$i-$i-$i $w$s$f", year, month, day, identifier, myfloat): + echo "yes, we have a match!" + +As can be seen from the examples, strings are matched verbatim except for +substrings starting with ``$``. These constructions are available: + +================= ======================================================== +``$i`` Matches an integer. This uses ``parseutils.parseInt``. +``$f`` Matches a floating pointer number. Uses ``parseFloat``. +``$w`` Matches an ASCII identifier: ``[A-Z-a-z_][A-Za-z_0-9]*``. +``$s`` Skips optional whitespace. +``$$`` Matches a single dollar sign. +``$.`` Matches if the end of the input string has been reached. +``$*`` Matches until the token following the ``$*`` was found. + The match is allowed to be of 0 length. +``$+`` Matches until the token following the ``$+`` was found. + The match must consist of at least one char. +``${foo}`` User defined matcher. Uses the proc ``foo`` to perform + the match. See below for more details. +``$[foo]`` Call user defined proc ``foo`` to **skip** some optional + parts in the input string. See below for more details. +================= ======================================================== + +Even though ``$*`` and ``$+`` look similar to the regular expressions ``.*`` +and ``.+`` they work quite differently, there is no non-deterministic +state machine involved and the matches are non-greedy. ``[$*]`` +matches ``[xyz]`` via ``parseutils.parseUntil``. + +Furthermore no backtracking is performed, if parsing fails after a value +has already been bound to a matched subexpression this value is not restored +to its original value. This rarely causes problems in practice and if it does +for you, it's easy enough to bind to a temporary variable first. + + +Startswith vs full match +======================== + +``scanf`` returns true if the input string **starts with** the specified +pattern. If instead it should only return true if theres is also nothing +left in the input, append ``$.`` to your pattern. + + +User definable matchers +======================= + +One very nice advantage over regular expressions is that ``scanf`` is +extensible with ordinary Nim procs. The proc is either enclosed in ``${}`` +or in ``$[]``. ``${}`` matches and binds the result +to a variable (that was passed to the ``scanf`` macro) while ``$[]`` merely +optional tokens. + + +In this example, we define a helper proc ``skipSep`` that skips some separators +which we then use in our scanf pattern to help us in the matching process: + +.. code-block:: nim + + proc someSep(input: string; start: int; seps: set[char] = {':','-','.'}): int = + # Note: The parameters and return value must match to what ``scanf`` requires + result = 0 + while input[start+result] in seps: inc result + + if scanf(input, "$w${someSep}$w", key, value): + ... + +It also possible to pass arguments to a user definable matcher: + +.. code-block:: nim + + proc ndigits(input: string; start: int; intVal: var int; n: int): int = + # matches exactly ``n`` digits. Matchers need to return 0 if nothing + # matched or otherwise the number of processed chars. + var x = 0 + var i = 0 + while i < n and i+start < input.len and input[i+start] in {'0'..'9'}: + x = x * 10 + input[i+start].ord - '0'.ord + inc i + # only overwrite if we had a match + if i == n: + result = n + intVal = x + + # match an ISO date extracting year, month, day at the same time. + # Also ensure the input ends after the ISO date: + var year, month, day: int + if scanf("2013-01-03", "${ndigits(4)}-${ndigits(2)}-${ndigits(2)}$.", year, month, day): + ... + +]## + + +import macros, parseutils + +proc conditionsToIfChain(n, idx, res: NimNode; start: int): NimNode = + assert n.kind == nnkStmtList + if start >= n.len: return newAssignment(res, newLit true) + var ifs: NimNode = nil + if n[start+1].kind == nnkEmpty: + ifs = conditionsToIfChain(n, idx, res, start+3) + else: + ifs = newIfStmt((n[start+1], + newTree(nnkStmtList, newCall(bindSym"inc", idx, n[start+2]), + conditionsToIfChain(n, idx, res, start+3)))) + result = newTree(nnkStmtList, n[start], ifs) + +proc notZero(x: NimNode): NimNode = newCall(bindSym"!=", x, newLit 0) + +proc buildUserCall(x: string; args: varargs[NimNode]): NimNode = + let y = parseExpr(x) + result = newTree(nnkCall) + if y.kind in nnkCallKinds: result.add y[0] + else: result.add y + for a in args: result.add a + if y.kind in nnkCallKinds: + for i in 1..<y.len: result.add y[i] + +macro scanf*(input: string; pattern: static[string]; results: varargs[typed]): bool = + ## See top level documentation of his module of how ``scanf`` works. + template matchBind(parser) {.dirty.} = + var resLen = genSym(nskLet, "resLen") + conds.add newLetStmt(resLen, newCall(bindSym(parser), input, results[i], idx)) + conds.add resLen.notZero + conds.add resLen + + var i = 0 + var p = 0 + var idx = genSym(nskVar, "idx") + var res = genSym(nskVar, "res") + result = newTree(nnkStmtListExpr, newVarStmt(idx, newLit 0), newVarStmt(res, newLit false)) + var conds = newTree(nnkStmtList) + var fullMatch = false + while p < pattern.len: + if pattern[p] == '$': + inc p + case pattern[p] + of '$': + var resLen = genSym(nskLet, "resLen") + conds.add newLetStmt(resLen, newCall(bindSym"skip", input, newLit($pattern[p]), idx)) + conds.add resLen.notZero + conds.add resLen + of 'w': + if i < results.len or getType(results[i]).typeKind != ntyString: + matchBind "parseIdent" + else: + error("no string var given for $w") + inc i + of 'i': + if i < results.len or getType(results[i]).typeKind != ntyInt: + matchBind "parseInt" + else: + error("no int var given for $d") + inc i + of 'f': + if i < results.len or getType(results[i]).typeKind != ntyFloat: + matchBind "parseFloat" + else: + error("no float var given for $f") + inc i + of 's': + conds.add newCall(bindSym"inc", idx, newCall(bindSym"skipWhitespace", input, idx)) + conds.add newEmptyNode() + conds.add newEmptyNode() + of '.': + if p == pattern.len-1: + fullMatch = true + else: + error("invalid format string") + of '*', '+': + if i < results.len or getType(results[i]).typeKind != ntyString: + var min = ord(pattern[p] == '+') + var q=p+1 + var token = "" + while q < pattern.len and pattern[q] != '$': + token.add pattern[q] + inc q + var resLen = genSym(nskLet, "resLen") + conds.add newLetStmt(resLen, newCall(bindSym"parseUntil", input, results[i], newLit(token), idx)) + conds.add newCall(bindSym"!=", resLen, newLit min) + conds.add resLen + else: + error("no string var given for $" & pattern[p]) + inc i + of '{': + inc p + var nesting = 0 + let start = p + while true: + case pattern[p] + of '{': inc nesting + of '}': + if nesting == 0: break + dec nesting + of '\0': error("expected closing '}'") + else: discard + inc p + let expr = pattern.substr(start, p-1) + if i < results.len: + var resLen = genSym(nskLet, "resLen") + conds.add newLetStmt(resLen, buildUserCall(expr, input, results[i], idx)) + conds.add newCall(bindSym"!=", resLen, newLit 0) + conds.add resLen + else: + error("no var given for $" & expr) + inc i + of '[': + inc p + var nesting = 0 + let start = p + while true: + case pattern[p] + of '[': inc nesting + of ']': + if nesting == 0: break + dec nesting + of '\0': error("expected closing ']'") + else: discard + inc p + let expr = pattern.substr(start, p-1) + conds.add newCall(bindSym"inc", idx, buildUserCall(expr, input, idx)) + conds.add newEmptyNode() + conds.add newEmptyNode() + else: error("invalid format string") + inc p + else: + var token = "" + while p < pattern.len and pattern[p] != '$': + token.add pattern[p] + inc p + var resLen = genSym(nskLet, "resLen") + conds.add newLetStmt(resLen, newCall(bindSym"skip", input, newLit(token), idx)) + conds.add resLen.notZero + conds.add resLen + result.add conditionsToIfChain(conds, idx, res, 0) + if fullMatch: + result.add newCall(bindSym">=", idx, newCall(bindSym"len", input)) + else: + result.add res + +template atom*(input: string; idx: int; c: char): bool = + ## Used in scanp for the matching of atoms (usually chars). + input[idx] == c + +template atom*(input: string; idx: int; s: set[char]): bool = + input[idx] in s + +#template prepare*(input: string): int = 0 +template success*(x: int): bool = x != 0 + +template nxt*(input: string; idx, step: int = 1) = inc(idx, step) + +macro scanp*(input, idx: typed; pattern: varargs[untyped]): bool = + ## See top level documentation of his module of how ``scanp`` works. + type StmtTriple = tuple[init, cond, action: NimNode] + + template interf(x): untyped = bindSym(x, brForceOpen) + + proc toIfChain(n: seq[StmtTriple]; idx, res: NimNode; start: int): NimNode = + if start >= n.len: return newAssignment(res, newLit true) + var ifs: NimNode = nil + if n[start].cond.kind == nnkEmpty: + ifs = toIfChain(n, idx, res, start+1) + else: + ifs = newIfStmt((n[start].cond, + newTree(nnkStmtList, n[start].action, + toIfChain(n, idx, res, start+1)))) + result = newTree(nnkStmtList, n[start].init, ifs) + + proc attach(x, attached: NimNode): NimNode = + if attached == nil: x + else: newStmtList(attached, x) + + proc placeholder(n, x, j: NimNode): NimNode = + if n.kind == nnkPrefix and n[0].eqIdent("$"): + let n1 = n[1] + if n1.eqIdent"_" or n1.eqIdent"current": + result = newTree(nnkBracketExpr, x, j) + elif n1.eqIdent"input": + result = x + elif n1.eqIdent"i" or n1.eqIdent"index": + result = j + else: + error("unknown pattern " & repr(n)) + else: + result = copyNimNode(n) + for i in 0 ..< n.len: + result.add placeholder(n[i], x, j) + + proc atm(it, input, idx, attached: NimNode): StmtTriple = + template `!!`(x): untyped = attach(x, attached) + case it.kind + of nnkIdent: + var resLen = genSym(nskLet, "resLen") + result = (newLetStmt(resLen, newCall(it, input, idx)), + newCall(interf"success", resLen), + !!newCall(interf"nxt", input, idx, resLen)) + of nnkCallKinds: + # *{'A'..'Z'} !! s.add(!_) + template buildWhile(init, cond, action): untyped = + while true: + init + if not cond: break + action + + # (x) a # bind action a to (x) + if it[0].kind == nnkPar and it.len == 2: + result = atm(it[0], input, idx, placeholder(it[1], input, idx)) + elif it.kind == nnkInfix and it[0].eqIdent"->": + # bind matching to some action: + result = atm(it[1], input, idx, placeholder(it[2], input, idx)) + elif it.kind == nnkInfix and it[0].eqIdent"as": + let cond = if it[1].kind in nnkCallKinds: placeholder(it[1], input, idx) + else: newCall(it[1], input, idx) + result = (newLetStmt(it[2], cond), + newCall(interf"success", it[2]), + !!newCall(interf"nxt", input, idx, it[2])) + elif it.kind == nnkPrefix and it[0].eqIdent"*": + let (init, cond, action) = atm(it[1], input, idx, attached) + result = (getAst(buildWhile(init, cond, action)), + newEmptyNode(), newEmptyNode()) + elif it.kind == nnkPrefix and it[0].eqIdent"+": + # x+ is the same as xx* + result = atm(newTree(nnkPar, it[1], newTree(nnkPrefix, ident"*", it[1])), + input, idx, attached) + elif it.kind == nnkPrefix and it[0].eqIdent"?": + # optional. + let (init, cond, action) = atm(it[1], input, idx, attached) + if cond.kind == nnkEmpty: + error("'?' operator applied to a non-condition") + else: + result = (newTree(nnkStmtList, init, newIfStmt((cond, action))), + newEmptyNode(), newEmptyNode()) + elif it.kind == nnkPrefix and it[0].eqIdent"~": + # not operator + let (init, cond, action) = atm(it[1], input, idx, attached) + if cond.kind == nnkEmpty: + error("'~' operator applied to a non-condition") + else: + result = (init, newCall(bindSym"not", cond), action) + elif it.kind == nnkInfix and it[0].eqIdent"|": + let a = atm(it[1], input, idx, attached) + let b = atm(it[2], input, idx, attached) + if a.cond.kind == nnkEmpty or b.cond.kind == nnkEmpty: + error("'|' operator applied to a non-condition") + else: + result = (newStmtList(a.init, + newIfStmt((a.cond, a.action), (newTree(nnkStmtListExpr, b.init, b.cond), b.action))), + newEmptyNode(), newEmptyNode()) + elif it.kind == nnkInfix and it[0].eqIdent"^*": + # a ^* b is rewritten to: (a *(b a))? + #exprList = expr ^+ comma + template tmp(a, b): untyped = ?(a, *(b, a)) + result = atm(getAst(tmp(it[1], it[2])), input, idx, attached) + + elif it.kind == nnkInfix and it[0].eqIdent"^+": + # a ^* b is rewritten to: (a +(b a))? + template tmp(a, b): untyped = (a, *(b, a)) + result = atm(getAst(tmp(it[1], it[2])), input, idx, attached) + elif it.kind == nnkCommand and it.len == 2 and it[0].eqIdent"pred": + # enforce that the wrapped call is interpreted as a predicate, not a non-terminal: + result = (newEmptyNode(), placeholder(it[1], input, idx), newEmptyNode()) + else: + var resLen = genSym(nskLet, "resLen") + result = (newLetStmt(resLen, placeholder(it, input, idx)), + newCall(interf"success", resLen), !!newCall(interf"nxt", input, idx, resLen)) + of nnkStrLit..nnkTripleStrLit: + var resLen = genSym(nskLet, "resLen") + result = (newLetStmt(resLen, newCall(interf"skip", input, it, idx)), + newCall(interf"success", resLen), !!newCall(interf"nxt", input, idx, resLen)) + of nnkCurly, nnkAccQuoted, nnkCharLit: + result = (newEmptyNode(), newCall(interf"atom", input, idx, it), !!newCall(interf"nxt", input, idx)) + of nnkCurlyExpr: + if it.len == 3 and it[1].kind == nnkIntLit and it[2].kind == nnkIntLit: + var h = newTree(nnkPar, it[0]) + for count in 2..it[1].intVal: h.add(it[0]) + for count in it[1].intVal .. it[2].intVal-1: h.add(newTree(nnkPrefix, ident"?", it[0])) + result = atm(h, input, idx, attached) + elif it.len == 2 and it[1].kind == nnkIntLit: + var h = newTree(nnkPar, it[0]) + for count in 2..it[1].intVal: h.add(it[0]) + result = atm(h, input, idx, attached) + else: + error("invalid pattern") + of nnkPar: + if it.len == 1: + result = atm(it[0], input, idx, attached) + else: + # concatenation: + var conds: seq[StmtTriple] = @[] + for x in it: conds.add atm(x, input, idx, attached) + var res = genSym(nskVar, "res") + result = (newStmtList(newVarStmt(res, newLit false), + toIfChain(conds, idx, res, 0)), res, newEmptyNode()) + else: + error("invalid pattern") + + #var idx = genSym(nskVar, "idx") + var res = genSym(nskVar, "res") + result = newTree(nnkStmtListExpr, #newVarStmt(idx, newCall(interf"prepare", input)), + newVarStmt(res, newLit false)) + var conds: seq[StmtTriple] = @[] + for it in pattern: + conds.add atm(it, input, idx, nil) + result.add toIfChain(conds, idx, res, 0) + result.add res + when defined(debugScanp): + echo repr result + + +when isMainModule: + proc twoDigits(input: string; x: var int; start: int): int = + if input[start] == '0' and input[start+1] == '0': + result = 2 + x = 13 + else: + result = 0 + + proc someSep(input: string; start: int; seps: set[char] = {';',',','-','.'}): int = + result = 0 + while input[start+result] in seps: inc result + + proc demangle(s: string; res: var string; start: int): int = + while s[result+start] in {'_', '@'}: inc result + res = "" + while result+start < s.len and s[result+start] > ' ' and s[result+start] != '_': + res.add s[result+start] + inc result + while result+start < s.len and s[result+start] > ' ': + inc result + + proc parseGDB(resp: string): seq[string] = + const + digits = {'0'..'9'} + hexdigits = digits + {'a'..'f', 'A'..'F'} + whites = {' ', '\t', '\C', '\L'} + result = @[] + var idx = 0 + while true: + var prc = "" + var info = "" + if scanp(resp, idx, *`whites`, '#', *`digits`, +`whites`, ?("0x", *`hexdigits`, " in "), + demangle($input, prc, $index), *`whites`, '(', * ~ ')', ')', + *`whites`, "at ", +(~{'\C', '\L', '\0'} -> info.add($_)) ): + result.add prc & " " & info + else: + break + + var key, val: string + var intval: int + var floatval: float + doAssert scanf("abc:: xyz 89 33.25", "$w$s::$s$w$s$i $f", key, val, intval, floatVal) + doAssert key == "abc" + doAssert val == "xyz" + doAssert intval == 89 + doAssert floatVal == 33.25 + + let xx = scanf("$abc", "$$$i", intval) + doAssert xx == false + + + let xx2 = scanf("$1234", "$$$i", intval) + doAssert xx2 + + let yy = scanf(";.--Breakpoint00 [output]", "$[someSep]Breakpoint${twoDigits}$[someSep({';','.','-'})] [$+]$.", intVal, key) + doAssert yy + doAssert key == "output" + doAssert intVal == 13 + + var ident = "" + var idx = 0 + let zz = scanp("foobar x x x xWZ", idx, +{'a'..'z'} -> add(ident, $_), *(*{' ', '\t'}, "x"), ~'U', "Z") + doAssert zz + doAssert ident == "foobar" + + const digits = {'0'..'9'} + var year = 0 + var idx2 = 0 + if scanp("201655-8-9", idx2, `digits`{4,6} -> (year = year * 10 + ord($_) - ord('0')), "-8", "-9"): + doAssert year == 201655 + + const gdbOut = """ + #0 @foo_96013_1208911747@8 (x0=...) + at c:/users/anwender/projects/nim/temp.nim:11 + #1 0x00417754 in tempInit000 () at c:/users/anwender/projects/nim/temp.nim:13 + #2 0x0041768d in NimMainInner () + at c:/users/anwender/projects/nim/lib/system.nim:2605 + #3 0x004176b1 in NimMain () + at c:/users/anwender/projects/nim/lib/system.nim:2613 + #4 0x004176db in main (argc=1, args=0x712cc8, env=0x711ca8) + at c:/users/anwender/projects/nim/lib/system.nim:2620""" + const result = @["foo c:/users/anwender/projects/nim/temp.nim:11", + "tempInit000 c:/users/anwender/projects/nim/temp.nim:13", + "NimMainInner c:/users/anwender/projects/nim/lib/system.nim:2605", + "NimMain c:/users/anwender/projects/nim/lib/system.nim:2613", + "main c:/users/anwender/projects/nim/lib/system.nim:2620"] + doAssert parseGDB(gdbOut) == result diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index a5a4ee509..c3d6d75bd 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -669,7 +669,7 @@ proc repeat*(s: string, n: Natural): string {.noSideEffect, result = newStringOfCap(n * s.len) for i in 1..n: result.add(s) -template spaces*(n: Natural): string = repeat(' ',n) +template spaces*(n: Natural): string = repeat(' ', n) ## Returns a String with `n` space characters. You can use this proc ## to left align strings. Example: ## diff --git a/tests/ccgbugs/tmissingvolatile.nim b/tests/ccgbugs/tmissingvolatile.nim index 4d25e5c22..d61778ed4 100644 --- a/tests/ccgbugs/tmissingvolatile.nim +++ b/tests/ccgbugs/tmissingvolatile.nim @@ -1,7 +1,7 @@ discard """ output: "1" cmd: r"nim c --hints:on $options -d:release $file" - ccodecheck: "'NI volatile state;'" + ccodecheck: "'NI volatile state0;'" """ # bug #1539 diff --git a/tests/closure/tdeeplynested.nim b/tests/closure/tdeeplynested.nim new file mode 100644 index 000000000..ddf4fa6a4 --- /dev/null +++ b/tests/closure/tdeeplynested.nim @@ -0,0 +1,20 @@ +discard """ + output: '''int: 108''' +""" + +# bug #4070 + +proc id(f: (proc())): auto = + return f + +proc foo(myinteger: int): (iterator(): int) = + return iterator(): int {.closure.} = + proc bar() = + proc kk() = + echo "int: ", myinteger + + kk() + + id(bar)() + +discard foo(108)() diff --git a/web/news.txt b/web/news.txt index c9561c7a5..b6ce533c8 100644 --- a/web/news.txt +++ b/web/news.txt @@ -45,6 +45,9 @@ Library Additions - The rlocks module has been added providing reentrant lock synchronization primitive. - A generic "sink operator" written as ``&=`` has been added to the ``system`` and the ``net`` modules. +- Added ``strscans`` module that implements a ``scanf`` for easy input extraction. +- Added a version of ``parseutils.parseUntil`` that can deal with a string ``until`` token. The other + versions are for ``char`` and ``set[char]``. Compiler Additions @@ -62,6 +65,7 @@ Language Additions - Nim now supports ``partial`` object declarations to mitigate the problems that arise when types are mutually dependent and yet should be kept in different modules. +- ``include`` statements are not restricted to top level statements anymore. 2016-01-27 Nim in Action is now available! diff --git a/web/website.ini b/web/website.ini index d1f8a04bf..2084d9240 100644 --- a/web/website.ini +++ b/web/website.ini @@ -40,7 +40,7 @@ srcdoc2: "pure/concurrency/threadpool.nim;pure/concurrency/cpuinfo.nim" srcdoc: "system/threads.nim;system/channels.nim;js/dom" srcdoc2: "pure/os;pure/strutils;pure/math;pure/matchers;pure/algorithm" srcdoc2: "pure/stats;impure/nre;windows/winlean" -srcdoc2: "pure/complex;pure/times;pure/osproc;pure/pegs;pure/dynlib" +srcdoc2: "pure/complex;pure/times;pure/osproc;pure/pegs;pure/dynlib;pure/strscans" srcdoc2: "pure/parseopt;pure/parseopt2;pure/hashes;pure/strtabs;pure/lexbase" srcdoc2: "pure/parsecfg;pure/parsexml;pure/parsecsv;pure/parsesql" srcdoc2: "pure/streams;pure/terminal;pure/cgi;pure/unicode" |