astreum 0.2.30__tar.gz → 0.2.32__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- {astreum-0.2.30/src/astreum.egg-info → astreum-0.2.32}/PKG-INFO +35 -14
- {astreum-0.2.30 → astreum-0.2.32}/README.md +34 -13
- {astreum-0.2.30 → astreum-0.2.32}/pyproject.toml +1 -1
- astreum-0.2.32/src/astreum/_node.py +423 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/node.py +12 -0
- {astreum-0.2.30 → astreum-0.2.32/src/astreum.egg-info}/PKG-INFO +35 -14
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum.egg-info/SOURCES.txt +1 -0
- {astreum-0.2.30 → astreum-0.2.32}/LICENSE +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/setup.cfg +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/__init__.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/crypto/__init__.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/crypto/ed25519.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/crypto/quadratic_form.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/crypto/wesolowski.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/crypto/x25519.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/format.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/lispeum/__init__.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/lispeum/environment.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/lispeum/expression.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/lispeum/parser.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/lispeum/tokenizer.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/models/__init__.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/models/account.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/models/accounts.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/models/block.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/models/merkle.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/models/message.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/models/patricia.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/models/transaction.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/relay/__init__.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/relay/peer.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/relay/route.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/relay/setup.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/storage/__init__.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/storage/object.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum/storage/setup.py +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum.egg-info/requires.txt +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/src/astreum.egg-info/top_level.txt +0 -0
- {astreum-0.2.30 → astreum-0.2.32}/tests/test_node_machine.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.32
|
|
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
|
|
@@ -58,24 +58,45 @@ node = Node(config)
|
|
|
58
58
|
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.
|
|
59
59
|
|
|
60
60
|
```python
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
# Define a named function int.add (stack body) and call it with bytes 1 and 2
|
|
62
|
+
|
|
63
|
+
import uuid
|
|
64
|
+
from astreum._node import Node, Env, Expr
|
|
65
|
+
|
|
66
|
+
# 1) Spin‑up a stand‑alone VM
|
|
67
|
+
node = Node()
|
|
68
|
+
|
|
69
|
+
# 2) Create an environment (simple manual setup)
|
|
70
|
+
env_id = uuid.uuid4()
|
|
71
|
+
node.environments[env_id] = Env()
|
|
72
|
+
|
|
73
|
+
# 3) Build a function value using a low‑level stack body via `sk`.
|
|
74
|
+
# Body does: $0 $1 add (i.e., a + b)
|
|
75
|
+
low_body = Expr.ListExpr([
|
|
76
|
+
Expr.Symbol("$0"), # a (first arg)
|
|
77
|
+
Expr.Symbol("$1"), # b (second arg)
|
|
78
|
+
Expr.Symbol("add"),
|
|
79
|
+
])
|
|
64
80
|
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
fn_body = Expr.ListExpr([
|
|
82
|
+
Expr.Symbol("a"),
|
|
83
|
+
Expr.Symbol("b"),
|
|
84
|
+
Expr.ListExpr([low_body, Expr.Symbol("sk")]),
|
|
85
|
+
])
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
|
|
88
|
+
int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
|
|
70
89
|
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
expr, _ = parse(tokenize(source))
|
|
90
|
+
# 4) Store under the name "int.add"
|
|
91
|
+
node.env_set(env_id, b"int.add", int_add_fn)
|
|
74
92
|
|
|
75
|
-
#
|
|
76
|
-
|
|
93
|
+
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
94
|
+
bound = node.env_get(env_id, b"int.add")
|
|
95
|
+
call = Expr.ListExpr([Expr.Byte(1), Expr.Byte(2), bound])
|
|
96
|
+
res = node.high_eval(env_id, call)
|
|
77
97
|
|
|
78
|
-
|
|
98
|
+
# sk returns a list of bytes; for 1+2 expect a single byte with value 3
|
|
99
|
+
print([b.value for b in res.elements]) # [3]
|
|
79
100
|
```
|
|
80
101
|
|
|
81
102
|
### Handling errors
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, List, Optional, Union
|
|
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, enabled: bool = True, limit: Optional[int] = None):
|
|
97
|
+
self.enabled = enabled
|
|
98
|
+
self.limit: Optional[int] = limit
|
|
99
|
+
self.used: int = 0
|
|
100
|
+
|
|
101
|
+
def charge_bytes(self, n: int) -> bool:
|
|
102
|
+
if not self.enabled:
|
|
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
|
|
110
|
+
|
|
111
|
+
class Node:
|
|
112
|
+
def __init__(self):
|
|
113
|
+
self.environments: Dict[uuid.UUID, Env] = {}
|
|
114
|
+
self.in_memory_storage: Dict[bytes, bytes] = {}
|
|
115
|
+
self.machine_environments_lock = threading.RLock()
|
|
116
|
+
|
|
117
|
+
# ---- Env helpers ----
|
|
118
|
+
def env_get(self, env_id: uuid.UUID, key: bytes) -> Optional[Expr]:
|
|
119
|
+
cur = self.environments.get(env_id)
|
|
120
|
+
while cur is not None:
|
|
121
|
+
if key in cur.data:
|
|
122
|
+
return cur.data[key]
|
|
123
|
+
cur = self.environments.get(cur.parent_id) if cur.parent_id else None
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
def env_set(self, env_id: uuid.UUID, key: bytes, value: Expr) -> bool:
|
|
127
|
+
with self.machine_environments_lock:
|
|
128
|
+
env = self.environments.get(env_id)
|
|
129
|
+
if env is None:
|
|
130
|
+
return False
|
|
131
|
+
env.data[key] = value
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
# ---- Storage (persistent) ----
|
|
135
|
+
def _local_get(self, key: bytes) -> Optional[bytes]:
|
|
136
|
+
return self.in_memory_storage.get(key)
|
|
137
|
+
|
|
138
|
+
def _local_set(self, key: bytes, value: bytes) -> None:
|
|
139
|
+
self.in_memory_storage[key] = value
|
|
140
|
+
|
|
141
|
+
# ---- Eval ----
|
|
142
|
+
def low_eval(self, code: List[bytes], meter: Meter) -> Union[bytes, Expr.Error]:
|
|
143
|
+
|
|
144
|
+
heap: Dict[bytes, bytes] = {}
|
|
145
|
+
|
|
146
|
+
stack: List[bytes] = []
|
|
147
|
+
pc = 0
|
|
148
|
+
|
|
149
|
+
while True:
|
|
150
|
+
if pc >= len(code):
|
|
151
|
+
if len(stack) != 1:
|
|
152
|
+
return Expr.Error("bad stack")
|
|
153
|
+
return stack.pop()
|
|
154
|
+
|
|
155
|
+
tok = code[pc]
|
|
156
|
+
pc += 1
|
|
157
|
+
|
|
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)
|
|
233
|
+
continue
|
|
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
|
+
|
|
248
|
+
# not an opcode → literal blob
|
|
249
|
+
stack.append(tok)
|
|
250
|
+
|
|
251
|
+
def high_eval(self, env_id: uuid.UUID, expr: Expr, meter = None) -> Expr:
|
|
252
|
+
|
|
253
|
+
if meter is None:
|
|
254
|
+
meter = Meter()
|
|
255
|
+
|
|
256
|
+
# ---------- atoms ----------
|
|
257
|
+
if isinstance(expr, Expr.Error):
|
|
258
|
+
return expr
|
|
259
|
+
|
|
260
|
+
if isinstance(expr, Expr.Symbol):
|
|
261
|
+
bound = self.env_get(env_id, expr.value.encode())
|
|
262
|
+
if bound is None:
|
|
263
|
+
return Expr.Error(f"unbound symbol '{expr.value}'", origin=expr)
|
|
264
|
+
return bound
|
|
265
|
+
|
|
266
|
+
if not isinstance(expr, Expr.ListExpr):
|
|
267
|
+
return expr # Expr.Byte or other literals passthrough
|
|
268
|
+
|
|
269
|
+
# ---------- empty / single ----------
|
|
270
|
+
if len(expr.elements) == 0:
|
|
271
|
+
return expr
|
|
272
|
+
if len(expr.elements) == 1:
|
|
273
|
+
return self.high_eval(env_id=env_id, expr=expr.elements[0], meter=meter)
|
|
274
|
+
|
|
275
|
+
tail = expr.elements[-1]
|
|
276
|
+
|
|
277
|
+
# ---------- (value name def) ----------
|
|
278
|
+
if isinstance(tail, Expr.Symbol) and tail.value == "def":
|
|
279
|
+
if len(expr.elements) < 3:
|
|
280
|
+
return Expr.Error("def expects (value name def)", origin=expr)
|
|
281
|
+
name_e = expr.elements[-2]
|
|
282
|
+
if not isinstance(name_e, Expr.Symbol):
|
|
283
|
+
return Expr.Error("def name must be symbol", origin=name_e)
|
|
284
|
+
value_e = expr.elements[-3]
|
|
285
|
+
value_res = self.high_eval(env_id=env_id, expr=value_e, meter=meter)
|
|
286
|
+
if isinstance(value_res, Expr.Error):
|
|
287
|
+
return value_res
|
|
288
|
+
self.env_set(env_id, name_e.value.encode(), value_res)
|
|
289
|
+
return value_res
|
|
290
|
+
|
|
291
|
+
# ---- LOW-LEVEL call: ( arg1 arg2 ... ( (body) sk ) ) ----
|
|
292
|
+
if isinstance(tail, Expr.ListExpr):
|
|
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]
|
|
296
|
+
if not isinstance(body_expr, Expr.ListExpr):
|
|
297
|
+
return Expr.Error("sk body must be list", origin=body_expr)
|
|
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)
|
|
315
|
+
|
|
316
|
+
# resolve ALL preceding args into bytes (can be Byte or List[Byte])
|
|
317
|
+
args_exprs = expr.elements[:-1]
|
|
318
|
+
arg_bytes: List[bytes] = []
|
|
319
|
+
for a in args_exprs:
|
|
320
|
+
v = self.high_eval(env_id=env_id, expr=a, meter=meter)
|
|
321
|
+
if isinstance(v, Expr.Error):
|
|
322
|
+
return v
|
|
323
|
+
vb = to_bytes(v)
|
|
324
|
+
if isinstance(vb, Expr.Error):
|
|
325
|
+
return vb
|
|
326
|
+
arg_bytes.append(vb)
|
|
327
|
+
|
|
328
|
+
# build low-level code with $0-based placeholders ($0 = first arg)
|
|
329
|
+
code: List[bytes] = []
|
|
330
|
+
|
|
331
|
+
def emit(tok: Expr) -> Union[None, Expr.Error]:
|
|
332
|
+
if isinstance(tok, Expr.Symbol):
|
|
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):
|
|
347
|
+
code.append(bytes([tok.value & 0xFF]))
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
if isinstance(tok, Expr.ListExpr):
|
|
351
|
+
rv = self.high_eval(env_id, tok, meter=meter)
|
|
352
|
+
if isinstance(rv, Expr.Error):
|
|
353
|
+
return rv
|
|
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
|
+
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# ---------- (... (body params fn)) HIGH-LEVEL CALL ----------
|
|
379
|
+
if isinstance(tail, Expr.ListExpr):
|
|
380
|
+
fn_form = tail
|
|
381
|
+
if (len(fn_form.elements) >= 3
|
|
382
|
+
and isinstance(fn_form.elements[-1], Expr.Symbol)
|
|
383
|
+
and fn_form.elements[-1].value == "fn"):
|
|
384
|
+
|
|
385
|
+
body_expr = fn_form.elements[-3]
|
|
386
|
+
params_expr = fn_form.elements[-2]
|
|
387
|
+
|
|
388
|
+
if not isinstance(body_expr, Expr.ListExpr):
|
|
389
|
+
return Expr.Error("fn body must be list", origin=body_expr)
|
|
390
|
+
if not isinstance(params_expr, Expr.ListExpr):
|
|
391
|
+
return Expr.Error("fn params must be list", origin=params_expr)
|
|
392
|
+
|
|
393
|
+
params: List[bytes] = []
|
|
394
|
+
for p in params_expr.elements:
|
|
395
|
+
if not isinstance(p, Expr.Symbol):
|
|
396
|
+
return Expr.Error("fn param must be symbol", origin=p)
|
|
397
|
+
params.append(p.value.encode())
|
|
398
|
+
|
|
399
|
+
args_exprs = expr.elements[:-1]
|
|
400
|
+
if len(args_exprs) != len(params):
|
|
401
|
+
return Expr.Error("arity mismatch", origin=expr)
|
|
402
|
+
|
|
403
|
+
arg_bytes: List[bytes] = []
|
|
404
|
+
for a in args_exprs:
|
|
405
|
+
v = self.high_eval(env_id, a, meter=meter)
|
|
406
|
+
if isinstance(v, Expr.Error):
|
|
407
|
+
return v
|
|
408
|
+
if not isinstance(v, Expr.Byte):
|
|
409
|
+
return Expr.Error("argument must resolve to Byte", origin=a)
|
|
410
|
+
arg_bytes.append(bytes([v.value & 0xFF]))
|
|
411
|
+
|
|
412
|
+
# child env, bind params -> Expr.Byte
|
|
413
|
+
child_env = uuid.uuid4()
|
|
414
|
+
self.environments[child_env] = Env(parent_id=env_id)
|
|
415
|
+
for name_b, val_b in zip(params, arg_bytes):
|
|
416
|
+
self.env_set(child_env, name_b, Expr.Byte(val_b[0]))
|
|
417
|
+
|
|
418
|
+
# evaluate HL body, metered from the top
|
|
419
|
+
return self.high_eval(child_env, body_expr, meter=meter)
|
|
420
|
+
|
|
421
|
+
# ---------- default: resolve each element and return list ----------
|
|
422
|
+
resolved: List[Expr] = [self.high_eval(env_id, e, meter=meter) for e in expr.elements]
|
|
423
|
+
return Expr.ListExpr(resolved)
|
|
@@ -473,6 +473,18 @@ 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
|
+
|
|
477
|
+
## DEF: (def x 1) -> ()
|
|
478
|
+
|
|
479
|
+
## GET: (x) -> 1
|
|
480
|
+
|
|
481
|
+
## ADD: (+ 1 2) -> (+ 3) -> 3
|
|
482
|
+
|
|
483
|
+
## NAND: (~& 1 1) -> (~& 0) -> 0
|
|
484
|
+
|
|
485
|
+
##
|
|
486
|
+
|
|
487
|
+
|
|
476
488
|
## List: ints -> (1 2)
|
|
477
489
|
# push: (list.push 3 ints) -> (1 2 3) / (list.push 0 0 ints) -> (0 1 2)
|
|
478
490
|
elif first.value == "list.push":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.32
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|