@pineforge/codegen-pyodide 0.8.0 → 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 +797 -45
- package/pineforge_codegen/analyzer/call_handlers.py +368 -68
- package/pineforge_codegen/analyzer/contracts.py +91 -0
- package/pineforge_codegen/analyzer/diagnostics.py +18 -4
- package/pineforge_codegen/analyzer/tables.py +20 -2
- package/pineforge_codegen/analyzer/types.py +53 -0
- package/pineforge_codegen/codegen/__init__.py +4 -0
- package/pineforge_codegen/codegen/base.py +1613 -111
- package/pineforge_codegen/codegen/drawing.py +560 -0
- package/pineforge_codegen/codegen/emit_top.py +536 -45
- package/pineforge_codegen/codegen/input.py +31 -0
- package/pineforge_codegen/codegen/security.py +791 -39
- package/pineforge_codegen/codegen/ta.py +87 -1
- package/pineforge_codegen/codegen/tables.py +115 -10
- package/pineforge_codegen/codegen/types.py +478 -46
- package/pineforge_codegen/codegen/visit_call.py +331 -75
- package/pineforge_codegen/codegen/visit_expr.py +116 -14
- package/pineforge_codegen/codegen/visit_stmt.py +208 -23
- package/pineforge_codegen/parser.py +129 -29
- package/pineforge_codegen/signatures.py +2 -2
- package/pineforge_codegen/support_checker.py +320 -19
- package/pineforge_codegen-0.9.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +7 -10
- package/pineforge_codegen-0.8.0.tar.gz +0 -0
|
@@ -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)
|
|
@@ -576,39 +643,64 @@ class Parser:
|
|
|
576
643
|
|
|
577
644
|
# -- Function definition --
|
|
578
645
|
|
|
646
|
+
def _parse_param_type_annotation(self) -> str | None:
|
|
647
|
+
"""Consume an optional function-parameter type annotation and return it
|
|
648
|
+
as a canonical hint string ('float', 'string', 'array<line>', 'pivot',
|
|
649
|
+
'chart.point', ...), or ``None`` for a bare untyped param.
|
|
650
|
+
|
|
651
|
+
Leaves the parser positioned at the parameter name. Supports the Pine
|
|
652
|
+
qualifier prefixes (``series`` / ``simple`` / ``const`` — consumed but
|
|
653
|
+
not part of the C++ type), builtin types, user-defined / drawing type
|
|
654
|
+
names, and the ``T[]`` postfix-array shorthand (normalised to
|
|
655
|
+
``array<T>`` by ``_parse_type_hint_string``).
|
|
656
|
+
"""
|
|
657
|
+
TYPE_TOKENS = {TokenType.TYPE_INT, TokenType.TYPE_FLOAT,
|
|
658
|
+
TokenType.TYPE_BOOL, TokenType.TYPE_STRING}
|
|
659
|
+
# Optional qualifiers — they do not affect the C++ param type.
|
|
660
|
+
while self._check(TokenType.IDENT) and self._current().value in (
|
|
661
|
+
"series", "simple", "const",
|
|
662
|
+
):
|
|
663
|
+
self._advance()
|
|
664
|
+
# Is there a type annotation before the parameter name? A builtin type
|
|
665
|
+
# token always is; an IDENT is a type only if followed by another IDENT
|
|
666
|
+
# (``line ln``), by ``[`` (``line[] arr``), or by ``<`` (the generic
|
|
667
|
+
# collection syntax ``array<float> xs`` / ``matrix<float> m`` /
|
|
668
|
+
# ``map<string,float> mp``). Without the ``<`` case the generic type is
|
|
669
|
+
# mis-consumed as the parameter name and the whole function definition
|
|
670
|
+
# silently fails to parse (its body leaks to top-level scope).
|
|
671
|
+
has_type = False
|
|
672
|
+
if self._current().type in TYPE_TOKENS:
|
|
673
|
+
has_type = True
|
|
674
|
+
elif self._check(TokenType.IDENT):
|
|
675
|
+
nxt = self._peek().type
|
|
676
|
+
if nxt in (TokenType.IDENT, TokenType.LBRACKET, TokenType.LT):
|
|
677
|
+
has_type = True
|
|
678
|
+
if not has_type:
|
|
679
|
+
return None
|
|
680
|
+
return self._parse_type_hint_string()
|
|
681
|
+
|
|
579
682
|
def _parse_func_def(self) -> FuncDef:
|
|
580
683
|
"""Parse: name(param1, param2) => expr_or_block"""
|
|
581
684
|
start_tok = self._current()
|
|
582
685
|
name = self._consume(TokenType.IDENT).value
|
|
583
686
|
self._consume(TokenType.LPAREN)
|
|
584
|
-
TYPE_TOKENS = {TokenType.TYPE_INT, TokenType.TYPE_FLOAT,
|
|
585
|
-
TokenType.TYPE_BOOL, TokenType.TYPE_STRING}
|
|
586
687
|
params = []
|
|
688
|
+
param_type_hints: list = []
|
|
689
|
+
param_defaults: list = []
|
|
587
690
|
while not self._check(TokenType.RPAREN):
|
|
588
|
-
# Consume optional
|
|
589
|
-
#
|
|
590
|
-
#
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
"simple",
|
|
594
|
-
"const",
|
|
595
|
-
):
|
|
596
|
-
self._advance()
|
|
597
|
-
# Handle optional type annotation: type param (e.g., int len, float src,
|
|
598
|
-
# string s). Built-in types are dedicated tokens; user-defined types are
|
|
599
|
-
# IDENTs and handled by the "next is IDENT" check below.
|
|
600
|
-
if self._current().type in TYPE_TOKENS:
|
|
601
|
-
self._advance() # skip the type annotation
|
|
691
|
+
# Consume the optional type annotation (builtin / user / drawing /
|
|
692
|
+
# ``T[]``), returning the canonical hint string. Handles ``float[] arr``,
|
|
693
|
+
# ``line[] ln``, ``color c``, ``SDZone z``, ``string tf``, as well as
|
|
694
|
+
# the untyped bare-name case.
|
|
695
|
+
hint = self._parse_param_type_annotation()
|
|
602
696
|
param_name = self._consume(TokenType.IDENT).value
|
|
603
|
-
|
|
604
|
-
# 'param_name' was actually a (user-defined) type name parsed as IDENT,
|
|
605
|
-
# next is the real name.
|
|
606
|
-
param_name = self._consume(TokenType.IDENT).value
|
|
607
|
-
# Skip default value: param = expr
|
|
697
|
+
pdefault = None
|
|
608
698
|
if self._check(TokenType.EQUALS):
|
|
609
699
|
self._advance() # consume '='
|
|
610
|
-
self._parse_expression() #
|
|
700
|
+
pdefault = self._parse_expression() # default value
|
|
611
701
|
params.append(param_name)
|
|
702
|
+
param_type_hints.append(hint)
|
|
703
|
+
param_defaults.append(pdefault)
|
|
612
704
|
self._match(TokenType.COMMA)
|
|
613
705
|
self._consume(TokenType.RPAREN)
|
|
614
706
|
self._skip_newlines()
|
|
@@ -625,6 +717,13 @@ class Parser:
|
|
|
625
717
|
expr = self._parse_expression()
|
|
626
718
|
node = FuncDef(name=name, params=params, body=[ExprStmt(expr=expr)], is_single_expr=True)
|
|
627
719
|
|
|
720
|
+
# Record per-param type hints + defaults (mirrors _parse_method_def) so
|
|
721
|
+
# the analyzer can type UDT/string/array params and the codegen can emit
|
|
722
|
+
# them with the correct C++ type (``pivot hi``, ``std::string s``).
|
|
723
|
+
node.annotations = {
|
|
724
|
+
"param_type_hints": param_type_hints,
|
|
725
|
+
"param_defaults": param_defaults,
|
|
726
|
+
}
|
|
628
727
|
return self._set_loc(node, start_tok)
|
|
629
728
|
|
|
630
729
|
def _parse_type_or_enum_decl(self):
|
|
@@ -701,7 +800,8 @@ class Parser:
|
|
|
701
800
|
if self._current().type in TYPE_KEYWORDS:
|
|
702
801
|
param_type = self._parse_type_hint_string()
|
|
703
802
|
elif (self._current().type == TokenType.IDENT
|
|
704
|
-
and self._peek().type
|
|
803
|
+
and self._peek().type in (TokenType.IDENT, TokenType.LBRACKET, TokenType.LT)):
|
|
804
|
+
# ``line ln`` / ``float[] arr`` / ``array<float> xs`` typed param.
|
|
705
805
|
param_type = self._parse_type_hint_string()
|
|
706
806
|
p = self._consume(TokenType.IDENT).value
|
|
707
807
|
pdefault = None
|
|
@@ -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)],
|