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.
- opencode_pine2pyne-0.1.0.dist-info/METADATA +123 -0
- opencode_pine2pyne-0.1.0.dist-info/RECORD +23 -0
- opencode_pine2pyne-0.1.0.dist-info/WHEEL +5 -0
- opencode_pine2pyne-0.1.0.dist-info/entry_points.txt +2 -0
- opencode_pine2pyne-0.1.0.dist-info/licenses/LICENSE +201 -0
- opencode_pine2pyne-0.1.0.dist-info/top_level.txt +1 -0
- pine2pyne/README.md +178 -0
- pine2pyne/TRANSPILER_BEST_PRACTICES.md +425 -0
- pine2pyne/TRANSPILER_USAGE.md +264 -0
- pine2pyne/__init__.py +86 -0
- pine2pyne/__main__.py +12 -0
- pine2pyne/ast_nodes.py +352 -0
- pine2pyne/cli.py +212 -0
- pine2pyne/codegen.py +836 -0
- pine2pyne/errors.py +48 -0
- pine2pyne/import_resolver.py +407 -0
- pine2pyne/lexer.py +546 -0
- pine2pyne/parser.py +1510 -0
- pine2pyne/pine_builtins.py +464 -0
- pine2pyne/symbol_table.py +156 -0
- pine2pyne/tokens.py +143 -0
- pine2pyne/transformer.py +2327 -0
- pine2pyne/type_inference.py +328 -0
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
|