Chapter 8
The Error System
Turning crashes into teaching moments
Most languages, when something goes wrong, hand a beginner a stack trace and a cryptic message. Bloom takes the opposite approach: every error tries to guess what you meant and explain the fix in plain language. All of this machinery lives in src/lang/errors.ts.
An error in Bloom is more than a message. It is a small structured object:
export interface ErrorSuggestion {
message: string; // What went wrong
suggestion: string | null; // "Did you mean ...?"
hint: string | null; // A plain-language nudge
documentation: string | null; // Usage docs for a built-in
}
The three optional fields are what make Bloom errors feel friendly. formatErrorWithSuggestion() stacks them into the final text the playground shows.
Typo Detection with Levenshtein Distance
When you call a function that doesn't exist, Bloom doesn't just say "undefined." It measures how close your typo is to every name it knows and offers the nearest match. The measure is the classic Levenshtein edit distance — the number of single-character insertions, deletions, or substitutions needed to turn one word into another.
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1, // substitution
matrix[i][j - 1] + 1, // insertion
matrix[i - 1][j] + 1 // deletion
);
findClosestMatch() runs that distance against a candidate list and accepts a match only if the distance is within a small threshold (2 for undefined variables), so unrelated names never produce nonsense suggestions:
circle → "Did you mean 'circle'?" plus its usage docsbackground → suggested, with background(color) documentationThe candidate list comes from getAllKnownIdentifiers(), which unions every built-in function (BUILTIN_FUNCTIONS), built-in variable (BUILTIN_VARIABLES — mouseX, frame, width …), and named color (NAMED_COLORS). At runtime the interpreter also passes in the program's own declared variables, so suggestions can point at names you defined.
Cross-Language "False Friends"
Many beginners arrive from Python or JavaScript and reach for keywords that don't exist in Bloom. createUndefinedVariableError() special-cases the most common ones before it even tries fuzzy matching:
function / deffn insteadvar / constlet insteadelifelse ifnull / None / undefinednilTrue / Falsetrue / falseThere is also a dedicated path for the two functions every sketch needs. If setup or draw is missing, the error includes a ready-to-paste skeleton — for example fn setup() { size(400, 400) }.
Arity and Type Errors
createArityError() fires when a built-in gets the wrong number of arguments. Because BUILTIN_FUNCTIONS stores each function's parameter list, the error can show the exact signature you should have used:
circle expects 3 argument(s) but got 2
Check the arguments: circle(x, y, radius)
You might be missing some arguments
Usage: circle(x, y, radius) - Draw a circle
createTypeError() translates raw type names into beginner phrasing — number becomes "a number (like 42 or 3.14)", array becomes "a list (like [1, 2, 3])" — and tailors the hint to the operation. Adding text to a number, indexing with a decimal, and iterating over a non-array each get their own targeted advice.
Catching Mistakes Before Running
Some mistakes are valid syntax but almost certainly wrong. checkCommonMistakes() scans the raw source line-by-line against a table of regular-expression patterns (COMMON_MISTAKES) and surfaces warnings without stopping the program. It strips line comments first so a // note never trips a pattern.
and instead of && for logical AND== for equals (not ===)print("hi")A pattern can carry a suppressIf regex so a per-line heuristic can use whole-program context — for instance, "missing size()" stays quiet if a size( call appears anywhere in the file. These warnings are wired into both backends in index.ts, so you get the same nudges whether your sketch runs on the VM or the interpreter.
! into a "use not" message and explains .5 vs 0.5) and the parser (a bare = inside an if condition becomes "did you mean ==?"). errors.ts handles the runtime and whole-program layer.
Where It's Used
- The interpreter calls
createUndefinedVariableError(),createArityError(), andcreateTypeError()as it evaluates. createRuntime()inindex.tsrunscheckCommonMistakes()on start and forwards each hint through theonWarningcallback.- The language server reuses the same candidate lists for autocomplete and hover docs.
In the Source Code
Everything in this page lives in src/lang/errors.ts. Key exports:
levenshteinDistance()/findClosestMatch()— typo detectioncreateUndefinedVariableError()— name suggestions and cross-language false friendscreateArityError()/createTypeError()— argument and type problemscheckCommonMistakes()— pre-run pattern warningsformatErrorWithSuggestion()— assembles the final message