#lang scheme/base

(require (for-template scheme/base))

(provide at-syntax)

;; -------------------------------------------------------------------
;; NOTE: This library is for internal use only, it is can change
;; and/or disappear.  Do not use without protective eyewear!
;; -------------------------------------------------------------------

#|

The `(at-syntax expr)' form is a useful syntax-time utility that can
be used to sort of evaluate an expression at syntax time, and doing so
in a well behaved way (eg, it respects the source for-syntax bindings,
but it does have some issues).  It can be used to implement an escape
to the syntax level that is not restricted like `begin-for-syntax'.

The basic idea of the code is to plant the given expression on the
right hand side of a `let-syntax' -- inside a `(lambda (stx) ...)'  to
make it a valid transformer, with a singe use of this macro so that we
get it to execute with `local-expand'.  The macro returns a 3d
expression that contains the evaluated expression "somehwhere",
depending on the expansion of `let-syntax' -- so to make it easy to
find we plant it inside a thunk (so this works as long as `let-syntax'
does not include 3d procedure values in its expansion).  Finally, the
constructed `let-syntax' is expanded, we search through the resulting
syntax for the thunk, then apply it to get the desired value.

Here's a silly example to demonstrate:

  > (define-syntax (compile-time-if stx)
      (syntax-case stx ()
        [(_ cond expr1 expr2)
         (if (at-syntax #'cond) #'expr1 #'expr2)]))
  > (define-for-syntax x 8)
  > (define x 100)
  > (compile-time-if (< x 10) (+ x 10) (- x 10))
  110

And another example, creating a macro for syntax-time expressions:

  > (define-syntax (compile-time-value stx)
      (syntax-case stx ()
        [(_ expr) #`(quote #,(at-syntax #'expr))]))
  > (compile-time-value (* x 2))
  16

but the `quote' here is a hint that this can get 3d values into
syntax, and all the problems that are involved.  Also, note that it
breaks if you try to do something like:

  > (compile-time-value (begin (set! x 11) x))
  8

(and, of course, it cannot be used to define new bindings).

|#

(define (at-syntax expr)
  (let loop ([x (with-syntax ([e expr])
                  (local-expand
                   #'(let-syntax ([here (lambda (stx)
                                          (datum->syntax stx (lambda () e)))])
                       here)
                   'expression '()))])
    (cond [(procedure? x) (x)]
          [(pair? x) (or (loop (car x)) (loop (cdr x)))]
          [(syntax? x) (loop (syntax-e x))]
          [else #f])))
