natizon 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.
- natizon-0.1.0/LICENSES/Apache-2.0.txt +73 -0
- natizon-0.1.0/PKG-INFO +110 -0
- natizon-0.1.0/README.md +88 -0
- natizon-0.1.0/pyproject.toml +67 -0
- natizon-0.1.0/src/natizon/__init__.py +19 -0
- natizon-0.1.0/src/natizon/_transformer.py +135 -0
- natizon-0.1.0/src/natizon/exceptions.py +50 -0
- natizon-0.1.0/src/natizon/grammar/common_zig.lark +60 -0
- natizon-0.1.0/src/natizon/grammar/zon.lark +52 -0
- natizon-0.1.0/src/natizon/parser.py +121 -0
- natizon-0.1.0/src/natizon/py.typed +0 -0
- natizon-0.1.0/src/natizon/types.py +29 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
|
10
|
+
|
|
11
|
+
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
|
12
|
+
|
|
13
|
+
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
|
14
|
+
|
|
15
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
|
16
|
+
|
|
17
|
+
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
|
18
|
+
|
|
19
|
+
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
|
20
|
+
|
|
21
|
+
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
|
22
|
+
|
|
23
|
+
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
|
24
|
+
|
|
25
|
+
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
|
26
|
+
|
|
27
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
|
28
|
+
|
|
29
|
+
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
|
30
|
+
|
|
31
|
+
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
|
32
|
+
|
|
33
|
+
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
|
34
|
+
|
|
35
|
+
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
|
36
|
+
|
|
37
|
+
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
|
38
|
+
|
|
39
|
+
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
|
40
|
+
|
|
41
|
+
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
|
42
|
+
|
|
43
|
+
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
|
44
|
+
|
|
45
|
+
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
|
46
|
+
|
|
47
|
+
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
|
48
|
+
|
|
49
|
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
|
50
|
+
|
|
51
|
+
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
|
52
|
+
|
|
53
|
+
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
|
54
|
+
|
|
55
|
+
END OF TERMS AND CONDITIONS
|
|
56
|
+
|
|
57
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
58
|
+
|
|
59
|
+
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
|
60
|
+
|
|
61
|
+
Copyright [yyyy] [name of copyright owner]
|
|
62
|
+
|
|
63
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
64
|
+
you may not use this file except in compliance with the License.
|
|
65
|
+
You may obtain a copy of the License at
|
|
66
|
+
|
|
67
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
68
|
+
|
|
69
|
+
Unless required by applicable law or agreed to in writing, software
|
|
70
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
71
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
72
|
+
See the License for the specific language governing permissions and
|
|
73
|
+
limitations under the License.
|
natizon-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: natizon
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A pure Python, native ZON (Zig Object Notation) parser.
|
|
5
|
+
Keywords: zig,ZON,Zig Object Notation,parser
|
|
6
|
+
Author-email: Eric Joldasov <bratishkaerik@landless-city.net>
|
|
7
|
+
Requires-Python: >= 3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Text Processing :: General
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
License-File: LICENSES/Apache-2.0.txt
|
|
18
|
+
Requires-Dist: lark>=1.3.1
|
|
19
|
+
Project-URL: Homepage, https://github.com/BratishkaErik/natizon
|
|
20
|
+
Project-URL: Issues, https://github.com/BratishkaErik/natizon/issues
|
|
21
|
+
Project-URL: Repository, https://github.com/BratishkaErik/natizon
|
|
22
|
+
|
|
23
|
+
# natizon
|
|
24
|
+
|
|
25
|
+
`natizon` (short for "native-ZON") is a pure Python parser for
|
|
26
|
+
[ZON (Zig Object Notation)](https://ziglang.org/documentation/0.16.0/std/#std.zon). Built on top of
|
|
27
|
+
[Lark](https://github.com/lark-parser/lark), it provides a familiar, `json`-like interface for decoding ZON strings
|
|
28
|
+
directly into Python data structures: like dictionaries, lists, strings, booleans, and numbers.
|
|
29
|
+
It relies strictly on standard Python types, without AST wrappers and so on.
|
|
30
|
+
|
|
31
|
+
> [!NOTE]
|
|
32
|
+
> `natizon` is slightly more lenient than the official `std.zon` parser. This flexibility is intentional,
|
|
33
|
+
> making it easier to consume and work with data in Python environments.
|
|
34
|
+
|
|
35
|
+
## ZON to Python Type Mapping
|
|
36
|
+
|
|
37
|
+
When you pass a ZON string to `natizon.loads()`, the parser automatically converts ZON primitives and structures into
|
|
38
|
+
their closest Python types.
|
|
39
|
+
|
|
40
|
+
Here's breakdown:
|
|
41
|
+
|
|
42
|
+
### Primitives and Literals
|
|
43
|
+
|
|
44
|
+
| ZON Type | ZON Example | Python Type | Python Value | Notes |
|
|
45
|
+
|:----------------------|:---------------------|:------------|:-----------------|:---------------------------------------------------------------|
|
|
46
|
+
| **Null** | `null` | `NoneType` | `None` | |
|
|
47
|
+
| **Boolean** | `true`, `false` | `bool` | `True`, `False` | |
|
|
48
|
+
| **Integer** | `42`, `0x2A` | `int` | `42` | |
|
|
49
|
+
| **Float** | `3.14`, `inf`, `nan` | `float` | `3.14` | Supports ZON-specific keywords: `nan` and `inf`. |
|
|
50
|
+
| **Char Literal** | `'a'` | `int` | `97` | Evaluates to the integer Unicode code point. |
|
|
51
|
+
| **String** | `"Hello"` | `str` | `"Hello"` | Handles standard escapes and Unicode `\u{...}`. |
|
|
52
|
+
| **Multiline String** | `\\Line 1` | `str` | `"Line 1"` | Strips the `\\` prefix and joins multiple lines with newlines. |
|
|
53
|
+
| **Enum Literal** | `.linux` | `str` | `"linux"` | Parsed simply as strings. |
|
|
54
|
+
| **Quoted Identifier** | `.@"complex-key!"` | `str` | `"complex-key!"` | Slices off the `@` prefix and evaluates the string. |
|
|
55
|
+
|
|
56
|
+
### Structures and Containers
|
|
57
|
+
|
|
58
|
+
| ZON Type | ZON Example | Python Type | Python Value | Notes |
|
|
59
|
+
|:--------------------|:---------------|:------------|:-------------|:---------------------------------------------------------------|
|
|
60
|
+
| **Array** | `.{ 1, 2, 3 }` | `list` | `[1, 2, 3]` | Parses as a `tuple` if `use_tuples=True` is set. |
|
|
61
|
+
| **Struct** | `.{ .x = 1 }` | `dict` | `{"x": 1}` | Raises `ValueError` if duplicate field names are encountered. |
|
|
62
|
+
| **Empty Container** | `.{}` | `dict` | `{}` | Parses using Array rules if `empty_mode` is set to `SEQUENCE`. |
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```shell
|
|
67
|
+
pip install natizon
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
`natizon` exposes a `loads()` function that works similarly to the standard library's `json.loads()`.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from natizon import loads
|
|
76
|
+
|
|
77
|
+
zon_data = r"""
|
|
78
|
+
.{
|
|
79
|
+
.package_name = "network_tools",
|
|
80
|
+
.version = "2.1.0",
|
|
81
|
+
.supported_platforms = .{ .linux, .macos, .windows },
|
|
82
|
+
.dependencies = .{
|
|
83
|
+
.lib_a = .{ .url = "https://server.com/a.tar" },
|
|
84
|
+
.lib_b = .{ .path = "../local_b" }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
# Parses directly into standard Python dicts and lists
|
|
90
|
+
parsed_data = loads(zon_data)
|
|
91
|
+
|
|
92
|
+
print(parsed_data["package_name"]) # "network_tools"
|
|
93
|
+
print(parsed_data["supported_platforms"]) # ["linux", "macos", "windows"]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Parsing Options
|
|
97
|
+
|
|
98
|
+
You can customize how `natizon` handles specific ZON structures:
|
|
99
|
+
|
|
100
|
+
* **`use_tuples`** (`bool`, default `False`): If `True`, parses ZON arrays (e.g., `.{ 1, 2, 3 }`) as Python `tuple`s
|
|
101
|
+
instead of `list`s.
|
|
102
|
+
* **`empty_mode`** (`EmptyContainerMode`, default `EmptyContainerMode.DICT`): Controls whether an empty container `.{}`
|
|
103
|
+
becomes an empty dictionary (`{}`) or an empty sequence (`[]` / `()`).
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from natizon import loads, EmptyContainerMode
|
|
107
|
+
|
|
108
|
+
data = loads(".{}", use_tuples=True, empty_mode=EmptyContainerMode.SEQUENCE)
|
|
109
|
+
print(data) # Output: ()
|
|
110
|
+
```
|
natizon-0.1.0/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# natizon
|
|
2
|
+
|
|
3
|
+
`natizon` (short for "native-ZON") is a pure Python parser for
|
|
4
|
+
[ZON (Zig Object Notation)](https://ziglang.org/documentation/0.16.0/std/#std.zon). Built on top of
|
|
5
|
+
[Lark](https://github.com/lark-parser/lark), it provides a familiar, `json`-like interface for decoding ZON strings
|
|
6
|
+
directly into Python data structures: like dictionaries, lists, strings, booleans, and numbers.
|
|
7
|
+
It relies strictly on standard Python types, without AST wrappers and so on.
|
|
8
|
+
|
|
9
|
+
> [!NOTE]
|
|
10
|
+
> `natizon` is slightly more lenient than the official `std.zon` parser. This flexibility is intentional,
|
|
11
|
+
> making it easier to consume and work with data in Python environments.
|
|
12
|
+
|
|
13
|
+
## ZON to Python Type Mapping
|
|
14
|
+
|
|
15
|
+
When you pass a ZON string to `natizon.loads()`, the parser automatically converts ZON primitives and structures into
|
|
16
|
+
their closest Python types.
|
|
17
|
+
|
|
18
|
+
Here's breakdown:
|
|
19
|
+
|
|
20
|
+
### Primitives and Literals
|
|
21
|
+
|
|
22
|
+
| ZON Type | ZON Example | Python Type | Python Value | Notes |
|
|
23
|
+
|:----------------------|:---------------------|:------------|:-----------------|:---------------------------------------------------------------|
|
|
24
|
+
| **Null** | `null` | `NoneType` | `None` | |
|
|
25
|
+
| **Boolean** | `true`, `false` | `bool` | `True`, `False` | |
|
|
26
|
+
| **Integer** | `42`, `0x2A` | `int` | `42` | |
|
|
27
|
+
| **Float** | `3.14`, `inf`, `nan` | `float` | `3.14` | Supports ZON-specific keywords: `nan` and `inf`. |
|
|
28
|
+
| **Char Literal** | `'a'` | `int` | `97` | Evaluates to the integer Unicode code point. |
|
|
29
|
+
| **String** | `"Hello"` | `str` | `"Hello"` | Handles standard escapes and Unicode `\u{...}`. |
|
|
30
|
+
| **Multiline String** | `\\Line 1` | `str` | `"Line 1"` | Strips the `\\` prefix and joins multiple lines with newlines. |
|
|
31
|
+
| **Enum Literal** | `.linux` | `str` | `"linux"` | Parsed simply as strings. |
|
|
32
|
+
| **Quoted Identifier** | `.@"complex-key!"` | `str` | `"complex-key!"` | Slices off the `@` prefix and evaluates the string. |
|
|
33
|
+
|
|
34
|
+
### Structures and Containers
|
|
35
|
+
|
|
36
|
+
| ZON Type | ZON Example | Python Type | Python Value | Notes |
|
|
37
|
+
|:--------------------|:---------------|:------------|:-------------|:---------------------------------------------------------------|
|
|
38
|
+
| **Array** | `.{ 1, 2, 3 }` | `list` | `[1, 2, 3]` | Parses as a `tuple` if `use_tuples=True` is set. |
|
|
39
|
+
| **Struct** | `.{ .x = 1 }` | `dict` | `{"x": 1}` | Raises `ValueError` if duplicate field names are encountered. |
|
|
40
|
+
| **Empty Container** | `.{}` | `dict` | `{}` | Parses using Array rules if `empty_mode` is set to `SEQUENCE`. |
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
pip install natizon
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
`natizon` exposes a `loads()` function that works similarly to the standard library's `json.loads()`.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from natizon import loads
|
|
54
|
+
|
|
55
|
+
zon_data = r"""
|
|
56
|
+
.{
|
|
57
|
+
.package_name = "network_tools",
|
|
58
|
+
.version = "2.1.0",
|
|
59
|
+
.supported_platforms = .{ .linux, .macos, .windows },
|
|
60
|
+
.dependencies = .{
|
|
61
|
+
.lib_a = .{ .url = "https://server.com/a.tar" },
|
|
62
|
+
.lib_b = .{ .path = "../local_b" }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# Parses directly into standard Python dicts and lists
|
|
68
|
+
parsed_data = loads(zon_data)
|
|
69
|
+
|
|
70
|
+
print(parsed_data["package_name"]) # "network_tools"
|
|
71
|
+
print(parsed_data["supported_platforms"]) # ["linux", "macos", "windows"]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Parsing Options
|
|
75
|
+
|
|
76
|
+
You can customize how `natizon` handles specific ZON structures:
|
|
77
|
+
|
|
78
|
+
* **`use_tuples`** (`bool`, default `False`): If `True`, parses ZON arrays (e.g., `.{ 1, 2, 3 }`) as Python `tuple`s
|
|
79
|
+
instead of `list`s.
|
|
80
|
+
* **`empty_mode`** (`EmptyContainerMode`, default `EmptyContainerMode.DICT`): Controls whether an empty container `.{}`
|
|
81
|
+
becomes an empty dictionary (`{}`) or an empty sequence (`[]` / `()`).
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from natizon import loads, EmptyContainerMode
|
|
85
|
+
|
|
86
|
+
data = loads(".{}", use_tuples=True, empty_mode=EmptyContainerMode.SEQUENCE)
|
|
87
|
+
print(data) # Output: ()
|
|
88
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core >= 3.12.0, <4"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "natizon"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">= 3.12"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Eric Joldasov", email = "bratishkaerik@landless-city.net" }
|
|
11
|
+
]
|
|
12
|
+
description = "A pure Python, native ZON (Zig Object Notation) parser."
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = "Apache-2.0"
|
|
15
|
+
license-files = [
|
|
16
|
+
"LICENSES/*",
|
|
17
|
+
]
|
|
18
|
+
keywords = ["zig", "ZON", "Zig Object Notation", "parser"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Topic :: Text Processing :: General",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
#
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Typing :: Typed",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"lark>=1.3.1",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/BratishkaErik/natizon"
|
|
35
|
+
Repository = "https://github.com/BratishkaErik/natizon"
|
|
36
|
+
Issues = "https://github.com/BratishkaErik/natizon/issues"
|
|
37
|
+
|
|
38
|
+
[dependency-groups]
|
|
39
|
+
test = [
|
|
40
|
+
"pytest>=9.1.0",
|
|
41
|
+
]
|
|
42
|
+
lint = [
|
|
43
|
+
"ruff>=0.15.17",
|
|
44
|
+
"ty>=0.0.49",
|
|
45
|
+
]
|
|
46
|
+
dev = [
|
|
47
|
+
{ include-group = "test" },
|
|
48
|
+
{ include-group = "lint" },
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
preview = true
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
# Enable all `pydocstyle` rules, limiting to those that adhere to the
|
|
56
|
+
# Google convention via `convention = "google"`.
|
|
57
|
+
select = ["D"]
|
|
58
|
+
|
|
59
|
+
# On top of the Google convention, disable `D417`, which requires
|
|
60
|
+
# documentation for every function parameter.
|
|
61
|
+
ignore = ["D417"]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.pydocstyle]
|
|
64
|
+
convention = "google"
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint.per-file-ignores]
|
|
67
|
+
"tests/**" = ["D"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""ZON (Zig Object Notation) native Python parser.
|
|
2
|
+
|
|
3
|
+
This package provides a `json`-like interface
|
|
4
|
+
for decoding ZON strings directly into standard, native
|
|
5
|
+
Python data structures (dicts, lists, primitives).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .exceptions import ZonDecodeError, ZonError, ZonInternalError
|
|
9
|
+
from .parser import EmptyContainerMode, loads
|
|
10
|
+
from .types import ZonType
|
|
11
|
+
|
|
12
|
+
__all__ = (
|
|
13
|
+
"EmptyContainerMode",
|
|
14
|
+
"ZonDecodeError",
|
|
15
|
+
"ZonError",
|
|
16
|
+
"ZonInternalError",
|
|
17
|
+
"ZonType",
|
|
18
|
+
"loads",
|
|
19
|
+
)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import re
|
|
3
|
+
from typing import Final, final, assert_never
|
|
4
|
+
|
|
5
|
+
from lark import Token, Transformer, v_args
|
|
6
|
+
|
|
7
|
+
from .types import EmptyContainerMode, ZonType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _decode_zig_string(s: str) -> str:
|
|
11
|
+
"""Decodes Zig escape sequences into Python characters before literal evaluation."""
|
|
12
|
+
|
|
13
|
+
def _unicode_replacer(match: re.Match) -> str:
|
|
14
|
+
hex_val = match.group(1)
|
|
15
|
+
# Python's \U escape expects exactly 8 padded hex digits
|
|
16
|
+
return f"\\U{hex_val.zfill(8)}"
|
|
17
|
+
|
|
18
|
+
# Translate \u{XXXX} to \U0000XXXX
|
|
19
|
+
s = re.sub(r"\\u\{([0-9a-fA-F]+)}", _unicode_replacer, s)
|
|
20
|
+
return ast.literal_eval(s)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Stop warnings, functions are used at runtime by Lark:
|
|
24
|
+
# noinspection PyMethodMayBeStatic
|
|
25
|
+
@final
|
|
26
|
+
class _ZonTransformer(Transformer):
|
|
27
|
+
"""Transforms the parsed Lark Tree directly into Python data structures."""
|
|
28
|
+
|
|
29
|
+
use_tuples: Final[bool]
|
|
30
|
+
empty_mode: Final[EmptyContainerMode]
|
|
31
|
+
|
|
32
|
+
def __init__(self, use_tuples: bool, empty_mode: EmptyContainerMode) -> None:
|
|
33
|
+
super().__init__(visit_tokens=True)
|
|
34
|
+
self.use_tuples = use_tuples
|
|
35
|
+
self.empty_mode = empty_mode
|
|
36
|
+
|
|
37
|
+
def plain_id(self, token: Token) -> str:
|
|
38
|
+
return str(token)
|
|
39
|
+
|
|
40
|
+
def quoted_id(self, token: Token) -> str:
|
|
41
|
+
# Slices off the '@' prefix before evaluating the string literal
|
|
42
|
+
val = _decode_zig_string(token.value[1:])
|
|
43
|
+
if "\x00" in val:
|
|
44
|
+
raise ValueError("Identifier cannot contain null bytes")
|
|
45
|
+
return val
|
|
46
|
+
|
|
47
|
+
def single_string(self, token: Token) -> str:
|
|
48
|
+
return _decode_zig_string(token.value)
|
|
49
|
+
|
|
50
|
+
def multiline_string(self, *tokens: Token) -> str:
|
|
51
|
+
cleaned_lines = (
|
|
52
|
+
# Strip prefix and normalize line endings
|
|
53
|
+
t.value.removeprefix("\\\\").rstrip("\r")
|
|
54
|
+
for t in tokens
|
|
55
|
+
)
|
|
56
|
+
return "\n".join(cleaned_lines)
|
|
57
|
+
|
|
58
|
+
def float_val(self, token: Token) -> float:
|
|
59
|
+
val = token.value.replace("_", "")
|
|
60
|
+
# Safely handle python's limitation with standard float(0x1.0p) via float.fromhex
|
|
61
|
+
if val.lower().startswith("0x"):
|
|
62
|
+
return float.fromhex(val)
|
|
63
|
+
return float(val)
|
|
64
|
+
|
|
65
|
+
def neg_float_val(self, token: Token) -> float:
|
|
66
|
+
# Lark filters anonymous "-" literal; only FLOAT_LIT token is passed
|
|
67
|
+
return -self.float_val(token)
|
|
68
|
+
|
|
69
|
+
def int_val(self, token: Token) -> int:
|
|
70
|
+
return int(token.value.replace("_", ""), 0)
|
|
71
|
+
|
|
72
|
+
def neg_int_val(self, token: Token) -> int:
|
|
73
|
+
val = self.int_val(token)
|
|
74
|
+
if val == 0:
|
|
75
|
+
raise ValueError("Integer literal '-0' is ambiguous")
|
|
76
|
+
return -val
|
|
77
|
+
|
|
78
|
+
def nan_val(self, _: Token) -> float:
|
|
79
|
+
return float("nan")
|
|
80
|
+
|
|
81
|
+
def inf_val(self, _: Token) -> float:
|
|
82
|
+
return float("inf")
|
|
83
|
+
|
|
84
|
+
def neg_inf_val(self, _: Token) -> float:
|
|
85
|
+
return float("-inf")
|
|
86
|
+
|
|
87
|
+
def true_val(self, _: Token) -> bool:
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
def false_val(self, _: Token) -> bool:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
def null_val(self, _: Token) -> None:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def char_val(self, token: Token) -> int:
|
|
97
|
+
evaluated_char = _decode_zig_string(token.value)
|
|
98
|
+
return ord(evaluated_char)
|
|
99
|
+
|
|
100
|
+
def field_init(self, identifier: str, value: ZonType) -> tuple[str, ZonType]:
|
|
101
|
+
return identifier, value
|
|
102
|
+
|
|
103
|
+
def decl_literal(self, identifier: str) -> str:
|
|
104
|
+
return identifier
|
|
105
|
+
|
|
106
|
+
def keyed_struct(self, *items: tuple[str, ZonType]) -> dict[str, ZonType]:
|
|
107
|
+
result = dict(items)
|
|
108
|
+
|
|
109
|
+
if len(result) != len(items):
|
|
110
|
+
seen = set()
|
|
111
|
+
|
|
112
|
+
for k, _ in items:
|
|
113
|
+
if k in seen:
|
|
114
|
+
raise ValueError(f"Duplicate struct field name: '{k}'")
|
|
115
|
+
seen.add(k)
|
|
116
|
+
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
def array_struct(self, *items: ZonType) -> list[ZonType] | tuple[ZonType, ...]:
|
|
120
|
+
return items if self.use_tuples else list(items)
|
|
121
|
+
|
|
122
|
+
def populated_struct(self, body: ZonType) -> ZonType:
|
|
123
|
+
return body
|
|
124
|
+
|
|
125
|
+
def empty_struct(self) -> dict[str, ZonType] | list[ZonType] | tuple[ZonType, ...]:
|
|
126
|
+
match self.empty_mode:
|
|
127
|
+
case EmptyContainerMode.DICT:
|
|
128
|
+
return {}
|
|
129
|
+
case EmptyContainerMode.SEQUENCE:
|
|
130
|
+
return self.array_struct()
|
|
131
|
+
case _ as unreachable:
|
|
132
|
+
assert_never(unreachable)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
ZonTransformer = v_args(inline=True)(_ZonTransformer)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Exceptions for the ZON parser."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ZonError(Exception):
|
|
5
|
+
"""Base exception for all ZON-related errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ZonInternalError(ZonError, RuntimeError):
|
|
9
|
+
"""Raised when the ZON parser fails to initialize.
|
|
10
|
+
|
|
11
|
+
Note: most likely this is a bug or corruption in the `natizon` package,
|
|
12
|
+
not an error on the user side. Recommended to report to upstream,
|
|
13
|
+
unless you know what you're doing.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ZonDecodeError(ZonError, ValueError):
|
|
18
|
+
"""Raised when a ZON string cannot be parsed.
|
|
19
|
+
|
|
20
|
+
Similar to `json.JSONDecodeError`.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
original_exc: The underlying exception caught during parsing or transformation.
|
|
24
|
+
line: The line number where the error occurred, if available.
|
|
25
|
+
column: The column number where the error occurred, if available.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
original_exc: Exception
|
|
29
|
+
line: int | None
|
|
30
|
+
column: int | None
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
message: str,
|
|
35
|
+
original_exc: Exception,
|
|
36
|
+
line: int | None = None,
|
|
37
|
+
column: int | None = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Initialize the ZON decode error.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
message: A description of the decode error.
|
|
43
|
+
original_exc: The underlying exception caught during parsing.
|
|
44
|
+
line: The line number where the error occurred (1-indexed).
|
|
45
|
+
column: The column number where the error occurred (1-indexed).
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(f"Failed to decode ZON: {message}")
|
|
48
|
+
self.original_exc = original_exc
|
|
49
|
+
self.line = line
|
|
50
|
+
self.column = column
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Stuff that exist in both Zig and ZON formats.
|
|
2
|
+
// Mostly terminals
|
|
3
|
+
|
|
4
|
+
// Identifiers
|
|
5
|
+
PLAIN_ID: /[a-zA-Z_][a-zA-Z0-9_]*/
|
|
6
|
+
QUOTED_ID: /@"([^"\\]|\\.)*"/
|
|
7
|
+
|
|
8
|
+
// Strings and chars
|
|
9
|
+
_ESCAPE_SINGLE: /\\[nr\\t'"]/
|
|
10
|
+
_ESCAPE_UNICODE: /\\x[0-9a-fA-F]{2}/ | /\\u\{[0-9a-fA-F]+\}/
|
|
11
|
+
|
|
12
|
+
_STR_UNESCAPED: /[^"\\]/
|
|
13
|
+
STRING_SINGLE: "\"" (_ESCAPE_SINGLE | _ESCAPE_UNICODE | _STR_UNESCAPED)* "\""
|
|
14
|
+
STRING_MULTI: /\\\\[^\n]*/
|
|
15
|
+
|
|
16
|
+
_CHAR_UNESCAPED: /[^'\\]/
|
|
17
|
+
CHAR_LIT: "'" (_ESCAPE_SINGLE | _ESCAPE_UNICODE | _CHAR_UNESCAPED) "'"
|
|
18
|
+
|
|
19
|
+
// Keywords
|
|
20
|
+
TRUE: "true"
|
|
21
|
+
FALSE: "false"
|
|
22
|
+
NULL: "null"
|
|
23
|
+
|
|
24
|
+
// Number parts
|
|
25
|
+
_HEX_START: /[0-9a-fA-F][0-9a-fA-F_]*/
|
|
26
|
+
_HEX_FRAC: /[0-9a-fA-F_]+/
|
|
27
|
+
|
|
28
|
+
_OCT_START: /[0-7][0-7_]*/
|
|
29
|
+
_BIN_START: /[01][01_]*/
|
|
30
|
+
|
|
31
|
+
_DEC_START: /[0-9][0-9_]*/
|
|
32
|
+
_DEC_FRAC: /[0-9_]+/
|
|
33
|
+
|
|
34
|
+
_EXP_P: /[pP][-+]?/ _DEC_FRAC
|
|
35
|
+
_EXP_E: /[eE][-+]?/ _DEC_FRAC
|
|
36
|
+
|
|
37
|
+
// Integers
|
|
38
|
+
HEX_INT: /0[xX]/ _HEX_START
|
|
39
|
+
OCT_INT: /0[oO]/ _OCT_START
|
|
40
|
+
BIN_INT: /0[bB]/ _BIN_START
|
|
41
|
+
DEC_INT: _DEC_START
|
|
42
|
+
|
|
43
|
+
INT_LIT: HEX_INT
|
|
44
|
+
| OCT_INT
|
|
45
|
+
| BIN_INT
|
|
46
|
+
| DEC_INT
|
|
47
|
+
|
|
48
|
+
// Floats
|
|
49
|
+
HEX_FLOAT_FRAC: /0[xX]/ _HEX_START "." _HEX_FRAC _EXP_P?
|
|
50
|
+
HEX_FLOAT_EXP: /0[xX]/ _HEX_START _EXP_P
|
|
51
|
+
DEC_FLOAT_FRAC: _DEC_START "." _DEC_FRAC _EXP_E?
|
|
52
|
+
DEC_FLOAT_EXP: _DEC_START _EXP_E
|
|
53
|
+
|
|
54
|
+
FLOAT_LIT: HEX_FLOAT_FRAC
|
|
55
|
+
| HEX_FLOAT_EXP
|
|
56
|
+
| DEC_FLOAT_FRAC
|
|
57
|
+
| DEC_FLOAT_EXP
|
|
58
|
+
|
|
59
|
+
// Other stuff
|
|
60
|
+
COMMENT: "//" /[^\n]*/
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// ZON (Zig Object Notation) Grammar
|
|
2
|
+
|
|
3
|
+
?start: expr
|
|
4
|
+
|
|
5
|
+
?expr: struct_init
|
|
6
|
+
| decl_literal
|
|
7
|
+
| string
|
|
8
|
+
| number
|
|
9
|
+
| bool
|
|
10
|
+
| null_val
|
|
11
|
+
| char_literal
|
|
12
|
+
|
|
13
|
+
struct_init: "." "{" struct_body "}" -> populated_struct
|
|
14
|
+
| "." "{" "}" -> empty_struct
|
|
15
|
+
|
|
16
|
+
?struct_body: field_init ("," field_init)* ","? -> keyed_struct
|
|
17
|
+
| expr ("," expr)* ","? -> array_struct
|
|
18
|
+
|
|
19
|
+
field_init: "." identifier "=" expr
|
|
20
|
+
decl_literal: "." identifier
|
|
21
|
+
|
|
22
|
+
?identifier: PLAIN_ID -> plain_id
|
|
23
|
+
| QUOTED_ID -> quoted_id
|
|
24
|
+
|
|
25
|
+
?string: STRING_SINGLE -> single_string
|
|
26
|
+
| STRING_MULTI+ -> multiline_string
|
|
27
|
+
|
|
28
|
+
?bool: TRUE -> true_val
|
|
29
|
+
| FALSE -> false_val
|
|
30
|
+
|
|
31
|
+
null_val: NULL
|
|
32
|
+
|
|
33
|
+
?char_literal: CHAR_LIT -> char_val
|
|
34
|
+
|
|
35
|
+
// ZON-exclusive number literals
|
|
36
|
+
NAN: "nan"
|
|
37
|
+
INF: "inf"
|
|
38
|
+
|
|
39
|
+
?number: FLOAT_LIT -> float_val
|
|
40
|
+
| INT_LIT -> int_val
|
|
41
|
+
| "-" FLOAT_LIT -> neg_float_val
|
|
42
|
+
| "-" INT_LIT -> neg_int_val
|
|
43
|
+
| NAN -> nan_val
|
|
44
|
+
| INF -> inf_val
|
|
45
|
+
| "-" INF -> neg_inf_val
|
|
46
|
+
|
|
47
|
+
%import .common_zig (PLAIN_ID, QUOTED_ID, STRING_SINGLE, STRING_MULTI, CHAR_LIT, FLOAT_LIT, INT_LIT, COMMENT)
|
|
48
|
+
%import .common_zig (TRUE, FALSE, NULL)
|
|
49
|
+
%import common.WS
|
|
50
|
+
|
|
51
|
+
%ignore COMMENT
|
|
52
|
+
%ignore WS
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""ZON parser."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from typing import Final, Self, final
|
|
7
|
+
|
|
8
|
+
from lark import Lark
|
|
9
|
+
from lark.exceptions import GrammarError, UnexpectedInput, VisitError
|
|
10
|
+
|
|
11
|
+
from ._transformer import ZonTransformer, _ZonTransformer
|
|
12
|
+
from .exceptions import ZonDecodeError, ZonInternalError
|
|
13
|
+
from .types import EmptyContainerMode, ZonType
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
type _TransformerMap = Mapping[tuple[bool, EmptyContainerMode], _ZonTransformer]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@final
|
|
22
|
+
class _ZonParserImpl:
|
|
23
|
+
"""Cache Lark parser and stateless transformers."""
|
|
24
|
+
|
|
25
|
+
parser: Final[Lark]
|
|
26
|
+
_transformers: Final[_TransformerMap]
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
log.debug("Initializing Lark ZON parser...")
|
|
30
|
+
|
|
31
|
+
options = {
|
|
32
|
+
"parser": "lalr",
|
|
33
|
+
"cache": True,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
current_package = __package__ or "natizon"
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
self.parser = Lark.open_from_package(
|
|
40
|
+
package=current_package,
|
|
41
|
+
search_paths=("grammar",),
|
|
42
|
+
grammar_path="zon.lark",
|
|
43
|
+
**options,
|
|
44
|
+
)
|
|
45
|
+
except (OSError, GrammarError, ImportError) as e:
|
|
46
|
+
raise ZonInternalError(
|
|
47
|
+
"Failed to load ZON grammar. The package may be corrupted."
|
|
48
|
+
) from e
|
|
49
|
+
|
|
50
|
+
self._transformers = {
|
|
51
|
+
(False, EmptyContainerMode.DICT): ZonTransformer(
|
|
52
|
+
use_tuples=False, empty_mode=EmptyContainerMode.DICT
|
|
53
|
+
),
|
|
54
|
+
(False, EmptyContainerMode.SEQUENCE): ZonTransformer(
|
|
55
|
+
use_tuples=False, empty_mode=EmptyContainerMode.SEQUENCE
|
|
56
|
+
),
|
|
57
|
+
(True, EmptyContainerMode.DICT): ZonTransformer(
|
|
58
|
+
use_tuples=True, empty_mode=EmptyContainerMode.DICT
|
|
59
|
+
),
|
|
60
|
+
(True, EmptyContainerMode.SEQUENCE): ZonTransformer(
|
|
61
|
+
use_tuples=True, empty_mode=EmptyContainerMode.SEQUENCE
|
|
62
|
+
),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
@lru_cache(maxsize=1)
|
|
67
|
+
def get_instance(cls) -> Self:
|
|
68
|
+
return cls()
|
|
69
|
+
|
|
70
|
+
def parse(
|
|
71
|
+
self, text: str, use_tuples: bool, empty_mode: EmptyContainerMode
|
|
72
|
+
) -> ZonType:
|
|
73
|
+
try:
|
|
74
|
+
tree = self.parser.parse(text)
|
|
75
|
+
transformer = self._transformers[(use_tuples, empty_mode)]
|
|
76
|
+
return transformer.transform(tree)
|
|
77
|
+
except UnexpectedInput as e:
|
|
78
|
+
# Lark's UnexpectedEOF uses -1. UnexpectedToken can use '?'.
|
|
79
|
+
# We normalize any non-positive integer to None.
|
|
80
|
+
line = e.line if isinstance(e.line, int) and e.line > 0 else None
|
|
81
|
+
column = e.column if isinstance(e.column, int) and e.column > 0 else None
|
|
82
|
+
|
|
83
|
+
raise ZonDecodeError(
|
|
84
|
+
"Syntax error in ZON text",
|
|
85
|
+
original_exc=e,
|
|
86
|
+
line=line,
|
|
87
|
+
column=column,
|
|
88
|
+
) from e
|
|
89
|
+
except VisitError as e:
|
|
90
|
+
raise ZonDecodeError(
|
|
91
|
+
f"Data transformation failed: {e.orig_exc!s}",
|
|
92
|
+
original_exc=e.orig_exc,
|
|
93
|
+
) from e
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def loads(
|
|
97
|
+
text: str,
|
|
98
|
+
*,
|
|
99
|
+
use_tuples: bool = False,
|
|
100
|
+
empty_mode: EmptyContainerMode = EmptyContainerMode.DICT,
|
|
101
|
+
) -> ZonType:
|
|
102
|
+
"""Parses a ZON string into standard Python data structures.
|
|
103
|
+
|
|
104
|
+
Similar to `json.loads()`.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
text: The ZON string to parse.
|
|
108
|
+
use_tuples: If True, parses "ZON arrays" as tuples instead of lists.
|
|
109
|
+
empty_mode: Controls whether an empty structure `.{}`
|
|
110
|
+
becomes a "dict", or a "list/tuple".
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
ZonType: The parsed Python data structure.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
ZonDecodeError: If the ZON string contains invalid syntax or cannot be transformed.
|
|
117
|
+
ZonInternalError: If the parser's internal grammar fails to load.
|
|
118
|
+
"""
|
|
119
|
+
return _ZonParserImpl.get_instance().parse(
|
|
120
|
+
text, use_tuples=use_tuples, empty_mode=empty_mode
|
|
121
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Type definitions for the ZON parser."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from typing import final
|
|
5
|
+
|
|
6
|
+
# Represents any valid ZON value, including recursive collections.
|
|
7
|
+
type ZonType = (
|
|
8
|
+
None
|
|
9
|
+
| str
|
|
10
|
+
| int
|
|
11
|
+
| float
|
|
12
|
+
| bool
|
|
13
|
+
| dict[str, ZonType]
|
|
14
|
+
| list[ZonType]
|
|
15
|
+
| tuple[ZonType, ...]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@final
|
|
20
|
+
class EmptyContainerMode(StrEnum):
|
|
21
|
+
"""Controls how empty ZON structures `.{}` are parsed.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
DICT: Parses `.{}` as an empty dictionary `{}`.
|
|
25
|
+
SEQUENCE: Parses `.{}` as an empty sequence (list `[]` or tuple `()`, depending on parser settings).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
DICT = "dict"
|
|
29
|
+
SEQUENCE = "sequence"
|