stackvm 0.1.0__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.
- stackvm/__init__.py +3 -0
- stackvm/__main__.py +173 -0
- stackvm/_api.py +1407 -0
- stackvm/_ui.py +1036 -0
- stackvm/_util.py +269 -0
- stackvm/py.typed +0 -0
- stackvm/resources/icon.ico +0 -0
- stackvm-0.1.0.dist-info/METADATA +21 -0
- stackvm-0.1.0.dist-info/RECORD +10 -0
- stackvm-0.1.0.dist-info/WHEEL +4 -0
stackvm/_util.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from collections import ChainMap
|
|
5
|
+
from collections.abc import Callable, MutableMapping
|
|
6
|
+
from dataclasses import InitVar, dataclass, field
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from io import IOBase
|
|
9
|
+
from types import NoneType
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Final, Protocol, Self
|
|
11
|
+
|
|
12
|
+
if sys.version_info < (3, 15):
|
|
13
|
+
|
|
14
|
+
class sentinel:
|
|
15
|
+
name: str
|
|
16
|
+
|
|
17
|
+
def __init__(self, name: str) -> None:
|
|
18
|
+
self.name = name
|
|
19
|
+
|
|
20
|
+
def __repr__(self) -> str:
|
|
21
|
+
return self.name
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
TYPES: Final[tuple[type, ...]] = (NoneType, bool, int, float, str, bytes, slice, list, dict, IOBase)
|
|
25
|
+
FILE_MODES: Final[tuple[str, ...]] = ("r", "w", "a", "r+", "w+", "a+", "rb", "wb", "ab", "rb+", "wb+", "ab+")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OpCode(Enum):
|
|
29
|
+
args: int
|
|
30
|
+
_stack_effect: int | Callable[..., int]
|
|
31
|
+
|
|
32
|
+
def __new__(cls, value: int, args: int, stack_effect: int | Callable[..., int]) -> Self:
|
|
33
|
+
self = object.__new__(cls)
|
|
34
|
+
self._value_ = value
|
|
35
|
+
self.args = args
|
|
36
|
+
self._stack_effect = stack_effect
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
NOOP = (0, 0, 0)
|
|
40
|
+
|
|
41
|
+
NONE = (1, 0, 1)
|
|
42
|
+
INT = (2, 1, 1)
|
|
43
|
+
CHR = (3, 1, 1)
|
|
44
|
+
BOOL = (4, 1, 1)
|
|
45
|
+
|
|
46
|
+
TO_INT = (8, 0, 0)
|
|
47
|
+
TO_FLOAT = (9, 0, 0)
|
|
48
|
+
TO_CHR = (10, 0, 0)
|
|
49
|
+
TO_STR = (11, 0, 0)
|
|
50
|
+
TO_BOOL = (12, 0, 0)
|
|
51
|
+
TO_BYTES = (13, 0, 0)
|
|
52
|
+
|
|
53
|
+
ORD = (16, 0, 0)
|
|
54
|
+
SLICE = (17, 0, -2)
|
|
55
|
+
TYPE = (18, 0, 0)
|
|
56
|
+
|
|
57
|
+
LOAD = (20, 1, 1)
|
|
58
|
+
STORE = (21, 1, -1)
|
|
59
|
+
GLOBAL = (22, 1, 0)
|
|
60
|
+
|
|
61
|
+
COPY = (24, 1, 1)
|
|
62
|
+
SWAP = (25, 1, 0)
|
|
63
|
+
ROLL = (26, 1, 0)
|
|
64
|
+
POP = (27, 0, -1)
|
|
65
|
+
|
|
66
|
+
ADD = (32, 0, -1)
|
|
67
|
+
SUB = (33, 0, -1)
|
|
68
|
+
MUL = (34, 0, -1)
|
|
69
|
+
DIV = (35, 0, -1)
|
|
70
|
+
FLOORDIV = (36, 0, -1)
|
|
71
|
+
MOD = (37, 0, -1)
|
|
72
|
+
POW = (38, 0, -1)
|
|
73
|
+
AND = (39, 0, -1)
|
|
74
|
+
OR = (40, 0, -1)
|
|
75
|
+
INVERT = (41, 0, -1)
|
|
76
|
+
XOR = (42, 0, -1)
|
|
77
|
+
LSHIFT = (43, 0, -1)
|
|
78
|
+
RSHIFT = (44, 0, -1)
|
|
79
|
+
|
|
80
|
+
NEG = (48, 0, 0)
|
|
81
|
+
ABS = (49, 0, 0)
|
|
82
|
+
NOT = (50, 0, 0)
|
|
83
|
+
|
|
84
|
+
EQ = (56, 0, -1)
|
|
85
|
+
NE = (57, 0, -1)
|
|
86
|
+
LT = (58, 0, -1)
|
|
87
|
+
GT = (59, 0, -1)
|
|
88
|
+
LE = (60, 0, -1)
|
|
89
|
+
GE = (61, 0, -1)
|
|
90
|
+
IS = (62, 0, -1)
|
|
91
|
+
IS_NOT = (63, 0, -1)
|
|
92
|
+
CONTAINS = (64, 0, -1)
|
|
93
|
+
IS_TYPE = (65, 1, 0)
|
|
94
|
+
|
|
95
|
+
LIST = (72, 1, lambda arg: -arg + 1)
|
|
96
|
+
DICT = (73, 1, lambda arg: -(arg * 2) + 1)
|
|
97
|
+
LEN = (74, 0, 1)
|
|
98
|
+
APPEND = (75, 0, -1)
|
|
99
|
+
KEYS = (76, 0, 1)
|
|
100
|
+
|
|
101
|
+
GETITEM = (80, 0, 0)
|
|
102
|
+
SETITEM = (81, 0, -2)
|
|
103
|
+
DELITEM = (82, 0, -1)
|
|
104
|
+
|
|
105
|
+
JUMP = (88, 1, 0)
|
|
106
|
+
JUMP_FALSE = (89, 1, -1)
|
|
107
|
+
JUMP_TRUE = (90, 1, -1)
|
|
108
|
+
JUMP_INDIRECT = (91, 0, -1)
|
|
109
|
+
|
|
110
|
+
CALL = (96, 2, lambda _, arg2: -arg2 + 1)
|
|
111
|
+
CALL_INDIRECT = (97, 2, lambda _, arg2: -arg2)
|
|
112
|
+
|
|
113
|
+
NOW = (104, 0, 1)
|
|
114
|
+
OFTEN = (105, 0, 1)
|
|
115
|
+
SELDOM = (106, 0, 1)
|
|
116
|
+
|
|
117
|
+
INPUT = (112, 0, 1)
|
|
118
|
+
PRINT = (113, 0, -1)
|
|
119
|
+
|
|
120
|
+
OPEN = (120, 1, 0)
|
|
121
|
+
READ = (121, 1, 1)
|
|
122
|
+
WRITE = (122, 0, -1)
|
|
123
|
+
CLOSE = (123, 0, -1)
|
|
124
|
+
|
|
125
|
+
SIZE = (128, 0, 1)
|
|
126
|
+
LOCATE = (129, 0, 1)
|
|
127
|
+
|
|
128
|
+
ERROR = (240, 0, 0)
|
|
129
|
+
RETURN = (241, 0, 0)
|
|
130
|
+
|
|
131
|
+
SHOW = (254, 0, 0)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def zigzag(val: int) -> int:
|
|
135
|
+
if val >= 0:
|
|
136
|
+
return val << 1
|
|
137
|
+
else:
|
|
138
|
+
return (-val - 1) << 1 | 1
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def unzigzag(val: int) -> int:
|
|
142
|
+
if val % 2 == 0:
|
|
143
|
+
return val >> 1
|
|
144
|
+
else:
|
|
145
|
+
return (-val - 1) >> 1
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True, slots=True)
|
|
149
|
+
class Instruction:
|
|
150
|
+
opcode: OpCode
|
|
151
|
+
arg: int | sentinel = 0
|
|
152
|
+
arg2: int = 0
|
|
153
|
+
|
|
154
|
+
def __post_init__(self) -> None:
|
|
155
|
+
if not isinstance(self.arg, int | sentinel):
|
|
156
|
+
raise TypeError("Instruction argument must be int or sentinel")
|
|
157
|
+
|
|
158
|
+
def __str__(self) -> str:
|
|
159
|
+
result = self.opcode.name
|
|
160
|
+
|
|
161
|
+
if self.opcode.args >= 1 and self.opcode is not OpCode.CALL_INDIRECT:
|
|
162
|
+
result += f" {self.arg}"
|
|
163
|
+
|
|
164
|
+
if self.opcode.args >= 2:
|
|
165
|
+
result += f" {self.arg2}"
|
|
166
|
+
|
|
167
|
+
assert not isinstance(self.arg, sentinel)
|
|
168
|
+
if self.opcode is OpCode.CHR:
|
|
169
|
+
result += f" ({'' if self.arg == -1 else chr(self.arg)!r})"
|
|
170
|
+
elif self.opcode is OpCode.BOOL:
|
|
171
|
+
result += f" ({bool(self.arg)})"
|
|
172
|
+
elif self.opcode is OpCode.IS_TYPE:
|
|
173
|
+
result += f" ({'None' if self.arg == 0 else TYPES[self.arg].__name__})"
|
|
174
|
+
elif self.opcode is OpCode.OPEN:
|
|
175
|
+
result += f" ({FILE_MODES[self.arg]})"
|
|
176
|
+
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
def __bytes__(self) -> bytes:
|
|
180
|
+
if isinstance(self.arg, sentinel):
|
|
181
|
+
raise TypeError("Instructions with unresolved forward references cannot be encoded")
|
|
182
|
+
|
|
183
|
+
opcode = self.opcode.value
|
|
184
|
+
if self.has_second_arg():
|
|
185
|
+
if self.arg2 > 15:
|
|
186
|
+
raise TypeError("Function calls with more than 15 arguments cannot be encoded")
|
|
187
|
+
|
|
188
|
+
arg = zigzag(self.arg << 4 | self.arg2)
|
|
189
|
+
else:
|
|
190
|
+
arg = zigzag(self.arg)
|
|
191
|
+
|
|
192
|
+
chunks = []
|
|
193
|
+
while arg > 255:
|
|
194
|
+
chunks.append(arg & 255)
|
|
195
|
+
arg >>= 8
|
|
196
|
+
chunks.append(arg)
|
|
197
|
+
|
|
198
|
+
result = bytearray()
|
|
199
|
+
for chunk in chunks[:0:-1]:
|
|
200
|
+
result.extend((255, chunk))
|
|
201
|
+
result.extend((opcode, chunks[0]))
|
|
202
|
+
|
|
203
|
+
return bytes(result)
|
|
204
|
+
|
|
205
|
+
def has_second_arg(self) -> bool:
|
|
206
|
+
return self.opcode in {OpCode.CALL, OpCode.CALL_INDIRECT}
|
|
207
|
+
|
|
208
|
+
def stack_effect(self) -> int:
|
|
209
|
+
opcode_stack_effect = self.opcode._stack_effect
|
|
210
|
+
if callable(opcode_stack_effect):
|
|
211
|
+
args = self.opcode.args
|
|
212
|
+
if args == 2:
|
|
213
|
+
return opcode_stack_effect(self.arg, self.arg2)
|
|
214
|
+
else:
|
|
215
|
+
return opcode_stack_effect(self.arg)
|
|
216
|
+
|
|
217
|
+
return opcode_stack_effect
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if TYPE_CHECKING:
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True, slots=True)
|
|
223
|
+
class ResolvedInstruction(Instruction):
|
|
224
|
+
opcode: OpCode
|
|
225
|
+
arg: int = 0
|
|
226
|
+
arg2: int = 0
|
|
227
|
+
else:
|
|
228
|
+
ResolvedInstruction = Instruction
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dataclass(frozen=True, slots=True, weakref_slot=True, eq=False)
|
|
232
|
+
class StackItem:
|
|
233
|
+
obj: Any
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class StackUnderflowError(Exception):
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class InstructedError(Exception):
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class PopPeek(Protocol): # necessary due to the optional argument
|
|
245
|
+
def __call__(self, stack: list[Any], index: int = -1, /) -> Any: ...
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@dataclass(eq=False)
|
|
249
|
+
class VMFrame:
|
|
250
|
+
current: int = 0
|
|
251
|
+
stack: list[Any] = field(default_factory=list)
|
|
252
|
+
global_vars: set[int] = field(default_factory=set)
|
|
253
|
+
globals_: InitVar[MutableMapping[int, Any] | None] = None
|
|
254
|
+
env: ChainMap[int, Any] = field(default_factory=lambda: ChainMap[int, Any]())
|
|
255
|
+
prev: int | None = None
|
|
256
|
+
|
|
257
|
+
def __post_init__(self, globals_: MutableMapping[int, Any] | None) -> None:
|
|
258
|
+
self.env.maps = [{}, {} if globals_ is None else globals_]
|
|
259
|
+
|
|
260
|
+
def store(self, var: int, value: Any) -> None:
|
|
261
|
+
if var in self.global_vars:
|
|
262
|
+
self.env.maps[1][var] = value
|
|
263
|
+
else:
|
|
264
|
+
self.env[var] = value
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@dataclass
|
|
268
|
+
class VMState:
|
|
269
|
+
frames: list[VMFrame] = field(default_factory=lambda: [VMFrame()])
|
stackvm/py.typed
ADDED
|
File without changes
|
|
Binary file
|
|
@@ -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)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
stackvm/__init__.py,sha256=b-VIKxe6A6FiGQLOm400ivC36MuorFnAptu7TNnFOdI,75
|
|
2
|
+
stackvm/__main__.py,sha256=bm-cUp1H_SNrvpKkaLsId6DUG669hSq7zKKHyd2npb4,5057
|
|
3
|
+
stackvm/_api.py,sha256=W_qDQVSzfsCQoBQeGAm17mPXEB7vgEGbMN9Ruh9_9p4,50247
|
|
4
|
+
stackvm/_ui.py,sha256=bjkSHqtWoh3OoRXk7b-SHbMxZ45vQ9L3WYo-0bFCMyE,32466
|
|
5
|
+
stackvm/_util.py,sha256=EUVXQoJ6zKwfMrkO-f0-M6PVJvH7Fok8vtAHqcov1Qk,7028
|
|
6
|
+
stackvm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
stackvm/resources/icon.ico,sha256=K75CYa-xDQ9Yh81n0KmadeNHhT6i18Wwr014KuEdatM,370070
|
|
8
|
+
stackvm-0.1.0.dist-info/WHEEL,sha256=3NO1oYsF7mANFUWVdyEQLM-zmecczWWEmmF5rEbXrWk,80
|
|
9
|
+
stackvm-0.1.0.dist-info/METADATA,sha256=6Avij9yooSNKu1kc-6erzjl19ollMzeG1XtzajRBIfE,616
|
|
10
|
+
stackvm-0.1.0.dist-info/RECORD,,
|