astreum 0.2.29__py3-none-any.whl → 0.2.61__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.
Files changed (58) hide show
  1. astreum/__init__.py +9 -1
  2. astreum/_communication/__init__.py +11 -0
  3. astreum/{models → _communication}/message.py +101 -64
  4. astreum/_communication/peer.py +23 -0
  5. astreum/_communication/ping.py +33 -0
  6. astreum/_communication/route.py +95 -0
  7. astreum/_communication/setup.py +322 -0
  8. astreum/_communication/util.py +42 -0
  9. astreum/_consensus/__init__.py +20 -0
  10. astreum/_consensus/account.py +95 -0
  11. astreum/_consensus/accounts.py +38 -0
  12. astreum/_consensus/block.py +311 -0
  13. astreum/_consensus/chain.py +66 -0
  14. astreum/_consensus/fork.py +100 -0
  15. astreum/_consensus/genesis.py +72 -0
  16. astreum/_consensus/receipt.py +136 -0
  17. astreum/_consensus/setup.py +115 -0
  18. astreum/_consensus/transaction.py +215 -0
  19. astreum/_consensus/workers/__init__.py +9 -0
  20. astreum/_consensus/workers/discovery.py +48 -0
  21. astreum/_consensus/workers/validation.py +125 -0
  22. astreum/_consensus/workers/verify.py +63 -0
  23. astreum/_lispeum/__init__.py +16 -0
  24. astreum/_lispeum/environment.py +13 -0
  25. astreum/_lispeum/expression.py +190 -0
  26. astreum/_lispeum/high_evaluation.py +236 -0
  27. astreum/_lispeum/low_evaluation.py +123 -0
  28. astreum/_lispeum/meter.py +18 -0
  29. astreum/_lispeum/parser.py +51 -0
  30. astreum/_lispeum/tokenizer.py +22 -0
  31. astreum/_node.py +198 -0
  32. astreum/_storage/__init__.py +7 -0
  33. astreum/_storage/atom.py +109 -0
  34. astreum/_storage/patricia.py +478 -0
  35. astreum/_storage/setup.py +35 -0
  36. astreum/models/block.py +48 -39
  37. astreum/node.py +755 -563
  38. astreum/utils/bytes.py +24 -0
  39. astreum/utils/integer.py +25 -0
  40. astreum/utils/logging.py +219 -0
  41. {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/METADATA +50 -14
  42. astreum-0.2.61.dist-info/RECORD +57 -0
  43. astreum/lispeum/__init__.py +0 -2
  44. astreum/lispeum/environment.py +0 -40
  45. astreum/lispeum/expression.py +0 -86
  46. astreum/lispeum/parser.py +0 -41
  47. astreum/lispeum/tokenizer.py +0 -52
  48. astreum/models/account.py +0 -91
  49. astreum/models/accounts.py +0 -34
  50. astreum/models/transaction.py +0 -106
  51. astreum/relay/__init__.py +0 -0
  52. astreum/relay/peer.py +0 -9
  53. astreum/relay/route.py +0 -25
  54. astreum/relay/setup.py +0 -58
  55. astreum-0.2.29.dist-info/RECORD +0 -33
  56. {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/WHEEL +0 -0
  57. {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/licenses/LICENSE +0 -0
  58. {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/top_level.txt +0 -0
astreum/node.py CHANGED
@@ -1,589 +1,781 @@
1
- import socket
2
- import threading
3
- import time
4
- from queue import Queue
5
- from pathlib import Path
6
- from typing import Tuple, Dict, Union, Optional, List
7
- from datetime import datetime, timedelta, timezone
8
- import uuid
9
-
10
- from astreum.lispeum.environment import Env
11
- from astreum.lispeum.expression import Expr
12
- from astreum.relay.peer import Peer
13
- from astreum.relay.route import Route
14
- from astreum.relay.setup import load_ed25519, load_x25519, make_routes, setup_outgoing, setup_udp
15
- from astreum.storage.object import ObjectRequest, ObjectRequestType, ObjectResponse, ObjectResponseType
16
- from astreum.storage.setup import storage_setup
17
-
18
- from .models.transaction import Transaction
19
- from .format import encode, decode
20
- from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
21
- from cryptography.hazmat.primitives import serialization
22
- from .crypto import ed25519, x25519
23
- import blake3
24
- import struct
25
- from .models.message import Message, MessageTopic
26
-
27
- def encode_ip_address(host: str, port: int) -> bytes:
28
- ip_bytes = socket.inet_pton(socket.AF_INET6 if ':' in host else socket.AF_INET, host)
29
- port_bytes = struct.pack("!H", port)
30
- return ip_bytes + port_bytes
31
-
32
- def decode_ip_address(data: bytes) -> tuple[str, int]:
33
- if len(data) == 6:
34
- ip = socket.inet_ntop(socket.AF_INET, data[:4])
35
- port = struct.unpack("!H", data[4:6])[0]
36
- elif len(data) == 18:
37
- ip = socket.inet_ntop(socket.AF_INET6, data[:16])
38
- port = struct.unpack("!H", data[16:18])[0]
39
- else:
40
- raise ValueError("Invalid address byte format")
41
- return ip, port
42
-
43
- class Node:
44
- def __init__(self, config: dict = {}):
45
- self._machine_setup()
46
- machine_only = bool(config.get('machine-only', True))
47
- if not machine_only:
48
- (
49
- self.storage_path,
50
- self.memory_storage,
51
- self.storage_get_relay_timeout,
52
- self.storage_index
53
- ) = storage_setup(config)
54
-
55
- self._relay_setup(config=config)
56
- self._validation_setup(config=config)
57
-
58
- def _validation_setup(self, config: dict):
59
- if True:
60
- self.validator_transactions: Dict[bytes, Transaction] = {}
61
- # validator thread
62
- pass
63
-
64
- def _create_block(self):
65
- pass
66
-
67
- def _relay_setup(self, config: dict):
68
- self.use_ipv6 = config.get('use_ipv6', False)
69
-
70
- # key loading
71
- self.relay_secret_key = load_x25519(config.get('relay_secret_key'))
72
- self.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
73
-
74
- # derive pubs + routes
75
- self.relay_public_key = self.relay_secret_key.public_key()
76
- self.peer_route, self.validation_route = make_routes(
77
- self.relay_public_key,
78
- self.validation_secret_key
79
- )
80
-
81
- # sockets + queues + threads
82
- (self.incoming_socket,
83
- self.incoming_port,
84
- self.incoming_queue,
85
- self.incoming_populate_thread,
86
- self.incoming_process_thread
87
- ) = setup_udp(config.get('incoming_port', 7373), self.use_ipv6)
88
-
89
- (self.outgoing_socket,
90
- self.outgoing_queue,
91
- self.outgoing_thread
92
- ) = setup_outgoing(self.use_ipv6)
93
-
94
- # other workers & maps
95
- self.object_request_queue = Queue()
96
- self.peer_manager_thread = threading.Thread(
97
- target=self._relay_peer_manager,
98
- daemon=True
99
- )
100
- self.peer_manager_thread.start()
101
-
102
- self.peers, self.addresses = {}, {} # peers: Dict[X25519PublicKey,Peer], addresses: Dict[(str,int),X25519PublicKey]
103
-
104
- # bootstrap pings
105
- for addr in config.get('bootstrap', []):
106
- self._send_ping(addr)
107
-
108
- def _local_object_get(self, data_hash: bytes) -> Optional[bytes]:
109
- if self.memory_storage is not None:
110
- return self.memory_storage.get(data_hash)
111
-
112
- file_path = self.storage_path / data_hash.hex()
113
- if file_path.exists():
114
- return file_path.read_bytes()
115
- return None
116
-
117
- def _local_object_put(self, hash: bytes, data: bytes) -> bool:
118
- if self.memory_storage is not None:
119
- self.memory_storage[hash] = data
120
- return True
121
-
122
- file_path = self.storage_path / hash.hex()
123
- file_path.write_bytes(data)
124
- return True
125
-
126
- def _object_get(self, hash: bytes) -> Optional[bytes]:
127
- local_data = self._local_object_get(hash)
128
- if local_data:
129
- return local_data
130
-
131
- # find the nearest peer route node to the hash and send an object request
132
- closest_peer = self._get_closest_local_peer(hash)
133
- if closest_peer:
134
- object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, content=hash)
135
- self.outgoing_queue.put((object_request_message.to_bytes(), self.peers[closest_peer].address))
136
-
137
- # wait for upto self.storage_get_relay_timeout seconds for the object to be stored/until local_object_get returns something
138
- start_time = time.time()
139
- while time.time() - start_time < self.storage_get_relay_timeout:
140
- # Check if the object has been stored locally
141
- local_data = self._local_object_get(hash)
142
- if local_data:
143
- return local_data
144
- # Sleep briefly to avoid hammering the local storage
145
- time.sleep(0.1)
1
+ # DEPRECATED
2
+
3
+ # import socket
4
+ # import threading
5
+ # import time
6
+ # from queue import Queue
7
+ # from pathlib import Path
8
+ # from typing import Tuple, Dict, Union, Optional, List
9
+ # from datetime import datetime, timedelta, timezone
10
+ # import uuid
11
+
12
+ # from astreum.lispeum.environment import Env
13
+ # from astreum.lispeum.expression import Expr
14
+ # from astreum.relay.peer import Peer
15
+ # from astreum.relay.route import Route
16
+ # from astreum.relay.setup import load_ed25519, load_x25519, make_routes, setup_outgoing, setup_udp
17
+ # from astreum.storage.object import ObjectRequest, ObjectRequestType, ObjectResponse, ObjectResponseType
18
+ # from astreum.storage.setup import storage_setup
19
+
20
+ # from .models.transaction import Transaction
21
+ # from .format import encode, decode
22
+ # from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
23
+ # from cryptography.hazmat.primitives import serialization
24
+ # from .crypto import ed25519, x25519
25
+ # import blake3
26
+ # import struct
27
+ # from .models.message import Message, MessageTopic
28
+
29
+ # def encode_ip_address(host: str, port: int) -> bytes:
30
+ # ip_bytes = socket.inet_pton(socket.AF_INET6 if ':' in host else socket.AF_INET, host)
31
+ # port_bytes = struct.pack("!H", port)
32
+ # return ip_bytes + port_bytes
33
+
34
+ # def decode_ip_address(data: bytes) -> tuple[str, int]:
35
+ # if len(data) == 6:
36
+ # ip = socket.inet_ntop(socket.AF_INET, data[:4])
37
+ # port = struct.unpack("!H", data[4:6])[0]
38
+ # elif len(data) == 18:
39
+ # ip = socket.inet_ntop(socket.AF_INET6, data[:16])
40
+ # port = struct.unpack("!H", data[16:18])[0]
41
+ # else:
42
+ # raise ValueError("Invalid address byte format")
43
+ # return ip, port
44
+
45
+ # class Node:
46
+ # def __init__(self, config: dict = {}):
47
+ # self._machine_setup()
48
+ # machine_only = bool(config.get('machine-only', True))
49
+ # if not machine_only:
50
+ # (
51
+ # self.storage_path,
52
+ # self.memory_storage,
53
+ # self.storage_get_relay_timeout,
54
+ # self.storage_index
55
+ # ) = storage_setup(config)
56
+
57
+ # self._relay_setup(config=config)
58
+ # self._validation_setup(config=config)
59
+
60
+ # def _validation_setup(self, config: dict):
61
+ # if True:
62
+ # self.validator_transactions: Dict[bytes, Transaction] = {}
63
+ # # validator thread
64
+ # pass
65
+
66
+ # def _create_block(self):
67
+ # pass
68
+
69
+ # def _relay_setup(self, config: dict):
70
+ # self.use_ipv6 = config.get('use_ipv6', False)
71
+
72
+ # # key loading
73
+ # self.relay_secret_key = load_x25519(config.get('relay_secret_key'))
74
+ # self.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
75
+
76
+ # # derive pubs + routes
77
+ # self.relay_public_key = self.relay_secret_key.public_key()
78
+ # self.peer_route, self.validation_route = make_routes(
79
+ # self.relay_public_key,
80
+ # self.validation_secret_key
81
+ # )
82
+
83
+ # # sockets + queues + threads
84
+ # (self.incoming_socket,
85
+ # self.incoming_port,
86
+ # self.incoming_queue,
87
+ # self.incoming_populate_thread,
88
+ # self.incoming_process_thread
89
+ # ) = setup_udp(config.get('incoming_port', 7373), self.use_ipv6)
90
+
91
+ # (self.outgoing_socket,
92
+ # self.outgoing_queue,
93
+ # self.outgoing_thread
94
+ # ) = setup_outgoing(self.use_ipv6)
95
+
96
+ # # other workers & maps
97
+ # self.object_request_queue = Queue()
98
+ # self.peer_manager_thread = threading.Thread(
99
+ # target=self._relay_peer_manager,
100
+ # daemon=True
101
+ # )
102
+ # self.peer_manager_thread.start()
103
+
104
+ # self.peers, self.addresses = {}, {} # peers: Dict[X25519PublicKey,Peer], addresses: Dict[(str,int),X25519PublicKey]
105
+
106
+ # # bootstrap pings
107
+ # for addr in config.get('bootstrap', []):
108
+ # self._send_ping(addr)
109
+
110
+ # def _local_object_get(self, data_hash: bytes) -> Optional[bytes]:
111
+ # if self.memory_storage is not None:
112
+ # return self.memory_storage.get(data_hash)
113
+
114
+ # file_path = self.storage_path / data_hash.hex()
115
+ # if file_path.exists():
116
+ # return file_path.read_bytes()
117
+ # return None
118
+
119
+ # def _local_object_put(self, hash: bytes, data: bytes) -> bool:
120
+ # if self.memory_storage is not None:
121
+ # self.memory_storage[hash] = data
122
+ # return True
123
+
124
+ # file_path = self.storage_path / hash.hex()
125
+ # file_path.write_bytes(data)
126
+ # return True
127
+
128
+ # def _object_get(self, hash: bytes) -> Optional[bytes]:
129
+ # local_data = self._local_object_get(hash)
130
+ # if local_data:
131
+ # return local_data
132
+
133
+ # # find the nearest peer route node to the hash and send an object request
134
+ # closest_peer = self._get_closest_local_peer(hash)
135
+ # if closest_peer:
136
+ # object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, content=hash)
137
+ # self.outgoing_queue.put((object_request_message.to_bytes(), self.peers[closest_peer].address))
138
+
139
+ # # wait for upto self.storage_get_relay_timeout seconds for the object to be stored/until local_object_get returns something
140
+ # start_time = time.time()
141
+ # while time.time() - start_time < self.storage_get_relay_timeout:
142
+ # # Check if the object has been stored locally
143
+ # local_data = self._local_object_get(hash)
144
+ # if local_data:
145
+ # return local_data
146
+ # # Sleep briefly to avoid hammering the local storage
147
+ # time.sleep(0.1)
146
148
 
147
- # If we reach here, the object was not received within the timeout period
148
- return None
149
-
150
- # RELAY METHODS
151
- def _relay_incoming_queue_populating(self):
152
- while True:
153
- try:
154
- data, addr = self.incoming_socket.recvfrom(4096)
155
- self.incoming_queue.put((data, addr))
156
- except Exception as e:
157
- print(f"Error in _relay_populate_incoming_queue: {e}")
158
-
159
- def _relay_incoming_queue_processing(self):
160
- while True:
161
- try:
162
- data, addr = self.incoming_queue.get()
163
- message = Message.from_bytes(data)
164
- match message.topic:
165
- case MessageTopic.PING:
166
- peer_pub_key = self.addresses.get(addr)
167
- if peer_pub_key in self.peers:
168
- self.peers[peer_pub_key].timestamp = datetime.now(timezone.utc)
169
- continue
170
-
171
- is_validator_flag = decode(message.body)
172
-
173
- if peer_pub_key not in self.peers:
174
- self._send_ping(addr)
175
-
176
- peer = Peer(my_sec_key=self.relay_secret_key, peer_pub_key=peer_pub_key)
177
- self.peers[peer.sender] = peer
178
- self.peer_route.add_peer(peer_pub_key)
179
- if is_validator_flag == [1]:
180
- self.validation_route.add_peer(peer_pub_key)
181
-
182
- if peer.timestamp < datetime.now(timezone.utc) - timedelta(minutes=5.0):
183
- self._send_ping(addr)
149
+ # # If we reach here, the object was not received within the timeout period
150
+ # return None
151
+
152
+ # # RELAY METHODS
153
+ # def _relay_incoming_queue_populating(self):
154
+ # while True:
155
+ # try:
156
+ # data, addr = self.incoming_socket.recvfrom(4096)
157
+ # self.incoming_queue.put((data, addr))
158
+ # except Exception as e:
159
+ # print(f"Error in _relay_populate_incoming_queue: {e}")
160
+
161
+ # def _relay_incoming_queue_processing(self):
162
+ # while True:
163
+ # try:
164
+ # data, addr = self.incoming_queue.get()
165
+ # message = Message.from_bytes(data)
166
+ # match message.topic:
167
+ # case MessageTopic.PING:
168
+ # peer_pub_key = self.addresses.get(addr)
169
+ # if peer_pub_key in self.peers:
170
+ # self.peers[peer_pub_key].timestamp = datetime.now(timezone.utc)
171
+ # continue
172
+
173
+ # is_validator_flag = decode(message.body)
174
+
175
+ # if peer_pub_key not in self.peers:
176
+ # self._send_ping(addr)
177
+
178
+ # peer = Peer(my_sec_key=self.relay_secret_key, peer_pub_key=peer_pub_key)
179
+ # self.peers[peer.sender] = peer
180
+ # self.peer_route.add_peer(peer_pub_key)
181
+ # if is_validator_flag == [1]:
182
+ # self.validation_route.add_peer(peer_pub_key)
183
+
184
+ # if peer.timestamp < datetime.now(timezone.utc) - timedelta(minutes=5.0):
185
+ # self._send_ping(addr)
184
186
 
185
- case MessageTopic.OBJECT_REQUEST:
186
- try:
187
- object_request = ObjectRequest.from_bytes(message.body)
188
-
189
- match object_request.type:
190
- # -------------- OBJECT_GET --------------
191
- case ObjectRequestType.OBJECT_GET:
192
- object_hash = object_request.hash
193
-
194
- # 1. If we already have the object, return it.
195
- local_data = self._local_object_get(object_hash)
196
- if local_data is not None:
197
- resp = ObjectResponse(
198
- type=ObjectResponseType.OBJECT_FOUND,
199
- data=local_data,
200
- hash=object_hash
201
- )
202
- obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
203
- self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
204
- return # done
205
-
206
- # 2. If we know a provider, tell the requester.
207
- if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
208
- self.storage_index = {}
209
- if object_hash in self.storage_index:
210
- provider_bytes = self.storage_index[object_hash]
211
- resp = ObjectResponse(
212
- type=ObjectResponseType.OBJECT_PROVIDER,
213
- data=provider_bytes,
214
- hash=object_hash
215
- )
216
- obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
217
- self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
218
- return # done
219
-
220
- # 3. Otherwise, direct the requester to a peer nearer to the hash.
221
- nearest = self._get_closest_local_peer(object_hash)
222
- if nearest:
223
- nearest_key, nearest_peer = nearest
224
- peer_info = encode([
225
- nearest_key.public_bytes(
226
- encoding=serialization.Encoding.Raw,
227
- format=serialization.PublicFormat.Raw
228
- ),
229
- encode_ip_address(*nearest_peer.address)
230
- ])
231
- resp = ObjectResponse(
232
- type=ObjectResponseType.OBJECT_NEAREST_PEER,
233
- data=peer_info,
234
- hash=object_hash
235
- )
236
- obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
237
- self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
238
-
239
- # -------------- OBJECT_PUT --------------
240
- case ObjectRequestType.OBJECT_PUT:
241
- # Ensure the hash is present / correct.
242
- obj_hash = object_request.hash or blake3.blake3(object_request.data).digest()
243
-
244
- nearest = self._get_closest_local_peer(obj_hash)
245
- # If a strictly nearer peer exists, forward the PUT.
246
- if nearest and self._is_closer_than_local_peers(obj_hash, nearest[0]):
247
- fwd_req = ObjectRequest(
248
- type=ObjectRequestType.OBJECT_PUT,
249
- data=object_request.data,
250
- hash=obj_hash
251
- )
252
- obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=fwd_req.to_bytes())
253
- self.outgoing_queue.put((obj_req_msg.to_bytes(), nearest[1].address))
254
- else:
255
- # We are closest → remember who can provide the object.
256
- peer_pub_key = self.addresses.get(addr)
257
- provider_record = encode([
258
- peer_pub_key.public_bytes(),
259
- encode_ip_address(*addr)
260
- ])
261
- if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
262
- self.storage_index = {}
263
- self.storage_index[obj_hash] = provider_record
264
-
265
- except Exception as e:
266
- print(f"Error processing OBJECT_REQUEST: {e}")
267
-
268
- case MessageTopic.OBJECT_RESPONSE:
269
- try:
270
- object_response = ObjectResponse.from_bytes(message.body)
271
- if object_response.hash not in self.object_request_queue:
272
- continue
187
+ # case MessageTopic.OBJECT_REQUEST:
188
+ # try:
189
+ # object_request = ObjectRequest.from_bytes(message.body)
190
+
191
+ # match object_request.type:
192
+ # # -------------- OBJECT_GET --------------
193
+ # case ObjectRequestType.OBJECT_GET:
194
+ # object_hash = object_request.hash
195
+
196
+ # # 1. If we already have the object, return it.
197
+ # local_data = self._local_object_get(object_hash)
198
+ # if local_data is not None:
199
+ # resp = ObjectResponse(
200
+ # type=ObjectResponseType.OBJECT_FOUND,
201
+ # data=local_data,
202
+ # hash=object_hash
203
+ # )
204
+ # obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
205
+ # self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
206
+ # return # done
207
+
208
+ # # 2. If we know a provider, tell the requester.
209
+ # if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
210
+ # self.storage_index = {}
211
+ # if object_hash in self.storage_index:
212
+ # provider_bytes = self.storage_index[object_hash]
213
+ # resp = ObjectResponse(
214
+ # type=ObjectResponseType.OBJECT_PROVIDER,
215
+ # data=provider_bytes,
216
+ # hash=object_hash
217
+ # )
218
+ # obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
219
+ # self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
220
+ # return # done
221
+
222
+ # # 3. Otherwise, direct the requester to a peer nearer to the hash.
223
+ # nearest = self._get_closest_local_peer(object_hash)
224
+ # if nearest:
225
+ # nearest_key, nearest_peer = nearest
226
+ # peer_info = encode([
227
+ # nearest_key.public_bytes(
228
+ # encoding=serialization.Encoding.Raw,
229
+ # format=serialization.PublicFormat.Raw
230
+ # ),
231
+ # encode_ip_address(*nearest_peer.address)
232
+ # ])
233
+ # resp = ObjectResponse(
234
+ # type=ObjectResponseType.OBJECT_NEAREST_PEER,
235
+ # data=peer_info,
236
+ # hash=object_hash
237
+ # )
238
+ # obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
239
+ # self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
240
+
241
+ # # -------------- OBJECT_PUT --------------
242
+ # case ObjectRequestType.OBJECT_PUT:
243
+ # # Ensure the hash is present / correct.
244
+ # obj_hash = object_request.hash or blake3.blake3(object_request.data).digest()
245
+
246
+ # nearest = self._get_closest_local_peer(obj_hash)
247
+ # # If a strictly nearer peer exists, forward the PUT.
248
+ # if nearest and self._is_closer_than_local_peers(obj_hash, nearest[0]):
249
+ # fwd_req = ObjectRequest(
250
+ # type=ObjectRequestType.OBJECT_PUT,
251
+ # data=object_request.data,
252
+ # hash=obj_hash
253
+ # )
254
+ # obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=fwd_req.to_bytes())
255
+ # self.outgoing_queue.put((obj_req_msg.to_bytes(), nearest[1].address))
256
+ # else:
257
+ # # We are closest → remember who can provide the object.
258
+ # peer_pub_key = self.addresses.get(addr)
259
+ # provider_record = encode([
260
+ # peer_pub_key.public_bytes(),
261
+ # encode_ip_address(*addr)
262
+ # ])
263
+ # if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
264
+ # self.storage_index = {}
265
+ # self.storage_index[obj_hash] = provider_record
266
+
267
+ # except Exception as e:
268
+ # print(f"Error processing OBJECT_REQUEST: {e}")
269
+
270
+ # case MessageTopic.OBJECT_RESPONSE:
271
+ # try:
272
+ # object_response = ObjectResponse.from_bytes(message.body)
273
+ # if object_response.hash not in self.object_request_queue:
274
+ # continue
273
275
 
274
- match object_response.type:
275
- case ObjectResponseType.OBJECT_FOUND:
276
- if object_response.hash != blake3.blake3(object_response.data).digest():
277
- continue
278
- self.object_request_queue.remove(object_response.hash)
279
- self._local_object_put(object_response.hash, object_response.data)
280
-
281
- case ObjectResponseType.OBJECT_PROVIDER:
282
- _provider_public_key, provider_address = decode(object_response.data)
283
- provider_ip, provider_port = decode_ip_address(provider_address)
284
- obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=object_hash)
285
- self.outgoing_queue.put((obj_req_msg.to_bytes(), (provider_ip, provider_port)))
286
-
287
- case ObjectResponseType.OBJECT_NEAREST_PEER:
288
- # -- decode the peer info sent back
289
- nearest_peer_public_key_bytes, nearest_peer_address = (
290
- decode(object_response.data)
291
- )
292
- nearest_peer_public_key = X25519PublicKey.from_public_bytes(
293
- nearest_peer_public_key_bytes
294
- )
295
-
296
- # -- XOR-distance between the object hash and the candidate peer
297
- peer_bytes = nearest_peer_public_key.public_bytes(
298
- encoding=serialization.Encoding.Raw,
299
- format=serialization.PublicFormat.Raw,
300
- )
301
- object_response_xor = sum(
302
- a ^ b for a, b in zip(object_response.hash, peer_bytes)
303
- )
304
-
305
- # -- forward only if that peer is strictly nearer than any local peer
306
- if self._is_closer_than_local_peers(
307
- object_response.hash, nearest_peer_public_key
308
- ):
309
- nearest_peer_ip, nearest_peer_port = decode_ip_address(nearest_peer_address)
310
- obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, content=object_response.hash)
311
- self.outgoing_queue.put((obj_req_msg.to_bytes(), (nearest_peer_ip, nearest_peer_port),)
312
- )
276
+ # match object_response.type:
277
+ # case ObjectResponseType.OBJECT_FOUND:
278
+ # if object_response.hash != blake3.blake3(object_response.data).digest():
279
+ # continue
280
+ # self.object_request_queue.remove(object_response.hash)
281
+ # self._local_object_put(object_response.hash, object_response.data)
282
+
283
+ # case ObjectResponseType.OBJECT_PROVIDER:
284
+ # _provider_public_key, provider_address = decode(object_response.data)
285
+ # provider_ip, provider_port = decode_ip_address(provider_address)
286
+ # obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=object_hash)
287
+ # self.outgoing_queue.put((obj_req_msg.to_bytes(), (provider_ip, provider_port)))
288
+
289
+ # case ObjectResponseType.OBJECT_NEAREST_PEER:
290
+ # # -- decode the peer info sent back
291
+ # nearest_peer_public_key_bytes, nearest_peer_address = (
292
+ # decode(object_response.data)
293
+ # )
294
+ # nearest_peer_public_key = X25519PublicKey.from_public_bytes(
295
+ # nearest_peer_public_key_bytes
296
+ # )
297
+
298
+ # # -- XOR-distance between the object hash and the candidate peer
299
+ # peer_bytes = nearest_peer_public_key.public_bytes(
300
+ # encoding=serialization.Encoding.Raw,
301
+ # format=serialization.PublicFormat.Raw,
302
+ # )
303
+ # object_response_xor = sum(
304
+ # a ^ b for a, b in zip(object_response.hash, peer_bytes)
305
+ # )
306
+
307
+ # # -- forward only if that peer is strictly nearer than any local peer
308
+ # if self._is_closer_than_local_peers(
309
+ # object_response.hash, nearest_peer_public_key
310
+ # ):
311
+ # nearest_peer_ip, nearest_peer_port = decode_ip_address(nearest_peer_address)
312
+ # obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, content=object_response.hash)
313
+ # self.outgoing_queue.put((obj_req_msg.to_bytes(), (nearest_peer_ip, nearest_peer_port),)
314
+ # )
313
315
 
314
316
 
315
- except Exception as e:
316
- print(f"Error processing OBJECT_RESPONSE: {e}")
317
+ # except Exception as e:
318
+ # print(f"Error processing OBJECT_RESPONSE: {e}")
317
319
 
318
- except Exception as e:
319
- print(f"Error processing message: {e}")
320
+ # except Exception as e:
321
+ # print(f"Error processing message: {e}")
320
322
 
321
- def _relay_outgoing_queue_processor(self):
322
- while True:
323
- try:
324
- data, addr = self.outgoing_queue.get()
325
- self.outgoing_socket.sendto(data, addr)
326
- except Exception as e:
327
- print(f"Error sending message: {e}")
323
+ # def _relay_outgoing_queue_processor(self):
324
+ # while True:
325
+ # try:
326
+ # data, addr = self.outgoing_queue.get()
327
+ # self.outgoing_socket.sendto(data, addr)
328
+ # except Exception as e:
329
+ # print(f"Error sending message: {e}")
328
330
 
329
- def _relay_peer_manager(self):
330
- while True:
331
- try:
332
- time.sleep(60)
333
- for peer in self.peers.values():
334
- if (datetime.now(timezone.utc) - peer.timestamp).total_seconds() > 900:
335
- del self.peers[peer.sender]
336
- self.peer_route.remove_peer(peer.sender)
337
- if peer.sender in self.validation_route.buckets:
338
- self.validation_route.remove_peer(peer.sender)
339
- except Exception as e:
340
- print(f"Error in _peer_manager_thread: {e}")
341
-
342
- def _send_ping(self, addr: Tuple[str, int]):
343
- is_validator_flag = encode([1] if self.validation_secret_key else [0])
344
- ping_message = Message(topic=MessageTopic.PING, content=is_validator_flag)
345
- self.outgoing_queue.put((ping_message.to_bytes(), addr))
346
-
347
- def _get_closest_local_peer(self, hash: bytes) -> Optional[Tuple[X25519PublicKey, Peer]]:
348
- # Find the globally closest peer using XOR distance
349
- closest_peer = None
350
- closest_distance = None
331
+ # def _relay_peer_manager(self):
332
+ # while True:
333
+ # try:
334
+ # time.sleep(60)
335
+ # for peer in self.peers.values():
336
+ # if (datetime.now(timezone.utc) - peer.timestamp).total_seconds() > 900:
337
+ # del self.peers[peer.sender]
338
+ # self.peer_route.remove_peer(peer.sender)
339
+ # if peer.sender in self.validation_route.buckets:
340
+ # self.validation_route.remove_peer(peer.sender)
341
+ # except Exception as e:
342
+ # print(f"Error in _peer_manager_thread: {e}")
343
+
344
+ # def _send_ping(self, addr: Tuple[str, int]):
345
+ # is_validator_flag = encode([1] if self.validation_secret_key else [0])
346
+ # ping_message = Message(topic=MessageTopic.PING, content=is_validator_flag)
347
+ # self.outgoing_queue.put((ping_message.to_bytes(), addr))
348
+
349
+ # def _get_closest_local_peer(self, hash: bytes) -> Optional[Tuple[X25519PublicKey, Peer]]:
350
+ # # Find the globally closest peer using XOR distance
351
+ # closest_peer = None
352
+ # closest_distance = None
351
353
 
352
- # Check all peers
353
- for peer_key, peer in self.peers.items():
354
- # Calculate XOR distance between hash and peer's public key
355
- peer_bytes = peer_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
356
- # XOR each byte and sum them to get a total distance
357
- distance = sum(a ^ b for a, b in zip(hash, peer_bytes))
358
- # Update the closest peer if the distance is smaller
359
- if closest_distance is None or distance < closest_distance:
360
- closest_distance = distance
361
- closest_peer = (peer_key, peer)
354
+ # # Check all peers
355
+ # for peer_key, peer in self.peers.items():
356
+ # # Calculate XOR distance between hash and peer's public key
357
+ # peer_bytes = peer_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
358
+ # # XOR each byte and sum them to get a total distance
359
+ # distance = sum(a ^ b for a, b in zip(hash, peer_bytes))
360
+ # # Update the closest peer if the distance is smaller
361
+ # if closest_distance is None or distance < closest_distance:
362
+ # closest_distance = distance
363
+ # closest_peer = (peer_key, peer)
362
364
 
363
- return closest_peer
365
+ # return closest_peer
364
366
 
365
- def _is_closer_than_local_peers(self, hash: bytes, foreign_peer_public_key: X25519PublicKey) -> bool:
367
+ # def _is_closer_than_local_peers(self, hash: bytes, foreign_peer_public_key: X25519PublicKey) -> bool:
366
368
 
367
- # Get the closest local peer
368
- closest_local_peer = self._get_closest_local_peer(hash)
369
+ # # Get the closest local peer
370
+ # closest_local_peer = self._get_closest_local_peer(hash)
369
371
 
370
- # If we have no local peers, the foreign peer is closer by default
371
- if closest_local_peer is None:
372
- return True
372
+ # # If we have no local peers, the foreign peer is closer by default
373
+ # if closest_local_peer is None:
374
+ # return True
373
375
 
374
- # Calculate XOR distance for the foreign peer
375
- foreign_peer_bytes = foreign_peer_public_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
376
- foreign_distance = sum(a ^ b for a, b in zip(hash, foreign_peer_bytes))
376
+ # # Calculate XOR distance for the foreign peer
377
+ # foreign_peer_bytes = foreign_peer_public_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
378
+ # foreign_distance = sum(a ^ b for a, b in zip(hash, foreign_peer_bytes))
377
379
 
378
- # Get the closest local peer key and calculate its distance
379
- closest_peer_key, _ = closest_local_peer
380
- closest_peer_bytes = closest_peer_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
381
- local_distance = sum(a ^ b for a, b in zip(hash, closest_peer_bytes))
380
+ # # Get the closest local peer key and calculate its distance
381
+ # closest_peer_key, _ = closest_local_peer
382
+ # closest_peer_bytes = closest_peer_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
383
+ # local_distance = sum(a ^ b for a, b in zip(hash, closest_peer_bytes))
382
384
 
383
- # Return True if the foreign peer is closer (has smaller XOR distance)
384
- return foreign_distance < local_distance
385
-
386
- # MACHINE
387
- def _machine_setup(self):
388
- self.environments: Dict[uuid.UUID, Env] = {}
389
- self.machine_environments_lock = threading.Lock()
390
-
391
- def machine_create_environment(self, parent_id: Optional[uuid.UUID] = None) -> uuid.UUID:
392
- env_id = uuid.uuid4()
393
- with self.machine_environments_lock:
394
- while env_id in self.environments:
395
- env_id = uuid.uuid4()
396
- self.environments[env_id] = Env(parent_id=parent_id)
397
- return env_id
385
+ # # Return True if the foreign peer is closer (has smaller XOR distance)
386
+ # return foreign_distance < local_distance
387
+
388
+ # # MACHINE
389
+ # def _machine_setup(self):
390
+ # self.environments: Dict[uuid.UUID, Env] = {}
391
+ # self.machine_environments_lock = threading.Lock()
392
+
393
+ # def machine_create_environment(self, parent_id: Optional[uuid.UUID] = None) -> uuid.UUID:
394
+ # env_id = uuid.uuid4()
395
+ # with self.machine_environments_lock:
396
+ # while env_id in self.environments:
397
+ # env_id = uuid.uuid4()
398
+ # self.environments[env_id] = Env(parent_id=parent_id)
399
+ # return env_id
398
400
 
399
- def machine_get_or_create_environment(
400
- self,
401
- env_id: Optional[uuid.UUID] = None,
402
- parent_id: Optional[uuid.UUID] = None,
403
- max_exprs: Optional[int] = None
404
- ) -> uuid.UUID:
405
- with self.machine_environments_lock:
406
- if env_id is not None and env_id in self.environments:
407
- return env_id
408
- new_id = env_id if env_id is not None else uuid.uuid4()
409
- while new_id in self.environments:
410
- new_id = uuid.uuid4()
411
- self.environments[new_id] = Env(parent_id=parent_id, max_exprs=max_exprs)
412
- return new_id
413
-
414
- def machine_delete_environment(self, env_id: uuid.UUID) -> bool:
415
- with self.machine_environments_lock:
416
- removed = self.environments.pop(env_id, None)
417
- return removed is not None
418
-
419
- def machine_expr_get(self, env_id: uuid.UUID, name: str) -> Optional[Expr]:
420
- with self.machine_environments_lock:
421
- cur = self.environments.get(env_id)
422
- while cur is not None:
423
- if name in cur.data:
424
- return cur.data[name]
425
- if cur.parent_id:
426
- cur = self.environments.get(cur.parent_id)
427
- else:
428
- cur = None
429
- return None
401
+ # def machine_get_or_create_environment(
402
+ # self,
403
+ # env_id: Optional[uuid.UUID] = None,
404
+ # parent_id: Optional[uuid.UUID] = None,
405
+ # max_exprs: Optional[int] = None
406
+ # ) -> uuid.UUID:
407
+ # with self.machine_environments_lock:
408
+ # if env_id is not None and env_id in self.environments:
409
+ # return env_id
410
+ # new_id = env_id if env_id is not None else uuid.uuid4()
411
+ # while new_id in self.environments:
412
+ # new_id = uuid.uuid4()
413
+ # self.environments[new_id] = Env(parent_id=parent_id, max_exprs=max_exprs)
414
+ # return new_id
415
+
416
+ # def machine_delete_environment(self, env_id: uuid.UUID) -> bool:
417
+ # with self.machine_environments_lock:
418
+ # removed = self.environments.pop(env_id, None)
419
+ # return removed is not None
420
+
421
+ # def machine_expr_get(self, env_id: uuid.UUID, name: str) -> Optional[Expr]:
422
+ # with self.machine_environments_lock:
423
+ # cur = self.environments.get(env_id)
424
+ # while cur is not None:
425
+ # if name in cur.data:
426
+ # return cur.data[name]
427
+ # if cur.parent_id:
428
+ # cur = self.environments.get(cur.parent_id)
429
+ # else:
430
+ # cur = None
431
+ # return None
430
432
 
431
- def machine_expr_put(self, env_id: uuid.UUID, name: str, expr: Expr):
432
- with self.machine_environments_lock:
433
- env = self.environments.get(env_id)
434
- if env is None:
435
- return False
436
- env.put(name, expr)
437
- return True
438
-
439
- def machine_expr_eval(self, env_id: uuid.UUID, expr: Expr) -> Expr:
440
- if isinstance(expr, Expr.Boolean) or isinstance(expr, Expr.Integer) or isinstance(expr, Expr.String) or isinstance(expr, Expr.Error):
441
- return expr
433
+ # def machine_expr_put(self, env_id: uuid.UUID, name: str, expr: Expr):
434
+ # with self.machine_environments_lock:
435
+ # env = self.environments.get(env_id)
436
+ # if env is None:
437
+ # return False
438
+ # env.put(name, expr)
439
+ # return True
440
+
441
+ # def machine_expr_eval(self, env_id: uuid.UUID, expr: Expr) -> Expr:
442
+ # if isinstance(expr, Expr.Boolean) or isinstance(expr, Expr.Integer) or isinstance(expr, Expr.String) or isinstance(expr, Expr.Error):
443
+ # return expr
442
444
 
443
- elif isinstance(expr, Expr.Symbol):
444
- value = self.machine_expr_get(env_id=env_id, name=expr.value)
445
- if value:
446
- return value
447
- else:
448
- return Expr.Error(message=f"unbound symbol '{expr.value}'", origin=expr)
445
+ # elif isinstance(expr, Expr.Symbol):
446
+ # value = self.machine_expr_get(env_id=env_id, name=expr.value)
447
+ # if value:
448
+ # return value
449
+ # else:
450
+ # return Expr.Error(message=f"unbound symbol '{expr.value}'", origin=expr)
449
451
 
450
- elif isinstance(expr, Expr.ListExpr):
451
- if len(expr.elements) == 0:
452
- return expr
453
- if len(expr.elements) == 1:
454
- return self.machine_expr_eval(expr=expr.elements[0], env_id=env_id)
455
- first = expr.elements[0]
456
- if isinstance(first, Expr.Symbol):
457
- first_symbol_value = self.machine_expr_get(env_id=env_id, name=first.value)
452
+ # elif isinstance(expr, Expr.ListExpr):
453
+ # if len(expr.elements) == 0:
454
+ # return expr
455
+ # if len(expr.elements) == 1:
456
+ # return self.machine_expr_eval(expr=expr.elements[0], env_id=env_id)
457
+ # first = expr.elements[0]
458
+ # if isinstance(first, Expr.Symbol):
459
+ # first_symbol_value = self.machine_expr_get(env_id=env_id, name=first.value)
458
460
 
459
- if first_symbol_value and not isinstance(first_symbol_value, Expr.Function):
460
- evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
461
- return Expr.ListExpr(evaluated_elements)
461
+ # if first_symbol_value and not isinstance(first_symbol_value, Expr.Function):
462
+ # evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
463
+ # return Expr.ListExpr(evaluated_elements)
462
464
 
463
- elif first.value == "def":
464
- args = expr.elements[1:]
465
- if len(args) != 2:
466
- return Expr.Error(message=f"'def' expects exactly 2 arguments, got {len(args)}", origin=expr)
467
- if not isinstance(args[0], Expr.Symbol):
468
- return Expr.Error(message="first argument to 'def' must be a symbol", origin=args[0])
469
- result = self.machine_expr_eval(env_id=env_id, expr=args[1])
470
- if isinstance(result, Expr.Error):
471
- return result
465
+ # elif first.value == "def":
466
+ # args = expr.elements[1:]
467
+ # if len(args) != 2:
468
+ # return Expr.Error("def expects key value", origin=expr)
469
+ # if not isinstance(args[0], Expr.Symbol):
470
+ # return Expr.Error(message="first argument to 'def' must be a symbol", origin=args[0])
471
+ # result = self.machine_expr_eval(env_id=env_id, expr=args[1])
472
+ # if isinstance(result, Expr.Error):
473
+ # return result
472
474
 
473
- self.machine_expr_put(env_id=env_id, name=args[0].value, expr=result)
474
- return result
475
-
476
- # # List
477
- elif first.value == "list.each":
478
- internal_function = expr.elements[1]
479
-
480
-
481
-
482
- # Integer arithmetic primitives
483
- elif first.value == "+":
484
- args = expr.elements[1:]
485
- if not args:
486
- return Expr.Error("'+' expects at least 1 argument", origin=expr)
487
- vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
488
- for v in vals:
489
- if isinstance(v, Expr.Error): return v
490
- if not isinstance(v, Expr.Integer):
491
- return Expr.Error("'+' only accepts integer operands", origin=v)
492
- return Expr.Integer(abs(vals[0].value) if len(vals) == 1
493
- else sum(v.value for v in vals))
494
-
495
- elif first.value == "-":
496
- args = expr.elements[1:]
497
- if not args:
498
- return Expr.Error("'-' expects at least 1 argument", origin=expr)
499
- vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
500
- for v in vals:
501
- if isinstance(v, Expr.Error): return v
502
- if not isinstance(v, Expr.Integer):
503
- return Expr.Error("'-' only accepts integer operands", origin=v)
504
- if len(vals) == 1:
505
- return Expr.Integer(-vals[0].value)
506
- result = vals[0].value
507
- for v in vals[1:]:
508
- result -= v.value
509
- return Expr.Integer(result)
510
-
511
- elif first.value == "/":
512
- args = expr.elements[1:]
513
- if len(args) < 2:
514
- return Expr.Error("'/' expects at least 2 arguments", origin=expr)
515
- vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
516
- for v in vals:
517
- if isinstance(v, Expr.Error): return v
518
- if not isinstance(v, Expr.Integer):
519
- return Expr.Error("'/' only accepts integer operands", origin=v)
520
- result = vals[0].value
521
- for v in vals[1:]:
522
- if v.value == 0:
523
- return Expr.Error("division by zero", origin=v)
524
- if result % v.value:
525
- return Expr.Error("non-exact division", origin=expr)
526
- result //= v.value
527
- return Expr.Integer(result)
528
-
529
- elif first.value == "%":
530
- if len(expr.elements) != 3:
531
- return Expr.Error("'%' expects exactly 2 arguments", origin=expr)
532
- a = self.machine_expr_eval(env_id=env_id, expr=expr.elements[1])
533
- b = self.machine_expr_eval(env_id=env_id, expr=expr.elements[2])
534
- for v in (a, b):
535
- if isinstance(v, Expr.Error): return v
536
- if not isinstance(v, Expr.Integer):
537
- return Expr.Error("'%' only accepts integer operands", origin=v)
538
- if b.value == 0:
539
- return Expr.Error("division by zero", origin=expr.elements[2])
540
- return Expr.Integer(a.value % b.value)
541
-
542
- elif first.value in ("=", "!=", ">", "<", ">=", "<="):
543
- args = expr.elements[1:]
544
- if len(args) != 2:
545
- return Expr.Error(f"'{first.value}' expects exactly 2 arguments", origin=expr)
546
-
547
- left = self.machine_expr_eval(env_id=env_id, expr=args[0])
548
- right = self.machine_expr_eval(env_id=env_id, expr=args[1])
549
-
550
- for v in (left, right):
551
- if isinstance(v, Expr.Error):
552
- return v
553
- if not isinstance(v, Expr.Integer):
554
- return Expr.Error(f"'{first.value}' only accepts integer operands", origin=v)
555
-
556
- a, b = left.value, right.value
557
- match first.value:
558
- case "=": res = a == b
559
- case "!=": res = a != b
560
- case ">": res = a > b
561
- case "<": res = a < b
562
- case ">=": res = a >= b
563
- case "<=": res = a <= b
564
-
565
- return Expr.Boolean(res)
566
-
567
- if isinstance(first, Expr.Function):
568
- arg_exprs = expr.elements[1:]
569
- if len(arg_exprs) != len(first.params):
570
- return Expr.Error(f"arity mismatch: expected {len(first.params)}, got {len(arg_exprs)}", origin=expr)
571
-
572
- call_env = self.machine_create_environment(parent_id=env_id)
573
- for name, aexpr in zip(first.params, arg_exprs):
574
- val = self.machine_expr_eval(env_id, aexpr)
575
- if isinstance(val, Expr.Error): return val
576
- self.machine_expr_put(call_env, name, val)
577
-
578
- return self.machine_expr_eval(env_id=call_env, expr=first.body)
579
-
580
- else:
581
- evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
582
- return Expr.ListExpr(evaluated_elements)
475
+ # self.machine_expr_put(env_id=env_id, name=args[0].value, expr=result)
476
+ # return result
477
+
478
+
479
+ # ## DEF: (def x 1) -> ()
480
+
481
+ # ## GET: (x) -> 1
482
+
483
+ # ## ADD: (+ 1 2) -> (+ 3) -> 3
484
+
485
+ # ## NAND: (~& 1 1) -> (~& 0) -> 0
486
+
487
+ # ##
488
+
489
+
490
+ # ## List: ints -> (1 2)
491
+ # # push: (list.push 3 ints) -> (1 2 3) / (list.push 0 0 ints) -> (0 1 2)
492
+ # elif first.value == "list.push":
493
+ # args = expr.elements[1:]
494
+ # if len(args) == 2:
495
+ # val_expr, list_expr = args
496
+ # idx = None
497
+ # elif len(args) == 3:
498
+ # idx_expr, val_expr, list_expr = args
499
+ # idx = self.machine_expr_eval(env_id, idx_expr)
500
+ # if isinstance(idx, Expr.Error): return idx
501
+ # if not isinstance(idx, Expr.IntExpr):
502
+ # return Expr.Error("index must be int", origin=idx_expr)
503
+ # idx = idx.value
504
+ # else:
505
+ # return Expr.Error("list.push expects (value list) or (index value list)", origin=expr)
506
+
507
+ # lst = self.machine_expr_eval(env_id, list_expr)
508
+ # if isinstance(lst, Expr.Error): return lst
509
+ # if not isinstance(lst, Expr.ListExpr):
510
+ # return Expr.Error("last arg to list.push must be a list", origin=list_expr)
511
+
512
+ # val = self.machine_expr_eval(env_id, val_expr)
513
+ # if isinstance(val, Expr.Error): return val
514
+
515
+ # elems = list(lst.elements)
516
+ # if idx is None:
517
+ # elems.append(val)
518
+ # else:
519
+ # if idx < 0 or idx > len(elems):
520
+ # return Expr.Error("index out of range", origin=idx_expr)
521
+ # elems.insert(idx, val)
522
+ # return Expr.ListExpr(elems)
523
+
524
+ # # pop: (list.pop 1 ints) -> 2
525
+ # elif first.value == "list.pop":
526
+ # if len(expr.elements) < 3:
527
+ # return Expr.Error("list.pop expects index list", origin=expr)
528
+
529
+ # idx_expr, list_expr = expr.elements[1], expr.elements[2]
530
+ # idx = self.machine_expr_eval(env_id, idx_expr)
531
+ # if isinstance(idx, Expr.Error): return idx
532
+ # if not isinstance(idx, Expr.IntExpr):
533
+ # return Expr.Error("index must be int", origin=idx_expr)
534
+ # idx = idx.value
535
+
536
+ # lst = self.machine_expr_eval(env_id, list_expr)
537
+ # if isinstance(lst, Expr.Error): return lst
538
+ # if not isinstance(lst, Expr.ListExpr):
539
+ # return Expr.Error("second arg to list.pop must be a list", origin=list_expr)
540
+
541
+ # elems = list(lst.elements)
542
+ # if idx < 0 or idx >= len(elems):
543
+ # return Expr.Error("index out of range", origin=idx_expr)
544
+ # del elems[idx]
545
+ # return Expr.ListExpr(elems)
546
+
547
+ # # get: (list.get 1 ints) -> 2
548
+ # elif first.value == "list.get":
549
+ # if len(expr.elements) < 3:
550
+ # return Expr.Error("list.get expects index list", origin=expr)
551
+
552
+ # idx_expr, list_expr = expr.elements[1], expr.elements[2]
553
+ # idx = self.machine_expr_eval(env_id, idx_expr)
554
+ # if isinstance(idx, Expr.Error): return idx
555
+ # if not isinstance(idx, Expr.IntExpr):
556
+ # return Expr.Error("index must be int", origin=idx_expr)
557
+ # idx = idx.value
558
+
559
+ # lst = self.machine_expr_eval(env_id, list_expr)
560
+ # if isinstance(lst, Expr.Error): return lst
561
+ # if not isinstance(lst, Expr.ListExpr):
562
+ # return Expr.Error("second arg to list.get must be a list", origin=list_expr)
563
+
564
+ # if idx < 0 or idx >= len(lst.elements):
565
+ # return Expr.Error("index out of range", origin=idx_expr)
566
+ # return lst.elements[idx]
567
+
568
+ # # set: (list.set 1 3 ints) -> (1 3)
569
+ # elif first.value == "list.set":
570
+ # if len(expr.elements) < 4:
571
+ # return Expr.Error("list.set expects index value list", origin=expr)
572
+ # idx_expr, val_expr, list_expr = expr.elements[1], expr.elements[2], expr.elements[3]
573
+ # idx = self.machine_expr_eval(env_id, idx_expr)
574
+ # if isinstance(idx, Expr.Error): return idx
575
+ # if not isinstance(idx, Expr.IntExpr):
576
+ # return Expr.Error("index must be int", origin=idx_expr)
577
+ # idx = idx.value
578
+
579
+ # val = self.machine_expr_eval(env_id, val_expr)
580
+ # if isinstance(val, Expr.Error): return val
581
+
582
+ # lst = self.machine_expr_eval(env_id, list_expr)
583
+ # if isinstance(lst, Expr.Error): return lst
584
+ # if not isinstance(lst, Expr.ListExpr):
585
+ # return Expr.Error("third arg to list.set must be a list", origin=list_expr)
586
+
587
+ # elems = list(lst.elements)
588
+ # if idx < 0 or idx >= len(elems):
589
+ # return Expr.Error("index out of range", origin=idx_expr)
590
+ # elems[idx] = val
591
+ # return Expr.ListExpr(elems)
592
+
593
+ # ### each: (list.each fn list) -> ()
594
+ # elif first.value == "list.each":
595
+ # if len(expr.elements) < 3:
596
+ # return Expr.Error("list.each expects fn list", origin=expr)
597
+ # fn_expr, list_expr = expr.elements[1], expr.elements[2]
598
+ # lst = self.machine_expr_eval(env_id, list_expr)
599
+ # if isinstance(lst, Expr.Error):
600
+ # return lst
601
+ # if not isinstance(lst, Expr.ListExpr):
602
+ # return Expr.Error("second arg to list.each must be a list", origin=list_expr)
603
+
604
+ # for el in lst.elements:
605
+ # res = self.machine_expr_eval(env_id, Expr.ListExpr([fn_expr, el]))
606
+ # if isinstance(res, Expr.Error):
607
+ # return res
608
+ # return Expr.ListExpr([])
609
+
610
+ # ### fold: (list.fold fn init list) / (list.fold + 0 ints) -> 3
611
+ # elif first.value == "list.fold":
612
+ # fn_expr, init_expr, list_expr = expr.elements[1], expr.elements[2], expr.elements[3]
613
+ # acc = self.machine_expr_eval(env_id, init_expr)
614
+ # if isinstance(acc, Expr.Error):
615
+ # return acc
616
+
617
+ # lst = self.machine_expr_eval(env_id, list_expr)
618
+ # if isinstance(lst, Expr.Error):
619
+ # return lst
620
+ # if not isinstance(lst, Expr.ListExpr):
621
+ # return Expr.Error("third arg to list.fold must be a list", origin=list_expr)
622
+
623
+ # for el in lst.elements:
624
+ # call = Expr.ListExpr([fn_expr, acc, el])
625
+ # res = self.machine_expr_eval(env_id, call)
626
+ # if isinstance(res, Expr.Error):
627
+ # return res
628
+ # acc = res
629
+
630
+ # return acc
631
+
632
+ # ### sort: (list.sort fn list) / (list.sort (fn (a b) (a < b)) ints) -> (2 1)
633
+ # elif first.value == "list.sort":
634
+ # if len(expr.elements) < 3:
635
+ # return Expr.Error("list.sort fn list", origin=expr)
636
+ # fn_e, lst_e = expr.elements[1], expr.elements[2]
637
+
638
+ # lst = self.machine_expr_eval(env_id, lst_e)
639
+ # if isinstance(lst, Expr.Error): return lst
640
+ # if not isinstance(lst, Expr.ListExpr):
641
+ # return Expr.Error("second arg must be list", origin=lst_e)
642
+
643
+ # elems = list(lst.elements)
644
+ # for i in range(1, len(elems)):
645
+ # j = i
646
+ # while j > 0:
647
+ # cmp_res = self.machine_expr_eval(
648
+ # env_id,
649
+ # Expr.ListExpr([fn_e, elems[j-1], elems[j]])
650
+ # )
651
+ # if isinstance(cmp_res, Expr.Error): return cmp_res
652
+ # if not isinstance(cmp_res, Expr.BoolExpr):
653
+ # return Expr.Error("comparator must return bool", origin=fn_e)
654
+
655
+ # if cmp_res.value:
656
+ # elems[j-1], elems[j] = elems[j], elems[j-1]
657
+ # j -= 1
658
+ # else:
659
+ # break
660
+ # return Expr.ListExpr(elems)
661
+
662
+ # ### len: (list.len list) -> Int / (list.len ints) -> Integer(2)
663
+ # elif first.value == "list.len":
664
+ # if len(expr.elements) < 2:
665
+ # return Expr.Error("list.len list", origin=expr)
666
+ # lst_e = expr.elements[1]
667
+ # lst = self.machine_expr_eval(env_id, lst_e)
668
+ # if isinstance(lst, Expr.Error): return lst
669
+ # if not isinstance(lst, Expr.ListExpr):
670
+ # return Expr.Error("arg must be list", origin=lst_e)
671
+ # return Expr.Integer(len(lst.elements))
672
+
673
+ # ## Integer
674
+ # ### add
675
+ # elif first.value == "+":
676
+ # args = expr.elements[1:]
677
+ # if not args:
678
+ # return Expr.Error("'+' expects at least 1 argument", origin=expr)
679
+ # vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
680
+ # for v in vals:
681
+ # if isinstance(v, Expr.Error): return v
682
+ # if not isinstance(v, Expr.Integer):
683
+ # return Expr.Error("'+' only accepts integer operands", origin=v)
684
+ # return Expr.Integer(abs(vals[0].value) if len(vals) == 1
685
+ # else sum(v.value for v in vals))
686
+
687
+ # elif first.value == "-":
688
+ # args = expr.elements[1:]
689
+ # if not args:
690
+ # return Expr.Error("'-' expects at least 1 argument", origin=expr)
691
+ # vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
692
+ # for v in vals:
693
+ # if isinstance(v, Expr.Error): return v
694
+ # if not isinstance(v, Expr.Integer):
695
+ # return Expr.Error("'-' only accepts integer operands", origin=v)
696
+ # if len(vals) == 1:
697
+ # return Expr.Integer(-vals[0].value)
698
+ # result = vals[0].value
699
+ # for v in vals[1:]:
700
+ # result -= v.value
701
+ # return Expr.Integer(result)
702
+
703
+ # elif first.value == "/":
704
+ # args = expr.elements[1:]
705
+ # if len(args) < 2:
706
+ # return Expr.Error("'/' expects at least 2 arguments", origin=expr)
707
+ # vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
708
+ # for v in vals:
709
+ # if isinstance(v, Expr.Error): return v
710
+ # if not isinstance(v, Expr.Integer):
711
+ # return Expr.Error("'/' only accepts integer operands", origin=v)
712
+ # result = vals[0].value
713
+ # for v in vals[1:]:
714
+ # if v.value == 0:
715
+ # return Expr.Error("division by zero", origin=v)
716
+ # if result % v.value:
717
+ # return Expr.Error("non-exact division", origin=expr)
718
+ # result //= v.value
719
+ # return Expr.Integer(result)
720
+
721
+ # elif first.value == "%":
722
+ # if len(expr.elements) != 3:
723
+ # return Expr.Error("'%' expects exactly 2 arguments", origin=expr)
724
+ # a = self.machine_expr_eval(env_id=env_id, expr=expr.elements[1])
725
+ # b = self.machine_expr_eval(env_id=env_id, expr=expr.elements[2])
726
+ # for v in (a, b):
727
+ # if isinstance(v, Expr.Error): return v
728
+ # if not isinstance(v, Expr.Integer):
729
+ # return Expr.Error("'%' only accepts integer operands", origin=v)
730
+ # if b.value == 0:
731
+ # return Expr.Error("division by zero", origin=expr.elements[2])
732
+ # return Expr.Integer(a.value % b.value)
733
+
734
+ # elif first.value in ("=", "!=", ">", "<", ">=", "<="):
735
+ # args = expr.elements[1:]
736
+ # if len(args) != 2:
737
+ # return Expr.Error(f"'{first.value}' expects exactly 2 arguments", origin=expr)
738
+
739
+ # left = self.machine_expr_eval(env_id=env_id, expr=args[0])
740
+ # right = self.machine_expr_eval(env_id=env_id, expr=args[1])
741
+
742
+ # for v in (left, right):
743
+ # if isinstance(v, Expr.Error):
744
+ # return v
745
+ # if not isinstance(v, Expr.Integer):
746
+ # return Expr.Error(f"'{first.value}' only accepts integer operands", origin=v)
747
+
748
+ # a, b = left.value, right.value
749
+ # match first.value:
750
+ # case "=": res = a == b
751
+ # case "!=": res = a != b
752
+ # case ">": res = a > b
753
+ # case "<": res = a < b
754
+ # case ">=": res = a >= b
755
+ # case "<=": res = a <= b
756
+
757
+ # return Expr.Boolean(res)
758
+
759
+ # if isinstance(first, Expr.Function):
760
+ # arg_exprs = expr.elements[1:]
761
+ # if len(arg_exprs) != len(first.params):
762
+ # return Expr.Error(f"arity mismatch: expected {len(first.params)}, got {len(arg_exprs)}", origin=expr)
763
+
764
+ # call_env = self.machine_create_environment(parent_id=env_id)
765
+ # for name, aexpr in zip(first.params, arg_exprs):
766
+ # val = self.machine_expr_eval(env_id, aexpr)
767
+ # if isinstance(val, Expr.Error): return val
768
+ # self.machine_expr_put(call_env, name, val)
769
+
770
+ # return self.machine_expr_eval(env_id=call_env, expr=first.body)
771
+
772
+ # else:
773
+ # evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
774
+ # return Expr.ListExpr(evaluated_elements)
583
775
 
584
- elif isinstance(expr, Expr.Function):
585
- return expr
776
+ # elif isinstance(expr, Expr.Function):
777
+ # return expr
586
778
 
587
- else:
588
- raise ValueError(f"Unknown expression type: {type(expr)}")
779
+ # else:
780
+ # raise ValueError(f"Unknown expression type: {type(expr)}")
589
781