Complete Computing Environment: Lisp Programming

Table of Contents

(provide 'cce-lisps)

Common functions for Lisps

Via Endless Parentheses, bind a command to comment and uncomment a s-exp on C-M-;

(defun uncomment-sexp (&optional n)
  "Uncomment a sexp around point."
  (interactive "P")
  (let* ((initial-point (point-marker))
         (inhibit-field-text-motion t)
         (end (save-excursion
                (when (elt (syntax-ppss) 4)
                  (re-search-backward comment-start-skip
                (setq p (point-marker))
                (comment-forward (point-max))
         (beg (save-excursion
                (forward-line 0)
                (while (and (not (bobp))
                            (= end (save-excursion
                                     (comment-forward (point-max))
                  (forward-line -1))
                (goto-char (line-end-position))
                (re-search-backward comment-start-skip
                  (while (looking-at-p comment-start-skip)
                    (forward-char -1)))
    (unless (= beg end)
      (uncomment-region beg end)
      (goto-char p)
      ;; Indentify the "top-level" sexp inside the comment.
      (while (and (ignore-errors (backward-up-list) t)
                  (>= (point) beg))
        (skip-chars-backward (rx (syntax expression-prefix)))
        (setq p (point-marker)))
      ;; Re-comment everything before it.
        (comment-region beg p))
      ;; And everything after it.
      (goto-char p)
      (forward-sexp (or n 1))
      (skip-chars-forward "\r\n[:blank:]")
      (if (< (point) end)
            (comment-region (point) end))
        ;; If this is a closing delimiter, pull it up.
        (goto-char end)
        (skip-chars-forward "\r\n[:blank:]")
        (when (eq 5 (car (syntax-after (point))))
    ;; Without a prefix, it's more useful to leave point where
    ;; it was.
    (unless n
      (goto-char initial-point))))

(defun comment-sexp--raw ()
  "Comment the sexp at point or ahead of point."
  (pcase (or (bounds-of-thing-at-point 'sexp)
               (skip-chars-forward "\r\n[:blank:]")
               (bounds-of-thing-at-point 'sexp)))
    (`(,l . ,r)
     (goto-char r)
     (skip-chars-forward "\r\n[:blank:]")
       (comment-region l r))
     (skip-chars-forward "\r\n[:blank:]"))))

(defun comment-or-uncomment-sexp (&optional n)
  "Comment the sexp at point and move past it.
If already inside (or before) a comment, uncomment instead.
With a prefix argument N, (un)comment that many sexps."
  (interactive "P")
  (if (or (elt (syntax-ppss) 4)
          (< (save-excursion
               (skip-chars-forward "\r\n[:blank:]")
               (comment-forward 1)
      (uncomment-sexp n)
    (dotimes (_ (or n 1))

(global-set-key (kbd "C-M-;") #'comment-or-uncomment-sexp)


ELisp, and lisp in general, is what Emacs is supposed to be great at, right? I've been writing more and more Elisp lately – not just in FSEM, but entire libraries of my own, such as uber.el1 and kamd.el2 and having first-class support for Elisp is becoming more and more important to me.

I'm still in the process of really learning how Elisp works, and how to best go about writing it, but I know that I have a few needs in working with it:

  • Parentheses handling
  • Easy access to documentation
  • Easy reformatting and re-factoring

There's a handful of options these days on how to handle the parenthetical soup of Lisps, among them are paredit,3 smartparens 4 and lispy;5 I am currently playing with lispy since it seems like it actually works over SSH which paredit does not do.

(defun cce/lisp-like-mode-hook ()
  (lispy-mode -1)
  (aggressive-indent-mode -1)

(eval-after-load 'lisp-mode (lambda ()
                              (install-pkgs '(parinfer))
                              (require 'parinfer)
                              (setq parinfer-extensions
                                    '(defaults       ; should be included.
                                       pretty-parens  ; different paren styles for different modes.
                                       evil           ; If you use Evil.
                                       paredit        ; Introduce some paredit commands.
                                       smart-tab      ; C-b & C-f jump positions and smart shift with tab & S-tab.
                                       smart-yank))   ; Yank behavior depend on mode.
                              (add-hook 'clojure-mode-hook #'cce/lisp-like-mode-hook)
                              (add-hook 'emacs-lisp-mode-hook #'cce/lisp-like-mode-hook)
                              (add-hook 'common-lisp-mode-hook #'cce/lisp-like-mode-hook)
                              (add-hook 'scheme-mode-hook #'cce/lisp-like-mode-hook)
                              (add-hook 'lisp-mode-hook #'cce/lisp-like-mode-hook)
                              (add-hook 'emacs-lisp-mode-hook 'cce/lisp-like-mode-hook)))

Emacs has, built in, wonderful access to documentation, that's it biggest strength after all. Let's just get Eldoc enabled in lisp-mode buffers for that extra pizzazz.

(eval-after-load 'eldoc (lambda ()
                          (diminish 'eldoc-mode "[DOC]")))
(add-hook 'emacs-lisp-mode-hook 'eldoc-mode)

Lastly, some amount of tab completion would be nice, and to accomplish that I leverage company-mode 6 which has a bunch of pluggable backends. I should enable it in all prog-mode buffers, but for now I'm just going to enable it in a few places that I know I'm set up for.

(diminish 'company-mode "")
(add-hook 'emacs-lisp-mode-hook 'company-mode)

NEXT Clojure Programming

I've been messing around with Clojure in my free time. It's a cute little Lisp built on top of the JVM that has some incredibly cool toys, like Datomic7. As is the case with most lisps, Emacs has first-class support for Clojure and some of the most powerful tools for it. I'm still coming to terms with the Lisp tooling in Emacs as a whole, but I'm already working to integrate Clojure in more fully.

Start out by installing the core modes that I use, namely clojure-mode and CIDER 8, the two most prolifically used Emacs Clojure kits. If clojure-mode is the heart of Clojure support, CIDER is the brains, providing super nice introspection, autocomplete and evaluation, as well as the ability to connect to a live REPL of your running software using the cider-nrepl plugin.

(install-pkgs '(clojure-mode
(setq cider-lein-command "~/bin/lein")
(add-hook 'clojure-mode-hook 'cider-mode)

Flycheck can optionally support Clojure, let's make sure that's installed and enabled.

(install-pkgs '(flycheck-clojure))
(add-hook 'clojure-mode-hook 'flycheck-mode)

And we can enable ac to get AutoComplete based on the introspection provided by an active CIDER session. Most of this code is per the ac-cider readme file which seems awfully intense, but I'm rolling with it.

(install-pkgs '(ac-cider))

(add-hook 'cider-mode-hook 'ac-flyspell-workaround)
(add-hook 'cider-mode-hook 'ac-cider-setup)
(add-hook 'cider-repl-mode-hook 'ac-cider-setup)

(require 'auto-complete)
(add-to-list 'ac-modes 'cider-mode)
(add-to-list 'ac-modes 'cider-repl-mode)
(add-hook 'prog-mode-hook #'auto-complete-mode)

(defun set-auto-complete-as-completion-at-point-function ()
  (setq completion-at-point-functions '(auto-complete)))
(add-hook 'auto-complete-mode-hook 'set-auto-complete-as-completion-at-point-function)
(add-hook 'cider-mode-hook 'set-auto-complete-as-completion-at-point-function)

And of course there is an Eldoc backend.

;(install-pkgs '(cljdoc))
(add-hook 'clojure-mode-hook 'eldoc-mode)

Lastly, and most interestingly, there is a set up for first-class support of refactoring tooling, provided by clj-refactor; I should use it! This works OOTB, but for more advanced refactorings it requires an nREPL middleware, so read the readme to make sure you get it right.

(install-pkgs '(clj-refactor))

(add-hook 'clojure-mode-hook 'clj-refactor-mode)
(cljr-add-keybindings-with-prefix "C-c C-m")
(add-hook 'clojure-mode-hook 'lispy-mode)

Install Lein in to the user path so that I can do actual lisp things.

- name: install lein
  become: yes
  become_user: "{{local_account}}"
    dest: ~/bin/lein
    mode: 0775

When running Clojure inside of org-babel to do literate programming, I set my backend to Cider.

(setq org-babel-clojure-backend 'cider)
(setq org-babel-clojure-sync-nrepl-timeout nil)
(setq cider-cljs-lein-repl
      "(do (require 'figwheel-sidecar.repl-api)

DONE Set up a CIDER container

DONE Set up scad-clj and openscad-preview


I'm playing with Fennel, a scheme-like that compiles to Lua.

- name: luarocks installed
    - luarocks
    - luajit
    state: installed
  when: ansible_pkg_mgr == "dnf"

- name: luarocks installed
    - luarocks
    - luajit
    state: installed
  when: ansible_pkg_mgr == "apt"

Add luarocks directory to PATH and inject other variables:

export PATH=~/.luarocks/bin:$PATH
$(luarocks path)
- name: fennel installed
  become: yes
  become_user: "{{local_account}}"
  shell: luarocks --local install fennel
    creates: ~/.luarocks/bin/fennel

This from Phil Hagelberg lisp jam game, it allows me to hot-reload modules in a running game.

(eval-after-load 'fennel-mode
  '(define-key fennel-mode-map (kbd "C-c C-k")
     (defun pnh-fennel-hotswap ()
        (format "(lume.hotswap \"%s\")\n"
                (substring (file-name-nondirectory (buffer-file-name)) 0 -4))))))

(add-hook 'fennel-mode-hook (lambda ()
                                      (setq-local inferior-lisp-program "love .")))


Author: Ryan Rix

Created: 2019-05-07 Tue 11:13

Validate XHTML 1.0