@pineforge/codegen-pyodide 0.10.3 → 1.0.0-rc.1

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.
Files changed (51) hide show
  1. package/README.md +16 -16
  2. package/glue.py +24 -16
  3. package/package.json +1 -1
  4. package/pineforge_codegen/__init__.py +125 -34
  5. package/pineforge_codegen/analyzer/__init__.py +2 -0
  6. package/pineforge_codegen/analyzer/base.py +767 -77
  7. package/pineforge_codegen/analyzer/call_handlers.py +268 -42
  8. package/pineforge_codegen/analyzer/contracts.py +37 -0
  9. package/pineforge_codegen/analyzer/diagnostics.py +30 -4
  10. package/pineforge_codegen/analyzer/tables.py +49 -8
  11. package/pineforge_codegen/analyzer/types.py +33 -1
  12. package/pineforge_codegen/ast_nodes.py +32 -1
  13. package/pineforge_codegen/block_locals.py +185 -0
  14. package/pineforge_codegen/builtin_keywords.py +42 -0
  15. package/pineforge_codegen/codegen/base.py +907 -159
  16. package/pineforge_codegen/codegen/constant_fold.py +131 -0
  17. package/pineforge_codegen/codegen/drawing.py +221 -79
  18. package/pineforge_codegen/codegen/emit_top.py +961 -213
  19. package/pineforge_codegen/codegen/helpers.py +435 -14
  20. package/pineforge_codegen/codegen/host_members.py +162 -0
  21. package/pineforge_codegen/codegen/input.py +252 -85
  22. package/pineforge_codegen/codegen/security.py +4372 -377
  23. package/pineforge_codegen/codegen/session_market.py +71 -0
  24. package/pineforge_codegen/codegen/ta.py +1198 -101
  25. package/pineforge_codegen/codegen/tables.py +205 -71
  26. package/pineforge_codegen/codegen/tv_number_format.py +270 -0
  27. package/pineforge_codegen/codegen/types.py +2055 -62
  28. package/pineforge_codegen/codegen/visit_call.py +929 -131
  29. package/pineforge_codegen/codegen/visit_expr.py +765 -63
  30. package/pineforge_codegen/codegen/visit_stmt.py +610 -52
  31. package/pineforge_codegen/external_requests.py +877 -0
  32. package/pineforge_codegen/lexer.py +104 -22
  33. package/pineforge_codegen/library_inline.py +1304 -0
  34. package/pineforge_codegen/library_modules.py +126 -0
  35. package/pineforge_codegen/library_v5.py +683 -0
  36. package/pineforge_codegen/limits.py +138 -0
  37. package/pineforge_codegen/method_binding.py +33 -0
  38. package/pineforge_codegen/parser.py +384 -68
  39. package/pineforge_codegen/pine_libraries.py +266 -0
  40. package/pineforge_codegen/pine_spelling.py +216 -0
  41. package/pineforge_codegen/pragmas.py +64 -10
  42. package/pineforge_codegen/security_contexts.py +1585 -0
  43. package/pineforge_codegen/session_reads.py +84 -0
  44. package/pineforge_codegen/signatures.py +48 -23
  45. package/pineforge_codegen/support_checker.py +1106 -85
  46. package/pineforge_codegen/symbols.py +4 -2
  47. package/pineforge_codegen-1.0.0-rc.1.tar.gz +0 -0
  48. package/release.json +2 -2
  49. package/tables.json +23 -21
  50. package/transpile.worker.mjs +24 -16
  51. package/pineforge_codegen-0.10.3.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