@pineforge/codegen-pyodide 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/glue.py +35 -0
- package/index.mjs +10 -0
- package/package.json +10 -3
- package/pineforge_codegen/codegen/base.py +22 -10
- package/pineforge_codegen-0.7.2.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +1 -1
- package/transpile.worker.mjs +84 -0
- package/pineforge_codegen-0.7.0.tar.gz +0 -0
package/glue.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# CANONICAL GLUE — runtime-equivalent to the body of PY_GLUE in
|
|
2
|
+
# pineforge-app/apps/web/lib/pyodide-transpiler/glue.ts (produces identical
|
|
3
|
+
# transpile_json output). The browser worker and this gate run the same logic,
|
|
4
|
+
# so the gate's parity guarantee reflects shipped behavior.
|
|
5
|
+
# (Phase 3: ship this from the npm package so there is one source of truth.)
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
if "/codegen" not in sys.path:
|
|
10
|
+
sys.path.insert(0, "/codegen")
|
|
11
|
+
|
|
12
|
+
from pineforge_codegen import transpile
|
|
13
|
+
from pineforge_codegen.errors import CompileError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def transpile_json(source: str) -> str:
|
|
17
|
+
try:
|
|
18
|
+
cpp = transpile(source)
|
|
19
|
+
except CompileError as e:
|
|
20
|
+
diags = []
|
|
21
|
+
for d in e.diagnostics:
|
|
22
|
+
loc = d.location
|
|
23
|
+
message = d.message + " — " + d.hint if getattr(d, "hint", None) else d.message
|
|
24
|
+
entry = {
|
|
25
|
+
"line": loc.line if loc else 1,
|
|
26
|
+
"col": loc.col if loc else 1,
|
|
27
|
+
"message": message,
|
|
28
|
+
"severity": getattr(d.level, "value", "error"),
|
|
29
|
+
}
|
|
30
|
+
end_col = getattr(loc, "end_col", None) if loc else None
|
|
31
|
+
if end_col is not None:
|
|
32
|
+
entry["endCol"] = end_col
|
|
33
|
+
diags.append(entry)
|
|
34
|
+
return json.dumps({"ok": False, "error": str(e), "diagnostics": diags})
|
|
35
|
+
return json.dumps({"ok": True, "cpp": cpp})
|
package/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Entry for @pineforge/codegen-pyodide. Resolves the packaged payload paths and
|
|
2
2
|
// metadata so consumers (the app's build + Node oracle/grammar tooling) read
|
|
3
3
|
// everything from this one dependency — no git submodule.
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -18,3 +19,12 @@ export const archivePath = join(HERE, `pineforge_codegen-${release.codegen}.tar.
|
|
|
18
19
|
// so `import pineforge_codegen` resolves (oracle tests, grammar gen).
|
|
19
20
|
export const sourceRoot = HERE;
|
|
20
21
|
export const codegenSourceDir = join(HERE, "pineforge_codegen");
|
|
22
|
+
|
|
23
|
+
// Absolute path to the shipped ESM module worker — consumers copy it into their
|
|
24
|
+
// served /pyodide/ dir and load it via `new Worker(url, { type: "module" })`.
|
|
25
|
+
export const workerPath = join(HERE, "transpile.worker.mjs");
|
|
26
|
+
|
|
27
|
+
// Canonical Pyodide glue source (single source: gate/glue.py) — runPython'd to
|
|
28
|
+
// define transpile_json. The worker embeds this verbatim; exported here too so
|
|
29
|
+
// Node consumers (parity tests) run the exact same glue.
|
|
30
|
+
export const glue = readFileSync(join(HERE, "glue.py"), "utf8");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pineforge/codegen-pyodide",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Gate-validated Pyodide payload for the PineScript v6 -> C++ transpiler: archive (run in Pyodide), unpacked source, introspected tables, and release metadata.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"pineforge_codegen-*.tar.gz",
|
|
15
15
|
"pineforge_codegen/**/*",
|
|
16
16
|
"tables.json",
|
|
17
|
-
"release.json"
|
|
17
|
+
"release.json",
|
|
18
|
+
"transpile.worker.mjs",
|
|
19
|
+
"glue.py"
|
|
18
20
|
],
|
|
19
21
|
"engines": {
|
|
20
22
|
"node": ">=20"
|
|
@@ -22,7 +24,12 @@
|
|
|
22
24
|
"peerDependencies": {
|
|
23
25
|
"pyodide": "314.0.0"
|
|
24
26
|
},
|
|
25
|
-
"keywords": [
|
|
27
|
+
"keywords": [
|
|
28
|
+
"pinescript",
|
|
29
|
+
"transpiler",
|
|
30
|
+
"pyodide",
|
|
31
|
+
"codegen"
|
|
32
|
+
],
|
|
26
33
|
"homepage": "https://github.com/pineforge-4pass/pineforge-codegen-oss",
|
|
27
34
|
"repository": {
|
|
28
35
|
"type": "git",
|
|
@@ -185,19 +185,31 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
185
185
|
# This ensures sub-function series vars get cloned for the parent's call sites.
|
|
186
186
|
func_var_originals: dict[str, list[str]] = {} # func_name -> list of original var names
|
|
187
187
|
|
|
188
|
-
# First, collect all function-scoped series vars (union across all functions)
|
|
189
|
-
|
|
188
|
+
# First, collect all function-scoped series vars (union across all functions).
|
|
189
|
+
# Use an ordered, de-duplicated list (NOT a set): set iteration order is
|
|
190
|
+
# PYTHONHASHSEED-randomized, and this order reaches emitted C++ member
|
|
191
|
+
# declarations via ``orig_names`` -> ``func_var_originals`` ->
|
|
192
|
+
# ``_func_cs_var_remap``. ``ctx.func_series_vars`` is a dict whose VALUES
|
|
193
|
+
# are themselves sets (analyzer stores ``dict[str, set]``), so we must
|
|
194
|
+
# iterate each value in ``sorted`` order to be hash-seed independent.
|
|
195
|
+
all_func_scoped_series: list[str] = []
|
|
190
196
|
for svars in ctx.func_series_vars.values():
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
for sv in sorted(svars):
|
|
198
|
+
if sv not in all_func_scoped_series:
|
|
199
|
+
all_func_scoped_series.append(sv)
|
|
200
|
+
# Also include function-scoped var_members (same ordered-list rationale).
|
|
201
|
+
# ``ctx.func_var_members`` values are lists (already insertion-ordered).
|
|
202
|
+
all_func_scoped_vars: list[str] = []
|
|
194
203
|
for vlist in ctx.func_var_members.values():
|
|
195
204
|
for n, _, _ in vlist:
|
|
196
|
-
all_func_scoped_vars
|
|
205
|
+
if n not in all_func_scoped_vars:
|
|
206
|
+
all_func_scoped_vars.append(n)
|
|
197
207
|
|
|
198
208
|
# For each function with call-site cloning (has TA ranges or is called multiple times),
|
|
199
|
-
# include ALL function-scoped series/var vars that could be used in its body
|
|
200
|
-
|
|
209
|
+
# include ALL function-scoped series/var vars that could be used in its body.
|
|
210
|
+
# Iterate the dict directly (insertion-ordered) rather than ``set(...keys())``,
|
|
211
|
+
# which would randomize the order of emitted clones across hash seeds.
|
|
212
|
+
for fname in ctx.func_call_site_counts:
|
|
201
213
|
total_cs = ctx.func_call_site_counts[fname]
|
|
202
214
|
if total_cs <= 1:
|
|
203
215
|
continue # No cloning needed for single-call-site functions
|
|
@@ -207,9 +219,9 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
207
219
|
for n, _, _ in ctx.func_var_members[fname]:
|
|
208
220
|
if n not in orig_names:
|
|
209
221
|
orig_names.append(n)
|
|
210
|
-
# Include function's own series vars
|
|
222
|
+
# Include function's own series vars (set -> sorted for determinism)
|
|
211
223
|
if fname in ctx.func_series_vars:
|
|
212
|
-
for sv in ctx.func_series_vars[fname]:
|
|
224
|
+
for sv in sorted(ctx.func_series_vars[fname]):
|
|
213
225
|
if sv not in orig_names:
|
|
214
226
|
orig_names.append(sv)
|
|
215
227
|
# Include series vars from sub-functions (they share the same class members)
|
|
Binary file
|
package/release.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"codegen": "0.7.
|
|
2
|
+
"codegen": "0.7.2",
|
|
3
3
|
"pyodide": "314.0.0",
|
|
4
4
|
"python": "3.14.0",
|
|
5
5
|
"emscripten": "emscripten_5_0_3",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "343bba428db5fc925dc0ba459fed888bdf974496333b7d74c27f695afaca3e23"
|
|
7
7
|
}
|
package/tables.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// GENERATED by scripts/build-npm-package.mjs from gate/glue.py — DO NOT EDIT.
|
|
2
|
+
// Canonical client-side Pyodide transpile worker, shipped by
|
|
3
|
+
// @pineforge/codegen-pyodide. Consumers serve it from /pyodide/ and load it via:
|
|
4
|
+
// new Worker("/pyodide/transpile.worker.mjs", { type: "module" })
|
|
5
|
+
// A real ESM module worker (string-URL load bypasses the bundler), so Pyodide
|
|
6
|
+
// 314's module-worker requirement is satisfied. Mirrors the old in-app
|
|
7
|
+
// transpile.worker.ts; protocol is identical (see app protocol.ts).
|
|
8
|
+
import { loadPyodide } from "/pyodide/pyodide.mjs";
|
|
9
|
+
|
|
10
|
+
const GLUE = `# CANONICAL GLUE — runtime-equivalent to the body of PY_GLUE in
|
|
11
|
+
# pineforge-app/apps/web/lib/pyodide-transpiler/glue.ts (produces identical
|
|
12
|
+
# transpile_json output). The browser worker and this gate run the same logic,
|
|
13
|
+
# so the gate's parity guarantee reflects shipped behavior.
|
|
14
|
+
# (Phase 3: ship this from the npm package so there is one source of truth.)
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
if "/codegen" not in sys.path:
|
|
19
|
+
sys.path.insert(0, "/codegen")
|
|
20
|
+
|
|
21
|
+
from pineforge_codegen import transpile
|
|
22
|
+
from pineforge_codegen.errors import CompileError
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def transpile_json(source: str) -> str:
|
|
26
|
+
try:
|
|
27
|
+
cpp = transpile(source)
|
|
28
|
+
except CompileError as e:
|
|
29
|
+
diags = []
|
|
30
|
+
for d in e.diagnostics:
|
|
31
|
+
loc = d.location
|
|
32
|
+
message = d.message + " — " + d.hint if getattr(d, "hint", None) else d.message
|
|
33
|
+
entry = {
|
|
34
|
+
"line": loc.line if loc else 1,
|
|
35
|
+
"col": loc.col if loc else 1,
|
|
36
|
+
"message": message,
|
|
37
|
+
"severity": getattr(d.level, "value", "error"),
|
|
38
|
+
}
|
|
39
|
+
end_col = getattr(loc, "end_col", None) if loc else None
|
|
40
|
+
if end_col is not None:
|
|
41
|
+
entry["endCol"] = end_col
|
|
42
|
+
diags.append(entry)
|
|
43
|
+
return json.dumps({"ok": False, "error": str(e), "diagnostics": diags})
|
|
44
|
+
return json.dumps({"ok": True, "cpp": cpp})
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const post = (m) => self.postMessage(m);
|
|
48
|
+
let transpileJson = null;
|
|
49
|
+
|
|
50
|
+
async function init() {
|
|
51
|
+
try {
|
|
52
|
+
const pyodide = await loadPyodide({ indexURL: "/pyodide/" });
|
|
53
|
+
const manifestRes = await fetch("/pyodide/manifest.json", { cache: "no-cache" });
|
|
54
|
+
if (!manifestRes.ok) throw new Error(`fetch /pyodide/manifest.json: ${manifestRes.status}`);
|
|
55
|
+
const manifest = await manifestRes.json();
|
|
56
|
+
const archiveRes = await fetch(`/pyodide/${manifest.archive}`);
|
|
57
|
+
if (!archiveRes.ok) throw new Error(`fetch /pyodide/${manifest.archive}: ${archiveRes.status}`);
|
|
58
|
+
const buf = await archiveRes.arrayBuffer();
|
|
59
|
+
pyodide.unpackArchive(buf, "gztar", { extractDir: "/codegen" });
|
|
60
|
+
pyodide.runPython(GLUE);
|
|
61
|
+
const fn = pyodide.globals.get("transpile_json");
|
|
62
|
+
transpileJson = (source) => fn(source);
|
|
63
|
+
post({ type: "ready", codegenVersion: manifest.codegenVersion });
|
|
64
|
+
} catch (err) {
|
|
65
|
+
post({ type: "init-error", error: err instanceof Error ? err.message : String(err) });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const initPromise = init();
|
|
70
|
+
|
|
71
|
+
self.onmessage = async (ev) => {
|
|
72
|
+
const { id, source } = ev.data;
|
|
73
|
+
await initPromise;
|
|
74
|
+
if (!transpileJson) {
|
|
75
|
+
post({ type: "crash", id, error: "pyodide failed to initialize" });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const result = JSON.parse(transpileJson(source));
|
|
80
|
+
post({ type: "result", id, result });
|
|
81
|
+
} catch (err) {
|
|
82
|
+
post({ type: "crash", id, error: err instanceof Error ? err.message : String(err) });
|
|
83
|
+
}
|
|
84
|
+
};
|
|
Binary file
|