Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The rest of this page reads these two files.

## Why zig-hocon

- **Correct.** Passes 444 of the 445 tests in the [conformance suite](https://htmlpreview.github.io/?https://github.com/lejmr/zig-hocon/blob/main/conformance/report.html), one test for each sentence of the spec.\*
- **Correct.** Passes 445 of the 446 tests in the [conformance suite](https://htmlpreview.github.io/?https://github.com/lejmr/zig-hocon/blob/main/conformance/report.html), one test for each sentence of the spec.\*
- **Your schema is a Zig struct.** HOCON is only what sits on disk; the struct says what it holds, with defaults, optionals, enums, durations and sizes.
- **Fast.** A 10 MB config in a quarter of a second.\*\*
- **Clear errors.** File, line, column and a caret, also for a value of the wrong type.
Expand Down Expand Up @@ -211,13 +211,13 @@ b.step("check-config", "Check the config against Config").dependOn(&check.step);

| implementation | against the spec | against typesafe/config 1.4.9 |
|---|---|---|
| Java <sub>typesafe/config 1.4.9</sub> | 90.1% (401/445) | 99.8% (444/445) |
| hocon2 <sub>hocon2 0.6.3</sub> | 86.7% (386/445) | 80.7% (359/445) |
| pulse-pyhocon <sub>pulse-pyhocon 0.2.1</sub> | 71.0% (316/445) | 68.5% (305/445) |
| py.hocon <sub>hocon-parser 1.14.2 (Python)</sub> | 89.2% (397/445) | 88.5% (394/445) |
| pyhocon <sub>pyhocon 0.3.63</sub> | 70.6% (314/445) | 66.7% (297/445) |
| rs.hocon <sub>hocon-parser 1.14.2 (Rust)</sub> | 89.2% (397/445) | 86.5% (385/445) |
| zig-hocon <sub>zig-hocon code@20663cc</sub> | 99.8% (444/445) | 90.1% (401/445) |
| Java <sub>typesafe/config 1.4.9</sub> | 89.9% (401/446) | 99.8% (445/446) |
| hocon2 <sub>hocon2 0.6.3</sub> | 86.5% (386/446) | 80.5% (359/446) |
| pulse-pyhocon <sub>pulse-pyhocon 0.2.1</sub> | 70.9% (316/446) | 68.4% (305/446) |
| py.hocon <sub>hocon-parser 1.14.2 (Python)</sub> | 89.0% (397/446) | 88.3% (394/446) |
| pyhocon <sub>pyhocon 0.3.63</sub> | 70.4% (314/446) | 66.8% (298/446) |
| rs.hocon <sub>hocon-parser 1.14.2 (Rust)</sub> | 89.0% (397/446) | 86.3% (385/446) |
| zig-hocon <sub>zig-hocon code@031f68c</sub> | 99.8% (445/446) | 89.9% (401/446) |

The table keeps the best parsers; the others measured, and why they are not in it: [conformance/README.md](conformance/README.md#not-in-the-table).

Expand Down
4 changes: 2 additions & 2 deletions conformance/maintaining/COVERAGE.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ <h3>Unquoted strings</h3>
forbidden character or the two-character string &quot;//&quot; is
encountered.</mark> <mark class="ok" title="unquoted-strings.11 — unquoted-strings/010-embedded-null-not-recognized.conf">Embedded (non-initial) booleans, nulls, and numbers
are not recognized as such, they are part of the string.</mark></p>
<p data-line="270"><mark class="ok" title="unquoted-strings.12 — unquoted-strings/012-unquoted-cannot-start-with-hyphen.conf, unquoted-strings/018-leading-dot-is-not-a-number.conf">An unquoted string may not _begin_ with the digits 0-9 or with a
<p data-line="270"><mark class="ok" title="unquoted-strings.12 — unquoted-strings/012-unquoted-cannot-start-with-hyphen.conf, unquoted-strings/018-leading-dot-is-not-a-number.conf, unquoted-strings/025-digit-initial-invalid-number.conf">An unquoted string may not _begin_ with the digits 0-9 or with a
hyphen (`-`, 0x002D) because those are valid characters to begin a
JSON number.</mark> <mark class="ok" title="unquoted-strings.13 — unquoted-strings/013-negative-number-then-string.conf, unquoted-strings/021-plus-inside-a-number-illegal.conf, unquoted-strings/022-digit-then-exponent-letter-illegal.conf">The initial number character, plus any valid-in-JSON
JSON number.</mark> <mark class="ok" title="unquoted-strings.13 — unquoted-strings/013-negative-number-then-string.conf, unquoted-strings/021-plus-inside-a-number-illegal.conf, unquoted-strings/022-digit-then-exponent-letter-illegal.conf, unquoted-strings/025-digit-initial-invalid-number.conf">The initial number character, plus any valid-in-JSON
number characters that follow it, must be parsed as a number
value.</mark> Again, these characters are not special _inside_ an
unquoted string; they only trigger number parsing if they appear
Expand Down
6 changes: 5 additions & 1 deletion conformance/maintaining/FINDINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

What the validation pass turned up and did **not** fix. The divergences it was confident about are already in the sidecars; everything below is work, not record.

**Since then:** proofreading the suite against `spec/HOCON.md` found a rule no case pinned, where zig-hocon and pyhocon part ways. It is covered now:

- `unquoted-strings/025-digit-initial-invalid-number` — a value that begins with a digit but is not a valid number (`a = 1.2.3`, the shape of `127.0.0.1`) is an error: the initial number character and the number characters after it "must be parsed as a number value" (unquoted-strings.12, .13). pyhocon and typesafe/config read it as the string "1.2.3"; the row is `java: lenient`.

This pass ran *before* the tie-break rule in `PROCESS.md` was settled, so where a divergence says `expect` was left at java's value, the row is still unresolved — `fill-expected.py` now flags those with `review`.

## syntax-basics
Expand Down Expand Up @@ -75,7 +79,7 @@ The section maps to the spec faithfully: all seventeen cases quote sentences tha

**Uncovered normative sentences**

- "An unquoted string may not _begin_ with the digits 0-9" -- no case covers a digit-initial sequence whose number parse fails (e.g. `a = 1.2.3` or `a = 1x2`). 008 (`10.0bar`) only covers the case where the leading run IS a valid number. This is the digit-side twin of the hyphen divergence in 012 and is where java's number-parse fallback to unquoted text shows again; it is the most valuable uncovered sentence in the range.
- "An unquoted string may not _begin_ with the digits 0-9" -- no case covers a digit-initial sequence whose number parse fails (e.g. `a = 1.2.3` or `a = 1x2`). 008 (`10.0bar`) only covers the case where the leading run IS a valid number. This is the digit-side twin of the hyphen divergence in 012 and is where java's number-parse fallback to unquoted text shows again; it is the most valuable uncovered sentence in the range. _(covered since by 025-digit-initial-invalid-number)_
- "forbidden characters: ... or whitespace" -- whitespace is listed as a forbidden character, but no case pins that whitespace ends an unquoted string (the covered forbidden chars are only '@', '\\', '!', '#'). Hard to isolate because `a = foo bar` becomes a value concatenation, but the list member is currently untested here.
- "its initial characters do not parse as `true`, `false`, `null`, or a number" -- only the `true` arm is exercised (006). No case starts a token with `false` or `null` (e.g. `a = nullfoo`, `a = falsefoo`).

Expand Down
2 changes: 1 addition & 1 deletion conformance/maintaining/SECTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ observable parse behaviour.
| 149 | Commas | `commas` | ✅ 13 cases |
| 165 | Whitespace | `whitespace` | ✅ 13 cases |
| 186 | Duplicate keys and object merging | `duplicate-keys-and-object-merging` | ✅ 28 cases |
| 240 | Unquoted strings | `unquoted-strings` | ✅ 24 cases |
| 240 | Unquoted strings | `unquoted-strings` | ✅ 25 cases |
| 288 | Multi-line strings | `multi-line-strings` | ✅ 9 cases |
| 304 | Value concatenation | `value-concatenation` | ✅ 2 cases |
| 321 | String value concatenation | `string-value-concatenation` | ✅ 18 cases |
Expand Down
12 changes: 6 additions & 6 deletions conformance/report.html

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions conformance/reports/Java.java.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"mode": "java",
"java_version": "1.4.9",
"command": "conformance/adapters/oracle.sh hocon-java",
"ran_at": "2026-10-19T05:59:42Z",
"suite_digest": "cd4bdae3133b2042",
"total": 445,
"passed": 444,
"ran_at": "2026-10-22T19:43:12Z",
"suite_digest": "e05749cb283ceff4",
"total": 446,
"passed": 445,
"percent": 99.8,
"sections": {
"array-and-object-concatenation": {
Expand Down Expand Up @@ -136,8 +136,8 @@
"passed": 6
},
"unquoted-strings": {
"total": 24,
"passed": 24
"total": 25,
"passed": 25
},
"value-concatenation": {
"total": 2,
Expand Down Expand Up @@ -579,6 +579,7 @@
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "pass",
"unquoted-strings/023-unquoted-string-ends-at-end-of-file.conf": "pass",
"unquoted-strings/024-escape-sequence-in-unquoted-string-illegal.conf": "pass",
"unquoted-strings/025-digit-initial-invalid-number.conf": "pass",
"value-concatenation/001-simple-values-concatenate-to-string.conf": "pass",
"value-concatenation/004-string-concatenation-allowed-in-field-keys.conf": "pass",
"whitespace/001-nonbreaking-space-as-whitespace.conf": "pass",
Expand Down Expand Up @@ -639,7 +640,8 @@
"unchanged-from-json/013-trailing-dot-before-exponent-illegal.conf": "{\"a\":-1000}",
"unchanged-from-json/014-negative-leading-zeros-illegal.conf": "{\"a\":-33}",
"unquoted-strings/012-unquoted-cannot-start-with-hyphen.conf": "{\"a\":\"-foo\"}",
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "{\"a\":\"1e\"}"
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "{\"a\":\"1e\"}",
"unquoted-strings/025-digit-initial-invalid-number.conf": "{\"a\":\"1.2.3\"}"
},
"failures": [
{
Expand Down
22 changes: 16 additions & 6 deletions conformance/reports/Java.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"version": "typesafe/config 1.4.9",
"mode": "spec",
"command": "conformance/adapters/oracle.sh hocon-java",
"ran_at": "2026-10-19T05:59:42Z",
"suite_digest": "cd4bdae3133b2042",
"total": 445,
"ran_at": "2026-10-22T19:42:16Z",
"suite_digest": "e05749cb283ceff4",
"total": 446,
"passed": 401,
"percent": 90.1,
"percent": 89.9,
"sections": {
"array-and-object-concatenation": {
"total": 19,
Expand Down Expand Up @@ -135,7 +135,7 @@
"passed": 6
},
"unquoted-strings": {
"total": 24,
"total": 25,
"passed": 22
},
"value-concatenation": {
Expand Down Expand Up @@ -578,6 +578,7 @@
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "fail",
"unquoted-strings/023-unquoted-string-ends-at-end-of-file.conf": "pass",
"unquoted-strings/024-escape-sequence-in-unquoted-string-illegal.conf": "pass",
"unquoted-strings/025-digit-initial-invalid-number.conf": "fail",
"value-concatenation/001-simple-values-concatenate-to-string.conf": "pass",
"value-concatenation/004-string-concatenation-allowed-in-field-keys.conf": "pass",
"whitespace/001-nonbreaking-space-as-whitespace.conf": "pass",
Expand Down Expand Up @@ -638,7 +639,8 @@
"unchanged-from-json/013-trailing-dot-before-exponent-illegal.conf": "{\"a\":-1000}",
"unchanged-from-json/014-negative-leading-zeros-illegal.conf": "{\"a\":-33}",
"unquoted-strings/012-unquoted-cannot-start-with-hyphen.conf": "{\"a\":\"-foo\"}",
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "{\"a\":\"1e\"}"
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "{\"a\":\"1e\"}",
"unquoted-strings/025-digit-initial-invalid-number.conf": "{\"a\":\"1.2.3\"}"
},
"failures": [
{
Expand Down Expand Up @@ -1278,6 +1280,14 @@
"expected": "rejected",
"got": "{\"a\":\"1e\"}",
"java": "lenient"
},
{
"case": "unquoted-strings/025-digit-initial-invalid-number.conf",
"spec": "HOCON.md#unquoted-strings",
"rule": "an unquoted string may not begin with the digits 0-9, and the initial number character plus any valid-in-JSON number characters that follow it must be parsed as a number value -- 1.2.3 is that run, and it is not a number, so the input is rejected rather than read as a string (the everyday shape is an address like 127.0.0.1); typesafe/config and pyhocon both fall back to the unquoted string \"1.2.3\"",
"expected": "rejected",
"got": "{\"a\":\"1.2.3\"}",
"java": "lenient"
}
]
}
20 changes: 11 additions & 9 deletions conformance/reports/pyhocon.java.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
"mode": "java",
"java_version": "1.4.9",
"command": "conformance/adapters/oracle.sh hocon-py",
"ran_at": "2026-10-19T06:04:35Z",
"suite_digest": "cd4bdae3133b2042",
"total": 445,
"passed": 297,
"percent": 66.7,
"ran_at": "2026-10-22T19:40:53Z",
"suite_digest": "e05749cb283ceff4",
"total": 446,
"passed": 298,
"percent": 66.8,
"sections": {
"array-and-object-concatenation": {
"total": 19,
Expand Down Expand Up @@ -136,8 +136,8 @@
"passed": 5
},
"unquoted-strings": {
"total": 24,
"passed": 18
"total": 25,
"passed": 19
},
"value-concatenation": {
"total": 2,
Expand Down Expand Up @@ -579,6 +579,7 @@
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "pass",
"unquoted-strings/023-unquoted-string-ends-at-end-of-file.conf": "pass",
"unquoted-strings/024-escape-sequence-in-unquoted-string-illegal.conf": "fail",
"unquoted-strings/025-digit-initial-invalid-number.conf": "pass",
"value-concatenation/001-simple-values-concatenate-to-string.conf": "pass",
"value-concatenation/004-string-concatenation-allowed-in-field-keys.conf": "pass",
"whitespace/001-nonbreaking-space-as-whitespace.conf": "fail",
Expand Down Expand Up @@ -639,7 +640,8 @@
"unchanged-from-json/013-trailing-dot-before-exponent-illegal.conf": "{\"a\":\"-1.e3\"}",
"unchanged-from-json/014-negative-leading-zeros-illegal.conf": "{\"a\":-33}",
"unquoted-strings/012-unquoted-cannot-start-with-hyphen.conf": "{\"a\":\"-foo\"}",
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "{\"a\":\"1e\"}"
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "{\"a\":\"1e\"}",
"unquoted-strings/025-digit-initial-invalid-number.conf": "{\"a\":\"1.2.3\"}"
},
"failures": [
{
Expand Down Expand Up @@ -2057,7 +2059,7 @@
"a": "null bar"
},
"got": {
"a": "<pyhocon.config_tree.NoneValue object at 0x103471910>bar"
"a": "<pyhocon.config_tree.NoneValue object at 0x102093790>bar"
}
},
{
Expand Down
24 changes: 17 additions & 7 deletions conformance/reports/pyhocon.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"version": "pyhocon 0.3.63",
"mode": "spec",
"command": "conformance/adapters/oracle.sh hocon-py",
"ran_at": "2026-10-19T06:02:11Z",
"suite_digest": "cd4bdae3133b2042",
"total": 445,
"ran_at": "2026-10-22T19:38:06Z",
"suite_digest": "e05749cb283ceff4",
"total": 446,
"passed": 314,
"percent": 70.6,
"percent": 70.4,
"sections": {
"array-and-object-concatenation": {
"total": 19,
Expand Down Expand Up @@ -135,7 +135,7 @@
"passed": 5
},
"unquoted-strings": {
"total": 24,
"total": 25,
"passed": 16
},
"value-concatenation": {
Expand Down Expand Up @@ -578,6 +578,7 @@
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "fail",
"unquoted-strings/023-unquoted-string-ends-at-end-of-file.conf": "pass",
"unquoted-strings/024-escape-sequence-in-unquoted-string-illegal.conf": "fail",
"unquoted-strings/025-digit-initial-invalid-number.conf": "fail",
"value-concatenation/001-simple-values-concatenate-to-string.conf": "pass",
"value-concatenation/004-string-concatenation-allowed-in-field-keys.conf": "pass",
"whitespace/001-nonbreaking-space-as-whitespace.conf": "fail",
Expand Down Expand Up @@ -638,7 +639,8 @@
"unchanged-from-json/013-trailing-dot-before-exponent-illegal.conf": "{\"a\":\"-1.e3\"}",
"unchanged-from-json/014-negative-leading-zeros-illegal.conf": "{\"a\":-33}",
"unquoted-strings/012-unquoted-cannot-start-with-hyphen.conf": "{\"a\":\"-foo\"}",
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "{\"a\":\"1e\"}"
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "{\"a\":\"1e\"}",
"unquoted-strings/025-digit-initial-invalid-number.conf": "{\"a\":\"1.2.3\"}"
},
"failures": [
{
Expand Down Expand Up @@ -1796,7 +1798,7 @@
"a": "null bar"
},
"got": {
"a": "<pyhocon.config_tree.NoneValue object at 0x100ff5910>bar"
"a": "<pyhocon.config_tree.NoneValue object at 0x1020af790>bar"
}
},
{
Expand Down Expand Up @@ -2136,6 +2138,14 @@
"expected": "rejected",
"got": "{\"a\":\"caf\\\\u00e9\"}"
},
{
"case": "unquoted-strings/025-digit-initial-invalid-number.conf",
"spec": "HOCON.md#unquoted-strings",
"rule": "an unquoted string may not begin with the digits 0-9, and the initial number character plus any valid-in-JSON number characters that follow it must be parsed as a number value -- 1.2.3 is that run, and it is not a number, so the input is rejected rather than read as a string (the everyday shape is an address like 127.0.0.1); typesafe/config and pyhocon both fall back to the unquoted string \"1.2.3\"",
"expected": "rejected",
"got": "{\"a\":\"1.2.3\"}",
"java": "lenient"
},
{
"case": "whitespace/001-nonbreaking-space-as-whitespace.conf",
"spec": "HOCON.md#whitespace",
Expand Down
26 changes: 19 additions & 7 deletions conformance/reports/zig-hocon.java.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"suite": "hocon-conformance",
"implementation": "zig-hocon",
"version": "zig-hocon code@20663cc",
"version": "zig-hocon code@031f68c",
"mode": "java",
"java_version": "1.4.9",
"command": "conformance/adapters/zig-hocon.sh",
"ran_at": "2026-10-19T20:48:55Z",
"suite_digest": "cd4bdae3133b2042",
"total": 445,
"ran_at": "2026-10-22T19:44:29Z",
"suite_digest": "e05749cb283ceff4",
"total": 446,
"passed": 401,
"percent": 90.1,
"percent": 89.9,
"sections": {
"array-and-object-concatenation": {
"total": 19,
Expand Down Expand Up @@ -136,7 +136,7 @@
"passed": 6
},
"unquoted-strings": {
"total": 24,
"total": 25,
"passed": 22
},
"value-concatenation": {
Expand Down Expand Up @@ -579,6 +579,7 @@
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "fail",
"unquoted-strings/023-unquoted-string-ends-at-end-of-file.conf": "pass",
"unquoted-strings/024-escape-sequence-in-unquoted-string-illegal.conf": "pass",
"unquoted-strings/025-digit-initial-invalid-number.conf": "fail",
"value-concatenation/001-simple-values-concatenate-to-string.conf": "pass",
"value-concatenation/004-string-concatenation-allowed-in-field-keys.conf": "pass",
"whitespace/001-nonbreaking-space-as-whitespace.conf": "pass",
Expand Down Expand Up @@ -639,7 +640,8 @@
"unchanged-from-json/013-trailing-dot-before-exponent-illegal.conf": "rejected",
"unchanged-from-json/014-negative-leading-zeros-illegal.conf": "rejected",
"unquoted-strings/012-unquoted-cannot-start-with-hyphen.conf": "rejected",
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "rejected"
"unquoted-strings/022-digit-then-exponent-letter-illegal.conf": "rejected",
"unquoted-strings/025-digit-initial-invalid-number.conf": "rejected"
},
"failures": [
{
Expand Down Expand Up @@ -1226,6 +1228,16 @@
},
"got": "rejected (exit 1)",
"java": "lenient"
},
{
"case": "unquoted-strings/025-digit-initial-invalid-number.conf",
"spec": "HOCON.md#unquoted-strings",
"rule": "an unquoted string may not begin with the digits 0-9, and the initial number character plus any valid-in-JSON number characters that follow it must be parsed as a number value -- 1.2.3 is that run, and it is not a number, so the input is rejected rather than read as a string (the everyday shape is an address like 127.0.0.1); typesafe/config and pyhocon both fall back to the unquoted string \"1.2.3\"",
"expected": {
"a": "1.2.3"
},
"got": "rejected (exit 1)",
"java": "lenient"
}
]
}
Loading
Loading