03 Mar 2017

Languages as Dotfiles

posted by Leif Andersen and Ben Greenman

Tired of writing (require (for-syntax syntax/parse)) at the top of your Racket programs? This post shows how to make a #lang to customize your default programming environment.

Let’s build a language #lang scratch that:

We’ll follow a three-step recipe:

  1. build an empty scratch library,

  2. load the libraries, and

  3. change the reader.

At the end, we’ll see how to make scratch your default language in DrRacket.

Getting Started

First we need to make a scratch/ directory with info.rkt and main.rkt files:

$ mkdir scratch; cd scratch
$ touch info.rkt
$ touch main.rkt

Inside the info.rkt file, write:

#lang info
(define name "scratch")
(define deps '("base"))
(define version "0.0")

Inside the main.rkt file, write:

#lang racket/base

Now from inside the scratch/ directory, install the package:

$ raco pkg install

You are now the proud parent of a new Racket package.

raco-pkg-new is a shortcut for starting a new package.

For more information on the info.rkt file format, see the raco documentation.

Combining Libraries

Any program can now (require scratch) to import all bindings provided by the main.rkt file. Our next step is to reprovide bindings from other libraries in main.rkt.

Since we want to use scratch as a language, we also need to specify how to read a scratch program. The syntax/module-reader language provides a shorthand for doing so.

Here is the updated main.rkt file:

#lang racket/base

(require racket/format racket/list
         (for-syntax racket/base syntax/parse))

(provide (all-from-out racket/base racket/format racket/list)
         (for-syntax (all-from-out racket/base syntax/parse)))

(module* reader syntax/module-reader
  scratch)

The provide form declares the exports of the scratch module. In other words, if another module contains the form (require scratch) then that module will import bindings from racket/base, racket/format, racket/list, and syntax/parse.

The reader submodule is written in the syntax/module-reader language. This submodule imports all bindings from its enclosing module (scratch, or to be slightly more precise “the toplevel module in the file scratch/main.rkt”) and defines a language that provides those bindings and uses the reader from racket/base.

In short, this code does what we want.

#lang scratch

(define-syntax (did-it-work? stx)
  (syntax-parse stx
    [_ #'(first '(yes it did))]))

(did-it-work?)

Yes it does.

Annoyed that the require and provide forms are so similar? There’s a library for that.

Changing the Reader

Next, we want to enable the @-expression reader. This involves rexporting the scribble read and read-syntax functions in the reader submodule in main.rkt:

#lang racket/base

(require racket/list
         (for-syntax racket/base syntax/parse))

(provide (all-from-out racket/list racket/base)
         (for-syntax (all-from-out racket/base syntax/parse)))

(module* reader syntax/module-reader
  scratch
  #:read s:read
  #:read-syntax s:read-syntax
  (require (prefix-in s: scribble/reader)))

To test that it works, let’s embed some C syntax in our Racket program:

#lang scratch

(define-syntax (did-it-work? stx)
  (syntax-parse stx
    [_ #'(first '(yes it did))]))

(did-it-work?)

@~a{
 int main() {
   return 0;
 }}

At this point, running $ raco setup --check-pkg-deps scratch will report an undeclared dependency on at-exp-lib. Make sure to add at-exp-lib to the deps list in your info.rkt file, or run $ raco setup --fix-pkg-deps scratch

Using prefix-in is not necessary; it just clarifies where read and read-syntax come from.

If you think inline C strings are interesting, you should definitely watch Jay McCarthy’s RacketCon 2016 talk on remix.

DrRacket’s Automatic #lang Line

To make scratch the default language for new files in DrRacket:

  1. Click “Language” in the menu bar.

  2. Click “Choose Language” in the drop-down menu.

  3. Click the radio button for “The Racket Language”, then click the “Show Details” button at the bottom of the window.

  4. Type #lang scratch in the text box labeled “Automatic #lang line”.

Click “Ok”, and that’s the end. Enjoy.

The End

You can and should engineer the #lang line of your Racket programs to remove unnecessary boilerplate and/or enforce a project-specific development environment.

Notes:

  • Feel free to pubish your custom language on the Racket package server. (Make sure to run $ raco setup --check-pkg-deps scratch beforehand.)

  • Our personal “dotfiles” are racket-scratch and agile.

  • The title “Languages as Dotfiles” is a reference to Languages as Libraries

Made with Frog, a static-blog generator written in Racket.
Source code for this blog.