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