266: The expr syntax

by José Bollo

Status

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.

Abstract

The syntax expr allows Scheme programmers to write arithmetic expressions using a syntax similar to mathematical notation, potentially improving the readability of Scheme programs.

Issues

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.

Table of contents

Rationale

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:

However, SRFI-105 is compatible with this SRFI when $nfx$ is defined as below:


   (define-syntax $nfx$
      (syntax-rules ()
         ((_ x ...) (expr x ...))))

Specification

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 expr syntax

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.

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).

Operator types

Types of operators that can be defined are: prefix, left, right, list, compare, ternary.

Here is how each behaves when used:

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:

Parentheses in expressions

Expressions can have subexpressions in parentheses. They occur in three cases.

arguments

When argument of a call, spaces are interpreted as separating arguments.

Then f (a b ...) expands as ((expr f) (expr a) (expr b) ...).

subexpressions

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 ...).

escapes

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.

Predefined operators

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=?

Defining or redefining operators

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:

Procedural helpers look like the definition below except that lambdas are not duplicated:

Implementation

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.

Acknowledgements

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.


Editor: Arthur A. Gleckler