pine2pyne 0.2.4__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.
Files changed (75) hide show
  1. pine2pyne/__init__.py +84 -0
  2. pine2pyne/__main__.py +12 -0
  3. pine2pyne/ast_nodes.py +405 -0
  4. pine2pyne/ast_walk.py +93 -0
  5. pine2pyne/block_value_lowering.py +694 -0
  6. pine2pyne/bool_builtins.py +42 -0
  7. pine2pyne/bool_value_typing.py +580 -0
  8. pine2pyne/cli.py +247 -0
  9. pine2pyne/codegen.py +886 -0
  10. pine2pyne/compat/__init__.py +19 -0
  11. pine2pyne/compat/__main__.py +6 -0
  12. pine2pyne/compat/cli.py +114 -0
  13. pine2pyne/compat/comparison.py +105 -0
  14. pine2pyne/compat/corpus.py +73 -0
  15. pine2pyne/compat/report.py +95 -0
  16. pine2pyne/compat/runner.py +63 -0
  17. pine2pyne/condition_typing.py +260 -0
  18. pine2pyne/conditional_history_calls.py +96 -0
  19. pine2pyne/core/__init__.py +26 -0
  20. pine2pyne/core/contracts.py +125 -0
  21. pine2pyne/di.py +173 -0
  22. pine2pyne/emit/__init__.py +5 -0
  23. pine2pyne/emit/legacy.py +14 -0
  24. pine2pyne/emit/pynecomp/__init__.py +20 -0
  25. pine2pyne/emit/pynecomp/banner.py +30 -0
  26. pine2pyne/emit/pynecomp/blank_lines.py +69 -0
  27. pine2pyne/emit/pynecomp/emitter.py +61 -0
  28. pine2pyne/emit/pynecomp/imports.py +65 -0
  29. pine2pyne/emit/pynecomp/passes/__init__.py +84 -0
  30. pine2pyne/emit/pynecomp/passes/annotations.py +422 -0
  31. pine2pyne/emit/pynecomp/passes/base.py +69 -0
  32. pine2pyne/emit/pynecomp/passes/builtins.py +169 -0
  33. pine2pyne/emit/pynecomp/passes/hoisting.py +205 -0
  34. pine2pyne/emit/pynecomp/passes/imports.py +112 -0
  35. pine2pyne/emit/pynecomp/passes/legacy_colors.py +101 -0
  36. pine2pyne/emit/pynecomp/passes/library.py +205 -0
  37. pine2pyne/emit/pynecomp/passes/rewrites.py +215 -0
  38. pine2pyne/emit/pynecomp/printer.py +288 -0
  39. pine2pyne/emit/pynecomp/unparser.py +186 -0
  40. pine2pyne/emitted_names.py +152 -0
  41. pine2pyne/errors.py +69 -0
  42. pine2pyne/for_loop_end_bound.py +172 -0
  43. pine2pyne/grouped_history_lowering.py +310 -0
  44. pine2pyne/import_resolver.py +469 -0
  45. pine2pyne/input_hoisting.py +195 -0
  46. pine2pyne/legacy_function_names.py +74 -0
  47. pine2pyne/legacy_sessions.py +68 -0
  48. pine2pyne/lexer.py +630 -0
  49. pine2pyne/library_collision.py +434 -0
  50. pine2pyne/namespace_members.py +247 -0
  51. pine2pyne/node_ids.py +235 -0
  52. pine2pyne/once_lowering.py +75 -0
  53. pine2pyne/overload_resolution.py +475 -0
  54. pine2pyne/parse_repair.py +398 -0
  55. pine2pyne/parser.py +1873 -0
  56. pine2pyne/pine_builtins.py +394 -0
  57. pine2pyne/pine_semantics.py +604 -0
  58. pine2pyne/pipeline/__init__.py +18 -0
  59. pine2pyne/pipeline/service.py +70 -0
  60. pine2pyne/pipeline/stages.py +73 -0
  61. pine2pyne/scope_renamer.py +965 -0
  62. pine2pyne/strategy_when_lowering.py +101 -0
  63. pine2pyne/strict_boolean.py +101 -0
  64. pine2pyne/symbol_table.py +166 -0
  65. pine2pyne/tokens.py +143 -0
  66. pine2pyne/transformer.py +2510 -0
  67. pine2pyne/type_inference.py +422 -0
  68. pine2pyne/udt_field_defaults.py +149 -0
  69. pine2pyne/value_blocks.py +357 -0
  70. pine2pyne-0.2.4.dist-info/METADATA +99 -0
  71. pine2pyne-0.2.4.dist-info/RECORD +75 -0
  72. pine2pyne-0.2.4.dist-info/WHEEL +5 -0
  73. pine2pyne-0.2.4.dist-info/entry_points.txt +3 -0
  74. pine2pyne-0.2.4.dist-info/licenses/LICENSE +201 -0
  75. pine2pyne-0.2.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,247 @@
1
+ """
2
+ The members of each pynecore.lib namespace, as the runtime defines them.
3
+
4
+ GENERATED by tools/collect_namespace_members.py from opencode-pyneruntime 6.9.4; do not
5
+ edit by hand. A binding named like a namespace uses it to tell `N.m` on the
6
+ binding from `N.m` on the namespace (card #9).
7
+ """
8
+ from typing import Dict, FrozenSet
9
+
10
+ NAMESPACE_MEMBERS: Dict[str, FrozenSet[str]] = {
11
+ 'adjustment': frozenset({
12
+ 'dividends', 'none', 'splits',
13
+ }),
14
+ 'alert': frozenset({
15
+ 'alert', 'freq_all', 'freq_once_per_bar', 'freq_once_per_bar_close',
16
+ }),
17
+ 'array': frozenset({
18
+ 'abs', 'avg', 'binary_search', 'binary_search_leftmost', 'binary_search_rightmost',
19
+ 'clear', 'concat', 'copy', 'covariance', 'every', 'fill', 'first', 'from_items', 'get',
20
+ 'includes', 'indexof', 'insert', 'join', 'last', 'lastindexof', 'max', 'median', 'min',
21
+ 'mode', 'na_float', 'new', 'new_bool', 'new_box', 'new_color', 'new_float', 'new_int',
22
+ 'new_label', 'new_line', 'new_linefill', 'new_string', 'new_table',
23
+ 'percentile_linear_interpolation', 'percentile_nearest_rank', 'percentrank', 'pop',
24
+ 'push', 'range', 'remove', 'reverse', 'set', 'shift', 'size', 'slice', 'some', 'sort',
25
+ 'sort_indices', 'standardize', 'stdev', 'sum', 'unshift', 'variance',
26
+ }),
27
+ 'barmerge': frozenset({
28
+ 'gaps_off', 'gaps_on', 'lookahead_last_closed', 'lookahead_off', 'lookahead_on',
29
+ }),
30
+ 'barstate': frozenset({
31
+ 'isconfirmed', 'isfirst', 'ishistory', 'islast', 'islastconfirmedhistory', 'isnew',
32
+ 'isrealtime',
33
+ }),
34
+ 'box': frozenset({
35
+ 'all', 'copy', 'delete', 'get_bottom', 'get_left', 'get_right', 'get_top', 'na_float',
36
+ 'new', 'set_bgcolor', 'set_border_color', 'set_border_style', 'set_border_width',
37
+ 'set_bottom', 'set_bottom_right_point', 'set_extend', 'set_left', 'set_lefttop',
38
+ 'set_right', 'set_rightbottom', 'set_text', 'set_text_color', 'set_text_font_family',
39
+ 'set_text_formatting', 'set_text_halign', 'set_text_size', 'set_text_valign',
40
+ 'set_text_wrap', 'set_top', 'set_top_left_point', 'set_xloc',
41
+ }),
42
+ 'chart': frozenset({
43
+ 'bg_color', 'fg_color', 'is_heikinashi', 'is_kagi', 'is_linebreak', 'is_pnf',
44
+ 'is_range', 'is_renko', 'is_standard', 'left_visible_bar_time', 'point',
45
+ 'right_visible_bar_time',
46
+ }),
47
+ 'color': frozenset({
48
+ 'aqua', 'b', 'black', 'blue', 'from_gradient', 'fuchsia', 'g', 'gray', 'green', 'lime',
49
+ 'maroon', 'navy', 'new', 'olive', 'orange', 'purple', 'r', 'red', 'rgb', 'silver', 't',
50
+ 'teal', 'white', 'yellow',
51
+ }),
52
+ 'currency': frozenset({
53
+ 'AED', 'ARS', 'AUD', 'BDT', 'BHD', 'BRL', 'BTC', 'CAD', 'CHF', 'CLP', 'CNY', 'COP',
54
+ 'CZK', 'DKK', 'EGP', 'ETH', 'EUR', 'GBP', 'HKD', 'HUF', 'IDR', 'ILS', 'INR', 'ISK',
55
+ 'JPY', 'KES', 'KRW', 'KWD', 'LKR', 'MAD', 'MXN', 'MYR', 'NGN', 'NOK', 'NONE', 'NZD',
56
+ 'PEN', 'PHP', 'PKR', 'PLN', 'QAR', 'RON', 'RSD', 'RUB', 'SAR', 'SEK', 'SGD', 'THB',
57
+ 'TND', 'TRY', 'TWD', 'USD', 'USDT', 'VES', 'VND', 'ZAR',
58
+ }),
59
+ 'dayofweek': frozenset({
60
+ 'dayofweek', 'friday', 'monday', 'saturday', 'sunday', 'thursday', 'tuesday',
61
+ 'wednesday',
62
+ }),
63
+ 'display': frozenset({
64
+ 'all', 'data_window', 'none', 'pane', 'price_scale', 'status_line',
65
+ }),
66
+ 'dividends': frozenset({
67
+ 'future_amount', 'future_ex_date', 'future_pay_date', 'gross', 'na_float', 'net',
68
+ }),
69
+ 'earnings': frozenset({
70
+ 'actual', 'estimate', 'future_eps', 'future_period_end_time', 'future_revenue',
71
+ 'future_time', 'na_float', 'standardized',
72
+ }),
73
+ 'extend': frozenset({
74
+ 'both', 'left', 'none', 'right',
75
+ }),
76
+ 'font': frozenset({
77
+ 'family_default', 'family_monospace',
78
+ }),
79
+ 'footprint': frozenset({
80
+ 'buy_volume', 'delta', 'poc', 'sell_volume', 'total_volume', 'vah', 'val',
81
+ }),
82
+ 'format': frozenset({
83
+ 'inherit', 'mintick', 'percent', 'price', 'volume',
84
+ }),
85
+ 'hline': frozenset({
86
+ 'hline', 'style_dashed', 'style_dotted', 'style_solid',
87
+ }),
88
+ 'label': frozenset({
89
+ 'all', 'copy', 'delete', 'get_text', 'get_x', 'get_y', 'na_float', 'new', 'set_color',
90
+ 'set_point', 'set_size', 'set_style', 'set_text', 'set_text_font_family',
91
+ 'set_text_formatting', 'set_textalign', 'set_textcolor', 'set_tooltip', 'set_x',
92
+ 'set_xloc', 'set_xy', 'set_y', 'set_yloc', 'style_arrowdown', 'style_arrowup',
93
+ 'style_circle', 'style_cross', 'style_diamond', 'style_flag', 'style_label_center',
94
+ 'style_label_down', 'style_label_left', 'style_label_lower_left',
95
+ 'style_label_lower_right', 'style_label_right', 'style_label_up',
96
+ 'style_label_upper_left', 'style_label_upper_right', 'style_none', 'style_square',
97
+ 'style_text_outline', 'style_triangledown', 'style_triangleup', 'style_xcross',
98
+ }),
99
+ 'line': frozenset({
100
+ 'all', 'copy', 'delete', 'get_price', 'get_x1', 'get_x2', 'get_y1', 'get_y2',
101
+ 'na_float', 'new', 'set_color', 'set_extend', 'set_first_point', 'set_second_point',
102
+ 'set_style', 'set_width', 'set_x1', 'set_x2', 'set_xloc', 'set_xy1', 'set_xy2',
103
+ 'set_y1', 'set_y2', 'style_arrow_both', 'style_arrow_left', 'style_arrow_right',
104
+ 'style_dashed', 'style_dotted', 'style_solid',
105
+ }),
106
+ 'linefill': frozenset({
107
+ 'all', 'delete', 'get_line1', 'get_line2', 'new', 'set_color',
108
+ }),
109
+ 'location': frozenset({
110
+ 'abovebar', 'absolute', 'belowbar', 'bottom', 'top',
111
+ }),
112
+ 'log': frozenset({
113
+ 'broker_debug', 'broker_error', 'broker_info', 'broker_warning', 'error', 'handler',
114
+ 'info', 'logger', 'ohlcv_info', 'setup_security_file_log', 'sim_info', 'warning',
115
+ }),
116
+ 'map': frozenset({
117
+ 'clear', 'contains', 'copy', 'get', 'keys', 'new', 'put', 'put_all', 'remove', 'size',
118
+ 'values',
119
+ }),
120
+ 'math': frozenset({
121
+ 'abs', 'acos', 'asin', 'atan', 'avg', 'ceil', 'cos', 'e', 'exp', 'floor', 'log',
122
+ 'log10', 'max', 'min', 'na_float', 'phi', 'pi', 'pow', 'random', 'round',
123
+ 'round_to_mintick', 'rphi', 'sign', 'sin', 'sqrt', 'sum', 'tan', 'todegrees',
124
+ 'toradians',
125
+ }),
126
+ 'matrix': frozenset({
127
+ 'add_col', 'add_row', 'all', 'annotations', 'avg', 'col', 'columns', 'concat', 'copy',
128
+ 'delete', 'det', 'diff', 'eigenvalues', 'eigenvectors', 'elements_count', 'fill', 'get',
129
+ 'inv', 'is_antidiagonal', 'is_antisymmetric', 'is_binary', 'is_diagonal', 'is_identity',
130
+ 'is_square', 'is_stochastic', 'is_symmetric', 'is_triangular', 'is_zero', 'kron', 'max',
131
+ 'median', 'min', 'mode', 'mult', 'na_float', 'new', 'pinv', 'pow', 'rank', 'remove_col',
132
+ 'remove_row', 'reshape', 'reverse', 'row', 'rows', 'set', 'sort', 'submatrix', 'sum',
133
+ 'swap_columns', 'swap_rows', 'trace', 'transpose',
134
+ }),
135
+ 'order': frozenset({
136
+ 'ascending', 'descending',
137
+ }),
138
+ 'pivotpointtype': frozenset({
139
+ 'camarilla', 'classic', 'dm', 'fibonacci', 'traditional', 'woodie',
140
+ }),
141
+ 'plot': frozenset({
142
+ 'linestyle_dashed', 'linestyle_dotted', 'linestyle_solid', 'plot', 'style_area',
143
+ 'style_areabr', 'style_circles', 'style_columns', 'style_cross', 'style_histogram',
144
+ 'style_line', 'style_linebr', 'style_stepline', 'style_stepline_diamond',
145
+ 'style_steplinebr',
146
+ }),
147
+ 'polyline': frozenset({
148
+ 'all', 'delete', 'new', 'style_arrow_both', 'style_arrow_left', 'style_arrow_right',
149
+ 'style_dashed', 'style_dotted', 'style_solid',
150
+ }),
151
+ 'position': frozenset({
152
+ 'bottom_center', 'bottom_left', 'bottom_right', 'middle_center', 'middle_left',
153
+ 'middle_right', 'top_center', 'top_left', 'top_right',
154
+ }),
155
+ 'request': frozenset({
156
+ 'annotations', 'currency_rate', 'dividends', 'earnings', 'economic', 'financial',
157
+ 'footprint', 'nan', 'quandl', 'security', 'security_lower_tf', 'seed', 'splits',
158
+ }),
159
+ 'runtime': frozenset({
160
+ 'error',
161
+ }),
162
+ 'scale': frozenset({
163
+ 'left', 'none', 'right',
164
+ }),
165
+ 'session': frozenset({
166
+ 'extended', 'isfirstbar', 'isfirstbar_regular', 'islastbar', 'islastbar_regular',
167
+ 'ismarket', 'ispostmarket', 'ispremarket', 'regular',
168
+ }),
169
+ 'shape': frozenset({
170
+ 'arrowdown', 'arrowup', 'circle', 'cross', 'diamond', 'flag', 'labeldown', 'labelup',
171
+ 'square', 'triangledown', 'triangleup', 'xcross',
172
+ }),
173
+ 'size': frozenset({
174
+ 'auto', 'huge', 'large', 'normal', 'small', 'tiny',
175
+ }),
176
+ 'splits': frozenset({
177
+ 'denominator', 'numerator',
178
+ }),
179
+ 'strategy': frozenset({
180
+ 'account_currency', 'avg_losing_trade', 'avg_losing_trade_percent', 'avg_trade',
181
+ 'avg_trade_percent', 'avg_winning_trade', 'avg_winning_trade_percent', 'cancel',
182
+ 'cancel_all', 'cash', 'close', 'close_all', 'closedtrades', 'commission',
183
+ 'convert_to_account', 'convert_to_symbol', 'default_entry_qty', 'direction', 'entry',
184
+ 'equity', 'eventrades', 'exit', 'fixed', 'grossloss', 'grossloss_percent',
185
+ 'grossprofit', 'grossprofit_percent', 'initial_capital', 'long', 'losstrades',
186
+ 'margin_liquidation_price', 'max_contracts_held_all', 'max_contracts_held_long',
187
+ 'max_contracts_held_short', 'max_drawdown', 'max_drawdown_percent', 'max_runup',
188
+ 'max_runup_percent', 'na_float', 'netprofit', 'netprofit_percent', 'oca', 'openprofit',
189
+ 'openprofit_percent', 'opentrades', 'order', 'percent_of_equity', 'position_avg_price',
190
+ 'position_entry_name', 'position_size', 'risk', 'short', 'wintrades',
191
+ }),
192
+ 'string': frozenset({
193
+ 'contains', 'endswith', 'format', 'format_time', 'length', 'lower', 'match', 'na_float',
194
+ 'pos', 'repeat', 'replace', 'replace_all', 'split', 'startswith', 'substring',
195
+ 'tonumber', 'tostring', 'trim', 'upper',
196
+ }),
197
+ 'syminfo': frozenset({
198
+ 'basecurrency', 'country', 'currency', 'current_contract', 'description', 'employees',
199
+ 'expiration_date', 'industry', 'isin', 'main_tickerid', 'mincontract', 'minmove',
200
+ 'mintick', 'na_float', 'period', 'pointvalue', 'prefix', 'pricescale',
201
+ 'recommendations_buy', 'recommendations_buy_strong', 'recommendations_date',
202
+ 'recommendations_hold', 'recommendations_sell', 'recommendations_sell_strong',
203
+ 'recommendations_total', 'regular', 'root', 'sector', 'session', 'shareholders',
204
+ 'shares_outstanding_float', 'shares_outstanding_total', 'target_price_average',
205
+ 'target_price_date', 'target_price_estimates', 'target_price_high', 'target_price_low',
206
+ 'target_price_median', 'ticker', 'tickerid', 'timezone', 'type', 'volumetype',
207
+ }),
208
+ 'ta': frozenset({
209
+ 'accdist', 'alma', 'atr', 'barssince', 'bb', 'bbw', 'cci', 'change', 'cmo', 'cog',
210
+ 'correlation', 'cross', 'crossover', 'crossunder', 'cum', 'dev', 'dmi', 'ema',
211
+ 'falling', 'highest', 'highestbars', 'hma', 'iii', 'kc', 'kcw', 'linreg', 'lowest',
212
+ 'lowestbars', 'macd', 'max', 'median', 'mfi', 'min', 'mode', 'mom', 'na_float', 'nvi',
213
+ 'obv', 'percentile_linear_interpolation', 'percentile_nearest_rank', 'percentrank',
214
+ 'pivot_point_levels', 'pivothigh', 'pivotlow', 'pvi', 'pvt', 'range', 'rci', 'rising',
215
+ 'rma', 'roc', 'rsi', 'sar', 'sma', 'stdev', 'stoch', 'supertrend', 'swma', 'tr', 'tsi',
216
+ 'valuewhen', 'variance', 'vwap', 'vwma', 'wad', 'wma', 'wpr', 'wvad',
217
+ }),
218
+ 'table': frozenset({
219
+ 'all', 'cell', 'cell_set_bgcolor', 'cell_set_height', 'cell_set_text',
220
+ 'cell_set_text_color', 'cell_set_text_font_family', 'cell_set_text_formatting',
221
+ 'cell_set_text_halign', 'cell_set_text_size', 'cell_set_text_valign',
222
+ 'cell_set_tooltip', 'cell_set_width', 'clear', 'delete', 'merge_cells', 'new',
223
+ 'set_bgcolor', 'set_border_color', 'set_border_width', 'set_frame_color',
224
+ 'set_frame_width', 'set_position',
225
+ }),
226
+ 'text': frozenset({
227
+ 'align_bottom', 'align_center', 'align_left', 'align_right', 'align_top', 'format_bold',
228
+ 'format_italic', 'format_none', 'wrap_auto', 'wrap_none',
229
+ }),
230
+ 'ticker': frozenset({
231
+ 'heikinashi', 'inherit', 'kagi', 'linebreak', 'modify', 'new', 'pointfigure', 'renko',
232
+ 'standard',
233
+ }),
234
+ 'timeframe': frozenset({
235
+ 'change', 'from_seconds', 'in_seconds', 'isdaily', 'isdwm', 'isintraday', 'isminutes',
236
+ 'ismonthly', 'isseconds', 'isticks', 'isweekly', 'main_period', 'multiplier', 'period',
237
+ }),
238
+ 'volume_row': frozenset({
239
+ 'buy_volume', 'delta', 'down_price', 'sell_volume', 'total_volume', 'up_price',
240
+ }),
241
+ 'xloc': frozenset({
242
+ 'bar_index', 'bar_time',
243
+ }),
244
+ 'yloc': frozenset({
245
+ 'abovebar', 'belowbar', 'price',
246
+ }),
247
+ }
pine2pyne/node_ids.py ADDED
@@ -0,0 +1,235 @@
1
+ """The numbers PyneComp gives the blocks of a script: the suffix of a renamed local.
2
+
3
+ A local that shadows a name, or is named like a function of the script, is
4
+ spelled ``<name>__<n>__`` by PyneComp, ``n`` eight hex digits. ``n`` is the id
5
+ its parser gave the block the local is declared in. The parser numbers some
6
+ nodes, from 1, as it finishes them (post-order, in source order):
7
+
8
+ * a call, except a cast (``int(x)``, ``label(na)``) and Pine v4's ``iff()``,
9
+ which is parsed as a conditional;
10
+ * a history read ``x[n]``;
11
+ * every block: a function body (a one-line body too), each branch of an
12
+ ``if``/``else if``/``else``, each case of a ``switch``, a loop body;
13
+ * a ``type`` or ``enum`` declaration.
14
+
15
+ Pine v4 is converted while it is parsed and the nodes the conversion creates
16
+ are numbered too: a constant colour merged with ``transp`` (or with v4's
17
+ default transparency) is a ``color.new()`` call. (The session conversion of
18
+ ``time()``, two more calls, is in the tree already: :mod:`.legacy_sessions`.)
19
+
20
+ Measured with probe scripts compiled by PyneComp v6.0.80 (``tmp/probes``):
21
+ a meter function ``m(x) => m = x`` between two constructs reads how many ids
22
+ the construct takes. On the reference corpus the numbering names 87 of the 92
23
+ modules with such locals that pine2pyne parses.
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import dataclasses
28
+ from typing import Dict, Optional
29
+
30
+ from .legacy_sessions import SESSION_HELPER
31
+ from .ast_nodes import (
32
+ ASTNode, Assignment, EnumDecl, ExpressionStatement, ForInLoop, ForLoop, FuncDecl, FunctionCall, Identifier,
33
+ IfExpression, IfStatement, IndexAccess, InputDecl, Literal, MemberAccess, MethodCall, Script, SwitchExpression,
34
+ SwitchStatement, TupleDestructure, TypeDecl, WhileLoop,
35
+ )
36
+
37
+ #: calls that are casts: no node of their own
38
+ _CASTS = frozenset({'int', 'float', 'bool', 'string', 'color', 'line', 'label', 'box', 'table', 'linefill',
39
+ 'polyline', 'iff'})
40
+ _NAMED_COLOURS = frozenset({
41
+ 'aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange',
42
+ 'purple', 'red', 'silver', 'teal', 'white', 'yellow'})
43
+ #: Pine v4 calls whose colour takes ``transp``: the colour keywords, (colour, transp) positions
44
+ _V4_COLOUR_KEYWORDS = {'plot': ('color',), 'plotshape': ('color', 'textcolor'), 'plotchar': ('color',),
45
+ 'plotarrow': ('colorup', 'colordown'), 'fill': ('color',)}
46
+ _V4_COLOUR_POSITIONS = {'plot': (2, None), 'plotshape': (4, 5), 'plotchar': (4, 5), 'fill': (2, 3)}
47
+
48
+
49
+ def hex_suffixed(name: str, block_id: int) -> str:
50
+ """PyneComp's spelling of a renamed local of block ``block_id``."""
51
+ return f'{name}__{block_id:08x}__'
52
+
53
+
54
+ class BlockNumbers:
55
+ """The id of every block of a script, by the identity of its statement list
56
+ (or of the function, for a one-line body). Built once per script."""
57
+
58
+ def __init__(self, script: Script) -> None:
59
+ version_text = getattr(script.version, 'version', '') if script.version else ''
60
+ self._version = int(version_text) if str(version_text).isdigit() else 6
61
+ self._count = 0
62
+ self._ids: Dict[int, int] = {}
63
+ items = sorted(list(script.declarations) + list(script.body),
64
+ key=lambda item: (getattr(item, 'line', 0), getattr(item, 'column', 0)))
65
+ self._constant_colour_names = self._constant_colours(items) if self._version <= 4 else frozenset()
66
+ if script.script_decl is not None:
67
+ for value in script.script_decl.kwargs.values():
68
+ self._expression(value)
69
+ self._take()
70
+ for item in items:
71
+ self._statement(item)
72
+
73
+ def of(self, block) -> int:
74
+ """The id of a statement list (or of a FuncDecl's body); 0 when unknown."""
75
+ return self._ids.get(id(block), 0)
76
+
77
+ # ------------------------------------------------------------ numbering
78
+
79
+ def _take(self) -> int:
80
+ self._count += 1
81
+ return self._count
82
+
83
+ def _block(self, statements, owner: Optional[object] = None) -> None:
84
+ if isinstance(statements, list):
85
+ for statement in statements:
86
+ self._statement(statement)
87
+ else:
88
+ self._expression(statements)
89
+ number = self._take()
90
+ self._ids[id(statements)] = number
91
+ if owner is not None:
92
+ self._ids[id(owner)] = number
93
+
94
+ def _statement(self, node) -> None:
95
+ if isinstance(node, FuncDecl):
96
+ if node.name == SESSION_HELPER:
97
+ return # PyneComp's own helper
98
+ for parameter in node.params:
99
+ self._expression(parameter.default)
100
+ self._block(node.body, owner=node)
101
+ elif isinstance(node, IfStatement):
102
+ self._expression(node.condition)
103
+ self._block(node.body)
104
+ for condition, body in node.elseifs:
105
+ self._expression(condition)
106
+ self._block(body)
107
+ if node.else_body is not None:
108
+ self._block(node.else_body)
109
+ elif isinstance(node, ForLoop):
110
+ for value in (node.from_val, node.to_val, node.step):
111
+ self._expression(value)
112
+ self._block(node.body)
113
+ elif isinstance(node, ForInLoop):
114
+ self._expression(node.iterable)
115
+ self._block(node.body)
116
+ elif isinstance(node, WhileLoop):
117
+ self._expression(node.condition)
118
+ self._block(node.body)
119
+ elif isinstance(node, (SwitchStatement, SwitchExpression)):
120
+ self._switch(node)
121
+ elif isinstance(node, InputDecl):
122
+ for value in list(node.args) + list(node.kwargs.values()):
123
+ self._expression(value)
124
+ self._take()
125
+ elif isinstance(node, (TypeDecl, EnumDecl)):
126
+ self._take()
127
+ else:
128
+ self._expression(node)
129
+
130
+ def _switch(self, node) -> None:
131
+ self._expression(node.expr)
132
+ for case, body in node.cases:
133
+ self._expression(case)
134
+ self._block(body)
135
+ if node.default is not None:
136
+ self._block(node.default)
137
+
138
+ def _expression(self, node) -> None:
139
+ if node is None or not isinstance(node, (ASTNode, list)):
140
+ return
141
+ if isinstance(node, list):
142
+ for item in node:
143
+ self._statement(item)
144
+ return
145
+ if isinstance(node, TupleDestructure):
146
+ return # the names only: its value is the assignment's
147
+ if isinstance(node, FunctionCall):
148
+ if not isinstance(node.func, str):
149
+ self._expression(node.func)
150
+ for value in list(node.args) + list(node.kwargs.values()):
151
+ self._expression(value)
152
+ if isinstance(node.func, str) and node.func in _CASTS:
153
+ return
154
+ if self._version <= 4 and isinstance(node.func, str):
155
+ self._v4_conversion(node)
156
+ self._take()
157
+ return
158
+ if isinstance(node, MethodCall):
159
+ self._expression(node.object)
160
+ for value in list(node.args) + list(node.kwargs.values()):
161
+ self._expression(value)
162
+ self._take()
163
+ return
164
+ if isinstance(node, IndexAccess):
165
+ self._expression(node.object)
166
+ self._expression(node.index)
167
+ self._take()
168
+ return
169
+ if isinstance(node, IfExpression):
170
+ self._expression(node.condition)
171
+ self._block(node.true_expr)
172
+ if node.false_expr is not None:
173
+ self._block(node.false_expr)
174
+ return
175
+ if isinstance(node, SwitchExpression):
176
+ self._switch(node)
177
+ return
178
+ if isinstance(node, (Assignment, ExpressionStatement)) or dataclasses.is_dataclass(node):
179
+ for field in dataclasses.fields(node):
180
+ if field.name in ('line', 'column'):
181
+ continue
182
+ self._child(getattr(node, field.name))
183
+
184
+ def _child(self, value) -> None:
185
+ if isinstance(value, ASTNode):
186
+ self._expression(value)
187
+ elif isinstance(value, (list, tuple)):
188
+ for item in value:
189
+ self._child(item)
190
+ elif isinstance(value, dict):
191
+ for item in value.values():
192
+ self._child(item)
193
+
194
+ # ------------------------------------------------------------ Pine v4 conversion
195
+
196
+ def _v4_conversion(self, call: FunctionCall) -> None:
197
+ """The calls Pine v4's conversion adds around ``call``."""
198
+ if call.func in _V4_COLOUR_KEYWORDS:
199
+ for _ in range(self._merged_colours(call)):
200
+ self._take() # color.new(c, transp)
201
+
202
+ def _merged_colours(self, call: FunctionCall) -> int:
203
+ function = call.func
204
+ transparency = call.kwargs.get('transp')
205
+ colour_index, transp_index = _V4_COLOUR_POSITIONS.get(function, (None, None))
206
+ if transp_index is not None and len(call.args) > transp_index:
207
+ transparency = call.args[transp_index]
208
+ colours = [call.kwargs[key] for key in _V4_COLOUR_KEYWORDS[function] if key in call.kwargs]
209
+ if colour_index is not None and len(call.args) > colour_index:
210
+ colours.append(call.args[colour_index])
211
+ merged = 0
212
+ for colour in colours:
213
+ if self._is_constant_colour(colour):
214
+ merged += 1
215
+ elif transparency is not None and function == 'fill' and not isinstance(colour, Literal):
216
+ merged += 1
217
+ return merged
218
+
219
+ def _is_constant_colour(self, value) -> bool:
220
+ if isinstance(value, Literal):
221
+ return value.literal_type == 'color'
222
+ if isinstance(value, Identifier):
223
+ return value.name in self._constant_colour_names or (
224
+ value.name.startswith('color.') and value.name[len('color.'):] in _NAMED_COLOURS)
225
+ return isinstance(value, MemberAccess) and value.object == 'color' and value.member in _NAMED_COLOURS
226
+
227
+ def _constant_colours(self, items) -> frozenset:
228
+ """Top-level names bound once, to a constant colour."""
229
+ bindings: Dict[str, list] = {}
230
+ for item in items:
231
+ if isinstance(item, Assignment) and isinstance(item.target, str):
232
+ bindings.setdefault(item.target, []).append(item.value)
233
+ self._constant_colour_names = frozenset()
234
+ return frozenset(name for name, values in bindings.items()
235
+ if len(values) == 1 and self._is_constant_colour(values[0]))
@@ -0,0 +1,75 @@
1
+ """
2
+ `once` lowering: Pine v6's `once` structure as a persistent flag plus an `if`.
3
+
4
+ once close > mySMA var bool once_fired1 = false
5
+ printLabel(...) -> if not once_fired1 and close > mySMA
6
+ once_fired1 := true
7
+ printLabel(...)
8
+
9
+ This is the equivalent code the Pine v6 User Manual gives for `once`
10
+ ("Conditional structures", section "`once` structure"): the block runs the
11
+ first time its condition is true and never again. The condition is optional
12
+ and `true` by default, so a bare `once` checks only its flag. `and`
13
+ short-circuits, so the condition is not evaluated once the structure fired
14
+ ("evaluated only once ... and then never again after that"). The flag is set
15
+ before the block, so a `break` or `continue` in it cannot skip that.
16
+
17
+ The flag is a `var`, so it has a `var`'s scope: one per function call site,
18
+ one for all iterations of a loop, initialised the first time its block runs.
19
+ The lowering runs at the end of parsing, so no later stage sees a `once`
20
+ (card #59).
21
+ """
22
+ import dataclasses
23
+
24
+ from .ast_nodes import (
25
+ BinaryOp, Identifier, IfStatement, Literal, OnceStatement, Reassignment,
26
+ Script, UnaryOp, VarDecl,
27
+ )
28
+ from .ast_walk import contains, is_node
29
+ from .scope_renamer import collect_names, fresh_name
30
+
31
+ FLAG_STEM = 'once_fired'
32
+
33
+
34
+ def lower_once_structures(script: Script) -> Script:
35
+ """Return a copy of `script` with every `once` structure lowered to a
36
+ `var` flag and an `if`. A script without one is returned unchanged."""
37
+ if not contains(script, lambda node: isinstance(node, OnceStatement)):
38
+ return script
39
+ return _OnceLowering(collect_names(script)).rebuild(script)
40
+
41
+
42
+ class _OnceLowering:
43
+ def __init__(self, used_names):
44
+ self.used_names = used_names
45
+
46
+ def rebuild(self, value):
47
+ """`value` with each statement list's `once` structures expanded in place."""
48
+ if isinstance(value, list):
49
+ lowered = []
50
+ for item in value:
51
+ rebuilt = self.rebuild(item)
52
+ lowered.extend(self.flag_and_if(rebuilt) if isinstance(rebuilt, OnceStatement) else [rebuilt])
53
+ return lowered
54
+ if isinstance(value, tuple):
55
+ return tuple(self.rebuild(item) for item in value)
56
+ if isinstance(value, dict):
57
+ return {key: self.rebuild(item) for key, item in value.items()}
58
+ if not is_node(value):
59
+ return value
60
+ children = {field.name: self.rebuild(getattr(value, field.name))
61
+ for field in dataclasses.fields(value) if field.name not in ('line', 'column')}
62
+ return dataclasses.replace(value, **children)
63
+
64
+ def flag_and_if(self, once: OnceStatement) -> list:
65
+ position = {'line': once.line, 'column': once.column}
66
+ flag = fresh_name(FLAG_STEM, self.used_names)
67
+ not_fired = UnaryOp(op='not', operand=Identifier(name=flag, **position), **position)
68
+ condition = not_fired if once.condition is None else BinaryOp(
69
+ left=not_fired, op='and', right=once.condition, **position)
70
+ fired = Reassignment(target=flag, value=Literal(value=True, literal_type='bool', **position), **position)
71
+ return [
72
+ VarDecl(name=flag, value=Literal(value=False, literal_type='bool', **position), type_hint='bool',
73
+ **position),
74
+ IfStatement(condition=condition, body=[fired] + once.body, **position),
75
+ ]