Skip to content

Commit b948382

Browse files
authored
feat(EMIT-01): emit deterministic Python ES modules (#30)
* feat(PARSE-02): add binding-aware compiler front end * feat(EMIT-01): emit deterministic Python ES modules
1 parent 19d9536 commit b948382

9 files changed

Lines changed: 797 additions & 4 deletions

File tree

‎IMPLEMENTATION_PLAN.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,8 @@ and publishes no package.
434434
- [Fetch standard](https://fetch.spec.whatwg.org/)
435435
- [Python AST](https://docs.python.org/3/library/ast.html)
436436
- [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/)
437+
438+
The development checkout now provides the [ES module emitter](docs/compiler-emitter.md)
439+
for the approved single-module subset, with selected helpers, source maps and a
440+
versioned artifact manifest. MOD-01 and browser profile integration follow their
441+
explicit dependencies.

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,7 @@ Node.js 24 runs the native ECMAScript differential fixture. That test is skipped
104104
when Node is absent locally; CI and release builds require it. Runtime code
105105
needs only Python and the two declared companions. Tests make no external
106106
network requests.
107+
108+
The development [compiler emitter](docs/compiler-emitter.md) produces deterministic
109+
ES modules for the approved single-module Python profile. This API is not part
110+
of the published 0.1.0 distribution.

‎docs/compiler-emitter.md‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Experimental ES module emitter
2+
3+
The development checkout exposes `JavaScript.emitter.compile_source`. It
4+
validates source through the python-browser/1 front end, lowers the immutable IR
5+
and returns a CompiledArtifact without executing input or writing files. The
6+
published 0.1.0 package predates this compiler implementation.
7+
8+
```python
9+
from JavaScript.emitter import compile_source
10+
11+
artifact = compile_source(
12+
"def count(text):\n return len(text)",
13+
filename="counter.py",
14+
)
15+
print(artifact.manifest["application_exports"]) # ['count']
16+
```
17+
18+
`files` is a tuple of immutable OutputFile(path, data, role) values. The caller
19+
can inspect or pass those bytes to the future pocket-python application builder.
20+
The manifest bytes are separate from their own file/hash closure and can be
21+
saved as PYTHON-JAVASCRIPT.json. Every recorded file has exact size, SHA-256 and
22+
role; imports, notices and selected helper dependencies are explicit. Current
23+
single-module output has no static imports. The compiler ABI is
24+
python-browser-esm/1, independently of package and application-profile versions.
25+
26+
The emitted entrypoint is app.mjs by default, with a named `createModule` export:
27+
28+
```javascript
29+
import {createModule} from './app.mjs';
30+
const app = createModule({write: text => console.log(text)});
31+
console.log(app.count('A😀')); // 2n
32+
```
33+
34+
Each factory call initializes a fresh module in Python source order. Top-level
35+
print/call statements execute at that time. Functions are assigned when their
36+
definition executes; immutable literal defaults are created once per definition,
37+
including tuple identity across calls. Function-local unbound values and global
38+
use before definition raise source-located Python errors. Imports/aliases resolve
39+
through the validated JavaScript registry; only Array.of/length are adopted.
40+
The default output sink uses console.log with the print newline removed.
41+
42+
External function arguments accept BigInt, bool, null and native JavaScript
43+
strings. Number is rejected to avoid ambiguous rounded integers. An explicit
44+
`{type:'str', codepoints:[...]}` preserves Python surrogate/code-point distinctions;
45+
`{type:'tuple', items:[...]}` constructs a Python tuple. Cycles, arbitrary host
46+
objects and input depth over 64 fail explicitly. Native string input iterates
47+
Unicode code points at the boundary. Exported strings/tuples retain their frozen
48+
str/codepoints or tuple/items wrappers; they are not JavaScript arrays. The host
49+
protocol adapter will serialize these values separately.
50+
51+
Python integers use BigInt, including signed floor division/modulo and numeric
52+
bool behavior. Strings retain code points, negative indexing, membership and
53+
lexicographic ordering. Tuples retain truthiness, iteration, equality/order and
54+
alias identity. Chained comparisons and boolean operators evaluate each operand
55+
once, in order, with short-circuiting and original operand results.
56+
57+
Print formats supported values using Python spelling and tuple/string repr.
58+
The compiler generates a compact nonprintable-range table from its CPython
59+
Unicode database and records that database version, so a newer JavaScript
60+
Unicode database cannot silently change repr. This is build-time data, not
61+
runtime Python delegation. Raw surrogate text output requires a separately
62+
adopted sink and fails explicitly; escaped surrogate tuple repr is supported.
63+
Integer/string interning identity remains outside the profile. Dynamic values
64+
outside an adopted operation fail with a source-located TypeError rather than
65+
coercing to native JavaScript behavior.
66+
67+
Compiled calls use an explicit depth limit of 128 and report RecursionError
68+
before depending on host stack exhaustion. This is a profile resource limit,
69+
not CPython's configurable recursion limit. Arbitrary compiled applications are
70+
not instruction/memory sandboxed; the independent VM has its own budget model.
71+
Unsupported syntax/imports fail compilation before an artifact is returned.
72+
Ordinary error classes are preserved; argument-count wording is profile-specific.
73+
74+
Source maps use version 3 and original logical filename, lines/columns and
75+
sourcesContent. Runtime errors also carry original location records. Pass
76+
source_map=False to omit map bytes and their sourceMappingURL together. Use a
77+
logical filename when artifacts are intended for distribution; maps intentionally
78+
include the selected source. module_name must be a portable .mjs basename.
79+
80+
Only selected helpers and their declared dependency closure enter the output.
81+
NOTICES.txt records original helper provenance; no third-party interpreter or
82+
runtime is embedded. The repository has no declared code license; this notice
83+
does not choose or grant one. Redistribution licensing remains a release decision.
84+
85+
Run `python -m unittest discover -s tests -p 'test_compiler*.py' -v` with the
86+
installed development checkout and Node 24.19.0. Tests compare CPython and emitted
87+
Node output/errors/evaluation order, the single-module text_metrics application,
88+
precision/Unicode/tuple/default behavior and an independent Node source-map
89+
consumer. Local modules, async browser capabilities and the final browser parity
90+
gate remain MOD-01, ASYNC-01/BROWSER-01 and QA-03. This compiler executes only
91+
ahead-of-time approved source; it does not parse new source in the browser.

‎docs/compiler-language.md‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# Compiler source language contract
22

33
PARSE-01 adopts `python-browser/1` as the initial language profile, using Python
4-
3.14 grammar and semantics for ordinary Python constructs. This contract is
5-
future compiler work; the released package still provides Python-side
6-
JavaScript compatibility objects. The reference corpus checks Python behavior,
7-
not successful translation.
4+
3.14 grammar and semantics for ordinary Python constructs. The development checkout implements its front end and
5+
[ES module emitter](compiler-emitter.md). The released 0.1.0 package predates
6+
these APIs and provides Python-side JavaScript compatibility objects.
87

98
## First application and accepted source
109

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ source-include = [
3535
"tests/fixtures/conformance.mjs",
3636
"tests/fixtures/compiler_contract.json",
3737
"docs/compatibility.md",
38+
"docs/compiler*.md",
3839
"scripts/install_distribution.py",
3940
"CHANGELOG.md",
4041
"RELEASING.md",

0 commit comments

Comments
 (0)