toyc 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.
toyc-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: toyc
3
+ Version: 0.1.0
4
+ Summary: A toy compiler with GPU backends (Vulkan + OpenGL)
5
+ Author: spy1345a
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 spy1345a
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
11
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
12
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
14
+ following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all copies or substantial
17
+ portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
20
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
21
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ Project-URL: Homepage, https://github.com/spy1345a/toyc-repo
26
+ Project-URL: Repository, https://github.com/spy1345a/toyc-repo
27
+ Keywords: compiler,gpu,vulkan,opengl,toy
28
+ Classifier: Programming Language :: Python :: 3
29
+ Classifier: License :: OSI Approved :: MIT License
30
+ Classifier: Operating System :: OS Independent
31
+ Classifier: Topic :: Software Development :: Compilers
32
+ Requires-Python: >=3.10
33
+ Description-Content-Type: text/markdown
34
+ Requires-Dist: pyopengl>=3.1
35
+ Requires-Dist: vulkan>=1.3.275.1
36
+ Provides-Extra: vulkan
37
+ Requires-Dist: vulkan; extra == "vulkan"
38
+ Provides-Extra: opengl
39
+ Requires-Dist: PyOpenGL; extra == "opengl"
toyc-0.1.0/README.md ADDED
File without changes
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "toyc"
7
+ version = "0.1.0"
8
+ description = "A toy compiler with GPU backends (Vulkan + OpenGL)"
9
+ readme = "README.md"
10
+ license = { file = "toyc/LICENSE" }
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "spy1345a"}
14
+ ]
15
+ keywords = ["compiler", "gpu", "vulkan", "opengl", "toy"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Topic :: Software Development :: Compilers",
21
+ ]
22
+
23
+ # list any actual pip deps your code imports at runtime:
24
+ dependencies = [
25
+ "pyopengl>=3.1",
26
+ "vulkan>=1.3.275.1"
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/spy1345a/toyc-repo"
31
+ Repository = "https://github.com/spy1345a/toyc-repo"
32
+
33
+ [project.optional-dependencies]
34
+ vulkan = ["vulkan"]
35
+ opengl = ["PyOpenGL"]
36
+
37
+ # If you want a CLI entrypoint (e.g. `toyc compile foo.tc`): comming soon
38
+ # [project.scripts]
39
+ # toyc = "toyc.compiler:main"
toyc-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 spy1345a
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,10 @@
1
+ # compiler/__init__.py
2
+
3
+ from .lexer import Lexer
4
+ from .parser import Parser
5
+ from .gpu import Flattener
6
+ from .vm import Cpu , GpuVulkan , GpuOpengl
7
+ from .compiler import Compiler
8
+
9
+ __all__ = ["Lexer", "Parser", "Evaluator", "Flattener",
10
+ "Instruction", "compile_to_file", "load_from_file"]
@@ -0,0 +1,68 @@
1
+ # compiler/ast_nodes.py
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ @dataclass
7
+ class Number:
8
+ value: float | int
9
+
10
+ def __repr__(self):
11
+ return f"Number({self.value})"
12
+
13
+ def to_dict(self):
14
+ return {"type": "Number", "value": self.value}
15
+
16
+ @staticmethod
17
+ def from_dict(d):
18
+ return Number(d["value"])
19
+
20
+
21
+ @dataclass
22
+ class Var:
23
+ name: str
24
+
25
+ def __repr__(self):
26
+ return f"Var({self.name!r})"
27
+
28
+ def to_dict(self):
29
+ return {"type": "Var", "name": self.name}
30
+
31
+ @staticmethod
32
+ def from_dict(d):
33
+ return Var(d["name"])
34
+
35
+
36
+ @dataclass
37
+ class BinOp:
38
+ op: str
39
+ left: Any
40
+ right: Any
41
+
42
+ def __repr__(self):
43
+ return f"BinOp({self.op!r}, {self.left}, {self.right})"
44
+
45
+ def to_dict(self):
46
+ return {
47
+ "type": "BinOp",
48
+ "op": self.op,
49
+ "left": self.left.to_dict(),
50
+ "right": self.right.to_dict(),
51
+ }
52
+
53
+ @staticmethod
54
+ def from_dict(d):
55
+ return BinOp(
56
+ op = d["op"],
57
+ left = node_from_dict(d["left"]),
58
+ right = node_from_dict(d["right"]),
59
+ )
60
+
61
+
62
+ def node_from_dict(d):
63
+ """Reconstruct any AST node from a dict."""
64
+ kind = d["type"]
65
+ if kind == "Number": return Number.from_dict(d)
66
+ if kind == "Var": return Var.from_dict(d)
67
+ if kind == "BinOp": return BinOp.from_dict(d)
68
+ raise ValueError(f"Unknown node type: {kind!r}")
@@ -0,0 +1,199 @@
1
+ # compiler/compiler.py
2
+ #
3
+ # Responsible for:
4
+ # • Defining the Instr dataclass (the instruction set)
5
+ # • .toyc binary serialisation / deserialisation helpers
6
+ # • The Compiler class (AST → list[Instr], with disk write)
7
+ #
8
+ # It knows nothing about execution — import vm.py for that.
9
+ #
10
+ # Two calling styles are supported:
11
+ #
12
+ # A) AST-first (you already have an AST node):
13
+ # Compiler.compile(ast) → writes out.toyc
14
+ # Compiler.compile(ast, path="foo.toyc") → writes foo.toyc
15
+ # Compiler.compile(ast, path=None) → memory only
16
+ #
17
+ # B) File-first (point straight at a .toy source file):
18
+ # Compiler.compile("script.toy") → writes script.toyc
19
+ # Compiler.compile("script.toy", path=None) → memory only
20
+ #
21
+ # The Lexer and Parser are invoked internally so the caller doesn't
22
+ # need to tokenise or build an AST manually.
23
+
24
+ import os
25
+ import pickle
26
+ from dataclasses import dataclass
27
+ from typing import Any
28
+
29
+ from .ast_nodes import Number, Var, BinOp
30
+ from .lexer import Lexer
31
+ from .parser import Parser
32
+
33
+
34
+ # ── instruction set ───────────────────────────────────────────────────────────
35
+ # These map 1-to-1 to SPIR-V ops later:
36
+ # PUSH → OpConstant
37
+ # LOAD → OpLoad
38
+ # ADD → OpFAdd
39
+ # SUB → OpFSub
40
+ # MUL → OpFMul
41
+ # DIV → OpFDiv
42
+
43
+ @dataclass
44
+ class Instr:
45
+ op: str
46
+ arg: Any = None
47
+
48
+ def __repr__(self):
49
+ return f"{self.op} {self.arg!r}" if self.arg is not None else self.op
50
+
51
+
52
+ # ── .toyc binary format ───────────────────────────────────────────────────────
53
+ # Layout:
54
+ # bytes 0-3 : MAGIC b'\x54\x4F\x59\x43' ("TOYC")
55
+ # bytes 4+ : pickle.dumps(list[Instr])
56
+ #
57
+ # The magic header lets Cpu.run() detect compiled files by peeking at raw
58
+ # bytes, without relying on the file extension alone.
59
+
60
+ MAGIC = b'\x54\x4F\x59\x43' # "TOYC"
61
+
62
+
63
+ def write_bytecode(path: str, instructions: list) -> None:
64
+ """Serialise *instructions* to a .toyc file at *path*."""
65
+ with open(path, "wb") as f:
66
+ f.write(MAGIC)
67
+ pickle.dump(instructions, f)
68
+
69
+
70
+ def read_bytecode(path: str) -> list:
71
+ """Deserialise a .toyc file and return the list[Instr]."""
72
+ with open(path, "rb") as f:
73
+ magic = f.read(4)
74
+ if magic != MAGIC:
75
+ raise ValueError(
76
+ f"{path!r} is not a valid .toyc file (bad magic bytes)"
77
+ )
78
+ return pickle.load(f)
79
+
80
+
81
+ def is_compiled_bytecode(path: str) -> bool:
82
+ """Return True if *path* starts with the TOYC magic number."""
83
+ try:
84
+ with open(path, "rb") as f:
85
+ return f.read(4) == MAGIC
86
+ except OSError:
87
+ return False
88
+
89
+
90
+ # ── helpers ───────────────────────────────────────────────────────────────────
91
+
92
+ def _source_to_ast(toy_path: str):
93
+ """Read a .toy file and return its parsed AST using Lexer + Parser."""
94
+ with open(toy_path, "r", encoding="utf-8") as f:
95
+ source = f.read()
96
+ tokens = Lexer.tokenize(source)
97
+ return Parser.parse(tokens)
98
+
99
+
100
+ # ── Compiler: AST → bytecode ──────────────────────────────────────────────────
101
+
102
+ class Compiler:
103
+ """Walk an AST and emit a flat list[Instr].
104
+
105
+ Calling styles
106
+ --------------
107
+ # A) You already have tokens / an AST (original workflow):
108
+ tokens = Lexer.tokenize(code)
109
+ ast = Parser.parse(tokens)
110
+ Compiler.compile(ast) # writes out.toyc
111
+ Compiler.compile(ast, path="expr.toyc") # writes expr.toyc
112
+ Compiler.compile(ast, path=None) # memory only
113
+
114
+ # B) Point straight at a .toy source file:
115
+ Compiler.compile("script.toy") # reads script.toy,
116
+ # writes script.toyc next to it
117
+ Compiler.compile("script.toy", path=None) # reads script.toy,
118
+ # memory only (no .toyc written)
119
+ """
120
+
121
+ @staticmethod
122
+ def compile(node_or_path, path: str = "out.toyc") -> list:
123
+ """
124
+ Compile source or an AST node into bytecode.
125
+
126
+ Parameters
127
+ ----------
128
+ node_or_path : AST node OR str
129
+ • An AST node → compiled directly (original behaviour).
130
+ • A str ending in ``.toy`` → the file is read, lexed, and parsed
131
+ first; the default output path becomes a sibling .toyc with the
132
+ same stem (overrides the ``path`` default of ``"out.toyc"``).
133
+
134
+ path : str | None, optional
135
+ Where to write the .toyc file.
136
+ • ``"out.toyc"`` (default) when *node_or_path* is an AST node.
137
+ • Auto-derived sibling path when *node_or_path* is a .toy file.
138
+ • ``None`` → compile but do not write anything to disk.
139
+ The .toyc extension is enforced regardless of what is passed.
140
+
141
+ Returns
142
+ -------
143
+ list[Instr]
144
+ Flat bytecode list (always returned).
145
+ """
146
+
147
+ # ── resolve the input ────────────────────────────────────────────────
148
+
149
+ if isinstance(node_or_path, str):
150
+ # File-first mode
151
+ toy_path = node_or_path
152
+ if not toy_path.endswith(".toy"):
153
+ raise ValueError(
154
+ f"Expected a .toy source file, got: {toy_path!r}"
155
+ )
156
+ if not os.path.isfile(toy_path):
157
+ raise FileNotFoundError(f"Source file not found: {toy_path!r}")
158
+
159
+ node = _source_to_ast(toy_path)
160
+
161
+ # Derive sibling .toyc path unless the caller overrode it
162
+ if path == "out.toyc": # still the default → replace with sibling
163
+ path = os.path.splitext(os.path.abspath(toy_path))[0] + ".toyc"
164
+
165
+ else:
166
+ # AST-first mode — node_or_path is an AST node
167
+ node = node_or_path
168
+ # path stays as whatever the caller passed ("out.toyc" or explicit)
169
+
170
+ # ── emit bytecode ────────────────────────────────────────────────────
171
+
172
+ instructions: list[Instr] = []
173
+ Compiler._emit(node, instructions)
174
+
175
+ # ── write to disk ────────────────────────────────────────────────────
176
+
177
+ if path is not None:
178
+ if not path.endswith(".toyc"):
179
+ path = os.path.splitext(path)[0] + ".toyc"
180
+ write_bytecode(path, instructions)
181
+
182
+ return instructions
183
+
184
+ @staticmethod
185
+ def _emit(node, out: list) -> None:
186
+ if isinstance(node, Number):
187
+ out.append(Instr("PUSH", node.value))
188
+
189
+ elif isinstance(node, Var):
190
+ out.append(Instr("LOAD", node.name))
191
+
192
+ elif isinstance(node, BinOp):
193
+ Compiler._emit(node.left, out) # push left operand
194
+ Compiler._emit(node.right, out) # push right operand
195
+ op_map = {"+": "ADD", "-": "SUB", "*": "MUL", "/": "DIV"}
196
+ out.append(Instr(op_map[node.op]))
197
+
198
+ else:
199
+ raise TypeError(f"Unknown AST node: {type(node).__name__}")
@@ -0,0 +1,36 @@
1
+ # compiler/evaluator.py
2
+
3
+ from .ast_nodes import Number, Var, BinOp
4
+
5
+ class Evaluator:
6
+ def __init__(self, env=None):
7
+ # env holds variable values e.g {"a": 10, "b": 5}
8
+ self.env = env or {}
9
+
10
+ def eval(self, node):
11
+ # base case — just return the number
12
+ if isinstance(node, Number):
13
+ return node.value
14
+
15
+ # variable lookup
16
+ if isinstance(node, Var):
17
+ if node.name not in self.env:
18
+ raise NameError(f"Undefined variable: {node.name!r}")
19
+ return self.env[node.name]
20
+
21
+ # recursive case — evaluate both sides then apply operator
22
+ if isinstance(node, BinOp):
23
+ left = self.eval(node.left)
24
+ right = self.eval(node.right)
25
+
26
+ if node.op == "+": return left + right
27
+ if node.op == "-": return left - right
28
+ if node.op == "*": return left * right
29
+ if node.op == "/":
30
+ if right == 0:
31
+ raise ZeroDivisionError("Division by zero in expression")
32
+ return left / right
33
+
34
+ raise ValueError(f"Unknown operator: {node.op!r}")
35
+
36
+ raise TypeError(f"Unknown AST node: {type(node)}")
@@ -0,0 +1,6 @@
1
+ # compiler/__init__.py
2
+
3
+ from .flattener import Flattener
4
+
5
+
6
+ __version__ = "0.1.0"
@@ -0,0 +1,56 @@
1
+ # compiler/flattener.py
2
+
3
+ from ..ast_nodes import Number, Var, BinOp
4
+ from .instructions import Instruction, ADD, SUB, MUL, DIV, LOAD, VAR
5
+
6
+ class Flattener:
7
+ def __init__(self):
8
+ self.instructions = []
9
+ self.reg_counter = 0 # next free register
10
+ self.var_map = {} # var name → register index
11
+ self.const_values = [] # constant pool [1.0, 2.5, ...]
12
+
13
+ def new_reg(self):
14
+ """Allocate a new register, return its index."""
15
+ r = self.reg_counter
16
+ self.reg_counter += 1
17
+ return r
18
+
19
+ def flatten(self, node):
20
+ """Recursively flatten an AST node, return the register holding result."""
21
+
22
+ if isinstance(node, Number):
23
+ dest = self.new_reg()
24
+ const_idx = len(self.const_values)
25
+ self.const_values.append(float(node.value))
26
+ self.instructions.append(Instruction(LOAD, dest, const_idx))
27
+ return dest
28
+
29
+ if isinstance(node, Var):
30
+ # each unique variable gets one register
31
+ if node.name not in self.var_map:
32
+ dest = self.new_reg()
33
+ var_idx = len(self.var_map)
34
+ self.var_map[node.name] = (dest, var_idx)
35
+ self.instructions.append(Instruction(VAR, dest, var_idx))
36
+ return self.var_map[node.name][0]
37
+
38
+ if isinstance(node, BinOp):
39
+ left_reg = self.flatten(node.left)
40
+ right_reg = self.flatten(node.right)
41
+ dest = self.new_reg()
42
+
43
+ op = {"+": ADD, "-": SUB, "*": MUL, "/": DIV}[node.op]
44
+ self.instructions.append(Instruction(op, dest, left_reg, right_reg))
45
+ return dest
46
+
47
+ raise TypeError(f"Unknown node: {type(node)}")
48
+
49
+ def get_flat(self):
50
+ """Return flat int list ready for GPU upload.
51
+ Layout: [op, dest, src1, src2, op, dest, src1, src2, ...]
52
+ """
53
+ flat = []
54
+ for instr in self.instructions:
55
+ flat.extend(instr.to_list())
56
+ return flat
@@ -0,0 +1,27 @@
1
+ # compiler/instructions.py
2
+
3
+ # opcodes
4
+ ADD = 0
5
+ SUB = 1
6
+ MUL = 2
7
+ DIV = 3
8
+ LOAD = 4 # load a constant value into a register
9
+ VAR = 5 # load a variable into a register
10
+
11
+ OP_NAMES = {ADD: "ADD", SUB: "SUB", MUL: "MUL",
12
+ DIV: "DIV", LOAD: "LOAD", VAR: "VAR"}
13
+
14
+ class Instruction:
15
+ def __init__(self, op, dest, src1=0, src2=0):
16
+ self.op = op # opcode int
17
+ self.dest = dest # destination register index
18
+ self.src1 = src1 # source register 1 (or value for LOAD)
19
+ self.src2 = src2 # source register 2
20
+
21
+ def __repr__(self):
22
+ name = OP_NAMES.get(self.op, "???")
23
+ return f"{name:4} r{self.dest} r{self.src1} r{self.src2}"
24
+
25
+ def to_list(self):
26
+ """Serialize to flat list of 4 ints for GPU upload."""
27
+ return [self.op, self.dest, self.src1, self.src2]
@@ -0,0 +1,9 @@
1
+ # compiler/gpu/vulkan/__init__.py
2
+ #
3
+ # Re-exports GpuOpengl so callers can do either:
4
+ # from compiler.gpu.vulkan import GpuOpengl
5
+ # from compiler.gpu.vulkan.backend import GpuOpengl
6
+
7
+ from .backend import GpuOpengl
8
+
9
+ __all__ = ["GpuOpengl"]
@@ -0,0 +1,136 @@
1
+ # compiler/gpu/vulkan/backend.py
2
+ #
3
+ # GpuVulkan — Vulkan compute backend for the toy VM.
4
+ #
5
+ # Translates list[Instr] → SPIR-V opcodes → dispatches a Vulkan compute
6
+ # pipeline and reads back the result.
7
+ #
8
+ # Right now the SPIR-V emission and Vulkan dispatch are stubbed with clear
9
+ # TODO markers so the structure is in place and wires up cleanly to vm.py.
10
+ # Fill each stub in as you port your existing vulkan-llm Vulkan engine across.
11
+ #
12
+ # Instruction → SPIR-V opcode mapping (matches compiler.py comments):
13
+ # PUSH → OpConstant
14
+ # LOAD → OpLoad
15
+ # ADD → OpFAdd
16
+ # SUB → OpFSub
17
+ # MUL → OpFMul
18
+ # DIV → OpFDiv
19
+
20
+ from __future__ import annotations
21
+ from typing import Any
22
+
23
+
24
+ class GpuOpengl:
25
+ """Vulkan compute backend.
26
+
27
+ Accepts the same *program* forms as ``Cpu.run()``:
28
+
29
+ * ``list[Instr]`` — already-compiled bytecode, dispatch directly.
30
+ * ``str`` ending in ``.toyc`` — load compiled file, dispatch.
31
+ * ``str`` ending in ``.toy`` — lex + parse + compile in memory, dispatch.
32
+
33
+ The ``silent`` flag mirrors ``Cpu.run()`` — False by default so the result
34
+ is printed automatically, True to suppress printing when you capture the
35
+ return value yourself.
36
+ """
37
+
38
+ @staticmethod
39
+ def run(program, env: dict = None, silent: bool = False) -> Any:
40
+ """
41
+ Compile (if needed), emit SPIR-V, dispatch on GPU, return result.
42
+
43
+ Parameters
44
+ ----------
45
+ program : list[Instr] | str
46
+ Bytecode list, .toyc path, or .toy source path.
47
+ env : dict, optional
48
+ Variable bindings for LOAD instructions {name: value}.
49
+ silent : bool, optional
50
+ False (default) → result is printed before returning.
51
+ True → result returned quietly, no stdout output.
52
+ """
53
+ # Resolve whatever the caller passed into a flat list[Instr].
54
+ # We reuse Cpu._resolve() so file loading / on-the-fly compilation
55
+ # lives in exactly one place.
56
+ #from compiler.vm import Cpu # local import to avoid circular dependency
57
+ #instructions = Cpu._resolve(program)
58
+
59
+ #spirv = GpuVulkan._emit_spirv(instructions, env or {})
60
+ #result = GpuVulkan._dispatch(spirv)
61
+
62
+ #if not silent:
63
+ #print(result)
64
+
65
+ #return result
66
+
67
+ # ── SPIR-V emission ───────────────────────────────────────────────────────
68
+
69
+ @staticmethod
70
+ def _emit_spirv(instructions: list, env: dict) -> bytes:
71
+ """
72
+ Translate list[Instr] into a SPIR-V compute shader binary.
73
+
74
+ Each toy opcode maps to one SPIR-V op:
75
+ PUSH value → OpConstant (f32 constant)
76
+ LOAD name → OpLoad (load from uniform/push-constant)
77
+ ADD → OpFAdd
78
+ SUB → OpFSub
79
+ MUL → OpFMul
80
+ DIV → OpFDiv
81
+
82
+ Returns raw SPIR-V bytes ready to pass to vkCreateShaderModule.
83
+ """
84
+ # TODO: build the SPIR-V word stream here.
85
+ #
86
+ # Suggested approach:
87
+ # 1. Walk `instructions` and track a virtual register stack.
88
+ # 2. Emit OpConstant / OpLoad for PUSH / LOAD.
89
+ # 3. Emit OpFAdd / OpFSub / OpFMul / OpFDiv for arithmetic ops,
90
+ # consuming the top two registers and producing a new one.
91
+ # 4. Emit OpStore to write the final register to the output buffer.
92
+ # 5. Assemble the header (magic, version, bound, schema) and
93
+ # return the whole thing as bytes.
94
+ #
95
+ # Libraries that help:
96
+ # • pyspirv — pure-Python SPIR-V assembler
97
+ # • spirv-cross (via ctypes) — if you prefer C bindings
98
+ # • hand-roll with struct.pack('<I', word) — minimal dependency
99
+
100
+ raise NotImplementedError(
101
+ "GpuVulkan._emit_spirv() is not yet implemented.\n"
102
+ "Fill in the SPIR-V word stream in compiler/gpu/vulkan/backend.py."
103
+ )
104
+
105
+ # ── Vulkan dispatch ───────────────────────────────────────────────────────
106
+
107
+ @staticmethod
108
+ def _dispatch(spirv: bytes) -> Any:
109
+ """
110
+ Create a Vulkan compute pipeline from *spirv*, dispatch it, and
111
+ read back the scalar result from the output buffer.
112
+
113
+ Steps (mirrors a typical compute dispatch):
114
+ 1. vkCreateShaderModule(spirv)
115
+ 2. vkCreateComputePipeline(shader)
116
+ 3. Allocate input / output VkBuffers, upload constants / env vars.
117
+ 4. vkCmdDispatch(1, 1, 1) ← single workgroup for scalar ops
118
+ 5. vkMapMemory → read f32 result → vkUnmapMemory
119
+ 6. Teardown (pipeline, shader module, buffers).
120
+
121
+ Returns the scalar result as a Python float (or int).
122
+ """
123
+ # TODO: wire up your existing Vulkan engine from spy1345a/vulkan-llm.
124
+ #
125
+ # If you're using ctypes bindings to your C++ engine, the call will
126
+ # look roughly like:
127
+ #
128
+ # from compiler.gpu.vulkan._bindings import vulkan_lib
129
+ # result_buf = (ctypes.c_float * 1)()
130
+ # vulkan_lib.dispatch_spirv(spirv, len(spirv), result_buf)
131
+ # return result_buf[0]
132
+
133
+ raise NotImplementedError(
134
+ "GpuVulkan._dispatch() is not yet implemented.\n"
135
+ "Wire up the Vulkan compute dispatch in compiler/gpu/vulkan/backend.py."
136
+ )
@@ -0,0 +1,9 @@
1
+ # compiler/gpu/vulkan/__init__.py
2
+ #
3
+ # Re-exports GpuVulkan so callers can do either:
4
+ # from compiler.gpu.vulkan import GpuVulkan
5
+ # from compiler.gpu.vulkan.backend import GpuVulkan
6
+
7
+ from .backend import GpuVulkan
8
+
9
+ __all__ = ["GpuVulkan"]
@@ -0,0 +1,136 @@
1
+ # compiler/gpu/vulkan/backend.py
2
+ #
3
+ # GpuVulkan — Vulkan compute backend for the toy VM.
4
+ #
5
+ # Translates list[Instr] → SPIR-V opcodes → dispatches a Vulkan compute
6
+ # pipeline and reads back the result.
7
+ #
8
+ # Right now the SPIR-V emission and Vulkan dispatch are stubbed with clear
9
+ # TODO markers so the structure is in place and wires up cleanly to vm.py.
10
+ # Fill each stub in as you port your existing vulkan-llm Vulkan engine across.
11
+ #
12
+ # Instruction → SPIR-V opcode mapping (matches compiler.py comments):
13
+ # PUSH → OpConstant
14
+ # LOAD → OpLoad
15
+ # ADD → OpFAdd
16
+ # SUB → OpFSub
17
+ # MUL → OpFMul
18
+ # DIV → OpFDiv
19
+
20
+ from __future__ import annotations
21
+ from typing import Any
22
+
23
+
24
+ class GpuVulkan:
25
+ """Vulkan compute backend.
26
+
27
+ Accepts the same *program* forms as ``Cpu.run()``:
28
+
29
+ * ``list[Instr]`` — already-compiled bytecode, dispatch directly.
30
+ * ``str`` ending in ``.toyc`` — load compiled file, dispatch.
31
+ * ``str`` ending in ``.toy`` — lex + parse + compile in memory, dispatch.
32
+
33
+ The ``silent`` flag mirrors ``Cpu.run()`` — False by default so the result
34
+ is printed automatically, True to suppress printing when you capture the
35
+ return value yourself.
36
+ """
37
+
38
+ @staticmethod
39
+ def run(program, env: dict = None, silent: bool = False) -> Any:
40
+ """
41
+ Compile (if needed), emit SPIR-V, dispatch on GPU, return result.
42
+
43
+ Parameters
44
+ ----------
45
+ program : list[Instr] | str
46
+ Bytecode list, .toyc path, or .toy source path.
47
+ env : dict, optional
48
+ Variable bindings for LOAD instructions {name: value}.
49
+ silent : bool, optional
50
+ False (default) → result is printed before returning.
51
+ True → result returned quietly, no stdout output.
52
+ """
53
+ # Resolve whatever the caller passed into a flat list[Instr].
54
+ # We reuse Cpu._resolve() so file loading / on-the-fly compilation
55
+ # lives in exactly one place.
56
+ from compiler.vm import Cpu # local import to avoid circular dependency
57
+ instructions = Cpu._resolve(program)
58
+
59
+ spirv = GpuVulkan._emit_spirv(instructions, env or {})
60
+ result = GpuVulkan._dispatch(spirv)
61
+
62
+ if not silent:
63
+ print(result)
64
+
65
+ return result
66
+
67
+ # ── SPIR-V emission ───────────────────────────────────────────────────────
68
+
69
+ @staticmethod
70
+ def _emit_spirv(instructions: list, env: dict) -> bytes:
71
+ """
72
+ Translate list[Instr] into a SPIR-V compute shader binary.
73
+
74
+ Each toy opcode maps to one SPIR-V op:
75
+ PUSH value → OpConstant (f32 constant)
76
+ LOAD name → OpLoad (load from uniform/push-constant)
77
+ ADD → OpFAdd
78
+ SUB → OpFSub
79
+ MUL → OpFMul
80
+ DIV → OpFDiv
81
+
82
+ Returns raw SPIR-V bytes ready to pass to vkCreateShaderModule.
83
+ """
84
+ # TODO: build the SPIR-V word stream here.
85
+ #
86
+ # Suggested approach:
87
+ # 1. Walk `instructions` and track a virtual register stack.
88
+ # 2. Emit OpConstant / OpLoad for PUSH / LOAD.
89
+ # 3. Emit OpFAdd / OpFSub / OpFMul / OpFDiv for arithmetic ops,
90
+ # consuming the top two registers and producing a new one.
91
+ # 4. Emit OpStore to write the final register to the output buffer.
92
+ # 5. Assemble the header (magic, version, bound, schema) and
93
+ # return the whole thing as bytes.
94
+ #
95
+ # Libraries that help:
96
+ # • pyspirv — pure-Python SPIR-V assembler
97
+ # • spirv-cross (via ctypes) — if you prefer C bindings
98
+ # • hand-roll with struct.pack('<I', word) — minimal dependency
99
+
100
+ raise NotImplementedError(
101
+ "GpuVulkan._emit_spirv() is not yet implemented.\n"
102
+ "Fill in the SPIR-V word stream in compiler/gpu/vulkan/backend.py."
103
+ )
104
+
105
+ # ── Vulkan dispatch ───────────────────────────────────────────────────────
106
+
107
+ @staticmethod
108
+ def _dispatch(spirv: bytes) -> Any:
109
+ """
110
+ Create a Vulkan compute pipeline from *spirv*, dispatch it, and
111
+ read back the scalar result from the output buffer.
112
+
113
+ Steps (mirrors a typical compute dispatch):
114
+ 1. vkCreateShaderModule(spirv)
115
+ 2. vkCreateComputePipeline(shader)
116
+ 3. Allocate input / output VkBuffers, upload constants / env vars.
117
+ 4. vkCmdDispatch(1, 1, 1) ← single workgroup for scalar ops
118
+ 5. vkMapMemory → read f32 result → vkUnmapMemory
119
+ 6. Teardown (pipeline, shader module, buffers).
120
+
121
+ Returns the scalar result as a Python float (or int).
122
+ """
123
+ # TODO: wire up your existing Vulkan engine from spy1345a/vulkan-llm.
124
+ #
125
+ # If you're using ctypes bindings to your C++ engine, the call will
126
+ # look roughly like:
127
+ #
128
+ # from compiler.gpu.vulkan._bindings import vulkan_lib
129
+ # result_buf = (ctypes.c_float * 1)()
130
+ # vulkan_lib.dispatch_spirv(spirv, len(spirv), result_buf)
131
+ # return result_buf[0]
132
+
133
+ raise NotImplementedError(
134
+ "GpuVulkan._dispatch() is not yet implemented.\n"
135
+ "Wire up the Vulkan compute dispatch in compiler/gpu/vulkan/backend.py."
136
+ )
@@ -0,0 +1,98 @@
1
+ NUMBER = "NUMBER"
2
+ IDENT = "IDENT"
3
+ PLUS = "PLUS"
4
+ MINUS = "MINUS"
5
+ STAR = "STAR"
6
+ SLASH = "SLASH"
7
+ LPAREN = "LPAREN"
8
+ RPAREN = "RPAREN"
9
+ EOF = "EOF"
10
+
11
+ class Token:
12
+ def __init__(self, type, value):
13
+ self.type = type
14
+ self.value = value
15
+
16
+ def __repr__(self):
17
+ return f"Token({self.type}, {self.value!r})"
18
+
19
+
20
+ class Lexer:
21
+ KEYWORDS = {"kernel", "return"}
22
+
23
+ def __init__(self, text):
24
+ self.text = text
25
+ self.pos = 0
26
+ self.current = text[0] if text else None
27
+
28
+ # ── public API ──────────────────────────────────────────────────────────
29
+ @classmethod
30
+ def tokenize(cls, text):
31
+ """Lex *text* and return a list of Tokens (including the EOF token)."""
32
+ return cls(text)._run()
33
+
34
+ # ── internal helpers ─────────────────────────────────────────────────────
35
+ def _run(self):
36
+ tokens = []
37
+ while True:
38
+ tok = self._next_token()
39
+ tokens.append(tok)
40
+ if tok.type == EOF:
41
+ break
42
+ return tokens
43
+
44
+ def _advance(self):
45
+ self.pos += 1
46
+ self.current = self.text[self.pos] if self.pos < len(self.text) else None
47
+
48
+ def _skip_whitespace(self):
49
+ while self.current and self.current.isspace():
50
+ self._advance()
51
+
52
+ def _read_number(self):
53
+ result = ""
54
+ while self.current and (self.current.isdigit() or self.current == "."):
55
+ result += self.current
56
+ self._advance()
57
+ return Token(NUMBER, float(result) if "." in result else int(result))
58
+
59
+ def _read_ident(self):
60
+ result = ""
61
+ while self.current and (self.current.isalnum() or self.current == "_"):
62
+ result += self.current
63
+ self._advance()
64
+ tok_type = result.upper() if result in self.KEYWORDS else IDENT
65
+ return Token(tok_type, result)
66
+
67
+ _OPS = {"+": PLUS, "-": MINUS, "*": STAR, "/": SLASH,
68
+ "(": LPAREN, ")": RPAREN}
69
+
70
+ def _next_token(self):
71
+ while self.current:
72
+ if self.current.isspace():
73
+ self._skip_whitespace()
74
+ continue
75
+ if self.current.isdigit():
76
+ return self._read_number()
77
+ if self.current.isalpha() or self.current == "_":
78
+ return self._read_ident()
79
+ if self.current in self._OPS:
80
+ tok = Token(self._OPS[self.current], self.current)
81
+ self._advance()
82
+ return tok
83
+ raise SyntaxError(f"Unknown character: {self.current!r}")
84
+ return Token(EOF, None)
85
+
86
+
87
+ # ── test ─────────────────────────────────────────────────────────────────────
88
+ if __name__ == "__main__":
89
+ tests = [
90
+ "a + b * 2",
91
+ "(a + b) * (c - 3)",
92
+ "10.5 / x + y * 2",
93
+ "kernel",
94
+ ]
95
+ for src in tests:
96
+ print(f"\nInput: {src!r}")
97
+ for tok in Lexer.tokenize(src): # ← clean API
98
+ print(f" {tok}")
@@ -0,0 +1,78 @@
1
+ # compiler/parser.py
2
+
3
+ from .lexer import NUMBER, IDENT, PLUS, MINUS, STAR, SLASH, LPAREN, RPAREN, EOF
4
+ from .ast_nodes import Number, Var, BinOp
5
+
6
+ class Parser:
7
+ def __init__(self, tokens):
8
+ self.tokens = tokens
9
+ self.pos = 0
10
+ self.current = tokens[0]
11
+
12
+ # ── public API ───────────────────────────────────────────────────────────
13
+ @classmethod
14
+ def parse(cls, tokens):
15
+ """Parse a token list and return the AST root node."""
16
+ return cls(tokens)._run()
17
+
18
+ # ── internal helpers ─────────────────────────────────────────────────────
19
+ def _run(self):
20
+ node = self._expr()
21
+ self._eat(EOF)
22
+ return node
23
+
24
+ def _advance(self):
25
+ self.pos += 1
26
+ if self.pos < len(self.tokens):
27
+ self.current = self.tokens[self.pos]
28
+
29
+ def _eat(self, type):
30
+ if self.current.type == type:
31
+ tok = self.current
32
+ self._advance()
33
+ return tok
34
+ raise SyntaxError(
35
+ f"Expected {type}, got {self.current.type!r} ({self.current.value!r})"
36
+ )
37
+
38
+ # ── precedence level 3 (highest): numbers, vars, parenthesised expressions
39
+ def _factor(self):
40
+ tok = self.current
41
+
42
+ if tok.type == NUMBER:
43
+ self._advance()
44
+ return Number(tok.value)
45
+
46
+ if tok.type == IDENT:
47
+ self._advance()
48
+ return Var(tok.value)
49
+
50
+ if tok.type == LPAREN:
51
+ self._advance()
52
+ node = self._expr()
53
+ self._eat(RPAREN)
54
+ return node
55
+
56
+ raise SyntaxError(f"Unexpected token: {tok!r}")
57
+
58
+ # ── precedence level 2: * and /
59
+ def _term(self):
60
+ node = self._factor()
61
+
62
+ while self.current.type in (STAR, SLASH):
63
+ op = self.current.value
64
+ self._advance()
65
+ node = BinOp(op, node, self._factor())
66
+
67
+ return node
68
+
69
+ # ── precedence level 1 (lowest): + and -
70
+ def _expr(self):
71
+ node = self._term()
72
+
73
+ while self.current.type in (PLUS, MINUS):
74
+ op = self.current.value
75
+ self._advance()
76
+ node = BinOp(op, node, self._term())
77
+
78
+ return node
toyc-0.1.0/toyc/vm.py ADDED
@@ -0,0 +1,182 @@
1
+ # compiler/vm.py
2
+ #
3
+ # Responsible for:
4
+ # • The Cpu class (execute bytecode on the host CPU)
5
+ # • Input resolution (list[Instr], .toyc path, .toy path) — shared by
6
+ # all backends so file loading lives in one place
7
+ # • Re-exporting GpuVulkan so callers can import both backends from here:
8
+ #
9
+ # from compiler.vm import Cpu, GpuVulkan
10
+ #
11
+ # Calling styles (identical interface for both backends):
12
+ #
13
+ # Cpu.run("script.toy") → lex+parse+compile in memory, run on CPU
14
+ # Cpu.run("script.toyc") → load compiled file, run on CPU
15
+ # Cpu.run(bytecode_list) → run in memory directly
16
+ #
17
+ # GpuVulkan.run("script.toy") → same input forms, dispatches on GPU
18
+ # GpuVulkan.run("script.toyc") → load compiled file, dispatch on GPU
19
+
20
+ import os
21
+ from typing import Any
22
+
23
+ from .compiler import (
24
+ Instr,
25
+ Compiler,
26
+ read_bytecode,
27
+ is_compiled_bytecode,
28
+ )
29
+ from .lexer import Lexer
30
+ from .parser import Parser
31
+
32
+ # Re-export so `from compiler.vm import GpuVulkan` works
33
+ from .gpu.vulkan import GpuVulkan
34
+
35
+ from .gpu.opengl import GpuOpengl
36
+
37
+ __all__ = ["Cpu", "GpuVulkan"]
38
+
39
+
40
+ # ── Cpu: execute bytecode ─────────────────────────────────────────────────────
41
+
42
+ class Cpu:
43
+ """Stack-based virtual machine.
44
+
45
+ ``Cpu.run()`` accepts three forms of *program*:
46
+
47
+ 1. ``list[Instr]``
48
+ Already-compiled bytecode; executed directly, no I/O.
49
+
50
+ 2. ``str`` ending in ``.toyc``
51
+ Load the compiled file from disk and execute it.
52
+ No lexing, parsing, or compiling happens.
53
+
54
+ 3. ``str`` ending in ``.toy``
55
+ Read the source file, lex it, parse it, compile it in memory,
56
+ then execute. Nothing is written to disk.
57
+ """
58
+
59
+ @staticmethod
60
+ def run(program, env: dict = None, silent: bool = False) -> Any:
61
+ """
62
+ Execute *program* and return the top-of-stack result.
63
+
64
+ By default the result is printed to stdout automatically.
65
+ Pass ``silent=True`` when you are capturing the return value yourself
66
+ and do not want it printed as well.
67
+
68
+ Parameters
69
+ ----------
70
+ program : list[Instr] | str
71
+ Bytecode list, a .toyc compiled file path, or a .toy source path.
72
+ env : dict, optional
73
+ Variable bindings available to LOAD instructions {name: value}.
74
+ silent : bool, optional
75
+ False (default) → result is printed before returning.
76
+ True → result is returned quietly, no stdout output.
77
+
78
+ Examples
79
+ --------
80
+ Cpu.run("out.toyc") # prints result automatically
81
+ value = Cpu.run("out.toyc", silent=True) # capture only, no print
82
+ """
83
+ instructions = Cpu._resolve(program)
84
+ result = Cpu._execute(instructions, env or {})
85
+
86
+ if not silent:
87
+ print(result)
88
+
89
+ return result
90
+
91
+ # ── input resolution ──────────────────────────────────────────────────────
92
+
93
+ @staticmethod
94
+ def _resolve(program) -> list:
95
+ """Return a list[Instr] no matter what form *program* arrives in."""
96
+
97
+ # ① Already compiled in memory — use directly
98
+ if isinstance(program, list):
99
+ return program
100
+
101
+ if not isinstance(program, str):
102
+ raise TypeError(
103
+ f"Cpu.run() expects a list[Instr] or a file path str, "
104
+ f"got {type(program).__name__}"
105
+ )
106
+
107
+ path = program
108
+
109
+ # ② .toyc path — load compiled bytecode, skip all compile steps
110
+ if path.endswith(".toyc"):
111
+ if not os.path.isfile(path):
112
+ raise FileNotFoundError(f"Bytecode file not found: {path!r}")
113
+ return read_bytecode(path)
114
+
115
+ # ③ .toy source path — lex → parse → compile in memory, no disk write
116
+ if path.endswith(".toy"):
117
+ if not os.path.isfile(path):
118
+ raise FileNotFoundError(f"Source file not found: {path!r}")
119
+ return Cpu._compile_toy(path)
120
+
121
+ # ④ Unknown extension — peek at magic bytes as a last resort
122
+ if os.path.isfile(path) and is_compiled_bytecode(path):
123
+ return read_bytecode(path)
124
+
125
+ raise ValueError(
126
+ f"Cannot load {path!r}: expected a .toy source or .toyc bytecode file"
127
+ )
128
+
129
+ @staticmethod
130
+ def _compile_toy(toy_path: str) -> list:
131
+ """
132
+ Lex → parse → compile a .toy source file entirely in memory.
133
+ Nothing is written to disk (path=None passed to Compiler.compile).
134
+ """
135
+ with open(toy_path, "r", encoding="utf-8") as f:
136
+ source = f.read()
137
+
138
+ tokens = Lexer.tokenize(source)
139
+ ast = Parser.parse(tokens)
140
+ # path=None → memory only, no .toyc file created
141
+ return Compiler.compile(ast, path=None)
142
+
143
+ # ── core interpreter ──────────────────────────────────────────────────────
144
+
145
+ @staticmethod
146
+ def _execute(instructions: list, env: dict) -> Any:
147
+ stack: list = []
148
+
149
+ for instr in instructions:
150
+ if instr.op == "PUSH":
151
+ stack.append(instr.arg)
152
+
153
+ elif instr.op == "LOAD":
154
+ if instr.arg not in env:
155
+ raise NameError(f"Undefined variable: {instr.arg!r}")
156
+ stack.append(env[instr.arg])
157
+
158
+ elif instr.op == "ADD":
159
+ b, a = stack.pop(), stack.pop()
160
+ stack.append(a + b)
161
+
162
+ elif instr.op == "SUB":
163
+ b, a = stack.pop(), stack.pop()
164
+ stack.append(a - b)
165
+
166
+ elif instr.op == "MUL":
167
+ b, a = stack.pop(), stack.pop()
168
+ stack.append(a * b)
169
+
170
+ elif instr.op == "DIV":
171
+ b, a = stack.pop(), stack.pop()
172
+ if b == 0:
173
+ raise ZeroDivisionError("Division by zero in VM")
174
+ stack.append(a / b)
175
+
176
+ else:
177
+ raise RuntimeError(f"Unknown opcode: {instr.op!r}")
178
+
179
+ if not stack:
180
+ raise RuntimeError("Execution finished with an empty stack")
181
+
182
+ return stack[-1] # top of stack is the result
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: toyc
3
+ Version: 0.1.0
4
+ Summary: A toy compiler with GPU backends (Vulkan + OpenGL)
5
+ Author: spy1345a
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 spy1345a
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
11
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
12
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
14
+ following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all copies or substantial
17
+ portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
20
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
21
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ Project-URL: Homepage, https://github.com/spy1345a/toyc-repo
26
+ Project-URL: Repository, https://github.com/spy1345a/toyc-repo
27
+ Keywords: compiler,gpu,vulkan,opengl,toy
28
+ Classifier: Programming Language :: Python :: 3
29
+ Classifier: License :: OSI Approved :: MIT License
30
+ Classifier: Operating System :: OS Independent
31
+ Classifier: Topic :: Software Development :: Compilers
32
+ Requires-Python: >=3.10
33
+ Description-Content-Type: text/markdown
34
+ Requires-Dist: pyopengl>=3.1
35
+ Requires-Dist: vulkan>=1.3.275.1
36
+ Provides-Extra: vulkan
37
+ Requires-Dist: vulkan; extra == "vulkan"
38
+ Provides-Extra: opengl
39
+ Requires-Dist: PyOpenGL; extra == "opengl"
@@ -0,0 +1,22 @@
1
+ README.md
2
+ pyproject.toml
3
+ toyc/LICENSE
4
+ toyc/__init__.py
5
+ toyc/ast_nodes.py
6
+ toyc/compiler.py
7
+ toyc/evaluator.py
8
+ toyc/lexer.py
9
+ toyc/parser.py
10
+ toyc/vm.py
11
+ toyc.egg-info/PKG-INFO
12
+ toyc.egg-info/SOURCES.txt
13
+ toyc.egg-info/dependency_links.txt
14
+ toyc.egg-info/requires.txt
15
+ toyc.egg-info/top_level.txt
16
+ toyc/gpu/__init__.py
17
+ toyc/gpu/flattener.py
18
+ toyc/gpu/instructions.py
19
+ toyc/gpu/opengl/__init__.py
20
+ toyc/gpu/opengl/backend.py
21
+ toyc/gpu/vulkan/__init__.py
22
+ toyc/gpu/vulkan/backend.py
@@ -0,0 +1,8 @@
1
+ pyopengl>=3.1
2
+ vulkan>=1.3.275.1
3
+
4
+ [opengl]
5
+ PyOpenGL
6
+
7
+ [vulkan]
8
+ vulkan
@@ -0,0 +1 @@
1
+ toyc