返回 Skill 列表
extension
分类: 开发与工程无需 API Key

scope-capture

用于在运行时捕获和检查局部作用域的REPL调试工具。在调试函数、调查测试失败原因、理解中间值,或者需要在不手动构造值的情况下重新创建代码运行时上下文时使用。

person作者: jakexiaohubgithub

scope-capture

Capture local scope at runtime and recreate it in the REPL for debugging.

Quick Start

(require 'sc.api)

;; 1. Add spy to capture scope
(defn my-fn [x y]
  (let [result (+ x y)]
    (sc.api/spy result)))  ; Captures x, y, result

;; 2. Call the function - spy logs EP id
(my-fn 3 4)
; SPY [1 -1] ... saved scope with locals [x y result]
; => 7

;; 3. Recreate scope with defsc (defs all locals as vars)
(sc.api/defsc 1)
; => [#'user/x #'user/y #'user/result]

;; 4. Now evaluate expressions using captured values
x        ; => 3
y        ; => 4
result   ; => 7
(* x y)  ; => 12

;; 5. Clean up when done
(sc.api/dispose! 1)

Core Concepts

Code Site (CS) - A location in code where spy or brk is placed. Has a negative ID like -1, -2.

Execution Point (EP) - A specific execution of a Code Site. Has a positive ID like 1, 2, 3.

Log format: SPY [EP CS] e.g., SPY [7 -3] means Execution Point 7 at Code Site -3.

Recording Scope

spy - Capture and continue

;; Wrap an expression - captures scope AND the expression value
(sc.api/spy (some-expression))

;; Without expression - just capture scope at this point
(sc.api/spy)

;; With options
(sc.api/spy {:sc/dynamic-vars [*out* *my-var*]}
  (some-expression))

brk - Capture and block (breakpoint)

(defn process [data]
  (let [transformed (transform data)]
    (sc.api/brk transformed)))  ; Blocks here until released

;; In another thread, call the function
(future (process my-data))
; BRK [2 -1] ... saved scope, use sc.api/loose to resume

;; Release the breakpoint
(sc.api/loose 2)              ; Continue normally
(sc.api/loose-with 2 value)   ; Continue with replacement value
(sc.api/loose-with-err 2 ex)  ; Continue by throwing exception

Recreating Scope

defsc - Define vars (preferred for editor eval)

(sc.api/defsc 1)
; Defines vars for all captured locals in current namespace
; Now you can evaluate sub-expressions directly in your editor

x        ; works
(+ x y)  ; works

letsc - Let bindings

(sc.api/letsc 1
  (+ x y))  ; x and y bound from captured scope
; => 7

;; Useful for one-off evaluations without polluting namespace

ep-repl - Sub-REPL (JVM only)

(sc.repl/ep-repl 1)
; SC[1 -1]=> x
; => 3
; SC[1 -1]=> :repl/quit

Common Patterns

Spy without wrapping expression

(let [x (foo)
      y (bar x)]
  (sc.api/spy)  ; Just capture x and y here
  (something x y))

Spy only on errors

(try
  (my-fallible-code)
  (catch Throwable err
    (sc.api/spy err)  ; Capture scope when error occurs
    (throw err)))

Disable in loops

;; If spy/brk is in a loop, disable after first capture
(sc.api/disable! -1)  ; Use the Code Site ID (negative)

Combine multiple scopes

;; Capture from different places, then combine
(sc.api/defsc 1)  ; Defines a, b from first spy
(sc.api/defsc 2)  ; Defines x, y from second spy
;; Now all four vars available

Restore last execution point

(eval `(sc.api/defsc ~(sc.api/last-ep-id)))

Utility Functions

;; Get info about execution point
(sc.api/ep-info 1)
; => {:sc.ep/id 1
;     :sc.ep/local-bindings {x 3, y 4, ...}
;     :sc.ep/value 7
;     :sc.ep/code-site {...}}

;; Get info about code site
(sc.api/cs-info -1)

;; Get last execution point ID
(sc.api/last-ep-id)

;; Quiet versions (only log types, not values)
(sc.api/spyqt expr)
(sc.api/brkqt expr)

;; Clean up vars created by defsc
(sc.api/undefsc 1)

;; Free memory from execution point
(sc.api/dispose! 1)

Caveats

Memory leak warning: Remove spy/brk before production - they accumulate captured data.

Dynamic vars: Must be explicitly declared:

(sc.api/spy {:sc/dynamic-vars [*out* *my-var*]} expr)

defsc overwrites vars: If a var with same name exists (especially defonce), defsc will fail or overwrite it.

ClojureScript: Must use [ep-id cs-id] vector syntax:

;; ClojureScript requires both IDs
(sc.api/defsc [1 -1])
(sc.api/letsc [1 -1] expr)

Workflow Summary

  1. Add spy where you want to capture: (sc.api/spy expr) or just (sc.api/spy)
  2. Execute the code path (call the function, run the test, etc.)
  3. Note the EP ID from the log: SPY [1 -1]
  4. Recreate scope: (sc.api/defsc 1)
  5. Debug by evaluating sub-expressions in your editor
  6. Clean up: (sc.api/dispose! 1) and remove the spy call

Quick Reference

| Function | Purpose | |----------|---------| | (spy expr) | Capture scope + evaluate expr | | (spy) | Capture scope only | | (brk expr) | Capture scope + block execution | | (defsc ep) | Def vars from captured scope | | (letsc ep body) | Let-bind captured scope | | (loose ep) | Resume blocked brk | | (loose-with ep val) | Resume with replacement value | | (dispose! ep) | Free captured data | | (disable! cs) | Disable a code site | | (ep-info ep) | Get execution point info | | (last-ep-id) | Get most recent EP id |