diff options
127 files changed, 4443 insertions, 1499 deletions
diff --git a/.gitignore b/.gitignore index 7f6d35f71..81bfbad89 100755 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ nimcache lib/nimcache tools/nimcache +tools/nimweb tests/nimcache tests/accept/run/nimcache tests/accept/compile/nimcache diff --git a/compiler/ast.nim b/compiler/ast.nim index 8763e750e..0e4700065 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -172,6 +172,9 @@ type nkDiscardStmt, # a discard statement nkStmtList, # a list of statements nkImportStmt, # an import statement + nkImportExceptStmt, # an import x except a statement + nkExportStmt, # an export statement + nkExportExceptStmt, # an 'export except' statement nkFromStmt, # a from * import statement nkIncludeStmt, # an include statement nkBindStmt, # a bind statement @@ -647,7 +650,8 @@ type loc*: TLoc annex*: PLib # additional fields (seldom used, so we use a # reference to another object to safe space) - + constraint*: PNode # additional constraints like 'lit|result' + TTypeSeq* = seq[PType] TType* = object of TIdObj # types are identical iff they have the # same id; there may be multiple copies of a type @@ -673,7 +677,6 @@ type align*: int # the type's alignment requirements containerID*: int # used for type checking of generics loc*: TLoc - constraint*: PNode # additional constraints like 'lit|result' TPair*{.final.} = object key*, val*: PObject diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index da0de3e94..564f262d7 100755 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -429,7 +429,7 @@ proc debugTree(n: PNode, indent: int, maxRecDepth: int): PRope = [istr, makeYamlString($n.kind)]) if maxRecDepth != 0: case n.kind - of nkCharLit..nkInt64Lit: + of nkCharLit..nkUInt64Lit: appf(result, ",$N$1\"intVal\": $2", [istr, toRope(n.intVal)]) of nkFloatLit, nkFloat32Lit, nkFloat64Lit: appf(result, ",$N$1\"floatVal\": $2", @@ -585,8 +585,9 @@ proc StrTableContains(t: TStrTable, n: PSym): bool = proc StrTableRawInsert(data: var TSymSeq, n: PSym) = var h: THash = n.name.h and high(data) while data[h] != nil: - if data[h] == n: - InternalError(n.info, "StrTableRawInsert: " & n.name.s) + if data[h] == n: + # allowed for 'export' feature: + #InternalError(n.info, "StrTableRawInsert: " & n.name.s) return h = nextTry(h, high(data)) assert(data[h] == nil) @@ -617,23 +618,23 @@ proc StrTableAdd(t: var TStrTable, n: PSym) = StrTableRawInsert(t.data, n) inc(t.counter) -proc StrTableIncl*(t: var TStrTable, n: PSym): bool = +proc StrTableIncl*(t: var TStrTable, n: PSym): bool {.discardable.} = # returns true if n is already in the string table: # It is essential that `n` is written nevertheless! # This way the newest redefinition is picked by the semantic analyses! assert n.name != nil var h: THash = n.name.h and high(t.data) - while true: + while true: var it = t.data[h] - if it == nil: break - if it.name.id == n.name.id: + if it == nil: break + if it.name.id == n.name.id: t.data[h] = n # overwrite it with newer definition! return true # found it h = nextTry(h, high(t.data)) - if mustRehash(len(t.data), t.counter): + if mustRehash(len(t.data), t.counter): StrTableEnlarge(t) StrTableRawInsert(t.data, n) - else: + else: assert(t.data[h] == nil) t.data[h] = n inc(t.counter) diff --git a/compiler/babelcmd.nim b/compiler/babelcmd.nim new file mode 100644 index 000000000..956c6a6ae --- /dev/null +++ b/compiler/babelcmd.nim @@ -0,0 +1,90 @@ +# +# +# The Nimrod Compiler +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Implements some helper procs for Babel (Nimrod's package manager) support. + +import parseutils, strutils, strtabs, os, options, msgs, lists + +proc addPath*(path: string, info: TLineInfo) = + if not contains(options.searchPaths, path): + lists.PrependStr(options.searchPaths, path) + +proc versionSplitPos(s: string): int = + result = s.len-2 + while result > 1 and s[result] in {'0'..'9', '.'}: dec result + if s[result] != '-': result = s.len + +const + latest = "head" + +proc `<.`(a, b: string): bool = + # wether a has a smaller version than b: + if a == latest: return false + var i = 0 + var j = 0 + var verA = 0 + var verB = 0 + while true: + let ii = parseInt(a, verA, i) + let jj = parseInt(b, verB, j) + # if A has no number left, but B has, B is prefered: 0.8 vs 0.8.3 + if ii <= 0 or jj <= 0: return jj > 0 + if verA < verB: return true + elif verA > verB: return false + # else: same version number; continue: + inc i, ii + inc j, jj + if a[i] == '.': inc i + if b[j] == '.': inc j + +proc addPackage(packages: PStringTable, p: string) = + let x = versionSplitPos(p) + let name = p.subStr(0, x-1) + if x < p.len: + let version = p.subStr(x+1) + if packages[name] <. version: + packages[name] = version + else: + packages[name] = latest + +iterator chosen(packages: PStringTable): string = + for key, val in pairs(packages): + let res = if val == latest: key else: key & '-' & val + yield res + +proc addBabelPath(p: string, info: TLineInfo) = + if not contains(options.searchPaths, p): + Message(info, hintPath, p) + lists.PrependStr(options.lazyPaths, p) + +proc addPathWithNimFiles(p: string, info: TLineInfo) = + proc hasNimFile(dir: string): bool = + for kind, path in walkDir(dir): + if kind == pcFile and path.endsWith(".nim"): + result = true + break + if hasNimFile(p): + addBabelPath(p, info) + else: + for kind, p2 in walkDir(p): + if hasNimFile(p2): addBabelPath(p2, info) + +proc addPathRec(dir: string, info: TLineInfo) = + var packages = newStringTable(modeStyleInsensitive) + var pos = dir.len-1 + if dir[pos] in {DirSep, AltSep}: inc(pos) + for k,p in os.walkDir(dir): + if k == pcDir and p[pos] != '.': + addPackage(packages, p) + for p in packages.chosen: + addPathWithNimFiles(p, info) + +proc babelPath*(path: string, info: TLineInfo) = + addPathRec(path, info) + addBabelPath(path, info) diff --git a/compiler/c2nim/cpp.nim b/compiler/c2nim/cpp.nim index 3b7f58fcc..c210eca3a 100755 --- a/compiler/c2nim/cpp.nim +++ b/compiler/c2nim/cpp.nim @@ -42,6 +42,7 @@ proc parseDefine(p: var TParser): PNode = result = newNodeP(nkTemplateDef, p) getTok(p) addSon(result, skipIdentExport(p)) + addSon(result, ast.emptyNode) eat(p, pxParLe) var params = newNodeP(nkFormalParams, p) # return type; not known yet: @@ -60,6 +61,7 @@ proc parseDefine(p: var TParser): PNode = addSon(result, ast.emptyNode) # no generic parameters addSon(result, params) addSon(result, ast.emptyNode) # no pragmas + addSon(result, ast.emptyNode) var kind = parseDefineBody(p, result) params.sons[0] = newIdentNodeP(kind, p) eatNewLine(p, result) diff --git a/compiler/c2nim/tests/systest.c b/compiler/c2nim/tests/systest.c index 241526e07..2a9dd6c28 100755 --- a/compiler/c2nim/tests/systest.c +++ b/compiler/c2nim/tests/systest.c @@ -17,6 +17,8 @@ int aw_instance_callback_set (AW_CALLBACK c, callback_t callback); unsigned long int wawa; +#define MAX(x, y) ((x) < (y)? (y) : (x)) + #define AW_BUILD 85 // AW 5.0 // Limits #define AW_MAX_AVCHANGE_PER_SECOND 10 diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 84c56cd28..71e4fe39b 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -146,7 +146,7 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = proc addComma(r: PRope): PRope = result = if r == nil: r else: con(r, ~", ") - const CallPattern = "$1.ClEnv? $1.ClPrc($3$1.ClEnv) : (($4)($1.ClPrc))($2);$n" + const CallPattern = "$1.ClEnv? $1.ClPrc($3$1.ClEnv) : (($4)($1.ClPrc))($2)" var op: TLoc initLocExpr(p, ri.sons[0], op) var pl: PRope @@ -164,7 +164,7 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = if i < length - 1: app(pl, ~", ") template genCallPattern = - lineF(p, cpsStmts, CallPattern, op.r, pl, pl.addComma, rawProc) + lineF(p, cpsStmts, CallPattern & ";$n", op.r, pl, pl.addComma, rawProc) let rawProc = getRawProcType(p, typ) if typ.sons[0] != nil: diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 1ac3dad7d..20636f122 100755 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -939,22 +939,31 @@ proc genNew(p: BProc, e: PNode) = var a, b: TLoc reftype, bt: PType + sizeExpr: PRope refType = skipTypes(e.sons[1].typ, abstractVarRange) InitLocExpr(p, e.sons[1], a) initLoc(b, locExpr, a.t, OnHeap) + # 'genNew' also handles 'unsafeNew': + if e.len == 3: + var se: TLoc + InitLocExpr(p, e.sons[2], se) + sizeExpr = se.rdLoc + else: + sizeExpr = ropef("sizeof($1)", + getTypeDesc(p.module, skipTypes(reftype.sons[0], abstractRange))) let args = [getTypeDesc(p.module, reftype), genTypeInfo(p.module, refType), - getTypeDesc(p.module, skipTypes(reftype.sons[0], abstractRange))] + sizeExpr] if a.s == OnHeap and optRefcGc in gGlobalOptions: # use newObjRC1 as an optimization; and we don't need 'keepAlive' either if canFormAcycle(a.t): linefmt(p, cpsStmts, "if ($1) #nimGCunref($1);$n", a.rdLoc) else: linefmt(p, cpsStmts, "if ($1) #nimGCunrefNoCycle($1);$n", a.rdLoc) - b.r = ropecg(p.module, "($1) #newObjRC1($2, sizeof($3))", args) + b.r = ropecg(p.module, "($1) #newObjRC1($2, $3)", args) linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc) else: - b.r = ropecg(p.module, "($1) #newObj($2, sizeof($3))", args) + b.r = ropecg(p.module, "($1) #newObj($2, $3)", args) genAssignment(p, a, b, {needToKeepAlive}) # set the object type: bt = skipTypes(refType.sons[0], abstractRange) genObjectInit(p, cpsStmts, bt, a, false) @@ -1645,7 +1654,7 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) = initLocExpr(p, n.sons[0], a) var r = rdLoc(a) if skipTypes(n.sons[0].typ, abstractInst).kind in {tyRef, tyPtr, tyVar} and - n.sons[0].kind notin {nkHiddenAddr, nkAddr}: + n.sons[0].kind notin {nkHiddenAddr, nkAddr, nkObjDownConv}: app(r, "->Sup") for i in countup(2, abs(inheritanceDiff(dest, src))): app(r, ".Sup") r = con("&", r) diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim index cb654cbb5..baf4f5586 100644 --- a/compiler/ccgmerge.nim +++ b/compiler/ccgmerge.nim @@ -148,20 +148,47 @@ proc atEndMark(buf: cstring, pos: int): bool = while s < NimMergeEndMark.len and buf[pos+s] == NimMergeEndMark[s]: inc s result = s == NimMergeEndMark.len +when false: + proc readVerbatimSection(L: var TBaseLexer): PRope = + var pos = L.bufpos + var buf = L.buf + result = newMutableRope(30_000) + while true: + case buf[pos] + of CR: + pos = lexbase.HandleCR(L, pos) + buf = L.buf + result.data.add(tnl) + of LF: + pos = lexbase.HandleLF(L, pos) + buf = L.buf + result.data.add(tnl) + of '\0': + InternalError("ccgmerge: expected: " & NimMergeEndMark) + break + else: + if atEndMark(buf, pos): + inc pos, NimMergeEndMark.len + break + result.data.add(buf[pos]) + inc pos + L.bufpos = pos + freezeMutableRope(result) + proc readVerbatimSection(L: var TBaseLexer): PRope = var pos = L.bufpos var buf = L.buf - result = newMutableRope(30_000) + var r = newStringOfCap(30_000) while true: case buf[pos] of CR: pos = lexbase.HandleCR(L, pos) buf = L.buf - result.data.add(tnl) + r.add(tnl) of LF: pos = lexbase.HandleLF(L, pos) buf = L.buf - result.data.add(tnl) + r.add(tnl) of '\0': InternalError("ccgmerge: expected: " & NimMergeEndMark) break @@ -169,10 +196,10 @@ proc readVerbatimSection(L: var TBaseLexer): PRope = if atEndMark(buf, pos): inc pos, NimMergeEndMark.len break - result.data.add(buf[pos]) + r.add(buf[pos]) inc pos L.bufpos = pos - freezeMutableRope(result) + result = r.toRope proc readKey(L: var TBaseLexer, result: var string) = var pos = L.bufpos diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 5927c6afd..2f07d24cb 100755 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -122,6 +122,7 @@ proc genBreakState(p: BProc, n: PNode) = lineF(p, cpsStmts, "if ((((NI*) $1.ClEnv)[0]) < 0) break;$n", [rdLoc(a)]) # lineF(p, cpsStmts, "if (($1) < 0) break;$n", [rdLoc(a)]) +proc genVarPrototypeAux(m: BModule, sym: PSym) proc genSingleVar(p: BProc, a: PNode) = var v = a.sons[0].sym if sfCompileTime in v.flags: return @@ -140,6 +141,9 @@ proc genSingleVar(p: BProc, a: PNode) = genObjectInit(p.module.preInitProc, cpsInit, v.typ, v.loc, true) # Alternative construction using default constructor (which may zeromem): # if sfImportc notin v.flags: constructLoc(p.module.preInitProc, v.loc) + if sfExportc in v.flags and generatedHeader != nil: + genVarPrototypeAux(generatedHeader, v) + else: assignLocalVar(p, v) initLocalVar(p, v, immediateAsgn) @@ -878,7 +882,8 @@ proc genStmts(p: BProc, t: PNode) = # we have to emit the type information for object types here to support # separate compilation: genTypeSection(p.module, t) - of nkCommentStmt, nkNilLit, nkIteratorDef, nkIncludeStmt, nkImportStmt, + of nkCommentStmt, nkNilLit, nkIteratorDef, nkIncludeStmt, + nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt, nkFromStmt, nkTemplateDef, nkMacroDef: nil of nkPragma: genPragma(p, t) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 71d55c879..399785c82 100755 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -558,17 +558,6 @@ include "ccgexprs.nim", "ccgstmts.nim" # ----------------------------- dynamic library handling ----------------- # We don't finalize dynamic libs as this does the OS for us. -proc libCandidates(s: string, dest: var TStringSeq) = - var le = strutils.find(s, '(') - var ri = strutils.find(s, ')', le+1) - if le >= 0 and ri > le: - var prefix = substr(s, 0, le - 1) - var suffix = substr(s, ri + 1) - for middle in split(substr(s, le + 1, ri - 1), '|'): - libCandidates(prefix & middle & suffix, dest) - else: - add(dest, s) - proc isGetProcAddr(lib: PLib): bool = let n = lib.path result = n.kind in nkCallKinds and n.typ != nil and @@ -870,8 +859,6 @@ proc genVarPrototypeAux(m: BModule, sym: PSym) = proc genVarPrototype(m: BModule, sym: PSym) = genVarPrototypeAux(m, sym) - if sfExportc in sym.flags and generatedHeader != nil: - genVarPrototypeAux(generatedHeader, sym) proc addIntTypes(result: var PRope) {.inline.} = appf(result, "#define NIM_INTBITS $1", [ diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim index 687653aaf..e7bd54ef0 100755 --- a/compiler/cgmeth.nim +++ b/compiler/cgmeth.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -65,10 +65,10 @@ proc sameMethodBucket(a, b: PSym): bool = break if sameType(aa, bb) or (aa.kind == tyObject) and (bb.kind == tyObject) and - (inheritanceDiff(bb, aa) < 0): + (inheritanceDiff(bb, aa) < 0): nil - else: - return + else: + return result = true proc attachDispatcher(s: PSym, dispatcher: PNode) = @@ -106,17 +106,16 @@ proc methodDef*(s: PSym, fromCache: bool) = # attach to itself to prevent bugs: attachDispatcher(disp, newSymNode(disp)) -proc relevantCol(methods: TSymSeq, col: int): bool = +proc relevantCol(methods: TSymSeq, col: int): bool = # returns true iff the position is relevant var t = methods[0].typ.sons[col] - result = false - if skipTypes(t, skipPtrs).kind == tyObject: - for i in countup(1, high(methods)): - if not SameType(methods[i].typ.sons[col], t): + if skipTypes(t, skipPtrs).kind == tyObject: + for i in countup(1, high(methods)): + let t2 = skipTypes(methods[i].typ.sons[col], skipPtrs) + if not SameType(t2, t): return true proc cmpSignatures(a, b: PSym, relevantCols: TIntSet): int = - result = 0 for col in countup(1, sonsLen(a.typ) - 1): if Contains(relevantCols, col): var aa = skipTypes(a.typ.sons[col], skipPtrs) diff --git a/compiler/commands.nim b/compiler/commands.nim index a1a9f0791..a26626cc4 100755 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -11,7 +11,7 @@ import os, msgs, options, nversion, condsyms, strutils, extccomp, platform, lists, - wordrecg, parseutils + wordrecg, parseutils, babelcmd proc writeCommandLineUsage*() @@ -28,7 +28,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdlinePass, info: TLineInfo) const HelpMessage = "Nimrod Compiler Version $1 (" & compileDate & ") [$2: $3]\n" & - "Copyright (c) 2004-2012 by Andreas Rumpf\n" + "Copyright (c) 2004-2013 by Andreas Rumpf\n" const Usage = slurp"doc/basicopt.txt".replace("//", "") @@ -194,20 +194,6 @@ proc processPath(path: string): string = "projectname", options.gProjectName, "projectpath", options.gProjectPath]) -proc addPath(path: string, info: TLineInfo) = - if not contains(options.searchPaths, path): - lists.PrependStr(options.searchPaths, path) - -proc addPathRec(dir: string, info: TLineInfo) = - var pos = dir.len-1 - if dir[pos] in {DirSep, AltSep}: inc(pos) - for k,p in os.walkDir(dir): - if k == pcDir and p[pos] != '.': - addPathRec(p, info) - if not contains(options.searchPaths, p): - Message(info, hintPath, p) - lists.PrependStr(options.searchPaths, p) - proc track(arg: string, info: TLineInfo) = var a = arg.split(',') if a.len != 3: LocalError(info, errTokenExpected, "FILE,LINE,COLUMN") @@ -227,11 +213,16 @@ proc processSwitch(switch, arg: string, pass: TCmdlinePass, info: TLineInfo) = of "path", "p": expectArg(switch, arg, pass, info) addPath(processPath(arg), info) - of "recursivepath": + of "babelpath": + if pass in {passCmd2, passPP}: + expectArg(switch, arg, pass, info) + let path = processPath(arg) + babelpath(path, info) + of "excludepath": expectArg(switch, arg, pass, info) - var path = processPath(arg) - addPathRec(path, info) - addPath(path, info) + let path = processPath(arg) + lists.ExcludeStr(options.searchPaths, path) + lists.ExcludeStr(options.lazyPaths, path) of "nimcache": expectArg(switch, arg, pass, info) options.nimcacheDir = processPath(arg) @@ -474,6 +465,9 @@ proc processSwitch(switch, arg: string, pass: TCmdlinePass, info: TLineInfo) = of "stdout": expectNoArg(switch, arg, pass, info) incl(gGlobalOptions, optStdout) + of "listfullpaths": + expectNoArg(switch, arg, pass, info) + gListFullPaths = true else: if strutils.find(switch, '.') >= 0: options.setConfigVar(switch, arg) else: InvalidCmdLineOption(pass, switch, info) diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 234029ea9..17366f6e9 100755 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -60,6 +60,7 @@ proc InitDefines*() = DefineSymbol("niminheritable") DefineSymbol("nimmixin") DefineSymbol("nimeffects") + DefineSymbol("nimbabel") # add platform specific symbols: case targetCPU diff --git a/compiler/depends.nim b/compiler/depends.nim index 2e0f833a0..1468cbdb9 100755 --- a/compiler/depends.nim +++ b/compiler/depends.nim @@ -33,7 +33,7 @@ proc addDotDependency(c: PPassContext, n: PNode): PNode = for i in countup(0, sonsLen(n) - 1): var imported = getModuleName(n.sons[i]) addDependencyAux(g.module.name.s, imported) - of nkFromStmt: + of nkFromStmt, nkImportExceptStmt: var imported = getModuleName(n.sons[0]) addDependencyAux(g.module.name.s, imported) of nkStmtList, nkBlockStmt, nkStmtListExpr, nkBlockExpr: diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 496136c23..2b7c567c6 100755 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -137,7 +137,27 @@ proc genRecComment(d: PDoc, n: PNode): PRope = if result != nil: return else: n.comment = nil - + +proc findDocComment(n: PNode): PNode = + if n == nil: return nil + if not isNil(n.comment) and startsWith(n.comment, "##"): return n + for i in countup(0, safeLen(n)-1): + result = findDocComment(n.sons[i]) + if result != nil: return + +proc extractDocComment*(s: PSym, d: PDoc = nil): string = + let n = findDocComment(s.ast) + result = "" + if not n.isNil: + if not d.isNil: + var dummyHasToc: bool + renderRstToOut(d[], parseRst(n.comment, toFilename(n.info), + toLineNumber(n.info), toColumn(n.info), + dummyHasToc, d.options + {roSkipPounds}), + result) + else: + result = n.comment.substr(2).replace("\n##", "\n").strip + proc isVisible(n: PNode): bool = result = false if n.kind == nkPostfix: @@ -145,6 +165,9 @@ proc isVisible(n: PNode): bool = var v = n.sons[0].ident result = v.id == ord(wStar) or v.id == ord(wMinus) elif n.kind == nkSym: + # we cannot generate code for forwarded symbols here as we have no + # exception tracking information here. Instead we copy over the comment + # from the proc header. result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported} elif n.kind == nkPragmaExpr: result = isVisible(n.sons[0]) @@ -273,7 +296,7 @@ proc generateDoc*(d: PDoc, n: PNode) = generateDoc(d, lastSon(n.sons[0])) of nkImportStmt: for i in 0 .. sonsLen(n)-1: traceDeps(d, n.sons[i]) - of nkFromStmt: traceDeps(d, n.sons[0]) + of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0]) else: nil proc genSection(d: PDoc, kind: TSymKind) = @@ -350,9 +373,11 @@ proc CommandRstAux(filename, outExt: string) = var d = newDocumentor(filen, options.gConfigVars) var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc, {roSupportRawDirective}) - d.modDesc = newMutableRope(30_000) - renderRstToOut(d[], rst, d.modDesc.data) - freezeMutableRope(d.modDesc) + var modDesc = newStringOfCap(30_000) + #d.modDesc = newMutableRope(30_000) + renderRstToOut(d[], rst, modDesc) + #freezeMutableRope(d.modDesc) + d.modDesc = toRope(modDesc) writeOutput(d, filename, outExt) generateIndex(d) diff --git a/compiler/ecmasgen.nim b/compiler/ecmasgen.nim index 30b1ac8f0..34b3b4ff5 100755 --- a/compiler/ecmasgen.nim +++ b/compiler/ecmasgen.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -207,27 +207,42 @@ proc genObjectInfo(p: var TProc, typ: PType, name: PRope) = appf(p.g.typeInfo, "$1.base = $2;$n", [name, genTypeInfo(p, typ.sons[0])]) -proc genEnumInfo(p: var TProc, typ: PType, name: PRope) = - var - s, n: PRope - length: int - field: PSym - length = sonsLen(typ.n) - s = nil +proc genTupleFields(p: var TProc, typ: PType): PRope = + var s: PRope = nil + for i in 0 .. <typ.len: + if i > 0: app(s, ", " & tnl) + s.appf("{kind: 1, offset: \"Field$1\", len: 0, " & + "typ: $2, name: \"Field$1\", sons: null}", + [i.toRope, genTypeInfo(p, typ.sons[i])]) + result = ropef("{kind: 2, len: $1, offset: 0, " & + "typ: null, name: null, sons: [$2]}", [toRope(typ.len), s]) + +proc genTupleInfo(p: var TProc, typ: PType, name: PRope) = + var s = ropef("var $1 = {size: 0, kind: $2, base: null, node: null, " & + "finalizer: null};$n", [name, toRope(ord(typ.kind))]) + prepend(p.g.typeInfo, s) + appf(p.g.typeInfo, "var NNI$1 = $2;$n", + [toRope(typ.id), genTupleFields(p, typ)]) + appf(p.g.typeInfo, "$1.node = NNI$2;$n", [name, toRope(typ.id)]) + +proc genEnumInfo(p: var TProc, typ: PType, name: PRope) = + let length = sonsLen(typ.n) + var s: PRope = nil for i in countup(0, length - 1): if (typ.n.sons[i].kind != nkSym): InternalError(typ.n.info, "genEnumInfo") - field = typ.n.sons[i].sym + let field = typ.n.sons[i].sym if i > 0: app(s, ", " & tnl) + let extName = if field.ast == nil: field.name.s else: field.ast.strVal appf(s, "{kind: 1, offset: $1, typ: $2, name: $3, len: 0, sons: null}", - [toRope(field.position), name, makeCString(field.name.s)]) - n = ropef("var NNI$1 = {kind: 2, offset: 0, typ: null, " & + [toRope(field.position), name, makeCString(extName)]) + var n = ropef("var NNI$1 = {kind: 2, offset: 0, typ: null, " & "name: null, len: $2, sons: [$3]};$n", [toRope(typ.id), toRope(length), s]) s = ropef("var $1 = {size: 0, kind: $2, base: null, node: null, " & "finalizer: null};$n", [name, toRope(ord(typ.kind))]) prepend(p.g.typeInfo, s) app(p.g.typeInfo, n) appf(p.g.typeInfo, "$1.node = NNI$2;$n", [name, toRope(typ.id)]) - if typ.sons[0] != nil: + if typ.sons[0] != nil: appf(p.g.typeInfo, "$1.base = $2;$n", [name, genTypeInfo(p, typ.sons[0])]) @@ -259,7 +274,8 @@ proc genTypeInfo(p: var TProc, typ: PType): PRope = appf(p.g.typeInfo, "$1.base = $2;$n", [result, genTypeInfo(p, typ.sons[1])]) of tyEnum: genEnumInfo(p, t, result) - of tyObject, tyTuple: genObjectInfo(p, t, result) + of tyObject: genObjectInfo(p, t, result) + of tyTuple: genTupleInfo(p, t, result) else: InternalError("genTypeInfo(" & $t.kind & ')') proc gen(p: var TProc, n: PNode, r: var TCompRes) @@ -938,7 +954,9 @@ proc genSym(p: var TProc, n: PNode, r: var TCompRes) = of skProc, skConverter, skMethod: discard mangleName(s) r.res = s.loc.r - if lfNoDecl in s.loc.flags or s.magic != mNone or isGenericRoutine(s): nil + if lfNoDecl in s.loc.flags or s.magic != mNone or isGenericRoutine(s) or + {sfImportc, sfInfixCall} * s.flags != {}: + nil elif s.kind == skMethod and s.getBody.kind == nkEmpty: # we cannot produce code for the dispatcher yet: nil @@ -962,24 +980,45 @@ proc genDeref(p: var TProc, n: PNode, r: var TCompRes) = if a.kind != etyBaseIndex: InternalError(n.info, "genDeref") r.res = ropef("$1[$2]", [a.com, a.res]) +proc genArg(p: var TProc, n: PNode, r: var TCompRes) = + var a: TCompRes + gen(p, n, a) + if a.kind == etyBaseIndex: + app(r.res, a.com) + app(r.res, ", ") + app(r.res, a.res) + else: + app(r.res, mergeExpr(a)) + proc genArgs(p: var TProc, n: PNode, r: var TCompRes) = app(r.res, "(") for i in countup(1, sonsLen(n) - 1): if i > 1: app(r.res, ", ") - var a: TCompRes - gen(p, n.sons[i], a) - if a.kind == etyBaseIndex: - app(r.res, a.com) - app(r.res, ", ") - app(r.res, a.res) - else: - app(r.res, mergeExpr(a)) + genArg(p, n.sons[i], r) app(r.res, ")") proc genCall(p: var TProc, n: PNode, r: var TCompRes) = gen(p, n.sons[0], r) genArgs(p, n, r) +proc genInfixCall(p: var TProc, n: PNode, r: var TCompRes) = + gen(p, n.sons[1], r) + if r.kind == etyBaseIndex: + if r.com == nil: + GlobalError(n.info, "cannot invoke with infix syntax") + r.res = ropef("$1[0]", [r.res, r.com]) + r.com = nil + app(r.res, ".") + var op: TCompRes + gen(p, n.sons[0], op) + app(r.res, mergeExpr(op)) + + app(r.res, "(") + for i in countup(2, sonsLen(n) - 1): + if i > 2: app(r.res, ", ") + genArg(p, n.sons[i], r) + app(r.res, ")") + proc genEcho(p: var TProc, n: PNode, r: var TCompRes) = useMagic(p, "rawEcho") app(r.res, "rawEcho") @@ -1176,10 +1215,13 @@ proc genConStrStr(p: var TProc, n: PNode, r: var TCompRes) = proc genRepr(p: var TProc, n: PNode, r: var TCompRes) = var t = skipTypes(n.sons[1].typ, abstractVarRange) case t.kind - of tyInt..tyInt64: - unaryExpr(p, n, r, "", "reprInt($1)") + of tyInt..tyUInt64: + unaryExpr(p, n, r, "", "(\"\"+ ($1))") of tyEnum, tyOrdinal: - binaryExpr(p, n, r, "", "reprEnum($1, $2)") + gen(p, n.sons[1], r) + useMagic(p, "cstrToNimstr") + r.res = ropef("cstrToNimstr($1.node.sons[$2].name)", + [genTypeInfo(p, t), r.res]) else: # XXX: internalError(n.info, "genRepr: Not implemented") @@ -1463,7 +1505,8 @@ proc genStmt(p: var TProc, n: PNode, r: var TCompRes) = of nkAsmStmt: genAsmStmt(p, n, r) of nkTryStmt: genTryStmt(p, n, r) of nkRaiseStmt: genRaiseStmt(p, n, r) - of nkTypeSection, nkCommentStmt, nkIteratorDef, nkIncludeStmt, nkImportStmt, + of nkTypeSection, nkCommentStmt, nkIteratorDef, nkIncludeStmt, + nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt, nkFromStmt, nkTemplateDef, nkMacroDef, nkPragma: nil of nkProcDef, nkMethodDef, nkConverterDef: var s = n.sons[namePos].sym @@ -1510,9 +1553,12 @@ proc gen(p: var TProc, n: PNode, r: var TCompRes) = else: r.res = toRope(f.ToStrMaxPrecision) of nkBlockExpr: genBlock(p, n, r) of nkIfExpr: genIfExpr(p, n, r) - of nkCallKinds: + of nkCallKinds: if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic != mNone): genMagic(p, n, r) + elif n.sons[0].kind == nkSym and sfInfixCall in n.sons[0].sym.flags and + n.len >= 2: + genInfixCall(p, n, r) else: genCall(p, n, r) of nkCurly: genSetConstr(p, n, r) @@ -1526,6 +1572,7 @@ proc gen(p: var TProc, n: PNode, r: var TCompRes) = of nkCheckedFieldExpr: genCheckedFieldAccess(p, n, r) of nkObjDownConv: gen(p, n.sons[0], r) of nkObjUpConv: upConv(p, n, r) + of nkCast: gen(p, n.sons[1], r) of nkChckRangeF: genRangeChck(p, n, r, "chckRangeF") of nkChckRange64: genRangeChck(p, n, r, "chckRange64") of nkChckRange: genRangeChck(p, n, r, "chckRange") @@ -1555,7 +1602,7 @@ proc newModule(module: PSym): BModule = proc genHeader(): PRope = result = ropef("/* Generated by the Nimrod Compiler v$1 */$n" & - "/* (c) 2012 Andreas Rumpf */$n$n" & "$nvar Globals = this;$n" & + "/* (c) 2013 Andreas Rumpf */$n$n" & "$nvar Globals = this;$n" & "var framePtr = null;$n" & "var excHandler = null;$n", [toRope(versionAsString)]) diff --git a/compiler/evalffi.nim b/compiler/evalffi.nim new file mode 100644 index 000000000..ba6e7ee8f --- /dev/null +++ b/compiler/evalffi.nim @@ -0,0 +1,443 @@ +# +# +# The Nimrod Compiler +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This file implements the FFI part of the evaluator for Nimrod code. + +import ast, astalgo, ropes, types, options, tables, dynlib, libffi, msgs + +when defined(windows): + const libcDll = "msvcrt.dll" +else: + const libcDll = "libc.so(.6|.5|)" + +type + TDllCache = tables.TTable[string, TLibHandle] +var + gDllCache = initTable[string, TLibHandle]() + gExeHandle = LoadLib() + +proc getDll(cache: var TDllCache; dll: string; info: TLineInfo): pointer = + result = cache[dll] + if result.isNil: + var libs: seq[string] = @[] + libCandidates(dll, libs) + for c in libs: + result = LoadLib(c) + if not result.isNil: break + if result.isNil: + GlobalError(info, "cannot load: " & dll) + cache[dll] = result + +const + nkPtrLit = nkIntLit # hopefully we can get rid of this hack soon + +proc importcSymbol*(sym: PSym): PNode = + let name = ropeToStr(sym.loc.r) + + # the AST does not support untyped pointers directly, so we use an nkIntLit + # that contains the address instead: + result = newNodeIT(nkPtrLit, sym.info, sym.typ) + case name + of "stdin": result.intVal = cast[TAddress](system.stdin) + of "stdout": result.intVal = cast[TAddress](system.stdout) + of "stderr": result.intVal = cast[TAddress](system.stderr) + else: + let lib = sym.annex + if lib != nil and lib.path.kind notin {nkStrLit..nkTripleStrLit}: + GlobalError(sym.info, "dynlib needs to be a string lit for the REPL") + var theAddr: pointer + if lib.isNil and not gExehandle.isNil: + # first try this exe itself: + theAddr = gExehandle.symAddr(name) + # then try libc: + if theAddr.isNil: + let dllhandle = gDllCache.getDll(libcDll, sym.info) + theAddr = dllhandle.checkedSymAddr(name) + else: + let dllhandle = gDllCache.getDll(lib.path.strVal, sym.info) + theAddr = dllhandle.checkedSymAddr(name) + result.intVal = cast[TAddress](theAddr) + +proc mapType(t: ast.PType): ptr libffi.TType = + if t == nil: return addr libffi.type_void + + case t.kind + of tyBool, tyEnum, tyChar, tyInt..tyInt64, tyUInt..tyUInt64, tySet: + case t.getSize + of 1: result = addr libffi.type_uint8 + of 2: result = addr libffi.type_sint16 + of 4: result = addr libffi.type_sint32 + of 8: result = addr libffi.type_sint64 + else: result = nil + of tyFloat, tyFloat64: result = addr libffi.type_double + of tyFloat32: result = addr libffi.type_float + of tyVar, tyPointer, tyPtr, tyRef, tyCString, tySequence, tyString, tyExpr, + tyStmt, tyTypeDesc, tyProc, tyArray, tyArrayConstr, tyNil: + result = addr libffi.type_pointer + of tyDistinct: + result = mapType(t.sons[0]) + else: + result = nil + # too risky: + #of tyFloat128: result = addr libffi.type_longdouble + +proc mapCallConv(cc: TCallingConvention, info: TLineInfo): TABI = + case cc + of ccDefault: result = DEFAULT_ABI + of ccStdCall: result = when defined(windows): STDCALL else: DEFAULT_ABI + of ccCDecl: result = DEFAULT_ABI + else: + GlobalError(info, "cannot map calling convention to FFI") + +template rd(T, p: expr): expr {.immediate.} = (cast[ptr T](p))[] +template wr(T, p, v: expr) {.immediate.} = (cast[ptr T](p))[] = v +template `+!`(x, y: expr): expr {.immediate.} = + cast[pointer](cast[TAddress](x) + y) + +proc packSize(v: PNode, typ: PType): int = + ## computes the size of the blob + case typ.kind + of tyPtr, tyRef, tyVar: + if v.kind in {nkNilLit, nkPtrLit}: + result = sizeof(pointer) + else: + result = sizeof(pointer) + packSize(v.sons[0], typ.sons[0]) + of tyDistinct, tyGenericInst: + result = packSize(v, typ.sons[0]) + of tyArray, tyArrayConstr: + # consider: ptr array[0..1000_000, int] which is common for interfacing; + # we use the real length here instead + if v.kind in {nkNilLit, nkPtrLit}: + result = sizeof(pointer) + elif v.len != 0: + result = v.len * packSize(v.sons[0], typ.sons[1]) + else: + result = typ.getSize.int + +proc pack(v: PNode, typ: PType, res: pointer) + +proc getField(n: PNode; position: int): PSym = + case n.kind + of nkRecList: + for i in countup(0, sonsLen(n) - 1): + result = getField(n.sons[i], position) + if result != nil: return + of nkRecCase: + result = getField(n.sons[0], position) + if result != nil: return + for i in countup(1, sonsLen(n) - 1): + case n.sons[i].kind + of nkOfBranch, nkElse: + result = getField(lastSon(n.sons[i]), position) + if result != nil: return + else: internalError(n.info, "getField(record case branch)") + of nkSym: + if n.sym.position == position: result = n.sym + else: nil + +proc packObject(x: PNode, typ: PType, res: pointer) = + InternalAssert x.kind == nkPar + # compute the field's offsets: + discard typ.getSize + for i in countup(0, sonsLen(x) - 1): + var it = x.sons[i] + if it.kind == nkExprColonExpr: + internalAssert it.sons[0].kind == nkSym + let field = it.sons[0].sym + pack(it.sons[1], field.typ, res +! field.offset) + elif typ.n != nil: + let field = getField(typ.n, i) + pack(it, field.typ, res +! field.offset) + else: + GlobalError(x.info, "cannot pack unnamed tuple") + +const maxPackDepth = 20 +var packRecCheck = 0 + +proc pack(v: PNode, typ: PType, res: pointer) = + template awr(T, v: expr) {.immediate, dirty.} = + wr(T, res, v) + + case typ.kind + of tyBool: awr(bool, v.intVal != 0) + of tyChar: awr(char, v.intVal.chr) + of tyInt: awr(int, v.intVal.int) + of tyInt8: awr(int8, v.intVal.int8) + of tyInt16: awr(int16, v.intVal.int16) + of tyInt32: awr(int32, v.intVal.int32) + of tyInt64: awr(int64, v.intVal.int64) + of tyUInt: awr(uint, v.intVal.uint) + of tyUInt8: awr(uint8, v.intVal.uint8) + of tyUInt16: awr(uint16, v.intVal.uint16) + of tyUInt32: awr(uint32, v.intVal.uint32) + of tyUInt64: awr(uint64, v.intVal.uint64) + of tyEnum, tySet: + case v.typ.getSize + of 1: awr(uint8, v.intVal.uint8) + of 2: awr(uint16, v.intVal.uint16) + of 4: awr(int32, v.intVal.int32) + of 8: awr(int64, v.intVal.int64) + else: + GlobalError(v.info, "cannot map value to FFI (tyEnum, tySet)") + of tyFloat: awr(float, v.floatVal) + of tyFloat32: awr(float32, v.floatVal) + of tyFloat64: awr(float64, v.floatVal) + + of tyPointer, tyProc, tyCString, tyString: + if v.kind == nkNilLit: + # nothing to do since the memory is 0 initialized anyway + nil + elif v.kind == nkPtrLit: + awr(pointer, cast[pointer](v.intVal)) + elif v.kind in {nkStrLit..nkTripleStrLit}: + awr(cstring, cstring(v.strVal)) + else: + GlobalError(v.info, "cannot map pointer/proc value to FFI") + of tyPtr, tyRef, tyVar: + if v.kind == nkNilLit: + # nothing to do since the memory is 0 initialized anyway + nil + elif v.kind == nkPtrLit: + awr(pointer, cast[pointer](v.intVal)) + else: + if packRecCheck > maxPackDepth: + packRecCheck = 0 + GlobalError(v.info, "cannot map value to FFI " & typeToString(v.typ)) + inc packRecCheck + pack(v.sons[0], typ.sons[0], res +! sizeof(pointer)) + dec packRecCheck + awr(pointer, res +! sizeof(pointer)) + of tyArray, tyArrayConstr: + let baseSize = typ.sons[1].getSize + for i in 0 .. <v.len: + pack(v.sons[i], typ.sons[1], res +! i * baseSize) + of tyObject, tyTuple: + packObject(v, typ, res) + of tyNil: + nil + of tyDistinct, tyGenericInst: + pack(v, typ.sons[0], res) + else: + GlobalError(v.info, "cannot map value to FFI " & typeToString(v.typ)) + +proc unpack(x: pointer, typ: PType, n: PNode): PNode + +proc unpackObjectAdd(x: pointer, n, result: PNode) = + case n.kind + of nkRecList: + for i in countup(0, sonsLen(n) - 1): + unpackObjectAdd(x, n.sons[i], result) + of nkRecCase: + GlobalError(result.info, "case objects cannot be unpacked") + of nkSym: + var pair = newNodeI(nkExprColonExpr, result.info, 2) + pair.sons[0] = n + pair.sons[1] = unpack(x +! n.sym.offset, n.sym.typ, nil) + #echo "offset: ", n.sym.name.s, " ", n.sym.offset + result.add pair + else: nil + +proc unpackObject(x: pointer, typ: PType, n: PNode): PNode = + # compute the field's offsets: + discard typ.getSize + + # iterate over any actual field of 'n' ... if n is nil we need to create + # the nkPar node: + if n.isNil: + result = newNode(nkPar) + result.typ = typ + if typ.n.isNil: + InternalError("cannot unpack unnamed tuple") + unpackObjectAdd(x, typ.n, result) + else: + result = n + if result.kind != nkPar: + GlobalError(n.info, "cannot map value from FFI") + if typ.n.isNil: + GlobalError(n.info, "cannot unpack unnamed tuple") + for i in countup(0, sonsLen(n) - 1): + var it = n.sons[i] + if it.kind == nkExprColonExpr: + internalAssert it.sons[0].kind == nkSym + let field = it.sons[0].sym + it.sons[1] = unpack(x +! field.offset, field.typ, it.sons[1]) + else: + let field = getField(typ.n, i) + n.sons[i] = unpack(x +! field.offset, field.typ, it) + +proc unpackArray(x: pointer, typ: PType, n: PNode): PNode = + if n.isNil: + result = newNode(nkBracket) + result.typ = typ + newSeq(result.sons, lengthOrd(typ).int) + else: + result = n + if result.kind != nkBracket: + GlobalError(n.info, "cannot map value from FFI") + let baseSize = typ.sons[1].getSize + for i in 0 .. < result.len: + result.sons[i] = unpack(x +! i * baseSize, typ.sons[1], result.sons[i]) + +proc canonNodeKind(k: TNodeKind): TNodeKind = + case k + of nkCharLit..nkUInt64Lit: result = nkIntLit + of nkFloatLit..nkFloat128Lit: result = nkFloatLit + of nkStrLit..nkTripleStrLit: result = nkStrLit + else: result = k + +proc unpack(x: pointer, typ: PType, n: PNode): PNode = + template aw(k, v, field: expr) {.immediate, dirty.} = + if n.isNil: + result = newNode(k) + result.typ = typ + else: + # check we have the right field: + result = n + if result.kind.canonNodeKind != k.canonNodeKind: + #echo "expected ", k, " but got ", result.kind + #debug result + return newNodeI(nkExceptBranch, n.info) + #GlobalError(n.info, "cannot map value from FFI") + result.field = v + + template setNil() = + if n.isNil: + result = newNode(nkNilLit) + result.typ = typ + else: + reset n[] + result = n + result.kind = nkNilLit + result.typ = typ + + template awi(kind, v: expr) {.immediate, dirty.} = aw(kind, v, intVal) + template awf(kind, v: expr) {.immediate, dirty.} = aw(kind, v, floatVal) + template aws(kind, v: expr) {.immediate, dirty.} = aw(kind, v, strVal) + + case typ.kind + of tyBool: awi(nkIntLit, rd(bool, x).ord) + of tyChar: awi(nkCharLit, rd(char, x).ord) + of tyInt: awi(nkIntLit, rd(int, x)) + of tyInt8: awi(nkInt8Lit, rd(int8, x)) + of tyInt16: awi(nkInt16Lit, rd(int16, x)) + of tyInt32: awi(nkInt32Lit, rd(int32, x)) + of tyInt64: awi(nkInt64Lit, rd(int64, x)) + of tyUInt: awi(nkUIntLit, rd(uint, x).biggestInt) + of tyUInt8: awi(nkUInt8Lit, rd(uint8, x).biggestInt) + of tyUInt16: awi(nkUInt16Lit, rd(uint16, x).biggestInt) + of tyUInt32: awi(nkUInt32Lit, rd(uint32, x).biggestInt) + of tyUInt64: awi(nkUInt64Lit, rd(uint64, x).biggestInt) + of tyEnum: + case typ.getSize + of 1: awi(nkIntLit, rd(uint8, x).biggestInt) + of 2: awi(nkIntLit, rd(uint16, x).biggestInt) + of 4: awi(nkIntLit, rd(int32, x).biggestInt) + of 8: awi(nkIntLit, rd(int64, x).biggestInt) + else: + GlobalError(n.info, "cannot map value from FFI (tyEnum, tySet)") + of tyFloat: awf(nkFloatLit, rd(float, x)) + of tyFloat32: awf(nkFloat32Lit, rd(float32, x)) + of tyFloat64: awf(nkFloat64Lit, rd(float64, x)) + of tyPointer, tyProc: + let p = rd(pointer, x) + if p.isNil: + setNil() + elif n != nil and n.kind == nkStrLit: + # we passed a string literal as a pointer; however strings are already + # in their unboxed representation so nothing it to be unpacked: + result = n + else: + awi(nkPtrLit, cast[TAddress](p)) + of tyPtr, tyRef, tyVar: + let p = rd(pointer, x) + if p.isNil: + setNil() + elif n == nil or n.kind == nkPtrLit: + awi(nkPtrLit, cast[TAddress](p)) + elif n != nil and n.len == 1: + internalAssert n.kind == nkRefTy + n.sons[0] = unpack(p, typ.sons[0], n.sons[0]) + result = n + else: + GlobalError(n.info, "cannot map value from FFI " & typeToString(typ)) + of tyObject, tyTuple: + result = unpackObject(x, typ, n) + of tyArray, tyArrayConstr: + result = unpackArray(x, typ, n) + of tyCString, tyString: + let p = rd(cstring, x) + if p.isNil: + setNil() + else: + aws(nkStrLit, $p) + of tyNil: + setNil() + of tyDistinct, tyGenericInst: + result = unpack(x, typ.sons[0], n) + else: + # XXX what to do with 'array' here? + GlobalError(n.info, "cannot map value from FFI " & typeToString(typ)) + +proc fficast*(x: PNode, destTyp: PType): PNode = + if x.kind == nkPtrLit and x.typ.kind in {tyPtr, tyRef, tyVar, tyPointer, + tyProc, tyCString, tyString, + tySequence}: + result = newNodeIT(x.kind, x.info, destTyp) + result.intVal = x.intVal + elif x.kind == nkNilLit: + result = newNodeIT(x.kind, x.info, destTyp) + else: + # we play safe here and allocate the max possible size: + let size = max(packSize(x, x.typ), packSize(x, destTyp)) + var a = alloc0(size) + pack(x, x.typ, a) + # cast through a pointer needs a new inner object: + let y = if x.kind == nkRefTy: newNodeI(nkRefTy, x.info, 1) + else: x.copyTree + y.typ = x.typ + result = unpack(a, destTyp, y) + dealloc a + +proc callForeignFunction*(call: PNode): PNode = + InternalAssert call.sons[0].kind == nkPtrLit + + var cif: TCif + var sig: TParamList + # use the arguments' types for varargs support: + for i in 1..call.len-1: + sig[i-1] = mapType(call.sons[i].typ) + if sig[i-1].isNil: + GlobalError(call.info, "cannot map FFI type") + + let typ = call.sons[0].typ + if prep_cif(cif, mapCallConv(typ.callConv, call.info), cuint(call.len-1), + mapType(typ.sons[0]), sig) != OK: + GlobalError(call.info, "error in FFI call") + + var args: TArgList + let fn = cast[pointer](call.sons[0].intVal) + for i in 1 .. call.len-1: + var t = call.sons[i].typ + args[i-1] = alloc0(packSize(call.sons[i], t)) + pack(call.sons[i], t, args[i-1]) + let retVal = if isEmptyType(typ.sons[0]): pointer(nil) + else: alloc(typ.sons[0].getSize.int) + + libffi.call(cif, fn, retVal, args) + + if retVal.isNil: + result = emptyNode + else: + result = unpack(retVal, typ.sons[0], nil) + result.info = call.info + + if retVal != nil: dealloc retVal + for i in 1 .. call.len-1: + call.sons[i] = unpack(args[i-1], typ.sons[i], call[i]) + dealloc args[i-1] diff --git a/compiler/evals.nim b/compiler/evals.nim index 924540b26..4b83cb703 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -18,6 +18,9 @@ import msgs, os, condsyms, idents, renderer, types, passes, semfold, transf, parser, ropes, rodread, idgen, osproc, streams, evaltempl +when hasFFI: + import evalffi + type PStackFrame* = ref TStackFrame TStackFrame*{.final.} = object @@ -34,12 +37,20 @@ type ## emConst?) emStatic ## evaluate for enforced compile time eval ## ('static' context) + + TSandboxFlag* = enum ## what the evaluation engine should allow + allowCast, ## allow unsafe language feature: 'cast' + allowFFI, ## allow the FFI + allowInfiniteLoops ## allow endless loops + TSandboxFlags* = set[TSandboxFlag] + TEvalContext* = object of passes.TPassContext module*: PSym tos*: PStackFrame # top of stack lastException*: PNode callsite: PNode # for 'callsite' magic mode*: TEvalMode + features: TSandboxFlags globals*: TIdNodeTable # state of global vars getType*: proc(n: PNode): PNode {.closure.} @@ -65,6 +76,7 @@ proc newEvalContext*(module: PSym, mode: TEvalMode): PEvalContext = new(result) result.module = module result.mode = mode + result.features = {allowFFI} initIdNodeTable(result.globals) proc pushStackFrame*(c: PEvalContext, t: PStackFrame) {.inline.} = @@ -78,7 +90,7 @@ proc popStackFrame*(c: PEvalContext) {.inline.} = proc evalMacroCall*(c: PEvalContext, n, nOrig: PNode, sym: PSym): PNode proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode -proc raiseCannotEval(c: PEvalContext, info: TLineInfo): PNode = +proc raiseCannotEval(c: PEvalContext, info: TLineInfo): PNode = result = newNodeI(nkExceptBranch, info) # creating a nkExceptBranch without sons # means that it could not be evaluated @@ -162,9 +174,12 @@ proc evalWhile(c: PEvalContext, n: PNode): PNode = of nkExceptBranch, nkReturnToken: break else: nil dec(gWhileCounter) - if gWhileCounter <= 0: - stackTrace(c, n, errTooManyIterations) - break + if gWhileCounter <= 0: + if allowInfiniteLoops in c.features: + gWhileCounter = 0 + else: + stackTrace(c, n, errTooManyIterations) + break proc evalBlock(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) @@ -303,45 +318,9 @@ proc evalVar(c: PEvalContext, n: PNode): PNode = else: if x.kind notin {nkEmpty..nkNilLit}: discardSons(x) - for i in countup(0, sonsLen(result) - 1): addSon(x, result.sons[i]) + for j in countup(0, sonsLen(result) - 1): addSon(x, result.sons[j]) result = emptyNode -proc evalCall(c: PEvalContext, n: PNode): PNode = - var d = newStackFrame() - d.call = n - var prc = n.sons[0] - let isClosure = prc.kind == nkClosure - setlen(d.params, sonsLen(n) + ord(isClosure)) - if isClosure: - #debug prc - result = evalAux(c, prc.sons[1], {efLValue}) - if isSpecial(result): return - d.params[sonsLen(n)] = result - result = evalAux(c, prc.sons[0], {}) - else: - result = evalAux(c, prc, {}) - - if isSpecial(result): return - prc = result - # bind the actual params to the local parameter of a new binding - if prc.kind != nkSym: - InternalError(n.info, "evalCall " & n.renderTree) - return - d.prc = prc.sym - if prc.sym.kind notin {skProc, skConverter, skMacro}: - InternalError(n.info, "evalCall") - return - for i in countup(1, sonsLen(n) - 1): - result = evalAux(c, n.sons[i], {}) - if isSpecial(result): return - d.params[i] = result - if n.typ != nil: d.params[0] = getNullValue(n.typ, n.info) - pushStackFrame(c, d) - result = evalAux(c, prc.sym.getBody, {}) - if result.kind == nkExceptBranch: return - if n.typ != nil: result = d.params[0] - popStackFrame(c) - proc aliasNeeded(n: PNode, flags: TEvalFlags): bool = result = efLValue in flags or n.typ == nil or n.typ.kind in {tyExpr, tyStmt, tyTypeDesc} @@ -371,6 +350,12 @@ proc evalGlobalVar(c: PEvalContext, s: PSym, flags: TEvalFlags): PNode = if not aliasNeeded(result, flags): result = copyTree(result) else: + when hasFFI: + if sfImportc in s.flags and allowFFI in c.features: + result = importcSymbol(s) + IdNodeTablePut(c.globals, s, result) + return result + result = s.ast if result == nil or result.kind == nkEmpty: result = getNullValue(s.typ, s.info) @@ -381,6 +366,51 @@ proc evalGlobalVar(c: PEvalContext, s: PSym, flags: TEvalFlags): PNode = else: result = raiseCannotEval(nil, s.info) +proc evalCall(c: PEvalContext, n: PNode): PNode = + var d = newStackFrame() + d.call = n + var prc = n.sons[0] + let isClosure = prc.kind == nkClosure + setlen(d.params, sonsLen(n) + ord(isClosure)) + if isClosure: + #debug prc + result = evalAux(c, prc.sons[1], {efLValue}) + if isSpecial(result): return + d.params[sonsLen(n)] = result + result = evalAux(c, prc.sons[0], {}) + else: + result = evalAux(c, prc, {}) + + if isSpecial(result): return + prc = result + # bind the actual params to the local parameter of a new binding + if prc.kind != nkSym: + InternalError(n.info, "evalCall " & n.renderTree) + return + d.prc = prc.sym + if prc.sym.kind notin {skProc, skConverter, skMacro}: + InternalError(n.info, "evalCall") + return + for i in countup(1, sonsLen(n) - 1): + result = evalAux(c, n.sons[i], {}) + if isSpecial(result): return + d.params[i] = result + if n.typ != nil: d.params[0] = getNullValue(n.typ, n.info) + + when hasFFI: + if sfImportc in prc.sym.flags and allowFFI in c.features: + var newCall = newNodeI(nkCall, n.info, n.len) + newCall.sons[0] = evalGlobalVar(c, prc.sym, {}) + for i in 1 .. <n.len: + newCall.sons[i] = d.params[i] + return callForeignFunction(newCall) + + pushStackFrame(c, d) + result = evalAux(c, prc.sym.getBody, {}) + if result.kind == nkExceptBranch: return + if n.typ != nil: result = d.params[0] + popStackFrame(c) + proc evalArrayAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalAux(c, n.sons[0], flags) if isSpecial(result): return @@ -519,7 +549,9 @@ proc evalSym(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = of skConst: result = s.ast of skEnumField: result = newIntNodeT(s.position, n) else: result = nil - if result == nil or {sfImportc, sfForward} * s.flags != {}: + let mask = if hasFFI and allowFFI in c.features: {sfForward} + else: {sfImportc, sfForward} + if result == nil or mask * s.flags != {}: result = raiseCannotEval(c, n.info) proc evalIncDec(c: PEvalContext, n: PNode, sign: biggestInt): PNode = @@ -617,6 +649,18 @@ proc evalConv(c: PEvalContext, n: PNode): PNode = # foldConv() cannot deal with everything that we want to do here: result = a +proc evalCast(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = + if allowCast in c.features: + when hasFFI: + result = evalAux(c, n.sons[1], {efLValue}) + if isSpecial(result): return + InternalAssert result.typ != nil + result = fficast(result, n.typ) + else: + result = evalConv(c, n) + else: + result = raiseCannotEval(c, n.info) + proc evalCheckedFieldAccess(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result = evalAux(c, n.sons[0], flags) @@ -1363,7 +1407,9 @@ proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = result.typ = n.typ of nkPragmaBlock: result = evalAux(c, n.sons[1], flags) - of nkIdentDefs, nkCast, nkYieldStmt, nkAsmStmt, nkForStmt, nkPragmaExpr, + of nkCast: + result = evalCast(c, n, flags) + of nkIdentDefs, nkYieldStmt, nkAsmStmt, nkForStmt, nkPragmaExpr, nkLambdaKinds, nkContinueStmt, nkIdent, nkParForStmt, nkBindStmt: result = raiseCannotEval(c, n.info) of nkRefTy: @@ -1388,7 +1434,7 @@ proc eval*(c: PEvalContext, n: PNode): PNode = if sonsLen(result) >= 1: stackTrace(c, n, errUnhandledExceptionX, typeToString(result.typ)) else: - stackTrace(c, n, errCannotInterpretNodeX, renderTree(n)) + stackTrace(c, result, errCannotInterpretNodeX, renderTree(n)) proc evalConstExprAux(module: PSym, e: PNode, mode: TEvalMode): PNode = var p = newEvalContext(module, mode) @@ -1431,8 +1477,9 @@ proc evalMacroCall(c: PEvalContext, n, nOrig: PNode, sym: PSym): PNode = dec(evalTemplateCounter) c.callsite = nil -proc myOpen(module: PSym): PPassContext = +proc myOpen(module: PSym): PPassContext = var c = newEvalContext(module, emRepl) + c.features = {allowCast, allowFFI, allowInfiniteLoops} pushStackFrame(c, newStackFrame()) result = c diff --git a/compiler/importer.nim b/compiler/importer.nim index 1723e9e0e..d274b4693 100755 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -11,11 +11,10 @@ import intsets, strutils, os, ast, astalgo, msgs, options, idents, rodread, lookups, - semdata, passes + semdata, passes, renderer proc evalImport*(c: PContext, n: PNode): PNode proc evalFrom*(c: PContext, n: PNode): PNode -proc importAllSymbols*(c: PContext, fromMod: PSym) proc getModuleName*(n: PNode): string = # This returns a short relative module name without the nim extension @@ -29,8 +28,11 @@ proc getModuleName*(n: PNode): string = of nkSym: result = n.sym.name.s else: - internalError(n.info, "getModuleName") - result = "" + # hacky way to implement 'x / y /../ z': + result = renderTree(n, {renderNoComments}).replace(" ") + #localError(n.info, errGenerated, + # "invalide module name: '$1'" % renderTree(n)) + #result = "" proc checkModuleName*(n: PNode): int32 = # This returns the full canonical path for a given module import @@ -42,42 +44,43 @@ proc checkModuleName*(n: PNode): int32 = else: result = fullPath.fileInfoIdx -proc rawImportSymbol(c: PContext, s: PSym) = +proc rawImportSymbol(c: PContext, s: PSym) = # This does not handle stubs, because otherwise loading on demand would be # pointless in practice. So importing stubs is fine here! - var copy = s # do not copy symbols when importing! # check if we have already a symbol of the same name: var check = StrTableGet(c.tab.stack[importTablePos], s.name) - if check != nil and check.id != copy.id: - if s.kind notin OverloadableSyms: + if check != nil and check.id != s.id: + if s.kind notin OverloadableSyms: # s and check need to be qualified: - Incl(c.AmbiguousSymbols, copy.id) + Incl(c.AmbiguousSymbols, s.id) Incl(c.AmbiguousSymbols, check.id) - StrTableAdd(c.tab.stack[importTablePos], copy) - if s.kind == skType: + # thanks to 'export' feature, it could be we import the same symbol from + # multiple sources, so we need to call 'StrTableAdd' here: + StrTableAdd(c.tab.stack[importTablePos], s) + if s.kind == skType: var etyp = s.typ - if etyp.kind in {tyBool, tyEnum} and sfPure notin s.flags: - for j in countup(0, sonsLen(etyp.n) - 1): + if etyp.kind in {tyBool, tyEnum} and sfPure notin s.flags: + for j in countup(0, sonsLen(etyp.n) - 1): var e = etyp.n.sons[j].sym - if (e.Kind != skEnumField): + if e.Kind != skEnumField: InternalError(s.info, "rawImportSymbol") # BUGFIX: because of aliases for enums the symbol may already # have been put into the symbol table # BUGFIX: but only iff they are the same symbols! var it: TIdentIter check = InitIdentIter(it, c.tab.stack[importTablePos], e.name) - while check != nil: - if check.id == e.id: + while check != nil: + if check.id == e.id: e = nil - break + break check = NextIdentIter(it, c.tab.stack[importTablePos]) - if e != nil: + if e != nil: rawImportSymbol(c, e) else: # rodgen assures that converters and patterns are no stubs if s.kind == skConverter: addConverter(c, s) if hasPattern(s): addPattern(c, s) - + proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = let ident = lookups.considerAcc(n) let s = StrTableGet(fromMod.tab, ident) @@ -98,20 +101,43 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym) = rawImportSymbol(c, e) e = NextIdentIter(it, fromMod.tab) else: rawImportSymbol(c, s) - -proc importAllSymbols(c: PContext, fromMod: PSym) = + +proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: TIntSet) = var i: TTabIter var s = InitTabIter(i, fromMod.tab) - while s != nil: - if s.kind != skModule: - if s.kind != skEnumField: - if not (s.Kind in ExportableSymKinds): + while s != nil: + if s.kind != skModule: + if s.kind != skEnumField: + if s.Kind notin ExportableSymKinds: InternalError(s.info, "importAllSymbols: " & $s.kind) - rawImportSymbol(c, s) # this is correct! + if exceptSet.empty or s.name.id notin exceptSet: + rawImportSymbol(c, s) s = NextIter(i, fromMod.tab) +proc importAllSymbols*(c: PContext, fromMod: PSym) = + var exceptSet: TIntSet + importAllSymbolsExcept(c, fromMod, exceptSet) + +proc importForwarded(c: PContext, n: PNode, exceptSet: TIntSet) = + if n.isNil: return + case n.kind + of nkExportStmt: + for a in n: + assert a.kind == nkSym + let s = a.sym + if s.kind == skModule: + importAllSymbolsExcept(c, s, exceptSet) + elif exceptSet.empty or s.name.id notin exceptSet: + rawImportSymbol(c, s) + of nkExportExceptStmt: + localError(n.info, errGenerated, "'export except' not implemented") + else: + for i in 0 ..safeLen(n)-1: + importForwarded(c, n.sons[i], exceptSet) + proc evalImport(c: PContext, n: PNode): PNode = result = n + var emptySet: TIntSet for i in countup(0, sonsLen(n) - 1): var f = checkModuleName(n.sons[i]) if f != InvalidFileIDX: @@ -120,7 +146,8 @@ proc evalImport(c: PContext, n: PNode): PNode = Message(n.sons[i].info, warnDeprecated, m.name.s) # ``addDecl`` needs to be done before ``importAllSymbols``! addDecl(c, m) # add symbol to symbol table of module - importAllSymbols(c, m) + importAllSymbolsExcept(c, m, emptySet) + importForwarded(c, m.ast, emptySet) proc evalFrom(c: PContext, n: PNode): PNode = result = n @@ -131,3 +158,18 @@ proc evalFrom(c: PContext, n: PNode): PNode = n.sons[0] = newSymNode(m) addDecl(c, m) # add symbol to symbol table of module for i in countup(1, sonsLen(n) - 1): importSymbol(c, n.sons[i], m) + +proc evalImportExcept*(c: PContext, n: PNode): PNode = + result = n + checkMinSonsLen(n, 2) + var f = checkModuleName(n.sons[0]) + if f != InvalidFileIDX: + var m = gImportModule(c.module, f) + n.sons[0] = newSymNode(m) + addDecl(c, m) # add symbol to symbol table of module + var exceptSet = initIntSet() + for i in countup(1, sonsLen(n) - 1): + let ident = lookups.considerAcc(n.sons[i]) + exceptSet.incl(ident.id) + importAllSymbolsExcept(c, m, exceptSet) + importForwarded(c, m.ast, exceptSet) diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 309e01c6f..0c3eea3be 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -257,6 +257,8 @@ proc captureVar(o: POuterContext, i: PInnerContext, local: PSym, # Currently captures are restricted to a single level of nesting: LocalError(info, errIllegalCaptureX, local.name.s) i.fn.typ.callConv = ccClosure + #echo "captureVar ", i.fn.name.s, i.fn.id, " ", local.name.s, local.id + incl(i.fn.typ.flags, tfCapturesEnv) # we need to remember which inner most closure belongs to this lambda: @@ -286,9 +288,10 @@ proc interestingVar(s: PSym): bool {.inline.} = sfGlobal notin s.flags proc semCaptureSym*(s, owner: PSym) = - if interestingVar(s) and owner.id != s.owner.id: + if interestingVar(s) and owner.id != s.owner.id and s.kind != skResult: if owner.typ != nil and not isGenericRoutine(owner): owner.typ.callConv = ccClosure + #echo "semCaptureSym ", owner.name.s, owner.id, " ", s.name.s, s.id # since the analysis is not entirely correct, we don't set 'tfCapturesEnv' # here diff --git a/compiler/lists.nim b/compiler/lists.nim index 1998581ce..67b32f919 100755 --- a/compiler/lists.nim +++ b/compiler/lists.nim @@ -89,9 +89,25 @@ proc Remove*(list: var TLinkedList, entry: PListEntry) = list.head = entry.next if entry.next != nil: entry.next.prev = entry.prev if entry.prev != nil: entry.prev.next = entry.next - + +proc bringToFront*(list: var TLinkedList, entry: PListEntry) = + if entry == list.head: return + if entry == list.tail: list.tail = entry.prev + if entry.next != nil: entry.next.prev = entry.prev + if entry.prev != nil: entry.prev.next = entry.next + entry.prev = nil + entry.next = list.head + list.head = entry + +proc ExcludeStr*(list: var TLinkedList, data: string) = + var it = list.head + while it != nil: + let nxt = it.next + if PStrEntry(it).data == data: remove(list, it) + it = nxt + proc Find*(list: TLinkedList, fn: TCompareProc, closure: Pointer): PListEntry = result = list.head - while result != nil: + while result != nil: if fn(result, closure): return result = result.next diff --git a/compiler/llstream.nim b/compiler/llstream.nim index 1d1722176..8ccf24b99 100755 --- a/compiler/llstream.nim +++ b/compiler/llstream.nim @@ -103,17 +103,24 @@ proc continueLine(line: string, inTripleString: bool): bool {.inline.} = line[0] == ' ' or line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs) -proc LLreadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int = - var inTripleString = false +proc countTriples(s: string): int = + var i = 0 + while i < s.len: + if s[i] == '"' and s[i+1] == '"' and s[i+2] == '"': + inc result + inc i, 2 + inc i + +proc LLreadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int = s.s = "" s.rd = 0 var line = newStringOfCap(120) + var triples = 0 while ReadLineFromStdin(if s.s.len == 0: ">>> " else: "... ", line): add(s.s, line) add(s.s, "\n") - if line.contains("\"\"\""): - inTripleString = not inTripleString - if not continueLine(line, inTripleString): break + inc triples, countTriples(line) + if not continueLine(line, (triples and 1) == 1): break inc(s.lineOffset) result = min(bufLen, len(s.s) - s.rd) if result > 0: diff --git a/compiler/main.nim b/compiler/main.nim index 4767c1537..cba96a104 100755 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -302,7 +302,7 @@ when has_LLVM_Backend: compileProject() proc CommandCompileToEcmaScript = - incl(gGlobalOptions, optSafeCode) + #incl(gGlobalOptions, optSafeCode) setTarget(osEcmaScript, cpuEcmaScript) #initDefines() DefineSymbol("nimrod") # 'nimrod' is always defined @@ -316,6 +316,7 @@ proc InteractivePasses = #setTarget(osNimrodVM, cpuNimrodVM) initDefines() DefineSymbol("nimrodvm") + when hasFFI: DefineSymbol("nimffi") registerPass(verbosePass) registerPass(semPass) registerPass(evalPass) @@ -524,11 +525,11 @@ proc MainCommand = gCmd = cmdGenDepend wantMainModule() CommandGenDepend() - of "dump": + of "dump": gCmd = cmdDump condsyms.ListSymbols() - for it in iterSearchPath(): MsgWriteln(it) - of "check": + for it in iterSearchPath(searchPaths): MsgWriteln(it) + of "check": gCmd = cmdCheck wantMainModule() CommandCheck() diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 587ac4867..0f2affc36 100755 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -132,7 +132,7 @@ const errNumberOutOfRange: "number $1 out of valid range", errNnotAllowedInCharacter: "\\n not allowed in character literal", errClosingBracketExpected: "closing ']' expected, but end of file reached", - errMissingFinalQuote: "missing final \'", + errMissingFinalQuote: "missing final \' for character literal", errIdentifierExpected: "identifier expected, but found \'$1\'", errNewlineExpected: "newline expected, but found \'$1\'", errInvalidModuleName: "invalid module name: '$1'", @@ -575,7 +575,15 @@ template toFilename*(info: TLineInfo): string = template toFullPath*(info: TLineInfo): string = info.fileIndex.toFullPath - + +proc toMsgFilename*(info: TLineInfo): string = + if info.fileIndex < 0: result = "???" + else: + if gListFullPaths: + result = fileInfos[info.fileIndex].fullPath + else: + result = fileInfos[info.fileIndex].projPath + proc toLinenumber*(info: TLineInfo): int {.inline.} = result = info.line @@ -666,7 +674,7 @@ proc writeContext(lastinfo: TLineInfo) = var info = lastInfo for i in countup(0, len(msgContext) - 1): if msgContext[i] != lastInfo and msgContext[i] != info: - MsgWriteln(posContextFormat % [toFilename(msgContext[i]), + MsgWriteln(posContextFormat % [toMsgFilename(msgContext[i]), coordToStr(msgContext[i].line), coordToStr(msgContext[i].col), getMessageStr(errInstantiationFrom, "")]) @@ -720,7 +728,7 @@ proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string, ignoreMsg = optHints notin gOptions or msg notin gNotes frmt = posHintFormat inc(gHintCounter) - let s = frmt % [toFilename(info), coordToStr(info.line), + let s = frmt % [toMsgFilename(info), coordToStr(info.line), coordToStr(info.col), getMessageStr(msg, arg)] if not ignoreMsg: MsgWriteln(s) @@ -732,6 +740,9 @@ proc Fatal*(info: TLineInfo, msg: TMsgKind, arg = "") = proc GlobalError*(info: TLineInfo, msg: TMsgKind, arg = "") = liMessage(info, msg, arg, doRaise) +proc GlobalError*(info: TLineInfo, arg: string) = + liMessage(info, errGenerated, arg, doRaise) + proc LocalError*(info: TLineInfo, msg: TMsgKind, arg = "") = liMessage(info, msg, arg, doNothing) diff --git a/compiler/nimrod.nim b/compiler/nimrod.nim index e192b64ab..6c999128c 100755 --- a/compiler/nimrod.nim +++ b/compiler/nimrod.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. diff --git a/compiler/options.nim b/compiler/options.nim index 2d45201dc..7350f81b7 100755 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -13,6 +13,7 @@ import const hasTinyCBackend* = defined(tinyc) useEffectSystem* = true + hasFFI* = defined(useFFI) type # please make sure we have under 32 options # (improves code efficiency a lot!) @@ -91,7 +92,7 @@ var optPatterns} gGlobalOptions*: TGlobalOptions = {optRefcGC, optThreadAnalysis} gExitcode*: int8 - searchPaths*: TLinkedList + searchPaths*, lazyPaths*: TLinkedList outFile*: string = "" headerFile*: string = "" gCmd*: TCommands = cmdNone # the command @@ -100,7 +101,10 @@ var gWholeProject*: bool # for 'doc2': output any dependency gEvalExpr*: string # expression for idetools --eval gLastCmdTime*: float # when caas is enabled, we measure each command + gListFullPaths*: bool +proc importantComments*(): bool {.inline.} = gCmd in {cmdDoc, cmdIdeTools} + const genSubDir* = "nimcache" NimExt* = "nim" @@ -199,27 +203,53 @@ proc completeGeneratedFilePath*(f: string, createSubDir: bool = true): string = result = joinPath(subdir, tail) #echo "completeGeneratedFilePath(", f, ") = ", result -iterator iterSearchPath*(): string = +iterator iterSearchPath*(SearchPaths: TLinkedList): string = var it = PStrEntry(SearchPaths.head) - while it != nil: + while it != nil: yield it.data it = PStrEntry(it.Next) proc rawFindFile(f: string): string = - for it in iterSearchPath(): + for it in iterSearchPath(SearchPaths): result = JoinPath(it, f) - if ExistsFile(result): + if existsFile(result): return result.canonicalizePath result = "" +proc rawFindFile2(f: string): string = + var it = PStrEntry(lazyPaths.head) + while it != nil: + result = JoinPath(it.data, f) + if existsFile(result): + bringToFront(lazyPaths, it) + return result.canonicalizePath + it = PStrEntry(it.Next) + result = "" + proc FindFile*(f: string): string {.procvar.} = - result = rawFindFile(f) - if len(result) == 0: result = rawFindFile(toLower(f)) + result = f.rawFindFile + if result.len == 0: + result = f.toLower.rawFindFile + if result.len == 0: + result = f.rawFindFile2 + if result.len == 0: + result = f.toLower.rawFindFile2 proc findModule*(modulename: string): string {.inline.} = # returns path to module result = FindFile(AddFileExt(modulename, nimExt)) +proc libCandidates*(s: string, dest: var seq[string]) = + var le = strutils.find(s, '(') + var ri = strutils.find(s, ')', le+1) + if le >= 0 and ri > le: + var prefix = substr(s, 0, le - 1) + var suffix = substr(s, ri + 1) + for middle in split(substr(s, le + 1, ri - 1), '|'): + libCandidates(prefix & middle & suffix, dest) + else: + add(dest, s) + proc binaryStrSearch*(x: openarray[string], y: string): int = var a = 0 var b = len(x) - 1 diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim index ee1f69818..21c7faf19 100644 --- a/compiler/parampatterns.nim +++ b/compiler/parampatterns.nim @@ -48,8 +48,8 @@ proc add(code: var TPatternCode, op: TOpcode) {.inline.} = add(code, chr(ord(op))) proc whichAlias*(p: PSym): TAliasRequest = - if p.typ.constraint != nil: - result = TAliasRequest(p.typ.constraint.strVal[0].ord) + if p.constraint != nil: + result = TAliasRequest(p.constraint.strVal[0].ord) proc compileConstraints(p: PNode, result: var TPatternCode) = case p.kind diff --git a/compiler/parser.nim b/compiler/parser.nim index 3634168bb..a2c7f71d2 100755 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -875,10 +875,10 @@ proc parseExprStmt(p: var TParser): PNode = getTok(p) skipComment(p, result) if p.tok.tokType == tkSad: getTok(p) - if not (p.tok.TokType in {tkOf, tkElif, tkElse, tkExcept}): + if p.tok.TokType notin {tkOf, tkElif, tkElse, tkExcept}: let body = parseStmt(p) addSon(result, newProcNode(nkDo, body.info, body)) - while true: + while true: if p.tok.tokType == tkSad: getTok(p) var b: PNode case p.tok.tokType @@ -903,30 +903,34 @@ proc parseExprStmt(p: var TParser): PNode = addSon(b, parseStmt(p)) addSon(result, b) if b.kind == nkElse: break - -proc parseImportOrIncludeStmt(p: var TParser, kind: TNodeKind): PNode = - var a: PNode + +proc parseImport(p: var TParser, kind: TNodeKind): PNode = result = newNodeP(kind, p) - getTok(p) # skip `import` or `include` + getTok(p) # skip `import` or `export` optInd(p, result) - while true: - case p.tok.tokType - of tkEof, tkSad, tkDed: - break - of tkSymbol, tkAccent: - a = parseSymbol(p) - of tkRStrLit: - a = newStrNodeP(nkRStrLit, p.tok.literal, p) - getTok(p) - of tkStrLit: - a = newStrNodeP(nkStrLit, p.tok.literal, p) - getTok(p) - of tkTripleStrLit: - a = newStrNodeP(nkTripleStrLit, p.tok.literal, p) + var a = parseExpr(p) + addSon(result, a) + if p.tok.tokType in {tkComma, tkExcept}: + if p.tok.tokType == tkExcept: + result.kind = succ(kind) + getTok(p) + optInd(p, result) + while p.tok.tokType notin {tkEof, tkSad, tkDed}: + a = parseExpr(p) + if a.kind == nkEmpty: break + addSon(result, a) + if p.tok.tokType != tkComma: break getTok(p) - else: - parMessage(p, errIdentifierExpected, p.tok) - break + optInd(p, a) + expectNl(p) + +proc parseIncludeStmt(p: var TParser): PNode = + result = newNodeP(nkIncludeStmt, p) + getTok(p) # skip `import` or `include` + optInd(p, result) + while p.tok.tokType notin {tkEof, tkSad, tkDed}: + var a = parseExpr(p) + if a.kind == nkEmpty: break addSon(result, a) if p.tok.tokType != tkComma: break getTok(p) @@ -934,37 +938,16 @@ proc parseImportOrIncludeStmt(p: var TParser, kind: TNodeKind): PNode = expectNl(p) proc parseFromStmt(p: var TParser): PNode = - var a: PNode result = newNodeP(nkFromStmt, p) getTok(p) # skip `from` optInd(p, result) - case p.tok.tokType - of tkSymbol, tkAccent: - a = parseSymbol(p) - of tkRStrLit: - a = newStrNodeP(nkRStrLit, p.tok.literal, p) - getTok(p) - of tkStrLit: - a = newStrNodeP(nkStrLit, p.tok.literal, p) - getTok(p) - of tkTripleStrLit: - a = newStrNodeP(nkTripleStrLit, p.tok.literal, p) - getTok(p) - else: - parMessage(p, errIdentifierExpected, p.tok) - return + var a = parseExpr(p) addSon(result, a) #optInd(p, a); eat(p, tkImport) optInd(p, result) - while true: - case p.tok.tokType #optInd(p, a); - of tkEof, tkSad, tkDed: - break - of tkSymbol, tkAccent: - a = parseSymbol(p) - else: - parMessage(p, errIdentifierExpected, p.tok) - break + while p.tok.tokType notin {tkEof, tkSad, tkDed}: + a = parseExpr(p) + if a.kind == nkEmpty: break addSon(result, a) if p.tok.tokType != tkComma: break getTok(p) @@ -1279,14 +1262,7 @@ proc parseEnum(p: var TParser): PNode = result = newNodeP(nkEnumTy, p) a = nil getTok(p) - if false and p.tok.tokType == tkOf: - a = newNodeP(nkOfInherit, p) - getTok(p) - optInd(p, a) - addSon(a, parseTypeDesc(p)) - addSon(result, a) - else: - addSon(result, ast.emptyNode) + addSon(result, ast.emptyNode) optInd(p, result) while true: case p.tok.tokType @@ -1425,18 +1401,6 @@ proc parseDistinct(p: var TParser): PNode = optInd(p, result) addSon(result, parseTypeDesc(p)) -proc parsePointerInTypeSection(p: var TParser, kind: TNodeKind): PNode = - result = newNodeP(kind, p) - getTok(p) - optInd(p, result) - if not isOperator(p.tok): - case p.tok.tokType - of tkObject: addSon(result, parseObject(p)) - of tkTuple: addSon(result, parseTuple(p, true)) - else: - if isExprStart(p): - addSon(result, parseTypeDesc(p)) - proc parseTypeDef(p: var TParser): PNode = result = newNodeP(nkTypeDef, p) addSon(result, identWithPragma(p)) @@ -1445,15 +1409,6 @@ proc parseTypeDef(p: var TParser): PNode = if p.tok.tokType == tkEquals: getTok(p) optInd(p, result) - #var a: PNode - #case p.tok.tokType - #of tkObject: a = parseObject(p) - #of tkEnum: a = parseEnum(p) - #of tkDistinct: a = parseDistinct(p) - #of tkTuple: a = parseTuple(p, true) - #of tkRef: a = parsePointerInTypeSection(p, nkRefTy) - #of tkPtr: a = parsePointerInTypeSection(p, nkPtrTy) - #else: a = parseTypeDesc(p) addSon(result, parseTypeDefAux(p)) else: addSon(result, ast.emptyNode) @@ -1511,11 +1466,12 @@ proc simpleStmt(p: var TParser): PNode = of tkBreak: result = parseBreakOrContinue(p, nkBreakStmt) of tkContinue: result = parseBreakOrContinue(p, nkContinueStmt) of tkCurlyDotLe: result = parseStmtPragma(p) - of tkImport: result = parseImportOrIncludeStmt(p, nkImportStmt) + of tkImport: result = parseImport(p, nkImportStmt) + of tkExport: result = parseImport(p, nkExportStmt) of tkFrom: result = parseFromStmt(p) - of tkInclude: result = parseImportOrIncludeStmt(p, nkIncludeStmt) + of tkInclude: result = parseIncludeStmt(p) of tkComment: result = newCommentStmt(p) - else: + else: if isExprStart(p): result = parseExprStmt(p) else: result = ast.emptyNode if result.kind != nkEmpty: skipComment(p, result) diff --git a/compiler/patterns.nim b/compiler/patterns.nim index af259c916..ff7f18ac0 100644 --- a/compiler/patterns.nim +++ b/compiler/patterns.nim @@ -71,8 +71,8 @@ proc inSymChoice(sc, x: PNode): bool = proc checkTypes(c: PPatternContext, p: PSym, n: PNode): bool = # check param constraints first here as this is quite optimized: - if p.typ.constraint != nil: - result = matchNodeKinds(p.typ.constraint, n) + if p.constraint != nil: + result = matchNodeKinds(p.constraint, n) if not result: return if isNil(n.typ): result = p.typ.kind in {tyEmpty, tyStmt} @@ -237,6 +237,15 @@ proc addToArgList(result, n: PNode) = else: for i in 0 .. <n.len: result.add(n.sons[i]) +when false: + proc procPatternMatches*(c: PContext, s: PSym, n: PNode): bool = + ## for AST-based overloading: + var ctx: TPatternContext + ctx.owner = s + ctx.c = c + ctx.formals = sonsLen(s.typ)-1 + result = matches(ctx, s.ast.sons[patternPos], n) + proc applyRule*(c: PContext, s: PSym, n: PNode): PNode = ## returns a tree to semcheck if the rule triggered; nil otherwise var ctx: TPatternContext diff --git a/compiler/procfind.nim b/compiler/procfind.nim index fde4d22ea..4a1fb5ac8 100755 --- a/compiler/procfind.nim +++ b/compiler/procfind.nim @@ -29,7 +29,7 @@ proc equalGenericParams(procA, procB: PNode): bool = a = procA.sons[i].sym b = procB.sons[i].sym if (a.name.id != b.name.id) or - not sameTypeOrNil(a.typ, b.typ, {TypeDescExactMatch}): return + not sameTypeOrNil(a.typ, b.typ, {TypeDescExactMatch}): return if (a.ast != nil) and (b.ast != nil): if not ExprStructuralEquivalent(a.ast, b.ast): return result = true @@ -44,7 +44,7 @@ proc SearchForProc*(c: PContext, fn: PSym, tos: int): PSym = if equalGenericParams(result.ast.sons[genericParamsPos], fn.ast.sons[genericParamsPos]): case equalParams(result.typ.n, fn.typ.n) - of paramsEqual: + of paramsEqual: return of paramsIncompatible: LocalError(fn.info, errNotOverloadable, fn.name.s) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index b9d522694..48a190ec1 100755 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -753,8 +753,7 @@ proc doParamsAux(g: var TSrcGen, params: PNode) = proc gsub(g: var TSrcGen, n: PNode, c: TContext) = if isNil(n): return - var - L: int + var a: TContext if n.comment != nil: pushCom(g, n) case n.kind # atoms: @@ -1096,7 +1095,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = incl(a.flags, rfInConstExpr) gsection(g, n, a, tkConst, "const") of nkVarSection, nkLetSection: - L = sonsLen(n) + var L = sonsLen(n) if L == 0: return if n.kind == nkVarSection: putWithSpace(g, tkVar, "var") else: putWithSpace(g, tkLet, "let") @@ -1134,13 +1133,27 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkCurlyDotLe, "{.") gcomma(g, n, emptyContext) put(g, tkCurlyDotRi, ".}") - of nkImportStmt: - putWithSpace(g, tkImport, "import") + of nkImportStmt, nkExportStmt: + if n.kind == nkImportStmt: + putWithSpace(g, tkImport, "import") + else: + putWithSpace(g, tkExport, "export") gcoms(g) indentNL(g) gcommaAux(g, n, g.indent) dedent(g) putNL(g) + of nkImportExceptStmt, nkExportExceptStmt: + if n.kind == nkImportExceptStmt: + putWithSpace(g, tkImport, "import") + else: + putWithSpace(g, tkExport, "export") + gsub(g, n.sons[0]) + put(g, tkSpaces, Space) + putWithSpace(g, tkExcept, "except") + gcommaAux(g, n, g.indent, 1) + gcoms(g) + putNL(g) of nkFromStmt: putWithSpace(g, tkFrom, "from") gsub(g, n.sons[0]) diff --git a/compiler/rodread.nim b/compiler/rodread.nim index 2d6399438..5dccee9a7 100755 --- a/compiler/rodread.nim +++ b/compiler/rodread.nim @@ -330,9 +330,6 @@ proc decodeType(r: PRodReader, info: TLineInfo): PType = if r.s[r.pos] == '@': inc(r.pos) result.containerID = decodeVInt(r.s, r.pos) - if r.s[r.pos] == '`': - inc(r.pos) - result.constraint = decodeNode(r, UnknownLineInfo()) decodeLoc(r, result.loc, info) while r.s[r.pos] == '^': inc(r.pos) @@ -423,6 +420,9 @@ proc decodeSym(r: PRodReader, info: TLineInfo): PSym = result.offset = - 1 decodeLoc(r, result.loc, result.info) result.annex = decodeLib(r, info) + if r.s[r.pos] == '#': + inc(r.pos) + result.constraint = decodeNode(r, UnknownLineInfo()) if r.s[r.pos] == '(': if result.kind in routineKinds: result.ast = decodeNodeLazyBody(r, result.info, result) diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim index 691d20553..c0a0cc4eb 100755 --- a/compiler/rodwrite.nim +++ b/compiler/rodwrite.nim @@ -233,9 +233,6 @@ proc encodeType(w: PRodWriter, t: PType, result: var string) = if t.containerID != 0: add(result, '@') encodeVInt(t.containerID, result) - if t.constraint != nil: - add(result, '`') - encodeNode(w, UnknownLineInfo(), t.constraint, result) encodeLoc(w, t.loc, result) for i in countup(0, sonsLen(t) - 1): if t.sons[i] == nil: @@ -295,6 +292,9 @@ proc encodeSym(w: PRodWriter, s: PSym, result: var string) = encodeVInt(s.offset, result) encodeLoc(w, s.loc, result) if s.annex != nil: encodeLib(w, s.annex, s.info, result) + if s.constraint != nil: + add(result, '#') + encodeNode(w, UnknownLineInfo(), s.constraint, result) # lazy loading will soon reload the ast lazily, so the ast needs to be # the last entry of a symbol: if s.ast != nil: diff --git a/compiler/sem.nim b/compiler/sem.nim index 9844d71b0..555f5e7b7 100755 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -38,13 +38,15 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType proc semStmt(c: PContext, n: PNode): PNode proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) proc addParams(c: PContext, n: PNode, kind: TSymKind) -proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) -proc addResultNode(c: PContext, n: PNode) +proc maybeAddResult(c: PContext, s: PSym, n: PNode) proc instGenericContainer(c: PContext, n: PNode, header: PType): PType proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc fixImmediateParams(n: PNode): PNode proc activate(c: PContext, n: PNode) proc semQuoteAst(c: PContext, n: PNode): PNode +proc finishMethod(c: PContext, s: PSym) + +proc IndexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode proc typeMismatch(n: PNode, formal, actual: PType) = if formal.kind != tyError and actual.kind != tyError: diff --git a/compiler/semcall.nim b/compiler/semcall.nim index a5107bf64..67d157261 100755 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -15,8 +15,22 @@ proc sameMethodDispatcher(a, b: PSym): bool = if a.kind == skMethod and b.kind == skMethod: var aa = lastSon(a.ast) var bb = lastSon(b.ast) - if aa.kind == nkSym and bb.kind == nkSym and aa.sym == bb.sym: - result = true + if aa.kind == nkSym and bb.kind == nkSym: + if aa.sym == bb.sym: + result = true + else: + nil + # generics have no dispatcher yet, so we need to compare the method + # names; however, the names are equal anyway because otherwise we + # wouldn't even consider them to be overloaded. But even this does + # not work reliably! See tmultim6 for an example: + # method collide[T](a: TThing, b: TUnit[T]) is instantiated and not + # method collide[T](a: TUnit[T], b: TThing)! This means we need to + # *instantiate* every candidate! However, we don't keep more than 2-3 + # candidated around so we cannot implement that for now. So in order + # to avoid subtle problems, the call remains ambiguous and needs to + # be disambiguated by the programmer; this way the right generic is + # instantiated. proc resolveOverloads(c: PContext, n, orig: PNode, filter: TSymKinds): TCandidate = @@ -84,6 +98,35 @@ proc resolveOverloads(c: PContext, n, orig: PNode, getProcHeader(best.calleeSym), getProcHeader(alt.calleeSym), args]) + +proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = + if a.kind == nkHiddenCallConv and a.sons[0].kind == nkSym and + isGenericRoutine(a.sons[0].sym): + let finalCallee = generateInstance(c, a.sons[0].sym, x.bindings, a.info) + a.sons[0].sym = finalCallee + a.sons[0].typ = finalCallee.typ + #a.typ = finalCallee.typ.sons[0] + +proc instGenericConvertersSons*(c: PContext, n: PNode, x: TCandidate) = + assert n.kind in nkCallKinds + if x.genericConverter: + for i in 1 .. <n.len: + instGenericConvertersArg(c, n.sons[i], x) + +proc IndexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode = + var m: TCandidate + initCandidate(m, f) + result = paramTypesMatch(c, m, f, a, arg, nil) + if m.genericConverter and result != nil: + instGenericConvertersArg(c, result, m) + +proc ConvertTo*(c: PContext, f: PType, n: PNode): PNode = + var m: TCandidate + initCandidate(m, f) + result = paramTypesMatch(c, m, f, n.typ, n, nil) + if m.genericConverter and result != nil: + instGenericConvertersArg(c, result, m) + proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = assert x.state == csMatch var finalCallee = x.calleeSym @@ -101,6 +144,7 @@ proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = if ContainsGenericType(result.typ): result.typ = errorType(c) return result = x.call + instGenericConvertersSons(c, result, x) result.sons[0] = newSymNode(finalCallee, result.sons[0].info) result.typ = finalCallee.typ.sons[0] diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index e6123b1bc..48fe5b4d7 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -318,7 +318,7 @@ proc semIs(c: PContext, n: PNode): PNode = if not containsGenericType(t1): result = evalIsOp(n) proc semOpAux(c: PContext, n: PNode) = - let flags = {efDetermineType} + const flags = {efDetermineType} for i in countup(1, n.sonsLen- 1): var a = n.sons[i] if a.kind == nkExprEqExpr and sonsLen(a) == 2: @@ -589,11 +589,12 @@ proc semStaticExpr(c: PContext, n: PNode): PNode = proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode, flags: TExprFlags): PNode = - if efWantIterator in flags: - result = semOverloadedCall(c, n, nOrig, {skIterator}) - elif efInTypeOf in flags: + if flags*{efInTypeOf, efWantIterator} != {}: + # consider: 'for x in pReturningArray()' --> we don't want the restriction + # to 'skIterator' anymore; skIterator is preferred in sigmatch already for + # typeof support. # for ``type(countup(1,3))``, see ``tests/ttoseq``. - result = semOverloadedCall(c, n, nOrig, + result = semOverloadedCall(c, n, nOrig, {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator}) else: result = semOverloadedCall(c, n, nOrig, @@ -663,6 +664,7 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = result = nil else: result = m.call + instGenericConvertersSons(c, result, m) # we assume that a procedure that calls something indirectly # has side-effects: if tfNoSideEffect notin t.flags: incl(c.p.owner.flags, sfSideEffect) @@ -1658,6 +1660,26 @@ proc fixImmediateParams(n: PNode): PNode = result = n +proc semExport(c: PContext, n: PNode): PNode = + var x = newNodeI(n.kind, n.info) + #let L = if n.kind == nkExportExceptStmt: L = 1 else: n.len + for i in 0.. <n.len: + let a = n.sons[i] + var o: TOverloadIter + var s = initOverloadIter(o, c, a) + if s == nil: + localError(a.info, errGenerated, "invalid expr for 'export': " & + renderTree(a)) + while s != nil: + if s.kind in ExportableSymKinds+{skModule}: + x.add(newSymNode(s, a.info)) + s = nextOverloadIter(o, c, a) + if c.module.ast.isNil: + c.module.ast = newNodeI(nkStmtList, n.info) + assert c.module.ast.kind == nkStmtList + c.module.ast.add x + result = n + proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = n if gCmd == cmdIdeTools: suggestExpr(c, n) @@ -1851,12 +1873,18 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkImportStmt: if not isTopLevel(c): LocalError(n.info, errXOnlyAtModuleScope, "import") result = evalImport(c, n) + of nkImportExceptStmt: + if not isTopLevel(c): LocalError(n.info, errXOnlyAtModuleScope, "import") + result = evalImportExcept(c, n) of nkFromStmt: if not isTopLevel(c): LocalError(n.info, errXOnlyAtModuleScope, "from") result = evalFrom(c, n) of nkIncludeStmt: 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") + result = semExport(c, n) of nkPragmaBlock: result = semPragmaBlock(c, n) of nkStaticStmt: diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 5a818103d..95a394a09 100755 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -89,9 +89,7 @@ proc instantiateBody(c: PContext, n: PNode, result: PSym) = # add it here, so that recursive generic procs are possible: addDecl(c, result) pushProcCon(c, result) - if result.kind in {skProc, skMethod, skConverter, skMacro}: - addResult(c, result.typ.sons[0], n.info, result.kind) - addResultNode(c, n) + maybeAddResult(c, result, n) var b = n.sons[bodyPos] var symMap: TIdTable InitIdTable symMap @@ -163,6 +161,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, result.typ = newTypeS(tyProc, c) rawAddSon(result.typ, nil) result.typ.callConv = fn.typ.callConv + if result.kind == skIterator: result.typ.flags.incl(tfIterator) var oldPrc = GenericCacheGet(fn, entry[]) if oldPrc == nil: fn.procInstCache.safeAdd(entry) @@ -182,6 +181,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, popOwner() c.friendModule = oldFriend dec(c.InstCounter) + if result.kind == skMethod: finishMethod(c, result) proc instGenericContainer(c: PContext, n: PNode, header: PType): PType = var cl: TReplTypeVars diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index bd8a3ba02..b2fd0fb04 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -146,8 +146,11 @@ proc catches(tracked: PEffects, e: PType) = dec L else: inc i - setLen(tracked.exc.sons, L) - + if not isNil(tracked.exc.sons): + setLen(tracked.exc.sons, L) + else: + assert L == 0 + proc catchesAll(tracked: PEffects) = if not isNil(tracked.exc.sons): setLen(tracked.exc.sons, tracked.bottom) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index f8860212b..c38e2f3ad 100755 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -172,11 +172,16 @@ proc fitRemoveHiddenConv(c: PContext, typ: Ptype, n: PNode): PNode = changeType(result, typ) proc findShadowedVar(c: PContext, v: PSym): PSym = - for i in countdown(c.tab.tos - 2, 0): + for i in countdown(c.tab.tos - 2, ModuleTablePos+1): let shadowed = StrTableGet(c.tab.stack[i], v.name) if shadowed != nil and shadowed.kind in skLocalVars: return shadowed +proc identWithin(n: PNode, s: PIdent): bool = + for i in 0 .. n.safeLen-1: + if identWithin(n.sons[i], s): return true + result = n.kind == nkSym and n.sym.name.id == s.id + proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = if isTopLevel(c): result = semIdentWithPragma(c, kind, n, {sfExported}) @@ -239,7 +244,10 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = let shadowed = findShadowedVar(c, v) if shadowed != nil: shadowed.flags.incl(sfShadowed) - Message(a.info, warnShadowIdent, v.name.s) + # a shadowed variable is an error unless it appears on the right + # side of the '=': + if warnShadowIdent in gNotes and not identWithin(def, v.name): + Message(a.info, warnShadowIdent, v.name.s) if def != nil and def.kind != nkEmpty: # this is only needed for the evaluation pass: v.ast = def @@ -247,7 +255,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = if a.kind != nkVarTuple: v.typ = typ b = newNodeI(nkIdentDefs, a.info) - if gCmd == cmdDoc: + if importantComments(): # keep documentation information: b.comment = a.comment addSon(b, newSymNode(v)) @@ -287,7 +295,7 @@ proc semConst(c: PContext, n: PNode): PNode = v.ast = def # no need to copy if sfGenSym notin v.flags: addInterfaceDecl(c, v) var b = newNodeI(nkConstDef, a.info) - if gCmd == cmdDoc: b.comment = a.comment + if importantComments(): b.comment = a.comment addSon(b, newSymNode(v)) addSon(b, ast.emptyNode) # no type description addSon(b, copyTree(def)) @@ -368,6 +376,15 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = b.add(ast.emptyNode) stmts.add(b) +proc addForVarDecl(c: PContext, v: PSym) = + if warnShadowIdent in gNotes: + let shadowed = findShadowedVar(c, v) + if shadowed != nil: + # XXX should we do this here? + #shadowed.flags.incl(sfShadowed) + Message(v.info, warnShadowIdent, v.name.s) + addDecl(c, v) + proc semForVars(c: PContext, n: PNode): PNode = result = n var length = sonsLen(n) @@ -383,7 +400,7 @@ proc semForVars(c: PContext, n: PNode): PNode = # for an example: v.typ = n.sons[length-2].typ n.sons[0] = newSymNode(v) - if sfGenSym notin v.flags: addDecl(c, v) + if sfGenSym notin v.flags: addForVarDecl(c, v) else: LocalError(n.info, errWrongNumberOfVariables) elif length-2 != sonsLen(iter): @@ -394,7 +411,7 @@ proc semForVars(c: PContext, n: PNode): PNode = if getCurrOwner().kind == skModule: incl(v.flags, sfGlobal) v.typ = iter.sons[i] n.sons[i] = newSymNode(v) - if sfGenSym notin v.flags: addDecl(c, v) + if sfGenSym notin v.flags: addForVarDecl(c, v) Inc(c.p.nestedLoopCounter) n.sons[length-1] = SemStmt(c, n.sons[length-1]) Dec(c.p.nestedLoopCounter) @@ -669,17 +686,18 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = if n.sons[pragmasPos].kind != nkEmpty: pragma(c, s, n.sons[pragmasPos], lambdaPragmas) s.options = gOptions - if n.sons[bodyPos].kind != nkEmpty: - if sfImportc in s.flags: + if n.sons[bodyPos].kind != nkEmpty: + if sfImportc in s.flags: LocalError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s) - if efDetermineType notin 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) - sideEffectsCheck(c, s) + #if efDetermineType notin flags: + # XXX not good enough; see tnamedparamanonproc.nim + 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) + sideEffectsCheck(c, s) else: LocalError(n.info, errImplOfXexpected, s.name.s) closeScope(c.tab) # close scope for parameters @@ -689,13 +707,16 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = proc activate(c: PContext, n: PNode) = # XXX: This proc is part of my plan for getting rid of # forward declarations. stay tuned. - case n.kind - of nkLambdaKinds: - discard semLambda(c, n, {}) - of nkCallKinds: - for i in 1 .. <n.len: activate(c, n[i]) - else: - nil + when false: + # well for now it breaks code ... I added the test case in main.nim of the + # compiler itself to break bootstrapping :P + case n.kind + of nkLambdaKinds: + discard semLambda(c, n, {}) + of nkCallKinds: + for i in 1 .. <n.len: activate(c, n[i]) + else: + nil proc instantiateDestructor*(c: PContext, typ: PType): bool @@ -713,6 +734,12 @@ proc doDestructorStuff(c: PContext, s: PSym, n: PNode) = useSym(t.sons[i].destructor), n.sons[paramsPos][1][0]])) +proc maybeAddResult(c: PContext, s: PSym, n: PNode) = + if s.typ.sons[0] != nil and + (s.kind != skIterator or s.typ.callConv == ccClosure): + addResult(c, s.typ.sons[0], n.info, s.kind) + addResultNode(c, n) + proc semProcAux(c: PContext, n: PNode, kind: TSymKind, validPragmas: TSpecialWords): PNode = result = semProcAnnotation(c, n) @@ -780,6 +807,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, n.sons[pragmasPos] = proto.ast.sons[pragmasPos] if n.sons[namePos].kind != nkSym: InternalError(n.info, "semProcAux") n.sons[namePos].sym = proto + if importantComments() and not isNil(proto.ast.comment): + n.comment = proto.ast.comment proto.ast = n # needed for code generation popOwner() pushOwner(s) @@ -792,17 +821,13 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, if n.sons[genericParamsPos].kind == nkEmpty: ParamsTypeCheck(c, s.typ) pushProcCon(c, s) - if s.typ.sons[0] != nil and - (kind != skIterator or s.typ.callConv == ccClosure): - addResult(c, s.typ.sons[0], n.info, kind) - addResultNode(c, n) + maybeAddResult(c, s, n) if sfImportc notin s.flags: # no semantic checking for importc: let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) # unfortunately we cannot skip this step when in 'system.compiles' # context as it may even be evaluated in 'system.compiles': n.sons[bodyPos] = transformBody(c.module, semBody, s) - #if s.typ.sons[0] != nil and kind != skIterator: addResultNode(c, n) popProcCon(c) else: if s.typ.sons[0] != nil and kind != skIterator: @@ -846,31 +871,30 @@ proc semIterator(c: PContext, n: PNode): PNode = proc semProc(c: PContext, n: PNode): PNode = result = semProcAux(c, n, skProc, procPragmas) +proc hasObjParam(s: PSym): bool = + var t = s.typ + for col in countup(1, sonsLen(t)-1): + if skipTypes(t.sons[col], skipPtrs).kind == tyObject: + return true + +proc finishMethod(c: PContext, s: PSym) = + if hasObjParam(s): + methodDef(s, false) + proc semMethod(c: PContext, n: PNode): PNode = if not isTopLevel(c): LocalError(n.info, errXOnlyAtModuleScope, "method") result = semProcAux(c, n, skMethod, methodPragmas) var s = result.sons[namePos].sym - var t = s.typ - var hasObjParam = false - - for col in countup(1, sonsLen(t)-1): - if skipTypes(t.sons[col], skipPtrs).kind == tyObject: - hasObjParam = true - break - - # XXX this not really correct way to do it: Perhaps it should be done after - # generic instantiation. Well it's good enough for now: - if hasObjParam: - methodDef(s, false) - else: - LocalError(n.info, errXNeedsParamObjectType, "method") + if not isGenericRoutine(s): + if hasObjParam(s): + methodDef(s, false) + else: + LocalError(n.info, errXNeedsParamObjectType, "method") proc semConverterDef(c: PContext, n: PNode): PNode = if not isTopLevel(c): LocalError(n.info, errXOnlyAtModuleScope, "converter") checkSonsLen(n, bodyPos + 1) - if n.sons[genericParamsPos].kind != nkEmpty: - LocalError(n.info, errNoGenericParamsAllowedForX, "converter") result = semProcAux(c, n, skConverter, converterPragmas) var s = result.sons[namePos].sym var t = s.typ @@ -1075,7 +1099,8 @@ proc insertDestructors(c: PContext, varSection: PNode): varTyp = varId.sym.typ info = varId.info - if varTyp != nil and instantiateDestructor(c, varTyp): + if varTyp != nil and instantiateDestructor(c, varTyp) and + sfGlobal notin varId.sym.flags: var tryStmt = newNodeI(nkTryStmt, info) if j < totalVars - 1: diff --git a/compiler/semthreads.nim b/compiler/semthreads.nim index 75621be79..9fc6a54d9 100755 --- a/compiler/semthreads.nim +++ b/compiler/semthreads.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index f644683f5..3ad275601 100755 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -635,6 +635,13 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, genericParams.addSon(newSymNode(s)) result = typeClass +proc semParamType(c: PContext, n: PNode, constraint: var PNode): PType = + if n.kind == nkCurlyExpr: + result = semTypeNode(c, n.sons[0], nil) + constraint = semNodeKindConstraints(n) + else: + result = semTypeNode(c, n, nil) + proc semProcTypeNode(c: PContext, n, genericParams: PNode, prev: PType, kind: TSymKind): PType = var @@ -660,13 +667,14 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, checkMinSonsLen(a, 3) var typ: PType = nil - def: PNode = nil + def: PNode = nil + constraint: PNode = nil length = sonsLen(a) hasType = a.sons[length-2].kind != nkEmpty hasDefault = a.sons[length-1].kind != nkEmpty if hasType: - typ = semTypeNode(c, a.sons[length-2], nil) + typ = semParamType(c, a.sons[length-2], constraint) if hasDefault: def = semExprWithType(c, a.sons[length-1]) @@ -689,6 +697,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, arg.name.s, arg.info).skipIntLit arg.typ = finalType arg.position = counter + arg.constraint = constraint inc(counter) if def != nil and def.kind != nkEmpty: arg.ast = copyTree(def) if ContainsOrIncl(check, arg.name.id): @@ -787,6 +796,12 @@ proc semTypeExpr(c: PContext, n: PNode): PType = else: LocalError(n.info, errTypeExpected, n.renderTree) +proc freshType(res, prev: PType): PType {.inline.} = + if prev.isNil: + result = copyType(res, res.owner, keepId=false) + else: + result = res + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil if gCmd == cmdIdeTools: suggestExpr(c, n) @@ -825,6 +840,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = checkSonsLen(n, 3) result = semTypeNode(c, n.sons[1], prev) if result.kind in NilableTypes and n.sons[2].kind == nkNilLit: + result = freshType(result, prev) result.flags.incl(tfNotNil) else: LocalError(n.info, errGenerated, "invalid type") @@ -832,11 +848,6 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = semTypeExpr(c, n) else: result = semTypeExpr(c, n) - of nkCurlyExpr: - result = semTypeNode(c, n.sons[0], nil) - if result != nil: - result = copyType(result, getCurrOwner(), true) - result.constraint = semNodeKindConstraints(n) of nkWhenStmt: var whenResult = semWhen(c, n, false) if whenResult.kind == nkStmtList: whenResult.kind = nkStmtListType @@ -919,6 +930,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkSharedTy: checkSonsLen(n, 1) result = semTypeNode(c, n.sons[0], prev) + result = freshType(result, prev) result.flags.incl(tfShared) else: LocalError(n.info, errTypeExpected) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 799622355..953dcfa74 100755 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -12,18 +12,19 @@ import intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst, - magicsys, condsyms, idents, lexer, options + magicsys, condsyms, idents, lexer, options, parampatterns, strutils, + docgen type TCandidateState* = enum csEmpty, csMatch, csNoMatch TCandidate* {.final.} = object - exactMatches*: int + exactMatches*: int # also misused to prefer iters over procs + genericMatches: int # also misused to prefer constraints subtypeMatches: int intConvMatches: int # conversions to int are not as expensive convMatches: int - genericMatches: int state*: TCandidateState callee*: PType # may not be nil! calleeSym*: PSym # may be nil @@ -33,6 +34,8 @@ type baseTypeMatch: bool # needed for conversions from T to openarray[T] # for example proxyMatch*: bool # to prevent instantiations + genericConverter*: bool # true if a generic converter needs to + # be instantiated inheritancePenalty: int # to prefer closest father object type TTypeRelation* = enum # order is important! @@ -57,6 +60,7 @@ proc initCandidateAux(c: var TCandidate, callee: PType) {.inline.} = c.callee = callee c.call = nil c.baseTypeMatch = false + c.genericConverter = false c.inheritancePenalty = 0 proc initCandidate*(c: var TCandidate, callee: PType) = @@ -571,17 +575,27 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, for i in countup(0, len(c.converters) - 1): var src = c.converters[i].typ.sons[1] var dest = c.converters[i].typ.sons[0] - if (typeRel(m, f, dest) == isEqual) and - (typeRel(m, src, a) == isEqual): + # for generic type converters we need to check 'src <- a' before + # 'f <- dest' in order to not break the unification: + # see tests/tgenericconverter: + let srca = typeRel(m, src, a) + if srca notin {isEqual, isGeneric}: continue + + let destIsGeneric = containsGenericType(dest) + if destIsGeneric: + dest = generateTypeInstance(c, m.bindings, arg, dest) + let fdest = typeRel(m, f, dest) + if fdest in {isEqual, isGeneric}: markUsed(arg, c.converters[i]) var s = newSymNode(c.converters[i]) s.typ = c.converters[i].typ s.info = arg.info - result = newNodeIT(nkHiddenCallConv, arg.info, s.typ.sons[0]) + result = newNodeIT(nkHiddenCallConv, arg.info, dest) addSon(result, s) addSon(result, copyTree(arg)) inc(m.convMatches) - return + m.genericConverter = srca == isGeneric or destIsGeneric + return result proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType, arg: PNode): PNode = @@ -692,8 +706,8 @@ proc ParamTypesMatchAux(c: PContext, m: var TCandidate, f, a: PType, else: result = userConvMatch(c, m, base(f), a, arg) -proc ParamTypesMatch(c: PContext, m: var TCandidate, f, a: PType, - arg, argOrig: PNode): PNode = +proc ParamTypesMatch*(c: PContext, m: var TCandidate, f, a: PType, + arg, argOrig: PNode): PNode = if arg == nil or arg.kind notin nkSymChoices: result = ParamTypesMatchAux(c, m, f, a, arg, argOrig) else: @@ -739,27 +753,21 @@ proc ParamTypesMatch(c: PContext, m: var TCandidate, f, a: PType, result = ParamTypesMatchAux(c, m, f, arg.sons[best].typ, arg.sons[best], argOrig) -proc IndexTypesMatch*(c: PContext, f, a: PType, arg: PNode): PNode = - var m: TCandidate - initCandidate(m, f) - result = paramTypesMatch(c, m, f, a, arg, nil) - -proc ConvertTo*(c: PContext, f: PType, n: PNode): PNode = - var m: TCandidate - initCandidate(m, f) - result = paramTypesMatch(c, m, f, n.typ, n, nil) - -proc argtypeMatches*(c: PContext, f, a: PType): bool = - var m: TCandidate - initCandidate(m, f) - result = paramTypesMatch(c, m, f, a, ast.emptyNode, nil) != nil - proc setSon(father: PNode, at: int, son: PNode) = if sonsLen(father) <= at: setlen(father.sons, at + 1) father.sons[at] = son -proc matchesAux*(c: PContext, n, nOrig: PNode, - m: var TCandidate, marker: var TIntSet) = +proc matchesAux(c: PContext, n, nOrig: PNode, + m: var TCandidate, marker: var TIntSet) = + template checkConstraint(n: expr) {.immediate, dirty.} = + if not formal.constraint.isNil: + if matchNodeKinds(formal.constraint, n): + # better match over other routines with no such restriction: + inc(m.genericMatches, 100) + else: + m.state = csNoMatch + return + var f = 1 # iterates over formal parameters var a = 1 # iterates over the actual given arguments m.state = csMatch # until proven otherwise @@ -792,7 +800,8 @@ proc matchesAux*(c: PContext, n, nOrig: PNode, n.sons[a].sons[1], nOrig.sons[a].sons[1]) if arg == nil: m.state = csNoMatch - return + return + checkConstraint(n.sons[a].sons[1]) if m.baseTypeMatch: assert(container == nil) container = newNodeI(nkBracket, n.sons[a].info) @@ -837,10 +846,10 @@ proc matchesAux*(c: PContext, n, nOrig: PNode, m.baseTypeMatch = false var arg = ParamTypesMatch(c, m, formal.typ, n.sons[a].typ, n.sons[a], nOrig.sons[a]) - if arg == nil: + if arg == nil: m.state = csNoMatch - return - if m.baseTypeMatch: + return + if m.baseTypeMatch: assert(container == nil) container = newNodeI(nkBracket, n.sons[a].info) addSon(container, arg) @@ -849,6 +858,7 @@ proc matchesAux*(c: PContext, n, nOrig: PNode, if f != formalLen - 1: container = nil else: setSon(m.call, formal.position + 1, arg) + checkConstraint(n.sons[a]) inc(a) inc(f) @@ -880,4 +890,13 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = setSon(m.call, formal.position + 1, copyTree(formal.ast)) inc(f) +proc argtypeMatches*(c: PContext, f, a: PType): bool = + var m: TCandidate + initCandidate(m, f) + let res = paramTypesMatch(c, m, f, a, ast.emptyNode, nil) + #instantiateGenericConverters(c, res, m) + # XXX this is used by patterns.nim too; I think it's better to not + # instantiate generic converters for that + result = res != nil + include suggest diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 404f1c1bb..130666f4d 100755 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -39,6 +39,8 @@ proc SymToStr(s: PSym, isLocal: bool, section: string, li: TLineInfo): string = result.add($ToLinenumber(li)) result.add(sep) result.add($ToColumn(li)) + result.add(sep) + result.add(s.extractDocComment.escape) proc SymToStr(s: PSym, isLocal: bool, section: string): string = result = SymToStr(s, isLocal, section, s.info) diff --git a/compiler/transf.nim b/compiler/transf.nim index 5ba997524..679f7d12f 100755 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -150,7 +150,7 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode = newVar.owner = getCurrOwner(c) IdNodeTablePut(c.transCon.mapping, it.sons[0].sym, newSymNode(newVar)) var defs = newTransNode(nkIdentDefs, it.info, 3) - if gCmd == cmdDoc: + if importantComments(): # keep documentation information: pnode(defs).comment = it.comment defs[0] = newSymNode(newVar).PTransNode @@ -665,7 +665,7 @@ proc transform(c: PTransf, n: PNode): PTransNode = of nkIdentDefs, nkConstDef: result = transformSons(c, n) # XXX comment handling really sucks: - if gCmd == cmdDoc: + if importantComments(): pnode(result).comment = n.comment else: result = transformSons(c, n) diff --git a/compiler/types.nim b/compiler/types.nim index 825b1027a..998ba43d2 100755 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1,7 +1,7 @@ # # # The Nimrod Compiler -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -632,7 +632,8 @@ proc SameTypeOrNil*(a, b: PType, flags: TTypeCmpFlags = {}): bool = result = SameTypeAux(a, b, c) proc equalParam(a, b: PSym): TParamsEquality = - if SameTypeOrNil(a.typ, b.typ, {TypeDescExactMatch}): + if SameTypeOrNil(a.typ, b.typ, {TypeDescExactMatch}) and + ExprStructuralEquivalent(a.constraint, b.constraint): if a.ast == b.ast: result = paramsEqual elif a.ast != nil and b.ast != nil: @@ -875,20 +876,24 @@ proc inheritanceDiff*(a, b: PType): int = # | returns: -x iff `a` is the x'th direct superclass of `b` # | returns: +x iff `a` is the x'th direct subclass of `b` # | returns: `maxint` iff `a` and `b` are not compatible at all + assert a.kind == tyObject + assert b.kind == tyObject var x = a result = 0 while x != nil: + x = skipTypes(x, skipPtrs) if sameObjectTypes(x, b): return x = x.sons[0] dec(result) var y = b result = 0 while y != nil: + y = skipTypes(y, skipPtrs) if sameObjectTypes(y, a): return y = y.sons[0] inc(result) result = high(int) - + proc typeAllowedAux(marker: var TIntSet, typ: PType, kind: TSymKind): bool proc typeAllowedNode(marker: var TIntSet, n: PNode, kind: TSymKind): bool = result = true diff --git a/config/nimrod.cfg b/config/nimrod.cfg index 564a0d4a6..9db13e836 100755 --- a/config/nimrod.cfg +++ b/config/nimrod.cfg @@ -13,11 +13,6 @@ cc = gcc arm.linux.gcc.exe = "arm-linux-gcc" arm.linux.gcc.linkerexe = "arm-linux-gcc" -@if nim: - # use the old fixed library for bootstrapping with Nim: - lib = "nimlib" -@end - path="$lib/core" path="$lib/pure" path="$lib/pure/collections" @@ -36,7 +31,10 @@ path="$lib/windows" path="$lib/posix" path="$lib/ecmas" path="$lib/pure/unidecode" -#recursivePath:"$home/.babel/lib" + +@if nimbabel: + babelpath="$home/.babel/libs/" +@end @if release or quick: obj_checks:off diff --git a/doc/advopt.txt b/doc/advopt.txt index 09ca9bee1..52c2c8636 100755 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -22,11 +22,11 @@ Advanced options: -m, --mainmodule:FILE set the project main module -o, --out:FILE set the output filename --stdout output to stdout + --listFullPaths list full paths in messages -w, --warnings:on|off turn all warnings on|off --warning[X]:on|off turn specific warning X on|off --hints:on|off turn all hints on|off --hint[X]:on|off turn specific hint X on|off - --recursivePath:PATH add a path and all of its subdirectories --lib:PATH set the system library path --import:PATH add an automatically imported module --include:PATH add an automatically included module @@ -67,6 +67,8 @@ Advanced options: --gc:refc|boehm|none use Nimrod's native GC|Boehm GC|no GC --index:on|off turn index file generation on|off --putenv:key=value set an environment variable + --babelPath:PATH add a path for Babel support + --excludePath:PATH exclude a path from the list of search paths --listCmd list the commands used to execute external programs --parallelBuild=0|1|... perform a parallel build value = number of processors (0 for auto-detect) diff --git a/doc/lib.txt b/doc/lib.txt index f4d3dde30..a429a8289 100755 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -38,11 +38,11 @@ Core * `threads <threads.html>`_ Nimrod thread support. **Note**: This is part of the system module. Do not - import it explicitely. + import it explicitly. * `channels <channels.html>`_ Nimrod message passing support for threads. **Note**: This is part of the - system module. Do not import it explicitely. + system module. Do not import it explicitly. * `locks <locks.html>`_ Locks and condition variables for Nimrod. diff --git a/doc/manual.txt b/doc/manual.txt index f6ff576bf..1ac241b06 100755 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -479,11 +479,8 @@ The grammar's start symbol is ``module``. -Semantics -========= - Types ------ +===== All expressions have a `type`:idx: which is known at compile time. Nimrod is statically typed. One can declare new types, which is in essence defining @@ -502,7 +499,7 @@ These are the major type classes: Ordinal types -~~~~~~~~~~~~~ +------------- `Ordinal types`:idx: have the following characteristics: - Ordinal types are countable and ordered. This property allows @@ -519,7 +516,7 @@ the types ``uint`` and ``uint64`` are no ordinal types. Pre-defined integer types -~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------- These integer types are pre-defined: ``int`` @@ -593,7 +590,7 @@ widening type conversion are *implicit*: myInt16 + myInt # of type ``int`` myInt16 + 2i32 # of type ``int32`` -However, ``int`` literals are implicitely convertible to a smaller integer type +However, ``int`` literals are implicitly convertible to a smaller integer type if the literal's value fits this smaller type and such a conversion is less expensive than other implicit conversions, so ``myInt16 + 34`` produces an ``int16`` result. @@ -602,7 +599,7 @@ For further details, see `Convertible relation`_. Subrange types -~~~~~~~~~~~~~~ +-------------- A `subrange`:idx: type is a range of values from an ordinal type (the base type). To define a subrange type, one must specify it's limiting values: the lowest and highest value of the type: @@ -642,7 +639,7 @@ This means that the following code is accepted: Pre-defined floating point types -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------- The following floating point types are pre-defined: @@ -703,7 +700,7 @@ the ``+``, ``-``, ``*``, ``/`` operators for floating point types. Boolean type -~~~~~~~~~~~~ +------------ The `boolean`:idx: type is named `bool`:idx: in Nimrod and can be one of the two pre-defined values ``true`` and ``false``. Conditions in while, if, elif, when statements need to be of type bool. @@ -727,7 +724,7 @@ The size of the bool type is one byte. Character type -~~~~~~~~~~~~~~ +-------------- The `character type`:idx: is named ``char`` in Nimrod. Its size is one byte. Thus it cannot represent an UTF-8 character, but a part of it. The reason for this is efficiency: for the overwhelming majority of use-cases, @@ -741,7 +738,7 @@ character. ``TRune`` is declared in the ``unicode`` module. Enumeration types -~~~~~~~~~~~~~~~~~ +----------------- `Enumeration`:idx: types define a new type whose values consist of the ones specified. The values are ordered. Example: @@ -811,7 +808,7 @@ via ``TMyEnum.value``: String type -~~~~~~~~~~~ +----------- All string literals are of the type `string`:idx:. A string in Nimrod is very similar to a sequence of characters. However, strings in Nimrod are both zero-terminated and have a length field. One can retrieve the length with the @@ -838,7 +835,7 @@ module can be used for iteration over all Unicode characters. CString type -~~~~~~~~~~~~ +------------ The `cstring`:idx: type represents a pointer to a zero-terminated char array compatible to the type ``char*`` in Ansi C. Its primary purpose lies in easy interfacing with C. The index operation ``s[i]`` means the i-th *char* of @@ -864,13 +861,13 @@ not work. Structured types -~~~~~~~~~~~~~~~~ +---------------- A variable of a `structured type`:idx: can hold multiple values at the same time. Structured types can be nested to unlimited levels. Arrays, sequences, tuples, objects and sets belong to the structured types. Array and sequence types -~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ `Arrays`:idx: are a homogeneous type, meaning that each element in the array has the same type. Arrays always have a fixed length which is specified at compile time (except for open arrays). They can be indexed by any ordinal type. @@ -918,7 +915,7 @@ The current implementation does not support nested open arrays. Varargs -~~~~~~~ +------- A `varargs`:idx: parameter is an openarray parameter that additionally allows to pass a variable number of arguments to a procedure. The compiler @@ -954,7 +951,7 @@ parameter ``a``. (Note that ``$`` applied to strings is a nop.) Tuples and object types -~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- A variable of a `tuple`:idx: or `object`:idx: type is a heterogeneous storage container. A tuple or object defines various named *fields* of a type. A tuple also @@ -1022,7 +1019,7 @@ introduce new object roots apart from ``system.TObject``. Object variants -~~~~~~~~~~~~~~~ +--------------- Often an object hierarchy is overkill in certain situations where simple `variant`:idx: types are needed. @@ -1069,7 +1066,7 @@ the ``case`` statement: The branches in a ``case`` section may be indented too. Set type -~~~~~~~~ +-------- The `set type`:idx: models the mathematical notion of a set. The set's basetype can only be an ordinal type. The reason is that sets are implemented as high performance bit vectors. @@ -1104,7 +1101,7 @@ operation meaning Reference and pointer types -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------- References (similar to `pointers`:idx: in other programming languages) are a way to introduce many-to-one relationships. This means different references can point to and modify the same location in memory (also called `aliasing`:idx:). @@ -1193,14 +1190,14 @@ mysterious crashes. **Note**: The example only works because the memory is initialized to zero (``alloc0`` instead of ``alloc`` does this): ``d.s`` is thus initialized to -``nil`` which the string assignment can handle. You need to know low level +``nil`` which the string assignment can handle. One needs to know low level details like this when mixing garbage collected data with unmanaged memory. .. XXX finalizers for traced objects Not nil annotation -~~~~~~~~~~~~~~~~~~ +------------------ All types for that ``nil`` is a valid value can be annotated to exclude ``nil`` as a valid value with the `not nil`:idx: annotation: @@ -1225,7 +1222,7 @@ for now the compiler can only catch the most trivial type violations. Procedural type -~~~~~~~~~~~~~~~ +--------------- A `procedural type`:idx: is internally a pointer to a procedure. ``nil`` is an allowed value for variables of a procedural type. Nimrod uses procedural types to achieve `functional`:idx: programming techniques. @@ -1335,7 +1332,7 @@ accesses its environment. If it does so, it has the calling convention Distinct type -~~~~~~~~~~~~~ +------------- A distinct type is new type derived from a `base type`:idx: that is incompatible with its base type. In particular, it is an essential property @@ -1431,9 +1428,9 @@ currency. This can be solved with templates_. Void type -~~~~~~~~~ +--------- -The `void`:idx: type denotes the absence of any type. Parameters of +The `void`:idx: type denotes the absense of any type. Parameters of type ``void`` are treated as non-existent, ``void`` as a return type means that the procedure does not return a value: @@ -1471,14 +1468,14 @@ cannot have the type ``void``. Type relations --------------- +============== The following section defines several relations on types that are needed to describe the type checking done by the compiler. Type equality -~~~~~~~~~~~~~ +------------- Nimrod uses structural type equivalence for most types. Only for objects, enumerations and distinct types name equivalence is used. The following algorithm (in pseudo-code) determines type equality: @@ -1523,7 +1520,7 @@ auxiliary set ``s`` to detect this case. Type equality modulo type distinction -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------- The following algorithm (in pseudo-code) determines whether two types are equal with no respect to ``distinct`` types. For brevity the cycle check @@ -1565,7 +1562,7 @@ with an auxiliary set ``s`` is omitted: Subtype relation -~~~~~~~~~~~~~~~~ +---------------- If object ``a`` inherits from ``b``, ``a`` is a subtype of ``b``. This subtype relation is extended to the types ``var``, ``ref``, ``ptr``: @@ -1584,7 +1581,7 @@ relation is extended to the types ``var``, ``ref``, ``ptr``: Convertible relation -~~~~~~~~~~~~~~~~~~~~ +-------------------- A type ``a`` is **implicitly** convertible to type ``b`` iff the following algorithm returns true: @@ -1651,20 +1648,21 @@ The type conversion ``T(a)`` is an L-value if ``a`` is an L-value and Assignment compatibility -~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ An expression ``b`` can be assigned to an expression ``a`` iff ``a`` is an `l-value` and ``isImplicitlyConvertible(b.typ, a.typ)`` holds. Overloading resolution -~~~~~~~~~~~~~~~~~~~~~~ +---------------------- To be written. Statements and expressions --------------------------- +========================== + Nimrod uses the common statement/expression paradigm: `Statements`:idx: do not produce a value in contrast to expressions. Call expressions are statements. If the called procedure returns a value, it is not a valid statement @@ -1698,7 +1696,7 @@ statements always have to be intended:: Discard statement -~~~~~~~~~~~~~~~~~ +----------------- Syntax:: @@ -1729,7 +1727,7 @@ been declared with the `discardable`:idx: pragma: Var statement -~~~~~~~~~~~~~ +------------- Syntax:: @@ -1790,7 +1788,7 @@ If a proc is annotated with the ``noinit`` pragma this refers to its implicit let statement -~~~~~~~~~~~~~ +------------- A `Let`:idx: statement declares new local and global `single assignment`:idx: variables and binds a value to them. The syntax is the of the ``var`` @@ -1802,7 +1800,7 @@ For let variables the same pragmas are available as for ordinary variables. Const section -~~~~~~~~~~~~~ +------------- Syntax:: @@ -1840,14 +1838,14 @@ they contain such a type. Static statement/expression -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------- Syntax:: staticExpr ::= 'static' '(' optInd expr optPar ')' staticStmt ::= 'static' ':' stmt A `static`:idx: statement/expression can be used to enforce compile -time evaluation explicitely. Enforced compile time evaluation can even evaluate +time evaluation explicitly. Enforced compile time evaluation can even evaluate code that has side effects: .. code-block:: @@ -1865,7 +1863,7 @@ support the FFI at compile time. If statement -~~~~~~~~~~~~ +------------ Syntax:: @@ -1895,7 +1893,7 @@ part, execution continues with the statement after the ``if`` statement. Case statement -~~~~~~~~~~~~~~ +-------------- Syntax:: @@ -1961,7 +1959,7 @@ a list of its elements: When statement -~~~~~~~~~~~~~~ +-------------- Syntax:: @@ -1995,7 +1993,7 @@ within ``object`` definitions. Return statement -~~~~~~~~~~~~~~~~ +---------------- Syntax:: @@ -2026,7 +2024,7 @@ variables, ``result`` is initialized to (binary) zero: Yield statement -~~~~~~~~~~~~~~~ +--------------- Syntax:: @@ -2046,7 +2044,7 @@ for further information. Block statement -~~~~~~~~~~~~~~~ +--------------- Syntax:: @@ -2071,7 +2069,7 @@ block to specify which block is to leave. Break statement -~~~~~~~~~~~~~~~ +--------------- Syntax:: @@ -2088,7 +2086,7 @@ absent, the innermost block is left. While statement -~~~~~~~~~~~~~~~ +--------------- Syntax:: @@ -2110,7 +2108,7 @@ so that they can be left with a ``break`` statement. Continue statement -~~~~~~~~~~~~~~~~~~ +------------------ Syntax:: @@ -2137,7 +2135,7 @@ Is equivalent to: Assembler statement -~~~~~~~~~~~~~~~~~~~ +------------------- Syntax:: asmStmt ::= 'asm' [pragma] (STR_LIT | RSTR_LIT | TRIPLESTR_LIT) @@ -2159,7 +2157,7 @@ specified in the statement's pragmas. The default special character is ``'`'``: """ If expression -~~~~~~~~~~~~~ +------------- An `if expression` is almost like an if statement, but it is an expression. Example: @@ -2172,12 +2170,12 @@ required. ``Elif`` parts are also allowed (but unlikely to be good style). When expression -~~~~~~~~~~~~~~~ +--------------- Just like an `if expression`, but corresponding to the when statement. Case expression -~~~~~~~~~~~~~~~ +--------------- The `case expression` is again very similar to the case statement: @@ -2195,7 +2193,7 @@ effects. When multiple statements are given for a branch, Nimrod will use the last expression as the result value, much like in an `expr` template. Table constructor -~~~~~~~~~~~~~~~~~ +----------------- A `table constructor`:idx: is syntactic sugar for an array constructor: @@ -2223,7 +2221,7 @@ has lots of advantages: Type conversions -~~~~~~~~~~~~~~~~ +---------------- Syntactically a `type conversion` is like a procedure call, but a type name replaces the procedure name. A type conversion is always safe in the sense that a failure to convert a type to another @@ -2231,10 +2229,9 @@ results in an exception (if it cannot be determined statically). Type casts -~~~~~~~~~~ +---------- Example: - .. code-block:: nimrod cast[int](x) @@ -2244,16 +2241,33 @@ only needed for low-level programming and are inherently unsafe. The addr operator -~~~~~~~~~~~~~~~~~ -The `addr` operator returns the address of an l-value. If the -type of the location is ``T``, the `addr` operator result is -of the type ``ptr T``. Taking the address of an object that resides -on the stack is **unsafe**, as the pointer may live longer than the -object on the stack and can thus reference a non-existing object. +----------------- +The `addr`:idx: operator returns the address of an l-value. If the type of the +location is ``T``, the `addr` operator result is of the type ``ptr T``. An +address is always an untraced reference. Taking the address of an object that +resides on the stack is **unsafe**, as the pointer may live longer than the +object on the stack and can thus reference a non-existing object. You can get +the address of variables, but you can't use it on variables declared through +``let`` statements: + +.. code-block:: nimrod + + let t1 = "Hello" + var + t2 = t1 + t3 : pointer = addr(t2) + echo repr(addr(t2)) + # --> ref 0x7fff6b71b670 --> 0x10bb81050"Hello" + echo cast[ptr string](t3)[] + # --> Hello + # The following line doesn't compile: + echo repr(addr(t1)) + # Error: expression has no address Procedures -~~~~~~~~~~ +========== + What most programming languages call `methods`:idx: or `functions`:idx: are called `procedures`:idx: in Nimrod (which is the correct terminology). A procedure declaration defines an identifier and associates it with a block @@ -2332,7 +2346,7 @@ notation. (Thus an operator can have more than two parameters): assert `*+`(3, 4, 6) == `*`(a, `+`(b, c)) Closures -~~~~~~~~ +-------- Procedures can appear at the top level in a module as well as inside other scopes, in which case they are called nested procs. A nested proc can access @@ -2344,7 +2358,7 @@ visible in both places). The closure environment may be allocated on the heap or on the stack if the compiler determines that this would be safe. Anonymous Procs -~~~~~~~~~~~~~~~ +--------------- Procs can also be treated as expressions, in which case it's allowed to omit the proc's name. @@ -2360,7 +2374,7 @@ Procs as expressions can appear both as nested procs and inside top level executable code. Do notation -~~~~~~~~~~~ +----------- As a special more convenient notation, proc expressions involved in procedure calls can use the ``do`` keyword: @@ -2399,7 +2413,7 @@ The compatibility works in the other direction too as the ``do`` syntax can be used with macros and templates expecting ``stmt`` blocks. Nonoverloadable builtins -~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------ The following builtin procs cannot be overloaded for reasons of implementation simplicity (they require specialized semantic checking):: @@ -2408,12 +2422,12 @@ simplicity (they require specialized semantic checking):: is, of, echo, shallowCopy, getAst Thus they act more like keywords than like ordinary identifiers; unlike a -keyword however, a redefinition may `shadow`:id: the definition in +keyword however, a redefinition may `shadow`:idx: the definition in the ``system`` module. Var parameters -~~~~~~~~~~~~~~ +-------------- The type of a parameter may be prefixed with the ``var`` keyword: .. code-block:: nimrod @@ -2465,7 +2479,7 @@ One can use `tuple unpacking`:idx: to access the tuple's fields: Var return type -~~~~~~~~~~~~~~~ +--------------- A proc, converter or iterator may return a ``var`` type which means that the returned value is an l-value and can be modified by the caller: @@ -2479,7 +2493,7 @@ returned value is an l-value and can be modified by the caller: WriteAccessToG() = 6 assert g == 6 -It is a compile time error if the implicitely introduced pointer could be +It is a compile time error if the implicitly introduced pointer could be used to access a location beyond its lifetime: .. code-block:: nimrod @@ -2499,7 +2513,7 @@ starts with the prefix ``m`` per convention. Overloading of the subscript operator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------- The ``[]`` subscript operator for arrays/openarrays/sequences can be overloaded. Overloading support is only possible if the first parameter has no type that @@ -2508,7 +2522,7 @@ does not check this restriction. Multi-methods -~~~~~~~~~~~~~ +============= Procedures always use static dispatch. `Multi-methods`:idx: use dynamic dispatch. @@ -2580,7 +2594,7 @@ evaluation or dead code elimination do not work with methods. Iterators and the for statement -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +=============================== Syntax:: @@ -2598,8 +2612,9 @@ Syntax:: The `for`:idx: statement is an abstract mechanism to iterate over the elements of a container. It relies on an `iterator`:idx: to do so. Like ``while`` statements, ``for`` statements open an `implicit block`:idx:, so that they -can be left with a ``break`` statement. The ``for`` loop declares +can be left with a ``break`` statement. +The ``for`` loop declares iteration variables (``x`` in the example) - their scope reaches until the end of the loop body. The iteration variables' types are inferred by the return type of the iterator. @@ -2632,18 +2647,13 @@ The compiler generates code as if the programmer would have written this: echo(ch) inc(i) -The current implementation always inlines the iterator code leading to zero -overhead for the abstraction. But this may increase the code size. Later -versions of the compiler will only inline iterators which have the calling -convention ``inline``. - If the iterator yields a tuple, there have to be as many iteration variables as there are components in the tuple. The i'th iteration variable's type is the type of the i'th component. Implict items/pairs invocations -+++++++++++++++++++++++++++++++ +------------------------------- If the for loop expression ``e`` does not denote an iterator and the for loop has exactly 1 variable, the for loop expression is rewritten to ``items(e)``; @@ -2660,8 +2670,88 @@ the rewriting step, so that all overloadings of ``items``/``pairs`` are taken into account. +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 +increasee in code size. Inline iterators are second class +citizens; one cannot pass them around like first class procs. + +In contrast to that, a `closure iterator`:idx: can be passed around: + +.. code-block:: nimrod + iterator count0(): int {.closure.} = + yield 0 + + iterator count2(): int {.closure.} = + var x = 1 + yield x + inc x + yield x + + proc invoke(iter: iterator(): int {.closure.}) = + for x in iter(): echo x + + invoke(count0) + invoke(count2) + +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. + +Iterators that are neither marked ``{.closure.}`` nor ``{.inline.}`` explicitly +default to being inline, but that this may change in future versions of the +implementation. + +The ``iterator`` type is always of the calling convention ``closure`` +implicitly; the following example shows how to use iterators to implement +a `collaborative tasking`:idx: system: + +.. code-block:: nimrod + # simple tasking: + type + TTask = iterator (ticker: int) + + iterator a1(ticker: int) {.closure.} = + echo "a1: A" + yield + echo "a1: B" + yield + echo "a1: C" + yield + echo "a1: D" + + iterator a2(ticker: int) {.closure.} = + echo "a2: A" + yield + echo "a2: B" + yield + echo "a2: C" + + proc runTasks(t: varargs[TTask]) = + var ticker = 0 + while true: + let x = t[ticker mod t.len] + if finished(x): break + x(ticker) + inc ticker + + runTasks(a1, a2) + +The builtin ``system.finished`` can be used to determine if an iterator has +finished its operation; no exception is raised on an attempt to invoke an +iterator that has already finished its work. + + Type sections -~~~~~~~~~~~~~ +============= Syntax:: @@ -2697,10 +2787,10 @@ possible within a single ``type`` section. Exception handling ------------------- +================== Try statement -~~~~~~~~~~~~~ +------------- Syntax:: @@ -2755,7 +2845,7 @@ is not executed (if an exception occurs). Except and finally statements -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------- `except`:idx: and `finally`:idx: can also be used as a stand-alone statements. Any statements following them in the current block will be considered to be @@ -2766,9 +2856,26 @@ in an implicit try block: finally: close(f) ... +The ``except`` statement has a limitation in this form: you can't specify the +type of the exception, you have to catch everything. Also, if you want to use +both ``finally`` and ``except`` you need to reverse the usual sequence of the +statements. Example: + +.. code-block:: nimrod + proc test() = + raise newException(E_base, "Hey ho") + + proc tester() = + finally: echo "3. Finally block" + except: echo "2. Except block" + echo "1. Pre exception" + test() + echo "4. Post exception" + # --> 1, 2, 3 is printed, 4 is never reached + Raise statement -~~~~~~~~~~~~~~~ +--------------- Syntax:: @@ -2791,7 +2898,7 @@ exception (unless a raise hook has been provided). OnRaise builtin -~~~~~~~~~~~~~~~ +--------------- ``system.onRaise`` can be used to override the behaviour of ``raise`` for a single ``try`` statement. `onRaise`:idx: has to be called within the ``try`` @@ -2815,10 +2922,10 @@ This allows for a Lisp-like `condition system`:idx:\: Effect system -------------- +============= Exception tracking -~~~~~~~~~~~~~~~~~~ +------------------ Nimrod supports `exception tracking`:idx:. The `raises`:idx: pragma can used to explicitly define which exceptions a proc/iterator/method/converter is allowed @@ -2858,9 +2965,9 @@ compatibility: For a routine ``p`` the compiler uses inference rules to determine the set of possibly raised exceptions; the algorithm operates on ``p``'s call graph: -1. Every indirect call via some proc type ``T`` is assumed to - raise ``system.E_Base`` (the base type of the exception hierarchy) and thus - any exception unless ``T`` has an explicit ``raises`` list. +1. Every indirect call via some proc type ``T`` is assumed to + raise ``system.E_Base`` (the base type of the exception hierarchy) and + thus any exception unless ``T`` has an explicit ``raises`` list. 2. Every call to a proc ``q`` which has an unknown body (due to a forward declaration or an ``importc`` pragma) is assumed to raise ``system.E_Base`` unless ``q`` has an explicit ``raises`` list. @@ -2873,7 +2980,7 @@ possibly raised exceptions; the algorithm operates on ``p``'s call graph: Tag tracking -~~~~~~~~~~~~ +------------ The exception tracking is part of Nimrod's `effect system`:idx:. Raising an exception is an *effect*. Other effects can also be defined. A user defined @@ -2895,7 +3002,7 @@ exception tracking. Read/Write tracking -~~~~~~~~~~~~~~~~~~~ +------------------- **Note**: Read/write tracking is not yet implemented! @@ -2904,7 +3011,7 @@ exception tracking. Effects pragma -~~~~~~~~~~~~~~ +-------------- The `effects`:idx: pragma has been designed to assist the programmer with the effects analysis. It is a statement that makes the compiler output all inferred @@ -2914,7 +3021,7 @@ effects up to the ``effects``'s position: proc p(what: bool) = if what: raise newException(EIO, "IO") - {.effect.} + {.effects.} else: raise newException(EOS, "OS") @@ -2923,7 +3030,7 @@ listed as it cannot be raised in the branch the ``effects`` pragma appears in. Generics --------- +======== Example: @@ -2981,7 +3088,7 @@ introduce type parameters or to instantiate a generic proc, iterator or type. Is operator -~~~~~~~~~~~ +----------- The `is`:idx: operator checks for type equivalence at compile time. It is therefore very useful for type specialization within generic code: @@ -2996,7 +3103,7 @@ therefore very useful for type specialization within generic code: Type operator -~~~~~~~~~~~~~ +------------- The `type`:idx: (in many other languages called `typeof`:idx:) operator can be used to get the type of an expression: @@ -3019,7 +3126,7 @@ other interpretations: Type Classes -~~~~~~~~~~~~ +------------ A `type class`:idx: is a special pseudo-type that can be used to match against types in the context of overload resolution or the ``is`` operator. @@ -3108,13 +3215,15 @@ the dot syntax: If anonymous type classes are used, the ``type`` operator can be used to discover the instantiated type of each param. + User defined type classes -~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------- To be written. + Return Type Inference -~~~~~~~~~~~~~~~~~~~~~ +--------------------- If a type class is used as the return type of a proc and it won't be bound to a concrete type by some of the proc params, Nimrod will infer the return type @@ -3132,7 +3241,7 @@ also influence the inferred return type. Symbol lookup in generics -~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------- The symbol binding rules in generics are slightly subtle: There are "open" and "closed" symbols. A "closed" symbol cannot be re-bound in the instantiation @@ -3171,7 +3280,7 @@ A symbol can be forced to be open by a `mixin`:idx: declaration: Templates ---------- +========= A `template`:idx: is a simple form of a macro: It is a simple substitution mechanism that operates on Nimrod's abstract syntax trees. It is processed in @@ -3202,7 +3311,7 @@ Real types can be used too; this implies that expressions are expected. Ordinary vs immediate templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------- There are two different kinds of templates: `immediate`:idx: templates and ordinary templates. Ordinary templates take part in overloading resolution. As @@ -3229,7 +3338,7 @@ receive undeclared identifiers: Scoping in templates -~~~~~~~~~~~~~~~~~~~~ +-------------------- The template body does not open a new scope. To open a new scope a ``block`` statement can be used: @@ -3251,7 +3360,7 @@ statement can be used: Passing a code block to a template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------------- If there is a ``stmt`` parameter it should be the last in the template declaration, because statements are passed to a template via a @@ -3298,7 +3407,7 @@ Symbol binding within templates happens after template instantiation: Bind statement -~~~~~~~~~~~~~~ +-------------- Syntax:: @@ -3329,7 +3438,7 @@ A ``bind`` statement can also be used in generics for the same purpose. Identifier construction -~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- In templates identifiers can be constructed with the backticks notation: @@ -3348,7 +3457,7 @@ In the example ``name`` is instantiated with ``myint``, so \`T name\` becomes Lookup rules for template parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------ A parameter ``p`` in a template is even substituted in the expression ``x.p``. Thus template arguments can be used as field names and a global symbol can be @@ -3389,7 +3498,7 @@ But the global symbol can properly be captured by a ``bind`` statement: Hygiene in templates -~~~~~~~~~~~~~~~~~~~~ +-------------------- Per default templates are `hygienic`:idx:\: Local identifiers declared in a template cannot be accessed in the instantiation context: @@ -3398,7 +3507,7 @@ template cannot be accessed in the instantiation context: template newException*(exceptn: typeDesc, message: string): expr = var - e: ref exceptn # e is implicitely gensym'ed here + e: ref exceptn # e is implicitly gensym'ed here new(e) e.msg = message e @@ -3444,7 +3553,7 @@ a template. ``inject`` and ``gensym`` have no effect in ``dirty`` templates. Macros ------- +====== A `macro`:idx: is a special kind of low level template. Macros can be used to implement `domain specific languages`:idx:. Like templates, macros come in @@ -3463,7 +3572,7 @@ There are two ways to invoke a macro: Expression Macros -~~~~~~~~~~~~~~~~~ +----------------- The following example implements a powerful ``debug`` command that accepts a variable number of arguments: @@ -3517,12 +3626,12 @@ children. BindSym -~~~~~~~ +------- The above ``debug`` macro relies on the fact that ``write``, ``writeln`` and ``stdout`` are declared in the system module and thus visible in the instantiating context. There is a way to use bound identifiers -(aka `symbols`:idx) instead of using unbound identifiers. The ``bindSym`` +(aka `symbols`:idx:) instead of using unbound identifiers. The ``bindSym`` builtin can be used for that: .. code-block:: nimrod @@ -3565,7 +3674,7 @@ overloaded symbols implicitly. Statement Macros -~~~~~~~~~~~~~~~~ +---------------- Statement macros are defined just as expression macros. However, they are invoked by an expression following a colon:: @@ -3609,7 +3718,7 @@ powerful programming construct that still suffices. So the "check list" is: Macros as pragmas -~~~~~~~~~~~~~~~~~ +----------------- Whole routines (procs, iterators etc.) can also be passed to a template or a macro via the pragma notation: @@ -3629,17 +3738,17 @@ This is a simple syntactic transformation into: Special Types -------------- +============= typedesc -~~~~~~~~ +-------- -`typedesc` is a special type allowing you to treat types as compile-time values +`typedesc` is a special type allowing one to treat types as compile-time values (i.e. if types are compile-time values and all values have a type, then typedesc must be their type). When used as a regular proc param, typedesc acts as a type class. The proc -will be instantiated for each unique type parameter and you can refer to the +will be instantiated for each unique type parameter and one can refer to the instantiation type using the param name: .. code-block:: nimrod @@ -3654,9 +3763,9 @@ instantiation type using the param name: When used with macros and .compileTime. procs on the other hand, the compiler don't need to instantiate the code multiple times, because types then can be manipulated using the unified internal symbol representation. In such context -typedesc acts as any other type. You can create variables, store typedesc -values inside containers and so on. For example, here is how we can create -a type-safe wrapper for the unsafe `printf` function form C: +typedesc acts as any other type. One can create variables, store typedesc +values inside containers and so on. For example, here is how one can create +a type-safe wrapper for the unsafe `printf` function from C: .. code-block:: nimrod macro safePrintF(formatString: string{lit}, args: vararg[expr]): expr = @@ -3699,8 +3808,371 @@ types that will match the typedesc param: The constraint can be a concrete type or a type class. +Term rewriting macros +===================== + +`Term rewriting macros`:idx: are macros or templates that have not only +a *name* but also a *pattern* that is searched for after the semantic checking +phase of the compiler: This means they provide an easy way to enhance the +compilation pipeline with user defined optimizations: + +.. code-block:: nimrod + template optMul{`*`(a, 2)}(a: int): int = a+a + + let x = 3 + echo x * 2 + +The compiler now rewrites ``x * 2`` as ``x + x``. The code inside the +curlies is the pattern to match against. The operators ``*``, ``**``, +``|``, ``~`` have a special meaning in patterns if they are written in infix +notation, so to match verbatim against ``*`` the ordinary function call syntax +needs to be used. + + +Unfortunately optimizations are hard to get right and even the tiny example +is **wrong**: + +.. code-block:: nimrod + template optMul{`*`(a, 2)}(a: int): int = a+a + + proc f(): int = + echo "side effect!" + result = 55 + + echo f() * 2 + +We cannot duplicate 'a' if it denotes an expression that has a side effect! +Fortunately Nimrod supports side effect analysis: + +.. code-block:: nimrod + template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a + + proc f(): int = + echo "side effect!" + result = 55 + + echo f() * 2 # not optimized ;-) + +So what about ``2 * a``? We should tell the compiler ``*`` is commutative. We +cannot really do that however as the following code only swaps arguments +blindly: + +.. code-block:: nimrod + template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a + +What optimizers really need to do is a *canonicalization*: + +.. code-block:: nimrod + template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a + +The ``int{lit}`` parameter pattern matches against an expression of +type ``int``, but only if it's a literal. + + + +Parameter constraints +--------------------- + +The `parameter constraint`:idx: expression can use the operators ``|`` (or), +``&`` (and) and ``~`` (not) and the following predicates: + +=================== ===================================================== +Predicate Meaning +=================== ===================================================== +``atom`` The matching node has no children. +``lit`` The matching node is a literal like "abc", 12. +``sym`` The matching node must be a symbol (a bound + identifier). +``ident`` The matching node must be an identifier (an unbound + identifier). +``call`` The matching AST must be a call/apply expression. +``lvalue`` The matching AST must be an lvalue. +``sideeffect`` The matching AST must have a side effect. +``nosideeffect`` The matching AST must have no side effect. +``param`` A symbol which is a parameter. +``genericparam`` A symbol which is a generic parameter. +``module`` A symbol which is a module. +``type`` A symbol which is a type. +``var`` A symbol which is a variable. +``let`` A symbol which is a ``let`` variable. +``const`` A symbol which is a constant. +``result`` The special ``result`` variable. +``proc`` A symbol which is a proc. +``method`` A symbol which is a method. +``iterator`` A symbol which is an iterator. +``converter`` A symbol which is a converter. +``macro`` A symbol which is a macro. +``template`` A symbol which is a template. +``field`` A symbol which is a field in a tuple or an object. +``enumfield`` A symbol which is a field in an enumeration. +``forvar`` A for loop variable. +``label`` A label (used in ``block`` statements). +``nk*`` The matching AST must have the specified kind. + (Example: ``nkIfStmt`` denotes an ``if`` statement.) +``alias`` States that the marked parameter needs to alias + with *some* other parameter. +``noalias`` States that *every* other parameter must not alias + with the marked parameter. +=================== ===================================================== + +The ``alias`` and ``noalias`` predicates refer not only to the matching AST, +but also to every other bound parameter; syntactially they need to occur after +the ordinary AST predicates: + +.. code-block:: nimrod + template ex{a = b + c}(a: int{noalias}, b, c: int) = + # this transformation is only valid if 'b' and 'c' do not alias 'a': + a = b + inc a, b + + +Pattern operators +----------------- + +The operators ``*``, ``**``, ``|``, ``~`` have a special meaning in patterns +if they are written in infix notation. + + +The ``|`` operator +~~~~~~~~~~~~~~~~~~ + +The ``|`` operator if used as infix operator creates an ordered choice: + +.. code-block:: nimrod + template t{0|1}(): expr = 3 + let a = 1 + # outputs 3: + echo a + +The matching is performed after the compiler performed some optimizations like +constant folding, so the following does not work: + +.. code-block:: nimrod + template t{0|1}(): expr = 3 + # outputs 1: + echo 1 + +The reason is that the compiler already transformed the 1 into "1" for +the ``echo`` statement. However, a term rewriting macro should not change the +semantics anyway. In fact they can be deactived with the ``--patterns:off`` +command line option or temporarily with the ``patterns`` pragma. + + +The ``{}`` operator +~~~~~~~~~~~~~~~~~~~ + +A pattern expression can be bound to a pattern parameter via the ``expr{param}`` +notation: + +.. code-block:: nimrod + template t{(0|1|2){x}}(x: expr): expr = x+1 + let a = 1 + # outputs 2: + echo a + + +The ``~`` operator +~~~~~~~~~~~~~~~~~~ + +The ``~`` operator is the **not** operator in patterns: + +.. code-block:: nimrod + template t{x = (~x){y} and (~x){z}}(x, y, z: bool): stmt = + x = y + if x: x = z + + var + a = false + b = true + c = false + a = b and c + echo a + + +The ``*`` operator +~~~~~~~~~~~~~~~~~~ + +The ``*`` operator can *flatten* a nested binary expression like ``a & b & c`` +to ``&(a, b, c)``: + +.. code-block:: nimrod + var + calls = 0 + + proc `&&`(s: varargs[string]): string = + result = s[0] + for i in 1..len(s)-1: result.add s[i] + inc calls + + template optConc{ `&&` * a }(a: string): expr = &&a + + let space = " " + echo "my" && (space & "awe" && "some " ) && "concat" + + # check that it's been optimized properly: + doAssert calls == 1 + + +The second operator of `*` must be a parameter; it is used to gather all the +arguments. The expression ``"my" && (space & "awe" && "some " ) && "concat"`` +is passed to ``optConc`` in ``a`` as a special list (of kind ``nkArgList``) +which is flattened into a call expression; thus the invocation of ``optConc`` +produces: + +.. code-block:: nimrod + `&&`("my", space & "awe", "some ", "concat") + + +The ``**`` operator +~~~~~~~~~~~~~~~~~~~ + +The ``**`` is much like the ``*`` operator, except that it gathers not only +all the arguments, but also the matched operators in reverse polish notation: + +.. code-block:: nimrod + import macros + + type + TMatrix = object + dummy: int + + proc `*`(a, b: TMatrix): TMatrix = nil + proc `+`(a, b: TMatrix): TMatrix = nil + proc `-`(a, b: TMatrix): TMatrix = nil + proc `$`(a: TMatrix): string = result = $a.dummy + proc mat21(): TMatrix = + result.dummy = 21 + + macro optM{ (`+`|`-`|`*`) ** a }(a: TMatrix): expr = + echo treeRepr(a) + result = newCall(bindSym"mat21") + + var x, y, z: TMatrix + + echo x + y * z - x + +This passes the expression ``x + y * z - x`` to the ``optM`` macro as +an ``nnkArgList`` node containing:: + + Arglist + Sym "x" + Sym "y" + Sym "z" + Sym "*" + Sym "+" + Sym "x" + Sym "-" + +(Which is the reverse polish notation of ``x + y * z - x``.) + + +Parameters +---------- + +Parameters in a pattern are type checked in the matching process. If a +parameter is of the type ``varargs`` it is treated specially and it can match +0 or more arguments in the AST to be matched against: + +.. code-block:: nimrod + template optWrite{ + write(f, x) + ((write|writeln){w})(f, y) + }(x, y: varargs[expr], f: TFile, w: expr) = + w(f, x, y) + + + +Example: Partial evaluation +--------------------------- + +The following example shows how some simple partial evaluation can be +implemented with term rewriting: + +.. code-block:: nimrod + proc p(x, y: int; cond: bool): int = + result = if cond: x + y else: x - y + + template optP{p(x, y, true)}(x, y: expr): expr = x + y + template optP{p(x, y, false)}(x, y: expr): expr = x - y + + +Example: hoisting +----------------- + +The following example how some form of hoisting can be implemented: + +.. code-block:: nimrod + import pegs + + template optPeg{peg(pattern)}(pattern: string{lit}): TPeg = + var gl {.global, gensym.} = peg(pattern) + gl + + for i in 0 .. 3: + echo match("(a b c)", peg"'(' @ ')'") + echo match("W_HI_Le", peg"\y 'while'") + +The ``optPeg`` template optimizes the case of a peg constructor with a string +literal, so that the pattern will only be parsed once at program startup and +stored in a global ``gl`` which is then re-used. This optimization is called +hoisting because it is comparable to classical loop hoisting. + + +AST based overloading +===================== + +Parameter constraints can also be used for ordinary routine parameters; these +constraints affect ordinary overloading resolution then: + +.. code-block:: nimrod + proc optLit(a: string{lit|`const`}) = + echo "string literal" + proc optLit(a: string) = + echo "no string literal" + + const + constant = "abc" + + var + variable = "xyz" + + optLit("literal") + optLit(constant) + optLit(variable) + +However, the constraints ``alias`` and ``noalias`` are not available in +ordinary routines. + + +Move optimization +----------------- + +The ``call`` constraint is particularly useful to implement a `move`:idx: +optimization for types that have copying semantics: + +.. code-block:: nimrod + proc `[]=`*(t: var TTable, key: string, val: string) = + ## puts a (key, value)-pair into `t`. The semantics of string require + ## a copy here: + let idx = findInsertionPosition(key) + t[idx] = key + t[idx] = val + + proc `[]=`*(t: var TTable, key: string{call}, val: string{call}) = + ## puts a (key, value)-pair into `t`. Optimized version that knows that + ## the strings are unique and thus don't need to be copied: + let idx = findInsertionPosition(key) + shallowCopy t[idx], key + shallowCopy t[idx], val + + var t: TTable + # overloading resolution ensures that the optimized []= is called here: + t["abc"] = "xyz" + + + Modules -------- +======= Nimrod supports splitting a program into pieces by a `module`:idx: concept. Each module needs to be in its own file and has its own `namespace`:idx:. Modules enable `information hiding`:idx: and `separate compilation`:idx:. @@ -3741,6 +4213,46 @@ This is best illustrated by an example: return x + 1 +Import statement +~~~~~~~~~~~~~~~~ + +After the `import`:idx: statement a list of module names can follow or a single +module name followed by an ``except`` to prevent some symbols to be imported: + +.. code-block:: nimrod + import strutils except `%` + + # doesn't work then: + echo "$1" % "abc" + + +Export statement +~~~~~~~~~~~~~~~~ + +An `export`:idx: statement can be used for symbol fowarding so that client +modules don't need to import a module's dependencies: + +.. code-block:: nimrod + # module B + type TMyObject* = object + +.. code-block:: nimrod + # module A + import B + export B.TMyObject + + proc `$`*(x: TMyObject): string = "my object" + + +.. code-block:: nimrod + # module C + import A + + # B.TMyObject has been imported implicitly here: + var x: TMyObject + echo($x) + + Scope rules ----------- Identifiers are valid from the point of their declaration until the end of @@ -4366,10 +4878,14 @@ Threadvar pragma ---------------- A global variable can be marked with the `threadvar`:idx: pragma; it is -a `thead-local`:idx: variable then: +a `thread-local`:idx: variable then: .. code-block:: nimrod - var checkpoints* {.threadvar.}: seq[string] = @[] + var checkpoints* {.threadvar.}: seq[string] + +Due to implementation restrictions thread local variables cannot be +initialized within the ``var`` section. (Every thread local variable needs to +be replicated at thread creation.) Actor model diff --git a/doc/tut1.txt b/doc/tut1.txt index 95d13ceb9..746d11f01 100755 --- a/doc/tut1.txt +++ b/doc/tut1.txt @@ -349,6 +349,7 @@ provides. The example uses the built-in ``countup`` iterator: echo("Counting to ten: ") for i in countup(1, 10): echo($i) + # --> Outputs 1 2 3 4 5 6 7 8 9 10 on different lines The built-in ``$`` operator turns an integer (``int``) and many other types into a string. The variable ``i`` is implicitly declared by the ``for`` loop @@ -362,6 +363,7 @@ the same: while i <= 10: echo($i) inc(i) # increment i by 1 + # --> Outputs 1 2 3 4 5 6 7 8 9 10 on different lines Counting down can be achieved as easily (but is less often needed): @@ -369,6 +371,7 @@ Counting down can be achieved as easily (but is less often needed): echo("Counting down from 10 to 1: ") for i in countdown(10, 1): echo($i) + # --> Outputs 10 9 8 7 6 5 4 3 2 1 on different lines Since counting up occurs so often in programs, Nimrod also has a ``..`` iterator that does the same: @@ -780,12 +783,15 @@ important differences: * Iterators cannot contain a ``return`` statement and procs cannot contain a ``yield`` statement. * Iterators have no implicit ``result`` variable. -* Iterators do not support recursion. (This restriction will be gone in a - future version of the compiler.) +* Iterators do not support recursion. * Iterators cannot be forward declared, because the compiler must be able to inline an iterator. (This restriction will be gone in a future version of the compiler.) +However, you can also use a ``closure`` iterator to get a different set of +restrictions. See `first class iterators <manual.html#first-class-iterators>`_ +for details. + Basic types =========== @@ -916,6 +922,37 @@ types automatically and vice versa. The ``toInt`` and ``toFloat`` procs can be used for these conversions. +Internal type representation +============================ + +As mentioned earlier, the built-in ``$`` (stringify) operator turns any basic +type into a string, which you can then print to the screen with the ``echo`` +proc. However, advanced types, or types you may define yourself won't work with +the ``$`` operator until you define one for them. Sometimes you just want to +debug the current value of a complex type without having to write its ``$`` +operator. You can use then the ``repr`` proc which works with any type and +even complex data graphs with cycles. The following example shows that even for +basic types there is a difference between the ``$`` and ``repr`` outputs: + +.. code-block:: nimrod + var + myBool = true + myCharacter = 'n' + myString = "nimrod" + myInteger = 42 + myFloat = 3.14 + echo($myBool, ":", repr(myBool)) + # --> true:true + echo($myCharacter, ":", repr(myCharacter)) + # --> n:'n' + echo($myString, ":", repr(myString)) + # --> nimrod:0x10fa8c050"nimrod" + echo($myInteger, ":", repr(myInteger)) + # --> 42:42 + echo($myFloat, ":", repr(myFloat)) + # --> 3.1400000000000001e+00:3.1400000000000001e+00 + + Advanced types ============== @@ -1089,6 +1126,54 @@ copies the whole array contents. The built-in ``len`` proc returns the array's length. ``low(a)`` returns the lowest valid index for the array `a` and ``high(a)`` the highest valid index. +.. code-block:: nimrod + type + TDirection = enum + north, east, south, west + TBlinkLights = enum + off, on, slowBlink, mediumBlink, fastBlink + TLevelSetting = array[north..west, TBlinkLights] + var + level : TLevelSetting + level[north] = on + level[south] = slowBlink + level[east] = fastBlink + echo repr(level) # --> [on, fastBlink, slowBlink, off] + echo low(level) # --> north + echo len(level) # --> 4 + echo high(level) # --> west + +The syntax for nested arrays (multidimensional) in other languages is a matter +of appending more brackets because usually each dimension is restricted to the +same index type as the others. In nimrod you can have different dimensions with +different index types, so the nesting syntax is slightly different. Building on +the previous example where a level is defined as an array of enums indexed by +yet another enum, we can add the following lines to add a light tower type +subdivided in height levels accessed through their integer index: + +.. code-block:: nimrod + type + TLightTower = array[1..10, TLevelSetting] + var + tower: TLightTower + tower[1][north] = slowBlink + tower[1][east] = mediumBlink + echo len(tower) # --> 10 + echo len(tower[1]) # --> 4 + echo repr(tower) # --> [[slowBlink, mediumBlink, ...more output.. + # The following lines don't compile due to type mistmatch errors + #tower[north][east] = on + #tower[0][1] = on + +Note how the built-in ``len`` proc returns only the array's first dimension +length. Another way of defining the ``TLightTower`` to show better its +nested nature would be to omit the previous definition of the ``TLevelSetting`` +type and instead write it embedded directly as the type of the first dimension: + +.. code-block:: nimrod + type + TLightTower = array[1..10, array[north..west, TBlinkLights]] + Sequences --------- @@ -1120,6 +1205,28 @@ raised) for performance reasons. Thus one should use empty sequences ``@[]`` rather than ``nil`` as the *empty* value. But ``@[]`` creates a sequence object on the heap, so there is a trade-off to be made here. +The ``for`` statement can be used with one or two variables when used with a +sequence. When you use the one variable form, the variable will hold the value +provided by the sequence. The ``for`` statement is looping over the results +from the ``items()`` iterator from the `system <system.html>`_ module. But if +you use the two variable form, the first variable will hold the index position +and the second variable will hold the value. Here the ``for`` statement is +looping over the results from the ``pairs()`` iterator from the `system +<system.html>`_ module. Examples: + +.. code-block:: nimrod + for i in @[3, 4, 5]: + echo($i) + # --> 3 + # --> 4 + # --> 5 + + for i, value in @[3, 4, 5]: + echo("index: ", $i, ", value:", $value) + # --> index: 0, value:3 + # --> index: 1, value:4 + # --> index: 2, value:5 + Open arrays ----------- @@ -1221,14 +1328,10 @@ untraced references are *unsafe*. However for certain low-level operations Traced references are declared with the **ref** keyword, untraced references are declared with the **ptr** keyword. -The empty ``[]`` subscript notation can be used to *derefer* a reference, -meaning to retrieve the item the reference points to. The ``addr`` operator -returns the address of an item. An address is always an untraced reference: -``addr`` is an *unsafe* feature. - -The ``.`` (access a tuple/object field operator) -and ``[]`` (array/string/sequence index operator) operators perform implicit -dereferencing operations for reference types: +The empty ``[]`` subscript notation can be used to *derefer* a reference, +meaning to retrieve the item the reference points to. The ``.`` (access a +tuple/object field operator) and ``[]`` (array/string/sequence index operator) +operators perform implicit dereferencing operations for reference types: .. code-block:: nimrod @@ -1245,8 +1348,8 @@ dereferencing operations for reference types: To allocate a new traced object, the built-in procedure ``new`` has to be used. To deal with untraced memory, the procedures ``alloc``, ``dealloc`` and -``realloc`` can be used. The documentation of the system module contains -further information. +``realloc`` can be used. The documentation of the `system <system.html>`_ +module contains further information. If a reference points to *nothing*, it has the value ``nil``. diff --git a/doc/tut2.txt b/doc/tut2.txt index 9c55b0e35..c080d1339 100755 --- a/doc/tut2.txt +++ b/doc/tut2.txt @@ -218,7 +218,7 @@ So "pure object oriented" code is easy to write: import strutils stdout.writeln("Give a list of numbers (separated by spaces): ") - stdout.write(stdin.readLine.split.each(parseInt).max.`$`) + stdout.write(stdin.readLine.split.map(parseInt).max.`$`) stdout.writeln(" is the maximum!") @@ -433,45 +433,59 @@ handled, it is propagated through the call stack. This means that often the rest of the procedure - that is not within a ``finally`` clause - is not executed (if an exception occurs). +If you need to *access* the actual exception object or message inside an +``except`` branch you can use the getCurrentException() and +getCurrentExceptionMsg() procs from the `system <system.html>`_ module. +Example: + +.. code-block:: nimrod + try: + doSomethingHere() + except: + let + e = getCurrentException() + msg = getCurrentExceptionMsg() + echo "Got exception ", repr(e), " with message ", msg + Exception hierarchy ------------------- If you want to create your own exceptions you can inherit from E_Base, but you can also inherit from one of the existing exceptions if they fit your purpose. -The exception tree is: - -* E_Base - * EAsynch - * EControlC - * ESynch - * ESystem - * EIO - * EOS - * EInvalidLibrary - * EResourceExhausted - * EOutOfMemory - * EStackOverflow - * EArithmetic - * EDivByZero - * EOverflow - * EAccessViolation - * EAssertionFailed - * EInvalidValue - * EInvalidKey - * EInvalidIndex - * EInvalidField - * EOutOfRange - * ENoExceptionToReraise - * EInvalidObjectAssignment - * EInvalidObjectConversion - * EFloatingPoint - * EFloatInvalidOp - * EFloatDivByZero - * EFloatOverflow - * EFloatUnderflow - * EFloatInexact - * EDeadThread +The exception tree is:: + + * E_Base + * EAsynch + * EControlC + * ESynch + * ESystem + * EIO + * EOS + * EInvalidLibrary + * EResourceExhausted + * EOutOfMemory + * EStackOverflow + * EArithmetic + * EDivByZero + * EOverflow + * EAccessViolation + * EAssertionFailed + * EInvalidValue + * EInvalidKey + * EInvalidIndex + * EInvalidField + * EOutOfRange + * ENoExceptionToReraise + * EInvalidObjectAssignment + * EInvalidObjectConversion + * EFloatingPoint + * EFloatInvalidOp + * EFloatDivByZero + * EFloatOverflow + * EFloatUnderflow + * EFloatInexact + * EDeadThread See the `system <system.html>`_ module for a description of each exception. diff --git a/examples/cross_calculator/ios/scripts/xcode_prebuild.sh b/examples/cross_calculator/ios/scripts/xcode_prebuild.sh index 7626ba0b9..c6d38f164 100755 --- a/examples/cross_calculator/ios/scripts/xcode_prebuild.sh +++ b/examples/cross_calculator/ios/scripts/xcode_prebuild.sh @@ -21,7 +21,6 @@ DEST_NIMBASE=build/nimcache/nimbase.h # Ok, are we out now? if [ -d src ] then - # You may want to use a wildcard here to compile all nimrod files. $PATH_TO_NIMROD objc --noMain --app:lib \ --nimcache:build/nimcache --compileOnly \ --header --cpu:i386 ../nimrod_backend/backend.nim diff --git a/examples/cross_todo/readme.txt b/examples/cross_todo/readme.txt index acea8973a..326ed816f 100644 --- a/examples/cross_todo/readme.txt +++ b/examples/cross_todo/readme.txt @@ -3,3 +3,5 @@ called by different native user interfaces. This example builds on the knowledge learned from the cross_calculator example. Check it out first to learn how to set up nimrod on different platforms. +Additional implementations are provided at the external +https://github.com/gradha/nimrod-crossplatform-todo github repository. diff --git a/examples/maximum.nim b/examples/maximum.nim index 1e26ee1a7..ac6160f76 100755 --- a/examples/maximum.nim +++ b/examples/maximum.nim @@ -3,4 +3,4 @@ import strutils echo "Give a list of numbers (separated by spaces): " -stdin.readLine.split.each(parseInt).max.`$`.echo(" is the maximum!") +stdin.readLine.split.map(parseInt).max.`$`.echo(" is the maximum!") diff --git a/koch.nim b/koch.nim index 6bc63a6ce..2a22d7ca5 100755 --- a/koch.nim +++ b/koch.nim @@ -1,7 +1,7 @@ # # # Maintenance program for Nimrod -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -26,7 +26,7 @@ const +-----------------------------------------------------------------+ | Maintenance program for Nimrod | | Version $1| -| (c) 2012 Andreas Rumpf | +| (c) 2013 Andreas Rumpf | +-----------------------------------------------------------------+ Build time: $2, $3 @@ -49,6 +49,7 @@ Boot options: -d:tinyc include the Tiny C backend (not supported on Windows) -d:useGnuReadline use the GNU readline library for interactive mode (not needed on Windows) + -d:useFFI build Nimrod with FFI support at compile time -d:nativeStacktrace use native stack traces (only for Mac OS X or Linux) """ @@ -214,7 +215,7 @@ when defined(withUpdate): if errcode == 0: if output == "": # No changes - echo("No update. Exiting..") + echo("No update. Exiting...") return else: echo("Fetching updates from repo...") diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 966a21a1b..bf7510a92 100755 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -45,8 +45,16 @@ type nnkConstDef, nnkTypeDef, nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt, nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt, - nnkDiscardStmt, nnkStmtList, nnkImportStmt, nnkFromStmt, - nnkIncludeStmt, nnkBindStmt, nnkMixinStmt, + nnkDiscardStmt, nnkStmtList, + + nnkImportStmt, + nnkImportExceptStmt, + nnkExportStmt, + nnkExportExceptStmt, + nnkFromStmt, + nnkIncludeStmt, + + nnkBindStmt, nnkMixinStmt, nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr, nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy, nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen, diff --git a/lib/core/typeinfo.nim b/lib/core/typeinfo.nim index 26a14f444..eb2a0c9e5 100755 --- a/lib/core/typeinfo.nim +++ b/lib/core/typeinfo.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Dominik Picheta, Andreas Rumpf +# (c) Copyright 2013 Dominik Picheta, Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -139,12 +139,15 @@ proc invokeNewSeq*(x: TAny, len: int) = var z = newSeq(x.rawType, len) genericShallowAssign(x.value, addr(z), x.rawType) -proc extendSeq*(x: TAny, elems = 1) = - ## performs ``setLen(x, x.len+elems)``. `x` needs to represent a ``seq``. +proc extendSeq*(x: TAny) = + ## performs ``setLen(x, x.len+1)``. `x` needs to represent a ``seq``. assert x.rawType.kind == tySequence var y = cast[ptr PGenSeq](x.value)[] - var z = incrSeq(y, x.rawType.base.size * elems) - genericShallowAssign(x.value, addr(z), x.rawType) + var z = incrSeq(y, x.rawType.base.size) + # 'incrSeq' already freed the memory for us and copied over the RC! + # So we simply copy the raw pointer into 'x.value': + cast[ppointer](x.value)[] = z + #genericShallowAssign(x.value, addr(z), x.rawType) proc setObjectRuntimeType*(x: TAny) = ## this needs to be called to set `x`'s runtime object type field. diff --git a/lib/impure/web.nim b/lib/impure/web.nim index 417fe9746..5f04422d1 100755 --- a/lib/impure/web.nim +++ b/lib/impure/web.nim @@ -17,7 +17,8 @@ ## Currently only requesting URLs is implemented. The implementation depends ## on the libcurl library! ## -## **Deprecated since version 0.8.8:** Use the ``httpclient`` module instead. +## **Deprecated since version 0.8.8:** Use the +## `httpclient <httpclient.html>`_ module instead. ## {.deprecated.} diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index 5ecc16de7..be375a1a1 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -35,6 +35,9 @@ import sockets, os ## that in the future this type's fields will not be exported therefore breaking ## your code. ## +## **Warning:** The API of this module is unstable, and therefore is subject +## to change. +## ## Asynchronous sockets ## ==================== ## @@ -123,6 +126,7 @@ type handleTask*: proc (s: PAsyncSocket) {.closure.} lineBuffer: TaintedString ## Temporary storage for ``recvLine`` + sendBuffer: string ## Temporary storage for ``send`` sslNeedAccept: bool proto: TProtocol deleg: PDelegate @@ -152,6 +156,7 @@ proc newAsyncSocket(): PAsyncSocket = result.handleTask = (proc (s: PAsyncSocket) = nil) result.lineBuffer = "".TaintedString + result.sendBuffer = "" proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM, protocol: TProtocol = IPPROTO_TCP, @@ -222,10 +227,22 @@ proc asyncSockHandleWrite(h: PObject) = else: PAsyncSocket(h).deleg.mode = fmReadWrite else: - if PAsyncSocket(h).handleWrite != nil: - PAsyncSocket(h).handleWrite(PAsyncSocket(h)) + if PAsyncSocket(h).sendBuffer != "": + let sock = PAsyncSocket(h) + let bytesSent = sock.socket.sendAsync(sock.sendBuffer) + assert bytesSent > 0 + if bytesSent != sock.sendBuffer.len: + sock.sendBuffer = sock.sendBuffer[bytesSent .. -1] + elif bytesSent == sock.sendBuffer.len: + sock.sendBuffer = "" + + if PAsyncSocket(h).handleWrite != nil: + PAsyncSocket(h).handleWrite(PAsyncSocket(h)) else: - PAsyncSocket(h).deleg.mode = fmRead + if PAsyncSocket(h).handleWrite != nil: + PAsyncSocket(h).handleWrite(PAsyncSocket(h)) + else: + PAsyncSocket(h).deleg.mode = fmRead when defined(ssl): proc asyncSockDoHandshake(h: PObject) = @@ -337,7 +354,8 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket, # deleg.open is set in ``toDelegate``. client.socket = c - client.lineBuffer = "" + client.lineBuffer = "".TaintedString + client.sendBuffer = "" client.info = SockConnected proc accept*(server: PAsyncSocket, client: var PAsyncSocket) = @@ -422,6 +440,9 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool = ## sockets properly. This function guarantees that ``line`` is a full line, ## if this function can only retrieve some data; it will save this data and ## add it to the result when a full line is retrieved. + ## + ## Unlike ``sockets.recvLine`` this function will raise an EOS or ESSL + ## exception if an error occurs. setLen(line.string, 0) var dataReceived = "".TaintedString var ret = s.socket.recvLineAsync(dataReceived) @@ -440,8 +461,29 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool = of RecvDisconnected: result = true of RecvFail: + s.SocketError(async = true) result = false +proc send*(sock: PAsyncSocket, data: string) = + ## Sends ``data`` to socket ``sock``. This is basically a nicer implementation + ## of ``sockets.sendAsync``. + ## + ## If ``data`` cannot be sent immediately it will be buffered and sent + ## when ``sock`` becomes writeable (during the ``handleWrite`` event). + ## It's possible that only a part of ``data`` will be sent immediately, while + ## the rest of it will be buffered and sent later. + if sock.sendBuffer.len != 0: + sock.sendBuffer.add(data) + return + let bytesSent = sock.socket.sendAsync(data) + assert bytesSent >= 0 + if bytesSent == 0: + sock.sendBuffer.add(data) + sock.deleg.mode = fmReadWrite + elif bytesSent != data.len: + sock.sendBuffer.add(data[bytesSent .. -1]) + sock.deleg.mode = fmReadWrite + proc timeValFromMilliseconds(timeout = 500): TTimeVal = if timeout != -1: var seconds = timeout div 1000 @@ -556,7 +598,9 @@ when isMainModule: proc testRead(s: PAsyncSocket, no: int) = echo("Reading! " & $no) - var data = s.getSocket.recv() + var data = "" + if not s.recvLine(data): + OSError() if data == "": echo("Closing connection. " & $no) s.close() diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim index fcaf7b212..2a8d7eec2 100755 --- a/lib/pure/collections/intsets.nim +++ b/lib/pure/collections/intsets.nim @@ -190,6 +190,11 @@ proc `$`*(s: TIntSet): string = ## The `$` operator for int sets. dollarImpl() +proc empty*(s: TIntSet): bool {.inline.} = + ## returns true if `s` is empty. This is safe to call even before + ## the set has been initialized with `initIntSet`. + result = s.counter == 0 + when isMainModule: var x = initIntSet() x.incl(1) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 82679bb7f..298e7f27e 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -9,8 +9,16 @@ ## :Author: Alex Mitchell ## -## This module implements operations for the built-in `seq`:idx: type -## which were inspired by functional programming languages. +## This module implements operations for the built-in `seq`:idx: type which +## were inspired by functional programming languages. If you are looking for +## the typical `map` function which applies a function to every element in a +## sequence, it already exists in the `system <system.html>`_ module in both +## mutable and immutable styles. +## +## Also, for functional style programming you may want to pass `anonymous procs +## <manual.html#anonymous-procs>`_ to procs like ``filter`` to reduce typing. +## Anonymous procs can use `the special do notation <manual.html#do-notation>`_ +## which is more convenient in certain situations. ## ## **Note**: This interface will change as soon as the compiler supports ## closures and proper coroutines. @@ -19,7 +27,17 @@ when not defined(nimhygiene): {.pragma: dirty.} proc concat*[T](seqs: varargs[seq[T]]): seq[T] = - ## Takes several sequences' items and returns them inside of one sequence. + ## Takes several sequences' items and returns them inside a new sequence. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## let + ## s1 = @[1, 2, 3] + ## s2 = @[4, 5] + ## s3 = @[6, 7] + ## total = concat(s1, s2, s3) + ## assert total == @[1, 2, 3, 4, 5, 6, 7] var L = 0 for seqitm in items(seqs): inc(L, len(seqitm)) newSeq(result, L) @@ -30,14 +48,42 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] = inc(i) proc distnct*[T](seq1: seq[T]): seq[T] = - ## Removes duplicates from a sequence and returns it. + ## Returns a new sequence without duplicates. + ## + ## This proc is `misspelled` on purpose to avoid a clash with the keyword + ## ``distinct`` used to `define a derived type incompatible with its base + ## type <manual.html#distinct-type>`_. Example: + ## + ## .. code-block:: nimrod + ## let + ## dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] + ## dup2 = @["a", "a", "c", "d", "d"] + ## unique1 = distnct(dup1) + ## unique2 = distnct(dup2) + ## assert unique1 == @[1, 3, 4, 2, 8] + ## assert unique2 == @["a", "c", "d"] result = @[] for itm in items(seq1): if not result.contains(itm): result.add(itm) proc zip*[S, T](seq1: seq[S], seq2: seq[T]): seq[tuple[a: S, b: T]] = - ## Combines two sequences. If one sequence is too short, - ## the remaining items in the longer sequence are discarded. + ## Returns a new sequence with a combination of the two input sequences. + ## + ## For convenience you can access the returned tuples through the named + ## fields `a` and `b`. If one sequence is shorter, the remaining items in the + ## longer sequence are discarded. Example: + ## + ## .. code-block:: nimrod + ## let + ## short = @[1, 2, 3] + ## long = @[6, 5, 4, 3, 2, 1] + ## words = @["one", "two", "three"] + ## zip1 = zip(short, long) + ## zip2 = zip(short, words) + ## assert zip1 == @[(1, 6), (2, 5), (3, 4)] + ## assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] + ## assert zip1[2].b == 4 + ## assert zip2[2].b == "three" var m = min(seq1.len, seq2.len) newSeq(result, m) for i in 0 .. m-1: result[i] = (seq1[i], seq2[i]) @@ -45,21 +91,124 @@ proc zip*[S, T](seq1: seq[S], seq2: seq[T]): seq[tuple[a: S, b: T]] = iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T = ## Iterates through a sequence and yields every item that fulfills the ## predicate. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## let numbers = @[1, 4, 5, 8, 9, 7, 4] + ## for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): + ## echo($n) + ## # echoes 4, 8, 4 in separate lines for i in countup(0, len(seq1) -1): var item = seq1[i] if pred(item): yield seq1[i] proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] = - ## Returns all items in a sequence that fulfilled the predicate. + ## Returns a new sequence with all the items that fulfilled the predicate. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## let + ## colors = @["red", "yellow", "black"] + ## f1 = filter(colors, proc(x: string): bool = x.len < 6) + ## f2 = filter(colors) do (x: string) -> bool : x.len > 5 + ## assert f1 == @["red", "black"] + ## assert f2 == @["yellow"] accumulateResult(filter(seq1, pred)) template filterIt*(seq1, pred: expr): expr {.immediate, dirty.} = - ## Finds a specific item in a sequence as long as the - ## predicate returns true. The predicate needs to be an expression - ## containing ``it``: ``filterIt("abcxyz", it == 'x')``. - block: - var result: type(seq1) = @[] - for it in items(seq1): - if pred: result.add(it) - result + ## Returns a new sequence with all the items that fulfilled the predicate. + ## + ## Unlike the `proc` version, the predicate needs to be an expression using + ## the ``it`` variable for testing, like: ``filterIt("abcxyz", it == 'x')``. + ## Example: + ## + ## .. code-block:: nimrod + ## let + ## temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] + ## acceptable = filterIt(temperatures, it < 50 and it > -10) + ## assert acceptable == @[-2.0, 24.5, 44.31] + var result {.gensym.}: type(seq1) = @[] + for it in items(seq1): + if pred: result.add(it) + result + +template toSeq*(iter: expr): expr {.immediate.} = + ## Transforms any iterator into a sequence. + ## + ## Example: + ## + ## .. code-block:: nimrod + ## let + ## numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] + ## odd_numbers = toSeq(filter(numeric) do (x: int) -> bool: + ## if x mod 2 == 1: + ## result = true) + ## assert odd_numbers == @[1, 3, 5, 7, 9] + ## + var result {.gensym.}: seq[type(iter)] = @[] + for x in iter: add(result, x) + result + +when isMainModule: + import strutils + proc toStr(x: int): string {.procvar.} = $x + # concat test + let + s1 = @[1, 2, 3] + s2 = @[4, 5] + s3 = @[6, 7] + total = concat(s1, s2, s3) + assert total == @[1, 2, 3, 4, 5, 6, 7] + + # duplicates test + let + dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] + dup2 = @["a", "a", "c", "d", "d"] + unique1 = distnct(dup1) + unique2 = distnct(dup2) + assert unique1 == @[1, 3, 4, 2, 8] + assert unique2 == @["a", "c", "d"] + + # zip test + let + short = @[1, 2, 3] + long = @[6, 5, 4, 3, 2, 1] + words = @["one", "two", "three"] + zip1 = zip(short, long) + zip2 = zip(short, words) + assert zip1 == @[(1, 6), (2, 5), (3, 4)] + assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] + assert zip1[2].b == 4 + assert zip2[2].b == "three" + + # filter proc test + let + colors = @["red", "yellow", "black"] + f1 = filter(colors, proc(x: string): bool = x.len < 6) + f2 = filter(colors) do (x: string) -> bool : x.len > 5 + assert f1 == @["red", "black"] + assert f2 == @["yellow"] + + # filter iterator test + let numbers = @[1, 4, 5, 8, 9, 7, 4] + for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): + echo($n) + # echoes 4, 8, 4 in separate lines + + # filterIt test + let + temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] + acceptable = filterIt(temperatures, it < 50 and it > -10) + assert acceptable == @[-2.0, 24.5, 44.31] + + # toSeq test + let + numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] + odd_numbers = toSeq(filter(numeric) do (x: int) -> bool: + if x mod 2 == 1: + result = true) + assert odd_numbers == @[1, 3, 5, 7, 9] + echo "Finished doc tests" diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 3b252f1d2..4290af08a 100755 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -308,6 +308,52 @@ proc `$`*[A, B](t: TOrderedTable[A, B]): string = ## The `$` operator for ordered hash tables. dollarImpl() +proc sort*[A, B](t: var TOrderedTable[A, B], + cmp: proc (x,y: tuple[key: A, val: B]): int) = + ## sorts `t` according to `cmp`. This modifies the internal list + ## that kept the insertion order, so insertion order is lost after this + ## call but key lookup and insertions remain possible after `sort` (in + ## contrast to the `sort` for count tables). + var list = t.first + var + p, q, e, tail, oldhead: int + nmerges, psize, qsize, i: int + if t.counter == 0: return + var insize = 1 + while true: + p = list; oldhead = list + list = -1; tail = -1; nmerges = 0 + while p >= 0: + inc(nmerges) + q = p + psize = 0 + i = 0 + while i < insize: + inc(psize) + q = t.data[q].next + if q < 0: break + inc(i) + qsize = insize + while psize > 0 or (qsize > 0 and q >= 0): + if psize == 0: + e = q; q = t.data[q].next; dec(qsize) + elif qsize == 0 or q < 0: + e = p; p = t.data[p].next; dec(psize) + elif cmp((t.data[p].key, t.data[p].val), + (t.data[q].key, t.data[q].val)) <= 0: + e = p; p = t.data[p].next; dec(psize) + else: + e = q; q = t.data[q].next; dec(qsize) + if tail >= 0: t.data[tail].next = e + else: list = e + tail = e + p = q + t.data[tail].next = -1 + if nmerges <= 1: break + insize = insize * 2 + t.first = list + t.last = tail + # ------------------------------ count tables ------------------------------- type diff --git a/lib/pure/dynlib.nim b/lib/pure/dynlib.nim index 3ade1cbf9..a64b7f138 100755 --- a/lib/pure/dynlib.nim +++ b/lib/pure/dynlib.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2009 Andreas Rumpf +# (c) Copyright 2012 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -18,6 +18,10 @@ proc LoadLib*(path: string): TLibHandle ## loads a library from `path`. Returns nil if the library could not ## be loaded. +proc LoadLib*(): TLibHandle + ## gets the handle from the current executable. Returns nil if the + ## library could not be loaded. + proc UnloadLib*(lib: TLibHandle) ## unloads the library `lib` @@ -57,6 +61,7 @@ when defined(posix): importc, header: "<dlfcn.h>".} proc LoadLib(path: string): TLibHandle = return dlopen(path, RTLD_NOW) + proc LoadLib(): TLibHandle = return dlopen(nil, RTLD_NOW) proc UnloadLib(lib: TLibHandle) = dlclose(lib) proc symAddr(lib: TLibHandle, name: cstring): pointer = return dlsym(lib, name) @@ -78,6 +83,8 @@ elif defined(windows) or defined(dos): proc LoadLib(path: string): TLibHandle = result = cast[TLibHandle](winLoadLibrary(path)) + proc LoadLib(): TLibHandle = + result = cast[TLibHandle](winLoadLibrary(nil)) proc UnloadLib(lib: TLibHandle) = FreeLibrary(cast[THINSTANCE](lib)) proc symAddr(lib: TLibHandle, name: cstring): pointer = diff --git a/lib/pure/encodings.nim b/lib/pure/encodings.nim index 6421b6f44..ce4238409 100755 --- a/lib/pure/encodings.nim +++ b/lib/pure/encodings.nim @@ -254,6 +254,8 @@ when defined(windows): else: when defined(haiku): const iconvDll = "(libc.so.6|libiconv.so|libtextencoding.so)" + elif defined(macosx): + const iconvDll = "libiconv.dylib" else: const iconvDll = "(libc.so.6|libiconv.so)" @@ -449,8 +451,13 @@ proc convert*(s: string, destEncoding = "UTF-8", close(c) when IsMainModule: - var orig = "öäüß" - var crap = convert(orig, "CP1252", "UTF-8") - echo convert(crap, "ibm850", "CP1252") - echo getCurrentEncoding() + let + orig = "öäüß" + cp1252 = convert(orig, "CP1252", "UTF-8") + ibm850 = convert(cp1252, "ibm850", "CP1252") + current = getCurrentEncoding() + echo "Original string from source code: ", orig + echo "Forced ibm850 encoding: ", ibm850 + echo "Current encoding: ", current + echo "From ibm850 to current: ", convert(ibm850, current, "ibm850") diff --git a/lib/pure/fsmonitor.nim b/lib/pure/fsmonitor.nim index 92a80425a..a554cf963 100644 --- a/lib/pure/fsmonitor.nim +++ b/lib/pure/fsmonitor.nim @@ -17,9 +17,7 @@ ## module will therefore not work with any Linux kernel prior to that, unless ## it has been patched to support inotify. -when defined(windows): - {.error: "Windows is not yet supported by this module.".} -elif defined(linux): +when defined(linux) or defined(nimdoc): from posix import read else: {.error: "Your platform is not supported.".} diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim index 410c5e8bc..b61793866 100644 --- a/lib/pure/ftpclient.nim +++ b/lib/pure/ftpclient.nim @@ -26,6 +26,10 @@ import sockets, strutils, parseutils, times, os, asyncio ## var ftp = FTPClient("example.org", user = "user", pass = "pass") ## ftp.connect() ## ftp.retrFile("file.ext", "file.ext") +## +## **Warning:** The API of this module is unstable, and therefore is subject +## to change. + type TFTPClient* = object of TObject @@ -92,8 +96,9 @@ type EFTP* = object of ESynch proc FTPClient*(address: string, port = TPort(21), - user, pass = ""): TFTPClient = - ## Create a ``TFTPClient`` object. + user, pass = ""): PFTPClient = + ## Create a ``PFTPClient`` object. + new(result) result.user = user result.pass = pass result.address = address @@ -109,7 +114,7 @@ proc getDSock(ftp: PFTPClient): TSocket = proc getCSock(ftp: PFTPClient): TSocket = if ftp.isAsync: return ftp.asyncCSock else: return ftp.csock -template blockingOperation(sock: TSocket, body: stmt) = +template blockingOperation(sock: TSocket, body: stmt) {.immediate.} = if ftp.isAsync: sock.setBlocking(true) body @@ -274,11 +279,20 @@ proc getLines(ftp: PFTPClient, async: bool = false): bool = ## It doesn't if `async` is true, because it doesn't check for 226 then. if ftp.dsockConnected: var r = TaintedString"" - if getDSock(ftp).recvAsync(r): - if r.string != "": - ftp.job.lines.add(r.string) - else: - ftp.dsockConnected = False + if ftp.isAsync: + if ftp.asyncDSock.recvLine(r): + if r.string == "": + ftp.dsockConnected = false + else: + ftp.job.lines.add(r.string & "\n") + else: + assert(not async) + if ftp.dsock.recvLine(r): + if r.string == "": + ftp.dsockConnected = false + else: + ftp.job.lines.add(r.string & "\n") + else: OSError() if not async: var readSocks: seq[TSocket] = @[ftp.getCSock()] @@ -385,7 +399,7 @@ proc list*(ftp: PFTPClient, dir: string = "", async = false): string = proc retrText*(ftp: PFTPClient, file: string, async = false): string = ## Retrieves ``file``. File must be ASCII text. ## If ``async`` is true, this function will return immediately and - ## it will be your job to call ``poll`` to progress this operation. + ## it will be your job to call asyncio's ``poll`` to progress this operation. ftp.createJob(getLines, JRetrText) ftp.pasv() assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"] @@ -400,12 +414,14 @@ proc retrText*(ftp: PFTPClient, file: string, async = false): string = proc getFile(ftp: PFTPClient, async = false): bool = if ftp.dsockConnected: var r = "".TaintedString + var bytesRead = 0 var returned = false if async: if not ftp.isAsync: raise newException(EFTP, "FTPClient must be async.") - returned = ftp.AsyncDSock.recvAsync(r) + bytesRead = ftp.AsyncDSock.recvAsync(r, BufferSize) + returned = bytesRead != -1 else: - r = getDSock(ftp).recv() + bytesRead = getDSock(ftp).recv(r, BufferSize) returned = true let r2 = r.string if r2 != "": @@ -425,8 +441,9 @@ proc getFile(ftp: PFTPClient, async = false): bool = proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) = ## Downloads ``file`` and saves it to ``dest``. Usage of this function ## asynchronously is recommended to view the progress of the download. - ## The ``EvRetr`` event is given by ``poll`` when the download is finished, - ## and the ``filename`` field will be equal to ``file``. + ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function + ## when the download is finished, and the ``filename`` field will be equal + ## to ``file``. ftp.createJob(getFile, JRetr) ftp.job.file = open(dest, mode = fmWrite) ftp.pasv() @@ -450,11 +467,13 @@ proc doUpload(ftp: PFTPClient, async = false): bool = if ftp.dsockConnected: if ftp.job.toStore.len() > 0: assert(async) - if ftp.asyncDSock.sendAsync(ftp.job.toStore): + let bytesSent = ftp.asyncDSock.sendAsync(ftp.job.toStore) + if bytesSent == ftp.job.toStore.len: ftp.job.toStore = "" - ftp.job.progress.inc(ftp.job.toStore.len) - ftp.job.oneSecond.inc(ftp.job.toStore.len) - + elif bytesSent != ftp.job.toStore.len and bytesSent != 0: + ftp.job.toStore = ftp.job.toStore[bytesSent .. -1] + ftp.job.progress.inc(bytesSent) + ftp.job.oneSecond.inc(bytesSent) else: var s = newStringOfCap(4000) var len = ftp.job.file.readBuffer(addr(s[0]), 4000) @@ -472,8 +491,12 @@ proc doUpload(ftp: PFTPClient, async = false): bool = if not async: getDSock(ftp).send(s) else: - if not ftp.asyncDSock.sendAsync(s): - ftp.job.toStore = s + let bytesSent = ftp.asyncDSock.sendAsync(s) + if bytesSent == 0: + ftp.job.toStore.add(s) + elif bytesSent != s.len: + ftp.job.toStore.add(s[bytesSent .. -1]) + len = bytesSent ftp.job.progress.inc(len) ftp.job.oneSecond.inc(len) @@ -482,8 +505,9 @@ proc store*(ftp: PFTPClient, file, dest: string, async = false) = ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this ## function asynchronously is recommended to view the progress of ## the download. - ## The ``EvStore`` event is given by ``poll`` when the upload is finished, - ## and the ``filename`` field will be equal to ``file``. + ## The ``EvStore`` event is passed to the specified ``handleEvent`` function + ## when the upload is finished, and the ``filename`` field will be + ## equal to ``file``. ftp.createJob(doUpload, JStore) ftp.job.file = open(file) ftp.job.total = ftp.job.file.getFileSize() @@ -508,16 +532,6 @@ proc close*(ftp: PFTPClient) = ftp.csock.close() ftp.dsock.close() -discard """proc getSocket(h: PObject): tuple[info: TInfo, sock: TSocket] = - result = (SockIdle, InvalidSocket) - var ftp = PAsyncFTPClient(h) - if ftp.jobInProgress: - case ftp.job.typ - of JRetrText, JRetr, JStore: - if ftp.dsockStatus == SockConnecting or ftp.dsockStatus == SockConnected: - result = (ftp.dsockStatus, ftp.dsock) - else: result = (SockIdle, ftp.dsock)""" - proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = if ftp.jobInProgress: assertReply ftp.expectReply(), "226" # Make sure the transfer completed. @@ -540,32 +554,6 @@ proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = ftp.handleEvent(ftp, r) -discard """proc handleConnect(h: PObject) = - var ftp = PAsyncFTPClient(h) - ftp.dsockStatus = SockConnected - assert(ftp.jobInProgress) - if ftp.job.typ == JStore: - ftp.dele.mode = MWriteable - else: - ftp.dele.mode = MReadable""" - -discard """proc handleRead(h: PObject) = - var ftp = PAsyncFTPClient(h) - assert(ftp.jobInProgress) - assert(ftp.job.typ != JStore) - # This can never return true, because it shouldn't check for code - # 226 from csock. - assert(not ftp.job.prc(ftp[], true)) -""" - -discard """proc csockGetSocket(h: PObject): tuple[info: TInfo, sock: TSocket] = - # This only returns the csock if a job is in progress. Otherwise handle read - # would capture data which is not for it to capture. - result = (SockIdle, InvalidSocket) - var ftp = PAsyncFTPClient(h) - if ftp.jobInProgress: - result = (SockConnected, ftp.csock)""" - proc AsyncFTPClient*(address: string, port = TPort(21), user, pass = "", handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} = diff --git a/lib/pure/htmlparser.nim b/lib/pure/htmlparser.nim index 4136ecf57..ad952fc41 100755 --- a/lib/pure/htmlparser.nim +++ b/lib/pure/htmlparser.nim @@ -123,7 +123,7 @@ type tagVar ## the HTML ``var`` element const - tagStrs = [ + tagToStr* = [ "a", "abbr", "acronym", "address", "applet", "area", "b", "base", "basefont", "bdo", "big", "blockquote", "body", "br", "button", "caption", "center", "cite", "code", @@ -243,13 +243,13 @@ proc binaryStrSearch(x: openarray[string], y: string): int = proc htmlTag*(n: PXmlNode): THtmlTag = ## gets `n`'s tag as a ``THtmlTag``. if n.clientData == 0: - n.clientData = binaryStrSearch(tagStrs, n.tag)+1 + n.clientData = binaryStrSearch(tagToStr, n.tag)+1 result = THtmlTag(n.clientData) proc htmlTag*(s: string): THtmlTag = ## converts `s` to a ``THtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is ## returned. - result = THtmlTag(binaryStrSearch(tagStrs, s.toLower)+1) + result = THtmlTag(binaryStrSearch(tagToStr, s.toLower)+1) proc entityToUtf8*(entity: string): string = ## converts an HTML entity name like ``Ü`` to its UTF-8 equivalent. diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 184bca867..5be4af8a4 100755 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -80,62 +80,43 @@ proc fileError(msg: string) = e.msg = msg raise e -proc charAt(d: var string, i: var int, s: TSocket): char {.inline.} = - result = d[i] - while result == '\0': - d = string(s.recv()) - i = 0 - result = d[i] - proc parseChunks(s: TSocket): string = - # get chunks: - var i = 0 result = "" - var d = s.recv().string + var ri = 0 while true: + var chunkSizeStr = "" var chunkSize = 0 - var digitFound = false - while true: - case d[i] - of '0'..'9': - digitFound = true - chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('0')) - of 'a'..'f': - digitFound = true - chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('a') + 10) - of 'A'..'F': - digitFound = true - chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('A') + 10) - of '\0': - d = string(s.recv()) - i = -1 - else: break - inc(i) - if not digitFound: httpError("Chunksize expected") + if s.recvLine(chunkSizeStr): + var i = 0 + if chunkSizeStr == "": + httpError("Server terminated connection prematurely") + while true: + case chunkSizeStr[i] + of '0'..'9': + chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0')) + of 'a'..'f': + chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10) + of 'A'..'F': + chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10) + of '\0': + break + of ';': + # http://tools.ietf.org/html/rfc2616#section-3.6.1 + # We don't care about chunk-extensions. + break + else: + httpError("Invalid chunk size: " & chunkSizeStr) + inc(i) if chunkSize <= 0: break - while charAt(d, i, s) notin {'\C', '\L', '\0'}: inc(i) - if charAt(d, i, s) == '\C': inc(i) - if charAt(d, i, s) == '\L': inc(i) - else: httpError("CR-LF after chunksize expected") - - var x = substr(d, i, i+chunkSize-1) - var size = x.len - result.add(x) - inc(i, size) - if size < chunkSize: - # read in the rest: - var missing = chunkSize - size - var L = result.len - setLen(result, L + missing) - while missing > 0: - var bytesRead = s.recv(addr(result[L]), missing) - inc(L, bytesRead) - dec(missing, bytesRead) - # next chunk: - d = string(s.recv()) - i = 0 - # skip trailing CR-LF: - while charAt(d, i, s) in {'\C', '\L'}: inc(i) + result.setLen(ri+chunkSize) + var bytesRead = 0 + while bytesRead != chunkSize: + let ret = recv(s, addr(result[ri]), chunkSize-bytesRead) + ri += ret + bytesRead += ret + s.skip(2) # Skip \c\L + # Trailer headers will only be sent if the request specifies that we want + # them: http://tools.ietf.org/html/rfc2616#section-3.6.1 proc parseBody(s: TSocket, headers: PStringTable): string = @@ -238,12 +219,18 @@ type httpCONNECT ## Converts the request connection to a transparent ## TCP/IP tunnel, usually used for proxies. +when not defined(ssl): + type PSSLContext = ref object + let defaultSSLContext: PSSLContext = nil +else: + let defaultSSLContext = newContext(verifyMode = CVerifyNone) + proc request*(url: string, httpMethod = httpGET, extraHeaders = "", - body = ""): TResponse = + body = "", + sslContext: PSSLContext = defaultSSLContext): TResponse = ## | Requests ``url`` with the specified ``httpMethod``. ## | Extra headers can be specified and must be seperated by ``\c\L`` var r = parseUrl(url) - var headers = substr($httpMethod, len("http")) headers.add(" /" & r.path & r.query) @@ -257,7 +244,7 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "", var port = TPort(80) if r.scheme == "https": when defined(ssl): - s.wrapSocket(verifyMode = CVerifyNone) + sslContext.wrapSocket(s) else: raise newException(EHttpRequestErr, "SSL support was not compiled in. Cannot connect over SSL.") port = TPort(443) @@ -277,32 +264,32 @@ proc redirection(status: string): bool = if status.startsWith(i): return True -proc get*(url: string, maxRedirects = 5): TResponse = - ## | GET's the ``url`` and returns a ``TResponse`` object +proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse = + ## | GETs the ``url`` and returns a ``TResponse`` object ## | This proc also handles redirection result = request(url) for i in 1..maxRedirects: if result.status.redirection(): var locationHeader = result.headers["Location"] if locationHeader == "": httpError("location header expected") - result = request(locationHeader) + result = request(locationHeader, sslContext = sslContext) -proc getContent*(url: string): string = - ## | GET's the body and returns it as a string. +proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): string = + ## | GETs the body and returns it as a string. ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` - var r = get(url) + var r = get(url, sslContext = sslContext) if r.status[0] in {'4','5'}: raise newException(EHTTPRequestErr, r.status) else: return r.body proc post*(url: string, extraHeaders = "", body = "", - maxRedirects = 5): TResponse = - ## | POST's ``body`` to the ``url`` and returns a ``TResponse`` object. + maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse = + ## | POSTs ``body`` to the ``url`` and returns a ``TResponse`` object. ## | This proc adds the necessary Content-Length header. ## | This proc also handles redirection. var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L" - result = request(url, httpPOST, xh, body) + result = request(url, httpPOST, xh, body, sslContext) for i in 1..maxRedirects: if result.status.redirection(): var locationHeader = result.headers["Location"] @@ -310,8 +297,9 @@ proc post*(url: string, extraHeaders = "", body = "", var meth = if result.status != "307": httpGet else: httpPost result = request(locationHeader, meth, xh, body) -proc postContent*(url: string, extraHeaders = "", body = ""): string = - ## | POST's ``body`` to ``url`` and returns the response's body as a string +proc postContent*(url: string, extraHeaders = "", body = "", + sslContext: PSSLContext = defaultSSLContext): string = + ## | POSTs ``body`` to ``url`` and returns the response's body as a string ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` var r = post(url, extraHeaders, body) if r.status[0] in {'4','5'}: @@ -319,11 +307,12 @@ proc postContent*(url: string, extraHeaders = "", body = ""): string = else: return r.body -proc downloadFile*(url: string, outputFilename: string) = +proc downloadFile*(url: string, outputFilename: string, + sslContext: PSSLContext = defaultSSLContext) = ## Downloads ``url`` and saves it to ``outputFilename`` var f: TFile if open(f, outputFilename, fmWrite): - f.write(getContent(url)) + f.write(getContent(url, sslContext)) f.close() else: fileError("Unable to open file") diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim index 9d9ea2012..ce816b7d4 100755 --- a/lib/pure/httpserver.nim +++ b/lib/pure/httpserver.nim @@ -22,6 +22,8 @@ ## ## run(handleRequest, TPort(80)) ## +## **Warning:** The API of this module is unstable, and therefore is subject +## to change. import parseutils, strutils, os, osproc, strtabs, streams, sockets, asyncio diff --git a/lib/pure/irc.nim b/lib/pure/irc.nim index 81a1befb5..aa4e2d557 100644 --- a/lib/pure/irc.nim +++ b/lib/pure/irc.nim @@ -16,17 +16,23 @@ ## the amount of lag. ## ## .. code-block:: Nimrod -## var client = irc("irc.server.net", joinChans = @["#channel"]) +## +## var client = irc("picheta.me", joinChans = @["#bots"]) ## client.connect() ## while True: ## var event: TIRCEvent ## if client.poll(event): ## case event.typ -## of EvDisconnected: break +## of EvConnected: nil +## of EvDisconnected: +## client.reconnect() ## of EvMsg: -## # Where all the magic happens. +## # Write your message reading code here. +## +## **Warning:** The API of this module is unstable, and therefore is subject +## to change. -import sockets, strutils, parseutils, times, asyncio +import sockets, strutils, parseutils, times, asyncio, os type TIRC* = object of TObject @@ -34,11 +40,15 @@ type port: TPort nick, user, realname, serverPass: string case isAsync: bool - of false: - sock: TSocket of true: - handleEvent: proc (irc: var TAsyncIRC, ev: TIRCEvent) {.closure.} + handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.} asyncSock: PAsyncSocket + myDispatcher: PDispatcher + of false: + dummyA: pointer + dummyB: pointer # workaround a Nimrod API issue + dummyC: pointer + sock: TSocket status: TInfo lastPing: float lastPong: float @@ -46,6 +56,9 @@ type channelsToJoin: seq[string] msgLimit: bool messageBuffer: seq[tuple[timeToSend: float, m: string]] + lastReconnect: float + + PIRC* = ref TIRC PAsyncIRC* = ref TAsyncIRC TAsyncIRC* = object of TIRC @@ -68,9 +81,13 @@ type MError TIRCEventType* = enum - EvMsg, EvDisconnected + EvMsg, EvConnected, EvDisconnected TIRCEvent* = object ## IRC Event case typ*: TIRCEventType + of EvConnected: + ## Connected to server. + ## Only occurs with AsyncIRC. + nil of EvDisconnected: ## Disconnected from the server nil @@ -82,7 +99,7 @@ type origin*: string ## The channel/user that this msg originated from raw*: string ## Raw IRC message -proc send*(irc: var TIRC, message: string, sendImmediately = false) = +proc send*(irc: PIRC, message: string, sendImmediately = false) = ## Sends ``message`` as a raw command. It adds ``\c\L`` for you. var sendMsg = true if irc.msgLimit and not sendImmediately: @@ -104,15 +121,15 @@ proc send*(irc: var TIRC, message: string, sendImmediately = false) = # but I can't exactly check for EBrokenPipe. irc.status = SockClosed -proc privmsg*(irc: var TIRC, target, message: string) = +proc privmsg*(irc: PIRC, target, message: string) = ## Sends ``message`` to ``target``. ``Target`` can be a channel, or a user. irc.send("PRIVMSG $1 :$2" % [target, message]) -proc notice*(irc: var TIRC, target, message: string) = +proc notice*(irc: PIRC, target, message: string) = ## Sends ``notice`` to ``target``. ``Target`` can be a channel, or a user. irc.send("NOTICE $1 :$2" % [target, message]) -proc join*(irc: var TIRC, channel: string, key = "") = +proc join*(irc: PIRC, channel: string, key = "") = ## Joins ``channel``. ## ## If key is not ``""``, then channel is assumed to be key protected and this @@ -122,16 +139,19 @@ proc join*(irc: var TIRC, channel: string, key = "") = else: irc.send("JOIN " & channel & " " & key) -proc part*(irc: var TIRC, channel, message: string) = +proc part*(irc: PIRC, channel, message: string) = ## Leaves ``channel`` with ``message``. irc.send("PART " & channel & " :" & message) -proc close*(irc: var TIRC) = +proc close*(irc: PIRC) = ## Closes connection to an IRC server. ## ## **Warning:** This procedure does not send a ``QUIT`` message to the server. irc.status = SockClosed - irc.sock.close() + if irc.isAsync: + irc.asyncSock.close() + else: + irc.sock.close() proc isNumber(s: string): bool = ## Checks if `s` contains only numbers. @@ -202,12 +222,11 @@ proc parseMessage(msg: string): TIRCEvent = inc(i) # Skip `:`. result.params.add(msg[i..msg.len-1]) -proc connect*(irc: var TIRC) = +proc connect*(irc: PIRC) = ## Connects to an IRC server as specified by ``irc``. assert(irc.address != "") assert(irc.port != TPort(0)) - irc.sock = socket() irc.sock.connect(irc.address, irc.port) irc.status = SockConnected @@ -217,13 +236,28 @@ proc connect*(irc: var TIRC) = irc.send("NICK " & irc.nick, true) irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) +proc reconnect*(irc: PIRC, timeout = 5000) = + ## Reconnects to an IRC server. + ## + ## ``Timeout`` specifies the time to wait in miliseconds between multiple + ## consecutive reconnections. + ## + ## This should be used when an ``EvDisconnected`` event occurs. + let secSinceReconnect = int(epochTime() - irc.lastReconnect) + if secSinceReconnect < timeout: + sleep(timeout - secSinceReconnect) + irc.sock = socket() + irc.connect() + irc.lastReconnect = epochTime() + proc irc*(address: string, port: TPort = 6667.TPort, nick = "NimrodBot", user = "NimrodBot", realname = "NimrodBot", serverPass = "", joinChans: seq[string] = @[], - msgLimit: bool = true): TIRC = + msgLimit: bool = true): PIRC = ## Creates a ``TIRC`` object. + new(result) result.address = address result.port = port result.nick = nick @@ -237,8 +271,9 @@ proc irc*(address: string, port: TPort = 6667.TPort, result.msgLimit = msgLimit result.messageBuffer = @[] result.status = SockIdle + result.sock = socket() -proc processLine(irc: var TIRC, line: string): TIRCEvent = +proc processLine(irc: PIRC, line: string): TIRCEvent = if line.len == 0: irc.close() result.typ = EvDisconnected @@ -271,7 +306,7 @@ proc processLine(irc: var TIRC, line: string): TIRCEvent = if result.nick == irc.nick: irc.nick = result.params[0] -proc processOther(irc: var TIRC, ev: var TIRCEvent): bool = +proc processOther(irc: PIRC, ev: var TIRCEvent): bool = result = false if epochTime() - irc.lastPing >= 20.0: irc.lastPing = epochTime() @@ -290,7 +325,7 @@ proc processOther(irc: var TIRC, ev: var TIRCEvent): bool = break # messageBuffer is guaranteed to be from the quickest to the # later-est. -proc poll*(irc: var TIRC, ev: var TIRCEvent, +proc poll*(irc: PIRC, ev: var TIRCEvent, timeout: int = 500): bool = ## This function parses a single message from the IRC server and returns ## a TIRCEvent. @@ -316,46 +351,32 @@ proc poll*(irc: var TIRC, ev: var TIRCEvent, if processOther(irc, ev): result = true -proc getLag*(irc: var TIRC): float = +proc getLag*(irc: PIRC): float = ## Returns the latency between this client and the IRC server in seconds. ## ## If latency is unknown, returns -1.0. return irc.lag -proc isConnected*(irc: var TIRC): bool = +proc isConnected*(irc: PIRC): bool = ## Returns whether this IRC client is connected to an IRC server. return irc.status == SockConnected -proc getNick*(irc: var TIRC): string = +proc getNick*(irc: PIRC): string = ## Returns the current nickname of the client. return irc.nick # -- Asyncio dispatcher -proc connect*(irc: PAsyncIRC) = - ## Equivalent of connect for ``TIRC`` but specifically created for asyncio. - assert(irc.address != "") - assert(irc.port != TPort(0)) - - irc.asyncSock = AsyncSocket() - irc.asyncSock.connect(irc.address, irc.port) - proc handleConnect(s: PAsyncSocket, irc: PAsyncIRC) = # Greet the server :) - if irc.serverPass != "": irc[].send("PASS " & irc.serverPass, true) - irc[].send("NICK " & irc.nick, true) - irc[].send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) - -discard """proc handleConnect(h: PObject) = - var irc = PAsyncIRC(h) - - # Greet the server :) - if irc.serverPass != "": irc[].send("PASS " & irc.serverPass, true) - irc[].send("NICK " & irc.nick, true) - irc[].send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) - + if irc.serverPass != "": irc.send("PASS " & irc.serverPass, true) + irc.send("NICK " & irc.nick, true) + irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true) irc.status = SockConnected -""" + + var ev: TIRCEvent + ev.typ = EvConnected + irc.handleEvent(irc, ev) proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) = var line = "".TaintedString @@ -363,42 +384,55 @@ proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) = if ret: if line == "": var ev: TIRCEvent - irc[].close() + irc.close() ev.typ = EvDisconnected - irc[].handleEvent(irc[], ev) + irc.handleEvent(irc, ev) else: - var ev = irc[].processLine(line.string) - irc[].handleEvent(irc[], ev) - -discard """proc handleRead(h: PObject) = - var irc = PAsyncIRC(h) - var line = "".TaintedString - var ret = irc.sock.recvLineAsync(line) - case ret - of RecvFullLine: - var ev = irc[].processLine(irc.lineBuffer.string & line.string) - irc.handleEvent(irc[], ev, irc.userArg) - irc.lineBuffer = "".TaintedString - of RecvPartialLine: - if line.string != "": - string(irc.lineBuffer).add(line.string) - of RecvDisconnected: - var ev: TIRCEvent - irc[].close() - ev.typ = EvDisconnected - irc.handleEvent(irc[], ev, irc.userArg) - of RecvFail: nil""" + var ev = irc.processLine(line.string) + irc.handleEvent(irc, ev) proc handleTask(s: PAsyncSocket, irc: PAsyncIRC) = var ev: TIRCEvent - if irc[].processOther(ev): - irc.handleEvent(irc[], ev) + if irc.processOther(ev): + irc.handleEvent(irc, ev) + +proc register*(d: PDispatcher, irc: PAsyncIRC) = + ## Registers ``irc`` with dispatcher ``d``. + irc.asyncSock.handleConnect = + proc (s: PAsyncSocket) = + handleConnect(s, irc) + irc.asyncSock.handleRead = + proc (s: PAsyncSocket) = + handleRead(s, irc) + irc.asyncSock.handleTask = + proc (s: PAsyncSocket) = + handleTask(s, irc) + d.register(irc.asyncSock) + irc.myDispatcher = d + +proc connect*(irc: PAsyncIRC) = + ## Equivalent of connect for ``TIRC`` but specifically created for asyncio. + assert(irc.address != "") + assert(irc.port != TPort(0)) -discard """proc handleTask(h: PObject) = - var irc = PAsyncIRC(h) - var ev: TIRCEvent - if PAsyncIRC(h)[].processOther(ev): - irc.handleEvent(irc[], ev, irc.userArg)""" + irc.asyncSock.connect(irc.address, irc.port) + +proc reconnect*(irc: PAsyncIRC, timeout = 5000) = + ## Reconnects to an IRC server. + ## + ## ``Timeout`` specifies the time to wait in miliseconds between multiple + ## consecutive reconnections. + ## + ## This should be used when an ``EvDisconnected`` event occurs. + ## + ## When successfully reconnected an ``EvConnected`` event will occur. + let secSinceReconnect = int(epochTime() - irc.lastReconnect) + if secSinceReconnect < timeout: + sleep(timeout - secSinceReconnect) + irc.asyncSock = AsyncSocket() + irc.myDispatcher.register(irc) + irc.connect() + irc.lastReconnect = epochTime() proc asyncIRC*(address: string, port: TPort = 6667.TPort, nick = "NimrodBot", @@ -406,7 +440,7 @@ proc asyncIRC*(address: string, port: TPort = 6667.TPort, realname = "NimrodBot", serverPass = "", joinChans: seq[string] = @[], msgLimit: bool = true, - ircEvent: proc (irc: var TAsyncIRC, ev: TIRCEvent) {.closure.} + ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.} ): PAsyncIRC = ## Use this function if you want to use asyncio's dispatcher. ## @@ -429,19 +463,7 @@ proc asyncIRC*(address: string, port: TPort = 6667.TPort, result.msgLimit = msgLimit result.messageBuffer = @[] result.handleEvent = ircEvent - -proc register*(d: PDispatcher, irc: PAsyncIRC) = - ## Registers ``irc`` with dispatcher ``d``. - irc.asyncSock.handleConnect = - proc (s: PAsyncSocket) = - handleConnect(s, irc) - irc.asyncSock.handleRead = - proc (s: PAsyncSocket) = - handleRead(s, irc) - irc.asyncSock.handleTask = - proc (s: PAsyncSocket) = - handleTask(s, irc) - d.register(irc.asyncSock) + result.asyncSock = AsyncSocket() when isMainModule: #var m = parseMessage("ERROR :Closing Link: dom96.co.cc (Ping timeout: 252 seconds)") @@ -456,6 +478,8 @@ when isMainModule: var event: TIRCEvent if client.poll(event): case event.typ + of EvConnected: + nil of EvDisconnected: break of EvMsg: diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 941d88dfc..d2516adfb 100755 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -13,6 +13,23 @@ ## (unlike XML). It is easy for machines to parse and generate. ## JSON is based on a subset of the JavaScript Programming Language, ## Standard ECMA-262 3rd Edition - December 1999. +## +## Usage example: +## +## .. code-block:: nimrod +## let +## small_json = """{"test": 1.3, "key2": true}""" +## jobj = parseJson(small_json) +## assert (jobj.kind == JObject) +## echo($jobj["test"].fnum) +## echo($jobj["key2"].bval) +## +## Results in: +## +## .. code-block:: nimrod +## +## 1.3000000000000000e+00 +## true import hashes, strutils, lexbase, streams, unicode @@ -524,6 +541,11 @@ proc newJString*(s: String): PJsonNode = result.kind = JString result.str = s +proc newJStringMove(s: String): PJsonNode = + new(result) + result.kind = JString + shallowCopy(result.str, s) + proc newJInt*(n: biggestInt): PJsonNode = ## Creates a new `JInt PJsonNode`. new(result) @@ -607,7 +629,7 @@ proc len*(n: PJsonNode): int = else: nil proc `[]`*(node: PJsonNode, name: String): PJsonNode = - ## Gets a field from a `JObject`. + ## Gets a field from a `JObject`. Returns nil if the key is not found. assert(node.kind == JObject) for key, item in items(node.fields): if key == name: @@ -792,7 +814,9 @@ proc parseJson(p: var TJsonParser): PJsonNode = ## Parses JSON from a JSON Parser `p`. case p.tok of tkString: - result = newJString(p.a) + # we capture 'p.a' here, so we need to give it a fresh buffer afterwards: + result = newJStringMove(p.a) + p.a = "" discard getTok(p) of tkInt: result = newJInt(parseBiggestInt(p.a)) @@ -893,6 +917,10 @@ when isMainModule: echo(parsed["keyÄÖöoßß"]) echo() echo(pretty(parsed2)) + try: + echo(parsed["key2"][12123]) + raise newException(EInvalidValue, "That line was expected to fail") + except EInvalidIndex: echo() discard """ while true: diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim index bffc4ebb6..f52b554cc 100755 --- a/lib/pure/marshal.nim +++ b/lib/pure/marshal.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 53594db62..f9ab6d0f8 100755 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -141,6 +141,11 @@ proc randomize*() ## number, i.e. a tickcount. Note: Does nothing for the ECMAScript target, ## as ECMAScript does not support this. +proc randomize*(seed: int) + ## initializes the random number generator with a specific seed. + ## Note: Does nothing for the ECMAScript target, + ## as ECMAScript does not support this. + when not defined(ECMAScript): proc sqrt*(x: float): float {.importc: "sqrt", header: "<math.h>".} ## computes the square root of `x`. @@ -190,15 +195,17 @@ when not defined(ECMAScript): proc rand(): cint {.importc: "rand", nodecl.} when not defined(windows): - proc srand48(seed: cint) {.importc: "srand48", nodecl.} + proc srand48(seed: clong) {.importc: "srand48", nodecl.} proc drand48(): float {.importc: "drand48", nodecl.} proc random(max: float): float = result = drand48() * max proc randomize() = - let x = gettime(nil) - srand(x) - when defined(srand48): srand48(x) + randomize(gettime(nil)) + + proc randomize(seed: int) = + srand(cint(seed)) + when defined(srand48): srand48(seed) proc random(max: int): int = result = int(rand()) mod max @@ -217,6 +224,7 @@ else: proc random(max: float): float = result = float(mathrandom() * float(max)) proc randomize() = nil + proc randomize(seed: int) = nil proc sqrt*(x: float): float {.importc: "Math.sqrt", nodecl.} proc ln*(x: float): float {.importc: "Math.log", nodecl.} @@ -301,3 +309,18 @@ proc standardDeviation*(s: TRunningStat): float = {.pop.} {.pop.} + +when isMainModule and not defined(ECMAScript): + # Verifies random seed initialization. + let seed = gettime(nil) + randomize(seed) + const SIZE = 10 + var buf : array[0..SIZE, int] + # Fill the buffer with random values + for i in 0..SIZE-1: + buf[i] = random(high(int)) + # Check that the second random calls are the same for each position. + randomize(seed) + for i in 0..SIZE-1: + assert buf[i] == random(high(int)), "non deterministic random seeding" + echo "random values equal after reseeding" diff --git a/lib/pure/os.nim b/lib/pure/os.nim index a220c1da4..01daf5ad6 100755 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -554,7 +554,8 @@ proc splitFile*(path: string): tuple[dir, name, ext: string] {. var dotPos = path.len for i in countdown(len(path)-1, 0): if path[i] == ExtSep: - if dotPos == path.len and i > 0: dotPos = i + if dotPos == path.len and i > 0 and + path[i-1] notin {dirsep, altsep}: dotPos = i elif path[i] in {dirsep, altsep}: sepPos = i break @@ -1448,7 +1449,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [FTime].} = else: var a, b: Ttimespec a.tv_sec = TTime(milsecs div 1000) - a.tv_nsec = (milsecs mod 1000) * 1000 + a.tv_nsec = (milsecs mod 1000) * 1000 * 1000 discard posix.nanosleep(a, b) proc getFileSize*(file: string): biggestInt {.rtl, extern: "nos$1", diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index a8c0ea809..ecf38f9f6 100755 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -201,9 +201,9 @@ proc execProcesses*(cmds: openArray[string], q[r] = startCmd(cmds[i], options=options) inc(i) if i > high(cmds): break - for i in 0..m-1: - if q[i] != nil: close(q[i]) - result = max(waitForExit(q[i]), result) + for j in 0..m-1: + if q[j] != nil: close(q[j]) + result = max(waitForExit(q[j]), result) else: for i in 0..high(cmds): var p = startCmd(cmds[i], options=options) diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index 921f9a3d8..bb6ea6768 100755 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -49,26 +49,35 @@ proc raiseNoOK(status: string) = raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) proc parseStatus(r: TRedis): TRedisStatus = - var line = r.socket.recv.string - - if line[0] == '-': - raise newException(ERedis, strip(line)) - if line[0] != '+': - raiseInvalidReply('+', line[0]) + var line = "" + if r.socket.recvLine(line): + if line == "": + raise newException(ERedis, "Server closed connection prematurely") - return line.substr(1, line.len-3) # Strip '+' and \c\L. + if line[0] == '-': + raise newException(ERedis, strip(line)) + if line[0] != '+': + raiseInvalidReply('+', line[0]) + + return line.substr(1) # Strip '+' + else: + OSError() proc parseInteger(r: TRedis): TRedisInteger = - var line = r.socket.recv.string - - if line[0] == '-': - raise newException(ERedis, strip(line)) - if line[0] != ':': - raiseInvalidReply(':', line[0]) - - # Strip ':' and \c\L. - if parseBiggestInt(line, result, 1) == 0: - raise newException(EInvalidReply, "Unable to parse integer.") + var line = "" + if r.socket.recvLine(line): + if line == "": + raise newException(ERedis, "Server closed connection prematurely") + + if line[0] == '-': + raise newException(ERedis, strip(line)) + if line[0] != ':': + raiseInvalidReply(':', line[0]) + + # Strip ':' + if parseBiggestInt(line, result, 1) == 0: + raise newException(EInvalidReply, "Unable to parse integer.") + else: OSError() proc recv(sock: TSocket, size: int): TaintedString = result = newString(size).TaintedString @@ -838,8 +847,11 @@ proc save*(r: TRedis) = proc shutdown*(r: TRedis) = ## Synchronously save the dataset to disk and then shut down the server r.sendCommand("SHUTDOWN") - var s = r.socket.recv() - if s.string.len != 0: raise newException(ERedis, s.string) + var s = "".TaintedString + if r.socket.recvLine(s): + if s.string.len != 0: raise newException(ERedis, s.string) + else: + OSError() proc slaveof*(r: TRedis, host: string, port: string) = ## Make the server a slave of another instance, or promote it as master diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index 44a579a7d..0f3b44e00 100755 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -23,6 +23,8 @@ ## ## run(handleRequest) ## +## **Warning:** The API of this module is unstable, and therefore is subject +## to change. import sockets, strutils, os, strtabs, asyncio diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 371641b06..f233e53c8 100755 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -14,6 +14,8 @@ ## For OpenSSL support compile with ``-d:ssl``. When using SSL be aware that ## most functions will then raise ``ESSL`` on SSL errors. +{.deadCodeElim: on.} + when hostos == "solaris": {.passl: "-lsocket -lnsl".} @@ -45,12 +47,15 @@ when defined(ssl): TSSLAcceptResult* = enum AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess +const + BufferSize*: int = 4000 ## size of a buffered socket's buffer + type TSocketImpl = object ## socket type fd: cint case isBuffered: bool # determines whether this socket is buffered. of true: - buffer: array[0..4000, char] + buffer: array[0..BufferSize, char] currPos: int # current index in buffer bufLen: int # current length of buffer of false: nil @@ -60,6 +65,8 @@ type sslHandle: PSSL sslContext: PSSLContext sslNoHandshake: bool # True if needs handshake. + sslHasPeekChar: bool + sslPeekChar: char of false: nil TSocket* = ref TSocketImpl @@ -256,7 +263,10 @@ when defined(ssl): of protSSLv23: newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support. of protSSLv2: - newCTX = SSL_CTX_new(SSLv2_method()) + when not defined(linux): + newCTX = SSL_CTX_new(SSLv2_method()) + else: + SSLError() of protSSLv3: newCTX = SSL_CTX_new(SSLv3_method()) of protTLSv1: @@ -286,12 +296,55 @@ when defined(ssl): socket.sslContext = ctx socket.sslHandle = SSLNew(PSSLCTX(socket.sslContext)) socket.sslNoHandshake = false + socket.sslHasPeekChar = false if socket.sslHandle == nil: SSLError() if SSLSetFd(socket.sslHandle, socket.fd) != 1: SSLError() +proc SocketError*(socket: TSocket, err: int = -1, async = false) = + ## Raises proper errors based on return values of ``recv`` functions. + ## + ## If ``async`` is ``True`` no error will be thrown in the case when the + ## error was caused by no data being available to be read. + ## + ## If ``err`` is not lower than 0 no exception will be raised. + when defined(ssl): + if socket.isSSL: + if err <= 0: + var ret = SSLGetError(socket.sslHandle, err.cint) + case ret + of SSL_ERROR_ZERO_RETURN: + SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.") + of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: + if async: + return + else: SSLError("Not enough data on socket.") + of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ: + if async: + return + else: SSLError("Not enough data on socket.") + of SSL_ERROR_WANT_X509_LOOKUP: + SSLError("Function for x509 lookup has been called.") + of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: + SSLError() + else: SSLError("Unknown Error") + + if err == -1 and not (when defined(ssl): socket.isSSL else: false): + if async: + when defined(windows): + # TODO: Test on Windows + var err = WSAGetLastError() + if err == WSAEWOULDBLOCK: + return + else: OSError() + else: + if errno == EAGAIN or errno == EWOULDBLOCK: + return + else: OSError() + else: OSError() + proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} = ## Marks ``socket`` as accepting connections. ## ``Backlog`` specifies the maximum length of the @@ -844,11 +897,8 @@ proc checkBuffer(readfds: var seq[TSocket]): int = var res: seq[TSocket] = @[] result = 0 for s in readfds: - if s.isBuffered: - if s.bufLen <= 0 or s.currPos == s.bufLen: - res.add(s) - else: - inc(result) + if hasDataBuffered(s): + inc(result) else: res.add(s) readfds = res @@ -970,47 +1020,76 @@ template retRead(flags, readBytes: int) = proc recv*(socket: TSocket, data: pointer, size: int): int {.tags: [FReadIO].} = ## receives data from a socket + if size == 0: return if socket.isBuffered: if socket.bufLen == 0: retRead(0'i32, 0) - when true: - var read = 0 - while read < size: - if socket.currPos >= socket.bufLen: - retRead(0'i32, read) - - let chunk = min(socket.bufLen-socket.currPos, size-read) - var d = cast[cstring](data) - copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk) - read.inc(chunk) - socket.currPos.inc(chunk) - else: - var read = 0 - while read < size: - if socket.currPos >= socket.bufLen: - retRead(0'i32, read) - - var d = cast[cstring](data) - d[read] = socket.buffer[socket.currPos] - read.inc(1) - socket.currPos.inc(1) + var read = 0 + while read < size: + if socket.currPos >= socket.bufLen: + retRead(0'i32, read) + let chunk = min(socket.bufLen-socket.currPos, size-read) + var d = cast[cstring](data) + copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk) + read.inc(chunk) + socket.currPos.inc(chunk) + result = read else: when defined(ssl): if socket.isSSL: - result = SSLRead(socket.sslHandle, data, size) + if socket.sslHasPeekChar: + copyMem(data, addr(socket.sslPeekChar), 1) + socket.sslHasPeekChar = false + if size-1 > 0: + var d = cast[cstring](data) + result = SSLRead(socket.sslHandle, addr(d[1]), size-1) + 1 + else: + result = 1 + else: + result = SSLRead(socket.sslHandle, data, size) else: result = recv(socket.fd, data, size.cint, 0'i32) else: result = recv(socket.fd, data, size.cint, 0'i32) +proc recv*(socket: TSocket, data: var string, size: int): int = + ## higher-level version of the above + ## + ## When 0 is returned the socket's connection has been closed. + ## + ## This function will throw an EOS exception when an error occurs. A value + ## lower than 0 is never returned. + ## + ## **Note**: ``data`` must be initialised. + data.setLen(size) + result = recv(socket, cstring(data), size) + if result < 0: + data.setLen(0) + socket.SocketError(result) + data.setLen(result) + +proc recvAsync*(socket: TSocket, data: var string, size: int): int = + ## Async version of the above. + ## + ## When socket is non-blocking and no data is available on the socket, + ## ``-1`` will be returned and ``data`` will be ``""``. + ## + ## **Note**: ``data`` must be initialised. + data.setLen(size) + result = recv(socket, cstring(data), size) + if result < 0: + data.setLen(0) + socket.SocketError(async = true) + result = -1 + data.setLen(result) + proc waitFor(socket: TSocket, waited: var float, timeout: int): int {. tags: [FTime].} = ## returns the number of characters available to be read. In unbuffered - ## sockets this is always 1, otherwise this may as big as the buffer, currently - ## 4000. + ## sockets this is always 1, otherwise this may as big as ``BufferSize``. result = 1 if socket.isBuffered and socket.bufLen != 0 and socket.bufLen != socket.currPos: result = socket.bufLen - socket.currPos @@ -1040,6 +1119,18 @@ proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int {. result = read +proc recv*(socket: TSocket, data: var string, size: int, timeout: int): int = + ## higher-level version of the above. + ## + ## Similar to the non-timeout version this will throw an EOS exception + ## when an error occurs. + data.setLen(size) + result = recv(socket, cstring(data), size, timeout) + if result < 0: + data.setLen(0) + socket.SocketError() + data.setLen(result) + proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} = if socket.isBuffered: result = 1 @@ -1052,8 +1143,12 @@ proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} = else: when defined(ssl): if socket.isSSL: - raise newException(ESSL, "Sorry, you cannot use recvLine on an unbuffered SSL socket.") - + if not socket.sslHasPeekChar: + result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1) + socket.sslHasPeekChar = true + + c = socket.sslPeekChar + return result = recv(socket.fd, addr(c), 1, MSG_PEEK) proc recvLine*(socket: TSocket, line: var TaintedString): bool {. @@ -1062,14 +1157,12 @@ proc recvLine*(socket: TSocket, line: var TaintedString): bool {. ## added to ``line``, however if solely ``\r\L`` is received then ``line`` ## will be set to it. ## - ## ``True`` is returned if data is available. ``False`` usually suggests an - ## error, EOS exceptions are not raised in favour of this. + ## ``True`` is returned if data is available. ``False`` suggests an + ## error, EOS exceptions are not raised and ``False`` is simply returned + ## instead. ## ## If the socket is disconnected, ``line`` will be set to ``""`` and ``True`` ## will be returned. - ## - ## **Warning:** Using this function on a unbuffered ssl socket will result - ## in an error. template addNLIfEmpty(): stmt = if line.len == 0: line.add("\c\L") @@ -1096,6 +1189,8 @@ proc recvLine*(socket: TSocket, line: var TaintedString, timeout: int): bool {. tags: [FReadIO, FTime].} = ## variant with a ``timeout`` parameter, the timeout parameter specifies ## how many miliseconds to wait for data. + ## + ## ``ETimeout`` will be raised if ``timeout`` is exceeded. template addNLIfEmpty(): stmt = if line.len == 0: line.add("\c\L") @@ -1148,11 +1243,13 @@ proc recvLineAsync*(socket: TSocket, elif c == '\L': return RecvFullLine add(line.string, c) -proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} = +proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} = ## receives all the available data from the socket. ## Socket errors will result in an ``EOS`` error. ## If socket is not a connectionless socket and socket is not connected ## ``""`` will be returned. + ## + ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 4000 result = newStringOfCap(bufSize).TaintedString var pos = 0 @@ -1177,25 +1274,31 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} = add(result.string, buf) if bytesRead != bufSize-1: break +{.push warning[deprecated]: off.} proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {. - tags: [FReadIO].} = + tags: [FReadIO], deprecated.} = ## overloaded variant to support a ``timeout`` parameter, the ``timeout`` ## parameter specifies the amount of miliseconds to wait for data on the ## socket. + ## + ## **Deprecated since version 0.9.2**: This function is not safe for use. if socket.bufLen == 0: var s = @[socket] if s.select(timeout) != 1: raise newException(ETimeout, "Call to recv() timed out.") return socket.recv +{.pop.} proc recvAsync*(socket: TSocket, s: var TaintedString): bool {. - tags: [FReadIO].} = + tags: [FReadIO], deprecated.} = ## receives all the data from a non-blocking socket. If socket is non-blocking ## and there are no messages available, `False` will be returned. ## Other socket errors will result in an ``EOS`` error. ## If socket is not a connectionless socket and socket is not connected ## ``s`` will be set to ``""``. + ## + ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 1000 # ensure bufSize capacity: setLen(s.string, bufSize) @@ -1282,13 +1385,25 @@ proc recvFromAsync*(socket: TSocket, data: var String, length: int, return False else: OSError() -proc skip*(socket: TSocket) {.tags: [FReadIO].} = +proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} = ## skips all the data that is pending for the socket + ## + ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 1000 var buf = alloc(bufSize) while recv(socket, buf, bufSize) == bufSize: nil dealloc(buf) +proc skip*(socket: TSocket, size: int) = + ## Skips ``size`` amount of bytes. + ## + ## Returns the number of skipped bytes. + var dummy = alloc(size) + var bytesSkipped = 0 + while bytesSkipped != size: + bytesSkipped += recv(socket, dummy, size-bytesSkipped) + dealloc(dummy) + proc send*(socket: TSocket, data: pointer, size: int): int {. tags: [FWriteIO].} = ## sends data to a socket. @@ -1312,21 +1427,26 @@ proc send*(socket: TSocket, data: string) {.tags: [FWriteIO].} = OSError() -proc sendAsync*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} = - ## sends data to a non-blocking socket. Returns whether ``data`` was sent. - result = true - var bytesSent = send(socket, cstring(data), data.len) +proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} = + ## sends data to a non-blocking socket. + ## Returns ``0`` if no data could be sent, if data has been sent + ## returns the amount of bytes of ``data`` that was successfully sent. This + ## number may not always be the length of ``data`` but typically is. + ## + ## An EOS (or ESSL if socket is an SSL socket) exception is raised if an error + ## occurs. + result = send(socket, cstring(data), data.len) when defined(ssl): if socket.isSSL: - if bytesSent <= 0: - let ret = SSLGetError(socket.sslHandle, bytesSent.cint) + if result <= 0: + let ret = SSLGetError(socket.sslHandle, result.cint) case ret of SSL_ERROR_ZERO_RETURN: SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.") of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: SSLError("Unexpected error occured.") # This should just not happen. of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ: - return false + return 0 of SSL_ERROR_WANT_X509_LOOKUP: SSLError("Function for x509 lookup has been called.") of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: @@ -1334,17 +1454,18 @@ proc sendAsync*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} = else: SSLError("Unknown Error") else: return - if bytesSent == -1: + if result == -1: when defined(windows): var err = WSAGetLastError() # TODO: Test on windows. if err == WSAEINPROGRESS: - return false + return 0 else: OSError() else: if errno == EAGAIN or errno == EWOULDBLOCK: - return false + return 0 else: OSError() + proc trySend*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} = ## safe alternative to ``send``. Does not raise an EOS when an error occurs, @@ -1385,8 +1506,7 @@ proc sendTo*(socket: TSocket, address: string, port: TPort, result = socket.sendTo(address, port, cstring(data), data.len) when defined(Windows): - const - SOCKET_ERROR = -1 + const IOCPARM_MASK = 127 IOC_IN = int(-2147483648) FIONBIO = int(IOC_IN or ((sizeof(int) and IOCPARM_MASK) shl 16) or @@ -1399,7 +1519,7 @@ when defined(Windows): proc setBlocking(s: TSocket, blocking: bool) = when defined(Windows): var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking - if SOCKET_ERROR == ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)): + if ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)) == -1: OSError() else: # BSD sockets var x: int = fcntl(s.fd, F_GETFL, 0) diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 0ef5e3ff5..232205ebd 100755 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -267,7 +267,8 @@ proc newFileStream*(f: TFile): PFileStream = proc newFileStream*(filename: string, mode: TFileMode): PFileStream = ## creates a new stream from the file named `filename` with the mode `mode`. - ## If the file cannot be opened, nil is returned. + ## If the file cannot be opened, nil is returned. See the `system + ## <system.html>`_ module for a list of available TFileMode enums. var f: TFile if Open(f, filename, mode): result = newFileStream(f) diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index 09a243e97..77b463fc0 100755 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -36,11 +36,23 @@ proc len*(t: PStringTable): int {.rtl, extern: "nst$1".} = result = t.counter iterator pairs*(t: PStringTable): tuple[key, value: string] = - ## iterates over any (key, value) pair in the table `t`. + ## iterates over every (key, value) pair in the table `t`. for h in 0..high(t.data): if not isNil(t.data[h].key): yield (t.data[h].key, t.data[h].val) +iterator keys*(t: PStringTable): string = + ## iterates over every key in the table `t`. + for h in 0..high(t.data): + if not isNil(t.data[h].key): + yield t.data[h].key + +iterator values*(t: PStringTable): string = + ## iterates over every value in the table `t`. + for h in 0..high(t.data): + if not isNil(t.data[h].key): + yield t.data[h].val + type TFormatFlag* = enum ## flags for the `%` operator useEnvironment, ## use environment variable if the ``$key`` diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 0f11b4d89..8b64434d8 100755 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -186,7 +186,24 @@ iterator split*(s: string, seps: set[char] = Whitespace): string = ## for word in split(";;this;is;an;;example;;;", {';'}): ## writeln(stdout, word) ## - ## produces the same output. + ## produces the same output. The code: + ## + ## .. code-block:: nimrod + ## let date = "2012-11-20T22:08:08.398990" + ## let separators = {' ', '-', ':', 'T'} + ## for number in split(date, separators): + ## writeln(stdout, number) + ## + ## Results in: + ## + ## .. code-block:: nimrod + ## "2012" + ## "11" + ## "20" + ## "22" + ## "08" + ## "08.398990" + ## var last = 0 assert(not ('\0' in seps)) while last < len(s): @@ -833,13 +850,51 @@ proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, for c in items(s): case c of '\0'..'\31', '\128'..'\255': - add(result, '\\') + add(result, "\\x") add(result, toHex(ord(c), 2)) of '\\': add(result, "\\\\") of '\'': add(result, "\\'") of '\"': add(result, "\\\"") else: add(result, c) add(result, suffix) + +proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, + rtl, extern: "nsuUnescape".} = + ## Unescapes a string `s`. This complements ``escape`` as it performs the + ## opposite operations. + ## + ## If `s` does not begin with ``prefix`` and end with ``suffix`` a EInvalidValue + ## exception will be raised. + result = newStringOfCap(s.len) + var i = 0 + if s[0 .. prefix.len-1] != prefix: + raise newException(EInvalidValue, + "String does not start with a prefix of: " & prefix) + i.inc() + while True: + if i == s.len-suffix.len: break + case s[i] + of '\\': + case s[i+1]: + of 'x': + let j = parseHexInt(s[i+2 .. i+3]) + result.add(chr(j)) + inc(i, 2) + of '\\': + result.add('\\') + of '\'': + result.add('\'') + of '\"': + result.add('\"') + else: result.add("\\" & s[i+1]) + inc(i) + of '\0': break + else: + result.add(s[i]) + i.inc() + if s[i .. -1] != suffix: + raise newException(EInvalidValue, + "String does not end with a suffix of: " & suffix) proc validIdentifier*(s: string): bool {.noSideEffect, rtl, extern: "nsuValidIdentifier".} = diff --git a/lib/system.nim b/lib/system.nim index 9fa5258ce..b4c265f62 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -38,7 +38,8 @@ type char* {.magic: Char.} ## built-in 8 bit character type (unsigned) string* {.magic: String.} ## built-in string type cstring* {.magic: Cstring.} ## built-in cstring (*compatible string*) type - pointer* {.magic: Pointer.} ## built-in pointer type + pointer* {.magic: Pointer.} ## built-in pointer type, use the ``addr`` + ## operator to get a pointer to a variable const on* = true ## alias for ``true`` @@ -116,7 +117,13 @@ proc new*(T: typedesc): ref T = ## creates a new object of type ``T`` and returns a safe (traced) ## reference to it as result value new(result) - + +proc unsafeNew*[T](a: var ref T, size: int) {.magic: "New", noSideEffect.} + ## creates a new object of type ``T`` and returns a safe (traced) + ## reference to it in ``a``. This is **unsafe** as it allocates an object + ## of the passed ``size``. This should only be used for optimization + ## purposes when you know what you're doing! + proc internalNew*[T](a: var ref T) {.magic: "New", noSideEffect.} ## leaked implementation detail. Do not use. @@ -1060,86 +1067,87 @@ proc substr*(s: string, first, last: int): string {. ## is used instead: This means ``substr`` can also be used to `cut`:idx: ## or `limit`:idx: a string's length. -proc zeroMem*(p: Pointer, size: int) {.importc, noDecl.} - ## overwrites the contents of the memory at ``p`` with the value 0. - ## Exactly ``size`` bytes will be overwritten. Like any procedure - ## dealing with raw memory this is *unsafe*. - -proc copyMem*(dest, source: Pointer, size: int) {.importc: "memcpy", noDecl.} - ## copies the contents from the memory at ``source`` to the memory - ## at ``dest``. Exactly ``size`` bytes will be copied. The memory - ## regions may not overlap. Like any procedure dealing with raw - ## memory this is *unsafe*. - -proc moveMem*(dest, source: Pointer, size: int) {.importc: "memmove", noDecl.} - ## copies the contents from the memory at ``source`` to the memory - ## at ``dest``. Exactly ``size`` bytes will be copied. The memory - ## regions may overlap, ``moveMem`` handles this case appropriately - ## and is thus somewhat more safe than ``copyMem``. Like any procedure - ## dealing with raw memory this is still *unsafe*, though. - -proc equalMem*(a, b: Pointer, size: int): bool {. - importc: "equalMem", noDecl, noSideEffect.} - ## compares the memory blocks ``a`` and ``b``. ``size`` bytes will - ## be compared. If the blocks are equal, true is returned, false - ## otherwise. Like any procedure dealing with raw memory this is - ## *unsafe*. - -proc alloc*(size: int): pointer {.noconv, rtl, tags: [].} - ## allocates a new memory block with at least ``size`` bytes. The - ## block has to be freed with ``realloc(block, 0)`` or - ## ``dealloc(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. -proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].} - ## allocates a new memory block with at least ``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``. - ## The allocated memory belongs to its allocating thread! - ## Use `allocShared0` to allocate from a shared heap. -proc realloc*(p: Pointer, newsize: int): pointer {.noconv, rtl, tags: [].} - ## 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 - ## ``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 its allocating thread! - ## Use `reallocShared` to reallocate from a shared heap. -proc dealloc*(p: Pointer) {.noconv, rtl, tags: [].} - ## frees the memory allocated with ``alloc``, ``alloc0`` or - ## ``realloc``. 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. - ## The freed memory must belong to its allocating thread! - ## Use `deallocShared` to deallocate from a shared heap. - -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 allocShared0*(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 initialized with all bytes - ## containing zero, so it is somewhat safer than ``allocShared``. -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 deallocShared*(p: Pointer) {.noconv, rtl.} - ## frees the memory allocated with ``allocShared``, ``allocShared0`` or - ## ``reallocShared``. 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. +when not defined(nimrodVM): + proc zeroMem*(p: Pointer, size: int) {.importc, noDecl.} + ## overwrites the contents of the memory at ``p`` with the value 0. + ## Exactly ``size`` bytes will be overwritten. Like any procedure + ## dealing with raw memory this is *unsafe*. + + proc copyMem*(dest, source: Pointer, size: int) {.importc: "memcpy", noDecl.} + ## copies the contents from the memory at ``source`` to the memory + ## at ``dest``. Exactly ``size`` bytes will be copied. The memory + ## regions may not overlap. Like any procedure dealing with raw + ## memory this is *unsafe*. + + proc moveMem*(dest, source: Pointer, size: int) {.importc: "memmove", noDecl.} + ## copies the contents from the memory at ``source`` to the memory + ## at ``dest``. Exactly ``size`` bytes will be copied. The memory + ## regions may overlap, ``moveMem`` handles this case appropriately + ## and is thus somewhat more safe than ``copyMem``. Like any procedure + ## dealing with raw memory this is still *unsafe*, though. + + proc equalMem*(a, b: Pointer, size: int): bool {. + importc: "equalMem", noDecl, noSideEffect.} + ## compares the memory blocks ``a`` and ``b``. ``size`` bytes will + ## be compared. If the blocks are equal, true is returned, false + ## otherwise. Like any procedure dealing with raw memory this is + ## *unsafe*. + + proc alloc*(size: int): pointer {.noconv, rtl, tags: [].} + ## allocates a new memory block with at least ``size`` bytes. The + ## block has to be freed with ``realloc(block, 0)`` or + ## ``dealloc(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. + proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].} + ## allocates a new memory block with at least ``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``. + ## The allocated memory belongs to its allocating thread! + ## Use `allocShared0` to allocate from a shared heap. + proc realloc*(p: Pointer, newsize: int): pointer {.noconv, rtl, tags: [].} + ## 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 + ## ``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 its allocating thread! + ## Use `reallocShared` to reallocate from a shared heap. + proc dealloc*(p: Pointer) {.noconv, rtl, tags: [].} + ## frees the memory allocated with ``alloc``, ``alloc0`` or + ## ``realloc``. 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. + ## The freed memory must belong to its allocating thread! + ## Use `deallocShared` to deallocate from a shared heap. + + 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 allocShared0*(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 initialized with all bytes + ## containing zero, so it is somewhat safer than ``allocShared``. + 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 deallocShared*(p: Pointer) {.noconv, rtl.} + ## frees the memory allocated with ``allocShared``, ``allocShared0`` or + ## ``reallocShared``. 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. proc swap*[T](a, b: var T) {.magic: "Swap", noSideEffect.} ## swaps the values `a` and `b`. This is often more efficient than @@ -1215,15 +1223,16 @@ const # GC interface: -proc getOccupiedMem*(): int {.rtl.} - ## returns the number of bytes that are owned by the process and hold data. +when not defined(nimrodVM): + proc getOccupiedMem*(): int {.rtl.} + ## returns the number of bytes that are owned by the process and hold data. -proc getFreeMem*(): int {.rtl.} - ## returns the number of bytes that are owned by the process, but do not - ## hold any meaningful data. + proc getFreeMem*(): int {.rtl.} + ## returns the number of bytes that are owned by the process, but do not + ## hold any meaningful data. -proc getTotalMem*(): int {.rtl.} - ## returns the number of bytes that are owned by the process. + proc getTotalMem*(): int {.rtl.} + ## returns the number of bytes that are owned by the process. iterator countdown*[T](a, b: T, step = 1): T {.inline.} = @@ -1452,15 +1461,51 @@ proc pop*[T](s: var seq[T]): T {.inline, noSideEffect.} = result = s[L] setLen(s, L) -proc each*[T, S](data: openArray[T], op: proc (x: T): S {.closure.}): seq[S] = +proc each*[T, S](data: openArray[T], op: proc (x: T): S {.closure.}): seq[S] {. + deprecated.} = ## The well-known ``map`` operation from functional programming. Applies ## `op` to every item in `data` and returns the result as a sequence. + ## + ## **Deprecated since version 0.9:** Use the ``map`` proc instead. newSeq(result, data.len) for i in 0..data.len-1: result[i] = op(data[i]) -proc each*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) = +proc each*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) {. + deprecated.} = ## The well-known ``map`` operation from functional programming. Applies - ## `op` to every item in `data`. + ## `op` to every item in `data` modifying it directly. + ## + ## **Deprecated since version 0.9:** Use the ``map`` proc instead. + for i in 0..data.len-1: op(data[i]) + +proc map*[T, S](data: openArray[T], op: proc (x: T): S {.closure.}): seq[S] = + ## Returns a new sequence with the results of `op` applied to every item in + ## `data`. + ## + ## Since the input is not modified you can use this version of ``map`` to + ## transform the type of the elements in the input sequence. Example: + ## + ## .. code-block:: nimrod + ## let + ## a = @[1, 2, 3, 4] + ## b = map(a, proc(x: int): string = $x) + ## assert b == @["1", "2", "3", "4"] + newSeq(result, data.len) + for i in 0..data.len-1: result[i] = op(data[i]) + +proc map*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) = + ## Applies `op` to every item in `data` modifying it directly. + ## + ## Note that this version of ``map`` requires your input and output types to + ## be the same, since they are modified in-place. Example: + ## + ## .. code-block:: nimrod + ## var a = @["1", "2", "3", "4"] + ## echo repr(a) + ## # --> ["1", "2", "3", "4"] + ## map(a, proc(x: var string) = x &= "42") + ## echo repr(a) + ## # --> ["142", "242", "342", "442"] for i in 0..data.len-1: op(data[i]) iterator fields*[T: tuple](x: T): TObject {. @@ -1541,41 +1586,42 @@ when false: # ----------------- GC interface --------------------------------------------- -proc GC_disable*() {.rtl, inl.} - ## disables the GC. If called n-times, n calls to `GC_enable` are needed to - ## reactivate the GC. Note that in most circumstances one should only disable - ## the mark and sweep phase with `GC_disableMarkAndSweep`. +when not defined(nimrodVM): + proc GC_disable*() {.rtl, inl.} + ## disables the GC. If called n-times, n calls to `GC_enable` are needed to + ## reactivate the GC. Note that in most circumstances one should only disable + ## the mark and sweep phase with `GC_disableMarkAndSweep`. -proc GC_enable*() {.rtl, inl.} - ## enables the GC again. + proc GC_enable*() {.rtl, inl.} + ## enables the GC again. -proc GC_fullCollect*() {.rtl.} - ## forces a full garbage collection pass. - ## Ordinary code does not need to call this (and should not). + proc GC_fullCollect*() {.rtl.} + ## forces a full garbage collection pass. + ## Ordinary code does not need to call this (and should not). -type - TGC_Strategy* = enum ## the strategy the GC should use for the application - gcThroughput, ## optimize for throughput - gcResponsiveness, ## optimize for responsiveness (default) - gcOptimizeTime, ## optimize for speed - gcOptimizeSpace ## optimize for memory footprint - -proc GC_setStrategy*(strategy: TGC_Strategy) {.rtl, deprecated.} - ## tells the GC the desired strategy for the application. - ## **Deprecated** since version 0.8.14. This has always been a nop. - -proc GC_enableMarkAndSweep*() {.rtl.} -proc GC_disableMarkAndSweep*() {.rtl.} - ## the current implementation uses a reference counting garbage collector - ## with a seldomly run mark and sweep phase to free cycles. The mark and - ## sweep phase may take a long time and is not needed if the application - ## does not create cycles. Thus the mark and sweep phase can be deactivated - ## and activated separately from the rest of the GC. - -proc GC_getStatistics*(): string {.rtl.} - ## returns an informative string about the GC's activity. This may be useful - ## for tweaking. - + type + TGC_Strategy* = enum ## the strategy the GC should use for the application + gcThroughput, ## optimize for throughput + gcResponsiveness, ## optimize for responsiveness (default) + gcOptimizeTime, ## optimize for speed + gcOptimizeSpace ## optimize for memory footprint + + proc GC_setStrategy*(strategy: TGC_Strategy) {.rtl, deprecated.} + ## tells the GC the desired strategy for the application. + ## **Deprecated** since version 0.8.14. This has always been a nop. + + proc GC_enableMarkAndSweep*() {.rtl.} + proc GC_disableMarkAndSweep*() {.rtl.} + ## the current implementation uses a reference counting garbage collector + ## with a seldomly run mark and sweep phase to free cycles. The mark and + ## sweep phase may take a long time and is not needed if the application + ## does not create cycles. Thus the mark and sweep phase can be deactivated + ## and activated separately from the rest of the GC. + + proc GC_getStatistics*(): string {.rtl.} + ## returns an informative string about the GC's activity. This may be useful + ## for tweaking. + proc GC_ref*[T](x: ref T) {.magic: "GCref".} proc GC_ref*[T](x: seq[T]) {.magic: "GCref".} proc GC_ref*(x: string) {.magic: "GCref".} @@ -1701,28 +1747,29 @@ proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo".} ## get type information for `x`. Ordinary code should not use this, but ## the `typeinfo` module instead. -when not defined(EcmaScript) and not defined(NimrodVM): +when not defined(EcmaScript): #and not defined(NimrodVM): {.push stack_trace: off, profiler:off.} - proc initGC() - when not defined(boehmgc) and not defined(useMalloc): - proc initAllocator() {.inline.} + when not defined(NimrodVM): + proc initGC() + when not defined(boehmgc) and not defined(useMalloc): + proc initAllocator() {.inline.} - proc initStackBottom() {.inline, compilerproc.} = - # WARNING: This is very fragile! An array size of 8 does not work on my - # Linux 64bit system. Very strange, but we are at the will of GCC's - # optimizer... - when defined(setStackBottom): - var locals {.volatile.}: pointer - locals = addr(locals) - setStackBottom(locals) + proc initStackBottom() {.inline, compilerproc.} = + # WARNING: This is very fragile! An array size of 8 does not work on my + # Linux 64bit system. -- That's because the stack direction is the other + # way round. + when defined(setStackBottom): + var locals {.volatile.}: pointer + locals = addr(locals) + setStackBottom(locals) - var - strDesc: TNimType + var + strDesc: TNimType - strDesc.size = sizeof(string) - strDesc.kind = tyString - strDesc.flags = {ntfAcyclic} + strDesc.size = sizeof(string) + strDesc.kind = tyString + strDesc.flags = {ntfAcyclic} include "system/ansi_c" @@ -1730,28 +1777,27 @@ when not defined(EcmaScript) and not defined(NimrodVM): result = int(c_strcmp(x, y)) const pccHack = if defined(pcc): "_" else: "" # Hack for PCC - when defined(windows): - # work-around C's sucking abstraction: - # BUGFIX: stdin and stdout should be binary files! - proc setmode(handle, mode: int) {.importc: pccHack & "setmode", - header: "<io.h>".} - proc fileno(f: C_TextFileStar): int {.importc: pccHack & "fileno", - header: "<fcntl.h>".} - var - O_BINARY {.importc: pccHack & "O_BINARY", nodecl.}: int - - # we use binary mode in Windows: - setmode(fileno(c_stdin), O_BINARY) - setmode(fileno(c_stdout), O_BINARY) - - when defined(endb): - proc endbStep() + when not defined(NimrodVM): + when defined(windows): + # work-around C's sucking abstraction: + # BUGFIX: stdin and stdout should be binary files! + proc setmode(handle, mode: int) {.importc: pccHack & "setmode", + header: "<io.h>".} + proc fileno(f: C_TextFileStar): int {.importc: pccHack & "fileno", + header: "<fcntl.h>".} + var + O_BINARY {.importc: pccHack & "O_BINARY", nodecl.}: int + + # we use binary mode in Windows: + setmode(fileno(c_stdin), O_BINARY) + setmode(fileno(c_stdout), O_BINARY) + + when defined(endb): + proc endbStep() # ----------------- IO Part ------------------------------------------------ - type - CFile {.importc: "FILE", nodecl, final.} = object # empty record for - # data hiding + CFile {.importc: "FILE", nodecl, final, incompletestruct.} = object TFile* = ptr CFile ## The type representing a file handle. TFileMode* = enum ## The file mode when opening a file. @@ -1777,7 +1823,7 @@ when not defined(EcmaScript) and not defined(NimrodVM): ## The standard error stream. ## ## Note: In my opinion, this should not be used -- the concept of a - ## separate error stream is a design flaw of UNIX. A seperate *message + ## separate error stream is a design flaw of UNIX. A separate *message ## stream* is a good idea, but since it is named ``stderr`` there are few ## programs out there that distinguish properly between ``stdout`` and ## ``stderr``. So, that's what you get if you don't name your variables @@ -1934,31 +1980,32 @@ when not defined(EcmaScript) and not defined(NimrodVM): # ------------------------------------------------------------------------- - proc allocCStringArray*(a: openArray[string]): cstringArray = - ## creates a NULL terminated cstringArray from `a`. The result has to - ## be freed with `deallocCStringArray` after it's not needed anymore. - result = cast[cstringArray](alloc0((a.len+1) * sizeof(cstring))) - for i in 0 .. a.high: - # XXX get rid of this string copy here: - var x = a[i] - result[i] = cast[cstring](alloc0(x.len+1)) - copyMem(result[i], addr(x[0]), x.len) - - proc deallocCStringArray*(a: cstringArray) = - ## frees a NULL terminated cstringArray. - var i = 0 - while a[i] != nil: - dealloc(a[i]) - inc(i) - dealloc(a) - - proc atomicInc*(memLoc: var int, x: int = 1): int {.inline, discardable.} - ## atomic increment of `memLoc`. Returns the value after the operation. - - proc atomicDec*(memLoc: var int, x: int = 1): int {.inline, discardable.} - ## atomic decrement of `memLoc`. Returns the value after the operation. + when not defined(NimrodVM): + proc allocCStringArray*(a: openArray[string]): cstringArray = + ## creates a NULL terminated cstringArray from `a`. The result has to + ## be freed with `deallocCStringArray` after it's not needed anymore. + result = cast[cstringArray](alloc0((a.len+1) * sizeof(cstring))) + for i in 0 .. a.high: + # XXX get rid of this string copy here: + var x = a[i] + result[i] = cast[cstring](alloc0(x.len+1)) + copyMem(result[i], addr(x[0]), x.len) + + proc deallocCStringArray*(a: cstringArray) = + ## frees a NULL terminated cstringArray. + var i = 0 + while a[i] != nil: + dealloc(a[i]) + inc(i) + dealloc(a) + + proc atomicInc*(memLoc: var int, x: int = 1): int {.inline, discardable.} + ## atomic increment of `memLoc`. Returns the value after the operation. + + proc atomicDec*(memLoc: var int, x: int = 1): int {.inline, discardable.} + ## atomic decrement of `memLoc`. Returns the value after the operation. - include "system/atomics" + include "system/atomics" type PSafePoint = ptr TSafePoint @@ -1974,71 +2021,76 @@ when not defined(EcmaScript) and not defined(NimrodVM): when hasThreadSupport: include "system/syslocks" include "system/threads" - elif not defined(nogc): + elif not defined(nogc) and not defined(NimrodVM): when not defined(useNimRtl) and not defined(createNimRtl): initStackBottom() initGC() - proc setControlCHook*(hook: proc () {.noconv.}) - ## allows you to override the behaviour of your application when CTRL+C - ## is pressed. Only one such hook is supported. - - proc writeStackTrace*() {.tags: [FWriteIO].} - ## writes the current stack trace to ``stderr``. This is only works - ## for debug builds. - when hostOS != "standalone": - proc getStackTrace*(): string - ## gets the current stack trace. This only works for debug builds. - - proc getStackTrace*(e: ref E_Base): string - ## gets the stack trace associated with `e`, which is the stack that - ## lead to the ``raise`` statement. This only works for debug builds. + when not defined(NimrodVM): + proc setControlCHook*(hook: proc () {.noconv.}) + ## allows you to override the behaviour of your application when CTRL+C + ## is pressed. Only one such hook is supported. - {.push stack_trace: off, profiler:off.} - when hostOS == "standalone": - include "system/embedded" - else: - include "system/excpt" - - # we cannot compile this with stack tracing on - # as it would recurse endlessly! - include "system/arithm" - {.pop.} # stack trace + proc writeStackTrace*() {.tags: [FWriteIO].} + ## writes the current stack trace to ``stderr``. This is only works + ## for debug builds. + when hostOS != "standalone": + proc getStackTrace*(): string + ## gets the current stack trace. This only works for debug builds. + + proc getStackTrace*(e: ref E_Base): string + ## gets the stack trace associated with `e`, which is the stack that + ## lead to the ``raise`` statement. This only works for debug builds. + + {.push stack_trace: off, profiler:off.} + when hostOS == "standalone": + include "system/embedded" + else: + include "system/excpt" + + # we cannot compile this with stack tracing on + # as it would recurse endlessly! + include "system/arithm" + {.pop.} # stack trace {.pop.} # stack trace - when hostOS != "standalone": include "system/dyncalls" - include "system/sets" + when hostOS != "standalone" and not defined(NimrodVM): + include "system/dyncalls" + when not defined(NimrodVM): + include "system/sets" - const - GenericSeqSize = (2 * sizeof(int)) - - proc getDiscriminant(aa: Pointer, n: ptr TNimNode): int = - sysAssert(n.kind == nkCase, "getDiscriminant: node != nkCase") - var d: int - var a = cast[TAddress](aa) - case n.typ.size - of 1: d = ze(cast[ptr int8](a +% n.offset)[]) - of 2: d = ze(cast[ptr int16](a +% n.offset)[]) - of 4: d = int(cast[ptr int32](a +% n.offset)[]) - else: sysAssert(false, "getDiscriminant: invalid n.typ.size") - return d - - proc selectBranch(aa: Pointer, n: ptr TNimNode): ptr TNimNode = - var discr = getDiscriminant(aa, n) - if discr <% n.len: - result = n.sons[discr] - if result == nil: result = n.sons[n.len] - # n.sons[n.len] contains the ``else`` part (but may be nil) - else: - result = n.sons[n.len] - - include "system/mmdisp" - {.push stack_trace: off, profiler:off.} - when hostOS != "standalone": include "system/sysstr" - {.pop.} - - include "system/sysio" - when hasThreadSupport: - include "system/channels" + const + GenericSeqSize = (2 * sizeof(int)) + + proc getDiscriminant(aa: Pointer, n: ptr TNimNode): int = + sysAssert(n.kind == nkCase, "getDiscriminant: node != nkCase") + var d: int + var a = cast[TAddress](aa) + case n.typ.size + of 1: d = ze(cast[ptr int8](a +% n.offset)[]) + of 2: d = ze(cast[ptr int16](a +% n.offset)[]) + of 4: d = int(cast[ptr int32](a +% n.offset)[]) + else: sysAssert(false, "getDiscriminant: invalid n.typ.size") + return d + + proc selectBranch(aa: Pointer, n: ptr TNimNode): ptr TNimNode = + var discr = getDiscriminant(aa, n) + if discr <% n.len: + result = n.sons[discr] + if result == nil: result = n.sons[n.len] + # n.sons[n.len] contains the ``else`` part (but may be nil) + else: + result = n.sons[n.len] + + include "system/mmdisp" + {.push stack_trace: off, profiler:off.} + when hostOS != "standalone": include "system/sysstr" + {.pop.} + + include "system/sysio" + when hasThreadSupport: + include "system/channels" + else: + include "system/sysio" iterator lines*(filename: string): TaintedString {.tags: [FReadIO].} = ## Iterate over any line in the file named `filename`. @@ -2053,7 +2105,7 @@ when not defined(EcmaScript) and not defined(NimrodVM): var res = TaintedString(newStringOfCap(80)) while f.readLine(res): yield TaintedString(res) - when hostOS != "standalone": + when hostOS != "standalone" and not defined(NimrodVM): include "system/assign" include "system/repr" @@ -2078,43 +2130,46 @@ when not defined(EcmaScript) and not defined(NimrodVM): excHandler.raiseAction = action {.push stack_trace: off, profiler:off.} - when defined(endb): + when defined(endb) and not defined(NimrodVM): include "system/debugger" when defined(profiler) or defined(memProfiler): include "system/profiler" {.pop.} # stacktrace - proc likely*(val: bool): bool {.importc: "likely", nodecl, nosideeffect.} - ## can be used to mark a condition to be likely. This is a hint for the - ## optimizer. - - proc unlikely*(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.} - ## can be used to mark a condition to be unlikely. This is a hint for the - ## optimizer. + when not defined(NimrodVM): + proc likely*(val: bool): bool {.importc: "likely", nodecl, nosideeffect.} + ## can be used to mark a condition to be likely. This is a hint for the + ## optimizer. - proc rawProc*[T: proc](x: T): pointer {.noSideEffect, inline.} = - ## retrieves the raw proc pointer of the closure `x`. This is - ## useful for interfacing closures with C. - {.emit: """ - `result` = `x`.ClPrc; - """.} - - proc rawEnv*[T: proc](x: T): pointer {.noSideEffect, inline.} = - ## retrieves the raw environment pointer of the closure `x`. This is - ## useful for interfacing closures with C. - {.emit: """ - `result` = `x`.ClEnv; - """.} - - proc finished*[T: proc](x: T): bool {.noSideEffect, inline.} = - ## can be used to determine if a first class iterator has finished. - {.emit: """ - `result` = *((NI*) `x`.ClEnv) < 0; - """.} - -elif defined(ecmaScript) or defined(NimrodVM): + proc unlikely*(val: bool): bool {.importc: "unlikely", nodecl, nosideeffect.} + ## can be used to mark a condition to be unlikely. This is a hint for the + ## optimizer. + + proc rawProc*[T: proc](x: T): pointer {.noSideEffect, inline.} = + ## retrieves the raw proc pointer of the closure `x`. This is + ## useful for interfacing closures with C. + {.emit: """ + `result` = `x`.ClPrc; + """.} + + proc rawEnv*[T: proc](x: T): pointer {.noSideEffect, inline.} = + ## retrieves the raw environment pointer of the closure `x`. This is + ## useful for interfacing closures with C. + {.emit: """ + `result` = `x`.ClEnv; + """.} + + proc finished*[T: proc](x: T): bool {.noSideEffect, inline.} = + ## can be used to determine if a first class iterator has finished. + {.emit: """ + `result` = *((NI*) `x`.ClEnv) < 0; + """.} + +elif defined(ecmaScript): # Stubs: + proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = nil + proc GC_disable() = nil proc GC_enable() = nil proc GC_fullCollect() = nil @@ -2145,6 +2200,10 @@ elif defined(ecmaScript) or defined(NimrodVM): if x == y: return 0 if x < y: return -1 return 1 + + when defined(nimffi): + include "system/sysio" + proc quit*(errormsg: string, errorcode = QuitFailure) {.noReturn.} = ## a shorthand for ``echo(errormsg); quit(errorcode)``. diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 195bc2e60..33e1ea982 100755 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -21,9 +21,8 @@ proc c_strlen(a: CString): int {.nodecl, noSideEffect, importc: "strlen".} proc c_memset(p: pointer, value: cint, size: int) {.nodecl, importc: "memset".} type - C_TextFile {.importc: "FILE", nodecl, final.} = object # empty record for - # data hiding - C_BinaryFile {.importc: "FILE", nodecl, final.} = object + C_TextFile {.importc: "FILE", nodecl, final, incompleteStruct.} = object + C_BinaryFile {.importc: "FILE", nodecl, final, incompleteStruct.} = object C_TextFileStar = ptr CTextFile C_BinaryFileStar = ptr CBinaryFile diff --git a/lib/system/debugger.nim b/lib/system/debugger.nim index 68fbccef6..f234c9daf 100755 --- a/lib/system/debugger.nim +++ b/lib/system/debugger.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -579,6 +579,10 @@ proc hash(Data: Pointer, Size: int): THash = Dec(s) result = !$h +proc hashGcHeader(data: pointer): THash = + const headerSize = sizeof(int)*2 + result = hash(cast[pointer](cast[int](data) -% headerSize), headerSize) + proc genericHashAux(dest: Pointer, mt: PNimType, shallow: bool, h: THash): THash proc genericHashAux(dest: Pointer, n: ptr TNimNode, shallow: bool, @@ -606,20 +610,22 @@ proc genericHashAux(dest: Pointer, mt: PNimType, shallow: bool, result = h if x != nil: let s = cast[NimString](x) - when true: - result = result !& hash(x, s.len) + when defined(trackGcHeaders): + result = result !& hashGcHeader(x) else: - let y = cast[pointer](cast[int](x) -% 2*sizeof(int)) - result = result !& hash(y, s.len + 2*sizeof(int)) + result = result !& hash(x, s.len) of tySequence: var x = cast[ppointer](dest) var dst = cast[taddress](cast[ppointer](dest)[]) result = h if dst != 0: - for i in 0..cast[pgenericseq](dst).len-1: - result = result !& genericHashAux( - cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), - mt.Base, shallow, result) + when defined(trackGcHeaders): + result = result !& hashGcHeader(cast[ppointer](dest)[]) + else: + for i in 0..cast[pgenericseq](dst).len-1: + result = result !& genericHashAux( + cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize), + mt.Base, shallow, result) of tyObject, tyTuple: # we don't need to copy m_type field for tyObject, as they are equal anyway result = genericHashAux(dest, mt.node, shallow, h) @@ -630,13 +636,18 @@ proc genericHashAux(dest: Pointer, mt: PNimType, shallow: bool, result = result !& genericHashAux(cast[pointer](d +% i*% mt.base.size), mt.base, shallow, result) of tyRef: - if shallow: - result = h !& hash(dest, mt.size) - else: - result = h + when defined(trackGcHeaders): var s = cast[ppointer](dest)[] if s != nil: - result = result !& genericHashAux(s, mt.base, shallow, result) + result = result !& hashGcHeader(s) + else: + if shallow: + result = h !& hash(dest, mt.size) + else: + result = h + var s = cast[ppointer](dest)[] + if s != nil: + result = result !& genericHashAux(s, mt.base, shallow, result) # hash the object header: #const headerSize = sizeof(int)*2 #result = result !& hash(cast[pointer](cast[int](s) -% headerSize), diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 9055cca40..c7accbac0 100755 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. diff --git a/lib/wrappers/gtk/gtk2.nim b/lib/wrappers/gtk/gtk2.nim index a1bfa7fe1..6b418024e 100755 --- a/lib/wrappers/gtk/gtk2.nim +++ b/lib/wrappers/gtk/gtk2.nim @@ -16596,6 +16596,9 @@ proc message_dialog_new*(parent: PWindow, flags: TDialogFlags, thetype: TMessageType, buttons: TButtonsType, message_format: cstring): PMessageDialog{.varargs, cdecl, importc: "gtk_message_dialog_new", dynlib: lib.} +proc set_markup*(msgDialog: PMessageDialog, str: cstring) {.cdecl, + importc: "gtk_message_dialog_set_markup", dynlib: lib.} + proc signal_new*(name: cstring, signal_flags: TSignalRunType, object_type: TType, function_offset: guint, marshaller: TSignalMarshaller, return_val: TType, n_args: guint): guint{. @@ -16893,6 +16896,15 @@ type proc set_tooltip_text*(w: PWidget, t: cstring){.cdecl, dynlib: lib, importc: "gtk_widget_set_tooltip_text".} +proc get_tooltip_text*(w: PWidget): cstring{.cdecl, + dynlib: lib, importc: "gtk_widget_get_tooltip_text".} + +proc set_tooltip_markup*(w: PWidget, m: cstring) {.cdecl, dynlib: lib, + importc: "gtk_widget_set_tooltip_markup".} + +proc get_tooltip_markup*(w: PWidget): cstring {.cdecl, dynlib: lib, + importc: "gtk_widget_get_tooltip_markup".} + proc set_tooltip_column*(w: PTreeview, column: gint){.cdecl, dynlib: lib, importc: "gtk_tree_view_set_tooltip_column".} @@ -16905,6 +16917,9 @@ proc trigger_tooltip_query*(widg: PTooltip){.cdecl, dynlib: lib, proc set_has_tooltip*(widget: PWidget, b: gboolean){.cdecl, dynlib: lib, importc: "gtk_widget_set_has_tooltip".} +proc get_has_tooltip*(widget: PWidget): gboolean{.cdecl, dynlib: lib, + importc: "gtk_widget_get_has_tooltip".} + proc set_markup*(tp: PTooltip, mk: cstring){.cdecl, dynlib: lib, importc: "gtk_tooltip_set_markup".} @@ -17035,6 +17050,10 @@ proc remove*(combo_box: PComboBoxText; position: gint){.cdecl, importc: "gtk_combo_box_text_remove", dynlib: lib.} proc get_active_text*(combo_box: PComboBoxText): cstring{.cdecl, importc: "gtk_combo_box_text_get_active_text", dynlib: lib.} +proc is_active*(win: PWindow): gboolean{.cdecl, + importc: "gtk_window_is_active", dynlib: lib.} +proc has_toplevel_focus*(win: PWindow): gboolean{.cdecl, + importc: "gtk_window_has_toplevel_focus", dynlib: lib.} proc nimrod_init*() = var diff --git a/lib/wrappers/libffi.nim b/lib/wrappers/libffi.nim new file mode 100644 index 000000000..514ce024f --- /dev/null +++ b/lib/wrappers/libffi.nim @@ -0,0 +1,149 @@ +# -----------------------------------------------------------------*-C-*- +# libffi 3.0.10 - Copyright (c) 2011 Anthony Green +# - Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the ``Software''), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +# ----------------------------------------------------------------------- + +{.deadCodeElim: on.} + +when defined(windows): + const libffidll* = "libffi.dll" +elif defined(macosx): + const libffidll* = "libffi.dylib" +else: + const libffidll* = "libffi.so" + +type + TArg* = int + TSArg* = int + +when defined(windows) and defined(x86): + type + TABI* {.size: sizeof(cint).} = enum + FIRST_ABI, SYSV, STDCALL + + const DEFAULT_ABI* = SYSV +elif defined(amd64) and defined(windows): + type + TABI* {.size: sizeof(cint).} = enum + FIRST_ABI, WIN64 + const DEFAULT_ABI* = WIN64 +else: + type + TABI* {.size: sizeof(cint).} = enum + FIRST_ABI, SYSV, UNIX64 + + when defined(i386): + const DEFAULT_ABI* = SYSV + else: + const DEFAULT_ABI* = UNIX64 + +const + tkVOID* = 0 + tkINT* = 1 + tkFLOAT* = 2 + tkDOUBLE* = 3 + tkLONGDOUBLE* = 4 + tkUINT8* = 5 + tkSINT8* = 6 + tkUINT16* = 7 + tkSINT16* = 8 + tkUINT32* = 9 + tkSINT32* = 10 + tkUINT64* = 11 + tkSINT64* = 12 + tkSTRUCT* = 13 + tkPOINTER* = 14 + + tkLAST = tkPOINTER + tkSMALL_STRUCT_1B* = (tkLAST + 1) + tkSMALL_STRUCT_2B* = (tkLAST + 2) + tkSMALL_STRUCT_4B* = (tkLAST + 3) + +type + TType* = object + size*: int + alignment*: uint16 + typ*: uint16 + elements*: ptr ptr TType + +var + type_void* {.importc: "ffi_type_void", dynlib: libffidll.}: TType + type_uint8* {.importc: "ffi_type_uint8", dynlib: libffidll.}: TType + type_sint8* {.importc: "ffi_type_sint8", dynlib: libffidll.}: TType + type_uint16* {.importc: "ffi_type_uint16", dynlib: libffidll.}: TType + type_sint16* {.importc: "ffi_type_sint16", dynlib: libffidll.}: TType + type_uint32* {.importc: "ffi_type_uint32", dynlib: libffidll.}: TType + type_sint32* {.importc: "ffi_type_sint32", dynlib: libffidll.}: TType + type_uint64* {.importc: "ffi_type_uint64", dynlib: libffidll.}: TType + type_sint64* {.importc: "ffi_type_sint64", dynlib: libffidll.}: TType + type_float* {.importc: "ffi_type_float", dynlib: libffidll.}: TType + type_double* {.importc: "ffi_type_double", dynlib: libffidll.}: TType + type_pointer* {.importc: "ffi_type_pointer", dynlib: libffidll.}: TType + type_longdouble* {.importc: "ffi_type_longdouble", dynlib: libffidll.}: TType + +type + Tstatus* {.size: sizeof(cint).} = enum + OK, BAD_TYPEDEF, BAD_ABI + TTypeKind* = cuint + TCif* {.pure, final.} = object + abi*: TABI + nargs*: cuint + arg_types*: ptr ptr TType + rtype*: ptr TType + bytes*: cuint + flags*: cuint + +type + TRaw* = object + sint*: TSArg + +proc raw_call*(cif: var Tcif; fn: proc () {.cdecl.}; rvalue: pointer; + avalue: ptr TRaw) {.cdecl, importc: "ffi_raw_call", + dynlib: libffidll.} +proc ptrarray_to_raw*(cif: var Tcif; args: ptr pointer; raw: ptr TRaw) {.cdecl, + importc: "ffi_ptrarray_to_raw", dynlib: libffidll.} +proc raw_to_ptrarray*(cif: var Tcif; raw: ptr TRaw; args: ptr pointer) {.cdecl, + importc: "ffi_raw_to_ptrarray", dynlib: libffidll.} +proc raw_size*(cif: var Tcif): int {.cdecl, importc: "ffi_raw_size", + dynlib: libffidll.} + +proc prep_cif*(cif: var Tcif; abi: TABI; nargs: cuint; rtype: ptr TType; + atypes: ptr ptr TType): TStatus {.cdecl, importc: "ffi_prep_cif", + dynlib: libffidll.} +proc call*(cif: var Tcif; fn: proc () {.cdecl.}; rvalue: pointer; + avalue: ptr pointer) {.cdecl, importc: "ffi_call", dynlib: libffidll.} + +# the same with an easier interface: +type + TParamList* = array[0..100, ptr TType] + TArgList* = array[0..100, pointer] + +proc prep_cif*(cif: var Tcif; abi: TABI; nargs: cuint; rtype: ptr TType; + atypes: TParamList): TStatus {.cdecl, importc: "ffi_prep_cif", + dynlib: libffidll.} +proc call*(cif: var Tcif; fn, rvalue: pointer; + avalue: TArgList) {.cdecl, importc: "ffi_call", dynlib: libffidll.} + +# Useful for eliminating compiler warnings +##define FFI_FN(f) ((void (*)(void))f) diff --git a/lib/wrappers/lua/lua.nim b/lib/wrappers/lua/lua.nim index 000e09993..0346c4285 100755 --- a/lib/wrappers/lua/lua.nim +++ b/lib/wrappers/lua/lua.nim @@ -35,19 +35,32 @@ #** In french or in english # -when defined(MACOSX): - const - NAME* = "liblua(|5.2|5.1|5.0).dylib" - LIB_NAME* = "liblua(|5.2|5.1|5.0).dylib" -elif defined(UNIX): - const - NAME* = "liblua(|5.2|5.1|5.0).so(|.0)" - LIB_NAME* = "liblua(|5.2|5.1|5.0).so(|.0)" -else: - const - NAME* = "lua(|5.2|5.1|5.0).dll" - LIB_NAME* = "lua(|5.2|5.1|5.0).dll" - +when defined(useLuajit): + when defined(MACOSX): + const + NAME* = "libluajit.dylib" + LIB_NAME* = "libluajit.dylib" + elif defined(UNIX): + const + NAME* = "libluajit.so(|.0)" + LIB_NAME* = "libluajit.so(|.0)" + else: + const + NAME* = "luajit.dll" + LIB_NAME* = "luajit.dll" +else: + when defined(MACOSX): + const + NAME* = "liblua(|5.2|5.1|5.0).dylib" + LIB_NAME* = "liblua(|5.2|5.1|5.0).dylib" + elif defined(UNIX): + const + NAME* = "liblua(|5.2|5.1|5.0).so(|.0)" + LIB_NAME* = "liblua(|5.2|5.1|5.0).so(|.0)" + else: + const + NAME* = "lua(|5.2|5.1|5.0).dll" + LIB_NAME* = "lua(|5.2|5.1|5.0).dll" const VERSION* = "Lua 5.1" diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index d33eded68..438774a15 100755 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -39,6 +39,8 @@ ## OpenSSL support +{.deadCodeElim: on.} + when defined(WINDOWS): const DLLSSLName = "(ssleay32|libssl32).dll" diff --git a/lib/wrappers/sdl/sdl_image.nim b/lib/wrappers/sdl/sdl_image.nim index 7df9aedd4..16e41070b 100755 --- a/lib/wrappers/sdl/sdl_image.nim +++ b/lib/wrappers/sdl/sdl_image.nim @@ -128,19 +128,19 @@ # #****************************************************************************** -import +import sdl -when defined(windows): - const +when defined(windows): + const ImageLibName = "SDL_Image.dll" -elif defined(macosx): - const +elif defined(macosx): + const ImageLibName = "libSDL_image-1.2.0.dylib" -else: - const - ImageLibName = "libSDL_image.so" -const +else: + const + ImageLibName = "libSDL_image(.so|-1.2.so.0)" +const IMAGE_MAJOR_VERSION* = 1 IMAGE_MINOR_VERSION* = 2 IMAGE_PATCHLEVEL* = 5 diff --git a/packages/docutils/rst.nim b/packages/docutils/rst.nim index f594a39f5..b22bdf6ce 100755 --- a/packages/docutils/rst.nim +++ b/packages/docutils/rst.nim @@ -313,6 +313,10 @@ proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) = p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, p.col + p.tok[p.idx].col, msgKind, arg) +proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string, line, col: int) = + p.s.msgHandler(p.filename, p.line + line, + p.col + col, msgKind, arg) + proc rstMessage(p: TRstParser, msgKind: TMsgKind) = p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, p.col + p.tok[p.idx].col, msgKind, @@ -684,6 +688,9 @@ when false: proc parseUntil(p: var TRstParser, father: PRstNode, postfix: string, interpretBackslash: bool) = + let + line = p.tok[p.idx].line + col = p.tok[p.idx].col while true: case p.tok[p.idx].kind of tkPunct: @@ -707,7 +714,7 @@ proc parseUntil(p: var TRstParser, father: PRstNode, postfix: string, of tkWhite: add(father, newRstNode(rnLeaf, " ")) inc(p.idx) - else: rstMessage(p, meExpected, postfix) + else: rstMessage(p, meExpected, postfix, line, col) proc parseMarkdownCodeblock(p: var TRstParser): PRstNode = var args = newRstNode(rnDirArg) @@ -1012,10 +1019,10 @@ proc whichSection(p: TRstParser): TRstNodeKind = result = rnOptionList else: result = rnParagraph - of tkWord, tkOther, tkWhite: + of tkWord, tkOther, tkWhite: if match(p, tokenAfterNewLine(p), "ai"): result = rnHeadline - elif isDefList(p): result = rnDefList elif match(p, p.idx, "e) ") or match(p, p.idx, "e. "): result = rnEnumList + elif isDefList(p): result = rnDefList else: result = rnParagraph else: result = rnLeaf diff --git a/packages/docutils/rstgen.nim b/packages/docutils/rstgen.nim index 492322f6f..53bd8188e 100644 --- a/packages/docutils/rstgen.nim +++ b/packages/docutils/rstgen.nim @@ -390,7 +390,8 @@ proc renderField(d: PDoc, n: PRstNode, result: var string) = if d.target == outLatex: var fieldname = addNodes(n.sons[0]) var fieldval = esc(d.target, strip(addNodes(n.sons[1]))) - if cmpIgnoreStyle(fieldname, "author") == 0: + if cmpIgnoreStyle(fieldname, "author") == 0 or + cmpIgnoreStyle(fieldname, "authors") == 0: if d.meta[metaAuthor].len == 0: d.meta[metaAuthor] = fieldval b = true @@ -474,8 +475,8 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) = of rnTableRow: if len(n) >= 1: if d.target == outLatex: - var tmp = "" - renderRstToOut(d, n.sons[0], tmp) + #var tmp = "" + renderRstToOut(d, n.sons[0], result) for i in countup(1, len(n) - 1): result.add(" & ") renderRstToOut(d, n.sons[i], result) diff --git a/readme.md b/readme.md new file mode 100644 index 000000000..3e39b5f77 --- /dev/null +++ b/readme.md @@ -0,0 +1,66 @@ +# Nimrod Compiler +This repo contains the Nimrod compiler, Nimrod's stdlib, tools and +documentation. + +## Compiling +Compiling the Nimrod compiler is quite straightforward. Because +the Nimrod compiler itself is written in the Nimrod programming language +the C source of an older version of the compiler are needed to bootstrap the +latest version. The C sources are however included with this repository under +the build directory. + +Pre-compiled snapshots of the compiler are also available on +[Nimbuild](http://build.nimrod-code.org/). Your platform however may not +currently be built for. + +The compiler currently supports the following platform and architecture +combinations: + + * Windows (Windows XP or greater) - x86 and x86_64 + * Linux (most, if not all, distributions) - x86, x86_64, ppc64 and armv6l + * Mac OS X 10.04 or higher - x86, x86_64 and ppc64 + +In reality a lot more are supported, however they are not tested regularly. + +To build from source you will need: + + * gcc 3.x or later recommended. Other alternatives which may work + are: clang, Visual C++, Intel's C++ compiler + * unzip + * git or wget + +If you are on a fairly modern *nix system, the following steps should work: + +``` +$ git clone git://github.com/Araq/Nimrod.git +$ cd Nimrod +$ cd build +$ unzip csources.zip +$ cd .. +$ ./build.sh +$ bin/nimrod c koch +$ ./koch boot -d:release +``` + +The install script (``install.sh``) may then be used to install Nimrod, or you +can simply add it to your PATH. + +The above steps can be performed on Windows in a similar fashion, the +``build.bat`` and ``build64.bat`` (for x86_64 systems) are provided to be used +instead of ``build.sh``. + +## Getting help +A [forum](http://forum.nimrod-code.org/) is available if you have any questions, +and you can also get help in the IRC channel +on [Freenode](irc://irc.freenode.net/nimrod) in #nimrod. + +## License +The compiler is licensed under the GPLv2 license, the standard library is +licensed under the LGPL license with a linking exception so that you can link +to it statically. This means that you can use any license for your own programs +developed with Nimrod, allowing you to create commercial applications. + +Read copying.txt for more details. + +Copyright (c) 2004-2013 Andreas Rumpf. +All rights reserved. diff --git a/readme.txt b/readme.txt index c4d3b2ba7..3e39b5f77 100755 --- a/readme.txt +++ b/readme.txt @@ -1,21 +1,66 @@ -=========================================================== - Nimrod Compiler -=========================================================== - -This is the **Nimrod Compiler**. Nimrod is a new statically typed, imperative -programming language, that supports procedural, functional, object oriented and -generic programming styles while remaining simple and efficient. A special -feature that Nimrod inherited from Lisp is that Nimrod's abstract syntax tree -(AST) is part of the specification - this allows a powerful macro system which -can be used to create domain specific languages. - -*Nimrod* is a compiled, garbage-collected systems programming language -which has an excellent productivity/performance ratio. Nimrod's design -focuses on efficiency, expressiveness, elegance (in the order of -priority). - -See the file ``install.txt`` for installation instructions. See the file -``doc/intern.txt`` for the internal documentation for developers. - -Copyright (c) 2004-2012 Andreas Rumpf. -All rights reserved. +# Nimrod Compiler +This repo contains the Nimrod compiler, Nimrod's stdlib, tools and +documentation. + +## Compiling +Compiling the Nimrod compiler is quite straightforward. Because +the Nimrod compiler itself is written in the Nimrod programming language +the C source of an older version of the compiler are needed to bootstrap the +latest version. The C sources are however included with this repository under +the build directory. + +Pre-compiled snapshots of the compiler are also available on +[Nimbuild](http://build.nimrod-code.org/). Your platform however may not +currently be built for. + +The compiler currently supports the following platform and architecture +combinations: + + * Windows (Windows XP or greater) - x86 and x86_64 + * Linux (most, if not all, distributions) - x86, x86_64, ppc64 and armv6l + * Mac OS X 10.04 or higher - x86, x86_64 and ppc64 + +In reality a lot more are supported, however they are not tested regularly. + +To build from source you will need: + + * gcc 3.x or later recommended. Other alternatives which may work + are: clang, Visual C++, Intel's C++ compiler + * unzip + * git or wget + +If you are on a fairly modern *nix system, the following steps should work: + +``` +$ git clone git://github.com/Araq/Nimrod.git +$ cd Nimrod +$ cd build +$ unzip csources.zip +$ cd .. +$ ./build.sh +$ bin/nimrod c koch +$ ./koch boot -d:release +``` + +The install script (``install.sh``) may then be used to install Nimrod, or you +can simply add it to your PATH. + +The above steps can be performed on Windows in a similar fashion, the +``build.bat`` and ``build64.bat`` (for x86_64 systems) are provided to be used +instead of ``build.sh``. + +## Getting help +A [forum](http://forum.nimrod-code.org/) is available if you have any questions, +and you can also get help in the IRC channel +on [Freenode](irc://irc.freenode.net/nimrod) in #nimrod. + +## License +The compiler is licensed under the GPLv2 license, the standard library is +licensed under the LGPL license with a linking exception so that you can link +to it statically. This means that you can use any license for your own programs +developed with Nimrod, allowing you to create commercial applications. + +Read copying.txt for more details. + +Copyright (c) 2004-2013 Andreas Rumpf. +All rights reserved. diff --git a/tests/compile/mexporta.nim b/tests/compile/mexporta.nim new file mode 100644 index 000000000..b7d4ddec9 --- /dev/null +++ b/tests/compile/mexporta.nim @@ -0,0 +1,8 @@ +# module A +import mexportb +export mexportb.TMyObject, mexportb.xyz + +export mexportb.q + +proc `$`*(x: TMyObject): string = "my object" + diff --git a/tests/compile/mexportb.nim b/tests/compile/mexportb.nim new file mode 100644 index 000000000..10d89f388 --- /dev/null +++ b/tests/compile/mexportb.nim @@ -0,0 +1,7 @@ +# module B +type TMyObject* = object + +const xyz* = 13 + +proc q*(x: int): int = 6 +proc q*(x: string): string = "8" diff --git a/tests/compile/tclosure4.nim b/tests/compile/tclosure4.nim new file mode 100644 index 000000000..8e08376b6 --- /dev/null +++ b/tests/compile/tclosure4.nim @@ -0,0 +1,13 @@ + +import json, tables + +proc run(json_params: TTable) = + let json_elems = json_params["files"].elems + # These fail compilation. + var files = map(json_elems, proc (x: PJsonNode): string = x.str) + #var files = json_elems.map do (x: PJsonNode) -> string: x.str + echo "Hey!" + +when isMainModule: + let text = """{"files": ["a", "b", "c"]}""" + run(toTable((text.parseJson).fields)) diff --git a/tests/compile/tclosurebug2.nim b/tests/compile/tclosurebug2.nim new file mode 100644 index 000000000..ec4f0045b --- /dev/null +++ b/tests/compile/tclosurebug2.nim @@ -0,0 +1,194 @@ +import hashes, math + +type + TSlotEnum = enum seEmpty, seFilled, seDeleted + TKeyValuePair[A, B] = tuple[slot: TSlotEnum, key: A, val: B] + TKeyValuePairSeq[A, B] = seq[TKeyValuePair[A, B]] + + TOrderedKeyValuePair[A, B] = tuple[ + slot: TSlotEnum, next: int, key: A, val: B] + TOrderedKeyValuePairSeq[A, B] = seq[TOrderedKeyValuePair[A, B]] + TOrderedTable*[A, B] = object ## table that remembers insertion order + data: TOrderedKeyValuePairSeq[A, B] + counter, first, last: int + +const + growthFactor = 2 + +proc mustRehash(length, counter: int): bool {.inline.} = + assert(length > counter) + result = (length * 2 < counter * 3) or (length - counter < 4) + +proc nextTry(h, maxHash: THash): THash {.inline.} = + result = ((5 * h) + 1) and maxHash + +template rawGetImpl() {.dirty.} = + var h: THash = hash(key) and high(t.data) # start with real hash value + while t.data[h].slot != seEmpty: + if t.data[h].key == key and t.data[h].slot == seFilled: + return h + h = nextTry(h, high(t.data)) + result = -1 + +template rawInsertImpl() {.dirty.} = + var h: THash = hash(key) and high(data) + while data[h].slot == seFilled: + h = nextTry(h, high(data)) + data[h].key = key + data[h].val = val + data[h].slot = seFilled + +template AddImpl() {.dirty.} = + if mustRehash(len(t.data), t.counter): Enlarge(t) + RawInsert(t, t.data, key, val) + inc(t.counter) + +template PutImpl() {.dirty.} = + var index = RawGet(t, key) + if index >= 0: + t.data[index].val = val + else: + AddImpl() + +proc len*[A, B](t: TOrderedTable[A, B]): int {.inline.} = + ## returns the number of keys in `t`. + result = t.counter + +template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = + var h = t.first + while h >= 0: + var nxt = t.data[h].next + if t.data[h].slot == seFilled: yieldStmt + h = nxt + +iterator pairs*[A, B](t: TOrderedTable[A, B]): tuple[key: A, val: B] = + ## iterates over any (key, value) pair in the table `t` in insertion + ## order. + forAllOrderedPairs: + yield (t.data[h].key, t.data[h].val) + +iterator mpairs*[A, B](t: var TOrderedTable[A, B]): tuple[key: A, val: var B] = + ## iterates over any (key, value) pair in the table `t` in insertion + ## order. The values can be modified. + forAllOrderedPairs: + yield (t.data[h].key, t.data[h].val) + +iterator keys*[A, B](t: TOrderedTable[A, B]): A = + ## iterates over any key in the table `t` in insertion order. + forAllOrderedPairs: + yield t.data[h].key + +iterator values*[A, B](t: TOrderedTable[A, B]): B = + ## iterates over any value in the table `t` in insertion order. + forAllOrderedPairs: + yield t.data[h].val + +iterator mvalues*[A, B](t: var TOrderedTable[A, B]): var B = + ## iterates over any value in the table `t` in insertion order. The values + ## can be modified. + forAllOrderedPairs: + yield t.data[h].val + +proc RawGet[A, B](t: TOrderedTable[A, B], key: A): int = + rawGetImpl() + +proc `[]`*[A, B](t: TOrderedTable[A, B], key: A): B = + ## retrieves the value at ``t[key]``. If `key` is not in `t`, + ## default empty value for the type `B` is returned + ## and no exception is raised. One can check with ``hasKey`` whether the key + ## exists. + var index = RawGet(t, key) + if index >= 0: result = t.data[index].val + +proc mget*[A, B](t: var TOrderedTable[A, B], key: A): var B = + ## retrieves the value at ``t[key]``. The value can be modified. + ## If `key` is not in `t`, the ``EInvalidKey`` exception is raised. + var index = RawGet(t, key) + if index >= 0: result = t.data[index].val + else: raise newException(EInvalidKey, "key not found: " & $key) + +proc hasKey*[A, B](t: TOrderedTable[A, B], key: A): bool = + ## returns true iff `key` is in the table `t`. + result = rawGet(t, key) >= 0 + +proc RawInsert[A, B](t: var TOrderedTable[A, B], + data: var TOrderedKeyValuePairSeq[A, B], + key: A, val: B) = + rawInsertImpl() + data[h].next = -1 + if t.first < 0: t.first = h + if t.last >= 0: data[t.last].next = h + t.last = h + +proc Enlarge[A, B](t: var TOrderedTable[A, B]) = + var n: TOrderedKeyValuePairSeq[A, B] + newSeq(n, len(t.data) * growthFactor) + var h = t.first + t.first = -1 + t.last = -1 + while h >= 0: + var nxt = t.data[h].next + if t.data[h].slot == seFilled: + RawInsert(t, n, t.data[h].key, t.data[h].val) + h = nxt + swap(t.data, n) + +proc `[]=`*[A, B](t: var TOrderedTable[A, B], key: A, val: B) = + ## puts a (key, value)-pair into `t`. + putImpl() + +proc add*[A, B](t: var TOrderedTable[A, B], key: A, val: B) = + ## puts a new (key, value)-pair into `t` even if ``t[key]`` already exists. + AddImpl() + +proc initOrderedTable*[A, B](initialSize=64): TOrderedTable[A, B] = + ## creates a new ordered hash table that is empty. `initialSize` needs to be + ## a power of two. + assert isPowerOfTwo(initialSize) + result.counter = 0 + result.first = -1 + result.last = -1 + newSeq(result.data, initialSize) + +proc toOrderedTable*[A, B](pairs: openarray[tuple[key: A, + val: B]]): TOrderedTable[A, B] = + ## creates a new ordered hash table that contains the given `pairs`. + result = initOrderedTable[A, B](nextPowerOfTwo(pairs.len+10)) + for key, val in items(pairs): result[key] = val + +proc sort*[A, B](t: var TOrderedTable[A,B], + cmp: proc (x, y: tuple[key: A, val: B]): int {.closure.}) = + ## sorts the ordered table so that the entry with the highest counter comes + ## first. This is destructive (with the advantage of being efficient)! + ## You must not modify `t` afterwards! + ## You can use the iterators `pairs`, `keys`, and `values` to iterate over + ## `t` in the sorted order. + + # we use shellsort here; fast enough and simple + var h = 1 + while true: + h = 3 * h + 1 + if h >= high(t.data): break + while true: + h = h div 3 + for i in countup(h, high(t.data)): + var j = i + #echo(t.data.len, " ", j, " - ", h) + #echo(repr(t.data[j-h])) + proc rawCmp(x, y: TOrderedKeyValuePair[A, B]): int = + if x.slot in {seEmpty, seDeleted} and y.slot in {seEmpty, seDeleted}: + return 0 + elif x.slot in {seEmpty, seDeleted}: + return -1 + elif y.slot in {seEmpty, seDeleted}: + return 1 + else: + let item1 = (x.key, x.val) + let item2 = (y.key, y.val) + return cmp(item1, item2) + + while rawCmp(t.data[j-h], t.data[j]) <= 0: + swap(t.data[j], t.data[j-h]) + j = j-h + if j < h: break + if h == 1: break diff --git a/tests/compile/tcolonisproc.nim b/tests/compile/tcolonisproc.nim new file mode 100644 index 000000000..e55587dfc --- /dev/null +++ b/tests/compile/tcolonisproc.nim @@ -0,0 +1,12 @@ + +proc p(a, b: int, c: proc ()) = + c() + + +p(1, 3): + echo 1 + echo 3 + +p(1, 1, proc() = + echo 1 + echo 2) diff --git a/tests/compile/teffects1.nim b/tests/compile/teffects1.nim new file mode 100644 index 000000000..49af28469 --- /dev/null +++ b/tests/compile/teffects1.nim @@ -0,0 +1,17 @@ + +type + PMenu = ref object + PMenuItem = ref object + +proc createMenuItem*(menu: PMenu, label: string, + action: proc (i: PMenuItem, p: pointer) {.cdecl.}) = nil + +var s: PMenu +createMenuItem(s, "Go to definition...", + proc (i: PMenuItem, p: pointer) {.cdecl.} = + try: + echo(i.repr) + except EInvalidValue: + echo("blah") +) + diff --git a/tests/compile/texport.nim b/tests/compile/texport.nim new file mode 100644 index 000000000..99228dfce --- /dev/null +++ b/tests/compile/texport.nim @@ -0,0 +1,10 @@ +discard """ + output: "my object68" +""" + +import mexporta + +# B.TMyObject has been imported implicitly here: +var x: TMyObject +echo($x, q(0), q"0") + diff --git a/tests/compile/tforwardgeneric.nim b/tests/compile/tforwardgeneric.nim index 84bef15cc..ef263d733 100644 --- a/tests/compile/tforwardgeneric.nim +++ b/tests/compile/tforwardgeneric.nim @@ -1,5 +1,6 @@ discard """ output: "1.0000000000000000e+00 10" + ccodecheck: "!@'ClEnv'" """ proc p[T](a, b: T): T diff --git a/tests/compile/tircbot.nim b/tests/compile/tircbot.nim index 10482c3f6..d16c99b69 100644 --- a/tests/compile/tircbot.nim +++ b/tests/compile/tircbot.nim @@ -272,7 +272,7 @@ proc handleWebMessage(state: PState, line: string) = message.add(limitCommitMsg(commit["message"].str)) # Send message to #nimrod. - state.ircClient[].privmsg(joinChans[0], message) + state.ircClient.privmsg(joinChans[0], message) elif json.existsKey("redisinfo"): assert json["redisinfo"].existsKey("port") #let redisPort = json["redisinfo"]["port"].num @@ -336,10 +336,11 @@ proc hubConnect(state: PState) = state.dispatcher.register(state.sock) -proc handleIrc(irc: var TAsyncIRC, event: TIRCEvent, state: PState) = +proc handleIrc(irc: PAsyncIRC, event: TIRCEvent, state: PState) = case event.typ + of EvConnected: nil of EvDisconnected: - while not state.ircClient[].isConnected: + while not state.ircClient.isConnected: try: state.ircClient.connect() except: @@ -355,12 +356,12 @@ proc handleIrc(irc: var TAsyncIRC, event: TIRCEvent, state: PState) = let msg = event.params[event.params.len-1] let words = msg.split(' ') template pm(msg: string): stmt = - state.ircClient[].privmsg(event.origin, msg) + state.ircClient.privmsg(event.origin, msg) case words[0] of "!ping": pm("pong") of "!lag": - if state.ircClient[].getLag != -1.0: - var lag = state.ircClient[].getLag + if state.ircClient.getLag != -1.0: + var lag = state.ircClient.getLag lag = lag * 1000.0 pm($int(lag) & "ms between me and the server.") else: @@ -433,7 +434,7 @@ proc open(port: TPort = TPort(5123)): PState = res.hubPort = port res.hubConnect() let hirc = - proc (a: var TAsyncIRC, ev: TIRCEvent) = + proc (a: PAsyncIRC, ev: TIRCEvent) = handleIrc(a, ev, res) # Connect to the irc server. res.ircClient = AsyncIrc(ircServer, nick = botNickname, user = botNickname, diff --git a/tests/compile/titerovl.nim b/tests/compile/titerovl.nim new file mode 100644 index 000000000..be665b2b7 --- /dev/null +++ b/tests/compile/titerovl.nim @@ -0,0 +1,21 @@ +discard """ + output: '''9 +1 +2 +3 +''' +""" + +# Test the new overloading rules for iterators: + +# test that iterator 'p' is preferred: +proc p(): seq[int] = @[1, 2, 3] +iterator p(): int = yield 9 + +for x in p(): echo x + +# test that 'q' works in this position: +proc q(): seq[int] = @[1, 2, 3] + +for x in q(): echo x + diff --git a/tests/compile/tnamedparamanonproc.nim b/tests/compile/tnamedparamanonproc.nim new file mode 100644 index 000000000..272b84e91 --- /dev/null +++ b/tests/compile/tnamedparamanonproc.nim @@ -0,0 +1,14 @@ + +type + PButton = ref object + TButtonClicked = proc(button: PButton) {.nimcall.} + +proc newButton*(onClick: TButtonClicked) = + nil + +proc main() = + newButton(onClick = proc(b: PButton) = + var requestomat = 12 + ) + +main() diff --git a/tests/compile/tobject3.nim b/tests/compile/tobject3.nim new file mode 100644 index 000000000..935e6ca8c --- /dev/null +++ b/tests/compile/tobject3.nim @@ -0,0 +1,28 @@ +type + TFoo = ref object of TObject + Data: int + TBar = ref object of TFoo + nil + TBar2 = ref object of TBar + d2: int + +template super(self: TBar): TFoo = self + +template super(self: TBar2): TBar = self + +proc Foo(self: TFoo) = + echo "TFoo" + +#proc Foo(self: TBar) = +# echo "TBar" +# Foo(super(self)) +# works when this code is uncommented + +proc Foo(self: TBar2) = + echo "TBar2" + Foo(super(self)) + +var b: TBar2 +new(b) + +Foo(b) diff --git a/tests/compile/toverprc.nim b/tests/compile/toverprc.nim index 43271b684..f3aa66b80 100755 --- a/tests/compile/toverprc.nim +++ b/tests/compile/toverprc.nim @@ -21,7 +21,7 @@ proc takeParseInt(x: proc (y: string): int {.noSideEffect.}): int = result = x("123") echo "Give a list of numbers (separated by spaces): " -var x = stdin.readline.split.each(parseInt).max +var x = stdin.readline.split.map(parseInt).max echo x, " is the maximum!" echo "another number: ", takeParseInt(parseInt) diff --git a/tests/compile/tsecondarrayproperty.nim b/tests/compile/tsecondarrayproperty.nim new file mode 100644 index 000000000..07fdac1c4 --- /dev/null +++ b/tests/compile/tsecondarrayproperty.nim @@ -0,0 +1,28 @@ + +type + TFoo = object + data: array[0..100, int] + TSecond = distinct TFoo + +proc `[]` (self: var TFoo, x: int): var int = + return self.data[x] + +proc `[]=` (self: var TFoo, x, y: int) = + # only `[]` returning a 'var T' seems to not work for now :-/ + self.data[x] = y + +proc second(self: var TFoo): var TSecond = + return TSecond(self) + +proc `[]`(self: var TSecond, x: int): var int = + return TFoo(self).data[2*x] + +var f: TFoo + +for i in 0..f.data.high: f[i] = 2 * i + +echo f.second[1] + +#echo `second[]`(f,1) +# this is the only way I could use it, but not what I expected + diff --git a/tests/compile/twalker.nim b/tests/compile/twalker.nim index 3fdd8769b..89e6c2b9d 100755 --- a/tests/compile/twalker.nim +++ b/tests/compile/twalker.nim @@ -1,7 +1,7 @@ # iterate over all files with a given filter: import - os, times + "../../lib/pure/os.nim", ../../ lib / pure / times proc main(filter: string) = for filename in walkFiles(filter): diff --git a/tests/patterns/thoist.nim b/tests/patterns/thoist.nim new file mode 100644 index 000000000..7d14c0abf --- /dev/null +++ b/tests/patterns/thoist.nim @@ -0,0 +1,13 @@ +discard """ + output: '''true +true''' +""" + +import pegs + +template optPeg{peg(pattern)}(pattern: string{lit}): TPeg = + var gl {.global, gensym.} = peg(pattern) + gl + +echo match("(a b c)", peg"'(' @ ')'") +echo match("W_HI_Le", peg"\y 'while'") diff --git a/tests/patterns/tpartial.nim b/tests/patterns/tpartial.nim new file mode 100644 index 000000000..fdaa3414a --- /dev/null +++ b/tests/patterns/tpartial.nim @@ -0,0 +1,11 @@ +discard """ + output: '''-2''' +""" + +proc p(x, y: int; cond: bool): int = + result = if cond: x + y else: x - y + +template optP{p(x, y, true)}(x, y: expr): expr = x - y +template optP{p(x, y, false)}(x, y: expr): expr = x + y + +echo p(2, 4, true) diff --git a/tests/reject/tenummix.nim b/tests/reject/tenummix.nim index 8a2a19c1e..ec99a2b6a 100644 --- a/tests/reject/tenummix.nim +++ b/tests/reject/tenummix.nim @@ -1,6 +1,6 @@ discard """ file: "system.nim" - line: 663 + line: 669 errormsg: "type mismatch" """ diff --git a/tests/reject/timportexcept.nim b/tests/reject/timportexcept.nim new file mode 100644 index 000000000..93a7fd642 --- /dev/null +++ b/tests/reject/timportexcept.nim @@ -0,0 +1,10 @@ +discard """ + line: 9 + errormsg: "undeclared identifier: '%'" +""" + +import strutils except `%` + +# doesn't work +echo "$1" % "abc" + diff --git a/tests/reject/tnotnil.nim b/tests/reject/tnotnil.nim index 8676aedf8..b02e33713 100644 --- a/tests/reject/tnotnil.nim +++ b/tests/reject/tnotnil.nim @@ -1,5 +1,5 @@ discard """ - line: 11 + line: 22 errormgs: "type mismatch" """ @@ -7,9 +7,17 @@ type PObj = ref TObj not nil TObj = object x: int + + MyString = string not nil -var x: PObj = nil +#var x: PObj = nil proc p(x: string not nil): int = result = 45 +proc q(x: MyString) = nil +proc q2(x: string) = nil + +q2(nil) +q(nil) + diff --git a/tests/run/tastoverload1.nim b/tests/run/tastoverload1.nim new file mode 100644 index 000000000..c8705547a --- /dev/null +++ b/tests/run/tastoverload1.nim @@ -0,0 +1,21 @@ +discard """ + output: '''string literal +no string literal +no string literal''' +""" + +proc optLit(a: string{lit}) = + echo "string literal" + +proc optLit(a: string) = + echo "no string literal" + +const + constant = "abc" + +var + variable = "xyz" + +optLit("literal") +optLit(constant) +optLit(variable) diff --git a/tests/run/tdomulttest.nim b/tests/run/tdomulttest.nim new file mode 100644 index 000000000..4ee6de128 --- /dev/null +++ b/tests/run/tdomulttest.nim @@ -0,0 +1,17 @@ +discard """ + file: "tdomulttest.nim" + output: "555\ntest\nmulti lines\n99999999\nend" + disabled: true +""" +proc foo(bar, baz: proc (x: int): int) = + echo bar(555) + echo baz(99999999) + +foo do (x: int) -> int: + return x +do (x: int) -> int: + echo("test") + echo("multi lines") + return x + +echo("end") \ No newline at end of file diff --git a/tests/run/tgenericconverter.nim b/tests/run/tgenericconverter.nim new file mode 100644 index 000000000..e1c9f7c4c --- /dev/null +++ b/tests/run/tgenericconverter.nim @@ -0,0 +1,30 @@ +discard """ + output: '''666 +666''' +""" + +# test the new generic converters: + +type + TFoo2[T] = object + x: T + + TFoo[T] = object + data: array[0..100, T] + +converter toFoo[T](a: TFoo2[T]): TFoo[T] = + result.data[0] = a.x + +proc p(a: TFoo[int]) = + echo a.data[0] + +proc q[T](a: TFoo[T]) = + echo a.data[0] + + +var + aa: TFoo2[int] +aa.x = 666 + +p aa +q aa diff --git a/tests/run/titer9.nim b/tests/run/titer9.nim index 0d6c466c1..99874e70a 100644 --- a/tests/run/titer9.nim +++ b/tests/run/titer9.nim @@ -4,17 +4,17 @@ discard """ 0''' """ -iterator count(x: int, skip: bool): int {.closure.} = +iterator count[T](x: T, skip: bool): int {.closure.} = if skip: return x+10 else: yield x+1 if skip: return x+10 else: yield x+2 -proc takeProc(x: iterator (x: int, skip: bool): int) = +proc takeProc[T](x: iterator (x: T, skip: bool): int) = echo x(4, false) echo x(4, true) echo x(4, false) -takeProc(count) +takeProc(count[int]) diff --git a/tests/run/tmultim6.nim b/tests/run/tmultim6.nim new file mode 100644 index 000000000..5f45f572a --- /dev/null +++ b/tests/run/tmultim6.nim @@ -0,0 +1,30 @@ +discard """ + output: "collide: unit, thing | collide: unit, thing | collide: thing, unit |" +""" +# Test multi methods + +type + TThing = object {.inheritable.} + TUnit[T] = object of TThing + x: T + TParticle = object of TThing + a, b: int + +method collide(a, b: TThing) {.inline.} = + quit "to override!" + +method collide[T](a: TThing, b: TUnit[T]) {.inline.} = + write stdout, "collide: thing, unit | " + +method collide[T](a: TUnit[T], b: TThing) {.inline.} = + write stdout, "collide: unit, thing | " + +proc test(a, b: TThing) {.inline.} = + collide(a, b) + +var + a: TThing + b, c: TUnit[string] +collide(b, TThing(c)) +test(b, c) +collide(a, b) diff --git a/tests/run/ttables.nim b/tests/run/ttables.nim index 3eb17a803..681ff5424 100755 --- a/tests/run/ttables.nim +++ b/tests/run/ttables.nim @@ -19,6 +19,25 @@ const "50": 344490, "60": 344491, "70": 344492, "80": 344497} + sorteddata = { + "---00": 346677844, + "0": 34404, + "1": 344004, + "10": 34484, + "11": 34474, + "12": 789, + "19": 34464, + "2": 344774, "20": 34454, + "3": 342244, "30": 34141244, + "34": 123456, + "4": 3412344, "40": 344114, + "5": 341232144, "50": 344490, + "6": 34214544, "60": 344491, + "7": 3434544, "70": 344492, + "8": 344544, "80": 344497, + "9": 34435644, + "90": 343} + block tableTest1: var t = initTable[tuple[x, y: int], string]() t[(0,0)] = "00" @@ -86,5 +105,25 @@ block countTableTest1: block SyntaxTest: var x = toTable[int, string]({:}) +proc orderedTableSortTest() = + var t = initOrderedTable[string, int](2) + for key, val in items(data): t[key] = val + for key, val in items(data): assert t[key] == val + t.sort(proc (x, y: tuple[key: string, val: int]): int = cmp(x.key, y.key)) + var i = 0 + # `pairs` needs to yield in sorted order: + for key, val in pairs(t): + doAssert key == sorteddata[i][0] + doAssert val == sorteddata[i][1] + inc(i) + + # check that lookup still works: + for key, val in pairs(t): + doAssert val == t[key] + # check that insert still works: + t["newKeyHere"] = 80 + + +orderedTableSortTest() echo "true" diff --git a/tests/run/ttoseq.nim b/tests/run/ttoseq.nim index ec49489d0..34cc4824b 100755 --- a/tests/run/ttoseq.nim +++ b/tests/run/ttoseq.nim @@ -1,12 +1,11 @@ discard """ - output: "23456" + output: "2345623456" """ -template toSeq*(iter: expr): expr {.immediate.} = - var result: seq[type(iter)] = @[] - for x in iter: add(result, x) - result - +import sequtils + +for x in toSeq(countup(2, 6)): + stdout.write(x) for x in items(toSeq(countup(2, 6))): stdout.write(x) diff --git a/tests/specials.nim b/tests/specials.nim index ba46e67b4..08013ea7d 100644 --- a/tests/specials.nim +++ b/tests/specials.nim @@ -1,7 +1,7 @@ # # # Nimrod Tester -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. diff --git a/tests/tester.nim b/tests/tester.nim index 8156dab7f..60c84403e 100755 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -1,7 +1,7 @@ # # # Nimrod Tester -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -25,22 +25,35 @@ const type TTestAction = enum actionCompile, actionRun, actionReject - TSpec {.pure.} = object + TResultEnum = enum + reNimrodcCrash, # nimrod compiler seems to have crashed + reMsgsDiffer, # error messages differ + reFilesDiffer, # expected and given filenames differ + reLinesDiffer, # expected and given line numbers differ + reOutputsDiffer, + reExitcodesDiffer, + reInvalidPeg, + reCodegenFailure, + reCodeNotFound, + reExeNotFound, + reIgnored, # test is ignored + reSuccess # test was successful + TTarget = enum + targetC, targetCpp, targetObjC, targetJS + + TSpec = object action: TTestAction file, cmd: string outp: string line, exitCode: int msg: string - err: bool - disabled: bool + ccodeCheck: string + err: TResultEnum substr: bool - TResults {.pure.} = object + TResults = object total, passed, skipped: int data: string - TResultEnum = enum - reFailure, reIgnored, reSuccess - # ----------------------- Spec parser ---------------------------------------- when not defined(parseCfgBool): @@ -82,9 +95,9 @@ template parseSpecAux(fillResult: stmt) {.immediate.} = proc parseSpec(filename: string): TSpec = result.file = filename - result.err = true result.msg = "" result.outp = "" + result.ccodeCheck = "" result.cmd = cmdTemplate parseSpecAux: case normalize(e.key) @@ -103,8 +116,10 @@ proc parseSpec(filename: string): TSpec = of "exitcode": discard parseInt(e.value, result.exitCode) of "errormsg", "msg": result.msg = e.value - of "disabled": result.disabled = parseCfgBool(e.value) + of "disabled": + if parseCfgBool(e.value): result.err = reIgnored of "cmd": result.cmd = e.value + of "ccodecheck": result.ccodeCheck = e.value else: echo ignoreMsg(p, e) # ---------------------------------------------------------------------------- @@ -134,7 +149,6 @@ proc callCompiler(cmdTemplate, filename, options: string): TSpec = result.msg = "" result.file = "" result.outp = "" - result.err = true result.line = -1 if err =~ pegLineError: result.file = extractFilename(matches[0]) @@ -143,7 +157,7 @@ proc callCompiler(cmdTemplate, filename, options: string): TSpec = elif err =~ pegOtherError: result.msg = matches[0] elif suc =~ pegSuccess: - result.err = false + result.err = reSuccess proc initResults: TResults = result.total = 0 @@ -164,9 +178,9 @@ proc `$`(x: TResults): string = proc colorResult(r: TResultEnum): string = case r - of reFailure: result = "<span style=\"color:red\">no</span>" of reIgnored: result = "<span style=\"color:fuchsia\">ignored</span>" of reSuccess: result = "<span style=\"color:green\">yes</span>" + else: result = "<span style=\"color:red\">no</span>" const TableHeader4 = "<table border=\"1\"><tr><td>Test</td><td>Expected</td>" & @@ -217,12 +231,12 @@ proc listResults(reject, compile, run: TResults) = proc cmpMsgs(r: var TResults, expected, given: TSpec, test: string) = if strip(expected.msg) notin strip(given.msg): - r.addResult(test, expected.msg, given.msg, reFailure) + r.addResult(test, expected.msg, given.msg, reMsgsDiffer) elif extractFilename(expected.file) != extractFilename(given.file) and "internal error:" notin expected.msg: - r.addResult(test, expected.file, given.file, reFailure) + r.addResult(test, expected.file, given.file, reFilesDiffer) elif expected.line != given.line and expected.line != 0: - r.addResult(test, $expected.line, $given.line, reFailure) + r.addResult(test, $expected.line, $given.line, reLinesDiffer) else: r.addResult(test, expected.msg, given.msg, reSuccess) inc(r.passed) @@ -233,7 +247,7 @@ proc rejectSingleTest(r: var TResults, test, options: string) = inc(r.total) echo t var expected = parseSpec(test) - if expected.disabled: + if expected.err == reIgnored: r.addResult(t, "", "", reIgnored) inc(r.skipped) else: @@ -244,31 +258,47 @@ proc reject(r: var TResults, dir, options: string) = ## handle all the tests that the compiler should reject for test in os.walkFiles(dir / "t*.nim"): rejectSingleTest(r, test, options) +proc codegenCheck(test, check, ext: string, given: var TSpec) = + if check.len > 0: + try: + let (path, name, ext2) = test.splitFile + echo path / "nimcache" / name.changeFileExt(ext) + let contents = readFile(path / "nimcache" / name.changeFileExt(ext)).string + if contents.find(check.peg) < 0: + given.err = reCodegenFailure + except EInvalidValue: + given.err = reInvalidPeg + except EIO: + given.err = reCodeNotFound + +proc codegenChecks(test: string, expected: TSpec, given: var TSpec) = + codegenCheck(test, expected.ccodeCheck, ".c", given) + proc compile(r: var TResults, pattern, options: string) = for test in os.walkFiles(pattern): let t = extractFilename(test) echo t inc(r.total) let expected = parseSpec(test) - if expected.disabled: + if expected.err == reIgnored: r.addResult(t, "", reIgnored) inc(r.skipped) else: var given = callCompiler(expected.cmd, test, options) - r.addResult(t, given.msg, if given.err: reFailure else: reSuccess) - if not given.err: inc(r.passed) + if given.err == reSuccess: + codegenChecks(test, expected, given) + r.addResult(t, given.msg, given.err) + if given.err == reSuccess: inc(r.passed) proc compileSingleTest(r: var TResults, test, options: string) = + # does not extract the spec because the file is not supposed to have any let test = test.addFileExt(".nim") let t = extractFilename(test) inc(r.total) echo t let given = callCompiler(cmdTemplate, test, options) - r.addResult(t, given.msg, if given.err: reFailure else: reSuccess) - if not given.err: inc(r.passed) - -type - TTarget = enum targetC, targetJS + r.addResult(t, given.msg, given.err) + if given.err == reSuccess: inc(r.passed) proc runSingleTest(r: var TResults, test, options: string, target: TTarget) = var test = test.addFileExt(".nim") @@ -276,13 +306,13 @@ proc runSingleTest(r: var TResults, test, options: string, target: TTarget) = echo t inc(r.total) var expected = parseSpec(test) - if expected.disabled: + if expected.err == reIgnored: r.addResult(t, "", "", reIgnored) inc(r.skipped) else: var given = callCompiler(expected.cmd, test, options) - if given.err: - r.addResult(t, "", given.msg, reFailure) + if given.err != reSuccess: + r.addResult(t, "", given.msg, given.err) else: var exeFile: string if target == targetC: @@ -296,25 +326,26 @@ proc runSingleTest(r: var TResults, test, options: string, target: TTarget) = (if target==targetJS: "node " else: "") & exeFile) if exitCode != expected.ExitCode: r.addResult(t, "exitcode: " & $expected.ExitCode, - "exitcode: " & $exitCode, reFailure) + "exitcode: " & $exitCode, reExitCodesDiffer) else: - var success = strip(buf.string) == strip(expected.outp) - if expected.substr and not success: - success = expected.outp in buf.string - if success: inc(r.passed) - r.addResult(t, expected.outp, - buf.string, if success: reSuccess else: reFailure) + if strip(buf.string) != strip(expected.outp): + if not (expected.substr and expected.outp in buf.string): + given.err = reOutputsDiffer + if given.err == reSuccess: + codeGenChecks(test, expected, given) + if given.err == reSuccess: inc(r.passed) + r.addResult(t, expected.outp, buf.string, given.err) else: - r.addResult(t, expected.outp, "executable not found", reFailure) + r.addResult(t, expected.outp, "executable not found", reExeNotFound) proc runSingleTest(r: var TResults, test, options: string) = runSingleTest(r, test, options, targetC) - + proc run(r: var TResults, dir, options: string) = for test in os.walkFiles(dir / "t*.nim"): runSingleTest(r, test, options) include specials - + proc compileExample(r: var TResults, pattern, options: string) = for test in os.walkFiles(pattern): compileSingleTest(r, test, options) @@ -392,7 +423,12 @@ proc main() = runDLLTests r, p.cmdLineRest.string of "gc": runGCTests(r, p.cmdLineRest.string) - of "test", "comp", "rej": + of "test": + if p.kind != cmdArgument: quit usage + var testFile = p.key.string + p.next() + runSingleTest(r, testFile, p.cmdLineRest.string) + of "comp", "rej": if p.kind != cmdArgument: quit usage var testFile = p.key.string p.next() diff --git a/todo.txt b/todo.txt index 7e940c3e4..7da08f2bb 100755 --- a/todo.txt +++ b/todo.txt @@ -1,186 +1,114 @@ version 0.9.2 ============= - -- test&finish first class iterators: - * nested iterators - * arglist as a type? -- fix closure bug finally -- overloading based on ASTs: 'constraint' should not be in PType but for the - parameter *symbol* +- implement constructors + full 'not nil' checking +- ``restrict`` pragma + backend support +- fix: 'result' is not properly cleaned for NRVO +- fix: exhaustive checking in case statements - - implement ``partial`` pragma for partial evaluation: easily done with AST - overloading - - ``hoist`` pragma for loop hoisting: can be easily done with - AST overloading + global +version 0.9.4 +============= -- improve the compiler as a service -- ``=`` should be overloadable; requires specialization for ``=`` -- implement constructors - make 'bind' default for templates and introduce 'mixin'; special rule for ``[]=`` - implicit deref for parameter matching; overloading based on 'var T' +- ``=`` should be overloadable; requires specialization for ``=`` - optimize genericAssign in the code generator -Bugs ----- +version 0.9.X +============= + +- FFI: + * test libffi on windows + * test: times.format with the FFI +- test&finish first class iterators: + * nested iterators +- implement the missing features wrt inheritance +- improve the compiler as a service +- better support for macros that rewrite procs +- macros need access to types and symbols (partially implemented) +- result = result shr 8 for the "system()" wrapper +- rethink the syntax/grammar: + * parser is not strict enough with newlines + * change comment handling in the AST -- sneaking with qualifiedLookup() is really broken! -- bug: aporia.nim(968, 5) Error: ambiguous identifier: 'DELETE' -- - use a qualifier -- bug: the parser is not strict enough with newlines: 'echo "a" echo "b"' - compiles -- bug: blocks can "export" an identifier but the CCG generates {} for them ... + +Concurrency +----------- + +- shared memory heap: ``shared ref`` etc. The only hard part in the GC is to + "stop the world". However, it may be worthwhile to generate explicit + (or implicit) syncGC() calls in loops. Automatic loop injection seems + troublesome, but maybe we can come up with a simple heuristic. (All procs + that `new` shared memory are syncGC() candidates... But then 'new' itself + calls syncGC() so that's pointless.) Hm instead of an heuristic simply + provide a ``syncgc`` pragma to trigger compiler injection --> more general: + an ``injectLoop`` pragma +- 'writes: []' effect; track reads/writes for shared types +- use the effect system for static deadlock prevention and race detection version 0.9.XX ============== -- improve not-nil types -- make: - p(a, b): - echo a - echo b - - the same as: - - p(a, b, proc() = - echo a - echo b) - -- implement read/write tracking in the effect system -- implement the "snoopResult" pragma; no, make a strutils with string append - semantics instead ... -- implement "closure tuple consists of a single 'ref'" optimization -- JS gen: - - fix exception handling - object branch transitions can't work with the current 'reset'; add a 'reset' with an additional parameter --> re-evaluate this issue after constructors have been added -- fix remaining closure bugs: - - test evals.nim with closures - - what about macros with closures? - -- allow implicit forward declarations of procs via a pragma (so that the - wrappers can deactivate it) -- rethink the syntax: distinction between expr and stmt is unfortunate; - indentation handling is quite complex too; problem with exception handling - is that often the scope of ``try`` is wrong and apart from that ``try`` is - a full blown statement; a ``try`` expression might be a good idea to make - error handling more light-weight -- fix destructors; don't work yet when used as expression -- make use of ``tyIter`` to fix the implicit items/pairs issue -- better support for macros that rewrite procs -- macros need access to types and symbols (partially implemented) +- fix destructors; don't work yet when used as expression; alternative for + version 1: disallow expressions yielding a type with a destructor that are + not in a 'let/var' context (p(a.openFile, b.openFile) makes no sense anyway) - document nimdoc properly finally - make 'clamp' a magic for the range stuff -- we need to support iteration of 2 different data structures in parallel -- proc specialization in the code gen for write barrier specialization -- tlastmod returns wrong results on BSD (Linux, MacOS X: works) -- nested tuple unpacking; tuple unpacking in non-var-context -- make pegs support a compile-time option and make c2nim use regexes instead - per default? -- 'const' objects including case objects -- 'export' feature -- from buggymodule import * except optBroken, optBroken2 -- think about ``{:}.toTable[int, string]()`` -- mocking support with ``tyProxy`` that does: - o.p(x) --> p(o, x) --> myMacro(p, o, x) - - This is really the opposite of ``tyExpr``: - * For parameter ``tyExpr`` any argument matches. - * Argument ``tyProxy`` matches any parameter. - +- better type syntax for functions and tuples: tuple(int, int); (int,int)->int -Library -------- -- suffix trees -- locale support; i18n module -- bignums +Not essential for 1.0.0 +======================= - -Low priority ------------- - -- change how comments are part of the AST +- 'const' objects including case objects +- mocking support with ``tyProxy`` that does: fallback for ``.`` operator +- allow implicit forward declarations of procs via a pragma (so that the + wrappers can deactivate it) +- implement the "snoopResult" pragma; no, make a strutils with string append + semantics instead ... +- implement "closure tuple consists of a single 'ref'" optimization +- optimize method dispatchers - ``with proc `+`(x, y: T): T`` for generic code - new feature: ``distinct T with operations`` -- implement the "easy" constructors idea +- arglist as a type (iterator chaining); variable length type lists for generics - resizing of strings/sequences could take into account the memory that is allocated -- timeout for locks -- compilation cache: - - adapt thread var emulation to care about the new merge operation - - check for interface changes; if only the implementation changes, no - need to recompile clients; er ... what about templates, macros or anything - that has inlining semantics? - codegen should use "NIM_CAST" macro and respect aliasing rules for GCC -- GC: precise stack marking; - escape analysis for string/seq seems to be easy to do too; - even further write barrier specialization -- GC: marker procs Boehm GC - implement marker procs for message passing -- optimize method dispatchers - activate more thread tests -- implement ``--script:sh|bat`` command line option; think about script - generation - implement closures that support nesting of *procs* > 1 -Further optimization ideas -========================== +GC +== -- To optimize further copies away, you want to gather the additional - information inlining would provide, but don't inline for code size reasons. +- precise stack marking; embrace C++ code generation for that +- marker procs for Boehm GC +- implement 'mixed' GC mode -Version 2 and beyond -==================== +Optimizations +============= -- shared memory heap: ``shared ref`` etc. The only hard part in the GC is to - "stop the world". However, it may be worthwhile to generate explicit - (or implicit) syncGC() calls in loops. Automatic loop injection seems - troublesome, but maybe we can come up with a simple heuristic. (All procs - that `new` shared memory are syncGC() candidates... But then 'new' itself - calls syncGC() so that's pointless.) Hm instead of an heuristic simply - provide a ``syncgc`` pragma to trigger compiler injection --> more general: - an ``injectLoop`` pragma +- optimize 'if' with a constant condition +- escape analysis for string/seq seems to be easy to do too; + even further write barrier specialization +- inlining of first class functions +- proc specialization in the code gen for write barrier specialization -- const ptr/ref --> pointless because of aliasing; - much better: 'writes: []' effect - -- language change: inheritance should only work with reference types, so that - the ``type`` field is not needed for objects! --> zero overhead aggregation - BETTER: ``of`` and safe object conversions only work with ref objects. Same - for multi methods. - -- explicit nil types? - * nil seq[int] - * nil string - * nil ref int - * nil ptr THallo - * nil proc - -- better for backwards compatibility: default nilable, but ``not nil`` - notation: - - type - PWindow = ref TWindow not nil - - The problem with ``nil`` is that the language currently relies on it for - implicit initialization. Initialization is different from assignment. The - issues can "easily" dealt with by ensuring: - - var x = myProc() # checks myProc() initializes every pointer explicitely - -- guards for the 'case' statement; generalized case statement; - a guard looks like: - - case x - of nkStmtList if x.value == 0: - - a generalized case statement looks like: - - case x with `=~` + +Bugs +==== + +- sneaking with qualifiedLookup() is really broken! +- aporia.nim(968, 5) Error: ambiguous identifier: 'DELETE' -- + use a qualifier +- blocks can "export" an identifier but the CCG generates {} for them ... +- JS gen: fix exception handling +- the better scoping for locals is the wrong default for endb diff --git a/web/index.txt b/web/index.txt index b81ecae0d..99beb3743 100755 --- a/web/index.txt +++ b/web/index.txt @@ -33,7 +33,7 @@ from that model. # Prints the maximum integer from a list of integers # delimited by whitespace read from stdin. let tokens = stdin.readLine.split - echo tokens.each(parseInt).max, " is the maximum." + echo tokens.map(parseInt).max, " is the maximum." Nimrod is efficient @@ -101,7 +101,6 @@ Roadmap to 1.0 ============== Version 0.9.2 - * overloading based on ASTs (like already possible for term rewriting macros) * better interaction between macros, templates and overloading * the symbol binding rules for generics and templates may change again diff --git a/web/news.txt b/web/news.txt index 6cd120e39..e4e9108e0 100755 --- a/web/news.txt +++ b/web/news.txt @@ -2,7 +2,7 @@ News ==== -2012-XX-XX Version 0.9.2 released +2013-XX-XX Version 0.9.2 released ================================= Version 0.9.2 has been released! Get it `here <download.html>`_. @@ -17,6 +17,7 @@ Library Additions - Added ``system.onRaise`` to support a condition system. - Added ``macros.quote`` for AST quasi-quoting. +- Added ``system.unsafeNew`` to support hacky variable length objects. Changes affecting backwards compatibility @@ -33,6 +34,7 @@ Compiler Additions - The compiler can now warn about shadowed local variables. However, this needs to be turned on explicitly via ``--warning[ShadowIdent]:on``. - The compiler now supports almost every pragma in a ``push`` pragma. +- Generic converters have been implemented. Language Additions @@ -51,6 +53,11 @@ Language Additions that ``nil`` is not allowed. However currently the compiler performs no advanced static checking for this; for now it's merely for documentation purposes. +- An ``export`` statement has been added to the language: It can be used for + symbol forwarding so client modules don't have to import a module's + dependencies explicitly. +- Overloading based on ASTs has been implemented. +- Generics are now supported for multi methods. 2012-09-23 Version 0.9.0 released |