diff --git a/CHANGELOG.md b/CHANGELOG.md index 2674ca9..c86ff8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,36 @@ format changes. ## [Unreleased] +## [1.1.0] — 2026-09-11 + +### Added + +- **pxf: quoted entry names and keyed repeated fields** (#18; draft `-01` + §3.13, protowire#116, reference protowire-go#50). The vendored + `proto/pxf/annotations.proto` gains `(pxf.key) = 1316`, matching + canonical. The grammar accepts a string at entry-name position with + `=` and `{` everywhere (`Assignment::key_quoted`, `Block::name_quoted` + retain the spelling for `FormatDocument`); the schema layer rejects it + outside a keyed repeated field's block. A repeated message-typed field + whose `(pxf.key)` names a singular string field of the element message + decodes from the keyed block form — `children { greeting { … } }` or + `children = { greeting = { … } }` — with the entry name as the key, + entry order as list order, and duplicate names, the empty string, and + a disagreeing explicit key assignment as errors; the anonymous list + form still decodes, with an explicit empty key rejected. `Marshal` + writes the keyed form whenever every key is present, non-empty and + distinct (names bare when identifier-safe, quoted otherwise, the key + field omitted from the entry body), and the anonymous form otherwise. + `CanonicalizeKeyed(Document*, const Descriptor*)` (new + `protowire/pxf/keyed.h`) is the schema-aware AST rewrite behind `fmt`: + eligible anonymous bindings become keyed blocks, `name = { }` becomes + `name { }`, identifier-safe quoted names are unquoted, redundant key + assignments are dropped. `ValidateFile` reports a misplaced + `(pxf.key)` as `ViolationKind::kKeyOption` with a `detail`; + `KeyFieldName`, `KeyField` and `IsKeyed` are exported from + `protowire/pxf/annotations.h`. The spec repo's `testdata/keyed/` is + vendored and every fixture is pinned. + ### Fixed - **pxf: bind-time validation covers the import closure** (#34; draft @@ -28,7 +58,7 @@ format changes. quarter of a small document's decode; the walk now costs ~160 ns. `UnmarshalOptions::skip_validate` remains the escape hatch for callers that validated once. - + - **pb: repeated numeric fields are marshalled packed** (#32). `Marshal` wrote one tag + value per element for every element type, so `ListHolder { values: [1, 2, 3] }` was `08 01 08 02 08 03` where the @@ -41,8 +71,6 @@ format changes. canonical envelope has no repeated numerics, so its bytes are unchanged. -### Fixed - - **pxf: a dotted string map key is written bare** (#36; protowire#313, decided as (a)). The identifier-safe test `Marshal` and `FormatDocument` apply to a string map key stopped at `[A-Za-z0-9_]`, @@ -55,33 +83,59 @@ format changes. `fmt-dotted-keys`, is vendored and pinned; the decoder always read `a.b:` as one identifier, so only the writers move. -### Added +- **pb: a map entry always carries key and value, zero-valued or not** + (#24; protowire#295, decided on protowire-go#105). `pb.h`'s map branch + applied proto3 zero-skip inside the entry, so `metadata { "": "" }` + serialised as an empty entry (`22022a00`) where protobuf-go, protoc and + C++ protobuf write `22062a040a001200`. Field 1 and field 2 of every + entry are now written unconditionally; a value held through + `std::optional` or a smart pointer that is unset is written as its zero + value. `dump_envelope --vector zero-map-entry` prints the bytes for the + cross-port gate's golden, and an unknown vector name exits 3 with + `not-implemented: `. -- **pxf: quoted entry names and keyed repeated fields** (#18; draft `-01` - §3.13, protowire#116, reference protowire-go#50). The vendored - `proto/pxf/annotations.proto` gains `(pxf.key) = 1316`, matching - canonical. The grammar accepts a string at entry-name position with - `=` and `{` everywhere (`Assignment::key_quoted`, `Block::name_quoted` - retain the spelling for `FormatDocument`); the schema layer rejects it - outside a keyed repeated field's block. A repeated message-typed field - whose `(pxf.key)` names a singular string field of the element message - decodes from the keyed block form — `children { greeting { … } }` or - `children = { greeting = { … } }` — with the entry name as the key, - entry order as list order, and duplicate names, the empty string, and - a disagreeing explicit key assignment as errors; the anonymous list - form still decodes, with an explicit empty key rejected. `Marshal` - writes the keyed form whenever every key is present, non-empty and - distinct (names bare when identifier-safe, quoted otherwise, the key - field omitted from the entry body), and the anonymous form otherwise. - `CanonicalizeKeyed(Document*, const Descriptor*)` (new - `protowire/pxf/keyed.h`) is the schema-aware AST rewrite behind `fmt`: - eligible anonymous bindings become keyed blocks, `name = { }` becomes - `name { }`, identifier-safe quoted names are unquoted, redundant key - assignments are dropped. `ValidateFile` reports a misplaced - `(pxf.key)` as `ViolationKind::kKeyOption` with a `detail`; - `KeyFieldName`, `KeyField` and `IsKeyed` are exported from - `protowire/pxf/annotations.h`. The spec repo's `testdata/keyed/` is - vendored and every fixture is pinned. +- **pxf fmt: the quotes on a string map key spelled like a keyword or an + integer are kept** (#27; draft `-01` § Entries and Keys, "Canonical + spelling of map keys", protowire#306). `FormatDocument` wrote every + identifier-shaped key bare, so `"true": "v"` on a `map` + became `true: "v"` — a bool key, which no longer binds on a string + `K`. `MapEntry` gains `key_quoted`; a quoted key is unquoted only when + it is identifier-safe and not `null` / `true` / `false`, and a bare key + stays bare (so `404:` is no longer requoted on the way through fmt). + The marshaller uses the same test (`IsIdentifierSafe`, exported from + `protowire/pxf/format.h`), so `"123"`, `"true"` and `"null"` string + keys are written quoted; it also sorts bool keys (false, true). The + spec repo's `testdata/map-keys/` fixtures are vendored and pinned. + +- **pxf: bool map keys bind in exactly the grammar's spellings** + (absorbed into #27; protowire#284). The decoder bound any non-`true` + key on a `map` to `false` — `t`, `yes`, `"TRUE"`, `"0"` all + silently became a key the author did not write. A bool key is now + `true` / `false` bare (the keyword spelling, newly accepted as a map + key by the parser and decoder), `0` / `1` bare, or `"true"` / + `"false"` quoted, and anything else is an error naming the key and + field; on a string `K` the bare keyword is an error that says to + write it quoted. + +- **pxf: the lexer reads fractional and `µs` duration literals** (#20). + `1.5ms`, `1.234567ms`, `312.5µs`, `1h30m0.5s` and `-2.5s` — the forms + every port's encoder writes for a `google.protobuf.Duration` that is + not a whole multiple of its largest unit — tokenised as a float + followed by an identifier (or an integer followed by a stray byte for + the two-byte `µ`), so this port could not read its own output for any + measured latency. `LexNumber` now consumes an optional fraction first + and takes the duration branch when a time unit follows; the duration + scan admits `.` before a digit and the `C2 B5` micro sign (U+00B5 + only — U+03BC is not in the grammar). Mirrors protowire-go#76. + +- **detail: negative durations split toward zero, and the int64 edges + round-trip** (absorbed into #20). `ParseDuration` normalised nanos into + `[0, 1e9)`, so `-1ns` decoded to `seconds=-1, nanos=999999999`, which + `google.protobuf.Duration` forbids (nanos must carry the sign of the + value) and no other port reads back as `-1ns`; it now splits like + `time.Duration`. `FormatDuration` and `ParseDuration` take the + magnitude in unsigned arithmetic, so `INT64_MIN` formats as + `-2562047h47m16.854775808s` instead of a placeholder and reads back. ### Security @@ -138,64 +192,6 @@ format changes. and the three `pb/decimal-*` rows the manifest skipped for this port. -### Fixed - -- **pb: a map entry always carries key and value, zero-valued or not** - (#24; protowire#295, decided on protowire-go#105). `pb.h`'s map branch - applied proto3 zero-skip inside the entry, so `metadata { "": "" }` - serialised as an empty entry (`22022a00`) where protobuf-go, protoc and - C++ protobuf write `22062a040a001200`. Field 1 and field 2 of every - entry are now written unconditionally; a value held through - `std::optional` or a smart pointer that is unset is written as its zero - value. `dump_envelope --vector zero-map-entry` prints the bytes for the - cross-port gate's golden, and an unknown vector name exits 3 with - `not-implemented: `. - -- **pxf fmt: the quotes on a string map key spelled like a keyword or an - integer are kept** (#27; draft `-01` § Entries and Keys, "Canonical - spelling of map keys", protowire#306). `FormatDocument` wrote every - identifier-shaped key bare, so `"true": "v"` on a `map` - became `true: "v"` — a bool key, which no longer binds on a string - `K`. `MapEntry` gains `key_quoted`; a quoted key is unquoted only when - it is identifier-safe and not `null` / `true` / `false`, and a bare key - stays bare (so `404:` is no longer requoted on the way through fmt). - The marshaller uses the same test (`IsIdentifierSafe`, exported from - `protowire/pxf/format.h`), so `"123"`, `"true"` and `"null"` string - keys are written quoted; it also sorts bool keys (false, true). The - spec repo's `testdata/map-keys/` fixtures are vendored and pinned. - -- **pxf: bool map keys bind in exactly the grammar's spellings** - (absorbed into #27; protowire#284). The decoder bound any non-`true` - key on a `map` to `false` — `t`, `yes`, `"TRUE"`, `"0"` all - silently became a key the author did not write. A bool key is now - `true` / `false` bare (the keyword spelling, newly accepted as a map - key by the parser and decoder), `0` / `1` bare, or `"true"` / - `"false"` quoted, and anything else is an error naming the key and - field; on a string `K` the bare keyword is an error that says to - write it quoted. - - - -- **pxf: the lexer reads fractional and `µs` duration literals** (#20). - `1.5ms`, `1.234567ms`, `312.5µs`, `1h30m0.5s` and `-2.5s` — the forms - every port's encoder writes for a `google.protobuf.Duration` that is - not a whole multiple of its largest unit — tokenised as a float - followed by an identifier (or an integer followed by a stray byte for - the two-byte `µ`), so this port could not read its own output for any - measured latency. `LexNumber` now consumes an optional fraction first - and takes the duration branch when a time unit follows; the duration - scan admits `.` before a digit and the `C2 B5` micro sign (U+00B5 - only — U+03BC is not in the grammar). Mirrors protowire-go#76. - -- **detail: negative durations split toward zero, and the int64 edges - round-trip** (absorbed into #20). `ParseDuration` normalised nanos into - `[0, 1e9)`, so `-1ns` decoded to `seconds=-1, nanos=999999999`, which - `google.protobuf.Duration` forbids (nanos must carry the sign of the - value) and no other port reads back as `-1ns`; it now splits like - `time.Duration`. `FormatDuration` and `ParseDuration` take the - magnitude in unsigned arithmetic, so `INT64_MIN` formats as - `-2562047h47m16.854775808s` instead of a placeholder and reads back. - ## [1.0.0] — 2026-05-13 First major-version cut. Implements the three one-time spec changes diff --git a/CMakeLists.txt b/CMakeLists.txt index 368e07d..011d00d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.20) -project(protowire VERSION 1.0.0 LANGUAGES CXX) +project(protowire VERSION 1.1.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7df2ee9..c4e39c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,6 +120,8 @@ Cutting a release: 1. Bump `project(... VERSION X.Y.Z ...)` in `CMakeLists.txt`. 2. Add a `## [X.Y.Z]` section to `CHANGELOG.md`. 3. Tag `vX.Y.Z` on `main`. -4. The `.github/workflows/release.yml` workflow will produce build - artifacts and post a GitHub Release. (Conan / vcpkg distribution is - intentionally deferred to a later milestone.) +4. Publish a GitHub Release on the tag (`gh release create vX.Y.Z`) + whose body summarises the new `CHANGELOG.md` section. There is no + release workflow and no build artifacts; consumers build from source + via the cmake flow above. (Conan / vcpkg distribution is intentionally + deferred to a later milestone.)