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

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/f45aeec0-e1ee-5109-5ce6-255c2d05f87f%40leastfixedpoint.com.

Reply via email to