Commit | Line | Data |
---|---|---|
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 | |
31 | about 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 | |
51 | symbols 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 | |
59 | don'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. | |
77 | Defaulting 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 | |
97 | given 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 | |
125 | string and SCRIPT-PACKAGE is a package designator. If NAME does not specify a symbol of | |
126 | script-package, returns nil. Otherwise returns 2 values: | |
127 | 1. the symbol | |
128 | 2. :external if the symbol is external. :internal if the symbol is internal. NIL if | |
129 | the 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. | |
167 | The 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 | |
234 | compilation 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 | |
296 | no 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 | |
300 | and 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 |