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.
@@ -0,0 +1,464 @@
1
+ """
2
+ Mapping of Pine Script built-in functions, variables, and constants to PyneCore equivalents.
3
+ """
4
+ from typing import Dict, Set
5
+
6
+
7
+ # Functions that need to be renamed
8
+ FUNCTION_RENAMES: Dict[str, str] = {
9
+ 'array.from': 'array.from_items', # 'from' is Python keyword
10
+ }
11
+
12
+ # Module renames
13
+ MODULE_RENAMES: Dict[str, str] = {
14
+ 'str': 'string', # str is Python built-in
15
+ }
16
+
17
+ # Type renames (Pine Script -> Python/PyneCore)
18
+ TYPE_RENAMES: Dict[str, str] = {
19
+ # Drawing types
20
+ 'line': 'Line',
21
+ 'label': 'Label',
22
+ 'box': 'Box',
23
+ 'table': 'Table',
24
+ 'linefill': 'LineFill',
25
+ 'polyline': 'Polyline',
26
+ 'color': 'Color',
27
+ 'chart.point': 'ChartPoint',
28
+
29
+ # Data structure types
30
+ 'matrix': 'Matrix',
31
+
32
+ # Basic types (in type hints)
33
+ 'string': 'str',
34
+ 'int': 'int',
35
+ 'float': 'float',
36
+ 'bool': 'bool',
37
+ }
38
+
39
+ # Built-in variables that are always Series
40
+ SERIES_VARIABLES: Set[str] = {
41
+ 'open', 'high', 'low', 'close', 'volume',
42
+ 'hl2', 'hlc3', 'ohlc4', 'hlcc4',
43
+ 'bar_index', 'last_bar_index', 'last_bar_time',
44
+ 'time', 'time_close', 'time_tradingday', 'timenow',
45
+ 'dayofmonth', 'dayofweek', 'hour', 'minute', 'second', 'month', 'weekofyear', 'year',
46
+ }
47
+
48
+ # Constants that should be preserved as-is
49
+ CONSTANTS: Set[str] = {
50
+ # Strategy constants
51
+ 'strategy.long',
52
+ 'strategy.short',
53
+ 'strategy.commission.percent',
54
+ 'strategy.commission.cash_per_contract',
55
+ 'strategy.commission.cash_per_order',
56
+ 'strategy.percent',
57
+ 'strategy.fixed',
58
+ 'strategy.cash',
59
+ 'strategy.percent_of_equity',
60
+ 'strategy.closedtrades',
61
+ 'strategy.opentrades',
62
+
63
+ # Color constants
64
+ 'color.red', 'color.green', 'color.blue', 'color.white', 'color.black',
65
+ 'color.yellow', 'color.orange', 'color.purple', 'color.gray',
66
+ 'color.silver', 'color.maroon', 'color.fuchsia', 'color.lime',
67
+ 'color.olive', 'color.navy', 'color.teal', 'color.aqua',
68
+
69
+ # Plot styles (need remapping to plot_style module)
70
+ 'plot.style_line',
71
+ 'plot.style_stepline',
72
+ 'plot.style_histogram',
73
+ 'plot.style_cross',
74
+ 'plot.style_area',
75
+ 'plot.style_columns',
76
+ 'plot.style_circles',
77
+ 'plot.style_linebr',
78
+ 'plot.style_steplinebr',
79
+
80
+ # Barstate
81
+ 'barstate.isfirst',
82
+ 'barstate.islast',
83
+ 'barstate.ishistory',
84
+ 'barstate.isrealtime',
85
+ 'barstate.isnew',
86
+ 'barstate.isconfirmed',
87
+ }
88
+
89
+ # Plot style constants need special handling
90
+ PLOT_STYLE_REMAP: Dict[str, str] = {
91
+ 'plot.style_line': 'plot_style.style_line',
92
+ 'plot.style_stepline': 'plot_style.style_stepline',
93
+ 'plot.style_histogram': 'plot_style.style_histogram',
94
+ 'plot.style_cross': 'plot_style.style_cross',
95
+ 'plot.style_area': 'plot_style.style_area',
96
+ 'plot.style_columns': 'plot_style.style_columns',
97
+ 'plot.style_circles': 'plot_style.style_circles',
98
+ 'plot.style_linebr': 'plot_style.style_linebr',
99
+ 'plot.style_steplinebr': 'plot_style.style_steplinebr',
100
+ }
101
+
102
+ # Modules that should be imported from pynecore.lib
103
+ # Synced with https://github.com/PyneSys/pynecore/tree/main/src/pynecore/lib
104
+ PYNECORE_LIB_MODULES: Set[str] = {
105
+ # Core modules (submodules as .py files in pynecore/lib/)
106
+ 'script', 'input', 'plot', 'color', 'ta', 'math', 'strategy',
107
+ 'array', 'matrix', 'map', 'string',
108
+ 'timeframe', 'runtime', 'alert', 'label', 'line', 'box', 'table',
109
+ 'linefill', 'polyline', 'log', 'barstate', 'session', 'syminfo',
110
+ 'adjustment', 'barmerge', 'chart', 'dividends', 'earnings',
111
+ # Higher-timeframe / other-symbol data access. Present in upstream PyneCore
112
+ # (lib/request.py + lib/ticker.py); without them here the transpiler emits
113
+ # request.*/ticker.* calls it never imports -> NameError at runtime.
114
+ 'request', 'ticker',
115
+ # Other upstream lib modules that were likewise never imported. Found by
116
+ # diffing pynecore/lib/*.py against this set — 'splits' is used by the
117
+ # request.splits samples, 'dayofweek' by session/date logic.
118
+ 'dayofweek', 'splits', 'footprint', 'pivotpointtype', 'volume_row',
119
+ # Standalone functions (defined in pynecore/lib/__init__.py)
120
+ 'currency', 'timestamp', 'max_bars_back',
121
+ # Plotting and coloring functions
122
+ 'bgcolor', 'barcolor', 'alertcondition', 'plotarrow', 'plotbar', 'plotcandle',
123
+ 'plotchar', 'plotshape', 'fill',
124
+ # Utility functions
125
+ 'na', 'nz', 'fixnan',
126
+ # Constant namespaces
127
+ 'xloc', 'yloc', 'position', 'size', 'shape', 'location',
128
+ 'extend', 'font', 'text', 'display', 'scale', 'hline', 'format',
129
+ 'order',
130
+ }
131
+
132
+ # Drawing and structural types that should be imported from pynecore.types
133
+ DRAWING_TYPES: Set[str] = {
134
+ 'Line', 'Label', 'Box', 'Table', 'LineFill', 'Polyline', 'Color', 'Matrix',
135
+ }
136
+
137
+ # Chart types that should be imported from pynecore.types.chart
138
+ CHART_TYPES: Set[str] = {
139
+ 'ChartPoint',
140
+ }
141
+
142
+ # PyneCore object method transformations
143
+ # Maps method names to the module that should be used for the function call
144
+ # Format: method_name -> (module_name, requires_transformation)
145
+ PYNECORE_METHOD_TRANSFORMS: dict[str, str] = {
146
+ # Matrix methods
147
+ 'add_row': 'matrix',
148
+ 'add_col': 'matrix',
149
+ 'columns': 'matrix',
150
+ 'rows': 'matrix',
151
+ 'fill': 'matrix',
152
+ 'avg': 'matrix',
153
+ 'max': 'matrix',
154
+ 'min': 'matrix',
155
+ 'median': 'matrix',
156
+ 'mode': 'matrix',
157
+ 'sum': 'matrix',
158
+ 'concat': 'matrix',
159
+ 'copy': 'matrix', # Also used by labels/lines but context-dependent
160
+ 'det': 'matrix',
161
+ 'diff': 'matrix',
162
+ 'eigenvalues': 'matrix',
163
+ 'eigenvectors': 'matrix',
164
+ 'inv': 'matrix',
165
+ 'kron': 'matrix',
166
+ 'mult': 'matrix',
167
+ 'pinv': 'matrix',
168
+ 'pow': 'matrix',
169
+ 'rank': 'matrix',
170
+ 'remove_col': 'matrix',
171
+ 'remove_row': 'matrix',
172
+ 'reshape': 'matrix',
173
+ 'reverse': 'matrix',
174
+ 'col': 'matrix',
175
+ 'row': 'matrix',
176
+ 'set': 'matrix', # Also used by labels/lines
177
+ 'sort': 'matrix',
178
+ 'submatrix': 'matrix',
179
+ 'swap_columns': 'matrix',
180
+ 'swap_rows': 'matrix',
181
+ 'trace': 'matrix',
182
+ 'transpose': 'matrix',
183
+
184
+ # Map methods
185
+ 'put': 'map',
186
+ 'put_all': 'map',
187
+ 'get': 'map', # Also used by arrays
188
+ 'contains': 'map',
189
+ 'remove': 'map',
190
+ 'clear': 'map',
191
+ 'keys': 'map',
192
+ 'values': 'map',
193
+ 'size': 'map', # Also used by arrays
194
+
195
+ # Array methods (handled differently - array methods stay as is mostly)
196
+
197
+ # Label methods
198
+ 'set_x': 'label',
199
+ 'set_y': 'label',
200
+ 'set_xy': 'label',
201
+ 'set_text': 'label',
202
+ 'set_color': 'label',
203
+ 'set_textcolor': 'label',
204
+ 'set_size': 'label',
205
+ 'set_style': 'label',
206
+ 'set_textalign': 'label',
207
+ 'set_tooltip': 'label',
208
+ 'get_x': 'label',
209
+ 'get_y': 'label',
210
+ 'get_text': 'label',
211
+ 'delete': 'label', # Also line, box
212
+
213
+ # Line methods
214
+ 'set_x1': 'line',
215
+ 'set_y1': 'line',
216
+ 'set_x2': 'line',
217
+ 'set_y2': 'line',
218
+ 'set_xy1': 'line',
219
+ 'set_xy2': 'line',
220
+ 'set_extend': 'line',
221
+ 'set_width': 'line',
222
+ 'get_x1': 'line',
223
+ 'get_y1': 'line',
224
+ 'get_x2': 'line',
225
+ 'get_y2': 'line',
226
+ 'get_price': 'line',
227
+
228
+ # Box methods
229
+ 'set_left': 'box',
230
+ 'set_right': 'box',
231
+ 'set_top': 'box',
232
+ 'set_bottom': 'box',
233
+ 'set_lefttop': 'box',
234
+ 'set_rightbottom': 'box',
235
+ 'set_border_color': 'box',
236
+ 'set_border_width': 'box',
237
+ 'set_border_style': 'box',
238
+ 'set_bgcolor': 'box',
239
+ 'set_text': 'box',
240
+ 'set_text_color': 'box',
241
+ 'set_text_size': 'box',
242
+ 'get_left': 'box',
243
+ 'get_right': 'box',
244
+ 'get_top': 'box',
245
+ 'get_bottom': 'box',
246
+
247
+ # Table methods
248
+ 'cell': 'table',
249
+ 'merge_cells': 'table',
250
+ 'set_position': 'table',
251
+ }
252
+
253
+ # Canonical module-to-methods sets for heuristic method → module resolution.
254
+ # Used when the transformer cannot determine the type from symbol table and
255
+ # must infer the module from the method name alone.
256
+ # Built as the UNION of all inline set definitions in transformer.py.
257
+
258
+ LABEL_METHODS: set[str] = {
259
+ 'set_x', 'set_y', 'set_xy', 'set_text', 'set_color', 'set_textcolor',
260
+ 'set_size', 'set_style', 'set_textalign', 'set_tooltip',
261
+ 'get_x', 'get_y', 'get_text', 'copy', 'delete',
262
+ }
263
+
264
+ LINE_METHODS: set[str] = {
265
+ 'set_x1', 'set_y1', 'set_x2', 'set_y2', 'set_xy1', 'set_xy2',
266
+ 'set_extend', 'set_width', 'set_xloc',
267
+ 'get_x1', 'get_y1', 'get_x2', 'get_y2', 'get_price', 'copy', 'delete',
268
+ }
269
+
270
+ BOX_METHODS: set[str] = {
271
+ 'set_left', 'set_right', 'set_top', 'set_bottom',
272
+ 'set_lefttop', 'set_rightbottom',
273
+ 'set_border_color', 'set_border_width', 'set_border_style',
274
+ 'set_bgcolor', 'set_text', 'set_text_color', 'set_text_size',
275
+ 'get_left', 'get_right', 'get_top', 'get_bottom', 'copy', 'delete',
276
+ }
277
+
278
+ TABLE_METHODS: set[str] = {
279
+ 'cell', 'merge_cells', 'set_position', 'cell_set_text',
280
+ # Remaining table.py members whose first argument is a Table. The cell_set_*
281
+ # methods are table-unique, so method-style calls (`myTable.cell_set_*(...)`)
282
+ # resolve unambiguously; the set_border_*/set_frame_* setters are table-only
283
+ # too. Without these, a method-style call was left unrewritten and failed with
284
+ # e.g. `'Table' object has no attribute 'cell_set_text_formatting'`.
285
+ 'cell_set_bgcolor', 'cell_set_height', 'cell_set_text_color',
286
+ 'cell_set_text_font_family', 'cell_set_text_formatting', 'cell_set_text_halign',
287
+ 'cell_set_text_size', 'cell_set_text_valign', 'cell_set_tooltip', 'cell_set_width',
288
+ 'set_bgcolor', 'set_border_color', 'set_border_width',
289
+ 'set_frame_color', 'set_frame_width',
290
+ }
291
+
292
+ MAP_METHODS: set[str] = {'put', 'put_all', 'get', 'remove', 'clear', 'keys', 'values', 'size', 'contains'}
293
+
294
+ MATRIX_METHODS: set[str] = {
295
+ 'add_row', 'add_col', 'get', 'set',
296
+ # Matrix-UNIQUE members of lib/matrix.py (no array/map/drawing collision), so
297
+ # method-style calls like `m.rows()`, `m.columns()`, `t.transpose()` route to
298
+ # the matrix module instead of hitting a Matrix property/int with `()`. The
299
+ # AMBIGUOUS names shared with arrays (concat, sort, copy, get/set, fill,
300
+ # reverse, sum, max, min, avg, median, mode, delete) are deliberately NOT
301
+ # added here — they need the type-aware resolver (see R7/R11) since the
302
+ # unknown-type name-heuristic gives arrays priority.
303
+ 'col', 'columns', 'det', 'diff', 'eigenvalues', 'eigenvectors',
304
+ 'elements_count', 'inv', 'is_antidiagonal', 'is_antisymmetric', 'is_binary',
305
+ 'is_diagonal', 'is_identity', 'is_square', 'is_stochastic', 'is_symmetric',
306
+ 'is_triangular', 'is_zero', 'kron', 'mult', 'pinv', 'pow', 'rank',
307
+ 'remove_col', 'remove_row', 'reshape', 'row', 'rows', 'submatrix',
308
+ 'swap_columns', 'swap_rows', 'trace', 'transpose',
309
+ }
310
+
311
+ # Array-unique methods that unambiguously map to the array module.
312
+ # Does NOT include shared methods (get, set, size, etc.) to avoid
313
+ # misresolving map/matrix calls when type info is unavailable.
314
+ ARRAY_METHODS: set[str] = {
315
+ 'push', 'pop', 'shift', 'unshift', 'slice', 'insert', 'indexof', 'lastindexof',
316
+ 'includes', 'sort', 'sort_indices', 'reverse', 'concat', 'first', 'last',
317
+ 'join', 'percentile_linear_interpolation', 'percentile_nearest_rank', 'percentrank',
318
+ 'abs', 'binary_search', 'binary_search_leftmost', 'binary_search_rightmost',
319
+ 'every', 'some', 'range', 'standardize', 'stdev', 'variance', 'covariance',
320
+ 'fill',
321
+ }
322
+
323
+ # Map-unique methods (methods that ONLY belong to map, not array)
324
+ MAP_UNIQUE_METHODS: set[str] = {'put', 'put_all', 'keys', 'values', 'contains'}
325
+
326
+ # Methods shared between array, map, and matrix.
327
+ # These can only be resolved via type-aware path (symbol table lookup).
328
+ # In heuristic fallback, prefer array since it's the most common collection.
329
+ SHARED_COLLECTION_METHODS: set[str] = {
330
+ 'get', 'set', 'size', 'clear', 'remove', 'copy',
331
+ 'avg', 'min', 'max', 'sum', 'median', 'mode',
332
+ }
333
+
334
+
335
+ def get_function_name(pine_func: str) -> str:
336
+ """Get the PyneCore function name for a Pine Script function."""
337
+ return FUNCTION_RENAMES.get(pine_func, pine_func)
338
+
339
+
340
+ def get_module_name(pine_module: str) -> str:
341
+ """Get the PyneCore module name for a Pine Script module."""
342
+ return MODULE_RENAMES.get(pine_module, pine_module)
343
+
344
+
345
+ def get_type_name(pine_type: str) -> str:
346
+ """Get the Python/PyneCore type name for a Pine Script type."""
347
+ # Strip .new suffix from UDT types (e.g., TradeStats.new -> TradeStats)
348
+ if '.new' in pine_type and pine_type and pine_type[0].isupper():
349
+ pine_type = pine_type.replace('.new', '')
350
+ return TYPE_RENAMES.get(pine_type, pine_type)
351
+
352
+
353
+ def is_series_variable(name: str) -> bool:
354
+ """Check if a variable is a built-in Series variable."""
355
+ return name in SERIES_VARIABLES
356
+
357
+
358
+ def is_constant(name: str) -> bool:
359
+ """Check if a name is a built-in constant."""
360
+ return name in CONSTANTS
361
+
362
+
363
+ def needs_plot_style_remap(name: str) -> bool:
364
+ """Check if a name needs plot_style module remapping."""
365
+ return name in PLOT_STYLE_REMAP
366
+
367
+
368
+ def get_plot_style_remap(name: str) -> str:
369
+ """Get the remapped plot_style constant name."""
370
+ return PLOT_STYLE_REMAP.get(name, name)
371
+
372
+
373
+ # Input functions that extract to main() parameters
374
+ INPUT_FUNCTIONS: Set[str] = {
375
+ 'input.int',
376
+ 'input.float',
377
+ 'input.bool',
378
+ 'input.string',
379
+ 'input.color',
380
+ 'input.source',
381
+ 'input.timeframe',
382
+ 'input.session',
383
+ 'input.symbol',
384
+ 'input.price',
385
+ 'input.time',
386
+ }
387
+
388
+
389
+ def is_input_function(func_name: str) -> bool:
390
+ """Check if function is an input.* function."""
391
+ return func_name in INPUT_FUNCTIONS
392
+
393
+
394
+ # Pine Script input.* positional parameter names (after defval, title).
395
+ # Pine Script allows these as positional; PyneCore requires keyword-only.
396
+ INPUT_POSITIONAL_PARAMS: Dict[str, list[str]] = {
397
+ 'input.int': ['minval', 'maxval', 'step', 'tooltip', 'inline', 'group', 'confirm', 'display', 'options'],
398
+ 'input.float': ['minval', 'maxval', 'step', 'tooltip', 'inline', 'group', 'confirm', 'display', 'options'],
399
+ 'input.bool': ['tooltip', 'inline', 'group', 'confirm', 'display'],
400
+ 'input.string': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
401
+ 'input.color': ['tooltip', 'inline', 'group', 'confirm', 'display'],
402
+ 'input.timeframe': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
403
+ 'input.session': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
404
+ 'input.symbol': ['options', 'tooltip', 'inline', 'group', 'confirm', 'display'],
405
+ 'input.source': ['tooltip', 'inline', 'group', 'confirm', 'display'],
406
+ 'input.price': ['tooltip', 'inline', 'group', 'confirm', 'display'],
407
+ 'input.time': ['tooltip', 'inline', 'group', 'confirm', 'display'],
408
+ }
409
+
410
+
411
+ # Python reserved words that cannot be used as function/variable names.
412
+ PYTHON_RESERVED_WORDS: Set[str] = {
413
+ 'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
414
+ 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
415
+ 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
416
+ 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
417
+ 'try', 'while', 'with', 'yield',
418
+ }
419
+
420
+
421
+ def sanitize_identifier(name: str) -> str:
422
+ """Rename Python reserved words used as identifiers by appending underscore."""
423
+ if name in PYTHON_RESERVED_WORDS:
424
+ return f'{name}_'
425
+ return name
426
+
427
+
428
+ # TA functions that return Series
429
+ TA_FUNCTIONS: Set[str] = {
430
+ 'ta.sma', 'ta.ema', 'ta.wma', 'ta.vwma', 'ta.swma', 'ta.alma',
431
+ 'ta.rma', 'ta.hma', 'ta.linreg', 'ta.rsi', 'ta.macd',
432
+ 'ta.stoch', 'ta.cci', 'ta.mfi', 'ta.obv', 'ta.sar',
433
+ 'ta.atr', 'ta.tr', 'ta.bb', 'ta.bbw', 'ta.kc', 'ta.kcw',
434
+ 'ta.dmi', 'ta.adx', 'ta.supertrend', 'ta.highest', 'ta.lowest',
435
+ 'ta.valuewhen', 'ta.barssince', 'ta.change', 'ta.roc',
436
+ 'ta.mom', 'ta.cum', 'ta.dev', 'ta.stdev', 'ta.variance',
437
+ 'ta.correlation', 'ta.median', 'ta.mode', 'ta.range',
438
+ 'ta.cog', 'ta.percentile_linear_interpolation',
439
+ 'ta.percentile_nearest_rank', 'ta.percentrank', 'ta.pivothigh',
440
+ 'ta.pivotlow', 'ta.rising', 'ta.falling', 'ta.cross', 'ta.crossover',
441
+ 'ta.crossunder',
442
+ }
443
+
444
+
445
+ def is_ta_function(func_name: str) -> bool:
446
+ """Check if function is a ta.* function."""
447
+ return func_name in TA_FUNCTIONS
448
+
449
+
450
+ # Pine Script built-in "variable-functions": used without () in Pine Script
451
+ # but require () in PyneCore because they're implemented as functions.
452
+ # Values are the default arguments to pass when auto-calling.
453
+ TA_VARIABLE_FUNCTIONS: Dict[str, str] = {
454
+ 'ta.vwap': '(high + low + close) / 3', # default source is hlc3
455
+ 'ta.tr': '', # no args needed
456
+ 'ta.accdist': '',
457
+ 'ta.obv': '',
458
+ 'ta.iii': '',
459
+ 'ta.nvi': '',
460
+ 'ta.pvi': '',
461
+ 'ta.pvt': '',
462
+ 'ta.wad': '',
463
+ 'ta.wvad': '',
464
+ }
@@ -0,0 +1,156 @@
1
+ """
2
+ Symbol table for tracking variable declarations and scopes.
3
+
4
+ Used by the transformer to determine variable types and manage scope.
5
+ """
6
+ from typing import Dict, List, Optional, Set
7
+ from enum import Enum
8
+
9
+
10
+ class VariableKind(Enum):
11
+ """Classification of variable kinds in Pine Script."""
12
+ VAR = "var" # Persistent variable (var keyword)
13
+ VARIP = "varip" # Persistent intrabar variable
14
+ INPUT = "input" # Input parameter
15
+ SERIES = "series" # Series variable (default for globals)
16
+ SIMPLE = "simple" # Simple (non-series) variable
17
+ FUNCTION = "function" # Function name
18
+ PARAMETER = "parameter" # Function parameter
19
+ TYPE = "type" # User-defined type (UDT) declaration
20
+
21
+
22
+ class Symbol:
23
+ """Represents a symbol (variable, function) in the symbol table."""
24
+
25
+ def __init__(
26
+ self,
27
+ name: str,
28
+ kind: VariableKind,
29
+ type_hint: Optional[str] = None,
30
+ is_global: bool = False,
31
+ line: int = 0,
32
+ column: int = 0,
33
+ ):
34
+ self.name = name
35
+ self.kind = kind
36
+ self.type_hint = type_hint
37
+ self.is_global = is_global
38
+ self.line = line
39
+ self.column = column
40
+ self.is_series = False # Will be set by type inference
41
+ self.is_indexed = False # True if variable is indexed with [n]
42
+ self.is_from_ta_function = False # True if assigned from ta.* function
43
+
44
+
45
+ class Scope:
46
+ """Represents a lexical scope."""
47
+
48
+ def __init__(self, parent: Optional['Scope'] = None):
49
+ self.parent = parent
50
+ self.symbols: Dict[str, Symbol] = {}
51
+
52
+ def define(self, symbol: Symbol) -> None:
53
+ """Define a symbol in this scope."""
54
+ self.symbols[symbol.name] = symbol
55
+
56
+ def lookup(self, name: str) -> Optional[Symbol]:
57
+ """Look up a symbol in this scope or parent scopes."""
58
+ if name in self.symbols:
59
+ return self.symbols[name]
60
+ if self.parent:
61
+ return self.parent.lookup(name)
62
+ return None
63
+
64
+ def lookup_local(self, name: str) -> Optional[Symbol]:
65
+ """Look up a symbol only in this scope (not parent)."""
66
+ return self.symbols.get(name)
67
+
68
+
69
+ class SymbolTable:
70
+ """Manages symbol definitions and scopes."""
71
+
72
+ def __init__(self):
73
+ self.global_scope = Scope()
74
+ self.current_scope = self.global_scope
75
+ self.builtin_symbols = self._init_builtins()
76
+
77
+ def _init_builtins(self) -> Set[str]:
78
+ """Initialize built-in Pine Script variables."""
79
+ return {
80
+ # Price data
81
+ 'open', 'high', 'low', 'close', 'volume',
82
+ 'hl2', 'hlc3', 'ohlc4', 'hlcc4',
83
+ # Bar data
84
+ 'bar_index', 'time', 'timenow',
85
+ # Bar state
86
+ 'barstate.isfirst', 'barstate.islast', 'barstate.ishistory',
87
+ 'barstate.isrealtime', 'barstate.isnew', 'barstate.isconfirmed',
88
+ # Constants
89
+ 'na', 'true', 'false',
90
+ # Color constants
91
+ 'color.red', 'color.green', 'color.blue', 'color.white', 'color.black',
92
+ 'color.yellow', 'color.orange', 'color.purple', 'color.gray',
93
+ # Plot styles
94
+ 'plot.style_line', 'plot.style_stepline', 'plot.style_histogram',
95
+ 'plot.style_cross', 'plot.style_area', 'plot.style_columns',
96
+ 'plot.style_circles', 'plot.style_linebr', 'plot.style_steplinebr',
97
+ # Strategy constants
98
+ 'strategy.long', 'strategy.short',
99
+ 'strategy.commission.percent', 'strategy.commission.cash_per_contract',
100
+ 'strategy.commission.cash_per_order',
101
+ 'strategy.percent', 'strategy.fixed', 'strategy.cash',
102
+ 'strategy.percent_of_equity',
103
+ }
104
+
105
+ def enter_scope(self) -> None:
106
+ """Enter a new scope."""
107
+ self.current_scope = Scope(parent=self.current_scope)
108
+
109
+ def exit_scope(self) -> None:
110
+ """Exit the current scope."""
111
+ if self.current_scope.parent:
112
+ self.current_scope = self.current_scope.parent
113
+
114
+ def define(self, symbol: Symbol) -> None:
115
+ """Define a symbol in the current scope."""
116
+ self.current_scope.define(symbol)
117
+
118
+ def lookup(self, name: str) -> Optional[Symbol]:
119
+ """Look up a symbol in current or parent scopes."""
120
+ # Check builtins first
121
+ if name in self.builtin_symbols:
122
+ return Symbol(name, VariableKind.SERIES, is_global=True)
123
+
124
+ return self.current_scope.lookup(name)
125
+
126
+ def lookup_global(self, name: str) -> Optional[Symbol]:
127
+ """Look up a symbol only in global scope."""
128
+ return self.global_scope.lookup_local(name)
129
+
130
+ def is_builtin(self, name: str) -> bool:
131
+ """Check if name is a built-in symbol."""
132
+ return name in self.builtin_symbols
133
+
134
+ def get_all_globals(self) -> List[Symbol]:
135
+ """Get all symbols defined in global scope."""
136
+ return list(self.global_scope.symbols.values())
137
+
138
+ def mark_as_indexed(self, name: str) -> None:
139
+ """Mark a variable as being indexed (e.g., close[1])."""
140
+ symbol = self.lookup(name)
141
+ if symbol:
142
+ symbol.is_indexed = True
143
+ symbol.is_series = True # Indexed variables are always Series
144
+
145
+ def mark_as_series(self, name: str) -> None:
146
+ """Mark a variable as a Series type."""
147
+ symbol = self.lookup(name)
148
+ if symbol:
149
+ symbol.is_series = True
150
+
151
+ def mark_from_ta_function(self, name: str) -> None:
152
+ """Mark a variable as assigned from a ta.* function."""
153
+ symbol = self.lookup(name)
154
+ if symbol:
155
+ symbol.is_from_ta_function = True
156
+ symbol.is_series = True # ta.* functions return Series