astreum 0.2.31__py3-none-any.whl → 0.2.33__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 +256 -146
- {astreum-0.2.31.dist-info → astreum-0.2.33.dist-info}/METADATA +35 -14
- {astreum-0.2.31.dist-info → astreum-0.2.33.dist-info}/RECORD +6 -6
- {astreum-0.2.31.dist-info → astreum-0.2.33.dist-info}/WHEEL +0 -0
- {astreum-0.2.31.dist-info → astreum-0.2.33.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.31.dist-info → astreum-0.2.33.dist-info}/top_level.txt +0 -0
astreum/_node.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import Dict, List, Optional
|
|
2
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
3
3
|
import uuid
|
|
4
4
|
import threading
|
|
5
5
|
|
|
@@ -74,14 +74,14 @@ class Expr:
|
|
|
74
74
|
return self.value
|
|
75
75
|
|
|
76
76
|
class Error:
|
|
77
|
-
def __init__(self,
|
|
78
|
-
self.
|
|
77
|
+
def __init__(self, topic: str, origin: Optional['Expr'] = None):
|
|
78
|
+
self.topic = topic
|
|
79
79
|
self.origin = origin
|
|
80
80
|
|
|
81
81
|
def __repr__(self):
|
|
82
82
|
if self.origin is None:
|
|
83
|
-
return f'(
|
|
84
|
-
return f'(
|
|
83
|
+
return f'({self.topic} err)'
|
|
84
|
+
return f'({self.origin} {self.topic} err)'
|
|
85
85
|
|
|
86
86
|
class Env:
|
|
87
87
|
def __init__(
|
|
@@ -93,34 +93,24 @@ class Env:
|
|
|
93
93
|
self.parent_id = parent_id
|
|
94
94
|
|
|
95
95
|
class Meter:
|
|
96
|
-
def __init__(self,
|
|
96
|
+
def __init__(self, enabled: bool = True, limit: Optional[int] = None):
|
|
97
97
|
self.enabled = enabled
|
|
98
|
-
self.
|
|
99
|
-
self.
|
|
98
|
+
self.limit: Optional[int] = limit
|
|
99
|
+
self.used: int = 0
|
|
100
100
|
|
|
101
|
-
def
|
|
101
|
+
def charge_bytes(self, n: int) -> bool:
|
|
102
102
|
if not self.enabled:
|
|
103
|
-
return
|
|
104
|
-
|
|
103
|
+
return True
|
|
104
|
+
if n < 0:
|
|
105
|
+
n = 0
|
|
106
|
+
if self.limit is not None and (self.used + n) >= self.limit:
|
|
107
|
+
return False
|
|
108
|
+
self.used += n
|
|
109
|
+
return True
|
|
105
110
|
|
|
106
111
|
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
112
|
def __init__(self):
|
|
114
113
|
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
114
|
self.in_memory_storage: Dict[bytes, bytes] = {}
|
|
125
115
|
self.machine_environments_lock = threading.RLock()
|
|
126
116
|
|
|
@@ -149,11 +139,9 @@ class Node:
|
|
|
149
139
|
self.in_memory_storage[key] = value
|
|
150
140
|
|
|
151
141
|
# ---- Eval ----
|
|
152
|
-
def low_eval(self, code: List[bytes],
|
|
142
|
+
def low_eval(self, code: List[bytes], meter: Meter) -> Union[bytes, Expr.Error]:
|
|
153
143
|
|
|
154
|
-
heap: Dict[
|
|
155
|
-
|
|
156
|
-
meter = Meter(self.machine_costs, enabled=metered)
|
|
144
|
+
heap: Dict[bytes, bytes] = {}
|
|
157
145
|
|
|
158
146
|
stack: List[bytes] = []
|
|
159
147
|
pc = 0
|
|
@@ -161,85 +149,110 @@ class Node:
|
|
|
161
149
|
while True:
|
|
162
150
|
if pc >= len(code):
|
|
163
151
|
if len(stack) != 1:
|
|
164
|
-
|
|
152
|
+
return Expr.Error("bad stack")
|
|
165
153
|
return stack.pop()
|
|
166
154
|
|
|
167
155
|
tok = code[pc]
|
|
168
156
|
pc += 1
|
|
169
157
|
|
|
170
|
-
#
|
|
171
|
-
if tok
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
#
|
|
195
|
-
if
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
158
|
+
# ---------- ADD ----------
|
|
159
|
+
if tok == b"add":
|
|
160
|
+
if len(stack) < 2:
|
|
161
|
+
return Expr.Error("underflow")
|
|
162
|
+
b_b = stack.pop()
|
|
163
|
+
a_b = stack.pop()
|
|
164
|
+
a_i = tc_to_int(a_b)
|
|
165
|
+
b_i = tc_to_int(b_b)
|
|
166
|
+
res_i = a_i + b_i
|
|
167
|
+
width = max(len(a_b), len(b_b), min_tc_width(res_i))
|
|
168
|
+
res_b = int_to_tc(res_i, width)
|
|
169
|
+
# charge for both operands' byte widths
|
|
170
|
+
if not meter.charge_bytes(len(a_b) + len(b_b)):
|
|
171
|
+
return Expr.Error("meter limit")
|
|
172
|
+
stack.append(res_b)
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
# ---------- NAND ----------
|
|
176
|
+
if tok == b"nand":
|
|
177
|
+
if len(stack) < 2:
|
|
178
|
+
return Expr.Error("underflow")
|
|
179
|
+
b_b = stack.pop()
|
|
180
|
+
a_b = stack.pop()
|
|
181
|
+
res_b = nand_bytes(a_b, b_b)
|
|
182
|
+
# bitwise cost: 2 * max(len(a), len(b))
|
|
183
|
+
if not meter.charge_bytes(2 * max(len(a_b), len(b_b), 1)):
|
|
184
|
+
return Expr.Error("meter limit")
|
|
185
|
+
stack.append(res_b)
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
# ---------- JUMP ----------
|
|
189
|
+
if tok == b"jump":
|
|
190
|
+
if len(stack) < 1:
|
|
191
|
+
return Expr.Error("underflow")
|
|
192
|
+
tgt_b = stack.pop()
|
|
193
|
+
if not meter.charge_bytes(1):
|
|
194
|
+
return Expr.Error("meter limit")
|
|
195
|
+
tgt_i = tc_to_int(tgt_b)
|
|
196
|
+
if tgt_i < 0 or tgt_i >= len(code):
|
|
197
|
+
return Expr.Error("bad jump")
|
|
198
|
+
pc = tgt_i
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
# ---------- HEAP GET ----------
|
|
202
|
+
if tok == b"heap_get":
|
|
203
|
+
if len(stack) < 1:
|
|
204
|
+
return Expr.Error("underflow")
|
|
205
|
+
key = stack.pop()
|
|
206
|
+
val = heap.get(key) or b""
|
|
207
|
+
# get cost: 1
|
|
208
|
+
if not meter.charge_bytes(1):
|
|
209
|
+
return Expr.Error("meter limit")
|
|
210
|
+
stack.append(val)
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# ---------- HEAP SET ----------
|
|
214
|
+
if tok == b"heap_set":
|
|
215
|
+
if len(stack) < 2:
|
|
216
|
+
return Expr.Error("underflow")
|
|
217
|
+
val = stack.pop()
|
|
218
|
+
key = stack.pop()
|
|
219
|
+
if not meter.charge_bytes(len(val)):
|
|
220
|
+
return Expr.Error("meter limit")
|
|
221
|
+
heap[key] = val
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
# ---------- STORAGE GET ----------
|
|
225
|
+
if tok == b"storage_get":
|
|
226
|
+
if len(stack) < 1:
|
|
227
|
+
return Expr.Error("underflow")
|
|
228
|
+
key = stack.pop()
|
|
229
|
+
val = self._local_get(key) or b""
|
|
230
|
+
if not meter.charge_bytes(1):
|
|
231
|
+
return Expr.Error("meter limit")
|
|
232
|
+
stack.append(val)
|
|
237
233
|
continue
|
|
238
234
|
|
|
235
|
+
# ---------- STORAGE SET ----------
|
|
236
|
+
if tok == b"storage_set":
|
|
237
|
+
if len(stack) < 2:
|
|
238
|
+
return Expr.Error("underflow")
|
|
239
|
+
val = stack.pop()
|
|
240
|
+
key = stack.pop()
|
|
241
|
+
if not meter.charge_bytes(len(val)):
|
|
242
|
+
return Expr.Error("meter limit")
|
|
243
|
+
self._local_set(key, val)
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
# if no opcode matched above, treat token as literal
|
|
247
|
+
|
|
239
248
|
# not an opcode → literal blob
|
|
240
249
|
stack.append(tok)
|
|
241
250
|
|
|
242
|
-
def high_eval(self, env_id: uuid.UUID, expr: Expr) -> Expr:
|
|
251
|
+
def high_eval(self, env_id: uuid.UUID, expr: Expr, meter = None) -> Expr:
|
|
252
|
+
|
|
253
|
+
if meter is None:
|
|
254
|
+
meter = Meter()
|
|
255
|
+
|
|
243
256
|
# ---------- atoms ----------
|
|
244
257
|
if isinstance(expr, Expr.Error):
|
|
245
258
|
return expr
|
|
@@ -257,7 +270,7 @@ class Node:
|
|
|
257
270
|
if len(expr.elements) == 0:
|
|
258
271
|
return expr
|
|
259
272
|
if len(expr.elements) == 1:
|
|
260
|
-
return self.high_eval(env_id, expr.elements[0])
|
|
273
|
+
return self.high_eval(env_id=env_id, expr=expr.elements[0], meter=meter)
|
|
261
274
|
|
|
262
275
|
tail = expr.elements[-1]
|
|
263
276
|
|
|
@@ -269,70 +282,98 @@ class Node:
|
|
|
269
282
|
if not isinstance(name_e, Expr.Symbol):
|
|
270
283
|
return Expr.Error("def name must be symbol", origin=name_e)
|
|
271
284
|
value_e = expr.elements[-3]
|
|
272
|
-
value_res = self.high_eval(env_id=env_id, expr=value_e)
|
|
285
|
+
value_res = self.high_eval(env_id=env_id, expr=value_e, meter=meter)
|
|
273
286
|
if isinstance(value_res, Expr.Error):
|
|
274
287
|
return value_res
|
|
275
288
|
self.env_set(env_id, name_e.value.encode(), value_res)
|
|
276
289
|
return value_res
|
|
277
290
|
|
|
278
|
-
#
|
|
291
|
+
# ---- LOW-LEVEL call: ( arg1 arg2 ... ( (body) sk ) ) ----
|
|
279
292
|
if isinstance(tail, Expr.ListExpr):
|
|
280
|
-
|
|
281
|
-
if
|
|
282
|
-
|
|
283
|
-
and fn_form.elements[-1].value == "sk"):
|
|
284
|
-
|
|
285
|
-
body_expr = fn_form.elements[-3]
|
|
286
|
-
params_expr = fn_form.elements[-2]
|
|
287
|
-
|
|
293
|
+
inner = tail.elements
|
|
294
|
+
if len(inner) >= 2 and isinstance(inner[-1], Expr.Symbol) and inner[-1].value == "sk":
|
|
295
|
+
body_expr = inner[-2]
|
|
288
296
|
if not isinstance(body_expr, Expr.ListExpr):
|
|
289
297
|
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
298
|
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
+
# helper: turn an Expr into a contiguous bytes buffer
|
|
300
|
+
def to_bytes(v: Expr) -> Union[bytes, Expr.Error]:
|
|
301
|
+
if isinstance(v, Expr.Byte):
|
|
302
|
+
return bytes([v.value & 0xFF])
|
|
303
|
+
if isinstance(v, Expr.ListExpr):
|
|
304
|
+
# expect a list of Expr.Byte
|
|
305
|
+
out: bytearray = bytearray()
|
|
306
|
+
for el in v.elements:
|
|
307
|
+
if isinstance(el, Expr.Byte):
|
|
308
|
+
out.append(el.value & 0xFF)
|
|
309
|
+
else:
|
|
310
|
+
return Expr.Error("byte list must contain only Byte", origin=el)
|
|
311
|
+
return bytes(out)
|
|
312
|
+
if isinstance(v, Expr.Error):
|
|
313
|
+
return v
|
|
314
|
+
return Expr.Error("argument must resolve to Byte or (Byte ...)", origin=v)
|
|
299
315
|
|
|
300
|
-
#
|
|
316
|
+
# resolve ALL preceding args into bytes (can be Byte or List[Byte])
|
|
301
317
|
args_exprs = expr.elements[:-1]
|
|
302
|
-
if len(args_exprs) != len(params):
|
|
303
|
-
return Expr.Error("arity mismatch", origin=expr)
|
|
304
|
-
|
|
305
318
|
arg_bytes: List[bytes] = []
|
|
306
319
|
for a in args_exprs:
|
|
307
|
-
v = self.high_eval(env_id, a)
|
|
320
|
+
v = self.high_eval(env_id=env_id, expr=a, meter=meter)
|
|
308
321
|
if isinstance(v, Expr.Error):
|
|
309
322
|
return v
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
323
|
+
vb = to_bytes(v)
|
|
324
|
+
if isinstance(vb, Expr.Error):
|
|
325
|
+
return vb
|
|
326
|
+
arg_bytes.append(vb)
|
|
313
327
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
# build low-level code with param substitution
|
|
328
|
+
# build low-level code with $0-based placeholders ($0 = first arg)
|
|
317
329
|
code: List[bytes] = []
|
|
318
|
-
|
|
330
|
+
|
|
331
|
+
def emit(tok: Expr) -> Union[None, Expr.Error]:
|
|
319
332
|
if isinstance(tok, Expr.Symbol):
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
333
|
+
name = tok.value
|
|
334
|
+
if name.startswith("$"):
|
|
335
|
+
idx_s = name[1:]
|
|
336
|
+
if not idx_s.isdigit():
|
|
337
|
+
return Expr.Error("invalid sk placeholder", origin=tok)
|
|
338
|
+
idx = int(idx_s) # $0 is first
|
|
339
|
+
if idx < 0 or idx >= len(arg_bytes):
|
|
340
|
+
return Expr.Error("arity mismatch in sk placeholder", origin=tok)
|
|
341
|
+
code.append(arg_bytes[idx])
|
|
342
|
+
return None
|
|
343
|
+
code.append(name.encode())
|
|
344
|
+
return None
|
|
345
|
+
|
|
346
|
+
if isinstance(tok, Expr.Byte):
|
|
323
347
|
code.append(bytes([tok.value & 0xFF]))
|
|
324
|
-
|
|
325
|
-
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
if isinstance(tok, Expr.ListExpr):
|
|
351
|
+
rv = self.high_eval(env_id, tok, meter=meter)
|
|
326
352
|
if isinstance(rv, Expr.Error):
|
|
327
353
|
return rv
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return
|
|
354
|
+
rb = to_bytes(rv)
|
|
355
|
+
if isinstance(rb, Expr.Error):
|
|
356
|
+
return rb
|
|
357
|
+
code.append(rb)
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
if isinstance(tok, Expr.Error):
|
|
361
|
+
return tok
|
|
362
|
+
|
|
363
|
+
return Expr.Error("invalid token in sk body", origin=tok)
|
|
364
|
+
|
|
365
|
+
for t in body_expr.elements:
|
|
366
|
+
err = emit(t)
|
|
367
|
+
if isinstance(err, Expr.Error):
|
|
368
|
+
return err
|
|
369
|
+
|
|
370
|
+
# Execute low-level code built from sk-body using the caller's meter
|
|
371
|
+
res = self.low_eval(code, meter=meter)
|
|
372
|
+
if isinstance(res, Expr.Error):
|
|
373
|
+
return res
|
|
374
|
+
return Expr.ListExpr([Expr.Byte(b) for b in res])
|
|
375
|
+
|
|
333
376
|
|
|
334
|
-
res_bytes = self.low_eval(code, metered=False)
|
|
335
|
-
return Expr.ListExpr([Expr.Byte(b) for b in res_bytes])
|
|
336
377
|
|
|
337
378
|
# ---------- (... (body params fn)) HIGH-LEVEL CALL ----------
|
|
338
379
|
if isinstance(tail, Expr.ListExpr):
|
|
@@ -361,7 +402,7 @@ class Node:
|
|
|
361
402
|
|
|
362
403
|
arg_bytes: List[bytes] = []
|
|
363
404
|
for a in args_exprs:
|
|
364
|
-
v = self.high_eval(env_id, a)
|
|
405
|
+
v = self.high_eval(env_id, a, meter=meter)
|
|
365
406
|
if isinstance(v, Expr.Error):
|
|
366
407
|
return v
|
|
367
408
|
if not isinstance(v, Expr.Byte):
|
|
@@ -374,9 +415,78 @@ class Node:
|
|
|
374
415
|
for name_b, val_b in zip(params, arg_bytes):
|
|
375
416
|
self.env_set(child_env, name_b, Expr.Byte(val_b[0]))
|
|
376
417
|
|
|
377
|
-
# evaluate HL body
|
|
378
|
-
return self.high_eval(child_env, body_expr)
|
|
418
|
+
# evaluate HL body, metered from the top
|
|
419
|
+
return self.high_eval(child_env, body_expr, meter=meter)
|
|
379
420
|
|
|
380
421
|
# ---------- default: resolve each element and return list ----------
|
|
381
|
-
resolved: List[Expr] = [self.high_eval(env_id, e) for e in expr.elements]
|
|
422
|
+
resolved: List[Expr] = [self.high_eval(env_id, e, meter=meter) for e in expr.elements]
|
|
382
423
|
return Expr.ListExpr(resolved)
|
|
424
|
+
|
|
425
|
+
# ===============================
|
|
426
|
+
# 3. Lightweight Parser for Expr (postfix, limited forms)
|
|
427
|
+
# ===============================
|
|
428
|
+
|
|
429
|
+
class ParseError(Exception):
|
|
430
|
+
pass
|
|
431
|
+
|
|
432
|
+
def tokenize(source: str) -> List[str]:
|
|
433
|
+
"""Tokenize a minimal Lispeum subset for this module.
|
|
434
|
+
|
|
435
|
+
Supports:
|
|
436
|
+
- Integers (e.g., -1, 0, 255) → Byte
|
|
437
|
+
- Symbols (e.g., add, nand, def, fn, sk, int.sub, $0)
|
|
438
|
+
- Lists using parentheses
|
|
439
|
+
"""
|
|
440
|
+
tokens: List[str] = []
|
|
441
|
+
cur: List[str] = []
|
|
442
|
+
for ch in source:
|
|
443
|
+
if ch.isspace():
|
|
444
|
+
if cur:
|
|
445
|
+
tokens.append("".join(cur))
|
|
446
|
+
cur = []
|
|
447
|
+
continue
|
|
448
|
+
if ch in ("(", ")"):
|
|
449
|
+
if cur:
|
|
450
|
+
tokens.append("".join(cur))
|
|
451
|
+
cur = []
|
|
452
|
+
tokens.append(ch)
|
|
453
|
+
continue
|
|
454
|
+
cur.append(ch)
|
|
455
|
+
if cur:
|
|
456
|
+
tokens.append("".join(cur))
|
|
457
|
+
return tokens
|
|
458
|
+
|
|
459
|
+
def _parse_one(tokens: List[str], pos: int = 0) -> Tuple[Expr, int]:
|
|
460
|
+
if pos >= len(tokens):
|
|
461
|
+
raise ParseError("unexpected end")
|
|
462
|
+
tok = tokens[pos]
|
|
463
|
+
|
|
464
|
+
if tok == '(': # list
|
|
465
|
+
items: List[Expr] = []
|
|
466
|
+
i = pos + 1
|
|
467
|
+
while i < len(tokens):
|
|
468
|
+
if tokens[i] == ')':
|
|
469
|
+
# special-case error form at close: (origin topic err) or (topic err)
|
|
470
|
+
if len(items) >= 3 and isinstance(items[-1], Expr.Symbol) and items[-1].value == 'err' and isinstance(items[-2], Expr.Symbol):
|
|
471
|
+
return Expr.Error(items[-2].value, origin=items[-3]), i + 1
|
|
472
|
+
if len(items) == 2 and isinstance(items[-1], Expr.Symbol) and items[-1].value == 'err' and isinstance(items[-2], Expr.Symbol):
|
|
473
|
+
return Expr.Error(items[-2].value), i + 1
|
|
474
|
+
return Expr.ListExpr(items), i + 1
|
|
475
|
+
expr, i = _parse_one(tokens, i)
|
|
476
|
+
items.append(expr)
|
|
477
|
+
raise ParseError("expected ')'")
|
|
478
|
+
|
|
479
|
+
if tok == ')':
|
|
480
|
+
raise ParseError("unexpected ')'")
|
|
481
|
+
|
|
482
|
+
# try integer → Byte
|
|
483
|
+
try:
|
|
484
|
+
n = int(tok)
|
|
485
|
+
return Expr.Byte(n), pos + 1
|
|
486
|
+
except ValueError:
|
|
487
|
+
return Expr.Symbol(tok), pos + 1
|
|
488
|
+
|
|
489
|
+
def parse(tokens: List[str]) -> Tuple[Expr, List[str]]:
|
|
490
|
+
"""Parse tokens into an Expr and return (expr, remaining_tokens)."""
|
|
491
|
+
expr, next_pos = _parse_one(tokens, 0)
|
|
492
|
+
return expr, tokens[next_pos:]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.33
|
|
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
|
|
@@ -76,24 +76,45 @@ node = Node(config)
|
|
|
76
76
|
The Lispeum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Lispeum source text, and the node tokenizes, parses, and **evaluates** the resulting AST inside an isolated environment.
|
|
77
77
|
|
|
78
78
|
```python
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
# Define a named function int.add (stack body) and call it with bytes 1 and 2
|
|
80
|
+
|
|
81
|
+
import uuid
|
|
82
|
+
from astreum._node import Node, Env, Expr
|
|
83
|
+
|
|
84
|
+
# 1) Spin‑up a stand‑alone VM
|
|
85
|
+
node = Node()
|
|
86
|
+
|
|
87
|
+
# 2) Create an environment (simple manual setup)
|
|
88
|
+
env_id = uuid.uuid4()
|
|
89
|
+
node.environments[env_id] = Env()
|
|
90
|
+
|
|
91
|
+
# 3) Build a function value using a low‑level stack body via `sk`.
|
|
92
|
+
# Body does: $0 $1 add (i.e., a + b)
|
|
93
|
+
low_body = Expr.ListExpr([
|
|
94
|
+
Expr.Symbol("$0"), # a (first arg)
|
|
95
|
+
Expr.Symbol("$1"), # b (second arg)
|
|
96
|
+
Expr.Symbol("add"),
|
|
97
|
+
])
|
|
82
98
|
|
|
83
|
-
|
|
84
|
-
|
|
99
|
+
fn_body = Expr.ListExpr([
|
|
100
|
+
Expr.Symbol("a"),
|
|
101
|
+
Expr.Symbol("b"),
|
|
102
|
+
Expr.ListExpr([low_body, Expr.Symbol("sk")]),
|
|
103
|
+
])
|
|
85
104
|
|
|
86
|
-
|
|
87
|
-
|
|
105
|
+
params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
|
|
106
|
+
int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
|
|
88
107
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
expr, _ = parse(tokenize(source))
|
|
108
|
+
# 4) Store under the name "int.add"
|
|
109
|
+
node.env_set(env_id, b"int.add", int_add_fn)
|
|
92
110
|
|
|
93
|
-
#
|
|
94
|
-
|
|
111
|
+
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
112
|
+
bound = node.env_get(env_id, b"int.add")
|
|
113
|
+
call = Expr.ListExpr([Expr.Byte(1), Expr.Byte(2), bound])
|
|
114
|
+
res = node.high_eval(env_id, call)
|
|
95
115
|
|
|
96
|
-
|
|
116
|
+
# sk returns a list of bytes; for 1+2 expect a single byte with value 3
|
|
117
|
+
print([b.value for b in res.elements]) # [3]
|
|
97
118
|
```
|
|
98
119
|
|
|
99
120
|
### Handling errors
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
astreum/__init__.py,sha256=y2Ok3EY_FstcmlVASr80lGR_0w-dH-SXDCCQFmL6uwA,28
|
|
2
|
-
astreum/_node.py,sha256=
|
|
2
|
+
astreum/_node.py,sha256=cJSzj7N4unkOH5T_nDa7bKy_jBD-p0oJTDlAXjuFFYw,18773
|
|
3
3
|
astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
|
|
4
4
|
astreum/node.py,sha256=SuVm1b0QWl1FpDUaLRH1fiFYnXCrPs6qYeUQlPDae8w,38358
|
|
5
5
|
astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -27,8 +27,8 @@ astreum/relay/setup.py,sha256=ynvGaJdlDtw_f5LLiow2Wo7IRzUjvgk8eSr1Sv4_zTg,2090
|
|
|
27
27
|
astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
|
|
29
29
|
astreum/storage/setup.py,sha256=1-9ztEFI_BvRDvAA0lAn4mFya8iq65THTArlj--M3Hg,626
|
|
30
|
-
astreum-0.2.
|
|
31
|
-
astreum-0.2.
|
|
32
|
-
astreum-0.2.
|
|
33
|
-
astreum-0.2.
|
|
34
|
-
astreum-0.2.
|
|
30
|
+
astreum-0.2.33.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
31
|
+
astreum-0.2.33.dist-info/METADATA,sha256=t-sfdHamxd73CFIfgiXrl5zph6CnZRtlwCRCqVrQGVA,6149
|
|
32
|
+
astreum-0.2.33.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
33
|
+
astreum-0.2.33.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
34
|
+
astreum-0.2.33.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|