diff options
-rw-r--r-- | compiler/ast.nim | 5 | ||||
-rw-r--r-- | compiler/jsgen.nim | 28 | ||||
-rw-r--r-- | compiler/options.nim | 1 | ||||
-rw-r--r-- | compiler/sempass2.nim | 46 | ||||
-rw-r--r-- | compiler/transf.nim | 2 | ||||
-rw-r--r-- | compiler/writetracking.nim | 262 | ||||
-rw-r--r-- | lib/pure/coro.nim | 36 | ||||
-rw-r--r-- | lib/pure/strutils.nim | 27 | ||||
-rw-r--r-- | lib/system/endb.nim | 22 | ||||
-rw-r--r-- | lib/system/sysio.nim | 11 | ||||
-rw-r--r-- | lib/wrappers/iup.nim | 2 | ||||
-rw-r--r-- | lib/wrappers/odbcsql.nim | 4 | ||||
-rw-r--r-- | tests/js/tbyvar.nim | 10 | ||||
-rw-r--r-- | web/news.txt | 2 |
14 files changed, 400 insertions, 58 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index a0a5d204a..0ad0a0718 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -291,12 +291,12 @@ const sfNoForward* = sfRegister # forward declarations are not required (per module) - sfNoRoot* = sfBorrow # a local variable is provably no root so it doesn't - # require RC ops sfCompileToCpp* = sfInfixCall # compile the module as C++ code sfCompileToObjc* = sfNamedParamCall # compile the module as Objective-C code sfExperimental* = sfOverriden # module uses the .experimental switch sfGoto* = sfOverriden # var is used for 'goto' code generation + sfWrittenTo* = sfBorrow # param is assigned to + sfEscapes* = sfProcvar # param escapes const # getting ready for the future expr/stmt merge @@ -527,6 +527,7 @@ const # deprecated and this mess can be cleaned up. tfVoid* = tfVarargs # for historical reasons we conflated 'void' with # 'empty' ('@[]' has the type 'seq[empty]'). + tfReturnsNew* = tfInheritable skError* = skUnknown # type flags that are essential for type equality: diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 4a0e22db7..c72365cce 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -1032,7 +1032,7 @@ proc genDeref(p: PProc, n: PNode, r: var TCompRes) = if a.typ != etyBaseIndex: internalError(n.info, "genDeref") r.res = "$1[$2]" % [a.address, a.res] -proc genArg(p: PProc, n: PNode, r: var TCompRes) = +proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) = var a: TCompRes gen(p, n, a) if a.typ == etyBaseIndex: @@ -1042,6 +1042,20 @@ proc genArg(p: PProc, n: PNode, r: var TCompRes) = else: add(r.res, a.res) +proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes) = + var a: TCompRes + gen(p, n, a) + if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and + a.typ == etyBaseIndex: + add(r.res, "$1[$2]" % [a.address, a.res]) + elif a.typ == etyBaseIndex: + add(r.res, a.address) + add(r.res, ", ") + add(r.res, a.res) + else: + add(r.res, a.res) + + proc genArgs(p: PProc, n: PNode, r: var TCompRes) = add(r.res, "(") var hasArgs = false @@ -1052,13 +1066,17 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes) = for i in countup(1, sonsLen(n) - 1): let it = n.sons[i] + var paramType : PNode = nil if i < sonsLen(typ): assert(typ.n.sons[i].kind == nkSym) - let paramType = typ.n.sons[i] + paramType = typ.n.sons[i] if paramType.typ.isCompileTimeOnly: continue if hasArgs: add(r.res, ", ") - genArg(p, it, r) + if paramType.isNil: + genArgNoParam(p, it, r) + else: + genArg(p, it, paramType.sym, r) hasArgs = true add(r.res, ")") r.kind = resExpr @@ -1083,7 +1101,7 @@ proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) = add(r.res, "(") for i in countup(2, sonsLen(n) - 1): if i > 2: add(r.res, ", ") - genArg(p, n.sons[i], r) + genArgNoParam(p, n.sons[i], r) add(r.res, ")") r.kind = resExpr @@ -1097,7 +1115,7 @@ proc genEcho(p: PProc, n: PNode, r: var TCompRes) = let it = n.sons[i] if it.typ.isCompileTimeOnly: continue if i > 0: add(r.res, ", ") - genArg(p, it, r) + genArgNoParam(p, it, r) add(r.res, ")") r.kind = resExpr diff --git a/compiler/options.nim b/compiler/options.nim index adf2017d6..1f167e2a6 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -13,6 +13,7 @@ import const hasTinyCBackend* = defined(tinyc) useEffectSystem* = true + useWriteTracking* = false hasFFI* = defined(useFFI) newScopeForIf* = true useCaas* = not defined(noCaas) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 3431ee2ff..d363eee77 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -9,7 +9,7 @@ import intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, - wordrecg, strutils, options, guards + wordrecg, strutils, options, guards, writetracking # Second semantic checking pass over the AST. Necessary because the old # way had some inherent problems. Performs: @@ -17,7 +17,7 @@ import # * effect+exception tracking # * "usage before definition" checking # * checks for invalid usages of compiletime magics (not implemented) -# * checks for invalid usages of PNimNode (not implemented) +# * checks for invalid usages of NimNode (not implemented) # * later: will do an escape analysis for closures at least # Predefined effects: @@ -29,21 +29,6 @@ import # --> a TR macro can annotate the proc with user defined annotations # --> the effect system can access these -# Load&Store analysis is performed on *paths*. A path is an access like -# obj.x.y[i].z; splitting paths up causes some problems: -# -# var x = obj.x -# var z = x.y[i].z -# -# Alias analysis is affected by this too! A good solution is *type splitting*: -# T becomes T1 and T2 if it's known that T1 and T2 can't alias. -# -# An aliasing problem and a race condition are effectively the same problem. -# Type based alias analysis is nice but not sufficient; especially splitting -# an array and filling it in parallel should be supported but is not easily -# done: It essentially requires a built-in 'indexSplit' operation and dependent -# typing. - # ------------------------ exception and tag tracking ------------------------- discard """ @@ -438,17 +423,41 @@ proc documentEffect(n, x: PNode, effectType: TSpecialWord, idx: int): PNode = result = newNode(nkExprColonExpr, n.info, @[ newIdentNode(getIdent(specialWords[effectType]), n.info), effects]) +proc documentWriteEffect(n: PNode; flag: TSymFlag; pragmaName: string): PNode = + let s = n.sons[namePos].sym + let params = s.typ.n + + var effects = newNodeI(nkBracket, n.info) + for i in 1 ..< params.len: + if params[i].kind == nkSym and flag in params[i].sym.flags: + effects.add params[i] + + if effects.len > 0: + result = newNode(nkExprColonExpr, n.info, @[ + newIdentNode(getIdent(pragmaName), n.info), effects]) + +proc documentNewEffect(n: PNode): PNode = + let s = n.sons[namePos].sym + if tfReturnsNew in s.typ.flags: + result = newIdentNode(getIdent("new"), n.info) + proc documentRaises*(n: PNode) = if n.sons[namePos].kind != nkSym: return let pragmas = n.sons[pragmasPos] let p1 = documentEffect(n, pragmas, wRaises, exceptionEffects) let p2 = documentEffect(n, pragmas, wTags, tagEffects) + let p3 = documentWriteEffect(n, sfWrittenTo, "writes") + let p4 = documentNewEffect(n) + let p5 = documentWriteEffect(n, sfEscapes, "escapes") - if p1 != nil or p2 != nil: + if p1 != nil or p2 != nil or p3 != nil or p4 != nil or p5 != nil: if pragmas.kind == nkEmpty: n.sons[pragmasPos] = newNodeI(nkPragma, n.info) if p1 != nil: n.sons[pragmasPos].add p1 if p2 != nil: n.sons[pragmasPos].add p2 + if p3 != nil: n.sons[pragmasPos].add p3 + if p4 != nil: n.sons[pragmasPos].add p4 + if p5 != nil: n.sons[pragmasPos].add p5 template notGcSafe(t): expr = {tfGcSafe, tfNoSideEffect} * t.flags == {} @@ -900,6 +909,7 @@ proc trackProc*(s: PSym, body: PNode) = message(s.info, warnLockLevel, "declared lock level is $1, but real lock level is $2" % [$s.typ.lockLevel, $t.maxLockLevel]) + when useWriteTracking: trackWrites(s, body) proc trackTopLevelStmt*(module: PSym; n: PNode) = if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, diff --git a/compiler/transf.nim b/compiler/transf.nim index 5c7472a39..4ca40ab74 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -115,7 +115,7 @@ proc transformSymAux(c: PTransf, n: PNode): PNode = # return liftIterSym(n) var b: PNode var tc = c.transCon - if sfBorrow in n.sym.flags: + if sfBorrow in n.sym.flags and n.sym.kind in routineKinds: # simply exchange the symbol: b = n.sym.getBody if b.kind != nkSym: internalError(n.info, "wrong AST for borrowed symbol") diff --git a/compiler/writetracking.nim b/compiler/writetracking.nim new file mode 100644 index 000000000..db3e6c53a --- /dev/null +++ b/compiler/writetracking.nim @@ -0,0 +1,262 @@ +# +# +# The Nim Compiler +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements the write tracking analysis. Read my block post for +## a basic description of the algorithm and ideas. + +import idents, ast, astalgo, trees, renderer, msgs, types + +const + debug = false + +type + AssignToResult = enum + asgnNil, # 'nil' is fine + asgnNew, # 'new(result)' + asgnOther # result = fooBar # not a 'new' --> 'result' might not 'new' + NewLocation = enum + newNone, + newLit, + newCall + W = object # WriteTrackContext + owner: PSym + returnsNew: AssignToResult # assignments to 'result' + markAsWrittenTo, markAsEscaping: PNode + assignments: seq[(PNode, PNode)] # list of all assignments in this proc + +proc returnsNewExpr*(n: PNode): NewLocation = + case n.kind + of nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit, + nkFloatLit..nkFloat64Lit, nkNilLit: + result = newLit + of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, + nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch, + nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast: + result = returnsNewExpr(n.lastSon) + of nkCurly, nkBracket, nkPar, nkObjConstr, nkClosure, + nkIfExpr, nkIfStmt, nkWhenStmt, nkCaseStmt, nkTryStmt: + result = newLit + for i in ord(n.kind == nkObjConstr) .. <n.len: + let x = returnsNewExpr(n.sons[i]) + case x + of newNone: return newNone + of newLit: discard + of newCall: result = newCall + of nkCallKinds: + if n.sons[0].typ != nil and tfReturnsNew in n.sons[0].typ.flags: + result = newCall + else: + result = newNone + +proc deps(w: var W; dest, src: PNode) = + # let x = (localA, localB) + # compute 'returnsNew' property: + let retNew = returnsNewExpr(src) + if dest.kind == nkSym and dest.sym.kind == skResult: + if retNew != newNone: + if w.returnsNew != asgnOther: w.returnsNew = asgnNew + else: + w.returnsNew = asgnOther + # 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)) + +proc depsArgs(w: var W; n: PNode) = + if n.sons[0].typ.isNil: return + var typ = skipTypes(n.sons[0].typ, abstractInst) + if typ.kind != tyProc: return + # echo n.info, " ", n, " ", w.owner.name.s, " ", typeToString(typ) + assert(sonsLen(typ) == sonsLen(typ.n)) + for i in 1 ..< n.len: + let it = n.sons[i] + if i < sonsLen(typ): + assert(typ.n.sons[i].kind == nkSym) + let paramType = typ.n.sons[i] + if paramType.typ.isCompileTimeOnly: continue + if sfWrittenTo in paramType.sym.flags or paramType.typ.kind == tyVar: + # p(f(x, y), X, g(h, z)) + deps(w, it, w.markAsWrittenTo) + if sfEscapes in paramType.sym.flags: + deps(w, it, w.markAsEscaping) + +proc deps(w: var W; n: PNode) = + case n.kind + of nkLetSection, nkVarSection: + for child in n: + let last = lastSon(child) + if last.kind == nkEmpty: continue + 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]) + else: + for i in 0 .. child.len-3: + deps(w, child.sons[i], last) + of nkAsgn, nkFastAsgn: + 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)) + 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 + # 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. + 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 + +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 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 (r.len > 0) and (info != {} or src == w.markAsEscaping): + possibleAliases(w, r) + var destIsParam = false + for a in r: + if a.kind in {skResult, skParam} and a.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) + +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 = @[] + deps(w, body) + markDirty(w) + markEscaping(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/coro.nim b/lib/pure/coro.nim index 6ef5f6f54..8fa529474 100644 --- a/lib/pure/coro.nim +++ b/lib/pure/coro.nim @@ -15,8 +15,7 @@ import macros import arch import lists -const coroDefaultStackSize = 512 * 1024 - +const defaultStackSize = 512 * 1024 type Coroutine = ref object # prev: ptr Coroutine @@ -38,8 +37,7 @@ proc GC_addStack(starts: pointer) {.cdecl, importc.} proc GC_removeStack(starts: pointer) {.cdecl, importc.} proc GC_setCurrentStack(starts, pos: pointer) {.cdecl, importc.} - -proc coroStart*(c: proc(), stacksize: int=coroDefaultStackSize) = +proc start*(c: proc(), stacksize: int=defaultStackSize) = ## Adds coroutine to event loop. It does not run immediately. var coro = Coroutine() coro.fn = c @@ -49,20 +47,22 @@ proc coroStart*(c: proc(), stacksize: int=coroDefaultStackSize) = coroutines.append(coro) {.push stackTrace: off.} -proc coroYield*(sleepTime: float=0) = +proc suspend*(sleepTime: float=0) = ## Stops coroutine execution and resumes no sooner than after ``sleeptime`` seconds. ## Until then other coroutines are executed. + ## + ## This is similar to a `yield`:idx:, or a `yieldFrom`:idx in Python. var oldFrame = getFrame() var sp {.volatile.}: pointer GC_setCurrentStack(current.stack, cast[pointer](addr sp)) - current.sleepTime = sleep_time + current.sleepTime = sleepTime current.lastRun = epochTime() if setjmp(current.ctx) == 0: longjmp(mainCtx, 1) setFrame(oldFrame) {.pop.} -proc coroRun*() = +proc run*() = ## Starts main event loop which exits when all coroutines exit. Calling this proc ## starts execution of first coroutine. var node = coroutines.head @@ -108,18 +108,16 @@ proc coroRun*() = else: node = node.next - -proc coroAlive*(c: proc()): bool = +proc alive*(c: proc()): bool = ## Returns ``true`` if coroutine has not returned, ``false`` otherwise. for coro in items(coroutines): if coro.fn == c: return true -proc coroWait*(c: proc(), interval=0.01) = +proc wait*(c: proc(), interval=0.01) = ## Returns only after coroutine ``c`` has returned. ``interval`` is time in seconds how often. - while coroAlive(c): - coroYield interval - + while alive(c): + suspend interval when isMainModule: var stackCheckValue = 1100220033 @@ -128,18 +126,18 @@ when isMainModule: proc c1() = for i in 0 .. 3: echo "c1" - coroYield 0.05 + suspend 0.05 echo "c1 exits" proc c2() = for i in 0 .. 3: echo "c2" - coroYield 0.025 - coroWait(c1) + suspend 0.025 + wait(c1) echo "c2 exits" - coroStart(c1) - coroStart(c2) - coroRun() + start(c1) + start(c2) + run() echo "done ", stackCheckValue diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index e3f99b895..ae3bd7f63 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1197,8 +1197,8 @@ proc editDistance*(a, b: string): int {.noSideEffect, # floating point formating: -proc c_sprintf(buf, frmt: cstring) {.header: "<stdio.h>", importc: "sprintf", - varargs, noSideEffect.} +proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>", + importc: "sprintf", varargs, noSideEffect.} type FloatFormatMode* = enum ## the different modes of floating point formating @@ -1209,7 +1209,8 @@ type {.deprecated: [TFloatFormat: FloatFormatMode].} proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, - precision: range[0..32] = 16): string {. + precision: range[0..32] = 16; + decimalSep = '.'): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. ## @@ -1225,6 +1226,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, var frmtstr {.noinit.}: array[0..5, char] buf {.noinit.}: array[0..2500, char] + L: cint frmtstr[0] = '%' if precision > 0: frmtstr[1] = '#' @@ -1232,15 +1234,20 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, frmtstr[3] = '*' frmtstr[4] = floatFormatToChar[format] frmtstr[5] = '\0' - c_sprintf(buf, frmtstr, precision, f) + L = c_sprintf(buf, frmtstr, precision, f) else: frmtstr[1] = floatFormatToChar[format] frmtstr[2] = '\0' - c_sprintf(buf, frmtstr, f) - result = $buf + L = c_sprintf(buf, frmtstr, f) + result = newString(L) + for i in 0 ..< L: + # Depending on the locale either dot or comma is produced, + # but nothing else is possible: + if buf[i] in {'.', ','}: result[i] = decimalsep + else: result[i] = buf[i] proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, - precision: range[0..32] = 16): string {. + precision: range[0..32] = 16; decimalSep = '.'): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. ## @@ -1250,7 +1257,7 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, ## of significant digits to be printed. ## `precision`'s default value is the maximum number of meaningful digits ## after the decimal point for Nim's ``float`` type. - result = formatBiggestFloat(f, format, precision) + result = formatBiggestFloat(f, format, precision, decimalSep) proc formatSize*(bytes: BiggestInt, decimalSep = '.'): string = ## Rounds and formats `bytes`. Examples: @@ -1464,8 +1471,8 @@ when isMainModule: doAssert wordWrap(inp, 10, false) == outp doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001" - doAssert formatBiggestFloat(0.00000000001, ffScientific, 1) in - ["1.0e-11", "1.0e-011"] + doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in + ["1,0e-11", "1,0e-011"] doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" when not defined(testing): diff --git a/lib/system/endb.nim b/lib/system/endb.nim index cba11ac5e..b2cc5624b 100644 --- a/lib/system/endb.nim +++ b/lib/system/endb.nim @@ -298,7 +298,7 @@ proc dbgEvaluate(stream: File, s: cstring, start: int, f: PFrame) = for i in 0 .. f.len-1: let v = getLocal(f, i) if c_strcmp(v.name, dbgTemp.data) == 0: - writeVariable(stream, v) + writeVariable(stream, v) proc dbgOut(s: cstring, start: int, currFrame: PFrame) = var dbgTemp: StaticStr @@ -396,7 +396,13 @@ proc commandPrompt() = again = false quit(1) # BUGFIX: quit with error code > 0 elif ?"e" or ?"eval": + var + prevState = dbgState + prevSkipFrame = dbgSkipToFrame + dbgState = dbSkipCurrent dbgEvaluate(stdout, dbgUser.data, i, dbgFramePtr) + dbgState = prevState + dbgSkipToFrame = prevSkipFrame elif ?"o" or ?"out": dbgOut(dbgUser.data, i, dbgFramePtr) elif ?"stackframe": @@ -404,9 +410,21 @@ proc commandPrompt() = elif ?"w" or ?"where": dbgShowExecutionPoint() elif ?"l" or ?"locals": + var + prevState = dbgState + prevSkipFrame = dbgSkipToFrame + dbgState = dbSkipCurrent listLocals(stdout, dbgFramePtr) + dbgState = prevState + dbgSkipToFrame = prevSkipFrame elif ?"g" or ?"globals": + var + prevState = dbgState + prevSkipFrame = dbgSkipToFrame + dbgState = dbSkipCurrent listGlobals(stdout) + dbgState = prevState + dbgSkipToFrame = prevSkipFrame elif ?"u" or ?"up": if dbgDown <= 0: debugOut("[Warning] cannot go up any further ") @@ -484,7 +502,7 @@ proc dbgWriteStackTrace(f: PFrame) = inc(i) b = b.prev for j in countdown(i-1, 0): - if tempFrames[j] == nil: + if tempFrames[j] == nil: write(stdout, "(") write(stdout, skipped) write(stdout, " calls omitted) ...") diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 265f92fa2..3d0b2aa8a 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -90,12 +90,21 @@ proc readLine(f: File, line: var TaintedString): bool = let m = memchr(addr line.string[pos], '\l'.ord, space) if m != nil: # \l found: Could be our own or the one by fgets, in any case, we're done - let last = cast[ByteAddress](m) - cast[ByteAddress](addr line.string[0]) + var last = cast[ByteAddress](m) - cast[ByteAddress](addr line.string[0]) if last > 0 and line.string[last-1] == '\c': line.string.setLen(last-1) return true + # We have to distinguish between two possible cases: + # \0\l\0 => line ending in a null character. + # \0\l\l => last line without newline, null was put there by fgets. + elif last > 0 and line.string[last-1] == '\0': + if last < pos + space - 1 and line.string[last+1] != '\0': + dec last line.string.setLen(last) return true + else: + # fgets will have inserted a null byte at the end of the string. + dec space # No \l found: Increase buffer and read more inc pos, space space = 128 # read in 128 bytes at a time diff --git a/lib/wrappers/iup.nim b/lib/wrappers/iup.nim index cbd9b5ae9..d910173ca 100644 --- a/lib/wrappers/iup.nim +++ b/lib/wrappers/iup.nim @@ -307,6 +307,8 @@ proc menuv*(children: ptr PIhandle): PIhandle {. proc button*(title, action: cstring): PIhandle {. importc: "IupButton", cdecl, dynlib: dllname.} +proc link*(url, title: cstring): PIhandle {. + importc: "IupLink", cdecl, dynlib: dllname.} proc canvas*(action: cstring): PIhandle {. importc: "IupCanvas", cdecl, dynlib: dllname.} proc dialog*(child: PIhandle): PIhandle {. diff --git a/lib/wrappers/odbcsql.nim b/lib/wrappers/odbcsql.nim index 971861a6a..43ad80f76 100644 --- a/lib/wrappers/odbcsql.nim +++ b/lib/wrappers/odbcsql.nim @@ -680,8 +680,12 @@ proc SQLBrowseConnect*(hdbc: SqlHDBC, szConnStrIn: PSQLCHAR, dynlib: odbclib, importc.} proc SQLExecDirect*(StatementHandle: SqlHStmt, StatementText: PSQLCHAR, TextLength: TSqlInteger): TSqlSmallInt{.dynlib: odbclib, importc.} +proc SQLExecDirectW*(StatementHandle: SqlHStmt, StatementText: WideCString, + TextLength: TSqlInteger): TSqlSmallInt{.dynlib: odbclib, importc.} proc SQLPrepare*(StatementHandle: SqlHStmt, StatementText: PSQLCHAR, TextLength: TSqlInteger): TSqlSmallInt{.dynlib: odbclib, importc.} +proc SQLPrepareW*(StatementHandle: SqlHStmt, StatementText: WideCString, + TextLength: TSqlInteger): TSqlSmallInt{.dynlib: odbclib, importc.} proc SQLCloseCursor*(StatementHandle: SqlHStmt): TSqlSmallInt{.dynlib: odbclib, importc.} proc SQLExecute*(StatementHandle: SqlHStmt): TSqlSmallInt{.dynlib: odbclib, importc.} diff --git a/tests/js/tbyvar.nim b/tests/js/tbyvar.nim index 1269e6f66..9714cd56b 100644 --- a/tests/js/tbyvar.nim +++ b/tests/js/tbyvar.nim @@ -31,3 +31,13 @@ proc main = echo y main() + +# Test: pass var seq to var openarray +var s = @[2, 1] +proc foo(a: var openarray[int]) = a[0] = 123 + +proc bar(s: var seq[int], a: int) = + doAssert(a == 5) + foo(s) +s.bar(5) +doAssert(s == @[123, 1]) diff --git a/web/news.txt b/web/news.txt index 040442695..a13334e3f 100644 --- a/web/news.txt +++ b/web/news.txt @@ -62,6 +62,8 @@ News - Define ``nimPinToCpu`` to make the ``threadpool`` use explicit thread affinities. This can speed up or slow down the thread pool; it's up to you to benchmark it. + - ``strutils.formatFloat`` and ``formatBiggestFloat`` do not depend on the C + locale anymore and now take an optional ``decimalSep = '.'`` parameter. Language Additions |