astreum 0.1.1__py3-none-any.whl → 0.1.3__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/machine/__init__.py +121 -0
- astreum/machine/environment.py +22 -0
- astreum/machine/error.py +2 -0
- astreum/machine/expression.py +50 -0
- astreum/machine/parser.py +40 -0
- astreum/machine/tokenizer.py +52 -0
- {astreum-0.1.1.dist-info → astreum-0.1.3.dist-info}/METADATA +9 -1
- astreum-0.1.3.dist-info/RECORD +12 -0
- astreum/machine.py +0 -10
- astreum-0.1.1.dist-info/RECORD +0 -7
- {astreum-0.1.1.dist-info → astreum-0.1.3.dist-info}/LICENSE +0 -0
- {astreum-0.1.1.dist-info → astreum-0.1.3.dist-info}/WHEEL +0 -0
- {astreum-0.1.1.dist-info → astreum-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from typing import Callable, Dict, List, Optional, Tuple
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
from src.astreum.machine.environment import Environment
|
|
6
|
+
from src.astreum.machine.expression import Expr
|
|
7
|
+
from src.astreum.machine.tokenizer import tokenize
|
|
8
|
+
from src.astreum.machine.parser import parse
|
|
9
|
+
|
|
10
|
+
class AstreumMachine:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.global_env = Environment()
|
|
13
|
+
|
|
14
|
+
self.sessions: Dict[str, Environment] = {}
|
|
15
|
+
|
|
16
|
+
self.lock = threading.Lock()
|
|
17
|
+
|
|
18
|
+
def create_session(self) -> str:
|
|
19
|
+
session_id = str(uuid.uuid4())
|
|
20
|
+
with self.lock:
|
|
21
|
+
self.sessions[session_id] = Environment(parent=self.global_env)
|
|
22
|
+
return session_id
|
|
23
|
+
|
|
24
|
+
def terminate_session(self, session_id: str) -> bool:
|
|
25
|
+
with self.lock:
|
|
26
|
+
if session_id in self.sessions:
|
|
27
|
+
del self.sessions[session_id]
|
|
28
|
+
return True
|
|
29
|
+
else:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
def get_session_env(self, session_id: str) -> Optional[Environment]:
|
|
33
|
+
with self.lock:
|
|
34
|
+
return self.sessions.get(session_id, None)
|
|
35
|
+
|
|
36
|
+
def evaluate_code(self, code: str, session_id: str) -> Tuple[Optional[Expr], Optional[str]]:
|
|
37
|
+
session_env = self.get_session_env(session_id)
|
|
38
|
+
if session_env is None:
|
|
39
|
+
return None, f"Session ID {session_id} not found."
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
tkns = tokenize(input=code)
|
|
43
|
+
expr, _ = parse(tokens=tkns)
|
|
44
|
+
result = self.evaluate_expression(expr, session_env)
|
|
45
|
+
return result, None
|
|
46
|
+
except Exception as e:
|
|
47
|
+
return None, str(e)
|
|
48
|
+
|
|
49
|
+
def evaluate_expression(self, expr: Expr, env: Environment) -> Expr:
|
|
50
|
+
if isinstance(expr, Expr.Integer):
|
|
51
|
+
return expr
|
|
52
|
+
|
|
53
|
+
elif isinstance(expr, Expr.String):
|
|
54
|
+
return expr
|
|
55
|
+
|
|
56
|
+
elif isinstance(expr, Expr.Symbol):
|
|
57
|
+
value = env.get(expr.value)
|
|
58
|
+
if value is not None:
|
|
59
|
+
return value
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError("Variable not found in environments.")
|
|
62
|
+
|
|
63
|
+
elif isinstance(expr, Expr.ListExpr):
|
|
64
|
+
if not expr.elements:
|
|
65
|
+
raise ValueError("Empty list cannot be evaluated.")
|
|
66
|
+
|
|
67
|
+
first = expr.elements[0]
|
|
68
|
+
|
|
69
|
+
if isinstance(first, Expr.Symbol):
|
|
70
|
+
|
|
71
|
+
first_symbol_value = env.get(first.value)
|
|
72
|
+
|
|
73
|
+
if first_symbol_value and not isinstance(first_symbol_value, Expr.Function):
|
|
74
|
+
evaluated_elements = [self.evaluate_expression(e, env) for e in expr.elements]
|
|
75
|
+
return Expr.ListExpr(evaluated_elements)
|
|
76
|
+
args = expr.elements[1:]
|
|
77
|
+
|
|
78
|
+
if len(fn_params) != len(args):
|
|
79
|
+
raise ValueError(f"Expected {len(fn_params)} arguments, got {len(args)}.")
|
|
80
|
+
|
|
81
|
+
# Create a new environment for the function execution, inheriting from the function's defining environment
|
|
82
|
+
new_env = Environment(parent=env)
|
|
83
|
+
|
|
84
|
+
# Evaluate and bind each argument
|
|
85
|
+
for param, arg in zip(fn_params, args):
|
|
86
|
+
evaluated_arg = self.evaluate_expression(arg, env)
|
|
87
|
+
new_env.set(param, evaluated_arg)
|
|
88
|
+
|
|
89
|
+
# Evaluate the function body within the new environment
|
|
90
|
+
return self.evaluate_expression(fn_body, new_env)
|
|
91
|
+
|
|
92
|
+
elif first.value in ["def", "+"]:
|
|
93
|
+
args = expr.elements[1:]
|
|
94
|
+
|
|
95
|
+
match first.value:
|
|
96
|
+
case "def":
|
|
97
|
+
if len(args) != 2:
|
|
98
|
+
raise ValueError("def expects exactly two arguments: a symbol and an expression")
|
|
99
|
+
if not isinstance(args[0], Expr.Symbol):
|
|
100
|
+
raise ValueError("First argument to def must be a symbol")
|
|
101
|
+
|
|
102
|
+
var_name = args[0].value
|
|
103
|
+
var_value = self.evaluate_expression(args[1], env)
|
|
104
|
+
env.set(var_name, var_value)
|
|
105
|
+
return args[0]
|
|
106
|
+
|
|
107
|
+
case "+":
|
|
108
|
+
evaluated_args = [self.evaluate_expression(arg, env) for arg in args]
|
|
109
|
+
if not all(isinstance(arg, Expr.Integer) for arg in evaluated_args):
|
|
110
|
+
raise ValueError("All arguments to + must be integers")
|
|
111
|
+
|
|
112
|
+
result = sum(arg.value for arg in evaluated_args)
|
|
113
|
+
return Expr.Integer(result)
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
evaluated_elements = [self.evaluate_expression(e, env) for e in expr.elements]
|
|
117
|
+
return Expr.ListExpr(evaluated_elements)
|
|
118
|
+
elif isinstance(expr, Expr.Function):
|
|
119
|
+
return expr
|
|
120
|
+
else:
|
|
121
|
+
raise ValueError(f"Unknown expression type: {type(expr)}")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Dict, Optional
|
|
2
|
+
from src.astreum.machine.expression import Expr
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Environment:
|
|
6
|
+
def __init__(self, parent: 'Environment' = None):
|
|
7
|
+
self.data: Dict[str, Expr] = {}
|
|
8
|
+
self.parent = parent
|
|
9
|
+
|
|
10
|
+
def set(self, name: str, value: Expr):
|
|
11
|
+
self.data[name] = value
|
|
12
|
+
|
|
13
|
+
def get(self, name: str) -> Optional[Expr]:
|
|
14
|
+
if name in self.data:
|
|
15
|
+
return self.data[name]
|
|
16
|
+
elif self.parent:
|
|
17
|
+
return self.parent.get(name)
|
|
18
|
+
else:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
return f"Environment({self.data})"
|
astreum/machine/error.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
class Expr:
|
|
4
|
+
class ListExpr:
|
|
5
|
+
def __init__(self, elements: List['Expr']):
|
|
6
|
+
self.elements = elements
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def value(self):
|
|
10
|
+
inner = " ".join(str(e) for e in self.elements)
|
|
11
|
+
return f"({inner})"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __repr__(self):
|
|
15
|
+
if not self.elements:
|
|
16
|
+
return "()"
|
|
17
|
+
|
|
18
|
+
inner = " ".join(str(e) for e in self.elements)
|
|
19
|
+
return f"({inner})"
|
|
20
|
+
|
|
21
|
+
class Symbol:
|
|
22
|
+
def __init__(self, value: str):
|
|
23
|
+
self.value = value
|
|
24
|
+
|
|
25
|
+
def __repr__(self):
|
|
26
|
+
return self.value
|
|
27
|
+
|
|
28
|
+
class Integer:
|
|
29
|
+
def __init__(self, value: int):
|
|
30
|
+
self.value = value
|
|
31
|
+
|
|
32
|
+
def __repr__(self):
|
|
33
|
+
return str(self.value)
|
|
34
|
+
|
|
35
|
+
class String:
|
|
36
|
+
def __init__(self, value: str):
|
|
37
|
+
self.value = value
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return f'"{self.value}"'
|
|
41
|
+
|
|
42
|
+
class Function:
|
|
43
|
+
def __init__(self, params: List[str], body: 'Expr'):
|
|
44
|
+
self.params = params
|
|
45
|
+
self.body = body
|
|
46
|
+
|
|
47
|
+
def __repr__(self):
|
|
48
|
+
params_str = " ".join(self.params)
|
|
49
|
+
body_str = str(self.body)
|
|
50
|
+
return f"(fn ({params_str}) {body_str})"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
from src.astreum.machine.error import ParseError
|
|
3
|
+
from src.astreum.machine.expression import Expr
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse(tokens: List[str]) -> Tuple[Expr, List[str]]:
|
|
7
|
+
if not tokens:
|
|
8
|
+
raise ParseError("Unexpected end of input")
|
|
9
|
+
|
|
10
|
+
first_token, *rest = tokens
|
|
11
|
+
|
|
12
|
+
if first_token == '(':
|
|
13
|
+
if not rest:
|
|
14
|
+
raise ParseError("Expected token after '('")
|
|
15
|
+
|
|
16
|
+
list_items = []
|
|
17
|
+
inner_tokens = rest
|
|
18
|
+
|
|
19
|
+
while inner_tokens:
|
|
20
|
+
if inner_tokens[0] == ')':
|
|
21
|
+
return Expr.ListExpr(list_items), inner_tokens[1:]
|
|
22
|
+
|
|
23
|
+
expr, inner_tokens = parse(inner_tokens)
|
|
24
|
+
list_items.append(expr)
|
|
25
|
+
|
|
26
|
+
raise ParseError("Expected closing ')'")
|
|
27
|
+
|
|
28
|
+
elif first_token == ')':
|
|
29
|
+
raise ParseError("Unexpected closing parenthesis")
|
|
30
|
+
|
|
31
|
+
elif first_token.startswith('"') and first_token.endswith('"'):
|
|
32
|
+
string_content = first_token[1:-1]
|
|
33
|
+
return Expr.String(string_content), rest
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
try:
|
|
37
|
+
number = int(first_token)
|
|
38
|
+
return Expr.Integer(number), rest
|
|
39
|
+
except ValueError:
|
|
40
|
+
return Expr.Symbol(first_token), rest
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# Tokenizer function
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from src.astreum.machine.error import ParseError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def tokenize(input: str) -> List[str]:
|
|
10
|
+
tokens = []
|
|
11
|
+
current_token = ""
|
|
12
|
+
in_string = False
|
|
13
|
+
escape = False
|
|
14
|
+
|
|
15
|
+
for char in input:
|
|
16
|
+
if in_string:
|
|
17
|
+
if escape:
|
|
18
|
+
current_token += char
|
|
19
|
+
escape = False
|
|
20
|
+
elif char == '\\':
|
|
21
|
+
escape = True
|
|
22
|
+
elif char == '"':
|
|
23
|
+
in_string = False
|
|
24
|
+
tokens.append(f'"{current_token}"')
|
|
25
|
+
current_token = ""
|
|
26
|
+
else:
|
|
27
|
+
current_token += char
|
|
28
|
+
else:
|
|
29
|
+
if char.isspace():
|
|
30
|
+
if current_token:
|
|
31
|
+
tokens.append(current_token)
|
|
32
|
+
current_token = ""
|
|
33
|
+
elif char == '(' or char == ')':
|
|
34
|
+
if current_token:
|
|
35
|
+
tokens.append(current_token)
|
|
36
|
+
current_token = ""
|
|
37
|
+
tokens.append(char)
|
|
38
|
+
elif char == '"':
|
|
39
|
+
if current_token:
|
|
40
|
+
tokens.append(current_token)
|
|
41
|
+
current_token = ""
|
|
42
|
+
in_string = True
|
|
43
|
+
else:
|
|
44
|
+
current_token += char
|
|
45
|
+
|
|
46
|
+
if in_string:
|
|
47
|
+
raise ParseError("Unterminated string literal")
|
|
48
|
+
|
|
49
|
+
if current_token:
|
|
50
|
+
tokens.append(current_token)
|
|
51
|
+
|
|
52
|
+
return tokens
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -13,4 +13,12 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
|
|
15
15
|
# lib
|
|
16
|
+
|
|
16
17
|
Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
18
|
+
|
|
19
|
+
[View on PyPI](https://pypi.org/project/astreum/)
|
|
20
|
+
|
|
21
|
+
## Testing
|
|
22
|
+
|
|
23
|
+
python -m unittest discover -s tests
|
|
24
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
astreum/__init__.py,sha256=-hmy95qFWlCbmHEcj5sGniM-UtpIn-iwwhVWFrFkd_w,37
|
|
2
|
+
astreum/machine/__init__.py,sha256=xitq5kFvtcBY88w3yEfsV3r75IIwBhoX6x0jx7as2Fw,5240
|
|
3
|
+
astreum/machine/environment.py,sha256=F1zXHTZpfX4GazUbPhgh43j8EdfV5bedS-b4xWBB_NY,611
|
|
4
|
+
astreum/machine/error.py,sha256=MvqBaZZt33rNELNhUJ2lER3TE3aS8WVqsWF2hz2AwoA,38
|
|
5
|
+
astreum/machine/expression.py,sha256=yOovmkiE11IEIjqEuYwuG7-jx6msOpgyzl3C3yD331c,1298
|
|
6
|
+
astreum/machine/parser.py,sha256=5tSAVi5z7laEYZHsoDofEOq4KKTfo5md6epT2Bsgk8o,1199
|
|
7
|
+
astreum/machine/tokenizer.py,sha256=4QzdZ11ocZKxlcjvV54PUxDuZxuL4et_sz3ILwfp3ko,1441
|
|
8
|
+
astreum-0.1.3.dist-info/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
9
|
+
astreum-0.1.3.dist-info/METADATA,sha256=RoW6ek_WGNmQzA_n5oHrY7YHS6Z2hMJQeF1OoMOL7z4,740
|
|
10
|
+
astreum-0.1.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
11
|
+
astreum-0.1.3.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
12
|
+
astreum-0.1.3.dist-info/RECORD,,
|
astreum/machine.py
DELETED
astreum-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
astreum/__init__.py,sha256=-hmy95qFWlCbmHEcj5sGniM-UtpIn-iwwhVWFrFkd_w,37
|
|
2
|
-
astreum/machine.py,sha256=pstgeN47YOr6vG3-vx4TMOjmzZD2Xpd66CgSKWB-UNc,296
|
|
3
|
-
astreum-0.1.1.dist-info/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
4
|
-
astreum-0.1.1.dist-info/METADATA,sha256=W3dD-otz_e9ox6dbsLGrVMxdge0W4VF8WxH_-hbg_ww,637
|
|
5
|
-
astreum-0.1.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
6
|
-
astreum-0.1.1.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
7
|
-
astreum-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|