3 ":"; CL_SOURCE_REGISTRY
=$
(pwd)/build
/src
/:
4 ":"; ASDF_OUTPUT_TRANSLATIONS
=$
(pwd)/src
:$
(pwd)/build
/src
5 ":"; export CL_SOURCE_REGISTRY ASDF_OUTPUT_TRANSLATIONS
6 ":"; exec cl-launch
-X
-l
"sbcl cmucl" -s asdf
-i
"(main)" -- "$0" "$@" ||
exit 1
8 ;;;--------------------------------------------------------------------------
11 (defun whitespace-char-p
(char
)
13 (#.(loop for i below char-code-limit
14 for ch
= (code-char i
)
15 unless
(with-input-from-string
(in (string ch
))
20 (defun slurp-file
(pathname
)
21 (with-open-file
(stream pathname
)
22 (let* ((length
(file-length stream
))
23 (string
(make-string length
))
24 (n
(read-sequence string stream
))
25 (ch
(read-char stream nil
)))
26 (unless
(and
(= n length
) (null ch
))
27 (error
"File `~A' unexpectedly changed size when reading" pathname
))
30 (defun update-hash-table
(map alist
)
31 (dolist
(item alist map
)
32 (let ((key
(car item
))
34 (if value
(setf
(gethash key map
) value
)
37 (defun copy-hash-table
(map
)
38 (let ((new
(make-hash-table
:test (hash-table-test map
))))
39 (maphash
(lambda
(key value
)
40 (setf
(gethash key new
) value
))
44 (defun build-hash-table
(alist
&key
(test #'eql))
45 (update-hash-table
(make-hash-table
:test test) alist
))
47 (defun modified-hash-table
(map alist
)
48 (update-hash-table
(copy-hash-table map
) alist
))
50 (defun binary-search
(item-key vector lessp
&key
(key
#'identity))
51 (let ((len
(length vector
)))
53 (funcall lessp item-key
(funcall key
(aref vector
0))))
55 (let ((lo
0) (hi len
))
56 (loop
(let ((gap
(- hi lo
)))
57 (when
(< gap
2) (return))
58 (let ((mid
(+ lo
(floor gap
2))))
59 (if (funcall lessp item-key
60 (funcall key
(aref vector mid
)))
63 (cond
(( = lo hi
) (values
0 nil
))
64 ((funcall lessp
(funcall key
(aref vector lo
))
67 (t
(values lo t
)))))))
69 (defun add-to-sorted-vector
(item vector lessp
&key
(key
#'identity))
70 (multiple-value-bind
(index foundp
)
71 (binary-search
(funcall key item
) vector lessp
)
73 (let ((len
(fill-pointer vector
))
74 (size
(array-dimension vector
0)))
76 (setf vector
(adjust-array vector
(* 2 size
))))
77 (setf
(fill-pointer vector
) (1+ len
))
78 (replace vector vector
79 :start1
(1+ index
) :end1
(1+ len
)
80 :start2 index
:end2 len
)
81 (setf
(aref vector index
) item
)))
84 (defmacro sorted-vector-push
(item vector-place lessp
&key key
86 (let ((item-var
(gensym
"ITEM-")))
87 (multiple-value-bind
(vars values temps store-form load-form
)
88 (get-setf-expansion vector-place env
)
89 `(let* ((,item-var ,item)
90 ,@(mapcar #'list vars values)
91 (,(car temps) (add-to-sorted-vector ,item-var ,load-form ,lessp
92 ,@(and key `(:key
,key
)))))
95 (defparameter
*char-latex-map
*
96 (build-hash-table
'((#\\ . "\\textbackslash{}")
103 (#\^ . "\\textasciicircum{}")
104 (#\~ . "\\textasciitilde{}")
105 (#\` . "\\textasciigrave{}")
106 (#\' . "\\textquotesingle{}")
107 (#\- . "\\fakeminus{}")
111 (defun string-latex (string &optional (map *char-latex-map*))
112 (with-output-to-string (out)
113 (dotimes (i (length string))
114 (let ((char (char string i)))
115 (multiple-value-bind (latex foundp) (gethash char map)
116 (if foundp (write-string latex out)
117 (write-char char out)))))))
119 ;;;--------------------------------------------------------------------------
123 ((string :type string :initarg :string :reader token-string)
124 (line :type fixnum :initarg :line :reader token-line)
125 (start-column :type fixnum :initarg :start-column :reader token-start-column)
126 (end-column :type fixnum :initarg :end-column :reader token-end-column)
127 (active-alignment-points :type list
128 :initform (make-array 4
132 :accessor token-active-alignment-points
)))
134 (defmethod print-object
((token token
) stream
)
135 (print-unreadable-object
(token stream
:type t
)
136 (prin1
(token-string token
) stream
)))
138 (defclass whitespace
(token
)
141 (defclass tabulation
(whitespace
)
144 (defclass indentation
(whitespace
)
147 (defclass operator
(token
)
148 ((latex
:type string
:initarg
:latex
:reader token-latex
)))
150 (defmethod shared-initialize
:after
((token operator
) slot-names
&key
)
151 (when
(and
(not
(slot-boundp token
'latex))
152 (or (eq slot-names t) (member 'latex slot-names
)))
153 (setf
(slot-value token
'latex)
154 (slot-value token 'string
))))
156 (defclass identifier
(token
)
159 (defclass reserved
(token
)
162 (defclass numeric
(token
)
165 (defclass literal
(token
)
168 (defclass comment-delimiter
(operator
)
171 (defclass comment-body
(token
)
174 (defgeneric token-width
(token
)
175 (:method
((token token
)) (length
(token-string token
))))
176 (defgeneric token-alignment-points
(token
)
177 (:method
((token token
)) nil
))
178 (defgeneric activate-token-alignment-point
(token offset
)
179 (:method
((token token
) offset
)
180 (let* ((vec
(active-alignment-points token
))
181 (len
(fill-pointer vec
)))
182 (defgeneric format-token
(token stream
)
183 (:method
((token token
) stream
)
184 (princ
(string-latex
(token-string token
)) stream
)))
186 (defclass alignment-point
()
189 (defclass relative-alignment-point
(alignment-point
)
192 (defclass absolute-alignment-point
(alignment-point
)
195 (defun tokenize-whitespace
(line pos end emit
&key force-indent
)
196 (when
(whitespace-char-p
(char line pos
))
197 (let ((next
(or
(position-if-not
#'whitespace-char-p line
201 (cond
((or
(zerop pos
) force-indent
) 'indentation)
202 ((find #\tab line :start pos :end next) 'tabulation
)
206 (defun move-over (line pos end string)
207 (let ((next (+ pos (length string))))
208 (if (and (<= next end)
209 (string= string line :start2 pos :end2 next))
213 (defun try-tokenize (line pos end emit class alist)
215 (multiple-value-bind (string initargs)
216 (if (consp item) (values (car item) (cdr item))
218 (let ((next (move-over line pos end string)))
220 (return (apply emit class pos next initargs)))))))
222 ;;;--------------------------------------------------------------------------
223 ;;; Language definitions.
225 (defparameter *language-matchers* nil)
227 (defmacro deflanguage (name (pathname) &body body)
229 (pushnew (cons ',name
(lambda
(,pathname
) ,@body
))
234 (let ((templates (mapcar (lambda (type)
235 (make-pathname :type type :case :common))
236 '("C" "CC" "CPP" "C++" "CXX"
237 "H" "HH" "HPP" "H++" "HXX"
239 (deflanguage c-language
(pathname
)
240 (find pathname templates
:test #'pathname-match-p)))
242 (let ((templates
(mapcar
(lambda
(type)
243 (make-pathname
:type type :case :common
))
244 '("LISP" "EL" "SCM"))))
245 (deflanguage lisp-language (pathname)
246 (find pathname templates :test #'pathname-match-p
)))
248 (deflanguage make-language
(pathname
)
249 (pathname-match-p pathname
250 (make-pathname
:name
"MAKEFILE" :case :common
)))
252 (defun guess-language
(pathname
)
253 (car
(or
(find-if
(lambda
(item
)
254 (funcall
(cdr item
) pathname
))
256 (error
"No language found for `~A'." pathname
))))
258 (defclass base-language
()
261 ;;;--------------------------------------------------------------------------
262 ;;; C
(-ish
) language scanner.
264 (defclass c-language
(base-language
)
265 ((state
:type (member
:toplevel
:comment
)
266 :initform
:toplevel
:accessor lang-state
)))
268 (defparameter
*c-keywords
*
269 (build-hash-table
'(("alignas" . t)
328 ("reinterpret_cast" . t)
335 ("static_assert" . t)
366 ("_Static_assert" . t)
367 ("_Thread_local" . t)
370 ("__attribute__" . t)
373 ("__extension__" . t)
380 ("__volatile__" . t))
383 (defparameter
*c-preprocessor-keywords
*
384 (build-hash-table
'(("define" . :toplevel)
387 ("endif" . :toplevel)
388 ("error" . :toplevel)
390 ("ifdef" . :toplevel)
391 ("ifndef" . :toplevel)
392 ("include" . :include)
394 ("pragma" . :toplevel)
395 ("undef" . :toplevel))))
397 (defun scan-c-identifier (line pos end)
399 (let ((char (char line pos)))
400 (or (char= char #\_) (alpha-char-p char))))
401 (let ((next (or (position-if-not (lambda (char)
403 (alphanumericp char)))
405 :start (1+ pos) :end end)
407 (values (subseq line pos next) next))
410 (defun tokenize-c-comment (lang line pos end emit)
411 (and (>= (- end pos) 2)
412 (char= (char line pos) #\/)
413 (let ((body-start (+ pos 2))
414 (ch (char line (1+ pos))))
415 (or (and (char= ch #\/)
416 (setf (lang-state lang) :toplevel)
417 (funcall emit 'comment-delimiter pos body-start
)
418 (when
(< body-start end
)
419 (funcall emit
'comment-body body-start end))
422 (funcall emit 'comment-delimiter pos body-start
)
423 (let* ((end-delim
(search
"*/" line
424 :start2 body-start
:end2 end
))
425 (body-end
(or end-delim end
))
426 (next
(if end-delim
(+ end-delim
2) end
)))
427 (when
(< body-start body-end
)
428 (funcall emit
'comment-body body-start body-end))
430 (setf (lang-state lang) :toplevel)
431 (funcall emit 'comment-delimiter end-delim next
))
433 (setf
(lang-state lang
) :comment
)
436 (defmethod next-token
((lang c-language
) line pos end emit
)
437 (let ((state
(lang-state lang
))
438 (char
(char line pos
)))
442 (let ((next
(or
(tokenize-whitespace line pos end emit
)
444 (cond
((and
(< next end
)
445 (char
= (char line next
) #\#))
446 (setf
(lang-state lang
) :preproc-keyword
)
447 (funcall emit
'operator next (1+ next)))
451 (tokenize-whitespace line pos end emit)
453 (multiple-value-bind (ident next) (scan-c-identifier line pos end)
456 (if (gethash ident *c-keywords*)
461 (let ((i (cond ((digit-char-p char)
463 ((and (char= char #\.)
465 (digit-char-p (char line (1+ pos))))
470 (loop (when (>= i end) (return))
471 (let ((char (char line i)))
472 (cond ((or (char= char #\e)
476 (if (and (>= (- end i) 2)
477 (let ((ch (char line (1+ i))))
482 ((or (char= char #\_)
484 (alphanumericp char))
488 (funcall emit 'numeric pos i
)))
490 (and
(or
(char
= char
#\")
493 (loop
(when
(>= i end
)
494 (error
"Missing `~A'." char
))
495 (let ((ch
(char line i
)))
496 (cond
((char
= ch char
) (return))
497 ((char
= ch
#\\) (incf i 2))
499 (funcall emit
'literal pos (1+ i))))
501 (tokenize-c-comment lang line pos end emit)
503 (try-tokenize line pos end emit 'operator
504 '("->" "++" ("--" :latex "{--}\\,{--}")
505 "<<" ">>" "<=" ">=" "==" "!=" "&&" "||"
506 "*=" "/=" "%=" "+=" "-=" "<<=" ">>="
508 "<:" ":>" "<%" "%>" "%:" "%:%:"))
510 (funcall emit 'operator pos
(1+ pos
))))
514 (let ((next
(tokenize-whitespace line pos end emit
)))
515 (when next
(setf pos next
)))
516 (when
(and
(< pos end
)
517 (char
= (char line pos
) #\*))
518 (setf pos
(funcall emit
'comment-delimiter pos (1+ pos))))
519 (let* ((end-delim (search "*/" line :start2 pos :end2 end))
520 (body-end (or end-delim end))
521 (next (if end-delim (+ end-delim 2) end)))
522 (when (< pos body-end)
523 (funcall emit 'comment-body pos body-end
))
525 (setf
(lang-state lang
) :toplevel
)
526 (funcall emit
'comment-delimiter body-end next))
531 (or (tokenize-whitespace line pos end emit)
532 (tokenize-c-comment lang line pos end emit)
534 (multiple-value-bind (ident next) (scan-c-identifier line pos end)
535 (let ((next-state (gethash ident *c-preprocessor-keywords*)))
536 (funcall emit (if next-state 'reserved
'identifier) pos next)
537 (setf (lang-state lang) (or next-state :toplevel))))
539 (progn (setf (lang-state lang) :toplevel) pos))))))
541 (defun tokenize-line (lang line)
542 (let ((tokens nil) (pos 0) (end (length line)))
543 (loop (if (>= pos end) (return (nreverse tokens))
544 (next-token lang line pos end
545 (lambda (class start end &rest initargs)
546 (assert (= start pos))
547 (push (apply #'make-instance class
548 :string
(subseq line start end
)
554 (defgeneric scan-line
((lang c-language
) line
)
559 (state
(lang-state lang
))
561 (comment
(if (eq state
:comment
) :trad nil
))
562 (comment-begin
(if (eq state
:comment
) :star nil
)
566 (when
(>= i end
) (return))
567 (let ((ch
(char line i
))
569 (cond
((char
= ch
#\tab) (setf space 8))
570 ((whitespace-char-p ch
) (incf space
))
572 (when
(and
(plusp start
) (>= space
2))
573 (push
(cons i
:align
) markers
))
575 ((#\, #\;) (setf space 1))
576 ((#\( #\[ #\{) (push (cons (1+ i) :align) markers)))))
580 (#\" (push (cons (1+ i) :align) merkers)
581 (setf state
:string
))
582 (#\' (setf state :char))
583 (#\/ (when (< (1+ i) end)
584 (let ((nch
(char line
(1+ i
))))
586 (#\/ (push (cons i :comment-start) markers)
587 (push
(cons
(+ i
2) :comment-body
) markers
)
592 (#\* (push (cons i :comment-start) markers)
593 (push
(cons
(1+ i
) :align
) markers
)
594 (push
(cons
(+ i
2) :comment-body
) markers
)
600 (cond
(escape
(setf escape nil
))
601 ((char
= ch
#\") (setf state :toplevel))
602 ((char
= ch
#\\) (setf escape t))))
604 (cond
(escape
(setf escape nil
))
605 ((char
= ch
#\') (setf state :toplevel))
606 ((char
= ch
#\\) (setf escape t))))
608 (when
(and comment-begin
(not
(whitespace-char-p ch
)))
609 (cond
((char
= ch
#\*)-
610 (push
(cons i
:comment-mid
) markers
)
611 (push
(cons
(1+ i
) :comment-body
) markers
))
615 (#\space (incf space))
621 ;;;--------------------------------------------------------------------------
622 ;;; The indentation algorithm.
624 (defvar
*indent-high-water-mark
* 0)
627 ((position
:type fixnum
:initarg
:position
:reader offset-position
)
628 (livep
:type boolean
:initarg
:livep
:initform nil
629 :accessor offset-live-p
)))
632 ((text
:type string
:initarg
:text
:reader line-text
)
633 (offsets
:type list
:initarg
:offsets
:initform nil
634 :reader line-offsets
)))
638 ;;;--------------------------------------------------------------------------
642 (format t
"Hello, world!~%"))
646 ;;;----- That
's all, folks --------------------------------------------------