yapyon 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. yapyon-0.1.0/CHANGELOG.md +307 -0
  2. yapyon-0.1.0/LICENSE +674 -0
  3. yapyon-0.1.0/MANIFEST.in +18 -0
  4. yapyon-0.1.0/PKG-INFO +306 -0
  5. yapyon-0.1.0/README.md +279 -0
  6. yapyon-0.1.0/docs/GRAMMAR.md +543 -0
  7. yapyon-0.1.0/docs/SPEC.md +818 -0
  8. yapyon-0.1.0/examples/gateway.ypy +56 -0
  9. yapyon-0.1.0/examples/registry.ypy +40 -0
  10. yapyon-0.1.0/pyproject.toml +48 -0
  11. yapyon-0.1.0/setup.cfg +4 -0
  12. yapyon-0.1.0/src/yapyon/__init__.py +58 -0
  13. yapyon-0.1.0/src/yapyon/__main__.py +39 -0
  14. yapyon-0.1.0/src/yapyon/_cli.py +46 -0
  15. yapyon-0.1.0/src/yapyon/_xid.py +1449 -0
  16. yapyon-0.1.0/src/yapyon/emitter.py +225 -0
  17. yapyon-0.1.0/src/yapyon/lexer.py +1131 -0
  18. yapyon-0.1.0/src/yapyon/loader.py +185 -0
  19. yapyon-0.1.0/src/yapyon/multimap.py +165 -0
  20. yapyon-0.1.0/src/yapyon/parser.py +529 -0
  21. yapyon-0.1.0/src/yapyon/record.py +59 -0
  22. yapyon-0.1.0/src/yapyon/resolver.py +628 -0
  23. yapyon-0.1.0/src/yapyon/serializers.py +248 -0
  24. yapyon-0.1.0/src/yapyon/template.py +161 -0
  25. yapyon-0.1.0/src/yapyon.egg-info/PKG-INFO +306 -0
  26. yapyon-0.1.0/src/yapyon.egg-info/SOURCES.txt +41 -0
  27. yapyon-0.1.0/src/yapyon.egg-info/dependency_links.txt +1 -0
  28. yapyon-0.1.0/src/yapyon.egg-info/requires.txt +5 -0
  29. yapyon-0.1.0/src/yapyon.egg-info/top_level.txt +1 -0
  30. yapyon-0.1.0/tests/test_cli.py +81 -0
  31. yapyon-0.1.0/tests/test_conformance.py +321 -0
  32. yapyon-0.1.0/tests/test_diagnostics.py +169 -0
  33. yapyon-0.1.0/tests/test_emitter.py +165 -0
  34. yapyon-0.1.0/tests/test_identifiers.py +146 -0
  35. yapyon-0.1.0/tests/test_lexer.py +520 -0
  36. yapyon-0.1.0/tests/test_loader.py +216 -0
  37. yapyon-0.1.0/tests/test_parser.py +369 -0
  38. yapyon-0.1.0/tests/test_record.py +195 -0
  39. yapyon-0.1.0/tests/test_ref_grammar.py +49 -0
  40. yapyon-0.1.0/tests/test_resolver.py +364 -0
  41. yapyon-0.1.0/tests/test_subscripts.py +598 -0
  42. yapyon-0.1.0/tools/check_ref_grammar.py +269 -0
  43. yapyon-0.1.0/tools/gen_xid_table.py +83 -0
@@ -0,0 +1,307 @@
1
+ # Changelog
2
+
3
+ All notable changes to yapyon are recorded here. Section numbers refer to the
4
+ yapyon specification.
5
+
6
+ This project uses [PEP 440](https://peps.python.org/pep-0440/) versions.
7
+ `0.1.0` is the first release that is not a pre-release; every `0.1.0aN`
8
+ before it needed `pip install yapyon --pre` to be seen.
9
+
10
+ ---
11
+
12
+ ## 0.1.0 — 2026-09-29
13
+
14
+ The first release that is not an alpha, and the first `pip install yapyon`
15
+ finds without `--pre`.
16
+
17
+ This is the **self-contained tier**: typed literals, holes and splicing,
18
+ serializers, multimaps, and layering within a document — all resolvable from
19
+ the document's own bytes, with no ambient scope, no includes and no
20
+ environment at the format level. Consumer functions and file embedding are a
21
+ deliberate later tier, not a gap in this one.
22
+
23
+ **Breaking.** The two-tier limits change what a document between the tiers
24
+ does, and the identifier rule changes which keys are legal and which are the
25
+ same key. Both are carried here rather than left spread down the alpha line,
26
+ so if you installed `0.1.0a1`–`a4` with `--pre`, read the two entries below
27
+ before upgrading.
28
+
29
+ ### Changed
30
+
31
+ - **§5.3's expansion limits now have two tiers, and passing the spec's
32
+ threshold is a `shiran` rather than an `akan`.** Resolution depth 32 and
33
+ rendered size 1 MiB were hard errors, which forced one implementation's
34
+ resource bound onto every consumer of the format. They are now the
35
+ specification's **warn tier**: passing one emits a warning and resolution
36
+ continues. Refusal moved to an implementation-chosen **ceiling**, which this
37
+ implementation sets at `4x` the warn tier — **depth 128, rendered size
38
+ 4 MiB** — and which `loads`/`load` still move via `max_depth` / `max_size`.
39
+ Those two arguments keep their names and their meaning of *the point at
40
+ which loading fails*; what changed is that they now default above the
41
+ warning rather than sitting on it.
42
+
43
+ **This is a behaviour change.** A document between the two tiers — deeper
44
+ than 32, or rendering more than 1 MiB — previously raised `AkanError` and
45
+ now loads successfully while emitting a shiran to `warn=`. Code relying on
46
+ the old failure should pass `max_depth=32, max_size=1048576` to restore it
47
+ exactly.
48
+
49
+ The ceiling is deliberately finite: chained doubling is exponential, so a
50
+ resolver with no ceiling is a denial-of-service in anything parsing
51
+ untrusted configuration. The spec now declines to fix one ceiling for every
52
+ implementation while requiring that one exist and be documented.
53
+
54
+ **Unrelated and unchanged: a cycle or a missing target is still an `akan`**
55
+ at any depth. Those share §5.3 with the limits but are a different thing —
56
+ the document can never resolve, so the error belongs at the point of the
57
+ mistake.
58
+
59
+ ---
60
+
61
+ - **Identifiers are pinned to Unicode 14.0.0 and may not contain invisible
62
+ characters.** The identifier tables were the interpreter's, so a key with a
63
+ character from Unicode 15 or later loaded on Python 3.13 and was akan on
64
+ 3.11. yapyon now ships a generated Unicode 14 table (`tools/gen_xid_table.py`)
65
+ and `UNICODE_VERSION` is `"14.0.0"` on every install. **Breaking:** keys and
66
+ hole names may no longer contain `Default_Ignorable_Code_Point` characters.
67
+ Those include the Hangul fillers, which made a key with no visible glyph
68
+ legal, variation selectors, the combining grapheme joiner, and, on Python
69
+ 3.13+, ZWJ and ZWNJ.
70
+ - **Width variants and canonically equivalent spellings are one name.** A name
71
+ is folded, width variants to their standard form and then NFC, and two
72
+ names that fold alike are the same: `name: 1` loads as `{"name": 1}`,
73
+ `{name}` finds it, and `name` beside it is a duplicate key. Previously
74
+ `café` precomposed and decomposed were two keys that rendered identically.
75
+ No other NFKC merge applies (`fi` stays distinct from `fi`). **Breaking**
76
+ wherever a document relied on two such spellings being different keys.
77
+ - A keyword, string prefix or dunder name in another spelling (`True`,
78
+ `b"s"`, `__ROOT__`) is akan: reserved words have exactly one spelling.
79
+
80
+ - **SPEC §1's laws restated after review; no behaviour changes.** Law 2 now
81
+ says what the format does: a record computes nothing, and the full form's
82
+ only computation is named application of its declared encodings — the
83
+ serializers, which the old "never … transform" wording contradicted. "No
84
+ environment" moved from law 2 to law 3, its one home. Law 7 says "during
85
+ loading" rather than "at parse time", "never *merely* warned about" rather
86
+ than "never a warning", and names both kinds of `shiran`.
87
+
88
+ ### Added
89
+
90
+ - **Negative list indices, with Python's meaning.** `{xs[-1]}` is the last
91
+ element and `{xs[-3]}` the third from the end; `{xs[-0]}` is `{xs[0]}`,
92
+ because `-0` is the integer 0. They were reserved in SPEC §9 on the
93
+ reasoning "refuse now, allow later", which was never a ruling. Allowing them
94
+ is a widening: no document that loaded before changes meaning. Useful on a
95
+ multimap's list view, where `{mm.k[-1]}` is the last value filed under `k`
96
+ without knowing how many there are. `+` is still not an index sign.
97
+
98
+ ### Fixed
99
+
100
+ - **A negative index reached through a reference was neither refused nor
101
+ range-checked.** With `i: -1`, `{xs[i]}` quietly took Python's meaning while
102
+ the written `{xs[-1]}` was refused as reserved; with `i: -5` on a short list
103
+ it raised a bare `IndexError` instead of an akan. A position outside the
104
+ list is now akan at either end, whichever way the index is spelled.
105
+
106
+ ## 0.1.0a4 — 2026-09-19
107
+
108
+ A fix release. Two of the three entries below are bugs in `0.1.0a3` that could
109
+ corrupt or misreport data, and both were found by checking the library against
110
+ something it had not been written for rather than by a new test case. Nothing
111
+ here is breaking; code written against `0.1.0a3` keeps working.
112
+
113
+ ### Fixed
114
+
115
+ - **`__AS_TOML__()` refuses a mapping nested in a sequence instead of
116
+ corrupting it.** A list of mappings — TOML's `[[table-array]]`, or an inline
117
+ table inside an array — reached `str()` and was written as a quoted string,
118
+ so `{"count": [{"name": "x"}]}` emitted `count = ["{'name': 'x'}"]` and read
119
+ back as text rather than a table. That is a silent reinterpretation, which
120
+ §5.1.2 forbids: where a format cannot carry a value faithfully the encoding
121
+ is akan, never approximate. The value now raises at the point of the
122
+ mistake. Emitting real `[[name]]` headers is a separate feature and is not
123
+ part of this change. Affects `0.1.0a3`.
124
+ - **A stalled serializer is reported as the cycle it is.** Two serializers
125
+ whose targets hold each other — `b1.m` encoding `b2` while `b2.m` encodes
126
+ `b1` — failed with *"a YString cannot be serialized"*, naming the wrong
127
+ problem at the wrong line and mentioning neither the cycle nor the other
128
+ reference. The encoder conflated *has no faithful encoding* (a multimap,
129
+ bytes — permanent) with *is not resolved yet* (still holds a y-string), so a
130
+ hard error pre-empted the resolver before it could detect the cycle. Affects
131
+ `0.1.0a3`.
132
+ - **The refusal to serialize a bare anchor says why.**
133
+ `{__PARENT__.__AS_JSON__()}` reported *"needs a value to encode"*, which is
134
+ false — an anchor names a value — and suggested the spelling the author had
135
+ already written. The refusal is correct (an anchor always contains the hole
136
+ naming it, so the reference is circular in every document); only the message
137
+ was wrong. Behaviour is unchanged.
138
+
139
+ ### Documentation
140
+
141
+ - **The specification is published**, as two documents under `docs/`:
142
+ `GRAMMAR.md` (*are these bytes well-formed yapyon?* — self-contained,
143
+ normative for syntax) and `SPEC.md` (*what do well-formed bytes mean?*).
144
+ References run one way, so a parser can be written from the grammar alone.
145
+ Both ship in the sdist.
146
+
147
+ ---
148
+
149
+ ## 0.1.0a3 — 2026-09-14
150
+
151
+ The first release to carry breaking changes. It also fixes a parse bug present
152
+ in **every** previously published version, described under *Fixed* below.
153
+
154
+ ### Removed — breaking
155
+
156
+ - **`Template.fill(mapping)` is gone.** A `yt` no longer defers its holes to a
157
+ mapping supplied later by the consumer; it resolves them against the
158
+ document at parse time, by the ordinary scope rules, exactly as `y` does.
159
+ The difference between the two is now delivery alone — `y` joins the parts
160
+ into a `str`, `yt` hands over the literal text and the resolved values
161
+ separately, so that a consumer which knows the destination (shell, SQL,
162
+ HTML) can escape each value for it. `y` : `yt` :: f-string : t-string.
163
+ `Template.render()` produces the joined string where that is what is wanted;
164
+ there is deliberately no `__str__` that joins implicitly. (§6)
165
+
166
+ Consequences, all of them simplifying: `__ROOT__` has one meaning rather
167
+ than two, an unbound hole is an error at parse time where the author can see
168
+ it rather than at fill time, and a `yt` value is determined by the
169
+ document's own bytes.
170
+
171
+ *Migrating:* code that built a `Template` and called `.fill(data)` was using
172
+ late binding, which this release does not replace. Resolve the values in the
173
+ document, or join with `.render()`.
174
+
175
+ - **Dunder keys are akan.** `__foo__: "x"` loaded in `0.1.0a1` and `0.1.0a2`;
176
+ it is now an error. The `__dunder__` namespace belongs to yapyon rather than
177
+ to the document, which is what makes "every key of a mapping is addressable
178
+ from a hole" true — previously `__foo__: "x"` loaded while `y"{__foo__}"`
179
+ could never name it, and `__ROOT__` itself was a legal key. `__` and `___`
180
+ count as dunders too. (§7)
181
+
182
+ - **The shadowing warning no longer fires.** A local definition shadowing an
183
+ outer one used to emit a `shiran:`. Nothing about it was ambiguous — the
184
+ scope search is total and deterministic — and it fired on correct code,
185
+ since layered configuration is deliberate shadowing. The fix it suggested
186
+ (`{__ROOT__.x}`) resolved to a *different value*, which makes it a trap
187
+ rather than a warning. Anything reading warnings will see fewer.
188
+
189
+ ### Fixed
190
+
191
+ - **`\"` inside a string now parses.** `x: "a\"b"` failed as *"dangling
192
+ backslash in string"* in `0.1.0a1` and `0.1.0a2`, so **no document could put
193
+ a double quote inside a double-quoted string** — though `\"` and `\'` have
194
+ been in the escape table since the first release. The fix is a scanning rule
195
+ (a backslash consumes the character it protects), so it applies to raw and
196
+ bytes literals equally, and it is strictly widening: nothing that parsed
197
+ before stops parsing.
198
+
199
+ - **`python -m yapyon.<stage>` no longer leaks a traceback** on a document
200
+ error. Running an already-imported module under `-m` executes it a second
201
+ time, giving the stage a different `AkanError` class from the one being
202
+ caught. The per-module entry points delegate through the package path now.
203
+ **`python -m yapyon <stage>` is the documented spelling.**
204
+
205
+ ### Added
206
+
207
+ - **Subscripts — `a.b` is sugar for `a["b"]`.** The bracket is the general
208
+ form, and its contents yield a key: a quoted literal, or a reference whose
209
+ *value* is the key. So `{d[p]}` means "the value of `d` at the key stored in
210
+ `p`", and the inner reference resolves in the scope of the y-string's own
211
+ position. This also gives list indices (`{xs[0]}`) and quoted segments
212
+ (`{a["b.c"]}`), which were previously reserved. (§5.1)
213
+
214
+ - **Multimap traversal yields a list view.** `{mm.registry}` names *every*
215
+ value filed under `registry`, in order, as a list — 0 entries give an empty
216
+ list, N give N — and `{mm.registry[0]}` indexes it. Splicing the view into
217
+ text stays akan exactly as for any list. (§5.2)
218
+
219
+ - **Relative references `__PARENT__` and `__KEY__`.** A block can name its own
220
+ position, so the same text can appear in every block and resolve to that
221
+ block, and a block can be renamed or moved without editing its contents.
222
+ (§5.1.1)
223
+
224
+ store: "/cfg"
225
+ personas:
226
+ kimi:
227
+ id: y"{__PARENT__.__KEY__}"
228
+ prompt: y"{__ROOT__.store}/{__PARENT__.__KEY__}.md"
229
+
230
+ - **Named serializers — `__AS_JSON__()`, `__AS_TOML__()`, `__AS_YAML__()`.**
231
+ Splicing a container into text is barred because there is no one text form
232
+ for it; a named serializer is the author saying which they mean, in the
233
+ document, where a reader can see it. It stands to a container as `b64"..."`
234
+ does to bytes. A serializer is an *encoding*, not a function: no arguments,
235
+ no composition, and the set belongs to the format. (§5.1.2)
236
+
237
+ body: y"payload={cfg.__AS_JSON__()}"
238
+
239
+ All three writers are ours and the core remains dependency-free. **yapyon
240
+ does not prune** — dropping a key because its value is `None` or `""` would
241
+ be a silent reinterpretation — and because nothing is pruned, `None` reaches
242
+ the TOML writer, where it is akan rather than approximated, TOML having no
243
+ null.
244
+
245
+ - **An emitter — `yapyon.emit()` and `python -m yapyon record`** — rendering a
246
+ record back to canonical yapyon, with canonical spelling now normative
247
+ (§4.4). It reads the AST and never the loaded values, which is what keeps
248
+ `3.10` from coming back as `3.1` and a `b"..."` literal from being respelled
249
+ as base64. It is a **normalizer, not a round-tripper**: comments do not
250
+ survive.
251
+
252
+ - **Shirans are shown at the command line**, on stderr, and never change the
253
+ exit status — so a warning cannot corrupt `record`'s output when it is
254
+ redirected into a file, nor break a pipeline on a legal document.
255
+
256
+ - **`yapyon.UNICODE_VERSION`**, reporting the Unicode data version this
257
+ install's identifier tables come from (it follows the host interpreter).
258
+ This is the one place a document's meaning leans on something outside its
259
+ own bytes, so it is a machine-readable fact rather than a sentence in a
260
+ README that can go stale.
261
+
262
+ - **Diagnostics can name their source file.** Every stage accepts `source=`;
263
+ with one, an error reads `name:line:col`.
264
+
265
+ - **A `shiran:` for an avoidable bracket.** `{a["bar"]}` and `{a.bar}` mean
266
+ identically the same thing, so the warning is about spelling and taking the
267
+ fix changes nothing. It stays silent wherever the bracket was needed.
268
+
269
+ - A worked record in `examples/`.
270
+
271
+ ---
272
+
273
+ ## 0.1.0a2 — 2026-09-13
274
+
275
+ ### Added
276
+
277
+ - **The yapyon record (§12)** — a second, restricted form in which every leaf
278
+ has a literal value: no y-strings, nothing deferred. `loads_record` /
279
+ `load_record` run the pipeline without the resolver and refuse the y-family;
280
+ `is_record` tests a document by lexing. It gives a minimal implementation
281
+ something genuinely conforming to ship, and it is a safe mode by design
282
+ rather than an after-the-fact carve-out.
283
+
284
+ ### Changed
285
+
286
+ - **Names are UAX #31 identifiers** — `XID_Start | "_"` then `XID_Continue`,
287
+ exact codepoints, no normalization. The previous test admitted `a²`, which
288
+ was a legal key no hole could name, and rejected a decomposed `é`. Keys and
289
+ hole segments now answer to one rule. (§7)
290
+ - **Multimaps compare by entry sequence**, and a multimap is never equal to a
291
+ mapping. Previously `OrderedMultimap` had no `__eq__` at all, so two loads
292
+ of the same bytes compared unequal. (§7.1)
293
+ - **Holes are recognised before escapes decode**, so `\x7b` is content rather
294
+ than a live hole — matching Python's f-string layering.
295
+ - Akan messages say what was expected, not only what was found.
296
+
297
+ ---
298
+
299
+ ## 0.1.0a1 — 2026-09-11
300
+
301
+ First public release. A config/data format: Python-style typed literals with
302
+ YAML-like block structure, and parse-time splicing (y-strings) in place of
303
+ YAML's anchors.
304
+
305
+ - Lexer, parser, resolver and loader; `y` / `yb` / `yt` / `ry` splicing;
306
+ ordered multimaps; a conformance suite over the splice matrix and the
307
+ documented divergences from Python and YAML.