]> git.donarmstrong.com Git - lilypond.git/blobdiff - guile18/ice-9/debugging/traps.scm
Import guile-1.8 as multiple upstream tarball component
[lilypond.git] / guile18 / ice-9 / debugging / traps.scm
diff --git a/guile18/ice-9/debugging/traps.scm b/guile18/ice-9/debugging/traps.scm
new file mode 100755 (executable)
index 0000000..080d7bc
--- /dev/null
@@ -0,0 +1,1037 @@
+;;;; (ice-9 debugging traps) -- abstraction of libguile's traps interface
+
+;;; Copyright (C) 2002, 2004 Free Software Foundation, Inc.
+;;; Copyright (C) 2005 Neil Jerram
+;;;
+;; This library is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU Lesser General Public
+;; License as published by the Free Software Foundation; either
+;; version 2.1 of the License, or (at your option) any later version.
+;; 
+;; This library is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; Lesser General Public License for more details.
+;; 
+;; You should have received a copy of the GNU Lesser General Public
+;; License along with this library; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+;;; This module provides an abstraction around Guile's low level trap
+;;; handler interface; its aim is to make the low level trap mechanism
+;;; shareable between the debugger and other applications, and to
+;;; insulate the rest of the debugger code a bit from changes that may
+;;; occur in the low level trap interface in future.
+
+(define-module (ice-9 debugging traps)
+  #:use-module (ice-9 regex)
+  #:use-module (oop goops)
+  #:use-module (oop goops describe)
+  #:use-module (ice-9 debugging trc)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-2)
+  #:export (tc:type
+            tc:continuation
+            tc:expression
+            tc:return-value
+            tc:stack
+            tc:frame
+            tc:depth
+            tc:real-depth
+            tc:exit-depth
+           tc:fired-traps
+           ;; Interface for users of <trap> subclasses defined in
+           ;; this module.
+            add-trapped-stack-id!
+           remove-trapped-stack-id!
+           <procedure-trap>
+           <exit-trap>
+           <entry-trap>
+           <apply-trap>
+           <step-trap>
+           <source-trap>
+           <location-trap>
+           install-trap
+           uninstall-trap
+            all-traps
+            get-trap
+           list-traps
+           trap-ordering
+            behaviour-ordering
+           throw->trap-context
+           on-lazy-handler-dispatch
+           ;; Interface for authors of new <trap> subclasses.
+           <trap-context>
+           <trap>
+           trap->behaviour
+           trap-runnable?
+           install-apply-frame-trap
+           install-breakpoint-trap
+           install-enter-frame-trap
+           install-exit-frame-trap
+           install-trace-trap
+           uninstall-apply-frame-trap
+           uninstall-breakpoint-trap
+           uninstall-enter-frame-trap
+           uninstall-exit-frame-trap
+           uninstall-trace-trap
+           frame->source-position
+           frame-file-name
+           without-traps
+            guile-trap-features)
+  #:re-export (make)
+  #:export-syntax (trap-here))
+
+;; How to debug the debugging infrastructure, when needed.  Grep for
+;; "(trc " to find other symbols that can be passed to trc-add.
+;; (trc-add 'after-gc-hook)
+
+;; In Guile 1.7 onwards, weak-vector and friends are provided by the
+;; (ice-9 weak-vector) module.
+(cond ((string>=? (version) "1.7")
+       (use-modules (ice-9 weak-vector))))
+
+;;; The current low level traps interface is as follows.
+;;;
+;;; All trap handlers are subject to SCM_TRAPS_P, which is controlled
+;;; by the `traps' setting of `(evaluator-traps-interface)' but also
+;;; (and more relevant in most cases) by the `with-traps' procedure.
+;;; Basically, `with-traps' sets SCM_TRAPS_P to 1 during execution of
+;;; its thunk parameter.
+;;;
+;;; Note that all trap handlers are called with SCM_TRAPS_P set to 0
+;;; for the duration of the call, to avoid nasty recursive trapping
+;;; loops.  If a trap handler knows what it is doing, it can override
+;;; this by `(trap-enable traps)'.
+;;;
+;;; The apply-frame handler is called when Guile is about to perform
+;;; an application if EITHER the `apply-frame' evaluator trap option
+;;; is set, OR the `trace' debug option is set and the procedure to
+;;; apply has its `trace' procedure property set.  The arguments
+;;; passed are:
+;;;
+;;; - the symbol 'apply-frame
+;;;
+;;; - a continuation or debug object describing the current stack
+;;;
+;;; - a boolean indicating whether the application is tail-recursive.
+;;;
+;;; The enter-frame handler is called when the evaluator begins a new
+;;; evaluation frame if EITHER the `enter-frame' evaluator trap option
+;;; is set, OR the `breakpoints' debug option is set and the code to
+;;; be evaluated has its `breakpoint' source property set.  The
+;;; arguments passed are:
+;;;
+;;; - the symbol 'enter-frame
+;;;
+;;; - a continuation or debug object describing the current stack
+;;;
+;;; - a boolean indicating whether the application is tail-recursive.
+;;;
+;;; - an unmemoized copy of the expression to be evaluated.
+;;;
+;;; If the `enter-frame' evaluator trap option is set, the enter-frame
+;;; handler is also called when about to perform an application in
+;;; SCM_APPLY, immediately before possibly calling the apply-frame
+;;; handler.  (I don't totally understand this.)  In this case, the
+;;; arguments passed are:
+;;;
+;;; - the symbol 'enter-frame
+;;;
+;;; - a continuation or debug object describing the current stack.
+;;;
+;;; The exit-frame handler is called when Guile exits an evaluation
+;;; frame (in SCM_CEVAL) or an application frame (in SCM_APPLY), if
+;;; EITHER the `exit-frame' evaluator trap option is set, OR the
+;;; `trace' debug option is set and the frame is marked as having been
+;;; traced.  The frame will be marked as having been traced if the
+;;; apply-frame handler was called for this frame.  (This is trickier
+;;; than it sounds because of tail recursion: the same debug frame
+;;; could have been used for multiple applications, only some of which
+;;; were traced - I think.)  The arguments passed are:
+;;;
+;;; - the symbol 'exit-frame
+;;;
+;;; - a continuation or debug object describing the current stack
+;;;
+;;; - the result of the evaluation or application.
+
+;;; {Trap Context}
+;;;
+;;; A trap context is a GOOPS object that encapsulates all the useful
+;;; information about a particular trap.  Encapsulating this
+;;; information in a single object also allows us:
+;;;
+;;; - to defer the calculation of information that is time-consuming
+;;; to calculate, such as the stack, and to cache such information so
+;;; that it is only ever calculated once per trap
+;;;
+;;; - to pass all interesting information to trap behaviour procedures
+;;; in a single parameter, which (i) is convenient and (ii) makes for
+;;; a more future-proof interface.
+;;;
+;;; It also allows us - where very carefully documented! - to pass
+;;; information from one behaviour procedure to another.
+
+(define-class <trap-context> ()
+  ;; Information provided directly by the trap calls from the
+  ;; evaluator.  The "type" slot holds a keyword indicating the type
+  ;; of the trap: one of #:evaluation, #:application, #:return,
+  ;; #:error.
+  (type #:getter tc:type
+        #:init-keyword #:type)
+  ;; The "continuation" slot holds the continuation (or debug object,
+  ;; if "cheap" traps are enabled, which is the default) at the point
+  ;; of the trap.  For an error trap it is #f.
+  (continuation #:getter tc:continuation
+                #:init-keyword #:continuation)
+  ;; The "expression" slot holds the source code expression, for an
+  ;; evaluation trap.
+  (expression #:getter tc:expression
+              #:init-keyword #:expression
+              #:init-value #f)
+  ;; The "return-value" slot holds the return value, for a return
+  ;; trap, or the error args, for an error trap.
+  (return-value #:getter tc:return-value
+                #:init-keyword #:return-value
+                #:init-value #f)
+  ;; The list of trap objects which fired in this trap context.
+  (fired-traps #:getter tc:fired-traps
+              #:init-value '())
+  ;; The set of symbols which, if one of them is set in the CAR of the
+  ;; handler-return-value slot, will cause the CDR of that slot to
+  ;; have an effect.
+  (handler-return-syms #:init-value '())
+  ;; The value which the trap handler should return to the evaluator.
+  (handler-return-value #:init-value #f)
+  ;; Calculated and cached information.  "stack" is the stack
+  ;; (computed from the continuation (or debug object) by make-stack,
+  ;; or else (in the case of an error trap) by (make-stack #t ...).
+  (stack #:init-value #f)
+  (frame #:init-value #f)
+  (depth #:init-value #f)
+  (real-depth #:init-value #f)
+  (exit-depth #:init-value #f))
+
+(define-method (tc:stack (ctx <trap-context>))
+  (or (slot-ref ctx 'stack)
+      (let ((stack (make-stack (tc:continuation ctx))))
+        (slot-set! ctx 'stack stack)
+        stack)))
+
+(define-method (tc:frame (ctx <trap-context>))
+  (or (slot-ref ctx 'frame)
+      (let ((frame (cond ((tc:continuation ctx) => last-stack-frame)
+                        (else (stack-ref (tc:stack ctx) 0)))))
+        (slot-set! ctx 'frame frame)
+        frame)))
+
+(define-method (tc:depth (ctx <trap-context>))
+  (or (slot-ref ctx 'depth)
+      (let ((depth (stack-length (tc:stack ctx))))
+        (slot-set! ctx 'depth depth)
+        depth)))
+
+(define-method (tc:real-depth (ctx <trap-context>))
+  (or (slot-ref ctx 'real-depth)
+      (let* ((stack (tc:stack ctx))
+            (real-depth (apply +
+                               (map (lambda (i)
+                                      (if (frame-real? (stack-ref stack i))
+                                          1
+                                          0))
+                                    (iota (tc:depth ctx))))))
+        (slot-set! ctx 'real-depth real-depth)
+        real-depth)))
+
+(define-method (tc:exit-depth (ctx <trap-context>))
+  (or (slot-ref ctx 'exit-depth)
+      (let* ((stack (tc:stack ctx))
+            (depth (tc:depth ctx))
+            (exit-depth (let loop ((exit-depth depth))
+                          (if (or (zero? exit-depth)
+                                  (frame-real? (stack-ref stack
+                                                          (- depth
+                                                             exit-depth))))
+                              exit-depth
+                              (loop (- exit-depth 1))))))
+       (slot-set! ctx 'exit-depth exit-depth)
+        exit-depth)))
+
+;;; {Stack IDs}
+;;;
+;;; Mechanism for limiting trapping to contexts whose stack ID matches
+;;; one of a registered set.  The default is for traps to fire
+;;; regardless of stack ID.
+
+(define trapped-stack-ids (list #t))
+(define all-stack-ids-trapped? #t)
+
+(define (add-trapped-stack-id! id)
+  "Add ID to the set of stack ids for which traps are active.
+If `#t' is in this set, traps are active regardless of stack context.
+To remove ID again, use `remove-trapped-stack-id!'.  If you add the
+same ID twice using `add-trapped-stack-id!', you will need to remove
+it twice."
+  (set! trapped-stack-ids (cons id trapped-stack-ids))
+  (set! all-stack-ids-trapped? (memq #t trapped-stack-ids)))
+
+(define (remove-trapped-stack-id! id)
+  "Remove ID from the set of stack ids for which traps are active."
+  (set! trapped-stack-ids (delq1! id trapped-stack-ids))
+  (set! all-stack-ids-trapped? (memq #t trapped-stack-ids)))
+
+(define (trap-here? cont)
+  ;; Return true if the stack id of the specified continuation (or
+  ;; debug object) is in the set that we should trap for; otherwise
+  ;; false.
+  (or all-stack-ids-trapped?
+      (memq (stack-id cont) trapped-stack-ids)))
+
+;;; {Global State}
+;;;
+;;; Variables tracking registered handlers, relevant procedures, and
+;;; what's turned on as regards the evaluator's debugging options.
+
+(define enter-frame-traps '())
+(define apply-frame-traps '())
+(define exit-frame-traps '())
+(define breakpoint-traps '())
+(define trace-traps '())
+
+(define (non-null? hook)
+  (not (null? hook)))
+
+;; The low level frame handlers must all be initialized to something
+;; harmless.  Otherwise we hit a problem immediately when trying to
+;; enable one of these handlers.
+(trap-set! enter-frame-handler noop)
+(trap-set! apply-frame-handler noop)
+(trap-set! exit-frame-handler noop)
+
+(define set-debug-and-trap-options
+  (let ((dopts (debug-options))
+       (topts (evaluator-traps-interface))
+       (setting (lambda (key opts)
+                  (let ((l (memq key opts)))
+                    (and l
+                         (not (null? (cdr l)))
+                         (cadr l)))))
+       (debug-set-boolean! (lambda (key value)
+                             ((if value debug-enable debug-disable) key)))
+       (trap-set-boolean! (lambda (key value)
+                            ((if value trap-enable trap-disable) key))))
+    (let ((save-debug (memq 'debug dopts))
+         (save-trace (memq 'trace dopts))
+         (save-breakpoints (memq 'breakpoints dopts))
+         (save-enter-frame (memq 'enter-frame topts))
+         (save-apply-frame (memq 'apply-frame topts))
+         (save-exit-frame (memq 'exit-frame topts))
+         (save-enter-frame-handler (setting 'enter-frame-handler topts))
+         (save-apply-frame-handler (setting 'apply-frame-handler topts))
+         (save-exit-frame-handler (setting 'exit-frame-handler topts)))
+      (lambda ()
+       (let ((need-trace (non-null? trace-traps))
+             (need-breakpoints (non-null? breakpoint-traps))
+             (need-enter-frame (non-null? enter-frame-traps))
+             (need-apply-frame (non-null? apply-frame-traps))
+             (need-exit-frame (non-null? exit-frame-traps)))
+         (debug-set-boolean! 'debug
+                             (or need-trace
+                                 need-breakpoints
+                                 need-enter-frame
+                                 need-apply-frame
+                                 need-exit-frame
+                                 save-debug))
+         (debug-set-boolean! 'trace
+                             (or need-trace
+                                 save-trace))
+         (debug-set-boolean! 'breakpoints
+                             (or need-breakpoints
+                                 save-breakpoints))
+         (trap-set-boolean! 'enter-frame
+                            (or need-enter-frame
+                                save-enter-frame))
+         (trap-set-boolean! 'apply-frame
+                            (or need-apply-frame
+                                save-apply-frame))
+         (trap-set-boolean! 'exit-frame
+                            (or need-exit-frame
+                                save-exit-frame))
+         (trap-set! enter-frame-handler
+                    (cond ((or need-breakpoints
+                               need-enter-frame)
+                           enter-frame-handler)
+                          (else save-enter-frame-handler)))
+         (trap-set! apply-frame-handler
+                    (cond ((or need-trace
+                               need-apply-frame)
+                           apply-frame-handler)
+                          (else save-apply-frame-handler)))
+         (trap-set! exit-frame-handler
+                    (cond ((or need-exit-frame)
+                           exit-frame-handler)
+                          (else save-exit-frame-handler))))
+       ;;(write (evaluator-traps-interface))
+       *unspecified*))))
+
+(define (enter-frame-handler key cont . args)
+  ;; For a non-application entry, ARGS is (TAIL? EXP), where EXP is an
+  ;; unmemoized copy of the source expression.  For an application
+  ;; entry, ARGS is empty.
+  (if (trap-here? cont)
+      (let* ((application-entry? (null? args))
+             (trap-context (make <trap-context>
+                             #:type #:evaluation
+                             #:continuation cont
+                             #:expression (if application-entry?
+                                              #f
+                                              (cadr args)))))
+       (trc 'enter-frame-handler)
+       (if (and (not application-entry?)
+                 (memq 'tweaking guile-trap-features))
+           (slot-set! trap-context 'handler-return-syms '(instead)))
+        (run-traps (if application-entry?
+                      enter-frame-traps
+                      (append enter-frame-traps breakpoint-traps))
+                  trap-context)
+       (slot-ref trap-context 'handler-return-value))))
+
+(define (apply-frame-handler key cont tail?)
+  (if (trap-here? cont)
+      (let ((trap-context (make <trap-context>
+                            #:type #:application
+                            #:continuation cont)))
+       (trc 'apply-frame-handler tail?)
+        (run-traps (append apply-frame-traps trace-traps) trap-context)
+       (slot-ref trap-context 'handler-return-value))))
+
+(define (exit-frame-handler key cont retval)
+  (if (trap-here? cont)
+      (let ((trap-context (make <trap-context>
+                            #:type #:return
+                            #:continuation cont
+                            #:return-value retval)))
+       (trc 'exit-frame-handler retval (tc:depth trap-context))
+       (if (memq 'tweaking guile-trap-features)
+            (slot-set! trap-context 'handler-return-syms '(instead)))
+        (run-traps exit-frame-traps trap-context)
+       (slot-ref trap-context 'handler-return-value))))
+
+(define-macro (trap-installer trap-list)
+  `(lambda (trap)
+     (set! ,trap-list (cons trap ,trap-list))
+     (set-debug-and-trap-options)))
+
+(define install-enter-frame-trap (trap-installer enter-frame-traps))
+(define install-apply-frame-trap (trap-installer apply-frame-traps))
+(define install-exit-frame-trap (trap-installer exit-frame-traps))
+(define install-breakpoint-trap (trap-installer breakpoint-traps))
+(define install-trace-trap (trap-installer trace-traps))
+
+(define-macro (trap-uninstaller trap-list)
+  `(lambda (trap)
+     (or (memq trap ,trap-list)
+         (error "Trap list does not include the specified trap"))
+     (set! ,trap-list (delq1! trap ,trap-list))
+     (set-debug-and-trap-options)))
+
+(define uninstall-enter-frame-trap (trap-uninstaller enter-frame-traps))
+(define uninstall-apply-frame-trap (trap-uninstaller apply-frame-traps))
+(define uninstall-exit-frame-trap (trap-uninstaller exit-frame-traps))
+(define uninstall-breakpoint-trap (trap-uninstaller breakpoint-traps))
+(define uninstall-trace-trap (trap-uninstaller trace-traps))
+
+(define trap-ordering (make-object-property))
+(define behaviour-ordering (make-object-property))
+
+(define (run-traps traps trap-context)
+  (let ((behaviours (apply append
+                          (map (lambda (trap)
+                                 (trap->behaviour trap trap-context))
+                               (sort traps
+                                     (lambda (t1 t2)
+                                       (< (or (trap-ordering t1) 0)
+                                          (or (trap-ordering t2) 0))))))))
+    (for-each (lambda (proc)
+               (proc trap-context))
+             (sort (delete-duplicates behaviours)
+                   (lambda (b1 b2)
+                    (< (or (behaviour-ordering b1) 0)
+                       (or (behaviour-ordering b2) 0)))))))
+
+;;; {Pseudo-Traps for Non-Trap Events}
+
+;;; Once there is a body of code to do with responding to (debugging,
+;;; tracing, etc.) traps, it makes sense to be able to leverage that
+;;; same code for certain events that are trap-like, but not actually
+;;; traps in the sense of the calls made by libguile's evaluator.
+
+;;; The main example of this is when an error is signalled.  Guile
+;;; doesn't yet have a 100% reliable way of hooking into errors, but
+;;; in practice most errors go through a lazy-catch whose handler is
+;;; lazy-handler-dispatch (defined in ice-9/boot-9.scm), which in turn
+;;; calls default-lazy-handler.  So we can present most errors as
+;;; pseudo-traps by modifying default-lazy-handler.
+
+(define default-default-lazy-handler default-lazy-handler)
+
+(define (throw->trap-context key args . stack-args)
+  (let ((ctx (make <trap-context>
+              #:type #:error
+              #:continuation #f
+              #:return-value (cons key args))))
+    (slot-set! ctx 'stack
+              (let ((caller-stack (and (= (length stack-args) 1)
+                                       (car stack-args))))
+                (if (stack? caller-stack)
+                    caller-stack
+                    (apply make-stack #t stack-args))))
+    ctx))
+
+(define (on-lazy-handler-dispatch behaviour . ignored-keys)
+  (set! default-lazy-handler
+       (if behaviour
+           (lambda (key . args)
+             (or (memq key ignored-keys)
+                 (behaviour (throw->trap-context key
+                                                 args
+                                                 lazy-handler-dispatch)))
+             (apply default-default-lazy-handler key args))
+           default-default-lazy-handler)))
+
+;;; {Trap Classes}
+
+;;; Class: <trap>
+;;;
+;;; <trap> is the base class for traps.  Any actual trap should be an
+;;; instance of a class derived from <trap>, not of <trap> itself,
+;;; because there is no base class method for the install-trap,
+;;; trap-runnable? and uninstall-trap GFs.
+(define-class <trap> ()
+  ;; "number" slot: the number of this trap (assigned automatically).
+  (number)
+  ;; "installed" slot: whether this trap is installed.
+  (installed #:init-value #f)
+  ;; "condition" slot: if non-#f, this is a thunk which is called when
+  ;; the trap fires, to determine whether trap processing should
+  ;; proceed any further.
+  (condition #:init-value #f #:init-keyword #:condition)
+  ;; "skip-count" slot: a count of valid (after "condition"
+  ;; processing) firings of this trap to skip.
+  (skip-count #:init-value 0 #:init-keyword #:skip-count)
+  ;; "single-shot" slot: if non-#f, this trap is removed after it has
+  ;; successfully fired (after "condition" and "skip-count"
+  ;; processing) for the first time.
+  (single-shot #:init-value #f #:init-keyword #:single-shot)
+  ;; "behaviour" slot: procedure or list of procedures to call
+  ;; (passing the trap context as parameter) if we finally decide
+  ;; (after "condition" and "skip-count" processing) to run this
+  ;; trap's behaviour.
+  (behaviour #:init-value '() #:init-keyword #:behaviour)
+  ;; "repeat-identical-behaviour" slot: normally, if multiple <trap>
+  ;; objects are triggered by the same low level trap, and they
+  ;; request the same behaviour, it's only useful to do that behaviour
+  ;; once (per low level trap); so by default multiple requests for
+  ;; the same behaviour are coalesced.  If this slot is non-#f, the
+  ;; contents of the "behaviour" slot are uniquified so that they
+  ;; avoid being coalesced in this way.
+  (repeat-identical-behaviour #:init-value #f
+                             #:init-keyword #:repeat-identical-behaviour)
+  ;; "observer" slot: this is a procedure that is called with one
+  ;; EVENT argument when the trap status changes in certain
+  ;; interesting ways, currently the following.  (1) When the trap is
+  ;; uninstalled because of the target becoming inaccessible; EVENT in
+  ;; this case is 'target-gone.
+  (observer #:init-value #f #:init-keyword #:observer))
+
+(define last-assigned-trap-number 0)
+(define all-traps (make-weak-value-hash-table 7))
+
+(define-method (initialize (trap <trap>) initargs)
+  (next-method)
+  ;; Assign a trap number, and store in the hash of all traps.
+  (set! last-assigned-trap-number (+ last-assigned-trap-number 1))
+  (slot-set! trap 'number last-assigned-trap-number)
+  (hash-set! all-traps last-assigned-trap-number trap)
+  ;; Listify the behaviour slot, if not a list already.
+  (let ((behaviour (slot-ref trap 'behaviour)))
+    (if (procedure? behaviour)
+       (slot-set! trap 'behaviour (list behaviour)))))
+
+(define-generic install-trap)          ; provided mostly by subclasses
+(define-generic uninstall-trap)                ; provided mostly by subclasses
+(define-generic trap->behaviour)       ; provided by <trap>
+(define-generic trap-runnable?)                ; provided by subclasses
+
+(define-method (install-trap (trap <trap>))
+  (if (slot-ref trap 'installed)
+      (error "Trap is already installed"))
+  (slot-set! trap 'installed #t))
+
+(define-method (uninstall-trap (trap <trap>))
+  (or (slot-ref trap 'installed)
+      (error "Trap is not installed"))
+  (slot-set! trap 'installed #f))
+
+;;; uniquify-behaviour
+;;;
+;;; Uniquify BEHAVIOUR by wrapping it in a new lambda.
+(define (uniquify-behaviour behaviour)
+  (lambda (trap-context)
+    (behaviour trap-context)))
+
+;;; trap->behaviour
+;;;
+;;; If TRAP is runnable, given TRAP-CONTEXT, return a list of
+;;; behaviour procs to call with TRAP-CONTEXT as a parameter.
+;;; Otherwise return the empty list.
+(define-method (trap->behaviour (trap <trap>) (trap-context <trap-context>))
+  (if (and
+       ;; Check that the trap is runnable.  Runnability is implemented
+       ;; by the subclass and allows us to check, for example, that
+       ;; the procedure being applied in an apply-frame trap matches
+       ;; this trap's procedure.
+       (trap-runnable? trap trap-context)
+       ;; Check the additional condition, if specified.
+       (let ((condition (slot-ref trap 'condition)))
+        (or (not condition)
+            ((condition))))
+       ;; Check for a skip count.
+       (let ((skip-count (slot-ref trap 'skip-count)))
+        (if (zero? skip-count)
+            #t
+            (begin
+              (slot-set! trap 'skip-count (- skip-count 1))
+              #f))))
+      ;; All checks passed, so we will return the contents of this
+      ;; trap's behaviour slot.
+      (begin
+       ;; First, though, remove this trap if its single-shot slot
+       ;; indicates that it should fire only once.
+       (if (slot-ref trap 'single-shot)
+           (uninstall-trap trap))
+       ;; Add this trap object to the context's list of traps which
+       ;; fired here.
+       (slot-set! trap-context 'fired-traps
+                  (cons trap (tc:fired-traps trap-context)))
+       ;; Return trap behaviour, uniquified if necessary.
+       (if (slot-ref trap 'repeat-identical-behaviour)
+           (map uniquify-behaviour (slot-ref trap 'behaviour))
+           (slot-ref trap 'behaviour)))
+      '()))
+
+;;; Class: <procedure-trap>
+;;;
+;;; An installed instance of <procedure-trap> triggers on invocation
+;;; of a specific procedure.
+(define-class <procedure-trap> (<trap>)
+  ;; "procedure" slot: the procedure to trap on.  This is implemented
+  ;; virtually, using the following weak vector slot, so as to avoid
+  ;; this trap preventing the GC of the target procedure.
+  (procedure #:init-keyword #:procedure
+            #:allocation #:virtual
+            #:slot-ref
+            (lambda (trap)
+              (vector-ref (slot-ref trap 'procedure-wv) 0))
+            #:slot-set!
+            (lambda (trap proc)
+              (if (slot-bound? trap 'procedure-wv)
+                  (vector-set! (slot-ref trap 'procedure-wv) 0 proc)
+                  (slot-set! trap 'procedure-wv (weak-vector proc)))))
+  (procedure-wv))
+
+;; Customization of the initialize method: set up to handle what
+;; should happen when the procedure is GC'd.
+(define-method (initialize (trap <procedure-trap>) initargs)
+  (next-method)
+  (let* ((proc (slot-ref trap 'procedure))
+        (existing-traps (volatile-target-traps proc)))
+    ;; If this is the target's first trap, give the target procedure
+    ;; to the volatile-target-guardian, so we can find out if it
+    ;; becomes inaccessible.
+    (or existing-traps (volatile-target-guardian proc))
+    ;; Add this trap to the target procedure's list of traps.
+    (set! (volatile-target-traps proc)
+         (cons trap (or existing-traps '())))))
+
+(define procedure-trace-count (make-object-property))
+
+(define-method (install-trap (trap <procedure-trap>))
+  (next-method)
+  (let* ((proc (slot-ref trap 'procedure))
+         (trace-count (or (procedure-trace-count proc) 0)))
+    (set-procedure-property! proc 'trace #t)
+    (set! (procedure-trace-count proc) (+ trace-count 1)))
+  (install-trace-trap trap))
+
+(define-method (uninstall-trap (trap <procedure-trap>))
+  (next-method)
+  (let* ((proc (slot-ref trap 'procedure))
+         (trace-count (or (procedure-trace-count proc) 0)))
+    (if (= trace-count 1)
+        (set-procedure-property! proc 'trace #f))
+    (set! (procedure-trace-count proc) (- trace-count 1)))
+  (uninstall-trace-trap trap))
+
+(define-method (trap-runnable? (trap <procedure-trap>)
+                              (trap-context <trap-context>))
+  (eq? (slot-ref trap 'procedure)
+       (frame-procedure (tc:frame trap-context))))
+
+;;; Class: <exit-trap>
+;;;
+;;; An installed instance of <exit-trap> triggers on stack frame exit
+;;; past a specified stack depth.
+(define-class <exit-trap> (<trap>)
+  ;; "depth" slot: the reference depth for the trap.
+  (depth #:init-keyword #:depth))
+
+(define-method (install-trap (trap <exit-trap>))
+  (next-method)
+  (install-exit-frame-trap trap))
+
+(define-method (uninstall-trap (trap <exit-trap>))
+  (next-method)
+  (uninstall-exit-frame-trap trap))
+
+(define-method (trap-runnable? (trap <exit-trap>)
+                              (trap-context <trap-context>))
+  (<= (tc:exit-depth trap-context)
+      (slot-ref trap 'depth)))
+
+;;; Class: <entry-trap>
+;;;
+;;; An installed instance of <entry-trap> triggers on any frame entry.
+(define-class <entry-trap> (<trap>))
+
+(define-method (install-trap (trap <entry-trap>))
+  (next-method)
+  (install-enter-frame-trap trap))
+
+(define-method (uninstall-trap (trap <entry-trap>))
+  (next-method)
+  (uninstall-enter-frame-trap trap))
+
+(define-method (trap-runnable? (trap <entry-trap>)
+                              (trap-context <trap-context>))
+  #t)
+
+;;; Class: <apply-trap>
+;;;
+;;; An installed instance of <apply-trap> triggers on any procedure
+;;; application.
+(define-class <apply-trap> (<trap>))
+
+(define-method (install-trap (trap <apply-trap>))
+  (next-method)
+  (install-apply-frame-trap trap))
+
+(define-method (uninstall-trap (trap <apply-trap>))
+  (next-method)
+  (uninstall-apply-frame-trap trap))
+
+(define-method (trap-runnable? (trap <apply-trap>)
+                              (trap-context <trap-context>))
+  #t)
+
+;;; Class: <step-trap>
+;;;
+;;; An installed instance of <step-trap> triggers on the next frame
+;;; entry, exit or application, optionally with source location inside
+;;; a specified file.
+(define-class <step-trap> (<exit-trap>)
+  ;; "file-name" slot: if non-#f, indicates that this trap should
+  ;; trigger only for steps in source code from the specified file.
+  (file-name #:init-value #f #:init-keyword #:file-name)
+  ;; "exit-depth" slot: when non-#f, indicates that the next step may
+  ;; be a frame exit past this depth; otherwise, indicates that the
+  ;; next step must be an application or a frame entry.
+  (exit-depth #:init-value #f #:init-keyword #:exit-depth))
+
+(define-method (initialize (trap <step-trap>) initargs)
+  (next-method)
+  (slot-set! trap 'depth (slot-ref trap 'exit-depth)))
+
+(define-method (install-trap (trap <step-trap>))
+  (next-method)
+  (install-enter-frame-trap trap)
+  (install-apply-frame-trap trap))
+
+(define-method (uninstall-trap (trap <step-trap>))
+  (next-method)
+  (uninstall-enter-frame-trap trap)
+  (uninstall-apply-frame-trap trap))
+
+(define-method (trap-runnable? (trap <step-trap>)
+                              (trap-context <trap-context>))
+  (if (eq? (tc:type trap-context) #:return)
+      ;; We're in the context of an exit-frame trap.  Trap should only
+      ;; be run if exit-depth is set and this exit-frame has returned
+      ;; past the set depth.
+      (and (slot-ref trap 'exit-depth)
+          (next-method)
+          ;; OK to run the trap here, but we should first reset the
+          ;; exit-depth slot to indicate that the step after this one
+          ;; must be an application or frame entry.
+          (begin
+            (slot-set! trap 'exit-depth #f)
+            #t))
+      ;; We're in the context of an application or frame entry trap.
+      ;; Check whether trap is limited to a specified file.
+      (let ((file-name (slot-ref trap 'file-name)))
+       (and (or (not file-name)
+                (equal? (frame-file-name (tc:frame trap-context)) file-name))
+            ;; Trap should run here, but we should also set exit-depth to
+            ;; the current stack length, so that - if we don't stop at any
+            ;; other steps first - the next step shows the return value of
+            ;; the current application or evaluation.
+            (begin
+              (slot-set! trap 'exit-depth (tc:depth trap-context))
+              (slot-set! trap 'depth (tc:depth trap-context))
+              #t)))))
+
+(define (frame->source-position frame)
+  (let ((source (if (frame-procedure? frame)
+                   (or (frame-source frame)
+                       (let ((proc (frame-procedure frame)))
+                         (and proc
+                              (procedure? proc)
+                              (procedure-source proc))))
+                   (frame-source frame))))
+    (and source
+        (string? (source-property source 'filename))
+        (list (source-property source 'filename)
+              (source-property source 'line)
+              (source-property source 'column)))))
+
+(define (frame-file-name frame)
+  (cond ((frame->source-position frame) => car)
+       (else #f)))
+
+;;; Class: <source-trap>
+;;;
+;;; An installed instance of <source-trap> triggers upon evaluation of
+;;; a specified source expression.
+(define-class <source-trap> (<trap>)
+  ;; "expression" slot: the expression to trap on.  This is
+  ;; implemented virtually, using the following weak vector slot, so
+  ;; as to avoid this trap preventing the GC of the target source
+  ;; code.
+  (expression #:init-keyword #:expression
+             #:allocation #:virtual
+             #:slot-ref
+             (lambda (trap)
+               (vector-ref (slot-ref trap 'expression-wv) 0))
+             #:slot-set!
+             (lambda (trap expr)
+               (if (slot-bound? trap 'expression-wv)
+                   (vector-set! (slot-ref trap 'expression-wv) 0 expr)
+                   (slot-set! trap 'expression-wv (weak-vector expr)))))
+  (expression-wv)
+  ;; source property slots - for internal use only
+  (filename)
+  (line)
+  (column))
+
+;; Customization of the initialize method: get and save the
+;; expression's source properties, or signal an error if it doesn't
+;; have the necessary properties.
+(define-method (initialize (trap <source-trap>) initargs)
+  (next-method)
+  (let* ((expr (slot-ref trap 'expression))
+        (filename (source-property expr 'filename))
+         (line (source-property expr 'line))
+         (column (source-property expr 'column))
+        (existing-traps (volatile-target-traps expr)))
+    (or (and filename line column)
+        (error "Specified source does not have the necessary properties"
+               filename line column))
+    (slot-set! trap 'filename filename)
+    (slot-set! trap 'line line)
+    (slot-set! trap 'column column)
+    ;; If this is the target's first trap, give the target expression
+    ;; to the volatile-target-guardian, so we can find out if it
+    ;; becomes inaccessible.
+    (or existing-traps (volatile-target-guardian expr))
+    ;; Add this trap to the target expression's list of traps.
+    (set! (volatile-target-traps expr)
+         (cons trap (or existing-traps '())))))
+
+;; Just in case more than one trap is installed on the same source
+;; expression ... so that we can still get the setting and resetting
+;; of the 'breakpoint source property correct.
+(define source-breakpoint-count (make-object-property))
+
+(define-method (install-trap (trap <source-trap>))
+  (next-method)
+  (let* ((expr (slot-ref trap 'expression))
+         (breakpoint-count (or (source-breakpoint-count expr) 0)))
+    (set-source-property! expr 'breakpoint #t)
+    (set! (source-breakpoint-count expr) (+ breakpoint-count 1)))
+  (install-breakpoint-trap trap))
+
+(define-method (uninstall-trap (trap <source-trap>))
+  (next-method)
+  (let* ((expr (slot-ref trap 'expression))
+         (breakpoint-count (or (source-breakpoint-count expr) 0)))
+    (if (= breakpoint-count 1)
+        (set-source-property! expr 'breakpoint #f))
+    (set! (source-breakpoint-count expr) (- breakpoint-count 1)))
+  (uninstall-breakpoint-trap trap))
+
+(define-method (trap-runnable? (trap <source-trap>)
+                              (trap-context <trap-context>))
+  (or (eq? (slot-ref trap 'expression)
+           (tc:expression trap-context))
+      (let ((trap-location (frame->source-position (tc:frame trap-context))))
+        (and trap-location
+             (string=? (car trap-location) (slot-ref trap 'filename))
+             (= (cadr trap-location) (slot-ref trap 'line))
+             (= (caddr trap-location) (slot-ref trap 'column))))))
+
+;; (trap-here EXPRESSION . OPTIONS)
+(define trap-here
+  (procedure->memoizing-macro
+   (lambda (expr env)
+     (let ((trap (apply make
+                        <source-trap>
+                        #:expression expr
+                        (local-eval `(list ,@(cddr expr))
+                                    env))))
+       (install-trap trap)
+       (set-car! expr 'begin)
+       (set-cdr! (cdr expr) '())
+       expr))))
+
+;;; Class: <location-trap>
+;;;
+;;; An installed instance of <location-trap> triggers on entry to a
+;;; frame with a more-or-less precisely specified source location.
+(define-class <location-trap> (<trap>)
+  ;; "file-regexp" slot: regexp matching the name(s) of the file(s) to
+  ;; trap in.
+  (file-regexp #:init-keyword #:file-regexp)
+  ;; "line" and "column" slots: position to trap at (0-based).
+  (line #:init-value #f #:init-keyword #:line)
+  (column #:init-value #f #:init-keyword #:column)
+  ;; "compiled-regexp" slot - self explanatory, internal use only
+  (compiled-regexp))
+
+(define-method (initialize (trap <location-trap>) initargs)
+  (next-method)
+  (slot-set! trap 'compiled-regexp
+             (make-regexp (slot-ref trap 'file-regexp))))
+
+(define-method (install-trap (trap <location-trap>))
+  (next-method)
+  (install-enter-frame-trap trap))
+
+(define-method (uninstall-trap (trap <location-trap>))
+  (next-method)
+  (uninstall-enter-frame-trap trap))
+
+(define-method (trap-runnable? (trap <location-trap>)
+                              (trap-context <trap-context>))
+  (and-let* ((trap-location (frame->source-position (tc:frame trap-context)))
+            (tcline (cadr trap-location))
+            (tccolumn (caddr trap-location)))
+    (and (= tcline (slot-ref trap 'line))
+        (= tccolumn (slot-ref trap 'column))
+         (regexp-exec (slot-ref trap 'compiled-regexp)
+                     (car trap-location) 0))))
+
+;;; {Misc Trap Utilities}
+
+(define (get-trap number)
+  (hash-ref all-traps number))
+
+(define (list-traps)
+  (for-each describe
+           (map cdr (sort (hash-fold acons '() all-traps)
+                          (lambda (x y) (< (car x) (car y)))))))
+
+;;; {Volatile Traps}
+;;;
+;;; Some traps are associated with Scheme objects that are likely to
+;;; be GC'd, such as procedures and read expressions.  When those
+;;; objects are GC'd, we want to allow their traps to evaporate as
+;;; well, or at least not to prevent them from doing so because they
+;;; are (now pointlessly) included on the various installed trap
+;;; lists.
+
+;; An object property that maps each volatile target to the list of
+;; traps that are installed on it.
+(define volatile-target-traps (make-object-property))
+
+;; A guardian that tells us when a volatile target is no longer
+;; accessible.
+(define volatile-target-guardian (make-guardian))
+
+;; An after GC hook that checks for newly inaccessible targets.
+(add-hook! after-gc-hook
+          (lambda ()
+            (trc 'after-gc-hook)
+            (let loop ((target (volatile-target-guardian)))
+              (if target
+                   ;; We have a target which is now inaccessible.  Get
+                   ;; the list of traps installed on it.
+                  (begin
+                    (trc 'after-gc-hook "got target")
+                    ;; Uninstall all the traps that are installed on
+                    ;; this target.
+                    (for-each (lambda (trap)
+                                (trc 'after-gc-hook "got trap")
+                                ;; If the trap is still installed,
+                                ;; uninstall it.
+                                (if (slot-ref trap 'installed)
+                                    (uninstall-trap trap))
+                                ;; If the trap has an observer, tell
+                                ;; it that the target has gone.
+                                (cond ((slot-ref trap 'observer)
+                                       =>
+                                       (lambda (proc)
+                                         (trc 'after-gc-hook "call obs")
+                                         (proc 'target-gone)))))
+                              (or (volatile-target-traps target) '()))
+                     ;; Check for any more inaccessible targets.
+                    (loop (volatile-target-guardian)))))))
+
+(define (without-traps thunk)
+  (with-traps (lambda ()
+               (trap-disable 'traps)
+               (thunk))))
+
+(define guile-trap-features
+  ;; Helper procedure, to test whether a specific possible Guile
+  ;; feature is supported.
+  (let ((supported?
+         (lambda (test-feature)
+           (case test-feature
+             ((tweaking)
+              ;; Tweaking is supported if the description of the cheap
+              ;; traps option includes the word "obsolete", or if the
+              ;; option isn't there any more.
+              (and (string>=? (version) "1.7")
+                   (let ((cheap-opt-desc
+                          (assq 'cheap (debug-options-interface 'help))))
+                     (or (not cheap-opt-desc)
+                         (string-match "obsolete" (caddr cheap-opt-desc))))))
+             (else
+              (error "Unexpected feature name:" test-feature))))))
+    ;; Compile the list of actually supported features from all
+    ;; possible features.
+    (let loop ((possible-features '(tweaking))
+               (actual-features '()))
+      (if (null? possible-features)
+          (reverse! actual-features)
+          (let ((test-feature (car possible-features)))
+            (loop (cdr possible-features)
+                  (if (supported? test-feature)
+                      (cons test-feature actual-features)
+                      actual-features)))))))
+
+;; Make sure that traps are enabled.
+(trap-enable 'traps)
+
+;;; (ice-9 debugging traps) ends here.