Compare commits

..

28 Commits

Author SHA1 Message Date
Georges Dupéron
0ed637a4f9 Noted in the documentation that debugging information is needed in order to access local variables 2017-01-22 03:15:36 +01:00
AlexKnauth
97b96c4686 add dependency on variable-reference->namespace top-level mode 2017-01-13 12:45:08 -05:00
AlexKnauth
b0c4666587 TODO: figure out how to make syntax-parse errors not loop 2017-01-12 17:55:23 -05:00
AlexKnauth
73466e025c this doesn't support 6.6 and earlier 2017-01-12 17:45:29 -05:00
AlexKnauth
9d38abfe7a change how make-variable-like-transformer is found 2017-01-12 17:26:56 -05:00
AlexKnauth
f4f253a31a use Matthew Flatt's solution to add a fresh scope
From the racket users list here:
https://groups.google.com/d/msg/racket-users/C7Wnn-9skig/myindSBDAgAJ
2017-01-12 17:18:50 -05:00
AlexKnauth
61167ee2d5 update .travis.yml 2017-01-12 16:48:29 -05:00
AlexKnauth
15ad001f5c use splicing-let-syntax instead of namespace mangling
and fix the tests; they were completely broken before.

This breaks the macro tests again.
2017-01-12 00:38:39 -05:00
AlexKnauth
ea215a57f8 add failing mutation tests 2017-01-11 22:19:20 -05:00
AlexKnauth
113cdf2662 split debug-repl + macros tests into a separate file 2017-01-11 18:04:12 -05:00
AlexKnauth
460c4a781a factor out test-with-io 2017-01-11 17:52:28 -05:00
Spencer Florence
403b8b668e add resume to debug repl 2017-01-10 20:24:50 -05:00
AlexKnauth
0424641fce support simple macros that don't expand to other macros
TODO: fix identifier out of context error for macros that do expand to
other macros
2017-01-05 23:22:36 -05:00
AlexKnauth
a22a691377 dynamic-require from either syntax/transformer or unstable/syntax 2017-01-04 19:42:38 -05:00
AlexKnauth
b175b6e68f add test for (debug-repl) before initialization of a variable in scope
closes #9
2017-01-04 19:35:43 -05:00
AlexKnauth
11ce8f0b32 allow local variables to shadow immutable top-level variables 2017-01-04 19:26:07 -05:00
Alex Knauth
ffbbed807c use list instead of quote in debug-repl example 2016-07-19 19:30:20 -04:00
AlexKnauth
ef75cf9384 change the prompt for the debug-repl 2016-07-19 19:28:35 -04:00
Alex Knauth
86ccc2df9e add debug-repl example to the README 2016-07-19 19:05:18 -04:00
AlexKnauth
a499333464 add for-label require in docs 2016-07-15 12:36:38 -04:00
AlexKnauth
4cfda3ce1f test a use of a hygienic macro 2016-07-15 09:26:46 -04:00
AlexKnauth
5787811b11 add documentation for debug-repl 2016-07-14 18:36:18 -04:00
AlexKnauth
b1004990c9 improve debug-repl tests 2016-07-14 18:13:13 -04:00
AlexKnauth
901bef6f7f only test on 6.3 and up 2016-07-14 18:09:06 -04:00
AlexKnauth
a4120cc7eb this depends on racket version 6.3
for syntax-debug-info and list-prefix?
2016-07-14 17:49:36 -04:00
AlexKnauth
0ed6bd49fd add debug-repl 2016-07-14 17:20:06 -04:00
AlexKnauth
5d652d31fb call it a lang-extension 2016-06-21 22:17:13 -04:00
Alex Knauth
e0c1bc198a update .travis.yml to test on 6.3, 6.4, and 6.5 2016-05-08 23:26:44 -04:00
10 changed files with 414 additions and 8 deletions

View File

@ -6,8 +6,7 @@ env:
global: global:
- RACKET_DIR=~/racket - RACKET_DIR=~/racket
matrix: matrix:
- RACKET_VERSION=6.2 - RACKET_VERSION=6.7
- RACKET_VERSION=6.2.1
- RACKET_VERSION=HEAD - RACKET_VERSION=HEAD
matrix: matrix:
@ -27,6 +26,6 @@ before_script:
script: script:
- raco pkg install --deps search-auto --link debug - raco pkg install --deps search-auto --link debug
- raco test -x -p debug - raco test -p debug
after_script: after_script:

View File

@ -1,9 +1,11 @@
debug [![Build Status](https://travis-ci.org/AlexKnauth/debug.png?branch=master)](https://travis-ci.org/AlexKnauth/debug) debug [![Build Status](https://travis-ci.org/AlexKnauth/debug.png?branch=master)](https://travis-ci.org/AlexKnauth/debug)
== ==
A meta-language for debugging, based on sugar/debug from [mbutterick/sugar](https://github.com/mbutterick/sugar) A lang-extension for debugging, based on sugar/debug from [mbutterick/sugar](https://github.com/mbutterick/sugar)
documentation: http://pkg-build.racket-lang.org/doc/debug/index.html documentation: http://pkg-build.racket-lang.org/doc/debug/index.html
### `#lang debug`
To debug the value of an expression, simply put `debug` in front of the language at the top of To debug the value of an expression, simply put `debug` in front of the language at the top of
the file (for instance `#lang debug racket`), and put `#R`, `#RR` or `#RRR` in front of the the file (for instance `#lang debug racket`), and put `#R`, `#RR` or `#RRR` in front of the
expression. expression.
@ -31,3 +33,33 @@ Shows the output:
(* 3 4) = 12 (* 3 4) = 12
15 15
``` ```
### `debug-repl`
```racket
> (require debug/repl)
> (define (f x y)
(debug-repl))
> (f 1 2)
-> ; in the debug-repl now
x
1
-> y
2
-> (+ x y)
3
-> ; exit the debug-repl by pressing ctrl-D
> ; back in the normal repl
(f (λ (g a) (g a)) (list add1 4))
-> ; a new debug-repl
x
#<procedure>
-> y
(list #<procedure:add1> 4)
-> (x string->number "3")
3
-> (x (first y) (second y))
5
-> ; exit this debug-repl by pressing ctrl-D
```

View File

@ -2,3 +2,6 @@
(define scribblings '(["scribblings/debug.scrbl" ()])) (define scribblings '(["scribblings/debug.scrbl" ()]))
(define compile-omit-paths
'("test/debug-repl-macros.rkt"
))

View File

@ -0,0 +1,13 @@
#lang racket/base
(provide make-variable-like-transformer)
(define make-variable-like-transformer
(with-handlers
([exn:fail:filesystem:missing-module?
(λ (e)
(dynamic-require 'unstable/syntax
'make-variable-like-transformer))])
(dynamic-require 'syntax/transformer
'make-variable-like-transformer)))

103
debug/repl.rkt Normal file
View File

@ -0,0 +1,103 @@
#lang racket/base
(provide debug-repl resume)
(require "private/make-variable-like-transformer.rkt"
racket/list
racket/splicing
(for-syntax racket/base
racket/list
syntax/parse
))
(define debug-repl-prompt-tag (make-continuation-prompt-tag 'debug-repl))
(define debug-repl-abort-handler values)
;; ----------------------------------------------------------------------------
(begin-for-syntax
;; syntax-find-local-variables : Syntax -> (Listof Id)
(define (syntax-find-local-variables stx)
(define debug-info (syntax-debug-info stx (syntax-local-phase-level) #t))
(define context (hash-ref debug-info 'context))
(define bindings (hash-ref debug-info 'bindings))
(remove-duplicates
(for/list ([binding (in-list bindings)]
#:when (hash-has-key? binding 'local)
#:when (context-subset? (hash-ref binding 'context) context))
(datum->syntax stx (hash-ref binding 'name) stx))
bound-identifier=?))
;; context-subset? : Context Context -> Boolean
(define (context-subset? a b)
;; TODO: use an actual set-of-scopes subset function
(list-prefix? a b))
;; non-macro-id? : Id -> Boolean
(define NON-MACRO (gensym 'NON-MACRO))
(define (non-macro-id? id)
(eq? NON-MACRO (syntax-local-value id (λ () NON-MACRO))))
)
(define-syntax debug-repl
(lambda (stx)
(syntax-parse stx
[(debug-repl)
#:do [(define all-vars (syntax-find-local-variables stx))
(define-values [xs ms]
(partition non-macro-id? all-vars))]
#:with [x ...] xs
#:with [m ...] ms
#:with [mv ...] (map (λ (m)
(datum->syntax
stx
`(quote ,(syntax-local-value m))))
ms)
#:with varref (syntax-local-introduce #'(#%variable-reference))
#'(debug-repl/varref+hash
varref
(list (list (quote-syntax x) (λ () x)) ...)
(list (list (quote-syntax m) mv) ...))])))
;; debug-repl/varref+hash :
;; Variable-Ref
;; (Listof (List Id (-> Any)))
;; (Listof (List Id Any))
;; ->
;; Any
(define (debug-repl/varref+hash varref var-list macro-list)
(define ns (variable-reference->namespace varref))
(define intro (make-syntax-introducer #true))
(for ([pair (in-list var-list)])
(namespace-define-transformer-binding!
ns
(intro (first pair))
(make-variable-like-transformer #`(#,(second pair)))))
(for ([pair (in-list macro-list)])
(namespace-define-transformer-binding!
ns
(intro (first pair))
(second pair)))
(define old-prompt-read (current-prompt-read))
(define old-eval (current-eval))
(define (new-prompt-read)
(write-char #\-)
(old-prompt-read))
(define (new-eval stx)
(old-eval (intro stx)))
(parameterize ([current-namespace ns]
[current-prompt-read new-prompt-read]
[current-eval new-eval])
(call-with-continuation-prompt
read-eval-print-loop
debug-repl-prompt-tag
debug-repl-abort-handler)))
;; namespace-define-transformer-binding! : Namespace Symbol Any -> Void
(define (namespace-define-transformer-binding! ns sym val)
(eval #`(define-syntax #,(datum->syntax #f sym) #,val) ns))
;; resume : Any ... -> Nothing
(define (resume . vs)
(apply abort-current-continuation debug-repl-prompt-tag vs))

View File

@ -1,15 +1,19 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label racket/base
debug/repl
))
@title{debug} @title{debug}
source code: @url{https://github.com/AlexKnauth/debug} source code: @url{https://github.com/AlexKnauth/debug}
A racket meta-language for debugging, based on sugar/debug. A racket lang-extension for debugging, based on sugar/debug.
@section{#lang debug} @section{#lang debug}
@defmodule[debug #:lang]{ @defmodule[debug #:lang]{
A meta-language (like @racketmodname[at-exp]) that allows for quick debugging A lang-extension (like @racketmodname[at-exp]) that allows for quick debugging
shorthands to a program written in any racket-based language that looks at the shorthands to a program written in any racket-based language that looks at the
readtable. readtable.
} }
@ -39,3 +43,42 @@ Examples:
;(* 3 4) = 12 ;(* 3 4) = 12
;15 ;15
} }
@section{debug-repl}
@defmodule[debug/repl]
@defform[(debug-repl)]{
Creates a repl for debugging, which can access local variables in the context
where it is used.
For example a @racket[(debug-repl)] in a @racket[let] form
@codeblock[#:keep-lang-line? #f]{
#lang racket
(let ([x 1] [y 2])
(debug-repl))
}
Will be able to access the @racket[x] and @racket[y] local variables (if
debugging information is enabled in DrRacket's
@seclink["Language" #:doc '(lib "scribblings/drracket/drracket.scrbl")]{
@onscreen{Choose Language}} window, or if the program was executed using
@exec{racket -l errortrace -t myprogram.rkt}).
It becomes much more useful in a function definition:
@codeblock[#:keep-lang-line? #f]{
#lang racket
(define (f x y)
(debug-repl))
}
Then if you call @racket[(f 1 2)], it will create a repl where @racket[x] is
@racket[1] and @racket[y] is @racket[2].
In one of these repls, you can try evaluating different expressions. If you're
debugging a higher-order function for example, you can try out the functions
it accepts or creates with multiple sets of arguments to see how they react.
@defproc[(resume [v any/c] ...) any]{
When called inside of a @racket[debug-repl], exits the repl. The call to
@racket[debug-repl] returns the arguments to @racket[resume].
}
}

View File

@ -0,0 +1,92 @@
#lang racket/base
(require "../repl.rkt"
"test-util.rkt"
rackunit
(for-syntax racket/base syntax/parse))
(define a 3)
(define b 4)
(test-case "local macros that don't refer to other macros"
(define (f tmp)
(define-syntax ?list
(syntax-parser
[(?list x:expr ...)
(define (?list-helper acc xs)
(syntax-parse (list acc xs)
[([acc:id ...] []) #'(list acc ...)]
[([acc:id ...] [x:expr y:expr ...])
#:with [tmp] (generate-temporaries #'[x])
#`(let ([tmp x])
(if tmp
#,(?list-helper #'[acc ... tmp] #'[y ...])
#false))]))
(?list-helper #'[] #'[x ...])]
[stx
;; TODO: figure out how to make syntax-parse's own errors
;; not cause infinite loops
(raise-syntax-error #f "bad syntax" #'stx)]))
(debug-repl)
tmp)
(test-with-io
#:i [i (open-input-string "a b tmp (?list a b tmp)")]
#:o [o (open-output-string)]
(check-equal? (f 1) 1)
(check-equal? (get-output-string o)
(string-append
"-> " #;a "3\n"
"-> " #;b "4\n"
"-> " #;tmp "1\n"
"-> " #;(?list a b tmp) "'(3 4 1)\n"
"-> ")))
(test-with-io
#:i [i (open-input-string "(?list . bluh)")]
#:o [o (open-output-string)]
(check-exn #rx"\\?list: bad syntax"
(λ () (f 1)))
(check-equal? (get-output-string o)
(string-append
"-> " #;(?list . bluh)))))
;; TODO: !!! identifier used out of context !!!
#;
(test-case "local macros that refer to other macros"
(define (f tmp)
(define-syntax ?list-helper
(syntax-parser
[(?list-helper [acc:id ...] []) #'(list acc ...)]
[(?list-helper [acc:id ...] [x:expr y:expr ...])
#'(let ([tmp x])
(if tmp
(?list-helper [acc ... tmp] [y ...])
#false))]))
(define-syntax-rule (?list x ...)
(?list-helper [] [x ...]))
(debug-repl)
tmp)
(test-with-io
#:i [i (open-input-string "a b tmp (?list a b tmp)")]
#:o [o (open-output-string)]
(check-equal? (f 1) 1)
(check-equal? (get-output-string o)
(string-append
"-> " #;a "3\n"
"-> " #;b "4\n"
"-> " #;tmp "1\n"
"-> " #;(?list a b tmp) "'(3 4 1)\n"
"-> ")))
(test-with-io
#:i [i (open-input-string "a b tmp (+ a b tmp)")]
#:o [o (open-output-string)]
(check-exn #rx"a: undefined;\n cannot use before initialization"
(λ () (f 1)))
(check-equal? (get-output-string o)
(string-append
"-> " #;b "4\n"
"-> " #;(+ b 13) "17\n"
"-> " #;(+ a b 13)))))

103
debug/test/debug-repl.rkt Normal file
View File

@ -0,0 +1,103 @@
#lang racket/base
(require "../repl.rkt"
"test-util.rkt"
rackunit)
(define a 3)
(define b 4)
(define-syntax-rule (with-other-vars body)
(let ([x 5] [z 6])
;; x and z are not available outside this scope
body))
(define (f x y)
;; x and y are local variables
(with-other-vars
(let ([y 7] [b 8] [c 9])
;; y, b, and c are local variables
(debug-repl)
x)))
(test-with-io
#:i [i (open-input-string "x y a b c (+ x y a b c) (with-other-vars x)")]
#:o [o (open-output-string)]
(check-equal? (f 1 2) 1)
(check-equal? (get-output-string o)
(string-append
"-> " #;x "1\n"
"-> " #;y "7\n"
"-> " #;a "3\n"
"-> " #;b "8\n"
"-> " #;c "9\n"
"-> " #;(+ x y a b c) "28\n"
"-> " #;(with-other-vars x) "1\n"
"-> "))
)
;; test for issue #9
(test-case "issue #9"
(define (f)
(when #true
(debug-repl))
(define a 1)
a)
(test-with-io
#:i [i (open-input-string "b (+ b 13)")]
#:o [o (open-output-string)]
(check-equal? (f) 1)
(check-equal? (get-output-string o)
(string-append
"-> " #;b "4\n"
"-> " #;(+ b 13) "17\n"
"-> ")))
(test-with-io
#:i [i (open-input-string "b (+ b 13) (+ a b 13)")]
#:o [o (open-output-string)]
(check-exn #rx"a: undefined;\n cannot use before initialization"
(λ () (f)))
(check-equal? (get-output-string o)
(string-append
"-> " #;b "4\n"
"-> " #;(+ b 13) "17\n"
"-> " #;(+ a b 13)))))
;; test for mutation
(define x-for-mutation 1)
(test-case "test for mutation"
(define (f1 x-for-mutation)
(debug-repl)
x-for-mutation)
(define (f2)
(debug-repl)
x-for-mutation)
(test-with-io
#:i [i (open-input-string "x-for-mutation")]
#:o [o (open-output-string)]
(check-equal? x-for-mutation 1)
(check-equal? (f1 2) 2)
(check-equal? (get-output-string o)
(string-append
"-> " #;x-for-mutation "2\n"
"-> "))
(check-equal? x-for-mutation 1))
(test-with-io
#:i [i (open-input-string "x-for-mutation")]
#:o [o (open-output-string)]
(check-equal? x-for-mutation 1)
(check-equal? (f2) 1)
(check-equal? (get-output-string o)
(string-append
"-> " #;x-for-mutation "1\n"
"-> "))
(check-equal? x-for-mutation 1)))

14
debug/test/test-util.rkt Normal file
View File

@ -0,0 +1,14 @@
#lang racket/base
(provide test-with-io)
(define-syntax-rule (test-with-io
#:i [i input-port]
#:o [o output-port]
body ...)
(let ([i input-port]
[o output-port])
(parameterize ([current-input-port i]
[current-output-port o])
body ...)))

View File

@ -2,9 +2,13 @@
(define collection 'multi) (define collection 'multi)
;; Require a version of racket after this commit:
;; make `variable-reference->namespace` enable top-level mode
;; d1c2daf15b8be048b5cea63d5a1d7206bfc8d43f
(define deps (define deps
'("rackunit-lib" '(["base" #:version "6.6.0.3"]
"base" "rackunit-lib"
)) ))
(define build-deps (define build-deps