diff options
-rw-r--r-- | compiler/main.nim | 4 | ||||
-rw-r--r-- | compiler/nimconf.nim | 106 | ||||
-rw-r--r-- | compiler/options.nim | 11 | ||||
-rw-r--r-- | compiler/scriptconfig.nim | 13 | ||||
-rw-r--r-- | compiler/writetracking.nim | 288 | ||||
-rw-r--r-- | lib/pure/os.nim | 526 | ||||
-rw-r--r-- | lib/pure/ospaths.nim | 560 | ||||
-rw-r--r-- | lib/pure/times.nim | 32 | ||||
-rw-r--r-- | lib/system.nim | 29 | ||||
-rw-r--r-- | lib/system/nimscript.nim | 31 | ||||
-rw-r--r-- | todo.txt | 2 |
11 files changed, 852 insertions, 750 deletions
diff --git a/compiler/main.nim b/compiler/main.nim index 7c043eb72..3bb6fc343 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -355,7 +355,9 @@ proc mainCommand* = gGlobalOptions.incl(optCaasEnabled) msgs.gErrorMax = high(int) # do not stop after first error serve(mainCommand) - of "nop", "help": discard + of "nop", "help": + # prevent the "success" message: + gCmd = cmdDump else: rawMessage(errInvalidCommandX, command) diff --git a/compiler/nimconf.nim b/compiler/nimconf.nim index b9c78cecc..496bd0123 100644 --- a/compiler/nimconf.nim +++ b/compiler/nimconf.nim @@ -9,106 +9,106 @@ # This module handles the reading of the config file. -import - llstream, nversion, commands, os, strutils, msgs, platform, condsyms, lexer, +import + llstream, nversion, commands, os, strutils, msgs, platform, condsyms, lexer, options, idents, wordrecg, strtabs # ---------------- configuration file parser ----------------------------- # we use Nim's scanner here to save space and work -proc ppGetTok(L: var TLexer, tok: var TToken) = +proc ppGetTok(L: var TLexer, tok: var TToken) = # simple filter rawGetTok(L, tok) while tok.tokType in {tkComment}: rawGetTok(L, tok) - + proc parseExpr(L: var TLexer, tok: var TToken): bool -proc parseAtom(L: var TLexer, tok: var TToken): bool = - if tok.tokType == tkParLe: +proc parseAtom(L: var TLexer, tok: var TToken): bool = + if tok.tokType == tkParLe: ppGetTok(L, tok) result = parseExpr(L, tok) if tok.tokType == tkParRi: ppGetTok(L, tok) else: lexMessage(L, errTokenExpected, "\')\'") - elif tok.ident.id == ord(wNot): + elif tok.ident.id == ord(wNot): ppGetTok(L, tok) result = not parseAtom(L, tok) else: result = isDefined(tok.ident) ppGetTok(L, tok) -proc parseAndExpr(L: var TLexer, tok: var TToken): bool = +proc parseAndExpr(L: var TLexer, tok: var TToken): bool = result = parseAtom(L, tok) - while tok.ident.id == ord(wAnd): + while tok.ident.id == ord(wAnd): ppGetTok(L, tok) # skip "and" var b = parseAtom(L, tok) result = result and b -proc parseExpr(L: var TLexer, tok: var TToken): bool = +proc parseExpr(L: var TLexer, tok: var TToken): bool = result = parseAndExpr(L, tok) - while tok.ident.id == ord(wOr): + while tok.ident.id == ord(wOr): ppGetTok(L, tok) # skip "or" var b = parseAndExpr(L, tok) result = result or b -proc evalppIf(L: var TLexer, tok: var TToken): bool = +proc evalppIf(L: var TLexer, tok: var TToken): bool = ppGetTok(L, tok) # skip 'if' or 'elif' result = parseExpr(L, tok) if tok.tokType == tkColon: ppGetTok(L, tok) else: lexMessage(L, errTokenExpected, "\':\'") - + var condStack: seq[bool] = @[] -proc doEnd(L: var TLexer, tok: var TToken) = +proc doEnd(L: var TLexer, tok: var TToken) = if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if") ppGetTok(L, tok) # skip 'end' setLen(condStack, high(condStack)) -type - TJumpDest = enum +type + TJumpDest = enum jdEndif, jdElseEndif proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest) -proc doElse(L: var TLexer, tok: var TToken) = +proc doElse(L: var TLexer, tok: var TToken) = if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if") ppGetTok(L, tok) if tok.tokType == tkColon: ppGetTok(L, tok) if condStack[high(condStack)]: jumpToDirective(L, tok, jdEndif) - -proc doElif(L: var TLexer, tok: var TToken) = + +proc doElif(L: var TLexer, tok: var TToken) = if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if") var res = evalppIf(L, tok) if condStack[high(condStack)] or not res: jumpToDirective(L, tok, jdElseEndif) else: condStack[high(condStack)] = true - -proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest) = + +proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest) = var nestedIfs = 0 - while true: + while true: if tok.ident != nil and tok.ident.s == "@": ppGetTok(L, tok) case whichKeyword(tok.ident) - of wIf: + of wIf: inc(nestedIfs) - of wElse: + of wElse: if dest == jdElseEndif and nestedIfs == 0: doElse(L, tok) - break - of wElif: + break + of wElif: if dest == jdElseEndif and nestedIfs == 0: doElif(L, tok) - break - of wEnd: - if nestedIfs == 0: + break + of wEnd: + if nestedIfs == 0: doEnd(L, tok) - break + break if nestedIfs > 0: dec(nestedIfs) - else: + else: discard ppGetTok(L, tok) elif tok.tokType == tkEof: lexMessage(L, errTokenExpected, "@end") else: ppGetTok(L, tok) - -proc parseDirective(L: var TLexer, tok: var TToken) = + +proc parseDirective(L: var TLexer, tok: var TToken) = ppGetTok(L, tok) # skip @ case whichKeyword(tok.ident) of wIf: @@ -126,13 +126,13 @@ proc parseDirective(L: var TLexer, tok: var TToken) = ppGetTok(L, tok) else: case tok.ident.s.normalize - of "putenv": + of "putenv": ppGetTok(L, tok) var key = tokToStr(tok) ppGetTok(L, tok) os.putEnv(key, tokToStr(tok)) ppGetTok(L, tok) - of "prependenv": + of "prependenv": ppGetTok(L, tok) var key = tokToStr(tok) ppGetTok(L, tok) @@ -145,17 +145,17 @@ proc parseDirective(L: var TLexer, tok: var TToken) = os.putEnv(key, os.getEnv(key) & tokToStr(tok)) ppGetTok(L, tok) else: lexMessage(L, errInvalidDirectiveX, tokToStr(tok)) - -proc confTok(L: var TLexer, tok: var TToken) = + +proc confTok(L: var TLexer, tok: var TToken) = ppGetTok(L, tok) - while tok.ident != nil and tok.ident.s == "@": + while tok.ident != nil and tok.ident.s == "@": parseDirective(L, tok) # else: give the token to the parser - -proc checkSymbol(L: TLexer, tok: TToken) = - if tok.tokType notin {tkSymbol..pred(tkIntLit), tkStrLit..tkTripleStrLit}: + +proc checkSymbol(L: TLexer, tok: TToken) = + if tok.tokType notin {tkSymbol..pred(tkIntLit), tkStrLit..tkTripleStrLit}: lexMessage(L, errIdentifierExpected, tokToStr(tok)) - -proc parseAssignment(L: var TLexer, tok: var TToken) = + +proc parseAssignment(L: var TLexer, tok: var TToken) = if tok.ident.id == getIdent("-").id or tok.ident.id == getIdent("--").id: confTok(L, tok) # skip unnecessary prefix var info = getLineInfo(L, tok) # save for later in case of an error @@ -163,13 +163,13 @@ proc parseAssignment(L: var TLexer, tok: var TToken) = var s = tokToStr(tok) confTok(L, tok) # skip symbol var val = "" - while tok.tokType == tkDot: + while tok.tokType == tkDot: add(s, '.') confTok(L, tok) checkSymbol(L, tok) add(s, tokToStr(tok)) confTok(L, tok) - if tok.tokType == tkBracketLe: + if tok.tokType == tkBracketLe: # BUGFIX: val, not s! # BUGFIX: do not copy '['! confTok(L, tok) @@ -180,7 +180,7 @@ proc parseAssignment(L: var TLexer, tok: var TToken) = else: lexMessage(L, errTokenExpected, "']'") add(val, ']') let percent = tok.ident.id == getIdent("%=").id - if tok.tokType in {tkColon, tkEquals} or percent: + if tok.tokType in {tkColon, tkEquals} or percent: if len(val) > 0: add(val, ':') confTok(L, tok) # skip ':' or '=' or '%' checkSymbol(L, tok) @@ -226,15 +226,7 @@ proc getSystemConfigPath(filename: string): string = if not existsFile(result): result = "/etc/" & filename proc loadConfigs*(cfg: string) = - # set default value (can be overwritten): - if libpath == "": - # choose default libpath: - var prefix = getPrefixDir() - when defined(posix): - if prefix == "/usr": libpath = "/usr/lib/nim" - elif prefix == "/usr/local": libpath = "/usr/local/lib/nim" - else: libpath = joinPath(prefix, "lib") - else: libpath = joinPath(prefix, "lib") + setDefaultLibpath() if optSkipConfigFile notin gGlobalOptions: readConfigFile(getSystemConfigPath(cfg)) @@ -246,10 +238,10 @@ proc loadConfigs*(cfg: string) = if optSkipParentConfigFiles notin gGlobalOptions: for dir in parentDirs(pd, fromRoot=true, inclusive=false): readConfigFile(dir / cfg) - + if optSkipProjConfigFile notin gGlobalOptions: readConfigFile(pd / cfg) - + if gProjectName.len != 0: # new project wide config file: var projectConfig = changeFileExt(gProjectFull, "nimcfg") diff --git a/compiler/options.nim b/compiler/options.nim index 1f167e2a6..adb340fea 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -191,6 +191,17 @@ proc getPrefixDir*(): string = else: result = splitPath(getAppDir()).head +proc setDefaultLibpath*() = + # set default value (can be overwritten): + if libpath == "": + # choose default libpath: + var prefix = getPrefixDir() + when defined(posix): + if prefix == "/usr": libpath = "/usr/lib/nim" + elif prefix == "/usr/local": libpath = "/usr/local/lib/nim" + else: libpath = joinPath(prefix, "lib") + else: libpath = joinPath(prefix, "lib") + proc canonicalizePath*(path: string): string = when not FileSystemCaseSensitive: result = path.expandFilename.toLower else: result = path.expandFilename diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 148a382c2..115ce940d 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -68,9 +68,20 @@ proc setupVM*(module: PSym; scriptName: string): PEvalContext = setResult(a, os.getCurrentDir()) cbos moveFile: os.moveFile(getString(a, 0), getString(a, 1)) + cbos copyFile: + os.copyFile(getString(a, 0), getString(a, 1)) cbos getLastModificationTime: setResult(a, toSeconds(getLastModificationTime(getString(a, 0)))) + cbconf getEnv: + setResult(a, os.getEnv(a.getString 0)) + cbconf existsEnv: + setResult(a, os.existsEnv(a.getString 0)) + cbconf dirExists: + setResult(a, os.dirExists(a.getString 0)) + cbconf fileExists: + setResult(a, os.fileExists(a.getString 0)) + cbconf thisDir: setResult(a, vthisDir) cbconf put: @@ -87,6 +98,8 @@ proc setupVM*(module: PSym; scriptName: string): PEvalContext = setResult(a, os.paramCount()) cbconf cmpIgnoreStyle: setResult(a, strutils.cmpIgnoreStyle(a.getString 0, a.getString 1)) + cbconf cmpIgnoreCase: + setResult(a, strutils.cmpIgnoreCase(a.getString 0, a.getString 1)) cbconf setCommand: options.command = a.getString 0 cbconf getCommand: diff --git a/compiler/writetracking.nim b/compiler/writetracking.nim index db3e6c53a..38258b07a 100644 --- a/compiler/writetracking.nim +++ b/compiler/writetracking.nim @@ -9,8 +9,13 @@ ## This module implements the write tracking analysis. Read my block post for ## a basic description of the algorithm and ideas. +## The algorithm operates in 2 phases: +## +## * Collecting information about assignments (and pass-by-var calls). +## * Computing an aliasing relation based on the assignments. This relation +## is then used to compute the 'writes' and 'escapes' effects. -import idents, ast, astalgo, trees, renderer, msgs, types +import intsets, idents, ast, astalgo, trees, renderer, msgs, types const debug = false @@ -24,11 +29,87 @@ type newNone, newLit, newCall + RootInfo = enum + rootIsResultOrParam, + rootIsHeapAccess, + rootIsSym, + markAsWrittenTo, + markAsEscaping + + Assignment = object # \ + # Note that the transitive closures MUST be computed in + # phase 2 of the algorithm. + dest, src: seq[ptr TSym] # we use 'ptr' here to save RC ops and GC cycles + destNoTc, srcNoTc: int # length of 'dest', 'src' without the + # transitive closure + destInfo: set[RootInfo] + info: TLineInfo + W = object # WriteTrackContext owner: PSym returnsNew: AssignToResult # assignments to 'result' - markAsWrittenTo, markAsEscaping: PNode - assignments: seq[(PNode, PNode)] # list of all assignments in this proc + assignments: seq[Assignment] # list of all assignments in this proc + +proc allRoots(n: PNode; result: var seq[ptr TSym]; info: var set[RootInfo]) = + case n.kind + of nkSym: + if n.sym.kind in {skParam, skVar, skTemp, skLet, skResult, skForVar}: + if n.sym.kind in {skResult, skParam}: incl(info, rootIsResultOrParam) + result.add(cast[ptr TSym](n.sym)) + of nkHiddenDeref, nkDerefExpr: + incl(info, rootIsHeapAccess) + allRoots(n.sons[0], result, info) + of nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, + nkHiddenAddr, nkObjUpConv, nkObjDownConv: + allRoots(n.sons[0], result, info) + of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkConv, + nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch, + nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast: + allRoots(n.lastSon, result, info) + of nkCallKinds: + if getMagic(n) == mSlice: + allRoots(n.sons[1], result, info) + else: + # we do significantly better here by using the available escape + # information: + if n.sons[0].typ.isNil: return + var typ = n.sons[0].typ + if typ != nil: + typ = skipTypes(typ, abstractInst) + if typ.kind != tyProc: typ = nil + else: assert(sonsLen(typ) == sonsLen(typ.n)) + + for i in 1 ..< n.len: + let it = n.sons[i] + if typ != nil and i < sonsLen(typ): + assert(typ.n.sons[i].kind == nkSym) + let paramType = typ.n.sons[i] + if paramType.typ.isCompileTimeOnly: continue + if sfEscapes in paramType.sym.flags or paramType.typ.kind == tyVar: + allRoots(it, result, info) + else: + allRoots(it, result, info) + else: + for i in 0..<n.safeLen: + allRoots(n.sons[i], result, info) + +proc addAsgn(a: var Assignment; dest, src: PNode; destInfo: set[RootInfo]) = + a.dest = @[] + a.src = @[] + a.destInfo = destInfo + allRoots(dest, a.dest, a.destInfo) + if dest.kind == nkSym: incl(a.destInfo, rootIsSym) + if src != nil: + var dummy: set[RootInfo] + allRoots(src, a.src, dummy) + a.destNoTc = a.dest.len + a.srcNoTc = a.src.len + a.info = dest.info + #echo "ADDING ", dest.info, " ", a.destInfo + +proc srcHasSym(a: Assignment; x: ptr TSym): bool = + for i in 0 ..< a.srcNoTc: + if a.src[i] == x: return true proc returnsNewExpr*(n: PNode): NewLocation = case n.kind @@ -54,10 +135,10 @@ proc returnsNewExpr*(n: PNode): NewLocation = else: result = newNone -proc deps(w: var W; dest, src: PNode) = +proc deps(w: var W; dest, src: PNode; destInfo: set[RootInfo]) = # let x = (localA, localB) # compute 'returnsNew' property: - let retNew = returnsNewExpr(src) + let retNew = if src.isNil: newNone else: returnsNewExpr(src) if dest.kind == nkSym and dest.sym.kind == skResult: if retNew != newNone: if w.returnsNew != asgnOther: w.returnsNew = asgnNew @@ -66,7 +147,10 @@ proc deps(w: var W; dest, src: PNode) = # mark the dependency, but # rule out obviously innocent assignments like 'somebool = true' if dest.kind == nkSym and retNew == newLit: discard - else: w.assignments.add((dest, src)) + else: + let L = w.assignments.len + w.assignments.setLen(L+1) + addAsgn(w.assignments[L], dest, src, destInfo) proc depsArgs(w: var W; n: PNode) = if n.sons[0].typ.isNil: return @@ -80,11 +164,14 @@ proc depsArgs(w: var W; n: PNode) = assert(typ.n.sons[i].kind == nkSym) let paramType = typ.n.sons[i] if paramType.typ.isCompileTimeOnly: continue + var destInfo: set[RootInfo] = {} if sfWrittenTo in paramType.sym.flags or paramType.typ.kind == tyVar: # p(f(x, y), X, g(h, z)) - deps(w, it, w.markAsWrittenTo) + destInfo.incl markAsWrittenTo if sfEscapes in paramType.sym.flags: - deps(w, it, w.markAsEscaping) + destInfo.incl markAsEscaping + if destInfo != {}: + deps(w, it, nil, destInfo) proc deps(w: var W; n: PNode) = case n.kind @@ -95,168 +182,91 @@ proc deps(w: var W; n: PNode) = if child.kind == nkVarTuple and last.kind == nkPar: internalAssert child.len-2 == last.len for i in 0 .. child.len-3: - deps(w, child.sons[i], last.sons[i]) + deps(w, child.sons[i], last.sons[i], {}) else: for i in 0 .. child.len-3: - deps(w, child.sons[i], last) + deps(w, child.sons[i], last, {}) of nkAsgn, nkFastAsgn: - deps(w, n.sons[0], n.sons[1]) + deps(w, n.sons[0], n.sons[1], {}) else: for i in 0 ..< n.safeLen: deps(w, n.sons[i]) if n.kind in nkCallKinds: if getMagic(n) in {mNew, mNewFinalize, mNewSeq}: # may not look like an assignment, but it is: - deps(w, n.sons[1], newNodeIT(nkObjConstr, n.info, n.sons[1].typ)) + deps(w, n.sons[1], newNodeIT(nkObjConstr, n.info, n.sons[1].typ), {}) else: depsArgs(w, n) -type - RootInfo = enum - rootIsResultOrParam, - rootIsHeapAccess - -proc allRoots(n: PNode; result: var seq[PSym]; info: var set[RootInfo]) = - case n.kind - of nkSym: - if n.sym.kind in {skParam, skVar, skTemp, skLet, skResult, skForVar}: - if result.isNil: result = @[] - if n.sym notin result: - if n.sym.kind in {skResult, skParam}: incl(info, rootIsResultOrParam) - result.add n.sym - of nkHiddenDeref, nkDerefExpr: - incl(info, rootIsHeapAccess) - allRoots(n.sons[0], result, info) - of nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, - nkHiddenAddr, nkObjUpConv, nkObjDownConv: - allRoots(n.sons[0], result, info) - of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkConv, - nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch, - nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast: - allRoots(n.lastSon, result, info) - of nkCallKinds: - if getMagic(n) == mSlice: - allRoots(n.sons[1], result, info) - else: - # we do significantly better here by using the available escape - # information: - if n.sons[0].typ.isNil: return - var typ = n.sons[0].typ - if typ != nil: - typ = skipTypes(typ, abstractInst) - if typ.kind != tyProc: typ = nil - else: assert(sonsLen(typ) == sonsLen(typ.n)) - - for i in 1 ..< n.len: - let it = n.sons[i] - if typ != nil and i < sonsLen(typ): - assert(typ.n.sons[i].kind == nkSym) - let paramType = typ.n.sons[i] - if paramType.typ.isCompileTimeOnly: continue - if sfEscapes in paramType.sym.flags or paramType.typ.kind == tyVar: - allRoots(it, result, info) - else: - allRoots(it, result, info) - else: - for i in 0..<n.safeLen: - allRoots(n.sons[i], result, info) - -proc allRoots(n: PNode; result: var seq[PSym]) = - var dummy: set[RootInfo] - allRoots(n, result, dummy) - -proc hasSym(n: PNode; x: PSym): bool = - when false: - if n.kind == nkSym: - result = n.sym == x - else: - for i in 0..safeLen(n)-1: - if hasSym(n.sons[i], x): return true - else: - var tmp: seq[PSym] - allRoots(n, tmp) - result = not tmp.isNil and x in tmp - -when debug: - proc `$`*(x: PSym): string = x.name.s - -proc possibleAliases(w: W; result: var seq[PSym]) = - var todo = 0 +proc possibleAliases(w: var W; result: var seq[ptr TSym]) = # this is an expensive fixpoint iteration. We could speed up this analysis # by a smarter data-structure but we wait until prolifing shows us it's # expensive. Usually 'w.assignments' is small enough. + var alreadySeen = initIntSet() + template addNoDup(x) = + if not alreadySeen.containsOrIncl(x.id): result.add x + for x in result: alreadySeen.incl x.id + + var todo = 0 while todo < result.len: let x = result[todo] inc todo - when debug: - if w.owner.name.s == "m3": echo "select ", x, " ", todo, " ", result.len - for dest, src in items(w.assignments): - if src.hasSym(x): - # dest = f(..., s, ...) - allRoots(dest, result) - when debug: - if w.owner.name.s == "m3": echo "A ", result - elif dest.kind == nkSym and dest.sym == x: - # s = f(..., x, ....) - allRoots(src, result) - when debug: - if w.owner.name.s == "m3": echo "B ", result - else: - when debug: - if w.owner.name.s == "m3": echo "C ", x, " ", todo, " ", result.len + for a in mitems(w.assignments): + #if a.srcHasSym(x): + # # y = f(..., x, ...) + # for i in 0 ..< a.destNoTc: addNoDup a.dest[i] + if a.destNoTc > 0 and a.dest[0] == x and rootIsSym in a.destInfo: + # x = f(..., y, ....) + for i in 0 ..< a.srcNoTc: addNoDup a.src[i] -proc markDirty(w: W) = - for dest, src in items(w.assignments): - var r: seq[PSym] = nil - var info: set[RootInfo] - allRoots(dest, r, info) - when debug: - if w.owner.info ?? "temp18": - echo "ASGN ", dest, " = ", src, " |", heapAccess, " ", r.name.s - if rootIsHeapAccess in info or src == w.markAsWrittenTo: - # we have an assignment like: - # local.foo = bar - # --> check which parameter it may alias and mark these parameters - # as dirty: - possibleAliases(w, r) - for a in r: - if a.kind == skParam and a.owner == w.owner: - incl(a.flags, sfWrittenTo) +proc markWriteOrEscape(w: var W) = + ## Both 'writes' and 'escapes' effects ultimately only care + ## about *parameters*. + ## However, due to aliasing, even locals that might not look as parameters + ## have to count as parameters if they can alias a parameter: + ## + ## .. code-block:: nim + ## proc modifies(n: Node) {.writes: [n].} = + ## let x = n + ## x.data = "abc" + ## + ## We call a symbol *parameter-like* if it is a parameter or can alias a + ## parameter. + ## Let ``p``, ``q`` be *parameter-like* and ``x``, ``y`` be general + ## expressions. + ## + ## A write then looks like ``p[] = x``. + ## An escape looks like ``p[] = q`` or more generally + ## like ``p[] = f(q)`` where ``f`` can forward ``q``. + for a in mitems(w.assignments): + if a.destInfo != {}: + possibleAliases(w, a.dest) -proc markEscaping(w: W) = - # let p1 = p - # let p2 = q - # p2.x = call(..., p1, ...) - for dest, src in items(w.assignments): - var r: seq[PSym] = nil - var info: set[RootInfo] - allRoots(dest, r, info) + if {rootIsHeapAccess, markAsWrittenTo} * a.destInfo != {}: + for p in a.dest: + if p.kind == skParam and p.owner == w.owner: + incl(p.flags, sfWrittenTo) - if (r.len > 0) and (info != {} or src == w.markAsEscaping): - possibleAliases(w, r) + if {rootIsResultOrParam, rootIsHeapAccess, markAsEscaping}*a.destInfo != {}: var destIsParam = false - for a in r: - if a.kind in {skResult, skParam} and a.owner == w.owner: + for p in a.dest: + if p.kind in {skResult, skParam} and p.owner == w.owner: destIsParam = true break if destIsParam: - var victims: seq[PSym] = @[] - allRoots(src, victims) - possibleAliases(w, victims) - for v in victims: - if v.kind == skParam and v.owner == w.owner: - incl(v.flags, sfEscapes) + possibleAliases(w, a.src) + for p in a.src: + if p.kind == skParam and p.owner == w.owner: + incl(p.flags, sfEscapes) proc trackWrites*(owner: PSym; body: PNode) = var w: W w.owner = owner - w.markAsWrittenTo = newNodeI(nkArgList, body.info) - w.markAsEscaping = newNodeI(nkArgList, body.info) w.assignments = @[] + # Phase 1: Collect and preprocess any assignments in the proc body: deps(w, body) - markDirty(w) - markEscaping(w) + # Phase 2: Compute the 'writes' and 'escapes' effects: + markWriteOrEscape(w) if w.returnsNew != asgnOther and not isEmptyType(owner.typ.sons[0]) and containsGarbageCollectedRef(owner.typ.sons[0]): incl(owner.typ.flags, tfReturnsNew) - diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 3d592a526..f413371cb 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -27,147 +27,7 @@ else: {.error: "OS module not ported to your operating system!".} include "system/ansi_c" - -type - ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read - ## from an environment variable - WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write - ## to an environment variable - - ReadDirEffect* = object of ReadIOEffect ## effect that denotes a write - ## operation to the directory structure - WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write operation to - ## the directory structure - - OSErrorCode* = distinct int32 ## Specifies an OS Error Code. - -{.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect, - FReadDir: ReadDirEffect, - FWriteDir: WriteDirEffect, - TOSErrorCode: OSErrorCode -].} -const - doslike = defined(windows) or defined(OS2) or defined(DOS) - # DOS-like filesystem - -when defined(Nimdoc): # only for proper documentation: - const - CurDir* = '.' - ## The constant string used by the operating system to refer to the - ## current directory. - ## - ## For example: '.' for POSIX or ':' for the classic Macintosh. - - ParDir* = ".." - ## The constant string used by the operating system to refer to the - ## parent directory. - ## - ## For example: ".." for POSIX or "::" for the classic Macintosh. - - DirSep* = '/' - ## The character used by the operating system to separate pathname - ## components, for example, '/' for POSIX or ':' for the classic - ## Macintosh. - - AltSep* = '/' - ## An alternative character used by the operating system to separate - ## pathname components, or the same as `DirSep` if only one separator - ## character exists. This is set to '/' on Windows systems where `DirSep` - ## is a backslash. - - PathSep* = ':' - ## The character conventionally used by the operating system to separate - ## search patch components (as in PATH), such as ':' for POSIX or ';' for - ## Windows. - - FileSystemCaseSensitive* = true - ## true if the file system is case sensitive, false otherwise. Used by - ## `cmpPaths` to compare filenames properly. - - ExeExt* = "" - ## The file extension of native executables. For example: - ## "" for POSIX, "exe" on Windows. - - ScriptExt* = "" - ## The file extension of a script file. For example: "" for POSIX, - ## "bat" on Windows. - - DynlibFormat* = "lib$1.so" - ## The format string to turn a filename into a `DLL`:idx: file (also - ## called `shared object`:idx: on some operating systems). - -elif defined(macos): - const - CurDir* = ':' - ParDir* = "::" - DirSep* = ':' - AltSep* = Dirsep - PathSep* = ',' - FileSystemCaseSensitive* = false - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "$1.dylib" - - # MacOS paths - # =========== - # MacOS directory separator is a colon ":" which is the only character not - # allowed in filenames. - # - # A path containing no colon or which begins with a colon is a partial path. - # E.g. ":kalle:petter" ":kalle" "kalle" - # - # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" - # When generating paths, one is safe if one ensures that all partial paths - # begin with a colon, and all full paths end with a colon. - # In full paths the first name (e g HD above) is the name of a mounted - # volume. - # These names are not unique, because, for instance, two diskettes with the - # same names could be inserted. This means that paths on MacOS are not - # waterproof. In case of equal names the first volume found will do. - # Two colons "::" are the relative path to the parent. Three is to the - # grandparent etc. -elif doslike: - const - CurDir* = '.' - ParDir* = ".." - DirSep* = '\\' # seperator within paths - AltSep* = '/' - PathSep* = ';' # seperator between paths - FileSystemCaseSensitive* = false - ExeExt* = "exe" - ScriptExt* = "bat" - DynlibFormat* = "$1.dll" -elif defined(PalmOS) or defined(MorphOS): - const - DirSep* = '/' - AltSep* = Dirsep - PathSep* = ';' - ParDir* = ".." - FileSystemCaseSensitive* = false - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "$1.prc" -elif defined(RISCOS): - const - DirSep* = '.' - AltSep* = '.' - ParDir* = ".." # is this correct? - PathSep* = ',' - FileSystemCaseSensitive* = true - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = "lib$1.so" -else: # UNIX-like operating system - const - CurDir* = '.' - ParDir* = ".." - DirSep* = '/' - AltSep* = DirSep - PathSep* = ':' - FileSystemCaseSensitive* = true - ExeExt* = "" - ScriptExt* = "" - DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" +include ospaths when defined(posix): when NoFakeVars: @@ -177,11 +37,6 @@ when defined(posix): var pathMax {.importc: "PATH_MAX", header: "<stdlib.h>".}: cint -const - ExtSep* = '.' - ## The character which separates the base filename from the extension; - ## for example, the '.' in ``os.nim``. - proc osErrorMsg*(): string {.rtl, extern: "nos$1", deprecated.} = ## Retrieves the operating system's error flag, ``errno``. ## On Windows ``GetLastError`` is checked before ``errno``. @@ -300,61 +155,6 @@ proc osLastError*(): OSErrorCode = result = OSErrorCode(errno) {.pop.} -proc unixToNativePath*(path: string, drive=""): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Converts an UNIX-like path to a native one. - ## - ## On an UNIX system this does nothing. Else it converts - ## '/', '.', '..' to the appropriate things. - ## - ## On systems with a concept of "drives", `drive` is used to determine - ## which drive label to use during absolute path conversion. - ## `drive` defaults to the drive of the current working directory, and is - ## ignored on systems that do not have a concept of "drives". - - when defined(unix): - result = path - else: - var start: int - if path[0] == '/': - # an absolute path - when doslike: - if drive != "": - result = drive & ":" & DirSep - else: - result = $DirSep - elif defined(macos): - result = "" # must not start with ':' - else: - result = $DirSep - start = 1 - elif path[0] == '.' and path[1] == '/': - # current directory - result = $CurDir - start = 2 - else: - result = "" - start = 0 - - var i = start - while i < len(path): # ../../../ --> :::: - if path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': - # parent directory - when defined(macos): - if result[high(result)] == ':': - add result, ':' - else: - add result, ParDir - else: - add result, ParDir & DirSep - inc(i, 3) - elif path[i] == '/': - add result, DirSep - inc(i) - else: - add result, path[i] - inc(i) - when defined(windows): when useWinUnicode: template wrapUnary(varname, winApiProc, arg: expr) {.immediate.} = @@ -518,220 +318,6 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) -proc joinPath*(head, tail: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Joins two directory names to one. - ## - ## For example on Unix: - ## - ## .. code-block:: nim - ## joinPath("usr", "lib") - ## - ## results in: - ## - ## .. code-block:: nim - ## "usr/lib" - ## - ## If head is the empty string, tail is returned. If tail is the empty - ## string, head is returned with a trailing path separator. If tail starts - ## with a path separator it will be removed when concatenated to head. Other - ## path separators not located on boundaries won't be modified. More - ## examples on Unix: - ## - ## .. code-block:: nim - ## assert joinPath("usr", "") == "usr/" - ## assert joinPath("", "lib") == "lib" - ## assert joinPath("", "/lib") == "/lib" - ## assert joinPath("usr/", "/lib") == "usr/lib" - if len(head) == 0: - result = tail - elif head[len(head)-1] in {DirSep, AltSep}: - if tail[0] in {DirSep, AltSep}: - result = head & substr(tail, 1) - else: - result = head & tail - else: - if tail[0] in {DirSep, AltSep}: - result = head & tail - else: - result = head & DirSep & tail - -proc joinPath*(parts: varargs[string]): string {.noSideEffect, - rtl, extern: "nos$1OpenArray".} = - ## The same as `joinPath(head, tail)`, but works with any number of directory - ## parts. You need to pass at least one element or the proc will assert in - ## debug builds and crash on release builds. - result = parts[0] - for i in 1..high(parts): - result = joinPath(result, parts[i]) - -proc `/` * (head, tail: string): string {.noSideEffect.} = - ## The same as ``joinPath(head, tail)`` - ## - ## Here are some examples for Unix: - ## - ## .. code-block:: nim - ## assert "usr" / "" == "usr/" - ## assert "" / "lib" == "lib" - ## assert "" / "/lib" == "/lib" - ## assert "usr/" / "/lib" == "usr/lib" - return joinPath(head, tail) - -proc splitPath*(path: string): tuple[head, tail: string] {. - noSideEffect, rtl, extern: "nos$1".} = - ## Splits a directory into (head, tail), so that - ## ``head / tail == path`` (except for edge cases like "/usr"). - ## - ## Examples: - ## - ## .. code-block:: nim - ## splitPath("usr/local/bin") -> ("usr/local", "bin") - ## splitPath("usr/local/bin/") -> ("usr/local/bin", "") - ## splitPath("bin") -> ("", "bin") - ## splitPath("/bin") -> ("", "bin") - ## splitPath("") -> ("", "") - var sepPos = -1 - for i in countdown(len(path)-1, 0): - if path[i] in {DirSep, AltSep}: - sepPos = i - break - if sepPos >= 0: - result.head = substr(path, 0, sepPos-1) - result.tail = substr(path, sepPos+1) - else: - result.head = "" - result.tail = path - -proc parentDirPos(path: string): int = - var q = 1 - if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 - for i in countdown(len(path)-q, 0): - if path[i] in {DirSep, AltSep}: return i - result = -1 - -proc parentDir*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Returns the parent directory of `path`. - ## - ## This is often the same as the ``head`` result of ``splitPath``. - ## If there is no parent, "" is returned. - ## | Example: ``parentDir("/usr/local/bin") == "/usr/local"``. - ## | Example: ``parentDir("/usr/local/bin/") == "/usr/local"``. - let sepPos = parentDirPos(path) - if sepPos >= 0: - result = substr(path, 0, sepPos-1) - else: - result = "" - -proc tailDir*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Returns the tail part of `path`.. - ## - ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``. - ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``. - ## | Example: ``tailDir("bin") == ""``. - var q = 1 - if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 - for i in 0..len(path)-q: - if path[i] in {DirSep, AltSep}: - return substr(path, i+1) - result = "" - -proc isRootDir*(path: string): bool {. - noSideEffect, rtl, extern: "nos$1".} = - ## Checks whether a given `path` is a root directory - result = parentDirPos(path) < 0 - -iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = - ## Walks over all parent directories of a given `path` - ## - ## If `fromRoot` is set, the traversal will start from the file system root - ## diretory. If `inclusive` is set, the original argument will be included - ## in the traversal. - ## - ## Relative paths won't be expanded by this proc. Instead, it will traverse - ## only the directories appearing in the relative path. - if not fromRoot: - var current = path - if inclusive: yield path - while true: - if current.isRootDir: break - current = current.parentDir - yield current - else: - for i in countup(0, path.len - 2): # ignore the last / - # deal with non-normalized paths such as /foo//bar//baz - if path[i] in {DirSep, AltSep} and - (i == 0 or path[i-1] notin {DirSep, AltSep}): - yield path.substr(0, i) - - if inclusive: yield path - -proc `/../` * (head, tail: string): string {.noSideEffect.} = - ## The same as ``parentDir(head) / tail`` unless there is no parent directory. - ## Then ``head / tail`` is performed instead. - let sepPos = parentDirPos(head) - if sepPos >= 0: - result = substr(head, 0, sepPos-1) / tail - else: - result = head / tail - -proc normExt(ext: string): string = - if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here - else: result = ExtSep & ext - -proc searchExtPos(s: string): int = - # BUGFIX: do not search until 0! .DS_Store is no file extension! - result = -1 - for i in countdown(len(s)-1, 1): - if s[i] == ExtSep: - result = i - break - elif s[i] in {DirSep, AltSep}: - break # do not skip over path - -proc splitFile*(path: string): tuple[dir, name, ext: string] {. - noSideEffect, rtl, extern: "nos$1".} = - ## Splits a filename into (dir, filename, extension). - ## `dir` does not end in `DirSep`. - ## `extension` includes the leading dot. - ## - ## Example: - ## - ## .. code-block:: nim - ## var (dir, name, ext) = splitFile("usr/local/nimc.html") - ## assert dir == "usr/local" - ## assert name == "nimc" - ## assert ext == ".html" - ## - ## If `path` has no extension, `ext` is the empty string. - ## If `path` has no directory component, `dir` is the empty string. - ## If `path` has no filename component, `name` and `ext` are empty strings. - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = (path, "", "") - else: - var sepPos = -1 - var dotPos = path.len - for i in countdown(len(path)-1, 0): - if path[i] == ExtSep: - 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 - result.dir = substr(path, 0, sepPos-1) - result.name = substr(path, sepPos+1, dotPos-1) - result.ext = substr(path, dotPos) - -proc extractFilename*(path: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Extracts the filename of a given `path`. This is the same as - ## ``name & ext`` from ``splitFile(path)``. - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = "" - else: - result = splitPath(path).tail - proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", tags: [ReadDirEffect].} = ## Returns the full path of `filename`, raises OSError in case of an error. @@ -757,61 +343,6 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", if r.isNil: raiseOSError(osLastError()) setLen(result, c_strlen(result)) -proc changeFileExt*(filename, ext: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Changes the file extension to `ext`. - ## - ## If the `filename` has no extension, `ext` will be added. - ## If `ext` == "" then any extension is removed. - ## `Ext` should be given without the leading '.', because some - ## filesystems may use a different character. (Although I know - ## of none such beast.) - var extPos = searchExtPos(filename) - if extPos < 0: result = filename & normExt(ext) - else: result = substr(filename, 0, extPos-1) & normExt(ext) - -proc addFileExt*(filename, ext: string): string {. - noSideEffect, rtl, extern: "nos$1".} = - ## Adds the file extension `ext` to `filename`, unless - ## `filename` already has an extension. - ## - ## `Ext` should be given without the leading '.', because some - ## filesystems may use a different character. - ## (Although I know of none such beast.) - var extPos = searchExtPos(filename) - if extPos < 0: result = filename & normExt(ext) - else: result = filename - -proc cmpPaths*(pathA, pathB: string): int {. - noSideEffect, rtl, extern: "nos$1".} = - ## Compares two paths. - ## - ## On a case-sensitive filesystem this is done - ## case-sensitively otherwise case-insensitively. Returns: - ## - ## | 0 iff pathA == pathB - ## | < 0 iff pathA < pathB - ## | > 0 iff pathA > pathB - if FileSystemCaseSensitive: - result = cmp(pathA, pathB) - else: - result = cmpIgnoreCase(pathA, pathB) - -proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = - ## Checks whether a given `path` is absolute. - ## - ## On Windows, network paths are considered absolute too. - when doslike: - var len = len(path) - result = (len > 1 and path[0] in {'/', '\\'}) or - (len > 2 and path[0] in Letters and path[1] == ':') - elif defined(macos): - result = path.len > 0 and path[0] != ':' - elif defined(RISCOS): - result = path[0] == '$' - elif defined(posix): - result = path[0] == '/' - when defined(Windows): proc openHandle(path: string, followSymlink=true): Handle = var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL @@ -1649,24 +1180,7 @@ proc exclFilePermissions*(filename: string, ## setFilePermissions(filename, getFilePermissions(filename)-permissions) setFilePermissions(filename, getFilePermissions(filename)-permissions) -proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = - ## Returns the home directory of the current user. - ## - ## This proc is wrapped by the expandTilde proc for the convenience of - ## processing paths coming from user configuration files. - when defined(windows): return string(getEnv("USERPROFILE")) & "\\" - else: return string(getEnv("HOME")) & "/" - -proc getConfigDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = - ## Returns the config directory of the current user for applications. - when defined(windows): return string(getEnv("APPDATA")) & "\\" - else: return string(getEnv("HOME")) & "/.config/" - -proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = - ## Returns the temporary directory of the current user for applications to - ## save temporary files in. - when defined(windows): return string(getEnv("TEMP")) & "\\" - else: return "/tmp/" +include ospaths proc expandSymlink*(symlinkPath: string): string = ## Returns a string representing the path to which the symbolic link points. @@ -1907,42 +1421,6 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", close(f) else: raiseOSError(osLastError()) -proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].} = - ## Expands a path starting with ``~/`` to a full path. - ## - ## If `path` starts with the tilde character and is followed by `/` or `\\` - ## this proc will return the reminder of the path appended to the result of - ## the getHomeDir() proc, otherwise the input path will be returned without - ## modification. - ## - ## The behaviour of this proc is the same on the Windows platform despite not - ## having this convention. Example: - ## - ## .. code-block:: nim - ## let configFile = expandTilde("~" / "appname.cfg") - ## echo configFile - ## # --> C:\Users\amber\appname.cfg - if len(path) > 1 and path[0] == '~' and (path[1] == '/' or path[1] == '\\'): - result = getHomeDir() / path[2..len(path)-1] - else: - result = path - -proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} = - ## Searches for `exe` in the current working directory and then - ## in directories listed in the ``PATH`` environment variable. - ## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe` - ## is added the `ExeExt <#ExeExt>`_ file extension if it has none. - result = addFileExt(exe, os.ExeExt) - if existsFile(result): return - var path = string(os.getEnv("PATH")) - for candidate in split(path, PathSep): - when defined(windows): - var x = candidate / result - else: - var x = expandTilde(candidate) / result - if existsFile(x): return x - result = "" - when defined(Windows): type DeviceId* = int32 diff --git a/lib/pure/ospaths.nim b/lib/pure/ospaths.nim new file mode 100644 index 000000000..55962a6a5 --- /dev/null +++ b/lib/pure/ospaths.nim @@ -0,0 +1,560 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# Included by the ``os`` module but a module in its own right for NimScript +# support. + +when defined(nimscript): + {.pragma: rtl.} + {.push hint[ConvFromXtoItselfNotNeeded]:off.} + +when not declared(getEnv) or defined(nimscript): + type + ReadEnvEffect* = object of ReadIOEffect ## effect that denotes a read + ## from an environment variable + WriteEnvEffect* = object of WriteIOEffect ## effect that denotes a write + ## to an environment variable + + ReadDirEffect* = object of ReadIOEffect ## effect that denotes a write + ## operation to the directory structure + WriteDirEffect* = object of WriteIOEffect ## effect that denotes a write operation to + ## the directory structure + + OSErrorCode* = distinct int32 ## Specifies an OS Error Code. + + {.deprecated: [FReadEnv: ReadEnvEffect, FWriteEnv: WriteEnvEffect, + FReadDir: ReadDirEffect, + FWriteDir: WriteDirEffect, + TOSErrorCode: OSErrorCode + ].} + const + doslike = defined(windows) or defined(OS2) or defined(DOS) + # DOS-like filesystem + + when defined(Nimdoc): # only for proper documentation: + const + CurDir* = '.' + ## The constant string used by the operating system to refer to the + ## current directory. + ## + ## For example: '.' for POSIX or ':' for the classic Macintosh. + + ParDir* = ".." + ## The constant string used by the operating system to refer to the + ## parent directory. + ## + ## For example: ".." for POSIX or "::" for the classic Macintosh. + + DirSep* = '/' + ## The character used by the operating system to separate pathname + ## components, for example, '/' for POSIX or ':' for the classic + ## Macintosh. + + AltSep* = '/' + ## An alternative character used by the operating system to separate + ## pathname components, or the same as `DirSep` if only one separator + ## character exists. This is set to '/' on Windows systems where `DirSep` + ## is a backslash. + + PathSep* = ':' + ## The character conventionally used by the operating system to separate + ## search patch components (as in PATH), such as ':' for POSIX or ';' for + ## Windows. + + FileSystemCaseSensitive* = true + ## true if the file system is case sensitive, false otherwise. Used by + ## `cmpPaths` to compare filenames properly. + + ExeExt* = "" + ## The file extension of native executables. For example: + ## "" for POSIX, "exe" on Windows. + + ScriptExt* = "" + ## The file extension of a script file. For example: "" for POSIX, + ## "bat" on Windows. + + DynlibFormat* = "lib$1.so" + ## The format string to turn a filename into a `DLL`:idx: file (also + ## called `shared object`:idx: on some operating systems). + + elif defined(macos): + const + CurDir* = ':' + ParDir* = "::" + DirSep* = ':' + AltSep* = Dirsep + PathSep* = ',' + FileSystemCaseSensitive* = false + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "$1.dylib" + + # MacOS paths + # =========== + # MacOS directory separator is a colon ":" which is the only character not + # allowed in filenames. + # + # A path containing no colon or which begins with a colon is a partial path. + # E.g. ":kalle:petter" ":kalle" "kalle" + # + # All other paths are full (absolute) paths. E.g. "HD:kalle:" "HD:" + # When generating paths, one is safe if one ensures that all partial paths + # begin with a colon, and all full paths end with a colon. + # In full paths the first name (e g HD above) is the name of a mounted + # volume. + # These names are not unique, because, for instance, two diskettes with the + # same names could be inserted. This means that paths on MacOS are not + # waterproof. In case of equal names the first volume found will do. + # Two colons "::" are the relative path to the parent. Three is to the + # grandparent etc. + elif doslike: + const + CurDir* = '.' + ParDir* = ".." + DirSep* = '\\' # seperator within paths + AltSep* = '/' + PathSep* = ';' # seperator between paths + FileSystemCaseSensitive* = false + ExeExt* = "exe" + ScriptExt* = "bat" + DynlibFormat* = "$1.dll" + elif defined(PalmOS) or defined(MorphOS): + const + DirSep* = '/' + AltSep* = Dirsep + PathSep* = ';' + ParDir* = ".." + FileSystemCaseSensitive* = false + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "$1.prc" + elif defined(RISCOS): + const + DirSep* = '.' + AltSep* = '.' + ParDir* = ".." # is this correct? + PathSep* = ',' + FileSystemCaseSensitive* = true + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = "lib$1.so" + else: # UNIX-like operating system + const + CurDir* = '.' + ParDir* = ".." + DirSep* = '/' + AltSep* = DirSep + PathSep* = ':' + FileSystemCaseSensitive* = true + ExeExt* = "" + ScriptExt* = "" + DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" + + const + ExtSep* = '.' + ## The character which separates the base filename from the extension; + ## for example, the '.' in ``os.nim``. + + + proc joinPath*(head, tail: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Joins two directory names to one. + ## + ## For example on Unix: + ## + ## .. code-block:: nim + ## joinPath("usr", "lib") + ## + ## results in: + ## + ## .. code-block:: nim + ## "usr/lib" + ## + ## If head is the empty string, tail is returned. If tail is the empty + ## string, head is returned with a trailing path separator. If tail starts + ## with a path separator it will be removed when concatenated to head. Other + ## path separators not located on boundaries won't be modified. More + ## examples on Unix: + ## + ## .. code-block:: nim + ## assert joinPath("usr", "") == "usr/" + ## assert joinPath("", "lib") == "lib" + ## assert joinPath("", "/lib") == "/lib" + ## assert joinPath("usr/", "/lib") == "usr/lib" + if len(head) == 0: + result = tail + elif head[len(head)-1] in {DirSep, AltSep}: + if tail[0] in {DirSep, AltSep}: + result = head & substr(tail, 1) + else: + result = head & tail + else: + if tail[0] in {DirSep, AltSep}: + result = head & tail + else: + result = head & DirSep & tail + + proc joinPath*(parts: varargs[string]): string {.noSideEffect, + rtl, extern: "nos$1OpenArray".} = + ## The same as `joinPath(head, tail)`, but works with any number of directory + ## parts. You need to pass at least one element or the proc will assert in + ## debug builds and crash on release builds. + result = parts[0] + for i in 1..high(parts): + result = joinPath(result, parts[i]) + + proc `/` * (head, tail: string): string {.noSideEffect.} = + ## The same as ``joinPath(head, tail)`` + ## + ## Here are some examples for Unix: + ## + ## .. code-block:: nim + ## assert "usr" / "" == "usr/" + ## assert "" / "lib" == "lib" + ## assert "" / "/lib" == "/lib" + ## assert "usr/" / "/lib" == "usr/lib" + return joinPath(head, tail) + + proc splitPath*(path: string): tuple[head, tail: string] {. + noSideEffect, rtl, extern: "nos$1".} = + ## Splits a directory into (head, tail), so that + ## ``head / tail == path`` (except for edge cases like "/usr"). + ## + ## Examples: + ## + ## .. code-block:: nim + ## splitPath("usr/local/bin") -> ("usr/local", "bin") + ## splitPath("usr/local/bin/") -> ("usr/local/bin", "") + ## splitPath("bin") -> ("", "bin") + ## splitPath("/bin") -> ("", "bin") + ## splitPath("") -> ("", "") + var sepPos = -1 + for i in countdown(len(path)-1, 0): + if path[i] in {DirSep, AltSep}: + sepPos = i + break + if sepPos >= 0: + result.head = substr(path, 0, sepPos-1) + result.tail = substr(path, sepPos+1) + else: + result.head = "" + result.tail = path + + proc parentDirPos(path: string): int = + var q = 1 + if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 + for i in countdown(len(path)-q, 0): + if path[i] in {DirSep, AltSep}: return i + result = -1 + + proc parentDir*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Returns the parent directory of `path`. + ## + ## This is often the same as the ``head`` result of ``splitPath``. + ## If there is no parent, "" is returned. + ## | Example: ``parentDir("/usr/local/bin") == "/usr/local"``. + ## | Example: ``parentDir("/usr/local/bin/") == "/usr/local"``. + let sepPos = parentDirPos(path) + if sepPos >= 0: + result = substr(path, 0, sepPos-1) + else: + result = "" + + proc tailDir*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Returns the tail part of `path`.. + ## + ## | Example: ``tailDir("/usr/local/bin") == "local/bin"``. + ## | Example: ``tailDir("usr/local/bin/") == "local/bin"``. + ## | Example: ``tailDir("bin") == ""``. + var q = 1 + if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2 + for i in 0..len(path)-q: + if path[i] in {DirSep, AltSep}: + return substr(path, i+1) + result = "" + + proc isRootDir*(path: string): bool {. + noSideEffect, rtl, extern: "nos$1".} = + ## Checks whether a given `path` is a root directory + result = parentDirPos(path) < 0 + + iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string = + ## Walks over all parent directories of a given `path` + ## + ## If `fromRoot` is set, the traversal will start from the file system root + ## diretory. If `inclusive` is set, the original argument will be included + ## in the traversal. + ## + ## Relative paths won't be expanded by this proc. Instead, it will traverse + ## only the directories appearing in the relative path. + if not fromRoot: + var current = path + if inclusive: yield path + while true: + if current.isRootDir: break + current = current.parentDir + yield current + else: + for i in countup(0, path.len - 2): # ignore the last / + # deal with non-normalized paths such as /foo//bar//baz + if path[i] in {DirSep, AltSep} and + (i == 0 or path[i-1] notin {DirSep, AltSep}): + yield path.substr(0, i) + + if inclusive: yield path + + proc `/../` * (head, tail: string): string {.noSideEffect.} = + ## The same as ``parentDir(head) / tail`` unless there is no parent directory. + ## Then ``head / tail`` is performed instead. + let sepPos = parentDirPos(head) + if sepPos >= 0: + result = substr(head, 0, sepPos-1) / tail + else: + result = head / tail + + proc normExt(ext: string): string = + if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here + else: result = ExtSep & ext + + proc searchExtPos(s: string): int = + # BUGFIX: do not search until 0! .DS_Store is no file extension! + result = -1 + for i in countdown(len(s)-1, 1): + if s[i] == ExtSep: + result = i + break + elif s[i] in {DirSep, AltSep}: + break # do not skip over path + + proc splitFile*(path: string): tuple[dir, name, ext: string] {. + noSideEffect, rtl, extern: "nos$1".} = + ## Splits a filename into (dir, filename, extension). + ## `dir` does not end in `DirSep`. + ## `extension` includes the leading dot. + ## + ## Example: + ## + ## .. code-block:: nim + ## var (dir, name, ext) = splitFile("usr/local/nimc.html") + ## assert dir == "usr/local" + ## assert name == "nimc" + ## assert ext == ".html" + ## + ## If `path` has no extension, `ext` is the empty string. + ## If `path` has no directory component, `dir` is the empty string. + ## If `path` has no filename component, `name` and `ext` are empty strings. + if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: + result = (path, "", "") + else: + var sepPos = -1 + var dotPos = path.len + for i in countdown(len(path)-1, 0): + if path[i] == ExtSep: + 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 + result.dir = substr(path, 0, sepPos-1) + result.name = substr(path, sepPos+1, dotPos-1) + result.ext = substr(path, dotPos) + + proc extractFilename*(path: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Extracts the filename of a given `path`. This is the same as + ## ``name & ext`` from ``splitFile(path)``. + if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: + result = "" + else: + result = splitPath(path).tail + + + proc changeFileExt*(filename, ext: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Changes the file extension to `ext`. + ## + ## If the `filename` has no extension, `ext` will be added. + ## If `ext` == "" then any extension is removed. + ## `Ext` should be given without the leading '.', because some + ## filesystems may use a different character. (Although I know + ## of none such beast.) + var extPos = searchExtPos(filename) + if extPos < 0: result = filename & normExt(ext) + else: result = substr(filename, 0, extPos-1) & normExt(ext) + + proc addFileExt*(filename, ext: string): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Adds the file extension `ext` to `filename`, unless + ## `filename` already has an extension. + ## + ## `Ext` should be given without the leading '.', because some + ## filesystems may use a different character. + ## (Although I know of none such beast.) + var extPos = searchExtPos(filename) + if extPos < 0: result = filename & normExt(ext) + else: result = filename + + proc cmpPaths*(pathA, pathB: string): int {. + noSideEffect, rtl, extern: "nos$1".} = + ## Compares two paths. + ## + ## On a case-sensitive filesystem this is done + ## case-sensitively otherwise case-insensitively. Returns: + ## + ## | 0 iff pathA == pathB + ## | < 0 iff pathA < pathB + ## | > 0 iff pathA > pathB + if FileSystemCaseSensitive: + result = cmp(pathA, pathB) + else: + when defined(nimscript): + result = cmpic(pathA, pathB) + else: + result = cmpIgnoreCase(pathA, pathB) + + proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} = + ## Checks whether a given `path` is absolute. + ## + ## On Windows, network paths are considered absolute too. + when doslike: + var len = len(path) + result = (len > 1 and path[0] in {'/', '\\'}) or + (len > 2 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':') + elif defined(macos): + result = path.len > 0 and path[0] != ':' + elif defined(RISCOS): + result = path[0] == '$' + elif defined(posix): + result = path[0] == '/' + + proc unixToNativePath*(path: string, drive=""): string {. + noSideEffect, rtl, extern: "nos$1".} = + ## Converts an UNIX-like path to a native one. + ## + ## On an UNIX system this does nothing. Else it converts + ## '/', '.', '..' to the appropriate things. + ## + ## On systems with a concept of "drives", `drive` is used to determine + ## which drive label to use during absolute path conversion. + ## `drive` defaults to the drive of the current working directory, and is + ## ignored on systems that do not have a concept of "drives". + + when defined(unix): + result = path + else: + var start: int + if path[0] == '/': + # an absolute path + when doslike: + if drive != "": + result = drive & ":" & DirSep + else: + result = $DirSep + elif defined(macos): + result = "" # must not start with ':' + else: + result = $DirSep + start = 1 + elif path[0] == '.' and path[1] == '/': + # current directory + result = $CurDir + start = 2 + else: + result = "" + start = 0 + + var i = start + while i < len(path): # ../../../ --> :::: + if path[i] == '.' and path[i+1] == '.' and path[i+2] == '/': + # parent directory + when defined(macos): + if result[high(result)] == ':': + add result, ':' + else: + add result, ParDir + else: + add result, ParDir & DirSep + inc(i, 3) + elif path[i] == '/': + add result, DirSep + inc(i) + else: + add result, path[i] + inc(i) + +when declared(getEnv) or defined(nimscript): + proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + ## Returns the home directory of the current user. + ## + ## This proc is wrapped by the expandTilde proc for the convenience of + ## processing paths coming from user configuration files. + when defined(windows): return string(getEnv("USERPROFILE")) & "\\" + else: return string(getEnv("HOME")) & "/" + + proc getConfigDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + ## Returns the config directory of the current user for applications. + when defined(windows): return string(getEnv("APPDATA")) & "\\" + else: return string(getEnv("HOME")) & "/.config/" + + proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect].} = + ## Returns the temporary directory of the current user for applications to + ## save temporary files in. + when defined(windows): return string(getEnv("TEMP")) & "\\" + else: return "/tmp/" + + proc expandTilde*(path: string): string {.tags: [ReadEnvEffect].} = + ## Expands a path starting with ``~/`` to a full path. + ## + ## If `path` starts with the tilde character and is followed by `/` or `\\` + ## this proc will return the reminder of the path appended to the result of + ## the getHomeDir() proc, otherwise the input path will be returned without + ## modification. + ## + ## The behaviour of this proc is the same on the Windows platform despite not + ## having this convention. Example: + ## + ## .. code-block:: nim + ## let configFile = expandTilde("~" / "appname.cfg") + ## echo configFile + ## # --> C:\Users\amber\appname.cfg + if len(path) > 1 and path[0] == '~' and (path[1] == '/' or path[1] == '\\'): + result = getHomeDir() / path.substr(2) + else: + result = path + + when not declared(split): + iterator split(s: string, sep: char): string = + var last = 0 + if len(s) > 0: + while last <= len(s): + var first = last + while last < len(s) and s[last] != sep: inc(last) + yield substr(s, first, last-1) + inc(last) + + proc findExe*(exe: string): string {.tags: [ReadDirEffect, ReadEnvEffect].} = + ## Searches for `exe` in the current working directory and then + ## in directories listed in the ``PATH`` environment variable. + ## Returns "" if the `exe` cannot be found. On DOS-like platforms, `exe` + ## is added the `ExeExt <#ExeExt>`_ file extension if it has none. + result = addFileExt(exe, ExeExt) + if existsFile(result): return + var path = string(getEnv("PATH")) + for candidate in split(path, PathSep): + when defined(windows): + var x = candidate / result + else: + var x = expandTilde(candidate) / result + if existsFile(x): return x + result = "" + +when defined(nimscript): + {.pop.} # hint[ConvFromXtoItselfNotNeeded]:off diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 6d45dc7f1..aa4ae5ace 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1051,26 +1051,30 @@ proc parse*(value, layout: string): TimeInfo = return info # Leap year calculations are adapted from: -# from http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years +# http://www.codeproject.com/Articles/7358/Ultra-fast-Algorithms-for-Working-with-Leap-Years # The dayOfTheWeek procs are adapated from: -# from http://stason.org/TULARC/society/calendars/2-5-What-day-of-the-week-was-2-August-1953.html +# http://stason.org/TULARC/society/calendars/2-5-What-day-of-the-week-was-2-August-1953.html -# Note: for leap years, start date is assumed to be 1 AD. -# counts the number of leap years up to January 1st of a given year. -# Keep in mind that if specified year is a leap year, the leap day -# has not happened before January 1st of that year. -proc countLeapYears(yearSpan: int): int = - (((yearSpan - 1) / 4) - ((yearSpan - 1) / 100) + ((yearSpan - 1)/400)).int - -proc countDays(yearSpan: int): int = +proc countLeapYears*(yearSpan: int): int = + ## Returns the number of leap years spanned by a given number of years. + ## + ## Note: for leap years, start date is assumed to be 1 AD. + ## counts the number of leap years up to January 1st of a given year. + ## Keep in mind that if specified year is a leap year, the leap day + ## has not happened before January 1st of that year. + (((yearSpan - 1) / 4) - ((yearSpan - 1) / 100) + ((yearSpan - 1) / 400)).int + +proc countDays*(yearSpan: int): int = + ## Returns the number of days spanned by a given number of years. (yearSpan - 1) * 365 + countLeapYears(yearSpan) -proc countYears(daySpan: int): int = - # counts the number of years spanned by a given number of days. +proc countYears*(daySpan: int): int = + ## Returns the number of years spanned by a given number of days. ((daySpan - countLeapYears(daySpan div 365)) div 365) -proc countYearsAndDays(daySpan: int): tuple[years: int, days: int] = - # counts the number of years spanned by a given number of days and the remainder as days. +proc countYearsAndDays*(daySpan: int): tuple[years: int, days: int] = + ## Returns the number of years spanned by a given number of days and the + ## remainder as days. let days = daySpan - countLeapYears(daySpan div 365) result.years = days div 365 result.days = days mod 365 diff --git a/lib/system.nim b/lib/system.nim index 2a3f72f3b..042813ae1 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1375,7 +1375,8 @@ type # these work for most platforms: culonglong* {.importc: "unsigned long long", nodecl.} = uint64 ## This is the same as the type ``unsigned long long`` in *C*. - cstringArray* {.importc: "char**", nodecl.} = ptr array [0..ArrayDummySize, cstring] + cstringArray* {.importc: "char**", nodecl.} = ptr + array [0..ArrayDummySize, cstring] ## This is binary compatible to the type ``char**`` in *C*. The array's ## high value is large enough to disable bounds checking in practice. ## Use `cstringArrayToSeq` to convert it into a ``seq[string]``. @@ -1886,7 +1887,7 @@ iterator pairs*[T](a: openArray[T]): tuple[key: int, val: T] {.inline.} = yield (i, a[i]) inc(i) -iterator mpairs*[T](a: var openArray[T]): tuple[key: int, val: var T] {.inline.} = +iterator mpairs*[T](a: var openArray[T]): tuple[key:int, val:var T]{.inline.} = ## iterates over each item of `a`. Yields ``(index, a[index])`` pairs. ## ``a[index]`` can be modified. var i = 0 @@ -1903,7 +1904,7 @@ iterator pairs*[IX, T](a: array[IX, T]): tuple[key: IX, val: T] {.inline.} = if i >= high(IX): break inc(i) -iterator mpairs*[IX, T](a: var array[IX, T]): tuple[key: IX, val: var T] {.inline.} = +iterator mpairs*[IX, T](a:var array[IX, T]):tuple[key:IX,val:var T] {.inline.} = ## iterates over each item of `a`. Yields ``(index, a[index])`` pairs. ## ``a[index]`` can be modified. var i = low(IX) @@ -3149,25 +3150,31 @@ proc staticExec*(command: string, input = "", cache = ""): string {. ## .. code-block:: nim ## const stateMachine = staticExec("dfaoptimizer", "input", "0.8.0") -proc `+=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {.magic: "Inc", noSideEffect.} +proc `+=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. + magic: "Inc", noSideEffect.} ## Increments an ordinal -proc `-=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {.magic: "Dec", noSideEffect.} +proc `-=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. + magic: "Dec", noSideEffect.} ## Decrements an ordinal -proc `*=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {.inline, noSideEffect.} = +proc `*=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. + inline, noSideEffect.} = ## Binary `*=` operator for ordinals x = x * y -proc `+=`*[T: float|float32|float64] (x: var T, y: T) {.inline, noSideEffect.} = +proc `+=`*[T: float|float32|float64] (x: var T, y: T) {. + inline, noSideEffect.} = ## Increments in placee a floating point number x = x + y -proc `-=`*[T: float|float32|float64] (x: var T, y: T) {.inline, noSideEffect.} = +proc `-=`*[T: float|float32|float64] (x: var T, y: T) {. + inline, noSideEffect.} = ## Decrements in place a floating point number x = x - y -proc `*=`*[T: float|float32|float64] (x: var T, y: T) {.inline, noSideEffect.} = +proc `*=`*[T: float|float32|float64] (x: var T, y: T) {. + inline, noSideEffect.} = ## Multiplies in place a floating point number x = x * y @@ -3380,8 +3387,8 @@ when hasAlloc: x.add(y) proc safeAdd*(x: var string, y: string) = - ## Adds ``y`` to ``x`` unless ``x`` is not yet initalized; in that case, ``x`` - ## becomes ``y`` + ## Adds ``y`` to ``x`` unless ``x`` is not yet initalized; in that + ## case, ``x`` becomes ``y`` if x == nil: x = y else: x.add(y) diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index c714f88ee..38e1f81a7 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -19,6 +19,7 @@ proc listFiles*(dir: string): seq[string] = builtin proc removeDir(dir: string) = builtin proc removeFile(dir: string) = builtin proc moveFile(src, dest: string) = builtin +proc copyFile(src, dest: string) = builtin proc createDir(dir: string) = builtin proc getOsError: string = builtin proc setCurrentDir(dir: string) = builtin @@ -30,6 +31,25 @@ proc switch*(key: string, val="") = builtin proc getCommand*(): string = builtin proc setCommand*(cmd: string) = builtin proc cmpIgnoreStyle(a, b: string): int = builtin +proc cmpIgnoreCase(a, b: string): int = builtin + +proc cmpic*(a, b: string): int = cmpIgnoreCase(a, b) + +proc getEnv*(key: string): string = builtin +proc existsEnv*(key: string): bool = builtin +proc fileExists*(filename: string): bool = builtin +proc dirExists*(dir: string): bool = builtin + +proc existsFile*(filename: string): bool = fileExists(filename) +proc existsDir*(dir: string): bool = dirExists(dir) + +proc toExe*(filename: string): string = + ## On Windows adds ".exe" to `filename`, else returns `filename` unmodified. + (when defined(windows): filename & ".exe" else: filename) + +proc toDll*(filename: string): string = + ## On Windows adds ".dll" to `filename`, on Posix produces "lib$filename.so". + (when defined(windows): filename & ".dll" else: "lib" & filename & ".so") proc strip(s: string): string = var i = 0 @@ -79,6 +99,11 @@ proc mvFile*(`from`, to: string) {.raises: [OSError].} = moveFile `from`, to checkOsError() +proc cpFile*(`from`, to: string) {.raises: [OSError].} = + log "mvFile: " & `from` & ", " & to: + copyFile `from`, to + checkOsError() + proc exec*(command: string, input = "", cache = "") = ## Executes an external process. log "exec: " & command: @@ -120,7 +145,7 @@ template withDir*(dir: string; body: untyped): untyped = ## ## If you need a permanent change, use the `cd() <#cd>`_ proc. Usage example: ## - ## .. code-block:: nimrod + ## .. code-block:: nim ## withDir "foo": ## # inside foo ## #back to last dir @@ -157,8 +182,8 @@ var binDir*, backend*: string skipDirs*, skipFiles*, skipExt*, installDirs*, installFiles*, - installExt*, bin*: seq[string] - requiresData*: seq[string] + installExt*, bin*: seq[string] = @[] + requiresData*: seq[string] = @[] proc requires*(deps: varargs[string]) = for d in deps: requiresData.add(d) diff --git a/todo.txt b/todo.txt index 82d1f32db..463ffc3b1 100644 --- a/todo.txt +++ b/todo.txt @@ -1,7 +1,7 @@ version 0.11.4 ============== -- make ``os`` and ``math`` work with NimScript +- document NimScript - document special cased varargs[untyped] and varargs[typed] - The remaining bugs of the lambda lifting pass that is responsible to enable |