RSS Feed

Lisp Project of the Day

trivial-indent

You can support this project by donating at:

Donate using PatreonDonate using Liberapay

trivial-indentideeditor

Documentation😀
Docstrings😀
Tests 🥺
Examples😀
RepositoryActivity🤨
CI 🥺

This is another great library by @shinmera. It is a portability layer for defining indentation rules for both SLIME and SLY.

Let's pretend we have a macro:

POFTHEDAY> (defmacro super-let (name value &body body)
             `(let ((,name ,value))
                ,@body))

POFTHEDAY> (super-let foo
               100500
             (format nil "Foo is ~A" foo))
"Foo is 100500"

But what if we want Emacs to indent it like this?

POFTHEDAY> (super-let foo
                      100500
             (format nil "Foo is ~A" foo))
"Foo is 100500"

Then we can set indentation rule, and trivial-indent will make it work for both SLIME and SLY:

;; This rule says: indent first two arguments
;; with 11 spaces if they are on their own line.
;; And other arguments indent like usual function's body.
POFTHEDAY> (trivial-indent:define-indentation super-let
               (11 11 &body))

;; And now all super-let form will be indented according to this rule.
POFTHEDAY> (super-let blah
                      'minor
             (format nil "blah -> ~A" blah))
"blah -> MINOR"

I have an advice for you. If you are using SLY, it might cache indentation rules. When you've defined a rule but don't see an effect, call (slynk:update-indentation-information) in the REPL.

Now let's try to define a more complex indentation rule. Yesterday I wrote such macro for running Genetic Algorithm:

(def macro run-ga ((genome &key (population-size 100)
                                (max-iterations 1000)
                                (survive-ratio 0.5))
                   &body fitness-code)
  (let ((var-names (get-var-names genome)))
    
    (unless genome
      ...)))

;; After that you might run it like this:

(run-ga (((x 0 10)
          (y -10 10)
          (z '(:foo :bar :bazz)))
         :max-iterations 100
         :survive-ratio 0.2)
  
  ;; Our fitness-function
  (abs (- (ecase z
            (:foo 10)
            (:bar 100)
            (:bazz 1000))
           (+ x y))))

As you can see, without special rules, these algorithm parameters are hard to distinguish from the genom definition (the first argument).

But what if we want to separate keyword arguments visually and to make this form look like this?

(run-ga (((x 0 10)
          (y -10 10)s
          (z '(:foo :bar :bazz)))
    :max-iterations 100
    :survive-ratio 0.2)
  
  ;; Our fitness-function
  (abs (- (ecase z
            (:foo 10)
            (:bar 100)
            (:bazz 1000))
           (+ x y))))

Then we might define the following rule:

POFTHEDAY> (trivial-indent:define-indentation
               genetic-algorithm:run-ga
               ((&whole &lambda &rest -4) &body))

It says, that run-ga macro has a list-like first argument. Keyword &whole &lambda tells that first item of this list would be indented as a regular lambda-list.

Following &rest -4 tells Emacs to deindent rest items of the first list.

And finally, &body keyword says to handle the rest of the macro arguments like a function's body.

There is also ability to define a custom function which will decide how to indent a form. But I didn't use this feature yet.

You can read detail about the structure of indentation rules in this Emacs docstring.


Brought to you by 40Ants under Creative Commons License