[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Converting BNFs to skeletons: An example implementation for LOOP and cl-

From: D
Subject: Converting BNFs to skeletons: An example implementation for LOOP and cl-loop
Date: Sat, 17 Apr 2021 17:50:46 +0000

As the title suggests, I essentially implemented a small, BNF-like
mini-language on top of the skeleton language which allows otherwise
very simple partial templates to expand into expressions of arbitrary
complexity.  With a bit of generalizing this approach can easily
generate skeletons for other DSLs from the syntax of the DSL.  The
main reason I'm bringing it up is because I could use some input
regarding whether it would be worth breaking this package apart into
two separate ones (I could imagine using the same code to write an rx
skeleton for example, could be neat) or whether this package is a
product of overengineering and I'd be better off trying to simplify
rather than generalize.

(I'll try to explain my thought process below, but for those who
prefer to get the bigger picture from the code itself can just skip
straight to the Elisp.)

Maybe I should first explain *why* I did this.  This whole thing
started with me trying to come up with a small typing aid to help me
remember the syntax of the loop facility.  Since I always liked the
way features like auto-insert worked I decided I would like it to be
based on an expansion mechanism with only minor completion features.
I quickly realized that trying to generate an appropriate skeleton for
this boiled down to basically rewrite the syntax specification, so
that's exactly what I did.  Here's an excerpt of CL's LOOP macro
syntax taken from the CLHS:

loop [name-clause] {variable-clause}* {main-clause}* => result*
name-clause::= named name
variable-clause::= with-clause | initial-final | for-as-clause
with-clause::= with var1 [type-spec] [= form1] {and var2 [type-spec] [=

This structure translated quite naturally into an alist (with some
minor additions for formatting and inlining options to make selection
less tedious), where each element is either another keyword of the
alist or a complex expression in my mini language (described in the
docstring of `loopy-syntax-alist'):

((loop (list (symbol loop) (optional name-clause)
             (* variable-clause) (* main-clause)))
 ;; variable-clause::= with-clause | initial-final | for-as-clause
 (variable-clause (choose for as with
                          initially finally)
 ;; with-clause::= with var1 [type-spec] [= form1]
 (with (symbol with) variable type-spec (optional assignment)
              ;; {and var2 [type-spec] [= form2]}*
              (* (symbol and) variable type-spec (optional assignment)))

All that is left at that point to obtain a skeleton is to recursively
expand the syntax into subskeletons and skeleton elements, prompting
the user whenever a decision needs to be made or some arbitrary sexp
needs to be inserted (lists to be iterated over, bodies of do
statements, etc).

(define-skeleton loopy-insert
  "Insert a LOOP macro interactively."
;;; long docstring
  ;; This command generates the above alist with some parameters to
  ;; account for both Common Lisp's LOOP and Emacs Lisp's cl-loop.
  (loopy--expand 'loop)

Obviously the main complexity of the package rests in the
implementation of loopy--expand, and to generalize this package would
mean to provide a way to define one's own syntax elements (so some
kind of defsyntax) to provide things like `arithmetic-clause', see
loopy-syntax-alist (docstring), loopy--expand and for example
loopy--expand-arithmetic.  As a consequence loopy.el would shrink by
quite a bit and whatever-package-name.el could be utilized for other
projects more easily.. if it is worth reusing.

Attachment: loopy.el
Description: Text Data

reply via email to

[Prev in Thread] Current Thread [Next in Thread]