yaffle 0.0.1__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.
@@ -0,0 +1,3 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .mypy_cache/
yaffle-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.5
2
+ Name: yaffle
3
+ Version: 0.0.1
4
+ Summary: Runtime for Python code generated by the yaffle compiler
5
+ Project-URL: Homepage, https://yafflelang.org
6
+ Project-URL: Repository, https://github.com/noahcoolboy/yaffle
7
+ Project-URL: Documentation, https://github.com/noahcoolboy/yaffle/blob/main/docs/targets/python.md
8
+ Author: Noah
9
+ License-Expression: ISC
10
+ Keywords: binary,file-format,parser,serialization,yaffle
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+
19
+ # yaffle
20
+
21
+ The runtime for Python code generated by the [yaffle](https://yafflelang.org) compiler. yaffle
22
+ is a language for binary file formats; the compiler turns a schema into readers, writers and lazy
23
+ views. Generated modules import `yaffle`; it needs Python 3.12+ and has no dependencies.
24
+
25
+ See the [Python target documentation](https://github.com/noahcoolboy/yaffle/blob/main/docs/targets/python.md).
26
+ License: ISC.
yaffle-0.0.1/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # yaffle
2
+
3
+ The runtime for Python code generated by the [yaffle](https://yafflelang.org) compiler. yaffle
4
+ is a language for binary file formats; the compiler turns a schema into readers, writers and lazy
5
+ views. Generated modules import `yaffle`; it needs Python 3.12+ and has no dependencies.
6
+
7
+ See the [Python target documentation](https://github.com/noahcoolboy/yaffle/blob/main/docs/targets/python.md).
8
+ License: ISC.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "yaffle"
7
+ version = "0.0.1"
8
+ description = "Runtime for Python code generated by the yaffle compiler"
9
+ readme = "README.md"
10
+ license = "ISC"
11
+ requires-python = ">=3.12"
12
+ authors = [{ name = "Noah" }]
13
+ keywords = ["yaffle", "binary", "parser", "serialization", "file-format"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Topic :: Software Development :: Libraries",
20
+ ]
21
+ dependencies = []
22
+
23
+ [project.urls]
24
+ Homepage = "https://yafflelang.org"
25
+ Repository = "https://github.com/noahcoolboy/yaffle"
26
+ Documentation = "https://github.com/noahcoolboy/yaffle/blob/main/docs/targets/python.md"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["yaffle"]
30
+
31
+ [tool.hatch.build.targets.sdist]
32
+ include = ["yaffle", "tests", "README.md"]
@@ -0,0 +1,390 @@
1
+ """Unit tests of yaffle (run: python -m unittest discover -s packages/runtime-python/tests)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import gc
7
+ import math
8
+ import unittest
9
+ from dataclasses import dataclass
10
+ from typing import Any
11
+
12
+ import yaffle as rt
13
+ from yaffle.layout import StickyPolicy
14
+
15
+
16
+ def code_of(f: Any) -> tuple[str, list[Any]]:
17
+ try:
18
+ f()
19
+ except rt.YaffleError as e:
20
+ return e.code, e.path
21
+ raise AssertionError("no YaffleError")
22
+
23
+
24
+ class Primitives(unittest.TestCase):
25
+ def test_every_prim_round_trips_in_both_endiannesses(self) -> None:
26
+ samples = {
27
+ "u8": [0, 255], "i8": [-128, 127], "u16": [0, 0xFFFF], "i16": [-0x8000, 0x7FFF],
28
+ "u24": [0, 0xFFFFFF], "i24": [-0x800000, 0x7FFFFF], "u32": [0, 0xFFFFFFFF],
29
+ "i32": [-(2**31), 2**31 - 1], "u40": [2**40 - 1], "i40": [-(2**39)], "u48": [2**48 - 1],
30
+ "i48": [-(2**47), 2**47 - 1], "u56": [2**56 - 1], "i56": [-(2**55)],
31
+ "u64": [2**64 - 1], "i64": [-(2**63), 2**63 - 1],
32
+ } # fmt: skip
33
+ for kind, values in samples.items():
34
+ for le in (True, False):
35
+ for v in values:
36
+ b = rt.pack_prim(kind, v, le)
37
+ self.assertEqual(len(b), rt.PRIMS[kind].size)
38
+ self.assertEqual(rt.read_prim(b, 0, kind, le), v, (kind, le, v))
39
+ r = rt.Reader(b)
40
+ self.assertEqual(r.prim(kind, le), v)
41
+
42
+ def test_u24_byte_order(self) -> None:
43
+ self.assertEqual(rt.pack_prim("u24", 0x010203, True), b"\x03\x02\x01")
44
+ self.assertEqual(rt.pack_prim("u24", 0x010203, False), b"\x01\x02\x03")
45
+ self.assertEqual(rt.Reader(b"\xff\xff\xff").i_n(3, True), -1)
46
+
47
+ def test_floats(self) -> None:
48
+ self.assertEqual(rt.pack_prim("f16", 1.0, True), b"\x00\x3c")
49
+ self.assertEqual(rt.pack_prim("f16", float("nan"), True), b"\x00\x7e")
50
+ self.assertEqual(rt.pack_prim("f16", 1e9, False), b"\x7c\x00") # overflow -> Infinity
51
+ self.assertEqual(rt.pack_prim("f32", -1e40, False), b"\xff\x80\x00\x00")
52
+ self.assertEqual(rt.pack_prim("f32", float("nan"), False), b"\x7f\xc0\x00\x00")
53
+ # round to nearest, ties to even: 1 + 2^-11 is halfway between two halfs
54
+ self.assertEqual(rt.pack_prim("f16", 1 + 2**-11, True), b"\x00\x3c")
55
+ self.assertEqual(rt.Reader(b"\x00\x00\x80\x3f").f32(True), 1.0)
56
+ self.assertTrue(math.isnan(rt.Reader(b"\x01\x7c").f16(True)))
57
+
58
+ def test_check_prim(self) -> None:
59
+ self.assertEqual(code_of(lambda: rt.check_prim(256, "u8"))[0], "RANGE")
60
+ self.assertEqual(code_of(lambda: rt.check_prim(True, "u8"))[0], "INPUT")
61
+ self.assertEqual(code_of(lambda: rt.check_prim("1", "u8"))[0], "INPUT")
62
+ self.assertEqual(code_of(lambda: rt.check_prim(1.5, "u8"))[0], "RANGE")
63
+ self.assertEqual(code_of(lambda: rt.check_prim(float("inf"), "u8"))[0], "INPUT")
64
+ self.assertEqual(rt.check_prim(2.0, "u8"), 2)
65
+ self.assertEqual(rt.check_prim(3, "f32"), 3.0)
66
+ self.assertEqual(code_of(lambda: rt.check_prim(None, "f32"))[0], "INPUT")
67
+
68
+
69
+ class Integers(unittest.TestCase):
70
+ def test_truncating_division_and_remainder(self) -> None:
71
+ self.assertEqual((rt.div(7, 2), rt.div(-7, 2), rt.div(7, -2), rt.div(-7, -2)), (3, -3, -3, 3))
72
+ self.assertEqual((rt.mod(7, 2), rt.mod(-7, 2), rt.mod(7, -2), rt.mod(-7, -2)), (1, -1, 1, -1))
73
+ self.assertEqual(code_of(lambda: rt.div(1, 0))[0], "RANGE")
74
+ self.assertEqual(code_of(lambda: rt.mod(1, 0))[0], "RANGE")
75
+
76
+ def test_shifts_and_rounding(self) -> None:
77
+ self.assertEqual(rt.shl(1, 63), 2**63)
78
+ self.assertEqual(rt.shr(-8, 1), -4)
79
+ self.assertEqual(code_of(lambda: rt.shl(1, 64))[0], "RANGE")
80
+ self.assertEqual(code_of(lambda: rt.shr(1, -1))[0], "RANGE")
81
+ self.assertEqual([rt.round_half_away(x) for x in (0.5, 1.5, 2.5, -0.5, -2.5, 2.4)], [1, 2, 3, -1, -3, 2])
82
+ self.assertEqual(rt.trunc(-2.9), -2)
83
+ self.assertEqual(code_of(lambda: rt.round_half_away(float("nan")))[0], "RANGE")
84
+
85
+ def test_float_division(self) -> None:
86
+ self.assertEqual(rt.fdiv(1.0, 0.0), math.inf)
87
+ self.assertEqual(rt.fdiv(-1.0, 0.0), -math.inf)
88
+ self.assertTrue(math.isnan(rt.fdiv(0.0, 0.0)))
89
+ self.assertTrue(math.isnan(rt.fmod(1.0, 0.0)))
90
+ self.assertEqual(rt.fmod(-7.0, 2.0), -1.0)
91
+
92
+ def test_lengths_and_aggregates(self) -> None:
93
+ self.assertEqual(code_of(lambda: rt.length(-1))[0], "RANGE")
94
+ self.assertEqual(rt.sum_of([1, 2, 3]), 6)
95
+ self.assertEqual(rt.max_of([1, 5, 3]), 5)
96
+ self.assertEqual(code_of(lambda: rt.min_of([]))[0], "RANGE")
97
+ self.assertEqual(rt.lengths([[1], [], None, [1, 2]]), [1, 0, 0, 2])
98
+ self.assertEqual(code_of(lambda: rt.at([1], 1))[0], "RANGE")
99
+ self.assertEqual(rt.index_of(["a", "b"], "b"), 1)
100
+ self.assertEqual(code_of(lambda: rt.index_of(["a"], "c"))[0], "INPUT")
101
+
102
+ def test_equals(self) -> None:
103
+ self.assertTrue(rt.equals(1, 1.0))
104
+ self.assertFalse(rt.equals(True, 1))
105
+ self.assertTrue(rt.equals(float("nan"), float("nan")))
106
+ self.assertTrue(rt.equals(b"ab", bytearray(b"ab")))
107
+ self.assertTrue(rt.equals([1, [2]], [1, [2]]))
108
+
109
+
110
+ class Strings(unittest.TestCase):
111
+ def test_decode_and_nul(self) -> None:
112
+ r = rt.Reader(b"hi\x00xyz")
113
+ self.assertEqual(r.str(6, 1, "utf8", True), "hi")
114
+ self.assertEqual(rt.Reader("é\x00".encode("utf-16-be")).str_until(2, "utf16", False), "é")
115
+ self.assertEqual(rt.Reader(b"\x00\xf6\x01\x00").str(1, 4, "utf32", True), "\U0001f600")
116
+
117
+ def test_invalid_input_is_check(self) -> None:
118
+ self.assertEqual(code_of(lambda: rt.Reader(b"\xff").str(1, 1, "utf8", True))[0], "CHECK")
119
+ self.assertEqual(code_of(lambda: rt.Reader(b"\x80").str(1, 1, "ascii", True))[0], "CHECK")
120
+ self.assertEqual(code_of(lambda: rt.Reader(b"\x00\xd8").str(1, 2, "utf16", True))[0], "CHECK")
121
+ self.assertEqual(code_of(lambda: rt.Reader(b"\x00\x00\x11\x00").str(1, 4, "utf32", True))[0], "CHECK")
122
+ self.assertEqual(rt.Reader(b"\xe9").str(1, 1, "latin1", True), "é")
123
+
124
+ def test_encode(self) -> None:
125
+ self.assertEqual(code_of(lambda: rt.encode_string("a\x00", "utf8", True))[0], "INPUT")
126
+ self.assertEqual(code_of(lambda: rt.encode_string("é", "ascii", True))[0], "INPUT")
127
+ self.assertEqual(code_of(lambda: rt.encode_string("\ud800", "utf16", True))[0], "INPUT")
128
+ self.assertEqual(rt.code_units("\U0001f600", "utf16"), 2)
129
+ self.assertEqual(rt.code_units("\U0001f600", "utf8"), 4)
130
+ self.assertEqual(rt.encoded_bytes("ab", "utf32"), 8)
131
+ w = rt.Writer(rt.Region())
132
+ self.assertEqual(code_of(lambda: w.str("abc", 1, "utf8", True, "count", 2))[0], "INPUT")
133
+
134
+
135
+ class Checksums(unittest.TestCase):
136
+ def test_known_values(self) -> None:
137
+ self.assertEqual(rt.crc32(b"123456789"), 0xCBF43926)
138
+ self.assertEqual(rt.adler32(b"Wikipedia"), 0x11E60398)
139
+ self.assertEqual(rt.crc32("123456789"), 0xCBF43926)
140
+
141
+
142
+ class Json(unittest.TestCase):
143
+ def test_scalars(self) -> None:
144
+ self.assertEqual([rt.float_to_json(x) for x in (math.nan, math.inf, -math.inf, -0.0, 1.5)],
145
+ ["NaN", "Infinity", "-Infinity", "-0", 1.5]) # fmt: skip
146
+ self.assertTrue(math.isnan(rt.float_from_json("NaN")))
147
+ self.assertEqual(math.copysign(1, rt.float_from_json("-0")), -1)
148
+ self.assertEqual(code_of(lambda: rt.int_from_json(True))[0], "INPUT")
149
+ self.assertEqual(code_of(lambda: rt.int_from_json(1.5))[0], "RANGE")
150
+ self.assertEqual(rt.big_from_json("-12"), -12)
151
+ self.assertEqual(code_of(lambda: rt.big_from_json(12))[0], "INPUT")
152
+ self.assertEqual(rt.from_hex("0a 0B"), b"\x0a\x0b")
153
+ self.assertEqual(code_of(lambda: rt.from_hex("0"))[0], "INPUT")
154
+
155
+ def test_refs(self) -> None:
156
+ c = rt.ToJsonCtx()
157
+ shared = [1]
158
+ self.assertIsNone(c.ref(shared, "/a"))
159
+ self.assertEqual(c.ref(shared, "/b"), {"$ref": "/a"})
160
+ self.assertIsNone(c.ref([], "/c"))
161
+ self.assertIsNone(c.ref(b"x", "/d"))
162
+ f = rt.FromJsonCtx()
163
+ out = f.arr([1, 2], "/x", lambda j, p: j)
164
+ self.assertIs(f.deref({"$ref": "/x"})[0], out) # type: ignore[index]
165
+ self.assertEqual(code_of(lambda: f.deref({"$ref": "/nope"}))[0], "INPUT")
166
+ self.assertEqual(rt.ptr("/a", "b/c~"), "/a/b~1c~0")
167
+
168
+
169
+ class Reading(unittest.TestCase):
170
+ def test_eof_and_windows(self) -> None:
171
+ r = rt.Reader(b"\x01\x02\x03")
172
+ self.assertEqual(code_of(lambda: r.u32(True))[0], "EOF")
173
+ saved = r.window(2)
174
+ self.assertEqual(r.u8(), 1)
175
+ self.assertEqual(code_of(lambda: r.u16(True))[0], "EOF")
176
+ r.close_window(saved, False)
177
+ self.assertEqual(r.pos, 2)
178
+ self.assertEqual(code_of(lambda: rt.Reader(b"\x01").pad(1, 0, True))[0], "CHECK")
179
+ self.assertEqual(code_of(lambda: rt.Reader(b"\x05").bool(True))[0], "CHECK")
180
+
181
+ def test_targets_share_identity(self) -> None:
182
+ @dataclass
183
+ class S:
184
+ x: int = 0
185
+
186
+ r = rt.Reader(b"\x07\x00")
187
+
188
+ def read() -> S:
189
+ s = r.take_into(S)
190
+ s.x = r.u8()
191
+ return s
192
+
193
+ a = r.target_struct(0, "S", read, S)
194
+ b = r.target_struct(0, "S", read, S)
195
+ self.assertIs(a, b)
196
+ self.assertEqual(r.pos, 0)
197
+
198
+ def test_union_errors(self) -> None:
199
+ r = rt.Reader(b"\x01")
200
+
201
+ def bad() -> int:
202
+ r.u16(True)
203
+ return 0
204
+
205
+ code, _ = code_of(lambda: r.union([("A", bad), ("B", bad)]))
206
+ self.assertEqual(code, "UNION")
207
+ v = r.union([("A", bad), ("B", r.u8)])
208
+ self.assertEqual((v.type, v.value), ("B", 1))
209
+
210
+
211
+ class Writing(unittest.TestCase):
212
+ def test_placeables_and_pointers(self) -> None:
213
+ region = rt.Region()
214
+ w = rt.Writer(region)
215
+ w.u8(1)
216
+ target = [1, 2]
217
+
218
+ def body() -> None:
219
+ w.u8(0xAA)
220
+ w.u8(0xBB)
221
+
222
+ p = w.target(target, "k", "t", body)
223
+ p2 = w.target(target, "k", "t", body)
224
+ self.assertIs(p, p2)
225
+ w.pointer("u16", True, p, False, None)
226
+ w.pointer("u16", True, None, False, 0)
227
+ self.assertEqual(bytes(region.finish()), b"\x01\x05\x00\x00\x00\xaa\xbb")
228
+
229
+ def test_alignment_constraints_and_overlap(self) -> None:
230
+ region = rt.Region()
231
+ w = rt.Writer(region)
232
+ w.u8(1)
233
+ a = w.place("a", None, lambda: w.u8(2), constraint=(4, 0))
234
+ w.place("b", None, lambda: w.u8(3), fixed=2)
235
+ out = bytes(region.finish())
236
+ self.assertEqual(a.block.pos, 4)
237
+ self.assertEqual(out, b"\x01\x00\x03\x00\x02")
238
+ region = rt.Region()
239
+ w = rt.Writer(region)
240
+ w.u32(0, True)
241
+ w.place("b", None, lambda: w.u8(3), fixed=2)
242
+ self.assertEqual(code_of(region.finish)[0], "OVERLAP")
243
+
244
+ def test_layout_items_order(self) -> None:
245
+ region = rt.Region()
246
+ w = rt.Writer(region)
247
+ # The region's layout orders its root struct's targets (a struct's own `layout`).
248
+ region.layout = ((("y",), (("align", 4),)), (("x",), ()))
249
+ lroot = rt.LStruct()
250
+ x = w.place("x", None, lambda: w.u8(0x11))
251
+ y = w.place("y", None, lambda: w.u8(0x22))
252
+ lroot.set("x", x)
253
+ lroot.set("y", y)
254
+ w.u8(0)
255
+ region.lroot = lroot
256
+ out = bytes(region.finish())
257
+ self.assertEqual(out, b"\x00\x00\x00\x00\x22\x11")
258
+
259
+ def test_layout_from_the_root_struct(self) -> None:
260
+ # A layout computed by the struct's writer reaches a region without its own.
261
+ region = rt.Region()
262
+ w = rt.Writer(region)
263
+ w.u8(0)
264
+ lroot = rt.LStruct()
265
+ lroot.set("x", w.place("x", None, lambda: w.u8(0x11)))
266
+ lroot.layout = ((("x",), (("align", rt.layout_align(8)),)),)
267
+ region.attach_root(lroot)
268
+ self.assertEqual(bytes(region.finish()), b"\x00" + b"\x00" * 7 + b"\x11")
269
+ self.assertEqual(code_of(lambda: rt.layout_align(0))[0], "RANGE")
270
+ self.assertEqual(code_of(lambda: rt.layout_align(True))[0], "RANGE")
271
+ self.assertEqual(code_of(lambda: rt.layout_pad(-1))[0], "RANGE")
272
+ self.assertEqual(rt.layout_pad(0), 0)
273
+
274
+ def test_ref_pointers(self) -> None:
275
+ region = rt.Region()
276
+ w = rt.Writer(region)
277
+ start = w.offset
278
+ w.str("abc", 1, "utf8", True, "until")
279
+ w.ref_site("str", start, "abc")
280
+ w.ref_pointer("u8", True, "str", "abc", lambda: b"abc\x00", False, None)
281
+ w.ref_pointer("u8", True, "str", "zzz", lambda: b"zzz\x00", False, None)
282
+ self.assertEqual(code_of(region.finish)[0], "REF")
283
+
284
+ def test_deferred_values_and_extents(self) -> None:
285
+ region = rt.Region()
286
+ w = rt.Writer(region)
287
+ e = w.extent()
288
+ w.defer_prim("u8", True, lambda: e.size)
289
+ w.bytes(b"xyz")
290
+ w.close(e)
291
+ self.assertEqual(bytes(region.finish()), b"\x04xyz")
292
+
293
+ def test_sticky_layout_keeps_positions(self) -> None:
294
+ region = rt.Region()
295
+ region.sticky = StickyPolicy("default", 1, 8)
296
+ w = rt.Writer(region)
297
+ w.u8(0)
298
+ p = w.place("p", None, lambda: w.u8(9))
299
+ p.orig, p.orig_size = 6, 1
300
+ out = bytes(region.finish())
301
+ self.assertEqual(out, b"\x00\x00\x00\x00\x00\x00\x09")
302
+
303
+
304
+ class Codecs(unittest.TestCase):
305
+ def test_reuse_tracks_objects(self) -> None:
306
+ @dataclass
307
+ class V:
308
+ a: int
309
+
310
+ v = V(1)
311
+ rt.remember_codec(v, "s", b"ENC", b"\x01")
312
+ self.assertEqual(rt.reuse_codec(v, "s", b"\x01"), b"ENC")
313
+ self.assertIsNone(rt.reuse_codec(v, "s", b"\x02"))
314
+ self.assertIsNone(rt.reuse_codec(V(1), "s", b"\x01"))
315
+
316
+ owner = V(2)
317
+ data = b"plain bytes value"
318
+ rt.remember_codec(data, "t", b"E", data, owner)
319
+ self.assertEqual(rt.reuse_codec(data, "t", data), b"E")
320
+ del owner
321
+ gc.collect()
322
+ self.assertIsNone(rt.reuse_codec(data, "t", data))
323
+
324
+ def test_codec_errors(self) -> None:
325
+ class Bad:
326
+ def decode(self, data: bytes, ctx: rt.CodecContext) -> bytes:
327
+ raise ValueError("boom")
328
+
329
+ def encode(self, data: bytes, ctx: rt.CodecContext) -> bytes:
330
+ return b""
331
+
332
+ self.assertEqual(code_of(lambda: rt.decode(Bad(), "bad", b"", rt.CodecContext(), False))[0], "CODEC")
333
+
334
+ class Async:
335
+ async def decode(self, data: bytes, ctx: rt.CodecContext) -> bytes:
336
+ return data
337
+
338
+ async def encode(self, data: bytes, ctx: rt.CodecContext) -> bytes:
339
+ return data
340
+
341
+ self.assertEqual(code_of(lambda: rt.decode(Async(), "a", b"x", rt.CodecContext(), False))[0], "CODEC")
342
+ out = asyncio.run(rt.decode_async(Async(), "a", b"x", rt.CodecContext(), False))
343
+ self.assertEqual(out, (b"x", 1))
344
+
345
+ def test_streams(self) -> None:
346
+ self.assertEqual(rt.join_stream([b"ab", b"c"]), b"abc")
347
+ self.assertEqual(rt.split_by_sizes(b"abc", [1, 2]), [b"a", b"bc"])
348
+ self.assertEqual(code_of(lambda: rt.split_by_sizes(b"abc", [1]))[0], "DERIVE")
349
+ self.assertEqual(code_of(lambda: rt.slice_stream(b"abc", 2, 5))[0], "EOF")
350
+
351
+ def test_leb128(self) -> None:
352
+ self.assertEqual(rt.uleb128.write(624485), bytes.fromhex("e58e26"))
353
+ self.assertEqual(rt.uleb128.read(bytes.fromhex("e58e26")), 624485)
354
+ self.assertEqual(rt.sleb128.write(-123456), bytes.fromhex("c0bb78"))
355
+ self.assertEqual(rt.sleb128.read(bytes.fromhex("c0bb78")), -123456)
356
+ self.assertEqual(rt.uleb128.size(b"\x80\x80"), None)
357
+ r = rt.Reader(bytes.fromhex("e58e2600"))
358
+ self.assertEqual(r.extern(rt.uleb128, ()), 624485)
359
+ self.assertEqual(r.pos, 3)
360
+
361
+
362
+ class Errors(unittest.TestCase):
363
+ def test_paths(self) -> None:
364
+ e = rt.YaffleError("CHECK", "bad", 16)
365
+ e.prefix("x", -1, "")
366
+ e.prefix("entries", 2)
367
+ self.assertEqual(e.path, ["entries", 2, "x"])
368
+ self.assertEqual(str(e), "CHECK at entries[2].x (offset 0x10): bad")
369
+ e.sealed = True
370
+ e.prefix("outer")
371
+ self.assertEqual(e.path, ["entries", 2, "x"])
372
+ r = rt.safe(lambda: rt.fail("EOF", "x"))
373
+ self.assertFalse(r.ok)
374
+ self.assertEqual(r.error.code if r.error else None, "EOF")
375
+
376
+
377
+ class Sources(unittest.TestCase):
378
+ def test_async_source_rejected_by_sync_parse(self) -> None:
379
+ class Src:
380
+ size = 1
381
+
382
+ async def read(self, off: int, n: int) -> bytes:
383
+ return b"\x00"
384
+
385
+ self.assertEqual(code_of(lambda: rt.read_all(Src()))[0], "INPUT")
386
+ self.assertEqual(asyncio.run(rt.read_all_async(Src())), b"\x00")
387
+
388
+
389
+ if __name__ == "__main__":
390
+ unittest.main()
@@ -0,0 +1,77 @@
1
+ """yaffle: the runtime of the Python code the yaffle compiler generates.
2
+
3
+ Generated modules import it as ``_rt``. Pure Python 3.12+, no dependencies. Besides what
4
+ generated code calls, it offers sources (:func:`file_source`), errors (:class:`YaffleError`),
5
+ checksums and canonical JSON helpers.
6
+
7
+ Modules: ``errors``, ``prim`` (primitive encodings), ``ints`` (number semantics), ``strings``,
8
+ ``checksum``, ``arrays`` (array builtins and equality), ``jsonc`` (canonical JSON), ``values``
9
+ (checks of values), ``externs`` (host code), ``codec`` (``@via`` and codec reuse), ``streams``
10
+ (streams), ``reader``, ``writer`` and ``layout`` (parse and serialize), ``api`` (root API
11
+ glue and sources), and for views ``store`` (bytes read on demand), ``view``, ``commit`` and
12
+ ``async_view``.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from . import (
18
+ api,
19
+ arrays,
20
+ async_view,
21
+ checksum,
22
+ codec,
23
+ commit,
24
+ errors,
25
+ externs,
26
+ ints,
27
+ jsonc,
28
+ layout,
29
+ prim,
30
+ reader,
31
+ streams,
32
+ strings,
33
+ values,
34
+ view,
35
+ writer,
36
+ )
37
+ from .api import *
38
+ from .arrays import *
39
+ from .async_view import *
40
+ from .checksum import *
41
+ from .codec import *
42
+ from .commit import *
43
+ from .errors import *
44
+ from .externs import *
45
+ from .ints import *
46
+ from .jsonc import *
47
+ from .layout import *
48
+ from .prim import *
49
+ from .reader import *
50
+ from .streams import *
51
+ from .strings import *
52
+ from .values import *
53
+ from .view import *
54
+ from .writer import *
55
+
56
+ __version__ = "0.0.1"
57
+
58
+ __all__ = [
59
+ *api.__all__,
60
+ *arrays.__all__,
61
+ *async_view.__all__,
62
+ *checksum.__all__,
63
+ *codec.__all__,
64
+ *commit.__all__,
65
+ *errors.__all__,
66
+ *externs.__all__,
67
+ *ints.__all__,
68
+ *jsonc.__all__,
69
+ *layout.__all__,
70
+ *prim.__all__,
71
+ *reader.__all__,
72
+ *streams.__all__,
73
+ *strings.__all__,
74
+ *values.__all__,
75
+ *view.__all__,
76
+ *writer.__all__,
77
+ ]