From b995d27dcaf7cbb5f5d240ea31d5c02a62603eb4 Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:29:52 -0700 Subject: [PATCH 1/3] test(PARSE-01): adopt the compiler language contract --- IMPLEMENTATION_PLAN.md | 4 + docs/compiler-language.md | 104 ++++++++++++++++++++++++++ tests/fixtures/compiler_contract.json | 86 +++++++++++++++++++++ tests/test_compiler_contract.py | 31 ++++++++ 4 files changed, 225 insertions(+) create mode 100644 docs/compiler-language.md create mode 100644 tests/fixtures/compiler_contract.json create mode 100644 tests/test_compiler_contract.py diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 5643902..2219c45 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -366,6 +366,10 @@ BROWSER-01 uses the pocket-python host contract. QA-03 proves CPython/Node/brows parity; PERF-01 measures output/helpers. This sequence does not require completing Date, RegExp, Map/Set or prototype/descriptor expansion first. +[The adopted compiler language contract](docs/compiler-language.md) specifies the +first syntax/semantic decisions and reference corpus. It does not claim that +translation is implemented yet. + ## Compiler contracts and artifacts Preserve ordinary supported Python truthiness, integers, Unicode, evaluation diff --git a/docs/compiler-language.md b/docs/compiler-language.md new file mode 100644 index 0000000..972de51 --- /dev/null +++ b/docs/compiler-language.md @@ -0,0 +1,104 @@ +# 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. + +## First application and accepted source + +Start with a single-module equivalent of Shimify's text_metrics application: +Unicode character count, explicitly selected ASCII word separators, LF-based +line count and a tuple of Python integer results. MOD-01 later adds its local +helper import and package re-export. The selected input domain is builtin str; +non-string input raises TypeError("text must be str"). String subclass overrides +and arbitrary user objects are outside the first profile. + +| Syntax/API | First adopted contract | +| --- | --- | +| Scalars | None, bool, arbitrary-precision int and Unicode str literals; tuples of supported values. Floats/bytes/complex wait for separate numeric/binary contracts. | +| Bindings | Simple names, immutable module constants, ordinary local assignments and top-level plain functions. Preserve lexical binding identity and evaluation order. | +| Functions | Positional arguments and immutable literal defaults; return, direct calls and recursion. Keyword/variadic calls, nested closures, decorators, annotations and async functions are deferred. | +| Control flow | if/elif/else, for over approved strings/tuples, break/continue, ordinary return and expression statements. while/comprehensions/generators are deferred initially. | +| Operations | Supported scalar arithmetic/comparisons, Python and/or/not, identity on supported values, string membership and supported indexing. Mixed or unknown operand forms need helpers or a source-located rejection. | +| Builtins/errors | len on adopted strings/tuples, print of adopted values, approved isinstance checks and explicit builtin TypeError raising needed by the fixture. No eval/exec, reflection or undeclared I/O. | +| JavaScript API | Resolve explicit imports/aliases through a versioned symbol/method registry. Start with Array.of and length as a binding fixture; other exports/methods need adopted signatures and parity cases before lowering. | +| Imports | Only approved JavaScript registry imports in the single-module profile. Local modules follow MOD-01; Python stdlib/third-party/native imports require explicit future adapters. | + +Every AST form and operation is supported, rejected or deferred explicitly. A +Python-side JavaScript API existing today does not automatically make it +translatable. No Date/RegExp/Map/Set expansion is a prerequisite for this compiler. + +The first operators are integer +, -, *, // and %, unary +/-, boolean and/or/not, +adopted scalar comparisons and string membership. True division, exponentiation +and bitwise operations are deferred. Indexing supports builtin strings/tuples +with integer indices and Python negative-index/error behavior; slicing is +deferred. Identity tests initially cover None/bool singletons and retained +object aliases, not implementation-dependent integer/string interning. + +## Python and JavaScript semantics + +Preserve Python truthiness and short-circuit operand results, rather than native +JavaScript truthiness for containers. Strings count and iterate Unicode code +points, preserving astral characters and combining marks without normalization. +Python integers retain precision beyond 2**53; use deliberate representations +and conversions, not silent Number rounding. Floor division/modulo must match +negative operands and Python errors. Compare values and side effects in the +original order with each operand evaluated once. + +Tuple results remain distinguishable from JavaScript arrays in internal and +transport contracts; implement required Python operations or reject them. Do not +claim general tuple hashing, mixed comparisons or arbitrary object equality. +Explicit JavaScript Array/String/Number/Object values retain their own approved +API semantics. Crossing the two models needs an explicit conversion/signature. + +Defaults are definition-time values, with immutable literal restrictions in the +first profile. A direct call resolves to the actual lexical binding; aliases, +local shadowing and recursion must not be rewritten by spelling alone. Preserve +source-visible guards and errors even when static types guide lowering. + +Runtime boundary values must satisfy the export/input contract. Incompatible +operations raise the adopted Python error or fail validation clearly; the +compiler must not emit superficially equivalent JavaScript. Unsupported async, +classes, reflection, mutable module initialization and dynamic execution remain +source-located diagnostics, not an interpreter/server fallback. + +## Front end and diagnostics + +Use ast -> bind -> validate -> typed IR -> lower -> emit. Never import or execute +application/dependency modules to compile them. Carry original file/line/column +spans and stable binding identities through every pass. Validate imports, +exports, operation signatures and unsupported forms before publishing output. + +Diagnostic families: PJS100 unsupported syntax/feature, PJS110 unresolved or +unsupported binding/import, PJS120 incompatible operation/value contract. +Include code, phase, original path, one-based line and column, and a concrete +message. Reject an entire unsupported compilation without partially publishing +an executable artifact. Do not catch an application exception and retry a +callback or silently alter evaluation order. + +## Output and conformance + +EMIT-01 produces deterministic ES modules, source maps and an explicit helper +closure. Its manifest identifies compiler/language/target versions, source and +output hashes, entrypoint/exports, static module imports, required helpers and +notices. Application profile schema 2 is defined by pocket-python; it must not +be confused with its current MicroPython runtime asset lock. + +Use CPython as the ordinary Python semantics oracle and native Node/browser +observations for explicit JavaScript APIs. tests/fixtures/compiler_contract.json +provides initial positive and rejected/deferred cases; parsing known sources +for the contract does not prove a compiler already exists. PARSE-02 implements +the front end and EMIT-01 must execute emitted output against that corpus. + +MOD-01 adds explicit local-module identity and initialization; ASYNC-01 adds +supported await/Promise/cancellation behavior. BROWSER-01 integrates main-thread +DOM/events and native Fetch through the versioned host protocol; QA-03 verifies +CPython/Node/browser parity and source-map errors. Origin/CORS and worker message +constraints remain explicit. + +This build-time application compiler does not execute newly entered source in +the browser. That is a separate independent-interpreter milestone. Shared +fixtures/host protocols do not make the two engines' IR, bytecode or value models +interchangeable. diff --git a/tests/fixtures/compiler_contract.json b/tests/fixtures/compiler_contract.json new file mode 100644 index 0000000..01491c4 --- /dev/null +++ b/tests/fixtures/compiler_contract.json @@ -0,0 +1,86 @@ +{ + "schema_version": 1, + "profile": "python-browser/1", + "reference": "CPython 3.14", + "cases": [ + { + "id": "large-integer", + "source": "print(9007199254740993 + 1)", + "stdout": "9007199254740994\n" + }, + { + "id": "unicode", + "source": "print(len('A😀é'))", + "stdout": "4\n" + }, + { + "id": "negative-division", + "source": "print(-7 // 3, -7 % 3)", + "stdout": "-3 2\n" + }, + { + "id": "short-circuit", + "source": "print('' or 'fallback', 'left' and 'right')", + "stdout": "fallback right\n" + }, + { + "id": "loop-and-tuple", + "source": "def selected(text):\n count = 0\n for character in text:\n if character == 'a':\n count = count + 1\n return len(text), count\nprint(selected('a😀a'))", + "stdout": "(3, 2)\n" + }, + { + "id": "default", + "source": "def add(value, amount=2):\n return value + amount\nprint(add(3))", + "stdout": "5\n" + }, + { + "id": "alias", + "source": "from JavaScript import Array as A\nprint(A.of(1, 2).length)", + "stdout": "2\n" + } + ], + "boundaries": [ + { + "id": "class", + "classification": "deferred", + "source": "class Example:\n pass", + "diagnostic": "PJS100" + }, + { + "id": "async", + "classification": "deferred", + "source": "async def example():\n return 1", + "diagnostic": "PJS100" + }, + { + "id": "unapproved-import", + "classification": "rejected", + "source": "import os", + "diagnostic": "PJS110" + }, + { + "id": "dynamic-execution", + "classification": "rejected", + "source": "eval('2 + 2')", + "diagnostic": "PJS110" + }, + { + "id": "mutable-global", + "classification": "deferred", + "source": "values = []", + "diagnostic": "PJS100" + }, + { + "id": "shadowed-call", + "classification": "rejected", + "source": "def selected(len):\n return len(1)", + "diagnostic": "PJS120" + }, + { + "id": "keyword-call", + "classification": "deferred", + "source": "def selected(value):\n return value\nselected(value=1)", + "diagnostic": "PJS100" + } + ] +} diff --git a/tests/test_compiler_contract.py b/tests/test_compiler_contract.py new file mode 100644 index 0000000..578b9e8 --- /dev/null +++ b/tests/test_compiler_contract.py @@ -0,0 +1,31 @@ +import ast +import json +import os +from pathlib import Path +import subprocess +import sys +import unittest + + +CORPUS = Path(__file__).parent / "fixtures" / "compiler_contract.json" + + +class CompilerContractTests(unittest.TestCase): + def test_reference_behavior_and_feature_boundaries(self): + corpus = json.loads(CORPUS.read_text(encoding="utf-8")) + entries = corpus["cases"] + corpus["boundaries"] + self.assertEqual(len({entry["id"] for entry in entries}), len(entries)) + for case in corpus["cases"]: + with self.subTest(case=case["id"]): + result = subprocess.run( + [sys.executable, "-I", "-c", case["source"]], + capture_output=True, text=True, encoding="utf-8", timeout=5, + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.replace("\r\n", "\n"), case["stdout"]) + for case in corpus["boundaries"]: + with self.subTest(boundary=case["id"]): + self.assertIn(case["classification"], {"rejected", "deferred"}) + self.assertIn(case["diagnostic"], {"PJS100", "PJS110", "PJS120"}) + ast.parse(case["source"]) From 96ef3d5f8a959bb7b77dc46d2def5ff8df1403a3 Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:30:34 -0700 Subject: [PATCH 2/3] build(PARSE-01): include the compiler reference corpus --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 8361f5c..8d7461b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ module-name = "JavaScript" source-include = [ "tests/test_*.py", "tests/fixtures/conformance.mjs", + "tests/fixtures/compiler_contract.json", "docs/compatibility.md", "scripts/install_distribution.py", "CHANGELOG.md", From 8c1470949ea422dbc5a313e5a91693a2e1bf4bdf Mon Sep 17 00:00:00 2001 From: ellainng <299958882+ellainng@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:31:00 -0700 Subject: [PATCH 3/3] test(PARSE-01): make isolated oracle output UTF-8 --- tests/test_compiler_contract.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_compiler_contract.py b/tests/test_compiler_contract.py index 578b9e8..d1f7285 100644 --- a/tests/test_compiler_contract.py +++ b/tests/test_compiler_contract.py @@ -1,6 +1,5 @@ import ast import json -import os from pathlib import Path import subprocess import sys @@ -18,9 +17,8 @@ def test_reference_behavior_and_feature_boundaries(self): for case in corpus["cases"]: with self.subTest(case=case["id"]): result = subprocess.run( - [sys.executable, "-I", "-c", case["source"]], + [sys.executable, "-I", "-X", "utf8", "-c", case["source"]], capture_output=True, text=True, encoding="utf-8", timeout=5, - env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(result.stdout.replace("\r\n", "\n"), case["stdout"])