├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── debug ├── info.rkt ├── lang │ ├── language-info.rkt │ ├── reader.rkt │ └── runtime-config.rkt ├── no-output │ └── lang │ │ └── reader.rkt ├── private │ └── make-variable-like-transformer.rkt ├── reader.rkt ├── repl.rkt ├── report.rkt ├── report │ └── helpers.rkt ├── scribblings │ └── debug.scrbl └── test │ ├── debug-repl-macros.rkt │ ├── debug-repl.rkt │ ├── test-util.rkt │ └── test.rkt ├── info.rkt └── typed └── debug ├── report.rkt ├── report └── helpers.rkt └── test ├── typed-racket.rkt └── typed-with-reader.rkt /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: CI 3 | jobs: 4 | build: 5 | name: "Build on Racket '${{ matrix.racket-version }}' (${{ matrix.racket-variant }})" 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | racket-version: ["stable", "current"] 10 | racket-variant: ["BC", "CS"] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: Bogdanp/setup-racket@v0.12 14 | with: 15 | architecture: x64 16 | distribution: full 17 | variant: ${{ matrix.racket-variant }} 18 | version: ${{ matrix.racket-version }} 19 | - name: Installing debug and its dependencies 20 | run: raco pkg install --no-docs --auto --link --name debug 21 | - name: Compiling debug and building its docs 22 | run: raco setup --check-pkg-deps --unused-pkg-deps debug typed 23 | - name: Testing debug 24 | run: raco test -p debug 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Racket-generated files 2 | compiled/ 3 | doc/ 4 | 5 | # Editor-generated files 6 | *~ 7 | \#* 8 | .\#* 9 | 10 | # OS-generated files 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | debug 2 | 3 | MIT License 4 | 5 | Copyright (c) 2015-2022 Alex Knauth, Suzanne Soy, Matthew Butterick, and Sorawee Porncharoenwase 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | debug 2 | == 3 | A lang-extension for debugging, based on sugar/debug from [mbutterick/sugar](https://github.com/mbutterick/sugar) 4 | 5 | documentation: http://pkg-build.racket-lang.org/doc/debug/index.html 6 | 7 | ### `#lang debug` 8 | 9 | To debug the value of an expression, simply put `debug` in front of the language at the top of 10 | the file (for instance `#lang debug racket`), and put `#R`, `#RR` or `#RRR` in front of the 11 | expression. 12 | 13 | - `#R` reports the value and returns it 14 | - `#RR` reports the value with a line number and returns it 15 | - `#RRR` reports the value with the file and line number, and returns it 16 | 17 | ```racket 18 | #lang debug racket 19 | #R(+ 1 2) 20 | ``` 21 | Shows the output: 22 | ``` 23 | (+ 1 2) = 3 24 | 3 25 | ``` 26 | 27 | ```racket 28 | #lang debug racket 29 | (+ 1 2 #R(* 3 4)) 30 | ``` 31 | Shows the output: 32 | ``` 33 | (* 3 4) = 12 34 | 15 35 | ``` 36 | 37 | ### `#lang debug/no-output` 38 | 39 | Allows `#R`, `#RR` and `#RRR` like `#lang debug`, but they don't add any debug output, they just return the expression inside. 40 | 41 | ### `debug-repl` 42 | 43 | ```racket 44 | > (require debug/repl) 45 | > (define (f x y) 46 | (debug-repl)) 47 | > (f 1 2) 48 | -> ; in the debug-repl now 49 | x 50 | 1 51 | -> y 52 | 2 53 | -> (+ x y) 54 | 3 55 | -> ; exit the debug-repl by pressing ctrl-D 56 | > ; back in the normal repl 57 | (f (λ (g a) (g a)) (list add1 4)) 58 | -> ; a new debug-repl 59 | x 60 | # 61 | -> y 62 | (list # 4) 63 | -> (x string->number "3") 64 | 3 65 | -> (x (first y) (second y)) 66 | 5 67 | -> ; exit this debug-repl by pressing ctrl-D 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /debug/info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define scribblings '(["scribblings/debug.scrbl" ()])) 4 | 5 | (define compile-omit-paths 6 | '("test/debug-repl-macros.rkt" 7 | )) 8 | -------------------------------------------------------------------------------- /debug/lang/language-info.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide get-language-info) 4 | 5 | (require racket/match) 6 | 7 | (define (get-language-info data) 8 | (define other-get-info 9 | (match data 10 | [(vector mod sym data2) 11 | ((dynamic-require mod sym) data2)] 12 | [_ (lambda (key default) default)])) 13 | (lambda (key default) 14 | (case key 15 | [(configure-runtime) 16 | (define config-vec '#[debug/lang/runtime-config configure #f]) 17 | (define other-config (other-get-info key default)) 18 | (cond [(list? other-config) (cons config-vec other-config)] 19 | [else (list config-vec)])] 20 | [else (other-get-info key default)]))) 21 | -------------------------------------------------------------------------------- /debug/lang/reader.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide (rename-out [debug-read read] 4 | [debug-read-syntax read-syntax] 5 | [debug-get-info get-info])) 6 | 7 | (require (only-in syntax/module-reader make-meta-reader) 8 | racket/syntax 9 | version/utils 10 | syntax/parse/define 11 | "../reader.rkt" 12 | (for-syntax racket/base racket/list)) 13 | 14 | (define-values (debug-read debug-read-syntax debug-get-info) 15 | (make-meta-reader 16 | 'debug 17 | "language path" 18 | (lambda (bstr) 19 | (let* ([str (bytes->string/latin-1 bstr)] 20 | [sym (string->symbol str)]) 21 | (and (module-path? sym) 22 | (vector 23 | ;; try submod first: 24 | `(submod ,sym reader) 25 | ;; fall back to /lang/reader: 26 | (string->symbol (string-append str "/lang/reader")))))) 27 | wrap-reader 28 | wrap-reader 29 | (lambda (proc) 30 | (lambda (key defval) 31 | (define (fallback) (if proc (proc key defval) defval)) 32 | (case key 33 | [else (fallback)]))))) 34 | -------------------------------------------------------------------------------- /debug/lang/runtime-config.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide configure) 4 | 5 | (require (only-in debug/reader use-debug-readtable)) 6 | 7 | (define (configure data) 8 | (use-debug-readtable)) 9 | -------------------------------------------------------------------------------- /debug/no-output/lang/reader.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide (rename-out [debug-read read] 4 | [debug-read-syntax read-syntax] 5 | [debug-get-info get-info])) 6 | 7 | (require (only-in syntax/module-reader make-meta-reader) 8 | racket/syntax 9 | version/utils 10 | syntax/parse/define 11 | (for-syntax racket/base racket/list)) 12 | 13 | ;; Defines the same reader syntax as the language defined by debug/lang/reader.rkt, 14 | ;; but doesn't acutually do anything 15 | 16 | (define report-char #\R) 17 | 18 | (define-simple-macro (require-a-lot require-spec) 19 | #:with [i ...] (range -10 11) 20 | (require (for-meta i require-spec) ...)) 21 | 22 | (require-a-lot racket/base) 23 | 24 | (define (make-debug-readtable [rt (current-readtable)]) 25 | (make-readtable rt report-char 'dispatch-macro report-proc)) 26 | 27 | (define (wrap-reader reader) 28 | (define (rd . args) 29 | (parameterize ([current-readtable (make-debug-readtable (current-readtable))]) 30 | (apply reader args))) 31 | rd) 32 | 33 | 34 | (define (report-proc c in src ln col pos) 35 | (define c2 (peek-char in)) 36 | (define c3 (peek-char in 1)) 37 | (cond [(and (char=? c3 report-char) (char=? c2 report-char)) 38 | (read-char in) 39 | (read-char in) 40 | (read-syntax/recursive src in)] 41 | [(char=? c2 report-char) 42 | (read-char in) 43 | (read-syntax/recursive src in)] 44 | [else 45 | (read-syntax/recursive src in)])) 46 | 47 | 48 | (define-values (debug-read debug-read-syntax debug-get-info) 49 | (make-meta-reader 50 | 'debug/no-output 51 | "language path" 52 | (lambda (bstr) 53 | (let* ([str (bytes->string/latin-1 bstr)] 54 | [sym (string->symbol str)]) 55 | (and (module-path? sym) 56 | (vector 57 | ;; try submod first: 58 | `(submod ,sym reader) 59 | ;; fall back to /lang/reader: 60 | (string->symbol (string-append str "/lang/reader")))))) 61 | wrap-reader 62 | wrap-reader 63 | (lambda (proc) 64 | (lambda (key defval) 65 | (define (fallback) (if proc (proc key defval) defval)) 66 | (case key 67 | [else (fallback)]))))) 68 | -------------------------------------------------------------------------------- /debug/private/make-variable-like-transformer.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide make-variable-like-transformer) 4 | 5 | (define make-variable-like-transformer 6 | (with-handlers 7 | ([exn:fail:filesystem:missing-module? 8 | (λ (e) 9 | (dynamic-require 'unstable/syntax 10 | 'make-variable-like-transformer))]) 11 | (dynamic-require 'syntax/transformer 12 | 'make-variable-like-transformer))) 13 | 14 | -------------------------------------------------------------------------------- /debug/reader.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide make-debug-readtable 4 | use-debug-readtable 5 | current-syntax-introducer 6 | wrap-reader) 7 | 8 | (require (only-in syntax/module-reader make-meta-reader) 9 | racket/syntax 10 | version/utils 11 | syntax/parse/define 12 | (for-syntax racket/base racket/list)) 13 | 14 | (define-simple-macro (require-a-lot require-spec) 15 | #:with [i ...] (range -10 11) 16 | (require (for-meta i require-spec) ...)) 17 | 18 | (require-a-lot racket/base) 19 | 20 | 21 | ;; originally from mbutterick/sugar, sugar/debug.rkt, reader submodule 22 | ;; https://github.com/mbutterick/sugar/blob/0ffe3173879cef51d29b4c91a336a4de6c3f8ef8/sugar/debug.rkt 23 | 24 | (define report-char #\R) 25 | (define name-char #\N) 26 | 27 | 28 | (define (make-debug-readtable [rt (current-readtable)]) 29 | (make-readtable rt report-char 'dispatch-macro report-proc)) 30 | 31 | 32 | (define (use-debug-readtable [orig-rt (current-readtable)]) 33 | (port-count-lines! (current-input-port)) 34 | (current-readtable (make-debug-readtable orig-rt))) 35 | 36 | 37 | ;; current-syntax-introducer : (Parameterof [Syntax -> Syntax]) 38 | (define current-syntax-introducer 39 | (make-parameter (λ (x) x))) 40 | 41 | ;; current-intro-id-syntax : (Parameterof (U #false Syntax)) 42 | ;; A value of #false means that it is not nested within another debug expression 43 | ;; A syntax object means that it is nested within another debug expression, 44 | ;; where the macros are already bound, and it should use `datum->syntax` with 45 | ;; the syntax object value as the lexical context context. 46 | (define current-intro-id-syntax 47 | (make-parameter #false)) 48 | 49 | ;; Any -> Any 50 | (define (add-language-info-prop stx) 51 | (cond 52 | [(syntax? stx) 53 | (define old-prop (syntax-property stx 'module-language)) 54 | (define new-prop `#(debug/lang/language-info get-language-info ,old-prop)) 55 | (syntax-property stx 'module-language new-prop)] 56 | [else stx])) 57 | 58 | (define (wrap-reader reader) 59 | (define (rd . args) 60 | (define intro 61 | (cond [(procedure-arity-includes? make-syntax-introducer 1) 62 | (make-syntax-introducer #t)] 63 | [else 64 | (make-syntax-introducer)])) 65 | (parameterize ([current-readtable (make-debug-readtable (current-readtable))] 66 | [current-syntax-introducer intro]) 67 | (define stx (add-language-info-prop (apply reader args))) 68 | (if (and (syntax? stx) (version<=? "6.2.900.4" (version))) 69 | (intro stx) 70 | stx))) 71 | rd) 72 | 73 | ;; ------------------------------------------------------------------- 74 | 75 | ;; maybe-add-let 76 | (define (maybe-add-local-require ctxt debug-expr) 77 | (define/with-syntax expr debug-expr) 78 | (cond [(current-intro-id-syntax) 79 | #'expr] 80 | [else 81 | (define/with-syntax debug/report (datum->syntax ctxt 'debug/report)) 82 | #'(let () 83 | (local-require debug/report) 84 | expr)])) 85 | 86 | 87 | (define (report-proc c in src ln col pos) 88 | (define c2 (peek-char in)) 89 | (define c3 (peek-char in 1)) 90 | (define c4 (peek-char in 2)) 91 | (define intro (current-syntax-introducer)) 92 | (cond [(and (char=? c2 report-char) (char=? c3 report-char) (char=? c4 name-char)) 93 | (read-char in) 94 | (read-char in) 95 | (read-char in) 96 | (define/with-syntax ctxt (or (current-intro-id-syntax) #'here)) 97 | (define/with-syntax report/file (datum->syntax #'ctxt 'report/file)) 98 | (define/with-syntax name (intro (read-syntax/recursive src in))) 99 | (define/with-syntax stx 100 | (parameterize ([current-intro-id-syntax #'ctxt]) 101 | (intro (read-syntax/recursive src in)))) 102 | (intro 103 | (maybe-add-local-require #'ctxt 104 | #'(report/file stx name)))] 105 | [(and (char=? c2 report-char) (char=? c3 name-char)) 106 | (read-char in) 107 | (read-char in) 108 | (define/with-syntax ctxt (or (current-intro-id-syntax) #'here)) 109 | (define/with-syntax report/line (datum->syntax #'ctxt 'report/line)) 110 | (define/with-syntax name (intro (read-syntax/recursive src in))) 111 | (define/with-syntax stx 112 | (parameterize ([current-intro-id-syntax #'ctxt]) 113 | (intro (read-syntax/recursive src in)))) 114 | (intro 115 | (maybe-add-local-require #'ctxt 116 | #'(report/line stx name)))] 117 | [(char=? c2 name-char) 118 | (read-char in) 119 | (define/with-syntax ctxt (or (current-intro-id-syntax) #'here)) 120 | (define/with-syntax report (datum->syntax #'ctxt 'report)) 121 | (define/with-syntax name (intro (read-syntax/recursive src in))) 122 | (define/with-syntax stx 123 | (parameterize ([current-intro-id-syntax #'ctxt]) 124 | (intro (read-syntax/recursive src in)))) 125 | (intro 126 | (maybe-add-local-require #'ctxt 127 | #'(report stx name)))] 128 | [(and (char=? c3 report-char) (char=? c2 report-char)) 129 | (read-char in) 130 | (read-char in) 131 | (define/with-syntax ctxt (or (current-intro-id-syntax) #'here)) 132 | (define/with-syntax report/file (datum->syntax #'ctxt 'report/file)) 133 | (define/with-syntax stx 134 | (parameterize ([current-intro-id-syntax #'ctxt]) 135 | (intro (read-syntax/recursive src in)))) 136 | (intro 137 | (maybe-add-local-require #'ctxt 138 | #'(report/file stx)))] 139 | [(char=? c2 report-char) 140 | (read-char in) 141 | (define/with-syntax ctxt (or (current-intro-id-syntax) #'here)) 142 | (define/with-syntax report/line (datum->syntax #'ctxt 'report/line)) 143 | (define/with-syntax stx 144 | (parameterize ([current-intro-id-syntax #'ctxt]) 145 | (intro (read-syntax/recursive src in)))) 146 | (intro 147 | (maybe-add-local-require #'ctxt 148 | #'(report/line stx)))] 149 | [else 150 | (define/with-syntax ctxt (or (current-intro-id-syntax) #'here)) 151 | (define/with-syntax report (datum->syntax #'ctxt 'report)) 152 | (define/with-syntax stx 153 | (parameterize ([current-intro-id-syntax #'ctxt]) 154 | (intro (read-syntax/recursive src in)))) 155 | (intro 156 | (maybe-add-local-require #'ctxt 157 | #'(report stx)))])) 158 | 159 | -------------------------------------------------------------------------------- /debug/repl.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide debug-repl resume) 4 | 5 | (require "private/make-variable-like-transformer.rkt" 6 | racket/list 7 | racket/splicing 8 | (for-syntax racket/base 9 | racket/list 10 | syntax/parse 11 | pretty-format 12 | )) 13 | 14 | (define current-debug-repl-escape (make-parameter #f)) 15 | 16 | (begin-for-syntax 17 | ;; syntax-find-local-variables : Syntax -> (Listof Id) 18 | (define (syntax-find-local-variables stx) 19 | (define debug-info (syntax-debug-info stx (syntax-local-phase-level) #t)) 20 | (unless (hash-has-key? debug-info 'bindings) 21 | (pretty-eprintf 22 | (string-append 23 | "warning: debug-repl cannot find the local bindings\n" 24 | " debug-info: ~v\n") 25 | debug-info)) 26 | (define context (hash-ref debug-info 'context)) 27 | (define bindings (hash-ref debug-info 'bindings '())) 28 | (remove-duplicates 29 | (for/list ([binding (in-list bindings)] 30 | #:when (hash-has-key? binding 'local) 31 | #:when (context-subset? (hash-ref binding 'context) context)) 32 | (datum->syntax stx (hash-ref binding 'name) stx)) 33 | bound-identifier=?)) 34 | 35 | ;; context-subset? : Context Context -> Boolean 36 | (define (context-subset? a b) 37 | ;; TODO: use an actual set-of-scopes subset function 38 | (list-prefix? a b)) 39 | 40 | ;; non-macro-id? : Id -> Boolean 41 | (define NON-MACRO (gensym 'NON-MACRO)) 42 | (define (non-macro-id? id) 43 | (eq? NON-MACRO (syntax-local-value id (λ () NON-MACRO)))) 44 | ) 45 | 46 | (define-syntax debug-repl 47 | (lambda (stx) 48 | (syntax-parse stx 49 | [(debug-repl) 50 | #:do [(define all-vars (syntax-find-local-variables stx)) 51 | (define-values [xs ms] 52 | (partition non-macro-id? all-vars))] 53 | #:with [x ...] xs 54 | #:with [m ...] ms 55 | #:with [mv ...] (map (λ (m) 56 | (datum->syntax 57 | stx 58 | `(quote ,(syntax-local-value m)))) 59 | ms) 60 | #:with varref (syntax-local-introduce #'(#%variable-reference)) 61 | #'(debug-repl/varref+hash 62 | varref 63 | (list (list (quote-syntax x) (λ () x)) ...) 64 | (list (list (quote-syntax m) mv) ...))]))) 65 | 66 | ;; debug-repl/varref+hash : 67 | ;; Variable-Ref 68 | ;; (Listof (List Id (-> Any))) 69 | ;; (Listof (List Id Any)) 70 | ;; -> 71 | ;; Any 72 | (define (debug-repl/varref+hash varref var-list macro-list) 73 | (define ns (variable-reference->namespace varref)) 74 | (define intro (make-syntax-introducer #true)) 75 | (for ([pair (in-list var-list)]) 76 | (namespace-define-transformer-binding! 77 | ns 78 | (intro (first pair)) 79 | (make-variable-like-transformer #`(#,(second pair))))) 80 | (for ([pair (in-list macro-list)]) 81 | (namespace-define-transformer-binding! 82 | ns 83 | (intro (first pair)) 84 | (second pair))) 85 | (define old-prompt-read (current-prompt-read)) 86 | (define old-eval (current-eval)) 87 | (define (new-prompt-read) 88 | (write-char #\-) 89 | (old-prompt-read)) 90 | (define (new-eval stx) 91 | (old-eval (intro stx))) 92 | (let/ec k 93 | (parameterize ([current-namespace ns] 94 | [current-prompt-read new-prompt-read] 95 | [current-eval new-eval] 96 | [current-debug-repl-escape k]) 97 | (read-eval-print-loop)))) 98 | 99 | ;; namespace-define-transformer-binding! : Namespace Symbol Any -> Void 100 | (define (namespace-define-transformer-binding! ns sym val) 101 | (eval #`(define-syntax #,(datum->syntax #f sym) #,val) ns)) 102 | 103 | ;; resume : Any ... -> Nothing 104 | (define (resume . vs) 105 | (define k (current-debug-repl-escape)) 106 | (unless k 107 | (error 'resume "must be called within a debug-repl")) 108 | (apply k vs)) 109 | -------------------------------------------------------------------------------- /debug/report.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide (all-defined-out)) 4 | 5 | (require "report/helpers.rkt" 6 | (for-syntax racket/base)) 7 | 8 | ;; from mbutterick/sugar, typed/sugar/debug.rkt 9 | ;; https://github.com/mbutterick/sugar/blob/0ffe3173879cef51d29b4c91a336a4de6c3f8ef8/typed/sugar/debug.rkt 10 | ;; using normal racket/base so that it doesn't have a dependancy on typed racket 11 | 12 | ;; Identifiers referenced in the templates of these macros must be either: 13 | ;; - defined in racket/base, or 14 | ;; - defined in "report/helpers.rkt" with type declarations in 15 | ;; typed/debug/report/helpers 16 | ;; 17 | ;; Otherwise these macros will break in typed racket. 18 | 19 | (define-syntax (report stx) 20 | (syntax-case stx () 21 | [(_ expr) #'(report expr expr)] 22 | [(_ expr name) 23 | #'(pass-through-values 24 | (λ () expr) 25 | (effect/report 'name))])) 26 | 27 | 28 | (define-syntax (report/line stx) 29 | (syntax-case stx () 30 | [(_ expr) #'(report/line expr expr)] 31 | [(_ expr name) 32 | (with-syntax ([line (syntax-line #'expr)]) 33 | #'(pass-through-values 34 | (λ () expr) 35 | (effect/report/line 'name 'line)))])) 36 | 37 | 38 | (define-syntax (report/file stx) 39 | (syntax-case stx () 40 | [(_ expr) #'(report/file expr expr)] 41 | [(_ expr name) 42 | (with-syntax ([file (syntax-source #'expr)] 43 | [line (syntax-line #'expr)]) 44 | #'(pass-through-values 45 | (λ () expr) 46 | (effect/report/file 'name 'line 'file)))])) 47 | 48 | 49 | (define-syntax-rule (define-multi-version multi-name name) 50 | (define-syntax-rule (multi-name x (... ...)) 51 | (begin (name x) (... ...)))) 52 | 53 | (define-multi-version report* report) 54 | (define-multi-version report*/line report/line) 55 | (define-multi-version report*/file report/file) 56 | 57 | -------------------------------------------------------------------------------- /debug/report/helpers.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Any helper functions that the report macros expand into should be 4 | ;; defined in this file. 5 | 6 | ;; Type annotations for these helpers should go in 7 | ;; typed/debug/report/helpers.rkt 8 | 9 | (provide pass-through-values 10 | effect/report 11 | effect/report/line 12 | effect/report/file) 13 | 14 | (require racket/match 15 | racket/struct 16 | pretty-format) 17 | 18 | ;; pass-through-values : 19 | ;; (∀ (X ...) 20 | ;; (-> (-> (values X ...)) 21 | ;; (-> (Listof Any) Void) 22 | ;; (values X ...))) 23 | (define (pass-through-values thunk effect) 24 | (let ([lst (call-with-values thunk list)]) 25 | (effect lst) 26 | (apply values lst))) 27 | 28 | ;; effect/report : Any -> [(Listof Any) -> Void] 29 | (define ((effect/report name) expr-results) 30 | (pretty-eprintf "~a = ~v\n" 31 | name (show-results expr-results))) 32 | 33 | ;; effect/report/line : Any Natural -> [(Listof Any) -> Void] 34 | (define ((effect/report/line name line) expr-results) 35 | (pretty-eprintf "~a = ~v on line ~a\n" 36 | name (show-results expr-results) line)) 37 | 38 | ;; effect/report/file : Any Natural Any -> [(Listof Any) -> Void] 39 | (define ((effect/report/file name line file) expr-results) 40 | (pretty-eprintf "~a = ~v on line ~a in ~v\n" 41 | name (show-results expr-results) line file)) 42 | 43 | ;; ------------------------------------------------------------------- 44 | 45 | (struct printed-values (vs) 46 | #:methods gen:custom-write 47 | [(define write-proc 48 | (make-constructor-style-printer 49 | (λ (self) 'values) 50 | (λ (self) (printed-values-vs self))))]) 51 | 52 | ;; show-results : (Listof Any) -> Any 53 | (define (show-results expr-results) 54 | (match expr-results 55 | [(list expr-result) expr-result] 56 | [_ (printed-values expr-results)])) 57 | 58 | -------------------------------------------------------------------------------- /debug/scribblings/debug.scrbl: -------------------------------------------------------------------------------- 1 | #lang scribble/manual 2 | 3 | @(require (for-label racket/base 4 | debug/repl 5 | )) 6 | 7 | @title{debug} 8 | 9 | source code: @url{https://github.com/AlexKnauth/debug} 10 | 11 | A racket lang-extension for debugging, based on sugar/debug. 12 | 13 | @section{#lang debug} 14 | 15 | @defmodule[debug #:lang]{ 16 | A lang-extension (like @racketmodname[at-exp]) that allows for quick debugging 17 | shorthands to a program written in any racket-based language that looks at the 18 | readtable. 19 | } 20 | 21 | To debug the value of an expression, simply put debug in front of the language 22 | at the top of the file (for instance @hash-lang[] @racketmodname[debug] 23 | @racketmodname[racket]), and put @litchar{#R}, @litchar{#RR} or @litchar{#RRR} 24 | in front of the expression. 25 | 26 | @itemize[ 27 | @item{@bold{@litchar{#R}} reports the value and returns it} 28 | @item{@bold{@litchar{#RR}} reports the value with a line number and returns it} 29 | @item{@bold{@litchar{#RRR}} reports the value with the file and line number, and returns it} 30 | ] 31 | 32 | Examples: 33 | @codeblock{ 34 | #lang debug racket 35 | #R(+ 1 2) 36 | ;(+ 1 2) = 3 37 | ;3 38 | } 39 | 40 | @codeblock{ 41 | #lang debug racket 42 | (+ 1 2 #R(* 3 4)) 43 | ;(* 3 4) = 12 44 | ;15 45 | } 46 | 47 | @section{debug-repl} 48 | 49 | @defmodule[debug/repl] 50 | 51 | @defform[(debug-repl)]{ 52 | Creates a repl for debugging, which can access local variables in the context 53 | where it is used. 54 | 55 | For example a @racket[(debug-repl)] in a @racket[let] form 56 | @codeblock[#:keep-lang-line? #f]{ 57 | #lang racket 58 | (let ([x 1] [y 2]) 59 | (debug-repl)) 60 | } 61 | Will be able to access the @racket[x] and @racket[y] local variables (if 62 | debugging information is enabled in DrRacket's 63 | @seclink["Language" #:doc '(lib "scribblings/drracket/drracket.scrbl") 64 | #:indirect? #true]{ 65 | @onscreen{Choose Language}} window, or if the program was executed using 66 | @exec{racket -l errortrace -t myprogram.rkt}). 67 | 68 | It becomes much more useful in a function definition: 69 | @codeblock[#:keep-lang-line? #f]{ 70 | #lang racket 71 | (define (f x y) 72 | (debug-repl)) 73 | } 74 | Then if you call @racket[(f 1 2)], it will create a repl where @racket[x] is 75 | @racket[1] and @racket[y] is @racket[2]. 76 | 77 | In one of these repls, you can try evaluating different expressions. If you're 78 | debugging a higher-order function for example, you can try out the functions 79 | it accepts or creates with multiple sets of arguments to see how they react. 80 | } 81 | 82 | -------------------------------------------------------------------------------- /debug/test/debug-repl-macros.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require "../repl.rkt" 4 | "test-util.rkt" 5 | rackunit 6 | (for-syntax racket/base syntax/parse)) 7 | 8 | (define a 3) 9 | (define b 4) 10 | 11 | (test-case "local macros that don't refer to other macros" 12 | (define (f tmp) 13 | (define-syntax ?list 14 | (syntax-parser 15 | [(?list x:expr ...) 16 | (define (?list-helper acc xs) 17 | (syntax-parse (list acc xs) 18 | [([acc:id ...] []) #'(list acc ...)] 19 | [([acc:id ...] [x:expr y:expr ...]) 20 | #:with [tmp] (generate-temporaries #'[x]) 21 | #`(let ([tmp x]) 22 | (if tmp 23 | #,(?list-helper #'[acc ... tmp] #'[y ...]) 24 | #false))])) 25 | (?list-helper #'[] #'[x ...])] 26 | [stx 27 | ;; TODO: figure out how to make syntax-parse's own errors 28 | ;; not cause infinite loops 29 | (raise-syntax-error #f "bad syntax" #'stx)])) 30 | (debug-repl) 31 | tmp) 32 | 33 | (test-with-io 34 | #:i [i (open-input-string "a b tmp (?list a b tmp)")] 35 | #:o [o (open-output-string)] 36 | (check-equal? (f 1) 1) 37 | (check-equal? (get-output-string o) 38 | (string-append 39 | "-> " #;a "3\n" 40 | "-> " #;b "4\n" 41 | "-> " #;tmp "1\n" 42 | "-> " #;(?list a b tmp) "'(3 4 1)\n" 43 | "-> "))) 44 | 45 | (test-with-io 46 | #:i [i (open-input-string "(?list . bluh)")] 47 | #:o [o (open-output-string)] 48 | (check-exn #rx"\\?list: bad syntax" 49 | (λ () (f 1))) 50 | (check-equal? (get-output-string o) 51 | (string-append 52 | "-> " #;(?list . bluh))))) 53 | 54 | ;; TODO: !!! identifier used out of context !!! 55 | #; 56 | (test-case "local macros that refer to other macros" 57 | (define (f tmp) 58 | (define-syntax ?list-helper 59 | (syntax-parser 60 | [(?list-helper [acc:id ...] []) #'(list acc ...)] 61 | [(?list-helper [acc:id ...] [x:expr y:expr ...]) 62 | #'(let ([tmp x]) 63 | (if tmp 64 | (?list-helper [acc ... tmp] [y ...]) 65 | #false))])) 66 | (define-syntax-rule (?list x ...) 67 | (?list-helper [] [x ...])) 68 | (debug-repl) 69 | tmp) 70 | 71 | (test-with-io 72 | #:i [i (open-input-string "a b tmp (?list a b tmp)")] 73 | #:o [o (open-output-string)] 74 | (check-equal? (f 1) 1) 75 | (check-equal? (get-output-string o) 76 | (string-append 77 | "-> " #;a "3\n" 78 | "-> " #;b "4\n" 79 | "-> " #;tmp "1\n" 80 | "-> " #;(?list a b tmp) "'(3 4 1)\n" 81 | "-> "))) 82 | 83 | (test-with-io 84 | #:i [i (open-input-string "a b tmp (+ a b tmp)")] 85 | #:o [o (open-output-string)] 86 | (check-exn #rx"a: undefined;\n cannot use before initialization" 87 | (λ () (f 1))) 88 | (check-equal? (get-output-string o) 89 | (string-append 90 | "-> " #;b "4\n" 91 | "-> " #;(+ b 13) "17\n" 92 | "-> " #;(+ a b 13))))) 93 | -------------------------------------------------------------------------------- /debug/test/debug-repl.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require "../repl.rkt" 4 | "test-util.rkt" 5 | rackunit) 6 | 7 | (define a 3) 8 | (define b 4) 9 | 10 | (define-syntax-rule (with-other-vars body) 11 | (let ([x 5] [z 6]) 12 | ;; x and z are not available outside this scope 13 | body)) 14 | 15 | (define (f x y) 16 | ;; x and y are local variables 17 | (with-other-vars 18 | (let ([y 7] [b 8] [c 9]) 19 | ;; y, b, and c are local variables 20 | (debug-repl) 21 | x))) 22 | 23 | (test-with-io 24 | #:i [i (open-input-string "x y a b c (+ x y a b c) (with-other-vars x)")] 25 | #:o [o (open-output-string)] 26 | (check-equal? (f 1 2) 1) 27 | (check-equal? (get-output-string o) 28 | (string-append 29 | "-> " #;x "1\n" 30 | "-> " #;y "7\n" 31 | "-> " #;a "3\n" 32 | "-> " #;b "8\n" 33 | "-> " #;c "9\n" 34 | "-> " #;(+ x y a b c) "28\n" 35 | "-> " #;(with-other-vars x) "1\n" 36 | "-> ")) 37 | ) 38 | 39 | ;; test for issue #9 40 | (test-case "issue #9" 41 | (define (f) 42 | (when #true 43 | (debug-repl)) 44 | (define a 1) 45 | a) 46 | 47 | (test-with-io 48 | #:i [i (open-input-string "b (+ b 13)")] 49 | #:o [o (open-output-string)] 50 | (check-equal? (f) 1) 51 | (check-equal? (get-output-string o) 52 | (string-append 53 | "-> " #;b "4\n" 54 | "-> " #;(+ b 13) "17\n" 55 | "-> "))) 56 | 57 | (test-with-io 58 | #:i [i (open-input-string "b (+ b 13) (+ a b 13)")] 59 | #:o [o (open-output-string)] 60 | (check-exn #rx"a: undefined;\n cannot use before initialization" 61 | (λ () (f))) 62 | (check-equal? (get-output-string o) 63 | (string-append 64 | "-> " #;b "4\n" 65 | "-> " #;(+ b 13) "17\n" 66 | "-> " #;(+ a b 13))))) 67 | 68 | ;; test for mutation 69 | (define x-for-mutation 1) 70 | 71 | (test-case "test for mutation" 72 | 73 | (define (f1 x-for-mutation) 74 | (debug-repl) 75 | x-for-mutation) 76 | 77 | (define (f2) 78 | (debug-repl) 79 | x-for-mutation) 80 | 81 | (test-with-io 82 | #:i [i (open-input-string "x-for-mutation")] 83 | #:o [o (open-output-string)] 84 | (check-equal? x-for-mutation 1) 85 | (check-equal? (f1 2) 2) 86 | (check-equal? (get-output-string o) 87 | (string-append 88 | "-> " #;x-for-mutation "2\n" 89 | "-> ")) 90 | (check-equal? x-for-mutation 1)) 91 | 92 | (test-with-io 93 | #:i [i (open-input-string "x-for-mutation")] 94 | #:o [o (open-output-string)] 95 | (check-equal? x-for-mutation 1) 96 | (check-equal? (f2) 1) 97 | (check-equal? (get-output-string o) 98 | (string-append 99 | "-> " #;x-for-mutation "1\n" 100 | "-> ")) 101 | (check-equal? x-for-mutation 1))) 102 | 103 | 104 | -------------------------------------------------------------------------------- /debug/test/test-util.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide test-with-io) 4 | 5 | (define-syntax-rule (test-with-io 6 | #:i [i input-port] 7 | #:o [o output-port] 8 | body ...) 9 | (let ([i input-port] 10 | [o output-port]) 11 | (parameterize ([current-input-port i] 12 | [current-output-port o]) 13 | body ...))) 14 | 15 | -------------------------------------------------------------------------------- /debug/test/test.rkt: -------------------------------------------------------------------------------- 1 | #lang debug racket/base 2 | 3 | ;; originally from mbutterick/sugar, sugar/test/debug-meta-lang.rkt 4 | ;; https://github.com/mbutterick/sugar/blob/0ffe3173879cef51d29b4c91a336a4de6c3f8ef8/sugar/test/debug-meta-lang.rkt 5 | 6 | (require rackunit 7 | (for-meta 1 (only-in racket/base begin-for-syntax)) 8 | (for-meta 2 (only-in racket/base begin-for-syntax)) 9 | (for-meta 3 (only-in racket/base let #%app open-output-string get-output-string parameterize 10 | current-error-port #%datum) 11 | rackunit)) 12 | 13 | (let ([out (open-output-string)] 14 | [let "something else"] 15 | [local-require "something else entirely"] 16 | [only-in "completely unexpected!"] 17 | [report "well, not really"]) 18 | (parameterize ([current-error-port out]) 19 | #R5 20 | #RN x 5) 21 | (check-equal? (get-output-string out) "5 = 5\nx = 5\n")) 22 | 23 | (let ([out (open-output-string)] 24 | [report/line "outta the blue!"]) 25 | (parameterize ([current-error-port out]) 26 | #RR5 27 | #RRN x 5) 28 | (check-equal? (get-output-string out) "5 = 5 on line 26\nx = 5 on line 27\n")) 29 | 30 | (let ([out (open-output-string)]) 31 | (parameterize ([current-error-port out]) 32 | #RR (+ 1 2 #R 5 33 | #RN x (* 1 2 3))) 34 | (check-equal? (get-output-string out) 35 | "5 = 5\nx = 6\n(+ 1 2 (report 5) (report (* 1 2 3) x)) = 14 on line 32\n")) 36 | 37 | (begin-for-syntax 38 | (begin-for-syntax 39 | (begin-for-syntax 40 | (let ([out (open-output-string)]) 41 | (parameterize ([current-error-port out]) 42 | #RR5) 43 | (check-equal? (get-output-string out) "5 = 5 on line 42\n"))))) 44 | -------------------------------------------------------------------------------- /info.rkt: -------------------------------------------------------------------------------- 1 | #lang info 2 | 3 | (define collection 'multi) 4 | 5 | (define license 'MIT) 6 | 7 | ;; Require a version of racket after this commit: 8 | ;; make `variable-reference->namespace` enable top-level mode 9 | ;; d1c2daf15b8be048b5cea63d5a1d7206bfc8d43f 10 | 11 | (define deps 12 | '(["base" #:version "6.6.0.3"] 13 | "rackunit-lib" 14 | "typed-racket-lib" 15 | "pretty-format" 16 | )) 17 | 18 | (define build-deps 19 | '("rackunit-lib" 20 | "rackunit-typed" 21 | "scribble-lib" 22 | "racket-doc" 23 | "scribble-doc" 24 | )) 25 | 26 | -------------------------------------------------------------------------------- /typed/debug/report.rkt: -------------------------------------------------------------------------------- 1 | #lang typed/racket/base 2 | 3 | (require debug/report 4 | "report/helpers.rkt") 5 | 6 | (provide (all-from-out debug/report)) 7 | 8 | -------------------------------------------------------------------------------- /typed/debug/report/helpers.rkt: -------------------------------------------------------------------------------- 1 | #lang s-exp typed-racket/base-env/extra-env-lang 2 | 3 | (require debug/report/helpers) 4 | 5 | (type-environment 6 | ;; type annotations for report helpers go here: 7 | 8 | [pass-through-values 9 | ;; (∀ (X ...) 10 | ;; (-> (-> (values X ...)) 11 | ;; (-> (Listof Any) Void) 12 | ;; (values X ...))) 13 | (-polydots (x) 14 | (cl->* 15 | (-> 16 | (-> (-values-dots (list) x 'x)) 17 | (-> (-lst Univ) -Void) 18 | (-values-dots (list) x 'x)) 19 | (-> 20 | (-> ManyUniv) 21 | (-> (-lst Univ) -Void) 22 | ManyUniv)))] 23 | 24 | [effect/report (-> Univ (-> (-lst Univ) -Void))] 25 | [effect/report/line (-> Univ -Nat (-> (-lst Univ) -Void))] 26 | [effect/report/file (-> Univ -Nat Univ (-> (-lst Univ) -Void))] 27 | ) 28 | -------------------------------------------------------------------------------- /typed/debug/test/typed-racket.rkt: -------------------------------------------------------------------------------- 1 | #lang typed/racket 2 | 3 | (require typed/debug/report) 4 | (module+ test 5 | (require typed/rackunit)) 6 | 7 | (define x 5) 8 | 9 | (module+ test 10 | (define p (open-output-string)) 11 | 12 | (parameterize ([current-error-port p]) 13 | (check-equal? (ann (report x) : Positive-Byte) 5)) 14 | 15 | (check-equal? (get-output-string p) "x = 5\n")) 16 | -------------------------------------------------------------------------------- /typed/debug/test/typed-with-reader.rkt: -------------------------------------------------------------------------------- 1 | #lang debug typed/racket 2 | (require typed/debug/report) 3 | (module+ test 4 | (require typed/rackunit)) 5 | 6 | (define x 2) 7 | (define (f) #R x #R (+ x 4)) 8 | 9 | (module+ test 10 | (define p (open-output-string)) 11 | (parameterize ([current-error-port p]) 12 | #R (f)) 13 | (check-equal? (get-output-string p) "x = 2\n(+ x 4) = 6\n(f) = 6\n")) 14 | --------------------------------------------------------------------------------