Merge branch 'master' of github.com:Araq/Nimrod

This commit is contained in:
Araq 2013-03-16 20:40:26 +01:00
commit d914963539
21 changed files with 838 additions and 635 deletions

View file

@ -955,15 +955,22 @@ proc enumSpecifier(p: var TParser): PNode =
result = newNodeP(nkConstSection, p) result = newNodeP(nkConstSection, p)
getTok(p, result) getTok(p, result)
var i = 0 var i = 0
var hasUnknown = false
while true: while true:
var name = skipIdentExport(p) var name = skipIdentExport(p)
var val: PNode var val: PNode
if p.tok.xkind == pxAsgn: if p.tok.xkind == pxAsgn:
getTok(p, name) getTok(p, name)
val = constantExpression(p) val = constantExpression(p)
if val.kind == nkIntLit: i = int(val.intVal)+1 if val.kind == nkIntLit:
else: parMessage(p, errXExpected, "int literal") i = int(val.intVal)+1
hasUnknown = false
else: else:
hasUnknown = true
else:
if hasUnknown:
parMessage(p, warnUser, "computed const value may be wrong: " &
name.renderTree)
val = newIntNodeP(nkIntLit, i, p) val = newIntNodeP(nkIntLit, i, p)
inc(i) inc(i)
var c = createConst(name, ast.emptyNode, val, p) var c = createConst(name, ast.emptyNode, val, p)

View file

@ -9,6 +9,20 @@ extern "C" {
# endif # endif
#endif #endif
enum
{
/* 8bit, color or not */
CV_LOAD_IMAGE_UNCHANGED =-1,
/* 8bit, gray */
CV_LOAD_IMAGE_GRAYSCALE =0,
/* ?, color */
CV_LOAD_IMAGE_COLOR =1,
/* any depth, ? */
CV_LOAD_IMAGE_ANYDEPTH =2,
/* ?, any color */
CV_LOAD_IMAGE_ANYCOLOR =4
};
typedef void (*callback_t) (int rc); typedef void (*callback_t) (int rc);
typedef const char* (*callback2)(int rc, long L, const char* buffer); typedef const char* (*callback2)(int rc, long L, const char* buffer);

View file

@ -892,6 +892,12 @@ proc getFileHeader(cfilenoext: string): PRope =
result = getCopyright(cfilenoext) result = getCopyright(cfilenoext)
addIntTypes(result) addIntTypes(result)
proc genFilenames(m: BModule): PRope =
discard cgsym(m, "dbgRegisterFilename")
result = nil
for i in 0.. <fileInfos.len:
result.appf("dbgRegisterFilename($1);$n", fileInfos[i].projPath.makeCString)
proc genMainProc(m: BModule) = proc genMainProc(m: BModule) =
const const
CommonMainBody = CommonMainBody =
@ -950,6 +956,8 @@ proc genMainProc(m: BModule) =
nimMain = PosixNimMain nimMain = PosixNimMain
otherMain = PosixCMain otherMain = PosixCMain
if gBreakpoints != nil: discard cgsym(m, "dbgRegisterBreakpoint") if gBreakpoints != nil: discard cgsym(m, "dbgRegisterBreakpoint")
if optEndb in gOptions:
gBreakpoints.app(m.genFilenames)
let initStackBottomCall = if emulatedThreadVars() or let initStackBottomCall = if emulatedThreadVars() or
platform.targetOS == osStandalone: "".toRope platform.targetOS == osStandalone: "".toRope

View file

@ -44,7 +44,6 @@ type
TBlock{.final.} = object TBlock{.final.} = object
id: int # the ID of the label; positive means that it id: int # the ID of the label; positive means that it
# has been used (i.e. the label should be emitted) # has been used (i.e. the label should be emitted)
nestedTryStmts: int # how many try statements is it nested into
isLoop: bool # whether it's a 'block' or 'while' isLoop: bool # whether it's a 'block' or 'while'
TGlobals{.final.} = object TGlobals{.final.} = object
@ -62,7 +61,6 @@ type
module: BModule module: BModule
g: PGlobals g: PGlobals
BeforeRetNeeded: bool BeforeRetNeeded: bool
nestedTryStmts: int
unique: int unique: int
blocks: seq[TBlock] blocks: seq[TBlock]
@ -485,10 +483,6 @@ proc genLineDir(p: var TProc, n: PNode, r: var TCompRes) =
((p.prc == nil) or not (sfPure in p.prc.flags)): ((p.prc == nil) or not (sfPure in p.prc.flags)):
appf(r.com, "F.line = $1;$n", [toRope(line)]) appf(r.com, "F.line = $1;$n", [toRope(line)])
proc finishTryStmt(p: var TProc, r: var TCompRes, howMany: int) =
for i in countup(1, howMany):
app(r.com, "excHandler = excHandler.prev;" & tnl)
proc genWhileStmt(p: var TProc, n: PNode, r: var TCompRes) = proc genWhileStmt(p: var TProc, n: PNode, r: var TCompRes) =
var var
cond, stmt: TCompRes cond, stmt: TCompRes
@ -498,7 +492,6 @@ proc genWhileStmt(p: var TProc, n: PNode, r: var TCompRes) =
length = len(p.blocks) length = len(p.blocks)
setlen(p.blocks, length + 1) setlen(p.blocks, length + 1)
p.blocks[length].id = - p.unique p.blocks[length].id = - p.unique
p.blocks[length].nestedTryStmts = p.nestedTryStmts
p.blocks[length].isLoop = true p.blocks[length].isLoop = true
labl = p.unique labl = p.unique
gen(p, n.sons[0], cond) gen(p, n.sons[0], cond)
@ -543,7 +536,6 @@ proc genTryStmt(p: var TProc, n: PNode, r: var TCompRes) =
if optStackTrace in p.Options: app(r.com, "framePtr = F;" & tnl) if optStackTrace in p.Options: app(r.com, "framePtr = F;" & tnl)
app(r.com, "try {" & tnl) app(r.com, "try {" & tnl)
length = sonsLen(n) length = sonsLen(n)
inc(p.nestedTryStmts)
genStmt(p, n.sons[0], a) genStmt(p, n.sons[0], a)
app(r.com, mergeStmt(a)) app(r.com, mergeStmt(a))
i = 1 i = 1
@ -571,8 +563,6 @@ proc genTryStmt(p: var TProc, n: PNode, r: var TCompRes) =
appf(epart, "$1}$n", [mergeStmt(a)]) appf(epart, "$1}$n", [mergeStmt(a)])
inc(i) inc(i)
if epart != nil: appf(r.com, "} catch (EXC) {$n$1", [epart]) if epart != nil: appf(r.com, "} catch (EXC) {$n$1", [epart])
finishTryStmt(p, r, p.nestedTryStmts)
dec(p.nestedTryStmts)
app(r.com, "} finally {" & tnl & "excHandler = excHandler.prev;" & tnl) app(r.com, "} finally {" & tnl & "excHandler = excHandler.prev;" & tnl)
if (i < length) and (n.sons[i].kind == nkFinally): if (i < length) and (n.sons[i].kind == nkFinally):
genStmt(p, n.sons[i].sons[0], a) genStmt(p, n.sons[i].sons[0], a)
@ -655,7 +645,6 @@ proc genBlock(p: var TProc, n: PNode, r: var TCompRes) =
sym.loc.a = idx sym.loc.a = idx
setlen(p.blocks, idx + 1) setlen(p.blocks, idx + 1)
p.blocks[idx].id = - p.unique # negative because it isn't used yet p.blocks[idx].id = - p.unique # negative because it isn't used yet
p.blocks[idx].nestedTryStmts = p.nestedTryStmts
labl = p.unique labl = p.unique
if n.kind == nkBlockExpr: genStmtListExpr(p, n.sons[1], r) if n.kind == nkBlockExpr: genStmtListExpr(p, n.sons[1], r)
else: genStmt(p, n.sons[1], r) else: genStmt(p, n.sons[1], r)
@ -682,7 +671,6 @@ proc genBreakStmt(p: var TProc, n: PNode, r: var TCompRes) =
if idx < 0 or not p.blocks[idx].isLoop: if idx < 0 or not p.blocks[idx].isLoop:
InternalError(n.info, "no loop to break") InternalError(n.info, "no loop to break")
p.blocks[idx].id = abs(p.blocks[idx].id) # label is used p.blocks[idx].id = abs(p.blocks[idx].id) # label is used
finishTryStmt(p, r, p.nestedTryStmts - p.blocks[idx].nestedTryStmts)
appf(r.com, "break L$1;$n", [toRope(p.blocks[idx].id)]) appf(r.com, "break L$1;$n", [toRope(p.blocks[idx].id)])
proc genAsmStmt(p: var TProc, n: PNode, r: var TCompRes) = proc genAsmStmt(p: var TProc, n: PNode, r: var TCompRes) =
@ -1433,7 +1421,6 @@ proc genReturnStmt(p: var TProc, n: PNode, r: var TCompRes) =
if a.com != nil: appf(r.com, "$1;$n", mergeStmt(a)) if a.com != nil: appf(r.com, "$1;$n", mergeStmt(a))
else: else:
genLineDir(p, n, r) genLineDir(p, n, r)
finishTryStmt(p, r, p.nestedTryStmts)
app(r.com, "break BeforeRet;" & tnl) app(r.com, "break BeforeRet;" & tnl)
proc genProcBody(p: var TProc, prc: PSym, r: TCompRes): PRope = proc genProcBody(p: var TProc, prc: PSym, r: TCompRes): PRope =

View file

@ -57,8 +57,6 @@ Files: "compiler/*.nim"
Files: "build/empty.txt" Files: "build/empty.txt"
Files: "bin/empty.txt" Files: "bin/empty.txt"
Files: "packages/docutils/*.nim"
[Lib] [Lib]
Files: "lib/nimbase.h" Files: "lib/nimbase.h"
@ -85,6 +83,7 @@ Files: "lib/wrappers/zip/libzip_all.c"
Files: "lib/windows/*.nim" Files: "lib/windows/*.nim"
Files: "lib/posix/*.nim" Files: "lib/posix/*.nim"
Files: "lib/js/*.nim" Files: "lib/js/*.nim"
Files: "lib/packages/docutils/*.nim"
[Other] [Other]

View file

@ -490,7 +490,7 @@ proc getArrayConstr(m: PSym, n: PNode): PNode =
proc foldArrayAccess(m: PSym, n: PNode): PNode = proc foldArrayAccess(m: PSym, n: PNode): PNode =
var x = getConstExpr(m, n.sons[0]) var x = getConstExpr(m, n.sons[0])
if x == nil: return if x == nil or x.typ.skipTypes({tyGenericInst}).kind == tyTypeDesc: return
var y = getConstExpr(m, n.sons[1]) var y = getConstExpr(m, n.sons[1])
if y == nil: return if y == nil: return
@ -542,6 +542,11 @@ proc foldConStrStr(m: PSym, n: PNode): PNode =
if a == nil: return nil if a == nil: return nil
result.strVal.add(getStrOrChar(a)) result.strVal.add(getStrOrChar(a))
proc newSymNodeTypeDesc*(s: PSym; info: TLineInfo): PNode =
result = newSymNode(s, info)
result.typ = newType(tyTypeDesc, s.owner)
result.typ.addSonSkipIntLit(s.typ)
proc getConstExpr(m: PSym, n: PNode): PNode = proc getConstExpr(m: PSym, n: PNode): PNode =
result = nil result = nil
case n.kind case n.kind
@ -569,6 +574,8 @@ proc getConstExpr(m: PSym, n: PNode): PNode =
if sfFakeConst notin s.flags: result = copyTree(s.ast) if sfFakeConst notin s.flags: result = copyTree(s.ast)
elif s.kind in {skProc, skMethod}: # BUGFIX elif s.kind in {skProc, skMethod}: # BUGFIX
result = n result = n
elif s.kind in {skType, skGenericParam}:
result = newSymNodeTypeDesc(s, n.info)
of nkCharLit..nkNilLit: of nkCharLit..nkNilLit:
result = copyNode(n) result = copyNode(n)
of nkIfExpr: of nkIfExpr:

View file

@ -31,11 +31,6 @@ proc getIdentNode(n: PNode): PNode =
illFormedAst(n) illFormedAst(n)
result = n result = n
proc newSymNodeTypeDesc(s: PSym; info: TLineInfo): PNode =
result = newSymNode(s, info)
result.typ = newType(tyTypeDesc, s.owner)
result.typ.addSonSkipIntLit(s.typ)
proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags, proc semGenericStmt(c: PContext, n: PNode, flags: TSemGenericFlags,
ctx: var TIntSet): PNode ctx: var TIntSet): PNode
proc semGenericStmtScope(c: PContext, n: PNode, proc semGenericStmtScope(c: PContext, n: PNode,

View file

@ -61,8 +61,8 @@ Executing Commands
Breakpoint Commands Breakpoint Commands
=================== ===================
``b``, ``setbreak`` <identifier> [fromline [toline]] [file] ``b``, ``setbreak`` [fromline [toline]] [file]
Set a new breakpoint named 'identifier' for the given file Set a new breakpoint for the given file
and line numbers. If no file is given, the current execution point's and line numbers. If no file is given, the current execution point's
filename is used. If the filename has no extension, ``.nim`` is filename is used. If the filename has no extension, ``.nim`` is
appended for your convenience. appended for your convenience.
@ -71,16 +71,16 @@ Breakpoint Commands
breakpoint contains a line number range. Some examples if it is still breakpoint contains a line number range. Some examples if it is still
unclear: unclear:
* ``b br1 12 15 thallo`` creates a breakpoint named ``br1`` that * ``b 12 15 thallo`` creates a breakpoint that
will be triggered if the instruction pointer reaches one of the will be triggered if the instruction pointer reaches one of the
lines 12-15 in the file ``thallo.nim``. lines 12-15 in the file ``thallo.nim``.
* ``b br1 12 thallo`` creates a breakpoint named ``br1`` that * ``b 12 thallo`` creates a breakpoint that
will be triggered if the instruction pointer reaches the will be triggered if the instruction pointer reaches the
line 12 in the file ``thallo.nim``. line 12 in the file ``thallo.nim``.
* ``b br1 12`` creates a breakpoint named ``br1`` that * ``b 12`` creates a breakpoint that
will be triggered if the instruction pointer reaches the will be triggered if the instruction pointer reaches the
line 12 in the current file. line 12 in the current file.
* ``b br1`` creates a breakpoint named ``br1`` that * ``b`` creates a breakpoint that
will be triggered if the instruction pointer reaches the will be triggered if the instruction pointer reaches the
current line in the current file again. current line in the current file again.

View file

@ -16,7 +16,10 @@ Pure libraries do not depend on any external ``*.dll`` or ``lib*.so`` binary
while impure libraries do. A wrapper is an impure library that is a very while impure libraries do. A wrapper is an impure library that is a very
low-level interface to a C library. low-level interface to a C library.
Read this `document <apis.html>`_ for a quick overview of the API design. Read this `document <apis.html>`_ for a quick overview of the API design. If
you can't find here some functionality you are looking for you could try using
the 3rd party `package manager Babel <https://github.com/nimrod-code/babel>`_
and its list of packages.
Pure libraries Pure libraries

View file

@ -70,6 +70,7 @@ if [ $# -eq 1 ] ; then
mkdir -p $libdir/windows mkdir -p $libdir/windows
mkdir -p $libdir/posix mkdir -p $libdir/posix
mkdir -p $libdir/js mkdir -p $libdir/js
mkdir -p $libdir/packages/docutils
cp bin/nimrod $bindir/nimrod cp bin/nimrod $bindir/nimrod
chmod 755 $bindir/nimrod chmod 755 $bindir/nimrod
@ -711,20 +712,26 @@ if [ $# -eq 1 ] ; then
chmod 644 $libdir/system/debugger.nim chmod 644 $libdir/system/debugger.nim
cp lib/system/dyncalls.nim $libdir/system/dyncalls.nim cp lib/system/dyncalls.nim $libdir/system/dyncalls.nim
chmod 644 $libdir/system/dyncalls.nim chmod 644 $libdir/system/dyncalls.nim
cp lib/system/jssys.nim $libdir/system/jssys.nim
chmod 644 $libdir/system/jssys.nim
cp lib/system/embedded.nim $libdir/system/embedded.nim cp lib/system/embedded.nim $libdir/system/embedded.nim
chmod 644 $libdir/system/embedded.nim chmod 644 $libdir/system/embedded.nim
cp lib/system/endb.nim $libdir/system/endb.nim
chmod 644 $libdir/system/endb.nim
cp lib/system/excpt.nim $libdir/system/excpt.nim cp lib/system/excpt.nim $libdir/system/excpt.nim
chmod 644 $libdir/system/excpt.nim chmod 644 $libdir/system/excpt.nim
cp lib/system/gc.nim $libdir/system/gc.nim cp lib/system/gc.nim $libdir/system/gc.nim
chmod 644 $libdir/system/gc.nim chmod 644 $libdir/system/gc.nim
cp lib/system/gc2.nim $libdir/system/gc2.nim cp lib/system/gc2.nim $libdir/system/gc2.nim
chmod 644 $libdir/system/gc2.nim chmod 644 $libdir/system/gc2.nim
cp lib/system/gc_genms.nim $libdir/system/gc_genms.nim
chmod 644 $libdir/system/gc_genms.nim
cp lib/system/gc_ms.nim $libdir/system/gc_ms.nim
chmod 644 $libdir/system/gc_ms.nim
cp lib/system/hti.nim $libdir/system/hti.nim cp lib/system/hti.nim $libdir/system/hti.nim
chmod 644 $libdir/system/hti.nim chmod 644 $libdir/system/hti.nim
cp lib/system/inclrtl.nim $libdir/system/inclrtl.nim cp lib/system/inclrtl.nim $libdir/system/inclrtl.nim
chmod 644 $libdir/system/inclrtl.nim chmod 644 $libdir/system/inclrtl.nim
cp lib/system/jssys.nim $libdir/system/jssys.nim
chmod 644 $libdir/system/jssys.nim
cp lib/system/mmdisp.nim $libdir/system/mmdisp.nim cp lib/system/mmdisp.nim $libdir/system/mmdisp.nim
chmod 644 $libdir/system/mmdisp.nim chmod 644 $libdir/system/mmdisp.nim
cp lib/system/profiler.nim $libdir/system/profiler.nim cp lib/system/profiler.nim $libdir/system/profiler.nim
@ -1103,6 +1110,14 @@ if [ $# -eq 1 ] ; then
chmod 644 $libdir/posix/posix.nim chmod 644 $libdir/posix/posix.nim
cp lib/js/dom.nim $libdir/js/dom.nim cp lib/js/dom.nim $libdir/js/dom.nim
chmod 644 $libdir/js/dom.nim chmod 644 $libdir/js/dom.nim
cp lib/packages/docutils/highlite.nim $libdir/packages/docutils/highlite.nim
chmod 644 $libdir/packages/docutils/highlite.nim
cp lib/packages/docutils/rst.nim $libdir/packages/docutils/rst.nim
chmod 644 $libdir/packages/docutils/rst.nim
cp lib/packages/docutils/rstast.nim $libdir/packages/docutils/rstast.nim
chmod 644 $libdir/packages/docutils/rstast.nim
cp lib/packages/docutils/rstgen.nim $libdir/packages/docutils/rstgen.nim
chmod 644 $libdir/packages/docutils/rstgen.nim
echo "installation successful" echo "installation successful"
else else

View file

@ -102,6 +102,16 @@ proc newAny(value: pointer, rawType: PNimType): TAny =
result.value = value result.value = value
result.rawType = rawType result.rawType = rawType
when defined(system.TVarSlot):
proc toAny*(x: TVarSlot): TAny {.inline.} =
## constructs a ``TAny`` object from a variable slot ``x``.
## This captures `x`'s address, so `x` can be modified with its
## ``TAny`` wrapper! The client needs to ensure that the wrapper
## **does not** live longer than `x`!
## This is provided for easier reflection capabilities of a debugger.
result.value = x.address
result.rawType = x.typ
proc toAny*[T](x: var T): TAny {.inline.} = proc toAny*[T](x: var T): TAny {.inline.} =
## constructs a ``TAny`` object from `x`. This captures `x`'s address, so ## constructs a ``TAny`` object from `x`. This captures `x`'s address, so
## `x` can be modified with its ``TAny`` wrapper! The client needs to ensure ## `x` can be modified with its ``TAny`` wrapper! The client needs to ensure

View file

@ -415,7 +415,15 @@ proc parseEnum*[T: enum](s: string, default: T): T =
proc repeatChar*(count: int, c: Char = ' '): string {.noSideEffect, proc repeatChar*(count: int, c: Char = ' '): string {.noSideEffect,
rtl, extern: "nsuRepeatChar".} = rtl, extern: "nsuRepeatChar".} =
## Returns a string of length `count` consisting only of ## Returns a string of length `count` consisting only of
## the character `c`. ## the character `c`. You can use this proc to left align strings. Example:
##
## .. code-block:: nimrod
## let
## width = 15
## text1 = "Hello user!"
## text2 = "This is a very long string"
## echo text1 & repeatChar(max(0, width - text1.len)) & "|"
## echo text2 & repeatChar(max(0, width - text2.len)) & "|"
result = newString(count) result = newString(count)
for i in 0..count-1: result[i] = c for i in 0..count-1: result[i] = c
@ -429,7 +437,8 @@ proc align*(s: string, count: int): string {.
noSideEffect, rtl, extern: "nsuAlignString".} = noSideEffect, rtl, extern: "nsuAlignString".} =
## Aligns a string `s` with spaces, so that is of length `count`. Spaces are ## Aligns a string `s` with spaces, so that is of length `count`. Spaces are
## added before `s` resulting in right alignment. If ``s.len >= count``, no ## added before `s` resulting in right alignment. If ``s.len >= count``, no
## spaces are added and `s` is returned unchanged. ## spaces are added and `s` is returned unchanged. If you need to left align
## a string use the repeatChar proc.
if s.len < count: if s.len < count:
result = newString(count) result = newString(count)
var spaces = count - s.len var spaces = count - s.len

View file

@ -132,6 +132,11 @@ proc toUTF8*(c: TRune): string {.rtl, extern: "nuc$1".} =
result = newString(1) result = newString(1)
result[0] = chr(i) result[0] = chr(i)
proc `$`*(runes: seq[TRune]): string =
## converts a sequence of runes to a string
result = ""
for rune in runes: result.add(rune.toUTF8)
const const
alphaRanges = [ alphaRanges = [
0x00d8, 0x00f6, # - 0x00d8, 0x00f6, # -
@ -1208,3 +1213,10 @@ proc cmpRunesIgnoreCase*(a, b: string): int {.rtl, extern: "nuc$1", procvar.} =
result = irune(toLower(ar)) - irune(toLower(br)) result = irune(toLower(ar)) - irune(toLower(br))
if result != 0: return if result != 0: return
result = a.len - b.len result = a.len - b.len
when isMainModule:
let
someString = "öÑ"
someRunes = @[runeAt(someString, 0), runeAt(someString, 2)]
compared = (someString == $someRunes)
assert compared == true

View file

@ -370,9 +370,34 @@ proc newSeq*[T](s: var seq[T], len: int) {.magic: "NewSeq", noSideEffect.}
## creates a new sequence of type ``seq[T]`` with length ``len``. ## creates a new sequence of type ``seq[T]`` with length ``len``.
## This is equivalent to ``s = @[]; setlen(s, len)``, but more ## This is equivalent to ``s = @[]; setlen(s, len)``, but more
## efficient since no reallocation is needed. ## efficient since no reallocation is needed.
##
## Note that the sequence will be filled with uninitialized entries, which
## can be a problem for sequences containing strings. After the creation of
## the sequence you should assign entries to the sequence instead of adding
## them. Example:
##
## .. code-block:: nimrod
## var inputStrings : seq[string]
## newSeq(inputStrings, 3)
## inputStrings[0] = "The fourth"
## inputStrings[1] = "assignment"
## inputStrings[2] = "would crash"
## #inputStrings[3] = "out of bounds"
proc newSeq*[T](len = 0): seq[T] = proc newSeq*[T](len = 0): seq[T] =
## creates a new sequence of type ``seq[T]`` with length ``len``. ## creates a new sequence of type ``seq[T]`` with length ``len``.
##
## Note that the sequence will be filled with uninitialized entries, which
## can be a problem for sequences containing strings. After the creation of
## the sequence you should assign entries to the sequence instead of adding
## them. Example:
##
## .. code-block:: nimrod
## var inputStrings = newSeq[string](3)
## inputStrings[0] = "The fourth"
## inputStrings[1] = "assignment"
## inputStrings[2] = "would crash"
## #inputStrings[3] = "out of bounds"
newSeq(result, len) newSeq(result, len)
proc len*[TOpenArray: openArray|varargs](x: TOpenArray): int {. proc len*[TOpenArray: openArray|varargs](x: TOpenArray): int {.
@ -1650,10 +1675,6 @@ const nimrodStackTrace = compileOption("stacktrace")
# of the code # of the code
var var
dbgLineHook*: proc () {.nimcall.}
## set this variable to provide a procedure that should be called before
## each executed instruction. This should only be used by debuggers!
## Only code compiled with the ``debugger:on`` switch calls this hook.
globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall.} globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall.}
## with this hook you can influence exception handling on a global level. ## with this hook you can influence exception handling on a global level.
## If not nil, every 'raise' statement ends up calling this hook. Ordinary ## If not nil, every 'raise' statement ends up calling this hook. Ordinary
@ -1691,13 +1712,14 @@ var
## continues and the program is terminated. ## continues and the program is terminated.
type type
PFrame = ptr TFrame PFrame* = ptr TFrame ## represents a runtime frame of the call stack;
TFrame {.importc, nodecl, final.} = object ## part of the debugger API.
prev: PFrame TFrame* {.importc, nodecl, final.} = object ## the frame itself
procname: CString prev*: PFrame ## previous frame; used for chaining the call stack
line: int # current line number procname*: cstring ## name of the proc that is currently executing
filename: CString line*: int ## line number of the proc that is currently executing
len: int # length of slots (when not debugging always zero) filename*: cstring ## filename of the proc that is currently executing
len*: int ## length of the inspectable slots
when not defined(JS): when not defined(JS):
{.push stack_trace:off, profiler:off.} {.push stack_trace:off, profiler:off.}
@ -2374,8 +2396,33 @@ proc astToStr*[T](x: T): string {.magic: "AstToStr", noSideEffect.}
proc InstantiationInfo*(index = -1): tuple[filename: string, line: int] {. proc InstantiationInfo*(index = -1): tuple[filename: string, line: int] {.
magic: "InstantiationInfo", noSideEffect.} magic: "InstantiationInfo", noSideEffect.}
## provides access to the compiler's instantiation stack line information. ## provides access to the compiler's instantiation stack line information.
## This is only useful for advanced meta programming. See the implementation ##
## of `assert` for an example. ## This proc is mostly useful for meta programming (eg. ``assert`` template)
## to retrieve information about the current filename and line number.
## Example:
##
## .. code-block:: nimrod
## import strutils
##
## template testException(exception, code: expr): stmt =
## try:
## let pos = instantiationInfo()
## discard(code)
## echo "Test failure at $1:$2 with '$3'" % [pos.filename,
## $pos.line, astToStr(code)]
## assert false, "A test expecting failure succeeded?"
## except exception:
## nil
##
## proc tester(pos: int): int =
## let
## a = @[1, 2, 3]
## result = a[pos]
##
## when isMainModule:
## testException(EInvalidIndex, tester(30))
## testException(EInvalidIndex, tester(1))
## # --> Test failure at example.nim:20 with 'tester(1)'
proc raiseAssert*(msg: string) {.noinline.} = proc raiseAssert*(msg: string) {.noinline.} =
raise newException(EAssertionFailed, msg) raise newException(EAssertionFailed, msg)

View file

@ -7,41 +7,16 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
# This file implements the embedded debugger that can be linked ## This file implements basic features for any debugger.
# with the application. Mostly we do not use dynamic memory here as that
# would interfere with the GC and trigger ON/OFF errors if the
# user program corrupts memory. Unfortunately, for dispaying
# variables we use the ``system.repr()`` proc which uses Nimrod
# strings and thus allocates memory from the heap. Pity, but
# I do not want to implement ``repr()`` twice.
type type
TStaticStr {.pure, final.} = object TVarSlot* {.compilerproc, final.} = object ## a slot in a frame
len: int address*: pointer ## the variable's address
data: array[0..100, char] typ*: PNimType ## the variable's type
name*: cstring ## the variable's name; for globals this is "module.name"
TDbgState = enum
dbOff, # debugger is turned off
dbStepInto, # debugger is in tracing mode
dbStepOver,
dbSkipCurrent,
dbQuiting, # debugger wants to quit
dbBreakpoints # debugger is only interested in breakpoints
TDbgBreakpoint {.final.} = object
low, high: int # range from low to high; if disabled
# both low and high are set to their negative values
# this makes the check faster and safes memory
filename: cstring
name: TStaticStr # name of breakpoint
TVarSlot {.compilerproc, final.} = object # variable slots used for debugger:
address: pointer
typ: PNimType
name: cstring # for globals this is "module.name"
PExtendedFrame = ptr TExtendedFrame PExtendedFrame = ptr TExtendedFrame
TExtendedFrame {.final.} = object # If the debugger is enabled the compiler TExtendedFrame = object # If the debugger is enabled the compiler
# provides an extended frame. Of course # provides an extended frame. Of course
# only slots that are # only slots that are
# needed are allocated and not 10_000, # needed are allocated and not 10_000,
@ -50,144 +25,77 @@ type
slots: array[0..10_000, TVarSlot] slots: array[0..10_000, TVarSlot]
var var
dbgUser: TStaticStr # buffer for user input; first command is ``step_into``
# needs to be global cause we store the last command
# in it
dbgState: TDbgState # state of debugger
dbgBP: array[0..127, TDbgBreakpoint] # breakpoints
dbgBPlen: int
dbgBPbloom: int64 # we use a bloom filter to speed up breakpoint checking
dbgSkipToFrame: PFrame # frame to be skipped to
dbgGlobalData: TExtendedFrame # this reserves much space, but dbgGlobalData: TExtendedFrame # this reserves much space, but
# for now it is the most practical way # for now it is the most practical way
maxDisplayRecDepth: int = 5 # do not display too much data! proc dbgRegisterGlobal(name: cstring, address: pointer,
typ: PNimType) {.compilerproc.} =
let i = dbgGlobalData.f.len
if i >= high(dbgGlobalData.slots):
#debugOut("[Warning] cannot register global ")
return
dbgGlobalData.slots[i].name = name
dbgGlobalData.slots[i].typ = typ
dbgGlobalData.slots[i].address = address
inc(dbgGlobalData.f.len)
proc setLen(s: var TStaticStr, newLen=0) = proc getLocal*(frame: PFrame; slot: int): TVarSlot {.inline.} =
s.len = newLen ## retrieves the meta data for the local variable at `slot`. CAUTION: An
s.data[newLen] = '\0' ## invalid `slot` value causes a corruption!
result = cast[PExtendedFrame](frame).slots[slot]
proc add(s: var TStaticStr, c: char) = proc getGlobalLen*(): int {.inline.} =
if s.len < high(s.data)-1: ## gets the number of registered globals.
s.data[s.len] = c result = dbgGlobalData.f.len
s.data[s.len+1] = '\0'
inc s.len
proc add(s: var TStaticStr, c: cstring) = proc getGlobal*(slot: int): TVarSlot {.inline.} =
var i = 0 ## retrieves the meta data for the global variable at `slot`. CAUTION: An
while c[i] != '\0': ## invalid `slot` value causes a corruption!
add s, c[i] result = dbgGlobalData.slots[slot]
inc i
proc assign(s: var TStaticStr, c: cstring) = # ------------------- breakpoint support ------------------------------------
setLen(s)
add s, c
proc `==`(a, b: TStaticStr): bool = type
if a.len == b.len: TBreakpoint* = object ## represents a break point
for i in 0 .. a.len-1: low*, high*: int ## range from low to high; if disabled
if a.data[i] != b.data[i]: return false ## both low and high are set to their negative values
return true filename*: cstring ## the filename of the breakpoint
proc `==`(a: TStaticStr, b: cstring): bool = var
result = c_strcmp(a.data, b) == 0 dbgBP: array[0..127, TBreakpoint] # breakpoints
dbgBPlen: int
dbgBPbloom: int64 # we use a bloom filter to speed up breakpoint checking
proc findBreakpoint(name: TStaticStr): int = dbgFilenames*: array[0..300, cstring] ## registered filenames;
# returns -1 if not found ## 'nil' terminated
for i in countdown(dbgBPlen-1, 0): dbgFilenameLen: int
if name == dbgBP[i].name: return i
return -1
proc write(f: TFile, s: TStaticStr) = proc dbgRegisterFilename(filename: cstring) {.compilerproc.} =
write(f, cstring(s.data)) # XXX we could check for duplicates here for DLL support
dbgFilenames[dbgFilenameLen] = filename
inc dbgFilenameLen
proc ListBreakPoints() = proc dbgRegisterBreakpoint(line: int,
write(stdout, "*** endb| Breakpoints:\n") filename, name: cstring) {.compilerproc.} =
for i in 0 .. dbgBPlen-1: let x = dbgBPlen
write(stdout, dbgBP[i].name) if x >= high(dbgBP):
write(stdout, ": ") #debugOut("[Warning] cannot register breakpoint")
write(stdout, abs(dbgBP[i].low)) return
write(stdout, "..") inc(dbgBPlen)
write(stdout, abs(dbgBP[i].high)) dbgBP[x].filename = filename
write(stdout, dbgBP[i].filename) dbgBP[x].low = line
if dbgBP[i].low < 0: dbgBP[x].high = line
write(stdout, " [disabled]\n") dbgBPbloom = dbgBPbloom or line
else:
write(stdout, "\n")
write(stdout, "***\n")
proc openAppend(filename: cstring): TFile = proc addBreakpoint*(filename: cstring, lo, hi: int): bool =
var p: pointer = fopen(filename, "ab") let x = dbgBPlen
if p != nil: if x >= high(dbgBP): return false
result = cast[TFile](p) inc(dbgBPlen)
write(result, "----------------------------------------\n") result = true
dbgBP[x].filename = filename
proc dbgRepr(p: pointer, typ: PNimType): string = dbgBP[x].low = lo
var cl: TReprClosure dbgBP[x].high = hi
initReprClosure(cl) for line in lo..hi: dbgBPbloom = dbgBPbloom or line
cl.recDepth = maxDisplayRecDepth
# locks for the GC turned out to be a bad idea...
# inc(recGcLock)
result = ""
reprAux(result, p, typ, cl)
# dec(recGcLock)
deinitReprClosure(cl)
proc writeVariable(stream: TFile, slot: TVarSlot) =
write(stream, slot.name)
write(stream, " = ")
writeln(stream, dbgRepr(slot.address, slot.typ))
proc ListFrame(stream: TFile, f: PExtendedFrame) =
write(stream, "*** endb| Frame (")
write(stream, f.f.len)
write(stream, " slots):\n")
for i in 0 .. f.f.len-1:
writeVariable(stream, f.slots[i])
write(stream, "***\n")
proc ListVariables(stream: TFile, f: PExtendedFrame) =
write(stream, "*** endb| Frame (")
write(stream, f.f.len)
write(stream, " slots):\n")
for i in 0 .. f.f.len-1:
writeln(stream, f.slots[i].name)
write(stream, "***\n")
proc debugOut(msg: cstring) =
# the *** *** markers are for easy recognition of debugger
# output for external frontends.
write(stdout, "*** endb| ")
write(stdout, msg)
write(stdout, "***\n")
proc dbgFatal(msg: cstring) =
debugOut(msg)
dbgAborting = True # the debugger wants to abort
quit(1)
proc findVariable(frame: PExtendedFrame, varname: cstring): int =
for i in 0 .. frame.f.len - 1:
if c_strcmp(frame.slots[i].name, varname) == 0: return i
return -1
proc dbgShowCurrentProc(dbgFramePointer: PFrame) =
if dbgFramePointer != nil:
write(stdout, "*** endb| now in proc: ")
write(stdout, dbgFramePointer.procname)
write(stdout, " ***\n")
else:
write(stdout, "*** endb| (proc name not available) ***\n")
proc dbgShowExecutionPoint() =
write(stdout, "*** endb| ")
write(stdout, framePtr.filename)
write(stdout, "(")
write(stdout, framePtr.line)
write(stdout, ") ")
write(stdout, framePtr.procname)
write(stdout, " ***\n")
const const
FileSystemCaseInsensitive = defined(windows) or defined(dos) or defined(os2) FileSystemCaseInsensitive = defined(windows) or defined(dos) or defined(os2)
@ -216,337 +124,29 @@ proc fileMatches(c, bp: cstring): bool =
inc(i) inc(i)
return true return true
proc dbgBreakpointReached(line: int): int = proc canonFilename*(filename: cstring): cstring =
for i in 0..dbgBPlen-1: ## returns 'nil' if the filename cannot be found.
if line >= dbgBP[i].low and line <= dbgBP[i].high and for i in 0 .. <dbgFilenameLen:
fileMatches(framePtr.filename, dbgBP[i].filename): return i result = dbgFilenames[i]
return -1 if fileMatches(result, filename): return result
result = nil
proc scanAndAppendWord(src: cstring, a: var TStaticStr, start: int): int = iterator listBreakpoints*(): ptr TBreakpoint =
result = start ## lists all breakpoints.
# skip whitespace: for i in 0..dbgBPlen-1: yield addr(dbgBP[i])
while src[result] in {'\t', ' '}: inc(result)
while True:
case src[result]
of 'a'..'z', '0'..'9': add(a, src[result])
of '_': nil # just skip it
of 'A'..'Z': add(a, chr(ord(src[result]) - ord('A') + ord('a')))
else: break
inc(result)
proc scanWord(src: cstring, a: var TStaticStr, start: int): int = proc isActive*(b: ptr TBreakpoint): bool = b.low > 0
setlen(a) proc flip*(b: ptr TBreakpoint) =
result = scanAndAppendWord(src, a, start) ## enables or disables 'b' depending on its current state.
b.low = -b.low; b.high = -b.high
proc scanFilename(src: cstring, a: var TStaticStr, start: int): int = proc checkBreakpoints*(filename: cstring, line: int): ptr TBreakpoint =
result = start ## in which breakpoint (if any) we are.
setLen a if (dbgBPbloom and line) != line: return nil
# skip whitespace: for b in listBreakpoints():
while src[result] in {'\t', ' '}: inc(result) if line >= b.low and line <= b.high and filename == b.filename: return b
while src[result] notin {'\t', ' ', '\0'}:
add(a, src[result])
inc(result)
proc scanNumber(src: cstring, a: var int, start: int): int = # ------------------- watchpoint support ------------------------------------
result = start
a = 0
while src[result] in {'\t', ' '}: inc(result)
while true:
case src[result]
of '0'..'9': a = a * 10 + ord(src[result]) - ord('0')
of '_': nil # skip underscores (nice for long line numbers)
else: break
inc(result)
proc dbgHelp() =
debugOut("""
list of commands (see the manual for further help):
GENERAL
h, help display this help message
q, quit quit the debugger and the program
<ENTER> repeat the previous debugger command
EXECUTING
s, step single step, stepping into routine calls
n, next single step, without stepping into routine calls
f, skipcurrent continue execution until the current routine finishes
c, continue, r, run continue execution until the next breakpoint
i, ignore continue execution, ignore all breakpoints
BREAKPOINTS
b, break <name> [fromline [toline]] [file]
set a new breakpoint named 'name' for line and file
if line or file are omitted the current one is used
breakpoints display the entire breakpoint list
disable <name> disable a breakpoint
enable <name> enable a breakpoint
DATA DISPLAY
e, eval <expr> evaluate the expression <expr>
o, out <file> <expr> evaluate <expr> and write it to <file>
w, where display the current execution point
stackframe [file] display current stack frame [and write it to file]
u, up go up in the call stack
d, down go down in the call stack
bt, backtrace display the entire call stack
l, locals display available local variables
g, globals display available global variables
maxdisplay <integer> set the display's recursion maximum
""")
proc InvalidCommand() =
debugOut("[Warning] invalid command ignored (type 'h' for help) ")
proc hasExt(s: cstring): bool =
# returns true if s has a filename extension
var i = 0
while s[i] != '\0':
if s[i] == '.': return true
inc i
proc setBreakPoint(s: cstring, start: int) =
var dbgTemp: TStaticStr
var i = scanWord(s, dbgTemp, start)
if i <= start:
InvalidCommand()
return
if dbgBPlen >= high(dbgBP):
debugOut("[Warning] no breakpoint could be set; out of breakpoint space ")
return
var x = dbgBPlen
inc(dbgBPlen)
dbgBP[x].name = dbgTemp
i = scanNumber(s, dbgBP[x].low, i)
if dbgBP[x].low == 0:
# set to current line:
dbgBP[x].low = framePtr.line
i = scanNumber(s, dbgBP[x].high, i)
if dbgBP[x].high == 0: # set to low:
dbgBP[x].high = dbgBP[x].low
for line in dbgBP[x].low .. dbgBP[x].high: dbgBPbloom = dbgBPbloom or line
i = scanFilename(s, dbgTemp, i)
if dbgTemp.len != 0:
debugOut("[Warning] explicit filename for breakpoint not supported")
when false:
if not hasExt(dbgTemp.data): add(dbgTemp, ".nim")
dbgBP[x].filename = dbgTemp
dbgBP[x].filename = framePtr.filename
else: # use current filename
dbgBP[x].filename = framePtr.filename
# skip whitespace:
while s[i] in {' ', '\t'}: inc(i)
if s[i] != '\0':
dec(dbgBPLen) # remove buggy breakpoint
InvalidCommand()
proc BreakpointSetEnabled(s: cstring, start, enabled: int) =
var dbgTemp: TStaticStr
var i = scanWord(s, dbgTemp, start)
if i <= start:
InvalidCommand()
return
var x = findBreakpoint(dbgTemp)
if x < 0: debugOut("[Warning] breakpoint does not exist ")
elif enabled * dbgBP[x].low < 0: # signs are different?
dbgBP[x].low = -dbgBP[x].low
dbgBP[x].high = -dbgBP[x].high
proc dbgEvaluate(stream: TFile, s: cstring, start: int,
currFrame: PExtendedFrame) =
var dbgTemp: tstaticstr
var i = scanWord(s, dbgTemp, start)
while s[i] in {' ', '\t'}: inc(i)
var f = currFrame
if s[i] == '.':
inc(i) # skip '.'
add(dbgTemp, '.')
i = scanAndAppendWord(s, dbgTemp, i)
# search for global var:
f = addr(dbgGlobalData)
if s[i] != '\0':
debugOut("[Warning] could not parse expr ")
return
var j = findVariable(f, dbgTemp.data)
if j < 0:
debugOut("[Warning] could not find variable ")
return
writeVariable(stream, f.slots[j])
proc dbgOut(s: cstring, start: int, currFrame: PExtendedFrame) =
var dbgTemp: tstaticstr
var i = scanFilename(s, dbgTemp, start)
if dbgTemp.len == 0:
InvalidCommand()
return
var stream = openAppend(dbgTemp.data)
if stream == nil:
debugOut("[Warning] could not open or create file ")
return
dbgEvaluate(stream, s, i, currFrame)
close(stream)
proc dbgStackFrame(s: cstring, start: int, currFrame: PExtendedFrame) =
var dbgTemp: TStaticStr
var i = scanFilename(s, dbgTemp, start)
if dbgTemp.len == 0:
# just write it to stdout:
ListFrame(stdout, currFrame)
else:
var stream = openAppend(dbgTemp.data)
if stream == nil:
debugOut("[Warning] could not open or create file ")
return
ListFrame(stream, currFrame)
close(stream)
proc readLine(f: TFile, line: var TStaticStr): bool =
while True:
var c = fgetc(f)
if c < 0'i32:
if line.len > 0: break
else: return false
if c == 10'i32: break # LF
if c == 13'i32: # CR
c = fgetc(f) # is the next char LF?
if c != 10'i32: ungetc(c, f) # no, put the character back
break
add line, chr(int(c))
result = true
proc dbgWriteStackTrace(f: PFrame)
proc CommandPrompt() =
# if we return from this routine, user code executes again
var
again = True
dbgFramePtr = framePtr # for going down and up the stack
dbgDown = 0 # how often we did go down
dbgTemp: TStaticStr
while again:
write(stdout, "*** endb| >>")
let oldLen = dbgUser.len
dbgUser.len = 0
if not readLine(stdin, dbgUser): break
if dbgUser.len == 0: dbgUser.len = oldLen
# now look what we have to do:
var i = scanWord(dbgUser.data, dbgTemp, 0)
template `?`(x: expr): expr = dbgTemp == cstring(x)
if ?"s" or ?"step":
dbgState = dbStepInto
again = false
elif ?"n" or ?"next":
dbgState = dbStepOver
dbgSkipToFrame = framePtr
again = false
elif ?"f" or ?"skipcurrent":
dbgState = dbSkipCurrent
dbgSkipToFrame = framePtr.prev
again = false
elif ?"c" or ?"continue" or ?"r" or ?"run":
dbgState = dbBreakpoints
again = false
elif ?"i" or ?"ignore":
dbgState = dbOff
again = false
elif ?"h" or ?"help":
dbgHelp()
elif ?"q" or ?"quit":
dbgState = dbQuiting
dbgAborting = True
again = false
quit(1) # BUGFIX: quit with error code > 0
elif ?"e" or ?"eval":
dbgEvaluate(stdout, dbgUser.data, i, cast[PExtendedFrame](dbgFramePtr))
elif ?"o" or ?"out":
dbgOut(dbgUser.data, i, cast[PExtendedFrame](dbgFramePtr))
elif ?"stackframe":
dbgStackFrame(dbgUser.data, i, cast[PExtendedFrame](dbgFramePtr))
elif ?"w" or ?"where":
dbgShowExecutionPoint()
elif ?"l" or ?"locals":
ListVariables(stdout, cast[PExtendedFrame](dbgFramePtr))
elif ?"g" or ?"globals":
ListVariables(stdout, addr(dbgGlobalData))
elif ?"u" or ?"up":
if dbgDown <= 0:
debugOut("[Warning] cannot go up any further ")
else:
dbgFramePtr = framePtr
for j in 0 .. dbgDown-2: # BUGFIX
dbgFramePtr = dbgFramePtr.prev
dec(dbgDown)
dbgShowCurrentProc(dbgFramePtr)
elif ?"d" or ?"down":
if dbgFramePtr != nil:
inc(dbgDown)
dbgFramePtr = dbgFramePtr.prev
dbgShowCurrentProc(dbgFramePtr)
else:
debugOut("[Warning] cannot go down any further ")
elif ?"bt" or ?"backtrace":
dbgWriteStackTrace(framePtr)
elif ?"b" or ?"break":
setBreakPoint(dbgUser.data, i)
elif ?"breakpoints":
ListBreakPoints()
elif ?"disable":
BreakpointSetEnabled(dbgUser.data, i, -1)
elif ?"enable":
BreakpointSetEnabled(dbgUser.data, i, +1)
elif ?"maxdisplay":
var parsed: int
i = scanNumber(dbgUser.data, parsed, i)
if dbgUser.data[i-1] in {'0'..'9'}:
if parsed == 0: maxDisplayRecDepth = -1
else: maxDisplayRecDepth = parsed
else:
InvalidCommand()
else: InvalidCommand()
proc endbStep() =
# we get into here if an unhandled exception has been raised
# XXX: do not allow the user to run the program any further?
# XXX: BUG: the frame is lost here!
dbgShowExecutionPoint()
CommandPrompt()
proc checkForBreakpoint(line: int) =
if (dbgBPbloom and line) != line: return
let i = dbgBreakpointReached(line)
if i >= 0:
write(stdout, "*** endb| reached ")
write(stdout, dbgBP[i].name)
write(stdout, " in ")
write(stdout, framePtr.filename)
write(stdout, "(")
write(stdout, framePtr.line)
write(stdout, ") ")
write(stdout, framePtr.procname)
write(stdout, " ***\n")
CommandPrompt()
# interface to the user program:
proc dbgRegisterBreakpoint(line: int,
filename, name: cstring) {.compilerproc.} =
let x = dbgBPlen
if x >= high(dbgBP):
debugOut("[Warning] cannot register breakpoint")
return
inc(dbgBPlen)
dbgBP[x].name.assign(name)
dbgBP[x].filename = filename
dbgBP[x].low = line
dbgBP[x].high = line
dbgBPbloom = dbgBPbloom or line
proc dbgRegisterGlobal(name: cstring, address: pointer,
typ: PNimType) {.compilerproc.} =
let i = dbgGlobalData.f.len
if i >= high(dbgGlobalData.slots):
debugOut("[Warning] cannot register global ")
return
dbgGlobalData.slots[i].name = name
dbgGlobalData.slots[i].typ = typ
dbgGlobalData.slots[i].address = address
inc(dbgGlobalData.f.len)
type type
THash = int THash = int
@ -665,7 +265,7 @@ proc dbgRegisterWatchpoint(address: pointer, name: cstring,
Watchpoints[i].address = address Watchpoints[i].address = address
return return
if L >= watchPoints.high: if L >= watchPoints.high:
debugOut("[Warning] cannot register watchpoint") #debugOut("[Warning] cannot register watchpoint")
return return
Watchpoints[L].name = name Watchpoints[L].name = name
Watchpoints[L].address = address Watchpoints[L].address = address
@ -676,99 +276,28 @@ proc dbgRegisterWatchpoint(address: pointer, name: cstring,
proc dbgUnregisterWatchpoints*() = proc dbgUnregisterWatchpoints*() =
WatchpointsLen = 0 WatchpointsLen = 0
proc dbgWriteStackTrace(f: PFrame) =
const
firstCalls = 32
var var
it = f dbgLineHook*: proc () {.nimcall.}
i = 0 ## set this variable to provide a procedure that should be called before
total = 0 ## each executed instruction. This should only be used by debuggers!
tempFrames: array [0..127, PFrame] ## Only code compiled with the ``debugger:on`` switch calls this hook.
# setup long head:
while it != nil and i <= high(tempFrames)-firstCalls:
tempFrames[i] = it
inc(i)
inc(total)
it = it.prev
# go up the stack to count 'total':
var b = it
while it != nil:
inc(total)
it = it.prev
var skipped = 0
if total > len(tempFrames):
# skip N
skipped = total-i-firstCalls+1
for j in 1..skipped:
if b != nil: b = b.prev
# create '...' entry:
tempFrames[i] = nil
inc(i)
# setup short tail:
while b != nil and i <= high(tempFrames):
tempFrames[i] = b
inc(i)
b = b.prev
for j in countdown(i-1, 0):
if tempFrames[j] == nil:
write(stdout, "(")
write(stdout, skipped)
write(stdout, " calls omitted) ...")
else:
write(stdout, tempFrames[j].filename)
if tempFrames[j].line > 0:
write(stdout, '(')
write(stdout, tempFrames[j].line)
write(stdout, ')')
write(stdout, ' ')
write(stdout, tempFrames[j].procname)
write(stdout, "\n")
proc strstr(s1, s2: cstring): cstring {.importc, header: "<string.h>".} dbgWatchpointHook*: proc (watchpointName: cstring) {.nimcall.}
proc interestingFilename(filename: cstring): bool =
#result = strstr(filename, "/rst.nim") == nil
result = true
proc checkWatchpoints = proc checkWatchpoints =
let L = WatchpointsLen let L = WatchpointsLen
for i in 0.. <L: for i in 0.. <L:
let newHash = genericHash(Watchpoints[i].address, Watchpoints[i].typ) let newHash = genericHash(Watchpoints[i].address, Watchpoints[i].typ)
if newHash != Watchpoints[i].oldValue: if newHash != Watchpoints[i].oldValue:
if interestingFilename(framePtr.filename): dbgWatchpointHook(Watchpoints[i].name)
dbgWriteStackTrace(framePtr)
debugOut(Watchpoints[i].name)
Watchpoints[i].oldValue = newHash Watchpoints[i].oldValue = newHash
proc endb(line: int, file: cstring) {.compilerproc.} = proc endb(line: int, file: cstring) {.compilerproc, noinline.} =
# This proc is called before every Nimrod code line! # This proc is called before every Nimrod code line!
# Thus, it must have as few parameters as possible to keep the
# code size small!
# Check if we are at an enabled breakpoint or "in the mood"
if framePtr == nil: return if framePtr == nil: return
let oldState = dbgState if dbgWatchpointHook != nil: checkWatchpoints()
checkWatchpoints()
framePtr.line = line # this is done here for smaller code size! framePtr.line = line # this is done here for smaller code size!
framePtr.filename = file framePtr.filename = file
if dbgLineHook != nil: dbgLineHook() if dbgLineHook != nil: dbgLineHook()
case oldState
of dbStepInto:
# we really want the command prompt here:
dbgShowExecutionPoint()
CommandPrompt()
of dbSkipCurrent, dbStepOver: # skip current routine
if framePtr == dbgSkipToFrame:
dbgShowExecutionPoint()
CommandPrompt()
else: # breakpoints are wanted though (I guess)
checkForBreakpoint(line)
of dbBreakpoints:
# debugger is only interested in breakpoints
checkForBreakpoint(line)
else: nil
proc initDebugger {.inline.} =
dbgState = dbStepInto
dbgUser.len = 1
dbgUser.data[0] = 's'
include "system/endb"

538
lib/system/endb.nim Normal file
View file

@ -0,0 +1,538 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This file implements the embedded debugger that can be linked
# with the application. Mostly we do not use dynamic memory here as that
# would interfere with the GC and trigger ON/OFF errors if the
# user program corrupts memory. Unfortunately, for dispaying
# variables we use the ``system.repr()`` proc which uses Nimrod
# strings and thus allocates memory from the heap. Pity, but
# I do not want to implement ``repr()`` twice.
const
EndbBeg = "*** endb"
EndbEnd = "***\n"
type
TStaticStr = object
len: int
data: array[0..100, char]
TBreakpointFilename = object
b: ptr TBreakpoint
filename: TStaticStr
TDbgState = enum
dbOff, # debugger is turned off
dbStepInto, # debugger is in tracing mode
dbStepOver,
dbSkipCurrent,
dbQuiting, # debugger wants to quit
dbBreakpoints # debugger is only interested in breakpoints
var
dbgUser: TStaticStr # buffer for user input; first command is ``step_into``
# needs to be global cause we store the last command
# in it
dbgState: TDbgState # state of debugger
dbgSkipToFrame: PFrame # frame to be skipped to
maxDisplayRecDepth: int = 5 # do not display too much data!
brkPoints: array[0..127, TBreakpointFilename]
proc setLen(s: var TStaticStr, newLen=0) =
s.len = newLen
s.data[newLen] = '\0'
proc add(s: var TStaticStr, c: char) =
if s.len < high(s.data)-1:
s.data[s.len] = c
s.data[s.len+1] = '\0'
inc s.len
proc add(s: var TStaticStr, c: cstring) =
var i = 0
while c[i] != '\0':
add s, c[i]
inc i
proc assign(s: var TStaticStr, c: cstring) =
setLen(s)
add s, c
proc `==`(a, b: TStaticStr): bool =
if a.len == b.len:
for i in 0 .. a.len-1:
if a.data[i] != b.data[i]: return false
return true
proc `==`(a: TStaticStr, b: cstring): bool =
result = c_strcmp(a.data, b) == 0
proc write(f: TFile, s: TStaticStr) =
write(f, cstring(s.data))
proc ListBreakPoints() =
write(stdout, EndbBeg)
write(stdout, "| Breakpoints:\n")
for b in listBreakpoints():
write(stdout, abs(b.low))
if b.high != b.low:
write(stdout, "..")
write(stdout, abs(b.high))
write(stdout, " ")
write(stdout, b.filename)
if b.isActive:
write(stdout, " [disabled]\n")
else:
write(stdout, "\n")
write(stdout, EndbEnd)
proc openAppend(filename: cstring): TFile =
var p: pointer = fopen(filename, "ab")
if p != nil:
result = cast[TFile](p)
write(result, "----------------------------------------\n")
proc dbgRepr(p: pointer, typ: PNimType): string =
var cl: TReprClosure
initReprClosure(cl)
cl.recDepth = maxDisplayRecDepth
# locks for the GC turned out to be a bad idea...
# inc(recGcLock)
result = ""
reprAux(result, p, typ, cl)
# dec(recGcLock)
deinitReprClosure(cl)
proc writeVariable(stream: TFile, slot: TVarSlot) =
write(stream, slot.name)
write(stream, " = ")
writeln(stream, dbgRepr(slot.address, slot.typ))
proc ListFrame(stream: TFile, f: PFrame) =
write(stream, EndbBeg)
write(stream, "| Frame (")
write(stream, f.len)
write(stream, " slots):\n")
for i in 0 .. f.len-1:
writeln(stream, getLocal(f, i).name)
write(stream, EndbEnd)
proc ListLocals(stream: TFile, f: PFrame) =
write(stream, EndbBeg)
write(stream, "| Frame (")
write(stream, f.len)
write(stream, " slots):\n")
for i in 0 .. f.len-1:
writeVariable(stream, getLocal(f, i))
write(stream, EndbEnd)
proc ListGlobals(stream: TFile) =
write(stream, EndbBeg)
write(stream, "| Globals:\n")
for i in 0 .. getGlobalLen()-1:
writeln(stream, getGlobal(i).name)
write(stream, EndbEnd)
proc debugOut(msg: cstring) =
# the *** *** markers are for easy recognition of debugger
# output for external frontends.
write(stdout, EndbBeg)
write(stdout, "| ")
write(stdout, msg)
write(stdout, EndbEnd)
proc dbgFatal(msg: cstring) =
debugOut(msg)
dbgAborting = True # the debugger wants to abort
quit(1)
proc dbgShowCurrentProc(dbgFramePointer: PFrame) =
if dbgFramePointer != nil:
write(stdout, "*** endb| now in proc: ")
write(stdout, dbgFramePointer.procname)
write(stdout, " ***\n")
else:
write(stdout, "*** endb| (proc name not available) ***\n")
proc dbgShowExecutionPoint() =
write(stdout, "*** endb| ")
write(stdout, framePtr.filename)
write(stdout, "(")
write(stdout, framePtr.line)
write(stdout, ") ")
write(stdout, framePtr.procname)
write(stdout, " ***\n")
proc scanAndAppendWord(src: cstring, a: var TStaticStr, start: int): int =
result = start
# skip whitespace:
while src[result] in {'\t', ' '}: inc(result)
while True:
case src[result]
of 'a'..'z', '0'..'9': add(a, src[result])
of '_': nil # just skip it
of 'A'..'Z': add(a, chr(ord(src[result]) - ord('A') + ord('a')))
else: break
inc(result)
proc scanWord(src: cstring, a: var TStaticStr, start: int): int =
setlen(a)
result = scanAndAppendWord(src, a, start)
proc scanFilename(src: cstring, a: var TStaticStr, start: int): int =
result = start
setLen a
while src[result] in {'\t', ' '}: inc(result)
while src[result] notin {'\t', ' ', '\0'}:
add(a, src[result])
inc(result)
proc scanNumber(src: cstring, a: var int, start: int): int =
result = start
a = 0
while src[result] in {'\t', ' '}: inc(result)
while true:
case src[result]
of '0'..'9': a = a * 10 + ord(src[result]) - ord('0')
of '_': nil # skip underscores (nice for long line numbers)
else: break
inc(result)
proc dbgHelp() =
debugOut("""
list of commands (see the manual for further help):
GENERAL
h, help display this help message
q, quit quit the debugger and the program
<ENTER> repeat the previous debugger command
EXECUTING
s, step single step, stepping into routine calls
n, next single step, without stepping into routine calls
f, skipcurrent continue execution until the current routine finishes
c, continue, r, run continue execution until the next breakpoint
i, ignore continue execution, ignore all breakpoints
BREAKPOINTS
b, break [fromline [toline]] [file]
set a new breakpoint for line and file
if line or file are omitted the current one is used
breakpoints display the entire breakpoint list
toggle fromline [file] enable or disable a breakpoint
filenames list all valid filenames
DATA DISPLAY
e, eval <expr> evaluate the expression <expr>
o, out <file> <expr> evaluate <expr> and write it to <file>
w, where display the current execution point
stackframe [file] display current stack frame [and write it to file]
u, up go up in the call stack
d, down go down in the call stack
bt, backtrace display the entire call stack
l, locals display available local variables
g, globals display available global variables
maxdisplay <integer> set the display's recursion maximum
""")
proc InvalidCommand() =
debugOut("[Warning] invalid command ignored (type 'h' for help) ")
proc hasExt(s: cstring): bool =
# returns true if s has a filename extension
var i = 0
while s[i] != '\0':
if s[i] == '.': return true
inc i
proc parseBreakpoint(s: cstring, start: int): TBreakpoint =
var dbgTemp: TStaticStr
var i = scanNumber(s, result.low, start)
if result.low == 0: result.low = framePtr.line
i = scanNumber(s, result.high, i)
if result.high == 0: result.high = result.low
i = scanFilename(s, dbgTemp, i)
if dbgTemp.len != 0:
if not hasExt(dbgTemp.data): add(dbgTemp, ".nim")
result.filename = canonFilename(dbgTemp.data.cstring)
if result.filename.isNil:
debugOut("[Warning] no breakpoint could be set; unknown filename ")
return
else:
result.filename = framePtr.filename
proc createBreakPoint(s: cstring, start: int) =
let br = parseBreakpoint(s, start)
if not br.filename.isNil:
if not addBreakpoint(br.filename, br.low, br.high):
debugOut("[Warning] no breakpoint could be set; out of breakpoint space ")
proc BreakpointToggle(s: cstring, start: int) =
var a = parseBreakpoint(s, start)
if not a.filename.isNil:
var b = checkBreakpoints(a.filename, a.low)
if not b.isNil: b.flip
else: debugOut("[Warning] unknown breakpoint ")
proc dbgEvaluate(stream: TFile, s: cstring, start: int, f: PFrame) =
var dbgTemp: tstaticstr
var i = scanWord(s, dbgTemp, start)
while s[i] in {' ', '\t'}: inc(i)
var v: TVarSlot
if s[i] == '.':
inc(i)
add(dbgTemp, '.')
i = scanAndAppendWord(s, dbgTemp, i)
for i in 0 .. getGlobalLen()-1:
let v = getGlobal(i)
if c_strcmp(v.name, dbgTemp.data) == 0:
writeVariable(stream, v)
else:
for i in 0 .. f.len-1:
let v = getLocal(f, i)
if c_strcmp(v.name, dbgTemp.data) == 0:
writeVariable(stream, v)
proc dbgOut(s: cstring, start: int, currFrame: PFrame) =
var dbgTemp: tstaticstr
var i = scanFilename(s, dbgTemp, start)
if dbgTemp.len == 0:
InvalidCommand()
return
var stream = openAppend(dbgTemp.data)
if stream == nil:
debugOut("[Warning] could not open or create file ")
return
dbgEvaluate(stream, s, i, currFrame)
close(stream)
proc dbgStackFrame(s: cstring, start: int, currFrame: PFrame) =
var dbgTemp: TStaticStr
var i = scanFilename(s, dbgTemp, start)
if dbgTemp.len == 0:
# just write it to stdout:
ListFrame(stdout, currFrame)
else:
var stream = openAppend(dbgTemp.data)
if stream == nil:
debugOut("[Warning] could not open or create file ")
return
ListFrame(stream, currFrame)
close(stream)
proc readLine(f: TFile, line: var TStaticStr): bool =
while True:
var c = fgetc(f)
if c < 0'i32:
if line.len > 0: break
else: return false
if c == 10'i32: break # LF
if c == 13'i32: # CR
c = fgetc(f) # is the next char LF?
if c != 10'i32: ungetc(c, f) # no, put the character back
break
add line, chr(int(c))
result = true
proc ListFilenames() =
write(stdout, EndbBeg)
write(stdout, "| Files:\n")
var i = 0
while true:
let x = dbgFilenames[i]
if x.isNil: break
write(stdout, x)
write(stdout, "\n")
inc i
write(stdout, EndbEnd)
proc dbgWriteStackTrace(f: PFrame)
proc CommandPrompt() =
# if we return from this routine, user code executes again
var
again = True
dbgFramePtr = framePtr # for going down and up the stack
dbgDown = 0 # how often we did go down
dbgTemp: TStaticStr
while again:
write(stdout, "*** endb| >>")
let oldLen = dbgUser.len
dbgUser.len = 0
if not readLine(stdin, dbgUser): break
if dbgUser.len == 0: dbgUser.len = oldLen
# now look what we have to do:
var i = scanWord(dbgUser.data, dbgTemp, 0)
template `?`(x: expr): expr = dbgTemp == cstring(x)
if ?"s" or ?"step":
dbgState = dbStepInto
again = false
elif ?"n" or ?"next":
dbgState = dbStepOver
dbgSkipToFrame = framePtr
again = false
elif ?"f" or ?"skipcurrent":
dbgState = dbSkipCurrent
dbgSkipToFrame = framePtr.prev
again = false
elif ?"c" or ?"continue" or ?"r" or ?"run":
dbgState = dbBreakpoints
again = false
elif ?"i" or ?"ignore":
dbgState = dbOff
again = false
elif ?"h" or ?"help":
dbgHelp()
elif ?"q" or ?"quit":
dbgState = dbQuiting
dbgAborting = True
again = false
quit(1) # BUGFIX: quit with error code > 0
elif ?"e" or ?"eval":
dbgEvaluate(stdout, dbgUser.data, i, dbgFramePtr)
elif ?"o" or ?"out":
dbgOut(dbgUser.data, i, dbgFramePtr)
elif ?"stackframe":
dbgStackFrame(dbgUser.data, i, dbgFramePtr)
elif ?"w" or ?"where":
dbgShowExecutionPoint()
elif ?"l" or ?"locals":
ListLocals(stdout, dbgFramePtr)
elif ?"g" or ?"globals":
ListGlobals(stdout)
elif ?"u" or ?"up":
if dbgDown <= 0:
debugOut("[Warning] cannot go up any further ")
else:
dbgFramePtr = framePtr
for j in 0 .. dbgDown-2: # BUGFIX
dbgFramePtr = dbgFramePtr.prev
dec(dbgDown)
dbgShowCurrentProc(dbgFramePtr)
elif ?"d" or ?"down":
if dbgFramePtr != nil:
inc(dbgDown)
dbgFramePtr = dbgFramePtr.prev
dbgShowCurrentProc(dbgFramePtr)
else:
debugOut("[Warning] cannot go down any further ")
elif ?"bt" or ?"backtrace":
dbgWriteStackTrace(framePtr)
elif ?"b" or ?"break":
createBreakPoint(dbgUser.data, i)
elif ?"breakpoints":
ListBreakPoints()
elif ?"toggle":
BreakpointToggle(dbgUser.data, i)
elif ?"filenames":
ListFilenames()
elif ?"maxdisplay":
var parsed: int
i = scanNumber(dbgUser.data, parsed, i)
if dbgUser.data[i-1] in {'0'..'9'}:
if parsed == 0: maxDisplayRecDepth = -1
else: maxDisplayRecDepth = parsed
else:
InvalidCommand()
else: InvalidCommand()
proc endbStep() =
# we get into here if an unhandled exception has been raised
# XXX: do not allow the user to run the program any further?
# XXX: BUG: the frame is lost here!
dbgShowExecutionPoint()
CommandPrompt()
proc dbgWriteStackTrace(f: PFrame) =
const
firstCalls = 32
var
it = f
i = 0
total = 0
tempFrames: array [0..127, PFrame]
# setup long head:
while it != nil and i <= high(tempFrames)-firstCalls:
tempFrames[i] = it
inc(i)
inc(total)
it = it.prev
# go up the stack to count 'total':
var b = it
while it != nil:
inc(total)
it = it.prev
var skipped = 0
if total > len(tempFrames):
# skip N
skipped = total-i-firstCalls+1
for j in 1..skipped:
if b != nil: b = b.prev
# create '...' entry:
tempFrames[i] = nil
inc(i)
# setup short tail:
while b != nil and i <= high(tempFrames):
tempFrames[i] = b
inc(i)
b = b.prev
for j in countdown(i-1, 0):
if tempFrames[j] == nil:
write(stdout, "(")
write(stdout, skipped)
write(stdout, " calls omitted) ...")
else:
write(stdout, tempFrames[j].filename)
if tempFrames[j].line > 0:
write(stdout, '(')
write(stdout, tempFrames[j].line)
write(stdout, ')')
write(stdout, ' ')
write(stdout, tempFrames[j].procname)
write(stdout, "\n")
proc checkForBreakpoint =
let b = checkBreakpoints(framePtr.filename, framePtr.line)
if b != nil:
write(stdout, "*** endb| reached ")
write(stdout, framePtr.filename)
write(stdout, "(")
write(stdout, framePtr.line)
write(stdout, ") ")
write(stdout, framePtr.procname)
write(stdout, " ***\n")
CommandPrompt()
proc lineHookImpl() {.nimcall.} =
case dbgState
of dbStepInto:
# we really want the command prompt here:
dbgShowExecutionPoint()
CommandPrompt()
of dbSkipCurrent, dbStepOver: # skip current routine
if framePtr == dbgSkipToFrame:
dbgShowExecutionPoint()
CommandPrompt()
else:
# breakpoints are wanted though (I guess)
checkForBreakpoint()
of dbBreakpoints:
# debugger is only interested in breakpoints
checkForBreakpoint()
else: nil
proc watchpointHookImpl(name: cstring) {.nimcall.} =
dbgWriteStackTrace(framePtr)
debugOut(name)
proc initDebugger {.inline.} =
dbgState = dbStepInto
dbgUser.len = 1
dbgUser.data[0] = 's'
dbgWatchpointHook = watchpointHookImpl
dbgLineHook = lineHookImpl

View file

@ -491,6 +491,7 @@ proc toU32(a: int): int32 {.noStackFrame, compilerproc.} =
proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b
proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b
type NimString = string # hack for hti.nim
include "system/hti" include "system/hti"
proc isFatPointer(ti: PNimType): bool = proc isFatPointer(ti: PNimType): bool =

21
tests/run/tfieldindex.nim Normal file
View file

@ -0,0 +1,21 @@
discard """
output: "1"
"""
type
TMyTuple = tuple[a, b: int]
proc indexOf*(t: typedesc, name: string): int {.compiletime.} =
## takes a tuple and looks for the field by name.
## returs index of that field.
var
d: t
i = 0
for n, x in fieldPairs(d):
if n == name: return i
i.inc
raise newException(EInvalidValue, "No field " & name & " in type " &
astToStr(t))
echo TMyTuple.indexOf("b")

View file

@ -1,6 +1,8 @@
discard """ discard """
file: "tfinally.nim" file: "tfinally.nim"
output: "came here 3" output: '''came
here
3'''
""" """
# Test return in try statement: # Test return in try statement:
@ -9,10 +11,10 @@ proc main: int =
try: try:
return 1 return 1
finally: finally:
stdout.write("came ") echo("came")
return 2 return 2
finally: finally:
stdout.write("here ") echo("here ")
return 3 return 3
echo main() #OUT came here 3 echo main() #OUT came here 3

View file

@ -1,6 +1,9 @@
discard """ discard """
file: "tfinally2.nim" file: "tfinally2.nim"
output: "ABCD" output: '''A
B
C
D'''
""" """
# Test break in try statement: # Test break in try statement:
@ -11,15 +14,15 @@ proc main: int =
try: try:
break AB break AB
finally: finally:
stdout.write("A") echo("A")
stdout.write("skipped") echo("skipped")
finally: finally:
block B: block B:
stdout.write("B") echo("B")
stdout.write("skipped") echo("skipped")
stdout.write("C") echo("C")
finally: finally:
stdout.writeln("D") echo("D")
discard main() #OUT ABCD discard main() #OUT ABCD

View file

@ -176,14 +176,10 @@ proc runJsTests(r: var TResults, options: string) =
runSingleTest(r, filename, options & " -d:nodejs", targetJS) runSingleTest(r, filename, options & " -d:nodejs", targetJS)
runSingleTest(r, filename, options & " -d:nodejs -d:release", targetJS) runSingleTest(r, filename, options & " -d:nodejs -d:release", targetJS)
# texceptions, texcpt1, texcsub, tfinally, tfinally2,
# tfinally3
for t in os.walkFiles("tests/js/t*.nim"): for t in os.walkFiles("tests/js/t*.nim"):
test(t) test(t)
test "tests/run/tactiontable" for testfile in ["texceptions", "texcpt1", "texcsub", "tfinally", "tfinally2", "tfinally3", "tactiontable", "tmultim1", "tmultim3", "tmultim4"]:
test "tests/run/tmultim1" test "tests/run/" & testfile & ".nim"
test "tests/run/tmultim3"
test "tests/run/tmultim4"
# ------------------------- register special tests here ----------------------- # ------------------------- register special tests here -----------------------
proc runSpecialTests(r: var TResults, options: string) = proc runSpecialTests(r: var TResults, options: string) =