Moved package-related code to namespace.lisp, added back *enable-package-system*.
[clinton/parenscript.git] / src / namespace.lisp
CommitLineData
06babcf5
VS
1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2;;; ParenScript package system
3
4(in-package :parenscript)
5
6(defclass script-package ()
7 ;; configuration slots
8 ((name :accessor script-package-name :initform nil :initarg :name :type string
9 :documentation "Canonical name of the package (a String).")
10 (nicknames :accessor script-package-nicknames :initform nil :initarg :nicknames
11 :documentation "List of nicknames for the package (as strings).")
12 (prefix :accessor script-package-prefix :initform nil :initarg :prefix :type string
13 :documentation "The prefix string that will be used when translating the symbols in the current package to Javascript.")
14 (lisp-package :accessor script-package-lisp-package :initform nil :initarg :lisp-package)
15 (secondary-lisp-packages :accessor script-package-secondary-lisp-packages :initform nil
16 :initarg :secondary-lisp-packages)
17 (exports :accessor script-package-exports :initarg :exports
18 :initform nil;(make-hash-table :test #'equal)
19 :documentation "List of exported identifiers.")
20 (used-packages :accessor script-package-used-packages :initform nil :initarg :used-packages
21 :documentation "")
22 (documentation :accessor script-package-documentation :initform nil :initarg :documentation)
23 (compilation-env :accessor script-package-comp-env :initform nil :initarg :comp-env)
24 (locked? :accessor script-package-locked? :initform nil :initarg :locked?
25 :documentation "t if redefinition of top-level symbols is disallowed.")
26 ;; internal use slots
27 (symbol-table :accessor script-package-symbol-table :initform nil :initarg :symbol-table
28 :documentation "Contains symbols when there is no lisp package for this package.")
29 )
30 (:documentation "A Parenscript package is a lisp object that holds information
31about a set of code.
32
33"))
34
35(defmethod print-object ((sp script-package) stream)
36 (format stream "#<SCRIPT-PACKAGE ~s>" (script-package-name sp)))
37
38(defclass compilation-environment ()
39 ((script-packages :accessor comp-env-script-packages :initform nil :initarg :packages
40 :documentation "List of packages defined in this environment.")
41 (current-package :accessor comp-env-current-package :initform nil :initarg :current-package
42 :documentation "Current in-package.")
43 (lisp-to-script-package-table
44 :accessor comp-env-lisp-to-script-package-table :initform (make-hash-table)
45 :documentation "Maps a lisp package to a script package.")
46 (compiling-toplevel-p
47 :accessor comp-env-compiling-toplevel-p :initform nil :initarg :processing-toplevel-p
48 :documentation "T if the environment is currently processing toplevel forms.")
49 (symbol-table :accessor symbol-to-script-package :initform (make-hash-table)
50 :documentation "Maps symbols to script packages. Used for only the
51symbols in script packages that do not have a primary lisp package."))
52 (:documentation ""))
53
54(defgeneric symbol-script-package (symbol)
55 (:documentation "Gets the Parenscript package associated with a Lisp/Parenscript symbol."))
56
57(defvar *warn-ps-package* nil
58 "If true, warns when ParenScript attempts to compile symbols that
59don't have an associated ParenScript package.")
60
61(defun lisp-to-script-package (lisp-package &optional (comp-env *compilation-environment*))
62 "Gets a script package corresponding to the given Lisp package."
63 (gethash lisp-package (comp-env-lisp-to-script-package-table comp-env)))
64
65(defsetf lisp-to-script-package (lisp-package &optional (comp-env *compilation-environment*))
66 (script-package)
67 "Sets the script package corresponding to the given Lisp package."
68 `(setf (gethash ,lisp-package (comp-env-lisp-to-script-package-table ,comp-env))
69 ,script-package))
70
71(defmethod symbol-script-package ((symbol symbol))
72 (if (symbol-package symbol)
73 (or (lisp-to-script-package (symbol-package symbol) *compilation-environment*)
74 (progn (when *warn-ps-package*
75 (warn 'simple-style-warning
76 :format-control "~s is a symbol with lisp package ~s, which has no corresponding ParenScript package.
77Defaulting to :parenscript-user."
78 :format-arguments (list symbol (symbol-package symbol))))
79 (find-script-package "PARENSCRIPT-USER" (make-basic-compilation-environment))))
80 (find-script-package "UNINTERNED" *compilation-environment*)))
81
82(defun find-script-package (name &optional (comp-env *compilation-environment*))
83 "Find the script package with the name NAME in the given compilation environment."
84 (typecase name
85 ((or symbol string)
86 (find-if #'(lambda (script-package)
87 (find (string name)
88 (cons (script-package-name script-package)
89 (script-package-nicknames script-package))
90 :test #'equal))
91 (comp-env-script-packages comp-env)))
92 (script-package name)
93 (t (error "~A has unknown type" name))))
94
95(defun script-intern (name script-package-name)
96 "Returns a Parenscript symbol with the string value STRING interned for the
97given SCRIPT-PACKAGE."
98 (declare (type string name))
99 (let ((script-package (find-script-package script-package-name)))
100 (flet ((find-exported-symbol (name script-package)
101 (let ((res
102 (find name (script-package-exports script-package)
103 :key #'(lambda (exported-symbol) (string exported-symbol))
104 :test #'equal)))
105 res)))
106 (let ((res
107 (or
108 (some #'(lambda (used-package)
109 (find-exported-symbol name used-package))
110 (script-package-used-packages script-package))
111 (if (script-package-lisp-package script-package)
112 (intern name (script-package-lisp-package script-package))
113 (progn
114 (let ((sym (intern-without-package name)))
115 (setf (gethash name (script-package-symbol-table script-package))
116 sym)
117 (setf (gethash sym (symbol-to-script-package (script-package-comp-env script-package)))
118 script-package)
119 sym))))))
120 (declare (type symbol res))
121 res))))
122
123(defun find-script-symbol (name script-package)
124 "Finds the symbol with name NAME in the script package SCRIPT-PACKAGE. NAME is a
125string and SCRIPT-PACKAGE is a package designator. If NAME does not specify a symbol of
126script-package, returns nil. Otherwise returns 2 values:
1271. the symbol
1282. :external if the symbol is external. :internal if the symbol is internal. NIL if
129the symbol is not interned in the package."
130 (setf script-package (find-script-package script-package))
131 (let (symbol interned-p)
132
133 (if (script-package-lisp-package script-package)
134 (multiple-value-bind (lisp-symbol lisp-status)
135 (find-symbol name (script-package-lisp-package script-package))
136 (setf symbol lisp-symbol)
137 (setf interned-p (and lisp-status t)))
138 (multiple-value-bind (sym sym-found-p)
139 (gethash name (script-package-symbol-table script-package))
140 (setf symbol sym)
141 (setf interned-p sym-found-p)))
142 (let ((exported? (member symbol (script-package-exports script-package))))
143 (values symbol
144 (if exported? :external (if interned-p :internal nil))))))
145
146(defun script-export (symbols
147 &optional (script-package (comp-env-current-package *compilation-environment*)))
148 "Exports the given symbols in the given script package."
149 (when (not (listp symbols)) (setf symbols (list symbols)))
150 (setf script-package (find-script-package script-package))
151 (let ((symbols-not-in-package
152 (remove-if #'(lambda (symbol)
153 (declare (type symbol symbol))
154 (eql symbol (find-script-symbol (string symbol) script-package)))
155 symbols)))
156 (when symbols-not-in-package
157 (error "Invalid exports. The following symbols are not interned in the package ~A:~%~A"
158 (script-package-name script-package) symbols-not-in-package)))
159 (mapc #'(lambda (symbol)
160 (pushnew symbol (script-package-exports script-package)))
161 symbols)
162 t)
163
164(defun use-script-package (packages-to-use
165 &optional (into-package (comp-env-current-package *compilation-environment*)))
166 "use-script-package causes INTO-PACKAGE to inherit all the external symbols of packages-to-use.
167The inherited symbols become accessible as internal symbols of package."
168 (when (not (listp packages-to-use)) (setf packages-to-use (list packages-to-use)))
169 (setf packages-to-use (mapcar #'find-script-package packages-to-use))
170 (setf into-package (find-script-package into-package))
171
172 (let ((all-used-symbols (apply #'append (mapcar #'script-package-exports packages-to-use))))
173 (mapc #'(lambda (used-symbol)
174 (let ((symbol-same-name (find-script-symbol (string used-symbol) into-package)))
175 (when (not (or (null symbol-same-name)
176 (eql symbol-same-name used-symbol)))
177 (error "Import of symbol ~A into package ~A conflicts with interned symbol ~A"
178 used-symbol (script-package-name into-package) symbol-same-name))))
179 all-used-symbols))
180 (setf (script-package-used-packages into-package)
181 (append (script-package-used-packages into-package) packages-to-use)))
182
183(defun intern-without-package (name)
184 (macrolet ((with-temp-package ((var) &body body)
185 (let ((result-var (gensym)))
186 `(let* ((,var (make-package ',(gensym)))
187 (,result-var (progn ,@body)))
188 (delete-package ,var)
189 ,result-var))))
190 (with-temp-package (package)
191 (let ((sym (intern name package)))
192 (unintern sym package)
193 sym))))
194
195(defun create-script-package (comp-env
196 &key name nicknames prefix secondary-lisp-packages used-packages
197 lisp-package exports documentation)
198 "Creates a script package in the given compilation environment"
199 (when (and lisp-package (not (find-package lisp-package)))
200 (error "Package ~A does not exists" lisp-package))
201 (let* ((script-package
202 (make-instance 'script-package
203 :name (string name)
204 :comp-env comp-env
205 :prefix prefix
206 :nicknames (mapcar #'string nicknames)
207 :lisp-package (when lisp-package (find-package lisp-package))
208 :secondary-lisp-packages (mapcar #'find-package secondary-lisp-packages)
209 :documentation documentation)))
210 (use-script-package used-packages script-package)
211 (labels ((package-intern (string-like)
212 (script-intern (string string-like) script-package)))
213 (script-export (mapcar #'package-intern exports) script-package))
214 (push script-package (comp-env-script-packages comp-env))
215 script-package))
216
217(defmethod initialize-instance :after ((package script-package) &key)
218 (assert (script-package-comp-env package))
219 (when (null (script-package-lisp-package package))
220 (setf (script-package-symbol-table package)
221 (make-hash-table :test #'equal)))
222 (let ((lisp-packages
223 (remove-if #'null
224 (cons (script-package-lisp-package package)
225 (script-package-secondary-lisp-packages package)))))
226 (dolist (lisp-package lisp-packages)
227 (when (lisp-to-script-package lisp-package (script-package-comp-env package))
228 (error "Lisp package already has corresponding script package: ~A" (package-name lisp-package)))
229 (setf (lisp-to-script-package lisp-package (script-package-comp-env package))
230 package))))
231
232(defgeneric comp-env-find-package (comp-env package-designator)
233 (:documentation "Finds the script package named by PACKAGE-DESIGNATOR in the current
234compilation environment. PACKAGE-DESIGNATOR is a string or symbol.")
235 (:method ((comp-env compilation-environment) (name string))
236 (find name (comp-env-script-packages comp-env)
237 :key #'script-package-name :test #'equal))
238 (:method ((comp-env compilation-environment) (package-designator symbol))
239 (comp-env-find-package comp-env (string package-designator))))
240
241;; TODO loop through all defined macros and add them to the script package's
242;; macro environment
243; (labels ((name-member (name)
244; (eql (script-package-lisp-package script-package) (symbol-package name)))
245; (import-macro (name function)
246; (when (name-member name)
247; (setf (gethash name (script-package-macro-table script-package))
248; function)))
249; (import-special-form (name function)
250; (when (name-member name)
251; (setf (gethash name (script-package-special-form-table script-package))
252; function))))
253; (maphash #'import-special-form *toplevel-special-forms*)
254; (maphash #'import-special-form *toplevel-special-forms*)
255
256;(defgeneric comp-env-select-package (comp-env script-package)
257; (:documentation "")
258; (:method ((comp-env compilation-environment) (package script-package))
259; (setf (comp-env-current-package
260
261
262(defvar *enable-package-system* nil)
263
264;;; Interface for reading in identifier
265
266(defgeneric lisp-symbol-to-ps-identifier (symbol context &optional compilation-environment)
267 (:documentation "Context is one of :special-form, :macro or nil."))
268
269(defmethod lisp-symbol-to-ps-identifier ((symbol symbol) (context (eql :special-form)) &optional comp-ev)
270 (declare (ignore context comp-ev))
271 (symbol-name symbol))
272
273(defmethod lisp-symbol-to-ps-identifier ((symbol symbol) (context (eql :macro)) &optional comp-ev)
274 (declare (ignore context comp-ev))
275 symbol)
276
277(defmethod lisp-symbol-to-ps-identifier :around ((symbol symbol) context &optional comp-ev)
278 (declare (ignore context comp-ev))
279 (if *enable-package-system*
280 (call-next-method)
281 (symbol-name symbol)))
282
283;;; Symbol obfuscation (this should really go somewhere else)
284(defvar *obfuscate-standard-identifiers* nil)
285
286(defparameter *obfuscation-table* (make-hash-table))
287
288(defun obfuscated-symbol (symbol)
289 (or (gethash symbol *obfuscation-table*)
290 (setf (gethash symbol *obfuscation-table*) (string (gensym)))))
291
292;;; Interface for printing identifiers
293
294(defvar *package-prefix-style* :prefix
295 "Determines how package symbols are serialized to JavaScript identifiers. NIL for
296no prefixes. :prefix to prefix variables with something like packagename_identifier.")
297
298(defgeneric js-translate-symbol-contextually (symbol package env)
299 (:documentation "Translates a symbol to a string in the given environment & package
300and for the given symbol."))
301
302(defmethod js-translate-symbol-contextually ((symbol symbol) (package ps::script-package) (env ps::compilation-environment))
303 (cond ((member (ps::script-package-lisp-package package) (mapcar #'find-package '(:keyword :parenscript.global)))
304 (symbol-to-js symbol))
305 (*obfuscate-standard-identifiers* (obfuscated-symbol symbol))
306 (t (if (and *enable-package-system* (eql *package-prefix-style* :prefix))
307 (format nil "~A~A"
308 (or (ps::script-package-prefix package) (concatenate 'string (ps::script-package-name package) "_"))
309 (symbol-to-js symbol))
310 (symbol-to-js symbol)))))
311