THE SPROUT BOOK

The Sprout Programming Language

A practical, chapter-by-chapter guide to writing Sprout—from your first bloom to reusable gardens of packages.

ABOUT THIS BOOK

Read from top to bottom if you are new to Sprout, or use the chapter list as a reference. Every example is complete enough to paste into the playground.

CHAPTER 1

Your first program

Create a file named hello.sprout. Statements end with semicolons, and bloom sends values to standard output.

SPROUT
bloom("Hello, garden!");
TERMINAL
npm start -- hello.sprout
OUTPUT
Hello, garden!
CHAPTER 2

Installing and running Sprout

Sprout runs on Node.js. From the project root, install the TypeScript development dependencies, then pass a .sprout file to the CLI.

TERMINAL
npm install
npm start -- examples/fizzbuzz.sprout

Or pipe a program directly into the interpreter:

TERMINAL
printf 'bloom("Hello, Sprout!");
' | npm start

Sprout syntax

Every core keyword follows the garden metaphor. You plant mutable values, protect constants as seed, grow functions, branch through decisions, and bloom output. Conventional spellings remain available as compatibility aliases.

SPROUT
seed message = "Small syntax, big ideas.";

grow celebrate(value) {
  branch (value != nil) {
    bloom("Sprout: ${value}");
  }
  harvest true;
}

celebrate(message);
plantmutable bindingalias: let
seedconstant bindingalias: const
growfunctionalias: fn
harvestreturn a valuealias: return
branchconditionalalias: if
otherwisealternate branchalias: else
tendwhile loopalias: while
eachfor loopalias: for
prune / skipbreak / continuealias: break / continue
bloomdisplay outputalias: print / show

Values & variables

Use plant for values that may change and seed for values that stay fixed. Sprout supports numbers, strings, booleans, nil, lists, and objects.

SPROUT
seed language = "Sprout";
plant year = 2026;
plant ready = true;
plant colors = ["moss", "fern", "lime"];
plant author = { name: "Ada", curious: true };

year += 1;
bloom(language, year);

Data types and scope

Sprout is dynamically typed: values carry their type at runtime, and bindings do not need annotations. A nested block can read bindings from its parent scope. A plant can be reassigned; a seed cannot.

Number42, 3.14Arithmetic and comparisons
String"fern"Text and interpolation
Booleantrue, falseBranch conditions
NilnilThe absence of a value
List[1, 2, 3]Ordered mutable values
Object{ name: "ivy" }Named mutable properties
Functiongrow(x) { … }Callable closures
SPROUT
seed garden = "north";
plant count = 1;

{
  plant count = 2; // shadows the outer count
  bloom(garden, count);
}

count += 1;
bloom(count);

Functions and closures

Declare named functions with grow and send a value back with harvest. A function without an explicit harvest produces nil.

SPROUT
grow greet(name) {
  harvest "Hello, ${name}!";
}

bloom(greet("gardener"));

Anonymous functions capture their garden

An anonymous grow expression can be stored or passed to another function. It remembers bindings from the scope where it was created.

SPROUT
grow multiplier(factor) {
  harvest grow(value) {
    harvest value * factor;
  };
}

plant double = multiplier(2);
bloom(double(21));
OUTPUT
42

Control flow

Use branch/otherwise for decisions, tend for condition-based loops, and each ... in for collections. Inside a loop, prune stops growth and skip moves to the next item.

SPROUT
each (plant n in range(1, 6)) {
  branch (n % 2 == 0) {
    bloom(n, "is even");
  } otherwise {
    bloom(n, "is odd");
  }
}

Strings and comments

Strings use double quotes. Put an expression inside ${...} to interpolate it. A comment begins with // and continues to the end of the line.

SPROUT
seed plantName = "sunflower";
seed height = 180;

// Expressions are evaluated in the current scope.
bloom("The ${plantName} grew to ${height / 100}m");
bloom("line one\nline two");

Supported escapes are \n, \t, \", and \\. Unknown escapes keep the escaped character.

Lists & objects

Lists are ordered and zero-indexed. Objects group named values. Read and update both with familiar index or property syntax.

SPROUT
plant favorite = {
  name: "Monstera",
  heights: [12, 18, 25]
};

favorite.heights[0] += 3;
bloom(favorite.name, max(favorite.heights));

Collection pipelines

Higher-order built-ins make list transformations concise. Each callback receives the current value and can close over surrounding bindings.

SPROUT
seed harvests = [3, 8, 5, 12, 7];

plant ripe = filter(harvests, grow(weight) {
  harvest weight >= 7;
});

plant crates = map(ripe, grow(weight) {
  harvest weight * 2;
});

plant total = reduce(crates, grow(sum, weight) {
  harvest sum + weight;
}, 0);

bloom(crates);
bloom(total);
OUTPUT
[16, 24, 14]
54

Built-ins

Sprout includes practical helpers for output, collections, strings, and numbers.

bloom(...values)Display values on one line (print and show are aliases).
len(value)Return the length of a string, list, or object.
range(start?, end, step?)Create a list of numbers.
map(list, grow)Transform every item in a list.
filter(list, grow)Keep items that pass a test.
reduce(list, grow, seed)Fold a list into one value.
split(text, separator)Split text into a list.
join(list, separator)Join a list into text.
min(...) / max(...)Find the smallest or largest number.

Modules

Split programs with named exports and imports. Local module paths resolve relative to the importing file.

SPROUT
// math.sprout
export grow square(n) { harvest n * n; }
export plant answer = 42;

// app.sprout
import { square, answer } from "./math";
bloom(square(answer));

The Garden package manager

Sprout manages packages as a garden. A project is cultivated, dependencies are grafted into it, installed packages live in garden_modules, and unwanted packages are uprooted. Package sources are currently local directories; a network nursery is not implemented yet.

Cultivate a garden

TERMINAL
mkdir my-garden && cd my-garden
npm start -- cultivate my-garden

The command creates a garden.json manifest:

GARDEN.JSON
{
  "name": "my-garden",
  "version": "0.1.0",
  "main": "index.sprout",
  "grafts": {}
}

Graft a package

A package is another directory with its own garden.json and entry file. Graft it by local path, then inspect the garden:

TERMINAL
npm start -- graft ../pollinator
npm start -- garden

Grafting copies the package into garden_modules, adds a file: entry under grafts, and updates garden-lock.json. Commit both garden files so another gardener can reproduce the same installation.

SPROUT
import { pollinate } from "pollinator";
bloom(pollinate("tomato"));

Restore or uproot grafts

TERMINAL
# Restore every graft listed in garden.json
npm start -- graft

# Remove a graft from the manifest, lockfile, and modules bed
npm start -- uproot pollinator
NEXT STEP

Ready to grow some code?

Open the playground and use Sprout's garden vocabulary.

Open playground

Errors and debugging

Sprout reports lexical, parsing, and runtime errors with a short message. The CLI writes errors to standard error and exits with status 1, which makes failures usable in shell scripts and CI.

SPROUT
seed height = 10;
height = 20;
SPROUT ERRORcannot assign to const 'height'

Common errors

undefined variable 'name'Declare the binding before use and check its scope.
expected ';' after expressionEnd the previous statement with a semicolon.
expected N arguments, got MCall the function with exactly its declared parameter count.
index out of rangeUse an integer index between zero and len(list) - 1.
operator expects numbersCheck the operand types before arithmetic.
module not foundCheck the relative path or graft the missing package.

CLI reference

The CLI runs source files and manages local gardens. Commands are invoked through the repository's npm script.

npm start -- file.sprout

Run a source file.

npm start -- cultivate [name]

Create garden.json in the current directory.

npm start -- graft [./path]

Graft one local package, or restore every manifest graft.

npm start -- garden

List grafts in the current garden.

npm start -- uproot <name>

Remove a graft from disk, manifest, and lockfile.

npm start -- help

Show the command summary.

The older commands init, install, list, and uninstall remain supported for existing scripts.

Keywords and operators

Keywords

plantseedgrowharvestbranchotherwisetendeachinpruneskipimportexportfromtruefalsenilandor

Operators

Arithmetic+ - * / %
Comparison== != < <= > >=
Logic! and or
Assignment= += -= *= /= %=
Update++ --
Accessvalue[index] object.property

Truthiness

nil, false, 0, and an empty string are falsey. All other values—including empty lists and empty objects—are truthy. The and and or operators short-circuit and return operand values.