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 CHANGED
@@ -1 +1,2 @@
1
1
  from .machine import AstreumMachine
2
+ from .node import Node
@@ -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)
@@ -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())
@@ -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.data[name] = value
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: