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.
Because of missing implementation, attributable to langage restrictions, the current version does not allow adding new operator or redefining an existing operator.
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 inside (expr ...) 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, introduced the ability to write expressions in a more usual way. This 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 ...))))
The implementations must export the following library:
(srfi srfi-266)
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 the syntax keyword: expr.
The syntactic keyword expr should be followed by
an expression E ...:
(expr E ...)
The syntax expr transforms an
expression E ... to its equivalent Scheme expression during
syntactic 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 := TERM
| EXPRESSION infix-operator TERM
TERM := VALUE
| CALL
| prefix-operator TERM
VALUE := constant
| TARGET
TARGET := symbol
| SUBEXPRESSION
CALL := TARGET '(' ')'
| TARGET ARGUMENTS
ARGUMENTS := SUBEXPRESSION
| ARGUMENTS SUBEXPRESSION
SUBEXPRESSION := '(' EXPRESSION ')'
| '(' '(' scheme-expression ')' ')'
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 identifier bound to one of the
operators defined by
expr.
Each operator is associated to an operation which is a procedure or a syntax.
The behaviour of syntax expr is to translate
usage of operators to calls to operations. This is done using rules
of replacement involving the type of the operator and its priority.
The SRFI comes with a set of operators. These operators are listed below.
Operators are of the below types:
+)
<)
if/else)
Operators have a priority number. The lower the priority number is, the more prioritized is the operator.
Example of priority of operators: the operators
+ and * have priority numbers of
50 and 40 respectively. Thus:
(expr a + b * c)
becomes
(+ a (* b c))
(expr a * b + c)
becomes
(+ (* a b) c)
Calls are made of two parts: the callee and the argument(s):
When a callee has no argument, it must be followed by the empty list.
When a callee has N argument (with N ≥ 1), it must be followed by N subexpression(s).
Examples:
(expr a())
becomes
(a)
(expr a(b))
becomes
(a b)
(expr a(b)(c))
becomes
(a b c)
Subexpressions are list data (i.e. lexically, they are enclosed in parentheses).
Subexpressions are of two different kinds 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).
The types of operators are prefix, left, right, list, compare, ternary, call, as.
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 ... if ... else ....
X if Y else Z becomes
(if Y X Z).
Virtual operator for function call operation.
X() becomes
(X).
X(Y1 ...)(Y2 ...) ... becomes
(X (expr Y1 ...) (expr Y2 ...) ...).
Special assignment within expression.
X as Y ... in Z becomes
(let-values (((Y ...) X)) Z).
Expressions can have subexpressions, wich are list data (i.e. lexically, they are enclosed in parentheses).
Subexpression also appear as argument in calls.
There is two kinds of subexpressions:
Except if made of only one list,
a subexpression (a ...)
expands as (expr a ...).
When a subexpression only contains one list, it expands to that inner list literally, without parsing.
In other words, ((a ...))
becomes (a ...) without applying expr
to the enclosed list.
The list of operators for
expr is given in the table below:
| 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=? |
Previous draft versions of this SRFI were using symbols for operators, not the binding of identifiers. This has been rejected because it was breaking solid foundation. The implication is that I was not more able to provide an implementation allowing to add operators or to redefine existing operators.
The abitlity to add operators to expr
was requested in the discussion, and I'm sad to give up on it. So I hope
that a later proposal will extend that SRFI with this feature,
something like (srfi srfi-266 extend) perhaps.
It appears that identifier-property can allow
to implement that SRFI and solve the extension issue.
Alas, during my implementation trials, it can't be done
using Chez Scheme because symbols for syntax can't be used
as identifier neither as target nor as property.
Implementations of expr can be made
using standard Scheme macros, using implementation specific macros, or
using the internals of implementations.
The sample implementation is for guile and uses syntax-case.
Source for the sample implementation.Many thanks to writers of the UNIX tool expr.
Thank you to contributors of the discussion in the mailing list, they made me change everything!
© 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.