astreum 0.2.61__py3-none-any.whl → 0.3.9__py3-none-any.whl
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.
- astreum/__init__.py +16 -7
- astreum/{_communication → communication}/__init__.py +3 -3
- astreum/communication/handlers/handshake.py +89 -0
- astreum/communication/handlers/object_request.py +176 -0
- astreum/communication/handlers/object_response.py +115 -0
- astreum/communication/handlers/ping.py +34 -0
- astreum/communication/handlers/route_request.py +76 -0
- astreum/communication/handlers/route_response.py +53 -0
- astreum/communication/models/__init__.py +0 -0
- astreum/communication/models/message.py +124 -0
- astreum/communication/models/peer.py +51 -0
- astreum/{_communication → communication/models}/route.py +7 -12
- astreum/communication/processors/__init__.py +0 -0
- astreum/communication/processors/incoming.py +98 -0
- astreum/communication/processors/outgoing.py +20 -0
- astreum/communication/setup.py +166 -0
- astreum/communication/start.py +37 -0
- astreum/{_communication → communication}/util.py +7 -0
- astreum/consensus/__init__.py +20 -0
- astreum/consensus/genesis.py +66 -0
- astreum/consensus/models/__init__.py +0 -0
- astreum/consensus/models/account.py +84 -0
- astreum/consensus/models/accounts.py +72 -0
- astreum/consensus/models/block.py +364 -0
- astreum/{_consensus → consensus/models}/chain.py +7 -7
- astreum/{_consensus → consensus/models}/fork.py +8 -8
- astreum/consensus/models/receipt.py +98 -0
- astreum/{_consensus → consensus/models}/transaction.py +76 -78
- astreum/{_consensus → consensus}/setup.py +18 -50
- astreum/consensus/start.py +67 -0
- astreum/consensus/validator.py +95 -0
- astreum/{_consensus → consensus}/workers/discovery.py +19 -1
- astreum/consensus/workers/validation.py +307 -0
- astreum/{_consensus → consensus}/workers/verify.py +29 -2
- astreum/crypto/chacha20poly1305.py +74 -0
- astreum/machine/__init__.py +20 -0
- astreum/machine/evaluations/__init__.py +0 -0
- astreum/{_lispeum → machine/evaluations}/high_evaluation.py +237 -236
- astreum/machine/evaluations/low_evaluation.py +281 -0
- astreum/machine/evaluations/script_evaluation.py +27 -0
- astreum/machine/models/__init__.py +0 -0
- astreum/machine/models/environment.py +31 -0
- astreum/{_lispeum → machine/models}/expression.py +36 -8
- astreum/machine/tokenizer.py +90 -0
- astreum/node.py +78 -767
- astreum/storage/__init__.py +7 -0
- astreum/storage/actions/get.py +183 -0
- astreum/storage/actions/set.py +178 -0
- astreum/{_storage → storage/models}/atom.py +55 -57
- astreum/{_storage/patricia.py → storage/models/trie.py} +227 -203
- astreum/storage/requests.py +28 -0
- astreum/storage/setup.py +22 -15
- astreum/utils/config.py +48 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/METADATA +27 -26
- astreum-0.3.9.dist-info/RECORD +71 -0
- astreum/_communication/message.py +0 -101
- astreum/_communication/peer.py +0 -23
- astreum/_communication/setup.py +0 -322
- astreum/_consensus/__init__.py +0 -20
- astreum/_consensus/account.py +0 -95
- astreum/_consensus/accounts.py +0 -38
- astreum/_consensus/block.py +0 -311
- astreum/_consensus/genesis.py +0 -72
- astreum/_consensus/receipt.py +0 -136
- astreum/_consensus/workers/validation.py +0 -125
- astreum/_lispeum/__init__.py +0 -16
- astreum/_lispeum/environment.py +0 -13
- astreum/_lispeum/low_evaluation.py +0 -123
- astreum/_lispeum/tokenizer.py +0 -22
- astreum/_node.py +0 -198
- astreum/_storage/__init__.py +0 -7
- astreum/_storage/setup.py +0 -35
- astreum/format.py +0 -75
- astreum/models/block.py +0 -441
- astreum/models/merkle.py +0 -205
- astreum/models/patricia.py +0 -393
- astreum/storage/object.py +0 -68
- astreum-0.2.61.dist-info/RECORD +0 -57
- /astreum/{models → communication/handlers}/__init__.py +0 -0
- /astreum/{_communication → communication/models}/ping.py +0 -0
- /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
- /astreum/{_lispeum → machine/models}/meter.py +0 -0
- /astreum/{_lispeum → machine}/parser.py +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/WHEEL +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from typing import Dict, List, Union
|
|
2
|
+
|
|
3
|
+
from astreum.storage.models.atom import ZERO32, Atom, AtomKind
|
|
4
|
+
from ..models.expression import Expr, error_expr
|
|
5
|
+
from ..models.meter import Meter
|
|
6
|
+
|
|
7
|
+
def tc_to_int(b: bytes) -> int:
|
|
8
|
+
"""bytes -> int using two's complement (width = len(b)*8)."""
|
|
9
|
+
if not b:
|
|
10
|
+
return 0
|
|
11
|
+
return int.from_bytes(b, "big", signed=True)
|
|
12
|
+
|
|
13
|
+
def int_to_tc(n: int, width_bytes: int) -> bytes:
|
|
14
|
+
"""int -> bytes (two's complement, fixed width)."""
|
|
15
|
+
if width_bytes <= 0:
|
|
16
|
+
return b"\x00"
|
|
17
|
+
return n.to_bytes(width_bytes, "big", signed=True)
|
|
18
|
+
|
|
19
|
+
def min_tc_width(n: int) -> int:
|
|
20
|
+
"""minimum bytes to store n in two's complement."""
|
|
21
|
+
if n == 0:
|
|
22
|
+
return 1
|
|
23
|
+
w = 1
|
|
24
|
+
while True:
|
|
25
|
+
try:
|
|
26
|
+
n.to_bytes(w, "big", signed=True)
|
|
27
|
+
return w
|
|
28
|
+
except OverflowError:
|
|
29
|
+
w += 1
|
|
30
|
+
|
|
31
|
+
def nand_bytes(a: bytes, b: bytes) -> bytes:
|
|
32
|
+
"""Bitwise NAND (NOT-AND) on two byte strings, zero-extending to max width.
|
|
33
|
+
|
|
34
|
+
The NAND gate yields 0 only when all inputs are 1; otherwise it outputs 1.
|
|
35
|
+
Truth table:
|
|
36
|
+
A | B | NAND
|
|
37
|
+
0 | 0 | 1
|
|
38
|
+
0 | 1 | 1
|
|
39
|
+
1 | 0 | 1
|
|
40
|
+
1 | 1 | 0
|
|
41
|
+
"""
|
|
42
|
+
w = max(len(a), len(b), 1)
|
|
43
|
+
au = int.from_bytes(a.rjust(w, b"\x00"), "big", signed=False)
|
|
44
|
+
bu = int.from_bytes(b.rjust(w, b"\x00"), "big", signed=False)
|
|
45
|
+
mask = (1 << (w * 8)) - 1
|
|
46
|
+
resu = (~(au & bu)) & mask
|
|
47
|
+
return resu.to_bytes(w, "big", signed=False)
|
|
48
|
+
|
|
49
|
+
def low_eval(self, code: List[bytes], meter: Meter) -> Expr:
|
|
50
|
+
"""Execute low-level bytecode with stack/heap semantics under metering."""
|
|
51
|
+
heap: Dict[bytes, bytes] = {}
|
|
52
|
+
|
|
53
|
+
stack: List[bytes] = []
|
|
54
|
+
pc = 0
|
|
55
|
+
|
|
56
|
+
while True:
|
|
57
|
+
if pc >= len(code):
|
|
58
|
+
if len(stack) != 1:
|
|
59
|
+
return error_expr("low_eval", "bad stack")
|
|
60
|
+
# wrap successful result as an Expr.Bytes
|
|
61
|
+
return Expr.Bytes(stack.pop())
|
|
62
|
+
|
|
63
|
+
tok = code[pc]
|
|
64
|
+
pc += 1
|
|
65
|
+
|
|
66
|
+
# ---------- ADD ----------
|
|
67
|
+
# if tok == b"add":
|
|
68
|
+
# if len(stack) < 2:
|
|
69
|
+
# return error_expr("low_eval", "underflow")
|
|
70
|
+
# b_b = stack.pop()
|
|
71
|
+
# a_b = stack.pop()
|
|
72
|
+
# a_i = tc_to_int(a_b)
|
|
73
|
+
# b_i = tc_to_int(b_b)
|
|
74
|
+
# res_i = a_i + b_i
|
|
75
|
+
# width = max(len(a_b), len(b_b), min_tc_width(res_i))
|
|
76
|
+
# res_b = int_to_tc(res_i, width)
|
|
77
|
+
# # charge for both operands' byte widths
|
|
78
|
+
# if not meter.charge_bytes(len(a_b) + len(b_b)):
|
|
79
|
+
# return error_expr("low_eval", "meter limit")
|
|
80
|
+
# stack.append(res_b)
|
|
81
|
+
# continue
|
|
82
|
+
|
|
83
|
+
# ---------- NAND ----------
|
|
84
|
+
|
|
85
|
+
if tok == b"nand":
|
|
86
|
+
if len(stack) < 2:
|
|
87
|
+
return error_expr("low_eval", "underflow")
|
|
88
|
+
b_b = stack.pop()
|
|
89
|
+
a_b = stack.pop()
|
|
90
|
+
res_b = nand_bytes(a_b, b_b)
|
|
91
|
+
# bitwise cost: 2 * max(len(a), len(b))
|
|
92
|
+
if not meter.charge_bytes(2 * max(len(a_b), len(b_b), 1)):
|
|
93
|
+
return error_expr("low_eval", "meter limit")
|
|
94
|
+
stack.append(res_b)
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# ---------- JUMP ----------
|
|
98
|
+
if tok == b"jump":
|
|
99
|
+
if len(stack) < 1:
|
|
100
|
+
return error_expr("low_eval", "underflow")
|
|
101
|
+
tgt_b = stack.pop()
|
|
102
|
+
if not meter.charge_bytes(1):
|
|
103
|
+
return error_expr("low_eval", "meter limit")
|
|
104
|
+
tgt_i = tc_to_int(tgt_b)
|
|
105
|
+
if tgt_i < 0 or tgt_i >= len(code):
|
|
106
|
+
return error_expr("low_eval", "bad jump")
|
|
107
|
+
pc = tgt_i
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
# ---------- HEAP GET ----------
|
|
111
|
+
if tok == b"heap_get":
|
|
112
|
+
if len(stack) < 1:
|
|
113
|
+
return error_expr("low_eval", "underflow")
|
|
114
|
+
key = stack.pop()
|
|
115
|
+
val = heap.get(key) or b""
|
|
116
|
+
# get cost: 1
|
|
117
|
+
if not meter.charge_bytes(1):
|
|
118
|
+
return error_expr("low_eval", "meter limit")
|
|
119
|
+
stack.append(val)
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
# ---------- HEAP SET ----------
|
|
123
|
+
if tok == b"heap_set":
|
|
124
|
+
if len(stack) < 2:
|
|
125
|
+
return error_expr("low_eval", "underflow")
|
|
126
|
+
val = stack.pop()
|
|
127
|
+
key = stack.pop()
|
|
128
|
+
if not meter.charge_bytes(len(val)):
|
|
129
|
+
return error_expr("low_eval", "meter limit")
|
|
130
|
+
heap[key] = val
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
# ---------- ATOM SLICE ----------
|
|
134
|
+
if tok == b"atom_slice":
|
|
135
|
+
if len(stack) < 3:
|
|
136
|
+
return error_expr("low_eval", "underflow")
|
|
137
|
+
len_b = stack.pop()
|
|
138
|
+
idx_b = stack.pop()
|
|
139
|
+
id_b = stack.pop()
|
|
140
|
+
|
|
141
|
+
idx = tc_to_int(idx_b)
|
|
142
|
+
length = tc_to_int(len_b)
|
|
143
|
+
if idx < 0 or length < 0:
|
|
144
|
+
return error_expr("low_eval", "bad slice")
|
|
145
|
+
|
|
146
|
+
atom = self.storage_get(key=id_b)
|
|
147
|
+
if atom is None:
|
|
148
|
+
return error_expr("low_eval", "unknown atom")
|
|
149
|
+
|
|
150
|
+
data = atom.data
|
|
151
|
+
slice_bytes = data[idx : idx + length]
|
|
152
|
+
|
|
153
|
+
if not meter.charge_bytes(len(slice_bytes)):
|
|
154
|
+
return error_expr("low_eval", "meter limit")
|
|
155
|
+
|
|
156
|
+
new_atom = Atom(data=slice_bytes, kind=atom.kind)
|
|
157
|
+
new_id = new_atom.object_id()
|
|
158
|
+
try:
|
|
159
|
+
self._hot_storage_set(key=new_id, value=new_atom)
|
|
160
|
+
except RuntimeError:
|
|
161
|
+
return error_expr("low_eval", "storage error")
|
|
162
|
+
|
|
163
|
+
stack.append(new_id)
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# ---------- ATOM LINK ----------
|
|
167
|
+
if tok == b"atom_link":
|
|
168
|
+
if len(stack) < 2:
|
|
169
|
+
return error_expr("low_eval", "underflow")
|
|
170
|
+
id2_b = stack.pop()
|
|
171
|
+
id1_b = stack.pop()
|
|
172
|
+
|
|
173
|
+
if not meter.charge_bytes(len(id1_b) + len(id2_b)):
|
|
174
|
+
return error_expr("low_eval", "meter limit")
|
|
175
|
+
|
|
176
|
+
atom = self.storage_get(key=id_b)
|
|
177
|
+
if atom is None:
|
|
178
|
+
return error_expr("low_eval", "unknown atom")
|
|
179
|
+
|
|
180
|
+
new_atom = Atom(data=id1_b, next_id=id2_b, kind=atom.kind)
|
|
181
|
+
new_id = new_atom.object_id()
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
self._hot_storage_set(key=new_id, value=new_atom)
|
|
185
|
+
except RuntimeError:
|
|
186
|
+
return error_expr("low_eval", "storage error")
|
|
187
|
+
|
|
188
|
+
stack.append(new_id)
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
# ---------- ATOM CONCAT ----------
|
|
192
|
+
if tok == b"atom_concat":
|
|
193
|
+
if len(stack) < 2:
|
|
194
|
+
return error_expr("low_eval", "underflow")
|
|
195
|
+
id2_b = stack.pop()
|
|
196
|
+
id1_b = stack.pop()
|
|
197
|
+
|
|
198
|
+
atom1 = self.storage_get(key=id1_b)
|
|
199
|
+
atom2 = self.storage_get(key=id2_b)
|
|
200
|
+
if atom1 is None or atom2 is None:
|
|
201
|
+
return error_expr("low_eval", "unknown atom")
|
|
202
|
+
|
|
203
|
+
joined = atom1.data + atom2.data
|
|
204
|
+
|
|
205
|
+
if not meter.charge_bytes(len(joined)):
|
|
206
|
+
return error_expr("low_eval", "meter limit")
|
|
207
|
+
|
|
208
|
+
new_atom = Atom(data=joined, kind=AtomKind.BYTES)
|
|
209
|
+
new_id = new_atom.object_id()
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
self._hot_storage_set(key=new_id, value=new_atom)
|
|
213
|
+
except RuntimeError:
|
|
214
|
+
return error_expr("low_eval", "storage error")
|
|
215
|
+
|
|
216
|
+
stack.append(new_id)
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
# ---------- ATOM NEW ----------
|
|
220
|
+
|
|
221
|
+
if tok == b"atom_new":
|
|
222
|
+
if len(stack) < 2:
|
|
223
|
+
return error_expr("low_eval", "underflow")
|
|
224
|
+
data_b = stack.pop()
|
|
225
|
+
kind_b = stack.pop()
|
|
226
|
+
|
|
227
|
+
if len(kind_b) != 1:
|
|
228
|
+
return error_expr("low_eval", "bad atom kind")
|
|
229
|
+
|
|
230
|
+
kind_value = kind_b[0]
|
|
231
|
+
try:
|
|
232
|
+
kind = AtomKind(kind_value)
|
|
233
|
+
except ValueError:
|
|
234
|
+
return error_expr("low_eval", "unknown atom kind")
|
|
235
|
+
|
|
236
|
+
if not meter.charge_bytes(len(data_b)):
|
|
237
|
+
return error_expr("low_eval", "meter limit")
|
|
238
|
+
|
|
239
|
+
new_atom = Atom(data=data_b, kind=kind)
|
|
240
|
+
new_id = new_atom.object_id()
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
self._hot_storage_set(key=new_id, value=new_atom)
|
|
244
|
+
except RuntimeError:
|
|
245
|
+
return error_expr("low_eval", "storage error")
|
|
246
|
+
|
|
247
|
+
stack.append(new_id)
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
# ---------- ATOM LOAD ----------
|
|
251
|
+
|
|
252
|
+
if tok == b"atom_load":
|
|
253
|
+
if len(stack) < 3:
|
|
254
|
+
return error_expr("low_eval", "underflow")
|
|
255
|
+
len_b = stack.pop()
|
|
256
|
+
idx_b = stack.pop()
|
|
257
|
+
id_b = stack.pop()
|
|
258
|
+
|
|
259
|
+
idx = tc_to_int(idx_b)
|
|
260
|
+
length = tc_to_int(len_b)
|
|
261
|
+
if idx < 0 or length < 0:
|
|
262
|
+
return error_expr("low_eval", "bad load")
|
|
263
|
+
if length > 32:
|
|
264
|
+
return error_expr("low_eval", "load too wide")
|
|
265
|
+
|
|
266
|
+
atom = self.storage_get(key=id_b)
|
|
267
|
+
if atom is None:
|
|
268
|
+
return error_expr("low_eval", "unknown atom")
|
|
269
|
+
|
|
270
|
+
data = atom.data
|
|
271
|
+
chunk = data[idx : idx + length]
|
|
272
|
+
|
|
273
|
+
if not meter.charge_bytes(len(chunk)):
|
|
274
|
+
return error_expr("low_eval", "meter limit")
|
|
275
|
+
|
|
276
|
+
stack.append(chunk)
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
# if no opcode matched above, treat token as literal
|
|
280
|
+
# not an opcode → literal blob
|
|
281
|
+
stack.append(tok)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""High level eval helper that reuses the existing tokenizer, parser and evaluator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from ..models.expression import Expr, error_expr
|
|
9
|
+
from ..parser import ParseError, parse
|
|
10
|
+
from ..tokenizer import tokenize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def script_eval(self, source: str, env_id: Optional[uuid.UUID] = None, meter=None) -> Expr:
|
|
14
|
+
"""Evaluate textual expressions by tokenizing, parsing and forwarding to high_eval."""
|
|
15
|
+
tokens = tokenize(source)
|
|
16
|
+
if not tokens:
|
|
17
|
+
return error_expr("eval", "no expression provided")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
expr, rest = parse(tokens)
|
|
21
|
+
except ParseError as exc:
|
|
22
|
+
return error_expr("eval", f"parse error: {exc}")
|
|
23
|
+
|
|
24
|
+
if rest:
|
|
25
|
+
return error_expr("eval", "unexpected tokens after expression")
|
|
26
|
+
|
|
27
|
+
return self.high_eval(expr=expr, env_id=env_id, meter=meter)
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from ast import Expr
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Env:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
data: Optional[Dict[str, Expr]] = None,
|
|
10
|
+
parent_id: Optional[uuid.UUID] = None,
|
|
11
|
+
):
|
|
12
|
+
self.data: Dict[str, Expr] = {} if data is None else data
|
|
13
|
+
self.parent_id = parent_id
|
|
14
|
+
|
|
15
|
+
def env_get(self, env_id: uuid.UUID, key: str) -> Optional[Expr]:
|
|
16
|
+
"""Resolve a value by walking the environment chain starting at env_id."""
|
|
17
|
+
cur = self.environments.get(env_id)
|
|
18
|
+
while cur is not None:
|
|
19
|
+
if key in cur.data:
|
|
20
|
+
return cur.data[key]
|
|
21
|
+
cur = self.environments.get(cur.parent_id) if cur.parent_id else None
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
def env_set(self, env_id: uuid.UUID, key: str, value: Expr) -> bool:
|
|
25
|
+
"""Bind a value to key within the specified environment if it exists."""
|
|
26
|
+
with self.machine_environments_lock:
|
|
27
|
+
env = self.environments.get(env_id)
|
|
28
|
+
if env is None:
|
|
29
|
+
return False
|
|
30
|
+
env.data[key] = value
|
|
31
|
+
return True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Any, List, Optional, Tuple
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from ...storage.models.atom import Atom, AtomKind
|
|
4
4
|
|
|
5
5
|
ZERO32 = b"\x00" * 32
|
|
6
6
|
ERROR_SYMBOL = "error"
|
|
@@ -88,7 +88,7 @@ class Expr:
|
|
|
88
88
|
|
|
89
89
|
if kind_enum is AtomKind.LIST:
|
|
90
90
|
# Empty list sentinel: zero-length payload and no next pointer.
|
|
91
|
-
if len(type_atom.data) == 0 and type_atom.
|
|
91
|
+
if len(type_atom.data) == 0 and type_atom.next_id == ZERO32:
|
|
92
92
|
return cls.ListExpr([])
|
|
93
93
|
|
|
94
94
|
elements: List[Expr] = []
|
|
@@ -102,8 +102,8 @@ class Expr:
|
|
|
102
102
|
raise ValueError("list element hash has unexpected length")
|
|
103
103
|
child_expr = cls.from_atoms(node, child_hash)
|
|
104
104
|
elements.append(child_expr)
|
|
105
|
-
next_id = current_atom.
|
|
106
|
-
if
|
|
105
|
+
next_id = current_atom.next_id
|
|
106
|
+
if next_id == ZERO32:
|
|
107
107
|
break
|
|
108
108
|
next_atom = _require(next_id, f"list element {idx}")
|
|
109
109
|
next_kind = _atom_kind(next_atom)
|
|
@@ -118,11 +118,17 @@ class Expr:
|
|
|
118
118
|
@staticmethod
|
|
119
119
|
def to_atoms(e: "Expr") -> Tuple[bytes, List[Atom]]:
|
|
120
120
|
def symbol(value: str) -> Tuple[bytes, List[Atom]]:
|
|
121
|
-
atom = Atom
|
|
121
|
+
atom = Atom(
|
|
122
|
+
data=value.encode("utf-8"),
|
|
123
|
+
kind=AtomKind.SYMBOL,
|
|
124
|
+
)
|
|
122
125
|
return atom.object_id(), [atom]
|
|
123
126
|
|
|
124
127
|
def bytes_value(data: bytes) -> Tuple[bytes, List[Atom]]:
|
|
125
|
-
atom = Atom
|
|
128
|
+
atom = Atom(
|
|
129
|
+
data=data,
|
|
130
|
+
kind=AtomKind.BYTES,
|
|
131
|
+
)
|
|
126
132
|
return atom.object_id(), [atom]
|
|
127
133
|
|
|
128
134
|
def lst(items: List["Expr"]) -> Tuple[bytes, List[Atom]]:
|
|
@@ -135,14 +141,14 @@ class Expr:
|
|
|
135
141
|
next_hash = ZERO32
|
|
136
142
|
elem_atoms: List[Atom] = []
|
|
137
143
|
for h in reversed(child_hashes):
|
|
138
|
-
a = Atom
|
|
144
|
+
a = Atom(data=h, next_id=next_hash, kind=AtomKind.LIST)
|
|
139
145
|
next_hash = a.object_id()
|
|
140
146
|
elem_atoms.append(a)
|
|
141
147
|
elem_atoms.reverse()
|
|
142
148
|
if elem_atoms:
|
|
143
149
|
head = elem_atoms[0].object_id()
|
|
144
150
|
else:
|
|
145
|
-
empty_atom = Atom
|
|
151
|
+
empty_atom = Atom(data=b"", kind=AtomKind.LIST)
|
|
146
152
|
elem_atoms = [empty_atom]
|
|
147
153
|
head = empty_atom.object_id()
|
|
148
154
|
return head, acc + elem_atoms
|
|
@@ -188,3 +194,25 @@ def error_expr(topic: str, message: str) -> Expr.ListExpr:
|
|
|
188
194
|
Expr.Bytes(topic_bytes),
|
|
189
195
|
Expr.Bytes(message_bytes),
|
|
190
196
|
])
|
|
197
|
+
|
|
198
|
+
def get_expr_list_from_storage(self, key: bytes) -> Optional["ListExpr"]:
|
|
199
|
+
"""Load a list expression from storage using the given atom list root hash."""
|
|
200
|
+
atoms = self.get_atom_list_from_storage(root_hash=key)
|
|
201
|
+
if atoms is None:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
expr_list = []
|
|
205
|
+
for atom in atoms:
|
|
206
|
+
match atom.kind:
|
|
207
|
+
case AtomKind.SYMBOL:
|
|
208
|
+
expr_list.append(Expr.Symbol(atom.data))
|
|
209
|
+
case AtomKind.BYTES:
|
|
210
|
+
expr_list.append(Expr.Bytes(atom.data))
|
|
211
|
+
case AtomKind.LIST:
|
|
212
|
+
expr_list.append(Expr.ListExpr([
|
|
213
|
+
Expr.Bytes(atom.data),
|
|
214
|
+
Expr.Symbol("ref")
|
|
215
|
+
]))
|
|
216
|
+
|
|
217
|
+
expr_list.reverse()
|
|
218
|
+
return Expr.ListExpr(expr_list)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def tokenize(source: str) -> List[str]:
|
|
5
|
+
tokens: List[str] = []
|
|
6
|
+
cur: List[str] = []
|
|
7
|
+
n = len(source)
|
|
8
|
+
i = 0
|
|
9
|
+
|
|
10
|
+
def flush_cur() -> None:
|
|
11
|
+
if cur:
|
|
12
|
+
tokens.append("".join(cur))
|
|
13
|
+
cur.clear()
|
|
14
|
+
|
|
15
|
+
def skip_line_comment(idx: int) -> int:
|
|
16
|
+
while idx < n and source[idx] != "\n":
|
|
17
|
+
idx += 1
|
|
18
|
+
return idx
|
|
19
|
+
|
|
20
|
+
def skip_ws_and_comments(idx: int) -> int:
|
|
21
|
+
while idx < n:
|
|
22
|
+
ch = source[idx]
|
|
23
|
+
if ch.isspace():
|
|
24
|
+
flush_cur()
|
|
25
|
+
idx += 1
|
|
26
|
+
continue
|
|
27
|
+
if ch == ";":
|
|
28
|
+
flush_cur()
|
|
29
|
+
idx = skip_line_comment(idx + 1)
|
|
30
|
+
continue
|
|
31
|
+
break
|
|
32
|
+
return idx
|
|
33
|
+
|
|
34
|
+
def skip_expression(idx: int) -> int:
|
|
35
|
+
idx = skip_ws_and_comments(idx)
|
|
36
|
+
if idx >= n:
|
|
37
|
+
return n
|
|
38
|
+
ch = source[idx]
|
|
39
|
+
if ch == "(":
|
|
40
|
+
depth = 0
|
|
41
|
+
while idx < n:
|
|
42
|
+
ch = source[idx]
|
|
43
|
+
if ch == "(":
|
|
44
|
+
depth += 1
|
|
45
|
+
idx += 1
|
|
46
|
+
continue
|
|
47
|
+
if ch == ")":
|
|
48
|
+
depth -= 1
|
|
49
|
+
idx += 1
|
|
50
|
+
if depth == 0:
|
|
51
|
+
break
|
|
52
|
+
continue
|
|
53
|
+
if ch == ";":
|
|
54
|
+
idx = skip_line_comment(idx + 1)
|
|
55
|
+
continue
|
|
56
|
+
if ch == "#" and idx + 1 < n and source[idx + 1] == ";":
|
|
57
|
+
idx = skip_expression(idx + 2)
|
|
58
|
+
continue
|
|
59
|
+
idx += 1
|
|
60
|
+
return idx
|
|
61
|
+
if ch == ")":
|
|
62
|
+
return idx + 1
|
|
63
|
+
while idx < n:
|
|
64
|
+
ch = source[idx]
|
|
65
|
+
if ch.isspace() or ch in ("(", ")", ";"):
|
|
66
|
+
break
|
|
67
|
+
if ch == "#" and idx + 1 < n and source[idx + 1] == ";":
|
|
68
|
+
break
|
|
69
|
+
idx += 1
|
|
70
|
+
return idx
|
|
71
|
+
|
|
72
|
+
while i < n:
|
|
73
|
+
i = skip_ws_and_comments(i)
|
|
74
|
+
if i >= n:
|
|
75
|
+
break
|
|
76
|
+
ch = source[i]
|
|
77
|
+
if ch == "#" and i + 1 < n and source[i + 1] == ";":
|
|
78
|
+
flush_cur()
|
|
79
|
+
i = skip_expression(i + 2)
|
|
80
|
+
continue
|
|
81
|
+
if ch in ("(", ")"):
|
|
82
|
+
flush_cur()
|
|
83
|
+
tokens.append(ch)
|
|
84
|
+
i += 1
|
|
85
|
+
continue
|
|
86
|
+
cur.append(ch)
|
|
87
|
+
i += 1
|
|
88
|
+
|
|
89
|
+
flush_cur()
|
|
90
|
+
return tokens
|