racket/racket/src/expander/expand/module.rkt
Matthew Flatt bff31f0768 expander: allow syntax-local-lift-require during #%module-begin expansion
Making `syntax-local-lift-require` during `#%module-begin` expansion
provides a solution to a long-standing problem with composing
languages via `#%module-begin`, where the bindings of some lanuage
should be made available for expanding the module body by
`#%module-begin` expansion (i.e., the language is *not* the initial
import). If the language does not support `require`, then there was no
way to expand and expose the language import dependency.

Closes #1352
2021-04-28 08:30:20 -06:00

1466 lines
70 KiB
Racket

#lang racket/base
(require "../common/promise.rkt"
"../common/struct-star.rkt"
"../common/performance.rkt"
"../common/parameter-like.rkt"
"../syntax/syntax.rkt"
"../syntax/debug.rkt"
"../syntax/property.rkt"
"../syntax/scope.rkt"
"../syntax/taint.rkt"
"../syntax/match.rkt"
"../syntax/track.rkt"
"../common/phase.rkt"
"../syntax/track.rkt"
"../syntax/error.rkt"
"../namespace/namespace.rkt"
"../namespace/module.rkt"
"../syntax/binding.rkt"
"../eval/protect.rkt"
"dup-check.rkt"
"free-id-set.rkt"
"stop-ids.rkt"
"require+provide.rkt"
"../common/module-path.rkt"
"lift-context.rkt"
"lift-key.rkt"
"../namespace/core.rkt"
"context.rkt"
"use-site.rkt"
"main.rkt"
"require.rkt"
"provide.rkt"
"def-id.rkt"
"prepare.rkt"
"log.rkt"
"syntax-id-error.rkt"
"../compile/main.rkt"
"../eval/top.rkt"
"../eval/module.rkt"
"cross-phase.rkt"
"parsed.rkt"
"expanded+parsed.rkt"
"append.rkt"
"save-and-restore.rkt")
(add-core-form!
'module
(lambda (s ctx)
(unless (eq? (expand-context-context ctx) 'top-level)
(log-expand ctx 'prim-module #f)
(raise-syntax-error #f "allowed only at the top level" s))
(performance-region
['expand 'module]
(expand-module s ctx #f))))
(add-core-form!
'module*
(lambda (s ctx)
(log-expand ctx 'prim-module #f)
(raise-syntax-error #f "illegal use (not in a module top-level)" s)))
(add-core-form!
'#%module-begin
(lambda (s ctx)
(log-expand ctx 'prim-module-begin #f)
(unless (eq? (expand-context-context ctx) 'module-begin)
(raise-syntax-error #f "not in a module-definition context" s))
(unless (expand-context-module-begin-k ctx)
(raise-syntax-error #f "not currently transforming a module" s))
;; This `#%module-begin` must be in a `module`; the
;; `module-begin-k` function continues that module's
;; expansion
((expand-context-module-begin-k ctx)
s
(struct*-copy expand-context ctx
[module-begin-k #f]))))
(add-core-form!
'#%declare
(lambda (s ctx)
(log-expand ctx 'prim-declare #f)
;; The `#%module-begin` expander handles `#%declare`
(raise-syntax-error #f "not allowed outside of a module body" s)))
;; ----------------------------------------
(define (expand-module s init-ctx enclosing-self
#:always-produce-compiled? [always-produce-compiled? #f]
#:keep-enclosing-scope-at-phase [keep-enclosing-scope-at-phase #f]
#:enclosing-is-cross-phase-persistent? [enclosing-is-cross-phase-persistent? #f]
#:enclosing-requires+provides [enclosing-r+p #f]
#:mpis-for-enclosing-reset [mpis-for-enclosing-reset #f]
;; For cross-linklet inlining among submodules compiled together:
#:modules-being-compiled [modules-being-compiled (make-hasheq)])
(define disarmed-s (syntax-disarm s))
(log-expand init-ctx 'prim-module disarmed-s)
(define-match m disarmed-s '(module id:module-name initial-require body ...))
(define rebuild-s (keep-as-needed init-ctx s #:keep-for-parsed? #t #:keep-for-error? #t))
(define initial-require (syntax->datum (m 'initial-require)))
(unless (or keep-enclosing-scope-at-phase
(module-path? initial-require))
(raise-syntax-error #f "not a module path" s (m 'initial-require)))
;; All module bodies start at phase 0
(define phase 0)
(define module-name-sym (syntax-e (m 'id:module-name)))
(define outside-scope (new-scope 'module))
(define inside-scope (new-multi-scope module-name-sym))
(define self (make-self-module-path-index (if enclosing-self
module-name-sym
(string->uninterned-symbol
(symbol->string module-name-sym)))
enclosing-self))
(define enclosing-mod (and enclosing-self
(module-path-index-join '(submod "..") self)))
(when (and #;enclosing-mod mpis-for-enclosing-reset)
(set-box! mpis-for-enclosing-reset
(cons enclosing-mod (unbox mpis-for-enclosing-reset))))
(define apply-module-scopes
(make-apply-module-scopes outside-scope inside-scope
init-ctx keep-enclosing-scope-at-phase
self enclosing-self enclosing-mod))
;; Initial require name provides the module's base scopes
(define initial-require-s (apply-module-scopes (m 'initial-require)))
(define all-scopes-s initial-require-s)
(define root-ctx (make-root-expand-context
#:self-mpi self
#:initial-scopes (if keep-enclosing-scope-at-phase
(root-expand-context-module-scopes init-ctx)
null)
#:outside-scope outside-scope
#:post-expansion-scope inside-scope
#:all-scopes-stx all-scopes-s))
;; Extract combined scopes
(define new-module-scopes (root-expand-context-module-scopes root-ctx))
;; A frame-id is used to determine when use-site scopes are needed
(define frame-id (root-expand-context-frame-id root-ctx))
;; Make a namespace for module expansion
(define (make-m-ns ns #:for-submodule? [for-submodule? (and enclosing-self #t)])
(make-module-namespace ns
#:mpi self
#:root-expand-context root-ctx
#:for-submodule? for-submodule?))
(define m-ns (make-m-ns (expand-context-namespace init-ctx)))
;; Initial context for all body expansions:
(define ctx (struct*-copy expand-context (copy-root-expand-context init-ctx root-ctx)
[allow-unbound? #f]
[namespace m-ns]
[post-expansion #:parent root-expand-context (lambda (s) (add-scope s inside-scope))]
[phase phase]
[just-once? #f]))
;; Add the module's scope to the body forms; use `disarmed-s` and
;; re-match to extract the body forms, because that improves sharing
(define bodys (let ([scoped-s (apply-module-scopes disarmed-s)])
(define-match m scoped-s '(_ _ _ body ...))
(m 'body)))
;; To keep track of all requires and provides
(define requires+provides (make-requires+provides self))
;; Table of symbols picked for each binding in this module:
(define defined-syms (root-expand-context-defined-syms root-ctx)) ; phase -> sym -> id
;; So that compilations of submodules can be preserved for
;; inclusion in an overall compiled module:
(define compiled-submodules (make-hasheq))
;; If we compile the module for use by `module*` submodules, keep that
;; compiled form to possibly avoid compiling again.
(define compiled-module-box (box #f))
;; Accumulate module path indexes used by submodules to refer to this module
(define mpis-to-reset (box null))
;; Initial require
(define (initial-require! #:bind? bind?)
(cond
[(not keep-enclosing-scope-at-phase)
;; Install the initial require
(perform-initial-require! initial-require self
all-scopes-s
m-ns
requires+provides
#:bind? bind?
#:who 'module)]
[else
;; For `(module* name #f ....)`, just register the enclosing module
;; as an import and visit it
(add-required-module! requires+provides
enclosing-mod
keep-enclosing-scope-at-phase
enclosing-is-cross-phase-persistent?)
(add-enclosing-module-defined-and-required! requires+provides
#:enclosing-requires+provides enclosing-r+p
enclosing-mod
keep-enclosing-scope-at-phase)
(namespace-module-visit! m-ns enclosing-mod
keep-enclosing-scope-at-phase)]))
(log-expand init-ctx 'prepare-env)
(initial-require! #:bind? #t)
(log-expand init-ctx 'rename-one bodys)
;; To detect whether the body is expanded multiple times:
(define again? #f)
;; The primitive `#%module-body` form calls this function to expand the
;; current module's body
(define (module-begin-k mb-s mb-init-ctx)
;; In case the module body is expanded multiple times, we clear
;; the requires, provides and definitions information each time.
;; Don't discard accumulated requires, though, since those may be
;; needed by pieces from a previous expansion. Also, be careful
;; not to change the current bindings when re-establishing the
;; requires.
(when again?
(requires+provides-reset! requires+provides)
(initial-require! #:bind? #f)
(hash-clear! compiled-submodules)
(set-box! compiled-module-box #f))
(set! again? #t)
;; In case a nested `#%module-begin` expansion is forced, save
;; and restore the module-expansion state:
(define ctx (struct*-copy expand-context mb-init-ctx
[module-begin-k
(lambda (s ctx)
(define new-requires+provides
;; Copy old `require` dependencies, which allows a
;; synthesized nested `#%module-begin` to use pieces
;; that depend on bindings introduced outside the
;; synthesized part --- a questionable practice,
;; but support for backward compatibility, at least.
(make-requires+provides self
#:copy-requires requires+provides))
(with-save-and-restore ([requires+provides new-requires+provides]
[compiled-submodules (make-hasheq)]
[compiled-module-box (box #f)]
[defined-syms (make-hasheq)])
(module-begin-k s ctx)))]
;; Also, force `post-expansion` to be right, in case 'module-begin
;; module is triggered within some other mode; a correct value
;; for `post-expansion` is important to getting phase-specific
;; binding right.
[post-expansion #:parent root-expand-context
(lambda (s) (add-scope s inside-scope))]))
;; In case `#%module-begin` expansion is forced on syntax that
;; that wasn't already introduced into the mdoule's inside scope,
;; add it to all the given body forms
(define added-s (add-scope mb-s inside-scope))
(define disarmed-mb-s (syntax-disarm added-s))
(define-match mb-m disarmed-mb-s '(#%module-begin body ...))
(define bodys (mb-m 'body))
(log-expand ctx 'rename-one added-s)
(define rebuild-mb-s (keep-as-needed ctx mb-s))
;; For variable repeferences before corresponding binding (phase >= 1)
(define need-eventually-defined (make-hasheqv)) ; phase -> list of id
;; For `syntax-local-lift-module-end-declaration`, which is accumulated
;; across phases:
(define module-ends (make-shared-module-ends))
;; Accumulate `#%declare` content
(define declared-keywords (make-hasheq))
;; Accumulated declared submodule names for `syntax-local-submodules`
(define declared-submodule-names (make-hasheq))
;; Requires that were lifted during `#%module-begin` expansion:
(define initial-lifted-requires (get-require-lifts (expand-context-require-lifts ctx)))
;; Module expansion always parses the module body along the way,
;; even if `to-parsed?` in `ctx` is not true. The body is parsed
;; so that the module can be declared for reference by
;; submodules. So, if expansion is supposed to a syntax object
;; instead of `module-parsed`, then we'll need to accumulate both
;; parsed and expanded results; see "expanded+parsed.rkt".
;; The expansion of the module body happens in 4 passes:
;; Pass 1: Partial expansion to determine imports and definitions
;; Pass 2: Complete expansion of remaining expressions
;; Pass 3: Parsing of provide forms
;; Pass 4: Parsing of `module*` submodules
;; Passes 1 and 2 are nested via `begin-for-syntax`:
(define expression-expanded-bodys
(let pass-1-and-2-loop ([bodys bodys] [phase phase] [keep-stops? (stop-at-module*? ctx)]
[initial-lifted-requires initial-lifted-requires])
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Pass 1: partially expand to discover all bindings and install all
;; defined macro transformers
;; Need to accumulate definition contexts created during
;; partial expansion:
(define def-ctx-scopes (box null))
(define partial-body-ctx (struct*-copy expand-context ctx
[context 'module]
[phase phase]
[namespace (namespace->namespace-at-phase m-ns phase)]
[stops (free-id-set phase (module-expand-stop-ids phase))]
[def-ctx-scopes def-ctx-scopes]
[need-eventually-defined need-eventually-defined] ; used only at phase 1 and up
[declared-submodule-names declared-submodule-names]
[lift-key #:parent root-expand-context (generate-lift-key)]
[lifts (make-lift-context
(make-wrap-as-definition self frame-id
inside-scope all-scopes-s
defined-syms requires+provides))]
[module-lifts (make-module-lift-context phase #t)]
[require-lifts (make-require-lift-context
phase
(make-parse-lifted-require m-ns self requires+provides
#:declared-submodule-names declared-submodule-names)
initial-lifted-requires)]
[to-module-lifts (make-to-module-lift-context
phase
#:shared-module-ends module-ends
#:end-as-expressions? #f)]))
;; Result is mostly a list of S-expressions, but can also
;; contain `compile-form` or `expanded+parsed` structures:
(define partially-expanded-bodys
(partially-expand-bodys bodys
#:phase phase
#:ctx partial-body-ctx
#:namespace m-ns
#:self self
#:frame-id frame-id
#:requires-and-provides requires+provides
#:need-eventually-defined need-eventually-defined
#:all-scopes-stx all-scopes-s
#:defined-syms defined-syms
#:declared-keywords declared-keywords
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled
#:mpis-to-reset mpis-to-reset
#:loop pass-1-and-2-loop))
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Pass 2: finish expanding expressions
(log-expand partial-body-ctx 'next-group)
(define body-ctx (struct*-copy expand-context (accumulate-def-ctx-scopes partial-body-ctx def-ctx-scopes)
[stops (if keep-stops?
(expand-context-stops ctx)
empty-free-id-set)]
[def-ctx-scopes #f]
[post-expansion #:parent root-expand-context #f]
[to-module-lifts (make-to-module-lift-context phase
#:shared-module-ends module-ends
#:end-as-expressions? #t)]))
(finish-expanding-body-expressions partially-expanded-bodys
#:phase phase
#:ctx body-ctx
#:self self
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled
#:mpis-to-reset mpis-to-reset)))
(log-expand ctx 'next-group)
;; Check that any tentatively allowed reference at phase >= 1 is ok
(check-defined-by-now need-eventually-defined self ctx requires+provides)
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Pass 3: resolve provides at all phases
(log-expand ctx 'next-group)
(define fully-expanded-bodys-except-post-submodules
(resolve-provides expression-expanded-bodys
#:requires-and-provides requires+provides
#:declared-submodule-names declared-submodule-names
#:namespace m-ns
#:phase phase
#:self self
#:ctx ctx))
;; Validate any cross-phase persistence request
(define is-cross-phase-persistent? (hash-ref declared-keywords '#:cross-phase-persistent #f))
(when is-cross-phase-persistent?
(unless (requires+provides-can-cross-phase-persistent? requires+provides)
(raise-syntax-error #f "cannot be cross-phase persistent due to required modules"
rebuild-s
(hash-ref declared-keywords '#:cross-phase-persistent)))
(check-cross-phase-persistent-form fully-expanded-bodys-except-post-submodules self))
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Pass 4: expand `module*` submodules
(log-expand ctx 'next-group)
;; Create a new namespace to avoid retaining the instance that
;; was needed to expand this module body:
(define submod-m-ns (make-m-ns m-ns #:for-submodule? #t))
(define submod-ctx (struct*-copy expand-context ctx
[frame-id #:parent root-expand-context #f]
[post-expansion #:parent root-expand-context #f]
[namespace submod-m-ns]))
(define declare-enclosing-module
;; Ensure this module on demand for `module*` submodules that might use it
(delay (declare-module-for-expansion fully-expanded-bodys-except-post-submodules
#:module-name-id (m 'id:module-name)
#:rebuild-s rebuild-s
#:requires-and-provides requires+provides
#:namespace submod-m-ns
#:self self
#:enclosing enclosing-self
#:root-ctx root-ctx
#:ctx submod-ctx
#:modules-being-compiled modules-being-compiled
#:fill compiled-module-box)))
(define fully-expanded-bodys
(cond
[(stop-at-module*? submod-ctx)
fully-expanded-bodys-except-post-submodules]
[else
(expand-post-submodules fully-expanded-bodys-except-post-submodules
#:declare-enclosing declare-enclosing-module
#:phase phase
#:self self
#:requires-and-provides requires+provides
#:enclosing-is-cross-phase-persistent? is-cross-phase-persistent?
#:all-scopes-s all-scopes-s
#:mpis-to-reset mpis-to-reset
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled
#:ctx submod-ctx)]))
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Finish
;; Assemble the `#%module-begin` result:
(cond
[(expand-context-to-parsed? submod-ctx)
(parsed-#%module-begin rebuild-mb-s (parsed-only fully-expanded-bodys))]
[else
(define mb-result-s
(rebuild
rebuild-mb-s
`(,(mb-m '#%module-begin) ,@(syntax-only fully-expanded-bodys))))
(cond
[(not (expand-context-in-local-expand? submod-ctx))
(expanded+parsed mb-result-s
(parsed-#%module-begin rebuild-mb-s (parsed-only fully-expanded-bodys)))]
[else mb-result-s])]))
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Actually expand the `#%module-body` form
;; The preceding function performs the expansion; here's where we
;; trigger it
(define mb-ctx
(struct*-copy expand-context ctx
[context 'module-begin]
[module-begin-k module-begin-k]
[in-local-expand? #f]
[lifts #f]
[module-lifts #f]
[to-module-lifts #f]
[require-lifts (make-require-lift-context
phase
(make-parse-lifted-require m-ns self requires+provides
#:declared-submodule-names (make-hasheq)))]))
(define mb-scopes-s
(if keep-enclosing-scope-at-phase
;; for `(module* name #f)`, use the `(module* ...)` form:
(apply-module-scopes disarmed-s)
;; otherwise, use the initial require
all-scopes-s))
;; Need to accumulate definition contexts created during
;; expansion to `#%module-begin`:
(define mb-def-ctx-scopes (box null))
;; Add `#%module-begin` around the body if it's not already present;
;; also logs 'rename-one
(define mb
(ensure-module-begin bodys
#:module-name-sym module-name-sym
#:scopes-s mb-scopes-s
#:m-ns m-ns
#:ctx mb-ctx
#:def-ctx-scopes mb-def-ctx-scopes
#:phase phase
#:s s))
(log-expand ctx 'next)
;; Expand the body
(define expanded-mb (performance-region
['expand 'module-begin]
(expand mb (struct*-copy expand-context (accumulate-def-ctx-scopes mb-ctx mb-def-ctx-scopes)
[def-ctx-scopes #f]))))
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Assemble the `module` result
(define-values (requires provides) (extract-requires-and-provides requires+provides self self))
(define result-form
(and (or (expand-context-to-parsed? init-ctx)
always-produce-compiled?)
(parsed-module rebuild-s
#f
(m 'id:module-name)
self
requires
provides
(requires+provides-all-bindings-simple? requires+provides)
(root-expand-context-encode-for-module root-ctx self self)
(parsed-#%module-begin-body
(if (expanded+parsed? expanded-mb)
(expanded+parsed-parsed expanded-mb)
expanded-mb))
(unbox compiled-module-box)
compiled-submodules)))
(define result-s
(cond
[(not (expand-context-to-parsed? init-ctx))
;; Shift the "self" reference that we have been using for expansion
;; to a generic and constant (for a particular submodule path)
;; "self", so that we can reocognize it for compilation or to shift
;; back on any future re-expansion:
(define generic-self (make-generic-self-module-path-index self))
;; Make `self` like `generic-self`; this hacky update plays the
;; role of applying a shift to identifiers that are in syntax
;; properties, such as the 'origin property
(imitate-generic-module-path-index! self)
(for ([mpi (in-list (unbox mpis-to-reset))])
(imitate-generic-module-path-index! mpi))
(let* ([result-s
(rebuild
rebuild-s
`(,(m 'module) ,(m 'id:module-name) ,initial-require-s ,(expanded+parsed-s expanded-mb)))]
[result-s
(syntax-module-path-index-shift result-s self generic-self)]
[result-s (attach-root-expand-context-properties result-s root-ctx self generic-self)]
[result-s (if (requires+provides-all-bindings-simple? requires+provides)
(syntax-property result-s 'module-body-context-simple? #t)
result-s)])
(log-expand init-ctx 'rename-one result-s)
result-s)]))
(cond
[(expand-context-to-parsed? init-ctx) result-form]
[always-produce-compiled?
(expanded+parsed result-s result-form)]
[else result-s]))
;; ----------------------------------------
;; Add `#%module-begin` to `bodys`, if needed, and otherwise
;; expand to a core `#%module-begin` form
(define (ensure-module-begin bodys
#:module-name-sym module-name-sym
#:scopes-s scopes-s
#:m-ns m-ns
#:ctx ctx
#:def-ctx-scopes def-ctx-scopes
#:phase phase
#:s s)
(define (make-mb-ctx)
(struct*-copy expand-context ctx
[context 'module-begin]
[only-immediate? #t]
[def-ctx-scopes def-ctx-scopes]))
(define mb
(cond
[(= 1 (length bodys))
;; Maybe it's already a `#%module-begin` form, or maybe it
;; will expand to one
(cond
[(eq? '#%module-begin (core-form-sym (syntax-disarm (car bodys)) phase))
;; Done
(car bodys)]
[else
;; A single body form might be a macro that expands to
;; the primitive `#%module-begin` form:
(define named-body-s (add-enclosing-name-property (car bodys) module-name-sym))
(log-expand ctx 'track-syntax 'property named-body-s (car bodys))
(define partly-expanded-body
(performance-region
['expand 'module-begin]
(expand named-body-s
(make-mb-ctx))))
(cond
[(eq? '#%module-begin (core-form-sym (syntax-disarm partly-expanded-body) phase))
;; Yes, it expanded to `#%module-begin`
partly-expanded-body]
[else
;; No, it didn't expand to `#%module-begin`
(add-module-begin (list partly-expanded-body) s scopes-s phase module-name-sym
(make-mb-ctx)
#:log-rename-one? #f)])])]
[else
;; Multiple body forms definitely need a `#%module-begin` wrapper
(add-module-begin bodys s scopes-s phase module-name-sym
(make-mb-ctx))]))
(define named-mb (add-enclosing-name-property mb module-name-sym))
(log-expand ctx 'track-syntax 'property named-mb mb)
named-mb)
;; Add `#%module-begin`, because it's needed
(define (add-module-begin bodys s scopes-s phase module-name-sym mb-ctx
#:log-rename-one? [log-rename-one? #t])
(define disarmed-scopes-s (syntax-disarm scopes-s))
(define mb-id (datum->syntax disarmed-scopes-s '#%module-begin))
;; If `mb-id` is not bound, we'd like to give a clear error message
(unless (resolve mb-id phase)
(raise-syntax-error #f "no #%module-begin binding in the module's language" s))
(define mb (datum->syntax disarmed-scopes-s `(,mb-id ,@bodys) s s))
(log-expand mb-ctx 'tag mb)
(define named-mb (add-enclosing-name-property mb module-name-sym))
(log-expand mb-ctx 'track-syntax 'property named-mb mb)
(define partly-expanded-mb (performance-region
['expand 'module-begin]
(expand named-mb
mb-ctx)))
(unless (eq? '#%module-begin (core-form-sym (syntax-disarm partly-expanded-mb) phase))
(raise-syntax-error #f "expansion of #%module-begin is not a #%plain-module-begin form" s
partly-expanded-mb))
partly-expanded-mb)
(define (add-enclosing-name-property stx module-name-sym)
(syntax-property stx 'enclosing-module-name module-name-sym))
;; ----------------------------------------
;; Make function to adjust syntax that appears in the original module body
(define (make-apply-module-scopes inside-scope outside-scope
init-ctx keep-enclosing-scope-at-phase
self enclosing-self enclosing-mod)
(lambda (s)
(performance-region
['expand 'module 'scopes]
(define s-without-enclosing
(if keep-enclosing-scope-at-phase
;; Keep enclosing module scopes for `(module* _ #f ....)`
s
;; Remove the scopes of the top level or a module outside of
;; this module, as well as any relevant use-site scopes
(remove-use-site-scopes
(remove-scopes s (root-expand-context-module-scopes init-ctx))
init-ctx)))
;; Add outside- and inside-edge scopes
(define s-with-edges
(add-scope (add-scope s-without-enclosing
outside-scope)
inside-scope))
(define s-with-suitable-enclosing
(cond
[keep-enclosing-scope-at-phase
;; Shift any references to the enclosing module to be relative to the
;; submodule
(syntax-module-path-index-shift
s-with-edges
enclosing-self
enclosing-mod)]
[else s-with-edges]))
;; In case we're expanding syntax that was previously expanded,
;; shift the generic "self" to the "self" for the current expansion:
(syntax-module-path-index-shift
s-with-suitable-enclosing
(make-generic-self-module-path-index self)
self
;; Also preserve the expansion-time code inspector
(current-code-inspector)))))
;; ----------------------------------------
;; Pass 1 of `module` expansion, which uncovers definitions,
;; requires, and `module` submodules
(define (partially-expand-bodys bodys
#:phase phase
#:ctx partial-body-ctx
#:namespace m-ns
#:self self
#:frame-id frame-id
#:requires-and-provides requires+provides
#:need-eventually-defined need-eventually-defined
#:all-scopes-stx all-scopes-stx
#:defined-syms defined-syms
#:declared-keywords declared-keywords
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled
#:mpis-to-reset mpis-to-reset
#:loop pass-1-and-2-loop)
(namespace-visit-available-modules! m-ns phase)
(let loop ([tail? #t] [bodys bodys])
(cond
[(null? bodys)
(cond
[(and tail? (not (zero? phase)))
null]
[tail?
;; Were at the very end of the module; if there are any lifted-to-end
;; declarations, keep going
(define bodys
(append
(get-and-clear-end-lifts! (expand-context-to-module-lifts partial-body-ctx))
(get-and-clear-provide-lifts! (expand-context-to-module-lifts partial-body-ctx))))
(cond
[(null? bodys) null]
[else
(define added-bodys (add-post-expansion-scope bodys partial-body-ctx))
(log-expand partial-body-ctx 'module-end-lifts added-bodys)
(loop #t added-bodys)])]
[else null])]
[else
(define rest-bodys (cdr bodys))
(log-expand partial-body-ctx 'next)
(define exp-body (performance-region
['expand 'form-in-module/1]
;; --- expand to core form ---
(expand (car bodys) partial-body-ctx)))
(define disarmed-exp-body (syntax-disarm exp-body))
(define lifted-defns (get-and-clear-lifts! (expand-context-lifts partial-body-ctx)))
(define lifted-reqs (get-and-clear-require-lifts! (expand-context-require-lifts partial-body-ctx)))
(define lifted-mods (get-and-clear-module-lifts! (expand-context-module-lifts partial-body-ctx)))
(define added-lifted-mods (add-post-expansion-scope lifted-mods partial-body-ctx))
(unless (and (null? lifted-defns) (null? lifted-reqs) (null? lifted-mods))
(log-expand partial-body-ctx 'module-pass1-lifts
(lifted-defns-extract-syntax lifted-defns)
lifted-reqs
added-lifted-mods))
(define exp-lifted-mods (loop #f added-lifted-mods))
(log-expand partial-body-ctx 'module-pass1-case exp-body)
(append/tail-on-null
;; Save any requires lifted during partial expansion
lifted-reqs
;; Ditto for expressions
lifted-defns
;; Ditto for modules, which need to be processed
exp-lifted-mods
;; Dispatch on form revealed by partial expansion
(case (core-form-sym disarmed-exp-body phase)
[(begin)
(log-expand partial-body-ctx 'prim-begin disarmed-exp-body)
(define-match m disarmed-exp-body '(begin e ...))
(define (track e) (syntax-track-origin e exp-body))
(define spliced-bodys (append (map track (m 'e)) rest-bodys))
(log-expand partial-body-ctx 'splice spliced-bodys)
(loop tail? spliced-bodys)]
[(begin-for-syntax)
(log-expand partial-body-ctx 'prim-begin-for-syntax disarmed-exp-body)
(define-match m disarmed-exp-body '(begin-for-syntax e ...))
(log-expand partial-body-ctx 'prepare-env)
(define ct-m-ns (namespace->namespace-at-phase m-ns (add1 phase)))
(prepare-next-phase-namespace partial-body-ctx)
(log-expand partial-body-ctx 'phase-up)
(define nested-bodys (pass-1-and-2-loop (m 'e) (add1 phase) #f null))
(log-expand partial-body-ctx 'next-group)
(namespace-run-available-modules! m-ns (add1 phase)) ; to support running `begin-for-syntax`
(eval-nested-bodys nested-bodys (add1 phase) ct-m-ns self partial-body-ctx)
(namespace-visit-available-modules! m-ns phase) ; since we're shifting back a phase
(log-expand partial-body-ctx 'exit-case
(let ([s-nested-bodys (for/list ([nested-body (in-list nested-bodys)])
(extract-syntax nested-body))])
(cons (m 'begin-for-syntax) s-nested-bodys)))
(cons
(semi-parsed-begin-for-syntax exp-body nested-bodys)
(loop tail? rest-bodys))]
[(define-values)
(log-expand partial-body-ctx 'prim-define-values disarmed-exp-body)
(define-match m disarmed-exp-body '(define-values (id ...) rhs))
(define ids (remove-use-site-scopes (m 'id) partial-body-ctx))
(check-no-duplicate-ids ids phase exp-body)
(check-ids-unbound ids phase requires+provides #:in exp-body)
(define syms (select-defined-syms-and-bind! ids defined-syms
self phase all-scopes-stx
#:frame-id frame-id
#:requires+provides requires+provides
#:in exp-body))
(for ([sym (in-list syms)])
;; In case `local-expand` created a binding with `sym` to a transformer
(namespace-unset-transformer! m-ns phase sym))
(add-defined-syms! requires+provides syms phase)
(log-expand partial-body-ctx 'exit-case `(,(m 'define-values) ,ids ,(m 'rhs)))
(cons
(semi-parsed-define-values exp-body syms ids (m 'rhs))
(loop tail? rest-bodys))]
[(define-syntaxes)
(log-expand partial-body-ctx 'prim-define-syntaxes disarmed-exp-body)
(define-match m disarmed-exp-body '(define-syntaxes (id ...) rhs))
(log-expand partial-body-ctx 'prepare-env)
(prepare-next-phase-namespace partial-body-ctx)
(log-expand partial-body-ctx 'phase-up)
(define ids (remove-use-site-scopes (m 'id) partial-body-ctx))
(check-no-duplicate-ids ids phase exp-body)
(check-ids-unbound ids phase requires+provides #:in exp-body)
(define syms (select-defined-syms-and-bind! ids defined-syms
self phase all-scopes-stx
#:frame-id frame-id
#:requires+provides requires+provides
#:in exp-body
#:as-transformer? #t))
(add-defined-syms! requires+provides syms phase #:as-transformer? #t)
;; Expand and evaluate RHS:
(define-values (exp-rhs parsed-rhs vals)
(expand+eval-for-syntaxes-binding 'define-syntaxes
(m 'rhs) ids
(struct*-copy expand-context partial-body-ctx
[lifts #f]
;; require lifts ok, others disallowed
[module-lifts #f]
[to-module-lifts #f]
[need-eventually-defined need-eventually-defined])
#:log-next? #f))
;; Install transformers in the namespace for expansion:
(for ([sym (in-list syms)]
[val (in-list vals)]
[id (in-list ids)])
(maybe-install-free=id-in-context! val id phase partial-body-ctx)
(namespace-set-transformer! m-ns phase sym val))
(log-expand partial-body-ctx 'exit-case `(,(m 'define-syntaxes) ,ids ,exp-rhs))
(define parsed-body (parsed-define-syntaxes (keep-properties-only exp-body) ids syms parsed-rhs))
(cons (if (expand-context-to-parsed? partial-body-ctx)
parsed-body
(expanded+parsed
(rebuild
exp-body
`(,(m 'define-syntaxes) ,ids ,exp-rhs))
parsed-body))
(loop tail? rest-bodys))]
[(#%require)
(log-expand partial-body-ctx 'prim-require disarmed-exp-body)
(define ready-body (remove-use-site-scopes disarmed-exp-body partial-body-ctx))
(define-match m ready-body '(#%require req ...))
(parse-and-perform-requires! (m 'req) exp-body #:self self
m-ns phase #:run-phase phase
requires+provides
#:declared-submodule-names declared-submodule-names
#:who 'module)
(log-expand partial-body-ctx 'exit-case ready-body)
(cons exp-body
(loop tail? rest-bodys))]
[(#%provide)
(log-expand partial-body-ctx 'prim-stop #f)
;; save for last pass
(cons exp-body
(loop tail? rest-bodys))]
[(module)
;; Submodule to parse immediately
(log-expand partial-body-ctx 'prim-submodule #f)
(define ready-body (remove-use-site-scopes exp-body partial-body-ctx))
(define submod
(expand-submodule ready-body self partial-body-ctx
#:is-star? #f
#:declared-submodule-names declared-submodule-names
#:mpis-to-reset mpis-to-reset
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled))
(cons submod
(loop tail? rest-bodys))]
[(module*)
;; Submodule to save for after this module
(log-expand partial-body-ctx 'prim-stop #f)
(cons exp-body
(loop tail? rest-bodys))]
[(#%declare)
(log-expand partial-body-ctx 'prim-declare disarmed-exp-body)
(define-match m disarmed-exp-body '(#%declare kw ...))
(for ([kw (in-list (m 'kw))])
(unless (keyword? (syntax-e kw))
(raise-syntax-error #f "expected a keyword" exp-body kw))
(unless (memq (syntax-e kw) '(#:cross-phase-persistent #:empty-namespace #:unsafe))
(raise-syntax-error #f "not an allowed declaration keyword" exp-body kw))
(when (hash-ref declared-keywords (syntax-e kw) #f)
(raise-syntax-error #f "keyword declared multiple times" exp-body kw))
(when (eq? (syntax-e kw) '#:unsafe)
(unless (eq? (current-code-inspector) initial-code-inspector)
(raise-syntax-error #f "unsafe compilation disallowed by code inspector" exp-body kw)))
(hash-set! declared-keywords (syntax-e kw) kw))
(define parsed-body (parsed-#%declare exp-body))
(cons (if (expand-context-to-parsed? partial-body-ctx)
parsed-body
(expanded+parsed exp-body parsed-body))
(loop tail? rest-bodys))]
[else
;; save expression for next pass
(log-expand partial-body-ctx 'prim-stop #f)
(cons exp-body
(loop tail? rest-bodys))]))])))
;; Convert lifted identifiers plus expression to a `define-values` form:
(define (make-wrap-as-definition self frame-id
inside-scope all-scopes-stx
defined-syms requires+provides)
(lambda (ids rhs phase)
(define scoped-ids (for/list ([id (in-list ids)])
(add-scope id inside-scope)))
(define syms
(select-defined-syms-and-bind! scoped-ids defined-syms
self phase all-scopes-stx
#:frame-id frame-id
#:requires+provides requires+provides))
(define s (add-scope (datum->syntax
#f
(list (datum->syntax (syntax-shift-phase-level core-stx phase)
'define-values)
scoped-ids
rhs))
inside-scope))
(values scoped-ids
(semi-parsed-define-values s syms scoped-ids rhs))))
(define (add-post-expansion-scope bodys ctx)
(define pe (root-expand-context-post-expansion ctx))
(if pe
(for/list ([body (in-list bodys)])
(apply-post-expansion pe body))
bodys))
;; ----------------------------------------
;; Pass 2 of `module` expansion, which expands all expressions
(define (finish-expanding-body-expressions partially-expanded-bodys
#:phase phase
#:ctx body-ctx
#:self self
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled
#:mpis-to-reset mpis-to-reset)
(let loop ([tail? #t] [bodys partially-expanded-bodys])
(cond
[(null? bodys)
(cond
[(and tail? (not (zero? phase)))
null]
[tail?
;; We're at the very end of the module, again, so check for lifted-to-end
;; declarations
(define bodys
(append
(get-and-clear-end-lifts! (expand-context-to-module-lifts body-ctx))
(get-and-clear-provide-lifts! (expand-context-to-module-lifts body-ctx))))
(cond
[(null? bodys)
null]
[else
(log-expand body-ctx 'module-end-lifts bodys)
(loop #t (add-post-expansion-scope bodys body-ctx))])]
[else null])]
[else
(log-expand body-ctx 'next)
(define body (car bodys))
(define rest-bodys (cdr bodys))
(define exp-body
(cond
[(or (parsed? body)
(expanded+parsed? body)
(semi-parsed-begin-for-syntax? body))
;; An already-parsed (enough for now) form
body]
[(semi-parsed-define-values? body)
(define ids (semi-parsed-define-values-ids body))
(define rhs-ctx (as-named-context (as-expression-context body-ctx) ids))
(define syms (semi-parsed-define-values-syms body))
(define s (semi-parsed-define-values-s body))
(define-match m (syntax-disarm s) #:unless (expand-context-to-parsed? rhs-ctx)
'(define-values _ _))
(define rebuild-s (keep-as-needed rhs-ctx s #:keep-for-parsed? #t))
(log-expand* body-ctx ['visit #f] ['enter-prim #f] ['prim-define-values #f])
(define exp-rhs (performance-region
['expand 'form-in-module/2]
(expand (semi-parsed-define-values-rhs body) rhs-ctx)))
(log-expand* body-ctx ['exit-prim/return #f])
(define comp-form
(parsed-define-values rebuild-s ids syms
(if (expand-context-to-parsed? rhs-ctx)
;; Have (and need only) parsed form
exp-rhs
;; Expand rhs again to parse it
(expand exp-rhs (as-to-parsed-context rhs-ctx)))))
(if (expand-context-to-parsed? rhs-ctx)
comp-form
(expanded+parsed
(rebuild
rebuild-s
`(,(m 'define-values) ,ids ,exp-rhs))
comp-form))]
[else
(define disarmed-body (syntax-disarm body))
(case (core-form-sym disarmed-body phase)
[(#%require #%provide module*)
;; handle earlier or later
body]
[else
(performance-region
['expand 'form-in-module/2]
(define exp-body (expand body (as-expression-context body-ctx)))
(if (expand-context-to-parsed? body-ctx)
;; Have (and need only) parsed form
exp-body
;; Expand again to parse it
(expanded+parsed
exp-body
(expand exp-body (as-to-parsed-context body-ctx)))))])]))
(define lifted-defns (get-and-clear-lifts! (expand-context-lifts body-ctx)))
(define lifted-requires
;; Get any requires and provides, keeping them as-is
(get-and-clear-require-lifts! (expand-context-require-lifts body-ctx)))
(define lifted-modules (get-and-clear-module-lifts! (expand-context-module-lifts body-ctx)))
(define no-lifts? (and (null? lifted-defns) (null? lifted-modules) (null? lifted-requires)))
(unless no-lifts?
(log-expand body-ctx 'module-pass2-lifts
lifted-requires
(add-post-expansion-scope lifted-modules body-ctx)
(lifted-defns-extract-syntax lifted-defns)))
(define exp-lifted-modules
;; If there were any module lifts, the `module` forms need to
;; be expanded
(expand-non-module*-submodules lifted-modules
phase
self
body-ctx
#:mpis-to-reset mpis-to-reset
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled))
(unless no-lifts? (log-expand body-ctx 'next-group))
(define exp-lifted-defns
;; If there were any lifts, the right-hand sides need to be expanded
(loop #f lifted-defns))
(unless no-lifts? (log-expand body-ctx 'next-group))
(append
lifted-requires
exp-lifted-modules
exp-lifted-defns
(cons exp-body
(loop tail? rest-bodys)))])))
(define (check-defined-by-now need-eventually-defined self ctx requires+provides)
;; If `need-eventually-defined` is not empty, report an error
(for ([(phase l) (in-hash need-eventually-defined)])
(for ([id (in-list (reverse l))])
(define b (resolve+shift id phase))
(define bound-here? (and b
(module-binding? b)
(eq? (module-binding-sym b) (syntax-e id))
(eq? (module-binding-module b) self)))
(define bound-kind (and bound-here?
(defined-sym-kind requires+provides (module-binding-sym b) phase)))
(unless (eq? bound-kind 'variable)
(raise-syntax-error #f
(string-append
(cond
[(not b) "reference to an unbound identifier"]
[(eq? bound-kind 'transformer) "identifier treated as a variable, but later defined as syntax"]
[else "identifier treated as a variable, but later bound differently"])
(format "\n at phase: ~a" (case phase
[(1) "1; the transformer environment"]
[else phase])))
id #f null
(syntax-debug-info-string id ctx))))))
;; ----------------------------------------
;; Pass 3 of `module` expansion, which parses `provide` forms and
;; matches them up with definitions and requires
(define (resolve-provides expression-expanded-bodys
#:requires-and-provides requires+provides
#:declared-submodule-names declared-submodule-names
#:namespace m-ns
#:phase phase
#:self self
#:ctx ctx)
(performance-region
['expand 'provide]
(let loop ([bodys expression-expanded-bodys] [phase phase])
(cond
[(null? bodys) null]
[(or (parsed? (car bodys))
(expanded+parsed? (car bodys)))
(log-expand ctx 'next)
(cons (car bodys)
(loop (cdr bodys) phase))]
[(semi-parsed-begin-for-syntax? (car bodys))
(log-expand ctx 'enter-begin-for-syntax)
(define nested-bodys (loop (semi-parsed-begin-for-syntax-body (car bodys)) (add1 phase)))
(log-expand ctx 'exit-begin-for-syntax)
;; Stil semi-parsed; finished in pass 4
(cons (struct-copy semi-parsed-begin-for-syntax (car bodys)
[body nested-bodys])
(loop (cdr bodys) phase))]
[else
(define disarmed-body (syntax-disarm (car bodys)))
(case (core-form-sym disarmed-body phase)
[(#%provide)
(log-expand* ctx ['enter-prim (car bodys)] ['prim-provide disarmed-body])
(define-match m disarmed-body '(#%provide spec ...))
(define-values (track-stxes specs)
(parse-and-expand-provides! (m 'spec) (car bodys)
requires+provides self
phase (struct*-copy expand-context ctx
[context 'top-level]
[phase phase]
[namespace (namespace->namespace-at-phase m-ns phase)]
[requires+provides requires+provides]
[declared-submodule-names declared-submodule-names])))
(cond
[(expand-context-to-parsed? ctx)
(loop (cdr bodys) phase)]
[else
(define new-s
(syntax-track-origin*
track-stxes
(rebuild
(car bodys)
`(,(m '#%provide) ,@specs))))
(log-expand ctx 'exit-prim new-s)
(cons new-s
(loop (cdr bodys) phase))])]
[else
(log-expand ctx 'next)
(cons (car bodys)
(loop (cdr bodys) phase))])]))))
;; ----------------------------------------
;; In support of pass 4, declare a module (in a temporary namespace)
;; before any `module*` submodule is expanded
(define (declare-module-for-expansion fully-expanded-bodys-except-post-submodules
#:module-name-id module-name-id
#:rebuild-s rebuild-s
#:requires-and-provides requires+provides
#:namespace m-ns
#:self self
#:enclosing enclosing-self
#:root-ctx root-ctx
#:ctx ctx
#:modules-being-compiled modules-being-compiled
#:fill compiled-module-box)
(define-values (requires provides) (extract-requires-and-provides requires+provides self self))
(define parsed-mod
(parsed-module rebuild-s
#f
module-name-id
self
requires
provides
(requires+provides-all-bindings-simple? requires+provides)
(root-expand-context-encode-for-module root-ctx self self)
(parsed-only fully-expanded-bodys-except-post-submodules)
#f
(hasheq)))
(define module-name (module-path-index-resolve (or enclosing-self self)))
(define compiled-module
(compile-module parsed-mod
(make-compile-context #:namespace m-ns
#:module-self enclosing-self
#:full-module-name (and enclosing-self
(resolved-module-path-name module-name)))
#:serializable? (expand-context-for-serializable? ctx)
#:to-correlated-linklet? (expand-context-to-correlated-linklet? ctx)
#:modules-being-compiled modules-being-compiled
#:need-compiled-submodule-rename? #f))
(set-box! compiled-module-box compiled-module)
(define root-module-name (resolved-module-path-root-name module-name))
(parameterize ([current-namespace m-ns]
[current-module-declare-name (make-resolved-module-path root-module-name)])
(eval-module compiled-module
#:with-submodules? #f)))
(define (attach-root-expand-context-properties s root-ctx orig-self new-self)
;; Original API:
(let* ([s (syntax-property s 'module-body-context (root-expand-context-all-scopes-stx root-ctx))]
[s (syntax-property s
'module-body-inside-context
(apply-post-expansion (root-expand-context-post-expansion root-ctx)
empty-syntax))])
s))
;; ----------------------------------------
;; Pass 4 of `module` expansion, which expands `module*` forms;
;; this pass muct happen after everything else for the module, since a
;; `module*` submodule can require from its enclosing module; in
;; addition to expanding `module*`, generate expanded `begin-for-syntax`
;; as needed and ensure that parsed `begin-for-syntax` has only parsed
;; forms
(define (expand-post-submodules fully-expanded-bodys-except-post-submodules
#:declare-enclosing declare-enclosing-module
#:phase phase
#:self self
#:requires-and-provides requires+provides
#:enclosing-is-cross-phase-persistent? enclosing-is-cross-phase-persistent?
#:all-scopes-s all-scopes-s
#:mpis-to-reset mpis-to-reset
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled
#:ctx submod-ctx)
(let loop ([bodys fully-expanded-bodys-except-post-submodules] [phase phase])
(cond
[(null? bodys) null]
[else
(define body (car bodys))
(define rest-bodys (cdr bodys))
(cond
[(semi-parsed-begin-for-syntax? body)
(define body-s (semi-parsed-begin-for-syntax-s body))
(log-expand submod-ctx 'enter-begin-for-syntax)
(define-match m (syntax-disarm body-s) '(begin-for-syntax _ ...))
(define rebuild-body-s (keep-as-needed submod-ctx body-s))
(define nested-bodys (loop (semi-parsed-begin-for-syntax-body body) (add1 phase)))
(define parsed-bfs (parsed-begin-for-syntax rebuild-body-s (parsed-only nested-bodys)))
(log-expand submod-ctx 'exit-begin-for-syntax)
(cons
(if (expand-context-to-parsed? submod-ctx)
parsed-bfs
(expanded+parsed
(rebuild rebuild-body-s `(,(m 'begin-for-syntax) ,@(syntax-only nested-bodys)))
parsed-bfs))
(loop rest-bodys phase))]
[(or (parsed? body)
(expanded+parsed? body))
;; We can skip any other parsed form
(log-expand submod-ctx 'next)
(cons body
(loop rest-bodys phase))]
[else
(define disarmed-body (syntax-disarm body))
(case (core-form-sym disarmed-body phase)
[(module*)
;; Ensure that the enclosing module is declared:
(force declare-enclosing-module)
(define ready-body (remove-use-site-scopes body submod-ctx))
(define-match f-m disarmed-body #:try '(module* name #f . _))
(define submod
(cond
[(f-m)
;; Need to shift the submodule relative to the enclosing module:
(define neg-phase (phase- 0 phase))
(define shifted-s (syntax-shift-phase-level ready-body neg-phase))
(define submod
(expand-submodule shifted-s self submod-ctx
#:is-star? #t
#:keep-enclosing-scope-at-phase neg-phase
#:enclosing-requires+provides requires+provides
#:enclosing-is-cross-phase-persistent? enclosing-is-cross-phase-persistent?
#:mpis-to-reset mpis-to-reset
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled))
(cond
[(parsed? submod) submod]
[(expanded+parsed? submod)
(struct-copy expanded+parsed submod
[s (syntax-shift-phase-level (expanded+parsed-s submod) phase)])]
[else (syntax-shift-phase-level submod phase)])]
[else
(expand-submodule ready-body self submod-ctx
#:is-star? #t
#:mpis-to-reset mpis-to-reset
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled)]))
(cons submod
(loop rest-bodys phase))]
[else
;; We can skip any other unparsed form
(log-expand submod-ctx 'next)
(cons body
(loop rest-bodys phase))])])])))
(define (stop-at-module*? ctx)
(free-id-set-member? (expand-context-stops ctx)
(expand-context-phase ctx)
(syntax-shift-phase-level (datum->syntax core-stx 'module*)
(expand-context-phase ctx))))
;; ----------------------------------------
(define (check-ids-unbound ids phase requires+provides #:in s)
(for ([id (in-list ids)])
(check-not-defined requires+provides id phase #:in s #:who 'module)))
;; ----------------------------------------
(define (eval-nested-bodys bodys phase m-ns self ctx)
;; The definitions and expression `bodys` are fully expanded and
;; parsed; evaluate them
(for ([body (in-list bodys)])
(define p (if (expanded+parsed? body)
(expanded+parsed-parsed body)
body))
(cond
[(parsed-define-values? p)
(define ids (parsed-define-values-ids p))
(define vals (eval-for-bindings 'define-values ids (parsed-define-values-rhs p) phase m-ns ctx))
(for ([id (in-list ids)]
[sym (in-list (parsed-define-values-syms p))]
[val (in-list vals)])
(namespace-set-variable! m-ns phase sym val))]
[(or (parsed-define-syntaxes? p)
(semi-parsed-begin-for-syntax? p))
;; already evaluated during expansion
(void)]
[(or (parsed-#%declare? p)
(syntax? p))
;; handled earlier or later
(void)]
[else
;; an expression
(parameterize ([current-namespace m-ns])
(parameterize-like
#:with ([current-expand-context ctx])
(eval-single-top
(compile-single p (make-compile-context
#:namespace m-ns
#:phase phase))
m-ns)))])))
;; ----------------------------------------
(define (expand-submodule s self ctx
#:is-star? is-star?
#:keep-enclosing-scope-at-phase [keep-enclosing-scope-at-phase #f]
#:enclosing-requires+provides [enclosing-r+p #f]
#:enclosing-is-cross-phase-persistent? [enclosing-is-cross-phase-persistent? #f]
#:mpis-to-reset mpis-to-reset
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled)
(log-expand* ctx ['enter-prim s] [(if is-star? 'prim-submodule* 'prim-submodule) #f])
;; Register name and check for duplicates
(define-match m s '(module name . _))
(define name (syntax-e (m 'name)))
(when (hash-ref declared-submodule-names name #f)
(raise-syntax-error #f "submodule already declared with the same name" s name))
(hash-set! declared-submodule-names name (syntax-e (m 'module)))
(log-expand* ctx ['enter-prim s])
(define submod
(expand-module s
(struct*-copy expand-context ctx
[context 'module]
[stops empty-free-id-set]
[post-expansion #:parent root-expand-context #f])
self
#:always-produce-compiled? #t
#:keep-enclosing-scope-at-phase keep-enclosing-scope-at-phase
#:enclosing-requires+provides enclosing-r+p
#:enclosing-is-cross-phase-persistent? enclosing-is-cross-phase-persistent?
#:mpis-for-enclosing-reset mpis-to-reset
#:modules-being-compiled modules-being-compiled))
(log-expand* ctx ['exit-prim (extract-syntax submod)])
;; Compile and declare the submodule for use by later forms
;; in the enclosing module:
(define ns (expand-context-namespace ctx))
(define module-name (module-path-index-resolve self))
(define root-module-name (resolved-module-path-root-name module-name))
(define compiled-submodule
(compile-module (if (expanded+parsed? submod)
(expanded+parsed-parsed submod)
submod)
(make-compile-context #:namespace ns
#:module-self self
#:full-module-name (resolved-module-path-name module-name))
#:force-linklet-directory? #t
#:serializable? (expand-context-for-serializable? ctx)
#:to-correlated-linklet? (expand-context-to-correlated-linklet? ctx)
#:modules-being-compiled modules-being-compiled
#:need-compiled-submodule-rename? #f))
(hash-set! compiled-submodules name (cons is-star? compiled-submodule))
(parameterize ([current-namespace ns]
[current-module-declare-name (make-resolved-module-path root-module-name)])
(eval-module compiled-submodule
#:with-submodules? #f))
;; Return the expanded submodule
(cond
[(not is-star?)
submod]
[(expanded+parsed? submod)
(struct-copy expanded+parsed submod
[parsed (struct-copy parsed-module (expanded+parsed-parsed submod)
[star? #t])])]
[else
(struct-copy parsed-module submod
[star? #t])]))
;; Expand `module` forms, leave `module*` forms alone:
(define (expand-non-module*-submodules bodys phase self ctx
#:mpis-to-reset mpis-to-reset
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled)
(for/list ([body (in-list bodys)])
(log-expand ctx 'next)
(case (core-form-sym (syntax-disarm body) phase)
[(module)
(expand-submodule body self ctx
#:is-star? #f
#:mpis-to-reset mpis-to-reset
#:declared-submodule-names declared-submodule-names
#:compiled-submodules compiled-submodules
#:modules-being-compiled modules-being-compiled)]
[else body])))
;; ----------------------------------------
(define (make-parse-lifted-require m-ns self requires+provides
#:declared-submodule-names declared-submodule-names)
(lambda (s phase)
(define-match m (syntax-disarm s) '(#%require req))
(parse-and-perform-requires! (list (m 'req)) s #:self self
m-ns phase #:run-phase phase
requires+provides
#:declared-submodule-names declared-submodule-names
#:who 'require)))
;; ----------------------------------------
(define (defn-extract-syntax defn)
(datum->syntax #f `(define-values ,(semi-parsed-define-values-ids defn)
,(semi-parsed-define-values-rhs defn))
(semi-parsed-define-values-s defn)))
(define (lifted-defns-extract-syntax lifted-defns)
(for/list ([lifted-defn (in-list lifted-defns)])
(defn-extract-syntax lifted-defn)))