Mohamed Amine Hajji / Projects
A compiler for a human-friendly language that targets Mapbox GL style expressions — hand-written lexer, parser, and static type checker, published on npm with one tiny public API: compile().
Live playground · npm · GitHub · Docs
Mapbox GL styles are driven by expressions — a JSON-array mini-language that is powerful but hard to read once it grows. Coloring roads by speed and type, for example, looks like this:
["case",
["all",
[">", ["get", "speed"], 80],
["==", ["get", "type"], "highway"]
],
"red",
"gray"
]
After working with these daily on geospatial products, I designed a small language that reads like normal code — variables, if/elif/else, match, interpolate, step, function calls — and compiles to exactly the expression Mapbox expects. The same rule becomes:
var speed = get("speed")
var type = get("type")
if speed > 80 and type == "highway" then
"red"
else
"gray"
Everything is built from scratch in TypeScript, with no parser generator: a lexer with significant newlines, a recursive-descent parser that produces a typed AST, and a compiler that emits Mapbox JSON. On top sits a small, deliberately sound static type checker. It only rejects what it can prove is wrong from syntax alone — a number used as a boolean, a string in arithmetic, out-of-order interpolation stops — and defers everything else to Mapbox, so it never blocks a valid style but catches mistakes before they reach the map, with a message like 'and' requires a boolean operand, but this is a number.
Every design decision that touches real Mapbox semantics was verified against the actual style-spec evaluator rather than trusted from the docs — which surfaced several places where the documentation and the real engine disagree. The package is published on npm with CI, a formal EBNF grammar, a language guide, and a browser playground.
inferType() powers every check (boolean operands, numeric arithmetic, match inputs), catching errors at compile time with precise, human-readable messages.let, if/elif/else → case, match with label lists, and interpolate/step ramps with compile-time ascending-stop checks.compile() — shipped as dual ESM/CJS on npm with CI, a language guide, an operator reference, and a live playground.Stack: TypeScript, Mapbox GL, Vitest, tsup, npm, GitHub Actions