diff options
-rwxr-xr-x | compiler/c2nim/cparse.nim | 11 | ||||
-rwxr-xr-x | compiler/c2nim/tests/systest.c | 14 | ||||
-rwxr-xr-x | compiler/cgen.nim | 8 | ||||
-rwxr-xr-x | compiler/jsgen.nim | 13 | ||||
-rwxr-xr-x | compiler/nimrod.ini | 3 | ||||
-rwxr-xr-x | compiler/semfold.nim | 11 | ||||
-rwxr-xr-x | compiler/semgnrc.nim | 5 | ||||
-rwxr-xr-x | doc/endb.txt | 12 | ||||
-rwxr-xr-x | doc/lib.txt | 5 | ||||
-rwxr-xr-x | install.sh | 19 | ||||
-rwxr-xr-x | lib/core/typeinfo.nim | 10 | ||||
-rwxr-xr-x | lib/pure/strutils.nim | 13 | ||||
-rw-r--r-- | lib/pure/unicode.nim | 12 | ||||
-rwxr-xr-x | lib/system.nim | 75 | ||||
-rwxr-xr-x | lib/system/debugger.nim | 679 | ||||
-rw-r--r-- | lib/system/endb.nim | 538 | ||||
-rwxr-xr-x | lib/system/jssys.nim | 1 | ||||
-rw-r--r-- | tests/run/tfieldindex.nim | 21 | ||||
-rwxr-xr-x | tests/run/tfinally.nim | 8 | ||||
-rwxr-xr-x | tests/run/tfinally2.nim | 17 | ||||
-rw-r--r-- | tests/specials.nim | 8 |
21 files changed, 843 insertions, 640 deletions
diff --git a/compiler/c2nim/cparse.nim b/compiler/c2nim/cparse.nim index 9f7a7bf36..b964ed976 100755 --- a/compiler/c2nim/cparse.nim +++ b/compiler/c2nim/cparse.nim @@ -955,15 +955,22 @@ proc enumSpecifier(p: var TParser): PNode = result = newNodeP(nkConstSection, p) getTok(p, result) var i = 0 + var hasUnknown = false while true: var name = skipIdentExport(p) var val: PNode if p.tok.xkind == pxAsgn: getTok(p, name) val = constantExpression(p) - if val.kind == nkIntLit: i = int(val.intVal)+1 - else: parMessage(p, errXExpected, "int literal") + if val.kind == nkIntLit: + i = int(val.intVal)+1 + hasUnknown = false + else: + hasUnknown = true else: + if hasUnknown: + parMessage(p, warnUser, "computed const value may be wrong: " & + name.renderTree) val = newIntNodeP(nkIntLit, i, p) inc(i) var c = createConst(name, ast.emptyNode, val, p) diff --git a/compiler/c2nim/tests/systest.c b/compiler/c2nim/tests/systest.c index 2a9dd6c28..b73eb5bee 100755 --- a/compiler/c2nim/tests/systest.c +++ b/compiler/c2nim/tests/systest.c @@ -9,6 +9,20 @@ extern "C" { # 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 const char* (*callback2)(int rc, long L, const char* buffer); diff --git a/compiler/cgen.nim b/compiler/cgen.nim index d06a6cb6f..b7849f074 100755 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -892,6 +892,12 @@ proc getFileHeader(cfilenoext: string): PRope = result = getCopyright(cfilenoext) 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) = const CommonMainBody = @@ -950,6 +956,8 @@ proc genMainProc(m: BModule) = nimMain = PosixNimMain otherMain = PosixCMain if gBreakpoints != nil: discard cgsym(m, "dbgRegisterBreakpoint") + if optEndb in gOptions: + gBreakpoints.app(m.genFilenames) let initStackBottomCall = if emulatedThreadVars() or platform.targetOS == osStandalone: "".toRope diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 64175dc93..1f95f7955 100755 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -44,7 +44,6 @@ type TBlock{.final.} = object id: int # the ID of the label; positive means that it # 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' TGlobals{.final.} = object @@ -62,7 +61,6 @@ type module: BModule g: PGlobals BeforeRetNeeded: bool - nestedTryStmts: int unique: int 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)): 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) = var cond, stmt: TCompRes @@ -498,7 +492,6 @@ proc genWhileStmt(p: var TProc, n: PNode, r: var TCompRes) = length = len(p.blocks) setlen(p.blocks, length + 1) p.blocks[length].id = - p.unique - p.blocks[length].nestedTryStmts = p.nestedTryStmts p.blocks[length].isLoop = true labl = p.unique 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) app(r.com, "try {" & tnl) length = sonsLen(n) - inc(p.nestedTryStmts) genStmt(p, n.sons[0], a) app(r.com, mergeStmt(a)) i = 1 @@ -571,8 +563,6 @@ proc genTryStmt(p: var TProc, n: PNode, r: var TCompRes) = appf(epart, "$1}$n", [mergeStmt(a)]) inc(i) 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) if (i < length) and (n.sons[i].kind == nkFinally): 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 setlen(p.blocks, idx + 1) p.blocks[idx].id = - p.unique # negative because it isn't used yet - p.blocks[idx].nestedTryStmts = p.nestedTryStmts labl = p.unique if n.kind == nkBlockExpr: genStmtListExpr(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: InternalError(n.info, "no loop to break") 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)]) 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)) else: genLineDir(p, n, r) - finishTryStmt(p, r, p.nestedTryStmts) app(r.com, "break BeforeRet;" & tnl) proc genProcBody(p: var TProc, prc: PSym, r: TCompRes): PRope = diff --git a/compiler/nimrod.ini b/compiler/nimrod.ini index 74cf534e4..9febe24aa 100755 --- a/compiler/nimrod.ini +++ b/compiler/nimrod.ini @@ -57,8 +57,6 @@ Files: "compiler/*.nim" Files: "build/empty.txt" Files: "bin/empty.txt" -Files: "packages/docutils/*.nim" - [Lib] Files: "lib/nimbase.h" @@ -85,6 +83,7 @@ Files: "lib/wrappers/zip/libzip_all.c" Files: "lib/windows/*.nim" Files: "lib/posix/*.nim" Files: "lib/js/*.nim" +Files: "lib/packages/docutils/*.nim" [Other] diff --git a/compiler/semfold.nim b/compiler/semfold.nim index e26700e27..d304ddd3c 100755 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -490,7 +490,7 @@ proc getArrayConstr(m: PSym, n: PNode): PNode = proc foldArrayAccess(m: PSym, n: PNode): PNode = 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]) if y == nil: return @@ -541,7 +541,12 @@ proc foldConStrStr(m: PSym, n: PNode): PNode = let a = getConstExpr(m, n.sons[i]) if a == nil: return nil 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 = result = nil case n.kind @@ -569,6 +574,8 @@ proc getConstExpr(m: PSym, n: PNode): PNode = if sfFakeConst notin s.flags: result = copyTree(s.ast) elif s.kind in {skProc, skMethod}: # BUGFIX result = n + elif s.kind in {skType, skGenericParam}: + result = newSymNodeTypeDesc(s, n.info) of nkCharLit..nkNilLit: result = copyNode(n) of nkIfExpr: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 2e1eccda2..8c3cef027 100755 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -31,11 +31,6 @@ proc getIdentNode(n: PNode): PNode = illFormedAst(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, ctx: var TIntSet): PNode proc semGenericStmtScope(c: PContext, n: PNode, diff --git a/doc/endb.txt b/doc/endb.txt index 900553f0e..d4437e5d9 100755 --- a/doc/endb.txt +++ b/doc/endb.txt @@ -61,8 +61,8 @@ Executing Commands Breakpoint Commands =================== -``b``, ``setbreak`` <identifier> [fromline [toline]] [file] - Set a new breakpoint named 'identifier' for the given file +``b``, ``setbreak`` [fromline [toline]] [file] + Set a new breakpoint for the given file and line numbers. If no file is given, the current execution point's filename is used. If the filename has no extension, ``.nim`` is appended for your convenience. @@ -71,16 +71,16 @@ Breakpoint Commands breakpoint contains a line number range. Some examples if it is still 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 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 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 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 current line in the current file again. diff --git a/doc/lib.txt b/doc/lib.txt index f18693614..2f781f375 100755 --- a/doc/lib.txt +++ b/doc/lib.txt @@ -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 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 diff --git a/install.sh b/install.sh index 886cc8800..f283a4ae8 100755 --- a/install.sh +++ b/install.sh @@ -70,6 +70,7 @@ if [ $# -eq 1 ] ; then mkdir -p $libdir/windows mkdir -p $libdir/posix mkdir -p $libdir/js + mkdir -p $libdir/packages/docutils cp bin/nimrod $bindir/nimrod chmod 755 $bindir/nimrod @@ -711,20 +712,26 @@ if [ $# -eq 1 ] ; then chmod 644 $libdir/system/debugger.nim cp lib/system/dyncalls.nim $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 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 chmod 644 $libdir/system/excpt.nim cp lib/system/gc.nim $libdir/system/gc.nim chmod 644 $libdir/system/gc.nim cp lib/system/gc2.nim $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 chmod 644 $libdir/system/hti.nim cp lib/system/inclrtl.nim $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 chmod 644 $libdir/system/mmdisp.nim cp lib/system/profiler.nim $libdir/system/profiler.nim @@ -1103,6 +1110,14 @@ if [ $# -eq 1 ] ; then chmod 644 $libdir/posix/posix.nim cp lib/js/dom.nim $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" else diff --git a/lib/core/typeinfo.nim b/lib/core/typeinfo.nim index eb2a0c9e5..6f8081b5f 100755 --- a/lib/core/typeinfo.nim +++ b/lib/core/typeinfo.nim @@ -102,6 +102,16 @@ proc newAny(value: pointer, rawType: PNimType): TAny = result.value = value 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.} = ## 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 diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 090ad640c..b5f5a41eb 100755 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -415,7 +415,15 @@ proc parseEnum*[T: enum](s: string, default: T): T = proc repeatChar*(count: int, c: Char = ' '): string {.noSideEffect, rtl, extern: "nsuRepeatChar".} = ## 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) for i in 0..count-1: result[i] = c @@ -429,7 +437,8 @@ proc align*(s: string, count: int): string {. noSideEffect, rtl, extern: "nsuAlignString".} = ## 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 - ## 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: result = newString(count) var spaces = count - s.len diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index f76573788..142178a86 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -132,6 +132,11 @@ proc toUTF8*(c: TRune): string {.rtl, extern: "nuc$1".} = result = newString(1) 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 alphaRanges = [ 0x00d8, 0x00f6, # - @@ -1208,3 +1213,10 @@ proc cmpRunesIgnoreCase*(a, b: string): int {.rtl, extern: "nuc$1", procvar.} = result = irune(toLower(ar)) - irune(toLower(br)) if result != 0: return result = a.len - b.len + +when isMainModule: + let + someString = "öÑ" + someRunes = @[runeAt(someString, 0), runeAt(someString, 2)] + compared = (someString == $someRunes) + assert compared == true diff --git a/lib/system.nim b/lib/system.nim index db78d2740..5bcd7b02d 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -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``. ## This is equivalent to ``s = @[]; setlen(s, len)``, but more ## 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] = ## 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) proc len*[TOpenArray: openArray|varargs](x: TOpenArray): int {. @@ -1650,10 +1675,6 @@ const nimrodStackTrace = compileOption("stacktrace") # of the code 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.} ## with this hook you can influence exception handling on a global level. ## If not nil, every 'raise' statement ends up calling this hook. Ordinary @@ -1691,13 +1712,14 @@ var ## continues and the program is terminated. type - PFrame = ptr TFrame - TFrame {.importc, nodecl, final.} = object - prev: PFrame - procname: CString - line: int # current line number - filename: CString - len: int # length of slots (when not debugging always zero) + PFrame* = ptr TFrame ## represents a runtime frame of the call stack; + ## part of the debugger API. + TFrame* {.importc, nodecl, final.} = object ## the frame itself + prev*: PFrame ## previous frame; used for chaining the call stack + procname*: cstring ## name of the proc that is currently executing + line*: int ## line number of the proc that is currently executing + filename*: cstring ## filename of the proc that is currently executing + len*: int ## length of the inspectable slots when not defined(JS): {.push stack_trace:off, profiler:off.} @@ -2374,9 +2396,34 @@ proc astToStr*[T](x: T): string {.magic: "AstToStr", noSideEffect.} proc InstantiationInfo*(index = -1): tuple[filename: string, line: int] {. magic: "InstantiationInfo", noSideEffect.} ## 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.} = raise newException(EAssertionFailed, msg) diff --git a/lib/system/debugger.nim b/lib/system/debugger.nim index 62d667285..eade1707f 100755 --- a/lib/system/debugger.nim +++ b/lib/system/debugger.nim @@ -7,187 +7,95 @@ # 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. +## This file implements basic features for any debugger. type - TStaticStr {.pure, final.} = object - len: int - data: array[0..100, char] - - 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" + TVarSlot* {.compilerproc, final.} = object ## a slot in a frame + address*: pointer ## the variable's address + typ*: PNimType ## the variable's type + name*: cstring ## the variable's name; for globals this is "module.name" PExtendedFrame = ptr TExtendedFrame - TExtendedFrame {.final.} = object # If the debugger is enabled the compiler - # provides an extended frame. Of course - # only slots that are - # needed are allocated and not 10_000, - # except for the global data description. + TExtendedFrame = object # If the debugger is enabled the compiler + # provides an extended frame. Of course + # only slots that are + # needed are allocated and not 10_000, + # except for the global data description. f: TFrame slots: array[0..10_000, TVarSlot] 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 # 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) = - s.len = newLen - s.data[newLen] = '\0' +proc getLocal*(frame: PFrame; slot: int): TVarSlot {.inline.} = + ## retrieves the meta data for the local variable at `slot`. CAUTION: An + ## invalid `slot` value causes a corruption! + result = cast[PExtendedFrame](frame).slots[slot] -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 getGlobalLen*(): int {.inline.} = + ## gets the number of registered globals. + result = dbgGlobalData.f.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 findBreakpoint(name: TStaticStr): int = - # returns -1 if not found - for i in countdown(dbgBPlen-1, 0): - if name == dbgBP[i].name: return i - return -1 - -proc write(f: TFile, s: TStaticStr) = - write(f, cstring(s.data)) - -proc ListBreakPoints() = - write(stdout, "*** endb| Breakpoints:\n") - for i in 0 .. dbgBPlen-1: - write(stdout, dbgBP[i].name) - write(stdout, ": ") - write(stdout, abs(dbgBP[i].low)) - write(stdout, "..") - write(stdout, abs(dbgBP[i].high)) - write(stdout, dbgBP[i].filename) - if dbgBP[i].low < 0: - write(stdout, " [disabled]\n") - else: - write(stdout, "\n") - write(stdout, "***\n") - -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: 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 getGlobal*(slot: int): TVarSlot {.inline.} = + ## retrieves the meta data for the global variable at `slot`. CAUTION: An + ## invalid `slot` value causes a corruption! + result = dbgGlobalData.slots[slot] + +# ------------------- breakpoint support ------------------------------------ -proc dbgShowExecutionPoint() = - write(stdout, "*** endb| ") - write(stdout, framePtr.filename) - write(stdout, "(") - write(stdout, framePtr.line) - write(stdout, ") ") - write(stdout, framePtr.procname) - write(stdout, " ***\n") +type + TBreakpoint* = object ## represents a break point + low*, high*: int ## range from low to high; if disabled + ## both low and high are set to their negative values + filename*: cstring ## the filename of the breakpoint + +var + dbgBP: array[0..127, TBreakpoint] # breakpoints + dbgBPlen: int + dbgBPbloom: int64 # we use a bloom filter to speed up breakpoint checking + + dbgFilenames*: array[0..300, cstring] ## registered filenames; + ## 'nil' terminated + dbgFilenameLen: int + +proc dbgRegisterFilename(filename: cstring) {.compilerproc.} = + # XXX we could check for duplicates here for DLL support + dbgFilenames[dbgFilenameLen] = filename + inc dbgFilenameLen + +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].filename = filename + dbgBP[x].low = line + dbgBP[x].high = line + dbgBPbloom = dbgBPbloom or line + +proc addBreakpoint*(filename: cstring, lo, hi: int): bool = + let x = dbgBPlen + if x >= high(dbgBP): return false + inc(dbgBPlen) + result = true + dbgBP[x].filename = filename + dbgBP[x].low = lo + dbgBP[x].high = hi + for line in lo..hi: dbgBPbloom = dbgBPbloom or line const FileSystemCaseInsensitive = defined(windows) or defined(dos) or defined(os2) @@ -216,337 +124,29 @@ proc fileMatches(c, bp: cstring): bool = inc(i) return true -proc dbgBreakpointReached(line: int): int = - for i in 0..dbgBPlen-1: - if line >= dbgBP[i].low and line <= dbgBP[i].high and - fileMatches(framePtr.filename, dbgBP[i].filename): return i - return -1 - -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 - # skip whitespace: - 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 <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 canonFilename*(filename: cstring): cstring = + ## returns 'nil' if the filename cannot be found. + for i in 0 .. <dbgFilenameLen: + result = dbgFilenames[i] + if fileMatches(result, filename): return result + result = nil -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 +iterator listBreakpoints*(): ptr TBreakpoint = + ## lists all breakpoints. + for i in 0..dbgBPlen-1: yield addr(dbgBP[i]) -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 isActive*(b: ptr TBreakpoint): bool = b.low > 0 +proc flip*(b: ptr TBreakpoint) = + ## enables or disables 'b' depending on its current state. + b.low = -b.low; b.high = -b.high -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 checkBreakpoints*(filename: cstring, line: int): ptr TBreakpoint = + ## in which breakpoint (if any) we are. + if (dbgBPbloom and line) != line: return nil + for b in listBreakpoints(): + if line >= b.low and line <= b.high and filename == b.filename: return b -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) +# ------------------- watchpoint support ------------------------------------ type THash = int @@ -665,7 +265,7 @@ proc dbgRegisterWatchpoint(address: pointer, name: cstring, Watchpoints[i].address = address return if L >= watchPoints.high: - debugOut("[Warning] cannot register watchpoint") + #debugOut("[Warning] cannot register watchpoint") return Watchpoints[L].name = name Watchpoints[L].address = address @@ -676,99 +276,28 @@ proc dbgRegisterWatchpoint(address: pointer, name: cstring, proc dbgUnregisterWatchpoints*() = WatchpointsLen = 0 -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 strstr(s1, s2: cstring): cstring {.importc, header: "<string.h>".} +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. -proc interestingFilename(filename: cstring): bool = - #result = strstr(filename, "/rst.nim") == nil - result = true + dbgWatchpointHook*: proc (watchpointName: cstring) {.nimcall.} proc checkWatchpoints = let L = WatchpointsLen for i in 0.. <L: let newHash = genericHash(Watchpoints[i].address, Watchpoints[i].typ) if newHash != Watchpoints[i].oldValue: - if interestingFilename(framePtr.filename): - dbgWriteStackTrace(framePtr) - debugOut(Watchpoints[i].name) + dbgWatchpointHook(Watchpoints[i].name) 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! - # 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 - let oldState = dbgState - checkWatchpoints() + if dbgWatchpointHook != nil: checkWatchpoints() framePtr.line = line # this is done here for smaller code size! framePtr.filename = file 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" diff --git a/lib/system/endb.nim b/lib/system/endb.nim new file mode 100644 index 000000000..2d6a25824 --- /dev/null +++ b/lib/system/endb.nim @@ -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 diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 789e39d6d..1c43bfdc7 100755 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -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 nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b +type NimString = string # hack for hti.nim include "system/hti" proc isFatPointer(ti: PNimType): bool = diff --git a/tests/run/tfieldindex.nim b/tests/run/tfieldindex.nim new file mode 100644 index 000000000..3ffa65e58 --- /dev/null +++ b/tests/run/tfieldindex.nim @@ -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") + diff --git a/tests/run/tfinally.nim b/tests/run/tfinally.nim index 29313c3fd..273aac72b 100755 --- a/tests/run/tfinally.nim +++ b/tests/run/tfinally.nim @@ -1,6 +1,8 @@ discard """ file: "tfinally.nim" - output: "came here 3" + output: '''came +here +3''' """ # Test return in try statement: @@ -9,10 +11,10 @@ proc main: int = try: return 1 finally: - stdout.write("came ") + echo("came") return 2 finally: - stdout.write("here ") + echo("here ") return 3 echo main() #OUT came here 3 diff --git a/tests/run/tfinally2.nim b/tests/run/tfinally2.nim index 3ed212a7c..e1e8d4c7e 100755 --- a/tests/run/tfinally2.nim +++ b/tests/run/tfinally2.nim @@ -1,6 +1,9 @@ discard """ file: "tfinally2.nim" - output: "ABCD" + output: '''A +B +C +D''' """ # Test break in try statement: @@ -11,15 +14,15 @@ proc main: int = try: break AB finally: - stdout.write("A") - stdout.write("skipped") + echo("A") + echo("skipped") finally: block B: - stdout.write("B") - stdout.write("skipped") - stdout.write("C") + echo("B") + echo("skipped") + echo("C") finally: - stdout.writeln("D") + echo("D") discard main() #OUT ABCD diff --git a/tests/specials.nim b/tests/specials.nim index 1818497a4..b5c49ec3c 100644 --- a/tests/specials.nim +++ b/tests/specials.nim @@ -176,14 +176,10 @@ proc runJsTests(r: var TResults, options: string) = runSingleTest(r, filename, options & " -d:nodejs", 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"): test(t) - test "tests/run/tactiontable" - test "tests/run/tmultim1" - test "tests/run/tmultim3" - test "tests/run/tmultim4" + for testfile in ["texceptions", "texcpt1", "texcsub", "tfinally", "tfinally2", "tfinally3", "tactiontable", "tmultim1", "tmultim3", "tmultim4"]: + test "tests/run/" & testfile & ".nim" # ------------------------- register special tests here ----------------------- proc runSpecialTests(r: var TResults, options: string) = |