From 4076afc735b0e2eddfebcefe2bc03cdcd3548ce2 Mon Sep 17 00:00:00 2001 From: Kaz Kylheku Date: Fri, 6 Jan 2012 08:58:36 -0800 Subject: Initial version of PKG from http://paste.lisp.org/display/72068. --- pkg.lisp | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 pkg.lisp diff --git a/pkg.lisp b/pkg.lisp new file mode 100644 index 0000000..0f12f40 --- /dev/null +++ b/pkg.lisp @@ -0,0 +1,287 @@ +;;; +;;; PKG---read time manipulation of package visibility. +;;; +;;; Kaz Kylheku +;;; December 2008 +;;; +;;; Concept: +;;; +;;; Common Lisp gives us a very coarse-grained instrument for +;;; controlling how symbols are interned. When the reader is entered +;;; to scan a form, there is some package in effect, stored in the +;;; dynamic variable *PACKAGE*. This package controls how unqualified +;;; symbol names are interpreted. Names are looked up through the package, +;;; and either resolve to symbols that are present in the package, +;;; visible through the package via use inheritance, or are not found. +;;; Names which are not found are interned as new symbols in the package. +;;; During the scanning of an entire form, the same *PACKAGE* is in +;;; effect, (unless some read-time manipulation via the sharpsign-dot +;;; read macro is performed). +;;; +;;; What if we want more fine-grained control over how names are interpreted +;;; inside a form? Suppose that deeply inside some nested compound form, +;;; we would like some sub-form to have its symbols treated through +;;; a specific package. Or what if we would like to suppress the behavior of +;;; being automatically interned into the surrounding package? +;;; +;;; It's possible to achieve this by giving semantics to an extensible +;;; read-time notation. I have chosen to implement this as a #@ read +;;; macro (sharpsign-at). The #@ read macro reads the following form as +;;; a directive or list of directives. Then it reads another form, which +;;; is returned as a result of the scan. The directives establish an +;;; anonymous package and customize the contents of that package. +;;; The form is then read under that package. After the form is read, +;;; the anonymous package is reconciled against the surrounding package; +;;; which may involve pushing symbols into the surrounding package. +;;; The anonymous package is then deleted. +;;; +;;; Syntax: +;;; +;;; sharpsign-at := #@ directive form +;;; +;;; directive := (use package-specifier-list) +;;; | (from package-specifier import symbol-specifier-list) +;;; | (inherit symbol-specifier-list) +;;; | (keep symbol-specifier-list) +;;; | (top) +;;; | (directive-list) +;;; +;;; directive-list := ({directive}*) +;;; +;;; package-specifier-list := {package-specifier}* +;;; +;;; symbol-specifier-list := {symbol-specifier}* +;;; +;;; package-specifier := symbol-specifier +;;; +;;; symbol-specifier := string-literal | symbol +;;; +;;; Note: symbol specifiers are treated using name equivalence. +;;; The specifier FOO, #:FOO and "FOO" are the same specifier, +;;; (assuming the usual treatment of letter case in symbol names). +;;; FOO is interned in a private package internal to the #@ reader +;;; implementation, and does not pollute any existing package. +;;; This simple use is encouraged. +;;; +;;; Semantics +;;; +;;; General notes +;;; +;;; When multiple directives appear, they are processed left +;;; to right. The effects of later directives may override +;;; those of earlier directives. +;;; +;;; Before the first directive is processed, an initial anonymous package is +;;; established. This package inherits everything from the surrouding +;;; package as if via USE-PACKAGE. It uses no other package. +;;; +;;; The actions of the directives are: +;;; +;;; (use package-specifier-list) +;;; +;;; This directive means to make visible all of the exported +;;; symbols in the specified packages. If any of the packages +;;; do not exist, an error is signaled. +;;; +;;; The packages are processed in left-to-right order, +;;; and made visible in the anonymous package. Whenever +;;; such a visibility would create a conflict, the +;;; conflict is resolved in favor of the package via a shadowing +;;; import. +;;; +;;; (from package-specifier import symbol-specifier-list) +;;; +;;; Symbols from the specified package (which must exist, or +;;; else an error is signaled) are made present in the +;;; anonymous package by importing. Conflicts are automatically resolved ;;; +;;; in favor of these symbols via shadowing imports. +;;; +;;; (inherit symbol-specifier-list) +;;; +;;; The anonymous package is erased, and replaced with a new +;;; empty anonymous package. Nothing is inherited or imported +;;; into this anonymous package execpt for the symbols specified +;;; by the list. If there are no symbols, the package is completely +;;; empty, with no symbols present or visible in it. +;;; +;;; Symbols specified in the list must all be visible in the surrounding +;;; package, or else an error is signaled. +;;; +;;; Remark: This form is most useful when it appears first, since it +;;; clobbers the effects of earlier directives by replacing +;;; the anonymous package. +;;; +;;; (keep symbol-specifier-list) +;;; +;;; Constraint: only one KEEP directive should be specified. +;;; +;;; First, the specified symbols, if any, are are looked up in the +;;; surrounding package. If any of them are visible there, they are +;;; imported into the anonymous package. +;;; +;;; Second, the list of symbol specifiers is remembered. +;;; When, at the end of processing, the anonymous package is +;;; reconciled against the surrounding package, this list specifies +;;; precisely which symbols present in the anonymous package +;;; are to be imported into the surrounding package. +;;; +;;; The default behavior, if a KEEP directive is not specified, +;;; is that all present symbols in the anonymous package are propagated. +;;; +;;; The KEEP directive's remembered list stays in effect regardless +;;; of the directives that follow. Directives which scrap the +;;; anonymous package for a new one do not affect the keep list. +;;; +;;; (top) +;;; +;;; This directive replaces the anonymous package with a new +;;; one which inherits symbols from the toplevel package, rather +;;; than the surrounding package. The toplevel package is +;;; the *PACKAGE* that is in effect when the reader is entered +;;; to scan a toplevel form. This may be diferent from the +;;; surrounding package, because #@ syntax instances may occur within +;;; forms that are already controlled by #@ syntax. The +;;; surrounding package for these nested instances is the +;;; anonymous package set up by the enclosing #@ syntax. +;;; The TOP directive is a way of gaining access to the outermost +;;; package. +;;; +;;; Reconciliation +;;; +;;; After the directives are processed, the FORM is read. Then, before +;;; the form is returned, package reconciliation takes place. +;;; This is done as if by the following steps: +;;; +;;; 1. All symbols which are present in the anonymous package, and whose +;;; home package is that package (i.e. all symbols that were newly interned +;;; in that anonymous package when FORM was read) are gathered into +;;; a list. If a keep list was established by a (KEEP) directive, +;;; then symbols which are not in that list are removed from further +;;; consideration. +;;; +;;; 2. The sourrounding package is installed as the current +;;; package in the *PACKAGE* variable. +;;; +;;; 3. The anonymous package is deleted by DELETE-PACKAGE, rendering +;;; the symbols in the list homeless. +;;; +;;; 4. The symbols in the list are propagated into the surrounding package +;;; by a an import (a non-shadowing import, which may cause conflicts to be +;;; signaled). +;;; + +(defpackage #:pkg + (:use :cl)) + +(in-package #:pkg) + +(eval-when (:compile-toplevel load-toplevel :execute) + (defconstant %packy-package% (find-package '#:pkg)) + (defconstant %dispatch-char% #\@) + + (defvar *env* nil) + + (defstruct env + (parent) + (this-package) + (previous-package) + (retain-syms t) + (stash)) + + (defun toplevel-package (env) + (if (env-parent env) + (toplevel-package (env-parent env)) + (env-previous-package env))) + + (defun reconcile-package (here-package above-package retain-syms) + (loop for sym being each present-symbol of here-package + when (and (eq (symbol-package sym) here-package) + (or (eq retain-syms t) + (member sym retain-syms :test #'string=))) + collect sym into syms + finally + (delete-package here-package) + (setf *package* above-package) + (import syms above-package))) + + (defun check-specifier (specifier) + (typecase specifier + ((or string symbol)) + (otherwise (error "#@: ~A does not name a symbol." specifier)))) + + (defun use-packages (specifiers) + (loop for specifier in specifiers + do (check-specifier specifier) + (let ((package (find-package specifier))) + (when (null package) + (error "#@: package ~A does not exist." specifier)) + (loop for sym being each external-symbol of package + do (if (find-symbol (symbol-name sym)) + (shadowing-import sym)) + finally (use-package package))))) + + (defun import-specified-syms (from-package specifiers &key no-error) + (let ((package (or (find-package from-package) + (error "#@: package ~A does not exist." from-package)))) + (loop for specifier in specifiers + do (check-specifier specifier) + (let ((symbol (or (find-symbol (symbol-name specifier) package) + (unless no-error + (error "#@: no symbol ~A in package ~A." + specifier from-package))))) + (if symbol + (shadowing-import symbol)))))) + + (defun import-all-symbols-from (from-package &optional (to-package *package*)) + (loop for sym being each symbol of from-package + do (import sym to-package))) + + (defun evaluate (form) + (if (consp form) + (if (consp (first form)) + (mapc #'evaluate form) + (let ((sym (first form))) + (case sym + (top (setf (env-this-package *env*) (make-package (gensym) :use nil) + (env-previous-package *env*) (toplevel-package *env*) + (env-parent *env*) nil + *package* (progn (delete-package *package*) + (env-this-package *env*))) + (import-all-symbols-from (toplevel-package *env*))) + (inherit (setf (env-this-package *env*) (make-package (gensym) + :use nil) + *package* (progn (delete-package *package*) + (env-this-package *env*))) + (import-specified-syms (env-previous-package *env*) + (rest form))) + (keep (setf (env-retain-syms *env*) (rest form)) + (import-specified-syms (env-previous-package *env*) + (rest form) + :no-error t)) + (use (use-packages (rest form))) + (from (destructuring-bind (from from-package import + &rest specifiers) form + (unless (and (eq import 'import)) + (error "#@: bad FROM package IMPORT syms syntax.")) + (import-specified-syms from-package specifiers))) + (otherwise (error "#@: ~A is an unknown directive." sym))))) + (error "#@: bad syntax: ~A" form))) + + (defun dispatch-macro (stream sub-character integer-param) + (declare (ignore integer-param)) + (let* ((temp-package-name (gensym)) + (temp-package (make-package (gensym) :use nil)) + (*env* (make-env :parent *env* + :this-package temp-package + :previous-package *package*)) + (*package* temp-package)) + (import-all-symbols-from (env-previous-package *env*)) + (evaluate (let ((*package* %packy-package%)) + (read stream t nil t))) + (prog1 + (read stream t nil t) + (reconcile-package (env-this-package *env*) + (env-previous-package *env*) + (env-retain-syms *env*))))) + + (set-dispatch-macro-character #\# #\@ #'dispatch-macro)) -- cgit v1.2.3