A superflat vector drawing of a ceramic throne.
Kaka Farm!

The Scheming Jew Blog!

Adding Extra Syntax Indentation To GNU Emacs Scheme Mode.

Date published:

Adding Extra Syntax Indentation To GNU Emacs Scheme Mode.

Reporting errors in the document.

If I have made any mistakes and you would like to report them, please send them using https://codeberg.org/kakafarm/kaka.farm/issues/new. You can also send hateful messages, religious or ideological proselytisation, or prophecies of imminent doom, I don't mind.

Bindings That Moo!

Let's say you have a fancy new Scheme syntax, let-moo*, provided in https://codeberg.org/kakafarm/scheme-cowsay/.

It does exactly what let* does, except that for each of the bindings, a cow will say the name of the bound name, the expression evaluated, and the value of the expression.

Consider this let*:

(let* ((our-date (current-date))
       (our-date-string (date->string our-date)))
  (display "Current date: ")
  (display our-date-string)
  (newline))

whose output is:

Our date: Sat Nov 09 17:00:16+0200 2024

If we used let-moo*:

(let-moo* ((our-date (current-date))
           (our-date-string (date->string our-date)))
  (display "Current date: ")
  (display our-date-string)
  (newline))

The output would be:

 _________________________________________
/ (our-date (current-date)) => #<date     \
| nanosecond: 67169000 second: 16 minute: |
| 0 hour: 17 day: 9 month: 11 year: 2024  |
\ zone-offset: 7200>)                     /
 -----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
 _________________________________________
/ (our-date-string (date->string          \
| our-date)) => "Sat Nov 09 17:00:16+0200 |
\ 2024"                                   /
 -----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Our date: Sat Nov 09 17:00:16+0200 2024

Such a beautiful and informative pair of cows!

Now, normally, if you haven't already added the proper indentation rules for let-moo* to Emacs, the code would be indented as follows:

(let-moo* ((our-date (current-date))
           (our-date-string (date->string our-date)))
          (display "Current date: ")
          (display our-date-string)
          (newline))

The Emacs Scheme mode figgers this whole sexp is the application of the function let-moo*. In procedure application the arguments should all be in the same indentation level.

So that is a good thing? No, that's a bad thing!

let-moo* is not a procedure, and we want it to be indented the same as let*!

Let us now add the proper indentation of let-moo* to Scheme mode.

Adding the syntax.

Now, to add an indentation setting to the let-moo* syntax to Scheme mode you have (at least?) three options:

  1. Add it to your Emacs configuration.

    This is as easy as adding

    (put 'let-moo* 'scheme-indent-function 1)

    to your init.el, but you should not do that unless this new syntax is ubiquitous in all of your Scheme source files, otherwise you will accidentally indent things in a wrong way.

  2. Add it to a .dir-locals.el file in a specific project directory.

  3. Add it to a specific Scheme source file in which you intent to use this new syntax as comments in the end of the file.

Add the setting as a comment to a specific Scheme source file using add-file-local-variable.

Once you've opened a Scheme source file, follow these steps:

  1. Run the add-file-local-variable command:

    M-x add-file-local-variable <RET>
  2. It will now prompt you in a minibuffer for the variable name to set:

    Add file local variable:

    Write eval in that minibuffer prompt:

    Add file local variable: eval
  3. Now you will be prompted for the expression that will be evaluated:

    Add eval with expression:

    Write (put 'let-moo* 'scheme-indent-function 1).

    Add eval with expression: (put 'let-moo* 'scheme-indent-function 1)

This would result with the following comments at the bottom of your file:

;; Local Variables:
;; eval: (put 'let-moo* 'scheme-indent-function 1)
;; End:

When the file is closed and then opened again, the Emacs Lisp code (put 'let-moo* 'scheme-indent-function 1) would be evaluated for this Scheme source file's buffer.

Add the setting to your project's directory in a .dir-locals.el file using add-dir-local-variable.

Let's say the path to your project's root directory is /home/calicocutpants/projects/super-scheme-package/.

  1. Open a scheme source file in the root directory of your project using the C-x C-f /home/calicocutpants/projects/super-scheme-package/super-duper.scm
  2. Run the add-dir-local-variable command.
  3. If you ran the add-dir-local-variable command in a Scheme source file, the default value would be scheme-mode, but if it isn't, enter:

    scheme-mode
  4. Enter:

    eval
  5. Enter:

    (put 'let-moo* 'scheme-indent-function 1)

You have created the /home/calicocutpants/projects/super-scheme-package/.dir-locals.el file, and it would have a line like this:

((scheme-mode . ((eval . (put 'define-checked 'scheme-indent-function 1)))))

The next time you open the source file, your let-moo* would properly indent.

What are we mooing here?

This is it for the howto, but we can explain what is going on a bit further.

Let's ask Emacs for the documentation of the put function using the command describe-symbol:

We get:

put is a built-in function in ‘C source code’.

(put SYMBOL PROPNAME VALUE)

Store SYMBOL’s PROPNAME property with value VALUE.
It can be retrieved with ‘(get SYMBOL PROPNAME)’.

  Probably introduced at or before Emacs version 1.6.

[back]

In our code SYMBOL is 'let-moo*, PROPNAME is 'schemeindent-function, and VALUE is 1, so it stores "'let-moo*'s scheme-indent-function property with the value 1".

What does it mean?

It means that Scheme Mode, when encountering let-moo* in the code, would ask for its scheme-indent-function property value.

If this property is not set for let-moo*, we will use the default function indentation.

If it is set, we would use that for indentation.

In our case the value is 1, which means that in our sexp, where let-moo* is our first element, only the second element would be distinguished and indented further to the right in comparison to the rest of the elements, the third element and onwards, which are the body of the sexp.

Consider the code:

(let-moo*
    ((a 2) (b 3) (c 5))
  (display (* a b c))
  (newline))

Here the first element of the sexp is let-moo*, ((a 2) (b 3) (c 5)) is our second distinguished element, the body is the third element and onwards - (display (* a b c)) and (newline).

That is the default indentation rule for let*, but what if we had implemented a fancy let-moo*, one that adds customisations to our cows?

A bit further - customisable cows - let-moo-parameterised*.

Did I tell you we have customisable cows? Our customisable cows are used with the let-moo-parameterised* syntax (here properly indented):

(let-moo-parameterised*
    ((a 2)
     (b (+ a 1))
     (c (+ a b)))
    ((eyes "oO")
     (tongue "U"))
  (display (* a b c))
  (newline))

As you can see, the proper way to indent the code is for both ((a 2) (b (+ a 1)) (c (+ a b))) and ((random-cow #t) (eyes "oO") (tongue "ww")) (which are the second and third elements of the sexp) to have the same distinguished indentation level, a bit further to the right than the body, which includes the fourth element and onwards - (display (* a b c)) and (newline).

The output would be:

 ____________
< (a 2) => 2 >
 ------------
        \   ^__^
         \  (oO)\_______
            (__)\       )\/\
             U  ||----w |
                ||     ||
 __________________
< (b (+ a 1)) => 3 >
 ------------------
        \   ^__^
         \  (oO)\_______
            (__)\       )\/\
             U  ||----w |
                ||     ||
 __________________
< (c (+ a b)) => 5 >
 ------------------
        \   ^__^
         \  (oO)\_______
            (__)\       )\/\
             U  ||----w |
                ||     ||
30

How do you distinguish the second and the third elements of this sexp?

You might have guessed it - set the scheme-indent-function property of let-moo-parameterised* to 2!

Just use 2 as the value in the put expression:

(put 'let-moo-parameterised* 'scheme-indent-function 2)

Well, that's about that.

See you later.

Tag feeds:

Kaka Farm by Yuval Langer is licensed under Attribution-ShareAlike 4.0 International