@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.
@@ -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
- cur = self._current()
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 = self.pos + 1
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
- if not self._check(TokenType.COMMA):
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 self._match(TokenType.COMMA):
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 Pine parameter type qualifiers, e.g. `series float x`,
589
- # `simple string maType`, `const int n`. Each is one qualifier in front
590
- # of the (optional) type and the name — NOT a separate parameter.
591
- while self._check(TokenType.IDENT) and self._current().value in (
592
- "series",
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
- if self._check(TokenType.IDENT):
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() # consume default value (discarded)
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 == TokenType.IDENT):
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)],