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

This commit is contained in:
Araq
2013-03-16 20:40:26 +01:00
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)
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)

View File

@@ -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);

View File

@@ -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

View File

@@ -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 =

View File

@@ -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]

View File

@@ -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:

View File

@@ -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,

View File

@@ -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.

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
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

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``.
## 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)

View File

@@ -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 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]
proc assign(s: var TStaticStr, c: cstring) =
setLen(s)
add s, c
# ------------------- breakpoint support ------------------------------------
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
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
proc `==`(a: TStaticStr, b: cstring): bool =
result = c_strcmp(a.data, b) == 0
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 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 dbgRegisterFilename(filename: cstring) {.compilerproc.} =
# XXX we could check for duplicates here for DLL support
dbgFilenames[dbgFilenameLen] = filename
inc dbgFilenameLen
proc write(f: TFile, s: TStaticStr) =
write(f, cstring(s.data))
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 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 dbgShowExecutionPoint() =
write(stdout, "*** endb| ")
write(stdout, framePtr.filename)
write(stdout, "(")
write(stdout, framePtr.line)
write(stdout, ") ")
write(stdout, framePtr.procname)
write(stdout, " ***\n")
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 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 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)
iterator listBreakpoints*(): ptr TBreakpoint =
## lists all breakpoints.
for i in 0..dbgBPlen-1: yield addr(dbgBP[i])
proc scanWord(src: cstring, a: var TStaticStr, start: int): int =
setlen(a)
result = scanAndAppendWord(src, a, start)
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 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 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 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 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)
# ------------------- 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"

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 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 =

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 """
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

View File

@@ -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

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 -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) =