Implementing CCL
Core Requirements
Section titled “Core Requirements”Every CCL implementation needs two operations:
- Parse - Convert text to flat key-value entries
- Build Hierarchy - Convert entries to nested structure via recursive parsing
build_hierarchy internally calls parse_indented, which strips common leading whitespace from a multiline value before recursively parsing it. This is a required internal function — it’s not typically exposed as a public API.
parse vs parse_indented
Section titled “parse vs parse_indented”The two functions differ in how they pick the indentation baseline N:
| Function | Use Case | Baseline N |
|---|---|---|
parse | Top-level parsing | N = 0 (any indented line is a continuation) |
parse_indented | Nested value parsing | N = indentation of the first content line |
With the toplevel_indent_strip behavior, you need context detection:
function parse(text): if text is empty or text[0] != '\n': baseline = 0 // Top-level: always 0 else: baseline = find_first_line_indent(text) // Nested: dynamic return parse_with_baseline(text, baseline)Worked example
Section titled “Worked example”Input:
server = host = localhost port = 8080Step 1 — Top-level parse (N = 0):
"server =" indent 0 → new entry" host = localhost" indent 2 > 0 → continuation" port = 8080" indent 2 > 0 → continuation
Result: [{key: "server", value: "\n host = localhost\n port = 8080"}]Step 2 — build_hierarchy calls parse_indented on the value:
Value starts with '\n' → nested contextFirst content line " host = localhost" has indent 2 → N = 2
" host = localhost" indent 2, 2 > 2 is false → new entry" port = 8080" indent 2, 2 > 2 is false → new entry
Result: [{key: "host", value: "localhost"}, {key: "port", value: "8080"}]Final hierarchy: {"server": {"host": "localhost", "port": "8080"}}
If a parser always used N = 0, top-level parsing would work but nested values like "\n host = localhost\n port = 8080" would collapse into a single entry because every line has indent > 0. See Continuation Lines for the full algorithm and Behavior Reference for top-level baseline choices.
Language Patterns
Section titled “Language Patterns”Functional (Gleam, OCaml)
Section titled “Functional (Gleam, OCaml)”pub fn parse(text: String) -> Result(List(Entry), ParseError)pub fn build_hierarchy(entries: List(Entry)) -> Result(CCL, ObjectError)Use Result types, pattern matching, immutable data.
Imperative (Go, Java)
Section titled “Imperative (Go, Java)”func Parse(text string) ([]Entry, error)func BuildHierarchy(entries []Entry) (CCL, error)Use error returns, interfaces, builder patterns.
Dynamic (Python, JavaScript)
Section titled “Dynamic (Python, JavaScript)”def parse(text: str) -> list[Entry]def build_hierarchy(entries: list[Entry]) -> CCLUse exceptions, native collections, optional type hints.
Recursive Parsing Algorithm
Section titled “Recursive Parsing Algorithm”function build_hierarchy(entries): result = {} for (key, value) in entries: if key == "": add_to_list(result, value) else if value_looks_like_ccl(value): nested = parse(value) # Uses nested context detection result[key] = build_hierarchy(nested) # Recurse else: result[key] = value return resultFixed-point termination: Recurse until no more CCL syntax found.
See Parsing Algorithm for details.
Optional Features
Section titled “Optional Features”Type-Safe Access (library convenience):
get_string(ccl, path...): stringget_int(ccl, path...): intEntry Processing (composition utilities):
filter(entries, predicate): entriescompose(entries1, entries2): entriesParse Variants:
load(text)— convenience function combiningparse+build_hierarchyin a single call
See Library Features for details.
Testing
Section titled “Testing”Use CCL Test Suite to validate your implementation:
- Core Parsing: Filter tests by
functions: ["parse"] - Object Construction: Filter by
functionscontainingbuild_hierarchy - Typed Access: Filter by
validationstarting withget_ - Behavior conflicts: Skip tests where
conflicts.behaviorsorconflicts.variantsmatches your choices
See Test Suite Guide for complete filtering examples.
Internal Representation Choices
Section titled “Internal Representation Choices”How you represent CCL data internally affects which features are easy to implement.
The OCaml Approach
Section titled “The OCaml Approach”The reference OCaml implementation represents all data as nested KeyMap structures:
type ccl = KeyMap of ccl StringMap.tAdvantages:
- Uniform data structure (everything is a nested map)
- Elegant recursion with
fixfunction - Clean pattern matching and algebraic properties
Trade-off: This model cannot distinguish between a string value:
name = Aliceand a nested key with empty value:
name = Alice =Both produce identical models: { "name": { "Alice": {} } }
Alternative: Tagged Union
Section titled “Alternative: Tagged Union”For implementations that need structure-preserving print, use a tagged union:
type Value = | String(string) | Object(map<string, Value>) | List(list<Value>)Advantages:
- Easy to implement
print(structure recovery is straightforward) - Can distinguish string values from nested structures
- Natural representation for most languages
Lazy Hierarchy Building
Section titled “Lazy Hierarchy Building”Another approach: preserve original entries and build hierarchy on-demand:
type CCL = { entries: List(Entry), hierarchy: Lazy(Object)}Advantages:
- Perfect structure preservation for
print - Can support both
printandcanonical_format - Deferred parsing cost
See Library Features for details on print vs canonical_format.
Common Challenges
Section titled “Common Challenges”Equals in Keys: With delimiter_prefer_spaced, prefer = as delimiter when multiple = exist. Fall back to first = when no spaced delimiter is found.
Infinite Recursion: Fixed-point algorithm terminates naturally
Duplicate Keys: Merge into object or list based on value types
Empty Keys: Treat = value as list item
Unicode/CRLF: Break only on LF; preserve CR, preserve unicode
See Syntax Reference for edge cases.