Skip to content

Getting Started

Louis Thibault edited this page Mar 16, 2021 · 3 revisions

First steps with Slurp

In this tutorial, we will embed a minimal (yet powerful!) Slurp interpreter into a Go program, and progressively add features. This will showcase Slurp's features and options, and introduce you to our workflow for building languages.

The easiest way to get started with Slurp is to build an interactive interpreter that reads input from the keyboard, evaluates it, and returns the result. This construct is known as a Read-Evaluate-Print Loop, or REPL, and Slurp provides one in its repl subpackage.

Using Slurp's built-in REPL is a simple 3-step process:

  1. Instantiate an Interpreter.
  2. Pass the interpreter to the REPL
  3. Start the REPL's main loop.

Compile and run the following program:

package main

import (
	"context"
	"log"

	"github.com/spy16/slurp"
	"github.com/spy16/slurp/repl"
)

func main() {
    // instantiate a new interpreter
    interp := slurp.New()

    // instantiate a new REPL
    r := repl.New(interp)

    // run the REPL's main loop until the context expires, or
    // a fatal error is encountered.
    if err := r.Loop(context.Background()); err != nil {
        log.Fatal(err)
    }
}

At first, it might look like nothing is happening, but try typing in some lisp expressions. For example, try (def x 42) followed by x. You'll see that you can define variables. Many other lisp expressions will also work.

The REPL will run until it encounters an error. If the error is Go's io.EOF, it interprets this as meaning there is no more data, and terminates gracefully. To see this in action, try using ctrl+D to exit the REPL.

The REPL may also terminate if the context passed to REPL.Loop expires. This provides a useful mechanism for running interpreters in the background using goroutines.

Our interpreter is already powerful and feature-packed, but it's not very user-friendly. Let's configure a prompt, which will introduce us to a recurring pattern throughout the Slurp codebase: options.

Configuring a Prompt

Right now, the REPL's output is a bit confusing. We want it to look more like this:

>>> (def x 42)
x
>>> x
42
>>> (def y ;; multiline prompt
  |   1024)
y

Modify the program to pass the repl.WithPrompts option to repl.New:

// ... snip ...

func main() {
	interp := slurp.New()
	r := repl.New(interp, repl.WithPrompts(">>>", "  |"))

	if err := r.Loop(context.Background()); err != nil {
		log.Fatal(err)
	}
}

Now recompile and run the program. 🎉 And voila!

Most REPLs print a banner when they are first run. The banner contains useful information for users, including hints and a URL to documentation. Let's do that now.

Configuring a Banner

Configuring a banner is as easy as configuring a prompt: just pass the relevant option to repl.New. In this case, the option is called WithBanner.

// ... snip ...

const banner = `Welcome to Slurp!

GoDoc:     https://pkg.go.dev/github.com/spy16/slurp
Tutorial:  https://github.com/spy16/slurp/wiki/Getting-Started
`

func main() {
	interp := slurp.New()
	r := repl.New(interp,
		repl.WithPrompts(">>>", "  |"),
		repl.WithBanner(banner))

	if err := r.Loop(context.Background()); err != nil {
		log.Fatal(err)
	}
}

When you recompile and run the program, you'll be greeted with a banner.

Note that most Slurp constructors accept option types. Options can be passed in any order, and are the main gateway for configuration. You may want to have a look at the interpreter options.

Next Steps

This concludes the introductory tutorial. From here, there are several things you might want to do:

Clone this wiki locally