Skip to content

Implementing CCL

Every CCL implementation needs two operations:

  1. Parse - Convert text to flat key-value entries
  2. 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.

The two functions differ in how they pick the indentation baseline N:

FunctionUse CaseBaseline N
parseTop-level parsingN = 0 (any indented line is a continuation)
parse_indentedNested value parsingN = 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)

Input:

server =
host = localhost
port = 8080

Step 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 context
First 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.

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.

func Parse(text string) ([]Entry, error)
func BuildHierarchy(entries []Entry) (CCL, error)

Use error returns, interfaces, builder patterns.

def parse(text: str) -> list[Entry]
def build_hierarchy(entries: list[Entry]) -> CCL

Use exceptions, native collections, optional type hints.

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 result

Fixed-point termination: Recurse until no more CCL syntax found.

See Parsing Algorithm for details.

Type-Safe Access (library convenience):

get_string(ccl, path...): string
get_int(ccl, path...): int

Entry Processing (composition utilities):

filter(entries, predicate): entries
compose(entries1, entries2): entries

Parse Variants:

  • load(text) — convenience function combining parse + build_hierarchy in a single call

See Library Features for details.

Use CCL Test Suite to validate your implementation:

  1. Core Parsing: Filter tests by functions: ["parse"]
  2. Object Construction: Filter by functions containing build_hierarchy
  3. Typed Access: Filter by validation starting with get_
  4. Behavior conflicts: Skip tests where conflicts.behaviors or conflicts.variants matches your choices

See Test Suite Guide for complete filtering examples.

How you represent CCL data internally affects which features are easy to implement.

The reference OCaml implementation represents all data as nested KeyMap structures:

type ccl = KeyMap of ccl StringMap.t

Advantages:

  • Uniform data structure (everything is a nested map)
  • Elegant recursion with fix function
  • Clean pattern matching and algebraic properties

Trade-off: This model cannot distinguish between a string value:

name = Alice

and a nested key with empty value:

name =
Alice =

Both produce identical models: { "name": { "Alice": {} } }

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

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 print and canonical_format
  • Deferred parsing cost

See Library Features for details on print vs canonical_format.

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.