#lang something // infix, optionally indentation-sensitive experimental Racket syntax

134 views
Skip to first unread message

Tony Garnock-Jones

unread,
Jul 21, 2019, 10:57:56 AM7/21/19
to Racket Users
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/4a00a9a6d37777f5aab4f28b03c53555b87e21d7/examples/if-then-else.rkt
[2]
https://github.com/tonyg/racket-something/blob/4a00a9a6d37777f5aab4f28b03c53555b87e21d7/examples/where.rkt
[3]
https://github.com/tonyg/racket-something/blob/4a00a9a6d37777f5aab4f28b03c53555b87e21d7/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

- Implementation of the shell support:
https://github.com/tonyg/racket-something/blob/4a00a9a6d37777f5aab4f28b03c53555b87e21d7/src/something/shell.rkt
- An example "shell script":
https://github.com/tonyg/racket-something/blob/4a00a9a6d37777f5aab4f28b03c53555b87e21d7/examples/sh.rkt
- Another small example "shell script", which I used while working on
the module today:

#lang something/shell

def in-hashlang? re :: source-filename
zero? (cat source-filename |
grep -Eq (format "^#lang.*(~a).*" re))

(find "." -iname "*.rkt"
| port->lines
|> filter (in-hashlang? "something")
|> ag -F "parameterize")

Cheers,
Tony

Tony Garnock-Jones

unread,
Jul 21, 2019, 12:09:43 PM7/21/19
to Racket Users
On Sunday, July 21, 2019 at 3:57:56 PM UTC+1, Tony Garnock-Jones wrote:
Recent threads have reminded me I never properly announced "#lang
something", an experiment from back in 2016.

I forgot to mention I also made a recording of #lang something/shell in action back in 2016: https://asciinema.org/a/83450

Cheers,
  Tony

Neil Van Dyke

unread,
Jul 22, 2019, 1:39:53 PM7/22/19
to Tony Garnock-Jones, Racket Users
The shell demo is great.  I can't look closely at `#lang something`
quite yet, and I hope someone will include it in a list of the most
interesting prior work for everyone interested in new syntax to look at.

Stephen De Gabrielle

unread,
Jul 22, 2019, 3:29:08 PM7/22/19
to Neil Van Dyke, Racket Users, Tony Garnock-Jones
Done:

Kr

S.


--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/dfc442cb-a7e3-9d86-2016-f72a51b58d18%40neilvandyke.org.
--
----
Reply all
Reply to author
Forward
0 new messages