Component colorize

You are here: All Systems / lisppaste / colorize

;;;; colorize.lisp

(in-package :colorize)

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defparameter *coloring-types* nil)
  (defparameter *version-token* (gensym))
)


(defclass coloring-type ()
  ((modes :initarg :modes :accessor coloring-type-modes)
   (default-mode :initarg :default-mode :accessor coloring-type-default-mode)
   (transition-functions :initarg :transition-functions :accessor coloring-type-transition-functions)
   (fancy-name :initarg :fancy-name :accessor coloring-type-fancy-name)
   (term-formatter :initarg :term-formatter :accessor coloring-type-term-formatter)
   (formatter-initial-values :initarg :formatter-initial-values :accessor coloring-type-formatter-initial-values :initform nil)
   (formatter-after-hook :initarg :formatter-after-hook :accessor coloring-type-formatter-after-hook :initform (constantly ""))
   (autodetect-function :initarg :autodetect-function :accessor coloring-type-autodetect-function
                        :initform (constantly nil)
)

   (parent-type :initarg :parent-type :accessor coloring-type-parent-type
                :initform nil
)

   (visible :initarg :visible :accessor coloring-type-visible
            :initform t
)
)
)


(defun find-coloring-type (type)
  (if (typep type 'coloring-type)
      type
      (cdr (assoc (symbol-name type) *coloring-types* :test #'string-equal :key #'symbol-name))
)
)


(defun autodetect-coloring-type (name)
  (car
   (find name *coloring-types*
         :key #'cdr
         :test #'(lambda (name type)
                   (and (coloring-type-visible type)
                        (funcall (coloring-type-autodetect-function type) name)
)
)
)
)
)


(defun coloring-types ()
  (loop for type-pair in *coloring-types*
        if (coloring-type-visible (cdr type-pair))
        collect (cons (car type-pair)
                      (coloring-type-fancy-name (cdr type-pair))
)
)
)


(defun (setf find-coloring-type) (new-value type)
  (if new-value
      (let ((found (assoc type *coloring-types*)))
        (if found
            (setf (cdr found) new-value)
            (setf *coloring-types*
                  (nconc *coloring-types*
                         (list (cons type new-value))
)
)
)
)

      (setf *coloring-types* (remove type *coloring-types* :key #'car))
)
)


(defvar *scan-calls* 0)

(defvar *reset-position* nil)

(defmacro with-gensyms ((&rest names) &body body)
  `(let ,(mapcar #'(lambda (name)
                     (list name `(make-symbol ,(symbol-name name)))
)
names
)

    ,@body
)
)


(defmacro with-scanning-functions (string-param position-place mode-place mode-wait-place &body body)
  (with-gensyms (num items position not-preceded-by string item new-mode until advancing)
    `(labels ((advance (,num)
               (setf ,position-place (+ ,position-place ,num))
               t
)

              (peek-any (,items &key ,not-preceded-by)
               (incf *scan-calls*)
               (let* ((,items (if (stringp ,items)
                                  (coerce ,items 'list) ,items
)
)

                      (,not-preceded-by (if (characterp ,not-preceded-by)
                                            (string ,not-preceded-by) ,not-preceded-by
)
)

                      (,position ,position-place)
                      (,string ,string-param)
)

                 (let ((,item (and
                               (< ,position (length ,string))
                               (find ,string ,items
                                     :test #'(lambda (,string ,item)
                                               #+nil
                                               (format t "looking for ~S in ~S starting at ~S~%"
                                                       ,item ,string ,position
)

                                               (if (characterp ,item)
                                                   (char= (elt ,string ,position)
                                                          ,item
)

                                                   (search ,item ,string :start2 ,position
                                                           :end2 (min (length ,string)
                                                                      (+ ,position (length ,item))
)
)
)
)
)
)
)
)

                   (if (characterp ,item)
                       (setf ,item (string ,item))
)

                   (if
                    (if ,item
                        (if ,not-preceded-by
                            (if (>= (- ,position (length ,not-preceded-by)) 0)
                                (not (string= (subseq ,string
                                                      (- ,position (length ,not-preceded-by))
                                                      ,position
)

                                              ,not-preceded-by
)
)

                                t
)

                            t
)

                        nil
)

                    ,item
                    (progn
                      (and *reset-position*
                           (setf ,position-place *reset-position*)
)

                      nil
)
)
)
)
)

              (scan-any (,items &key ,not-preceded-by)
                (let ((,item (peek-any ,items :not-preceded-by ,not-preceded-by)))
                  (and ,item (advance (length ,item)))
)
)

              (peek (,item &key ,not-preceded-by)
                (peek-any (list ,item) :not-preceded-by ,not-preceded-by)
)

              (scan (,item &key ,not-preceded-by)
               (scan-any (list ,item) :not-preceded-by ,not-preceded-by)
)
)

      (macrolet ((set-mode (,new-mode &key ,until (,advancing t))
                   (list 'progn
                         (list 'setf ',mode-place ,new-mode)
                         (list 'setf ',mode-wait-place
                               (list 'lambda (list ',position)
                                     (list 'let (list (list '*reset-position* ',position))
                                           (list 'values ,until ,advancing)
)
)
)
)
)
)

        ,@body
)
)
)
)


(defvar *formatter-local-variables*)

(defmacro define-coloring-type (name fancy-name &key modes default-mode transitions formatters
                                autodetect parent formatter-variables (formatter-after-hook '(constantly ""))
                                invisible
)

  (with-gensyms (parent-type term type string current-mode position position-foobage mode-wait new-position advance)
    `(let ((,parent-type (or (find-coloring-type ,parent)
                             (and ,parent
                                  (error "No such coloring type: ~S" ,parent)
)
)
)
)

      (setf (find-coloring-type ,name)
       (make-instance 'coloring-type
        :fancy-name ',fancy-name
        :modes (append ',modes (if ,parent-type (coloring-type-modes ,parent-type)))
        :default-mode (or ',default-mode
                          (if ,parent-type (coloring-type-default-mode ,parent-type))
)

        ,@(if autodetect
              `(:autodetect-function ,autodetect)
)

        :parent-type ,parent-type
        :visible (not ,invisible)
        :formatter-initial-values (lambda nil
                                    (list* ,@(mapcar #'(lambda (e)
                                                         `(cons ',(car e) ,(second e))
)

                                                     formatter-variables
)

                                           (if ,parent-type
                                               (funcall (coloring-type-formatter-initial-values ,parent-type))
                                               nil
)
)
)

        :formatter-after-hook (lambda nil
                                (symbol-macrolet ,(mapcar #'(lambda (e)
                                                              `(,(car e) (cdr (assoc ',(car e) *formatter-local-variables*)))
)

                                                          formatter-variables
)

                                    (concatenate 'string
                                                 (funcall ,formatter-after-hook)
                                                 (if ,parent-type
                                                     (funcall (coloring-type-formatter-after-hook ,parent-type))
                                                     ""
)
)
)
)

        :term-formatter
        (symbol-macrolet ,(mapcar #'(lambda (e)
                                      `(,(car e) (cdr (assoc ',(car e) *formatter-local-variables*)))
)

                                  formatter-variables
)

            (lambda (,term)
              (labels ((call-parent-formatter (&optional (,type (car ,term))
                                                         (,string (cdr ,term))
)

                         (if ,parent-type
                             (funcall (coloring-type-term-formatter ,parent-type)
                                      (cons ,type ,string)
)
)
)

                       (call-formatter (&optional (,type (car ,term))
                                                  (,string (cdr ,term))
)

                         (funcall
                          (case (first ,type)
                            ,@formatters
                            (t (lambda (,type text)
                                 (call-parent-formatter ,type text)
)
)
)

                          ,type ,string
)
)
)

                (call-formatter)
)
)
)

        :transition-functions
        (list
         ,@(loop for transition in transitions
                 collect (destructuring-bind (mode &rest table) transition
                           `(cons ',mode
                             (lambda (,current-mode ,string ,position)
                               (let ((,mode-wait (constantly nil))
                                     (,position-foobage ,position)
)

                                 (with-scanning-functions ,string ,position-foobage
                                                          ,current-mode ,mode-wait
                                                          (let ((*reset-position* ,position))
                                                            (cond ,@table)
)

                                                          (values ,position-foobage ,current-mode
                                                                  (lambda (,new-position)
                                                                    (setf ,position-foobage ,new-position)
                                                                    (let ((,advance (nth-value 1 (funcall ,mode-wait ,position-foobage))))
                                                                      (values ,position-foobage ,advance)
)
)
)
)

                                 
)
)
)
)
)
)
)
)
)
)
)


(defun full-transition-table (coloring-type-object)
  (let ((parent (coloring-type-parent-type coloring-type-object)))
    (if parent
        (append (coloring-type-transition-functions coloring-type-object)
                (full-transition-table parent)
)

        (coloring-type-transition-functions coloring-type-object)
)
)
)


(defun scan-string (coloring-type string)
  (let* ((coloring-type-object (or (find-coloring-type coloring-type)
                                   (error "No such coloring type: ~S" coloring-type)
)
)

         (transitions (full-transition-table coloring-type-object))
         (result nil)
         (low-bound 0)
         (current-mode (coloring-type-default-mode coloring-type-object))
         (mode-stack nil)
         (current-wait (constantly nil))
         (wait-stack nil)
         (current-position 0)
         (*scan-calls* 0)
)

    (flet ((finish-current (new-position new-mode new-wait &key (extend t) push pop)
             (let ((to (if extend new-position current-position)))
               (if (> to low-bound)
                   (setf result (nconc result
                                       (list (cons (cons current-mode mode-stack)
                                                   (subseq string low-bound
                                                           to
)
)
)
)
)
)

               (setf low-bound to)
               (when pop
                 (pop mode-stack)
                 (pop wait-stack)
)

               (when push
                 (push current-mode mode-stack)
                 (push current-wait wait-stack)
)

               (setf current-mode new-mode
                     current-position new-position
                     current-wait new-wait
)
)
)
)

      (loop
       (if (> current-position (length string))
           (return-from scan-string
             (progn
               (format *trace-output* "Scan was called ~S times.~%"
                       *scan-calls*
)

               (finish-current (length string) nil (constantly nil))
               result
)
)

           (or
            (loop for transition in
                  (mapcar #'cdr
                          (remove current-mode transitions
                                  :key #'car
                                  :test-not #'(lambda (a b)
                                                (or (eql a b)
                                                    (if (listp b)
                                                        (member a b)
)
)
)
)
)

                  if
                  (and transition
                       (multiple-value-bind
                             (new-position new-mode new-wait)
                           (funcall transition current-mode string current-position)
                         (when (> new-position current-position)
                           (finish-current new-position new-mode new-wait :extend nil :push t)
                           t
)
)
)

                  return t
)

            (multiple-value-bind
                  (pos advance)
                (funcall current-wait current-position)
              #+nil
              (format t "current-wait returns ~S ~S (mode is ~S, pos is ~S)~%" pos advance current-mode current-position)
              (and pos
                   (when (> pos current-position)
                     (finish-current (if advance
                                         pos
                                         current-position
)

                                     (car mode-stack)
                                     (car wait-stack)
                                     :extend advance
                                     :pop t
)

                     t
)
)
)

            (progn
              (incf current-position)
)
)

           
)
)
)
)
)


(defun format-scan (coloring-type scan)
  (let* ((coloring-type-object (or (find-coloring-type coloring-type)
                                   (error "No such coloring type: ~S" coloring-type)
)
)

         (color-formatter (coloring-type-term-formatter coloring-type-object))
         (*formatter-local-variables* (funcall (coloring-type-formatter-initial-values coloring-type-object)))
)

    (format nil "<span class=\"~A\">~{~A~}~A</span>"
            *css-background-class*
            (mapcar color-formatter scan)
            (funcall (coloring-type-formatter-after-hook coloring-type-object))
)
)
)


(defun html-colorization (coloring-type string)
  (format-scan coloring-type
               (mapcar #'(lambda (p)
                           (cons (car p)
                                 (let ((tt
                                        (html-encode:encode-for-tt (cdr p))
)
)

                                   (if (and (> (length tt) 0)
                                            (char= (elt tt (1- (length tt))) #\>)
)

                                       (format nil "~A~%" tt) tt
)
)
)
)

                       (scan-string coloring-type
                                    string
)
)
)
)


(defun colorize-file-to-stream (coloring-type input-file-name s2 &key (wrap t) (css-background "default"))
  (let* ((input-file (if (pathname-type (merge-pathnames input-file-name))
                         (merge-pathnames input-file-name)
                         (make-pathname :type "lisp"
                                        :defaults (merge-pathnames input-file-name)
)
)
)

         (*css-background-class* css-background)
)

    (with-open-file (s input-file :direction :input)
      (let ((lines nil)
            (string nil)
)

        (block done
          (loop (let ((line (read-line s nil nil)))
                  (if line
                      (push line lines)
                      (return-from done)
)
)
)
)

        (setf string (format nil "~{~A~%~}"
                             (nreverse lines)
)
)

        (if wrap
            (format s2
                    "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">
<html><head><style type=
\"text/css\">~A~%~A</style><body>
<table width=
\"100%\"><tr><td class=\"~A\">
<tt>~A</tt>
</tr></td></table></body></html>"

                    *coloring-css*
                    (make-background-css "white")
                    *css-background-class*
                    (html-colorization coloring-type string)
)

            (write-string (html-colorization coloring-type string) s2)
)
)
)
)
)


(defun colorize-file (coloring-type input-file-name &optional output-file-name)
  (let* ((input-file (if (pathname-type (merge-pathnames input-file-name))
                         (merge-pathnames input-file-name)
                         (make-pathname :type "lisp"
                                        :defaults (merge-pathnames input-file-name)
)
)
)

         (output-file (or output-file-name
                          (make-pathname :type "html"
                                         :defaults input-file
)
)
)
)

    (with-open-file (s2 output-file :direction :output :if-exists :supersede)
      (colorize-file-to-stream coloring-type input-file-name s2)
)
)
)

Lisppaste pastes can be made by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively.