astreum 0.2.29__py3-none-any.whl → 0.2.31__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/_node.py +382 -0
- astreum/node.py +196 -6
- {astreum-0.2.29.dist-info → astreum-0.2.31.dist-info}/METADATA +1 -1
- {astreum-0.2.29.dist-info → astreum-0.2.31.dist-info}/RECORD +7 -6
- {astreum-0.2.29.dist-info → astreum-0.2.31.dist-info}/WHEEL +0 -0
- {astreum-0.2.29.dist-info → astreum-0.2.31.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.29.dist-info → astreum-0.2.31.dist-info}/top_level.txt +0 -0
astreum/_node.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, List, Optional
|
|
3
|
+
import uuid
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
# ===============================
|
|
7
|
+
# 1. Helpers (no decoding, two's complement)
|
|
8
|
+
# ===============================
|
|
9
|
+
|
|
10
|
+
def tc_to_int(b: bytes) -> int:
|
|
11
|
+
"""bytes -> int using two's complement (width = len(b)*8)."""
|
|
12
|
+
if not b:
|
|
13
|
+
return 0
|
|
14
|
+
return int.from_bytes(b, "big", signed=True)
|
|
15
|
+
|
|
16
|
+
def int_to_tc(n: int, width_bytes: int) -> bytes:
|
|
17
|
+
"""int -> bytes (two's complement, fixed width)."""
|
|
18
|
+
if width_bytes <= 0:
|
|
19
|
+
return b"\x00"
|
|
20
|
+
return n.to_bytes(width_bytes, "big", signed=True)
|
|
21
|
+
|
|
22
|
+
def min_tc_width(n: int) -> int:
|
|
23
|
+
"""minimum bytes to store n in two's complement."""
|
|
24
|
+
if n == 0:
|
|
25
|
+
return 1
|
|
26
|
+
w = 1
|
|
27
|
+
while True:
|
|
28
|
+
try:
|
|
29
|
+
n.to_bytes(w, "big", signed=True)
|
|
30
|
+
return w
|
|
31
|
+
except OverflowError:
|
|
32
|
+
w += 1
|
|
33
|
+
|
|
34
|
+
def nand_bytes(a: bytes, b: bytes) -> bytes:
|
|
35
|
+
"""Bitwise NAND on two byte strings, zero-extending to max width."""
|
|
36
|
+
w = max(len(a), len(b), 1)
|
|
37
|
+
au = int.from_bytes(a.rjust(w, b"\x00"), "big", signed=False)
|
|
38
|
+
bu = int.from_bytes(b.rjust(w, b"\x00"), "big", signed=False)
|
|
39
|
+
mask = (1 << (w * 8)) - 1
|
|
40
|
+
resu = (~(au & bu)) & mask
|
|
41
|
+
return resu.to_bytes(w, "big", signed=False)
|
|
42
|
+
|
|
43
|
+
def bytes_touched(*vals: bytes) -> int:
|
|
44
|
+
"""For metering: how many bytes were manipulated (max of operands)."""
|
|
45
|
+
return max((len(v) for v in vals), default=1)
|
|
46
|
+
|
|
47
|
+
# ===============================
|
|
48
|
+
# 2. Structures
|
|
49
|
+
# ===============================
|
|
50
|
+
|
|
51
|
+
class Expr:
|
|
52
|
+
class ListExpr:
|
|
53
|
+
def __init__(self, elements: List['Expr']):
|
|
54
|
+
self.elements = elements
|
|
55
|
+
|
|
56
|
+
def __repr__(self):
|
|
57
|
+
if not self.elements:
|
|
58
|
+
return "()"
|
|
59
|
+
inner = " ".join(str(e) for e in self.elements)
|
|
60
|
+
return f"({inner})"
|
|
61
|
+
|
|
62
|
+
class Symbol:
|
|
63
|
+
def __init__(self, value: str):
|
|
64
|
+
self.value = value
|
|
65
|
+
|
|
66
|
+
def __repr__(self):
|
|
67
|
+
return self.value
|
|
68
|
+
|
|
69
|
+
class Byte:
|
|
70
|
+
def __init__(self, value: int):
|
|
71
|
+
self.value = value
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
return self.value
|
|
75
|
+
|
|
76
|
+
class Error:
|
|
77
|
+
def __init__(self, message: str, origin: Optional['Expr'] = None):
|
|
78
|
+
self.message = message
|
|
79
|
+
self.origin = origin
|
|
80
|
+
|
|
81
|
+
def __repr__(self):
|
|
82
|
+
if self.origin is None:
|
|
83
|
+
return f'(error "{self.message}")'
|
|
84
|
+
return f'(error "{self.message}" in {self.origin})'
|
|
85
|
+
|
|
86
|
+
class Env:
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
data: Optional[Dict[str, Expr]] = None,
|
|
90
|
+
parent_id: Optional[uuid.UUID] = None,
|
|
91
|
+
):
|
|
92
|
+
self.data: Dict[bytes, Expr] = {} if data is None else data
|
|
93
|
+
self.parent_id = parent_id
|
|
94
|
+
|
|
95
|
+
class Meter:
|
|
96
|
+
def __init__(self, prices: Dict[bytes, int], enabled: bool):
|
|
97
|
+
self.enabled = enabled
|
|
98
|
+
self.prices = prices
|
|
99
|
+
self.total = 0
|
|
100
|
+
|
|
101
|
+
def charge(self, op: bytes, size: int):
|
|
102
|
+
if not self.enabled:
|
|
103
|
+
return
|
|
104
|
+
self.total += self.prices.get(op, 0) * max(1, size)
|
|
105
|
+
|
|
106
|
+
class Node:
|
|
107
|
+
OPS = {
|
|
108
|
+
b"add", b"nand", b"jump",
|
|
109
|
+
b"heap_get", b"heap_set",
|
|
110
|
+
b"storage_get", b"storage_set",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
def __init__(self):
|
|
114
|
+
self.environments: Dict[uuid.UUID, Env] = {}
|
|
115
|
+
self.machine_costs: Dict[bytes, int] = {
|
|
116
|
+
b"add": 1,
|
|
117
|
+
b"nand": 1,
|
|
118
|
+
b"jump": 1,
|
|
119
|
+
b"heap_set": 1,
|
|
120
|
+
b"heap_get": 1,
|
|
121
|
+
b"storage_set": 1,
|
|
122
|
+
b"storage_get": 1,
|
|
123
|
+
}
|
|
124
|
+
self.in_memory_storage: Dict[bytes, bytes] = {}
|
|
125
|
+
self.machine_environments_lock = threading.RLock()
|
|
126
|
+
|
|
127
|
+
# ---- Env helpers ----
|
|
128
|
+
def env_get(self, env_id: uuid.UUID, key: bytes) -> Optional[Expr]:
|
|
129
|
+
cur = self.environments.get(env_id)
|
|
130
|
+
while cur is not None:
|
|
131
|
+
if key in cur.data:
|
|
132
|
+
return cur.data[key]
|
|
133
|
+
cur = self.environments.get(cur.parent_id) if cur.parent_id else None
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def env_set(self, env_id: uuid.UUID, key: bytes, value: Expr) -> bool:
|
|
137
|
+
with self.machine_environments_lock:
|
|
138
|
+
env = self.environments.get(env_id)
|
|
139
|
+
if env is None:
|
|
140
|
+
return False
|
|
141
|
+
env.data[key] = value
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
# ---- Storage (persistent) ----
|
|
145
|
+
def _local_get(self, key: bytes) -> Optional[bytes]:
|
|
146
|
+
return self.in_memory_storage.get(key)
|
|
147
|
+
|
|
148
|
+
def _local_set(self, key: bytes, value: bytes) -> None:
|
|
149
|
+
self.in_memory_storage[key] = value
|
|
150
|
+
|
|
151
|
+
# ---- Eval ----
|
|
152
|
+
def low_eval(self, code: List[bytes], metered: bool = False) -> bytes:
|
|
153
|
+
|
|
154
|
+
heap: Dict[int, bytes] = []
|
|
155
|
+
|
|
156
|
+
meter = Meter(self.machine_costs, enabled=metered)
|
|
157
|
+
|
|
158
|
+
stack: List[bytes] = []
|
|
159
|
+
pc = 0
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
if pc >= len(code):
|
|
163
|
+
if len(stack) != 1:
|
|
164
|
+
raise RuntimeError(f"bad stack state at end: {stack}")
|
|
165
|
+
return stack.pop()
|
|
166
|
+
|
|
167
|
+
tok = code[pc]
|
|
168
|
+
pc += 1
|
|
169
|
+
|
|
170
|
+
# opcode?
|
|
171
|
+
if tok in self.OPS:
|
|
172
|
+
# ---------- ADD ----------
|
|
173
|
+
if tok == b"add":
|
|
174
|
+
b_b = stack.pop()
|
|
175
|
+
a_b = stack.pop()
|
|
176
|
+
a_i = tc_to_int(a_b)
|
|
177
|
+
b_i = tc_to_int(b_b)
|
|
178
|
+
res_i = a_i + b_i
|
|
179
|
+
width = max(len(a_b), len(b_b), min_tc_width(res_i))
|
|
180
|
+
res_b = int_to_tc(res_i, width)
|
|
181
|
+
meter.charge(b"add", bytes_touched(a_b, b_b, res_b))
|
|
182
|
+
stack.append(res_b)
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
# ---------- NAND ----------
|
|
186
|
+
if tok == b"nand":
|
|
187
|
+
b_b = stack.pop()
|
|
188
|
+
a_b = stack.pop()
|
|
189
|
+
res_b = nand_bytes(a_b, b_b)
|
|
190
|
+
meter.charge(b"nand", bytes_touched(a_b, b_b, res_b))
|
|
191
|
+
stack.append(res_b)
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
# ---------- JUMP ----------
|
|
195
|
+
if tok == b"jump":
|
|
196
|
+
tgt_b = stack.pop()
|
|
197
|
+
tgt_i = tc_to_int(tgt_b)
|
|
198
|
+
meter.charge(b"jump", 0)
|
|
199
|
+
pc = tgt_i
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# ---------- HEAP GET ----------
|
|
203
|
+
if tok == b"heap_get":
|
|
204
|
+
key = stack.pop()
|
|
205
|
+
val = heap.get(key) or b""
|
|
206
|
+
meter.charge(b"heap_get", len(val))
|
|
207
|
+
stack.append(val)
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
# ---------- HEAP SET ----------
|
|
211
|
+
if tok == b"heap_set":
|
|
212
|
+
val = stack.pop()
|
|
213
|
+
key = stack.pop()
|
|
214
|
+
ok = heap[key] = val
|
|
215
|
+
if not ok:
|
|
216
|
+
raise RuntimeError("heap_set failed (env missing)")
|
|
217
|
+
meter.charge(b"heap_set", len(val))
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
# ---------- STORAGE GET ----------
|
|
221
|
+
if tok == b"storage_get":
|
|
222
|
+
key = stack.pop()
|
|
223
|
+
val = self._local_get(key) or b""
|
|
224
|
+
meter.charge(b"storage_get", len(val))
|
|
225
|
+
stack.append(val)
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
# ---------- STORAGE SET ----------
|
|
229
|
+
if tok == b"storage_set":
|
|
230
|
+
val = stack.pop()
|
|
231
|
+
key = stack.pop()
|
|
232
|
+
self._local_set(key, val)
|
|
233
|
+
meter.charge(b"storage_set", len(val))
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
# unreachable
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
# not an opcode → literal blob
|
|
240
|
+
stack.append(tok)
|
|
241
|
+
|
|
242
|
+
def high_eval(self, env_id: uuid.UUID, expr: Expr) -> Expr:
|
|
243
|
+
# ---------- atoms ----------
|
|
244
|
+
if isinstance(expr, Expr.Error):
|
|
245
|
+
return expr
|
|
246
|
+
|
|
247
|
+
if isinstance(expr, Expr.Symbol):
|
|
248
|
+
bound = self.env_get(env_id, expr.value.encode())
|
|
249
|
+
if bound is None:
|
|
250
|
+
return Expr.Error(f"unbound symbol '{expr.value}'", origin=expr)
|
|
251
|
+
return bound
|
|
252
|
+
|
|
253
|
+
if not isinstance(expr, Expr.ListExpr):
|
|
254
|
+
return expr # Expr.Byte or other literals passthrough
|
|
255
|
+
|
|
256
|
+
# ---------- empty / single ----------
|
|
257
|
+
if len(expr.elements) == 0:
|
|
258
|
+
return expr
|
|
259
|
+
if len(expr.elements) == 1:
|
|
260
|
+
return self.high_eval(env_id, expr.elements[0])
|
|
261
|
+
|
|
262
|
+
tail = expr.elements[-1]
|
|
263
|
+
|
|
264
|
+
# ---------- (value name def) ----------
|
|
265
|
+
if isinstance(tail, Expr.Symbol) and tail.value == "def":
|
|
266
|
+
if len(expr.elements) < 3:
|
|
267
|
+
return Expr.Error("def expects (value name def)", origin=expr)
|
|
268
|
+
name_e = expr.elements[-2]
|
|
269
|
+
if not isinstance(name_e, Expr.Symbol):
|
|
270
|
+
return Expr.Error("def name must be symbol", origin=name_e)
|
|
271
|
+
value_e = expr.elements[-3]
|
|
272
|
+
value_res = self.high_eval(env_id=env_id, expr=value_e)
|
|
273
|
+
if isinstance(value_res, Expr.Error):
|
|
274
|
+
return value_res
|
|
275
|
+
self.env_set(env_id, name_e.value.encode(), value_res)
|
|
276
|
+
return value_res
|
|
277
|
+
|
|
278
|
+
# ---------- (... (body params sk)) LOW-LEVEL CALL ----------
|
|
279
|
+
if isinstance(tail, Expr.ListExpr):
|
|
280
|
+
fn_form = tail
|
|
281
|
+
if (len(fn_form.elements) >= 3
|
|
282
|
+
and isinstance(fn_form.elements[-1], Expr.Symbol)
|
|
283
|
+
and fn_form.elements[-1].value == "sk"):
|
|
284
|
+
|
|
285
|
+
body_expr = fn_form.elements[-3]
|
|
286
|
+
params_expr = fn_form.elements[-2]
|
|
287
|
+
|
|
288
|
+
if not isinstance(body_expr, Expr.ListExpr):
|
|
289
|
+
return Expr.Error("sk body must be list", origin=body_expr)
|
|
290
|
+
if not isinstance(params_expr, Expr.ListExpr):
|
|
291
|
+
return Expr.Error("sk params must be list", origin=params_expr)
|
|
292
|
+
|
|
293
|
+
# params → bytes keys
|
|
294
|
+
params: List[bytes] = []
|
|
295
|
+
for p in params_expr.elements:
|
|
296
|
+
if not isinstance(p, Expr.Symbol):
|
|
297
|
+
return Expr.Error("sk param must be symbol", origin=p)
|
|
298
|
+
params.append(p.value.encode())
|
|
299
|
+
|
|
300
|
+
# args: preceding items; MUST resolve to Expr.Byte
|
|
301
|
+
args_exprs = expr.elements[:-1]
|
|
302
|
+
if len(args_exprs) != len(params):
|
|
303
|
+
return Expr.Error("arity mismatch", origin=expr)
|
|
304
|
+
|
|
305
|
+
arg_bytes: List[bytes] = []
|
|
306
|
+
for a in args_exprs:
|
|
307
|
+
v = self.high_eval(env_id, a)
|
|
308
|
+
if isinstance(v, Expr.Error):
|
|
309
|
+
return v
|
|
310
|
+
if not isinstance(v, Expr.Byte):
|
|
311
|
+
return Expr.Error("argument must resolve to Byte", origin=a)
|
|
312
|
+
arg_bytes.append(bytes([v.value & 0xFF]))
|
|
313
|
+
|
|
314
|
+
subst: Dict[bytes, bytes] = dict(zip(params, arg_bytes))
|
|
315
|
+
|
|
316
|
+
# build low-level code with param substitution
|
|
317
|
+
code: List[bytes] = []
|
|
318
|
+
for tok in body_expr.elements:
|
|
319
|
+
if isinstance(tok, Expr.Symbol):
|
|
320
|
+
sb = tok.value.encode()
|
|
321
|
+
code.append(subst.get(sb, sb))
|
|
322
|
+
elif isinstance(tok, Expr.Byte):
|
|
323
|
+
code.append(bytes([tok.value & 0xFF]))
|
|
324
|
+
elif isinstance(tok, Expr.ListExpr):
|
|
325
|
+
rv = self.high_eval(env_id, tok)
|
|
326
|
+
if isinstance(rv, Expr.Error):
|
|
327
|
+
return rv
|
|
328
|
+
if not isinstance(rv, Expr.Byte):
|
|
329
|
+
return Expr.Error("nested list must resolve to Byte", origin=tok)
|
|
330
|
+
code.append(bytes([rv.value & 0xFF]))
|
|
331
|
+
else:
|
|
332
|
+
return Expr.Error("invalid token in sk body", origin=tok)
|
|
333
|
+
|
|
334
|
+
res_bytes = self.low_eval(code, metered=False)
|
|
335
|
+
return Expr.ListExpr([Expr.Byte(b) for b in res_bytes])
|
|
336
|
+
|
|
337
|
+
# ---------- (... (body params fn)) HIGH-LEVEL CALL ----------
|
|
338
|
+
if isinstance(tail, Expr.ListExpr):
|
|
339
|
+
fn_form = tail
|
|
340
|
+
if (len(fn_form.elements) >= 3
|
|
341
|
+
and isinstance(fn_form.elements[-1], Expr.Symbol)
|
|
342
|
+
and fn_form.elements[-1].value == "fn"):
|
|
343
|
+
|
|
344
|
+
body_expr = fn_form.elements[-3]
|
|
345
|
+
params_expr = fn_form.elements[-2]
|
|
346
|
+
|
|
347
|
+
if not isinstance(body_expr, Expr.ListExpr):
|
|
348
|
+
return Expr.Error("fn body must be list", origin=body_expr)
|
|
349
|
+
if not isinstance(params_expr, Expr.ListExpr):
|
|
350
|
+
return Expr.Error("fn params must be list", origin=params_expr)
|
|
351
|
+
|
|
352
|
+
params: List[bytes] = []
|
|
353
|
+
for p in params_expr.elements:
|
|
354
|
+
if not isinstance(p, Expr.Symbol):
|
|
355
|
+
return Expr.Error("fn param must be symbol", origin=p)
|
|
356
|
+
params.append(p.value.encode())
|
|
357
|
+
|
|
358
|
+
args_exprs = expr.elements[:-1]
|
|
359
|
+
if len(args_exprs) != len(params):
|
|
360
|
+
return Expr.Error("arity mismatch", origin=expr)
|
|
361
|
+
|
|
362
|
+
arg_bytes: List[bytes] = []
|
|
363
|
+
for a in args_exprs:
|
|
364
|
+
v = self.high_eval(env_id, a)
|
|
365
|
+
if isinstance(v, Expr.Error):
|
|
366
|
+
return v
|
|
367
|
+
if not isinstance(v, Expr.Byte):
|
|
368
|
+
return Expr.Error("argument must resolve to Byte", origin=a)
|
|
369
|
+
arg_bytes.append(bytes([v.value & 0xFF]))
|
|
370
|
+
|
|
371
|
+
# child env, bind params -> Expr.Byte
|
|
372
|
+
child_env = uuid.uuid4()
|
|
373
|
+
self.environments[child_env] = Env(parent_id=env_id)
|
|
374
|
+
for name_b, val_b in zip(params, arg_bytes):
|
|
375
|
+
self.env_set(child_env, name_b, Expr.Byte(val_b[0]))
|
|
376
|
+
|
|
377
|
+
# evaluate HL body
|
|
378
|
+
return self.high_eval(child_env, body_expr)
|
|
379
|
+
|
|
380
|
+
# ---------- default: resolve each element and return list ----------
|
|
381
|
+
resolved: List[Expr] = [self.high_eval(env_id, e) for e in expr.elements]
|
|
382
|
+
return Expr.ListExpr(resolved)
|
astreum/node.py
CHANGED
|
@@ -463,7 +463,7 @@ class Node:
|
|
|
463
463
|
elif first.value == "def":
|
|
464
464
|
args = expr.elements[1:]
|
|
465
465
|
if len(args) != 2:
|
|
466
|
-
return Expr.Error(
|
|
466
|
+
return Expr.Error("def expects key value", origin=expr)
|
|
467
467
|
if not isinstance(args[0], Expr.Symbol):
|
|
468
468
|
return Expr.Error(message="first argument to 'def' must be a symbol", origin=args[0])
|
|
469
469
|
result = self.machine_expr_eval(env_id=env_id, expr=args[1])
|
|
@@ -473,13 +473,203 @@ class Node:
|
|
|
473
473
|
self.machine_expr_put(env_id=env_id, name=args[0].value, expr=result)
|
|
474
474
|
return result
|
|
475
475
|
|
|
476
|
-
# # List
|
|
477
|
-
elif first.value == "list.each":
|
|
478
|
-
internal_function = expr.elements[1]
|
|
479
476
|
|
|
480
|
-
|
|
477
|
+
## DEF: (def x 1) -> ()
|
|
478
|
+
|
|
479
|
+
## GET: (x) -> 1
|
|
480
|
+
|
|
481
|
+
## ADD: (+ 1 2) -> (+ 3) -> 3
|
|
481
482
|
|
|
482
|
-
|
|
483
|
+
## NAND: (~& 1 1) -> (~& 0) -> 0
|
|
484
|
+
|
|
485
|
+
##
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
## List: ints -> (1 2)
|
|
489
|
+
# push: (list.push 3 ints) -> (1 2 3) / (list.push 0 0 ints) -> (0 1 2)
|
|
490
|
+
elif first.value == "list.push":
|
|
491
|
+
args = expr.elements[1:]
|
|
492
|
+
if len(args) == 2:
|
|
493
|
+
val_expr, list_expr = args
|
|
494
|
+
idx = None
|
|
495
|
+
elif len(args) == 3:
|
|
496
|
+
idx_expr, val_expr, list_expr = args
|
|
497
|
+
idx = self.machine_expr_eval(env_id, idx_expr)
|
|
498
|
+
if isinstance(idx, Expr.Error): return idx
|
|
499
|
+
if not isinstance(idx, Expr.IntExpr):
|
|
500
|
+
return Expr.Error("index must be int", origin=idx_expr)
|
|
501
|
+
idx = idx.value
|
|
502
|
+
else:
|
|
503
|
+
return Expr.Error("list.push expects (value list) or (index value list)", origin=expr)
|
|
504
|
+
|
|
505
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
506
|
+
if isinstance(lst, Expr.Error): return lst
|
|
507
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
508
|
+
return Expr.Error("last arg to list.push must be a list", origin=list_expr)
|
|
509
|
+
|
|
510
|
+
val = self.machine_expr_eval(env_id, val_expr)
|
|
511
|
+
if isinstance(val, Expr.Error): return val
|
|
512
|
+
|
|
513
|
+
elems = list(lst.elements)
|
|
514
|
+
if idx is None:
|
|
515
|
+
elems.append(val)
|
|
516
|
+
else:
|
|
517
|
+
if idx < 0 or idx > len(elems):
|
|
518
|
+
return Expr.Error("index out of range", origin=idx_expr)
|
|
519
|
+
elems.insert(idx, val)
|
|
520
|
+
return Expr.ListExpr(elems)
|
|
521
|
+
|
|
522
|
+
# pop: (list.pop 1 ints) -> 2
|
|
523
|
+
elif first.value == "list.pop":
|
|
524
|
+
if len(expr.elements) < 3:
|
|
525
|
+
return Expr.Error("list.pop expects index list", origin=expr)
|
|
526
|
+
|
|
527
|
+
idx_expr, list_expr = expr.elements[1], expr.elements[2]
|
|
528
|
+
idx = self.machine_expr_eval(env_id, idx_expr)
|
|
529
|
+
if isinstance(idx, Expr.Error): return idx
|
|
530
|
+
if not isinstance(idx, Expr.IntExpr):
|
|
531
|
+
return Expr.Error("index must be int", origin=idx_expr)
|
|
532
|
+
idx = idx.value
|
|
533
|
+
|
|
534
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
535
|
+
if isinstance(lst, Expr.Error): return lst
|
|
536
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
537
|
+
return Expr.Error("second arg to list.pop must be a list", origin=list_expr)
|
|
538
|
+
|
|
539
|
+
elems = list(lst.elements)
|
|
540
|
+
if idx < 0 or idx >= len(elems):
|
|
541
|
+
return Expr.Error("index out of range", origin=idx_expr)
|
|
542
|
+
del elems[idx]
|
|
543
|
+
return Expr.ListExpr(elems)
|
|
544
|
+
|
|
545
|
+
# get: (list.get 1 ints) -> 2
|
|
546
|
+
elif first.value == "list.get":
|
|
547
|
+
if len(expr.elements) < 3:
|
|
548
|
+
return Expr.Error("list.get expects index list", origin=expr)
|
|
549
|
+
|
|
550
|
+
idx_expr, list_expr = expr.elements[1], expr.elements[2]
|
|
551
|
+
idx = self.machine_expr_eval(env_id, idx_expr)
|
|
552
|
+
if isinstance(idx, Expr.Error): return idx
|
|
553
|
+
if not isinstance(idx, Expr.IntExpr):
|
|
554
|
+
return Expr.Error("index must be int", origin=idx_expr)
|
|
555
|
+
idx = idx.value
|
|
556
|
+
|
|
557
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
558
|
+
if isinstance(lst, Expr.Error): return lst
|
|
559
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
560
|
+
return Expr.Error("second arg to list.get must be a list", origin=list_expr)
|
|
561
|
+
|
|
562
|
+
if idx < 0 or idx >= len(lst.elements):
|
|
563
|
+
return Expr.Error("index out of range", origin=idx_expr)
|
|
564
|
+
return lst.elements[idx]
|
|
565
|
+
|
|
566
|
+
# set: (list.set 1 3 ints) -> (1 3)
|
|
567
|
+
elif first.value == "list.set":
|
|
568
|
+
if len(expr.elements) < 4:
|
|
569
|
+
return Expr.Error("list.set expects index value list", origin=expr)
|
|
570
|
+
idx_expr, val_expr, list_expr = expr.elements[1], expr.elements[2], expr.elements[3]
|
|
571
|
+
idx = self.machine_expr_eval(env_id, idx_expr)
|
|
572
|
+
if isinstance(idx, Expr.Error): return idx
|
|
573
|
+
if not isinstance(idx, Expr.IntExpr):
|
|
574
|
+
return Expr.Error("index must be int", origin=idx_expr)
|
|
575
|
+
idx = idx.value
|
|
576
|
+
|
|
577
|
+
val = self.machine_expr_eval(env_id, val_expr)
|
|
578
|
+
if isinstance(val, Expr.Error): return val
|
|
579
|
+
|
|
580
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
581
|
+
if isinstance(lst, Expr.Error): return lst
|
|
582
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
583
|
+
return Expr.Error("third arg to list.set must be a list", origin=list_expr)
|
|
584
|
+
|
|
585
|
+
elems = list(lst.elements)
|
|
586
|
+
if idx < 0 or idx >= len(elems):
|
|
587
|
+
return Expr.Error("index out of range", origin=idx_expr)
|
|
588
|
+
elems[idx] = val
|
|
589
|
+
return Expr.ListExpr(elems)
|
|
590
|
+
|
|
591
|
+
### each: (list.each fn list) -> ()
|
|
592
|
+
elif first.value == "list.each":
|
|
593
|
+
if len(expr.elements) < 3:
|
|
594
|
+
return Expr.Error("list.each expects fn list", origin=expr)
|
|
595
|
+
fn_expr, list_expr = expr.elements[1], expr.elements[2]
|
|
596
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
597
|
+
if isinstance(lst, Expr.Error):
|
|
598
|
+
return lst
|
|
599
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
600
|
+
return Expr.Error("second arg to list.each must be a list", origin=list_expr)
|
|
601
|
+
|
|
602
|
+
for el in lst.elements:
|
|
603
|
+
res = self.machine_expr_eval(env_id, Expr.ListExpr([fn_expr, el]))
|
|
604
|
+
if isinstance(res, Expr.Error):
|
|
605
|
+
return res
|
|
606
|
+
return Expr.ListExpr([])
|
|
607
|
+
|
|
608
|
+
### fold: (list.fold fn init list) / (list.fold + 0 ints) -> 3
|
|
609
|
+
elif first.value == "list.fold":
|
|
610
|
+
fn_expr, init_expr, list_expr = expr.elements[1], expr.elements[2], expr.elements[3]
|
|
611
|
+
acc = self.machine_expr_eval(env_id, init_expr)
|
|
612
|
+
if isinstance(acc, Expr.Error):
|
|
613
|
+
return acc
|
|
614
|
+
|
|
615
|
+
lst = self.machine_expr_eval(env_id, list_expr)
|
|
616
|
+
if isinstance(lst, Expr.Error):
|
|
617
|
+
return lst
|
|
618
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
619
|
+
return Expr.Error("third arg to list.fold must be a list", origin=list_expr)
|
|
620
|
+
|
|
621
|
+
for el in lst.elements:
|
|
622
|
+
call = Expr.ListExpr([fn_expr, acc, el])
|
|
623
|
+
res = self.machine_expr_eval(env_id, call)
|
|
624
|
+
if isinstance(res, Expr.Error):
|
|
625
|
+
return res
|
|
626
|
+
acc = res
|
|
627
|
+
|
|
628
|
+
return acc
|
|
629
|
+
|
|
630
|
+
### sort: (list.sort fn list) / (list.sort (fn (a b) (a < b)) ints) -> (2 1)
|
|
631
|
+
elif first.value == "list.sort":
|
|
632
|
+
if len(expr.elements) < 3:
|
|
633
|
+
return Expr.Error("list.sort fn list", origin=expr)
|
|
634
|
+
fn_e, lst_e = expr.elements[1], expr.elements[2]
|
|
635
|
+
|
|
636
|
+
lst = self.machine_expr_eval(env_id, lst_e)
|
|
637
|
+
if isinstance(lst, Expr.Error): return lst
|
|
638
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
639
|
+
return Expr.Error("second arg must be list", origin=lst_e)
|
|
640
|
+
|
|
641
|
+
elems = list(lst.elements)
|
|
642
|
+
for i in range(1, len(elems)):
|
|
643
|
+
j = i
|
|
644
|
+
while j > 0:
|
|
645
|
+
cmp_res = self.machine_expr_eval(
|
|
646
|
+
env_id,
|
|
647
|
+
Expr.ListExpr([fn_e, elems[j-1], elems[j]])
|
|
648
|
+
)
|
|
649
|
+
if isinstance(cmp_res, Expr.Error): return cmp_res
|
|
650
|
+
if not isinstance(cmp_res, Expr.BoolExpr):
|
|
651
|
+
return Expr.Error("comparator must return bool", origin=fn_e)
|
|
652
|
+
|
|
653
|
+
if cmp_res.value:
|
|
654
|
+
elems[j-1], elems[j] = elems[j], elems[j-1]
|
|
655
|
+
j -= 1
|
|
656
|
+
else:
|
|
657
|
+
break
|
|
658
|
+
return Expr.ListExpr(elems)
|
|
659
|
+
|
|
660
|
+
### len: (list.len list) -> Int / (list.len ints) -> Integer(2)
|
|
661
|
+
elif first.value == "list.len":
|
|
662
|
+
if len(expr.elements) < 2:
|
|
663
|
+
return Expr.Error("list.len list", origin=expr)
|
|
664
|
+
lst_e = expr.elements[1]
|
|
665
|
+
lst = self.machine_expr_eval(env_id, lst_e)
|
|
666
|
+
if isinstance(lst, Expr.Error): return lst
|
|
667
|
+
if not isinstance(lst, Expr.ListExpr):
|
|
668
|
+
return Expr.Error("arg must be list", origin=lst_e)
|
|
669
|
+
return Expr.Integer(len(lst.elements))
|
|
670
|
+
|
|
671
|
+
## Integer
|
|
672
|
+
### add
|
|
483
673
|
elif first.value == "+":
|
|
484
674
|
args = expr.elements[1:]
|
|
485
675
|
if not args:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.31
|
|
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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
astreum/__init__.py,sha256=y2Ok3EY_FstcmlVASr80lGR_0w-dH-SXDCCQFmL6uwA,28
|
|
2
|
+
astreum/_node.py,sha256=ovOrboz7HYPLM2PdDat8O_Z62u-7FamMqi5c4lJfNmA,14499
|
|
2
3
|
astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
|
|
3
|
-
astreum/node.py,sha256=
|
|
4
|
+
astreum/node.py,sha256=SuVm1b0QWl1FpDUaLRH1fiFYnXCrPs6qYeUQlPDae8w,38358
|
|
4
5
|
astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
6
|
astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
|
|
6
7
|
astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
|
|
@@ -26,8 +27,8 @@ astreum/relay/setup.py,sha256=ynvGaJdlDtw_f5LLiow2Wo7IRzUjvgk8eSr1Sv4_zTg,2090
|
|
|
26
27
|
astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
28
|
astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
|
|
28
29
|
astreum/storage/setup.py,sha256=1-9ztEFI_BvRDvAA0lAn4mFya8iq65THTArlj--M3Hg,626
|
|
29
|
-
astreum-0.2.
|
|
30
|
-
astreum-0.2.
|
|
31
|
-
astreum-0.2.
|
|
32
|
-
astreum-0.2.
|
|
33
|
-
astreum-0.2.
|
|
30
|
+
astreum-0.2.31.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
31
|
+
astreum-0.2.31.dist-info/METADATA,sha256=485DWO4gUtf7DGSsbpZg4zOiIc5yzkQE_VC6q_Zdi30,5478
|
|
32
|
+
astreum-0.2.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
33
|
+
astreum-0.2.31.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
34
|
+
astreum-0.2.31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|