Skip to content

Indent-Stack Parser

Used by the TypeScript (packages/ccl-ts/src/ccl.ts) and Gleam (src/ccl/parser.gleam) implementations. Similar in spirit to how Python, YAML, and Pug parse indentation.

Walk the input line by line, tracking a baseline indent. For each line, if indent > baseline it is a continuation of the current entry’s value; otherwise it starts a new entry. Emit a flat list of Entry {key, value: string}. Build the tree in a second pass that recursively re-parses any value containing = until the fixed point.

def parse_ccl(text):
entries = parse_entries(text) # split on '='
hierarchy = build_hierarchy(entries) # group by indentation
return recursively_parse(hierarchy) # fixed point
def recursively_parse(entries):
result = {}
for entry in entries:
value = entry.value
if contains_ccl_syntax(value): # Has '=' character
# Recursively parse the value
parsed = parse_ccl(value)
result[entry.key] = parsed
else:
# Fixed point: plain string
result[entry.key] = value
return result

On the input from Complete Example (top-level baseline = 0, followed by a recursive call with baseline = 2 for each non-empty nested value):

LineIndentBaselineDecisionFlat entry list after this line
database =00indent ≤ baseline → open entry[{database, ""}]
host = ...20indent > baseline → continuation[{database, "host = localhost"}]
port = 543220indent > baseline → continuation[{database, "host = localhost\nport = 5432"}]
(blank)ignoredunchanged
users =00indent ≤ baseline → close + open[{database, …}, {users, ""}]
= alice20indent > baseline → continuation[{database, …}, {users, "= alice"}]
= bob20indent > baseline → continuation[{database, …}, {users, "= alice\n= bob"}]

build_hierarchy then recurses into each value that contains =, re-running the same tokenizer with the nested baseline (here, 2), until the leaves (localhost, 5432, alice, bob) contain no = and the fixed point is reached.

O(N). Each byte is visited once during tokenization and once more during recursive value re-parse, so total work is O(N). Tokenization is streaming-friendly. Natural fit for imperative languages (TypeScript) and for functional recursive state machines (Gleam).

Good fit when you want predictable performance on large inputs, a clean separation of tokenization from tree-building, or straightforward integration with typed AST libraries.

See also: Pacman Parser for the alternative strategy, and Rules every parser must implement for the strategy-agnostic contract both parsers satisfy.