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/_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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.13.6
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any