summary refs log tree commit diff stats
path: root/tests/niminaction/Chapter3
Commit message (Collapse)AuthorAgeFilesLines
* use doAssert in tests (#16486)flywind2020-12-281-13/+13
|
* Error -> Defect for defects (#13908)Jacek Sieka2020-04-281-1/+1
| | | | | | | | | | | | | | * Error -> Defect for defects The distinction between Error and Defect is subjective, context-dependent and somewhat arbitrary, so when looking at an exception, it's hard to guess what it is - this happens often when looking at a `raises` list _without_ opening the corresponding definition and digging through layers of inheritance. With the help of a little consistency in naming, it's at least possible to start disentangling the two error types and the standard lib can set a good example here.
* remove deprecated procs (#12535)Andreas Rumpf2019-11-051-1/+1
|
* lots of small changesArne Döring2018-12-112-2/+2
|
* fix for nimInActionArne Döring2018-11-233-13/+26
|
* Adds smaller code samples from Chapters 1-3 to the tester.Dominik Picheta2018-06-172-0/+94
|
* Add tests for examples from Nim in Action.Dominik Picheta2017-10-015-0/+220
#n164'>164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
#
#
#            Nim'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 Nim
# 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 '_': discard # 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 '_': discard # 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: discard

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