File: macro.rkt

package info (click to toggle)
racket-mode 20201227git0-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 1,040 kB
  • sloc: lisp: 9,808; makefile: 55
file content (169 lines) | stat: -rw-r--r-- 6,664 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#lang racket/base

(require (only-in macro-debugger/stepper-text
                  stepper-text)
         racket/contract
         racket/file
         racket/format
         racket/match
         (only-in racket/path
                  path-only)
         racket/pretty
         racket/system
         "../elisp.rkt"
         "../repl-session.rkt"
         "../syntax.rkt"
         "../util.rkt")

(provide macro-stepper
         macro-stepper/next)

(define step/c (cons/c (or/c 'original string? 'final) string?))
(define step-proc/c (-> (or/c 'next 'all) (listof step/c)))
(define step-proc #f)

(define/contract (make-expr-stepper str)
  (-> string? step-proc/c)
  (unless (current-session-id)
    (error 'make-expr-stepper "Does not work without a running REPL"))
  (define step-num #f)
  (define last-stx (string->namespace-syntax str))
  (define/contract (step what) step-proc/c
    (cond [(not step-num)
           (set! step-num 0)
           (list (cons 'original
                       (pretty-format-syntax last-stx)))]
          [else
           (define result
             (let loop ()
               (define this-stx (expand-once last-stx))
               (cond [(equal? (syntax->datum last-stx)
                              (syntax->datum this-stx))
                      (cond [(eq? what 'all)
                             (list (cons 'final
                                         (pretty-format-syntax this-stx)))]
                            [else (list)])]
                     [else
                      (set! step-num (add1 step-num))
                      (define step
                        (cons (~a step-num ": expand-once")
                              (diff-text (pretty-format-syntax last-stx)
                                         (pretty-format-syntax this-stx)
                                         #:unified 3)))
                      (set! last-stx this-stx)
                      (cond [(eq? what 'all) (cons step (loop))]
                            [else (list step)])])))
           (match result
             [(list) (list (cons 'final
                                 (pretty-format-syntax last-stx)))]
             [v v])]))
  step)

(define/contract (make-file-stepper path into-base?)
  (-> (and/c path-string? absolute-path?) boolean? step-proc/c)
  (assert-file-stepper-works)
  (define stx (file->syntax path))
  (define dir (path-only path))
  (define ns (make-base-namespace))
  (define raw-step (parameterize ([current-load-relative-directory dir]
                                  [current-namespace               ns])
                     (stepper-text stx
                                   (if into-base? (λ _ #t) (not-in-base)))))
  (define step-num #f)
  (define step-last-after "")
  (log-racket-mode-debug "~v ~v ~v" path into-base? raw-step)
  (define/contract (step what) step-proc/c
    (cond [(not step-num)
           (set! step-num 0)
           (list (cons 'original
                       (pretty-format-syntax stx)))]
          [else
           (define out (open-output-string))
           (cond [(parameterize ([current-output-port out])
                    (raw-step what))
                  (log-racket-mode-debug "~v" (get-output-string out))
                  (define in (open-input-string (get-output-string out)))
                  (let loop ()
                    (match (parameterize ([current-input-port in])
                             (read-step))
                      [(? eof-object?)
                       (cond [(eq? what 'all)
                              (list (cons 'final step-last-after))]
                             [else (list)])]
                      [(list title before after)
                       (set! step-num (add1 step-num))
                       (set! step-last-after after)
                       (cons (cons (~a step-num ": " title)
                                   (diff-text before after #:unified 3))
                             (loop))]))]
                 [else
                  (list (cons 'final step-last-after))])]))
  step)

(define (read-step)
  (define title (read-line))
  (define before (read))
  (define _arrow (read)) ; '==>
  (define after (read))
  (read-line)
  (match (read-line)
    [(? eof-object? e) e]
    [_ (list title
            (pretty-format #:mode 'write before)
            (pretty-format #:mode 'write  after))]))

(define (assert-file-stepper-works)
  (define step (stepper-text #'(module example racket/base 42)))
  (unless (step 'next)
    (error 'macro-debugger/stepper-text
           "does not work in your version of Racket.\nPlease try an older or newer version.")))

(define/contract (macro-stepper what into-base?)
  (-> (or/c (cons/c 'expr string?) (cons/c 'file path-string?)) elisp-bool/c
      (list/c step/c))
  (set! step-proc
        (match what
          [(cons 'expr str)  (make-expr-stepper str)]
          [(cons 'file path) (make-file-stepper path (as-racket-bool into-base?))]))
  (macro-stepper/next 'next))

(define/contract (macro-stepper/next what) step-proc/c
  (unless step-proc
    (error 'macro-stepper "Nothing to expand"))
  (define v (step-proc what))
  (match v
    [(list (cons 'final _)) (set! step-proc #f)]
    [_ (void)])
  v)

;; Borrowed from xrepl.
(define not-in-base
  (λ () (let ([base-stxs #f])
          (unless base-stxs
            (set! base-stxs ; all ids that are bound to a syntax in racket/base
                  (parameterize ([current-namespace (make-base-namespace)])
                    (let-values ([(vals stxs) (module->exports 'racket/base)])
                      (map (λ (s) (namespace-symbol->identifier (car s)))
                           (cdr (assq 0 stxs)))))))
          (λ (id) (not (ormap (λ (s) (free-identifier=? id s)) base-stxs))))))

(define (diff-text before-text after-text #:unified [-U 3])
  (define template "racket-mode-syntax-diff-~a")
  (define (make-temporary-file-with-text str)
    (define file (make-temporary-file template))
    (with-output-to-file file #:mode 'text #:exists 'replace
      (λ () (displayln str)))
    file)
  (define before-file (make-temporary-file-with-text before-text))
  (define after-file  (make-temporary-file-with-text after-text))
  (define out (open-output-string))
  (begin0 (parameterize ([current-output-port out])
            (system (format "diff -U ~a ~a ~a" -U before-file after-file))
            (match (get-output-string out)
              ["" " <empty diff>\n"]
              [(pregexp "\n(@@.+@@\n.+)$" (list _ v)) v]))
    (delete-file before-file)
    (delete-file after-file)))

(define (pretty-format-syntax stx)
  (pretty-format #:mode 'write (syntax->datum stx)))