iTranslated by AI
Notes on attempting custom indentation for Scheme libraries in Vim
A note on why I used to write Scheme libraries with special indentation, and why I eventually decided against it.
Scheme Library Formats
There are roughly four types of formats for Scheme libraries:
-
The "There is no library system, so manage with
load" school -- Embedded implementations like s7. - The "Implementation-specific module system" school -- Common in long-standing influential implementations like Bigloo or Gambit.
-
R6RS -- The school that writes libraries using
(library (lib name) ...). Used by ChezScheme, Racket (with R6RS support), GNU Guile, etc. -
R7RS -- The school that writes libraries using
(define-library (lib name) ...). Used by chibi-scheme, Gauche, etc.
R6RS and R7RS are often supported alongside an implementation's own module system.
In my personal project yuni, I adopted the R6RS library format. I require libraries to be written in R6RS format regardless of the implementation, whether it's Bigloo or Gauche. (yuni provides a dedicated loader for non-R6RS implementations).
Wanting to Ignore Standard Indentation Rules
R6RS libraries have a constraint where the library declaration and the body must be written within a single S-expression. For readability, I wanted to write it like this:
(library (hello)
(export hello)
(import (yuni scheme))
;; ↓ The top-level define stays at the same level as (library ...) (no indentation)
(define (hello)
(display "Hello.\n"))
) ;; ← Closing parenthesis for library
;; * In R6RS format, all expressions must be inside (library ...)
I wanted to lower the indentation of the define where (hello) is declared. however, in a typical editor, it ends up like this:
(library (hello)
(export hello)
(import (yuni scheme))
;; ↓ define ends up at the same level as (import ...)
(define (hello)
(display "Hello.\n"))
)
This looks a bit awkward.
Since I personally write Scheme code using Vim and slimv ( https://github.com/kovisoft/slimv ), I decided to handle this with Vim script.
(By the way, R7RS added a feature similar to C's #include, allowing the library body to be written in a separate file, which alleviated this indentation height issue.)
The Script I Wrote
Vim has a lispindent feature for Lisp-specific indentation that can be enabled with set lisp, but it's not flexible enough to describe special rules like the one above.
So, I declared a dedicated indentation function in .vimrc and specified it for indentexpr to implement the desired indentation rule.
function YuniLispIndentLib(lnum)
" Restore lisp mode (normally nolisp is needed to allow indentexpr to be called)
set lisp
let ind = lispindent(a:lnum) " Get lispindent
" Restore to original state
set nolisp
" Save cursor position as we will move it in subsequent processing
let curpos = getpos('.')
call cursor(1,1)
" Find the (import ...) line and get the indentation height of (import
let headpos = search('^ *(import')
if headpos
call search('(import')
let headpos2 = getpos('.')
" Get the end of the import declaration
call searchpair( '(', '', ')', 'W')
let headln = headpos2[1]
let headind = headpos2[2]
if headln < curpos[1]
" If the current line is after the import declaration, lower the indentation height
ind = ind - headind
endif
endif
" Restore cursor
call cursor(curpos[1], curpos[2])
return ind
endfunction
function YuniSchemeLibraryInit()
set indentexpr=YuniLispIndentLib
endfunction
augroup yuniconfig
" Set up indentexpr only for .sls extensions
au VimEnter *.sls call YuniSchemeLibraryInit()
augroup END
The key point is that lispindent can be called while getting the indentation. It seems slimv also customizes Vim's built-in Lisp indentation using this method.
R6RS Library Indentation Habits
So, I was able to implement a special indentation rule in my editor and briefly considered adopting it as a coding rule, but it seems that this kind of indentation is extremely rare. In other R6RS code, the common style appears to be indenting export and import further while keeping the top-level code indented by just one level.
(library (rnrs records syntactic (6))
(export define-record-type
record-type-descriptor
record-constructor-descriptor
fields mutable immutable parent parent-rtd protocol
sealed opaque nongenerative)
(import (for (rnrs base (6)) run expand)
(for (rnrs syntax-case (6)) expand)
(rename (r6rs private records-explicit)
(define-record-type define-record-type/explicit)))
;; ★ ↓ It's at the same height as export and import
(define-syntax define-record-type
;; Just check syntax, then send off to reference implementation
For a Scheme library, the top level is the only place where variables to be exported from the library can be declared, and having them at the edge of the screen has the advantage of making them visually easy to identify.
However, requiring special settings just to participate in editing is also a significant disadvantage, so I've come to think that I should just accept a two-character indent.
...One could argue that I should just use R7RS's include to separate the library's export/import declarations and the body into different files, but in yuni, I intentionally avoid the R7RS library format in favor of the R6RS format.
To achieve this kind of indentation, you simply need to add library to lispwords.
set lispwords+=library
Is the Edge a Prime Location?
Even if you're not a "Sumikko" (corner-dweller), it's a consensus that the edge is a crucial location for UX. For example, the Apple Newton could use the screen edge as a clipboard ( https://youtu.be/KFtgonf1KpA?t=796 ).
For this reason, the fact that top-level declarations come to the edge of the screen in both normal programs and libraries is an important trait. There was a time when I was seriously considering a dedicated library format just to preserve this in yuni. ...To put it another way, the qualities of "being editable in a standard editor" and "being compatible with the standard (R6RS)" are also important enough to be weighed against this.
That said, excluding C or JavaScript, there actually aren't many programming languages where important symbols can always be declared at the beginning of a line. For instance, in C++, APIs are often declared enclosed within a class declaration, so many of the declarations end up being indented.
Discussion