hitori 1.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,55 @@
1
+ # Blue Oak Model License
2
+
3
+ Version 1.0.0
4
+
5
+ ## Purpose
6
+
7
+ This license gives everyone as much permission to work with
8
+ this software as possible, while protecting contributors
9
+ from liability.
10
+
11
+ ## Acceptance
12
+
13
+ In order to receive this license, you must agree to its
14
+ rules. The rules of this license are both obligations
15
+ under that agreement and conditions to your license.
16
+ You must not do anything with this software that triggers
17
+ a rule that you cannot or will not follow.
18
+
19
+ ## Copyright
20
+
21
+ Each contributor licenses you to do everything with this
22
+ software that would otherwise infringe that contributor's
23
+ copyright in it.
24
+
25
+ ## Notices
26
+
27
+ You must ensure that everyone who gets a copy of
28
+ any part of this software from you, with or without
29
+ changes, also gets the text of this license or a link to
30
+ <https://blueoakcouncil.org/license/1.0.0>.
31
+
32
+ ## Excuse
33
+
34
+ If anyone notifies you in writing that you have not
35
+ complied with [Notices](#notices), you can keep your
36
+ license by taking all practical steps to comply within 30
37
+ days after the notice. If you do not do so, your license
38
+ ends immediately.
39
+
40
+ ## Patent
41
+
42
+ Each contributor licenses you to do everything with this
43
+ software that would otherwise infringe any patent claims
44
+ they can license or become able to license.
45
+
46
+ ## Reliability
47
+
48
+ No contributor can revoke this license.
49
+
50
+ ## No Liability
51
+
52
+ ***As far as the law allows, this software comes as is,
53
+ without any warranty or condition, and no contributor
54
+ will be liable to anyone for any damages related to this
55
+ software or this license, under any kind of legal claim.***
hitori-1.0.1/PKG-INFO ADDED
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.5
2
+ Name: hitori
3
+ Version: 1.0.1
4
+ Summary: Parsing and serializing Sakura Expressions
5
+ Keywords: Sakura Expressions,S-Expression,Parser
6
+ Author-email: Hannes Braun <hannes@hannesbraun.net>
7
+ Requires-Python: >= 3.12
8
+ Description-Content-Type: text/markdown
9
+ License-Expression: BlueOak-1.0.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Topic :: Software Development
14
+ License-File: LICENSE.md
15
+ Project-URL: Changelog, https://git.sr.ht/~hannes/hitori/refs
16
+ Project-URL: Documentation, https://hannesbraun.net/share/doc/hitori/index.html
17
+ Project-URL: Homepage, https://git.sr.ht/~hannes/hitori
18
+ Project-URL: Repository, https://git.sr.ht/~hannes/hitori
19
+ Import-Name: hitori
20
+
21
+ # hitori
22
+
23
+ *a Python library for [Sakura Expressions](https://git.sr.ht/~hannes/sakura-expressions)*
24
+
25
+ This library also supports parsing in fragments. The input can be given piece by
26
+ piece to the library before the final result is returned.
27
+
28
+ ## Installation
29
+
30
+ The package can be installed via the PyPI:
31
+
32
+ pip install hitori
33
+
34
+ ## Usage
35
+
36
+ You can find a documentation of the API here:
37
+ https://hannesbraun.net/share/doc/hitori
38
+
39
+ Here is a small example:
40
+
41
+ ```python
42
+ import hitori
43
+
44
+ root = hitori.parse("(foo bar baz)")
45
+ print(root[0][0]) # prints "foo"
46
+ serialized = hitori.serialize(root)
47
+ print(serialized) # prints "(foo bar baz)"
48
+ ```
49
+
50
+ ## License
51
+
52
+ hitori is published under the terms of the [Blue Oak Model License](LICENSE.md).
53
+
54
+
hitori-1.0.1/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # hitori
2
+
3
+ *a Python library for [Sakura Expressions](https://git.sr.ht/~hannes/sakura-expressions)*
4
+
5
+ This library also supports parsing in fragments. The input can be given piece by
6
+ piece to the library before the final result is returned.
7
+
8
+ ## Installation
9
+
10
+ The package can be installed via the PyPI:
11
+
12
+ pip install hitori
13
+
14
+ ## Usage
15
+
16
+ You can find a documentation of the API here:
17
+ https://hannesbraun.net/share/doc/hitori
18
+
19
+ Here is a small example:
20
+
21
+ ```python
22
+ import hitori
23
+
24
+ root = hitori.parse("(foo bar baz)")
25
+ print(root[0][0]) # prints "foo"
26
+ serialized = hitori.serialize(root)
27
+ print(serialized) # prints "(foo bar baz)"
28
+ ```
29
+
30
+ ## License
31
+
32
+ hitori is published under the terms of the [Blue Oak Model License](LICENSE.md).
33
+
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class ParseException(Exception):
5
+ pass
6
+
7
+
8
+ class SerializationException(Exception):
9
+ pass
10
+
11
+
12
+ type SExpression = list[SExpression] | str
13
+
14
+
15
+ class Parser:
16
+ """An S-Expression parser"""
17
+
18
+ def __init__(self):
19
+ self._cur_atom = ""
20
+ self._stack = [[]]
21
+ self._inside_comment = False
22
+ self._need_whitespace = False
23
+ self._inside_unquoted = False
24
+ self._inside_quoted = False
25
+ self._escape_active = False
26
+
27
+ def _push_char(self, c: str):
28
+ self._cur_atom += c
29
+
30
+ def _push_atom(self):
31
+ self._stack[-1].append(self._cur_atom)
32
+ self._cur_atom = ""
33
+
34
+ def _start_list(self):
35
+ self._stack.append([])
36
+
37
+ def _end_list(self):
38
+ self._stack[-2].append(self._stack[-1])
39
+ self._stack.pop()
40
+
41
+ def parse(self, s: str):
42
+ """
43
+ Parses the given string into an S-Expression.
44
+
45
+ This method can be called multiple times which might be useful if
46
+ the expression is received over the network and therefore split across
47
+ multiple strings. For example: calling `parse` with "a", "b" and then
48
+ "c" is the same as calling it once with "abc".
49
+ To retrieve the parsed value, call `Parser.finish`.
50
+ """
51
+
52
+ for val in s:
53
+ c = ord(val)
54
+ if (c < 0x20 and c != 0x9 and c != 0xa and c != 0xd) or c == 0x7f:
55
+ raise ParseException("invalid character")
56
+ if self._inside_comment:
57
+ if val == "\n":
58
+ self._inside_comment = False
59
+ elif self._escape_active:
60
+ self._push_char(val)
61
+ self._escape_active = False
62
+ elif val == "\\":
63
+ if self._need_whitespace:
64
+ raise ParseException("atom must be followed by a whitespace character or comment before the next atom starts")
65
+ self._escape_active = True
66
+ if not self._inside_quoted:
67
+ self._inside_unquoted = True
68
+ elif val == ";" and not self._inside_quoted:
69
+ if self._inside_unquoted:
70
+ self._inside_unquoted = False
71
+ self._push_atom()
72
+ self._inside_comment = True
73
+ self._need_whitespace = False
74
+ elif val == '"':
75
+ if self._inside_unquoted:
76
+ raise ParseException("a double quote is not allowed inside of an unquoted atom")
77
+ if self._need_whitespace:
78
+ raise ParseException("atom must be followed by a whitespace character or comment before the next atom starts")
79
+
80
+ if self._inside_quoted:
81
+ self._inside_quoted = False
82
+ self._push_atom()
83
+ self._need_whitespace = True
84
+ else:
85
+ self._inside_quoted = True
86
+ elif val == "(" and not self._inside_quoted:
87
+ if self._cur_atom != "":
88
+ self._push_atom()
89
+ self._inside_unquoted = False
90
+ self._need_whitespace = False
91
+ self._start_list()
92
+ elif val == ")" and not self._inside_quoted:
93
+ if self._cur_atom != "":
94
+ self._push_atom()
95
+ self._inside_unquoted = False
96
+ self._need_whitespace = False
97
+ self._end_list()
98
+ elif not self._inside_quoted and (val == " " or c == 0xa or c == 0x9 or c == 0xd):
99
+ if self._cur_atom != "":
100
+ self._push_atom()
101
+ self._inside_unquoted = False
102
+ self._need_whitespace = False
103
+ else:
104
+ if self._need_whitespace:
105
+ raise ParseException("atom must be followed by a whitespace character or comment before the next atom starts")
106
+ if not self._inside_quoted:
107
+ self._inside_unquoted = True
108
+ self._push_char(val)
109
+
110
+ def finish(self) -> list[SExpression]:
111
+ """Signals that no more data will be passed and returns the parsed S-Expression."""
112
+ if len(self._stack) > 1:
113
+ raise ParseException("at least one list is not closed")
114
+ if len(self._stack) == 0:
115
+ raise ParseException("too many closing parenthesis")
116
+ if self._escape_active:
117
+ raise ParseException("incomplete escape sequence")
118
+ elif self._cur_atom != "" and self._inside_unquoted:
119
+ self._push_atom()
120
+ elif self._inside_quoted:
121
+ raise ParseException("missing trailing quote for atom")
122
+ return self._stack[0]
123
+
124
+
125
+ def parse(data: str) -> list[SExpression]:
126
+ """Parses a string into an S-Expression"""
127
+ parser = Parser()
128
+ parser.parse(data)
129
+ return parser.finish()
130
+
131
+
132
+ def serialize(expressions: list[SExpression], compact: bool = False) -> str:
133
+ """
134
+ Serializes a list of S-Expressions to a string
135
+
136
+ The argument `compact` controls if spaces between elements in a list should
137
+ be left out when they are not necessary.
138
+ """
139
+
140
+ s = ""
141
+ was_atom = False
142
+ for i, sexp in enumerate(expressions):
143
+ is_atom = isinstance(sexp, str)
144
+ if compact and was_atom and is_atom:
145
+ s += " "
146
+ if is_atom:
147
+ quote = sexp == "" or any(c == " " or c == "(" or c == ")" or c == ";" or c == "\n" or c == "\r" or c == "\t" for c in sexp)
148
+ if quote:
149
+ s += '"'
150
+ for c in sexp:
151
+ if c == '"' or c == "\\":
152
+ s += "\\"
153
+ s += c
154
+ if quote:
155
+ s += '"'
156
+ elif isinstance(sexp, list):
157
+ s += "("
158
+ s += serialize(sexp, compact)
159
+ s += ")"
160
+ else:
161
+ raise SerializationException(f"the value {sexp} of type {type(sexp)} cannot be serialized into an s-expression")
162
+ if not compact and i < len(expressions) - 1:
163
+ s += " "
164
+ was_atom = is_atom
165
+ return s
166
+
File without changes
@@ -0,0 +1,27 @@
1
+ [project]
2
+ name = "hitori"
3
+ version = "1.0.1"
4
+ description = "Parsing and serializing Sakura Expressions"
5
+ authors = [{name = "Hannes Braun", email = "hannes@hannesbraun.net"}]
6
+ requires-python = ">= 3.12"
7
+ readme = "README.md"
8
+ license = "BlueOak-1.0.0"
9
+ license-files = ["LICENSE.md"]
10
+ keywords = ["Sakura Expressions", "S-Expression", "Parser"]
11
+ classifiers = [
12
+ "Development Status :: 5 - Production/Stable",
13
+ "Intended Audience :: Developers",
14
+ "Programming Language :: Python",
15
+ "Topic :: Software Development"
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://git.sr.ht/~hannes/hitori"
20
+ Documentation = "https://hannesbraun.net/share/doc/hitori/index.html"
21
+ Repository = "https://git.sr.ht/~hannes/hitori"
22
+ Changelog = "https://git.sr.ht/~hannes/hitori/refs"
23
+
24
+ [build-system]
25
+ requires = ["flit_core >= 3.12, < 5"]
26
+ build-backend = "flit_core.buildapi"
27
+