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 --------------------------------------------------