astreum 0.1.8__tar.gz → 0.1.10__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.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- {astreum-0.1.8/src/astreum.egg-info → astreum-0.1.10}/PKG-INFO +11 -7
- {astreum-0.1.8 → astreum-0.1.10}/README.md +8 -6
- {astreum-0.1.8 → astreum-0.1.10}/pyproject.toml +21 -17
- astreum-0.1.10/src/astreum/lispeum/storage.py +410 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/machine/__init__.py +352 -254
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/node/__init__.py +143 -54
- astreum-0.1.10/src/astreum/node/models.py +284 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/node/relay/__init__.py +100 -4
- astreum-0.1.10/src/astreum/node/relay/bucket.py +90 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/node/relay/message.py +17 -12
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/node/relay/peer.py +3 -0
- astreum-0.1.10/src/astreum/node/relay/route.py +161 -0
- {astreum-0.1.8 → astreum-0.1.10/src/astreum.egg-info}/PKG-INFO +11 -7
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum.egg-info/SOURCES.txt +1 -0
- astreum-0.1.10/src/astreum.egg-info/requires.txt +2 -0
- astreum-0.1.8/src/astreum/lispeum/storage.py +0 -457
- astreum-0.1.8/src/astreum/node/models.py +0 -96
- astreum-0.1.8/src/astreum/node/relay/bucket.py +0 -80
- astreum-0.1.8/src/astreum/node/relay/route.py +0 -125
- {astreum-0.1.8 → astreum-0.1.10}/LICENSE +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/setup.cfg +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/__init__.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/__init__.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/expression.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/parser.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/__init__.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/definition.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/__init__.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/all.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/any.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/fold.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/get.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/insert.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/map.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/position.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/list/remove.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/number/__init__.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/special/number/addition.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/lispeum/tokenizer.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/machine/environment.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/machine/error.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/node/relay/envelope.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/utils/__init__.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum/utils/bytes_format.py +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.1.8 → astreum-0.1.10}/src/astreum.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -11,6 +11,8 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.8
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pycryptodomex==3.21.0
|
|
15
|
+
Requires-Dist: cryptography==44.0.2
|
|
14
16
|
|
|
15
17
|
# lib
|
|
16
18
|
|
|
@@ -26,9 +28,10 @@ When initializing an Astreum Node, you need to provide a configuration dictionar
|
|
|
26
28
|
|
|
27
29
|
| Parameter | Type | Default | Description |
|
|
28
30
|
|-----------|------|---------|-------------|
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
31
|
+
| `private_key` | string | Auto-generated | Hex string of Ed25519 private key. If not provided, a new keypair will be generated automatically |
|
|
32
|
+
| `storage_path` | string | "storage" | Path to store data |
|
|
33
|
+
| `max_storage_space` | int | 1073741824 (1GB) | Maximum storage space in bytes |
|
|
34
|
+
| `max_object_recursion` | int | 50 | Maximum recursion depth for resolving nested objects |
|
|
32
35
|
|
|
33
36
|
### Network Configuration
|
|
34
37
|
|
|
@@ -38,15 +41,17 @@ When initializing an Astreum Node, you need to provide a configuration dictionar
|
|
|
38
41
|
| `incoming_port` | int | 7373 | Port to listen for incoming messages |
|
|
39
42
|
| `max_message_size` | int | 65536 | Maximum size of UDP datagrams in bytes |
|
|
40
43
|
| `num_workers` | int | 4 | Number of worker threads for message processing |
|
|
44
|
+
| `network_request_timeout` | float | 5.0 | Maximum time (in seconds) to wait for network object requests |
|
|
41
45
|
|
|
42
46
|
### Route Configuration
|
|
43
47
|
|
|
44
48
|
| Parameter | Type | Default | Description |
|
|
45
49
|
|-----------|------|---------|-------------|
|
|
46
|
-
| `peer_route` | bool | False | Whether to participate in the peer discovery route |
|
|
47
50
|
| `validation_route` | bool | False | Whether to participate in the block validation route |
|
|
48
51
|
| `bootstrap_peers` | list | [] | List of bootstrap peers in the format `[("hostname", port), ...]` |
|
|
49
52
|
|
|
53
|
+
> **Note:** The peer route is always enabled as it's necessary for object discovery and retrieval.
|
|
54
|
+
|
|
50
55
|
### Example Usage
|
|
51
56
|
|
|
52
57
|
```python
|
|
@@ -54,8 +59,7 @@ from astreum.node import Node
|
|
|
54
59
|
|
|
55
60
|
# Configuration dictionary
|
|
56
61
|
config = {
|
|
57
|
-
"
|
|
58
|
-
"followed_chain_id": b"main-chain-id-goes-here",
|
|
62
|
+
"private_key": "my-private-key-goes-here",
|
|
59
63
|
"storage_path": "./data/node1",
|
|
60
64
|
"incoming_port": 7373,
|
|
61
65
|
"use_ipv6": False,
|
|
@@ -12,9 +12,10 @@ When initializing an Astreum Node, you need to provide a configuration dictionar
|
|
|
12
12
|
|
|
13
13
|
| Parameter | Type | Default | Description |
|
|
14
14
|
|-----------|------|---------|-------------|
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
15
|
+
| `private_key` | string | Auto-generated | Hex string of Ed25519 private key. If not provided, a new keypair will be generated automatically |
|
|
16
|
+
| `storage_path` | string | "storage" | Path to store data |
|
|
17
|
+
| `max_storage_space` | int | 1073741824 (1GB) | Maximum storage space in bytes |
|
|
18
|
+
| `max_object_recursion` | int | 50 | Maximum recursion depth for resolving nested objects |
|
|
18
19
|
|
|
19
20
|
### Network Configuration
|
|
20
21
|
|
|
@@ -24,15 +25,17 @@ When initializing an Astreum Node, you need to provide a configuration dictionar
|
|
|
24
25
|
| `incoming_port` | int | 7373 | Port to listen for incoming messages |
|
|
25
26
|
| `max_message_size` | int | 65536 | Maximum size of UDP datagrams in bytes |
|
|
26
27
|
| `num_workers` | int | 4 | Number of worker threads for message processing |
|
|
28
|
+
| `network_request_timeout` | float | 5.0 | Maximum time (in seconds) to wait for network object requests |
|
|
27
29
|
|
|
28
30
|
### Route Configuration
|
|
29
31
|
|
|
30
32
|
| Parameter | Type | Default | Description |
|
|
31
33
|
|-----------|------|---------|-------------|
|
|
32
|
-
| `peer_route` | bool | False | Whether to participate in the peer discovery route |
|
|
33
34
|
| `validation_route` | bool | False | Whether to participate in the block validation route |
|
|
34
35
|
| `bootstrap_peers` | list | [] | List of bootstrap peers in the format `[("hostname", port), ...]` |
|
|
35
36
|
|
|
37
|
+
> **Note:** The peer route is always enabled as it's necessary for object discovery and retrieval.
|
|
38
|
+
|
|
36
39
|
### Example Usage
|
|
37
40
|
|
|
38
41
|
```python
|
|
@@ -40,8 +43,7 @@ from astreum.node import Node
|
|
|
40
43
|
|
|
41
44
|
# Configuration dictionary
|
|
42
45
|
config = {
|
|
43
|
-
"
|
|
44
|
-
"followed_chain_id": b"main-chain-id-goes-here",
|
|
46
|
+
"private_key": "my-private-key-goes-here",
|
|
45
47
|
"storage_path": "./data/node1",
|
|
46
48
|
"incoming_port": 7373,
|
|
47
49
|
"use_ipv6": False,
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "astreum"
|
|
3
|
-
version = "0.1.
|
|
4
|
-
authors = [
|
|
5
|
-
{ name="Roy R. O. Okello", email="roy@stelar.xyz" },
|
|
6
|
-
]
|
|
7
|
-
description = "Python library to interact with the Astreum blockchain and its Lispeum virtual machine."
|
|
8
|
-
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.8"
|
|
10
|
-
classifiers = [
|
|
11
|
-
"Programming Language :: Python :: 3",
|
|
12
|
-
"License :: OSI Approved :: MIT License",
|
|
13
|
-
"Operating System :: OS Independent",
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
[project]
|
|
2
|
+
name = "astreum"
|
|
3
|
+
version = "0.1.10"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Roy R. O. Okello", email="roy@stelar.xyz" },
|
|
6
|
+
]
|
|
7
|
+
description = "Python library to interact with the Astreum blockchain and its Lispeum virtual machine."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pycryptodomex==3.21.0",
|
|
17
|
+
"cryptography==44.0.2",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/astreum/lib"
|
|
18
22
|
Issues = "https://github.com/astreum/lib/issues"
|
|
@@ -0,0 +1,410 @@
|
|
|
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
|
+
is_leaf = True
|
|
60
|
+
type_bytes = b'N' # N for None
|
|
61
|
+
|
|
62
|
+
# Create the object with leaf flag and body
|
|
63
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes
|
|
64
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
65
|
+
objects[object_hash] = object_bytes
|
|
66
|
+
|
|
67
|
+
return object_hash
|
|
68
|
+
|
|
69
|
+
elif isinstance(expr, Expr.ListExpr):
|
|
70
|
+
# Create type object
|
|
71
|
+
is_leaf = False
|
|
72
|
+
type_bytes = b'L' # L for List
|
|
73
|
+
|
|
74
|
+
# Serialize each element and collect their hashes
|
|
75
|
+
element_hashes = []
|
|
76
|
+
for elem in expr.elements:
|
|
77
|
+
elem_hash = _serialize_expr(elem, objects)
|
|
78
|
+
element_hashes.append(elem_hash)
|
|
79
|
+
|
|
80
|
+
# Create value leaf with all element hashes
|
|
81
|
+
value_bytes = b''.join(element_hashes)
|
|
82
|
+
|
|
83
|
+
# Create the object with leaf flag and body
|
|
84
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes + value_bytes
|
|
85
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
86
|
+
objects[object_hash] = object_bytes
|
|
87
|
+
|
|
88
|
+
return object_hash
|
|
89
|
+
|
|
90
|
+
elif isinstance(expr, Expr.Symbol):
|
|
91
|
+
# Create the object - symbols are leaf nodes
|
|
92
|
+
is_leaf = True
|
|
93
|
+
type_bytes = b'S' # S for Symbol
|
|
94
|
+
value_bytes = expr.value.encode('utf-8')
|
|
95
|
+
|
|
96
|
+
# Create the object with leaf flag and body
|
|
97
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes + value_bytes
|
|
98
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
99
|
+
objects[object_hash] = object_bytes
|
|
100
|
+
|
|
101
|
+
return object_hash
|
|
102
|
+
|
|
103
|
+
elif isinstance(expr, Expr.Integer):
|
|
104
|
+
# Create the object - integers are leaf nodes
|
|
105
|
+
is_leaf = True
|
|
106
|
+
type_bytes = b'I' # I for Integer
|
|
107
|
+
value_bytes = struct.pack("<q", expr.value) # 8-byte little endian
|
|
108
|
+
|
|
109
|
+
# Create the object with leaf flag and body
|
|
110
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes + value_bytes
|
|
111
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
112
|
+
objects[object_hash] = object_bytes
|
|
113
|
+
|
|
114
|
+
return object_hash
|
|
115
|
+
|
|
116
|
+
elif isinstance(expr, Expr.String):
|
|
117
|
+
# Create the object - strings are leaf nodes
|
|
118
|
+
is_leaf = True
|
|
119
|
+
type_bytes = b'T' # T for Text/String
|
|
120
|
+
value_bytes = expr.value.encode('utf-8')
|
|
121
|
+
|
|
122
|
+
# Create the object with leaf flag and body
|
|
123
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes + value_bytes
|
|
124
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
125
|
+
objects[object_hash] = object_bytes
|
|
126
|
+
|
|
127
|
+
return object_hash
|
|
128
|
+
|
|
129
|
+
elif isinstance(expr, Expr.Boolean):
|
|
130
|
+
# Create the object - booleans are leaf nodes
|
|
131
|
+
is_leaf = True
|
|
132
|
+
type_bytes = b'B' # B for Boolean
|
|
133
|
+
value_bytes = b'1' if expr.value else b'0'
|
|
134
|
+
|
|
135
|
+
# Create the object with leaf flag and body
|
|
136
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes + value_bytes
|
|
137
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
138
|
+
objects[object_hash] = object_bytes
|
|
139
|
+
|
|
140
|
+
return object_hash
|
|
141
|
+
|
|
142
|
+
elif isinstance(expr, Expr.Function):
|
|
143
|
+
# Create the object - functions are not leaf nodes
|
|
144
|
+
is_leaf = False
|
|
145
|
+
type_bytes = b'F' # F for Function
|
|
146
|
+
|
|
147
|
+
# Serialize params
|
|
148
|
+
params_list = []
|
|
149
|
+
for param in expr.params:
|
|
150
|
+
params_list.append(param.encode('utf-8'))
|
|
151
|
+
params_bytes = b','.join(params_list)
|
|
152
|
+
|
|
153
|
+
# Serialize body recursively
|
|
154
|
+
body_hash = _serialize_expr(expr.body, objects)
|
|
155
|
+
|
|
156
|
+
# Create the object with leaf flag and body
|
|
157
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes + params_bytes + body_hash
|
|
158
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
159
|
+
objects[object_hash] = object_bytes
|
|
160
|
+
|
|
161
|
+
return object_hash
|
|
162
|
+
|
|
163
|
+
elif isinstance(expr, Expr.Error):
|
|
164
|
+
# Create the object - errors are not leaf nodes
|
|
165
|
+
is_leaf = False
|
|
166
|
+
type_bytes = b'E' # E for Error
|
|
167
|
+
|
|
168
|
+
# Serialize error components
|
|
169
|
+
category_bytes = expr.category.encode('utf-8')
|
|
170
|
+
message_bytes = expr.message.encode('utf-8')
|
|
171
|
+
details_bytes = b'' if expr.details is None else expr.details.encode('utf-8')
|
|
172
|
+
|
|
173
|
+
# Create the object with leaf flag and body
|
|
174
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes + category_bytes + b'\0' + message_bytes + b'\0' + details_bytes
|
|
175
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
176
|
+
objects[object_hash] = object_bytes
|
|
177
|
+
|
|
178
|
+
return object_hash
|
|
179
|
+
|
|
180
|
+
else:
|
|
181
|
+
# Default fallback for unknown types
|
|
182
|
+
is_leaf = True
|
|
183
|
+
type_bytes = b'U' # U for Unknown
|
|
184
|
+
value_bytes = str(expr).encode('utf-8')
|
|
185
|
+
|
|
186
|
+
# Create the object with leaf flag and body
|
|
187
|
+
object_bytes = struct.pack("?", is_leaf) + type_bytes + value_bytes
|
|
188
|
+
object_hash = hashlib.sha256(object_bytes).digest()
|
|
189
|
+
objects[object_hash] = object_bytes
|
|
190
|
+
|
|
191
|
+
return object_hash
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _deserialize_expr(obj_hash: bytes, objects: Dict[bytes, bytes]) -> Any:
|
|
195
|
+
"""
|
|
196
|
+
Deserialize a Lispeum expression from its hash.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
obj_hash: The hash of the object to deserialize
|
|
200
|
+
objects: Dictionary containing serialized objects
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The deserialized Lispeum expression
|
|
204
|
+
"""
|
|
205
|
+
if obj_hash not in objects:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
obj_bytes = objects[obj_hash]
|
|
209
|
+
|
|
210
|
+
# Extract leaf flag
|
|
211
|
+
is_leaf = struct.unpack("?", obj_bytes[0:1])[0]
|
|
212
|
+
|
|
213
|
+
# Get type indicator
|
|
214
|
+
type_indicator = obj_bytes[1:2]
|
|
215
|
+
|
|
216
|
+
# Deserialize based on type
|
|
217
|
+
if type_indicator == b'N': # None
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
elif type_indicator == b'L': # List
|
|
221
|
+
if is_leaf:
|
|
222
|
+
# Empty list
|
|
223
|
+
return Expr.ListExpr([])
|
|
224
|
+
|
|
225
|
+
# Non-leaf list has child element hashes
|
|
226
|
+
elements_bytes = obj_bytes[2:]
|
|
227
|
+
element_hashes = [elements_bytes[i:i+32] for i in range(0, len(elements_bytes), 32)]
|
|
228
|
+
|
|
229
|
+
# Deserialize each element
|
|
230
|
+
elements = []
|
|
231
|
+
for elem_hash in element_hashes:
|
|
232
|
+
elem = _deserialize_expr(elem_hash, objects)
|
|
233
|
+
elements.append(elem)
|
|
234
|
+
|
|
235
|
+
return Expr.ListExpr(elements)
|
|
236
|
+
|
|
237
|
+
elif type_indicator == b'S': # Symbol
|
|
238
|
+
value_bytes = obj_bytes[2:]
|
|
239
|
+
return Expr.Symbol(value_bytes.decode('utf-8'))
|
|
240
|
+
|
|
241
|
+
elif type_indicator == b'I': # Integer
|
|
242
|
+
value_bytes = obj_bytes[2:10] # 8 bytes for int64
|
|
243
|
+
value = struct.unpack("<q", value_bytes)[0]
|
|
244
|
+
return Expr.Integer(value)
|
|
245
|
+
|
|
246
|
+
elif type_indicator == b'T': # Text/String
|
|
247
|
+
value_bytes = obj_bytes[2:]
|
|
248
|
+
return Expr.String(value_bytes.decode('utf-8'))
|
|
249
|
+
|
|
250
|
+
elif type_indicator == b'B': # Boolean
|
|
251
|
+
value_bytes = obj_bytes[2:3]
|
|
252
|
+
return Expr.Boolean(value_bytes == b'1')
|
|
253
|
+
|
|
254
|
+
elif type_indicator == b'F': # Function
|
|
255
|
+
# Non-leaf function
|
|
256
|
+
remaining_bytes = obj_bytes[2:]
|
|
257
|
+
|
|
258
|
+
# Find the separator between params and body hash
|
|
259
|
+
params_end = remaining_bytes.find(b',', remaining_bytes.rfind(b','))
|
|
260
|
+
if params_end == -1:
|
|
261
|
+
params_end = 0 # No params
|
|
262
|
+
|
|
263
|
+
params_bytes = remaining_bytes[:params_end+1]
|
|
264
|
+
body_hash = remaining_bytes[params_end+1:]
|
|
265
|
+
|
|
266
|
+
# Parse params
|
|
267
|
+
params = []
|
|
268
|
+
if params_bytes:
|
|
269
|
+
for param_bytes in params_bytes.split(b','):
|
|
270
|
+
if param_bytes: # Skip empty strings
|
|
271
|
+
params.append(param_bytes.decode('utf-8'))
|
|
272
|
+
|
|
273
|
+
# Deserialize body
|
|
274
|
+
body = _deserialize_expr(body_hash, objects)
|
|
275
|
+
|
|
276
|
+
return Expr.Function(params, body)
|
|
277
|
+
|
|
278
|
+
elif type_indicator == b'E': # Error
|
|
279
|
+
remaining_bytes = obj_bytes[2:]
|
|
280
|
+
|
|
281
|
+
# Split by null bytes to get category, message, and details
|
|
282
|
+
parts = remaining_bytes.split(b'\0', 2)
|
|
283
|
+
|
|
284
|
+
category = parts[0].decode('utf-8')
|
|
285
|
+
message = parts[1].decode('utf-8') if len(parts) > 1 else ""
|
|
286
|
+
details = parts[2].decode('utf-8') if len(parts) > 2 else None
|
|
287
|
+
|
|
288
|
+
return Expr.Error(category, message, details)
|
|
289
|
+
|
|
290
|
+
else: # Unknown
|
|
291
|
+
value_bytes = obj_bytes[2:]
|
|
292
|
+
# Return as a string
|
|
293
|
+
return value_bytes.decode('utf-8')
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def store_expr(expr: Any, storage) -> bytes:
|
|
297
|
+
"""
|
|
298
|
+
Store a Lispeum expression in the provided storage.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
expr: The expression to store
|
|
302
|
+
storage: Storage interface with put(key, value) method
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
The hash of the root object
|
|
306
|
+
"""
|
|
307
|
+
# Convert expression to objects
|
|
308
|
+
root_hash, objects = expr_to_objects(expr)
|
|
309
|
+
|
|
310
|
+
# Store each object in the storage
|
|
311
|
+
for obj_hash, obj_data in objects.items():
|
|
312
|
+
storage.put(obj_hash, obj_data)
|
|
313
|
+
|
|
314
|
+
return root_hash
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def get_expr_from_storage(root_hash: bytes, storage, max_depth: int = 50) -> Any:
|
|
318
|
+
"""
|
|
319
|
+
Load a Lispeum expression from storage. Will recursively resolve
|
|
320
|
+
objects from the storage until a leaf node is reached.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
root_hash: The hash of the root object
|
|
324
|
+
storage: Storage interface with get method and get_recursive method
|
|
325
|
+
max_depth: Maximum recursion depth for resolution
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
The loaded Lispeum expression, or None if not found
|
|
329
|
+
"""
|
|
330
|
+
# Check if storage has the get_recursive method (newer storage interface)
|
|
331
|
+
if hasattr(storage, 'get_recursive'):
|
|
332
|
+
# Use the storage's built-in recursive retrieval
|
|
333
|
+
objects = storage.get_recursive(root_hash, max_depth)
|
|
334
|
+
else:
|
|
335
|
+
# Fallback to manual recursive retrieval for older storage interfaces
|
|
336
|
+
objects = {}
|
|
337
|
+
_load_objects_recursive(root_hash, storage, objects, max_depth)
|
|
338
|
+
|
|
339
|
+
# If no objects were retrieved, return None
|
|
340
|
+
if not objects:
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
# Deserialize the expression
|
|
344
|
+
return objects_to_expr(root_hash, objects)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _load_objects_recursive(obj_hash: bytes, storage, objects: Dict[bytes, bytes], max_depth: int, current_depth: int = 0) -> bool:
|
|
348
|
+
"""
|
|
349
|
+
Recursively load objects from storage.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
obj_hash: The hash of the object to load
|
|
353
|
+
storage: Storage interface with get(key) method
|
|
354
|
+
objects: Dictionary to store loaded objects
|
|
355
|
+
max_depth: Maximum recursion depth
|
|
356
|
+
current_depth: Current recursion depth
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
True if object was loaded, False otherwise
|
|
360
|
+
"""
|
|
361
|
+
# Check if we've reached max recursion depth
|
|
362
|
+
if current_depth >= max_depth:
|
|
363
|
+
print(f"Warning: Max recursion depth {max_depth} reached while loading objects")
|
|
364
|
+
return False
|
|
365
|
+
|
|
366
|
+
# Check if we already have this object
|
|
367
|
+
if obj_hash in objects:
|
|
368
|
+
return True
|
|
369
|
+
|
|
370
|
+
# Load the object from storage
|
|
371
|
+
obj_data = storage.get(obj_hash)
|
|
372
|
+
if obj_data is None:
|
|
373
|
+
return False
|
|
374
|
+
|
|
375
|
+
# Store the object
|
|
376
|
+
objects[obj_hash] = obj_data
|
|
377
|
+
|
|
378
|
+
# Check if this is a leaf node
|
|
379
|
+
is_leaf = struct.unpack("?", obj_data[0:1])[0]
|
|
380
|
+
if is_leaf:
|
|
381
|
+
# Leaf node, no need to recurse
|
|
382
|
+
return True
|
|
383
|
+
|
|
384
|
+
# For non-leaf nodes, recursively load child objects
|
|
385
|
+
type_indicator = obj_data[1:2]
|
|
386
|
+
|
|
387
|
+
if type_indicator == b'L': # List
|
|
388
|
+
# Non-leaf list has child element hashes
|
|
389
|
+
elements_bytes = obj_data[2:]
|
|
390
|
+
element_hashes = [elements_bytes[i:i+32] for i in range(0, len(elements_bytes), 32)]
|
|
391
|
+
|
|
392
|
+
# Load each element
|
|
393
|
+
for elem_hash in element_hashes:
|
|
394
|
+
_load_objects_recursive(elem_hash, storage, objects, max_depth, current_depth + 1)
|
|
395
|
+
|
|
396
|
+
elif type_indicator == b'F': # Function
|
|
397
|
+
# Non-leaf function has body hash
|
|
398
|
+
remaining_bytes = obj_data[2:]
|
|
399
|
+
|
|
400
|
+
# Find the separator between params and body hash
|
|
401
|
+
params_end = remaining_bytes.find(b',', remaining_bytes.rfind(b','))
|
|
402
|
+
if params_end == -1:
|
|
403
|
+
params_end = 0 # No params
|
|
404
|
+
|
|
405
|
+
body_hash = remaining_bytes[params_end+1:]
|
|
406
|
+
|
|
407
|
+
# Load body
|
|
408
|
+
_load_objects_recursive(body_hash, storage, objects, max_depth, current_depth + 1)
|
|
409
|
+
|
|
410
|
+
return True
|