@pineforge/codegen-pyodide 1.1.0 → 1.3.0
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/LICENSE +261 -0
- package/README.md +5 -0
- package/glue.py +4 -0
- package/package.json +6 -4
- package/pineforge_codegen/__init__.py +1 -0
- package/pineforge_codegen/codegen/base.py +48 -2
- package/pineforge_codegen/codegen/capabilities.py +319 -0
- package/pineforge_codegen/codegen/checked_settings.py +64 -5
- package/pineforge_codegen/codegen/emit_top.py +5 -3
- package/pineforge_codegen/codegen/helpers.py +7 -0
- package/pineforge_codegen/codegen/order_shapes.py +356 -0
- package/pineforge_codegen/codegen/security.py +54 -5
- package/pineforge_codegen/codegen/tables.py +12 -0
- package/pineforge_codegen/codegen/visit_call.py +61 -0
- package/pineforge_codegen/codegen/visit_expr.py +8 -2
- package/pineforge_codegen/codegen/visit_stmt.py +2 -4
- package/pineforge_codegen/diagnostic_codes.py +341 -0
- package/pineforge_codegen/diagnostics_catalog.json +631 -0
- package/pineforge_codegen/errors.py +23 -0
- package/pineforge_codegen/external_requests.py +16 -0
- package/pineforge_codegen/support_checker.py +19 -10
- package/pineforge_codegen-1.3.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +1 -1
- package/transpile.worker.mjs +4 -0
- package/pineforge_codegen-1.1.0.tar.gz +0 -0
|
@@ -32,6 +32,29 @@ class Diagnostic:
|
|
|
32
32
|
message: str
|
|
33
33
|
hint: str | None = None
|
|
34
34
|
|
|
35
|
+
# The stable code (``PF-E1203`` / ``PF-W0412``) and named arguments of the
|
|
36
|
+
# catalog template the English ``message`` and ``hint`` render from
|
|
37
|
+
# (``diagnostic_codes``, ``diagnostics_catalog.json``). Both are read off
|
|
38
|
+
# the text when first asked for, after the transpile: the text and the
|
|
39
|
+
# C++ never depend on them.
|
|
40
|
+
@property
|
|
41
|
+
def code(self) -> str:
|
|
42
|
+
return self._coded()[0]
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def args(self) -> dict:
|
|
46
|
+
return self._coded()[1]
|
|
47
|
+
|
|
48
|
+
def _coded(self) -> tuple[str, dict]:
|
|
49
|
+
severity = getattr(self.level, "value", self.level)
|
|
50
|
+
key = (severity, self.message, self.hint)
|
|
51
|
+
cached = self.__dict__.get("_pf_coded")
|
|
52
|
+
if cached is None or cached[0] != key:
|
|
53
|
+
from .diagnostic_codes import classify
|
|
54
|
+
cached = (key, classify(severity, self.message, self.hint))
|
|
55
|
+
self.__dict__["_pf_coded"] = cached
|
|
56
|
+
return cached[1]
|
|
57
|
+
|
|
35
58
|
|
|
36
59
|
class CompileError(Exception):
|
|
37
60
|
def __init__(self, diagnostics: list[Diagnostic]):
|
|
@@ -85,6 +85,19 @@ _RECORDED_PARAMS = {
|
|
|
85
85
|
# RequestRef}``: the run stops there only when no data is installed for the
|
|
86
86
|
# request that carries the same ref (``REQUEST_REF_ANNOTATION``).
|
|
87
87
|
UNPINNED_ANNOTATION = "pf_request_unpinned"
|
|
88
|
+
CAPABILITY_UNPINNED_ANNOTATION = "pf_capability_unpinned_requests"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _record_unpinned_capability(program, request):
|
|
92
|
+
notes = program.annotations = dict(program.annotations or {})
|
|
93
|
+
sites = notes.setdefault(CAPABILITY_UNPINNED_ANNOTATION, [])
|
|
94
|
+
sites.append({
|
|
95
|
+
"function": f"request.{request.callee.member}",
|
|
96
|
+
"symbol_node": request.args[0] if request.args else request.kwargs.get("symbol"),
|
|
97
|
+
"tf_node": request.args[1] if len(request.args) > 1 else request.kwargs.get("timeframe"),
|
|
98
|
+
"lookahead_node": request.kwargs.get("lookahead"),
|
|
99
|
+
"gaps_node": request.kwargs.get("gaps"),
|
|
100
|
+
})
|
|
88
101
|
REQUEST_REF_ANNOTATION = "pf_request_ref"
|
|
89
102
|
# On a request whose symbol can select the chart's or another symbol's: it
|
|
90
103
|
# read the chart before it read a feed, and keeps that lowering where no feed
|
|
@@ -785,10 +798,12 @@ def lower_no_data_requests(program: Program) -> Program:
|
|
|
785
798
|
backed.append(node)
|
|
786
799
|
continue
|
|
787
800
|
if lowering == ABSENT_LOWERING:
|
|
801
|
+
_record_unpinned_capability(program, node)
|
|
788
802
|
node.annotations = {**node.annotations, UNPINNED_ANNOTATION: unpinned_message(node)}
|
|
789
803
|
continue
|
|
790
804
|
swaps[id(node)] = _na_of(node, funcs)
|
|
791
805
|
if lowering == "unpinned":
|
|
806
|
+
_record_unpinned_capability(program, node)
|
|
792
807
|
unpinned.append(node)
|
|
793
808
|
if unpinned or backed:
|
|
794
809
|
index = ScriptIndex(program)
|
|
@@ -853,6 +868,7 @@ def unpin_requests(program: Program, reasons: dict[int, str]) -> None:
|
|
|
853
868
|
"results."))
|
|
854
869
|
continue
|
|
855
870
|
lowered = _na_of(request, funcs)
|
|
871
|
+
_record_unpinned_capability(program, request)
|
|
856
872
|
marker = (request.annotations or {}).get(UNPINNED_ANNOTATION)
|
|
857
873
|
if isinstance(marker, dict):
|
|
858
874
|
lowered.annotations = {**(lowered.annotations or {}),
|
|
@@ -2843,8 +2843,9 @@ class SupportChecker:
|
|
|
2843
2843
|
# Bare suffix shorthand — Pine treats "D"/"W"/"M"/"S" as 1<suffix>.
|
|
2844
2844
|
# Accept the same shorthand here.
|
|
2845
2845
|
return None
|
|
2846
|
-
if not digits.
|
|
2847
|
-
# catches "1.5", "-15", "abc",
|
|
2846
|
+
if not digits.isdecimal():
|
|
2847
|
+
# catches "1.5", "-15", "abc", and digits int() does not read
|
|
2848
|
+
# ("²"), etc.
|
|
2848
2849
|
return f"non-integer timeframe magnitude '{digits}'"
|
|
2849
2850
|
n = int(digits)
|
|
2850
2851
|
if n <= 0:
|
|
@@ -2859,15 +2860,11 @@ class SupportChecker:
|
|
|
2859
2860
|
return
|
|
2860
2861
|
if not isinstance(tf_node, StringLiteral):
|
|
2861
2862
|
return
|
|
2862
|
-
|
|
2863
|
-
if
|
|
2863
|
+
refusal = invalid_tf_literal(tf_node.value, fn_label)
|
|
2864
|
+
if refusal is None:
|
|
2864
2865
|
return
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
f"{fn_label}: invalid timeframe literal '{tf_node.value}'. "
|
|
2868
|
-
"Expected Pine TF format like '1', '15', '1H', '1D', '15S'.",
|
|
2869
|
-
hint=err,
|
|
2870
|
-
)
|
|
2866
|
+
message, hint = refusal
|
|
2867
|
+
self._err(tf_node, message, hint=hint)
|
|
2871
2868
|
|
|
2872
2869
|
def _check_request_security_lower_tf_symbol(self, node: FuncCall) -> None:
|
|
2873
2870
|
"""``request.security_lower_tf`` reads the chart's intrabars: the
|
|
@@ -2912,6 +2909,18 @@ class SupportChecker:
|
|
|
2912
2909
|
self._check_tf_literal(tf_node, "request.security_lower_tf")
|
|
2913
2910
|
|
|
2914
2911
|
|
|
2912
|
+
def invalid_tf_literal(tf_str: str, fn_label: str) -> tuple[str, str] | None:
|
|
2913
|
+
"""``(message, hint)`` refusing ``tf_str`` as the timeframe of
|
|
2914
|
+
``fn_label`` (``request.security`` / ``request.security_lower_tf``), or
|
|
2915
|
+
None when it is a Pine timeframe. The codegen refuses a timeframe string
|
|
2916
|
+
it resolves from a constant or a helper parameter with the same text."""
|
|
2917
|
+
err = SupportChecker._validate_pine_tf_literal(tf_str)
|
|
2918
|
+
if err is None:
|
|
2919
|
+
return None
|
|
2920
|
+
return (f"{fn_label}: invalid timeframe literal '{tf_str}'. "
|
|
2921
|
+
"Expected Pine TF format like '1', '15', '1H', '1D', '15S'.", err)
|
|
2922
|
+
|
|
2923
|
+
|
|
2915
2924
|
# ---------------------------------------------------------------------------
|
|
2916
2925
|
# Convenience entry point
|
|
2917
2926
|
# ---------------------------------------------------------------------------
|
|
Binary file
|
package/release.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"codegen": "1.
|
|
2
|
+
"codegen": "1.3.0",
|
|
3
3
|
"pyodide": "314.0.0",
|
|
4
4
|
"python": "3.14.0",
|
|
5
5
|
"emscripten": "emscripten_5_0_3",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "b058ef7c8ded42e7a80ccf551e62985e052d1e306aebf615ab9937544f060839"
|
|
7
7
|
}
|
package/tables.json
CHANGED
package/transpile.worker.mjs
CHANGED
|
@@ -32,6 +32,10 @@ def _diagnostic_entries(diagnostics) -> list:
|
|
|
32
32
|
"col": loc.col if loc else 1,
|
|
33
33
|
"message": message,
|
|
34
34
|
"severity": getattr(d.level, "value", "error"),
|
|
35
|
+
# The stable code and named arguments of the catalog templates the
|
|
36
|
+
# message (and the hint after " — ") render from.
|
|
37
|
+
"code": d.code,
|
|
38
|
+
"args": d.args,
|
|
35
39
|
}
|
|
36
40
|
end_col = getattr(loc, "end_col", None) if loc else None
|
|
37
41
|
if end_col is not None:
|
|
Binary file
|