@pineforge/codegen-pyodide 1.0.0 → 1.1.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/glue.py +2 -1
- package/package.json +1 -1
- package/pineforge_codegen/__init__.py +24 -9
- package/pineforge_codegen/analyzer/base.py +190 -1
- package/pineforge_codegen/analyzer/call_handlers.py +2 -0
- package/pineforge_codegen/analyzer/contracts.py +6 -0
- package/pineforge_codegen/analyzer/types.py +16 -5
- package/pineforge_codegen/ast_nodes.py +7 -0
- package/pineforge_codegen/codegen/base.py +136 -14
- package/pineforge_codegen/codegen/checked_settings.py +272 -0
- package/pineforge_codegen/codegen/collection_history.py +200 -0
- package/pineforge_codegen/codegen/emit_top.py +76 -14
- package/pineforge_codegen/codegen/helpers.py +12 -2
- package/pineforge_codegen/codegen/host_members.py +8 -0
- package/pineforge_codegen/codegen/input.py +36 -3
- package/pineforge_codegen/codegen/security.py +56 -1
- package/pineforge_codegen/codegen/ta.py +65 -1
- package/pineforge_codegen/codegen/types.py +136 -8
- package/pineforge_codegen/codegen/visit_call.py +92 -3
- package/pineforge_codegen/codegen/visit_expr.py +202 -3
- package/pineforge_codegen/codegen/visit_stmt.py +19 -4
- package/pineforge_codegen/collection_history.py +1595 -0
- package/pineforge_codegen/external_requests.py +3 -0
- package/pineforge_codegen/parser.py +19 -4
- package/pineforge_codegen/request_discovery.py +506 -0
- package/pineforge_codegen/support_checker.py +37 -0
- package/pineforge_codegen-1.1.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +1 -1
- package/transpile.worker.mjs +2 -1
- package/pineforge_codegen-1.0.0.tar.gz +0 -0
|
@@ -1131,6 +1131,65 @@ class TaSiteHelper:
|
|
|
1131
1131
|
)
|
|
1132
1132
|
return cache[id(node)]
|
|
1133
1133
|
|
|
1134
|
+
def _lazy_ctor_history_call(self, call) -> bool:
|
|
1135
|
+
"""A positional call of the one user function of its name whose body
|
|
1136
|
+
is a single ``T.new(...)`` of a user-defined type over
|
|
1137
|
+
``_lazy_call_history_pure_expr`` arguments of its parameters: its value
|
|
1138
|
+
is a new object of fixed fields, and TradingView runs it on every
|
|
1139
|
+
execution of its scope below a lazy edge when its history is read
|
|
1140
|
+
(fixtures/udt_history_tv udth_expr: ``(mk(bar_index * 7)[1]).v`` in a
|
|
1141
|
+
ternary's arm reads the previous bar's call)."""
|
|
1142
|
+
if not isinstance(call, FuncCall) or not isinstance(call.callee, Identifier):
|
|
1143
|
+
return False
|
|
1144
|
+
overloads = [fi for fi in self.ctx.func_infos if fi.name == call.callee.name]
|
|
1145
|
+
if call.kwargs or len(overloads) != 1 or overloads[0].node is None:
|
|
1146
|
+
return False
|
|
1147
|
+
node = overloads[0].node
|
|
1148
|
+
body = getattr(node, "body", None) or []
|
|
1149
|
+
if len(call.args) != len(node.params) or len(body) != 1:
|
|
1150
|
+
return False
|
|
1151
|
+
ctor = body[0].expr if isinstance(body[0], ExprStmt) else None
|
|
1152
|
+
if not (isinstance(ctor, FuncCall)
|
|
1153
|
+
and isinstance(ctor.callee, MemberAccess)
|
|
1154
|
+
and isinstance(ctor.callee.object, Identifier)
|
|
1155
|
+
and ctor.callee.object.name in self._udt_defs
|
|
1156
|
+
and ctor.callee.member == "new"):
|
|
1157
|
+
return False
|
|
1158
|
+
params = set(node.params)
|
|
1159
|
+
return all(self._lazy_call_history_pure_expr(arg, params)
|
|
1160
|
+
for arg in [*ctor.args, *ctor.kwargs.values()])
|
|
1161
|
+
|
|
1162
|
+
def _lazy_reference_history_object(self, obj) -> bool:
|
|
1163
|
+
"""Whether the history of the object or drawing reference ``obj``
|
|
1164
|
+
below a lazy edge is kept on every execution of its scope, as
|
|
1165
|
+
TradingView keeps it (udth_expr): a pure call or a
|
|
1166
|
+
``_lazy_ctor_history_call`` over pure arguments, a ternary over
|
|
1167
|
+
names, literals and operators, or a field of a named object (read
|
|
1168
|
+
na while the object is na, ``_lazy_history_read_cpp``)."""
|
|
1169
|
+
if self._reference_cpp_type(obj) is None:
|
|
1170
|
+
return False
|
|
1171
|
+
if isinstance(obj, FuncCall):
|
|
1172
|
+
return ((self._lazy_call_history_pure_call(obj)
|
|
1173
|
+
or self._lazy_ctor_history_call(obj))
|
|
1174
|
+
and all(self._lazy_call_history_pure_expr(arg, None)
|
|
1175
|
+
for arg in obj.args))
|
|
1176
|
+
if isinstance(obj, Ternary):
|
|
1177
|
+
return self._lazy_call_history_pure_expr(obj, None)
|
|
1178
|
+
return (isinstance(obj, MemberAccess)
|
|
1179
|
+
and isinstance(obj.object, Identifier)
|
|
1180
|
+
and self._reference_cpp_type(obj.object) is not None)
|
|
1181
|
+
|
|
1182
|
+
def _lazy_history_read_cpp(self, obj) -> str:
|
|
1183
|
+
"""The value a hoisted history read pushes: the expression, a field of
|
|
1184
|
+
a na object reading na (TradingView stops on the read itself)."""
|
|
1185
|
+
if (isinstance(obj, MemberAccess)
|
|
1186
|
+
and self._reference_cpp_type(obj.object) is not None):
|
|
1187
|
+
handle = self._reference_cpp_type(obj)
|
|
1188
|
+
receiver = self._visit_expr(obj.object)
|
|
1189
|
+
return (f"(is_na({receiver}) ? {handle}{{}} : "
|
|
1190
|
+
f"{handle}({self._visit_expr(obj)}))")
|
|
1191
|
+
return self._visit_expr(obj)
|
|
1192
|
+
|
|
1134
1193
|
def _lazy_call_history_units(self, stmt) -> list:
|
|
1135
1194
|
"""The ``pure_call(...)[k]`` reads below a lazy edge of ``stmt``'s
|
|
1136
1195
|
roots (``_lazy_edge_statement_roots``), in evaluation order, whose
|
|
@@ -1159,6 +1218,10 @@ class TaSiteHelper:
|
|
|
1159
1218
|
for arg in call.args)):
|
|
1160
1219
|
units.append(expr)
|
|
1161
1220
|
return
|
|
1221
|
+
if (under_lazy and self._registered_history_handle(expr) is not None
|
|
1222
|
+
and self._lazy_reference_history_object(call)):
|
|
1223
|
+
units.append(expr)
|
|
1224
|
+
return
|
|
1162
1225
|
scan(expr.object, under_lazy)
|
|
1163
1226
|
scan(expr.index, under_lazy)
|
|
1164
1227
|
elif isinstance(expr, FuncCall):
|
|
@@ -1219,7 +1282,8 @@ class TaSiteHelper:
|
|
|
1219
1282
|
hoisted = []
|
|
1220
1283
|
for read in units:
|
|
1221
1284
|
member = self._inline_history_member("hist_call", read)
|
|
1222
|
-
self._emit_history_series_write(
|
|
1285
|
+
self._emit_history_series_write(
|
|
1286
|
+
lines, pad, member, self._lazy_history_read_cpp(read.object))
|
|
1223
1287
|
self._hoisted_hist_reads[id(read)] = member
|
|
1224
1288
|
hoisted.append(id(read))
|
|
1225
1289
|
return hoisted
|
|
@@ -42,6 +42,7 @@ from ..ast_nodes import (
|
|
|
42
42
|
MemberAccess, MethodDef, NaLiteral, NumberLiteral, StringLiteral, SwitchStmt,
|
|
43
43
|
Subscript, Ternary, TupleAssign, TupleLiteral, UnaryOp, VarDecl,
|
|
44
44
|
)
|
|
45
|
+
from ..collection_history import history_annotation
|
|
45
46
|
from ..errors import Phase
|
|
46
47
|
from ..external_requests import UNPINNED_ANNOTATION
|
|
47
48
|
from ..limits import iter_ast_nodes
|
|
@@ -1545,14 +1546,26 @@ class TypeInferer:
|
|
|
1545
1546
|
return TypeSpec.primitive(primitive_name)
|
|
1546
1547
|
return None
|
|
1547
1548
|
if isinstance(node, Subscript):
|
|
1548
|
-
# History access preserves the
|
|
1549
|
-
# is
|
|
1550
|
-
#
|
|
1549
|
+
# History access preserves the drawing-handle and user-object
|
|
1550
|
+
# type: ``c[1]`` is the reference ``c`` held one bar back
|
|
1551
|
+
# (fixtures/udt_history_tv). This is intentionally not
|
|
1552
|
+
# generalized to arrays/maps: their subscripting rules are handled
|
|
1553
|
+
# by the collection paths below.
|
|
1551
1554
|
receiver_spec = self._type_spec_from_expr(node.object)
|
|
1552
1555
|
if (receiver_spec is not None
|
|
1553
1556
|
and receiver_spec.kind == "udt"
|
|
1554
|
-
and receiver_spec.name in DRAWING_TYPE_TO_CPP
|
|
1557
|
+
and (receiver_spec.name in DRAWING_TYPE_TO_CPP
|
|
1558
|
+
or receiver_spec.name in self._udt_defs)):
|
|
1555
1559
|
return receiver_spec
|
|
1560
|
+
annotation = history_annotation(node)
|
|
1561
|
+
if annotation is not None:
|
|
1562
|
+
# An array's or a matrix's history is a collection of its
|
|
1563
|
+
# type (pineforge_codegen/collection_history.py), the
|
|
1564
|
+
# analyzer's when no declaration registered one yet.
|
|
1565
|
+
if (receiver_spec is not None
|
|
1566
|
+
and receiver_spec.kind in ("array", "matrix")):
|
|
1567
|
+
return receiver_spec
|
|
1568
|
+
return annotation["spec"]
|
|
1556
1569
|
return None
|
|
1557
1570
|
if isinstance(node, Ternary):
|
|
1558
1571
|
true_spec = self._type_spec_from_expr(node.true_val)
|
|
@@ -1851,13 +1864,18 @@ class TypeInferer:
|
|
|
1851
1864
|
if return_spec is not None:
|
|
1852
1865
|
return return_spec
|
|
1853
1866
|
if recv_spec is not None and recv_spec.kind == "matrix":
|
|
1854
|
-
|
|
1867
|
+
# A method on a matrix's history names its method only
|
|
1868
|
+
# in the member (``_resolve_callee`` reports none).
|
|
1869
|
+
method = (member_name
|
|
1870
|
+
if history_annotation(node.callee.object) is not None
|
|
1871
|
+
else func_name)
|
|
1872
|
+
if method in MATRIX_RETURNING_METHODS:
|
|
1855
1873
|
return recv_spec
|
|
1856
|
-
if
|
|
1874
|
+
if method in ("row", "col"):
|
|
1857
1875
|
return TypeSpec.array(recv_spec.element)
|
|
1858
|
-
if
|
|
1876
|
+
if method == "get":
|
|
1859
1877
|
return recv_spec.element
|
|
1860
|
-
if
|
|
1878
|
+
if method == "eigenvalues":
|
|
1861
1879
|
return TypeSpec.array(TypeSpec.primitive("float"))
|
|
1862
1880
|
# Drawing method-form: ``a.copy()`` -> same handle type;
|
|
1863
1881
|
# ``lf.get_line1()`` -> line. (L-N6 alias-vs-copy typing.)
|
|
@@ -2206,6 +2224,9 @@ class TypeInferer:
|
|
|
2206
2224
|
# init RHS is such a builtin, so also match the builtin name directly.
|
|
2207
2225
|
if name in INT64_BUILTINS:
|
|
2208
2226
|
return "int64_t"
|
|
2227
|
+
handle = self._series_handle_cpp_type(name)
|
|
2228
|
+
if handle is not None:
|
|
2229
|
+
return handle
|
|
2209
2230
|
sym = self._variable_symbol(name)
|
|
2210
2231
|
# A float or bool an epoch reaches keeps its type: the analyzer types
|
|
2211
2232
|
# every request.security value float (a double holds an epoch
|
|
@@ -2225,6 +2246,51 @@ class TypeInferer:
|
|
|
2225
2246
|
return PINE_TYPE_TO_CPP.get(sym.pine_type, "double")
|
|
2226
2247
|
return "double"
|
|
2227
2248
|
|
|
2249
|
+
def _series_handle_type_name(self, name: str) -> str | None:
|
|
2250
|
+
"""The user-defined or drawing type (``Cell``, ``box``) of the
|
|
2251
|
+
history-read variable ``name``, else None. Such a variable holds a
|
|
2252
|
+
reference, and its history the references it held: ``(c[1]).v`` reads
|
|
2253
|
+
the object ``c`` held one bar back as it is now (fixtures/
|
|
2254
|
+
udt_history_tv; Pine v6 User Manual, "Type system": value vs.
|
|
2255
|
+
reference types). A buffer is keyed by spelling across scopes, so
|
|
2256
|
+
every scope's history-read variable of that spelling must hold the
|
|
2257
|
+
same type; any other variable keeps the buffer it always had.
|
|
2258
|
+
Cached per name."""
|
|
2259
|
+
cache = self.__dict__.setdefault("_series_handle_type_cache", {})
|
|
2260
|
+
if name in cache:
|
|
2261
|
+
return cache[name]
|
|
2262
|
+
found: set[str] = set()
|
|
2263
|
+
for scope in self.ctx.symbols.all_scopes:
|
|
2264
|
+
sym = scope.symbols.get(name)
|
|
2265
|
+
if sym is None or not getattr(sym, "is_series", False):
|
|
2266
|
+
continue
|
|
2267
|
+
spec = getattr(sym, "type_spec", None)
|
|
2268
|
+
if (spec is None or spec.kind != "udt"
|
|
2269
|
+
or not (spec.name in DRAWING_TYPE_TO_CPP
|
|
2270
|
+
or spec.name in self._udt_defs)):
|
|
2271
|
+
found = set()
|
|
2272
|
+
break
|
|
2273
|
+
found.add(spec.name)
|
|
2274
|
+
cache[name] = next(iter(found)) if len(found) == 1 else None
|
|
2275
|
+
return cache[name]
|
|
2276
|
+
|
|
2277
|
+
def _series_handle_cpp_type(self, name: str) -> str | None:
|
|
2278
|
+
"""The handle such a variable stores per bar (``Series<Cell>``,
|
|
2279
|
+
``Series<Box>``; a na reference is the handle's ``T{}``), else
|
|
2280
|
+
None (``_series_handle_type_name``)."""
|
|
2281
|
+
udt = self._series_handle_type_name(name)
|
|
2282
|
+
if udt is None:
|
|
2283
|
+
return None
|
|
2284
|
+
return DRAWING_TYPE_TO_CPP.get(udt) or self._safe_name(udt)
|
|
2285
|
+
|
|
2286
|
+
def _series_handle_target(self, name: str) -> str | None:
|
|
2287
|
+
"""``_visit_rhs_value``'s target for a value stored in such a
|
|
2288
|
+
variable's history: the drawing handle, or the user type's name."""
|
|
2289
|
+
udt = self._series_handle_type_name(name)
|
|
2290
|
+
if udt is None:
|
|
2291
|
+
return None
|
|
2292
|
+
return DRAWING_TYPE_TO_CPP.get(udt, udt)
|
|
2293
|
+
|
|
2228
2294
|
def _variable_symbol(self, name: str):
|
|
2229
2295
|
"""The symbol the variable ``name`` resolves to.
|
|
2230
2296
|
|
|
@@ -2303,6 +2369,18 @@ class TypeInferer:
|
|
|
2303
2369
|
declared_spec = (
|
|
2304
2370
|
declared_specs[index] if index < len(declared_specs) else None
|
|
2305
2371
|
)
|
|
2372
|
+
# A parameter holding a user-defined object or a drawing keeps the
|
|
2373
|
+
# references its calls passed (``_series_handle_cpp_type``).
|
|
2374
|
+
inferred_specs = list(getattr(func_info, "param_type_specs", ()) or ())
|
|
2375
|
+
for candidate in (
|
|
2376
|
+
declared_spec,
|
|
2377
|
+
inferred_specs[index] if index < len(inferred_specs) else None,
|
|
2378
|
+
):
|
|
2379
|
+
if (candidate is not None and candidate.kind == "udt"
|
|
2380
|
+
and (candidate.name in DRAWING_TYPE_TO_CPP
|
|
2381
|
+
or candidate.name in self._udt_defs)):
|
|
2382
|
+
return (DRAWING_TYPE_TO_CPP.get(candidate.name)
|
|
2383
|
+
or self._safe_name(candidate.name))
|
|
2306
2384
|
# A declared type is authoritative across every written call. Only a
|
|
2307
2385
|
# truly untyped slot may consult the per-callsite specialization map.
|
|
2308
2386
|
spec = declared_spec
|
|
@@ -3302,6 +3380,19 @@ class TypeInferer:
|
|
|
3302
3380
|
cpp_type = "int64_t"
|
|
3303
3381
|
return cpp_type if cpp_type in (*NA_PRESERVING_INT_TYPES, "bool") else None
|
|
3304
3382
|
|
|
3383
|
+
def _udt_field_is_double(self, target_node) -> bool:
|
|
3384
|
+
"""Whether a UDT field write target is a ``float`` field (a C++
|
|
3385
|
+
``double``)."""
|
|
3386
|
+
if not isinstance(target_node, MemberAccess):
|
|
3387
|
+
return False
|
|
3388
|
+
owner = self._type_spec_from_expr(target_node.object)
|
|
3389
|
+
if owner is None or owner.kind != "udt" or not owner.name:
|
|
3390
|
+
return False
|
|
3391
|
+
spec = (self._udt_field_type_specs.get(owner.name) or {}).get(
|
|
3392
|
+
target_node.member
|
|
3393
|
+
)
|
|
3394
|
+
return spec is not None and self._type_spec_to_cpp(spec) == "double"
|
|
3395
|
+
|
|
3305
3396
|
def _coerce_int_slot(self, cpp_val: str, node, target_cpp_type: str | None,
|
|
3306
3397
|
*, value_is_double: bool | None = None) -> str:
|
|
3307
3398
|
"""Route a value into an ``int``/``int64_t`` slot without losing ``na``.
|
|
@@ -3343,6 +3434,30 @@ class TypeInferer:
|
|
|
3343
3434
|
return na_preserving_int_cast(cpp_val, target_cpp_type)
|
|
3344
3435
|
return cpp_val
|
|
3345
3436
|
|
|
3437
|
+
def _coerce_double_slot(self, cpp_val: str, node) -> str:
|
|
3438
|
+
"""Route a value into a ``double`` that a designated initializer
|
|
3439
|
+
fills (a user-defined record's ``float`` field: ``Cell.new(v =
|
|
3440
|
+
bar_index)``). C++ refuses to narrow a non-constant ``int`` or
|
|
3441
|
+
``int64_t`` there, which did not compile; TradingView holds the
|
|
3442
|
+
number, an integer na as na (fixtures/array_history_tv uctor_float).
|
|
3443
|
+
A double, an integer literal or an expression of literals (a constant
|
|
3444
|
+
a double holds exactly narrows) and a bare na keep their spelling."""
|
|
3445
|
+
constant = self._pure_int_literal_value(node)
|
|
3446
|
+
if (cpp_val == "na<double>()"
|
|
3447
|
+
or self._INTEGRAL_CPP_TEXT.match(cpp_val)
|
|
3448
|
+
or (constant is not None and abs(constant) <= 2 ** 53)
|
|
3449
|
+
or self._emitted_value_is_double(node)):
|
|
3450
|
+
return cpp_val
|
|
3451
|
+
if cpp_val in {"na<int>()", "na<int64_t>()"}:
|
|
3452
|
+
return "na<double>()"
|
|
3453
|
+
# A bool (TradingView refuses one here: CE10123, CE10173) is 1 or 0,
|
|
3454
|
+
# never na, whatever is_na() of a bool resolves to.
|
|
3455
|
+
return (f"[&](){{ auto _pf_w = ({cpp_val}); "
|
|
3456
|
+
f"if constexpr (std::is_floating_point_v<decltype(_pf_w)> "
|
|
3457
|
+
f"|| std::is_same_v<decltype(_pf_w), bool>) "
|
|
3458
|
+
f"return (double)_pf_w; "
|
|
3459
|
+
f"else return is_na(_pf_w) ? na<double>() : (double)_pf_w; }}()")
|
|
3460
|
+
|
|
3346
3461
|
def _coerce_int_slot_with_cast(
|
|
3347
3462
|
self, cpp_val: str, node, target_cpp_type: str
|
|
3348
3463
|
) -> str:
|
|
@@ -3605,12 +3720,25 @@ class TypeInferer:
|
|
|
3605
3720
|
and ternaries / if / switch expressions. Returns the string
|
|
3606
3721
|
``"double"`` as the safe fallback when no narrower type can be
|
|
3607
3722
|
determined."""
|
|
3723
|
+
if isinstance(node, Subscript) and history_annotation(node) is not None:
|
|
3724
|
+
# An array's or a matrix's history is a collection of its type
|
|
3725
|
+
# (collection_history.py): a loop's local bound to it is one.
|
|
3726
|
+
spec = self._type_spec_from_expr(node)
|
|
3727
|
+
if spec is not None:
|
|
3728
|
+
return self._type_spec_to_cpp(spec)
|
|
3608
3729
|
if isinstance(node, Subscript) and isinstance(node.object, Identifier):
|
|
3609
3730
|
name = node.object.name
|
|
3610
3731
|
if name in self.ctx.series_vars:
|
|
3611
3732
|
return self._series_type_for(name)
|
|
3612
3733
|
if name in self.ctx.series_bar_fields:
|
|
3613
3734
|
return "double"
|
|
3735
|
+
if (isinstance(node, Subscript)
|
|
3736
|
+
and isinstance(node.object, (FuncCall, Ternary, MemberAccess))):
|
|
3737
|
+
# The history of an expression whose value is an object or a
|
|
3738
|
+
# drawing is a reference of its type (``_history_value_cpp_type``).
|
|
3739
|
+
reference = self._reference_cpp_type(node.object)
|
|
3740
|
+
if reference is not None:
|
|
3741
|
+
return reference
|
|
3614
3742
|
if isinstance(node, Subscript) and isinstance(node.object, FuncCall):
|
|
3615
3743
|
# A callable-result history read keeps the callable's scalar
|
|
3616
3744
|
# family. Collection subscripts follow separate element-type paths.
|
|
@@ -143,11 +143,13 @@ from ..ast_nodes import (
|
|
|
143
143
|
MemberAccess,
|
|
144
144
|
NaLiteral,
|
|
145
145
|
NumberLiteral,
|
|
146
|
+
Subscript,
|
|
146
147
|
Ternary,
|
|
147
148
|
TupleLiteral,
|
|
148
149
|
StringLiteral,
|
|
149
150
|
VarDecl,
|
|
150
151
|
)
|
|
152
|
+
from ..collection_history import history_annotation, literal_offset
|
|
151
153
|
from ..external_requests import RECORDED_KEY_ANNOTATION
|
|
152
154
|
from ..limits import iter_ast_nodes
|
|
153
155
|
from ..security_contexts import ticker_symbol_arg
|
|
@@ -583,7 +585,9 @@ class CallVisitor:
|
|
|
583
585
|
for arg in rest_nodes
|
|
584
586
|
)
|
|
585
587
|
)) or self._binds_fresh_array_to_reference(
|
|
586
|
-
method_info, rest_nodes, first_param=1)
|
|
588
|
+
method_info, rest_nodes, first_param=1)
|
|
589
|
+
or self._binds_temporary_drawing_to_reference(
|
|
590
|
+
method_info, [receiver_node, *rest_nodes]),
|
|
587
591
|
)
|
|
588
592
|
|
|
589
593
|
def _callable_target_callsite_idx(self, fi, node: FuncCall) -> int | None:
|
|
@@ -1211,6 +1215,12 @@ class CallVisitor:
|
|
|
1211
1215
|
returns its argument or a global) or a selection of a held and a
|
|
1212
1216
|
fresh array, and ``"keep"`` for every other argument, which keeps the
|
|
1213
1217
|
lowering it had: a variable, a field, ``na``, any other built-in."""
|
|
1218
|
+
annotation = history_annotation(node)
|
|
1219
|
+
if annotation is not None:
|
|
1220
|
+
# A copy of an array's history for the callee (collection_history.py);
|
|
1221
|
+
# at a zero offset, the variable itself.
|
|
1222
|
+
return ("keep" if literal_offset(node) == 0 or annotation["use"] != "copy"
|
|
1223
|
+
else "fresh")
|
|
1214
1224
|
if isinstance(node, Ternary):
|
|
1215
1225
|
kinds = {self._array_arg_kind(node.true_val, depth),
|
|
1216
1226
|
self._array_arg_kind(node.false_val, depth)}
|
|
@@ -1306,6 +1316,63 @@ class CallVisitor:
|
|
|
1306
1316
|
stage = stage or kind == "fresh"
|
|
1307
1317
|
return stage
|
|
1308
1318
|
|
|
1319
|
+
# The drawing types a callable takes as ``T&`` (emit_top: a parameter of
|
|
1320
|
+
# a type outside the user types is a reference).
|
|
1321
|
+
_REFERENCE_DRAWING_PARAMS = frozenset({"line", "box", "label", "linefill", "chart.point"})
|
|
1322
|
+
|
|
1323
|
+
def _drawing_arg_is_temporary(self, arg) -> bool:
|
|
1324
|
+
"""Whether the C++ of the drawing argument ``arg`` is a prvalue no
|
|
1325
|
+
``T&`` takes: a history read or the current slot of a history-read
|
|
1326
|
+
variable (``b[1]``, ``b[0]``: ``Series::operator[]`` returns a copy),
|
|
1327
|
+
a new or copied drawing or chart point, a user call's result, or a
|
|
1328
|
+
selection with such an arm. A variable, a field and a collection
|
|
1329
|
+
element are lvalues, which compiled before and keep their C++."""
|
|
1330
|
+
if isinstance(arg, Subscript):
|
|
1331
|
+
return True
|
|
1332
|
+
if isinstance(arg, Identifier):
|
|
1333
|
+
return self._identifier_reads_series(arg)
|
|
1334
|
+
if isinstance(arg, Ternary):
|
|
1335
|
+
return (self._drawing_arg_is_temporary(arg.true_val)
|
|
1336
|
+
or self._drawing_arg_is_temporary(arg.false_val))
|
|
1337
|
+
if not isinstance(arg, FuncCall):
|
|
1338
|
+
return False
|
|
1339
|
+
func_name, namespace = self._resolve_callee(arg.callee)
|
|
1340
|
+
if namespace in DRAWING_NS and func_name in ("new", "copy"):
|
|
1341
|
+
return True
|
|
1342
|
+
if self._is_chart_point_callee(arg.callee):
|
|
1343
|
+
return True
|
|
1344
|
+
if namespace is None and func_name in self._func_info_map:
|
|
1345
|
+
return True
|
|
1346
|
+
return (isinstance(arg.callee, MemberAccess)
|
|
1347
|
+
and self._typed_user_method_info(
|
|
1348
|
+
arg.callee.object, arg.callee.member)[1] is not None)
|
|
1349
|
+
|
|
1350
|
+
def _binds_temporary_drawing_to_reference(self, func_info, arg_nodes: list,
|
|
1351
|
+
first_param: int = 0) -> bool:
|
|
1352
|
+
"""Whether a call passes a temporary drawing (``_drawing_arg_is_temporary``)
|
|
1353
|
+
to a drawing parameter its callee takes as ``T&``: staging the call
|
|
1354
|
+
(``_ordered_user_call_expr``) binds it to a named forwarding
|
|
1355
|
+
reference. The handle is an arena id and Pine forbids reassigning a
|
|
1356
|
+
parameter, so the callee reads the drawing the argument names
|
|
1357
|
+
(udth_drawparam). A history-read parameter takes a Series bridge
|
|
1358
|
+
instead."""
|
|
1359
|
+
if func_info is None:
|
|
1360
|
+
return False
|
|
1361
|
+
specs = list(getattr(func_info, "param_type_specs", ()) or ())
|
|
1362
|
+
params = (list(func_info.node.params)
|
|
1363
|
+
if getattr(func_info, "node", None) is not None else [])
|
|
1364
|
+
series = self.ctx.func_series_vars.get(func_info.name, set())
|
|
1365
|
+
for offset, arg in enumerate(arg_nodes):
|
|
1366
|
+
index = first_param + offset
|
|
1367
|
+
spec = specs[index] if index < len(specs) else None
|
|
1368
|
+
if (arg is None or spec is None or spec.kind != "udt"
|
|
1369
|
+
or spec.name not in self._REFERENCE_DRAWING_PARAMS
|
|
1370
|
+
or (index < len(params) and params[index] in series)):
|
|
1371
|
+
continue
|
|
1372
|
+
if self._drawing_arg_is_temporary(arg):
|
|
1373
|
+
return True
|
|
1374
|
+
return False
|
|
1375
|
+
|
|
1309
1376
|
def _ordered_user_call_expr(
|
|
1310
1377
|
self,
|
|
1311
1378
|
call_head: str,
|
|
@@ -1584,6 +1651,17 @@ class CallVisitor:
|
|
|
1584
1651
|
args = self._array_method_args(callee.member, arg_nodes, recv_spec)
|
|
1585
1652
|
return self._array_method_expr(recv, callee.member, args, recv_spec, node)
|
|
1586
1653
|
|
|
1654
|
+
# A matrix method on a matrix's history (``(m[1]).get(0, 0)``:
|
|
1655
|
+
# collection_history.py), which no other receiver path reaches.
|
|
1656
|
+
if (isinstance(callee, MemberAccess)
|
|
1657
|
+
and isinstance(callee.object, Subscript)
|
|
1658
|
+
and history_annotation(callee.object) is not None
|
|
1659
|
+
and callee.member in MATRIX_METHODS):
|
|
1660
|
+
recv_spec = self._type_spec_from_expr(callee.object)
|
|
1661
|
+
if recv_spec is not None and recv_spec.kind == "matrix":
|
|
1662
|
+
return self._matrix_history_method_expr(
|
|
1663
|
+
callee.object, callee.member, recv_spec, node)
|
|
1664
|
+
|
|
1587
1665
|
# chart.point.now/new/from_index/from_time/copy — REAL data (a ChartPoint
|
|
1588
1666
|
# aggregate). Routed here BEFORE the obj.field.method receiver logic,
|
|
1589
1667
|
# which would otherwise mis-treat ``chart.point`` as a receiver object
|
|
@@ -1823,6 +1901,9 @@ class CallVisitor:
|
|
|
1823
1901
|
drawing = self._drawing_na_expr(node.args[0])
|
|
1824
1902
|
if drawing is not None:
|
|
1825
1903
|
return drawing
|
|
1904
|
+
history = self._collection_history_na(node.args[0])
|
|
1905
|
+
if history is not None:
|
|
1906
|
+
return history
|
|
1826
1907
|
args = ", ".join(self._visit_expr(a) for a in node.args)
|
|
1827
1908
|
return f"is_na({args})"
|
|
1828
1909
|
|
|
@@ -2564,9 +2645,13 @@ class CallVisitor:
|
|
|
2564
2645
|
if func_name in MATRIX_METHODS and node.args:
|
|
2565
2646
|
from ..ast_nodes import Identifier as _Ident
|
|
2566
2647
|
if func_name in MATRIX_NUMERIC_ONLY:
|
|
2567
|
-
|
|
2648
|
+
recv_node = node.args[0]
|
|
2649
|
+
if history_annotation(recv_node) is not None:
|
|
2650
|
+
# A matrix's history is a matrix of its type.
|
|
2651
|
+
recv_node = recv_node.object
|
|
2652
|
+
if not isinstance(recv_node, _Ident):
|
|
2568
2653
|
self._codegen_error(node, f"matrix.{func_name} receiver must be a variable reference")
|
|
2569
|
-
recv_name =
|
|
2654
|
+
recv_name = recv_node.name
|
|
2570
2655
|
recv_spec = self._collection_spec_for_name(recv_name)
|
|
2571
2656
|
if recv_spec is None or recv_spec.kind != "matrix":
|
|
2572
2657
|
self._codegen_error(node, f"matrix.{func_name}: receiver '{recv_name}' is not a known matrix variable")
|
|
@@ -2684,6 +2769,8 @@ class CallVisitor:
|
|
|
2684
2769
|
val = "na<int64_t>()"
|
|
2685
2770
|
elif f_cpp_type == "bool":
|
|
2686
2771
|
val = self._coerce_bool_expr(val, value_node)
|
|
2772
|
+
elif f_cpp_type == "double":
|
|
2773
|
+
val = self._coerce_double_slot(val, value_node)
|
|
2687
2774
|
elif (self._is_nullable_collection_cpp_type(f_cpp_type)
|
|
2688
2775
|
and val == "na<double>()"):
|
|
2689
2776
|
val = f"{f_cpp_type}{{}}"
|
|
@@ -2958,6 +3045,8 @@ class CallVisitor:
|
|
|
2958
3045
|
all_args,
|
|
2959
3046
|
source_order_nodes=[*node.args, *node.kwargs.values()],
|
|
2960
3047
|
force_stage=self._binds_fresh_array_to_reference(
|
|
3048
|
+
self._func_info_map.get(func_name), ordered_arg_nodes)
|
|
3049
|
+
or self._binds_temporary_drawing_to_reference(
|
|
2961
3050
|
self._func_info_map.get(func_name), ordered_arg_nodes),
|
|
2962
3051
|
)
|
|
2963
3052
|
return f"{call_head}({', '.join(all_args)})"
|