@pineforge/codegen-pyodide 0.10.4 → 1.0.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/README.md +16 -16
- package/glue.py +24 -16
- package/package.json +1 -1
- package/pineforge_codegen/__init__.py +125 -34
- package/pineforge_codegen/analyzer/__init__.py +2 -0
- package/pineforge_codegen/analyzer/base.py +754 -76
- package/pineforge_codegen/analyzer/call_handlers.py +260 -40
- package/pineforge_codegen/analyzer/contracts.py +37 -0
- package/pineforge_codegen/analyzer/diagnostics.py +30 -4
- package/pineforge_codegen/analyzer/tables.py +49 -8
- package/pineforge_codegen/analyzer/types.py +33 -1
- package/pineforge_codegen/ast_nodes.py +32 -1
- package/pineforge_codegen/block_locals.py +185 -0
- package/pineforge_codegen/builtin_keywords.py +42 -0
- package/pineforge_codegen/codegen/base.py +896 -156
- package/pineforge_codegen/codegen/constant_fold.py +131 -0
- package/pineforge_codegen/codegen/drawing.py +221 -79
- package/pineforge_codegen/codegen/emit_top.py +946 -213
- package/pineforge_codegen/codegen/helpers.py +435 -14
- package/pineforge_codegen/codegen/host_members.py +162 -0
- package/pineforge_codegen/codegen/input.py +252 -85
- package/pineforge_codegen/codegen/security.py +4372 -377
- package/pineforge_codegen/codegen/session_market.py +71 -0
- package/pineforge_codegen/codegen/ta.py +1188 -100
- package/pineforge_codegen/codegen/tables.py +193 -71
- package/pineforge_codegen/codegen/tv_number_format.py +270 -0
- package/pineforge_codegen/codegen/types.py +1882 -78
- package/pineforge_codegen/codegen/visit_call.py +920 -131
- package/pineforge_codegen/codegen/visit_expr.py +738 -57
- package/pineforge_codegen/codegen/visit_stmt.py +595 -49
- package/pineforge_codegen/external_requests.py +877 -0
- package/pineforge_codegen/lexer.py +104 -22
- package/pineforge_codegen/library_inline.py +1304 -0
- package/pineforge_codegen/library_modules.py +126 -0
- package/pineforge_codegen/library_v5.py +683 -0
- package/pineforge_codegen/limits.py +138 -0
- package/pineforge_codegen/method_binding.py +33 -0
- package/pineforge_codegen/parser.py +384 -68
- package/pineforge_codegen/pine_libraries.py +266 -0
- package/pineforge_codegen/pine_spelling.py +216 -0
- package/pineforge_codegen/pragmas.py +64 -10
- package/pineforge_codegen/security_contexts.py +1585 -0
- package/pineforge_codegen/session_reads.py +84 -0
- package/pineforge_codegen/signatures.py +48 -23
- package/pineforge_codegen/support_checker.py +1106 -85
- package/pineforge_codegen-1.0.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +23 -21
- package/transpile.worker.mjs +24 -16
- package/pineforge_codegen-0.10.4.tar.gz +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""A Pine library source parsed as a module.
|
|
2
|
+
|
|
3
|
+
A library is a script that starts with ``library()`` instead of
|
|
4
|
+
``strategy()``. What an importing script can reach is what it ``export``s:
|
|
5
|
+
functions, methods, user-defined types, enums and (since June 2025)
|
|
6
|
+
``export const`` variables. Everything else at its top level is private: the
|
|
7
|
+
helpers and constants the exports use, and example code (plots, inputs) that
|
|
8
|
+
only runs when the library itself is on a chart. A library may import other
|
|
9
|
+
libraries, and keeps its own ``//@version``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
|
|
16
|
+
from .ast_nodes import (
|
|
17
|
+
EnumDecl, FuncDef, ImportStmt, MethodDef, Program, StrategyDecl,
|
|
18
|
+
TupleAssign, TypeDecl, VarDecl,
|
|
19
|
+
)
|
|
20
|
+
from .errors import CompileError, Diagnostic, Level, Phase, SourceLocation
|
|
21
|
+
from .lexer import Lexer
|
|
22
|
+
from .limits import TimeBudget, check_ast_depth, check_source_size
|
|
23
|
+
from .parser import Parser
|
|
24
|
+
|
|
25
|
+
# The Pine versions whose rules the inliner implements (v5 through
|
|
26
|
+
# ``library_v5``; v6 lowers as the script does).
|
|
27
|
+
SUPPORTED_LIBRARY_VERSIONS = (5, 6)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class LibraryModule:
|
|
32
|
+
path: str # ``user/name/version``
|
|
33
|
+
pine_version: int
|
|
34
|
+
program: Program
|
|
35
|
+
title: str | None
|
|
36
|
+
imports: dict[str, ImportStmt] = field(default_factory=dict)
|
|
37
|
+
functions: dict[str, list[FuncDef]] = field(default_factory=dict)
|
|
38
|
+
methods: dict[str, list[MethodDef]] = field(default_factory=dict)
|
|
39
|
+
types: dict[str, TypeDecl] = field(default_factory=dict)
|
|
40
|
+
enums: dict[str, EnumDecl] = field(default_factory=dict)
|
|
41
|
+
# Top-level variables: name -> the VarDecl or TupleAssign binding it.
|
|
42
|
+
globals: dict[str, object] = field(default_factory=dict)
|
|
43
|
+
exports: set[str] = field(default_factory=set)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def name(self) -> str:
|
|
47
|
+
return self.path.split("/")[1]
|
|
48
|
+
|
|
49
|
+
def exported(self, node) -> bool:
|
|
50
|
+
return bool((getattr(node, "annotations", None) or {}).get("exported"))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _module_error(path: str, message: str, loc: SourceLocation | None = None) -> CompileError:
|
|
54
|
+
return CompileError([Diagnostic(
|
|
55
|
+
level=Level.ERROR, phase=Phase.PARSER,
|
|
56
|
+
location=loc or SourceLocation(file=path, line=1, col=1, end_col=1),
|
|
57
|
+
message=message,
|
|
58
|
+
)])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def normalize_library_text(text: str) -> str:
|
|
62
|
+
"""A leading byte-order mark dropped and every line end spelled ``\\n``:
|
|
63
|
+
TradingView ends a line at ``\\r\\n``, ``\\r`` or ``\\n``, and pinned
|
|
64
|
+
library sources keep the bytes pine-facade served (CRLF for some)."""
|
|
65
|
+
if text.startswith(""):
|
|
66
|
+
text = text[1:]
|
|
67
|
+
return text.replace("\r\n", "\n").replace("\r", "\n")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def parse_library_module(path: str, text: str, *,
|
|
71
|
+
budget: TimeBudget | None = None) -> LibraryModule:
|
|
72
|
+
"""Parse ``text``, the source of library ``path`` (``user/name/version``)."""
|
|
73
|
+
text = normalize_library_text(text)
|
|
74
|
+
check_source_size(text, path)
|
|
75
|
+
tokens = Lexer(text, filename=path, budget=budget).tokenize()
|
|
76
|
+
program = Parser(tokens, source=text, filename=path, budget=budget,
|
|
77
|
+
library=True).parse()
|
|
78
|
+
check_ast_depth(program, path)
|
|
79
|
+
if program.version not in SUPPORTED_LIBRARY_VERSIONS:
|
|
80
|
+
found = ("no //@version directive" if program.version is None
|
|
81
|
+
else f"//@version={program.version}")
|
|
82
|
+
raise _module_error(
|
|
83
|
+
path, f"library '{path}' has {found}; PineForge inlines v5 and v6 "
|
|
84
|
+
"libraries only")
|
|
85
|
+
decls = [stmt for stmt in program.body if isinstance(stmt, StrategyDecl)]
|
|
86
|
+
kinds = [(d.annotations or {}).get("decl_kind") for d in decls]
|
|
87
|
+
if kinds != ["library"]:
|
|
88
|
+
found = ", ".join(f"{k}()" for k in kinds) or "no declaration"
|
|
89
|
+
raise _module_error(
|
|
90
|
+
path, f"'{path}' is not a library: it has {found}, not one library() "
|
|
91
|
+
"declaration", decls[0].loc if decls else None)
|
|
92
|
+
title = None
|
|
93
|
+
decl = decls[0]
|
|
94
|
+
if decl.args and hasattr(decl.args[0], "value") and isinstance(decl.args[0].value, str):
|
|
95
|
+
title = decl.args[0].value
|
|
96
|
+
elif "title" in decl.kwargs and isinstance(getattr(decl.kwargs["title"], "value", None), str):
|
|
97
|
+
title = decl.kwargs["title"].value
|
|
98
|
+
module = LibraryModule(path=path, pine_version=program.version,
|
|
99
|
+
program=program, title=title)
|
|
100
|
+
for stmt in program.body:
|
|
101
|
+
if isinstance(stmt, ImportStmt):
|
|
102
|
+
alias = stmt.alias or stmt.name
|
|
103
|
+
if stmt.version is None or alias is None:
|
|
104
|
+
raise _module_error(
|
|
105
|
+
path, f"library '{path}' has a malformed import: "
|
|
106
|
+
f"'{stmt.path}'", stmt.loc)
|
|
107
|
+
module.imports[alias] = stmt
|
|
108
|
+
elif isinstance(stmt, FuncDef):
|
|
109
|
+
module.functions.setdefault(stmt.name, []).append(stmt)
|
|
110
|
+
elif isinstance(stmt, MethodDef):
|
|
111
|
+
module.methods.setdefault(stmt.name, []).append(stmt)
|
|
112
|
+
elif isinstance(stmt, TypeDecl):
|
|
113
|
+
module.types[stmt.name] = stmt
|
|
114
|
+
elif isinstance(stmt, EnumDecl):
|
|
115
|
+
module.enums[stmt.name] = stmt
|
|
116
|
+
elif isinstance(stmt, VarDecl):
|
|
117
|
+
module.globals.setdefault(stmt.name, stmt)
|
|
118
|
+
elif isinstance(stmt, TupleAssign):
|
|
119
|
+
for name in stmt.names:
|
|
120
|
+
if name != "_":
|
|
121
|
+
module.globals.setdefault(name, stmt)
|
|
122
|
+
if module.exported(stmt):
|
|
123
|
+
name = getattr(stmt, "name", None)
|
|
124
|
+
if isinstance(name, str) and not isinstance(stmt, MethodDef):
|
|
125
|
+
module.exports.add(name)
|
|
126
|
+
return module
|