Skip to content

Library Features

CCL values are always strings. Type conversion is a library convenience, not part of Core CCL.

Common Functions:

  • get_string(config, path...) - Extract string values
  • get_int(config, path...) - Parse integers with validation
  • get_bool(config, path...) - Parse booleans (true/false, yes/no, 1/0)
  • get_list(config, path...) - Extract lists from empty-key entries or duplicate keys

Example:

app =
name = MyApp
port = 8080
debug = true
name = get_string(config, "app", "name") // "MyApp"
port = get_int(config, "app", "port") // 8080
debug = get_bool(config, "app", "debug") // true

Manipulate CCL entries for composition and filtering.

Common Functions:

  • filter(entries, predicate) - Remove entries (e.g., comments)
  • compose(entries1, entries2) - Concatenate entry lists before downstream hierarchy building

Example:

/= Development config
database =
host = localhost
/= Production overrides
database =
host = prod.db.com
dev_entries = parse(dev_config)
prod_entries = parse(prod_config)
combined = compose(dev_entries, prod_entries)
final_config = build_hierarchy(combined)

compose is intentionally an entry-processing helper, not an object merge API. The expected flow is:

entries_a = parse(text_a)
entries_b = parse(text_b)
merged_entries = compose(entries_a, entries_b)
final_config = build_hierarchy(merged_entries)

This keeps composition pure at the Entry[] layer and gives [] the expected left/right identity for compose.

CCL provides two distinct formatting functions that serve different purposes.

FunctionPurposeProperty
printStandard formatStructure-preserving: print(parse(x)) == x for standard inputs
canonical_formatModel-level formatSemantic-preserving: transforms key = value to nested form

Purpose: Convert parsed entries back to CCL text format, preserving the original structure.

Key Property: For inputs in standard format:

print(parse(x)) == x

This is an entry-level isomorphism - the round-trip preserves the textual structure.

Example:

name = Alice
config =
port = 8080
debug = true

After parse and print, the structure is preserved exactly.

Purpose: Render a CCL value as normalized CCL text — stable key ordering, consistent indentation, no redundant whitespace. Two semantically equal inputs produce identical output.

Key Property: For a canonical input, canonical_format is a fixed point:

canonical_format(canonical_format(x)) == canonical_format(x)

Unlike print, which preserves the original text structure, canonical_format normalizes key order and indentation. The OCaml reference implementation’s pretty function is its equivalent.

Note on the underlying model: The canonical CCL data model represents all values as keys in a recursive map — name = Alice becomes {"name": {"Alice": {}}} internally. canonical_format renders this model back to CCL text. This means two inputs that are semantically equal in the model produce identical canonical_format output, even if they looked different as source text.

A CCL input is in standard format when:

  1. Keys have exactly one space before and after =
  2. Nested content uses 2-space indentation per level
  3. Line endings are LF only (CR characters become part of value content)
  4. No extra whitespace before keys or after values

Standard format:

key = value
nested =
child = value

Non-standard (extra spaces):

key = value
nested =

Use round_trip to verify the isomorphism property:

parse(print(parse(x))) == parse(x)

This verifies that print followed by parse produces identical entries to the original parse.

For structure-preserving print, implementations need to track whether a value was originally a string or nested structure. Options:

  1. Leaf flag: Mark nodes that were originally string values
  2. Original value storage: Keep raw string alongside children
  3. Entry preservation: Keep original entry list, build hierarchy on-demand

For new implementations, use a tagged union type:

type Value =
| String(string)
| Object(map<string, Value>)
| List(list<Value>)

This makes print straightforward to implement while still supporting canonical_format when needed.

Some implementations provide additional experimental features:

Dotted Representation (experimental):

  • Allows accessing nested values using dot-separated paths in a single argument
  • Example: get_string(config, "database.host") instead of get_string(config, "database", "host")
  • Not recommended for new implementations - use variadic path arguments as the standard API
  • May be useful for compatibility with existing configuration conventions

The CCL Test Suite provides tests for these features:

  • Type-Safe Access: get_string, get_int, get_bool, get_float, get_list — covered by tests tagged optional_typed_accessors
  • Entry Processing: filter, compose, and compose algebraic properties evaluated by normalizing build_hierarchy(compose(...))
  • Formatting: print and round_trip tests verify isomorphism properties
  • Experimental Features: experimental_dotted_keys tests for dotted representation

See Test Suite Guide for complete function list and filtering examples.