Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,8 @@ and publishes no package.
- [Fetch standard](https://fetch.spec.whatwg.org/)
- [Python AST](https://docs.python.org/3/library/ast.html)
- [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/)

The development checkout now provides the [ES module emitter](docs/compiler-emitter.md)
for the approved single-module subset, with selected helpers, source maps and a
versioned artifact manifest. MOD-01 and browser profile integration follow their
explicit dependencies.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,7 @@ Node.js 24 runs the native ECMAScript differential fixture. That test is skipped
when Node is absent locally; CI and release builds require it. Runtime code
needs only Python and the two declared companions. Tests make no external
network requests.

The development [compiler emitter](docs/compiler-emitter.md) produces deterministic
ES modules for the approved single-module Python profile. This API is not part
of the published 0.1.0 distribution.
91 changes: 91 additions & 0 deletions docs/compiler-emitter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Experimental ES module emitter

The development checkout exposes `JavaScript.emitter.compile_source`. It
validates source through the python-browser/1 front end, lowers the immutable IR
and returns a CompiledArtifact without executing input or writing files. The
published 0.1.0 package predates this compiler implementation.

```python
from JavaScript.emitter import compile_source

artifact = compile_source(
"def count(text):\n return len(text)",
filename="counter.py",
)
print(artifact.manifest["application_exports"]) # ['count']
```

`files` is a tuple of immutable OutputFile(path, data, role) values. The caller
can inspect or pass those bytes to the future pocket-python application builder.
The manifest bytes are separate from their own file/hash closure and can be
saved as PYTHON-JAVASCRIPT.json. Every recorded file has exact size, SHA-256 and
role; imports, notices and selected helper dependencies are explicit. Current
single-module output has no static imports. The compiler ABI is
python-browser-esm/1, independently of package and application-profile versions.

The emitted entrypoint is app.mjs by default, with a named `createModule` export:

```javascript
import {createModule} from './app.mjs';
const app = createModule({write: text => console.log(text)});
console.log(app.count('A😀')); // 2n
```

Each factory call initializes a fresh module in Python source order. Top-level
print/call statements execute at that time. Functions are assigned when their
definition executes; immutable literal defaults are created once per definition,
including tuple identity across calls. Function-local unbound values and global
use before definition raise source-located Python errors. Imports/aliases resolve
through the validated JavaScript registry; only Array.of/length are adopted.
The default output sink uses console.log with the print newline removed.

External function arguments accept BigInt, bool, null and native JavaScript
strings. Number is rejected to avoid ambiguous rounded integers. An explicit
`{type:'str', codepoints:[...]}` preserves Python surrogate/code-point distinctions;
`{type:'tuple', items:[...]}` constructs a Python tuple. Cycles, arbitrary host
objects and input depth over 64 fail explicitly. Native string input iterates
Unicode code points at the boundary. Exported strings/tuples retain their frozen
str/codepoints or tuple/items wrappers; they are not JavaScript arrays. The host
protocol adapter will serialize these values separately.

Python integers use BigInt, including signed floor division/modulo and numeric
bool behavior. Strings retain code points, negative indexing, membership and
lexicographic ordering. Tuples retain truthiness, iteration, equality/order and
alias identity. Chained comparisons and boolean operators evaluate each operand
once, in order, with short-circuiting and original operand results.

Print formats supported values using Python spelling and tuple/string repr.
The compiler generates a compact nonprintable-range table from its CPython
Unicode database and records that database version, so a newer JavaScript
Unicode database cannot silently change repr. This is build-time data, not
runtime Python delegation. Raw surrogate text output requires a separately
adopted sink and fails explicitly; escaped surrogate tuple repr is supported.
Integer/string interning identity remains outside the profile. Dynamic values
outside an adopted operation fail with a source-located TypeError rather than
coercing to native JavaScript behavior.

Compiled calls use an explicit depth limit of 128 and report RecursionError
before depending on host stack exhaustion. This is a profile resource limit,
not CPython's configurable recursion limit. Arbitrary compiled applications are
not instruction/memory sandboxed; the independent VM has its own budget model.
Unsupported syntax/imports fail compilation before an artifact is returned.
Ordinary error classes are preserved; argument-count wording is profile-specific.

Source maps use version 3 and original logical filename, lines/columns and
sourcesContent. Runtime errors also carry original location records. Pass
source_map=False to omit map bytes and their sourceMappingURL together. Use a
logical filename when artifacts are intended for distribution; maps intentionally
include the selected source. module_name must be a portable .mjs basename.

Only selected helpers and their declared dependency closure enter the output.
NOTICES.txt records original helper provenance; no third-party interpreter or
runtime is embedded. The repository has no declared code license; this notice
does not choose or grant one. Redistribution licensing remains a release decision.

Run `python -m unittest discover -s tests -p 'test_compiler*.py' -v` with the
installed development checkout and Node 24.19.0. Tests compare CPython and emitted
Node output/errors/evaluation order, the single-module text_metrics application,
precision/Unicode/tuple/default behavior and an independent Node source-map
consumer. Local modules, async browser capabilities and the final browser parity
gate remain MOD-01, ASYNC-01/BROWSER-01 and QA-03. This compiler executes only
ahead-of-time approved source; it does not parse new source in the browser.
7 changes: 3 additions & 4 deletions docs/compiler-language.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Compiler source language contract

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

## First application and accepted source

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ source-include = [
"tests/fixtures/conformance.mjs",
"tests/fixtures/compiler_contract.json",
"docs/compatibility.md",
"docs/compiler*.md",
"scripts/install_distribution.py",
"CHANGELOG.md",
"RELEASING.md",
Expand Down
Loading
Loading