src/module-impl.lisp, src/module-parse.lisp: Set module environment better.
[sod] / src / module-impl.lisp
index fe6b545..fc068d3 100644 (file)
@@ -7,7 +7,7 @@
 
 ;;;----- Licensing notice ---------------------------------------------------
 ;;;
-;;; This file is part of the Sensble Object Design, an object system for C.
+;;; This file is part of the Sensible Object Design, an object system for C.
 ;;;
 ;;; SOD is free software; you can redistribute it and/or modify
 ;;; it under the terms of the GNU General Public License as published by
 
 (cl:in-package #:sod)
 
+;; Establish a standard environment within the body of a module.  This is an
+;; attempt -- but not a wholly successful one -- to present the same
+;; environment to a module independent of the context in which we imported
+;; it.
+
+;;;--------------------------------------------------------------------------
+;;; Module variables.
+
+(eval-when (:load-toplevel :execute)
+  (macrolet ((fix (var &optional (value var))
+              (once-only (value)
+                `(add-module-binding ',var (lambda () ,value)))))
+
+    ;; Use `sod-user' package by default.  This seems the most useful.  Alas,
+    ;; some tenants might not keep it as tidy as we'd like, but there are
+    ;; probably useful ways to side-effect the package too.
+    (fix *package* (find-package "SOD-USER"))
+
+    ;; Stream bindings.  Hope that the values we find at load time are
+    ;; sufficiently sensible.
+    (fix *debug-io*)
+    (fix *error-output*)
+    (fix *query-io*)
+    (fix *standard-input*)
+    (fix *standard-output*)
+    (fix *terminal-io*)
+    (fix *trace-output*)
+
+    ;; Print state.
+    (fix *print-array* t)
+    (fix *print-base* 10)
+    (fix *print-case* :upcase)
+    (fix *print-circle* nil)
+    (fix *print-escape* t)
+    (fix *print-gensym* t)
+    (fix *print-length* nil)
+    (fix *print-level* nil)
+    (fix *print-lines* nil)
+    (fix *print-miser-width*)
+    (fix *print-pretty* t)
+    (fix *print-radix* nil)
+    (fix *print-readably* nil)
+    (fix *print-right-margin*)
+
+    ;; Read state.
+    (fix *read-base* 10)
+    (fix *read-eval* t)
+    (fix *read-suppress* nil)
+    (fix *readtable* (copy-readtable nil))))
+
 ;;;--------------------------------------------------------------------------
 ;;; Module basics.
 
     ;; exercise the property-set fiddling in `shared-initialize' and we can
     ;; catch unknown-property errors.
     (change-class module class :state t :pset pset)
-    (check-unused-properties pset)
-    module))
+    (check-unused-properties pset)))
 
 ;;;--------------------------------------------------------------------------
 ;;; Module objects.
 
-(defparameter *module-map* (make-hash-table :test #'equal)
+(defvar-unbound *module-map*
   "Hash table mapping true names to module objects.")
+(define-clear-the-decks reset-module-map
+  (setf *module-map* (make-hash-table :test #'equal)))
 
 (defun build-module
     (name thunk &key (truename (probe-file name)) location)
     (let ((existing (gethash truename *module-map*)))
       (cond ((null existing))
            ((eq (module-state existing) t)
+            (when (plusp (module-errors existing))
+              (error "Module `~A' contains errors" name))
             (return-from build-module existing))
            (t
-            (error "Module ~A already being imported at ~A"
+            (error "Module `~A' already being imported at ~A"
                    name (module-state existing))))))
 
   ;; Construct the new module.
         (with-module-environment ()
           (module-import *builtin-module*)
           (funcall thunk)
-          (finalize-module *module*))
+          (finalize-module *module*)
+          *module*)
       (when (and truename (not (eq (module-state *module*) t)))
        (remhash truename *module-map*)))))
 
   (progv
       (mapcar #'car *module-bindings-alist*)
       (module-variables module)
-    (unwind-protect (funcall thunk)
-      (setf (module-variables module)
-           (mapcar (compose #'car #'symbol-value)
-                   *module-bindings-alist*)))))
+    (handler-bind ((error (lambda (cond)
+                           (declare (ignore cond))
+                           (incf (slot-value module 'errors))
+                           :decline)))
+      (unwind-protect (funcall thunk)
+       (setf (module-variables module)
+             (mapcar (compose #'car #'symbol-value)
+                     *module-bindings-alist*))))))
 
 (defun call-with-temporary-module (thunk)
   "Invoke THUNK in the context of a temporary module, returning its values.
 ;;;--------------------------------------------------------------------------
 ;;; Code fragments.
 
-(export 'c-fragment)
-(defclass c-fragment ()
-  ((location :initarg :location :type file-location
-            :accessor c-fragment-location)
-   (text :initarg :text :type string :accessor c-fragment-text))
-  (:documentation
-   "Represents a fragment of C code to be written to an output file.
-
-   A C fragment is aware of its original location, and will bear proper #line
-   markers when written out."))
-
-(defun output-c-excursion (stream location thunk)
-  "Invoke THUNK surrounding it by writing #line markers to STREAM.
+(defun output-c-excursion (stream location func)
+  "Invoke FUNC surrounding it by writing #line markers to STREAM.
 
    The first marker describes LOCATION; the second refers to the actual
    output position in STREAM.  If LOCATION doesn't provide a line number then
    no markers are output after all.  If the output stream isn't
-   position-aware then no final marker is output."
-
-  (let* ((location (file-location location))
-        (line (file-location-line location))
-        (filename (file-location-filename location)))
-    (cond (line
-          (when (typep stream 'position-aware-stream)
-            (format stream "~&#line ~D~@[ ~S~]~%" line filename))
-          (funcall thunk)
-          (when (typep stream 'position-aware-stream)
-            (fresh-line stream)
-            (format stream "~&#line ~D ~S~%"
-                    (1+ (position-aware-stream-line stream))
-                    (let ((path (stream-pathname stream)))
-                      (if path (namestring path) "<sod-output>")))))
-         (t
-          (funcall thunk)))))
+   position-aware then no final marker is output.
+
+   FUNC is passed the output stream as an argument.  Complicated games may be
+   played with interposed streams.  Try not to worry about it."
+
+  (flet ((doit (stream)
+          (let* ((location (file-location location))
+                 (line (file-location-line location))
+                 (filename (file-location-filename location)))
+            (cond (line
+                   (when (typep stream 'position-aware-stream)
+                     (format stream "~&#line ~D~@[ ~S~]~%" line filename))
+                   (funcall func stream)
+                   (when (typep stream 'position-aware-stream)
+                     (fresh-line stream)
+                     (format stream "#line ~D ~S~%"
+                             (1+ (position-aware-stream-line stream))
+                             (let ((path (stream-pathname stream)))
+                               (if path (namestring path)
+                                   "<sod-output>")))))
+                  (t
+                   (funcall func stream))))))
+    (print-ugly-stuff stream #'doit)))
 
 (defmethod print-object ((fragment c-fragment) stream)
   (let ((text (c-fragment-text fragment))
-       (location (c-fragment-location fragment)))
+       (location (file-location fragment)))
     (if *print-escape*
        (print-unreadable-object (fragment stream :type t)
          (when location
                 (prin1 (subseq text 0 37) stream)
                 (write-string "..." stream))))
        (output-c-excursion stream location
-                           (lambda () (write-string text stream))))))
+                           (lambda (stream)
+                             (awhen (file-location-column location)
+                               (dotimes (i it) (write-char #\space stream)))
+                             (write-string text stream))))))
 
 (defmethod make-load-form ((fragment c-fragment) &optional environment)
   (make-load-form-saving-slots fragment :environment environment))
 
-(export 'code-fragment-item)
+(export '(code-fragment-item code-fragment code-fragment-reason
+         code-fragment-name code-fragment-constraints))
 (defclass code-fragment-item ()
-  ((fragment :initarg :fragment :type c-fragment :reader code-fragment)
+  ((fragment :initarg :fragment :type (or string c-fragment)
+            :reader code-fragment)
    (reason :initarg :reason :type keyword :reader code-fragment-reason)
    (name :initarg :name :type t :reader code-fragment-name)
    (constraints :initarg :constraints :type list
   (:documentation
    "A plain fragment of C to be dropped in at top-level."))
 
-(defmacro define-fragment ((reason name) &body things)
-  (categorize (thing things)
-      ((constraints (listp thing))
-       (frags (typep thing '(or string c-fragment))))
-    (when (null frags)
-      (error "Missing code fragment"))
-    (when (cdr frags)
-      (error "Multiple code fragments"))
-    `(add-to-module
-      *module*
-      (make-instance 'code-fragment-item
-                    :fragment ',(car frags)
-                    :name ,name
-                    :reason ,reason
-                    :constraints (list ,@(mapcar (lambda (constraint)
-                                                   (cons 'list constraint))
-                                                 constraints))))))
-
 ;;;--------------------------------------------------------------------------
 ;;; File searching.
 
    See `find-file' for the grubby details.")
 
 (export 'find-file)
-(defun find-file (scanner name what thunk)
+(defun find-file (home name what thunk)
   "Find a file called NAME on the module search path, and call THUNK on it.
 
-   The file is searched for relative to the SCANNER's current file, and also
+   The file is searched for relative to the HOME file or directory, and also
    in the directories mentioned in the `*module-dirs*' list.  If the file is
    found, then THUNK is invoked with two arguments: the name we used to find
    it (which might be relative to the starting directory) and the truename
    THUNK is not invoked with any additional handlers defined."
 
   (handler-case
-      (dolist (dir (cons (pathname (scanner-filename scanner)) *module-dirs*)
-              (values nil nil))
+      (dolist (dir (cons home *module-dirs*) (values nil nil))
        (let* ((path (merge-pathnames name dir))
               (probe (probe-file path)))
          (when probe