spacelanguage 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.
Files changed (70) hide show
  1. spacelanguage-0.1.0/.github/workflows/tests.yml +24 -0
  2. spacelanguage-0.1.0/.gitignore +8 -0
  3. spacelanguage-0.1.0/CMakeLists.txt +13 -0
  4. spacelanguage-0.1.0/PKG-INFO +187 -0
  5. spacelanguage-0.1.0/README.md +180 -0
  6. spacelanguage-0.1.0/benchmarks/__init__.py +1 -0
  7. spacelanguage-0.1.0/benchmarks/benchmark_vm.py +117 -0
  8. spacelanguage-0.1.0/csrc/binding.cpp +11 -0
  9. spacelanguage-0.1.0/csrc/ir.hpp +14 -0
  10. spacelanguage-0.1.0/csrc/value.hpp +126 -0
  11. spacelanguage-0.1.0/csrc/vm.cpp +929 -0
  12. spacelanguage-0.1.0/csrc/vm.hpp +67 -0
  13. spacelanguage-0.1.0/pyproject.toml +20 -0
  14. spacelanguage-0.1.0/spacelang/__init__.py +59 -0
  15. spacelanguage-0.1.0/spacelang/asm.py +255 -0
  16. spacelanguage-0.1.0/spacelang/builtins_py.py +61 -0
  17. spacelanguage-0.1.0/spacelang/compress.py +37 -0
  18. spacelanguage-0.1.0/spacelang/decoder.py +12 -0
  19. spacelanguage-0.1.0/spacelang/disasm.py +67 -0
  20. spacelanguage-0.1.0/spacelang/encoder.py +108 -0
  21. spacelanguage-0.1.0/spacelang/errors.py +63 -0
  22. spacelanguage-0.1.0/spacelang/ir.py +58 -0
  23. spacelanguage-0.1.0/spacelang/lexer.py +38 -0
  24. spacelanguage-0.1.0/spacelang/main.py +131 -0
  25. spacelanguage-0.1.0/spacelang/modules.py +30 -0
  26. spacelanguage-0.1.0/spacelang/names.py +38 -0
  27. spacelanguage-0.1.0/spacelang/parser.py +173 -0
  28. spacelanguage-0.1.0/spacelang/peephole.py +345 -0
  29. spacelanguage-0.1.0/spacelang/pyfront.py +678 -0
  30. spacelanguage-0.1.0/spacelang/quine.py +393 -0
  31. spacelanguage-0.1.0/spacelang/vm_cpp.py +23 -0
  32. spacelanguage-0.1.0/spacelang/vm_py.py +532 -0
  33. spacelanguage-0.1.0/tests/test_0/demo.names.json +11 -0
  34. spacelanguage-0.1.0/tests/test_0/demo.py +16 -0
  35. spacelanguage-0.1.0/tests/test_0/demo.sp +277 -0
  36. spacelanguage-0.1.0/tests/test_0/demo.spasm +133 -0
  37. spacelanguage-0.1.0/tests/test_0/demo_from_spasm.names.json +11 -0
  38. spacelanguage-0.1.0/tests/test_0/demo_from_spasm.sp +277 -0
  39. spacelanguage-0.1.0/tests/test_0/test.py +50 -0
  40. spacelanguage-0.1.0/tests/test_compress.py +24 -0
  41. spacelanguage-0.1.0/tests/test_differential.py +46 -0
  42. spacelanguage-0.1.0/tests/test_m0.py +45 -0
  43. spacelanguage-0.1.0/tests/test_m1.py +118 -0
  44. spacelanguage-0.1.0/tests/test_m2.py +101 -0
  45. spacelanguage-0.1.0/tests/test_main.py +104 -0
  46. spacelanguage-0.1.0/tests/test_peephole.py +99 -0
  47. spacelanguage-0.1.0/tests/test_pyfront.py +144 -0
  48. spacelanguage-0.1.0/tests/test_quine/quine.copy.py +2 -0
  49. spacelanguage-0.1.0/tests/test_quine/quine.names.json +4 -0
  50. spacelanguage-0.1.0/tests/test_quine/quine.py +2 -0
  51. spacelanguage-0.1.0/tests/test_quine/quine.sp +59 -0
  52. spacelanguage-0.1.0/tests/test_quine/quine.spasm +10 -0
  53. spacelanguage-0.1.0/tests/test_quine/quine_from_sp.copy.py +2 -0
  54. spacelanguage-0.1.0/tests/test_quine/quine_from_spasm.copy.names.json +18 -0
  55. spacelanguage-0.1.0/tests/test_quine/quine_from_spasm.copy.py +2 -0
  56. spacelanguage-0.1.0/tests/test_quine/quine_from_spasm.copy.sp +237 -0
  57. spacelanguage-0.1.0/tests/test_quine/quine_from_spasm.names.json +4 -0
  58. spacelanguage-0.1.0/tests/test_quine/quine_from_spasm.sp +59 -0
  59. spacelanguage-0.1.0/tests/test_quine/quine_self.copy.names.json +18 -0
  60. spacelanguage-0.1.0/tests/test_quine/quine_self.copy.py +11 -0
  61. spacelanguage-0.1.0/tests/test_quine/quine_self.copy.sp +237 -0
  62. spacelanguage-0.1.0/tests/test_quine/quine_self.names.json +18 -0
  63. spacelanguage-0.1.0/tests/test_quine/quine_self.py +11 -0
  64. spacelanguage-0.1.0/tests/test_quine/quine_self.sp +237 -0
  65. spacelanguage-0.1.0/tests/test_quine/quine_self.spasm +90 -0
  66. spacelanguage-0.1.0/tests/test_quine/run_sp.py +62 -0
  67. spacelanguage-0.1.0/tests/test_quine/to_sp.py +29 -0
  68. spacelanguage-0.1.0/tests/test_quine.py +201 -0
  69. spacelanguage-0.1.0/tests/test_vm_cpp.py +451 -0
  70. spacelanguage-0.1.0/tests/test_vm_py.py +204 -0
@@ -0,0 +1,24 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ name: Python ${{ matrix.python-version }} / ${{ matrix.os }}
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest, macos-latest, windows-latest]
15
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - run: python -m pip install --upgrade pip
23
+ - run: python -m pip install -e . pytest
24
+ - run: python -m pytest -q
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .venv/
5
+ venv/
6
+ build/
7
+ dist/
8
+ *.egg-info/
@@ -0,0 +1,13 @@
1
+ cmake_minimum_required(VERSION 3.18)
2
+ project(spacelang_cpp LANGUAGES CXX)
3
+
4
+ find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
5
+ find_package(pybind11 CONFIG REQUIRED)
6
+
7
+ pybind11_add_module(_spacelang_cpp MODULE
8
+ csrc/binding.cpp
9
+ csrc/vm.cpp
10
+ )
11
+ target_compile_features(_spacelang_cpp PRIVATE cxx_std_17)
12
+ target_include_directories(_spacelang_cpp PRIVATE csrc)
13
+ install(TARGETS _spacelang_cpp LIBRARY DESTINATION spacelang)
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.2
2
+ Name: spacelanguage
3
+ Version: 0.1.0
4
+ Summary: SpaceLang core package
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ # SpaceLang
9
+
10
+ SpaceLang is an experimental stack-based language toolkit. It currently
11
+ contains a Python reference VM, an experimental C++ VM, a small Python-subset
12
+ frontend, and tools for assembling, disassembling, and encoding SpaceLang
13
+ programs.
14
+
15
+ > **Status:** Early development. The frontend and VM implement a subset of
16
+ > Python and the project is not yet a drop-in Python runtime. See the source and
17
+ > tests for the behavior currently supported.
18
+
19
+ ## Requirements
20
+
21
+ - Python 3.10 or newer
22
+ - A C++17 compiler and CMake 3.18 or newer to build the native extension
23
+
24
+ ## Install
25
+
26
+ Clone the repository and install it in editable mode:
27
+
28
+ ```bash
29
+ git clone https://github.com/DwwLSM/spacelang.git
30
+ cd spacelang
31
+ python -m pip install -e .
32
+ ```
33
+
34
+ For development, install pytest as well:
35
+
36
+ ```bash
37
+ python -m pip install pytest
38
+ python -m pytest
39
+ ```
40
+
41
+ ## Run Python-subset source
42
+
43
+ Compile supported Python syntax to a SpaceLang program, then execute it with
44
+ the reference VM:
45
+
46
+ ```python
47
+ from spacelang.pyfront import compile_py
48
+ from spacelang.vm_py import VM
49
+
50
+ source = """
51
+ def add(a, b):
52
+ return a + b
53
+
54
+ i = 0
55
+ while i < 3:
56
+ print(add(i, 2))
57
+ i = i + 1
58
+ """
59
+
60
+ program = compile_py(source)
61
+ VM(program).run()
62
+ ```
63
+
64
+ Output:
65
+
66
+ ```text
67
+ 2
68
+ 3
69
+ 4
70
+ ```
71
+
72
+ The current frontend handles basic integer, boolean, and string constants,
73
+ arithmetic, comparisons, boolean expressions, calls, assignments, supported
74
+ imports, `if`/`else`, `while`, simple positional functions, and `for` loops
75
+ over `range(...)`. Sequence indexing is supported for indexable values.
76
+
77
+ This is still a Python subset, not full Python; features such as classes,
78
+ exceptions in source code, generators, nested or filtered comprehensions,
79
+ keyword arguments to user functions, and general iterator protocols are not implemented.
80
+ Unsupported syntax raises an error rather than being silently compiled.
81
+
82
+ ## Create a self-copying program
83
+
84
+ For Python files, `quine` writes a new file with a small hook that copies that
85
+ generated file to a `.copy.py` sibling when run. The input file is left
86
+ unchanged:
87
+
88
+ ```python
89
+ from spacelang.quine import quine
90
+
91
+ quine("example.py", "example_quine.py")
92
+ ```
93
+
94
+ Then run `python example_quine.py`. The CLI equivalent is:
95
+
96
+ ```bash
97
+ python -m spacelang.quine example.py example_quine.py
98
+ ```
99
+
100
+ The CLI also supports `--in-place --yes`; it creates a `.bak` backup before
101
+ replacing the source. The Python API itself rejects in-place operation.
102
+ SpaceLang `.sp`/`.spz` quines produce a companion `.names.json` file because
103
+ the instruction stream stores name IDs, not their textual names.
104
+
105
+ ## Command-line tools
106
+
107
+ The command-line interface supports Python compilation, assembly,
108
+ disassembly, execution with the reference VM, and `.sp`/`.spz` compression:
109
+
110
+ ```bash
111
+ python -m spacelang.main compile-py example.py example.sp
112
+ python -m spacelang.main run example.sp
113
+ python -m spacelang.main run example.sp --engine cpp
114
+ python -m spacelang.main disasm example.sp
115
+ python -m spacelang.main pack example.sp
116
+ python -m spacelang.main run example.spz
117
+ ```
118
+
119
+ Compilation enables the safe peephole optimizations and integer constant pool
120
+ by default. Pass `--no-optimize` to `compile-py` to preserve the frontend IR,
121
+ or `--compress-source` to factorize large integer constants before encoding.
122
+ The C++ engine is experimental and is not the default. GCC and Clang builds use
123
+ computed-goto instruction dispatch; other compilers use a portable switch
124
+ dispatcher. Use the Python engine as the reference for behavior that has not
125
+ yet been verified for native parity.
126
+
127
+ To compare both engines on the same recursive workload after installation:
128
+
129
+ ```bash
130
+ python -m benchmarks.benchmark_vm --n 25 --repeat 3
131
+ ```
132
+
133
+ The benchmark checks that both engines return the same result and reports the
134
+ median runtime and speedup. Timings depend on the host and are not a performance
135
+ guarantee.
136
+
137
+ ## Assemble and run instructions
138
+
139
+ The assembler accepts one mnemonic instruction per line. For example:
140
+
141
+ ```python
142
+ from spacelang.asm import assemble
143
+ from spacelang.vm_py import VM
144
+
145
+ program = assemble("""
146
+ PUSH_INT 2
147
+ PUSH_INT 3
148
+ OP 1
149
+ HALT
150
+ """)
151
+
152
+ print(VM(program).run())
153
+ ```
154
+
155
+ `OP 1` adds the two values on top of the stack. This example prints `5`.
156
+
157
+ ## Inspect and encode programs
158
+
159
+ ```python
160
+ from spacelang.asm import assemble
161
+ from spacelang.disasm import disassemble
162
+ from spacelang.encoder import encode
163
+ from spacelang.lexer import tokenize
164
+ from spacelang.parser import parse
165
+
166
+ program = assemble("PUSH_INT 2\nPUSH_INT 3\nOP 1\nHALT\n")
167
+
168
+ print(disassemble(program)) # Human-readable instructions
169
+ encoded = encode(program) # SpaceLang whitespace encoding
170
+ tokens = tokenize(encoded)
171
+ decoded_program = parse(tokens)
172
+ ```
173
+
174
+ The package also exports the core `Program`, `Instr`, and `OP` types, along
175
+ with `assemble`, `disassemble`, `encode`, `parse`, and `tokenize` from
176
+ `spacelang`.
177
+
178
+ ## Development
179
+
180
+ Run the test suite from the repository root:
181
+
182
+ ```bash
183
+ python -m pytest
184
+ ```
185
+
186
+ The tests cover the current IR, encoding/parser, assembler/disassembler, VM,
187
+ Python-subset frontend, and native/reference differential behavior.
@@ -0,0 +1,180 @@
1
+ # SpaceLang
2
+
3
+ SpaceLang is an experimental stack-based language toolkit. It currently
4
+ contains a Python reference VM, an experimental C++ VM, a small Python-subset
5
+ frontend, and tools for assembling, disassembling, and encoding SpaceLang
6
+ programs.
7
+
8
+ > **Status:** Early development. The frontend and VM implement a subset of
9
+ > Python and the project is not yet a drop-in Python runtime. See the source and
10
+ > tests for the behavior currently supported.
11
+
12
+ ## Requirements
13
+
14
+ - Python 3.10 or newer
15
+ - A C++17 compiler and CMake 3.18 or newer to build the native extension
16
+
17
+ ## Install
18
+
19
+ Clone the repository and install it in editable mode:
20
+
21
+ ```bash
22
+ git clone https://github.com/DwwLSM/spacelang.git
23
+ cd spacelang
24
+ python -m pip install -e .
25
+ ```
26
+
27
+ For development, install pytest as well:
28
+
29
+ ```bash
30
+ python -m pip install pytest
31
+ python -m pytest
32
+ ```
33
+
34
+ ## Run Python-subset source
35
+
36
+ Compile supported Python syntax to a SpaceLang program, then execute it with
37
+ the reference VM:
38
+
39
+ ```python
40
+ from spacelang.pyfront import compile_py
41
+ from spacelang.vm_py import VM
42
+
43
+ source = """
44
+ def add(a, b):
45
+ return a + b
46
+
47
+ i = 0
48
+ while i < 3:
49
+ print(add(i, 2))
50
+ i = i + 1
51
+ """
52
+
53
+ program = compile_py(source)
54
+ VM(program).run()
55
+ ```
56
+
57
+ Output:
58
+
59
+ ```text
60
+ 2
61
+ 3
62
+ 4
63
+ ```
64
+
65
+ The current frontend handles basic integer, boolean, and string constants,
66
+ arithmetic, comparisons, boolean expressions, calls, assignments, supported
67
+ imports, `if`/`else`, `while`, simple positional functions, and `for` loops
68
+ over `range(...)`. Sequence indexing is supported for indexable values.
69
+
70
+ This is still a Python subset, not full Python; features such as classes,
71
+ exceptions in source code, generators, nested or filtered comprehensions,
72
+ keyword arguments to user functions, and general iterator protocols are not implemented.
73
+ Unsupported syntax raises an error rather than being silently compiled.
74
+
75
+ ## Create a self-copying program
76
+
77
+ For Python files, `quine` writes a new file with a small hook that copies that
78
+ generated file to a `.copy.py` sibling when run. The input file is left
79
+ unchanged:
80
+
81
+ ```python
82
+ from spacelang.quine import quine
83
+
84
+ quine("example.py", "example_quine.py")
85
+ ```
86
+
87
+ Then run `python example_quine.py`. The CLI equivalent is:
88
+
89
+ ```bash
90
+ python -m spacelang.quine example.py example_quine.py
91
+ ```
92
+
93
+ The CLI also supports `--in-place --yes`; it creates a `.bak` backup before
94
+ replacing the source. The Python API itself rejects in-place operation.
95
+ SpaceLang `.sp`/`.spz` quines produce a companion `.names.json` file because
96
+ the instruction stream stores name IDs, not their textual names.
97
+
98
+ ## Command-line tools
99
+
100
+ The command-line interface supports Python compilation, assembly,
101
+ disassembly, execution with the reference VM, and `.sp`/`.spz` compression:
102
+
103
+ ```bash
104
+ python -m spacelang.main compile-py example.py example.sp
105
+ python -m spacelang.main run example.sp
106
+ python -m spacelang.main run example.sp --engine cpp
107
+ python -m spacelang.main disasm example.sp
108
+ python -m spacelang.main pack example.sp
109
+ python -m spacelang.main run example.spz
110
+ ```
111
+
112
+ Compilation enables the safe peephole optimizations and integer constant pool
113
+ by default. Pass `--no-optimize` to `compile-py` to preserve the frontend IR,
114
+ or `--compress-source` to factorize large integer constants before encoding.
115
+ The C++ engine is experimental and is not the default. GCC and Clang builds use
116
+ computed-goto instruction dispatch; other compilers use a portable switch
117
+ dispatcher. Use the Python engine as the reference for behavior that has not
118
+ yet been verified for native parity.
119
+
120
+ To compare both engines on the same recursive workload after installation:
121
+
122
+ ```bash
123
+ python -m benchmarks.benchmark_vm --n 25 --repeat 3
124
+ ```
125
+
126
+ The benchmark checks that both engines return the same result and reports the
127
+ median runtime and speedup. Timings depend on the host and are not a performance
128
+ guarantee.
129
+
130
+ ## Assemble and run instructions
131
+
132
+ The assembler accepts one mnemonic instruction per line. For example:
133
+
134
+ ```python
135
+ from spacelang.asm import assemble
136
+ from spacelang.vm_py import VM
137
+
138
+ program = assemble("""
139
+ PUSH_INT 2
140
+ PUSH_INT 3
141
+ OP 1
142
+ HALT
143
+ """)
144
+
145
+ print(VM(program).run())
146
+ ```
147
+
148
+ `OP 1` adds the two values on top of the stack. This example prints `5`.
149
+
150
+ ## Inspect and encode programs
151
+
152
+ ```python
153
+ from spacelang.asm import assemble
154
+ from spacelang.disasm import disassemble
155
+ from spacelang.encoder import encode
156
+ from spacelang.lexer import tokenize
157
+ from spacelang.parser import parse
158
+
159
+ program = assemble("PUSH_INT 2\nPUSH_INT 3\nOP 1\nHALT\n")
160
+
161
+ print(disassemble(program)) # Human-readable instructions
162
+ encoded = encode(program) # SpaceLang whitespace encoding
163
+ tokens = tokenize(encoded)
164
+ decoded_program = parse(tokens)
165
+ ```
166
+
167
+ The package also exports the core `Program`, `Instr`, and `OP` types, along
168
+ with `assemble`, `disassemble`, `encode`, `parse`, and `tokenize` from
169
+ `spacelang`.
170
+
171
+ ## Development
172
+
173
+ Run the test suite from the repository root:
174
+
175
+ ```bash
176
+ python -m pytest
177
+ ```
178
+
179
+ The tests cover the current IR, encoding/parser, assembler/disassembler, VM,
180
+ Python-subset frontend, and native/reference differential behavior.
@@ -0,0 +1 @@
1
+ """SpaceLang benchmark utilities."""
@@ -0,0 +1,117 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from contextlib import redirect_stdout
5
+ from io import StringIO
6
+ from statistics import median
7
+ from time import perf_counter
8
+
9
+ from spacelang.pyfront import compile_py
10
+ from spacelang.vm_cpp import VM as CppVM
11
+ from spacelang.vm_py import VM as PythonVM
12
+
13
+
14
+ def benchmark_engines(
15
+ source: str,
16
+ *,
17
+ repeats: int = 7,
18
+ warmup: int = 2,
19
+ result_name: str | None = "result",
20
+ ) -> dict[str, float]:
21
+ """Return median execution times for CPython and both SpaceLang VMs."""
22
+ if repeats < 1 or warmup < 0:
23
+ raise ValueError("repeats must be >= 1 and warmup must be non-negative")
24
+
25
+ python_code = compile(source, "<benchmark>", "exec")
26
+ program = compile_py(source)
27
+ engine_names = ("CPython", "SP Python", "SP C++")
28
+ samples = {name: [] for name in engine_names}
29
+
30
+ def run_once(engine: str) -> tuple[float, str, object]:
31
+ output = StringIO()
32
+ namespace = {"__name__": "__main__"}
33
+ machine = None
34
+ if engine == "SP Python":
35
+ machine = PythonVM(program)
36
+ elif engine == "SP C++":
37
+ machine = CppVM(program)
38
+
39
+ with redirect_stdout(output):
40
+ started = perf_counter()
41
+ if engine == "CPython":
42
+ exec(python_code, namespace, namespace)
43
+ else:
44
+ machine.run()
45
+ elapsed = perf_counter() - started
46
+
47
+ state = namespace if machine is None else machine.globals_
48
+ if result_name is None:
49
+ result = None
50
+ else:
51
+ if result_name not in state:
52
+ raise AssertionError(f"{engine} did not set {result_name!r}")
53
+ result = state[result_name]
54
+ return elapsed, output.getvalue(), result
55
+
56
+ for iteration in range(warmup + repeats):
57
+ offset = iteration % len(engine_names)
58
+ order = engine_names[offset:] + engine_names[:offset]
59
+ measured = {engine: run_once(engine) for engine in order}
60
+ reference_output = measured["CPython"][1]
61
+ reference_result = measured["CPython"][2]
62
+
63
+ for engine, (_, output, result) in measured.items():
64
+ if output != reference_output or type(result) is not type(reference_result) or result != reference_result:
65
+ raise AssertionError(f"{engine} differs from CPython")
66
+
67
+ if iteration >= warmup:
68
+ for engine, (elapsed, _, _) in measured.items():
69
+ samples[engine].append(elapsed)
70
+
71
+ return {engine: median(durations) for engine, durations in samples.items()}
72
+
73
+
74
+ def main() -> None:
75
+ parser = argparse.ArgumentParser(description="Compare CPython and both SpaceLang VM runtimes.")
76
+ parser.add_argument("--n", type=int, default=27, help="Fibonacci input (default: 27)")
77
+ parser.add_argument("--repeat", type=int, default=7, help="Measured runs per engine (default: 7)")
78
+ parser.add_argument("--warmup", type=int, default=2, help="Unmeasured runs per engine (default: 2)")
79
+ args = parser.parse_args()
80
+ if args.n < 0 or args.repeat < 1 or args.warmup < 0:
81
+ parser.error("--n must be non-negative, --repeat >= 1, and --warmup >= 0")
82
+
83
+ source = f"""
84
+ def fib(n):
85
+ if n < 2:
86
+ return n
87
+ return fib(n - 1) + fib(n - 2)
88
+
89
+ result = fib({args.n})
90
+ """
91
+ source = f"""
92
+
93
+ from math import sqrt
94
+ sum = 0
95
+ for i in range(100000):
96
+ sum = sqrt(sum) if sum > 20000000 else sqrt(i) * sqrt(3) * 2 + sum
97
+
98
+ result = sum
99
+
100
+ """
101
+
102
+
103
+ durations = benchmark_engines(source, repeats=args.repeat, warmup=args.warmup)
104
+ baseline = durations["CPython"]
105
+ for name, duration in durations.items():
106
+ comparison = "baseline" if name == "CPython" else (
107
+ f"{baseline / duration:.2f}x faster than CPython"
108
+ if duration < baseline
109
+ else f"{duration / baseline:.2f}x slower than CPython"
110
+ )
111
+ print(f"{name} median: {duration:.6f}s ({comparison})")
112
+ print(f"SP C++ vs SP Python: {durations['SP Python'] / durations['SP C++']:.2f}x faster")
113
+ print("All engines produced matching output and results.")
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()
@@ -0,0 +1,11 @@
1
+ #include <pybind11/pybind11.h>
2
+
3
+ #include "vm.hpp"
4
+
5
+ PYBIND11_MODULE(_spacelang_cpp, module) {
6
+ pybind11::class_<spacelang::VM>(module, "VM")
7
+ .def(pybind11::init<pybind11::object>())
8
+ .def("run", &spacelang::VM::run)
9
+ .def_property_readonly("stack", &spacelang::VM::stack)
10
+ .def_property_readonly("globals", &spacelang::VM::globals);
11
+ }
@@ -0,0 +1,14 @@
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+ #include <vector>
5
+
6
+ namespace spacelang {
7
+
8
+ struct Instr {
9
+ std::uint8_t op = 0;
10
+ std::vector<std::int64_t> args;
11
+ std::int32_t line = 0;
12
+ };
13
+
14
+ } // namespace spacelang
@@ -0,0 +1,126 @@
1
+ #pragma once
2
+
3
+ #include <cstdint>
4
+ #include <utility>
5
+
6
+ #include <Python.h>
7
+ #include <pybind11/pybind11.h>
8
+
9
+ namespace spacelang {
10
+
11
+ namespace py = pybind11;
12
+
13
+ struct Value {
14
+ enum class Tag : std::uint8_t { INT, FLOAT, BOOL, NONE, PYOBJ };
15
+
16
+ union Payload {
17
+ std::int64_t i;
18
+ double f;
19
+ bool b;
20
+ PyObject* obj;
21
+
22
+ Payload() : i(0) {}
23
+ };
24
+
25
+ Tag tag = Tag::NONE;
26
+ Payload data;
27
+
28
+ Value() = default;
29
+ explicit Value(std::int64_t value) : tag(Tag::INT) { data.i = value; }
30
+ explicit Value(double value) : tag(Tag::FLOAT) { data.f = value; }
31
+ explicit Value(bool value) : tag(Tag::BOOL) { data.b = value; }
32
+
33
+ static Value from_python(py::handle value) {
34
+ if (value.is_none()) {
35
+ return Value();
36
+ }
37
+ if (PyBool_Check(value.ptr())) {
38
+ return Value(value.ptr() == Py_True);
39
+ }
40
+ if (PyLong_Check(value.ptr())) {
41
+ const auto integer = PyLong_AsLongLong(value.ptr());
42
+ if (PyErr_Occurred()) {
43
+ PyErr_Clear();
44
+ } else {
45
+ return Value(static_cast<std::int64_t>(integer));
46
+ }
47
+ }
48
+ if (PyFloat_Check(value.ptr())) {
49
+ return Value(PyFloat_AS_DOUBLE(value.ptr()));
50
+ }
51
+ Value result;
52
+ result.tag = Tag::PYOBJ;
53
+ result.data.obj = value.ptr();
54
+ Py_INCREF(result.data.obj);
55
+ return result;
56
+ }
57
+
58
+ Value(const Value& other) : tag(other.tag), data(other.data) {
59
+ if (tag == Tag::PYOBJ) {
60
+ Py_INCREF(data.obj);
61
+ }
62
+ }
63
+
64
+ Value(Value&& other) noexcept : tag(other.tag), data(other.data) {
65
+ other.tag = Tag::NONE;
66
+ }
67
+
68
+ Value& operator=(Value other) noexcept {
69
+ swap(other);
70
+ return *this;
71
+ }
72
+
73
+ ~Value() {
74
+ if (tag == Tag::PYOBJ) {
75
+ Py_DECREF(data.obj);
76
+ }
77
+ }
78
+
79
+ void swap(Value& other) noexcept {
80
+ const auto temporary = data;
81
+ data = other.data;
82
+ other.data = temporary;
83
+ const auto previous_tag = tag;
84
+ tag = other.tag;
85
+ other.tag = previous_tag;
86
+ }
87
+
88
+ py::object to_python() const {
89
+ switch (tag) {
90
+ case Tag::INT:
91
+ return py::int_(data.i);
92
+ case Tag::FLOAT:
93
+ return py::float_(data.f);
94
+ case Tag::BOOL:
95
+ return py::bool_(data.b);
96
+ case Tag::NONE:
97
+ return py::none();
98
+ case Tag::PYOBJ:
99
+ return py::reinterpret_borrow<py::object>(data.obj);
100
+ }
101
+ throw py::type_error("invalid SpaceLang Value tag");
102
+ }
103
+
104
+ bool truthy() const {
105
+ switch (tag) {
106
+ case Tag::INT:
107
+ return data.i != 0;
108
+ case Tag::FLOAT:
109
+ return data.f != 0.0;
110
+ case Tag::BOOL:
111
+ return data.b;
112
+ case Tag::NONE:
113
+ return false;
114
+ case Tag::PYOBJ: {
115
+ const int result = PyObject_IsTrue(data.obj);
116
+ if (result < 0) {
117
+ throw py::error_already_set();
118
+ }
119
+ return result != 0;
120
+ }
121
+ }
122
+ return false;
123
+ }
124
+ };
125
+
126
+ } // namespace spacelang