Skip to content
utils.lisp 8.77 KiB
Newer Older
D Herring's avatar
D Herring committed
(in-package :able)

;;; Sequence and string utils

(defun split (string char)
  "Split string on char."
  (let ((string (string-right-trim " " string)))
    (loop for i = 0 then (1+ j)
      as j = (position char string :start i)
      collect (subseq string i j)
      while j)))

(defun string-replace (string from to)
  (map 'string (lambda (char)
                 (if (eq char from)
                     to
                     char))
    string))

(defun split-at (collection n)
  "Split a sequence at position 'n', returning both halves."
  (when (< n (length collection))
    (values (subseq collection 0 n)
      (subseq collection n))))

;(defun cl-user::sload (name)
;  (asdf:oos 'asdf:load-op name))

(defun take (list n)
  (let ((len (length list)))
    (butlast list (max (min len (- len n)) 0))))

(defun foldl (fun id list)
  (reduce (lambda (x y) (funcall fun x y)) list :initial-value id))
  
(defun randoms (count &optional (limit 64))
  "Generate a list of size 'count' random numbers bounded by 'limit'"
  (loop repeat count collect (random limit)))

(defun prefix-p (sequence prefix &optional (start 0))
  "Is 'prefix' the prefix of 'sequence', optionally starting at 'start'."
  (let ((seq-len (length sequence))
        (pre-len (length prefix)))
    (if (> (+ start pre-len) seq-len)
        nil
        (equalp (subseq sequence start (+ start pre-len)) prefix))))


D Herring's avatar
D Herring committed
;;;;;;;;;;;;;; file and directry handling ;;;;;;;;;;;;;;

(defun deduce-path-separator (pathstring)
  "Is this a forwardslash or backslash platform?"
  (if (find #\/ pathstring) #\/ #\\))

(defun dirname-from-pathstring (pathstring)
  (subseq pathstring 0
    (position
      (deduce-path-separator pathstring)
      pathstring :from-end t)))

(defun dirname-from-pathname (pathname)
  (dirname-from-pathstring (namestring pathname)))

(defun filename-from-pathname (pathname)
  (pathname-name (parse-namestring pathname)))

(defun filetype-from-pathstring (pathstring)
  (let ((start (position #\. pathstring :from-end t)))
    (when start
      (subseq pathstring (1+ start)))))

(defun filename-from-pathstring (pathstring)
  (let* ((separator (deduce-path-separator pathstring))
         (start (position separator pathstring :from-end t)))
    (when start
      (let ((end (position #\. pathstring :from-end t)))
        (if (and end (< end (length pathstring)) (> end start))
            (subseq pathstring (1+ start) end)
            (subseq pathstring (1+ start)))))))

(defun filetype? (path types)
  "Is the file specified in path of one of the types specified in types?"
  (member (filetype-from-pathstring path) types :test #'string=))

(defun lisp-file? (path)
  "Does path represent a lisp source file?"
  (filetype? path '("lisp" "cl" "l" "lsp")))

(defgeneric correct-path (pathname)
  (:documentation "Normalises all paths such that backslashes become forward slashes"))

(defmethod correct-path ((pathname pathname))
  (correct-path (namestring pathname)))

(defmethod correct-path ((pathname string))
  (string-replace pathname #\\ #\/))

;;;;;;;;;;;;;; functions for dealing with Tk text indices ;;;;;;;;;;;;;;

(defun get-row-integer (text-index)
  "Extracts the row number from a Tk text index."
  (let ((temp nil))
    (setf temp (first (split text-index #\.)))
    (setf temp (read-from-string temp))))

(defun get-col-integer (text-index)
  "Extracts the col number from a Tk text index."
  (let ((temp nil))
    (setf temp (second (split text-index #\.)))
    (setf temp (read-from-string temp))))

(defun text-row-add (text-index increment)
  "Increments the row of a Tk text index by increment (i.e. 5.0 -> 6.0)."
  (let ((temp (get-row-integer text-index)))
    (incf temp increment)
    (if (> temp 0)
        (format nil "~a.0" temp)
D Herring's avatar
D Herring committed
        "1.0")))
D Herring's avatar
D Herring committed

(defun text-col-add (text-index increment)
  "Increments the col of a Tk text index by increment (i.e. 4.1 -> 4.2)."
  (let ((row (get-row-integer text-index))
        (col (get-col-integer text-index)))
    (incf col increment)
    (format nil "~a.~a" row col)))

(defun strpos-to-textidx (str pos)
  "Convert a string position into a Tk text index."
  (let ((row 1)
        (col 0)
        (col-inc))
    (incf row (count #\Newline str :end pos))
    (setf col-inc (position #\Newline str :end pos :from-end t))
    (if col-inc
        (setf col (- pos col-inc 1))
        (setf col pos))
    (values (format nil "~a.~a" row col) row col)))

;;;;;;;;;;;;;; functions for dealing with strings ;;;;;;;;;;;;;;

(defun find-next-open (code-string start)
  (let ((x 0) (pos start))
    (loop while (> pos 0)
          while (>= x 0) do
          (decf pos)
          (case (char code-string pos)
            ((#\() (decf x))
             ((#\)) (incf x))))
  (if (< x 0) pos nil)))

(defun find-next-close (code-string start)
  (let ((x 0) (pos start))
    (labels ((find-next-close-inner (code-string)
               (when (< pos (length code-string))
                 (case (char code-string pos)
                   ((#\)) (decf x))
                 ((#\() (incf x)))
                 (when (>= x 0)
                   (incf pos)
                   (find-next-close-inner code-string)))))
      (find-next-close-inner code-string)
      (if (< x 0) pos nil))))

(defun find-current-sexp (code-string pos)
  (let ((start pos) (end pos))
    (labels ((get-inner-form ()
               (let* ((open (find-next-open code-string start))
                      (close (find-next-close code-string end)))
                 (when (and open close)
                   (setf start open)
                   (setf end (+ 1 close))
                   (when (and (> start 0) (< end (length code-string)))
                     (get-inner-form))))))
      (get-inner-form)
      (values (subseq code-string start end) start end))))

(defun trim-code (codestring)
  "Tidy strings front and back."
  (string-trim '(#\Newline #\Linefeed #\Space #\Tab #\Return) codestring))

(defun find-current-function (code-string pos)
  "From the cursor, finds the current function as token, start and end indices."
  (let ((next-open (find-next-open code-string pos))
        start end token)
    (when next-open
      (setf start (get-col-integer (strpos-to-textidx code-string next-open)))
      (loop :for i :from (1+ next-open) :below (length code-string)
         :do (if (member (char code-string i) '(#\Space #\Tab #\Newline) :test #'char=)
                 (setf next-open i)
                 (return)))
      (setf end (position #\Space code-string :start (1+ next-open) :test #'equal))
D Herring's avatar
D Herring committed
      (setf token (subseq code-string (+ next-open 1) end)))
    (values token start end)))

(defun longest-prefix-match (list)
  "Takes a list of strings and returns their longest common lexical prefix."
  (let ((best (if list (car list) ""))
        (longest most-positive-fixnum))
    (loop for this in (rest list) do
          (let ((len (mismatch this best)))
            (when (< len longest)
              (setf longest len)
              (setf best (subseq this 0 len)))))
    best))

;;;;;;;;;;;;;; environment ;;;;;;;;;;;;;;

(defun shutdown ()
  #+:clisp (ext:quit)
  #+:sbcl (sb-ext:quit)
  #+:ccl (ccl:quit))

(defun deliver ()
  ;;; SBCL seems to get upset if ABLE is running when this is called.
  ;;; It's best to load ABLE but not call start-able before invoking
  ;;; this on SBCL. Problem only manifests on Linux??
  #+:sbcl (sb-ext:save-lisp-and-die "able"
            :toplevel 'able:start
            :executable t)
  #+:clisp (ext:saveinitmem "able"
             :init-function 'able:start
             :executable t
             :quiet t
             :norc t)
  #+:ccl (ccl:save-application "able"
           :toplevel-function 'able:start
           :prepend-kernel t
           :error-handler :quit-quietly))

(defun function-lambda-list (fn)
  "Return an argument list for the supplied function."
  (let ((arglist))
    (handler-case
      #+:clisp (setf arglist (sys::arglist fn))
D Herring's avatar
D Herring committed
      #+:sbcl (setf arglist (sb-introspect:function-lambda-list fn))
D Herring's avatar
D Herring committed
      #+:ccl (setf arglist (ccl:arglist fn))
D Herring's avatar
D Herring committed
      (error (ex) (declare (ignore ex)) (setf arglist nil)))
D Herring's avatar
D Herring committed
    arglist))

(defun start-process (command-line)
  (let ((process))
    (progn
      #+:clisp
      (setf process
        (ext:run-program (car command-line) :arguments (cdr command-line)
          :input :stream :output :stream :wait t))
      #+:sbcl
      (let ((p (sb-ext:run-program (car command-line) (cdr command-line)
                 :input :stream :output :stream :error :output :wait nil :search t)))
        (setf process
          (make-two-way-stream
            (sb-ext:process-output p)
            (sb-ext:process-input p))))
      #+:ccl
      (let ((p (ccl:run-program (car command-line) (cdr command-line)
                 :input :stream :output t)))
        (setf process
          (make-two-way-stream
            (ccl:external-process-output-stream p)
            (ccl:external-process-input-stream p))))
      (sleep 1))
    process))