Hi everyone,
Recent threads have reminded me I never properly announced "#lang
something", an experiment from back in 2016.
The main idea is S-expressions, but with usually-implicit parentheses
and support for prefix/infix/postfix operators. Indentation for grouping
is explicitly represented in the S-expression returned from the reader.
+ keeps a semi-structured input format: reader yields ordinary syntax
+ macros Just Work
+ you can do "if ... then ... else ...": [1]
+ you can do "... where ...": [2]
- uses indentation (though it doesn't have to; see for example [3])
- the function syntax isn't `function(arg, ...)`
[1]
https://github.com/tonyg/racket-something/blob/4a00a9a6d3f5aab4f28b03c53555b87e21d7/examples/if-then-else.rkt
[2]
https://github.com/tonyg/racket-something/blob/4a00a9a6d3f5aab4f28b03c53555b87e21d7/examples/where.rkt
[3]
https://github.com/tonyg/racket-something/blob/4a00a9a6d3f5aab4f28b03c53555b87e21d7/src/something/shell2.rkt
(More links at the bottom of this message)
In addition to the reader, `#lang something` provides a small selection
of special forms that take advantage of the new syntax, and `#lang
something/shell` adds Unix-shell-like behaviour and a few associated
utilities.
This program:
#lang something
for { x: 1 .. 10 }
def y: x + 1
printf "x ~a y ~a\n" x y
... reads as this S-expression:
(module something-module something/base
(#%rewrite-body
(for (block (x (block (1 .. 10
(block (def y (block (x + 1)))
(printf "x ~a y ~a\n" x y)
The `#%rewrite-body` macro, together with its companion
`#%rewrite-infix`, consults an operator table, extendable via the
`def-operator` macro, to rewrite infix syntax into standard prefix
S-expressions.
The `block` syntax has many different interpretations. It has a macro
binding that turns it into a Racket `match-lambda*`, and it is used as
literal syntax as input to other macro definitions.
For example, here's one possible implementation of that `for` syntax:
#lang something
provide
for
require
for-syntax something/base
prefix-in base_ racket/base
def-syntax for stx
syntax-case stx (block)
_ (block (v (block exp)) ...) (block body ...)
(syntax (base_for ((v exp) ...) body ...))
def-operator .. 10 nonassoc in-range
Notice how the `block` S-expressions are rewritten into a normal
S-expression compatible with the underlying `for` from `racket/base`.
Generally, all of these forms are equivalent
x y z x y z: x y z { a; b }
a a
b b
and they are read as
(x y z (block a b))
and are then made available to the normal macro-expansion process (which
involves a new infix-rewriting semi-phase).
Colons are optional to indicate a following suite at the end of an
indentation-sensitive line. Indentation-sensitivity is disabled inside
parentheses. If inside a parenthesised expression,
indentation-sensitivity can be reenabled with a colon at the end of a line:
a b (c d:
e
f)
= (a b (c d (block e f)))
a b (c d
e
f)
= (a b (c d e f))
Conversely, long lines may be split up and logically continued over
subsequent physical lines with a trailing `\`:
a b c \
d \
e
= (a b c d e)
Semicolons may also appear in vertically-laid-out suites; these two are
equivalent:
x y z
a
b; c
d
x y z { a; b; c; d }
Suites may begin on the same line as their colon. Any indented
subsequent lines become children of the portion after the colon, rather
than the portion before.
This example:
x y z: a b
c d
e
reads as
(x y z (block (a b (block (c d) e
Square brackets are syntactic sugar for a `#%seq` macro:
[a; b; c; d e f]→(#%seq a b c (d e f))
[ →(#%seq a (b (block c)) (d e f))
a
b
c
d e f
]
Forms starting with `block` in expression context expand into
`match-lambda*` like this:
{
pat1a pat1b
exp1a
exp1b
pat2a
exp2
}
→ (match-lambda*
[(list pat1a pat1b) exp1a exp1b]
[(list pat2a) exp2])
The `map*` function exported from `something/lang/implicit` differs from
`map` in `racket/base` in that it takes its arguments in the opposite
order, permitting maps to be written
map* [1; 2; 3; 4]
item:
item + 1
map* [1; 2; 3; 4]
item: item + 1
map* [1; 2; 3; 4]: item: item + 1
map* [1; 2; 3; 4] { item: item + 1 }
A nice consequence of all of the above is that curried functions have an
interesting appearance:
def curried x:: y:: z:
[x; y; z]
require rackunit
check-equal? (((curried 1) 2) 3) [1; 2; 3]
Anyway, thought it worth mentioning.
A few more links:
- The repository: https://github.com/tonyg/racket-something
-