Viscosity
Review
Part III · A tree-walking interpreter · Day 7

26. Meet Drip

Before you build a language, take it for a drive. Drip is small enough to implement in a few weeks, and it still has the closures and classes that make real engines interesting.

About 17 minutes, including the exercises and quiz
By the end you will be able to
  • Read and write small Drip programs with variables, loops, functions, closures and classes.
  • Explain what dynamic typing means, and predict when a Drip type error appears.
  • Say what a language specification is for, and how Drip's test programs act as one.

A language you can hold in your head

JavaScript is a big language, with many years of features piled on top of each other. Starting there is a poor way to learn how a language works.

So for Part III you’ll build a smaller one. It’s called Drip. Drip is a dialect of Lox, the language Robert Nystrom designed for his book Crafting Interpreters[1]. It is Lox with two words respelled: fn instead of fun and let instead of var, so it looks more like the JavaScript you already know. Every Drip test program in this book is adapted from Lox’s MIT-licensed test suite.

Intuition

Drip is like a model kit of a real car. It is much smaller, and some comforts are missing. But the engine, the wheels and the steering are all there, and they work the way the real ones do. When you have built the model, the real car stops being a mystery.

Every example in this chapter runs on the interpreter you are about to write. Edit them freely.

Values and variables

Drip has numbers, strings, true and false, and nil, which means “no value”, like JavaScript’s null. Variables are made with let, and print is a statement, not a function, so it needs no parentheses:

let cups = 3;
print cups + 1;  // 4
Try itValues and variables
print 7 / 2;
print "drip" + "drop";
print 3 < 4 and 4 < 5;
print !true;
print nil;

let cups = 3;
cups = cups + 1;
print cups;
print 0.1 + 0.2;

// print "a" + 1;

A few things will feel familiar. Every number is a 64-bit float, just like in JavaScript, so 0.1 + 0.2 has the same rounding you met in Part I. Strings join with +. Comments start with //. Logic uses the words and and or instead of && and ||.

Deciding and repeating

if, while and for look the way you’d expect. One rule is different from JavaScript: only nil and false count as false. Everything else, including 0 and the empty string "", counts as true.

Try itif, while and for
let n = 1;
while (n < 50) {
  n = n * 2;
}
print n;

for (let i = 0; i < 3; i = i + 1) {
  print i;
}

if (0) print "0 is true";
else print "0 is false";

Functions and closures

Functions are declared with fn. They are values, so a function can return another function, and the inner one remembers the variables around it. That is a closure, exactly as in chapter 6:

Try itFunctions and a closure
fn square(x) {
  return x * x;
}
print square(4);

fn makeCounter() {
  let n = 0;
  fn count() {
    n = n + 1;
    return n;
  }
  return count;
}

let c = makeCounter();
print c();
print c();
print makeCounter;

Each call to makeCounter makes a new n, and the count it returns keeps that n alive. Making that work is the job of chapter 41.

Classes

Drip has classes with methods, this and a constructor. The constructor is the method called init. You make an instance by calling the class like a function: there is no new. A class inherits from another with <, and super calls the parent’s version of a method:

Try itClasses and inheritance
class Cup {
  init(ml) {
    this.ml = ml;
  }
  pour(x) {
    this.ml = this.ml - x;
    return this.ml;
  }
}

class Mug < Cup {
  pour(x) {
    print "careful, hot";
    return super.pour(x);
  }
}

let m = Mug(300);
print m.pour(50);
print m;

Dynamic typing

TypeScript, from chapter 13, checks types before your program runs. Drip doesn’t. A Drip variable can hold any kind of value, and it can hold a number now and a string later. Step through what happens when a + meets the wrong kind of value:

Step throughTypes travel with values
let a = 1;
let b = 2;
print a + b;
b = "2";
print a + b;
Variables a and b point at values that carry a type tag. The + operator reads both tags before it does anything.VARIABLES AND THEIR VALUESanumber1boutput:
let a = 1 makes a variable and puts the value 1 in it. The variable has no type of its own. The value does: it carries a tag that says “number”.
1/5
Definition

Dynamic typing means types belong to values, not variables, and they are checked while the program runs, at the moment an operation needs them. In Drip, + accepts two numbers or two strings. Anything else stops the program with a runtime error that names the line.

Dynamic doesn’t mean “anything goes”. JavaScript is dynamically typed too, but its + quietly converts: "a" + 1 is "a1". Drip refuses and says so. Both are legitimate designs. To try it, remove the // in front of the last line of the first example above.

Who decides what a program means?

Here’s a question with no obvious answer: what should print 7 / 2; show? 3? 3.5? An error? Each is a reasonable choice, and different languages make different ones. Drip prints 3.5. Something has to write that decision down, so that every implementation of the language makes the same choice.

Definition

A language specification (a spec) is a precise description of what every program in a language means: which programs are valid, and what each one does or which error it reports. An implementation is correct when it does what the spec says.

JavaScript’s spec is a document called ECMA-262, maintained by a committee called TC39[2]. Drip has no standards committee. Its spec is the description of Lox in Crafting Interpreters, with the two respellings, made checkable by 265 test programs in companion/corpus. Each one writes down what it should do in comments. Here are four lines from one of them, if/truth.drip:

// Everything else is true.
if (true) print true; // expect: true
if (0) print 0; // expect: 0
if ("") print "empty"; // expect: empty

Other tests expect an error instead, written as // expect runtime error: followed by the exact message. Run a test, and if the output differs from its comments in any way, the implementation has a bug.

Under the hood

You’ll build Drip twice: a tree-walking interpreter in Part III and a bytecode VM in Part IV. The companion code runs the same 265 programs against both, so the two must agree with the spec and with each other. That’s differential testing from chapter 15, at the scale of a whole language. JavaScript engines do the same with Test262, the official conformance suite for ECMA-262[3].

Your turn

Your first Drip programs. The tests check the exact output, line by line.

ExerciseAdd up 1 to n, in Drip

Write a Drip function sumTo(n) that returns 1 + 2 + … + n. The program then prints sumTo(10) and sumTo(100), so the expected output is 55 and then 5050.

fn sumTo(n) {
  // your code here
}

print sumTo(10);
print sumTo(100);
Extra practiceA counter class

Write a Drip class Counter. Its init() sets a field n to 0. Its method inc() adds 1 to n and returns the new value. Each instance has its own count, so the program below should print 1, 2, then 1.

class Counter {
  // your code here
}

let a = Counter();
print a.inc();
print a.inc();
let b = Counter();
print b.inc();

What you learned

  • Drip is a small dialect of Lox, with fn and let. It has numbers, strings, booleans, nil, functions, closures and classes.
  • Only nil and false are false in Drip. print is a statement. Classes use init, this, < and super.
  • Dynamic typing: values carry their types, and Drip checks them as each operation runs. A wrong type is a runtime error, not a silent conversion.
  • A language spec says precisely what programs mean. Drip’s is made concrete by 265 test programs that both of its implementations must pass.

Next: the first stage of the pipeline. What exactly is a “word” of Drip?

Check your understanding

0/6
  1. 1.

    What does print 7 / 2; show in Drip?

  2. 2.

    What does this Drip program print?

    if ("") print "yes";
    else print "no";
  3. 3.

    What happens when this Drip program runs?

    print "start";
    print "a" * 2;
  4. 4.

    Which of these is a valid Drip function declaration?

  5. 5.

    Drip’s tree-walker and its VM print different things for the same program. How do you decide which one is wrong?

  6. 6.

    What does this print?

    fn make() {
      let n = 0;
      fn inc() {
        n = n + 1;
        return n;
      }
      return inc;
    }
    let a = make();
    let b = make();
    print a();
    print a();
    print b();

References

  1. [1] Robert Nystrom (2021). Crafting Interpreters. Free web edition at craftinginterpreters.com (© 2015–2021); source, text and test suite at github.com/munificent/craftinginterpreters. github.com/munificent/craftinginterpreters (checked 2026-10-31)
  2. [2] Ecma International, Technical Committee 39 (TC39). ECMAScript® Language Specification (ECMA-262), current draft. Ecma International; living draft at tc39.es/ecma262, source at github.com/tc39/ecma262. tc39.es/ecma262/ (checked 2026-10-31)
  3. [3] Ecma TC39 and Test262 contributors. Test262: ECMAScript Test Suite. github.com/tc39/test262. github.com/tc39/test262 (checked 2026-10-31)