by José Bollo
This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-266@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
The syntax expr allows Scheme programmers to
write arithmetic expressions using a syntax similar to mathematical
notation, potentially improving the readability of Scheme programs.
Scheme's natural syntax for expressing formulae has its own quality.
First of all, it is never ambiguous, and it does not need to define
any operator priority. Conversely, syntax expr
introduces a kind of ambiguity that cannot be removed.
Scheme programmers expressing arithmetic expressions see all over their code the gap between what they learnt at school for expressing arithmetic formulae or what they use to write with some common other languages and what is written in Scheme.
This SRFI describes the syntax expr, which
fills the gap between Scheme and mathematical formulae for arithmetic.
Let's show it through examples. Here is some standard Scheme code:
; values of roots of ax²+bx+c
(let ((delta (sqrt (- (* b b) (* 4 a c)))))
(values (/ (+ (- b) delta) (* 2 a))
(/ (- (- b) delta) (* 2 a))))
; get right derivation function of g (for parameter dx)
(define (deriv g)
(lambda (x) (/ (- (g (+ x dx)) (g x)) dx)))
; get Newton iterating function of g (for parameter dx)
(define (newton-transform g)
(lambda (x) (- x (/ (g x) ((deriv g) x)))))
Using expr syntax,
the above examples are written:
(expr sqrt(b * b - 4 * a * c) as delta in
values ((- b + delta) / (2 * a)
(- b - delta) / (2 * a)))
(define (deriv g)
(lambda (x) (expr (g(x + dx) - g(x)) / dx)))
(define (newton-transform g)
(lambda (x) (expr x - g(x) / deriv(g)(x))))
The syntax expr is inspired by the eponymous
UNIX tool expr. The expression at its right is expressed using almost
standard formulae.
Writing arithmetic expressions is common when programming.
Writing logic expressions and comparison expressions is also
quite common. For this reason, the syntax expr
also handles comparisons and logic operators.
Thus instead of writing:
(if (and (< a 1) (not (= (+ b c) 9)))
...
it is possible to write:
(if (expr a < 1 and b + c != 9))
...
A previous SRFI, SRFI 105, Curly-infix-expressions, already introduced the ability to write expressions in a more usual way. The current SRFI differs from SRFI 105 on the following points:
expr.However, SRFI-105 is compatible with this SRFI when $nfx$ is defined as below:
(define-syntax $nfx$
(syntax-rules ()
((_ x ...) (expr x ...))))
According to SRFI 261, items exported by this SRFI can be imported using:
(import (srfi srfi-266))
or on systems supporting it, using:
(import (srfi expr-266))
This SRFI exports one main syntax item: expr.
It also exports expr-set-prefix,
expr-set-left-infix,
expr-set-right-infix,
expr-set-list,
expr-set-compare,
expr-set-ternary, which
serve to define or redefine operators
as described below.
The syntactic item expr should be followed by
an expression E ...:
(expr E ...)
The syntax expr transforms an
expression E ... to its equivalent Scheme expression during
syntaxic expansion.
This means that using expr has no runtime cost.
For example:
(expr a + b + c < d <= x - y - z)
will be translated to something looking like:
(let ((temporary-1 d))
(and (< (+ a b c) temporary-1)
(<= temporary-1 (- x y z))))
An expression E ... follows the grammar of EXPRESSION defined below:
EXPRESSION := FORMULA
| CALL
FORMULA := TERM
| FORMULA infix-operator TERM
TERM := VALUE
| prefix-operator TERM
VALUE := constant
| symbol
| '(' EXPRESSION ')'
| '(' '(' scheme-expression ')' ')'
CALL := FORMULA '(' ARGUMENTS ')'
| FORMULA EXPRESSION
ARGUMENTS := FORMULA
| ARGUMENTS FORMULA
An expression E ... is made of constants, variables,
operators, calls and subexpressions.
Constants are number, strings, vectors, booleans, ...
Variables are symbols related to variables bound in the context of the expression.
Operators are keywords. They are scanned as symbols, not as bindings to values. This rule is required for providing (1) an unambiguous translation of a rich set of operators, and (2) to provision that rich set without requiring any import of any library.
CAUTION: because of this rule, a potential clash may occur when the same symbol is used for an operator and a variable.
The SRFI comes with a predefined set of operators. These operators are listed below.
Operators are of one of the below types:
+)
<)
if/else)
Types of operators are described below.
Operators have a priority number. The lower the priority number is, the most prioritized the operator.
Example of priority of operators: the predefined operators
+ and * have respective priority numbers of
50 and 40. Thus:
(expr a + b * c)
becomes
(+ a (* b c))
(expr a * b + c)
becomes
(+ (* a b) c)
Calls are right-associative.
Calls are made of two parts: the called and the arguments:
Examples:
(expr a b)
becomes
(a b)
(expr a b c)
becomes
(a (b c))
(right associativity)
(expr a(b c))
becomes
(a b c)
(expr a(b) c)
becomes
((a b) c)
(expr a(b) + c)
becomes
(+ (a b) c)
(expr a((b c) d))
becomes
(a (b c) d)
(expr a(b - c))
becomes
(a (- b c))
(expr a(b (- c)))
becomes
(a (b (- c))
Subexpressions are expressions enclosed in parentheses.
Subexpressions are of three different types, depending one the context.
Subexpression are detailed below.
The syntax expr is not required to
optimize expressions. It is only required to translate the expression
(for example, (expr + - 3) should become (+ (- 3))
even if it looks like it should be -3).
Types of operators that can be defined are: prefix, left, right, list, compare, ternary.
Here is how each behaves when used:
Prefix operator for unary operation.
OPERATOR X becomes
(OPERATION X).
Infix operator for binary operation.
X OPERATOR Y becomes
(OPERATION X Y).
Left associative.
X OPERATOR Y OPERATOR Z becomes
(OPERATION (OPERATION X Y) Z).
Infix operator for binary operation.
X OPERATOR Y becomes
(OPERATION X Y).
Right associative.
X OPERATOR Y OPERATOR Z becomes
(OPERATION X (OPERATION Y Z)).
Infix operator for list operation.
X OPERATOR Y [OPERATOR Z]... becomes
(OPERATION X Y Z...).
Infix operator for compare-like operations.
X OPERATOR Y [OPERATOR Z]... becomes
(OPERATION X Y Z...).
Heterogeneous comparison.
X COMP1 [Y COMP1]... Z COMP2 A [COMP2 B]... becomes
(let ((tmp Z)) (and (COMP1 X Y... tmp) (COMP2 tmp A B...))).
Pair of operators for ternary operation.
X FIRST-OPERATOR Y SECOND-OPERATOR Z becomes
(OPERATION X Y Z).
Case of predefined ... if ... else ....
X if Y else Z becomes
(if Y X Z).
These types can be used for defining new operators.
See below for details on defining or
redefining operators for syntax expr.
The below operators are of a type that cannot be defined because they have a specific implementation:
No operator for function call operation.
X Y becomes
((expr X) (expr-for-args Y)).
Space separated arguments.
X (ARG1 ARG2 ...) becomes
((expr X) (expr ARG1) (expr ARG2) ...).
Right associative.
X Y Z becomes
(X (Y Z)).
Special assignment within expression.
X as Y ... in Z becomes
(let-values (((Y ...) X)) Z).
Expressions can have subexpressions in parentheses. They occur in three cases.
When argument of a call, spaces are interpreted as separating arguments.
Then f (a b ...) expands as
((expr f) (expr a) (expr b) ...).
Except if at the place of an argument of a call,
and except if made of only one subexpression,
a subexpression (a ...)
expands as (expr a ...).
Except if at the place of an argument of a call,
when a subexpression only contains one subexpression,
it expands to the inner subexpression, literally.
Then ((a ...))
becomes (a ...) without applying expr
to the enclosed subexpression.
The list of predefined operators for
expr is given in the below table:
| operator | type | priority | operation |
|---|---|---|---|
| @ | left | 10 | vector-ref |
| @. | left | 10 | list-ref |
| @@ | left | 10 | bytevector-u8-ref |
| @ | prefix | 10 | unbox |
| call | 10 | function call | |
| ** | left | 20 | expt |
| - | prefix | 30 | - |
| + | prefix | 30 | + |
| not | prefix | 30 | not |
| ? | prefix | 30 | boolean --> 0 or 1 |
| * | list | 40 | * |
| / | list | 40 | / |
| \ | left | 40 | quotient |
| % | left | 40 | remainder |
| + | list | 50 | + |
| - | list | 50 | - |
| < | comp | 80 | < |
| > | comp | 80 | > |
| <= | comp | 80 | <= |
| >= | comp | 80 | >= |
| = | comp | 80 | = |
| != | left | 90 | not = |
| and | list | 130 | and |
| or | list | 140 | or |
| implies | left | 150 | implies |
| if else | ternary | 160 | if |
| as in | as | 160 | let-values |
| ~ | prefix | 30 | bitwise-not |
| << | left | 60 | bitwise-arithmetic-shift-left |
| >> | left | 60 | bitwise-arithmetic-shift-right |
| & | list | 100 | bitwise-and |
| ^ | list | 110 | bitwise-xor |
| : | list | 120 | bitwise-ior |
| ~& | left | 100 | bitwise-nand |
| ~^ | left | 110 | bitwise-eqv |
| ~: | left | 120 | bitwise-nor |
| fx- | prefix | 30 | fxneg |
| fx~ | prefix | 30 | fxnot |
| fx* | left | 40 | fx* |
| fx\ | left | 40 | fxquotient |
| fx% | left | 40 | fxremainder |
| fx+ | left | 50 | fx+ |
| fx- | left | 50 | fx- |
| fx<< | left | 60 | fxarithmetic-shift-left |
| fx>> | left | 60 | fxarithmetic-shift-right |
| fx< | comp | 80 | fx<? |
| fx> | comp | 80 | fx>? |
| fx<= | comp | 80 | fx<=? |
| fx>= | comp | 80 | fx>=? |
| fx= | comp | 80 | fx=? |
| fx!= | left | 90 | not fx=? |
| fx& | list | 100 | fxand |
| fx^ | list | 110 | fxxor |
| fx: | list | 120 | fxior |
| fl- | prefix | 30 | fl- |
| fl* | left | 40 | fl* |
| fl/ | left | 40 | fl/ |
| fl\ | left | 40 | flquotient |
| fl% | left | 40 | flremainder |
| fl+ | left | 50 | fl+ |
| fl- | left | 50 | fl- |
| fl< | comp | 80 | fl<? |
| fl> | comp | 80 | fl>? |
| fl<= | comp | 80 | fl<=? |
| fl>= | comp | 80 | fl>=? |
| fl= | comp | 80 | fl=? |
| fl!= | left | 90 | not fl=? |
It is possible to define or to redefine operators. Definition occurs when the operator is not already defined. Redefinition occurs when the operator is already defined. In that case, the new definition replaces the older one.
Definition or redefinition set the priority of the operator as explained here.
Definition or redefinition are done using syntaxic helpers. These helpers are of two main types: basic or procedural.
Basic helpers are:
(expr-set-prefix operator priority operation)
Replace expressions ... operator a ...
by (operation a).
(expr-set-left-infix operator priority operation)
Replace expressions ... a operator b operator c ...
by (operation (operation a b) c).
Applies left associativity.
(expr-set-right-infix operator priority operation)
Replace expressions ... a operator b operator c ...
by (operation a (operation b c)).
Applies right associativity.
(expr-set-list operator priority operation)
Replace expressions ... a operator b operator c ...
by (operation a b c).
(expr-set-compare operator priority operation)
Replace expressions ... a operator b operator c ...
by (operation a b c).
(expr-set-ternary operator-1 operator-2 priority operation)
Replace expressions ... a operator-1 b operator-2 c ...
by (operation a b c).
Procedural helpers look like the definition below except that lambdas are not duplicated:
(expr-set-prefix operator priority (x) expression)
Replace expressions ... operator a ...
by ((lambda(x) expression) a).
(expr-set-left-infix operator priority (x y) expression)
Replace expressions ... a operator b operator c ...
by ((lambda(x y) expression) ((lambda(x y) expression) a b) c).
Applies left associativity.
(expr-set-right-infix operator priority (x y) expression)
Replace expressions ... a operator b operator c ...
by ((lambda(x y) expression) a ((lambda(x y) expression) b c)).
Applies right associativity.
(expr-set-list operator priority x expression)
Replace expressions ... a operator b operator c ...
by ((lambda x expression) a b c).
(expr-set-compare operator priority x expression)
Replace expressions ... a operator b operator c ...
by ((lambda x expression) a b c).
(expr-set-ternary operator-1 operator-2 priority (x y z) expression)
Replace expressions ... a operator-1 b operator-2 c ...
by ((lambda(x y z) expression) a b c).
Implementations of expr can be made
using standard Scheme macros, using implementation specific macros, or
using internals of implementations.
The sample implementation uses syntax-case. It works for Guile 3 and Chez Scheme.
Source for the sample implementation.Many thanks to writers of the UNIX tool expr.
© 2026 José Bollo.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.