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 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(message=f"'def' expects exactly 2 arguments, got {len(args)}", origin=expr)
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
- # Integer arithmetic primitives
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.29
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=OhiApRqQcPc6-CWbk2NeKdOiXQapy0mbl3uLK_xjmYU,28971
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.29.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
30
- astreum-0.2.29.dist-info/METADATA,sha256=JSIc8N98KW4Wmm89uMMFxyJKiEFxIU-zGRjIXf9JL24,5478
31
- astreum-0.2.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- astreum-0.2.29.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
33
- astreum-0.2.29.dist-info/RECORD,,
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,,