SpaceLang
SpaceLang is an experimental stack-based language toolkit. It currently contains a Python reference VM, an experimental C++ VM, a small Python-subset frontend, and tools for assembling, disassembling, and encoding SpaceLang programs.
Status: Early development. The frontend and VM implement a subset of Python and the project is not yet a drop-in Python runtime. See the source and tests for the behavior currently supported.
Requirements
- Python 3.10 or newer
- A C++17 compiler and CMake 3.18 or newer to build the native extension
Install
Install the package from a checkout:
git clone https://github.com/DwwLSM/spacelang.git
cd spacelang
python -m pip install .
For development, install the package with its test dependencies in editable mode:
python -m pip install -e ".[dev]"
python -m pytest
Run Python-subset source
Compile supported Python syntax to a SpaceLang program, then execute it with the reference VM:
from spacelang.pyfront import compile_py
from spacelang.vm_py import VM
source = """
def add(a, b):
return a + b
i = 0
while i < 3:
print(add(i, 2))
i = i + 1
"""
program = compile_py(source)
VM(program).run()
Output:
2
3
4
The current frontend handles basic integer, boolean, and string constants,
arithmetic, comparisons, boolean expressions, calls, assignments, supported
imports, if/else, while, simple positional functions, and for loops
over range(...). Sequence indexing is supported for indexable values.
This is still a Python subset, not full Python; features such as classes, exceptions in source code, generators, nested or filtered comprehensions, keyword arguments to user functions, and general iterator protocols are not implemented. Unsupported syntax raises an error rather than being silently compiled.
Run a .sp program
Write assembly mnemonics to a .spasm file, assemble it into the whitespace-only
.sp format, then run it with the reference VM:
example.spasm:
PUSH_INT 2
PUSH_INT 3
OP 1
CALL 0 1
STACK 3
HALT
Run these commands from an installed checkout:
spacelang asm example.spasm example.sp
spacelang disasm example.sp
spacelang run example.sp
The program prints 5. CALL 0 1 calls the print builtin; STACK 3
drops the builtin's return value. To use the source checkout without installing
the command, invoke the CLI as python -m spacelang.main run example.sp.
How whitespace becomes instructions
A .sp file is a stream of integer tokens written as spaces and newlines. The
number of spaces before each newline is one token's value; the newline ends that
token. The parser reads the first token as an opcode, then consumes the number
of argument tokens required by that opcode. For example, PUSH_INT 2 is encoded
as the tokens 7 (the PUSH_INT opcode) and 4 (zig-zag encoded integer 2).
OP 1 is the tokens 5 (OP) and 1 (addition). The VM executes the resulting
instructions; spaces are not executed one at a time.
For the example above, the token values are:
| Assembly | Space-count tokens |
|---|---|
PUSH_INT 2 |
7, 4 |
PUSH_INT 3 |
7, 6 |
OP 1 |
5, 1 |
CALL 0 1 |
1, 0, 1 |
STACK 3 |
6, 3 |
HALT |
29 |
Space instruction table
Every .sp token is the count of spaces before a newline. The first token of
each instruction is its opcode ID; the following tokens are its arguments.
PUSH_INT uses zig-zag encoding for its integer argument. PUSH_STR is
followed by a length and then one code-point token per character. Name IDs refer
to the program's name table. The .name, .label, and .enddef forms are
assembler directives, not VM opcodes.
| ID | Mnemonic | Arguments in .spasm |
Reference VM effect |
|---|---|---|---|
| 0 | NOP |
none | Do nothing. |
| 1 | CALL |
func_id, nargs |
Call a builtin or user function with positional arguments. A builtin's result is pushed. |
| 2 | CALL_KW |
func_id, npos, nkw |
Call a builtin with positional and keyword arguments; user functions are not supported here. |
| 3 | CALL_OBJ |
nargs |
Pop a callable and its positional arguments, call it, and push the result. |
| 4 | IMPORT |
module_id |
Import a supported module and push it. |
| 5 | OP |
operation_id |
Pop two operands, apply the selected binary operation, and push the result. |
| 6 | STACK |
stack_operation_id |
Apply a stack manipulation operation. |
| 7 | PUSH_INT |
integer |
Push an integer; its argument token is zig-zag encoded. |
| 8 | PUSH_STR |
length, codepoint... |
Push a string from the specified number of code points. |
| 9 | LOAD_GLOBAL |
name_id |
Push a global value; an undefined name raises an error. |
| 10 | STORE_GLOBAL |
name_id |
Pop a value and store it as a global. |
| 11 | GET_ATTR |
name_id |
Pop an object and push the named attribute. |
| 12 | CALL_METHOD |
name_id, nargs |
Pop a receiver and arguments, call the named method, and push its result. |
| 13 | INDEX |
reserved |
Pop a key and container, then push container[key]; the argument is currently ignored. |
| 14 | BUILD_LIST |
count |
Pop count values and push a list in their original order. |
| 15 | BUILD_TUPLE |
count |
Pop count values and push a tuple in their original order. |
| 16 | CONCAT |
count |
Pop and concatenate count homogeneous strings, bytes, lists, or tuples. |
| 17 | DEF |
func_id, nargs |
Mark a function entry; execution skips its body when reached in normal flow. |
| 18 | RET |
none | Return the top stack value from the current function. |
| 19 | LOAD_ARG |
index |
Push a function argument by slot index. |
| 20 | LOAD_LOCAL |
slot |
Push a local variable by slot index. |
| 21 | STORE_LOCAL |
slot |
Pop and store a local variable by slot index. |
| 22 | JMP |
target_ip |
Jump to an instruction index. |
| 23 | JZ |
target_ip |
Pop a condition; jump if it is false. |
| 24 | JNZ |
target_ip |
Pop a condition; jump if it is true. |
| 25 | RAISE |
none | Pop an exception value and raise it through the active handler, if any. |
| 26 | TRY |
catch_ip, finally_ip |
Begin a protected region; 0 means that the corresponding handler is absent. |
| 27 | END_TRY |
none | Close a try/catch/finally region and continue its control flow. |
| 28 | SETUP_WITH |
none | Pop a value onto the VM's with-stack; this alone does not implement Python's full with protocol. |
| 29 | HALT |
none | Stop execution; if the stack is non-empty, pop its top as the run result. |
OP operation IDs:
| ID | Operation |
|---|---|
| 1 | Addition (left + right) |
| 2 | Subtraction (left - right) |
| 3 | Multiplication (left * right) |
| 4 | True division (left / right) |
| 5 | Modulo (left % right) |
| 6 | Equal (left == right) |
| 7 | Less than (left < right) |
| 8 | Greater than (left > right) |
| 9 | Not equal (left != right) |
| 10 | Less than or equal (left <= right) |
| 11 | Greater than or equal (left >= right) |
STACK operation IDs (top means the rightmost stack value):
| ID | Operation | Effect |
|---|---|---|
| 1 | DUP |
Duplicate the top value. |
| 2 | SWAP |
Swap the top two values. |
| 3 | DROP |
Remove the top value. |
| 4 | OVER |
Copy the second value to the top. |
| 5 | ROT |
Rotate the top three values from [a, b, c] to [b, c, a]. |
IMPORT module IDs are 0=math, 1=random, 2=os, 3=re, and 4=sys.
Jump targets and name IDs are zero-based. Function IDs of 10000 or greater
identify user-defined functions in the reference VM.
Each individual token is limited to 65,535 spaces. A direct PUSH_INT 100000
would require an argument token of 200,000 spaces because positive integers use
zig-zag encoding, so it cannot be encoded as one token. For a Python-subset
source constant, use --compress-source; for 100000, the compiler can factor
it into smaller constants and multiplication instructions:
large.py:
print(100000)
spacelang compile-py large.py large.sp --compress-source
spacelang run large.sp
This prints 100000. --compress-source makes the integer representable using
multiple small tokens; it does not mean that one very long run of spaces is
executed. .spz is a separate gzip-compressed container for an already encoded
.sp program.
Create a self-copying program
For Python files, quine writes a new file with a small hook that copies that
generated file to a .copy.py sibling when run. The input file is left
unchanged:
from spacelang.quine import quine
quine("example.py", "example_quine.py")
Then run python example_quine.py. The CLI equivalent is:
python -m spacelang.quine example.py example_quine.py
The CLI also supports --in-place --yes; it creates a .bak backup before
replacing the source. The Python API itself rejects in-place operation.
SpaceLang .sp/.spz quines produce a companion .names.json file because
the instruction stream stores name IDs, not their textual names.
Command-line tools
The command-line interface supports Python compilation, assembly,
disassembly, execution with the reference VM, and .sp/.spz compression:
spacelang compile-py example.py example.sp
spacelang run example.sp
spacelang run example.sp --engine cpp
spacelang disasm example.sp
spacelang pack example.sp
spacelang run example.spz
The module form remains available when invoking the source checkout directly:
python -m spacelang.main run example.sp
Compilation enables the safe peephole optimizations and integer constant pool
by default. Pass --no-optimize to compile-py to preserve the frontend IR,
or --compress-source to factorize large integer constants before encoding.
The C++ engine is experimental and is not the default. GCC and Clang builds use
computed-goto instruction dispatch; other compilers use a portable switch
dispatcher. Use the Python engine as the reference for behavior that has not
yet been verified for native parity.
To compare both engines on the same recursive workload after installation:
python -m benchmarks.benchmark_vm --n 25 --repeat 3
The benchmark checks that both engines return the same result and reports the median runtime and speedup. Timings depend on the host and are not a performance guarantee.
Assemble and run instructions
The assembler accepts one mnemonic instruction per line. For example:
from spacelang.asm import assemble
from spacelang.vm_py import VM
program = assemble("""
PUSH_INT 2
PUSH_INT 3
OP 1
HALT
""")
print(VM(program).run())
OP 1 adds the two values on top of the stack. This example prints 5.
Inspect and encode programs
from spacelang.asm import assemble
from spacelang.disasm import disassemble
from spacelang.encoder import encode
from spacelang.lexer import tokenize
from spacelang.parser import parse
program = assemble("PUSH_INT 2\nPUSH_INT 3\nOP 1\nHALT\n")
print(disassemble(program)) # Human-readable instructions
encoded = encode(program) # SpaceLang whitespace encoding
tokens = tokenize(encoded)
decoded_program = parse(tokens)
The package also exports the core Program, Instr, and OP types, along
with assemble, disassemble, encode, parse, and tokenize from
spacelang.
Development
Run the test suite from the repository root:
python -m pytest
The tests cover the current IR, encoding/parser, assembler/disassembler, VM, Python-subset frontend, and native/reference differential behavior.
Render the introduction animation
The Manim scene source is in scene.py. Install the optional animation
dependency (and Manim's required system packages; see the
Manim installation guide):
python -m pip install -e ".[animation]"
Render an English version with subtitles:
manim -ql scene.py SpacelangIntroEnglishWithSubtitle
Use SpacelangIntroChineseWithSubtitle,
SpacelangIntroEnglishWithoutSubtitle, or
SpacelangIntroChineseWithoutSubtitle for the other variants. Manim writes
rendered videos and its generated assets under media/; these are ignored by
Git and can be recreated from scene.py.
Metadata
Release files for spacelanguage 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| spacelanguage-0.1.1.tar.gz | 58.2 kB | Without provenance | Details for spacelanguage-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| spacelanguage-0.1.1-cp313-cp313-macosx_15_0_arm64.whl | CPython 3.13 | CPython 3.13 | macOS 15.0+ ARM64 | Details for spacelanguage-0.1.1-cp313-cp313-macosx_15_0_arm64.whl |
Total release size: 189.2 kB
Release files / spacelanguage-0.1.1.tar.gz
| Download URL | spacelanguage-0.1.1.tar.gz |
|---|---|
| Size | 58.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e22886d1496551c6cc3991d4f079c3755a590775533d2e9fce2c3d31a544fb58
|
|
BLAKE2b-256 checksum How to use checksums |
a1dfc951f52f03e29632bfd8d494f6bf1977aa9e6e5272d57a3e1d61f2c54e5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.3
|
Release files / spacelanguage-0.1.1-cp313-cp313-macosx_15_0_arm64.whl
| Download URL | spacelanguage-0.1.1-cp313-cp313-macosx_15_0_arm64.whl |
|---|---|
| Size | 131.0 kB |
| Tags | CPython 3.13 macOS 15.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
192c27bdfe7d24d6edf34c88af59a97839581c2b9a72c62b02dea3fe7e99c3e4
|
|
BLAKE2b-256 checksum How to use checksums |
165b64065b76972b221014cb4c98b9a66e1eb48d4ffbf91d7b521932d1e56af3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.3
|
Security for spacelanguage 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.