summaryrefslogtreecommitdiff
path: root/module/ice-9/local-eval.scm
blob: ac8838f1b415785174a93bdb16e13ce9df60cc98 (plain)
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
;;; -*- mode: scheme; coding: utf-8; -*-
;;;
;;; Copyright (C) 2012, 2013, 2021 Free Software Foundation, Inc.
;;;
;;; 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

(define-module (ice-9 local-eval)
  #:use-module (ice-9 format)
  #:use-module (srfi srfi-9)
  #:use-module (srfi srfi-9 gnu)
  #:use-module (system base compile)
  #:use-module (system syntax)
  #:export (the-environment local-eval local-compile))

(define-record-type lexical-environment-type
  (make-lexical-environment scope wrapper boxes patterns)
  lexical-environment?
  (scope             lexenv-scope)
  (wrapper           lexenv-wrapper)
  (boxes             lexenv-boxes)
  (patterns          lexenv-patterns))

(set-record-type-printer!
 lexical-environment-type
 (lambda (e port)
   (format port "#<lexical-environment ~S (~S bindings)>"
           (syntax-module (lexenv-scope e))
           (+ (length (lexenv-boxes e)) (length (lexenv-patterns e))))))

(define-syntax-rule (make-box v)
  (case-lambda
   (() v)
   ((x) (set! v x))))

(define (make-transformer-from-box id trans)
  (set-procedure-property! trans 'identifier-syntax-box id)
  trans)

(define-syntax-rule (identifier-syntax-from-box box)
  (make-transformer-from-box
   (quote-syntax box)
   (identifier-syntax (id          (box))
                      ((set! id x) (box x)))))

(define (unsupported-binding name)
  (make-variable-transformer
   (lambda (x)
     (syntax-violation
      'local-eval
      "unsupported binding captured by (the-environment)"
      x))))

(define (within-nested-ellipses id lvl)
  (let loop ((s id) (n lvl))
    (if (zero? n)
        s
        (loop #`(#,s (... ...)) (- n 1)))))

;; Analyze the set of bound identifiers IDS.  Return four values:
;;
;; capture: A list of forms that will be emitted in the expansion of
;; `the-environment' to capture lexical variables.
;;
;; formals: Corresponding formal parameters for use in the lambda that
;; re-introduces those variables.  These are temporary identifiers, and
;; as such if we have a nested `the-environment', there is no need to
;; capture them.  (See the notes on nested `the-environment' and
;; proxies, below.)
;;
;; wrappers: A list of procedures of type SYNTAX -> SYNTAX, used to wrap
;; the expression to be evaluated in forms that re-introduce the
;; variable.  The forms will be nested so that the variable shadowing
;; semantics of the original form are maintained.
;;
;; patterns: A terrible hack.  The issue is that for pattern variables,
;; we can't emit lexically nested with-syntax forms, like:
;;
;;   (with-syntax ((foo 1)) (the-environment))
;;   => (with-syntax ((foo 1))
;;        ... #'(with-syntax ((foo ...)) ... exp) ...)
;;
;; The reason is that the outer "foo" substitutes into the inner "foo",
;; yielding something like:
;;
;;   (with-syntax ((foo 1))
;;     ... (with-syntax ((1 ...)) ...)
;;            
;; Which ain't what we want.  So we hide the information needed to
;; re-make the inner pattern binding form in the lexical environment
;; object, and then introduce those identifiers via another with-syntax.
;;
;;
;; There are four different kinds of lexical bindings: normal lexicals,
;; macros, displaced lexicals, and pattern variables.  See the
;; documentation of syntax-local-binding for more info on these.
;;
;; We capture normal lexicals via `make-box', which creates a
;; case-lambda that can reference or set a variable.  These get
;; re-introduced with an identifier-syntax.
;;
;; We can't capture macros currently.  However we do recognize our own
;; macros that are actually proxying lexicals, so that nested
;; `the-environment' forms are possible.  In that case we drill down to
;; the identifier for the already-existing box, and just capture that
;; box.
;;
;; And that's it: we skip displaced lexicals, and the pattern variables
;; are discussed above.
;;
(define (analyze-identifiers ids)
  (define (mktmp)
    (datum->syntax #'here (gensym "t ")))
  (let lp ((ids ids) (capture '()) (formals '()) (wrappers '()) (patterns '()))
    (cond
     ((null? ids)
      (values capture formals wrappers patterns))
     (else
      (let ((id (car ids)) (ids (cdr ids)))
        (call-with-values (lambda () (syntax-local-binding id))
          (lambda (type val)
            (case type
              ((lexical)
               (if (or-map (lambda (x) (bound-identifier=? x id)) formals)
                   (lp ids capture formals wrappers patterns)
                   (let ((t (mktmp)))
                     (lp ids
                         (cons #`(make-box #,id) capture)
                         (cons t formals)
                         (cons (lambda (x)
                                 #`(let-syntax ((#,id (identifier-syntax-from-box #,t)))
                                     #,x))
                               wrappers)
                         patterns))))
              ((displaced-lexical)
               (lp ids capture formals wrappers patterns))
              ((macro)
               (let ((b (procedure-property val 'identifier-syntax-box)))
                 (if b
                     (lp ids (cons b capture) (cons b formals)
                         (cons (lambda (x)
                                 #`(let-syntax ((#,id (identifier-syntax-from-box #,b)))
                                     #,x))
                               wrappers)
                         patterns)
                     (lp ids capture formals
                         (cons (lambda (x)
                                 #`(let-syntax ((#,id (unsupported-binding '#,id)))
                                     #,x))
                               wrappers)
                         patterns))))
              ((pattern-variable)
               (let ((t (datum->syntax id (gensym "p ")))
                     (nested (within-nested-ellipses id (cdr val))))
                 (lp ids capture formals
                     (cons (lambda (x)
                             #`(with-syntax ((#,t '#,nested))
                                 #,x))
                           wrappers)
                     ;; This dance is to hide these pattern variables
                     ;; from the expander.
                     (cons (list (datum->syntax #'here (syntax->datum id))
                                 (cdr val)
                                 t)
                           patterns))))
              ((ellipsis)
               (lp ids capture formals
                   (cons (lambda (x)
                           #`(with-ellipsis #,val #,x))
                         wrappers)
                   patterns))
              (else
               ;; Interestingly, this case can include globals (and
               ;; global macros), now that Guile tracks which globals it
               ;; introduces.  Not sure what to do here!  For now, punt.
               ;; 
               (lp ids capture formals wrappers patterns))))))))))

(define-syntax the-environment
  (lambda (x)
    (syntax-case x ()
      ((the-environment)
       #'(the-environment the-environment))
      ((the-environment scope)
       (call-with-values (lambda ()
                           (analyze-identifiers
                            (syntax-locally-bound-identifiers #'scope)))
         (lambda (capture formals wrappers patterns)
           (define (wrap-expression x)
             (let lp ((x x) (wrappers wrappers))
               (if (null? wrappers)
                   x
                   (lp ((car wrappers) x) (cdr wrappers)))))
           (with-syntax (((f ...) formals)
                         ((c ...) capture)
                         (((pname plvl pformal) ...) patterns)
                         (wrapped (wrap-expression #'(begin #f exp))))
             #'(make-lexical-environment
                #'scope
                (lambda (exp pformal ...)
                  (with-syntax ((exp exp)
                                (pformal pformal)
                                ...)
                    #'(lambda (f ...)
                        wrapped)))
                (list c ...)
                (list (list 'pname plvl #'pformal) ...)))))))))

(define (env-module e)
  (cond
   ((lexical-environment? e) (resolve-module (syntax-module (lexenv-scope e))))
   ((module? e) e)
   (else (error "invalid lexical environment" e))))

(define (env-boxes e)
  (cond
   ((lexical-environment? e) (lexenv-boxes e))
   ((module? e) '())
   (else (error "invalid lexical environment" e))))

(define (local-wrap x e)
  (cond
   ((lexical-environment? e)
    (apply (lexenv-wrapper e)
           (datum->syntax (lexenv-scope e) x)
           (map (lambda (l)
                  (let ((name (car l))
                        (lvl (cadr l))
                        (scope (caddr l)))
                    (within-nested-ellipses (datum->syntax scope name) lvl)))
                (lexenv-patterns e))))
   ((module? e) #`(lambda () #f #,x))
   (else (error "invalid lexical environment" e))))

(define (local-eval x e)
  "Evaluate the expression @var{x} within the lexical environment @var{e}."
  (apply (eval (local-wrap x e) (env-module e))
         (env-boxes e)))

(define* (local-compile x e #:key (opts '()))
  "Compile and evaluate the expression @var{x} within the lexical
environment @var{e}."
  (apply (compile (local-wrap x e) #:env (env-module e)
                  #:from 'scheme #:opts opts)
         (env-boxes e)))