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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

What changed for someone using zig-hocon: the Zig package, the `hocon` command line tool, libhocon for C and the `zig-hocon` Python package, which are released together under one version. The technical detail is in the commits each release links to.

## [Unreleased]

### Fixed

- An object after a substitution, `top = ${x} { … }`, is a merge, the same as `top = ${x}` and `top { … }` written as two fields. A `+=` inside it appends to the list `${x}` gives (`top = ${x} { arr += four }` was `[four]`, is `[one, four]`), a self-reference looks back at it, and `${top.n}` inside it sees the merged object instead of reporting a cycle. Eight new conformance cases pin it.
- Layers of a key are merged before they are resolved, as the spec has it: in `p : { a : ${y} }`, `p : ${x}` with `x.a` set, `${y}` is hidden and no longer evaluated (it was an error), and a reference in the earlier object sees the final value. Three more conformance cases, all 457 pass.

## [0.5.2] - 2026-10-23

The conformance suite has no open question left: the spec settles the last one, `url()` without a scheme, as an error ([lightbend/config#887](https://github.com/lightbend/config/pull/887)), and zig-hocon passes all 446 tests.
Expand Down
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 all 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.
- **Correct.** Passes all 457 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 @@ -210,13 +210,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% (402/446) | 100% (446/446) |
| hocon2 <sub>hocon2 0.6.3</sub> | 86.8% (387/446) | 80.7% (360/446) |
| pulse-pyhocon <sub>pulse-pyhocon 0.2.1</sub> | 71.1% (317/446) | 68.6% (306/446) |
| py.hocon <sub>hocon-parser 1.14.2 (Python)</sub> | 89.2% (398/446) | 88.6% (395/446) |
| pyhocon <sub>pyhocon 0.3.63</sub> | 70.6% (315/446) | 67.0% (299/446) |
| rs.hocon <sub>hocon-parser 1.14.2 (Rust)</sub> | 89.2% (398/446) | 86.5% (386/446) |
| zig-hocon <sub>zig-hocon code@96d9b8a</sub> | 100% (446/446) | 90.1% (402/446) |
| Java <sub>typesafe/config 1.4.9</sub> | 88.4% (404/457) | 100% (457/457) |
| hocon2 <sub>hocon2 0.6.3</sub> | 84.7% (387/457) | 78.8% (360/457) |
| pulse-pyhocon <sub>pulse-pyhocon 0.2.1</sub> | 69.4% (317/457) | 67.0% (306/457) |
| py.hocon <sub>hocon-parser 1.14.2 (Python)</sub> | 87.1% (398/457) | 86.4% (395/457) |
| pyhocon <sub>pyhocon 0.3.63</sub> | 70.0% (320/457) | 65.6% (300/457) |
| rs.hocon <sub>hocon-parser 1.14.2 (Rust)</sub> | 87.1% (398/457) | 84.5% (386/457) |
| zig-hocon <sub>zig-hocon code@5b9031b</sub> | 100% (457/457) | 88.4% (404/457) |

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
14 changes: 7 additions & 7 deletions conformance/maintaining/COVERAGE.html
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ <h4>Array and object concatenation</h4>
substitution, the two values are concatenated.</mark> <mark class="ok" title="array-and-object-concatenation.4 — array-and-object-concatenation/013-newlines-within-value-still-allow-concatenation.conf">Newlines may occur
_within_ the array or object, but not _between_ them.</mark> <mark class="ok" title="array-and-object-concatenation.5 — array-and-object-concatenation/006-newline-between-values-prevents-concatenation.conf, array-and-object-concatenation/017-newline-between-arrays-in-field-value-prevents-concatenation.conf">Newlines
_between_ prevent concatenation.</mark></p>
<p data-line="398"><mark class="ok" title="array-and-object-concatenation.6 — array-and-object-concatenation/007-object-concatenation-merges-and-second-overrides.conf">For objects, &quot;concatenation&quot; means &quot;merging&quot;, so the second object
<p data-line="398"><mark class="ok" title="array-and-object-concatenation.6 — array-and-object-concatenation/007-object-concatenation-merges-and-second-overrides.conf, plus-equals-field-separator/009-appends-through-object-concatenation.conf, plus-equals-field-separator/010-appends-through-chained-object-concatenations.conf, plus-equals-field-separator/011-appends-deep-inside-object-concatenation.conf, self-referential-examples/027-self-reference-inside-object-concatenation.conf, self-referential-examples/028-self-reference-through-two-concatenated-substitutions.conf, self-referential-examples/029-concatenated-object-refers-to-its-mixin.conf, self-referential-examples/030-concatenated-object-sees-its-own-field.conf, self-referential-examples/032-field-hidden-inside-object-concatenation.conf">For objects, &quot;concatenation&quot; means &quot;merging&quot;, so the second object
overrides the first.</mark></p>
<p data-line="401"><mark class="ok" title="array-and-object-concatenation.7 — array-and-object-concatenation/008-array-cannot-be-field-key.conf, array-and-object-concatenation/009-object-cannot-be-field-key.conf">Arrays and objects cannot be field keys, whether concatenation is
involved or not.</mark></p>
Expand Down Expand Up @@ -523,7 +523,7 @@ <h4>Self-Referential Substitutions</h4>
<mark class="ok" title="self-referential-substitutions.13 — self-referential-examples/004-optional-self-reference-disappears-silently.conf">If `${?foo}` refers to itself then it&#x27;s as if it referred to a
nonexistent value.</mark></p>
<h4>The `+=` field separator</h4>
<p data-line="721"><mark class="ok" title="the--field-separator.1 — plus-equals-field-separator/001-plus-equals-desugars-to-optional-self-ref-array.conf">Fields may have `+=` as a separator rather than `:` or `=`.</mark> <mark class="ok" title="the--field-separator.2 — plus-equals-field-separator/005-plus-equals-in-nested-object-with-substitution.conf, plus-equals-field-separator/007-plus-equals-under-a-dotted-key.conf, plus-equals-field-separator/008-plus-equals-with-an-object-containing-a-merge.conf, self-referential-examples/023-append-does-not-hide.conf">A
<p data-line="721"><mark class="ok" title="the--field-separator.1 — plus-equals-field-separator/001-plus-equals-desugars-to-optional-self-ref-array.conf">Fields may have `+=` as a separator rather than `:` or `=`.</mark> <mark class="ok" title="the--field-separator.2 — plus-equals-field-separator/005-plus-equals-in-nested-object-with-substitution.conf, plus-equals-field-separator/007-plus-equals-under-a-dotted-key.conf, plus-equals-field-separator/008-plus-equals-with-an-object-containing-a-merge.conf, plus-equals-field-separator/009-appends-through-object-concatenation.conf, plus-equals-field-separator/010-appends-through-chained-object-concatenations.conf, plus-equals-field-separator/011-appends-deep-inside-object-concatenation.conf, plus-equals-field-separator/012-appends-to-a-list-defined-in-object-concatenation.conf, self-referential-examples/023-append-does-not-hide.conf">A
field with `+=` transforms into a self-referential array
concatenation, like this:</mark></p>
<p data-line="725"> a += b</p>
Expand All @@ -541,7 +541,7 @@ <h4>Examples of Self-Referential Substitutions</h4>
<p data-line="743"> foo : ${foo} // an error</p>
<p data-line="745"><mark class="ok" title="examples-of-self-referential-substitutions.2 — self-referential-examples/002-self-reference-resolves-to-earlier-merged-value.conf">When `foo : ${foo}` is merged with an earlier value for `foo`,
however, the substitution can be resolved to that earlier value.</mark>
<mark class="ok" title="examples-of-self-referential-substitutions.3 — self-referential-examples/002-self-reference-resolves-to-earlier-merged-value.conf">When merging two objects, the self-reference in the overriding
<mark class="ok" title="examples-of-self-referential-substitutions.3 — plus-equals-field-separator/009-appends-through-object-concatenation.conf, plus-equals-field-separator/010-appends-through-chained-object-concatenations.conf, plus-equals-field-separator/011-appends-deep-inside-object-concatenation.conf, plus-equals-field-separator/012-appends-to-a-list-defined-in-object-concatenation.conf, self-referential-examples/002-self-reference-resolves-to-earlier-merged-value.conf, self-referential-examples/027-self-reference-inside-object-concatenation.conf, self-referential-examples/028-self-reference-through-two-concatenated-substitutions.conf">When merging two objects, the self-reference in the overriding
field refers to the overridden field.</mark> Say you have:</p>
<p data-line="750"> foo : { a : 1 }</p>
<p data-line="752">and then:</p>
Expand All @@ -560,7 +560,7 @@ <h4>Examples of Self-Referential Substitutions</h4>
rather than &quot;intractable cycle&quot;; as a result, the optional
substitution syntax `${?foo}` does not create a cycle:</mark></p>
<p data-line="776"> foo : ${?foo} // this field just disappears silently</p>
<p data-line="778"><mark class="ok" title="examples-of-self-referential-substitutions.8 — self-referential-examples/015-list-hidden-by-substituted-object-is-never-evaluated.conf, self-referential-examples/016-object-hidden-by-substituted-scalar-is-never-evaluated.conf, self-referential-examples/017-object-hidden-by-substituted-concatenation-is-never-evaluated.conf, self-referential-examples/018-object-merged-with-substituted-object-is-evaluated.conf, self-referential-examples/019-list-hidden-by-literal-scalar-is-never-evaluated.conf, self-referential-examples/020-object-hidden-by-literal-list-is-never-evaluated.conf, self-referential-examples/021-hidden-list-does-not-depend-on-key-order.conf">If a substitution is hidden by a value that could not be merged
<p data-line="778"><mark class="ok" title="examples-of-self-referential-substitutions.8 — self-referential-examples/015-list-hidden-by-substituted-object-is-never-evaluated.conf, self-referential-examples/016-object-hidden-by-substituted-scalar-is-never-evaluated.conf, self-referential-examples/017-object-hidden-by-substituted-concatenation-is-never-evaluated.conf, self-referential-examples/018-object-merged-with-substituted-object-is-evaluated.conf, self-referential-examples/019-list-hidden-by-literal-scalar-is-never-evaluated.conf, self-referential-examples/020-object-hidden-by-literal-list-is-never-evaluated.conf, self-referential-examples/021-hidden-list-does-not-depend-on-key-order.conf, self-referential-examples/031-field-hidden-by-a-substituted-object.conf, self-referential-examples/032-field-hidden-inside-object-concatenation.conf">If a substitution is hidden by a value that could not be merged
with it (by a non-object value) then it is never evaluated and no
error will be reported.</mark> So for example:</p>
<p data-line="782"> foo : ${does-not-exist}
Expand All @@ -581,7 +581,7 @@ <h4>Examples of Self-Referential Substitutions</h4>
an object or array value, for example, then it is not
self-referential even if there is a reference to the field itself
inside that object or array.</mark></p>
<p data-line="806"><mark class="ok" title="examples-of-self-referential-substitutions.14 — self-referential-examples/008-object-may-refer-to-sibling-path-within-itself.conf">Implementations must be careful to allow objects to refer to paths
<p data-line="806"><mark class="ok" title="examples-of-self-referential-substitutions.14 — self-referential-examples/008-object-may-refer-to-sibling-path-within-itself.conf, self-referential-examples/029-concatenated-object-refers-to-its-mixin.conf, self-referential-examples/030-concatenated-object-sees-its-own-field.conf">Implementations must be careful to allow objects to refer to paths
within themselves, for example:</mark></p>
<p data-line="809"> bar : { foo : 42,
baz : ${bar.foo}
Expand All @@ -590,9 +590,9 @@ <h4>Examples of Self-Referential Substitutions</h4>
part of resolving the substitution `${bar.foo}`, there would be a
cycle. <mark class="ok" title="examples-of-self-referential-substitutions.15 — self-referential-examples/024-substitution-target-is-fetched-lazily.conf">The implementation must only resolve the `foo` field in
`bar`, rather than recursing the entire `bar` object.</mark></p>
<p data-line="818"><mark class="ok" title="examples-of-self-referential-substitutions.16 — self-referential-examples/009-non-cycling-reference-looks-forward-across-merge.conf">Because there is no inherent cycle here, the substitution must
<p data-line="818"><mark class="ok" title="examples-of-self-referential-substitutions.16 — self-referential-examples/009-non-cycling-reference-looks-forward-across-merge.conf, self-referential-examples/029-concatenated-object-refers-to-its-mixin.conf, self-referential-examples/030-concatenated-object-sees-its-own-field.conf, self-referential-examples/033-object-below-a-substitution-looks-forward.conf">Because there is no inherent cycle here, the substitution must
&quot;look forward&quot; (including looking at the field currently being
defined).</mark> <mark class="ok" title="examples-of-self-referential-substitutions.17 — self-referential-examples/009-non-cycling-reference-looks-forward-across-merge.conf">To make this clearer, `bar.baz` would be `43` in:</mark></p>
defined).</mark> <mark class="ok" title="examples-of-self-referential-substitutions.17 — self-referential-examples/009-non-cycling-reference-looks-forward-across-merge.conf, self-referential-examples/033-object-below-a-substitution-looks-forward.conf">To make this clearer, `bar.baz` would be `43` in:</mark></p>
<p data-line="822"> bar : { foo : 42,
baz : ${bar.foo}
}
Expand Down
4 changes: 3 additions & 1 deletion conformance/maintaining/FINDINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

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:
**Since then:** proofreading the suite against `spec/HOCON.md` found rules no case pinned. They are 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`.
- `plus-equals-field-separator/009`–`012`, `self-referential-examples/027`–`030` — an object concatenated over a substitution, `top : ${x} { ... }`, is a merge (array-and-object-concatenation.6), the same as `top : ${x}` and `top { ... }` written as two fields. So a self-reference inside it, `+=` or the long form, looks back at the field the substitution gives (examples-of-self-referential-substitutions.3), and any other reference to `top.…` inside looks forward and sees the merged object (.14, .16). typesafe/config drops the inherited element of a `+=` (lightbend/config#375, #608; a fix is proposed in #882) and reports the rest as cycles (#586, #728); zig-hocon got the two-field form right and the one-value form wrong.
- `self-referential-examples/031`–`033` — layers are merged before they are resolved: an object a substitution gives on top hides a field of the object below it, which is then never evaluated (examples-of-self-referential-substitutions.8; lightbend/config#838, and #812 for the same inside `${base} { ... }`), and a reference in the object below looks forward at the merged value (.16). zig-hocon resolved the layer below first: it evaluated the hidden field and saw the old value.

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`.

Expand Down
4 changes: 2 additions & 2 deletions conformance/maintaining/SECTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ observable parse behaviour.
| 521 | Paths as keys | `paths-as-keys` | ✅ 11 cases |
| 574 | Substitutions | `substitutions` | ✅ 37 cases |
| 653 | Self-referential substitutions | `self-referential-substitutions` | ✅ 5 cases |
| 719 | The += field separator | `plus-equals-field-separator` | ✅ 8 cases |
| 738 | Examples of self-referential substitutions | `self-referential-examples` | ✅ 26 cases |
| 719 | The += field separator | `plus-equals-field-separator` | ✅ 12 cases |
| 738 | Examples of self-referential substitutions | `self-referential-examples` | ✅ 33 cases |
| 893 | List values from environment variables | `list-values-from-environment-variables` | ✅ 6 cases |
| 921 | Include syntax | `include-syntax` | ✅ 15 cases |
| 982 | Include semantics: merging | `include-merging` | ✅ 15 cases |
Expand Down
Loading
Loading