@pineforge/codegen-pyodide 0.8.1 → 0.9.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/package.json +1 -1
- package/pineforge_codegen/analyzer/base.py +350 -36
- package/pineforge_codegen/analyzer/call_handlers.py +244 -133
- package/pineforge_codegen/analyzer/contracts.py +29 -0
- package/pineforge_codegen/analyzer/diagnostics.py +18 -4
- package/pineforge_codegen/analyzer/tables.py +20 -2
- package/pineforge_codegen/codegen/base.py +1147 -110
- package/pineforge_codegen/codegen/drawing.py +1 -1
- package/pineforge_codegen/codegen/emit_top.py +253 -21
- package/pineforge_codegen/codegen/input.py +17 -0
- package/pineforge_codegen/codegen/security.py +506 -35
- package/pineforge_codegen/codegen/ta.py +87 -1
- package/pineforge_codegen/codegen/tables.py +56 -7
- package/pineforge_codegen/codegen/types.py +112 -30
- package/pineforge_codegen/codegen/visit_call.py +184 -53
- package/pineforge_codegen/codegen/visit_expr.py +62 -14
- package/pineforge_codegen/codegen/visit_stmt.py +74 -36
- package/pineforge_codegen/parser.py +73 -6
- package/pineforge_codegen/signatures.py +2 -2
- package/pineforge_codegen/support_checker.py +39 -2
- package/pineforge_codegen-0.9.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +6 -3
- package/pineforge_codegen-0.8.1.tar.gz +0 -0
|
@@ -258,6 +258,10 @@ class StmtVisitor:
|
|
|
258
258
|
# Global-scope non-var vars are class members — emit assignment, not declaration
|
|
259
259
|
is_global_member = node.name in self._global_member_vars
|
|
260
260
|
|
|
261
|
+
def remember_local_type(cpp_type: str | None) -> None:
|
|
262
|
+
if cpp_type and not is_global_member:
|
|
263
|
+
self._current_func_local_types[node.name] = cpp_type
|
|
264
|
+
|
|
261
265
|
# Check if it is a static (non-series) global member variable already evaluated inside _inputs_initialized_ block
|
|
262
266
|
is_static_global_input = False
|
|
263
267
|
if is_global_member and isinstance(node.value, FuncCall) and self._is_input_call(node.value):
|
|
@@ -284,7 +288,7 @@ class StmtVisitor:
|
|
|
284
288
|
title = self._get_input_title(node.value, var_name=node.name)
|
|
285
289
|
cpp_val = self._render_input_value(node.value, func_name_i, namespace_i, title)
|
|
286
290
|
if node.name in self.ctx.series_vars:
|
|
287
|
-
|
|
291
|
+
self._emit_history_series_write(lines, pad, safe, cpp_val)
|
|
288
292
|
elif is_global_member:
|
|
289
293
|
lines.append(f"{pad}{safe} = {cpp_val};")
|
|
290
294
|
else:
|
|
@@ -381,9 +385,12 @@ class StmtVisitor:
|
|
|
381
385
|
compute_args = self._ta_compute_args_for_site(site)
|
|
382
386
|
ret_type = "bool" if self._ta_name_from_site(site) in TA_RETURNS_BOOL else "double"
|
|
383
387
|
ta_name = self._ta_member_name(site)
|
|
384
|
-
ta_expr =
|
|
388
|
+
ta_expr = (
|
|
389
|
+
f"(history_advances_new_bar() ? {ta_name}.compute({compute_args}) "
|
|
390
|
+
f": {ta_name}.recompute({compute_args}))"
|
|
391
|
+
)
|
|
385
392
|
if node.name in self.ctx.series_vars:
|
|
386
|
-
|
|
393
|
+
self._emit_history_series_write(lines, pad, safe, ta_expr)
|
|
387
394
|
elif is_global_member:
|
|
388
395
|
lines.append(f"{pad}{safe} = {ta_expr};")
|
|
389
396
|
else:
|
|
@@ -393,7 +400,7 @@ class StmtVisitor:
|
|
|
393
400
|
# Non-var series variable — push instead of declare
|
|
394
401
|
if node.name in self.ctx.series_vars:
|
|
395
402
|
cpp_val = self._visit_expr(node.value)
|
|
396
|
-
|
|
403
|
+
self._emit_history_series_write(lines, pad, safe, cpp_val)
|
|
397
404
|
return
|
|
398
405
|
|
|
399
406
|
# If/switch expression: x = if cond ... else ...
|
|
@@ -451,12 +458,29 @@ class StmtVisitor:
|
|
|
451
458
|
lines.append(f"{pad}{cpp_type}& {safe} = {cpp_val};")
|
|
452
459
|
return
|
|
453
460
|
|
|
461
|
+
# Global-scope UDT array-element alias (BUG: Pine array elements of a
|
|
462
|
+
# user-defined type are references). A global loop-local bound from
|
|
463
|
+
# ``arr.get(i)`` and later field-mutated must ALIAS the element so the
|
|
464
|
+
# mutation writes back into the array. Emit a fresh per-iteration
|
|
465
|
+
# ``UDT& z = arr[i];`` reference (any hoisted class member was
|
|
466
|
+
# suppressed) — identical to the non-hoisted function-local alias path.
|
|
467
|
+
# Gated by _register_udt_array_get_ref_locals (which only records global-
|
|
468
|
+
# scope names); the ``_current_func_body is None`` guard confines this to
|
|
469
|
+
# global scope, so a function-local sharing the name keeps its own path.
|
|
470
|
+
if (node.name in self._udt_array_get_ref_locals
|
|
471
|
+
and getattr(self, "_current_func_body", None) is None):
|
|
472
|
+
udt_t = self._udt_var_types.get(node.name)
|
|
473
|
+
cpp_val = self._visit_rhs_value(node.value, node.name, target_cpp_type=udt_t)
|
|
474
|
+
lines.append(f"{pad}{udt_t}& {safe} = {cpp_val};")
|
|
475
|
+
return
|
|
476
|
+
|
|
454
477
|
# General declaration
|
|
455
478
|
cpp_type = self._type_for_decl(node) if not is_global_member else None
|
|
456
479
|
cpp_val = self._visit_rhs_value(node.value, node.name, target_cpp_type=cpp_type)
|
|
457
480
|
if is_global_member:
|
|
458
481
|
lines.append(f"{pad}{safe} = {cpp_val};")
|
|
459
482
|
else:
|
|
483
|
+
remember_local_type(cpp_type)
|
|
460
484
|
lines.append(f"{pad}{cpp_type} {safe} = {cpp_val};")
|
|
461
485
|
|
|
462
486
|
@staticmethod
|
|
@@ -591,7 +615,11 @@ class StmtVisitor:
|
|
|
591
615
|
compute_args = self._ta_compute_args_for_site(site)
|
|
592
616
|
ta_mem = self._ta_member_name(site)
|
|
593
617
|
result_var = f"_result_{ta_mem}"
|
|
594
|
-
lines.append(
|
|
618
|
+
lines.append(
|
|
619
|
+
f"{pad}auto {result_var} = (history_advances_new_bar() ? "
|
|
620
|
+
f"{ta_mem}.compute({compute_args}) : "
|
|
621
|
+
f"{ta_mem}.recompute({compute_args}));"
|
|
622
|
+
)
|
|
595
623
|
|
|
596
624
|
ta_name = self._ta_name_from_site(site)
|
|
597
625
|
fields = TA_TUPLE_FIELDS.get(ta_name, [f"field{i}" for i in range(len(node.names))])
|
|
@@ -611,7 +639,9 @@ class StmtVisitor:
|
|
|
611
639
|
# destructured names keep the plain scalar declaration.
|
|
612
640
|
if name in self.ctx.series_vars:
|
|
613
641
|
safe = self._safe_name(name)
|
|
614
|
-
|
|
642
|
+
self._emit_history_series_write(
|
|
643
|
+
lines, pad, safe, field_expr
|
|
644
|
+
)
|
|
615
645
|
else:
|
|
616
646
|
lines.append(f"{pad}double {name} = {field_expr};")
|
|
617
647
|
return
|
|
@@ -716,40 +746,37 @@ class StmtVisitor:
|
|
|
716
746
|
pad = " " * indent
|
|
717
747
|
start = self._visit_expr(node.start)
|
|
718
748
|
end = self._visit_expr(node.end)
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
f"({s_var} <= {e_var}) ? ({var} <= {e_var}) : ({var} >= {e_var}); "
|
|
745
|
-
f"{var} += ({s_var} <= {e_var}) ? 1 : -1) {{"
|
|
746
|
-
)
|
|
749
|
+
step = self._visit_expr(node.step) if node.step is not None else "1"
|
|
750
|
+
var = self._safe_name(node.var) # new AST uses .var instead of .var_name
|
|
751
|
+
|
|
752
|
+
# Pine infers loop direction from the initial ``from``/``to`` values for
|
|
753
|
+
# both implicit and explicit ``by`` loops. The ``by`` value is a positive
|
|
754
|
+
# magnitude; descending loops subtract it. ``to`` can change during the
|
|
755
|
+
# loop, so refresh the cached end expression after each iteration while
|
|
756
|
+
# keeping the initial direction and step fixed.
|
|
757
|
+
fid = self._for_counter
|
|
758
|
+
self._for_counter += 1
|
|
759
|
+
s_var = f"_for_start_{fid}"
|
|
760
|
+
e_var = f"_for_end_{fid}"
|
|
761
|
+
step_var = f"_for_step_{fid}"
|
|
762
|
+
down_var = f"_for_down_{fid}"
|
|
763
|
+
lines.append(f"{pad}int {s_var} = ({start});")
|
|
764
|
+
lines.append(f"{pad}int {e_var} = ({end});")
|
|
765
|
+
lines.append(f"{pad}int {step_var} = ({step});")
|
|
766
|
+
lines.append(f"{pad}if ({step_var} < 0) {step_var} = -{step_var};")
|
|
767
|
+
lines.append(f"{pad}if ({step_var} == 0) {step_var} = 1;")
|
|
768
|
+
lines.append(f"{pad}const bool {down_var} = ({s_var} > {e_var});")
|
|
769
|
+
lines.append(
|
|
770
|
+
f"{pad}for (int {var} = {s_var}; "
|
|
771
|
+
f"({down_var} ? ({var} >= {e_var}) : ({var} <= {e_var})); "
|
|
772
|
+
f"{var} += ({down_var} ? -{step_var} : {step_var}), {e_var} = ({end})) {{"
|
|
773
|
+
)
|
|
747
774
|
# Register the loop counter so reads of it inside the body resolve (the
|
|
748
775
|
# unknown-identifier guard in _visit_ident would otherwise flag it).
|
|
749
776
|
saved_loop = self._current_loop_vars
|
|
750
777
|
self._current_loop_vars = set(self._current_loop_vars)
|
|
751
|
-
if var:
|
|
752
|
-
self._current_loop_vars.add(var)
|
|
778
|
+
if node.var:
|
|
779
|
+
self._current_loop_vars.add(node.var)
|
|
753
780
|
_blk_saved = self._push_block_var_remap(node)
|
|
754
781
|
try:
|
|
755
782
|
for s in node.body:
|
|
@@ -787,9 +814,19 @@ class StmtVisitor:
|
|
|
787
814
|
pad = " " * indent
|
|
788
815
|
iterable = self._visit_expr(node.iterable)
|
|
789
816
|
saved_loop = self._current_loop_vars
|
|
817
|
+
saved_loop_specs = self._current_loop_var_specs
|
|
790
818
|
self._current_loop_vars = set(self._current_loop_vars)
|
|
819
|
+
self._current_loop_var_specs = dict(self._current_loop_var_specs)
|
|
820
|
+
iterable_spec = self._type_spec_from_expr(node.iterable)
|
|
821
|
+
elem_spec = (
|
|
822
|
+
iterable_spec.element
|
|
823
|
+
if iterable_spec is not None and iterable_spec.kind == "array"
|
|
824
|
+
else None
|
|
825
|
+
)
|
|
791
826
|
if node.var:
|
|
792
827
|
self._current_loop_vars.add(node.var)
|
|
828
|
+
if elem_spec is not None:
|
|
829
|
+
self._current_loop_var_specs[node.var] = elem_spec
|
|
793
830
|
if node.vars:
|
|
794
831
|
for v in node.vars:
|
|
795
832
|
if v != "_":
|
|
@@ -809,6 +846,7 @@ class StmtVisitor:
|
|
|
809
846
|
self._pop_block_var_remap(_blk_saved)
|
|
810
847
|
lines.append(f"{pad}}}")
|
|
811
848
|
self._current_loop_vars = saved_loop
|
|
849
|
+
self._current_loop_var_specs = saved_loop_specs
|
|
812
850
|
|
|
813
851
|
def _visit_while(self, node: WhileStmt, lines: list[str], indent: int) -> None:
|
|
814
852
|
pad = " " * indent
|
|
@@ -166,6 +166,28 @@ class Parser:
|
|
|
166
166
|
# ------------------------------------------------------------------
|
|
167
167
|
|
|
168
168
|
def _parse_statement(self):
|
|
169
|
+
stmt = self._parse_single_statement()
|
|
170
|
+
if not self._check(TokenType.COMMA):
|
|
171
|
+
return stmt
|
|
172
|
+
|
|
173
|
+
stmts: list = []
|
|
174
|
+
self._extend_statement_list(stmts, stmt)
|
|
175
|
+
while self._match(TokenType.COMMA):
|
|
176
|
+
if self._check(TokenType.NEWLINE) or self._check(TokenType.DEDENT) or self._at_end():
|
|
177
|
+
break
|
|
178
|
+
self._extend_statement_list(stmts, self._parse_single_statement())
|
|
179
|
+
return stmts
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _extend_statement_list(stmts: list, stmt) -> None:
|
|
183
|
+
if stmt is None:
|
|
184
|
+
return
|
|
185
|
+
if isinstance(stmt, list):
|
|
186
|
+
stmts.extend(stmt)
|
|
187
|
+
else:
|
|
188
|
+
stmts.append(stmt)
|
|
189
|
+
|
|
190
|
+
def _parse_single_statement(self):
|
|
169
191
|
cur = self._current()
|
|
170
192
|
|
|
171
193
|
# Control flow keywords
|
|
@@ -192,6 +214,33 @@ class Parser:
|
|
|
192
214
|
if cur.type in (TokenType.VAR, TokenType.VARIP):
|
|
193
215
|
return self._parse_var_keyword_decl()
|
|
194
216
|
|
|
217
|
+
# Type-qualifier prefix on a top-level declaration. Pine v6 allows
|
|
218
|
+
# ``const`` / ``simple`` / ``series`` to qualify a typed variable
|
|
219
|
+
# declaration (``const int DEFAULT = 1``). The qualifier carries no
|
|
220
|
+
# C++-side meaning (the engine infers mutability from ``var``/
|
|
221
|
+
# ``varip``), so consume it and parse the underlying declaration.
|
|
222
|
+
# Without this the qualifier is parsed as a bare identifier and the
|
|
223
|
+
# subsequent typed decl leaks the stray ``ExprStmt(const)`` that
|
|
224
|
+
# produces "Unknown variable 'const'" at codegen.
|
|
225
|
+
if (cur.type == TokenType.IDENT
|
|
226
|
+
and cur.value in ("const", "series", "simple")):
|
|
227
|
+
nxt = self._peek()
|
|
228
|
+
# ``const int NAME = ...`` / ``const float[] x = ...``
|
|
229
|
+
typed_after_qual = (
|
|
230
|
+
nxt.type in TYPE_KEYWORDS
|
|
231
|
+
or (nxt.type == TokenType.IDENT and self._is_ident_typed_var_decl(offset=1))
|
|
232
|
+
)
|
|
233
|
+
# ``const NAME = ...`` (qualifier on an untyped decl — rare but
|
|
234
|
+
# legal; ``const`` here is just dropped).
|
|
235
|
+
bare_after_qual = (
|
|
236
|
+
nxt.type == TokenType.IDENT
|
|
237
|
+
and self._peek(2).type == TokenType.EQUALS
|
|
238
|
+
and self._peek(3).type != TokenType.EQUALS
|
|
239
|
+
)
|
|
240
|
+
if typed_after_qual or bare_after_qual:
|
|
241
|
+
self._advance() # consume the qualifier prefix
|
|
242
|
+
return self._parse_single_statement()
|
|
243
|
+
|
|
195
244
|
# Type-annotated declaration: float x = ..., int x = ...
|
|
196
245
|
if cur.type in TYPE_KEYWORDS and self._peek().type == TokenType.IDENT:
|
|
197
246
|
# Check that the IDENT is followed by = (not == ) to confirm declaration
|
|
@@ -273,7 +322,7 @@ class Parser:
|
|
|
273
322
|
|
|
274
323
|
return self._set_loc(ExprStmt(expr=expr), start_tok)
|
|
275
324
|
|
|
276
|
-
def _is_ident_typed_var_decl(self) -> bool:
|
|
325
|
+
def _is_ident_typed_var_decl(self, offset: int = 0) -> bool:
|
|
277
326
|
"""Look ahead for ``IDENT [<...>] IDENT '='`` (UDT-typed declaration).
|
|
278
327
|
|
|
279
328
|
Triggered by ``Sample s = expr`` / ``array<Sample> w = expr`` /
|
|
@@ -283,15 +332,21 @@ class Parser:
|
|
|
283
332
|
``true``, ``false``), and function-definition shapes
|
|
284
333
|
(``IDENT '(' ... ')' '=>'``).
|
|
285
334
|
|
|
335
|
+
``offset`` shifts the starting position (used to look ahead past a
|
|
336
|
+
consumed type-qualifier prefix like ``const``).
|
|
337
|
+
|
|
286
338
|
Probe: data/validation/udt-method-probe-19-array-of-udt-method.
|
|
287
339
|
"""
|
|
288
|
-
|
|
340
|
+
base = self.pos + offset
|
|
341
|
+
if base >= len(self.tokens):
|
|
342
|
+
return False
|
|
343
|
+
cur = self.tokens[base]
|
|
289
344
|
if cur.type != TokenType.IDENT:
|
|
290
345
|
return False
|
|
291
346
|
if cur.value in ("enum", "type", "strategy", "indicator", "na", "true", "false"):
|
|
292
347
|
return False
|
|
293
348
|
# Skip past optional generic args after the type ident: IDENT [< ... >]
|
|
294
|
-
i =
|
|
349
|
+
i = base + 1
|
|
295
350
|
if i < len(self.tokens) and self.tokens[i].type == TokenType.LT:
|
|
296
351
|
depth = 1
|
|
297
352
|
i += 1
|
|
@@ -374,12 +429,24 @@ class Parser:
|
|
|
374
429
|
first = VarDecl(name=name_tok.value, value=value)
|
|
375
430
|
self._set_loc(first, start_tok)
|
|
376
431
|
|
|
377
|
-
# Check for comma-separated additional declarations: x=1, y=2, z=3
|
|
378
|
-
|
|
432
|
+
# Check for comma-separated additional declarations: x=1, y=2, z=3.
|
|
433
|
+
# Other comma-separated simple statements (``a := 1, b := 2`` or
|
|
434
|
+
# ``array.fill(a, na), array.set(a, 0, 1)``) are handled by the
|
|
435
|
+
# statement wrapper above, so do not greedily consume their comma.
|
|
436
|
+
if not (
|
|
437
|
+
self._check(TokenType.COMMA)
|
|
438
|
+
and self._peek().type == TokenType.IDENT
|
|
439
|
+
and self._peek(2).type == TokenType.EQUALS
|
|
440
|
+
):
|
|
379
441
|
return first
|
|
380
442
|
|
|
381
443
|
decls = [first]
|
|
382
|
-
while
|
|
444
|
+
while (
|
|
445
|
+
self._check(TokenType.COMMA)
|
|
446
|
+
and self._peek().type == TokenType.IDENT
|
|
447
|
+
and self._peek(2).type == TokenType.EQUALS
|
|
448
|
+
):
|
|
449
|
+
self._advance()
|
|
383
450
|
st = self._current()
|
|
384
451
|
n = self._consume(TokenType.IDENT)
|
|
385
452
|
self._consume(TokenType.EQUALS)
|
|
@@ -133,8 +133,8 @@ _ta("kcw", _sig([("source", F), ("length", I), ("mult", F, 1.5)]))
|
|
|
133
133
|
# --- Volatility & Range ---
|
|
134
134
|
_ta("atr", _sig([("length", I)]))
|
|
135
135
|
_ta("tr", _sig([("handle_na", B, False)]))
|
|
136
|
-
_ta("stdev", _sig([("source", F), ("length", I)]))
|
|
137
|
-
_ta("variance",_sig([("source", F), ("length", I)]))
|
|
136
|
+
_ta("stdev", _sig([("source", F), ("length", I), ("biased", B, True)]))
|
|
137
|
+
_ta("variance",_sig([("source", F), ("length", I), ("biased", B, True)]))
|
|
138
138
|
|
|
139
139
|
# --- Trend ---
|
|
140
140
|
_ta("supertrend", _sig([("factor", F), ("atrPeriod", I)],
|
|
@@ -342,10 +342,14 @@ UNSUPPORTED_NAMESPACE_VARS: dict[str, str] = {
|
|
|
342
342
|
# request.security parameter rules.
|
|
343
343
|
# Codegen supports symbol/timeframe/expression plus gaps/lookahead (read in
|
|
344
344
|
# _eval_security_* emission and forwarded to register_security_eval).
|
|
345
|
-
#
|
|
346
|
-
#
|
|
345
|
+
# `currency` is still rejected loudly (it changes the returned values via FX
|
|
346
|
+
# conversion, which codegen drops — silently wrong). `ignore_invalid_symbol` is
|
|
347
|
+
# a guaranteed no-op here: codegen forces request.security onto the current
|
|
348
|
+
# chart symbol (see the "symbol must reference current chart symbol" check),
|
|
349
|
+
# which is always valid, so the flag can never change the result — accept+ignore.
|
|
347
350
|
SECURITY_ALLOWED_PARAMS: frozenset[str] = frozenset(
|
|
348
351
|
{"symbol", "timeframe", "expression", "gaps", "lookahead",
|
|
352
|
+
"ignore_invalid_symbol",
|
|
349
353
|
# Data-adjustment constants — silently accepted and ignored by codegen;
|
|
350
354
|
# the underlying engine uses a fixed unadjusted data source.
|
|
351
355
|
"backadjustment", "settlement_as_close", "adjustment"}
|
|
@@ -462,6 +466,12 @@ class SupportChecker:
|
|
|
462
466
|
# (``panel.cell(...)``) is a visual sink whose args may carry visual
|
|
463
467
|
# constants, so it routes through ``_visit_children_const_ok``.
|
|
464
468
|
self._visual_container_vars: set[str] = set()
|
|
469
|
+
# User helpers whose body is only a visual sink (for example
|
|
470
|
+
# ``cell(table t, ..., align) => t.cell(..., text_halign=align)``).
|
|
471
|
+
# A call to such a helper is itself a visual context, so style
|
|
472
|
+
# constants passed through its arguments are safe and should not trip
|
|
473
|
+
# the free-expression constant-namespace rejection.
|
|
474
|
+
self._visual_sink_funcs: set[str] = set()
|
|
465
475
|
self._drawing_tuple_vars: set[str] = set()
|
|
466
476
|
self._func_tuple_drawing_returns: dict[str, list[bool]] = {}
|
|
467
477
|
# Track whether we are inside an if/ternary condition expression.
|
|
@@ -520,6 +530,8 @@ class SupportChecker:
|
|
|
520
530
|
if tuple_returns:
|
|
521
531
|
self._func_tuple_drawing_returns[stmt.name] = tuple_returns
|
|
522
532
|
self._collect_visual_container_params(stmt)
|
|
533
|
+
if self._func_body_is_visual_sink(stmt):
|
|
534
|
+
self._visual_sink_funcs.add(stmt.name)
|
|
523
535
|
elif isinstance(stmt, MethodDef):
|
|
524
536
|
self._user_methods.add(stmt.name)
|
|
525
537
|
self._collect_visual_container_params(stmt)
|
|
@@ -535,6 +547,27 @@ class SupportChecker:
|
|
|
535
547
|
if hint and str(hint).replace(" ", "") in _VISUAL_CONTAINER_TYPES:
|
|
536
548
|
self._visual_container_vars.add(pname)
|
|
537
549
|
|
|
550
|
+
def _expr_is_visual_sink_call(self, expr: ASTNode | None) -> bool:
|
|
551
|
+
if not isinstance(expr, FuncCall):
|
|
552
|
+
return False
|
|
553
|
+
ns, name = _qualified_name(expr.callee)
|
|
554
|
+
if ns is None and name in SKIP_FUNC_NAMES:
|
|
555
|
+
return True
|
|
556
|
+
if ns is not None and ns in SKIP_NAMESPACES:
|
|
557
|
+
return True
|
|
558
|
+
if ns in _DRAWING_NOOP_BY_NS and name in _DRAWING_NOOP_BY_NS[ns]:
|
|
559
|
+
return True
|
|
560
|
+
return ns is not None and ns in self._visual_container_vars
|
|
561
|
+
|
|
562
|
+
def _func_body_is_visual_sink(self, fn: FuncDef | MethodDef) -> bool:
|
|
563
|
+
if not fn.body:
|
|
564
|
+
return False
|
|
565
|
+
for stmt in fn.body:
|
|
566
|
+
expr = stmt.expr if isinstance(stmt, ExprStmt) else None
|
|
567
|
+
if not self._expr_is_visual_sink_call(expr):
|
|
568
|
+
return False
|
|
569
|
+
return True
|
|
570
|
+
|
|
538
571
|
@staticmethod
|
|
539
572
|
def _type_name_contains_drawing(type_name: str | None) -> bool:
|
|
540
573
|
if not type_name:
|
|
@@ -1207,6 +1240,10 @@ class SupportChecker:
|
|
|
1207
1240
|
self._visit_children_const_ok(node)
|
|
1208
1241
|
return
|
|
1209
1242
|
|
|
1243
|
+
if ns is None and name in self._visual_sink_funcs:
|
|
1244
|
+
self._visit_children_const_ok(node)
|
|
1245
|
+
return
|
|
1246
|
+
|
|
1210
1247
|
self._visit_children(node)
|
|
1211
1248
|
|
|
1212
1249
|
def _visit_Identifier(self, node: Identifier) -> None:
|
|
Binary file
|
package/release.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"codegen": "0.
|
|
2
|
+
"codegen": "0.9.0",
|
|
3
3
|
"pyodide": "314.0.0",
|
|
4
4
|
"python": "3.14.0",
|
|
5
5
|
"emscripten": "emscripten_5_0_3",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "fc828c2a32ba93bbeeffa8e0a4c4696a417a6cc64448c1b70bdce2267cd051d9"
|
|
7
7
|
}
|
package/tables.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"CODEGEN_VERSION": "0.
|
|
2
|
+
"CODEGEN_VERSION": "0.9.0",
|
|
3
3
|
"TA_CLASS_MAP_KEYS": [
|
|
4
4
|
"sma",
|
|
5
5
|
"ema",
|
|
@@ -577,11 +577,13 @@
|
|
|
577
577
|
],
|
|
578
578
|
"ta.stdev": [
|
|
579
579
|
"source",
|
|
580
|
-
"length"
|
|
580
|
+
"length",
|
|
581
|
+
"biased"
|
|
581
582
|
],
|
|
582
583
|
"ta.variance": [
|
|
583
584
|
"source",
|
|
584
|
-
"length"
|
|
585
|
+
"length",
|
|
586
|
+
"biased"
|
|
585
587
|
],
|
|
586
588
|
"ta.supertrend": [
|
|
587
589
|
"factor",
|
|
@@ -1574,6 +1576,7 @@
|
|
|
1574
1576
|
"backadjustment",
|
|
1575
1577
|
"expression",
|
|
1576
1578
|
"gaps",
|
|
1579
|
+
"ignore_invalid_symbol",
|
|
1577
1580
|
"lookahead",
|
|
1578
1581
|
"settlement_as_close",
|
|
1579
1582
|
"symbol",
|
|
Binary file
|