Skip to content

Commit bfa833e

Browse files
committed
Remove old code and move 0.2 README to docs, also add comprehensive API documentation
1 parent f811ac7 commit bfa833e

39 files changed

Lines changed: 606 additions & 4532 deletions

README.md

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,57 @@
1-
# PyLua - Embedded Python Interpreter for Luau
1+
# PyLua Embedded Python Interpreter for Luau
22

3-
PyLua is a Lua module that embeds a Python interpreter, allowing you to run Python code within your Luau scripts. It supports Python 3.12 syntax and below, making it lightweight and focused while still providing comprehensive Python functionality. PyLua is designed to be easy to use, making it ideal for Roblox game modding.
3+
> Notice: For v0.2 docs, see [docs/other/0.2.md](docs/other/0.2.md).
44
5-
> **⚠️ Documentation Notice**: Please read the [REWRITE_PLAN.md](internalDocs/REWRITE_PLAN.md) for details on the upcoming v0.3 rewrite. For v0.2 documentation, see [old/README.md](old/README.md).
5+
PyLua lets you run Python inside Luau (e.g., Roblox). The v0.3 rewrite is a proper interpreter built in Luau with a CPython-inspired design.
66

7-
## 📝 License
7+
## What is it?
88

9-
This repository is under the [`MIT license`](./LICENSE).
9+
- A Python 3.12-and-below interpreter implemented in Luau
10+
- Runs on [Lute] and other Luau-compatible runtimes
11+
- Embeddable API for executing/evaluating Python and sharing values via `globals()`
12+
13+
## Use cases
14+
15+
- Author gameplay logic in Python while running on Luau
16+
- Build modding hooks: expose Luau callbacks to Python scripts
17+
- Teach/prototype Python inside Roblox-like environments
18+
- Explore interpreter architecture (tokens → AST → bytecode → VM)
19+
20+
## How it’s built
21+
22+
Interpreter pipeline:
23+
24+
- Lexer → Parser → AST → Compiler → Bytecode → VM
25+
26+
Key modules (see `src/PyLua/`):
27+
28+
- `lexer.luau`, `parser/` – Python-compliant tokenization and parsing
29+
- `compiler.luau` – compile AST to bytecode
30+
- `vm/` – stack-based virtual machine
31+
- `objects/` – Python object model
32+
- `builtins/` – core built-in functions and types
33+
34+
## Status
35+
36+
- Version: `0.3.0-dev`
37+
- Target: Python 3.12 syntax and below (3.13+ out of scope)
38+
- Roadmap: `internalDocs/REWRITE_PLAN.md`
39+
40+
## Get started
41+
42+
See docs and examples for usage and API details:
43+
44+
- Docs home: `docs/README.md`
45+
- Examples: `docs/examples/`
46+
47+
You can also quickly try an example with Lute from the repo root:
48+
49+
```powershell
50+
lute docs/examples/hello_world.luau
51+
```
52+
53+
## License
54+
55+
MIT — see [`LICENSE`](./LICENSE).
56+
57+
[Lute]: https://github.com/luau-lang/lute

docs/API.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# PyLua API
2+
3+
This guide explains the public API users interact with when embedding Python in Luau. It reflects the current v0.3.0‑dev implementation.
4+
5+
## Importing
6+
7+
From this repository when running with Lute:
8+
9+
```lua
10+
-- From docs/examples/* the relative import is:
11+
local PyLua = require("../../src/PyLua")
12+
13+
-- From the repo root or your own project layout, adjust the path accordingly,
14+
-- e.g. local PyLua = require("src/PyLua").
15+
```
16+
17+
## Creating a runtime
18+
19+
```lua
20+
local py = PyLua.new({
21+
debug = false, -- optional; enables extra diagnostics in some subsystems
22+
timeout = 0, -- optional; planned, not enforced yet in v0.3.0‑dev
23+
maxRecursion = 0, -- optional; planned guard, not enforced yet
24+
builtins = nil, -- optional; override built-ins
25+
})
26+
```
27+
28+
Notes:
29+
30+
- A PyLua runtime holds its own globals and built-ins table.
31+
- The globals table is pre-populated with `__pylua_version__`, `__name__ = "__main__"`, and a few standard dunders.
32+
33+
## Running code
34+
35+
### execute(code)
36+
37+
Run Python statements. Returns no value; exceptions raise Luau errors.
38+
39+
```lua
40+
py:execute([[
41+
x = [1, 2, 3]
42+
y = { 'name': 'test', 'value': 42 }
43+
print("sum:", sum(x))
44+
]])
45+
```
46+
47+
### eval(code) -> any
48+
49+
Evaluate a single Python expression and return its value (converted when reasonable).
50+
51+
```lua
52+
local v = py:eval("1 + 2 * 3") -- 7
53+
```
54+
55+
Edge cases:
56+
57+
- `eval` requires an expression (not statements); use `execute` otherwise.
58+
- Errors are thrown as Luau `error(...)`; use `pcall` to handle them.
59+
60+
### compile(code) -> CodeObject
61+
62+
Compile Python source to a code object (bytecode + pools).
63+
64+
```lua
65+
local co = py:compile("1 + 2")
66+
-- co has fields: constants, names, varnames, bytecode, ...
67+
```
68+
69+
### runBytecode(codeObject) -> any
70+
71+
Execute a previously compiled code object.
72+
73+
```lua
74+
local result = py:runBytecode(co)
75+
```
76+
77+
## Sharing values via globals
78+
79+
Each runtime has a separate global namespace.
80+
81+
```lua
82+
-- Write from Luau, read from Python
83+
py:setGlobal("answer", 42)
84+
py:execute("print(answer)")
85+
86+
-- Or mutate via the table directly
87+
local g = py:globals()
88+
g.threshold = 10
89+
py:execute("print(threshold)")
90+
91+
-- Read back results written in Python
92+
py:execute("result = sum([1, 2, 3])")
93+
print(g.result) -- 6
94+
```
95+
96+
Helpers:
97+
98+
- `py:globals()` returns the globals table
99+
- `py:setGlobal(name, value)` and `py:getGlobal(name)` access single values
100+
101+
Type notes:
102+
103+
- Numbers, booleans, strings, lists/tuples/dicts/sets created in Python are exposed via PyLua’s object model.
104+
- On return from the VM, some primitives are unwrapped for convenience (e.g., `int` → Luau number).
105+
106+
## Capturing output / customizing builtins
107+
108+
`print(...)` is implemented in `src/PyLua/builtins/functions.luau` and supports swapping the writer for tests/tools.
109+
110+
```lua
111+
local Builtins = require("src/PyLua/builtins/functions")
112+
local lines = {}
113+
Builtins.setWriter(function(s) table.insert(lines, s) end)
114+
115+
py:execute("print('hello')")
116+
-- lines now contains { "hello" }
117+
```
118+
119+
Providing a custom builtins table via `PyLua.new({ builtins = ... })` is also possible, but most use cases are covered by adjusting the writer.
120+
121+
## Interop notes
122+
123+
- Globals are the simplest way to bridge data in and out.
124+
- Calling Luau functions from Python is planned but not yet implemented in this build.
125+
- Workaround: call from Luau by evaluating Python expressions that read/write globals.
126+
127+
Example pattern:
128+
129+
```lua
130+
-- Expose a Luau value and read back a result
131+
local g = py:globals()
132+
g.n = 5
133+
py:execute("result = [i*i for i in range(n)]")
134+
print(g.result) -- e.g., [0, 1, 4, 9, 16]
135+
```
136+
137+
## Error handling
138+
139+
Use `pcall` around API calls to catch errors raised from Python execution.
140+
141+
```lua
142+
local ok, err = pcall(function()
143+
py:execute("1/0")
144+
end)
145+
if not ok then
146+
warn("Python error:", err)
147+
end
148+
```
149+
150+
## Feature support and limits
151+
152+
Target: Python 3.12 syntax and a practical core subset.
153+
154+
Highlights supported:
155+
156+
- Statements: assignment, if/elif/else, while/for (with break/continue), def
157+
- Expressions: arithmetic/bitwise, comparisons, calls, attribute/subscript
158+
- Collections: list/tuple/set/dict literals; list comprehensions
159+
- Builtins: `print`, `len`, `type`, `range`, `int/float/str/bool`, `sum/min/max`, `bytes`, `repr/ascii/format`, `isinstance`
160+
161+
Important limitations (v0.3.0‑dev):
162+
163+
- Chained comparisons parsed but not yet compiled
164+
- Dict unpacking in literals not yet compiled
165+
- For-loop assignment targets beyond simple `Name` unsupported
166+
- Function defaults/kwargs are stubbed in function creation
167+
- Exceptions/try-except not implemented
168+
- f-strings tokenized; full runtime formatting in progress
169+
170+
See [LIMITATIONS.md](LIMITATIONS.md) for the up-to-date list.
171+
172+
## Version
173+
174+
The runtime exposes `__pylua_version__` in globals:
175+
176+
```lua
177+
print(py:globals().__pylua_version__)
178+
```

docs/ARCHITECTURE.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Architecture Overview
2+
3+
This document describes the PyLua v0.3 interpreter architecture and how the pieces fit together.
4+
5+
## Pipeline
6+
7+
Python Source → Lexer → Parser → AST → Compiler → Bytecode → VM → Result
8+
9+
- Lexer (`src/PyLua/lexer.luau`): Converts source text into tokens, including INDENT/DEDENT for Python’s significant whitespace.
10+
- Parser (`src/PyLua/parser/*`): Produces a Python-like AST from tokens (expressions, statements, function defs, loops, etc.).
11+
- AST (`src/PyLua/ast/*`): Node shape definitions and visitor utilities.
12+
- Compiler (`src/PyLua/compiler.luau`): Emits a compact Pythonic bytecode sequence from AST.
13+
- Bytecode (`src/PyLua/bytecode/*`): Opcodes and instruction structure used by the VM.
14+
- VM (`src/PyLua/vm/*`): Stack-based interpreter that executes the bytecode, managing frames and scopes.
15+
- Objects (`src/PyLua/objects/*`): Python object model, type registry, attribute lookup, and operators.
16+
- Builtins (`src/PyLua/builtins/*`): Core built-in functions (print, len, type, range, …) and small helpers.
17+
18+
## Directory map
19+
20+
- src/PyLua/init.luau: Public API (Runtime.new, execute, eval, compile, runBytecode, globals)
21+
- src/PyLua/lexer.luau: Python-compliant tokenization
22+
- src/PyLua/parser/: Modular parser (expressions/statements/postfix/…)
23+
- src/PyLua/ast/: AST node types and visitors
24+
- src/PyLua/compiler.luau: AST → bytecode
25+
- src/PyLua/bytecode/: Opcodes and instruction helpers
26+
- src/PyLua/vm/: Interpreter, frames, and operand stack
27+
- src/PyLua/objects/: Base PyObject, collections, functions, etc.
28+
- src/PyLua/builtins/: Built-in functions and exceptions
29+
30+
## Design notes
31+
32+
- CPython-inspired: names, opcodes, and phases mirror CPython where pragmatic.
33+
- Embeddable first: clean API boundary and globals sharing.
34+
- Incremental correctness: aim for Python 3.12 semantics within a practical subset; expand over time.
35+
36+
---
37+
38+
See also: [REWRITE_PLAN.md](../internalDocs/REWRITE_PLAN.md) for detailed milestones and checklists.

docs/AST.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# AST Nodes
2+
3+
The AST mirrors Python shapes at a pragmatic subset. Nodes carry line/column info.
4+
5+
- Base shape: `{ type: string, lineno: number, col_offset: number, ... }`
6+
- Expressions (subset): Constant, Name, Attribute, Subscript, BinOp, UnaryOp, Compare, Call, List, Tuple, Set, Dict, ListComp
7+
- Statements (subset): Expr, Assign, AugAssign, Return, If, While, For, Break, Continue, Pass, FunctionDef, Module
8+
9+
Location fields:
10+
11+
- `lineno`, `col_offset` are set on all nodes produced by the parser
12+
- Some compiler-generated helper nodes (e.g., listcomp transforms) propagate positions where feasible
13+
14+
See `src/PyLua/ast/nodes.luau` for exact type definitions.

docs/BUILTINS.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Built-in Functions
2+
3+
Implemented in `src/PyLua/builtins/functions.luau`. Highlights:
4+
5+
- `print(*args)` — writes space-separated values; can replace writer for testing
6+
- `len(x)` — length with `__len__` or type-specific rules
7+
- `type(x)` — returns a minimal type object: `<class 'name'>`
8+
- `range([start], stop[, step])` — produces an iterable `range` object
9+
- `int(x)`, `float(x)`, `str(x)`, `bool(x)` — conversions
10+
- `repr(x)`, `ascii(x)` — representation helpers
11+
- `format(value, spec?)` — thin wrapper over Lua string.format with Python-like coercions
12+
- `sum(iterable, start=0)`, `min(...)`, `max(...)`
13+
- `bytes(x)` — from string or list of ints
14+
- `isinstance(obj, type|tuple)` — simple type checks with minimal subtype map (e.g., bool ⊂ int)
15+
16+
Notes:
17+
18+
- `print` writer is replaceable via `Functions.setWriter` for tests
19+
- `bytes` returns a proper `bytes` PyObject; VM also constructs bytes from tagged constants

docs/BYTECODE.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Bytecode and Instructions
2+
3+
PyLua uses a small, Python-inspired instruction set. An instruction is `{ opcode: string, arg?: number, lineno?: number }`.
4+
5+
Key groups:
6+
7+
- Stack ops: `POP_TOP`, `ROT_TWO`, `ROT_THREE`, `DUP_TOP`
8+
- Constants and names: `LOAD_CONST`, `LOAD_NAME`, `STORE_NAME`, `LOAD_ATTR`, `STORE_ATTR`, `LOAD_SUBSCR`, `STORE_SUBSCR`
9+
- Binary ops: `BINARY_ADD`, `BINARY_SUBTRACT`, `BINARY_MULTIPLY`, `BINARY_DIVIDE`, `BINARY_MODULO`, `BINARY_POWER`, `BINARY_FLOOR_DIVIDE`, `BINARY_LSHIFT`, `BINARY_RSHIFT`, `BINARY_OR`, `BINARY_XOR`, `BINARY_AND`, `BINARY_MATRIX_MULTIPLY`
10+
- Unary ops: `UNARY_POSITIVE`, `UNARY_NEGATIVE`, `UNARY_NOT`, `UNARY_INVERT`
11+
- Compare: `COMPARE_OP` with arg mapping (0..9) for `<, <=, ==, !=, >, >=, is, is not, in, not in`
12+
- Control flow: `JUMP_FORWARD`, `POP_JUMP_IF_TRUE`, `POP_JUMP_IF_FALSE`, `JUMP_IF_TRUE_OR_POP`, `JUMP_IF_FALSE_OR_POP`, `SETUP_LOOP`, `POP_BLOCK`, `BREAK_LOOP`, `CONTINUE_LOOP`, `RETURN_VALUE`
13+
- Iteration: `GET_ITER`, `FOR_ITER`
14+
- Collections: `BUILD_LIST`, `BUILD_TUPLE`, `BUILD_SET`, `BUILD_MAP`
15+
- Functions: `MAKE_FUNCTION`, `CALL_FUNCTION`
16+
17+
Constants:
18+
19+
- Numbers, strings, bools, None; bytes literals stored as tagged constants to construct at runtime

docs/COMPILER.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Compiler
2+
3+
Compiles AST into a compact Python-like bytecode executed by the VM.
4+
5+
- File: `src/PyLua/compiler.luau`
6+
- Output: CodeObject with fields: `constants`, `names`, `varnames`, `bytecode`, and metadata
7+
8+
## Pools and indices
9+
10+
- Constants: de-duplicated by a serialized key; nil stored as a sentinel `{ __nil = true }`
11+
- Names: de-duplicated; both constants and names use zero-based indices (like CPython); VM adapts to 1-based tables
12+
13+
## Expressions
14+
15+
- Constants → `LOAD_CONST`
16+
- Name (Load/Store) → `LOAD_NAME` / `STORE_NAME`
17+
- Attribute → `LOAD_ATTR`/`STORE_ATTR`
18+
- Subscript → `LOAD_SUBSCR`/`STORE_SUBSCR`
19+
- Binary ops → `BINARY_*` (incl. `FLOOR_DIVIDE`, `MATRIX_MULTIPLY`)
20+
- Unary ops → `UNARY_*`
21+
- Compare → `COMPARE_OP` with small int tag (0..9); currently single comparator only
22+
- Calls → push func then args; `CALL_FUNCTION argc`
23+
- Collections → `BUILD_LIST`/`BUILD_TUPLE`/`BUILD_SET`/`BUILD_MAP`
24+
- List comprehension → compiled into a tiny function created via `MAKE_FUNCTION` then immediately `CALL_FUNCTION`
25+
26+
## Statements
27+
28+
- Expr → evaluate + `POP_TOP`
29+
- Assign / AugAssign → load/store targets; attribute/subscript targets supported
30+
- If/elif/else → conditional with `POP_JUMP_IF_FALSE` and `JUMP_FORWARD` (patched)
31+
- While → `SETUP_LOOP` + test/jumps; supports `break`/`continue` and optional `else`
32+
- For → `GET_ITER` + `FOR_ITER` loop; stores to simple Name targets
33+
- Return → push value or `None` and `RETURN_VALUE`
34+
- FunctionDef → compiles nested CodeObject and creates a function via `MAKE_FUNCTION`, then `STORE_NAME`
35+
- Module → implicit final `return None`
36+
37+
## Jump patching
38+
39+
- `JUMP_FORWARD` is relative; patched to the right offset from next instruction
40+
- Other jumps are absolute instruction indices (converted to 1-based in VM)
41+
42+
## Known gaps
43+
44+
- Chained comparisons not yet emitted (parser may build them)
45+
- Dict unpacking in literals not yet supported
46+
- For-loop assignment targets beyond simple Name are not yet compiled
47+
- Function defaults/kwargs in `MAKE_FUNCTION` are stubbed

0 commit comments

Comments
 (0)