Sujet : infix via code walker hook.
De : 643-408-1753 (at) *nospam* kylheku.com (Kaz Kylheku)
Groupes : comp.lang.lispDate : 31. Mar 2025, 22:24:03
Autres entêtes
Organisation : A noiseless patient Spider
Message-ID : <20250331141507.905@kylheku.com>
User-Agent : slrn/pre1.0.4-9 (Linux)
I've been toying with the idea for a while of using a code walker
or hooking into an existing code walker in order to support S-exp-ified
infix syntax for arithmetic.
I have an advanced proof of concept happening.
What is interesting here is that the work gives rise to a powerful
argument in favor of the separation of function and variable bindings,
which helps to make it trivial:
$ ./txr -i infix.tl
TXR's no-spray organic production means every bug is carefully removed by hand.
1> (ifx (let ((a 1) (b 2) (c 3))
((a + b) * 3)))
9
Notice how ifx "works through" the let (and trust me when I say, any layering
of syntax it is not interested in).
ifx will find all infix expressions in its interior and transform them
based on the pattern (arg1 op [arg ...]) to (op arg1 [arg ...]).
This is the complete implementation, but relies on an unreleased
*expand-hook* feature:
(defun-match infix-expand-hook
((@(as form (@x @y . @rest)) @env nil)
(if (fboundp y)
^(,y ,x ,*rest)
form))
((@form @nil @nil) form))
(defmacro ifx (:env env . body)
(let ((*expand-hook* (fun infix-expand-hook)))
(expand ^(progn ,*body) env)))
The implementation's macro-expanding code-walker invokes *expand-hook*
for each macro (symbol or compound), function call or malformed form
that it encounters. It passes the form, the macro-expansion-time
environment, and a type symbol. The type symbol is :macro when the
form is a macro invocation, :fun if it is a function call, or else
nil if it is invalid.
The above infix module won't make the transformation on a form
that looks valid. Concretely, if you define a function a, whether
globally or locally, then (a + b) will not be transformed.
As you can see, this also showcases the benefits of a function/variable
namespace separation in Lisp. Even though a has a binding, (a + b) can
be transformed to (+ a b). This is because its binding is a variable
binding, which we can ignore.
In a Lisp-1 like Clojure or Scheme, there is a difficulty here:
we have to determine that a is not a function. That requires
type information or else a run-time check. The latter is a nonstarter,
since we are looking to transform code at macro time.
But even if you have the former, type info, who is to say that is
correct; maybe someone wants (f + g) where those are functions.
Here is a toast to variable/function namespace separation.
-- TXR Programming Language: http://nongnu.org/txrCygnal: Cygwin Native Application Library: http://kylheku.com/cygnalMastodon: @Kazinator@mstdn.ca