@pineforge/codegen-pyodide 0.10.4 → 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.
- 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-rc.1.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
|
@@ -4,8 +4,8 @@ Mixin holding stateless / near-stateless name-mangling and AST-walk
|
|
|
4
4
|
helpers used everywhere in the codegen. Lives here so the heavier
|
|
5
5
|
visitor / emitter mixins can depend on it without owning its
|
|
6
6
|
implementation. Keep this module free of imports from any other
|
|
7
|
-
``codegen/*`` submodule
|
|
8
|
-
graph.
|
|
7
|
+
``codegen/*`` submodule (the generated data module ``host_members``
|
|
8
|
+
aside) so it stays at the bottom of the dependency graph.
|
|
9
9
|
|
|
10
10
|
Mixin contract: ``NamingHelper`` reads at most one piece of host state,
|
|
11
11
|
``self._all_member_names`` (used by ``_func_safe_name``). The class
|
|
@@ -14,23 +14,363 @@ mixing this in (``CodeGen``) sets that attribute in its constructor.
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import re
|
|
18
|
+
|
|
17
19
|
from ..ast_nodes import (
|
|
18
|
-
Identifier, MemberAccess,
|
|
20
|
+
Identifier, MemberAccess, TypeDecl, EnumDecl, FuncDef, MethodDef,
|
|
19
21
|
VarDecl, Assignment, TupleAssign, ForStmt, ForInStmt,
|
|
20
22
|
)
|
|
23
|
+
from ..limits import iter_ast_nodes
|
|
24
|
+
from ..signatures import SESSION_FLAG_MEMBERS
|
|
25
|
+
from .host_members import HOST_MEMBER_NAMES
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Integer C++ types an na-capable ``double`` expression may be narrowed into.
|
|
29
|
+
# ``bool`` has its own Pine truthiness lowering below: unlike a C++ cast, it
|
|
30
|
+
# treats both the floating NaN and the integer ``na`` sentinel as false.
|
|
31
|
+
NA_PRESERVING_INT_TYPES = ("int", "int64_t")
|
|
32
|
+
_INT_LITERAL_TEXT = re.compile(
|
|
33
|
+
r"^\s*\(?(?:[-+]?\d+)(?:LL|ULL|L|U)?\)?\s*$"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def na_preserving_int_cast(value_cpp: str, int_cpp_type: str = "int") -> str:
|
|
38
|
+
"""Narrow a numeric expression to an integer type without losing ``na``.
|
|
39
|
+
|
|
40
|
+
An *implicit* ``double`` -> ``int`` conversion of a NaN is undefined
|
|
41
|
+
behaviour ([conv.fpint]), and compilers disagree in practice: AppleClang
|
|
42
|
+
arm64 and g++ aarch64 produce 0 at every ``-O``, g++ x86-64 produces
|
|
43
|
+
``INT_MIN`` at ``-O0``/``-O1`` and 0 from ``-O2``. The engine's contract
|
|
44
|
+
(``include/pineforge/na.hpp``) is that an integer ``na`` *is*
|
|
45
|
+
``std::numeric_limits<T>::min()``, which is what ``is_na(T)`` tests, so
|
|
46
|
+
every such narrowing must be spelled out. Check the original value type:
|
|
47
|
+
converting an ``int64_t`` na sentinel to ``double`` first erases it.
|
|
48
|
+
|
|
49
|
+
The value is evaluated exactly once. ``na`` in, ``na<T>()`` out; anything
|
|
50
|
+
else truncates toward zero exactly as the implicit conversion did, so a
|
|
51
|
+
non-``na`` result is bit-for-bit what it was before. A floating value
|
|
52
|
+
outside ``int`` reads ``na<int>()`` too: its conversion is undefined, and
|
|
53
|
+
x86-64 (``cvttsd2si``: ``INT_MIN``, the ``na`` sentinel) and arm64
|
|
54
|
+
(``fcvtzs``: saturation) disagreed on an epoch a missed provenance
|
|
55
|
+
narrowed (W9-CG-EPOCH-INT64); ``na<int>()`` is the x86-64 answer, so
|
|
56
|
+
Linux runs keep their bits.
|
|
57
|
+
"""
|
|
58
|
+
# Integer literals are proven non-``na``. Keep the historical explicit
|
|
59
|
+
# cast for these tiny paths so a helper does not churn every matrix/color
|
|
60
|
+
# call that passes a literal index or channel.
|
|
61
|
+
if _INT_LITERAL_TEXT.fullmatch(value_cpp):
|
|
62
|
+
return f"({int_cpp_type})({value_cpp})"
|
|
63
|
+
if int_cpp_type == "int":
|
|
64
|
+
return (f"[&](){{ auto _pf_v = ({value_cpp}); "
|
|
65
|
+
f"if constexpr (std::is_floating_point_v<decltype(_pf_v)>) "
|
|
66
|
+
f"return (_pf_v >= -2147483648.0 && _pf_v < 2147483648.0) "
|
|
67
|
+
f"? (int)_pf_v : na<int>(); "
|
|
68
|
+
f"else return is_na(_pf_v) ? na<int>() : (int)_pf_v; }}()")
|
|
69
|
+
return (f"[&](){{ auto _pf_v = ({value_cpp}); "
|
|
70
|
+
f"return is_na(_pf_v) ? na<{int_cpp_type}>() : "
|
|
71
|
+
f"({int_cpp_type})_pf_v; }}()")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# C++ whose evaluation cannot be observed, so reading it twice -- or once per
|
|
75
|
+
# loop iteration -- is harmless: a literal, a name or member chain (a history
|
|
76
|
+
# read of it at a literal offset included), an na.
|
|
77
|
+
_PLAIN_READ_CPP = re.compile(
|
|
78
|
+
r"[A-Za-z_]\w*(?:(?:\.|->)[A-Za-z_]\w*)*(?:\[\d+\])?"
|
|
79
|
+
r"|[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?"
|
|
80
|
+
r'|std::string\("(?:[^"\\]|\\.)*"\)'
|
|
81
|
+
r"|na<[\w:]+>\(\)"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _strip_outer_parens(text: str) -> str:
|
|
86
|
+
text = text.strip()
|
|
87
|
+
while text.startswith("(") and text.endswith(")"):
|
|
88
|
+
depth = 0
|
|
89
|
+
for i, ch in enumerate(text):
|
|
90
|
+
depth += ch == "("
|
|
91
|
+
depth -= ch == ")"
|
|
92
|
+
if depth == 0 and i < len(text) - 1:
|
|
93
|
+
return text # the first "(" closes before the end: "(a) + (b)"
|
|
94
|
+
text = text[1:-1].strip()
|
|
95
|
+
return text
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def cpp_is_plain_read(value_cpp: str) -> bool:
|
|
99
|
+
"""True when evaluating ``value_cpp`` more than once cannot be observed."""
|
|
100
|
+
return _PLAIN_READ_CPP.fullmatch(_strip_outer_parens(value_cpp)) is not None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def evaluate_args_once(args: list[str], indices, render, token: str) -> str:
|
|
104
|
+
"""``render(args)`` with every argument evaluated once, left to right.
|
|
105
|
+
|
|
106
|
+
Pine evaluates each call argument exactly once. A lowering template that
|
|
107
|
+
reads an argument twice, or once per loop iteration, re-runs that
|
|
108
|
+
argument's C++, so a stateful call in it (a ``ta.*`` compute(), a user
|
|
109
|
+
function with state) runs more than once per execution. Every argument
|
|
110
|
+
in ``indices`` that is not a plain read is bound once to a lambda local,
|
|
111
|
+
together with every earlier argument that is not, so the left-to-right
|
|
112
|
+
order holds; the template then reads the bindings. A call whose
|
|
113
|
+
``indices`` arguments are all plain reads renders exactly as before.
|
|
114
|
+
"""
|
|
115
|
+
need = [i for i in indices if i < len(args) and not cpp_is_plain_read(args[i])]
|
|
116
|
+
if not need:
|
|
117
|
+
return render(list(args))
|
|
118
|
+
bound = list(args)
|
|
119
|
+
decls = []
|
|
120
|
+
for i in range(max(need) + 1):
|
|
121
|
+
if cpp_is_plain_read(args[i]):
|
|
122
|
+
continue
|
|
123
|
+
decls.append(f"auto&& {token}{i} = ({args[i]});")
|
|
124
|
+
bound[i] = f"{token}{i}"
|
|
125
|
+
return f"([&]() {{ {' '.join(decls)} return {render(bound)}; }}())"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def pine_index_int_cast(value_cpp: str) -> str:
|
|
129
|
+
"""Narrow a numeric index after checking its *original* na sentinel.
|
|
130
|
+
|
|
131
|
+
Table-driven matrix methods receive C++ text without the source AST.
|
|
132
|
+
Converting an integer sentinel to ``double`` first loses it (INT64_MIN is
|
|
133
|
+
finite as a double), so this helper retains the source C++ type until the
|
|
134
|
+
`is_na` check. Bool has no separate na state in Pine v6.
|
|
135
|
+
"""
|
|
136
|
+
if _INT_LITERAL_TEXT.fullmatch(value_cpp):
|
|
137
|
+
return f"(int)({value_cpp})"
|
|
138
|
+
return (
|
|
139
|
+
f"([&](){{ auto _pf_idx_v = ({value_cpp}); "
|
|
140
|
+
f"using _pf_idx_t = std::decay_t<decltype(_pf_idx_v)>; "
|
|
141
|
+
f"if constexpr (std::is_same_v<_pf_idx_t, bool>) "
|
|
142
|
+
f"return (int)_pf_idx_v; "
|
|
143
|
+
f"else return is_na(_pf_idx_v) ? na<int>() : (int)_pf_idx_v; }}())"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def pine_truth_cast(value_cpp: str) -> str:
|
|
148
|
+
"""Convert one scalar expression using Pine's boolean truthiness.
|
|
149
|
+
|
|
150
|
+
Pine's boolean context is false for ``na``. C++ differs for both numeric
|
|
151
|
+
sentinels: ``bool(NaN)`` is true and ``bool(INT_MIN)`` is true. Keep the
|
|
152
|
+
expression single-evaluation and use ``if constexpr`` so the generated
|
|
153
|
+
lambda is valid for either a real ``bool`` or a numeric scalar. The final
|
|
154
|
+
branch is intentionally a normal C++ conversion for a type that cannot
|
|
155
|
+
carry Pine ``na`` (for example an engine handle); callers only use this
|
|
156
|
+
helper at scalar boolean boundaries.
|
|
157
|
+
"""
|
|
158
|
+
return (
|
|
159
|
+
f"[&](){{ auto _pf_bool_v = ({value_cpp}); "
|
|
160
|
+
f"using _pf_bool_t = std::decay_t<decltype(_pf_bool_v)>; "
|
|
161
|
+
f"if constexpr (std::is_same_v<_pf_bool_t, bool>) {{ "
|
|
162
|
+
f"return _pf_bool_v; }} "
|
|
163
|
+
f"else if constexpr (std::is_floating_point_v<_pf_bool_t> || "
|
|
164
|
+
f"std::is_integral_v<_pf_bool_t>) {{ "
|
|
165
|
+
f"return is_na(_pf_bool_v) ? false : (_pf_bool_v != 0); }} "
|
|
166
|
+
f"else {{ return static_cast<bool>(_pf_bool_v); }} }}()"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def unary_sign_cpp(op: str, operand_cpp: str) -> str:
|
|
171
|
+
"""``(-x)`` / ``(+x)``, the operand parenthesized when its text starts
|
|
172
|
+
with a sign: a negative constant inlined as its literal (``NEG = -5``)
|
|
173
|
+
under ``-`` was ``(--5)``, a decrement of a literal, which does not
|
|
174
|
+
compile. Every other operand keeps its spelling."""
|
|
175
|
+
if operand_cpp[:1] in ("-", "+"):
|
|
176
|
+
return f"({op}({operand_cpp}))"
|
|
177
|
+
return f"({op}{operand_cpp})"
|
|
21
178
|
|
|
22
179
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
180
|
+
def color_alpha_cast(value_cpp: str, whole: bool = False) -> str:
|
|
181
|
+
"""Convert Pine transparency without feeding ``na<int>()`` to color.hpp.
|
|
182
|
+
|
|
183
|
+
color.hpp's ``new_color`` takes the transparency as a double and stores
|
|
184
|
+
the alpha byte nearest 255 * (100 - t) / 100, as TradingView does, so a
|
|
185
|
+
finite value passes through unrounded: ``color.new(c, 10.5)`` reads back
|
|
186
|
+
11 (tests/test_e2e_color_tapes.py). TradingView's
|
|
187
|
+
``color.new``/``color.rgb`` treat a missing transparency as 100 (fully
|
|
188
|
+
transparent); the integer ``na`` sentinel would read as a transparency
|
|
189
|
+
far below 0. ``whole`` marks an integer literal from 0 to 100, which keeps
|
|
190
|
+
the historic ``(int)`` conversion: it leaves the value as it is.
|
|
191
|
+
"""
|
|
192
|
+
value = "(int)_pf_color_v" if whole else "(double)_pf_color_v"
|
|
193
|
+
return (
|
|
194
|
+
f"[&](){{ auto _pf_color_v = ({value_cpp}); "
|
|
195
|
+
f"using _pf_color_t = std::decay_t<decltype(_pf_color_v)>; "
|
|
196
|
+
f"if constexpr (std::is_same_v<_pf_color_t, bool>) "
|
|
197
|
+
f"return _pf_color_v ? 1 : 0; "
|
|
198
|
+
f"else return is_na(_pf_color_v) ? 100 : {value}; }}()"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# A comment or a string or character literal of the generated C++, which
|
|
203
|
+
# spells no raw string literal and no digit separator.
|
|
204
|
+
_CPP_COMMENT_OR_LITERAL = re.compile(
|
|
205
|
+
r"//[^\n]*|/\*.*?\*/|\"(?:\\.|[^\"\\\n])*\"|'(?:\\.|[^'\\\n])*'", re.S
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def cpp_code_only(cpp: str) -> str:
|
|
210
|
+
"""``cpp`` with every comment and string or character literal replaced by
|
|
211
|
+
a space: what is left is code, which a script's string cannot spell."""
|
|
212
|
+
return _CPP_COMMENT_OR_LITERAL.sub(" ", cpp)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# Preserve the historic spelling for names already escaped in released TUs.
|
|
216
|
+
LEGACY_CPP_RESERVED = frozenset({
|
|
30
217
|
"exp", "log", "abs", "max", "min", "and", "or", "not",
|
|
31
218
|
"int", "float", "bool", "string", "short", "long", "new", "delete",
|
|
32
219
|
"class", "struct", "return", "void", "auto", "const", "static",
|
|
33
|
-
|
|
220
|
+
"prepare_script_run",
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
# Every C++17 keyword and alternative operator token, plus the C++20
|
|
224
|
+
# additions. Pine reserves some of these itself, but the others are legal
|
|
225
|
+
# Pine identifiers and must not reach a C++ declaration unchanged.
|
|
226
|
+
CPP_KEYWORDS = frozenset("""
|
|
227
|
+
alignas alignof and and_eq asm auto bitand bitor bool break case catch
|
|
228
|
+
char char8_t char16_t char32_t class co_await co_return co_yield compl
|
|
229
|
+
concept const consteval constexpr constinit const_cast continue decltype
|
|
230
|
+
default delete do double dynamic_cast else enum explicit export extern
|
|
231
|
+
false float for friend goto if inline int long mutable namespace new
|
|
232
|
+
noexcept not not_eq nullptr operator or or_eq private protected public
|
|
233
|
+
register reinterpret_cast requires return short signed sizeof static
|
|
234
|
+
static_assert static_cast struct switch template this thread_local throw
|
|
235
|
+
true try typedef typeid typename union unsigned using virtual void
|
|
236
|
+
volatile wchar_t while xor xor_eq
|
|
237
|
+
""".split())
|
|
238
|
+
|
|
239
|
+
# C++20 contextual spellings can be identifiers in some contexts, but can
|
|
240
|
+
# become special after future emitter changes or on newer compilers.
|
|
241
|
+
CPP_CONTEXTUAL = frozenset({"final", "override", "import", "module"})
|
|
242
|
+
|
|
243
|
+
# Macros visible through the standard and engine headers included by every
|
|
244
|
+
# generated TU. Their expansion in a member declaration is invalid C++.
|
|
245
|
+
CPP_STANDARD_MACROS = frozenset("""
|
|
246
|
+
__LINE__ __FILE__ __DATE__ __TIME__ __TIMESTAMP__ __cplusplus
|
|
247
|
+
__STDC__ __STDC_HOSTED__ __STDC_VERSION__ __STDC_MB_MIGHT_NEQ_WC__
|
|
248
|
+
__STDC_UTF_16__ __STDC_UTF_32__
|
|
249
|
+
NULL EOF NAN INFINITY HUGE_VAL HUGE_VALF HUGE_VALL
|
|
250
|
+
stdin stdout stderr assert offsetof va_start va_end va_arg va_copy
|
|
251
|
+
EXIT_SUCCESS EXIT_FAILURE RAND_MAX MB_CUR_MAX EDOM ERANGE EILSEQ
|
|
252
|
+
CHAR_BIT CHAR_MIN CHAR_MAX SCHAR_MIN SCHAR_MAX UCHAR_MAX
|
|
253
|
+
SHRT_MIN SHRT_MAX USHRT_MAX INT_MIN INT_MAX UINT_MAX
|
|
254
|
+
LONG_MIN LONG_MAX ULONG_MAX LLONG_MIN LLONG_MAX ULLONG_MAX
|
|
255
|
+
INT8_MIN INT8_MAX UINT8_MAX INT16_MIN INT16_MAX UINT16_MAX
|
|
256
|
+
INT32_MIN INT32_MAX UINT32_MAX INT64_MIN INT64_MAX UINT64_MAX
|
|
257
|
+
INTPTR_MIN INTPTR_MAX UINTPTR_MAX PTRDIFF_MIN PTRDIFF_MAX
|
|
258
|
+
SIZE_MAX SIG_ATOMIC_MIN SIG_ATOMIC_MAX WCHAR_MIN WCHAR_MAX
|
|
259
|
+
WINT_MIN WINT_MAX INTMAX_MIN INTMAX_MAX UINTMAX_MAX
|
|
260
|
+
M_E M_PI M_LOG2E M_LOG10E M_LN2 M_LN10 M_PI_2 M_PI_4
|
|
261
|
+
M_1_PI M_2_PI M_2_SQRTPI M_SQRT2 M_SQRT1_2
|
|
262
|
+
PINEFORGE_HAS_NATIVE_LOWERING_V1 PINEFORGE_HAS_NATIVE_LIVE_V1
|
|
263
|
+
PINEFORGE_HAS_AUX_SECURITY_FEED_V1 PINEFORGE_HAS_SCRIPT_RUN_PREPARE_V1
|
|
264
|
+
PINEFORGE_HAS_EXPLICIT_PINE_CAP_V1
|
|
265
|
+
PINEFORGE_HAS_EXPLICIT_PINE_EXECUTION_ADAPTER_V1
|
|
266
|
+
PINEFORGE_NO_STRATEGY_DECLS PF_PINE_TIME_HAS_SESSION_DAY
|
|
267
|
+
PF_PINE_TIME_SESSION_DAY_ARGS PF_VWAP_HAS_SESSION_ANCHOR
|
|
268
|
+
PF_VWAP_SESSION_ANCHOR_ARGS PF_ALMA_HAS_FLOOR
|
|
269
|
+
PF_KC_HAS_USE_TRUE_RANGE PF_VWAP_HAS_ANCHOR_INPUT
|
|
270
|
+
PF_PIVOT_LEVELS_HAS_ANCHOR
|
|
271
|
+
""".split())
|
|
272
|
+
|
|
273
|
+
# Identifiers used by the emitter as class/type names, namespaces, and
|
|
274
|
+
# generated or inherited methods. A user member with one of these names can
|
|
275
|
+
# hide a type/callee, or (GeneratedStrategy) conflict with the constructor.
|
|
276
|
+
CPP_EMITTER_NAMES = frozenset("""
|
|
277
|
+
GeneratedStrategy BacktestEngine Bar Series PineArray PineMap PineMatrix
|
|
278
|
+
PineGenericMatrix PineStrategyConfig PineStrategyHost StrategyOverrides
|
|
279
|
+
SymInfo ReportC MagnifierDistribution ChartPoint Line Box Label Linefill
|
|
280
|
+
std pineforge ta math
|
|
281
|
+
on_bar on_source_bar prepare_script_run configure_pine_strategy
|
|
282
|
+
configure_security_evaluators snapshot_script_state restore_script_state
|
|
283
|
+
commit_script_state set_strategy_override set_input
|
|
284
|
+
set_magnifier_volume_weighted strategy_declares_bar_magnifier fill_report run
|
|
285
|
+
precalculate
|
|
286
|
+
strategy_entry strategy_close strategy_close_all strategy_exit
|
|
287
|
+
strategy_exit_cancel_bracket strategy_cancel strategy_cancel_all strategy_order
|
|
288
|
+
pine_bar_index pine_last_bar_index prev_chart_close is_first_tick is_last_tick
|
|
289
|
+
history_advances_new_bar security_series_slot_is_new last_bar_dual_entry_path
|
|
290
|
+
live_position_size pending_order_count market_admission_journal
|
|
291
|
+
pine_time pine_time_close pine_time_tradingday pine_random
|
|
292
|
+
pine_runtime_error pine_enum_str_at pine_session_ismarket
|
|
293
|
+
pine_session_ispostmarket pine_session_ispremarket _PFSessionMarket
|
|
294
|
+
_pf_session_market_ script_tf_
|
|
295
|
+
tf_change tf_is_daily tf_is_intraday tf_is_monthly tf_is_seconds
|
|
296
|
+
tf_is_weekly tf_multiplier tf_to_seconds time_close timestamp main_period
|
|
297
|
+
round_to_mintick calc_qty
|
|
298
|
+
pf_noop
|
|
299
|
+
pf_line_new pf_line_new_pts pf_line_copy pf_line_delete
|
|
300
|
+
pf_line_get_price pf_line_get_x1 pf_line_get_x2 pf_line_get_y1 pf_line_get_y2
|
|
301
|
+
pf_line_set_first_point pf_line_set_second_point pf_line_set_x1 pf_line_set_x2
|
|
302
|
+
pf_line_set_xloc pf_line_set_xy1 pf_line_set_xy2 pf_line_set_y1 pf_line_set_y2
|
|
303
|
+
pf_label_new pf_label_new_pt pf_label_copy pf_label_delete
|
|
304
|
+
pf_label_get_text pf_label_get_x pf_label_get_y pf_label_set_point
|
|
305
|
+
pf_label_set_text pf_label_set_x pf_label_set_xloc pf_label_set_xy
|
|
306
|
+
pf_label_set_y pf_label_set_yloc
|
|
307
|
+
pf_box_new pf_box_new_pts pf_box_copy pf_box_delete
|
|
308
|
+
pf_box_get_bottom pf_box_get_left pf_box_get_right pf_box_get_top
|
|
309
|
+
pf_box_set_bottom pf_box_set_bottom_right_point pf_box_set_left
|
|
310
|
+
pf_box_set_lefttop pf_box_set_right pf_box_set_rightbottom pf_box_set_top
|
|
311
|
+
pf_box_set_top_left_point pf_box_set_xloc
|
|
312
|
+
pf_linefill_new pf_linefill_delete pf_linefill_get_line1 pf_linefill_get_line2
|
|
313
|
+
_PF_DRAWING_UNBOUNDED _PFDrawingArg _pf_drawing_get _pf_drawing_set
|
|
314
|
+
_pf_drawing_na _pf_collect_drawings _pf_collect_lines_ _pf_collect_boxes_
|
|
315
|
+
_pf_collect_labels_ _pf_new _pf_held
|
|
316
|
+
get_input_int get_input_float get_input_bool get_input_string
|
|
317
|
+
trace is_na na nz fixnan
|
|
318
|
+
""".split())
|
|
319
|
+
|
|
320
|
+
# The generated history members, ``_<kind>_<n>`` (codegen/base.py
|
|
321
|
+
# _prepare_inline_history_members), numbered past any script name spelled
|
|
322
|
+
# like one.
|
|
323
|
+
INLINE_HISTORY_KINDS = (
|
|
324
|
+
"hist_call", "series_arg", "udf_series_arg", "session_call", "fn_global_hist",
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# HOST_MEMBER_NAMES (codegen/host_members.py, derived by
|
|
329
|
+
# scripts/gen_host_members.py from the emitter and the host header): the host
|
|
330
|
+
# members the generated class reads or writes unqualified. A script identifier
|
|
331
|
+
# with one of these names would hide the member (``session_isfirstbar_ =
|
|
332
|
+
# close > open`` became the value of ``session.isfirstbar``).
|
|
333
|
+
CPP_RESERVED = set(
|
|
334
|
+
LEGACY_CPP_RESERVED | CPP_KEYWORDS | CPP_CONTEXTUAL |
|
|
335
|
+
CPP_STANDARD_MACROS | CPP_EMITTER_NAMES | HOST_MEMBER_NAMES
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Temporaries the emitter declares in the C++ it generates around a user's
|
|
339
|
+
# expression: nz's ``[&]{ auto _nz_v = (x); ... }()``, a comparison's
|
|
340
|
+
# ``_pna_l`` / ``_pna_r``, a history read's ``_hv``, a switch's
|
|
341
|
+
# ``__switch_val_N``, timestamp()'s calendar fields, the str.* / array.*
|
|
342
|
+
# templates' locals (``__pf_s``, ``__pf_it``, ...). An authored identifier
|
|
343
|
+
# spelled like one was captured: the generated local shadowed it where the
|
|
344
|
+
# user's expression read it (``nz(x, _nz_v)`` read the lambda's own
|
|
345
|
+
# ``_nz_v``) or initialized itself from itself (``_pna_l > close`` did not
|
|
346
|
+
# compile). ``_safe_name`` escapes an authored spelling of any of them. A
|
|
347
|
+
# temporary whose name the emitter allocates against the authored spellings
|
|
348
|
+
# itself (``__pf_map_iter_N``, ``_pf_udt_Item__pf2``) is not listed.
|
|
349
|
+
CPP_TEMPORARY_NAMES = frozenset("""
|
|
350
|
+
_nz_v _nz_y _fixnan_v _pna_l _pna_r _pfc_l _pfc_r _pfc_eq _hv _hidx _sv
|
|
351
|
+
_out _func_ret _pf_v _pf_idx_v _pf_idx_t _pf_bool_v _pf_bool_t
|
|
352
|
+
_pf_color_v _pf_color_t _pf_color_base _pf_color_alpha _pf_sign_v
|
|
353
|
+
_pf_series_raw _pf_raw _pf_val_t _pf_out _pf_prior
|
|
354
|
+
_pf_hist_offset_raw _pf_hist_offset_numeric _pf_hist_offset _pf_hist_bar
|
|
355
|
+
_pf_calendar_ts _pf_calendar_tz
|
|
356
|
+
_tz _yr _mo _dy _hr _min _sc _res _old _old_tz _had_old _secs
|
|
357
|
+
_last_tz _last_yr _last_mo _last_dy _last_hr _last_min _last_sc
|
|
358
|
+
_last_res
|
|
359
|
+
__pf_s __pf_r __pf_p __pf_i __pf_j __pf_t __pf_it __pf_m __pf_c __pf_n
|
|
360
|
+
__pf_k __pf_b __pf_ma __pf_mb __pf_f __pf_d __pf_best __pf_bc __pf_rank
|
|
361
|
+
__pf_idx __pf_occ
|
|
362
|
+
""".split())
|
|
363
|
+
_CPP_TEMPORARY_PATTERN = re.compile(
|
|
364
|
+
r"_v\d+|_secval_\d+(?:_v\d+)?|_tuple_result_\d+|_tuple_unused_\d+|__switch_val_\d+"
|
|
365
|
+
r"|_for_(?:start|end|end_eval)_\d+|_pf_(?:str|array|round)_a\d+"
|
|
366
|
+
r"|_pf_every_bar_ta_\d+|_pf_shared_\d+_\d+|__pf_array\w*|__pf_raw_\w+"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def is_emitter_temporary(name: str) -> bool:
|
|
371
|
+
"""Whether ``name`` is spelled like a temporary of the emitter's."""
|
|
372
|
+
return (name in CPP_TEMPORARY_NAMES
|
|
373
|
+
or _CPP_TEMPORARY_PATTERN.fullmatch(name) is not None)
|
|
34
374
|
|
|
35
375
|
|
|
36
376
|
# Bare C++ identifiers that ``strategy.*`` (and a few other) read-only
|
|
@@ -55,6 +395,9 @@ BUILTIN_ACCESSOR_NAMES = {
|
|
|
55
395
|
"avg_winning_trade_percent", "avg_losing_trade_percent",
|
|
56
396
|
"margin_liquidation_price", "open_profit", "current_equity",
|
|
57
397
|
"open_trades_capital_held",
|
|
398
|
+
# The host's own ``time_close()``: a ``time_close[k]`` history Series
|
|
399
|
+
# member of that name would shadow the call its value is pushed from.
|
|
400
|
+
"time_close",
|
|
58
401
|
}
|
|
59
402
|
|
|
60
403
|
|
|
@@ -77,12 +420,90 @@ class NamingHelper:
|
|
|
77
420
|
.replace('"', '\\"')
|
|
78
421
|
.replace("\n", "\\n")
|
|
79
422
|
.replace("\r", "\\r")
|
|
423
|
+
.replace("\t", "\\t")
|
|
80
424
|
)
|
|
81
425
|
|
|
426
|
+
def _initialise_safe_names(self, ast) -> None:
|
|
427
|
+
"""Reserve all authored spellings before assigning escaped ones.
|
|
428
|
+
|
|
429
|
+
A fixed ``_name_`` escape can itself collide with an authored
|
|
430
|
+
``_name_``. Pre-allocating against the entire AST makes the mapping
|
|
431
|
+
stable and one-to-one regardless of emission order.
|
|
432
|
+
"""
|
|
433
|
+
authored: set[str] = set()
|
|
434
|
+
bound: set[str] = set()
|
|
435
|
+
for node, _depth in iter_ast_nodes(ast):
|
|
436
|
+
for attr in ("name", "var", "member"):
|
|
437
|
+
value = getattr(node, attr, None)
|
|
438
|
+
if isinstance(value, str) and value:
|
|
439
|
+
authored.add(value)
|
|
440
|
+
for attr in ("names", "vars", "params", "members"):
|
|
441
|
+
values = getattr(node, attr, None)
|
|
442
|
+
if isinstance(values, list):
|
|
443
|
+
authored.update(value for value in values
|
|
444
|
+
if isinstance(value, str) and value)
|
|
445
|
+
if isinstance(node, TypeDecl):
|
|
446
|
+
authored.update(field.name for field in node.fields)
|
|
447
|
+
bound.add(node.name)
|
|
448
|
+
bound.update(field.name for field in node.fields)
|
|
449
|
+
elif isinstance(node, EnumDecl):
|
|
450
|
+
bound.add(node.name)
|
|
451
|
+
bound.update(node.members)
|
|
452
|
+
elif isinstance(node, VarDecl):
|
|
453
|
+
bound.add(node.name)
|
|
454
|
+
elif isinstance(node, TupleAssign):
|
|
455
|
+
bound.update(node.names)
|
|
456
|
+
elif isinstance(node, (FuncDef, MethodDef)):
|
|
457
|
+
bound.add(node.name)
|
|
458
|
+
bound.update(node.params)
|
|
459
|
+
elif isinstance(node, (ForStmt, ForInStmt)):
|
|
460
|
+
if node.var:
|
|
461
|
+
bound.add(node.var)
|
|
462
|
+
if isinstance(getattr(node, "vars", None), list):
|
|
463
|
+
bound.update(node.vars)
|
|
464
|
+
# A history-read builtin becomes a Series member of its own name.
|
|
465
|
+
ctx = getattr(self, "ctx", None)
|
|
466
|
+
if "time_close" in getattr(ctx, "series_vars", ()):
|
|
467
|
+
bound.add("time_close")
|
|
468
|
+
self._safe_name_map: dict[str, str] = {}
|
|
469
|
+
self._safe_name_occupied = authored
|
|
470
|
+
self._safe_name_bound = bound
|
|
471
|
+
for name in sorted(bound):
|
|
472
|
+
if (name in CPP_RESERVED or name in BUILTIN_ACCESSOR_NAMES
|
|
473
|
+
or is_emitter_temporary(name)):
|
|
474
|
+
self._allocate_safe_name(name)
|
|
475
|
+
|
|
476
|
+
def _allocate_safe_name(self, name: str) -> str:
|
|
477
|
+
if name in LEGACY_CPP_RESERVED or name in BUILTIN_ACCESSOR_NAMES:
|
|
478
|
+
base = f"_{name}_"
|
|
479
|
+
else:
|
|
480
|
+
base = f"pf_safe_{name}"
|
|
481
|
+
occupied = getattr(self, "_safe_name_occupied", set())
|
|
482
|
+
candidate = base
|
|
483
|
+
suffix = 2
|
|
484
|
+
while (candidate in occupied or candidate in CPP_RESERVED
|
|
485
|
+
or candidate in BUILTIN_ACCESSOR_NAMES
|
|
486
|
+
or is_emitter_temporary(candidate)):
|
|
487
|
+
candidate = f"{base}_{suffix}"
|
|
488
|
+
suffix += 1
|
|
489
|
+
self._safe_name_map[name] = candidate
|
|
490
|
+
occupied.add(candidate)
|
|
491
|
+
return candidate
|
|
492
|
+
|
|
82
493
|
def _safe_name(self, name: str) -> str:
|
|
83
|
-
"""Rename identifiers that collide with C++
|
|
84
|
-
|
|
85
|
-
|
|
494
|
+
"""Rename Pine identifiers that collide with emitted C++ names."""
|
|
495
|
+
mapping = getattr(self, "_safe_name_map", None)
|
|
496
|
+
if mapping is not None and name in mapping:
|
|
497
|
+
return mapping[name]
|
|
498
|
+
if mapping is not None and name not in self._safe_name_bound:
|
|
499
|
+
return name
|
|
500
|
+
if (name in CPP_RESERVED or name in BUILTIN_ACCESSOR_NAMES
|
|
501
|
+
or is_emitter_temporary(name)):
|
|
502
|
+
if mapping is None:
|
|
503
|
+
return (f"_{name}_" if name in LEGACY_CPP_RESERVED
|
|
504
|
+
or name in BUILTIN_ACCESSOR_NAMES
|
|
505
|
+
else f"pf_safe_{name}")
|
|
506
|
+
return self._allocate_safe_name(name)
|
|
86
507
|
return name
|
|
87
508
|
|
|
88
509
|
def _func_safe_name(self, name: str) -> str:
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Host members the generated C++ can read or write. GENERATED: do not
|
|
2
|
+
edit; run ``scripts/gen_host_members.py``.
|
|
3
|
+
|
|
4
|
+
Every public or protected member of ``pineforge::source::PineStrategyHost`` and its
|
|
5
|
+
bases that the emitter's string constants spell. A script identifier
|
|
6
|
+
with one of these names would hide the host's member from the generated
|
|
7
|
+
strategy class, so ``_safe_name`` renames it.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
HOST_MEMBER_NAMES = frozenset({
|
|
11
|
+
"_src_close_",
|
|
12
|
+
"_src_high_",
|
|
13
|
+
"_src_hl2_",
|
|
14
|
+
"_src_hlc3_",
|
|
15
|
+
"_src_hlcc4_",
|
|
16
|
+
"_src_low_",
|
|
17
|
+
"_src_ohlc4_",
|
|
18
|
+
"_src_open_",
|
|
19
|
+
"_src_series_active_",
|
|
20
|
+
"_src_volume_",
|
|
21
|
+
"avg_losing_trade",
|
|
22
|
+
"avg_losing_trade_percent",
|
|
23
|
+
"avg_trade",
|
|
24
|
+
"avg_trade_percent",
|
|
25
|
+
"avg_winning_trade",
|
|
26
|
+
"avg_winning_trade_percent",
|
|
27
|
+
"bar_index_",
|
|
28
|
+
"barstate_islast_",
|
|
29
|
+
"cancel",
|
|
30
|
+
"cancel_all",
|
|
31
|
+
"clear_security",
|
|
32
|
+
"closed_trade_commission",
|
|
33
|
+
"closed_trade_direction",
|
|
34
|
+
"closed_trade_entry_bar_index",
|
|
35
|
+
"closed_trade_entry_comment",
|
|
36
|
+
"closed_trade_entry_id",
|
|
37
|
+
"closed_trade_entry_price",
|
|
38
|
+
"closed_trade_entry_time",
|
|
39
|
+
"closed_trade_equity",
|
|
40
|
+
"closed_trade_exit_bar_index",
|
|
41
|
+
"closed_trade_exit_comment",
|
|
42
|
+
"closed_trade_exit_id",
|
|
43
|
+
"closed_trade_exit_price",
|
|
44
|
+
"closed_trade_exit_time",
|
|
45
|
+
"closed_trade_max_drawdown",
|
|
46
|
+
"closed_trade_max_drawdown_percent",
|
|
47
|
+
"closed_trade_max_runup",
|
|
48
|
+
"closed_trade_max_runup_percent",
|
|
49
|
+
"closed_trade_profit",
|
|
50
|
+
"closed_trade_profit_percent",
|
|
51
|
+
"closed_trade_size",
|
|
52
|
+
"commit_script_state",
|
|
53
|
+
"configure_pine_strategy",
|
|
54
|
+
"configure_security_evaluators",
|
|
55
|
+
"count_losstrades",
|
|
56
|
+
"count_wintrades",
|
|
57
|
+
"current_bar_",
|
|
58
|
+
"current_equity",
|
|
59
|
+
"evaluate_security",
|
|
60
|
+
"eventrades",
|
|
61
|
+
"fill_report",
|
|
62
|
+
"foreign_context_",
|
|
63
|
+
"get_input_bool",
|
|
64
|
+
"get_input_double",
|
|
65
|
+
"get_input_int",
|
|
66
|
+
"get_input_int64",
|
|
67
|
+
"get_input_source",
|
|
68
|
+
"get_input_string",
|
|
69
|
+
"get_syminfo_metadata",
|
|
70
|
+
"gross_loss",
|
|
71
|
+
"gross_profit",
|
|
72
|
+
"grossloss_percent",
|
|
73
|
+
"grossprofit_percent",
|
|
74
|
+
"history_advances_new_bar",
|
|
75
|
+
"initial_capital_",
|
|
76
|
+
"input_tf_",
|
|
77
|
+
"is_first_tick",
|
|
78
|
+
"is_last_tick_",
|
|
79
|
+
"last_bar_time_",
|
|
80
|
+
"main_period",
|
|
81
|
+
"margin_liquidation_price",
|
|
82
|
+
"max_contracts_held_all",
|
|
83
|
+
"max_contracts_held_long",
|
|
84
|
+
"max_contracts_held_short",
|
|
85
|
+
"max_drawdown_",
|
|
86
|
+
"max_drawdown_percent",
|
|
87
|
+
"max_runup_",
|
|
88
|
+
"max_runup_percent",
|
|
89
|
+
"net_profit",
|
|
90
|
+
"on_source_bar",
|
|
91
|
+
"open_profit",
|
|
92
|
+
"open_trade_commission",
|
|
93
|
+
"open_trade_direction",
|
|
94
|
+
"open_trade_entry_bar_index",
|
|
95
|
+
"open_trade_entry_comment",
|
|
96
|
+
"open_trade_entry_id",
|
|
97
|
+
"open_trade_entry_price",
|
|
98
|
+
"open_trade_entry_time",
|
|
99
|
+
"open_trade_max_drawdown",
|
|
100
|
+
"open_trade_max_drawdown_percent",
|
|
101
|
+
"open_trade_max_runup",
|
|
102
|
+
"open_trade_max_runup_percent",
|
|
103
|
+
"open_trade_profit",
|
|
104
|
+
"open_trade_profit_percent",
|
|
105
|
+
"open_trade_size",
|
|
106
|
+
"open_trades_capital_held",
|
|
107
|
+
"pine_bar_index",
|
|
108
|
+
"pine_last_bar_index",
|
|
109
|
+
"pine_session_ismarket",
|
|
110
|
+
"pine_session_ispostmarket",
|
|
111
|
+
"pine_session_ispremarket",
|
|
112
|
+
"pine_time_offset",
|
|
113
|
+
"position_entry_name",
|
|
114
|
+
"position_entry_price_",
|
|
115
|
+
"prepare_script_run",
|
|
116
|
+
"prev_bar_timestamp_",
|
|
117
|
+
"prev_chart_close",
|
|
118
|
+
"pyramid_entries_",
|
|
119
|
+
"recorded_series_",
|
|
120
|
+
"recorded_series_value",
|
|
121
|
+
"register_security_eval",
|
|
122
|
+
"register_security_lower_tf_eval",
|
|
123
|
+
"restore_script_state",
|
|
124
|
+
"round_to_mintick",
|
|
125
|
+
"run",
|
|
126
|
+
"script_tf_",
|
|
127
|
+
"security_column_value",
|
|
128
|
+
"security_eval_states_",
|
|
129
|
+
"security_lower_tf_sub_bar_index",
|
|
130
|
+
"security_series_slot_is_new",
|
|
131
|
+
"session_isfirstbar_",
|
|
132
|
+
"session_isfirstbar_regular_",
|
|
133
|
+
"session_islastbar_",
|
|
134
|
+
"session_islastbar_regular_",
|
|
135
|
+
"session_ismarket_",
|
|
136
|
+
"set_input",
|
|
137
|
+
"set_magnifier_volume_weighted",
|
|
138
|
+
"set_pine_risk_direction",
|
|
139
|
+
"set_pine_risk_max_cons_loss_days",
|
|
140
|
+
"set_pine_risk_max_drawdown",
|
|
141
|
+
"set_pine_risk_max_intraday_filled_orders",
|
|
142
|
+
"set_pine_risk_max_intraday_loss",
|
|
143
|
+
"set_pine_risk_max_position_size",
|
|
144
|
+
"set_strategy_override",
|
|
145
|
+
"signed_position_size",
|
|
146
|
+
"snapshot_script_state",
|
|
147
|
+
"strategy_cancel",
|
|
148
|
+
"strategy_cancel_all",
|
|
149
|
+
"strategy_close",
|
|
150
|
+
"strategy_entry",
|
|
151
|
+
"strategy_exit",
|
|
152
|
+
"strategy_exit_cancel_bracket",
|
|
153
|
+
"strategy_order",
|
|
154
|
+
"symbol_facts_",
|
|
155
|
+
"symbol_feeds_",
|
|
156
|
+
"syminfo_",
|
|
157
|
+
"syminfo_mintick_",
|
|
158
|
+
"time_close",
|
|
159
|
+
"trace",
|
|
160
|
+
"trace_enabled_",
|
|
161
|
+
"trades_",
|
|
162
|
+
})
|