binfix
20190813
BINFIX  A powerful binary infix syntax for Common LISP.
BINFIX
Viktor Cerovski, August 2019.
Introduction
BINFIX (blend from "Binary Infix") is a powerful infix syntax notation for Sexpressions of Common LISP ranging from simple arithmetic and logical forms to whole programs.
NEW FEATURES in v0.50 (July/August 2019): ;
separated definitions
of functions which makes use of def
for this purpose obsolete;
Simpler local definitions of longer/multiple functions;
No need to import any symbols to use BINFIX.
NEW FEATURE (July 2019): let
supports multiplevalue and
destructuringbind.
NEW FEATURES (July 2019): Introduced =>
for writing cond
forms,
use of ?
is depreciated. Introduced BINFIX withslots
.
NEW FEATURE (Jun 2019): Bterms, allowing indexinglike operations.
Currently developed v0.50, once it's finished, should mark transition from experimental to a stable version of BINFIX. Although there are still a few important new features to come, BINFIX begins to look about right.
Content
 Installation
 Bexpression
 Examples
 Indexing (new feature)
 Mappings
 Working with bits
 Support for macros
 More involved examples
 Controlling Bops (new feature)
 Implementation
 Appendix
Installation
Quicklisp makes the downloading/installation/loading trivial:
(ql:quickload :binfix)
System can be tested via
(asdf:testsystem :binfix)
Supported LISP implementations are SBCL (also used for development,) Clozure CL, ECL (tested with v15.3.7 and v16.1.3) and ABCL, while CLISP as of this release is not supported.
BINFIX shadows @
in Clozure CL and ECL, as well as var
(sbdebug:var
) and
struct
(sbalien:struct
) in SBCL.
The latest version is available at github, and can be obtained by
git clone https://github.com/vcerovski/binfix
There is also a syntaxhighlighting file for vim
editor, binfix.vim
.
Its installation consists of copying it into vimrc/syntax folder, which is
on Linux located at ~/.vim/syntax
(should be created if it doesn't exist.)
Once installed, after loading a LISP file, LISP+BINFIX syntax highlighting
can be activated by :set syntax=binfix
. Loading can be done automatically
by adding
filetype on
au BufNewFile,BufRead *.lisp,*.cl set syntax=binfix
to .vimrc
.
Bexpression
BINFIX rewrites a Bexpression (Bexpr) into an Sexpression,
written using curly brackets {
...}
, and may contain, in addition to
symbols, constants, Sexprs and Bexprs, also Boperations (Bops) and
Bterms. Bops are just symbols that BINFIX recognizes and prioritizes
according to each Bop's precedence (priority). In addition to priority,
each Bop may have properties, which further specify how to handle Bop's
left and righthand side. Bterm is a Bop that works from within Bexpr
to allow indexinglike operations.
BINFIX is a freeform notation (just like Sexpr), i.e any number of empty spaces (including tabs and newlines) between tokens is treated the same as a single white space.
Generally, quoting a BINFIX expression in REPL will produce the corresponding Sexpression.
Examples
For easier comparison of input and output forms in following examples, LISP
printer is first setq
(Bop =.
) to lowercase output with
{*printcase* =. :downcase}
=> :downcase
Arithmetic and logical expressions
Classic math stuff:
{2 * 3 + 4}
=> 10
'{a * {b + c}}
=> (* a (+ b c))
'{ {x + y} / x * y}
=> ( (/ (+ x y) (* x y)))
'{0 < x < 1 && y >= 1  y >= 2}
=> (or (and (< 0 x 1) (>= y 1)) (>= y 2))
'{ f x  g x  h x}
=> ( ( (f x)) (g x) (h x))
Expressions like {(f x y) * (g a b)}
and {{f x y} * {g a b}}
generally
produce the same result. The inner brackets, however, can be removed:
'{sqrt x * sin x}
=> (* (sqrt x) (sin x))
'{A ! i .= B ! j + C ! k}
=> (setf (aref a i) (+ (aref b j) (aref c k)))
'{a ! i j += b ! i k * c ! k j}
=> (incf (aref a i j) (* (aref b i k) (aref c k j)))
'{listp A && car A == 'x && cdr A  A}
=> (or (and (listp a) (eql (car a) 'x) (cdr x)) a)
Consing
Operation :.
stands for cons
. For instance,
{2 :. loop for i to 9 collect i}
=> (2 0 1 2 3 4 5 6 7 8 9)
with the familiar behavior:
{1 :. 2 :. 3 equal '(1 2 . 3)}
=> t
{1 :. 2 :. 3 :. {} equal '(1 2 3)}
=> t
Lambdas, definitions and type annotations
lambda
'{x > sqrt x * sin x}
=> (lambda (x) (* (sqrt x) (sin x)))
'{x :singlefloat > sqrt x * sin x}
=> (lambda (x) (declare (type singlefloat x)) (* (sqrt x) (sin x)))
'{x y > {x  y}/{x + y}}
=> (lambda (x y) (/ ( x y) (+ x y)))
Mixing of notations works as well, so each of the following
{x y > / ( x y) (+ x y)}
{x y > ( x y)/(+ x y)}
{x y > (/ ( x y) (+ x y))}
produces the same form.
Fancy way of writing {2 * 3 + 4}
{x > y > z > x * y + z @ 2 @ 3 @ 4}
=> 10
Quoting reveals the expanded Sexpr
'{x > y > z > x * y + z @ 2 @ 3 @ 4}
=>
(funcall (funcall (funcall
(lambda (x) (lambda (y) (lambda (z) (+ (* x y) z))))
2) 3) 4)
Indeed, @
is leftassociative, standing for funcall
.
More complicated types can be also explicitly given after an argument,
'{x :or symbol number > x :. x}
=>
(lambda (x) (declare (type (or symbol number) x)) (cons x x))
Mappings
mapcar
is also supported:
'{x > sin x * sqrt x @. (f x)}
=>
(mapcar (lambda (x) (* (sin x) (sqrt x))) (f x))
Alternatively, it is possible to use the expressiontermination symbol ;
,
{x > sin x * sqrt x @. f x;}
to the same effect.
reduce
is represented by @/
,
'{#'max @/ x y > abs{x  y} @. a b}
=>
(reduce #'max (mapcar (lambda (x y) (abs ( x y))) a b))
and other maps have their @
's as well.
defun
Factorial fun:
'{f n :integer := if {n <= 0} 1 {n * f {1 n}}}
=>
(defun f (n)
(declare (type integer n))
(if (<= n 0)
1
(* n (f (1 n)))))
Function documentation, local declarations, local bindings and comments have a straightforward syntax:
'{g x := "Auxilary fn."
declare (inline)
let x*x = x * x; ;; Note binds termination via ;
x*x / 1+ x*x}
=>
(defun g (x)
"Auxilary fn."
(declare (inline))
(let ((x*x (* x x)))
(/ x*x (1+ x*x))))
&optional
is optional
Explicitly tailrecursive version of f
'{fac n m = 1 :=
declare (integer m n)
if {n <= 0} m
{fac {n  1} {n * m}}}
=>
(defun fac (n &optional (m 1))
(declare (integer m n))
(if (<= n 0)
m
(fac ( n 1) (* n m))))
As you may by now expect, the following is also permitted
{fac n :integer m :integer = 1 :=
if {n <= 0} m
{fac {n  1} {n * m}}}
suppliedp variable var
for an optional/keyword argument is given by ?var
after the assignment.
{f x y = 0 ?suppliedy &key z = 0 ?suppliedz :=
<body expr>
}
,
where, within <body expr>
, boolean variables suppliedy
and suppliedz
are available (for the standard check whether respective values were provided
in the call of f
.)
Multiple definitions (new feature in v0.50)
Definitions of several functions are simply written as ;
separated individual
definitions,
'{f x y := print "Addition";
x + y;
g x :num y :num : x + y;
m x y :== `{,x + ,y}}
=>
(progn
(defun f (x y) (print "Addition") (+ x y))
(defmethod g ((x num) (y num)) (+ x y))
(defmacro m (x y) `(+ ,x ,y)))
Another way to obtain the same Sexpr until v0.50 was to use def
,
'{def f x y := print "Addition";
x + y
def g x :num y :num : x + y
def m x y :== `{,x + ,y}}
but this way of defining functions is obsolete.
Furthermore, to define types or compiler macros in v0.50, it is
sufficient to prefix a macro definition with type
or compilermacro
,
respectively. Here is the simplest example that puts together defining
various kinds of Common LISP functions using BINFIX,
'{f x := x;
g x : x;
m x :== x;
type I x :== x;
compilermacro c x :== x}
=>
(progn
(defun f (x) x)
(defmethod g (x) x)
(defmacro m (x) x)
(deftype i (x) x)
(definecompilermacro c (x) x))
Local functions (new feature in v0.50)
Version of fac
with a local recursive function f
prior to v0.50 could be
written as:
{fac n :integer :=
labels
f n m := {if {n = 0} m
{f (1 n) {n * m}}}
f n 1}
or, by using a single ;
to terminate definition, as in
{fac n :integer :=
labels
f n m := if {n = 0} m
{f (1 n) {n * m}};
f n 1}
where both forms are translated into the following Sexpr,
(defun fac (n)
(declare (type integer n))
(labels ((f (n m)
(if (= n 0)
m
(f (1 n) (* n m)))))
(f n 1)))
which can be demonstrated by simply evaluating the quoted expressions.
These two ways of defining local functions are not supported anymore starting
with v0.50. Instead, the local function definition(s) must be enclosed in
{
...}
,
{fac n :integer :=
labels {f n m :=
if {n = 0} m
{f (1 n) {n * m}}}
f n 1}
The new way of writing local definitions has advantages over old when multiple and/or more complicated local functions are defined.
The same syntax is used also in the case of flet
and macrolet
, except that
in the latter case :==
is written instead of :=
.
defmethod
The following two generic versions of f
'{f n :integer : if {n <= 0} 1 {n * f {1 n}}}
'{f (n integer): if {n <= 0} 1 {n * f {1 n}}}
both produce
(defmethod f ((n integer))
(if (<= n 0)
1
(* n (f (1 n)))))
:
supports also eqlspecialization via ==
op, analogous to
the way =
is used for optional arguments initialization, as well as an
optional method qualifier, given as the first argument after the method name,
that can be either a keyword or an atom surrounded by parens (i.e :around
,
(reduce)
etc.)
defmacro
Macros are defined via :==
operation, similar to the previous examples.
See Sec. Support for macros.
Type annotations, declarations and definitions
The examples shown so far demonstrate the possibility to typeannotate
symbols in binds and lambdalists by an (optional) keyword representing
the type (for instance :fixnum
, :myclass
, :simplearray singlefloat
,
:or symbol number
, :{symbol or number}
, etc.)
Bops that represent LISP forms which allow declaration(s), in BINFIX can
have in addition to the standard (declare ...)
form also unparenthesized
variant:
'{f x :fixnum y = 2 :=
declare (inline)
declare (fixnum y)
x + y ** 2}
=>
(defun f (x &optional (y 2))
(declare (type fixnum x))
(declare (inline))
(declare (fixnum y))
(+ x (expt y 2)))
Another way to declare x
and y
is
'{f x y = 2 :=
declare x y :fixnum
declare (inline)
x + y ** 2}
=>
(defun f (x &optional (y 2))
(declare (inline))
(declare (fixnum x y))
(+ x (expt y 2)))
Function types
Operation :>
can be used to specify function type. For example, in
SBCL 1.1.17 function sin
has declared type that can be written as
'{number :> singlefloat 1.0 1.0 
doublefloat 1.0 1.0 
complex singlefloat 
complex doublefloat .x. &optional}
=>
(function (number)
(values
(or (singlefloat 1.0 1.0)
(doublefloat 1.0 1.0)
(complex singlefloat)
(complex doublefloat))
&optional))
Function fac
with a local function from this example
can have its type declared as
'{fac n :integer :=
labels
{f n m := if {n = 0} m
{f (1 n) {n * m}}}
declare f {integer integer :> integer}
f n 1}
=>
(defun fac (n)
(declare (type integer n))
(labels ((f (n m)
(if (= n 0)
m
(f (1 n) (* n m)))))
(declare (ftype (function (integer integer) integer) f))
(f n 1)))
Declaration which annotates that symbol value of a symbol is a function can be
achieved by using >
instead of :>
in declaration of the symbol. For
instance:
'{f x :integer :=
let f = x > 1+ x;
declare f {integer > integer}
flet {f x := 1 x}
declare f {integer :> integer}
cons (f x) {f @ x}}
=>
(defun f (x)
(declare (type integer x))
(let ((f (lambda (x) (1+ x))))
(declare (type (function (integer) integer) f))
(flet ((f (x)
(1 x)))
(declare (ftype (function (integer) integer) f))
(cons (f x) (funcall f x)))))
which has the expected behavior: (f 0)
=> (1 . 1)
Type definitions are given using :type=
OP, as in
`{mod n :type= `(integer 0 (,n))}
=>
(deftype mod (n) `(integer 0 (,n)))
def
Program typically consists of a number of definitions. Bop def
can be used
to define variables, parameters, constants, structures, classes and
generic functions. For instance,
'{def parameter *x* = 1 *y* = 2
def struct point x y z
def f x := sqrt x * sin x}
=>
(progn
nil
(defparameter *x* 1)
(defparameter *y* 2)
(defstruct point x y z)
(defun f (x) (* (sqrt x) (sin x))))
As it is clear from the example, the definitions are wrapped up in progn
.
More detailed definitions are also straightforward to specify:
'{def parameter
*y* :singlefloat = 1f0
*z* :singlefloat = 1f0
def struct point "Point"
:printfunction {p s d >
declare (ignore d)
withslots x y z :_ p
format s "#<~$ ~$ ~$>" x y z}
:constructor createpoint (x y = *y* z = *z*)
x :singlefloat = 0f0
y :singlefloat = 0f0
z :singlefloat = 0f0;
point+= p :point q :point :=
p _'x += q _'x;
p _'y += q _'y;
p _'z += q _'z;
p;
point= p :point q :point :=
withslots x y z :_ p
withslots dx = x dy = y dz = z :_ q
x = dx;
y = dy;
z = dz;
p}
=>
(progn
(declaim (type singlefloat *y*)
(type singlefloat *z*))
(defparameter *y* 1.0)
(defparameter *z* 1.0)
(defstruct
(point
(:printfunction
(lambda (p s d)
(declare (ignore d))
(withslots (x y z)
p
(format s "#<~$ ~$ ~$>" x y z))))
(:constructor createpoint (x &optional (y *y*) (z *z*))))
"Point"
(x 0.0 :type singlefloat)
(y 0.0 :type singlefloat)
(z 0.0 :type singlefloat))
(progn
(defun point+= (p q)
(declare (type point p)
(type point q))
(incf (slotvalue p 'x) (slotvalue q 'x))
(incf (slotvalue p 'y) (slotvalue q 'y))
(incf (slotvalue p 'z) (slotvalue q 'z))
p)
(defun point= (p q)
(declare (type point p)
(type point q))
(withslots (x y z)
p
(withslots ((dx x) (dy y) (dz z))
q
(decf x dx)
(decf y dy)
(decf z dz)
p)))))
def class
syntax is like defclass
without parens. For this to work, class
options (:documentation
and :metaclass
) have to be given before
description of slots, while :defaultinitargs
comes last as usual, just
unparenthesized (see example.)
def
ining of symbols follows the same syntax as let
binding, which
is covered next.
Definitions and declarations without parens
BINFIX allows writing of standard LISP definition forms without outer parens, as in
'{declaim (fixnum a b c)
defvar a 0
defvar b 1 "variable b"
defvar c 2}
=>
(progn
(declaim (fixnum a b c))
(defvar a 0)
(defvar b 1 "variable b")
(defvar c 2))
This extends to all Common LISP defforms, declaim
and proclaim
.
The result is wrapped up in a progn
.
LETs (new feature)
LET symbolbinding forms (let
, let*
, symbolmacrolet
, etc) in BINFIX use
=
with an optional typeannotation:
'{let x :bit = 1
y = {2 ** 3}
z = 4
x + y * z}
=>
(let ((x 1) (y (expt 2 3)) (z 4))
(declare (type bit x))
(+ x (* y z)))
New feature: BINFIX let
supports multiplevaluebind
and
destructuringbind
A single ;
can be used as a terminator of bindings:
'{let x :bit = 1
y = 2 ** 3
z = f a;
x + y * z}
=>
(let ((x 1) (y (expt 2 3)) (z (f a)))
(declare (type bit x))
(+ x (* y z)))
Finally, a single ;
can also be used to separate forms in implicitprogn,
as in
'{let x :bit = 1
y = 2 ** 3
z = f a; ;; end of binds
print "Let binds"; ;; 1st form
x + y * z} ;; 2nd form of implicitprogn
=>
(let ((x 1) (y (expt 2 3)) (z (f a)))
(declare (type bit x))
(print "Let binds")
(+ x (* y z)))
Nesting of let
s without parens follows the rightassociativity
'{let a = f x;
if a
(g x)
let b = h x;
f b}
=>
(let ((a (f x)))
(if a
(g x)
(let ((b (h x)))
(f b))))
Note the three levels of parens gone.
SETs
In addition to =.
, =...
and .=
, Bops representing, respectively, a single
setq
, multiplevaluesetq
and setf
assignment, multiple assignments via
SETs can be done using =
,
'{psetq x = cos a * x + sin a * y
y =  sin a * x + cos a * y}
=>
(psetq x (+ (* (cos a) x) (* (sin a) y))
y (+ ( (* (sin a) x)) (* (cos a) y)))
If it is necessary to remove repeating sin a
and cos a
,
it is easy to use let
,
{let sin = sin a
cos = cos a;
psetq x = cos * x + sin * y
y =  sin * x + cos * y}
and in the case of SETF assignments, RHS are represented with a single expression,
'{psetf a ! 0 = {a ! 1}
a ! 1 = {a ! 0}}
=>
(psetf (aref a 0) (aref a 1)
(aref a 1) (aref a 0))
Alternatively, it is possible to use a single ;
as an expressiontermination
symbol,
'{psetf a ! 0 = a ! 1; ;; expr. termination via single ;
a ! 1 = a ! 0}
=>
(psetf (aref a 0) (aref a 1)
(aref a 1) (aref a 0))
It is also possible to mix infix SETFs with other expressions:
'{f x + setf a = b
c = d;
* h a c}
=>
(+ (f x)
(*
(setf a b
c d)
(h a c)))
setf
and setq
can be also represented via .=
and =.
Bops,
and the main difference is in prioritythe latter can be embedded
within lambdas without parens. For instance, both
'{a > {setf car a = 0} .@ list}
and
'{a > car a .= 0 .@ list}
=>
(mapc (lambda (a) (setf (car a) 0)) list)
In the case of implicitprogn within lambda,
'{a b > {setf car a = car b;
car b = 0}
.@ l1 l2}
=>
(mapc (lambda (a b)
(setf (car a) (car b)
(car b) 0))
l1 l2)
while
'{a b > car a .= car b;
car b .= 0
.@ l1 l2}
=>
(mapc (lambda (a b)
(setf (car a) (car b))
(setf (car b) 0))
l1 l2)
Implicit progn
An implicit progn
in BINFIX is achieved with a single ;
separating the
forms forming the progn. In all cases (>
, :=
, :
and LETs) the syntax
is following that of the LET example above.
As expected, other prog
s have to be explicitly given,
'{x > prog2 (format t "Calculating... ")
{f $ x * x}
(format t "done.~%")}
or
'{x > prog2
format t "Calculating... ";
f {x * x};
format t "done.~%"}
both producing the following form
(lambda (x)
(prog2 (format t "Calculating... ") (f (* x x)) (format t "done.~%")))
Since BINFIX is a freeform notation, the following oneliner also works:
'{x > prog2 format t "Calculating... "; f{x * x}; format t "done.~%"}
Bop <&
stands for prog1
,
'{x > {f {x * x} <&
format t "Calculation done.~%"}}
=>
(lambda (x) (prog1 (f (* x x)) (format t "Calculation done.~%")))
while multiplevalueprog1
is given by <&..
.
$
plitters
Infix $
is a vanishing OP, leaving only its arguments,
effectively splitting the list in two parts.
'{f $ g $ h x y z}
=> (f (g (h x y z)))
Effect of $
is similar to $
in Haskell, except that here it works
with Sexpr, so it is also possible to write
'{declare $ optimize (speed 1) (safety 1)}
or
'{declare {optimize $ speed 3; safety 1}}
both of which evaluate to
(declare (optimize (speed 1) (safety 1)))
$
also allows writing a shorter cond
, as in
(cond {p x $ f x}
{q x $ g x}
{r x $ h x}
{t $ x})
compared to the equivalent
(cond ((p x) (f x))
((q x) (g x))
((r x) (h x))
(t x))
$
parenthesizes its l.h.s, leaving r.h.s. unchanged. Another splitter is .$
,
which does the opposite, namely parenthesizes its r.h.s leaving l.h.s unchanged,
providing yet another way to omit parens:
'{loop for i to n
append loop for j to m
collect .$ i :. j}
=>
(loop for i to n
append (loop for j to m
collect (cons i j)))
Multiplechoice forms (cond
, case
, ...) (new feature)
An alternative, depreciated, syntax to describe multiplechoice forms is to
use ?
and ;
{cond p x ? f x;
q x ? g x;
r x ? h x;
t ? x}
Preferred way to write such a form is to use =>
instead:
{cond p x => f x;
q x => g x;
r x => h x;
t => x}
Similarly, case
like forms accept a Bexpr before =>
clauses,
{ecase f x;
0 1 2 => #\a;
3 4 => #\b;
6 => #\c}
where in simple cases =>
can be omitted
'{case f a; 1 a; 2 b; 3 c}
=>
(case (f a) (1 a) (2 b) (3 c))
Writing of implicitprogn in each clause is also supported in a straightforward way
{ecase f x;
0 1 2 => print "a"; g #\a;
3 4 => print "b"; g #\b;
6 => print "c"; h #\c}
=>
(ecase (f x)
((0 1 2) (print "a") (g #\a))
((3 4) (print "b") (g #\b))
(6 (print "c") (h #\c)))
See also ordinal
example below.
Destructuring, multiple values (new feature)
BINFIX let
supports binding of multiple values as well as
destructuring,
`{let a = 1 b = 2 c = 3
let x y z = values 1 2 3;
let (p (q = 2) r = 3) = '(1 nil);
a = x = p = 1 &&
b = y = q = 2 &&
c = z = r = 3}
=>
(let ((a 1) (b 2) (c 3))
(multiplevaluebind (x y z) (values 1 2 3)
(destructuringbind (p (&optional (q 2)) &optional (r 3)) '(1 nil)
(and (= a x p 1)
(= b y q 2)
(= c z r 3)))))
which evaluates to t
.
Multiple values (values
) are represented by .x.
as well as values
,
multiplevaluebind
by =..
, and destructuringbind
by ..=
'{a (b) c ..= (f x) a + 1 .x. b + 2 .x. c + 3}
=>
(destructuringbind (a (b) c) (f x) (values (+ a 1) (+ b 2) (+ c 3)))
Another way to write the same expr:
'{a (b) c ..= (f x) values a + 1; b + 2; c + 3}
multiplevaluecall
is represented by .@.
'{#'list .@. 1 '(b 2) 3}
=>
(multiplevaluecall #'list 1 '(b 2) 3)
=>
(1 (b 2) 3)
Both ..=
and =..
can be nested,
'{a b c =.. (f x)
x y z =.. (g z)
a * x + b * y + c * z}
=>
(multiplevaluebind (a b c)
(f x)
(multiplevaluebind (x y z) (g z) (+ (* a x) (* b y) (* c z))))
multiplevaluesetq
is given by =...
Loops
Loops can be also nested without writing parens:
'{loop for i = 1 to 3
collect loop for j = 2 to 4
collect {i :. j}}
=>
(loop for i = 1 to 3
collect (loop for j = 2 to 4
collect (cons i j)))
Hash tables and association lists
Hash tables are supported via ~!
(gethash
),
~~
(remhash
) and @~
(maphash
) Bops. See also indexing.
Association lists are accessible via !~~
(assoc
) and ~~!
(rassoc
).
Mappings
Mappings and function applications are what @
ops are all about,
as summarized in the following table,
@  funcall 
@.  mapcar 
@..  maplist 
@n  mapcan 
@.n  mapcon 
.@  mapc 
..@  mapl 
@/  reduce 
@~  maphash 
@@  apply 
.@.  multiplevaluecall 
They all have the same priority and are rightassociative. Since they bind
weaker than >
, they are easy to string together with lambdas, as in a
mapreduce expr.
{'max @/ x y > abs{x  y} @. a b}
Indexing (new feature)
Indexing can be done using square brackets, [
...]
, by default set to
aref
,
'{a[i;j] += b[i;k] * c[k;j]}
=>
(incf (aref a i j) (* (aref b i k) (aref c k j)))
or using doublesquare brackets, [[
...]]
, with one or two arguments, by
default set to indexing of hash table,
'{ table[[key; default]] }
=>
(gethash key table default)
What squarebrackets represent can be changed using setbinfix
.
The following table summarizes indexing Bops, from the weakest to the strongest binding:
thcdr  nthcdr 
thbit  logbitp 
!.. Â
thvalue  nthvalue 
!.  svref 
.!  elt 
th  nth 
!!.  rowmajoraref 
.!!.  bit 
!!  aref 
~!
!~~
~~!  gethash
assoc
rassoc 
.!.  bit 
!  aref 
!..
and thvalue
are mere synonyms and thus of the same priority, as are
.!
!.
and !!.
, while !!
is a weaker binding !
, allowing easier writing of expr. with arithmetic
operations with indices, like
{a !! i + j}
{a !! i + j; 1 k;}
etc. In the same relation stand .!.
and .!!.
Indexing of arrays is by default supported by the new squarebrackets BINFIX
reader, so the above two examples can be written as {a[i + j]}
and
{a[i + j; 1 k]}
, respectively.
Working with bits
Integer bitlogical BINFIX ops are given with a .
after the name of OP,
while bitarray version of the same OP with .
before and after the name.
For instance, {a or. b}
transforms to (logior a b)
, while
{a .or. b}
transforms to (bitior a b)
.
Support for macros
If BINFIX terms only are inserted under backquote, everything should work fine,
'{let t1 = 'x
t2 = '{x + x}
`{x > ,t1 / ,t2}}
=>
(let ((t1 'x) (t2 '(+ x x)))
`(lambda (x) (/ ,t1 ,t2)))
Replacing, however, BINFIX operations inside a backquoted BINFIX will not
work. This is currently not considered as a problem because direct call of
binfix
will cover some important cases of macro transformations in a
straightforward manner:
{m x y op = '/ type = :doublefloat :==
let a = (gensym)
b = (gensym)
binfix:binfix
`(let ,a ,type = ,x
,b ,type = ,y
{,a  ,b} ,op {,a + ,b})}
Now macro m
works as expected:
(macroexpand1 '(m (f x y) {a + b}))
=>
(let ((#:g805 (f x y)) (#:g806 (+ a b)))
(declare (type doublefloat #:g806)
(type doublefloat #:g805))
(/ ( #:g805 #:g806) (+ #:g805 #:g806)))
t
or,
(macroexpand1 '(m (f x y) {a + b}) * :doublefloat)
=>
(let ((#:g817 (f x y)) (#:g818 (+ a b)))
(declare (type doublefloat #:g817)
(type doublefloat #:g818))
(* ( #:g817 #:g818) (+ #:g817 #:g818)))
t
See more in implementation details
More involved examples
ordinal
Converting an integer into ordinal string in English can be defined as
{ordinal i :integer :=
let* a = i mod 10
b = i mod 100
suf = {cond
a = b = 1  a = 1 && 21 <= b <= 91 => "st";
a = b = 2  a = 2 && 22 <= b <= 92 => "nd";
a = b = 3  a = 3 && 23 <= b <= 93 => "rd";
t => "th"}
format () "~D~a" i suf}
It can be also written in a more "lispy" way without parens as
{ordinal1 i :integer :=
let* a = i mod 10
b = i mod 100
suf = {cond
= a b 1 or = a 1 and <= b 21 91 => "st";
= a b 2 or = a 2 and <= b 22 92 => "nd";
= a b 3 or = a 3 and <= b 23 93 => "rd";
t => "th"}
format () "~D~a" i suf}
which can be tried using @.
(mapcar
)
{#'ordinal @. '(0 1 12 22 43 57 1901)}
=> ("0th" "1st" "12th" "22nd" "43rd" "57th" "1901st")
(This example is picked up from Rust blog)
join
APLish joining of things into list:
{
defgeneric join (a b) &
join a :list b :list : append a b &
join a :t b :list : cons a b &
join a :list b :t : append a (list b) &
join a :t b :t : list a b &
defbinfix ++ join
}
; Must close here in order to use ++
{let e = '{2 in 'x ++ '(1 2 3) ++ '((a)) ++ 1 * 2}
format t "~S~%=> ~S" e (eval e)}
Evaluation of the above returns t
and prints the following
(member 2 (join 'x (join '(1 2 3) (join '((a)) (* 1 2)))))
=> (2 3 (a) 2)
Another way to write join
is as a single defgeneric
definition, using def generic
,
{def generic join a b;
"Generic join."
a :list b :list : append a b;
a :t b :list : a :. b;
a :list b :t : `(,@a ,b);
a :t b :t : list a b}
which expands into
(progn
(defgeneric join
(a b)
(:documentation "Generic join.")
(:method ((a list) (b list)) (append a b))
(:method ((a t) (b list)) (cons a b))
(:method ((a list) (b t)) `(,@a ,b))
(:method ((a t) (b t)) (list a b))))
(new feature in v0.50) This way of writing ;
separated instances is
possible also in the first example, by replacing the four join
lines with
join a :list b :list : append a b;
join a :t b :list : cons a b;
join a :list b :t : append a (list b);
join a :t b :t : list a b;
valuesbind
Macro multiplevaluebind
with symbol _
in variable list standing for
an ignored value can be defined as
{valuesbind v e &rest r :==
let* _ = ()
vars = a > if {a == '_} {car $ push (gensym) _} a @. v;
`(multiplevaluebind ,vars ,e
,@{_ && `({declare $ ignore ,@_})}
,@r)}
So, for instance,
(macroexpand1 '(valuesbind (a _) (truncate 10 3) a))
=>
(multiplevaluebind (a #:g823) (truncate 10 3) (declare (ignore #:g823)) a)
t
for
Nested BINFIX lambda lists can be used in definitions of macros, as in the following example of a procedural forloop macro
{for (v :symbol from below by = 1) &rest r :==
`(loop for,v fixnum from,from below,below ,@{by /= 1 && `(by,by)}
do ,@r)}
Now
(macroexpand1 '(for (i 0 n)
{a ! i .= 1+ i}))
=>
(loop for i fixnum from 0 below n
do (setf (aref a i) (1+ i)))
t
Cartesian to polar coordinates
An example from Common LISP the Language 2nd ed. where Cartesian coordinates are converted into polar coordinates via change of class can be straightforwardly written in BINFIX (prior to v0.50) as
{def class position () ();
class xyposition (position)
x :initform 0 :initarg :x
y :initform 0 :initarg :y;
class rhothetaposition (position)
rho :initform 0
theta :initform 0
def updateinstancefordifferentclass :before
old :xyposition
new :rhothetaposition &key :
;; Copy the position information from old to new to make new
;; be a rhothetaposition at the same position as old.
let x = old _'x
y = old _'y;
new _'rho .= sqrt {x * x + y * y};
new _'theta .= atan y x
;;; At this point an instance of the class xyposition can be
;;; changed to be an instance of the class rhothetaposition
;;; using changeclass:
& p1 =. makeinstance 'xyposition :x 2 :y 0
& changeclass p1 'rhothetaposition
;;; The result is that the instance bound to p1 is now
;;; an instance of the class rhothetaposition.
;;; The updateinstancefordifferentclass method
;;; performed the initialization of the rho and theta
;;; slots based on the values of the x and y slots,
;;; which were maintained by the old instance.
}
while in v0.50 def class
section of the code has to be finished by ;
and def
before updateinstancefordifferentclass
is superfluous,
{def class position () ();
class xyposition (position)
x :initform 0 :initarg :x
y :initform 0 :initarg :y;
class rhothetaposition (position)
rho :initform 0
theta :initform 0;
updateinstancefordifferentclass :before
old :xyposition
new :rhothetaposition &key :
;; Copy the position information from old to new to make new
;; be a rhothetaposition at the same position as old.
let x = old _'x
y = old _'y;
new _'rho .= sqrt {x * x + y * y};
new _'theta .= atan y x
;;; At this point an instance of the class xyposition can be
;;; changed to be an instance of the class rhothetaposition
;;; using changeclass:
& p1 =. makeinstance 'xyposition :x 2 :y 0
& changeclass p1 'rhothetaposition
;;; The result is that the instance bound to p1 is now
;;; an instance of the class rhothetaposition.
;;; The updateinstancefordifferentclass method
;;; performed the initialization of the rho and theta
;;; slots based on the values of the x and y slots,
;;; which were maintained by the old instance.
}
where Steele's comments are left verbatim.
Using BINFIX in packages (new feature)
v0.50 of BINFIX greatly simplifies use of BINFIX in packages by recognizing symbols representing Bops as having no package membership. Thus there is no need to export Bops by BINFIX and consequently no importing of Bops by a package is needed. The only symbols exported by BINFIX are names of macros needed for controlling Bops, described next.
Controlling Bops (new feature)
The following set of forms modify BINFIX behavior by adding/removing/redefining Bops. They must be evaluated before and outside Bexprs in which the modified behavior takes place.

(binfix:defBop Bop lispop priority &rest properties)
Macro function that defines new or redefines existing
Bop
to represent lisp operationlispop
(both of these arguments are symbols,) wherepriority
defines how weakly/stronglyBop
binds its arguments (precedence,) with givenproperties
. 
(binfix:setBop Bop lispop)
Macro function that associated with an existing
Bop
LISP symbollispop
. In effect it redefines whatBop
does without changing any of its properties.Perhaps its most important role is to set what squarebrackets in Bexpr represent. For instance,
(binfix:setBop binfix::index binfix::hashget)
sets Bterms
a[k]
to represent indexing of hashtablea
by keyk
, while(binfix:setBop binfix::index2 svref)
defines that
a[[i]]
represents(svref a i)
. 
(binfix:remBops Bop1 ... Bopn)
Macro function that removes specified Bops
Bop1
...Bopn
. After this macro form is executed, symbolsBop1
...Bopn
will not be interpreted as Bops within Bexpressions. 
(binfix:keepBops Bop1 ... Bopn)
Keep only given Bops
Bop1
...Bopn
. After this macro form is executed, only symbolsBop1
...Bopn
will be interpreted as Bops within Bexpressions, except in the case of:=
,:==
and:
which also requireprogn
Bop if implicitprogn is to be used. 
(binfix:keepBops)
After evaluation of this macro form, BINFIX is restored to its initial state.
Implementation
BINFIX expression is written as a list enclosed in curly brackets {
... }
handled through LISP reader, so the usual syntax rules of LISP apply, e.g a+b
is a single symbol, while a + b
is three symbols. Lisp reader after
tokenization calls the function binfix
which does shallow transformation of
BINFIX into Sexpr representation of the expression.
BINFIX uses a simple rewrite algorithm that divides a list in two, LHS and RHS of the lowest priority infix operator found within the list, then recursively processes each one.
protoBINFIX
Bootstrapping is done beginning with protoBINFIX,
(defparameter *binfix*
'((; infix (progn))
(:== def defmacro)
(:= def defun)
(: def defmethod)
( =. infix (setq))
(.= infix (setf))
(> deflambda)
($ infix ())
(symbolmacrolet let= symbolmacrolet)
(let let= let)
(let* let= let*)
(labels flet= labels)
(=.. varbind multiplevaluebind)
(.x. unreduc .x. values)
(:. infix (cons))
( infix (or))
(&& infix (and))
(== infix (eql))
(=c= infix (char=))
(in infix (member))
( ! infix (aref))))
(defun binfix (e &optional (ops *binfix*))
(cond ((atom e) e)
((null ops) (if (cdr e) e (car e)))
(t (let* ((op (car ops))
(op.rhs (member (pop op) e)))
(if (null op.rhs)
(binfix e (cdr ops))
(let ((lhs (ldiff e op.rhs)))
(macroexpand1
`(,@op ,lhs ,(cdr op.rhs)))))))))
(defmacro infix (op lhs rhs)
`(,@op ,(binfix lhs) ,(binfix rhs)))
(setmacrocharacter #\{
(lambda (s ch) (declare (ignore ch))
(binfix (readdelimitedlist #\} s t))))
(setmacrocharacter #\} (getmacrocharacter #\) ))
which captures the basics of BINFIX.
Since v0.15, BINFIX interns a symbol consisting of a single ;
char not
followed by ;
char, while two or more consecutive ;
are interpreted
as a usual LISP comment. This behavior is limited to BINFIX
expressions only, while outside of them the standard LISP rules apply.
The next bootstrap phase defines macros def
, deflambda
, let=
,
flet=
, unreduc
and varbind
, done in proto1.lisp
,
{defmacro def (what args body)
`(,what ,@(if (atom args)
`(,args ())
`(,(car args),(cdr args)))
,(binfix body));
deflambda args body :==
`(lambda ,(if (consp args) args `(,args))
,(binfix body));
let= let lhs body &aux vars :==
loop while {cadr body == '=}
do {push `(,(car body),(caddr body)) vars;
body =. cdddr body}
finally (return (let ((let `(,let ,(nreverse vars) ,(binfix body))))
(if lhs (binfix `(,@lhs ,let)) let)));
flet= flet lhs body &aux funs :==
loop for r = {'= in body} while r
for (name . lambda) = (ldiff body r)
do {push `(,name ,lambda ,(cadr r)) funs;
body =. cddr r}
finally {return let flet = `(,flet ,(reverse funs) ,(binfix body))
if lhs (binfix `(,@lhs ,flet)) flet};
unreduc op oplisp lhs rhs :==
labels
unreduce e &optional args arg =
(cond {null e $ nreverse {binfix (nreverse arg) :. args}}
{car e == op $ unreduce (cdr e) {binfix (nreverse arg) :. args}}
{t $ unreduce (cdr e) args {car e :. arg}})
`(,oplisp ,@(unreduce rhs `(,(binfix lhs))));
varbind op lhs rhs :== `(,op ,lhs ,(car rhs) ,(binfix (cdr rhs)))}
which wraps up protoBINFIX.
Since v0.15, BINFIX interns a symbol consisting of a single ;
char not
followed by ;
char, while two or more consecutive ;
are interpreted
as starting a comment. This behavior is limited to BINFIX
expressions only, while outside of them the standard LISP rules apply.
The rest is written using protoBINFIX syntax, and consists of handling of
lambda lists and let
s, a longer list of OPs with properties, redefined
binfix
to its full capability, and, finally, several interface functions for
dealing with OPs (lsbinfix
, defbinfix
and rmbinfix
).
Priorities of operations in protoBINFIX are given only relatively, with no numerical values and thus with no two operations of the same priority.
LHS and RHS of protoBINFIX expressions refer to other protoBINFIX expressions (since v0.22.3), which in particular means that there is no implicitprogn in protoBINFIX let's and def's.
Since v0.20, symbol of a BINFIX operation has a list of properties stored into
the symbol property binfix::properties
, which includes a numerically given
priority of the OP (which also considerably speeds up parsing.) The actual
value of number representing priority is supposed to be immaterial since only
relation to other Bops priority values is relevant. Defining a new samepriority
Bop should be done via defbinfix
with :as
option.
Using defbinfix
typically changes priority values of other Bops.
Since shallow transformation into standard syntax is done by function binfix
invoked recursively by the reader, binfix
cannot be directly called for
arbitrary macro transformation of BINFIX into BINFIX when standard macro
helpers BACKTICK, COMA and COMAAT are used. The reason is that {
...}
is
invoked before them while the correct order would be after them.
Examples of successful combinations of backquoting and BINFIX are given
above.
Problems with CLISP
The latest version of clisp
I have tried is 2.49.93+ (20180218)
, which
has two problems with BINFIX:

There seems to be a bug in
setmacrocharacter
(see here.) Workaround is possible, but BINFIX still doesn't work (while other implementations tried do.) 
Test subsystem of the current version of BINFIX uses
fiveam
, which requires ASDF>=3.1, whichclisp
does not seem to support. Workaround is of course possible pending solving problem 1.
Appendix
Syntax highlighting
Provided binfix.vim
file covers vim
editor with a syntaxhighlighting
extension, which is based and depends on lisp.vim
that comes
bundled with vim
.
Here are GUI and terminal looks:
(theme: solarized
, font: Inconsolata Medium
)
(theme: herald
, font: Terminus
)
Operation properties
Property  Description  

:def
 Operation (OP) is a definition requiring LHS to has a name and lambda list.  
:defm
 OP is a definition requiring LHS to have a name followed by unparenthesized method lambda list.  
:lhslambda
 OP has lambda list as its LHS.  
:rhslbinds
 OP has letbinds at the beginning of its RHS, [symbol [keyword]* =
expr]* declaration*  
:rhsfbinds
 OP has fletbinds at the beginning of its LHS, including optional declarations.  
:rhssbinds
 OP has symbolbinds as its RHS. They are letbinds without annotations or declarations.  
:rhsebinds
 OP has exprbinds at the beginning of its RHS.  
:unreduce
 All appearances of OP at the current level should be unreduced, i.e replaced with a single call with multiple arguments.  
:leftassoc
 OP is leftassociative (OPs are rightassociative by default.)  
:prefix
 OP is prefix with RHS being its arguments, given as one or more
atoms/exprs which can be also ; separated.
 
:prefixleft
 OP is prefix with RHS being its arguments, given as one or more
atoms/exprs which can be also ; separated.
Resulting forms will be appended to the forms on the LHS.
 
:alsoprefix
 OP can be used as prefix when LHS is missing.  
:alsounary
 OP can be used as unary when LHS is missing.  
:alsopostfix
 OP can be used as postfix when RHS is missing.  
:lambda/expr
 OP takes lambdalist at LHS and an expression at RHS, followed by body.  
:syms/expr
 OP takes a list of symbols as LHS (each with an optional [keywordtype](#types) annotation,) an expression as RHS followed by optional declarations and a BINFIXexpression.  
:split
 OP splits the expr at this point.  
:rhsargs
 OP takes LHS as 1st and RHS as remaining arguments, which can
be ; separated Bexpr.
 
:quotelhs
 OP quotes LHS.  
:quoterhs
 OP quotes RHS.  
:macro
 OP is a macro.  
:progn
 OP is in a prognmonad. Currently
implemented in combination with :prefix ,
:quoterhs /:macro properties (**DEPRECIATED**,)
and with :terms .
 
:single
 OP requires to be the only OP in the current expr with its
priority. For example, reading {values a b .x. c}
reports an ambiguity error.
 
:term
 OP is a Bterm. For example, Bop binfix::index
makes{a (binfix::index i (1+ j))} become
(aref a i (1+ j)) (new feature)
 
:rhsimplicitprogn symbol
 OP splits the RHS into blocks of Bexprs separated by
symbol and ;
(new feature)

Unused symbols
BINFIX does not use symbols ~
, %
and ^
. The use of splitter ?
as a Bop
is depreciated and will be removed. The current plan is that these four
will be left for userdefined Bops.
List of all operations
Command (binfix:listBops)
prints the table of all Bops and their properties
from the weakest to the strongestbinding Bop, with parens enclosing Bop(s) of
the same priority:
BINFIX LISP Properties
==============================================================================
( <& prog1
<&.. multiplevalueprog1 )
( & progn :progn )
( def nil :binfixdefs
defclass defclass :progn :prefix :quoterhs
defstruct defstruct :progn :prefix :quoterhs
deftype deftype :progn :prefix :quoterhs
defparameter defparameter :progn :prefix :quoterhs
defvar defvar :progn :prefix :quoterhs
defconstant defconstant :progn :prefix :quoterhs
definecondition definecondition :progn :prefix :quoterhs
definesetfexpander definesetfexpander :progn :prefix :quoterhs
definesetfmethod binfix::definesetfmethod :progn :prefix :quoterhs
defsetf defsetf :progn :prefix :quoterhs
defgeneric defgeneric :progn :prefix :quoterhs
defmethod defmethod :progn :prefix :quoterhs
definemethodcombination definemethodcombination :progn :prefix :quoterhs
defun defun :progn :prefix :quoterhs
defmacro defmacro :progn :prefix :quoterhs
definecompilermacro definecompilermacro :progn :prefix :quoterhs
definesymbolmacro definesymbolmacro :progn :prefix :quoterhs
definemodifymacro definemodifymacro :progn :prefix :quoterhs
declaim declaim :progn :prefix :quoterhs
proclaim proclaim :progn :prefix :quoterhs )
( :== defmacro :def ((type . deftype)
(compilermacro
. definecompilermacro))
:= defun :def
: defmethod :defm
:type= deftype :def )
( cond cond :rhsimplicitprogn binfix::=> :prefix
case case :rhsimplicitprogn binfix::=> :prefix
ccase ccase :rhsimplicitprogn binfix::=> :prefix
ecase ecase :rhsimplicitprogn binfix::=> :prefix
typecase typecase :rhsimplicitprogn binfix::=> :prefix
ctypecase ctypecase :rhsimplicitprogn binfix::=> :prefix
etypecase etypecase :rhsimplicitprogn binfix::=> :prefix )
( let let :rhslbinds
let* let* :rhslbinds
symbolmacrolet symbolmacrolet :rhslbinds
prog* prog* :rhslbinds
prog prog :rhslbinds
withslots withslots :rhsslots
macrolet macrolet :rhsmbinds
flet flet :rhsfbinds
labels labels :rhsfbinds )
( block block :prefix
tagbody tagbody :prefix
catch catch :prefix
prog1 prog1 :prefix
prog2 prog2 :prefix
progn progn :prefix )
( ? nil :split )
( setq setq :rhssbinds
set set :rhssbinds
psetq psetq :rhssbinds )
( setf setf :rhsebinds
psetf psetf :rhsebinds )
( $ nil :split :rhsargs
.$ nil :splitleft :rhsargs )
( .@ mapc :rhsargs
..@ mapl :rhsargs
@/ reduce :rhsargs
@. mapcar :rhsargs
@.. maplist :rhsargs
@n mapcan :rhsargs
@.n mapcon :rhsargs
@~ maphash
@@ apply :rhsargs
.@. multiplevaluecall :rhsargs
@ funcall :rhsargs :leftassoc :alsopostfix )
( :> function :lhslambda )
( > lambda :lhslambda )
( =.. multiplevaluebind :syms/expr
..= destructuringbind :lambda/expr )
( values values :prefix :single
.x. values :unreduce :single )
( loop loop :prefix :quoterhs )
( =... multiplevaluesetq :quotelhs
.= setf
+= incf
= decf
=. setq
.=. set )
(  or :unreduce
or or :unreduce :alsoprefix )
( && and :unreduce
and and :unreduce :alsoprefix )
( === equalp :single
equal equal :single
== eql :single
eql eql :single
eq eq :single
~~ remhash :single
subtypeof subtypep :single )
( :. cons )
( in member )
( thcdr nthcdr )
( =s= string= :single
=c= char= :single :unreduce
= = :single :unreduce :alsoprefix
/= /= :single :unreduce :alsoprefix
< < :single :unreduce :alsoprefix
> > :single :unreduce :alsoprefix
<= <= :single :unreduce :alsoprefix
>= >= :single :unreduce :alsoprefix )
( thbit logbitp )
( coerce coerce )
( !.. nthvalue
thvalue nthvalue )
( th nth )
( .! elt
!. svref
!!. rowmajoraref )
( .!!. bit :rhsargs )
( !! aref :rhsargs )
( ~! gethash :single :rhsargs
!~~ assoc :single
~~! rassoc :single )
( .eqv. biteqv :rhsargs
.or. bitior :rhsargs
.xor. bitxor :rhsargs
.and. bitand :rhsargs
.nand. bitand :rhsargs
.nor. bitnor :rhsargs
.not. bitnot :alsounary
.orc1. bitorc1 :rhsargs
.orc2. bitorc2 :rhsargs
.andc1. bitandc1 :rhsargs
.andc2. bitandc2 :rhsargs )
( dpb dpb :rhsargs )
( ldb ldb )
( ldbtest ldbtest )
( depositfield depositfield :rhsargs )
( maskfield maskfield )
( byte byte )
( eqv. logeqv :alsounary :unreduce )
( or. logior :alsounary :unreduce )
( xor. logxor :alsounary :unreduce )
( and. logand :alsounary :unreduce )
( nand. lognand )
( nor. lognor )
( test. logtest )
( orc1. logorc1 )
( orc2. logorc2 )
( andc1. logandc1 )
( andc2. logandc2 )
( << ash )
( lcm lcm :alsounary :unreduce )
( gcd gcd :alsounary :unreduce )
( mod mod )
( rem rem )
( min min :alsoprefix :unreduce :single
max max :alsoprefix :unreduce :single )
( + + :alsounary :unreduce )
(   :alsounary :unreduce )
( / / :alsounary )
( * * :alsoprefix :unreduce )
( ** expt )
( .!. bit :rhsargs )
( ! aref :rhsargs :single
_ slotvalue :single )
( ; binfix::; )
( index aref :term
index2 binfix::hashget :term :macro )

=> nil