stackvm 0.1.0__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.
- stackvm-0.1.0/PKG-INFO +21 -0
- stackvm-0.1.0/README.md +12 -0
- stackvm-0.1.0/pyproject.toml +14 -0
- stackvm-0.1.0/pyproject.toml.orig +16 -0
- stackvm-0.1.0/src/stackvm/__init__.py +3 -0
- stackvm-0.1.0/src/stackvm/__main__.py +173 -0
- stackvm-0.1.0/src/stackvm/_api.py +1407 -0
- stackvm-0.1.0/src/stackvm/_ui.py +1036 -0
- stackvm-0.1.0/src/stackvm/_util.py +269 -0
- stackvm-0.1.0/src/stackvm/py.typed +0 -0
- stackvm-0.1.0/src/stackvm/resources/icon.ico +0 -0
stackvm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: stackvm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Stack machine visualized
|
|
5
|
+
Author: abkwreu
|
|
6
|
+
Requires-Dist: flet>=1.0.5
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# StackVM
|
|
11
|
+
## Features
|
|
12
|
+
* Low-level, assembly-like programming language
|
|
13
|
+
* Fluent Python program building API
|
|
14
|
+
* Program visualization UI powered by Flet
|
|
15
|
+
|
|
16
|
+
## Getting started
|
|
17
|
+
* See `examples/examples.py` for example programs
|
|
18
|
+
* See the documentation for the `StackMachine` class for information on all of the opcodes
|
|
19
|
+
|
|
20
|
+
## Source
|
|
21
|
+
* GitHub: [https://www.github.com/Abkwreu/StackVM](https://www.github.com/Abkwreu/StackVM)
|
stackvm-0.1.0/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# StackVM
|
|
2
|
+
## Features
|
|
3
|
+
* Low-level, assembly-like programming language
|
|
4
|
+
* Fluent Python program building API
|
|
5
|
+
* Program visualization UI powered by Flet
|
|
6
|
+
|
|
7
|
+
## Getting started
|
|
8
|
+
* See `examples/examples.py` for example programs
|
|
9
|
+
* See the documentation for the `StackMachine` class for information on all of the opcodes
|
|
10
|
+
|
|
11
|
+
## Source
|
|
12
|
+
* GitHub: [https://www.github.com/Abkwreu/StackVM](https://www.github.com/Abkwreu/StackVM)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "stackvm"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Stack machine visualized"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = ["flet>=1.0.5"]
|
|
8
|
+
|
|
9
|
+
[[project.authors]]
|
|
10
|
+
name = "abkwreu"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.13.6,<0.14.0"]
|
|
14
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "stackvm"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Stack machine visualized"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "abkwreu" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"flet>=1.0.5",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["uv_build>=0.13.6,<0.14.0"]
|
|
16
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import tkinter as tk
|
|
3
|
+
from importlib import resources, util
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from tkinter import filedialog, messagebox, simpledialog, ttk
|
|
6
|
+
|
|
7
|
+
from stackvm._api import StackMachine
|
|
8
|
+
from stackvm._util import OpCode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_machine_from_python(file: str, root: tk.Tk) -> StackMachine | None:
|
|
12
|
+
result = messagebox.askokcancel(
|
|
13
|
+
title="Warning",
|
|
14
|
+
message="Python code contained within the selected file will be executed.\nContinue?",
|
|
15
|
+
icon="warning",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if not result:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
spec = util.spec_from_file_location("<stackvm>", file)
|
|
22
|
+
|
|
23
|
+
if spec is None:
|
|
24
|
+
messagebox.showerror(title="Error", message="Failed to load Python file")
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
module = util.module_from_spec(spec)
|
|
28
|
+
loader = spec.loader
|
|
29
|
+
|
|
30
|
+
if loader is None:
|
|
31
|
+
messagebox.showerror(title="Error", message="Failed to load Python file")
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
loader.exec_module(module)
|
|
36
|
+
except BaseException as e: # noqa: BLE001
|
|
37
|
+
messagebox.showerror(
|
|
38
|
+
title="Error",
|
|
39
|
+
message="Exception raised when importing module",
|
|
40
|
+
detail=f"{type(e).__name__}: {e}",
|
|
41
|
+
)
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
candidates = {name: machine for name, machine in vars(module).items() if isinstance(machine, StackMachine)}
|
|
45
|
+
|
|
46
|
+
if not candidates:
|
|
47
|
+
messagebox.showerror(title="Error", message="No instances of StackMachine found in module globals")
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
if len(candidates) >= 2:
|
|
51
|
+
options = sorted(candidates)
|
|
52
|
+
|
|
53
|
+
dialog = tk.Toplevel(root)
|
|
54
|
+
dialog.title("Input")
|
|
55
|
+
|
|
56
|
+
dialog.resizable(False, False)
|
|
57
|
+
|
|
58
|
+
choice = tk.StringVar(value=options[0])
|
|
59
|
+
|
|
60
|
+
tk.Label(dialog, text="Select stack machine to execute:").pack()
|
|
61
|
+
|
|
62
|
+
ttk.Combobox(dialog, textvariable=choice, values=options, state="readonly").pack(padx=20, pady=10)
|
|
63
|
+
|
|
64
|
+
selection: str = ""
|
|
65
|
+
|
|
66
|
+
def submit():
|
|
67
|
+
nonlocal selection
|
|
68
|
+
selection = choice.get()
|
|
69
|
+
dialog.destroy()
|
|
70
|
+
|
|
71
|
+
ttk.Button(dialog, text="Select", command=submit).pack(pady=10)
|
|
72
|
+
|
|
73
|
+
dialog.grab_set()
|
|
74
|
+
root.wait_window(dialog)
|
|
75
|
+
|
|
76
|
+
return candidates[selection]
|
|
77
|
+
else:
|
|
78
|
+
return next(iter(candidates.values()))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main():
|
|
82
|
+
root = tk.Tk()
|
|
83
|
+
|
|
84
|
+
icon = resources.files("stackvm").joinpath("resources", "icon.ico")
|
|
85
|
+
|
|
86
|
+
with resources.as_file(icon) as path:
|
|
87
|
+
root.iconbitmap(path)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
root.withdraw()
|
|
91
|
+
|
|
92
|
+
file = filedialog.askopenfilename(
|
|
93
|
+
title="Select a file...",
|
|
94
|
+
filetypes=[
|
|
95
|
+
("StackVM files", "*.bin *.svm"),
|
|
96
|
+
("Python files", "*.py"),
|
|
97
|
+
("All files", "*.*"),
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if not file:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
path = Path(file)
|
|
105
|
+
|
|
106
|
+
if path.suffix == ".py":
|
|
107
|
+
machine = get_machine_from_python(file, root)
|
|
108
|
+
|
|
109
|
+
if machine is None:
|
|
110
|
+
return
|
|
111
|
+
else:
|
|
112
|
+
machine = StackMachine.from_bytes(path.read_bytes())
|
|
113
|
+
|
|
114
|
+
if any(instr.opcode is OpCode.OPEN for instr in machine.instructions):
|
|
115
|
+
result = messagebox.askyesno(
|
|
116
|
+
title="Warning",
|
|
117
|
+
message="This program performs file I/O. Continue?",
|
|
118
|
+
icon="warning",
|
|
119
|
+
)
|
|
120
|
+
if not result:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
arg_input = simpledialog.askstring(
|
|
124
|
+
title="Input",
|
|
125
|
+
prompt="Enter initial arguments, separated by commas. Use quotes to indicate characters.",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if not arg_input:
|
|
129
|
+
arg_input = "()"
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
args = ast.literal_eval(arg_input)
|
|
133
|
+
except (ValueError, SyntaxError):
|
|
134
|
+
messagebox.showerror(title="Error", message="Could not parse arguments")
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
if not isinstance(args, tuple):
|
|
138
|
+
args = (args,)
|
|
139
|
+
|
|
140
|
+
for arg in args:
|
|
141
|
+
if not (isinstance(arg, None | int | bool) or isinstance(arg, str) and len(arg) <= 1):
|
|
142
|
+
messagebox.showerror(
|
|
143
|
+
title="Error",
|
|
144
|
+
message=f"Disallowed argument {arg} (only None, int, bool, and chr are permitted)",
|
|
145
|
+
)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
if any(instr.opcode in {OpCode.OFTEN, OpCode.SELDOM} for instr in machine.instructions):
|
|
149
|
+
seed_input = simpledialog.askstring(
|
|
150
|
+
title="Input",
|
|
151
|
+
prompt="Enter a seed (leave blank for random):",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if seed_input is None:
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
if not seed_input:
|
|
158
|
+
seed = None
|
|
159
|
+
else:
|
|
160
|
+
try:
|
|
161
|
+
seed = int(seed_input)
|
|
162
|
+
except ValueError:
|
|
163
|
+
seed = seed_input
|
|
164
|
+
else:
|
|
165
|
+
seed = None
|
|
166
|
+
finally:
|
|
167
|
+
root.destroy()
|
|
168
|
+
|
|
169
|
+
machine.ui(*args, seed=seed)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
main()
|