opencode-pine2pyne 0.1.0__py3-none-any.whl

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.
pine2pyne/errors.py ADDED
@@ -0,0 +1,48 @@
1
+ """
2
+ Custom error types for the Pine Script transpiler with source location tracking.
3
+ """
4
+
5
+
6
+ class Pine2PyneError(Exception):
7
+ """Base exception for all transpiler errors."""
8
+ def __init__(self, message: str, line: int = 0, column: int = 0, source_file: str = None):
9
+ self.message = message
10
+ self.line = line
11
+ self.column = column
12
+ self.source_file = source_file
13
+ super().__init__(self.format_message())
14
+
15
+ def format_message(self) -> str:
16
+ """Format error message with location information."""
17
+ location = f"{self.line}:{self.column}"
18
+ if self.source_file:
19
+ location = f"{self.source_file}:{location}"
20
+ return f"Error at {location}: {self.message}"
21
+
22
+
23
+ class LexerError(Pine2PyneError):
24
+ """Error during lexical analysis (tokenization)."""
25
+ pass
26
+
27
+
28
+ class ParserError(Pine2PyneError):
29
+ """Error during parsing (syntax error)."""
30
+ pass
31
+
32
+
33
+ class TransformerError(Pine2PyneError):
34
+ """Error during AST transformation."""
35
+ pass
36
+
37
+
38
+ class CodeGenError(Pine2PyneError):
39
+ """Error during code generation."""
40
+ pass
41
+
42
+
43
+ class UnsupportedFeatureError(Pine2PyneError):
44
+ """Feature not yet supported by the transpiler."""
45
+ def __init__(self, feature: str, line: int = 0, column: int = 0):
46
+ message = f"Unsupported feature: {feature}"
47
+ super().__init__(message, line, column)
48
+ self.feature = feature
@@ -0,0 +1,407 @@
1
+ """
2
+ Import resolver for PyneCore transpiled code.
3
+
4
+ Collects all used symbols and generates appropriate import statements.
5
+ """
6
+ from typing import Set, List
7
+ from .ast_nodes import *
8
+ from .pine_builtins import (
9
+ PYNECORE_LIB_MODULES, DRAWING_TYPES, CHART_TYPES,
10
+ SERIES_VARIABLES, is_series_variable, needs_plot_style_remap
11
+ )
12
+
13
+
14
+ class ImportResolver:
15
+ """Collects used symbols and generates import statements."""
16
+
17
+ def __init__(self):
18
+ self.uses_series = False
19
+ self.uses_persistent = False
20
+ self.uses_persistent_series = False
21
+ self.uses_na = False
22
+ self.uses_any = False
23
+ self.uses_udt = False # Custom type definitions
24
+ self.uses_udt_copy = False # udt_copy for UDT field copying
25
+ self.uses_method = False # User-defined methods
26
+ self.uses_method_call = False # method_call for explicit method calling
27
+ self.uses_pine_range = False # pine_range for loops
28
+ self.uses_cast_int = False # cast_int for type casting
29
+ self.uses_cast_float = False # cast_float for type casting
30
+ # Note: SeriesImpl for function params is handled by runtime SeriesTransformer
31
+ self.lib_modules: Set[str] = set()
32
+ self.builtin_variables: Set[str] = set()
33
+ # Global-scope names the script declares itself (var/varip/assignment). Pine
34
+ # lets a user variable shadow a built-in variable of the same name (they are
35
+ # not reserved keywords), so such a name must NOT be treated/imported as a
36
+ # built-in series — e.g. `var array<bool> hl2 = ...` shadows the `hl2`
37
+ # ((high+low)/2) price source, and importing the built-in would let the
38
+ # runtime overwrite the user's array each bar.
39
+ self.declared_names: Set[str] = set()
40
+ self.drawing_types: Set[str] = set()
41
+ self.chart_types: Set[str] = set()
42
+ self.uses_plot_style = False
43
+ self.aliased_modules: dict[str, str] = {} # Maps module name to alias (e.g., 'position' -> '_position_module')
44
+
45
+ def analyze(self, script: Script) -> None:
46
+ """Analyze script to determine what needs to be imported."""
47
+ # Pre-pass: collect every name the script declares, so a user variable that
48
+ # shadows a built-in (e.g. hl2) is not mistaken for the built-in below.
49
+ self._collect_declared_names(script)
50
+
51
+ # Check for Series usage (var declarations, globals)
52
+ for decl in script.declarations:
53
+ if isinstance(decl, (VarDecl, VaripDecl)):
54
+ self.uses_persistent = True
55
+ elif isinstance(decl, Assignment):
56
+ # Global assignments are Series
57
+ self.uses_series = True
58
+
59
+ # Check body for Series usage
60
+ if script.body:
61
+ self.uses_series = True
62
+
63
+ # Scan entire AST for function calls and identifiers
64
+ self._scan_node(script)
65
+
66
+ def _collect_declared_names(self, node: Any) -> None:
67
+ """Recursively collect GLOBAL-scope names the script declares (var/varip/
68
+ assignment) so they can shadow a same-named built-in variable.
69
+
70
+ Only global-scope declarations shadow a built-in everywhere the built-in
71
+ would be imported. Function parameters and function-local declarations are
72
+ scoped to that function, so they must NOT suppress the global import — a
73
+ script can have a function parameter named ``time`` while still using the
74
+ built-in ``time`` at global scope. We therefore skip ``FuncDecl`` subtrees
75
+ entirely and never collect ``Parameter`` names.
76
+
77
+ Pine declarations use a plain ``str`` name/target, so only simple
78
+ identifier targets are collected (tuple-unpacking targets are not built-in
79
+ names and need no shadow handling).
80
+ """
81
+ if isinstance(node, FuncDecl):
82
+ return # function-scoped names never shadow a global built-in import
83
+ if isinstance(node, (VarDecl, VaripDecl)):
84
+ if isinstance(node.name, str):
85
+ self.declared_names.add(node.name)
86
+ elif isinstance(node, Assignment):
87
+ if isinstance(node.target, str):
88
+ self.declared_names.add(node.target)
89
+
90
+ if isinstance(node, (list, tuple)):
91
+ for item in node:
92
+ self._collect_declared_names(item)
93
+ elif hasattr(node, '__dict__'):
94
+ for attr_value in node.__dict__.values():
95
+ if isinstance(attr_value, (ASTNode, list, tuple, dict)):
96
+ self._collect_declared_names(attr_value)
97
+
98
+ def _scan_node(self, node: Any) -> None:
99
+ """Recursively scan AST node for symbols."""
100
+ if node is None:
101
+ return
102
+
103
+ # Handle NaLiteral
104
+ if isinstance(node, NaLiteral):
105
+ self.uses_na = True
106
+
107
+ # Handle function calls
108
+ if isinstance(node, FunctionCall):
109
+ self._process_function_call(node)
110
+
111
+ # Handle member access
112
+ if isinstance(node, MemberAccess):
113
+ self._process_member_access(node)
114
+
115
+ # Handle identifiers
116
+ if isinstance(node, Identifier):
117
+ self._process_identifier(node.name)
118
+
119
+ # Handle script declaration
120
+ if isinstance(node, (IndicatorDecl, StrategyDecl, LibraryDecl)):
121
+ self.lib_modules.add('script')
122
+ # Scan decorator kwargs for module references (e.g., currency=currency.USD)
123
+ if hasattr(node, 'kwargs') and node.kwargs:
124
+ for value in node.kwargs.values():
125
+ self._scan_node(value)
126
+
127
+ # Handle input declarations
128
+ if isinstance(node, InputDecl):
129
+ self.lib_modules.add('input')
130
+
131
+ # Handle var/varip declarations (including inside blocks)
132
+ if isinstance(node, (VarDecl, VaripDecl)):
133
+ self.uses_persistent = True
134
+
135
+ # Handle type declarations (custom types)
136
+ if isinstance(node, TypeDecl):
137
+ self.uses_udt = True
138
+ # Scan field types for drawing type imports (e.g., lbl: Label)
139
+ from .pine_builtins import get_type_name
140
+ for field_info in node.fields:
141
+ if len(field_info) >= 2:
142
+ pine_type = field_info[1] # type_hint string (lowercase from Pine, e.g., "label")
143
+ default_val = field_info[2] if len(field_info) >= 3 else None
144
+ if isinstance(pine_type, str):
145
+ # Convert Pine type to Python type (e.g., "label" → "Label")
146
+ py_type = get_type_name(pine_type)
147
+ # Check if it's a drawing or chart type
148
+ if py_type in DRAWING_TYPES:
149
+ self.drawing_types.add(py_type)
150
+ elif py_type in CHART_TYPES:
151
+ self.chart_types.add(py_type)
152
+ # Codegen generates na(Type) for non-primitive fields without defaults
153
+ if default_val is None and py_type not in ('int', 'float', 'bool'):
154
+ self.uses_na = True
155
+ elif isinstance(default_val, NaLiteral):
156
+ self.uses_na = True
157
+
158
+ # Handle method declarations
159
+ if isinstance(node, FuncDecl):
160
+ if hasattr(node, 'is_method') and node.is_method:
161
+ self.uses_method = True
162
+
163
+ # Handle for loops (need pine_range)
164
+ if isinstance(node, ForLoop):
165
+ self.uses_pine_range = True
166
+
167
+ # Check for Any in type hints
168
+ if isinstance(node, (Assignment, Reassignment, VarDecl, VaripDecl, Parameter)):
169
+ if hasattr(node, 'type_hint') and node.type_hint:
170
+ type_hint_str = str(node.type_hint)
171
+ if 'Any' in type_hint_str:
172
+ self.uses_any = True
173
+
174
+ # Extract drawing/structural types from type hints like Persistent[Line], Matrix[float], etc.
175
+ import re
176
+ # Match type names inside brackets: Persistent[Line], Matrix[float], dict[str, Line], etc.
177
+ # Look for capital letter type names that might be drawing types
178
+ for match in re.finditer(r'\b([A-Z][a-zA-Z]*)\b', type_hint_str):
179
+ type_name = match.group(1)
180
+ if type_name in DRAWING_TYPES:
181
+ self.drawing_types.add(type_name)
182
+ elif type_name in CHART_TYPES:
183
+ self.chart_types.add(type_name)
184
+
185
+ # Recursively scan children
186
+ if isinstance(node, (list, tuple)):
187
+ for item in node:
188
+ self._scan_node(item)
189
+ elif hasattr(node, '__dict__'):
190
+ for attr_value in node.__dict__.values():
191
+ if isinstance(attr_value, (ASTNode, list, tuple, dict)):
192
+ self._scan_node(attr_value)
193
+
194
+ def _process_function_call(self, call: FunctionCall) -> None:
195
+ """Process a function call to extract module dependencies."""
196
+ func_name = None
197
+
198
+ if isinstance(call.func, str):
199
+ func_name = call.func
200
+ # Handle dotted function names like "box.new" -> extract "box"
201
+ if '.' in func_name:
202
+ module = func_name.split('.')[0]
203
+ if module in PYNECORE_LIB_MODULES:
204
+ self.lib_modules.add(module)
205
+ # Check for str.format -> import string
206
+ elif module == 'str':
207
+ self.lib_modules.add('string')
208
+ # Handle standalone function names like "plot", "bgcolor", "barcolor"
209
+ elif func_name in PYNECORE_LIB_MODULES:
210
+ self.lib_modules.add(func_name)
211
+ # Handle series variables used as functions (e.g., time(), time_close())
212
+ elif is_series_variable(func_name):
213
+ self.builtin_variables.add(func_name)
214
+ self.uses_series = True
215
+ elif isinstance(call.func, NaLiteral):
216
+ # Special case: na() used as a function call, not a literal
217
+ # Import the na function from pynecore.lib
218
+ self.lib_modules.add('na')
219
+ elif isinstance(call.func, MemberAccess):
220
+ # Extract module name from member access
221
+ if isinstance(call.func.object, str):
222
+ module = call.func.object
223
+ if module in PYNECORE_LIB_MODULES:
224
+ self.lib_modules.add(module)
225
+ # Check for str.format -> import string
226
+ elif module == 'str':
227
+ self.lib_modules.add('string')
228
+ func_name = f"{module}.{call.func.member}"
229
+ elif isinstance(call.func.object, Identifier):
230
+ module = call.func.object.name
231
+ if module in PYNECORE_LIB_MODULES:
232
+ self.lib_modules.add(module)
233
+ # Check for str.format -> import string
234
+ elif module == 'str':
235
+ self.lib_modules.add('string')
236
+ func_name = f"{module}.{call.func.member}"
237
+
238
+ # Recursively scan all arguments and kwargs for module references
239
+ for arg in call.args:
240
+ self._scan_node(arg)
241
+ for value in call.kwargs.values():
242
+ self._scan_node(value)
243
+ # Also check for plot style references in kwargs
244
+ if isinstance(value, MemberAccess):
245
+ # Handle both Identifier and str object types
246
+ module_name = value.object.name if isinstance(value.object, Identifier) else str(value.object)
247
+ full_name = f"{module_name}.{value.member}"
248
+ if needs_plot_style_remap(full_name):
249
+ self.uses_plot_style = True
250
+
251
+ def _process_member_access(self, member: MemberAccess) -> None:
252
+ """Process member access to extract module dependencies."""
253
+ if isinstance(member.object, str):
254
+ module = member.object
255
+ if module in PYNECORE_LIB_MODULES:
256
+ self.lib_modules.add(module)
257
+ # Also check for 'str' module (Pine uses str.format, etc.)
258
+ elif module == 'str':
259
+ self.lib_modules.add('string')
260
+
261
+ # Check for plot style
262
+ full_name = f"{module}.{member.member}"
263
+ if needs_plot_style_remap(full_name):
264
+ self.uses_plot_style = True
265
+
266
+ elif isinstance(member.object, Identifier):
267
+ module = member.object.name
268
+ if module in PYNECORE_LIB_MODULES:
269
+ self.lib_modules.add(module)
270
+ # Also check for 'str' module (Pine uses str.format, etc.)
271
+ elif module == 'str':
272
+ self.lib_modules.add('string')
273
+
274
+ # Check for plot style
275
+ full_name = f"{module}.{member.member}"
276
+ if needs_plot_style_remap(full_name):
277
+ self.uses_plot_style = True
278
+
279
+ def _process_identifier(self, name: str) -> None:
280
+ """Process identifier to check for built-in variables and module constants."""
281
+ # Check for dotted identifiers like "xloc.bar_time" or "line.style_dashed"
282
+ if '.' in name:
283
+ module = name.split('.')[0]
284
+ if module in PYNECORE_LIB_MODULES:
285
+ self.lib_modules.add(module)
286
+ # Check for built-in Series variables — but a name the script declares
287
+ # itself shadows the built-in, so it must not be imported as one.
288
+ elif is_series_variable(name) and name not in self.declared_names:
289
+ self.builtin_variables.add(name)
290
+ self.uses_series = True
291
+
292
+ def add_drawing_type(self, type_name: str) -> None:
293
+ """Manually add a drawing type that was detected."""
294
+ if type_name in DRAWING_TYPES:
295
+ self.drawing_types.add(type_name)
296
+ elif type_name in CHART_TYPES:
297
+ self.chart_types.add(type_name)
298
+
299
+ def add_module(self, module_name: str) -> None:
300
+ """Manually add a module dependency."""
301
+ if module_name in PYNECORE_LIB_MODULES:
302
+ self.lib_modules.add(module_name)
303
+
304
+ def generate_imports(self) -> List[str]:
305
+ """Generate import statements in the correct order."""
306
+ imports = []
307
+
308
+ # 0. Typing imports (if used)
309
+ if self.uses_any:
310
+ imports.append("from typing import Any")
311
+
312
+ # 1. Pine range (if used)
313
+ if self.uses_pine_range:
314
+ imports.append("from pynecore import pine_range")
315
+
316
+ # 2. Pine cast (if used)
317
+ if self.uses_cast_int or self.uses_cast_float:
318
+ cast_funcs = []
319
+ if self.uses_cast_float:
320
+ cast_funcs.append('cast_float')
321
+ if self.uses_cast_int:
322
+ cast_funcs.append('cast_int')
323
+ imports.append(f"from pynecore.core.pine_cast import {', '.join(sorted(cast_funcs))}")
324
+
325
+ # 3. Pine method (only import what's used)
326
+ if self.uses_method or self.uses_method_call:
327
+ method_imports = []
328
+ if self.uses_method:
329
+ method_imports.append('method')
330
+ if self.uses_method_call:
331
+ method_imports.append('method_call')
332
+ imports.append(f"from pynecore.core.pine_method import {', '.join(method_imports)}")
333
+
334
+ # 4. Pine UDT (only import what's used)
335
+ if self.uses_udt or self.uses_udt_copy:
336
+ udt_imports = []
337
+ if self.uses_udt:
338
+ udt_imports.append('udt')
339
+ if self.uses_udt_copy:
340
+ udt_imports.append('udt_copy')
341
+ imports.append(f"from pynecore.core.pine_udt import {', '.join(udt_imports)}")
342
+
343
+ # 5. PyneCore core types - Series only (Persistent moved to pynecore.types)
344
+ # Note: Series import is now handled in types import section with Persistent
345
+
346
+ # 6. Main library imports + built-in variables (combined into multi-line statement)
347
+ # Ensure na is imported when used as bare identifier (e.g., f = na)
348
+ if self.uses_na:
349
+ self.lib_modules.add('na')
350
+ self.lib_modules.add('NA')
351
+
352
+ all_lib_imports = []
353
+ if self.lib_modules:
354
+ all_lib_imports.extend(sorted(self.lib_modules))
355
+ if self.builtin_variables:
356
+ all_lib_imports.extend(sorted(self.builtin_variables))
357
+
358
+ if all_lib_imports:
359
+ # Sort and combine all lib imports
360
+ all_lib_imports = sorted(set(all_lib_imports))
361
+
362
+ # Separate regular imports from aliased imports
363
+ regular_imports = []
364
+ aliased_items = []
365
+ for item in all_lib_imports:
366
+ if item in self.aliased_modules:
367
+ # Generate alias like "position as _position_module"
368
+ aliased_items.append(f"{item} as {self.aliased_modules[item]}")
369
+ else:
370
+ regular_imports.append(item)
371
+
372
+ # Combine regular and aliased imports
373
+ combined_imports = regular_imports + aliased_items
374
+
375
+ # Generate multi-line import statement like ground truth
376
+ # Format: from pynecore.lib import (\n item1, item2, item3,\n item4, item5\n)
377
+ import_str = "from pynecore.lib import (\n "
378
+ import_str += ', '.join(combined_imports)
379
+ import_str += "\n)"
380
+ imports.append(import_str)
381
+
382
+ # 8. Plot styles (if used)
383
+ if self.uses_plot_style:
384
+ imports.append("from pynecore.lib import plot_style")
385
+
386
+ # 9. Types from pynecore.types (drawing types + Persistent + Series + Matrix if used)
387
+ types_import = []
388
+ if self.drawing_types:
389
+ types_import.extend(sorted(self.drawing_types))
390
+ if self.uses_persistent:
391
+ types_import.append('Persistent')
392
+ if self.uses_persistent_series:
393
+ types_import.append('PersistentSeries')
394
+ if self.uses_series:
395
+ types_import.append('Series')
396
+ # Matrix is always imported if we have drawing types or persistent (it's a common type)
397
+ # Actually, only import if explicitly used
398
+
399
+ if types_import:
400
+ imports.append(f"from pynecore.types import {', '.join(sorted(set(types_import)))}")
401
+
402
+ # 10. Chart types (if used)
403
+ if self.chart_types:
404
+ types = sorted(self.chart_types)
405
+ imports.append(f"from pynecore.types.chart import {', '.join(types)}")
406
+
407
+ return imports