
;; Unit system

(module unit mzscheme
  (require-for-syntax (lib "kerncase.ss" "syntax")
		      (lib "stx.ss" "syntax")
		      (lib "name.ss" "syntax")
		      (lib "context.ss" "syntax")
		      "list.ss"
		      "private/unitidmap.ss")

  ;; ----------------------------------------------------------------------
  ;; Structures and helpers

  (define undefined (letrec ([x x]) x)) ; initial value

  (define insp (current-inspector)) ; for named structures

  (define-struct unit (num-imports exports go)) ; unit value
  (define-struct (exn:fail:unit exn:fail) ()) ; run-time exception

  ;; For units with inferred names, generate a struct that prints using the name:
  (define (make-naming-constructor type name)
    (let-values ([(struct: make- ? -accessor -mutator)
		  (make-struct-type name type 0 0 #f null insp)])
      make-))

  ;; Make a unt value (call by the macro expansion of `unit')
  (define (make-a-unit name num-imports exports go)
    ((if name 
	 (make-naming-constructor 
	  struct:unit
	  (string->symbol (format "unit:~a" name)))
	 make-unit)
     num-imports exports go))

  ;; ----------------------------------------------------------------------
  ;; The `unit' syntactic form

  (define-syntaxes (_unit unit/no-expand)
    (let ([do-unit 
	   (lambda (stx expand?)
	     (syntax-case stx (import export)
	       [(_ (import ivar ...)
		   (export evar ...)
		   defn&expr ...)
		(let ([check-id (lambda (v)
				  (unless (identifier? v)
				    (raise-syntax-error
				     #f
				     "import is not an identifier"
				     stx
				     v)))]
		      [check-renamed-id 
		       (lambda (v)
			 (syntax-case v ()
			   [id (identifier? (syntax id)) (list v)]
			   [(lid eid) (and (identifier? (syntax lid))
					   (identifier? (syntax eid))) 
			    (list #'lid #'eid)]
			   [else (raise-syntax-error
				  #f
				  "export is not an identifier or renamed identifier"
				  stx
				  v)]))]
		      [expand-context (generate-expand-context)]
		      [def-ctx (and expand?
				    (syntax-local-make-definition-context))]
		      [localify (lambda (ids def-ctx)
				  (if (andmap identifier? ids)
				      ;; In expand mode, add internal defn context
				      (if expand?
					  (begin
					    ;; Treat imports as internal-defn names:
					    (syntax-local-bind-syntaxes ids #f def-ctx)
					    (cdr (syntax->list
						  (local-expand #`(stop #,@ids)
								'expression
								(list #'stop)
								def-ctx))))
					  ids)
				      ;; Let later checking report an error:
				      ids))])
		  (let ([ivars (localify (syntax->list (syntax (ivar ...))) def-ctx)]
			[evars (syntax->list (syntax (evar ...)))])
		    (for-each check-id ivars)
		    (for-each check-renamed-id evars)
		    
		    ;; Get import/export declared names:
		    (let* ([exported-names
			    (localify
			     (map (lambda (v)
				    (syntax-case v ()
				      [(lid eid) (syntax lid)]
				      [id (syntax id)]))
				  evars)
			     def-ctx)]
			   [extnames (map (lambda (v)
					    (syntax-case v ()
					      [(lid eid) (syntax eid)]
					      [id (syntax id)]))
					  evars)]
			   [imported-names ivars]
			   [declared-names (append imported-names exported-names)])
		      ;; Check that all exports are distinct (as symbols)
		      (let ([ht (make-hash-table)])
			(for-each (lambda (name)
				    (when (hash-table-get ht (syntax-e name) (lambda () #f))
				      (raise-syntax-error
				       #f
				       "duplicate export"
				       stx
				       name))
				    (hash-table-put! ht (syntax-e name) #t))
				  extnames))

		      ;; Expand all body expressions
		      ;; so that all definitions are exposed.
		      (letrec ([expand-all
				(if expand?
				    (lambda (defns&exprs)
				      (apply
				       append
				       (map
					(lambda (defn-or-expr)
					  (let ([defn-or-expr
						  (local-expand
						   defn-or-expr
						   expand-context
						   (append
						    (kernel-form-identifier-list (quote-syntax here))
						    declared-names)
						   def-ctx)])
					    (syntax-case defn-or-expr (begin define-values define-syntaxes)
					      [(begin . l)
					       (let ([l (syntax->list (syntax l))])
						 (unless l
						   (raise-syntax-error
						    #f
						    "bad syntax (illegal use of `.')"
						    defn-or-expr))
						 (expand-all (map (lambda (s)
								    (syntax-track-origin s defn-or-expr #'begin))
								  l)))]
					      [(define-syntaxes (id ...) rhs)
					       (andmap identifier? (syntax->list #'(id ...)))
					       (with-syntax ([rhs (local-transformer-expand
								   #'rhs
								   'expression
								   null)])
						 (syntax-local-bind-syntaxes (syntax->list #'(id ...)) #'rhs def-ctx)
						 (list #'(define-syntaxes (id ...) rhs)))]
					      [(define-values (id ...) rhs)
					       (andmap identifier? (syntax->list #'(id ...)))
					       (begin
						 (syntax-local-bind-syntaxes (syntax->list #'(id ...)) #f def-ctx)
						 (list defn-or-expr))]
					      [else (list defn-or-expr)])))
					defns&exprs)))
				    values)])
			(let ([all-expanded (expand-all (syntax->list (syntax (defn&expr ...))))])
			  ;; Get all the defined names, sorting out variable definitions
			  ;; from syntax definitions.
			  (let* ([definition?
				   (lambda (id)
				     (and (identifier? id)
					  (or (module-identifier=? id (quote-syntax define-values))
					      (module-identifier=? id (quote-syntax define-syntaxes)))))]
				 [all-defined-names/kinds
				  (apply
				   append
				   (map
				    (lambda (defn-or-expr)
				      (syntax-case defn-or-expr (define-values define-syntaxes)
					[(dv (id ...) expr)
					 (definition? (syntax dv))
					 (let ([l (syntax->list (syntax (id ...)))])
					   (for-each (lambda (i)
						       (unless (identifier? i)
							 (raise-syntax-error
							  #f
							  "not an identifier in definition"
							  defn-or-expr
							  i)))
						     l)
					   (let ([key (if (module-identifier=? (syntax dv) (quote-syntax define-syntaxes))
							  'stx
							  'val)])
					     (map (lambda (id) (cons key id)) l)))]
					[(define-values . l)
					 (raise-syntax-error
					  #f
					  "bad definition form"
					  defn-or-expr)]
					[(define-syntaxes . l)
					 (raise-syntax-error
					  #f
					  "bad syntax definition form"
					  defn-or-expr)]
					[else null]))
				    all-expanded))]
				 [all-defined-names (map cdr all-defined-names/kinds)]
				 [all-defined-val-names (map cdr 
							     (filter (lambda (i) (eq? (car i) 'val))
								     all-defined-names/kinds))])
			    ;; Check that all defined names (var + stx) are distinct:
			    (let ([name (check-duplicate-identifier
					 (append imported-names all-defined-names))])
			      (when name
				(raise-syntax-error 
				 #f
				 "variable imported and/or defined twice"
				 stx
				 name)))
			    ;; Check that all exported names are defined (as var):
			    (let ([ht (make-hash-table)]
				  [stx-ht (make-hash-table)])
			      (for-each
			       (lambda (kind+name)
				 (let ([name (cdr kind+name)])
				   (let ([l (hash-table-get ht (syntax-e name) (lambda () null))])
				     (hash-table-put! (if (eq? (car kind+name) 'val) ht stx-ht)
						      (syntax-e name) 
						      (cons name l)))))
			       all-defined-names/kinds)
			      (for-each 
			       (lambda (n)
				 (let ([v (hash-table-get ht (syntax-e n) (lambda () null))])
				   (unless (ormap (lambda (i) (bound-identifier=? i n)) v)
				     ;; Either not defined, or defined as syntax:
				     (let ([stx-v (hash-table-get stx-ht (syntax-e n) (lambda () null))])
				       (if (ormap (lambda (i) (bound-identifier=? i n)) stx-v)
					   (raise-syntax-error
					    #f
					    "cannot export syntax from a unit"
					    stx
					    n)
					   (raise-syntax-error
					    #f
					    "exported variable is not defined"
					    stx
					    n))))))
			       exported-names))

			    ;; Compute defined but not exported:
			    (let ([ht (make-hash-table)])
			      (for-each
			       (lambda (name)
				 (let ([l (hash-table-get ht (syntax-e name) (lambda () null))])
				   (hash-table-put! ht (syntax-e name) (cons name l))))
			       exported-names)
			      (let ([internal-names
				     (let loop ([l all-defined-val-names])
				       (cond
					[(null? l) null]
					[(let ([v (hash-table-get ht (syntax-e (car l)) (lambda () null))])
					   (ormap (lambda (i) (bound-identifier=? i (car l))) v))
					 (loop (cdr l))]
					[else (cons (car l) (loop (cdr l)))]))])
				;; Generate names for import/export boxes, etc:
				(with-syntax ([(ivar ...) ivars]
					      [(iloc ...) (generate-temporaries ivars)]
					      [(eloc ...) (generate-temporaries evars)]
					      [(extname ...) extnames]
					      [(expname ...) exported-names]
					      [(intname ...) internal-names])
				  ;; Change all definitions to set!s. Convert evars to set-box!,
				  ;; because set! on exported variables is not allowed.
				  (with-syntax ([(defn&expr ...) 
						 (let ([elocs (syntax->list (syntax (eloc ...)))])
						   (filter
						    values
						    (map (lambda (defn-or-expr)
							   (syntax-case defn-or-expr (define-values define-syntaxes)
							     [(define-values ids expr)
							      (let* ([ids (syntax->list (syntax ids))])
								(if (null? ids)
								    (syntax/loc defn-or-expr (set!-values ids expr))
								    (let ([do-one
									   (lambda (id tmp name)
									     (let loop ([evars exported-names]
											[elocs elocs])
									       (cond
										[(null? evars)
										 ;; not an exported id
										 (with-syntax ([id id][tmp tmp])
										   (syntax/loc
										       defn-or-expr
										     (set! id tmp)))]
										[(bound-identifier=? (car evars) id)
										 ;; set! exported id:
										 (with-syntax 
										     ([loc (car elocs)]
										      [tmp 
										       (if name
											   (with-syntax 
											       ([tmp tmp]
												[name name])
											     (syntax 
											      (let ([name tmp])
												name)))
											   tmp)])
										   (syntax/loc defn-or-expr
										     (set-box! loc tmp)))]
										[else (loop (cdr evars) 
											    (cdr elocs))])))])
								      (if (null? (cdr ids))
									  (do-one (car ids) (syntax expr) (car ids))
									  (let ([tmps (generate-temporaries ids)])
									    (with-syntax ([(tmp ...) tmps]
											  [(set ...)
											   (map (lambda (id tmp)
												  (do-one id tmp #f))
												ids tmps)])
									      (syntax/loc defn-or-expr
										(let-values ([(tmp ...) expr])
										  set ...))))))))]
							     [(define-syntaxes . l) #f]
							     [else defn-or-expr]))
							 all-expanded)))]
						[(stx-defn ...) 
						 (filter
						  values
						  (map (lambda (defn-or-expr)
							 (syntax-case defn-or-expr (define-syntaxes)
							   [(define-syntaxes . l) #'l]
							   [else #f]))
						       all-expanded))])
				    ;; Build up set! redirection chain:
				    (with-syntax ([redirections
						   (let ([varlocs 
							  (syntax->list 
							   (syntax ((ivar iloc) ... (expname eloc) ...)))])
						     (with-syntax ([vars (map stx-car varlocs)]
								   [rhss
								    (map
								     (lambda (varloc)
								       (with-syntax ([(var loc) varloc])
									 (syntax
									  (make-id-mapper (quote-syntax (unbox loc))
											  (quote-syntax var)))))
								     varlocs)])
						       (syntax
							([vars (values . rhss)]))))]
						  [num-imports (datum->syntax-object
								(quote-syntax here)
								(length (syntax->list (syntax (iloc ...))))
								#f)]
						  [name (syntax-local-infer-name stx)])
				      (syntax/loc stx
					(make-a-unit
					 'name
					 num-imports
					 (list (quote extname) ...)
					 (lambda ()
					   (let ([eloc (box undefined)] ...)
					     (list (vector eloc ...)
						   (lambda (iloc ...)
						     (letrec-syntaxes+values 
						      (stx-defn ... . redirections)
						      ([(intname) undefined] ...)
						      (void) ; in case the body would be empty
						      defn&expr ...))))))))))))))))))]))])
      (values (lambda (stx) (do-unit stx #t))
	      (lambda (stx) (do-unit stx #f)))))

  ;; ----------------------------------------------------------------------
  ;; check-expected-interface: used by the expansion of `compound-unit'
  
  (define (check-expected-interface tag unit num-imports exports)
    (unless (unit? unit)
      (raise
       (make-exn:fail:unit
	(string->immutable-string
	 (format "compound-unit: result of expression for tag ~s not a unit: ~e" tag unit))
	(current-continuation-marks))))
    (unless (= num-imports (unit-num-imports unit))
      (raise
       (make-exn:fail:unit
	(string->immutable-string
	 (format "compound-unit: unit for tag ~s expects ~a imports, given ~a" 
		 tag
		 (unit-num-imports unit)
		 num-imports))
	(current-continuation-marks))))
    (list->vector
     (map (lambda (ex)
	    (let loop ([l (unit-exports unit)][i 0])
	      (cond
	       [(null? l)
		(raise
		 (make-exn:fail:unit
		  (string->immutable-string
		   (format "compound-unit: unit for tag ~s has no ~s export" 
			   tag ex))
		  (current-continuation-marks)))]
	       [(eq? (car l) ex)
		i]
	       [else (loop (cdr l) (add1 i))])))
	  exports)))

  ;; ----------------------------------------------------------------------
  ;; The `compound-unit' syntactic form

  (define-syntax compound-unit
    (lambda (stx)
      (syntax-case stx (import export link)
	[(_ (import ivar ...)
	    (link [tag (unit-expr linkage ...)] ...)
	    (export exportage ...))
	 (let ([check-id (lambda (v)
			   (unless (identifier? v)
			     (raise-syntax-error
			      #f
			      "import is not an identifier"
			      stx
			      v)))]
	       [check-tag (lambda (v)
			   (unless (identifier? v)
			     (raise-syntax-error
			      #f
			      "tag is not an identifier"
			      stx
			      v)))]
	       [check-linkage (lambda (v)
				(syntax-case v ()
				  [id (identifier? (syntax id)) #t]
				  [(tag id ...)
				   (for-each (lambda (v)
					       (unless (identifier? v)
						 (raise-syntax-error
						  #f
						  "non-identifier in linkage"
						  stx
						  v)))
					     (syntax->list v))]
				  [else
				   (raise-syntax-error
				    #f
				    "ill-formed linkage"
				    stx
				    v)]))]
	       [check-exportage (lambda (v)
				  (syntax-case v ()
				    [(tag ex ...)
				     (begin
				       (unless (identifier? (syntax tag))
					 (raise-syntax-error
					  #f
					  "export tag is not an identifier"
					  stx
					  (syntax tag)))
				       (for-each 
					(lambda (e)
					  (syntax-case e ()
					    [id (identifier? (syntax id)) #t]
					    [(iid eid)
					     (begin
					       (unless (identifier? (syntax iid))
						 (raise-syntax-error
						  #f
						  "export internal name is not an identifier"
						  stx
						  (syntax iid)))
					       (unless (identifier? (syntax eid))
						 (raise-syntax-error
						  #f
						  "export internal name is not an identifier"
						  stx
						  (syntax eid))))]
					    [else
					     (raise-syntax-error
					      #f
					      (format "ill-formed export with tag ~a" 
						      (syntax-e (syntax tag)))
					      stx
					      e)]))
					(syntax->list (syntax (ex ...)))))]
				    [else
				     (raise-syntax-error
				      #f
				      "ill-formed export"
				      stx
				      v)]))]
	       [imports (syntax->list (syntax (ivar ...)))]
	       [tags (syntax->list (syntax (tag ...)))]
	       [linkages (map syntax->list (syntax->list (syntax ((linkage ...) ...))))]
	       [exports (syntax->list (syntax (exportage ...)))])
	   ;; Syntax checks:
	   (for-each check-id imports)
	   (for-each check-tag tags)
	   (for-each (lambda (l) (for-each check-linkage l)) linkages)
	   (for-each check-exportage exports)
	   ;; Check for duplicate imports
	   (let ([dup (check-duplicate-identifier imports)])
	     (when dup
	       (raise-syntax-error
		#f
		"duplicate import"
		stx
		dup)))
	   ;; Check for duplicate tags
	   (let ([dup (check-duplicate-identifier tags)])
	     (when dup
	       (raise-syntax-error
		#f
		"duplicate tag"
		stx
		dup)))
	   ;; Check referenced imports and tags
	   (let ([check-linkage-refs (lambda (v)
				       (syntax-case v ()
					 [(tag . exs)
					  (unless (ormap (lambda (t)
							   (bound-identifier=? t (syntax tag)))
							 tags)
					    (raise-syntax-error
					     #f
					     "linkage tag is not bound"
					     stx
					     (syntax tag)))]
					 [id (unless (ormap (lambda (i)
							      (bound-identifier=? i (syntax id)))
							    imports)
					       (raise-syntax-error
						#f
						"no imported identified for linkage"
						stx
						(syntax id)))]))]
		 [check-export-refs (lambda (v)
				      (syntax-case v ()
					[(tag . r)
					 (unless (ormap (lambda (t)
							  (bound-identifier=? t (syntax tag)))
							tags)
					   (raise-syntax-error
					    #f
					    "export tag is not bound"
					    stx
					    (syntax tag)))]))])
	     (for-each (lambda (l) (for-each check-linkage-refs l))
		       linkages)
	     (for-each check-export-refs exports)
	     ;; Get all export names, and check for duplicates
	     (let ([export-names
		    (apply
		     append
		     (map
		      (lambda (v)
			(syntax-case v ()
			  [(tag . exs)
			   (map
			    (lambda (e)
			      (syntax-case e ()
				[(iid eid) (syntax eid)]
				[id e]))
			    (syntax->list (syntax exs)))]))
		      exports))])
	       (let ([dup (check-duplicate-identifier export-names)])
		 (when dup
		   (raise-syntax-error
		    #f
		    "duplicate export"
		    stx
		    dup)))

	       (let ([constituents (generate-temporaries tags)]
		     [unit-export-positionss (generate-temporaries tags)]
		     [unit-setups (generate-temporaries tags)]
		     [unit-extracts (generate-temporaries tags)]
		     [unit-export-lists
		      ;; For each tag, get all expected exports
		      (let* ([hts (map (lambda (x) (make-hash-table)) tags)]
			     [get-add-name 
			      (lambda (tag)
				(ormap (lambda (t ht)
					 (and (bound-identifier=? t tag)
					      (lambda (name)
						(hash-table-put! ht (syntax-e name) name))))
				       tags hts))])
			;; Walk though linkages
			(for-each
			 (lambda (linkage-list)
			   (for-each 
			    (lambda (linkage)
			      (syntax-case linkage ()
				[(tag . ids)
				 (let ([add-name (get-add-name (syntax tag))])
				   (for-each add-name (syntax->list (syntax ids))))]
				[else (void)]))
			    linkage-list))
			 linkages)
			;; Walk through exports
			(for-each
			 (lambda (v)
			   (syntax-case v ()
			     [(tag . exs)
			      (let ([add-name (get-add-name (syntax tag))])
				(for-each 
				 (lambda (e)
				   (syntax-case e ()
				     [(iid eid) (add-name (syntax iid))]
				     [id (add-name (syntax id))]))
				 (syntax->list (syntax exs))))]))
			 exports)
			;; Extract names from hash tables
			(map (lambda (ht)
			       (hash-table-map ht (lambda (k v) v)))
			     hts))])
		 ;; Map exports to imports and indices based on expected unit exports
		 (let ([map-tag (lambda (t l)
				  (let loop ([tags tags][l l])
				    (if (bound-identifier=? (car tags) t)
					(car l)
					(loop (cdr tags) (cdr l)))))]
		       [unit-export-hts (map (lambda (export-list)
					       (let ([ht (make-hash-table)])
						 (let loop ([l export-list][p 0])
						   (unless (null? l)
						     (hash-table-put! ht (syntax-e (car l)) p)
						     (loop (cdr l) (add1 p))))
						 ht))
					     unit-export-lists)]
		       [interned-integer-lists null]
		       [interned-id-lists null])
		   (let ([make-mapping
			  (lambda (v)
			    (syntax-case v ()
			      [(tag . exs)
			       (let ([extract (map-tag (syntax tag)
						       unit-extracts)]
				     [ht (map-tag (syntax tag)
						  unit-export-hts)])
				 (with-syntax ([extract extract]
					       [pos-name
						(let ([il
						       (map
							(lambda (e)
							  (hash-table-get
							   ht
							   (syntax-e
							    (syntax-case e ()
							      [(iid eid) (syntax iid)]
							      [id e]))))
							(syntax->list (syntax exs)))])
						  (or (ormap (lambda (i)
							       (and (equal? il (cadadr i))
								    (car i)))
							     interned-integer-lists)
						      (let ([name (car (generate-temporaries 
									(list (syntax tag))))])
							(set! interned-integer-lists
							      (cons `(,name ',il)
								    interned-integer-lists))
							name)))])
				   (syntax (map extract pos-name))))]
			      [import v]))]
			 [collapse (lambda (l)
				     (let loop ([l l])
				       (cond
					[(null? l) null]
					[(identifier? (car l))
					 (let-values ([(ids rest)
						       (let loop ([l l][ids null])
							 (if (or (null? l)
								 (not (identifier? (car l))))
							     (values (reverse ids) l)
							     (loop (cdr l) (cons (car l) ids))))])
					   (let ([name
						  (let ([id-syms (map syntax-e ids)])
						    (or (ormap (lambda (i)
								 (and (equal? id-syms (cadr i))
								      (car i)))
							       interned-id-lists)
							(let ([name 
							       (car (generate-temporaries (list 'ids)))])
							  (set! interned-id-lists
								(cons (list* name id-syms ids)
								      interned-id-lists))
							  name)))])
					     (cons name
						   (loop rest))))]
					[else (cons (car l) (loop (cdr l)))])))])
		     (let ([export-mapping (collapse (map make-mapping exports))]
			   [import-mappings (map (lambda (linkage-list)
						   (collapse
						    (map make-mapping linkage-list)))
						 linkages)])
		       (with-syntax ([(constituent ...) constituents]
				     [(unit-export-positions ...) unit-export-positionss]
				     [(unit-setup ...) unit-setups]
				     [(unit-extract ...) unit-extracts]
				     [interned-integer-lists interned-integer-lists]
				     [interned-id-lists (map (lambda (i)
							       (with-syntax ([name (car i)]
									     [ids (cddr i)])
								 (syntax [name (list . ids)])))
							     interned-id-lists)]
				     [(unit-export-list ...) unit-export-lists]
				     [(import-mapping ...) import-mappings]
				     [(unit-import-count ...) 
				      (map (lambda (l) 
					     (datum->syntax-object
					      (quote-syntax here)
					      (apply
					       +
					       (map (lambda (v)
						      (if (identifier? v)
							  1
							  (length (cdr (syntax->list v)))))
						    l))
					      #f))
					   linkages)]
				     [num-imports (datum->syntax-object
						   (quote-syntax here)
						   (length imports)
						   #f)]
				     [export-names export-names]
				     [export-mapping export-mapping]
				     [name (syntax-local-infer-name stx)])
			 (syntax/loc
			  stx
			  (let ([constituent unit-expr]
				...)
			    (let ([unit-export-positions
				   (check-expected-interface 
                                    'tag
				    constituent
				    unit-import-count
				    'unit-export-list)]
				  ...)
			      (make-a-unit
			       'name
			       num-imports
			       (quote export-names)
			       (lambda ()
				 (let ([unit-setup ((unit-go constituent))] ...)
				   (let ([unit-extract
					  (lambda (pos)
					    (vector-ref (car unit-setup)
							(vector-ref unit-export-positions pos)))]
					 ...
					 .
					 interned-integer-lists)
				     (list (list->vector (append . export-mapping))
					   (lambda (ivar ...)
					     (let interned-id-lists
						 (void) ;; in case there are no units
					       (apply (list-ref unit-setup 1) 
						      (append . import-mapping))
					       ...))))))))))))))))))])))

  ;; ----------------------------------------------------------------------
  ;; check-unit: used by the expansion of `invoke-unit'
  
  (define (check-unit u n)
    (unless (unit? u)
      (raise
       (make-exn:fail:unit
	(string->immutable-string
	 (format "invoke-unit: result of unit expression was not a unit: ~e" u))
	(current-continuation-marks))))
    (unless (= (unit-num-imports u) n)
      (raise
       (make-exn:fail:unit
	(string->immutable-string
	 (format "invoke-unit: expected a unit with ~a imports, given one with ~a imports"
		 n (unit-num-imports u)))
	(current-continuation-marks)))))

  ;; ----------------------------------------------------------------------
  ;; The `invoke-unit' syntactic form
  
  (define-syntax invoke-unit
    (lambda (stx)
      (syntax-case stx (import export)
	[(_ unit-expr expr ...)
	 (let ([exprs (syntax (expr ...))])
	   (with-syntax ([(bx ...) (generate-temporaries (syntax (expr ...)))]
			 [num (datum->syntax-object
			       (quote-syntax here)
			       (length (syntax->list exprs)) 
			       #f)])
	     (syntax/loc
	      stx
	      (let ([u unit-expr])
		(check-unit u num)
		(let ([bx (box expr)] ...)
		  ((list-ref ((unit-go u)) 1)
		   bx ...))))))])))

  (define-syntaxes (define-values/invoke-unit
		     namespace-variable-bind/invoke-unit)
    (let ([mk
	   (lambda (global?)
	     (lambda (stx)
	       (syntax-case stx ()
		 [(_ exports unite . prefix+imports)
		  (let* ([badsyntax (lambda (s why)
				      (raise-syntax-error
				       #f
				       (format "bad syntax (~a)" why)
				       stx
				       s))]
			 [symcheck (lambda (s)
				     (or (identifier? s)
					 (badsyntax s "not an identifier")))])
		    (unless (stx-list? (syntax exports))
		      (badsyntax (syntax exports) "not a sequence of identifiers"))
		    (for-each symcheck (syntax->list (syntax exports)))
		    (let ([prefix (if (stx-null? (syntax prefix+imports))
				      #f
				      (stx-car (syntax prefix+imports)))])
		      (unless (or (not prefix)
				  (not (syntax-e prefix))
				  (identifier? prefix))
			(badsyntax prefix "prefix is not an identifier"))
		      (for-each symcheck (let ([v (syntax prefix+imports)])
					   (cond
					    [(stx-null? v) null]
					    [(stx-list? v) (cdr (syntax->list v))]
					    [else
					     (badsyntax (syntax prefix+imports) "illegal use of `.'")])))
		      (with-syntax ([(tagged-export ...) 
				     (if (and prefix (syntax-e prefix))
					 (let ([prefix (string-append
							(symbol->string 
							 (syntax-e prefix))
						      ":")])
					   (map (lambda (s)
						  (datum->syntax-object
						   s
						   (string->symbol
						    (string-append
						     prefix
						     (symbol->string (syntax-e s))))
						   s))
						(syntax->list (syntax exports))))
					 (syntax exports))]
				    [extract-unit (syntax (_unit
							    (import . exports)
							    (export)
							    (values . exports)))])
			(with-syntax ([invoke-unit (with-syntax ([(x . imports)
								  (if prefix
								      (syntax prefix+imports)
								      `(#f))])
						     (syntax (invoke-unit
							      (compound-unit
							       (import . imports)
							       (link [unit-to-invoke (unite . imports)]
								     [export-extractor 
								      (extract-unit (unit-to-invoke . exports))])
							       (export))
							      . imports)))])
			  (if global?
			      (syntax (let-values ([(tagged-export ...) invoke-unit])
					(namespace-set-variable-value! 'tagged-export tagged-export)
					...
					(void)))
			      (syntax (define-values (tagged-export ...) invoke-unit)))))))])))])
      (values (mk #f) (mk #t))))
  
  (provide (rename _unit unit) unit/no-expand
	   compound-unit invoke-unit unit?
	   (struct exn:fail:unit ())

	   define-values/invoke-unit
	   namespace-variable-bind/invoke-unit))
