Convert JS AST to strings in ls-compile-toplevel
[jscl.git] / src / print.lisp
1 ;;; print.lisp ---
2
3 ;; Copyright (C) 2012, 2013 David Vazquez
4 ;; Copyright (C) 2012 Raimon Grau
5
6 ;; JSCL is free software: you can redistribute it and/or
7 ;; modify it under the terms of the GNU General Public License as
8 ;; published by the Free Software Foundation, either version 3 of the
9 ;; License, or (at your option) any later version.
10 ;;
11 ;; JSCL is distributed in the hope that it will be useful, but
12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 ;; General Public License for more details.
15 ;;
16 ;; You should have received a copy of the GNU General Public License
17 ;; along with JSCL.  If not, see <http://www.gnu.org/licenses/>.
18
19 ;;; Printer
20
21 (defun lisp-escape-string (string)
22   (let ((output "")
23         (index 0)
24         (size (length string)))
25     (while (< index size)
26       (let ((ch (char string index)))
27         (when (or (char= ch #\") (char= ch #\\))
28           (setq output (concat output "\\")))
29         (when (or (char= ch #\newline))
30           (setq output (concat output "\\"))
31           (setq ch #\n))
32         (setq output (concat output (string ch))))
33       (incf index))
34     (concat "\"" output "\"")))
35
36 ;;; Return T if the string S contains characters which need to be
37 ;;; escaped to print the symbol name, NIL otherwise.
38 (defun escape-symbol-name-p (s)
39   (let ((dots-only t))
40     (dotimes (i (length s))
41       (let ((ch (char s i)))
42         (setf dots-only (and dots-only (char= ch #\.)))
43         (when (or (terminalp ch)
44                   (char= ch #\:)
45                   (char= ch #\\)
46                   (char= ch #\|))
47           (return-from escape-symbol-name-p t))))
48     dots-only))
49
50 ;;; Return T if the specified string can be read as a number
51 ;;; In case such a string is the name of a symbol then escaping
52 ;;; is required when printing to ensure correct reading.
53 (defun potential-number-p (string)
54   ;; The four rules for being a potential number are described in
55   ;; 2.3.1.1 Potential Numbers as Token
56   ;;
57   ;; First Rule
58   (dotimes (i (length string))
59     (let ((char (char string i)))
60       (cond
61         ;; Digits TODO: DIGIT-CHAR-P should work with the current
62         ;; radix here. If the radix is not decimal, then we have to
63         ;; make sure there is not a decimal-point in the string.
64         ((digit-char-p char))
65         ;; Signs, ratios, decimal point and extension mark
66         ((find char "+-/._^"))
67         ;; Number marker
68         ((alpha-char-p char)
69          (when (and (< i (1- (length string)))
70                     (alpha-char-p (char string (1+ i))))
71            ;; fail: adjacent letters are not number marker, or
72            ;; there is a decimal point in the string.
73            (return-from potential-number-p)))
74         (t
75          ;; fail: there is a non-allowed character
76          (return-from potential-number-p)))))
77   (and
78    ;; Second Rule. In particular string is not empty.
79    (find-if #'digit-char-p string)
80    ;; Third rule
81    (let ((first (char string 0)))
82      (and (not (char= first #\:))
83           (or (digit-char-p first)
84               (find first "+-._^"))))
85    ;; Fourth rule
86    (not (find (char string (1- (length string))) "+-)"))))
87
88 #+nil
89 (mapcar #'potential-number-p
90         '("1b5000" "777777q" "1.7J" "-3/4+6.7J" "12/25/83" "27^19"
91           "3^4/5" "6//7" "3.1.2.6" "^-43^" "3.141_592_653_589_793_238_4"
92           "-3.7+2.6i-6.17j+19.6k"))
93
94 #+nil
95 (mapcar #'potential-number-p '("/" "/5" "+" "1+" "1-" "foo+" "ab.cd" "_" "^" "^/-"))
96
97 (defun escape-token-p (string)
98   (or (potential-number-p string)
99       (escape-symbol-name-p string)))
100
101 ;;; Returns the token in a form that can be used for reading it back.
102 (defun escape-token (s)
103   (if (escape-token-p s)
104       (let ((result "|"))
105         (dotimes (i (length s))
106           (let ((ch (char s i)))
107             (when (or (char= ch #\|)
108                       (char= ch #\\))
109               (setf result (concat result "\\")))
110             (setf result (concat result (string ch)))))
111         (concat result "|"))
112       s))
113
114 (defvar *print-escape* t)
115 (defvar *print-circle* nil)
116
117 ;;; FIXME: Please, rewrite this in a more organized way.
118 (defun write-to-string (form &optional known-objects object-ids)
119   (when (and (not known-objects) *print-circle*)
120     ;; To support *print-circle* some objects must be tracked for
121     ;; sharing: conses, arrays and apparently-uninterned symbols.
122     ;; These objects are placed in an array and a parallel array is
123     ;; used to mark if they're found multiple times by assining them
124     ;; an id starting from 1.
125     ;;
126     ;; After the tracking has been completed the printing phas can
127     ;; begin: if an object has an id > 0 then #<n>= is prefixed and
128     ;; the id is changed to negative. If an object has an id < 0 then
129     ;; #<-n># is printed instead of the object.
130     ;;
131     ;; The processing is O(n^2) with n = number of tracked
132     ;; objects. Hopefully it will become good enough when the new
133     ;; compiler is available.
134     (setf known-objects (make-array 100))
135     (setf object-ids (make-array 100))
136     (let ((n 0)
137           (sz 100)
138           (count 0))
139       (labels ((mark (x)
140                  (let ((i (position x known-objects)))
141                    (if (= i -1)
142                        (progn
143                          (when (= n sz)
144                            (setf sz (* 2 sz))
145                            ;; KLUDGE: storage vectors are an internal
146                            ;; object which the printer should not know
147                            ;; about. Use standard vector with fill
148                            ;; pointers instead.
149                            (resize-storage-vector known-objects sz)
150                            (resize-storage-vector object-ids sz))
151                          (aset known-objects (1- (incf n)) x)
152                          t)
153                        (unless (aref object-ids i)
154                          (aset object-ids i (incf count))
155                          nil))))
156                (visit (x)
157                  (cond
158                    ((and x (symbolp x) (null (symbol-package x)))
159                     (mark x))
160                    ((consp x)
161                     (when (mark x)
162                       (visit (car x))
163                       (visit (cdr x))))
164                    ((vectorp x)
165                     (when (mark x)
166                       (dotimes (i (length x))
167                         (visit (aref x i))))))))
168         (visit form))))
169   (let ((prefix ""))
170     (when (and *print-circle*
171                (or (consp form)
172                    (vectorp form)
173                    (and form (symbolp form) (null (symbol-package form)))))
174       (let* ((ix (position form known-objects))
175              (id (aref object-ids ix)))
176         (cond
177           ((and id (> id 0))
178            (setf prefix (format nil "#~S=" id))
179            (aset object-ids ix (- id)))
180           ((and id (< id 0))
181            (return-from write-to-string (format nil "#~S#" (- id)))))))
182     (concat prefix
183             (cond
184               ((null form) "NIL")
185               ((symbolp form)
186                (let ((name (symbol-name form))
187                      (package (symbol-package form)))
188                  ;; Check if the symbol is accesible from the current package. It
189                  ;; is true even if the symbol's home package is not the current
190                  ;; package, because it could be inherited.
191                  (if (eq form (find-symbol (symbol-name form)))
192                      (escape-token (symbol-name form))
193                      ;; Symbol is not accesible from *PACKAGE*, so let us prefix
194                      ;; the symbol with the optional package or uninterned mark.
195                      (concat (cond
196                                ((null package) "#")
197                                ((eq package (find-package "KEYWORD")) "")
198                                (t (escape-token (package-name package))))
199                              ":"
200                              (if (and package
201                                       (eq (second (multiple-value-list
202                                                       (find-symbol name package)))
203                                           :internal))
204                                  ":"
205                                  "")
206                              (escape-token name)))))
207               ((integerp form) (integer-to-string form))
208               ((floatp form) (float-to-string form))
209               ((characterp form)
210                (concat "#\\"
211                        (case form
212                          (#\newline "newline")
213                          (#\space "space")
214                          (otherwise (string form)))))
215               ((stringp form) (if *print-escape*
216                                   (lisp-escape-string form)
217                                   form))
218               ((functionp form)
219                (let ((name (oget form "fname")))
220                  (if name
221                      (concat "#<FUNCTION " name ">")
222                      (concat "#<FUNCTION>"))))
223               ((listp form)
224                (concat "("
225                        (join-trailing (mapcar (lambda (x)
226                                                 (write-to-string x known-objects object-ids))
227                                               (butlast form)) " ")
228                        (let ((last (last form)))
229                          (if (null (cdr last))
230                              (write-to-string (car last) known-objects object-ids)
231                              (concat (write-to-string (car last) known-objects object-ids)
232                                      " . "
233                                      (write-to-string (cdr last) known-objects object-ids))))
234                        ")"))
235               ((vectorp form)
236                (let ((result "#(")
237                      (sep ""))
238                  (dotimes (i (length form))
239                    (setf result (concat result sep
240                                         (write-to-string (aref form i)
241                                                          known-objects
242                                                          object-ids)))
243                    (setf sep " "))
244                  (concat result ")")))
245               ((packagep form)
246                (concat "#<PACKAGE " (package-name form) ">"))
247               (t "#<javascript object>")))))
248
249 (defun prin1-to-string (form)
250   (let ((*print-escape* t))
251     (write-to-string form)))
252
253 (defun princ-to-string (form)
254   (let ((*print-escape* nil))
255     (write-to-string form)))
256
257 (defun write-line (x)
258   (write-string x)
259   (write-string *newline*)
260   x)
261
262 (defun warn (string)
263   (write-string "WARNING: ")
264   (write-line string))
265
266 (defun print (x)
267   (write-line (prin1-to-string x))
268   x)
269
270 (defun format (destination fmt &rest args)
271   (let ((len (length fmt))
272         (i 0)
273         (res "")
274         (arguments args))
275     (while (< i len)
276       (let ((c (char fmt i)))
277         (if (char= c #\~)
278             (let ((next (char fmt (incf i))))
279               (cond
280                ((char= next #\~)
281                 (concatf res "~"))
282                ((char= next #\%)
283                 (concatf res *newline*))
284                ((char= next #\*)
285                 (pop arguments))
286                (t
287                 (concatf res (format-special next (car arguments)))
288                 (pop arguments))))
289             (setq res (concat res (string c))))
290         (incf i)))
291     (if destination
292         (progn
293           (write-string res)
294           nil)
295         res)))
296
297 (defun format-special (chr arg)
298   (case (char-upcase chr)
299     (#\S (prin1-to-string arg))
300     (#\A (princ-to-string arg))))