astreum 0.1.5__py3-none-any.whl → 0.1.7__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.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- astreum/__init__.py +1 -0
- astreum/lispeum/storage.py +457 -0
- astreum/machine/__init__.py +3 -4
- astreum/machine/environment.py +9 -2
- astreum/node/__init__.py +461 -0
- astreum/node/models.py +96 -0
- astreum/node/relay/__init__.py +248 -0
- astreum/node/relay/bucket.py +80 -0
- astreum/node/relay/envelope.py +280 -0
- astreum/node/relay/message.py +105 -0
- astreum/node/relay/peer.py +171 -0
- astreum/node/relay/route.py +125 -0
- astreum/utils/__init__.py +0 -0
- astreum/utils/bytes_format.py +75 -0
- {astreum-0.1.5.dist-info → astreum-0.1.7.dist-info}/METADATA +2 -2
- {astreum-0.1.5.dist-info → astreum-0.1.7.dist-info}/RECORD +19 -8
- {astreum-0.1.5.dist-info → astreum-0.1.7.dist-info}/WHEEL +1 -1
- {astreum-0.1.5.dist-info → astreum-0.1.7.dist-info}/LICENSE +0 -0
- {astreum-0.1.5.dist-info → astreum-0.1.7.dist-info}/top_level.txt +0 -0
astreum/__init__.py
CHANGED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Storage utilities for Lispeum expressions.
|
|
3
|
+
|
|
4
|
+
This module provides functions to convert Lispeum expressions to an
|
|
5
|
+
object-based Merkle tree representation for storage and retrieval.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import struct
|
|
10
|
+
from typing import Dict, Tuple, Any, List, Optional
|
|
11
|
+
|
|
12
|
+
from astreum.lispeum.expression import Expr
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def expr_to_objects(expr: Any) -> Tuple[bytes, Dict[bytes, bytes]]:
|
|
16
|
+
"""
|
|
17
|
+
Convert a Lispeum expression to a collection of objects in a Merkle tree structure.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
expr: The expression to convert
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
A tuple containing (root_hash, objects_dict) where:
|
|
24
|
+
- root_hash is the hash of the root object
|
|
25
|
+
- objects_dict is a dictionary mapping object hashes to their serialized representations
|
|
26
|
+
"""
|
|
27
|
+
objects = {}
|
|
28
|
+
root_hash = _serialize_expr(expr, objects)
|
|
29
|
+
return root_hash, objects
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def objects_to_expr(root_hash: bytes, objects: Dict[bytes, bytes]) -> Any:
|
|
33
|
+
"""
|
|
34
|
+
Convert a collection of objects back to a Lispeum expression.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
root_hash: The hash of the root object
|
|
38
|
+
objects: A dictionary mapping object hashes to their serialized representations
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The reconstructed Lispeum expression
|
|
42
|
+
"""
|
|
43
|
+
return _deserialize_expr(root_hash, objects)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _serialize_expr(expr: Any, objects: Dict[bytes, bytes]) -> bytes:
|
|
47
|
+
"""
|
|
48
|
+
Serialize a Lispeum expression to bytes and add it to the objects dictionary.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
expr: The expression to serialize
|
|
52
|
+
objects: Dictionary to store serialized objects
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The hash of the serialized expression
|
|
56
|
+
"""
|
|
57
|
+
if expr is None:
|
|
58
|
+
# None type
|
|
59
|
+
type_bytes = b'N' # N for None
|
|
60
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
61
|
+
objects[type_hash] = type_bytes
|
|
62
|
+
|
|
63
|
+
# None values don't need a value leaf, just return the type hash
|
|
64
|
+
return type_hash
|
|
65
|
+
|
|
66
|
+
elif isinstance(expr, Expr.ListExpr):
|
|
67
|
+
# Create type leaf
|
|
68
|
+
type_bytes = b'L' # L for List
|
|
69
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
70
|
+
objects[type_hash] = type_bytes
|
|
71
|
+
|
|
72
|
+
# Serialize each element and collect their hashes
|
|
73
|
+
element_hashes = []
|
|
74
|
+
for elem in expr.elements:
|
|
75
|
+
elem_hash = _serialize_expr(elem, objects)
|
|
76
|
+
element_hashes.append(elem_hash)
|
|
77
|
+
|
|
78
|
+
# Create value leaf with all element hashes
|
|
79
|
+
value_bytes = b''.join(element_hashes)
|
|
80
|
+
value_hash = hashlib.sha256(value_bytes).digest()
|
|
81
|
+
objects[value_hash] = value_bytes
|
|
82
|
+
|
|
83
|
+
# Create the tree node with type and value
|
|
84
|
+
tree_bytes = type_hash + value_hash
|
|
85
|
+
tree_hash = hashlib.sha256(tree_bytes).digest()
|
|
86
|
+
objects[tree_hash] = tree_bytes
|
|
87
|
+
|
|
88
|
+
return tree_hash
|
|
89
|
+
|
|
90
|
+
elif isinstance(expr, Expr.Symbol):
|
|
91
|
+
# Create type leaf
|
|
92
|
+
type_bytes = b'S' # S for Symbol
|
|
93
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
94
|
+
objects[type_hash] = type_bytes
|
|
95
|
+
|
|
96
|
+
# Create value leaf
|
|
97
|
+
value_bytes = expr.value.encode('utf-8')
|
|
98
|
+
value_hash = hashlib.sha256(value_bytes).digest()
|
|
99
|
+
objects[value_hash] = value_bytes
|
|
100
|
+
|
|
101
|
+
# Create the tree node with type and value
|
|
102
|
+
tree_bytes = type_hash + value_hash
|
|
103
|
+
tree_hash = hashlib.sha256(tree_bytes).digest()
|
|
104
|
+
objects[tree_hash] = tree_bytes
|
|
105
|
+
|
|
106
|
+
return tree_hash
|
|
107
|
+
|
|
108
|
+
elif isinstance(expr, Expr.Integer):
|
|
109
|
+
# Create type leaf
|
|
110
|
+
type_bytes = b'I' # I for Integer
|
|
111
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
112
|
+
objects[type_hash] = type_bytes
|
|
113
|
+
|
|
114
|
+
# Create value leaf - use 2's complement little endian for integers
|
|
115
|
+
value_bytes = struct.pack("<q", expr.value) # 8-byte little endian
|
|
116
|
+
value_hash = hashlib.sha256(value_bytes).digest()
|
|
117
|
+
objects[value_hash] = value_bytes
|
|
118
|
+
|
|
119
|
+
# Create the tree node with type and value
|
|
120
|
+
tree_bytes = type_hash + value_hash
|
|
121
|
+
tree_hash = hashlib.sha256(tree_bytes).digest()
|
|
122
|
+
objects[tree_hash] = tree_bytes
|
|
123
|
+
|
|
124
|
+
return tree_hash
|
|
125
|
+
|
|
126
|
+
elif isinstance(expr, Expr.String):
|
|
127
|
+
# Create type leaf
|
|
128
|
+
type_bytes = b'T' # T for Text/String
|
|
129
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
130
|
+
objects[type_hash] = type_bytes
|
|
131
|
+
|
|
132
|
+
# Create value leaf
|
|
133
|
+
value_bytes = expr.value.encode('utf-8')
|
|
134
|
+
value_hash = hashlib.sha256(value_bytes).digest()
|
|
135
|
+
objects[value_hash] = value_bytes
|
|
136
|
+
|
|
137
|
+
# Create the tree node with type and value
|
|
138
|
+
tree_bytes = type_hash + value_hash
|
|
139
|
+
tree_hash = hashlib.sha256(tree_bytes).digest()
|
|
140
|
+
objects[tree_hash] = tree_bytes
|
|
141
|
+
|
|
142
|
+
return tree_hash
|
|
143
|
+
|
|
144
|
+
elif isinstance(expr, Expr.Boolean):
|
|
145
|
+
# Create type leaf
|
|
146
|
+
type_bytes = b'B' # B for Boolean
|
|
147
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
148
|
+
objects[type_hash] = type_bytes
|
|
149
|
+
|
|
150
|
+
# Create value leaf
|
|
151
|
+
value_bytes = b'1' if expr.value else b'0'
|
|
152
|
+
value_hash = hashlib.sha256(value_bytes).digest()
|
|
153
|
+
objects[value_hash] = value_bytes
|
|
154
|
+
|
|
155
|
+
# Create the tree node with type and value
|
|
156
|
+
tree_bytes = type_hash + value_hash
|
|
157
|
+
tree_hash = hashlib.sha256(tree_bytes).digest()
|
|
158
|
+
objects[tree_hash] = tree_bytes
|
|
159
|
+
|
|
160
|
+
return tree_hash
|
|
161
|
+
|
|
162
|
+
elif isinstance(expr, Expr.Function):
|
|
163
|
+
# Create type leaf
|
|
164
|
+
type_bytes = b'F' # F for Function
|
|
165
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
166
|
+
objects[type_hash] = type_bytes
|
|
167
|
+
|
|
168
|
+
# Serialize params
|
|
169
|
+
params_list = []
|
|
170
|
+
for param in expr.params:
|
|
171
|
+
params_list.append(param.encode('utf-8'))
|
|
172
|
+
params_bytes = b','.join(params_list)
|
|
173
|
+
params_hash = hashlib.sha256(params_bytes).digest()
|
|
174
|
+
objects[params_hash] = params_bytes
|
|
175
|
+
|
|
176
|
+
# Serialize body recursively
|
|
177
|
+
body_hash = _serialize_expr(expr.body, objects)
|
|
178
|
+
|
|
179
|
+
# Combine params and body hashes for the value
|
|
180
|
+
value_bytes = params_hash + body_hash
|
|
181
|
+
value_hash = hashlib.sha256(value_bytes).digest()
|
|
182
|
+
objects[value_hash] = value_bytes
|
|
183
|
+
|
|
184
|
+
# Create the tree node with type and value
|
|
185
|
+
tree_bytes = type_hash + value_hash
|
|
186
|
+
tree_hash = hashlib.sha256(tree_bytes).digest()
|
|
187
|
+
objects[tree_hash] = tree_bytes
|
|
188
|
+
|
|
189
|
+
return tree_hash
|
|
190
|
+
|
|
191
|
+
elif isinstance(expr, Expr.Error):
|
|
192
|
+
# Create type leaf
|
|
193
|
+
type_bytes = b'E' # E for Error
|
|
194
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
195
|
+
objects[type_hash] = type_bytes
|
|
196
|
+
|
|
197
|
+
# Serialize error components
|
|
198
|
+
category_bytes = expr.category.encode('utf-8')
|
|
199
|
+
category_hash = hashlib.sha256(category_bytes).digest()
|
|
200
|
+
objects[category_hash] = category_bytes
|
|
201
|
+
|
|
202
|
+
message_bytes = expr.message.encode('utf-8')
|
|
203
|
+
message_hash = hashlib.sha256(message_bytes).digest()
|
|
204
|
+
objects[message_hash] = message_bytes
|
|
205
|
+
|
|
206
|
+
if expr.details:
|
|
207
|
+
details_bytes = expr.details.encode('utf-8')
|
|
208
|
+
details_hash = hashlib.sha256(details_bytes).digest()
|
|
209
|
+
objects[details_hash] = details_bytes
|
|
210
|
+
|
|
211
|
+
# Combine all three components
|
|
212
|
+
value_bytes = category_hash + message_hash + details_hash
|
|
213
|
+
else:
|
|
214
|
+
# Just combine category and message
|
|
215
|
+
value_bytes = category_hash + message_hash
|
|
216
|
+
|
|
217
|
+
value_hash = hashlib.sha256(value_bytes).digest()
|
|
218
|
+
objects[value_hash] = value_bytes
|
|
219
|
+
|
|
220
|
+
# Create the tree node with type and value
|
|
221
|
+
tree_bytes = type_hash + value_hash
|
|
222
|
+
tree_hash = hashlib.sha256(tree_bytes).digest()
|
|
223
|
+
objects[tree_hash] = tree_bytes
|
|
224
|
+
|
|
225
|
+
return tree_hash
|
|
226
|
+
|
|
227
|
+
else:
|
|
228
|
+
# Unknown type - serialize as string
|
|
229
|
+
type_bytes = b'U' # U for Unknown
|
|
230
|
+
type_hash = hashlib.sha256(type_bytes).digest()
|
|
231
|
+
objects[type_hash] = type_bytes
|
|
232
|
+
|
|
233
|
+
# Create value leaf with string representation
|
|
234
|
+
value_bytes = str(expr).encode('utf-8')
|
|
235
|
+
value_hash = hashlib.sha256(value_bytes).digest()
|
|
236
|
+
objects[value_hash] = value_bytes
|
|
237
|
+
|
|
238
|
+
# Create the tree node with type and value
|
|
239
|
+
tree_bytes = type_hash + value_hash
|
|
240
|
+
tree_hash = hashlib.sha256(tree_bytes).digest()
|
|
241
|
+
objects[tree_hash] = tree_bytes
|
|
242
|
+
|
|
243
|
+
return tree_hash
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _deserialize_expr(obj_hash: bytes, objects: Dict[bytes, bytes]) -> Any:
|
|
247
|
+
"""
|
|
248
|
+
Deserialize a Lispeum expression from its hash.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
obj_hash: The hash of the object to deserialize
|
|
252
|
+
objects: Dictionary containing serialized objects
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
The deserialized Lispeum expression
|
|
256
|
+
"""
|
|
257
|
+
if obj_hash not in objects:
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
obj_data = objects[obj_hash]
|
|
261
|
+
|
|
262
|
+
# Check if this is a type-only node (for None)
|
|
263
|
+
if len(obj_data) == 1:
|
|
264
|
+
if obj_data == b'N':
|
|
265
|
+
return None
|
|
266
|
+
return None # Unrecognized single-byte type
|
|
267
|
+
|
|
268
|
+
# For regular nodes, expect 64 bytes (two 32-byte hashes)
|
|
269
|
+
if len(obj_data) == 64:
|
|
270
|
+
type_hash = obj_data[:32]
|
|
271
|
+
value_hash = obj_data[32:]
|
|
272
|
+
|
|
273
|
+
if type_hash not in objects or value_hash not in objects:
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
type_data = objects[type_hash]
|
|
277
|
+
value_data = objects[value_hash]
|
|
278
|
+
|
|
279
|
+
# Switch based on the type marker
|
|
280
|
+
if type_data == b'L': # List
|
|
281
|
+
elements = []
|
|
282
|
+
# Each hash is 32 bytes
|
|
283
|
+
hash_size = 32
|
|
284
|
+
for i in range(0, len(value_data), hash_size):
|
|
285
|
+
elem_hash = value_data[i:i+hash_size]
|
|
286
|
+
if elem_hash:
|
|
287
|
+
elem = _deserialize_expr(elem_hash, objects)
|
|
288
|
+
elements.append(elem)
|
|
289
|
+
return Expr.ListExpr(elements)
|
|
290
|
+
|
|
291
|
+
elif type_data == b'S': # Symbol
|
|
292
|
+
return Expr.Symbol(value_data.decode('utf-8'))
|
|
293
|
+
|
|
294
|
+
elif type_data == b'I': # Integer
|
|
295
|
+
int_value = struct.unpack("<q", value_data)[0]
|
|
296
|
+
return Expr.Integer(int_value)
|
|
297
|
+
|
|
298
|
+
elif type_data == b'T': # String (Text)
|
|
299
|
+
return Expr.String(value_data.decode('utf-8'))
|
|
300
|
+
|
|
301
|
+
elif type_data == b'B': # Boolean
|
|
302
|
+
return Expr.Boolean(value_data == b'1')
|
|
303
|
+
|
|
304
|
+
elif type_data == b'F': # Function
|
|
305
|
+
# Value contains params_hash and body_hash
|
|
306
|
+
params_hash = value_data[:32]
|
|
307
|
+
body_hash = value_data[32:]
|
|
308
|
+
|
|
309
|
+
if params_hash not in objects:
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
params_data = objects[params_hash]
|
|
313
|
+
params = [p.decode('utf-8') for p in params_data.split(b',') if p]
|
|
314
|
+
|
|
315
|
+
body = _deserialize_expr(body_hash, objects)
|
|
316
|
+
return Expr.Function(params, body)
|
|
317
|
+
|
|
318
|
+
elif type_data == b'E': # Error
|
|
319
|
+
# Check if we have details or just category and message
|
|
320
|
+
if len(value_data) == 64: # No details
|
|
321
|
+
category_hash = value_data[:32]
|
|
322
|
+
message_hash = value_data[32:]
|
|
323
|
+
details_hash = None
|
|
324
|
+
elif len(value_data) == 96: # With details
|
|
325
|
+
category_hash = value_data[:32]
|
|
326
|
+
message_hash = value_data[32:64]
|
|
327
|
+
details_hash = value_data[64:]
|
|
328
|
+
else:
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
if category_hash not in objects or message_hash not in objects:
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
category = objects[category_hash].decode('utf-8')
|
|
335
|
+
message = objects[message_hash].decode('utf-8')
|
|
336
|
+
|
|
337
|
+
details = None
|
|
338
|
+
if details_hash and details_hash in objects:
|
|
339
|
+
details = objects[details_hash].decode('utf-8')
|
|
340
|
+
|
|
341
|
+
return Expr.Error(category, message, details)
|
|
342
|
+
|
|
343
|
+
elif type_data == b'U': # Unknown
|
|
344
|
+
return Expr.String(value_data.decode('utf-8'))
|
|
345
|
+
|
|
346
|
+
return None # Unrecognized format
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def store_expr(expr: Any, storage) -> bytes:
|
|
350
|
+
"""
|
|
351
|
+
Store a Lispeum expression in the provided storage.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
expr: The expression to store
|
|
355
|
+
storage: Storage interface with put(key, value) method
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
The hash of the root object
|
|
359
|
+
"""
|
|
360
|
+
root_hash, objects = expr_to_objects(expr)
|
|
361
|
+
|
|
362
|
+
# Store all objects in the storage
|
|
363
|
+
for obj_hash, obj_data in objects.items():
|
|
364
|
+
storage.put(obj_hash, obj_data)
|
|
365
|
+
|
|
366
|
+
return root_hash
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def get_expr_from_storage(root_hash: bytes, storage) -> Any:
|
|
370
|
+
"""
|
|
371
|
+
Load a Lispeum expression from storage.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
root_hash: The hash of the root object
|
|
375
|
+
storage: Storage interface with get(key) method
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
The loaded Lispeum expression, or None if not found
|
|
379
|
+
"""
|
|
380
|
+
if not root_hash:
|
|
381
|
+
return None
|
|
382
|
+
|
|
383
|
+
# Build the objects dictionary from storage
|
|
384
|
+
objects = {}
|
|
385
|
+
queue = [root_hash]
|
|
386
|
+
visited = set()
|
|
387
|
+
|
|
388
|
+
while queue:
|
|
389
|
+
current_hash = queue.pop(0)
|
|
390
|
+
if current_hash in visited:
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
visited.add(current_hash)
|
|
394
|
+
obj_data = storage.get(current_hash)
|
|
395
|
+
|
|
396
|
+
if not obj_data:
|
|
397
|
+
# Can't find an object, return None
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
objects[current_hash] = obj_data
|
|
401
|
+
|
|
402
|
+
# For single-byte nodes (e.g., None), no further processing needed
|
|
403
|
+
if len(obj_data) == 1:
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
# For regular tree nodes (type + value)
|
|
407
|
+
if len(obj_data) == 64:
|
|
408
|
+
# Add both hashes to the queue
|
|
409
|
+
type_hash = obj_data[:32]
|
|
410
|
+
value_hash = obj_data[32:]
|
|
411
|
+
|
|
412
|
+
if type_hash not in visited:
|
|
413
|
+
queue.append(type_hash)
|
|
414
|
+
|
|
415
|
+
if value_hash not in visited:
|
|
416
|
+
queue.append(value_hash)
|
|
417
|
+
|
|
418
|
+
# For function and error types, we need to check the value fields
|
|
419
|
+
# which might contain additional hashes
|
|
420
|
+
if type_hash in objects:
|
|
421
|
+
type_data = objects[type_hash]
|
|
422
|
+
|
|
423
|
+
# For Function type, the value contains params_hash + body_hash
|
|
424
|
+
if type_data == b'F' and value_hash in objects:
|
|
425
|
+
value_data = objects[value_hash]
|
|
426
|
+
if len(value_data) == 64:
|
|
427
|
+
params_hash = value_data[:32]
|
|
428
|
+
body_hash = value_data[32:]
|
|
429
|
+
|
|
430
|
+
if params_hash not in visited:
|
|
431
|
+
queue.append(params_hash)
|
|
432
|
+
|
|
433
|
+
if body_hash not in visited:
|
|
434
|
+
queue.append(body_hash)
|
|
435
|
+
|
|
436
|
+
# For Error type, the value contains category_hash + message_hash + [details_hash]
|
|
437
|
+
elif type_data == b'E' and value_hash in objects:
|
|
438
|
+
value_data = objects[value_hash]
|
|
439
|
+
hash_size = 32
|
|
440
|
+
|
|
441
|
+
for i in range(0, len(value_data), hash_size):
|
|
442
|
+
component_hash = value_data[i:i+hash_size]
|
|
443
|
+
if component_hash and component_hash not in visited:
|
|
444
|
+
queue.append(component_hash)
|
|
445
|
+
|
|
446
|
+
# For List type, the value contains all element hashes
|
|
447
|
+
elif type_data == b'L' and value_hash in objects:
|
|
448
|
+
value_data = objects[value_hash]
|
|
449
|
+
hash_size = 32
|
|
450
|
+
|
|
451
|
+
for i in range(0, len(value_data), hash_size):
|
|
452
|
+
elem_hash = value_data[i:i+hash_size]
|
|
453
|
+
if elem_hash and elem_hash not in visited:
|
|
454
|
+
queue.append(elem_hash)
|
|
455
|
+
|
|
456
|
+
# Reconstruct the expression from objects
|
|
457
|
+
return objects_to_expr(root_hash, objects)
|
astreum/machine/__init__.py
CHANGED
|
@@ -17,12 +17,11 @@ from astreum.lispeum.tokenizer import tokenize
|
|
|
17
17
|
from astreum.lispeum.parser import parse
|
|
18
18
|
|
|
19
19
|
class AstreumMachine:
|
|
20
|
-
def __init__(self):
|
|
21
|
-
self.global_env = Environment()
|
|
22
|
-
|
|
20
|
+
def __init__(self, node: 'Node' = None):
|
|
21
|
+
self.global_env = Environment(node=node)
|
|
23
22
|
self.sessions: Dict[str, Environment] = {}
|
|
24
|
-
|
|
25
23
|
self.lock = threading.Lock()
|
|
24
|
+
|
|
26
25
|
|
|
27
26
|
def create_session(self) -> str:
|
|
28
27
|
session_id = str(uuid.uuid4())
|
astreum/machine/environment.py
CHANGED
|
@@ -3,14 +3,21 @@ from astreum.lispeum.expression import Expr
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Environment:
|
|
6
|
-
def __init__(self, parent: 'Environment' = None):
|
|
6
|
+
def __init__(self, parent: 'Environment' = None, node: 'Node' = None):
|
|
7
7
|
self.data: Dict[str, Expr] = {}
|
|
8
8
|
self.parent = parent
|
|
9
|
+
self.node = node
|
|
9
10
|
|
|
10
11
|
def set(self, name: str, value: Expr):
|
|
11
|
-
self.
|
|
12
|
+
if self.node:
|
|
13
|
+
self.node.post_global_storage(name, value)
|
|
14
|
+
else:
|
|
15
|
+
self.data[name] = value
|
|
12
16
|
|
|
13
17
|
def get(self, name: str) -> Optional[Expr]:
|
|
18
|
+
if self.node:
|
|
19
|
+
return self.node.query_global_storage(name)
|
|
20
|
+
|
|
14
21
|
if name in self.data:
|
|
15
22
|
return self.data[name]
|
|
16
23
|
elif self.parent:
|