@pineforge/codegen-pyodide 1.0.1 → 1.2.0
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.
- package/LICENSE +245 -0
- package/README.md +4 -0
- package/glue.py +6 -1
- package/package.json +6 -4
- package/pineforge_codegen/__init__.py +25 -9
- package/pineforge_codegen/analyzer/base.py +1 -0
- package/pineforge_codegen/analyzer/call_handlers.py +2 -0
- package/pineforge_codegen/analyzer/contracts.py +3 -0
- package/pineforge_codegen/codegen/base.py +38 -1
- package/pineforge_codegen/codegen/capabilities.py +186 -0
- package/pineforge_codegen/codegen/checked_settings.py +285 -0
- package/pineforge_codegen/codegen/emit_top.py +46 -9
- package/pineforge_codegen/codegen/helpers.py +8 -1
- package/pineforge_codegen/codegen/host_members.py +8 -0
- package/pineforge_codegen/codegen/input.py +36 -3
- package/pineforge_codegen/diagnostic_codes.py +341 -0
- package/pineforge_codegen/diagnostics_catalog.json +631 -0
- package/pineforge_codegen/errors.py +23 -0
- package/pineforge_codegen/external_requests.py +19 -0
- package/pineforge_codegen/request_discovery.py +506 -0
- package/pineforge_codegen-1.2.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +1 -1
- package/transpile.worker.mjs +6 -1
- package/pineforge_codegen-1.0.1.tar.gz +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
PineForge Source License 1.1
|
|
2
|
+
============================
|
|
3
|
+
|
|
4
|
+
Copyright 2025-2026 pineforge, LLC
|
|
5
|
+
|
|
6
|
+
Required Notice: Copyright 2025-2026 pineforge, LLC (https://github.com/pineforge-4pass/pineforge-codegen-oss)
|
|
7
|
+
|
|
8
|
+
The software is free of charge for the permitted purposes below, including
|
|
9
|
+
Personal Trading. Commercial Use, defined below, needs a separate commercial
|
|
10
|
+
license from the licensor.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Acceptance
|
|
14
|
+
|
|
15
|
+
In order to get any license under these terms, you must agree to them as both
|
|
16
|
+
strict obligations and conditions to all your licenses.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Copyright License
|
|
20
|
+
|
|
21
|
+
The licensor grants you a copyright license for the software to do everything
|
|
22
|
+
you might do with the software that would otherwise infringe the licensor's
|
|
23
|
+
copyright in it for any permitted purpose. However, you may only distribute
|
|
24
|
+
the software according to the Distribution License section, and make changes
|
|
25
|
+
or new works based on the software according to the Changes and New Works
|
|
26
|
+
License section.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Distribution License
|
|
30
|
+
|
|
31
|
+
The licensor grants you an additional copyright license to distribute copies
|
|
32
|
+
of the software. Your license to distribute covers distributing the software
|
|
33
|
+
with changes and new works permitted by the Changes and New Works License
|
|
34
|
+
section. Unless it is a permitted purpose, distributing the software, changed
|
|
35
|
+
or not, embedded in or bundled with a product or service made available to
|
|
36
|
+
others is Commercial Use under (3) of the Commercial Use section, which your
|
|
37
|
+
license to distribute does not cover. Distributing copies under this section
|
|
38
|
+
is not Commercial Use, and it gives the people who get them no license beyond
|
|
39
|
+
these terms.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Notices
|
|
43
|
+
|
|
44
|
+
You must ensure that anyone who gets a copy of any part of the software from
|
|
45
|
+
you also gets a copy of these terms or a link to them, as well as copies of
|
|
46
|
+
any plain-text lines beginning with `Required Notice:` that the licensor
|
|
47
|
+
provided with the software. For example:
|
|
48
|
+
|
|
49
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Changes and New Works License
|
|
53
|
+
|
|
54
|
+
The licensor grants you an additional copyright license to make changes and
|
|
55
|
+
new works based on the software for any permitted purpose.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Patent License
|
|
59
|
+
|
|
60
|
+
The licensor grants you a patent license for the software that covers patent
|
|
61
|
+
claims the licensor can license, or becomes able to license, that you would
|
|
62
|
+
infringe by using the software for a permitted purpose.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Permitted Purposes
|
|
66
|
+
|
|
67
|
+
Each of the following is a permitted purpose:
|
|
68
|
+
|
|
69
|
+
- **Noncommercial purposes.** Any noncommercial purpose.
|
|
70
|
+
|
|
71
|
+
- **Personal uses.** Personal use for research, experiment, and testing for
|
|
72
|
+
the benefit of public knowledge, personal study, private entertainment,
|
|
73
|
+
hobby projects, amateur pursuits, or religious observance, without any
|
|
74
|
+
anticipated commercial application.
|
|
75
|
+
|
|
76
|
+
- **Noncommercial organizations.** Use by any charitable organization,
|
|
77
|
+
educational institution, public research organization, public safety or
|
|
78
|
+
health organization, environmental protection organization, or government
|
|
79
|
+
institution for its teaching, research and other operations, regardless of
|
|
80
|
+
the source of funding or obligations resulting from the funding.
|
|
81
|
+
|
|
82
|
+
- **Personal Trading**, as defined in the next section.
|
|
83
|
+
|
|
84
|
+
Investment Management, defined in the Commercial Use section, is never a
|
|
85
|
+
permitted purpose, except as Personal Trading. That is so even when it is done
|
|
86
|
+
by a noncommercial organization or for a noncommercial purpose.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Personal Trading
|
|
90
|
+
|
|
91
|
+
**Personal Trading** means use of the software by a natural person to do any
|
|
92
|
+
of the following, solely for trades that meet conditions (a) and (b) below:
|
|
93
|
+
research, develop or backtest trading strategies, or execute trades in
|
|
94
|
+
securities, derivatives, currencies, crypto-assets, commodities or other
|
|
95
|
+
financial instruments.
|
|
96
|
+
|
|
97
|
+
(a) The trades are, or will be, placed for that person's own account.
|
|
98
|
+
|
|
99
|
+
(b) The trades are, or will be, funded solely by that person's own
|
|
100
|
+
capital.
|
|
101
|
+
|
|
102
|
+
For Personal Trading:
|
|
103
|
+
|
|
104
|
+
- A person's **household** is that person, their spouse or domestic
|
|
105
|
+
partner, and their dependants.
|
|
106
|
+
|
|
107
|
+
- A person's **own account** includes an account held by the person alone
|
|
108
|
+
or jointly with members of their household; an account of a member of
|
|
109
|
+
their household; and a retirement, pension, education-savings or other
|
|
110
|
+
tax-advantaged account, or an account of a revocable trust, held for the
|
|
111
|
+
benefit of the person or members of their household.
|
|
112
|
+
|
|
113
|
+
- A person's **own capital** includes capital belonging to members of
|
|
114
|
+
their household, and capital borrowed in the ordinary course from a
|
|
115
|
+
broker, bank or other lender, such as margin, leverage or credit on a
|
|
116
|
+
trading account, for which the person or their household is liable.
|
|
117
|
+
Money the person has received outright, such as pay, a gift or an
|
|
118
|
+
inheritance, is the person's own capital.
|
|
119
|
+
|
|
120
|
+
- Trading in the person's own name, for their own account and with their
|
|
121
|
+
own capital, is Personal Trading even when it is the person's business
|
|
122
|
+
or livelihood.
|
|
123
|
+
|
|
124
|
+
- Publishing or teaching about the person's own Personal Trading,
|
|
125
|
+
including its results, is part of Personal Trading even when the person
|
|
126
|
+
is paid for it, as long as they do not give Output to others.
|
|
127
|
+
|
|
128
|
+
- An account of a company, partnership, fund, trust or other
|
|
129
|
+
organization, other than an account described under own account above,
|
|
130
|
+
is not the person's own account, even if the person wholly owns or
|
|
131
|
+
controls it.
|
|
132
|
+
|
|
133
|
+
- Capital that an employer, a proprietary-trading firm, a funded-trader
|
|
134
|
+
program or anyone else outside the person's household provides or
|
|
135
|
+
allocates to the person to trade, and that the provider still owns or
|
|
136
|
+
whose losses the provider bears, is not the person's own capital, even
|
|
137
|
+
if the person shares in the profits.
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
## Commercial Use
|
|
141
|
+
|
|
142
|
+
**Investment Management** means using the software to manage, advise on, or
|
|
143
|
+
trade investment capital; to research, develop or backtest strategies for
|
|
144
|
+
investment capital that you, or anyone you do the work for, manage, advise on
|
|
145
|
+
or trade; or to operate the software for others who do any of these things;
|
|
146
|
+
in each case whoever the capital belongs to and whether or not for a fee.
|
|
147
|
+
|
|
148
|
+
**Investment capital** means any money or other assets held for investment or
|
|
149
|
+
trading, including capital belonging to any other person or entity, such as a
|
|
150
|
+
friend or relative, clients or investors, an endowment, a pension or
|
|
151
|
+
retirement fund, a sovereign or other public fund, or the treasury or reserves
|
|
152
|
+
of a foundation or other organization.
|
|
153
|
+
|
|
154
|
+
**Commercial Use** means any use of the software that is not a permitted
|
|
155
|
+
purpose, other than distributing copies under the Distribution License
|
|
156
|
+
section. Investment Management is Commercial Use for every individual and
|
|
157
|
+
every organization, including the noncommercial organizations listed under
|
|
158
|
+
Permitted Purposes, whatever its purpose, unless it is Personal Trading.
|
|
159
|
+
Unless it is a permitted purpose, each of the following is also Commercial
|
|
160
|
+
Use:
|
|
161
|
+
|
|
162
|
+
(1) managing, advising on, or trading capital belonging to any other person
|
|
163
|
+
or entity, whether or not for a fee;
|
|
164
|
+
|
|
165
|
+
(2) use by, for, or on behalf of any company, fund, partnership, or other
|
|
166
|
+
organization, including use by an individual in the course of work for
|
|
167
|
+
such an organization;
|
|
168
|
+
|
|
169
|
+
(3) embedding the software or its Output, changed or not, into any product
|
|
170
|
+
or service made available to others, or distributing either bundled
|
|
171
|
+
with such a product or service, including using the software to
|
|
172
|
+
generate Output for such a product or service; and
|
|
173
|
+
|
|
174
|
+
(4) operating any hosted, software-as-a-service, or otherwise public-facing
|
|
175
|
+
service through which others can run the software or receive its
|
|
176
|
+
Output.
|
|
177
|
+
|
|
178
|
+
Commercial Use requires a separate commercial license from the licensor. To
|
|
179
|
+
obtain one, contact: enterprise@pineforge.dev
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
## Output
|
|
183
|
+
|
|
184
|
+
**Output** means code that the software generates, such as the C++ source
|
|
185
|
+
code it generates from PineScript, and any program or library built from that
|
|
186
|
+
code. Backtest results, charts, reports and trade signals are not Output.
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
## Fair Use
|
|
190
|
+
|
|
191
|
+
You may have "fair use" rights for the software under the law. These terms do
|
|
192
|
+
not limit them.
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
## No Other Rights
|
|
196
|
+
|
|
197
|
+
These terms do not allow you to sublicense or transfer any of your licenses
|
|
198
|
+
to anyone else, or prevent the licensor from granting licenses to anyone
|
|
199
|
+
else. These terms do not imply any other licenses.
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
## Patent Defense
|
|
203
|
+
|
|
204
|
+
If you make any written claim that the software infringes or contributes to
|
|
205
|
+
infringement of any patent, your patent license for the software granted
|
|
206
|
+
under these terms ends immediately. If your company makes such a claim, your
|
|
207
|
+
patent license ends immediately for work on behalf of your company.
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
## Violations
|
|
211
|
+
|
|
212
|
+
The first time you are notified in writing that you have violated any of
|
|
213
|
+
these terms, or done anything with the software not covered by your licenses,
|
|
214
|
+
your licenses can nonetheless continue if you come into full compliance with
|
|
215
|
+
these terms, and take practical steps to correct past violations, within 32
|
|
216
|
+
days of receiving notice. Otherwise, all your licenses end immediately.
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
## No Liability
|
|
220
|
+
|
|
221
|
+
***As far as the law allows, the software comes as is, without any warranty
|
|
222
|
+
or condition, and the licensor will not be liable to you for any damages
|
|
223
|
+
arising out of these terms or the use or nature of the software, under any
|
|
224
|
+
kind of legal claim.***
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
## Definitions
|
|
228
|
+
|
|
229
|
+
The **licensor** is pineforge, LLC, and the **software** is the software the
|
|
230
|
+
licensor makes available under these terms.
|
|
231
|
+
|
|
232
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
233
|
+
|
|
234
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of
|
|
235
|
+
organization that you work for, plus all organizations that have control
|
|
236
|
+
over, are under the control of, or are under common control with that
|
|
237
|
+
organization. **Control** means ownership of substantially all the assets of
|
|
238
|
+
an entity, or the power to direct its management and policies by vote,
|
|
239
|
+
contract, or otherwise. Control can be direct or indirect.
|
|
240
|
+
|
|
241
|
+
**Your licenses** are all the licenses granted to you for the software under
|
|
242
|
+
these terms.
|
|
243
|
+
|
|
244
|
+
**Use** means anything you do with the software requiring one of your
|
|
245
|
+
licenses.
|
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@ Stable releases are on the `latest` dist-tag. A prerelease (for example
|
|
|
16
16
|
- `transpile.worker.mjs` — an ES module worker that runs the glue in Pyodide.
|
|
17
17
|
- `index.mjs` — exports `release`, `tables`, `archivePath`, `sourceRoot`, `codegenSourceDir`,
|
|
18
18
|
`workerPath` and `glue`.
|
|
19
|
+
- `pineforge_codegen/diagnostics_catalog.json` — the diagnostics catalog (since 1.2.0), also
|
|
20
|
+
importable as `@pineforge/codegen-pyodide/diagnostics_catalog.json`; the repository's
|
|
21
|
+
`docs/PUBLIC_CONTRACT.md` describes it.
|
|
22
|
+
- `LICENSE` — the PineForge Source License 1.1 (since 1.2.0), which `package.json` names.
|
|
19
23
|
|
|
20
24
|
## Publishing (maintainers)
|
|
21
25
|
A `v*` tag push, which the release workflow makes, publishes through npm OIDC Trusted
|
package/glue.py
CHANGED
|
@@ -23,6 +23,10 @@ def _diagnostic_entries(diagnostics) -> list:
|
|
|
23
23
|
"col": loc.col if loc else 1,
|
|
24
24
|
"message": message,
|
|
25
25
|
"severity": getattr(d.level, "value", "error"),
|
|
26
|
+
# The stable code and named arguments of the catalog templates the
|
|
27
|
+
# message (and the hint after " — ") render from.
|
|
28
|
+
"code": d.code,
|
|
29
|
+
"args": d.args,
|
|
26
30
|
}
|
|
27
31
|
end_col = getattr(loc, "end_col", None) if loc else None
|
|
28
32
|
if end_col is not None:
|
|
@@ -40,4 +44,5 @@ def transpile_json(source: str) -> str:
|
|
|
40
44
|
# A script that transpiled carries its warnings in the same entry format.
|
|
41
45
|
return json.dumps({"ok": True, "cpp": full["cpp"], "inputs": full["inputs"],
|
|
42
46
|
"strategyParams": full["strategyParams"],
|
|
43
|
-
"diagnostics": _diagnostic_entries(full["diagnostics"])
|
|
47
|
+
"diagnostics": _diagnostic_entries(full["diagnostics"]),
|
|
48
|
+
"requests": full["requests"]})
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pineforge/codegen-pyodide",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Gate-validated Pyodide payload for the PineScript v6 -> C++ transpiler: archive (run in Pyodide), unpacked source, introspected tables, and release metadata.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.mjs",
|
|
9
9
|
"./tables.json": "./tables.json",
|
|
10
|
-
"./release.json": "./release.json"
|
|
10
|
+
"./release.json": "./release.json",
|
|
11
|
+
"./diagnostics_catalog.json": "./pineforge_codegen/diagnostics_catalog.json"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"index.mjs",
|
|
@@ -16,7 +17,8 @@
|
|
|
16
17
|
"tables.json",
|
|
17
18
|
"release.json",
|
|
18
19
|
"transpile.worker.mjs",
|
|
19
|
-
"glue.py"
|
|
20
|
+
"glue.py",
|
|
21
|
+
"LICENSE"
|
|
20
22
|
],
|
|
21
23
|
"engines": {
|
|
22
24
|
"node": ">=20"
|
|
@@ -35,5 +37,5 @@
|
|
|
35
37
|
"type": "git",
|
|
36
38
|
"url": "git+https://github.com/pineforge-4pass/pineforge-codegen-oss.git"
|
|
37
39
|
},
|
|
38
|
-
"license": "
|
|
40
|
+
"license": "SEE LICENSE IN LICENSE"
|
|
39
41
|
}
|
|
@@ -6,6 +6,7 @@ from .lexer import Lexer
|
|
|
6
6
|
from .parser import Parser
|
|
7
7
|
from .analyzer import Analyzer
|
|
8
8
|
from .codegen import CodeGen
|
|
9
|
+
from .diagnostic_codes import diagnostics_catalog, render_diagnostic
|
|
9
10
|
from .errors import CompileError, Level, Phase
|
|
10
11
|
from .external_requests import lower_no_data_requests
|
|
11
12
|
from .builtin_keywords import bind_builtin_keywords
|
|
@@ -13,6 +14,7 @@ from .finite_ta_length import expand_finite_choice_extrema_lengths
|
|
|
13
14
|
from .library_inline import inline_libraries
|
|
14
15
|
from .limits import TimeBudget, check_ast_depth, check_source_size, ensure_recursion_headroom
|
|
15
16
|
from .pragmas import extract_pf_trace_pragmas
|
|
17
|
+
from .request_discovery import discover_requests, request_sites
|
|
16
18
|
from .security_contexts import specialize_security_contexts
|
|
17
19
|
from .block_locals import rename_block_locals
|
|
18
20
|
from .support_checker import check_support as _support_diagnostics
|
|
@@ -52,7 +54,9 @@ def _generate(pine_source: str, check_support: bool, filename: str,
|
|
|
52
54
|
the member of its name cannot hold gets a name of its own, and the
|
|
53
55
|
pipeline runs again (``block_locals``).
|
|
54
56
|
|
|
55
|
-
Returns ``(codegen, ctx, cpp, support_diagnostics)
|
|
57
|
+
Returns ``(codegen, ctx, cpp, support_diagnostics, sites)``: ``sites``
|
|
58
|
+
are the requests of another symbol's data the support checker lowered
|
|
59
|
+
(``request_discovery.request_sites``)."""
|
|
56
60
|
budget = None
|
|
57
61
|
clones: frozenset[str] = frozenset()
|
|
58
62
|
renamed: frozenset = frozenset()
|
|
@@ -67,6 +71,7 @@ def _generate(pine_source: str, check_support: bool, filename: str,
|
|
|
67
71
|
if any(d.level == Level.ERROR for d in support_diagnostics):
|
|
68
72
|
raise CompileError(support_diagnostics)
|
|
69
73
|
budget.check(phase=Phase.ANALYZER)
|
|
74
|
+
sites = request_sites(ast)
|
|
70
75
|
ast = lower_no_data_requests(ast)
|
|
71
76
|
ast = specialize_security_contexts(ast, filename=filename)
|
|
72
77
|
ast = bind_builtin_keywords(expand_finite_choice_extrema_lengths(ast))
|
|
@@ -86,18 +91,18 @@ def _generate(pine_source: str, check_support: bool, filename: str,
|
|
|
86
91
|
apart = gen.block_locals_needing_names - renamed
|
|
87
92
|
if apart:
|
|
88
93
|
renamed |= apart
|
|
89
|
-
del ast, ctx, gen, cpp
|
|
94
|
+
del ast, ctx, gen, cpp, sites
|
|
90
95
|
continue
|
|
91
96
|
needed = gen.session_functions_needing_clones
|
|
92
97
|
if not needed:
|
|
93
|
-
return gen, ctx, cpp, support_diagnostics
|
|
98
|
+
return gen, ctx, cpp, support_diagnostics, sites
|
|
94
99
|
# Only uncloned functions ask, so the set grows each time and the
|
|
95
100
|
# loop ends by the time every function that reads a flag at an
|
|
96
101
|
# offset is cloned.
|
|
97
102
|
if needed <= clones:
|
|
98
103
|
raise AssertionError("a cloned function asked for session clones again")
|
|
99
104
|
clones |= needed
|
|
100
|
-
del ast, ctx, gen, cpp
|
|
105
|
+
del ast, ctx, gen, cpp, sites
|
|
101
106
|
|
|
102
107
|
|
|
103
108
|
def transpile(pine_source: str, *, check_support: bool = True, filename: str = "<input>",
|
|
@@ -140,7 +145,8 @@ def transpile(pine_source: str, *, check_support: bool = True, filename: str = "
|
|
|
140
145
|
Returns:
|
|
141
146
|
Generated C++ source string.
|
|
142
147
|
"""
|
|
143
|
-
_gen, _ctx, cpp, _support = _generate(pine_source, check_support, filename,
|
|
148
|
+
_gen, _ctx, cpp, _support, _sites = _generate(pine_source, check_support, filename,
|
|
149
|
+
libraries)
|
|
144
150
|
return cpp
|
|
145
151
|
|
|
146
152
|
|
|
@@ -161,26 +167,36 @@ def transpile_full(pine_source: str, *, check_support: bool = True,
|
|
|
161
167
|
source order). Each has ``title`` / ``type`` /
|
|
162
168
|
``default`` and optionally ``min`` / ``max`` / ``step`` / ``options``
|
|
163
169
|
(omitted when the corresponding signature argument is absent or
|
|
164
|
-
references a non-const value).
|
|
165
|
-
:meth:`CodeGen.extract_input_manifest`.
|
|
170
|
+
references a non-const value); an ``input.symbol`` entry also has
|
|
171
|
+
``kind: "symbol"``. See :meth:`CodeGen.extract_input_manifest`.
|
|
166
172
|
- ``strategyParams``: the literal ``strategy(...)`` kwargs the analyzer
|
|
167
173
|
surfaced (e.g. ``initial_capital``, ``pyramiding``).
|
|
168
174
|
- ``diagnostics``: the warnings (:class:`~pineforge_codegen.errors.Diagnostic`,
|
|
169
175
|
``Level.WARNING``) the support checker and the analyzer raised for a
|
|
170
176
|
script that transpiled -- e.g. an approximated ``ta.vwap`` anchor. An
|
|
171
177
|
error still raises ``CompileError``, which carries the warnings too.
|
|
178
|
+
- ``requests``: every request site that reads another symbol's feed,
|
|
179
|
+
by line: its symbol and timeframe as registration computes them
|
|
180
|
+
before the first bar (``literal`` / ``input`` / ``computed`` /
|
|
181
|
+
``unresolvable``; ``literal`` / ``chart`` / ``input`` / ``computed``),
|
|
182
|
+
``lookahead``, ``gaps`` and ``ignore_invalid_symbol``. A site lowered
|
|
183
|
+
to ``na`` (its value reaches display sinks only) or in a helper
|
|
184
|
+
nothing reaches is not listed. See
|
|
185
|
+
:mod:`pineforge_codegen.request_discovery`.
|
|
172
186
|
|
|
173
187
|
Args mirror :func:`transpile`.
|
|
174
188
|
|
|
175
189
|
Returns:
|
|
176
190
|
``{"cpp": str, "inputs": list[dict], "strategyParams": dict,
|
|
177
|
-
"diagnostics": list[Diagnostic]}``.
|
|
191
|
+
"diagnostics": list[Diagnostic], "requests": list[dict]}``.
|
|
178
192
|
"""
|
|
179
|
-
gen, ctx, cpp, support_diagnostics = _generate(
|
|
193
|
+
gen, ctx, cpp, support_diagnostics, sites = _generate(
|
|
194
|
+
pine_source, check_support, filename, libraries)
|
|
180
195
|
return {
|
|
181
196
|
"cpp": cpp,
|
|
182
197
|
"inputs": gen.extract_input_manifest(),
|
|
183
198
|
"strategyParams": dict(ctx.strategy_params),
|
|
184
199
|
"diagnostics": [d for d in (*support_diagnostics, *ctx.diagnostics)
|
|
185
200
|
if d.level == Level.WARNING],
|
|
201
|
+
"requests": discover_requests(gen, ctx, sites),
|
|
186
202
|
}
|
|
@@ -876,6 +876,7 @@ class CallHandlers:
|
|
|
876
876
|
dead=bool(notes.get(DEAD_ANNOTATION)),
|
|
877
877
|
foreign=notes.get(LOWERING_ANNOTATION) == FEED_LOWERING,
|
|
878
878
|
ignore_invalid=node.kwargs.get("ignore_invalid_symbol"),
|
|
879
|
+
loc=node.loc,
|
|
879
880
|
))
|
|
880
881
|
|
|
881
882
|
return PineType.STRING if string_result else PineType.FLOAT
|
|
@@ -998,6 +999,7 @@ class CallHandlers:
|
|
|
998
999
|
is_lower_tf_array=True,
|
|
999
1000
|
context_resolved=context is not None,
|
|
1000
1001
|
dead=bool(notes.get(DEAD_ANNOTATION)),
|
|
1002
|
+
loc=node.loc,
|
|
1001
1003
|
))
|
|
1002
1004
|
|
|
1003
1005
|
# ``request.security_lower_tf`` returns an array; the value-level
|
|
@@ -196,6 +196,9 @@ class SecurityCallInfo:
|
|
|
196
196
|
# ``symbol`` string and ``ignore_invalid_symbol`` (the node, or None).
|
|
197
197
|
foreign: bool = False
|
|
198
198
|
ignore_invalid: Any = None
|
|
199
|
+
# The request call's source location (``request_discovery`` names the
|
|
200
|
+
# site by its line).
|
|
201
|
+
loc: Any = None
|
|
199
202
|
|
|
200
203
|
|
|
201
204
|
@dataclass
|
|
@@ -4414,7 +4414,44 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4414
4414
|
return None
|
|
4415
4415
|
|
|
4416
4416
|
def generate(self) -> str:
|
|
4417
|
-
"""Generate C++ source from the AnalyzerContext.
|
|
4417
|
+
"""Generate C++ source from the AnalyzerContext.
|
|
4418
|
+
|
|
4419
|
+
An error the settings metadata finds in an input's arguments (emitted
|
|
4420
|
+
in the constructor, ahead of the body: ``checked_settings``) is held
|
|
4421
|
+
until the rest is generated; the error raised is the first in source
|
|
4422
|
+
order, as before that metadata visited the arguments."""
|
|
4423
|
+
self._deferred_settings_errors: list[CompileError] | None = []
|
|
4424
|
+
try:
|
|
4425
|
+
try:
|
|
4426
|
+
cpp = self._generate_cpp()
|
|
4427
|
+
except CompileError as error:
|
|
4428
|
+
first = self._first_settings_error_before(error)
|
|
4429
|
+
if first is None:
|
|
4430
|
+
raise
|
|
4431
|
+
raise first from None
|
|
4432
|
+
if self._deferred_settings_errors:
|
|
4433
|
+
raise self._deferred_settings_errors[0]
|
|
4434
|
+
return cpp
|
|
4435
|
+
finally:
|
|
4436
|
+
self._deferred_settings_errors = None
|
|
4437
|
+
|
|
4438
|
+
def _defer_settings_error(self, error: CompileError) -> None:
|
|
4439
|
+
held = getattr(self, "_deferred_settings_errors", None)
|
|
4440
|
+
if held is None: # outside generate(): nothing holds it
|
|
4441
|
+
raise error
|
|
4442
|
+
held.append(error)
|
|
4443
|
+
|
|
4444
|
+
def _first_settings_error_before(self, error: CompileError) -> CompileError | None:
|
|
4445
|
+
"""A held settings error located before ``error``, else None."""
|
|
4446
|
+
def where(err: CompileError):
|
|
4447
|
+
loc = err.diagnostics[0].location if err.diagnostics else None
|
|
4448
|
+
return (loc.line, loc.col) if loc is not None else (float("inf"), 0)
|
|
4449
|
+
held = sorted(self._deferred_settings_errors, key=where)
|
|
4450
|
+
if held and where(held[0]) < where(error):
|
|
4451
|
+
return held[0]
|
|
4452
|
+
return None
|
|
4453
|
+
|
|
4454
|
+
def _generate_cpp(self) -> str:
|
|
4418
4455
|
# Every input is keyed by its title: refuse a non-constant one first,
|
|
4419
4456
|
# then flag inputs one override key would reach together.
|
|
4420
4457
|
self._check_input_titles()
|