ClaFi configuration format v1.0
1. Design Manifesto = [
This specification describes the configuration file format used in ClaFi. It combines the line-oriented simplicity of flat key-value files with the recursive structure of a tree. It structures configuration trees without losing the clarity of a plain text file.
The format is designed to be simple and fast to parse:
- Uniform grammar: Everything is a
key=valueassignment. No arbitrary headers or confusing scopes. - Single-pass parsing: No backtracking, lookahead, or state machines required.
- Zero-allocation value extraction: The Bookend Rule enables string decoding without copying or escaping.
- Trivial line dispatch: The first character of each line determines its entire semantic meaning.
The format uses comments starting with ;, value assignments with =, and square brackets [ for divisions. This layout mirrors legacy property files, allowing immediate syntax highlighting in VS Code when using .cfg or .ini extensions.
The core philosophy is No Escaping. The backslash (\) is treated as a literal character, which provides several advantages:
- Source-Code Friendly: You can paste C++, JavaScript, or Regex snippets directly into a value. *read how to paste multiline text in Continuations
- Path Safe: File paths are stored exactly as they appear in the file system.
- Predictable Round-Trips: Reading a file and writing it back results in the same data without substitutions.
2. Values & Arrays = [
Values are either quoted strings or unquoted literals. The format has no built-in type system; interpretation is left to the application.
2.1 Scalar Values
Content after the = is the value. If it starts and ends with ", the Bookend Rule applies. Otherwise, the value is taken literally after trimming outer whitespace.
; Quoted string
Name = "Nebula Framework"
; Unquoted literals
Version = 1.0
; Unquoted literal (application decides type)
Enabled = yes
; No escaping needed
Path = C:\Program Files\App
Parsers return strings. Applications interpret types like integers, floats, or booleans. The format is type-agnostic.
2.2 Arrays
Arrays group multiple values under a key. Each item begins with = on its own line.
2.2.1 Scalar Array
Flags = [
= "Optimize"
= "Debug"
= "Release"
]
2.2.2 Section Array (List of Sections)
Users = [
=[
Name = "Alice"
Active = 1
]
=[
Name = "Bob"
Active = 0
]
]
- When
Key = [is encountered, begin collecting items. - Each line starting with
=begins a new item. - If
=[, parse as an anonymous section until the matching]. - Otherwise, parse as a scalar value (applying Bookend Rule).
- Stop at
]on its own line.
3. Nested Sections = [
The format uses a tree structure where any attribute can branch into a deeper section. The format supports unlimited nesting depth.
Attributes assign nested sections using the [ token:
NestedSection = [
SubKey = "Value"
]
- Every section acts as a standalone sub-file.
- Nested sections use the full grammar.
- A matching closing bracket
]must occupy its own line.
4. Line-Orientation = [
The format is line-oriented. To keep parsers simple and git diffs clean, structural markers cannot be inlined.
| Marker | Requirement |
|---|---|
] (closing scope marker) | Must be the first non-whitespace character on its own line. |
= (array item marker) | Must be the first non-whitespace character on its own line. |
| or + | Must be the first non-whitespace character on its own line. |
5. Continuations = [
If a value spans multiple lines, the format provides two behaviors:
- Newline (
|): Appends the line text and inserts a newline (\n). - Concatenation (
+): Appends the line text directly without adding characters. Useful for long paths or keys.
LongText = "First Line"
| "Second Line"
| "Third Line"
; Value: First Line\nSecond Line\nThird Line
LongPath = "C:\Program"
+ " Files"
+ "\Application"
; Value: C:\Program Files\Application
* Note on Multiline Snippets: To paste multiline content, prepend the newline continuation marker (|) at the start of each subsequent line.
Because leading whitespace on continuation lines is stripped up to the | marker, optional indentation allows you to align subsequent lines with the first one - even within deeply nested sections - greatly improving visual readability.
NestedKey = Line one content
| Line two content
| Line three content
Note: Double quotes are optional.
Addressing the "No Escaping" Critique: Some argue that requiring a prepended | on continuation lines acts as an escape sequence. In this format, a multiline string is modeled as an array of independent, single-line strings presented separately. The | is a structural syntax marker indicating how separate lines compile into the tree, not an escape character.
6. The Bookend Rule = [
To support quotes and protect whitespace boundaries without escaping, the format uses one rule for boundary detection:
If a line starts and ends with a double-quote ("), the parser strips the first and last character.
To prevent data loss, a builder must wrap a value in double-quotes (") if the value:
- Starts and ends with double-quotes (e.g.
"Error"). - Is exactly an opening square bracket ([) - prevents section hijacking.
- Contains leading or trailing whitespace - prevents boundary loss during trimming.
The parser strips the outer quotes during load, restoring the exact value.
This preserves internal quotes and margins:
; 1. Quotes preserved without escaping
LogMessage = "System returned "Error 404" at runtime"
; Internal Value: System returned "Error 404" at runtime
; 2. Padding preserved (normally lost during trims)
PaddedText = " Preserved Margins "
; Internal Value: Preserved Margins
If the line does not have quotes at both ends, the parser treats it as an unquoted string and trims the outer whitespace.
7. Parser Guide = [
Implementing a parser is simple. Because the format is line-oriented, you do not need to scan back and forth within a line for transitions.
- Read: Take a line from the input.
- Initial Trim: Remove leading whitespace.
-
Dispatch:
- If
@: Handle metadata. - If
;: Skip line. - If
]: Pop scope from the stack. -
If
|or+: Apply Bookend Rule and append to the last processed value. -
If Key/Alpha: Split at
=.- If Value is
[: Push a new named section scope. - Else: Process value.
- If Value is
- If
8. Marker Reference = [
| Symbol | Logic |
|---|---|
@ | Optional Metadata Directive (e.g., encoding) |
; | Line Comment |
Key = [ | Starts a Named Section or Array |
=[ | Starts an Anonymous Section (Array Item) |
] | Ends current scope (Must be on new line) |
= | Value Assignment or Array Item Marker |
| | Newline continuation |
+ | Concatenation continuation |
" | Bookend Quotes (Stripped if at both ends of a line) |
Values may contain any symbols, including reserved markers. Avoid using them in keys.
9. Full Showcase = [
@ClaFi 1.0
@encoding utf-8
; Example usage
ColorMode=Dark
MainForm=[
Bounds=[
Left=934
Top=515
Right=2886
Bottom=1765
]
]
Tabs=[
OpenTabs="Tab1,Tab2,Tab3"
TabContent=[
=[
Id="Tab1"
Title="Themes"
Path="Home"
Preview=1
ThemeData=[
BgSaturation=0
AnchorHue=210
Harmony="Monochromatic"
]
]
=[
Id="Tab2"
Title="New Theme (2)"
Preview=1
ThemeData=[
BgSaturation=0.07333333
AnchorHue=71
]
]
]
SelectedTab="Tab3"
]
10. C++ Examples = [
This chapter provides C++ examples for implementing key features. The snippets demonstrate the line-oriented design.
10.1 Implementing the Bookend Rule
// Outer whitespace removed, as from every key and value before it is read
std::wstring_view trimmed(std::wstring_view source)
{
while (!source.empty() && std::iswspace(source.front()))
{
source.remove_prefix(1);
}
while (!source.empty() && std::iswspace(source.back()))
{
source.remove_suffix(1);
}
return source;
}
// Decoder following the Bookend Rule, over a trimmed value
std::wstring_view decodeValue(std::wstring_view source)
{
if (source.size() > 1 && source.front() == L'"' && source.back() == L'"')
{
return source.substr(1, source.size() - 2);
}
return source;
}
// Builder side: protecting quotes, brackets, and boundary spaces
std::wstring encodeValue(std::wstring_view source)
{
const bool isOpeningBracket = (source == L"[");
const bool isAlreadyBookended = (source.size() >= 2 && source.front() == L'"' && source.back() == L'"');
const bool hasBoundaryWhitespace = (!source.empty() && (std::iswspace(source.front()) || std::iswspace(source.back())));
if (isOpeningBracket || isAlreadyBookended || hasBoundaryWhitespace)
{
return L"\"" + std::wstring(source) + L"\"";
}
return std::wstring(source);
}
10.2 Core Parser Loop Structure
The main dispatch logic showcasing the format's line-oriented simplicity:
void ConfigFile::load(std::wifstream& stream)
{
std::wstring line;
Node* currentScope = rootNode;
while (getTrimLine(stream, line))
{
if (line.empty())
{
continue;
}
// Dispatch based on first character
switch (line.front())
{
case L';': // Comment
{
break;
}
case L']': // End current scope
{
currentScope = currentScope->parent();
break;
}
case L'|': // Newline continuation
case L'+': // Concatenation continuation
{
if (lastValueNode)
{
bool addNewline = (line.front() == L'|');
std::wstring_view chunk = trimmed(std::wstring_view{ line }.substr(1));
lastValueNode->appendValue(decodeValue(chunk), addNewline);
}
break;
}
default: // Key=Value assignment or Array item
{
size_t eqPos = line.find(L'=');
if (eqPos != std::wstring::npos)
{
std::wstring_view whole = line;
std::wstring_view key = trimmed(whole.substr(0, eqPos));
std::wstring_view value = trimmed(whole.substr(eqPos + 1));
if (key.empty())
{
// Array item
if (value == L"[")
{
currentScope = ¤tScope->addAnonymousSection();
}
else
{
currentScope->addArrayItem(decodeValue(value));
}
}
else
{
// Key=Value assignment - only a bare [ opens a section, a quoted one is a value
if (value == L"[")
{
currentScope = ¤tScope->addNamedSection(key);
}
else
{
lastValueNode = ¤tScope->assignValue(key, decodeValue(value));
}
}
}
else
{
// Syntax Error: Every non-reserved line must contain an '='
}
break;
}
}
}
}