R-Expressions

Rex - R-Expressions

Rex is an alternative to Lisp's S-Expressions that provides a much more flexible syntax while still having a simple, uniform model.

S-Expressions have a structure that looks something like this:

name := [a-zA-Z0-9_$!#%&*+,./:<=>?@\\^`|~]+-]+

exp :=
   | name
   | ()
   | (exp)
   | (exp exp...)

For Example:

((+ 3) (+ 3 4))

(print (* 3 4))

Where R-Expression have a structure that looks something like:

rune := [$!#%&*+,./:<=>?@\\^`|~-]+
name := [a-zA-Z0-9_]+

exp :=
    | name
    | (rune)
    | (rune exp)
    | (rune exp exp...)
    | exp exp

Notice that every (nested expression) must start with a rune, and expressions may be juxtaposed:

(+ 3)(+ 4 5)

print(* 4 5)

The advantage is that this structure can be unambiguously encoded to text in a large variety of different ways. There are five different layouts, and they can be mixed freely.

  • Nested Layout:

    (= x 3)(| print)(| concat msg (| show (* x x)))
  • Open Layout (indentation sensitive):

    = x 3
    | print
    | concat msg
        | show
            * x x
    
    = x 3
    | print
    | concat msg | show * x x
  • Infix Layout:

    (x = 3)(| print)(concat | msg | (show | (x * x)))
  • Closed Layouts: (nested and |prefix)

    (x=3)(print|(concat|msg)(show|(x*x)))
  • Or any combination:

    = x 3
    | print
    | concat msg (| show x*x)

There is also some syntactic sugar.

  • (no-rune nested expressions) default to | rune.

    = x 3
    | print
    (concat msg (show x*x))
  • You can write multiple runes within a () form (which work the same was as open-form:

    (x=3)(|print)(| concat msg | show x*x)

All of these different forms parse into the exact same R-expression structure.

This gives a much more expressive syntax, but still gives you all of the advantages of s-expressions:

  • Very simple parser and printer.

  • Generic parser and pretty-printer that can be shared between languages.

  • Enables traditional Lisp meta-programming (defmacro + syntax-rules).

An Example

In R-Expressions:

= (tabToPairs tab)
@ ks | listFromRow tabKeys-tab
@ vs | listFromRow tab
| listToRow (listZip ks vs)

In S-Expressions:

(define (tab-to-pairs tab)
 (let ((ks (list-from-row (tab-keys tab)))
       (vs (list-from-row tab)))
  (list-to-row (list-zip ks vs))))

The Rex Grammar

namechar = [a-zA-Z_]
runechar = [$!#%&*+,-./:<=>?@\\^`|~]

/"""(.*)/     -> P # Page
/'''(.*)/     -> P # Page
/;(.*)/       -> C # Comment
/(namechar+)/ -> N # Name
/(runechar+)/ -> R # Rune
/( +)/        -> S # Whitespace
/'([^']*)'/   -> T # Text
/"([^"]*)"/   -> T # Text

leaf = P | T | N
shut = (nest | leaf)+
shin = shut (R shut)*
form = R shin | shin
frag = R shin | R | shin

# In infix mode, always occurs after `form`.
plix = ')'
     | R S form plix
     | S ')'
     | S R S form plix
     | S form plix

# In prefix mode, always occurs after `R` or `form`.
pree = ')'
     | S ')'
     | S R pree
     | S form pree

# Body of parenthesis, decides if infix or prefix
prest = ')'
      | form plix
      | R pree

# Body of [bracket expression]
broke = ']'
      | S ']'
      | S frag broke

# Body of {curly expression}
carl = '}'
     | S '}'
     | S frag carl

# [Any] (nested) {expression}
nest = '(' prest
     | '(' S prest
     | '[' broke
     | '[' frag broke
     | '{' carl
     | '{' frag carl

# Open space (can include line-comment)
open = C
     | S C
     | S

loan = EOF
     | open EOF
     | open frag loan

lean = EOF
     | frag loan

line = open lean
     | lean

Last updated