@pineforge/codegen-pyodide 0.10.3 → 1.0.0-rc.1

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 (51) hide show
  1. package/README.md +16 -16
  2. package/glue.py +24 -16
  3. package/package.json +1 -1
  4. package/pineforge_codegen/__init__.py +125 -34
  5. package/pineforge_codegen/analyzer/__init__.py +2 -0
  6. package/pineforge_codegen/analyzer/base.py +767 -77
  7. package/pineforge_codegen/analyzer/call_handlers.py +268 -42
  8. package/pineforge_codegen/analyzer/contracts.py +37 -0
  9. package/pineforge_codegen/analyzer/diagnostics.py +30 -4
  10. package/pineforge_codegen/analyzer/tables.py +49 -8
  11. package/pineforge_codegen/analyzer/types.py +33 -1
  12. package/pineforge_codegen/ast_nodes.py +32 -1
  13. package/pineforge_codegen/block_locals.py +185 -0
  14. package/pineforge_codegen/builtin_keywords.py +42 -0
  15. package/pineforge_codegen/codegen/base.py +907 -159
  16. package/pineforge_codegen/codegen/constant_fold.py +131 -0
  17. package/pineforge_codegen/codegen/drawing.py +221 -79
  18. package/pineforge_codegen/codegen/emit_top.py +961 -213
  19. package/pineforge_codegen/codegen/helpers.py +435 -14
  20. package/pineforge_codegen/codegen/host_members.py +162 -0
  21. package/pineforge_codegen/codegen/input.py +252 -85
  22. package/pineforge_codegen/codegen/security.py +4372 -377
  23. package/pineforge_codegen/codegen/session_market.py +71 -0
  24. package/pineforge_codegen/codegen/ta.py +1198 -101
  25. package/pineforge_codegen/codegen/tables.py +205 -71
  26. package/pineforge_codegen/codegen/tv_number_format.py +270 -0
  27. package/pineforge_codegen/codegen/types.py +2055 -62
  28. package/pineforge_codegen/codegen/visit_call.py +929 -131
  29. package/pineforge_codegen/codegen/visit_expr.py +765 -63
  30. package/pineforge_codegen/codegen/visit_stmt.py +610 -52
  31. package/pineforge_codegen/external_requests.py +877 -0
  32. package/pineforge_codegen/lexer.py +104 -22
  33. package/pineforge_codegen/library_inline.py +1304 -0
  34. package/pineforge_codegen/library_modules.py +126 -0
  35. package/pineforge_codegen/library_v5.py +683 -0
  36. package/pineforge_codegen/limits.py +138 -0
  37. package/pineforge_codegen/method_binding.py +33 -0
  38. package/pineforge_codegen/parser.py +384 -68
  39. package/pineforge_codegen/pine_libraries.py +266 -0
  40. package/pineforge_codegen/pine_spelling.py +216 -0
  41. package/pineforge_codegen/pragmas.py +64 -10
  42. package/pineforge_codegen/security_contexts.py +1585 -0
  43. package/pineforge_codegen/session_reads.py +84 -0
  44. package/pineforge_codegen/signatures.py +48 -23
  45. package/pineforge_codegen/support_checker.py +1106 -85
  46. package/pineforge_codegen/symbols.py +4 -2
  47. package/pineforge_codegen-1.0.0-rc.1.tar.gz +0 -0
  48. package/release.json +2 -2
  49. package/tables.json +23 -21
  50. package/transpile.worker.mjs +24 -16
  51. package/pineforge_codegen-0.10.3.tar.gz +0 -0
@@ -0,0 +1,270 @@
1
+ """C++ number formatting emitted for Pine string calls.
2
+
3
+ The engine's str_tostring(..., "mintick") already performs tick rounding and
4
+ keeps trailing zeros, so the generated helper delegates that mode. Its default
5
+ str_tostring uses std::to_string, its percent multiplies by 100, and its
6
+ volume keeps two decimals; the TradingView tapes refute all three. The engine
7
+ str_format only replaces plain {i} and cannot parse MessageFormat styles.
8
+ These gaps are handled here in one TU-local formatter shared by str.tostring,
9
+ str.format and log.*. No runtime ABI or Python dependency is added.
10
+ """
11
+
12
+ TV_NUMBER_FORMAT_CPP = r"""
13
+ struct _PFTvFormatValue {
14
+ enum class Kind { Number, Text } kind;
15
+ double number = 0.0;
16
+ std::string text;
17
+
18
+ template <class T, std::enable_if_t<std::is_arithmetic_v<T> &&
19
+ !std::is_same_v<T, bool>, int> = 0>
20
+ _PFTvFormatValue(T value)
21
+ : kind(Kind::Number),
22
+ number(is_na(value) ? na<double>() : static_cast<double>(value)) {}
23
+ _PFTvFormatValue(bool value)
24
+ : kind(Kind::Text), text(value ? "true" : "false") {}
25
+ _PFTvFormatValue(const std::string& value)
26
+ : kind(Kind::Text), text(value) {}
27
+ _PFTvFormatValue(const char* value)
28
+ : kind(Kind::Text), text(value) {}
29
+ };
30
+
31
+ static std::string _pf_tv_trim(const std::string& value) {
32
+ const size_t first = value.find_first_not_of(" \t");
33
+ if (first == std::string::npos) return "";
34
+ const size_t last = value.find_last_not_of(" \t");
35
+ return value.substr(first, last - first + 1);
36
+ }
37
+
38
+ static void _pf_tv_increment_digits(std::string& digits) {
39
+ for (size_t i = digits.size(); i > 0; --i) {
40
+ if (digits[i - 1] != '9') {
41
+ ++digits[i - 1];
42
+ return;
43
+ }
44
+ digits[i - 1] = '0';
45
+ }
46
+ digits.insert(digits.begin(), '1');
47
+ }
48
+
49
+ static std::string _pf_tv_decimal(double value, int min_fraction,
50
+ int max_fraction, bool grouping,
51
+ int decimal_shift = 0) {
52
+ if (std::isnan(value)) return "NaN";
53
+ if (std::isinf(value)) return value < 0 ? "-Infinity" : "Infinity";
54
+ max_fraction = std::max(0, std::min(max_fraction, 15));
55
+ min_fraction = std::max(0, std::min(min_fraction, max_fraction));
56
+
57
+ // C++17's no-precision to_chars gives the shortest round-trip decimal
58
+ // spelling of this binary64 value. All subsequent scaling and rounding
59
+ // operate on its digits, never on a floating-point intermediate.
60
+ char buffer[128];
61
+ const auto converted = std::to_chars(buffer, buffer + sizeof buffer, value);
62
+ if (converted.ec != std::errc{})
63
+ throw std::runtime_error("shortest-decimal conversion failed");
64
+ std::string spelling(buffer, converted.ptr);
65
+ const bool negative = !spelling.empty() && spelling[0] == '-';
66
+ if (negative) spelling.erase(0, 1);
67
+
68
+ const size_t exponent_pos = spelling.find_first_of("eE");
69
+ const std::string mantissa = spelling.substr(0, exponent_pos);
70
+ int exponent = 0;
71
+ if (exponent_pos != std::string::npos) {
72
+ size_t i = exponent_pos + 1;
73
+ bool exponent_negative = false;
74
+ if (i < spelling.size() && (spelling[i] == '+' || spelling[i] == '-')) {
75
+ exponent_negative = spelling[i] == '-';
76
+ ++i;
77
+ }
78
+ for (; i < spelling.size(); ++i)
79
+ exponent = exponent * 10 + (spelling[i] - '0');
80
+ if (exponent_negative) exponent = -exponent;
81
+ }
82
+
83
+ std::string digits;
84
+ int decimal_point = 0;
85
+ bool after_point = false;
86
+ for (char ch : mantissa) {
87
+ if (ch == '.') {
88
+ after_point = true;
89
+ } else {
90
+ digits += ch;
91
+ if (!after_point) ++decimal_point;
92
+ }
93
+ }
94
+ decimal_point += exponent + decimal_shift;
95
+ const size_t leading_zeroes = digits.find_first_not_of('0');
96
+ if (leading_zeroes == std::string::npos) {
97
+ digits = "0";
98
+ decimal_point = 1;
99
+ } else {
100
+ digits.erase(0, leading_zeroes);
101
+ decimal_point -= static_cast<int>(leading_zeroes);
102
+ }
103
+
104
+ // The retained digits form an integer in units of 10^-max_fraction.
105
+ // The first discarded decimal digit decides a half-up tie exactly.
106
+ std::string units;
107
+ if (digits == "0") {
108
+ units = "0";
109
+ } else {
110
+ const int keep = decimal_point + max_fraction;
111
+ if (keep < 0) {
112
+ units = "0";
113
+ } else if (keep == 0) {
114
+ units = digits[0] >= '5' ? "1" : "0";
115
+ } else if (keep >= static_cast<int>(digits.size())) {
116
+ units = digits;
117
+ units.append(static_cast<size_t>(keep) - digits.size(), '0');
118
+ } else {
119
+ units = digits.substr(0, static_cast<size_t>(keep));
120
+ if (digits[static_cast<size_t>(keep)] >= '5')
121
+ _pf_tv_increment_digits(units);
122
+ }
123
+ }
124
+ const bool nonzero = units.find_first_not_of('0') != std::string::npos;
125
+ if (!nonzero) units = "0";
126
+ if (units.size() <= static_cast<size_t>(max_fraction))
127
+ units.insert(0, static_cast<size_t>(max_fraction) + 1 - units.size(), '0');
128
+ const size_t integer_size = units.size() - static_cast<size_t>(max_fraction);
129
+ std::string integer = units.substr(0, integer_size);
130
+ std::string fraction = units.substr(integer_size);
131
+ while (fraction.size() > static_cast<size_t>(min_fraction)
132
+ && fraction.back() == '0') fraction.pop_back();
133
+ if (grouping) {
134
+ std::string grouped;
135
+ for (size_t i = 0; i < integer_size; ++i) {
136
+ if (i && (integer_size - i) % 3 == 0) grouped += ',';
137
+ grouped += integer[i];
138
+ }
139
+ integer = grouped;
140
+ }
141
+ return (negative && nonzero ? "-" : "") + integer
142
+ + (fraction.empty() ? "" : "." + fraction);
143
+ }
144
+
145
+ static std::string _pf_tv_pattern(double value, const std::string& pattern) {
146
+ if (!std::isfinite(value)) return _pf_tv_decimal(value, 0, 0, false);
147
+ const std::string fmt = _pf_tv_trim(pattern);
148
+ const size_t dot = fmt.find('.');
149
+ const std::string integer = fmt.substr(0, dot);
150
+ const std::string fraction = dot == std::string::npos ? "" : fmt.substr(dot + 1);
151
+ const int max_fraction = static_cast<int>(std::count_if(
152
+ fraction.begin(), fraction.end(), [](char c) { return c == '0' || c == '#'; }));
153
+ const int min_fraction = static_cast<int>(std::count(fraction.begin(), fraction.end(), '0'));
154
+ const bool grouping = integer.find(',') != std::string::npos;
155
+ const bool percent = fmt.find('%') != std::string::npos;
156
+ return _pf_tv_decimal(value, min_fraction, max_fraction, grouping,
157
+ percent ? 2 : 0)
158
+ + (percent ? "%" : "");
159
+ }
160
+
161
+ static std::string pine_str_tostring_tv(double value,
162
+ const std::string& format_mode = "",
163
+ double mintick = 0.0) {
164
+ if (std::isnan(value)) return "NaN";
165
+ if (std::isinf(value)) return value < 0 ? "-Infinity" : "Infinity";
166
+ if (format_mode == "mintick")
167
+ return pine_str_tostring(value, format_mode, mintick);
168
+ if (format_mode == "percent")
169
+ return _pf_tv_pattern(value, "#.##") + "%";
170
+ if (format_mode == "volume") {
171
+ const double magnitude = std::fabs(value);
172
+ int decimal_shift = 0;
173
+ const char* unit = "";
174
+ if (magnitude >= 1.0e12) { decimal_shift = -12; unit = "T"; }
175
+ else if (magnitude >= 1.0e9) { decimal_shift = -9; unit = "B"; }
176
+ else if (magnitude >= 1.0e6) { decimal_shift = -6; unit = "M"; }
177
+ else if (magnitude >= 1.0e3) { decimal_shift = -3; unit = "K"; }
178
+ return _pf_tv_decimal(value, 0, decimal_shift ? 2 : 0, false,
179
+ decimal_shift) + unit;
180
+ }
181
+ return _pf_tv_pattern(value, format_mode.empty() ? "#.##########" : format_mode);
182
+ }
183
+
184
+ template <class T, std::enable_if_t<std::is_integral_v<T> &&
185
+ !std::is_same_v<T, bool>, int> = 0>
186
+ static std::string pine_str_tostring_tv(T value,
187
+ const std::string& format_mode = "",
188
+ double mintick = 0.0) {
189
+ return pine_str_tostring_tv(
190
+ is_na(value) ? na<double>() : static_cast<double>(value),
191
+ format_mode, mintick);
192
+ }
193
+
194
+ static std::string _pf_tv_number_style(double value, const std::string& style) {
195
+ if (!std::isfinite(value)) return _pf_tv_decimal(value, 0, 0, false);
196
+ const std::string fmt = _pf_tv_trim(style);
197
+ if (fmt.empty()) return _pf_tv_pattern(value, "#,###.###");
198
+ if (fmt == "integer") return _pf_tv_pattern(value, "#,###");
199
+ if (fmt == "percent") return _pf_tv_decimal(value, 0, 0, true, 2) + "%";
200
+ if (fmt == "currency") {
201
+ const std::string digits = _pf_tv_decimal(value, 2, 2, true);
202
+ return digits[0] == '-' ? "-$" + digits.substr(1) : "$" + digits;
203
+ }
204
+ return _pf_tv_pattern(value, fmt);
205
+ }
206
+
207
+ static std::string pine_str_format_tv(
208
+ const std::string& format_string,
209
+ const std::vector<_PFTvFormatValue>& args) {
210
+ std::string result;
211
+ bool quoted = false;
212
+ for (size_t i = 0; i < format_string.size();) {
213
+ const char c = format_string[i];
214
+ if (c == '\'') {
215
+ if (i + 1 < format_string.size() && format_string[i + 1] == '\'') {
216
+ result += '\'';
217
+ i += 2;
218
+ } else {
219
+ quoted = !quoted;
220
+ ++i;
221
+ }
222
+ continue;
223
+ }
224
+ if (c != '{' || quoted) {
225
+ result += c;
226
+ ++i;
227
+ continue;
228
+ }
229
+ const size_t end = format_string.find('}', i + 1);
230
+ if (end == std::string::npos) {
231
+ result += format_string.substr(i);
232
+ break;
233
+ }
234
+ const std::string inside = format_string.substr(i + 1, end - i - 1);
235
+ const size_t first_comma = inside.find(',');
236
+ const std::string index_text = _pf_tv_trim(inside.substr(0, first_comma));
237
+ if (index_text.empty() || !std::all_of(index_text.begin(), index_text.end(),
238
+ [](char ch) { return ch >= '0' && ch <= '9'; })) {
239
+ result += format_string.substr(i, end - i + 1);
240
+ i = end + 1;
241
+ continue;
242
+ }
243
+ const size_t index = static_cast<size_t>(std::stoul(index_text));
244
+ if (index >= args.size()) {
245
+ result += format_string.substr(i, end - i + 1);
246
+ i = end + 1;
247
+ continue;
248
+ }
249
+ const auto& arg = args[index];
250
+ if (arg.kind == _PFTvFormatValue::Kind::Text) {
251
+ result += arg.text;
252
+ } else if (first_comma == std::string::npos) {
253
+ result += _pf_tv_number_style(arg.number, "");
254
+ } else {
255
+ const size_t second_comma = inside.find(',', first_comma + 1);
256
+ const std::string type = _pf_tv_trim(inside.substr(
257
+ first_comma + 1, second_comma - first_comma - 1));
258
+ if (type == "number") {
259
+ const std::string style = second_comma == std::string::npos
260
+ ? "" : inside.substr(second_comma + 1);
261
+ result += _pf_tv_number_style(arg.number, style);
262
+ } else {
263
+ result += format_string.substr(i, end - i + 1);
264
+ }
265
+ }
266
+ i = end + 1;
267
+ }
268
+ return result;
269
+ }
270
+ """