VIBE Format Specification

Version: 1.0.1  ·  Status: Stable  ·  Date: 2025-07-20
Canonical URL: https://1ay1.github.io/vibe/specification.html
License: MIT

VIBEValues In Bracket Expression. A configuration language with one radical rule: every structured thing has a name. No anonymous objects, no positional records, no indentation guessing. The structure you see is the structure you get.


The First Law of VIBE

An array MUST NOT contain an object or another array.

This single sentence is the heart of the language. Every other format lets you write an anonymous list of records:

"replicas": [ { "host": "a" }, { "host": "b" } ]

…and every such list is a latent bug: the entries have no stable identity, they are addressed by a fragile array index, reordering silently rebinds every reference, and merging two configs is undefined. VIBE forbids the pattern outright. If a thing is worth structuring, it is worth naming:

replicas {
  east { host db-east.internal  port 5432 }
  west { host db-west.internal  port 5432 }
}

Arrays in VIBE hold scalars only — a bag of numbers, strings, or booleans. The moment you need structure, you need a key. This is not a missing feature; it is the point. See The Stability Paradox for the full argument.


The VIBE Guarantees

VIBE is defined by what it promises and what it refuses — not by a feature list. Every guarantee below is enforced by the grammar and verified by the conformance suite.

  1. One Parse. The same bytes produce the same value tree in every conforming parser, forever. VIBE has no ambiguous documents — none. If you can show two conforming parsers that disagree on any input, that is a spec bug and we will fix it.
  2. Named Entities. Every structured value has a name. There are no anonymous records, no positional identity, no [0]-addressed objects. Reordering never silently rebinds a reference.
  3. No Surprises. A value is exactly what it looks like. There is no implicit coercion that turns your data into something else behind your back (see No Footguns below).
  4. Frozen Grammar. The syntax and semantics are locked for all of VIBE 1.x. A document you write today parses identically under every future 1.x parser (see Versioning and Stability).
  5. Conformance-Tested. Compliance is not an honor system. A shared, language-neutral test suite decides whether a parser is conforming.

No Footguns

VIBE deliberately does not have the traps that make hand-written config dangerous in other formats:

What VIBE Refuses (and Why)

A format is defined by its refusals. VIBE will never add these, because each one re-introduces the ambiguity or instability the language exists to prevent:

Refused Why
Objects/arrays inside arrays Anonymous records have no stable identity (the First Law).
Anchors, references, aliases Non-local action; a value's meaning stops being readable in place (YAML's &/*).
Implicit type coercion The Norway problem and its whole family. A token means one thing.
Templates, conditionals, if Config is data, not a program. Logic belongs in your code, not your config file.
Significant indentation Whitespace-as-structure is the single largest source of config bugs.
Multiple ways to write the same thing One canonical form keeps diffs, merges, and tooling sane.

VIBE is for configuration that humans write by hand — not data that machines exchange. For APIs and wire formats, use JSON. We mean it. VIBE wins by being the best possible config language, not by trying to be everything.


Conformance and Notation

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.

A conforming VIBE document is a UTF-8 byte stream that satisfies the grammar in §Grammar and every normative MUST in this specification.

A conforming VIBE parser is a program that accepts every conforming document and produces the value tree this specification defines for it, and rejects every non-conforming document with an error. Behavior on non-conforming input is defined in §Error Handling; a conforming parser MUST NOT silently accept malformed input.

Sections marked (Non-normative) — notably §Future Considerations — are informative and impose no requirements.


Table of Contents

  1. The First Law of VIBE
  2. The VIBE Guarantees
  3. Overview
  4. Design Goals
  5. Grammar
  6. Lexical Analysis
  7. Character Encoding and Document Model
  8. Data Types
  9. String Literals
  10. Comments
  11. Arrays
  12. Objects
  13. Whitespace and Formatting
  14. Reserved Words
  15. Path Notation
  16. Duplicate Keys
  17. Canonical Form
  18. Complete Examples
  19. Parsing Algorithm
  20. Error Handling
  21. File Format
  22. Conformance Test Suite
  23. Implementation Guidelines
  24. Security Considerations
  25. Performance Requirements
  26. Validation and Schema
  27. Comparison to Other Formats
  28. Migration Guide
  29. Versioning and Stability
  30. Future Considerations
  31. References

Overview

VIBE (Values In Bracket Expression) is a hierarchical configuration file format designed to optimize both human readability and machine parsing performance. It combines the visual clarity of bracket-delimited structures with minimal syntax overhead, making it ideal for configuration files, data serialization, and human-editable structured data.

VIBE addresses common pain points in existing configuration formats: it eliminates YAML's indentation sensitivity, removes JSON's syntactic overhead (trailing commas, excessive quotes), and provides clearer visual hierarchy than INI files while maintaining simplicity.

Core Principles

Design Goals

1. Visual Hierarchy

The structure of data should be instantly recognizable through visual inspection alone. Nested objects use braces {} that create clear visual boundaries, while arrays use brackets [] for unambiguous collection identification. This bracket-based syntax provides immediate structural context without requiring counting indentation levels or tracking implicit scope.

2. Minimal Syntax

VIBE uses exactly 6 core token types (identifiers, strings, numbers, booleans, braces, brackets), making it one of the simplest structured formats to learn and implement. The absence of mandatory commas, semicolons, or colons reduces syntactic noise and simplifies both human editing and parser implementation.

3. Unambiguous Grammar

Each data structure has exactly one canonical representation in VIBE. This eliminates formatting debates, ensures consistent machine generation, and enables reliable diffing and version control. The deterministic grammar means there are no edge cases or surprising interpretations.

4. Predictable Parsing

The grammar admits single-pass, O(n) parsing with a simple state machine — no backtracking, no unbounded lookahead, no ambiguous productions. The point is not raw speed (config files are tiny); it is that parsing is predictable: the same input always produces the same tree with no surprising interpretations, and any competent parser is trivially fast as a side effect.

5. Type Safety

Clear, deterministic type inference rules ensure that values are consistently interpreted across all implementations. Numbers, booleans, and strings are unambiguously distinguished through syntax, eliminating the need for explicit type annotations while maintaining type safety.

6. Human-Friendly

The syntax is optimized for human reading and writing. Whitespace is non-significant (except as token separation), comments are allowed anywhere, and unquoted strings reduce visual clutter for simple values. Error messages should provide clear context and actionable guidance for resolution.

Grammar

Formal Grammar Definition (EBNF)

(* A document is a sequence of lines. Each line is blank, a comment, or a
   single key/value statement; every value is owned by a key — there are no
   anonymous top-level objects or arrays. *)
config        = { line } ;
line          = [ statement ] [ comment ] newline ;
statement     = key ( scalar_value | object | array ) ;
object        = "{" { line } "}" ;
array         = "[" { separator } [ value_list ] { separator } "]" ;
value_list    = scalar_value { separator scalar_value } ;
separator     = whitespace | newline | comment newline ;
key           = identifier | quoted_string ;
scalar_value  = number | boolean | string ;   (* identifier is a string *)
comment       = "#" { any_char - newline } ;
identifier    = ( letter | "_" ) { letter | digit | "_" | "-" } ;
string        = quoted_string | unquoted_string ;
quoted_string = '"' { string_char | escape_sequence } '"' ;
string_char   = any_char - '"' - "\" - control_char ;
unquoted_string = unquoted_char { unquoted_char } ;
unquoted_char = ( 0x21 ... 0x7E ) - "{" - "}" - "[" - "]" - "#" - '"' ;
number        = integer | float ;
integer       = [ "-" ] digit { digit } ;
float         = [ "-" ] digit { digit } "." digit { digit } ;
boolean       = "true" | "false" ;
whitespace    = ( " " | "\t" | "\r" ) { " " | "\t" | "\r" } ;
newline       = "\n" ;
escape_sequence = "\" ( '"' | "\" | "n" | "t" | "r" | "u" hex hex hex hex ) ;
(* "\u" hex hex hex hex is a VIBE 1.1 feature; a strict 1.0 parser rejects it
   with invalid-escape. See String Literals. *)

Grammar notes (normative). 1. A statement is always key value. Bare objects, arrays, or scalars with no key are not statements and MUST be rejected (unexpected-token). 2. scalar_value never includes a container: the value of a key is either a scalar, or an object/array opened by {/[ immediately after the key. An identifier on its own is a string (see Type Inference); it is not a distinct value kind. 3. Inside an array, items are separated by any run of whitespace, newlines, and full-line/inline comments; leading and trailing separators are allowed, so [], [ ], and a multi-line array with blank lines are all valid.

Note on key. A key is normally a bare identifier. A quoted_string key is permitted so that keys which are not valid identifiers — URL paths ("/api/login"), header names ("content-type"), IP/CIDR ("10.0.0.0/8") — can still be named. The key's value is the decoded string content (after escape processing), compared byte-exactly. unquoted_char intentionally admits the full printable-ASCII range minus the five structural characters and the quote, so values like s3cr3t!@% are unquoted strings; anything outside that range (spaces, non-ASCII, control characters) requires quoting.

Tokens

VIBE recognizes exactly six token kinds:

  1. IDENTIFIER - [a-zA-Z_][a-zA-Z0-9_-]*
  2. STRING - Quoted or unquoted text
  3. LEFT_BRACE - {
  4. RIGHT_BRACE - }
  5. LEFT_BRACKET - [
  6. RIGHT_BRACKET - ]

(NUMBER and BOOLEAN are not separate token kinds: they are IDENTIFIER/STRING tokens re-classified by the deterministic Type Inference step. A newline is a statement terminator, not a value token.)

Syntax Rules

Assignment

key value

Object Declaration

name {
  statements...
}

Array Declaration

name [value1 value2 value3]

or multi-line:

name [
  value1
  value2
  value3
]

Lexical Analysis

Tokenization Process

The lexer processes the input character by character, maintaining state to determine token boundaries:

  1. Whitespace Handling: Spaces, tabs, and newlines are used as separators but are not significant tokens
  2. Comment Recognition: # starts a comment that continues to end of line
  3. String Recognition: Quoted strings begin with " and end with matching "
  4. Identifier Recognition: Alphanumeric characters, underscores, and hyphens
  5. Operator Recognition: Braces and brackets are single-character tokens

Character Classes

ALPHA     = [a-zA-Z]
DIGIT     = [0-9]
ALNUM     = ALPHA | DIGIT
IDENT_START = ALPHA | "_"
IDENT_CHAR  = ALNUM | "_" | "-"
UNQUOTED_CHAR = [0x21-0x7E] - [{}[]#] - WHITESPACE
WHITESPACE = " " | "\t" | "\r"
NEWLINE   = "\n"

Token Examples

# Valid identifiers
server
database_host
ssl-enabled
_private
config2

# Valid unquoted strings
localhost
192.168.1.1
/usr/local/bin
example.com:8080

# Invalid identifiers (would be treated as strings)
2servers    # starts with digit
server.host # contains dot (would be unquoted string)
配置        # Unicode not allowed in identifiers

Character Encoding and Document Model

This section defines, byte-for-byte, what a VIBE document is. These rules exist to close the gaps that cause parser differentials — the situation where two parsers disagree about the same bytes, which is both a correctness bug and a security vulnerability (see Security Considerations).

Encoding

  1. A VIBE document MUST be a stream of bytes encoded in UTF-8.
  2. A parser MUST reject any input that is not well-formed UTF-8 with an encoding-error. Overlong encodings, unpaired surrogate code points (U+D800–U+DFFF), and truncated sequences are all ill-formed and MUST be rejected. A parser MUST NOT attempt to auto-detect UTF-16, Latin-1, or any other encoding.
  3. No byte order mark. A leading U+FEFF (BOM) MUST be rejected with an encoding-error, and producers MUST NOT emit one. (A BOM is meaningless in UTF-8 and is a common source of “invisible first key” bugs.)

Forbidden Code Points

  1. U+0000 (NUL) MUST NOT appear anywhere in a document, including inside quoted strings. A parser that receives length-delimited input MUST reject an embedded NUL with an illegal-character error. (Implementations built on NUL-terminated C strings inherently truncate at the first NUL; such an implementation MUST document this and SHOULD provide a length-aware entry point to remain safe against NUL-smuggling.)
  2. Raw C0 control characters (U+0001–U+001F) and U+007F MUST NOT appear literally inside quoted strings, with the sole exception of the horizontal tab (U+0009). To include other control characters, use an escape such as \n, \r, or \t. This keeps a copy-pasted config from carrying invisible, diff-hostile bytes.
  3. Unicode noncharacters (U+FFFE, U+FFFF, and the like) are permitted inside quoted strings but SHOULD be avoided.

Line Terminators

  1. The statement separator is the line feed LF (U+000A).
  2. CRLF (U+000D U+000A) is accepted: the carriage return is treated as insignificant whitespace and the LF terminates the line. This makes VIBE robust to Windows editors and git autocrlf.
  3. A lone CR (classic-Mac line ending) is not a line terminator; it is insignificant whitespace. Producers targeting maximum portability SHOULD use LF. A parser MUST report line numbers by counting LF terminators only, so that error positions are stable across platforms.

Whitespace

Outside quoted strings, the space (U+0020), horizontal tab (U+0009), and carriage return (U+000D) are insignificant — they separate tokens but carry no meaning. Indentation is never significant. There is no other whitespace: a parser MUST NOT treat U+00A0 (no-break space), U+2028/U+2029, or other Unicode spaces as token separators; they are ordinary characters and, outside quoted strings, an illegal-character error.

The Document Model

  1. A document is a sequence of statements that populate an implicit root object. The parse result is always an object (never a bare scalar or array).
  2. An empty document — zero bytes, or a document containing only whitespace, newlines, and comments — is valid and yields an empty root object. This is the canonical “nothing configured” state; a parser MUST NOT error on it.
  3. Keys and string contents are compared and stored byte-exactly. A parser MUST NOT apply Unicode normalization (NFC/NFD), case folding, or trimming to keys or values. "caf\u00e9" (composed) and "cafe\u0301" (decomposed) are different keys. Since identifiers are ASCII-only (below), this only affects quoted keys and string values.

Data Types

VIBE supports 5 fundamental data types with automatic type inference based on syntax patterns. Type inference is total and deterministic: every token maps to exactly one type, and the mapping is identical in every conforming parser.

Type Inference Algorithm

Type determination follows a strict precedence order to ensure unambiguous interpretation:

function inferType(value):
    stripped = strip_whitespace(value)

    # Check in order of specificity
    if stripped matches /^-?\d+$/
        return INTEGER
    else if stripped matches /^-?\d+\.\d+$/
        return FLOAT
    else if stripped == "true" or stripped == "false"
        return BOOLEAN
    else if starts_with(stripped, '"') and ends_with(stripped, '"')
        return STRING (quoted, with escape sequence processing)
    else
        return STRING (unquoted, literal value)

Key Points: - Type inference is deterministic and order-independent - No ambiguity between types - each syntax pattern maps to exactly one type - Implementations must follow this exact algorithm for compatibility

Token boundary (normative). Type is inferred from a whole token, never a prefix. The lexer first reads a maximal unquoted token (up to whitespace, a structural character, #, or "), then classifies that entire token. So 12abc is the single token 12abc, which does not match the integer or float pattern and is therefore the string "12abc" — a parser MUST NOT read 12 as an integer and then choke on abc. Conversely a token that does fully match the numeric pattern but is out of range (e.g. an integer beyond the implementation's width) is an invalid-number error, because it was unambiguously intended as a number. In short: matching the numeric pattern is an all-or-nothing property of the complete token.

Integer Type

Definition: A sequence of digits optionally preceded by a minus sign, representing whole numbers.

Syntax Pattern: -?[0-9]+

Range: Implementations MUST support at least 64-bit signed integers (−2^63 to 2^63−1). A value that does not fit the implementation's integer type MUST be rejected with an invalid-number error — a conforming parser MUST NOT silently clamp, wrap, or truncate an out-of-range integer, because doing so corrupts configuration without warning.

Examples:

count 42
negative -17
zero 0
large 9223372036854775807
min_value -9223372036854775808

Rules: - Leading zeros are allowed but discouraged (e.g., 007 is valid but should be written as 7) - No thousands separators (commas, underscores, or spaces) - Plus sign prefix is not supported (use unsigned form) - Scientific notation is not supported for integers

Invalid Syntax:

with_comma 1,000    # Commas not allowed
with_space 1 000    # Spaces not allowed
with_plus +42       # Plus sign not supported
hex 0xFF            # Only decimal notation supported

Float Type

Definition: A sequence of digits with exactly one decimal point, representing real numbers.

Syntax Pattern: -?[0-9]+\.[0-9]+

Range: A float MUST be represented as at least IEEE 754 double precision (binary64). A syntactically valid float whose magnitude is too large to represent (it would round to infinity) MUST be rejected with an invalid-number error — just like an out-of-range integer, a parser MUST NOT silently substitute infinity. Values that merely lose precision in the low-order digits (the normal case for binary64) are accepted and rounded to the nearest representable double, which is deterministic per IEEE 754.

Examples:

pi 3.14159
negative -0.5
zero 0.0
small 0.001
precise 123.456789012345

Rules: - Decimal point is REQUIRED (distinguishes from integers). - At least one digit MUST appear on both sides of the decimal point. - Exactly one decimal point; 1.2.3 is a string, not a float. - Leading zeros after the decimal are significant (0.0010.1). - Negative zero (-0.0) is preserved as the IEEE 754 value −0.0.

No special float values, no exponents — by design. These tokens are strings, never floats, because silently minting a non-finite number from a bare word is exactly the Norway-problem class of bug:

Token VIBE type Note
NaN, nan, Infinity, inf string Not-a-number and infinities are never inferred.
1.5e10, 6.022e23 string Scientific/exponential notation is not a VIBE number.
0x1p4, 1_000.0 string No hex floats, no digit separators.

If an application genuinely needs infinity or scientific notation, it stores the string and interprets it itself — the format refuses to guess.

Invalid Syntax (rejected, not reinterpreted):

trailing_dot 42.    # requires digits after the decimal
leading_dot .42     # requires digits before the decimal

Boolean Type

Definition: Logical true/false values represented by the exact literals true or false.

Syntax: Lowercase keywords only: true | false

Case Sensitivity: VIBE is case-sensitive for boolean literals. True, TRUE, False, FALSE, or any other capitalization variants are treated as unquoted strings, not booleans.

Examples:

enabled true
debug false
production true
maintenance_mode false

Common Mistakes:

enabled True        # This is a STRING "True", not boolean
debug FALSE         # This is a STRING "FALSE", not boolean  
active yes          # This is a STRING "yes", not boolean
disabled no         # This is a STRING "no", not boolean

Design Rationale: Using only lowercase true and false eliminates ambiguity and aligns with most programming languages (JavaScript, Python, JSON). Alternative boolean representations (yes/no, on/off, 1/0) can be used as strings and interpreted by application logic if needed.

String Type

Strings represent textual data and come in two forms: quoted and unquoted.

Unquoted Strings

Unquoted strings provide a cleaner syntax for simple identifiers and values without spaces or special characters.

Syntax: Any sequence of non-whitespace, non-special characters

Valid Characters: Letters, digits, and the characters: _, -, ., /, :, @

Termination: Unquoted strings end at: - Whitespace (space, tab, newline) - Structural characters ({, }, [, ]) - Comment start (#)

Restrictions: - MUST NOT match the pattern of a number (integer or float) - MUST NOT be exactly true or false (reserved for booleans) - MUST NOT contain spaces or special characters that could create ambiguity - Unicode characters beyond ASCII require quoted strings

Examples:

hostname server1.example.com
path /usr/local/bin
email admin@example.com
version 2.1.0-beta
identifier user_id_123
url_path /api/v1/users

When to Quote: Use quoted strings when: - Value contains spaces - Value matches a number pattern but should be a string - Value is true or false but should be a string - Value contains special characters (#, {, }, [, ]) - Value contains Unicode characters beyond ASCII protocol https url https://api.example.com/v1 email user@example.com port_name 8080 # String, not number, because of underscore password s3cr3t!@#$%^&*() connection_string postgres://user:pass@host:5432/db

Unicode values must be quoted

language "中文" protocol "сервер" port "データベース"

Structural characters must be quoted when ambiguous

config_with_braces "{\"key\": \"value\"}" array_syntax "[1, 2, 3]" comment_text "This contains # a hash"


#### Quoted Strings

**Delimiters**: Double quotes `"`

**Required When**:
- Value contains whitespace
- Value contains special characters: `# { } [ ]`
- Value has leading/trailing whitespace
- Value contains Unicode (non-ASCII) characters
- Value would otherwise be ambiguous

**Examples**:

message "Hello, world!" path_with_spaces "/Program Files/App" special "value with # comment character" empty "" just_spaces " " unicode_string "你好 世界" # Unicode requires quotes mixed_special "配置 # with comment char"


### Array Type

**Definition**: Ordered collection of scalar values (integers, floats, booleans, strings)

**Syntax**: Values separated by whitespace, enclosed in `[]`

**Restrictions**:
- Arrays can **ONLY** contain scalar values: integers, floats, booleans, and strings
- **Objects are NOT allowed inside arrays** - The parser will reject this with an error
- **Nested arrays are NOT allowed** - Arrays cannot contain other arrays
- All values are parsed independently as primitives
- Use named objects instead of object arrays for complex data

> **Design Philosophy**: This restriction is intentional and core to VIBE's design. Arrays of objects create instability through index-based references, ambiguous merging, and lack of inherent identity. VIBE forces you to use named objects, which provide stable path-based references and deterministic configuration merging. See [The Stability Paradox](docs/Stability_Paradox.md) for the full rationale.

**Examples**:

numbers [1 2 3] strings [hello world foo] mixed [42 "hello" true 3.14] hosts [server1.com server2.com server3.com] flags [enabled debug verbose]


**Invalid** (objects in arrays not allowed):

❌ This is NOT valid VIBE syntax - parser will reject this

servers [ { host server1.com port 8080 } ]

❌ Nested arrays also not allowed

matrix [ [1 2 3] [4 5 6] ]


**Error messages you'll see**:
- `"Objects cannot be placed inside arrays. Use named objects instead."`
- `"Arrays cannot be nested inside other arrays."`

**Use this pattern instead**:

Use named objects for complex data

servers { primary { host server1.com port 8080 } secondary { host server2.com port 8081 } }


### Object Type

**Definition**: Unordered collection of key-value pairs

**Syntax**: Statements enclosed in `{}`

**Examples**:

config { host localhost port 8080 }


## String Literals

### Escape Sequences

Quoted strings support the following escape sequences. A conforming **VIBE 1.0**
parser MUST recognize all five core escapes:

| Escape | Character | Unicode | Description | Since |
|--------|-----------|---------|-------------|-------|
| `\"` | `"` | U+0022 | Double quote | 1.0 |
| `\\` | `\` | U+005C | Backslash | 1.0 |
| `\n` | | U+000A | Newline (LF) | 1.0 |
| `\r` | | U+000D | Carriage return (CR) | 1.0 |
| `\t` | | U+0009 | Horizontal tab | 1.0 |
| `\uXXXX` | | U+XXXX | Unicode codepoint (4 hex digits) | 1.1 |

Any backslash **not** followed by one of the escapes above (for its version) is
an `invalid-escape` error. In particular, a strict VIBE 1.0 parser — including
the reference implementation — MUST reject `\uXXXX` with `invalid-escape`;
`\uXXXX` support is introduced in VIBE 1.1 and is OPTIONAL in 1.0. Note that a
Unicode character embedded directly in a quoted UTF-8 string needs no escape at
all — `\uXXXX` exists only for encoding codepoints that are awkward to type.

### Unicode Support

- A document MUST be well-formed UTF-8 (see [Character Encoding and Document
  Model](#character-encoding-and-document-model)).
- Non-ASCII characters MAY appear directly, unescaped, inside **quoted strings**
  and **comments** — and this is the RECOMMENDED way to write them (`"café"`, not
  an escape).
- **Identifiers and unquoted strings are ASCII-only** (bytes 0x21–0x7E). To use a
  key or value containing non-ASCII text, quote it. A non-ASCII byte where an
  identifier is expected is an `illegal-character` error.
- The `\uXXXX` escape (four hex digits, Basic Multilingual Plane) is a **VIBE 1.1**
  feature and is OPTIONAL; a strict 1.0 parser rejects it with `invalid-escape`.
  There is intentionally **no** `\U`-style 8-digit escape and no surrogate-pair
  escaping: characters outside the BMP (emoji, etc.) are written **directly** as
  UTF-8 inside a quoted string, which needs no escape at all.

**Examples**:

greeting "Hello, world! 🌍" # astral character written directly — the normal way thumbs_up "👍 nice" # no escape needed for emoji escaped "She said \"Hello\"" multiline "Line 1\nLine 2\nLine 3" windows_path "C:\Users\Name" # backslashes must be escaped

ASCII identifiers with Unicode quoted values

configuration "aplicación" # ASCII key, quoted Unicode value settings "сервер" language english # ASCII key and unquoted ASCII value language "español" # ASCII key, quoted Unicode value


### String Parsing Rules

1. **Unquoted String Parsing**:
   - Starts with first non-whitespace character.
   - Continues until whitespace, a structural character, or end of line.
   - Cannot contain `" # { } [ ]`.
   - ASCII printable only (0x21–0x7E); e.g. `s3cr3t!@%`, `/usr/local`, `10.0.0.1`,
     `https://x.example` are all valid unquoted strings. Non-ASCII or
     whitespace-containing values must be quoted.

2. **Quoted String Parsing**:
   - Starts with `"`
   - Continues until matching `"` (not escaped)
   - Must be closed on same logical line
   - Processes escape sequences

3. **Empty Strings**:
   - Only possible with quoted strings: `""`
   - Unquoted empty values are not allowed

## Comments

### Syntax

Comments start with `#` and continue to the end of the line.

This is a full line comment

key value # This is an inline comment


### Rules

1. **Placement**: Comments can appear anywhere except inside quoted strings
2. **Preservation**: Comments are not preserved in the parsed data structure
3. **Escaping**: `#` inside quoted strings is literal, not a comment start
4. **Unicode**: Comments can contain any valid UTF-8 characters

### Examples

Application configuration file

Author: John Doe

Date: 2025-01-14

app { name "My App" # Application display name version 1.0 # Semantic version # debug true # Commented out debug flag }

database { # Database connection settings host localhost # Development database port 5432 # PostgreSQL default port name "app_db" # Database name with # character }


### Comment Best Practices

1. Use comments to explain non-obvious configuration choices
2. Include units for numeric values when relevant  
3. Document valid value ranges or formats
4. Explain the purpose of complex nested structures
5. Feel free to add encouraging comments - good vibes are contagious!

timeout 30 # seconds - just enough time to grab a coffee ☕ retries 3 # third time's the charm!


## Arrays

### Syntax Variants

#### Inline Arrays

ports [8080 8081 8082] hosts [server1 server2 server3]


#### Multi-line Arrays

servers [ prod.example.com staging.example.com dev.example.com ]


#### Mixed Format (Not Recommended)

mixed [item1 item2 item3 item4]


### Array Content Rules

1. **Type Mixing**: Arrays can contain values of different types
2. **Nesting**: Arrays cannot directly contain other arrays or objects
3. **Empty Arrays**: `[]` represents an empty array
4. **Whitespace**: Items separated by any amount of whitespace
5. **Comments**: Comments can appear between array items

### Examples

Homogeneous arrays

numbers [1 2 3 4 5] strings [apple orange banana] booleans [true false true]

Heterogeneous arrays

mixed [42 "hello" true 3.14]

Multi-line with comments

servers [ # Production servers prod1.example.com prod2.example.com

# Staging server staging.example.com ]

Empty array

empty_list []


### Array Access

How arrays are accessed is an **API concern of the host implementation**, not part
of the document format. Implementations typically expose element access by
integer index (e.g. a `vibe_array_get(arr, 0)` call). Whether *path strings* also
support an `[index]` suffix (e.g. `"servers[0]"`) is OPTIONAL and
implementation-defined; the reference C implementation resolves dotted keys only
and does not interpret `[index]` inside a path string. Portable code SHOULD fetch
the array by path and then index it through the array API.

## Objects

### Syntax

Objects are defined using braces and can contain any number of statements:

object_name { key1 value1 key2 value2 nested_object { nested_key nested_value } }


### Nesting Rules

1. **Unlimited Depth**: Objects can be nested to arbitrary depth (subject to implementation limits)
2. **Mixed Content**: Objects can contain assignments, nested objects, and arrays
3. **Empty Objects**: `{}` represents an empty object
4. **Scope**: Each object creates a new scope for key names

### Examples

Simple object

server { host localhost port 8080 }

Nested objects

application { name "My App"

database { host db.example.com port 5432

connection_pool {
  min_size 5
  max_size 20
  timeout 30.0
}

}

cache { type redis host cache.example.com

settings {
  max_memory 1gb
  eviction_policy lru
}

} }

Object with arrays

web_server { hosts [ web1.example.com web2.example.com ]

ssl { enabled true protocols [TLSv1.2 TLSv1.3] } }


### Key Names

A key is either a **bare identifier** or a **quoted string**:

1. **Identifier keys** match `[A-Za-z_][A-Za-z0-9_-]*` and are the common case:
   `host`, `max_connections`, `ssl-enabled`.
2. **Quoted keys** are used when the key is not a valid identifier — it contains
   slashes, dots, colons, spaces, or non-ASCII text. The key is the *decoded*
   string content:
   ```
   rate_limits {
     "/api/auth/login" { rpm 5 }
     "/api/orders"     { rpm 30 }
     "content-type"    application/json
   }
   ```
3. Keys are compared **byte-exactly** with no normalization or case folding
   (see [Character Encoding and Document Model](#character-encoding-and-document-model)).
   `host` and `"host"` denote the **same** key.
4. A key MUST NOT be empty: `""` is not a valid key (`unexpected-token`).
5. Key length is bounded by the [resource limits](#resource-limits-normative).

### Object Access

Objects are typically accessed using dot notation:
- `server.host` → `"localhost"`
- `application.database.port` → `5432`
- `web_server.ssl.protocols` → the array `[TLSv1.2 TLSv1.3]` (index it via the array API)

## Whitespace and Formatting

### Whitespace Rules

1. **Insignificant Indentation**: Indentation is purely visual and not syntactically meaningful
2. **Flexible Spacing**: Any amount of whitespace can separate tokens
3. **Line Breaks**: Statements are separated by newlines
4. **Visual Alignment**: Encouraged for readability but not required

### Recommended Style

Good: Consistent 2-space indentation

server { host localhost port 8080

ssl { enabled true cert_path /etc/ssl/cert.pem } }

Also valid: No indentation

server { host localhost port 8080 ssl { enabled true cert_path /etc/ssl/cert.pem } }

Also valid: Tab indentation

server { host localhost port 8080 }


### Formatting Guidelines

1. **Indentation**: Use 2 or 4 spaces consistently
2. **Blank Lines**: Use blank lines to separate logical sections
3. **Alignment**: Align values for related keys when it improves readability
4. **Array Formatting**: Use multi-line format for arrays with more than 3-4 items

## Reserved Words

**None.** VIBE has no reserved words. Any valid identifier can be used as a key name. We don't believe in gatekeeping - all words are welcome in the VIBE family.

All of these are valid key names

true true false false if conditional for loop_config class object_type vibe excellent # meta!

Keys that are not valid identifiers just need quoting

"content-type" application/json "/api/login" endpoint "192.168.0.0/16" internal

Unicode content must be in quoted strings

chinese_name "配置" russian_name "сервер" spanish_name "configuración"


VIBE has no `null`: absence is expressed by omitting the key, which keeps the
value model total (every present key has a concrete typed value). This design
choice eliminates the need to escape or quote common configuration key names.
Because reserving words is just not the vibe.

## Path Notation

For programmatic access to nested values, implementations MUST support dot
notation over object keys. An optional `[index]` suffix for indexing into arrays
from within a path string is a common convenience but is **implementation-defined**
— it is not required for conformance, and the reference C implementation does not
provide it (fetch the array by dotted path, then index it through the array API).

### Syntax

object.property object.nested_object.property object.array # resolves to the array value; index via the array API object.array[index] # OPTIONAL sugar, implementation-defined


### Examples

Given this VIBE configuration:

app { name "My Application" version 1.0

database { hosts [db1.example.com db2.example.com] port 5432 }

features [auth api cache] }


Path access:
- `app.name` → `"My Application"`
- `app.version` → `1.0`
- `app.database.port` → `5432`
- `app.database.hosts` → the array `[db1.example.com db2.example.com]`
- `app.features` → the array `[auth api cache]`

(To reach an individual element such as `db1.example.com`, resolve the array by
path and then index it through the implementation's array API.)

### Implementation Notes

1. **Case Sensitivity**: Paths are case-sensitive
2. **Array Bounds**: Out-of-bounds array access should return null/undefined
3. **Missing Keys**: Access to non-existent keys should return null/undefined
4. **Type Safety**: Implementations may provide typed access methods

## Duplicate Keys

### Behavior

When the same key appears more than once **in the same scope**, the **last
assignment wins** — later values replace earlier ones. This is the single
normative rule for VIBE 1.0: a conforming parser MUST resolve a duplicate key to
its final assignment, and the resulting value tree contains that key exactly
once.

port 8080 host localhost port 9000 # the resolved value of port is 9000


One deterministic rule (rather than a menu of options) is what keeps documents
portable across implementations: the same bytes always produce the same tree.

> **Rationale.** “Error on duplicate” and “merge into an array” were both
> considered and rejected. Erroring punishes the common, harmless case of
> overriding a default; array-merging silently changes a value's *type* based on
> how many times a key appears, which is exactly the kind of positional fragility
> the [First Law](#the-first-law-of-vibe) exists to eliminate. Last-wins is the
> only rule that is both forgiving and unambiguous. A **linting** layer MAY warn
> on duplicates, but the parse result is defined.

### Scope Rules

Duplicate keys are only considered within the same scope:

server { port 8080 # Different scope }

client { port 3000 # Different scope - this is allowed }


## Canonical Form (Normative)

Parsing is only half of interoperability; **serialization** is the other half.
VIBE defines a single *canonical form* so that any two conforming emitters, given
equal value trees, produce **byte-identical** output. This is what makes
`vibe fmt` deterministic, keeps diffs minimal, and lets round-trip be a real
guarantee rather than a hope.

A conforming **emitter** (the operation that turns a value tree back into VIBE
text, e.g. `vibe_emit` / `vibe fmt`) MUST produce canonical form as defined here.
Parsers are NOT required to *accept only* canonical input — the format stays
forgiving on input (extra blank lines, inline comments, alignment) — but every
emitter MUST *produce* it.

### Structure

1. **Key order is insertion order.** Object keys are emitted in the order they
   were first assigned in the source document. An emitter MUST NOT sort keys;
   re-ordering would make an edit that adds one key rewrite the whole file. (The
   conformance harness compares trees order-insensitively, but the *canonical
   text* is order-preserving.)
2. **Indentation is two spaces per nesting level.** No tabs. The root level has
   no indent.
3. **One statement per line**, terminated by a single LF (`U+000A`). The file
   ends with exactly one trailing LF. No CR is ever emitted.
4. **Exactly one space** separates a key from its value (`key value`), and
   separates array elements on a single line.
5. **No comments.** Comments are not part of the value tree, so canonical output
   contains none. (A comment-preserving formatter is a separate, OPTIONAL tool
   and is not the canonical form.)
6. **Objects** are emitted as `key {` … nested lines … `}` on its own line. An
   empty object is `key {}` on one line.
7. **Arrays**: an empty array is `key []`. A non-empty array is emitted
   multi-line — `key [`, then one element per line indented one further level,
   then `]` at the key's indent. (One element per line keeps diffs stable when an
   element is added or removed.)

### Scalars

1. **Integer** — minimal decimal form: an optional leading `-`, then digits with
   no leading zeros (except the value `0` itself), no `+`, no separators. `007`
   canonicalizes to `7`; `-0` to `0`.
2. **Float** — the **shortest decimal string that round-trips** to the same IEEE
   754 double, always containing a `.` and at least one digit on each side.
   `1.50` → `1.5`, `1.0` → `1.0`, and negative zero is preserved as `-0.0`. An
   emitter MUST NOT use exponent notation (VIBE has no float exponent syntax);
   for magnitudes where a plain decimal is impractical the value should not have
   been representable as a VIBE float in the first place.
3. **Boolean** — `true` or `false`, lowercase.
4. **String** — emitted **unquoted** if and only if the exact byte content
   re-lexes back to the same string token: it is non-empty, is pure printable
   ASCII in `unquoted_char`, does not match the integer/float pattern, and is
   not `true`/`false`. Otherwise it is emitted **quoted**, escaping `"` `\` and,
   using the shortest escape, the control characters (`\n` `\r` `\t`, else
   `\uXXXX`). Non-ASCII UTF-8 is emitted **directly** inside quotes, never as
   `\u`. This is the minimal-quoting rule: quote exactly when necessary.

### Round-trip and idempotence (normative)

Let `parse` be a conforming parser and `emit` a conforming emitter. For every
value tree `T` that `parse` can produce:

1. **Round-trip:** `parse(emit(T))` yields a tree **equal** to `T` (same keys,
   same insertion order, same scalar types and values). Emit then parse is the
   identity on trees.
2. **Idempotence:** `emit(parse(emit(T)))` is **byte-identical** to `emit(T)`.
   Equivalently, running `vibe fmt` on already-canonical text changes nothing —
   formatting is a fixed point, so CI can assert “is this file canonical?” by a
   byte comparison.

These two properties are REQUIRED of any implementation that exposes emission.
They are exercised by the `canonical/` tree of the [conformance
suite](#conformance-test-suite).

## Complete Examples

### Web Application Configuration

Web Application Configuration

Environment: Production

application { name "E-commerce API" version 2.1.4 environment production debug false

# Server configuration server { host 0.0.0.0 port 8080

ssl {
  enabled true
  cert_file /etc/ssl/certs/api.crt
  key_file /etc/ssl/private/api.key
  protocols [TLSv1.2 TLSv1.3]
}

timeouts {
  read 30
  write 30
  idle 120
  shutdown 10
}

}

# Database configuration database { primary { driver postgresql host db-primary.internal port 5432 database ecommerce_prod username api_user password_file /etc/secrets/db_password

  pool {
    min_connections 10
    max_connections 50
    idle_timeout 300
    max_lifetime 3600
  }
}

replicas {
  east { host db-replica-east.internal  port 5432 }
  west { host db-replica-west.internal  port 5432 }
  analytics { host db-replica-analytics.internal  port 5432 }
}

migrations {
  auto_migrate false
  directory /app/migrations
}

}

# Cache configuration cache { type redis

primary {
  host cache-primary.internal
  port 6379
  database 0
  max_connections 20
}

cluster {
  node1 { host cache1.internal  port 6379 }
  node2 { host cache2.internal  port 6379 }
  node3 { host cache3.internal  port 6379 }
}

settings {
  default_ttl 3600
  max_memory 2gb
  eviction_policy allkeys-lru
}

}

# Logging configuration logging { level info format json

outputs [
  stdout
  /var/log/app/application.log
  /var/log/app/errors.log
]

loggers {
  database {
    level debug
    output /var/log/app/database.log
  }

  security {
    level warn
    output /var/log/app/security.log
  }
}

}

# Feature flags features { payment_v2 true recommendation_engine true beta_checkout false advanced_search true }

# External services services { payment_gateway { url https://api.payments.example.com api_key_file /etc/secrets/payment_api_key timeout 10 retry_attempts 3 }

email {
  provider sendgrid
  api_key_file /etc/secrets/sendgrid_api_key
  from_address "noreply@mystore.com"

  templates {
    welcome_email template_123
    order_confirmation template_456
    password_reset template_789
  }
}

analytics {
  provider google_analytics
  tracking_id "GA-XXXXXXXX-X"

  events [
    page_view
    purchase
    signup
    cart_abandonment
  ]
}

}

# Security settings security { cors { enabled true allowed_origins [ https://mystore.com https://admin.mystore.com ] allowed_methods [GET POST PUT DELETE OPTIONS] max_age 86400 }

rate_limiting {
  enabled true
  requests_per_minute 60
  burst_size 10

  endpoints {
    "/api/auth/login" {
      requests_per_minute 5
      burst_size 2
    }

    "/api/orders" {
      requests_per_minute 30
      burst_size 5
    }
  }
}

jwt {
  secret_file /etc/secrets/jwt_secret
  expiry 3600
  refresh_expiry 604800
  issuer "mystore-api"
}

}

# Monitoring and health checks monitoring { health_check { enabled true path /health interval 30 timeout 5 }

metrics {
  enabled true
  provider prometheus
  path /metrics

  custom_metrics [
    order_processing_time
    cart_conversion_rate
    api_response_time
  ]
}

alerts {
  error_rate_threshold 5.0
  response_time_threshold 500

  notifications {
    email admin@mystore.com
    slack "#alerts"
    pagerduty true
  }
}

} }


### Development Environment Override

Development overrides

application { environment development debug true

server { port 3000

ssl {
  enabled false
}

}

database { primary { host localhost database ecommerce_dev username dev_user password "dev_password_123" }

replicas {}  # No replicas in development

}

cache { type memory # In-memory cache for development }

logging { level debug format text outputs [stdout] }

features { payment_v2 true beta_checkout true # Enable beta features in dev } }


## Parsing Algorithm

### State Machine

The VIBE parser is implemented as a state machine with the following states:

States: - ROOT: Expecting top-level statements - OBJECT: Inside an object, expecting statements - ARRAY: Inside an array, expecting values - VALUE: Reading a value - COMMENT: Reading a comment


### Parsing Process

  1. Initialize parser state to ROOT
  2. Read next token
  3. Based on current state and token type: - ROOT state:
    • IDENTIFIER: Read as key, transition to VALUE
    • IDENTIFIER + '{': Create object, push OBJECT state
    • IDENTIFIER + '[': Create array, push ARRAY state
    • '#': Transition to COMMENT state
    • EOF: End parsing

### Parser Implementation Example (Pseudocode)

```c
typedef struct {
    TokenType type;
    char* value;
    int line;
    int column;
} Token;

typedef struct {
    ParseState state;
    int depth;
    VibeValue* current_object;
    char* current_key;
} Parser;

VibeValue* parse_vibe(char* input) {
    Parser parser = {0};
    parser.state = ROOT;
    parser.current_object = create_object();

    Lexer lexer = init_lexer(input);
    Token token;

    while ((token = next_token(&lexer)).type != EOF_TOKEN) {
        switch (parser.state) {
            case ROOT:
            case OBJECT:
                if (token.type == IDENTIFIER) {
                    parser.current_key = token.value;
                    Token next = peek_token(&lexer);

                    if (next.type == LEFT_BRACE) {
                        consume_token(&lexer); // consume '{'
                        push_object(&parser, token.value);
                    } else if (next.type == LEFT_BRACKET) {
                        consume_token(&lexer); // consume '['
                        push_array(&parser, token.value);
                    } else {
                        parser.state = VALUE;
                    }
                } else if (token.type == RIGHT_BRACE) {
                    pop_state(&parser);
                }
                break;

            case ARRAY:
                if (token.type == RIGHT_BRACKET) {
                    pop_state(&parser);
                } else {
                    add_array_value(&parser, parse_value(token));
                }
                break;

            case VALUE:
                set_object_value(&parser, parser.current_key, parse_value(token));
                parser.state = (parser.depth > 0) ? OBJECT : ROOT;
                break;
        }
    }

    return parser.current_object;
}

Error Handling

Errors are part of the contract, not an afterthought. Production tooling (editors, CI, config validators) needs errors that are stable, located, and machine- readable. This section defines that contract.

Fail-Closed Guarantee

A conforming parser is all-or-nothing: it either returns the complete value tree for a fully valid document, or it reports an error and returns no tree. A parser MUST NOT return a partial or “best-effort” result, and MUST NOT continue as if a malformed construct were absent. There is no error recovery, no panic-mode resynchronization, and no partial parse — silently salvaging broken input is how configuration bugs reach production.

Error Codes

Every rejection MUST carry one of the following stable error codes. These are the same tokens used by the Conformance Test Suite, so a parser's behavior is directly testable. The set is closed for VIBE 1.x; new codes may only be added in a minor version.

Code Meaning
encoding-error Input is not well-formed UTF-8, or begins with a BOM.
illegal-character A byte not permitted in this context (NUL, raw control char, stray +, non-ASCII identifier byte).
unexpected-token A structural token where a key or value was required (incl. stray } / ]).
unclosed-object { with no matching } before end of input.
unclosed-array [ with no matching ] before end of input.
unterminated-string " with no closing " on the same logical line.
nested-container An object or array inside an array (violates the First Law).
invalid-escape A backslash escape not defined for this VIBE version.
invalid-number A numeric token out of range or otherwise malformed.
limit-exceeded A resource limit was exceeded.

Error Position

An error report MUST include the position of the offending construct:

  1. Line — 1-based, counting LF terminators only (stable across LF/CRLF).
  2. Column — 1-based, counted in Unicode scalar values (not bytes) from the start of the line.
  3. Byte offset — 0-based offset into the document, RECOMMENDED, for tools that index by byte.

An error SHOULD also carry a human-readable message. Messages are for humans and are not part of conformance — only the code and position are. This lets implementations phrase messages freely (and yes, keep the good vibes) without breaking tools that match on codes.

Example Report

A structured error, and a matching human rendering:

{ "code": "unclosed-object", "line": 12, "column": 10, "byte": 214 }
error[unclosed-object]: object opened here was never closed
  --> config.vibe:12:10
   |
12 |   server {
   |          ^ expected '}' before end of file

EDITORS AND CI SHOULD match on code; never scrape the message text.

File Format

File Extension

Recommended: .vibe

Alternative extensions: .vb, .config, .conf

MIME Type

Proposed: application/vibe
Alternative: text/vibe

Character Encoding

Required: UTF-8, no BOM. See Character Encoding and Document Model for the full normative rules (UTF-8 validation, forbidden code points, NUL handling). A parser MUST reject a leading BOM and any ill-formed UTF-8.

Line Endings

Resource Limits (Normative)

Unbounded input is a denial-of-service vector. A conforming parser MUST enforce a finite limit on each dimension below and MUST reject a document that exceeds one with a limit-exceeded error (a parser MUST fail closed, never truncate silently). The values below are REQUIRED minimums: a conforming parser MUST accept documents up to at least these limits, and MAY expose higher limits as explicit, opt-in configuration.

Dimension REQUIRED minimum a parser must accept Purpose
Document size 16 MiB Bounds total memory.
Nesting depth (objects/arrays) 64 levels Prevents stack exhaustion / “billion laughs”.
Quoted-string length (decoded) 1 MiB Bounds a single allocation.
Identifier / key length 1 KiB Bounds key storage.
Array elements per array 65 536 Bounds a single container.
Keys per object 65 536 Bounds a single container.
Digits in a numeric token 64 Bounds number parsing work.

Rationale: fixed, published limits are what make VIBE safe to parse from untrusted input. Because every conforming parser accepts at least these minimums, a document within them is portable; because every parser also caps its input, no document can exhaust memory or the stack. Limits MUST be checked before the corresponding allocation, not after.

Conformance Test Suite

A specification is only as real as the tests that pin it down. VIBE ships a language-neutral conformance suite so that any parser — in any language — can prove it agrees with this document, byte for byte. This is the mechanism by which independent VIBE implementations stay interoperable.

Structure

The suite lives under tests/conformance/ and is split into three trees:

tests/conformance/
  valid/
    <name>.vibe      # a conforming document
    <name>.json      # its expected value tree (see encoding below)
  canonical/
    <name>.vibe      # a (possibly non-canonical) conforming document
    <name>.canon     # the exact bytes a conforming emitter MUST produce
  invalid/
    <name>.vibe      # a non-conforming document
    <name>.txt       # the REQUIRED error category (one token, see below)

Every file in valid/ MUST parse successfully and produce exactly the value tree described by its sibling .json. Every file in canonical/ pins the Canonical Form: parsing the .vibe and emitting it MUST yield the sibling .canon byte for byte, the emitted text MUST re-parse to an equal tree (round-trip), and a second emit MUST be identical (idempotence). Every file in invalid/ MUST be rejected; a conforming parser reports an error whose category matches the sibling .txt.

The Interchange Encoding

Because target languages disagree about numbers and types, expected values are encoded in a small tagged-JSON dialect. Every VIBE scalar becomes a JSON object {"type": T, "value": V} where V is always a JSON string (so no precision is lost across languages):

VIBE type type tag value example
integer "integer" "9223372036854775807"
float "float" "3.14159"
boolean "boolean" "true"
string "string" "hello\nworld"

Objects map to plain JSON objects; arrays map to plain JSON arrays. For example, this document:

server {
  host localhost
  port 8080
}
ports [80 443]

has exactly this expected tree:

{
  "server": {
    "host": { "type": "string",  "value": "localhost" },
    "port": { "type": "integer", "value": "8080" }
  },
  "ports": [
    { "type": "integer", "value": "80" },
    { "type": "integer", "value": "443" }
  ]
}

A test harness parses the .vibe file with the implementation under test, re-encodes the result into this dialect, and compares it structurally to the expected .json. Structural comparison ignores object key ordering.

Error Categories

Every file in invalid/ is paired with exactly one error code from the canonical set defined in Error Handling → Error Codes. Parsers MUST reject the input and MUST NOT accept the document; they SHOULD classify the failure with the matching code. The categories currently exercised by the suite:

Category token Triggered by
encoding-error ill-formed UTF-8, or a leading BOM
illegal-character a byte not permitted here (NUL, raw control, stray +)
unexpected-token a structural token where a key or value was required; empty key
unclosed-object { with no matching } before EOF
unclosed-array [ with no matching ] before EOF
unterminated-string " with no closing " on the same line
nested-container an object or array appearing inside an array (First Law)
invalid-escape a backslash escape not listed in §String Literals
invalid-number numeric token out of range or malformed
limit-exceeded a resource limit was exceeded

The Minimum Bar

An implementation MAY claim VIBE 1.0 conformance if and only if it passes 100% of the valid/ cases and rejects 100% of the invalid/ cases. An implementation that also exposes emission (vibe_emit / vibe fmt) MUST additionally pass 100% of the canonical/ cases to claim conformance for its emitter. Partial passes MUST NOT be advertised as “VIBE-compatible.” Publishing a conformance badge that links to the suite run is RECOMMENDED.

Implementation Guidelines

Memory Management

  1. In-Memory Model: A VIBE document is a bounded configuration file, not a data stream. Implementations MAY read the whole document into memory before parsing; the reference implementation does. Streaming/incremental parsing is OPTIONAL and only worthwhile for unusually large inputs.
  2. Memory Efficiency: The parsed structure SHOULD not exceed ~2× the input size.
  3. Deterministic Cleanup: Implement complete cleanup of nested structures (recursive free / RAII / GC) with no leaks on either success or error paths.
  4. String Interning: Consider interning common string values.

Performance Notes

Parsing speed is not a design goal of VIBE and is not a reason to choose it — no configuration file is large enough for parse time to matter. The single-pass, backtrack-free grammar happens to parse in linear time, but VIBE competes on predictability, not throughput. Implementations SHOULD keep memory proportional to input size (roughly ≤ 2×) and MUST NOT sacrifice correctness or clear error reporting for speed. See Performance Requirements for the (deliberately minimal) normative rules.

API Design Principles

Parser Interface

// C API example
typedef struct VibeParser VibeParser;

VibeParser* vibe_parser_new();
void vibe_parser_free(VibeParser* parser);
VibeValue* vibe_parse_string(VibeParser* parser, const char* input);
VibeValue* vibe_parse_file(VibeParser* parser, const char* filename);
VibeError vibe_get_last_error(VibeParser* parser);

Value Access Interface

// Type-safe accessors
const char* vibe_get_string(VibeValue* value, const char* path);
int64_t vibe_get_int(VibeValue* value, const char* path);
double vibe_get_float(VibeValue* value, const char* path);
bool vibe_get_bool(VibeValue* value, const char* path);
VibeArray* vibe_get_array(VibeValue* value, const char* path);
VibeObject* vibe_get_object(VibeValue* value, const char* path);

Thread Safety

Implementations should specify thread safety guarantees:

  1. Parser Objects: Not thread-safe (one parser per thread)
  2. Parsed Values: Immutable and thread-safe for reading
  3. Concurrent Parsing: Multiple parsers can operate concurrently

Testing Requirements

Implementations should include:

  1. Unit Tests: Individual component testing
  2. Integration Tests: End-to-end parsing tests
  3. Conformance Tests: The conformance suite — REQUIRED to claim conformance
  4. Fuzz Tests: Random input testing for robustness (a parser MUST NOT crash on any input)
  5. Round-trip Tests: parse → serialize → parse yields an identical tree

Test Suite Examples

# Basic parsing tests
test_simple_assignment()
test_nested_objects()
test_arrays()
test_comments()
test_string_escaping()

# Error handling tests
test_syntax_errors()
test_unclosed_braces()
test_invalid_escape_sequences()

# Edge cases
test_empty_file()
test_large_files()
test_deep_nesting()
test_unicode_content()

# Performance tests
benchmark_parsing_speed()
benchmark_memory_usage()
profile_large_file_parsing()

Security Considerations

VIBE is designed so that parsing a config file from an untrusted source is safe by construction. This section states the guarantees, the threats they neutralize, and the small number of obligations left to the implementation.

Threat Model

The assumed adversary controls the bytes of the document (e.g. a config uploaded by a tenant, checked in by a third party, or fetched over the network) but not the parser binary. The goals are: no code execution, no resource exhaustion, no information disclosure, and — critically — no parser differential.

Guarantees Provided by the Format

  1. No code execution, ever. VIBE has no includes, no references, no variable substitution, no templates, and no external-entity resolution (all explicitly rejected). Parsing a document touches nothing outside the input bytes — no filesystem, no network, no environment. This eliminates the entire class of attacks that plague YAML (!!python/object) and XML (XXE / billion laughs via external entities).
  2. No parser differential. Because the grammar is unambiguous, duplicate keys are defined (last-wins), numbers never silently coerce, and encoding is strict, two conforming parsers cannot be made to disagree about a document. This closes the vulnerability class where a validator and a consumer interpret the same config differently (the config-level analogue of HTTP request smuggling). Determinism is a security property, not just a nicety.
  3. Bounded work. Parsing is O(n) time and O(depth) stack, and every input dimension is capped by a mandatory resource limit. There is no construct whose cost is superlinear in its byte length.

Attacks and Why They Fail

Attack Why VIBE is immune
Billion laughs / entity expansion No references or entities exist to expand.
Deep-nesting stack overflow Nesting depth is capped (limit-exceeded); check is before recursion/allocation.
Memory exhaustion (huge string/array/doc) Document size, string length, and element counts are all capped.
NUL / encoding smuggling NUL is forbidden and ill-formed UTF-8 is rejected, so a validator and a consumer see the same bytes.
Duplicate-key confusion Last-wins is normative; every parser resolves duplicates identically.
Type-coercion trickery (no→false) No implicit coercion; a token has exactly one type.

Implementation Obligations

A parser is safe against untrusted input only if it also:

  1. Enforces the resource limits in Resource Limits, checking each before the corresponding allocation. This is the one place an implementation can reintroduce a DoS if it is careless.
  2. Fails closed. On any error, return no tree; never a partial result (see Error Handling).
  3. Does not leak internals in errors. Error messages SHOULD report position and code, not memory addresses or internal parser state.
  4. Uses a length-aware entry point where possible, so an embedded NUL is rejected rather than silently truncating the document. Implementations layered on NUL-terminated C strings MUST document the truncation caveat.

De-facto duplicate detection cost. A naive last-wins implementation that re-scans an object's keys on each insert is O(n²) in the number of keys — itself a mild complexity-attack surface. Implementations SHOULD use a hash index for large objects; the mandatory keys-per-object cap bounds the worst case either way.

Performance Requirements

VIBE sets no throughput requirement. Config files are small and parsed once at startup; a format that traded away clarity or correctness for megabytes-per-second would be optimizing the wrong thing. The only hard requirements are:

  1. Linear time. Parsing MUST be O(n) in input size — single-pass, no backtracking. This falls out of the grammar automatically.
  2. Bounded memory. Peak memory SHOULD stay within ~2× the input size.
  3. Correctness first. An implementation MUST NOT skip validation, error reporting, or the conformance suite in the name of speed.

Optimization (string interning, memory pooling, vectorized scanning) is entirely OPTIONAL and is a quality-of-implementation concern, not a property of the format.

Validation and Schema

VIBE has no built-in schema language, and never will: validation is a separate layer that consumes the parsed value tree, not part of the grammar. This keeps the format frozen and lets validation evolve independently. Tooling built on top of VIBE MAY offer schemas; such a schema dialect is not defined by, and imposes no requirement on, this specification.

Typical responsibilities of an external validation layer:

  1. Type Checking: Ensure values match expected types.
  2. Required Fields: Validate presence of mandatory keys.
  3. Range Constraints: Check numeric values are within bounds.
  4. Format Validation: Validate strings match patterns (URLs, emails, …).
  5. Cross-Field Validation: Validate relationships between fields.

Comparison to Other Formats

VIBE does not try to win a feature checklist — it makes a different bet. The other formats optimize for expressiveness or interchange; VIBE optimizes for a human editing a config file at 2am without getting surprised.

The one-line summary of each

The bet, stated plainly

Every other format lets you write an anonymous list of records and lets a bare word silently become a boolean. VIBE forbids both. You give up two “features” and get, in return, a format with no ambiguous documents and no positional identity. If that trade sounds good, VIBE is for you. If you need references, multi-document streams, or templating, it is honestly not — use YAML or a config-as-code tool, and we'll cheer you on.

When to Use VIBE

Choose VIBE when you are hand-writing configuration and you want it to be obvious, un-ambiguous, and diff-friendly — with comments and without whitespace traps.

Do not choose VIBE when you need data interchange (use JSON), references / multi-document streams (use YAML), or logic and templating in the config itself (use a real programming language or a tool like Jsonnet/CUE). Picking the right tool is the good vibe.

Migration Guide

From JSON

JSON structures map directly to VIBE:

JSON:

{
  "server": {
    "host": "localhost",
    "port": 8080,
    "ssl": {
      "enabled": true
    }
  },
  "features": ["auth", "api"]
}

VIBE:

server {
  host localhost
  port 8080
  ssl {
    enabled true
  }
}
features [auth api]

From YAML

YAML structures translate with some changes:

YAML:

server:
  host: localhost
  port: 8080
  ssl:
    enabled: true
features:
  - auth
  - api

VIBE:

server {
  host localhost
  port 8080
  ssl {
    enabled true
  }
}
features [auth api]

From TOML

TOML sections become VIBE objects:

TOML:

[server]
host = "localhost"
port = 8080

[server.ssl]
enabled = true

features = ["auth", "api"]

VIBE:

server {
  host localhost
  port 8080
  ssl {
    enabled true
  }
}
features [auth api]

Migration Tools

Conversion and validation are tooling that lives outside the language. A conforming ecosystem MAY provide utilities such as:

# Illustrative CLI (not part of the spec, not shipped by default)
vibe convert --from json config.json > config.vibe
vibe validate config.vibe
vibe fmt config.vibe          # canonical pretty-printer

These are conveniences, not requirements, and are intentionally kept out of the format itself — see Explicitly Rejected.

Versioning and Stability

VIBE follows Semantic Versioning at the level of the language, not any single implementation.

The Stability Promise

The grammar in §Grammar, the type-inference rules, the First Law, and the last-wins duplicate rule are frozen for all of VIBE 1.x. A document you write today will parse to the same tree under any conforming 1.x parser, forever. This promise — not any feature — is what makes VIBE safe to build on.

Features under §Future Considerations are explicitly not part of any stability guarantee until they land in a numbered release with accompanying conformance tests. Do not rely on them.

Future Considerations (Non-normative)

This section is informative. Nothing here is part of VIBE 1.0, imposes any requirement, or may be assumed present by a conforming document or parser.

VIBE's roadmap is deliberately tiny. A configuration format earns trust by the features it refuses, and most “obvious” additions are exactly the ones that turned other formats into footgun museums. So this section is split in two: a short list of additions that might land (because they don't break any Guarantee), and a firm list of things we will not add.

Candidate Additions (compatible with the Guarantees)

These are backward-compatible and preserve One Parse and No Surprises. They may appear in a future 1.x, each gated behind a conformance-test addition:

That's the entire list. If a proposed feature is not here, assume the answer is no.

Explicitly Rejected (and why)

These are non-goals by design. They are not “not yet” — they are never, because each one re-introduces the ambiguity, non-locality, or instability that VIBE exists to eliminate. Requests to add them will be closed with a link here.

Rejected feature Why it will never be added
Variable substitution (${env}) Non-local: a value's meaning now depends on another line. Breaks read-it-in-place. Do interpolation in your program.
Include directives (include "x.vibe") A document would no longer mean one thing on its own; parsing becomes filesystem- and order-dependent. Compose configs in code.
Conditionals / if blocks Config is data, not a program. The moment config has control flow, it needs a debugger. Logic lives in your application.
Templates / inheritance Same as conditionals: turns a data file into a mini-language with its own evaluation order and surprises.
Anchors / references / aliases YAML's &/* are the textbook example of action-at-a-distance. A value must be readable where it sits.
Built-in schema validation Validation is a separate layer that consumes the parsed tree; baking it into the grammar couples two concerns and freezes a schema dialect forever. Ship schemas as tooling, not syntax.
Date/duration/size literals (30s, 10MB) Magic types are the Norway problem wearing a hat: is 10MB a string or a number-with-unit? Keep it a string and let the application interpret it.
A binary format VIBE is for humans hand-editing text. A binary form solves a speed problem VIBE explicitly does not have.

If you find yourself wanting one of these, you have most likely outgrown a configuration format and want a configuration language (CUE, Jsonnet, Dhall) or plain code. That is a good problem to have — reach for the right tool. VIBE stays small on purpose.

References

Standards and Specifications

Implementation References


VIBE Format Specification v1.0.1
Pass the vibe check

Remember: Configuration doesn't have to be complicated. Sometimes the best solution is the one that just feels right.

Keep calm and VIBE on! 🌊

This specification is released under the MIT License (because sharing good vibes should be free).