A tiny Python → JavaScript transpiler
py2js-mini takes a subset of Python, lowers it into a custom intermediate representation, and emits equivalent JavaScript that runs with a small runtime library (pyrt.js). It demonstrates how transpilers work under the hood — from AST parsing → IR lowering → code generation → runtime emulation.
┌────────────┐ ┌────────────┐ ┌────────────┐
Python src → │ AST Parse │ → │ Lower IR │ → │ Emit JS │ → runnable .js
└────────────┘ └────────────┘ └────────────┘
(ast module) (lowering.py) (emit_js.py)
git clone https://github.com/adhqulm/py2js-mini.git
cd py2js-mini
pip install -e .Transpile and run:
py2js examples/hello.py -o out.js
node out.js
# ok 2Or pipe straight to Node:
py2js examples/classes_demo.py | node
# 4 6
# 0 0Write Python like this:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def move(self, dx, dy):
self.x = self.x + dx
self.y = self.y + dy
p = Point(1, 2)
p.move(3, 4)
print(p.x, p.y) # 4 6Run py2js point.py | node and get the same output as python point.py.
| Category | What's included |
|---|---|
| Functions | Positional args, defaults, *args, **kwargs, return values |
| Classes | __init__, methods, single inheritance, super() calls |
| Control flow | if/elif/else, for, while, break, continue, for…else, while…else |
| Exceptions | try/except/finally, raise, except Type as e, multiple handlers |
| Data types | Lists, tuples, dicts, strings with full slicing and indexing |
| Operators | Arithmetic (+ - * / //), comparison (including chained a < b < c), boolean (and or not), membership (in, not in) |
| Strings | upper, lower, split, join, replace, find, startswith, endswith, f-strings |
| Lists | append, pop, concatenation (+), repetition (*), slicing |
| Unpacking | a, b = (1, 2), starred h, *rest, t = [...], splat calls f(*args, **kw) |
| Other | print, len, str, range, with statements, from math import … |
The examples/ directory contains 29 demos. Here are a few highlights:
Exception handling — exc_demo.py
def div(a, b):
if b == 0:
raise ValueError("division by zero")
return a / b
try:
print(div(10, 2))
print(div(3, 0))
except ValueError as e:
print("caught", e)
finally:
print("finally runs")Inheritance & super — classes_v2_demo.py
class A:
def __init__(self, x):
self.x = x
class B(A):
def __init__(self, x, y):
super().__init__(x)
self.y = y
def add(self):
return self.x + self.y
b = B(5, 10)
print(b.add()) # 15Varargs & kwargs — kwargs_demo.py
def greet(greeting, *names, **opts):
for n in names:
print(greeting, n)
greet("Hello", "Alice", "Bob")Every example has a matching golden output in tests/golden/ — all 29 pass.
The transpiler is ~1,300 lines of Python across four modules:
| Module | Lines | Role |
|---|---|---|
ir.py |
209 | Dataclasses defining the intermediate representation (statements, expressions, module) |
lowering.py |
415 | Walks Python's ast and lowers it into IR nodes, desugaring complex patterns along the way |
emit_js.py |
632 | Traverses the IR and emits equivalent JavaScript, managing scope, declarations, and temporaries |
cli.py |
34 | Entry point — orchestrates lowering → emission → bundling with the runtime |
The JavaScript runtime (pyrt.js, 267 lines) provides Python semantics that JavaScript lacks natively: truthiness, floor division, tuple immutability, slicing, iteration helpers, and more.
python -m pytest tests/ -vTo regenerate golden outputs after changing behavior:
python tools/gen_golden.pyThis is an educational demo, not a full Python implementation. Not supported:
- Generators /
yield async/await- List / dict comprehensions
- Lambdas and closures
- Decorators
- Multiple inheritance
- Full standard library (only basic
mathfunctions) - Module system / package imports
MIT — Kristina