@pineforge/codegen-pyodide 0.10.4 → 1.0.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/README.md +16 -16
- package/glue.py +24 -16
- package/package.json +1 -1
- package/pineforge_codegen/__init__.py +125 -34
- package/pineforge_codegen/analyzer/__init__.py +2 -0
- package/pineforge_codegen/analyzer/base.py +754 -76
- package/pineforge_codegen/analyzer/call_handlers.py +260 -40
- package/pineforge_codegen/analyzer/contracts.py +37 -0
- package/pineforge_codegen/analyzer/diagnostics.py +30 -4
- package/pineforge_codegen/analyzer/tables.py +49 -8
- package/pineforge_codegen/analyzer/types.py +33 -1
- package/pineforge_codegen/ast_nodes.py +32 -1
- package/pineforge_codegen/block_locals.py +185 -0
- package/pineforge_codegen/builtin_keywords.py +42 -0
- package/pineforge_codegen/codegen/base.py +896 -156
- package/pineforge_codegen/codegen/constant_fold.py +131 -0
- package/pineforge_codegen/codegen/drawing.py +221 -79
- package/pineforge_codegen/codegen/emit_top.py +946 -213
- package/pineforge_codegen/codegen/helpers.py +435 -14
- package/pineforge_codegen/codegen/host_members.py +162 -0
- package/pineforge_codegen/codegen/input.py +252 -85
- package/pineforge_codegen/codegen/security.py +4372 -377
- package/pineforge_codegen/codegen/session_market.py +71 -0
- package/pineforge_codegen/codegen/ta.py +1188 -100
- package/pineforge_codegen/codegen/tables.py +193 -71
- package/pineforge_codegen/codegen/tv_number_format.py +270 -0
- package/pineforge_codegen/codegen/types.py +1882 -78
- package/pineforge_codegen/codegen/visit_call.py +920 -131
- package/pineforge_codegen/codegen/visit_expr.py +738 -57
- package/pineforge_codegen/codegen/visit_stmt.py +595 -49
- package/pineforge_codegen/external_requests.py +877 -0
- package/pineforge_codegen/lexer.py +104 -22
- package/pineforge_codegen/library_inline.py +1304 -0
- package/pineforge_codegen/library_modules.py +126 -0
- package/pineforge_codegen/library_v5.py +683 -0
- package/pineforge_codegen/limits.py +138 -0
- package/pineforge_codegen/method_binding.py +33 -0
- package/pineforge_codegen/parser.py +384 -68
- package/pineforge_codegen/pine_libraries.py +266 -0
- package/pineforge_codegen/pine_spelling.py +216 -0
- package/pineforge_codegen/pragmas.py +64 -10
- package/pineforge_codegen/security_contexts.py +1585 -0
- package/pineforge_codegen/session_reads.py +84 -0
- package/pineforge_codegen/signatures.py +48 -23
- package/pineforge_codegen/support_checker.py +1106 -85
- package/pineforge_codegen-1.0.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +23 -21
- package/transpile.worker.mjs +24 -16
- package/pineforge_codegen-0.10.4.tar.gz +0 -0
package/README.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# @pineforge/codegen-pyodide
|
|
2
2
|
|
|
3
3
|
Gate-validated Pyodide payload for the PineScript v6 → C++ transpiler. Built and
|
|
4
|
-
published from `pineforge-codegen-oss` by `.github/workflows/publish-pyodide.yml
|
|
4
|
+
published from `pineforge-codegen-oss` by `.github/workflows/publish-pyodide.yml`,
|
|
5
|
+
with the same version as the matching `pineforge-codegen` release on PyPI.
|
|
6
|
+
Stable releases are on the `latest` dist-tag. A prerelease (for example
|
|
7
|
+
`1.0.0-rc.1`) is published on `next` only.
|
|
5
8
|
|
|
6
9
|
## Contents
|
|
7
10
|
- `pineforge_codegen-<version>.tar.gz` — the gate-validated archive (unpack into Pyodide).
|
|
8
11
|
- `pineforge_codegen/` — unpacked Python source (put on `PYTHONPATH` for Node oracle/grammar tooling).
|
|
9
|
-
- `tables.json` — introspected codegen tables (
|
|
12
|
+
- `tables.json` — introspected codegen tables (PineForge's web app renders its tables from this).
|
|
10
13
|
- `release.json` — `{ codegen, pyodide, python, emscripten, sha256 }`.
|
|
11
|
-
- `
|
|
14
|
+
- `glue.py` — the `transpile_json(source)` glue from the repository's `gate/glue.py`; its JSON
|
|
15
|
+
envelopes are described in the repository's `docs/PUBLIC_CONTRACT.md`.
|
|
16
|
+
- `transpile.worker.mjs` — an ES module worker that runs the glue in Pyodide.
|
|
17
|
+
- `index.mjs` — exports `release`, `tables`, `archivePath`, `sourceRoot`, `codegenSourceDir`,
|
|
18
|
+
`workerPath` and `glue`.
|
|
12
19
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
the
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
3. On npmjs.com, configure the package's Trusted Publisher: GitHub Actions,
|
|
20
|
-
repo `pineforge-4pass/pineforge-codegen-oss`, workflow `publish-pyodide.yml`.
|
|
21
|
-
4. Thereafter releases publish via OIDC (no token): run `publish-pyodide.yml`
|
|
22
|
-
via **workflow_dispatch with `dry_run=false`**. NOTE: tag pushes currently run
|
|
23
|
-
a DRY-RUN only (a safe validation) — the sole real-publish path is dispatch +
|
|
24
|
-
`dry_run=false`. To switch to tag-driven releases later, change the publish
|
|
25
|
-
condition in the workflow.
|
|
20
|
+
## Publishing (maintainers)
|
|
21
|
+
A `v*` tag push, which the release workflow makes, publishes through npm OIDC Trusted
|
|
22
|
+
Publishing after the dependency audit and the full parity gate pass; published versions
|
|
23
|
+
carry npm provenance. A manual `workflow_dispatch` defaults to a dry run; `dry_run=false`
|
|
24
|
+
publishes for real. The one-time bootstrap is done: 0.7.0 was the manual first publish,
|
|
25
|
+
and every version since 0.7.1 carries provenance from this workflow.
|
package/glue.py
CHANGED
|
@@ -13,23 +13,31 @@ from pineforge_codegen import transpile_full
|
|
|
13
13
|
from pineforge_codegen.errors import CompileError
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def _diagnostic_entries(diagnostics) -> list:
|
|
17
|
+
diags = []
|
|
18
|
+
for d in diagnostics:
|
|
19
|
+
loc = d.location
|
|
20
|
+
message = d.message + " — " + d.hint if getattr(d, "hint", None) else d.message
|
|
21
|
+
entry = {
|
|
22
|
+
"line": loc.line if loc else 1,
|
|
23
|
+
"col": loc.col if loc else 1,
|
|
24
|
+
"message": message,
|
|
25
|
+
"severity": getattr(d.level, "value", "error"),
|
|
26
|
+
}
|
|
27
|
+
end_col = getattr(loc, "end_col", None) if loc else None
|
|
28
|
+
if end_col is not None:
|
|
29
|
+
entry["endCol"] = end_col
|
|
30
|
+
diags.append(entry)
|
|
31
|
+
return diags
|
|
32
|
+
|
|
33
|
+
|
|
16
34
|
def transpile_json(source: str) -> str:
|
|
17
35
|
try:
|
|
18
36
|
full = transpile_full(source)
|
|
19
37
|
except CompileError as e:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"col": loc.col if loc else 1,
|
|
27
|
-
"message": message,
|
|
28
|
-
"severity": getattr(d.level, "value", "error"),
|
|
29
|
-
}
|
|
30
|
-
end_col = getattr(loc, "end_col", None) if loc else None
|
|
31
|
-
if end_col is not None:
|
|
32
|
-
entry["endCol"] = end_col
|
|
33
|
-
diags.append(entry)
|
|
34
|
-
return json.dumps({"ok": False, "error": str(e), "diagnostics": diags})
|
|
35
|
-
return json.dumps({"ok": True, "cpp": full["cpp"], "inputs": full["inputs"], "strategyParams": full["strategyParams"]})
|
|
38
|
+
return json.dumps({"ok": False, "error": str(e),
|
|
39
|
+
"diagnostics": _diagnostic_entries(e.diagnostics)})
|
|
40
|
+
# A script that transpiled carries its warnings in the same entry format.
|
|
41
|
+
return json.dumps({"ok": True, "cpp": full["cpp"], "inputs": full["inputs"],
|
|
42
|
+
"strategyParams": full["strategyParams"],
|
|
43
|
+
"diagnostics": _diagnostic_entries(full["diagnostics"])})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pineforge/codegen-pyodide",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.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",
|
|
@@ -1,15 +1,107 @@
|
|
|
1
1
|
"""PineScript v6 to C++ transpiler."""
|
|
2
2
|
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
|
|
3
5
|
from .lexer import Lexer
|
|
4
6
|
from .parser import Parser
|
|
5
7
|
from .analyzer import Analyzer
|
|
6
8
|
from .codegen import CodeGen
|
|
9
|
+
from .errors import CompileError, Level, Phase
|
|
10
|
+
from .external_requests import lower_no_data_requests
|
|
11
|
+
from .builtin_keywords import bind_builtin_keywords
|
|
7
12
|
from .finite_ta_length import expand_finite_choice_extrema_lengths
|
|
13
|
+
from .library_inline import inline_libraries
|
|
14
|
+
from .limits import TimeBudget, check_ast_depth, check_source_size, ensure_recursion_headroom
|
|
8
15
|
from .pragmas import extract_pf_trace_pragmas
|
|
16
|
+
from .security_contexts import specialize_security_contexts
|
|
17
|
+
from .block_locals import rename_block_locals
|
|
18
|
+
from .support_checker import check_support as _support_diagnostics
|
|
9
19
|
from .support_checker import check_support_or_raise
|
|
10
20
|
|
|
11
21
|
|
|
12
|
-
def
|
|
22
|
+
def _parse_bounded(pine_source: str, filename: str, budget: TimeBudget | None = None):
|
|
23
|
+
ensure_recursion_headroom()
|
|
24
|
+
check_source_size(pine_source, filename)
|
|
25
|
+
budget = budget or TimeBudget(filename)
|
|
26
|
+
pragmas = extract_pf_trace_pragmas(
|
|
27
|
+
pine_source, filename=filename, budget=budget,
|
|
28
|
+
)
|
|
29
|
+
budget.check(phase=Phase.LEXER)
|
|
30
|
+
tokens = Lexer(pine_source, filename=filename, budget=budget).tokenize()
|
|
31
|
+
budget.check(phase=Phase.LEXER)
|
|
32
|
+
ast = Parser(tokens, source=pine_source, filename=filename,
|
|
33
|
+
budget=budget).parse()
|
|
34
|
+
check_ast_depth(ast, filename)
|
|
35
|
+
budget.check(phase=Phase.PARSER)
|
|
36
|
+
return ast, pragmas, budget
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _generate(pine_source: str, check_support: bool, filename: str,
|
|
40
|
+
libraries: Mapping[str, str] | None = None):
|
|
41
|
+
"""The pipeline, first without the per-call clones of functions that read
|
|
42
|
+
a ``session.<flag>[k]``, as scripts compiled before such reads were
|
|
43
|
+
supported; then again with the functions whose reads that C++ holds
|
|
44
|
+
cloned as well, until the C++ asks for no more (a clone can make a
|
|
45
|
+
caller's read reach the C++: its callers are cloned per call site, which
|
|
46
|
+
can retype their parameters). A read in an argument the codegen leaves
|
|
47
|
+
out never costs a clone (the clones of a function called along a deep
|
|
48
|
+
call tree grow with every path to it).
|
|
49
|
+
|
|
50
|
+
Each pass inlines the libraries the script imports before the support
|
|
51
|
+
check (``library_inline``). A declaration in a top-level block whose type
|
|
52
|
+
the member of its name cannot hold gets a name of its own, and the
|
|
53
|
+
pipeline runs again (``block_locals``).
|
|
54
|
+
|
|
55
|
+
Returns ``(codegen, ctx, cpp, support_diagnostics)``."""
|
|
56
|
+
budget = None
|
|
57
|
+
clones: frozenset[str] = frozenset()
|
|
58
|
+
renamed: frozenset = frozenset()
|
|
59
|
+
while True:
|
|
60
|
+
ast, pragmas, budget = _parse_bounded(pine_source, filename, budget)
|
|
61
|
+
ast = inline_libraries(ast, pine_source, libraries=libraries,
|
|
62
|
+
filename=filename, budget=budget)
|
|
63
|
+
rename_block_locals(ast, renamed)
|
|
64
|
+
support_diagnostics = []
|
|
65
|
+
if check_support:
|
|
66
|
+
support_diagnostics = _support_diagnostics(ast, filename=filename)
|
|
67
|
+
if any(d.level == Level.ERROR for d in support_diagnostics):
|
|
68
|
+
raise CompileError(support_diagnostics)
|
|
69
|
+
budget.check(phase=Phase.ANALYZER)
|
|
70
|
+
ast = lower_no_data_requests(ast)
|
|
71
|
+
ast = specialize_security_contexts(ast, filename=filename)
|
|
72
|
+
ast = bind_builtin_keywords(expand_finite_choice_extrema_lengths(ast))
|
|
73
|
+
check_ast_depth(ast, filename)
|
|
74
|
+
budget.check(phase=Phase.ANALYZER)
|
|
75
|
+
ctx = Analyzer(ast, filename=filename, budget=budget,
|
|
76
|
+
session_clones=clones).analyze()
|
|
77
|
+
budget.check(phase=Phase.ANALYZER)
|
|
78
|
+
# Attach after analysis: pragma expressions are not part of the
|
|
79
|
+
# program body, so the analyzer never inspects them; the codegen
|
|
80
|
+
# consumes them directly from the context to emit the on_bar tail
|
|
81
|
+
# ``if (trace_enabled_) { trace(...); ... }`` block.
|
|
82
|
+
ctx.pf_trace_pragmas = pragmas
|
|
83
|
+
gen = CodeGen(ctx, budget=budget)
|
|
84
|
+
cpp = gen.generate()
|
|
85
|
+
budget.check(phase=Phase.CODEGEN)
|
|
86
|
+
apart = gen.block_locals_needing_names - renamed
|
|
87
|
+
if apart:
|
|
88
|
+
renamed |= apart
|
|
89
|
+
del ast, ctx, gen, cpp
|
|
90
|
+
continue
|
|
91
|
+
needed = gen.session_functions_needing_clones
|
|
92
|
+
if not needed:
|
|
93
|
+
return gen, ctx, cpp, support_diagnostics
|
|
94
|
+
# Only uncloned functions ask, so the set grows each time and the
|
|
95
|
+
# loop ends by the time every function that reads a flag at an
|
|
96
|
+
# offset is cloned.
|
|
97
|
+
if needed <= clones:
|
|
98
|
+
raise AssertionError("a cloned function asked for session clones again")
|
|
99
|
+
clones |= needed
|
|
100
|
+
del ast, ctx, gen, cpp
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def transpile(pine_source: str, *, check_support: bool = True, filename: str = "<input>",
|
|
104
|
+
libraries: Mapping[str, str] | None = None) -> str:
|
|
13
105
|
"""Transpile PineScript v6 source code to C++ code.
|
|
14
106
|
|
|
15
107
|
Semantic rules enforced in :class:`Analyzer` (before codegen), including:
|
|
@@ -17,10 +109,10 @@ def transpile(pine_source: str, *, check_support: bool = True, filename: str = "
|
|
|
17
109
|
|
|
18
110
|
Before analysis, :func:`support_checker.check_support_or_raise` rejects
|
|
19
111
|
scripts that use language constructs PineForge cannot faithfully execute
|
|
20
|
-
(e.g. ``indicator()`` declarations,
|
|
21
|
-
``
|
|
22
|
-
``check_support=False`` to bypass this gate
|
|
23
|
-
fixtures only).
|
|
112
|
+
(e.g. ``indicator()`` declarations, ``request.seed``, disallowed
|
|
113
|
+
``request.security`` parameters) and warns about approximated ones (e.g.
|
|
114
|
+
``bar_index``). Pass ``check_support=False`` to bypass this gate
|
|
115
|
+
(experimental; intended for tests of legacy fixtures only).
|
|
24
116
|
|
|
25
117
|
``// @pf-trace name=expr`` pragmas are extracted from ``pine_source``
|
|
26
118
|
via a pre-pass (the lexer strips comments before the parser sees them)
|
|
@@ -36,60 +128,59 @@ def transpile(pine_source: str, *, check_support: bool = True, filename: str = "
|
|
|
36
128
|
``file:line:col`` shown in ``CompileError`` (both ``str()`` and
|
|
37
129
|
:meth:`~pineforge_codegen.errors.CompileError.format`) points back
|
|
38
130
|
at the caller's file. Defaults to ``"<input>"``.
|
|
131
|
+
libraries: The sources of the Pine libraries the script imports,
|
|
132
|
+
by import path (``{"user/name/version": source_text}``); each
|
|
133
|
+
import the script uses is inlined from its source (see
|
|
134
|
+
:mod:`pineforge_codegen.library_inline`). ``None`` (default)
|
|
135
|
+
reads them from ``$PINEFORGE_PINE_LIBRARIES`` through the
|
|
136
|
+
script's own requests manifest under ``$PINEFORGE_REQUESTS_ROOT``
|
|
137
|
+
(see :mod:`pineforge_codegen.pine_libraries`); with neither, an
|
|
138
|
+
import is refused as before.
|
|
39
139
|
|
|
40
140
|
Returns:
|
|
41
141
|
Generated C++ source string.
|
|
42
142
|
"""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
ast = Parser(tokens, source=pine_source, filename=filename).parse()
|
|
46
|
-
if check_support:
|
|
47
|
-
check_support_or_raise(ast, filename=filename)
|
|
48
|
-
ast = expand_finite_choice_extrema_lengths(ast)
|
|
49
|
-
ctx = Analyzer(ast, filename=filename).analyze()
|
|
50
|
-
# Attach after analysis: pragma expressions are not part of the
|
|
51
|
-
# program body, so the analyzer never inspects them; the codegen
|
|
52
|
-
# consumes them directly from the context to emit the on_bar tail
|
|
53
|
-
# ``if (trace_enabled_) { trace(...); ... }`` block.
|
|
54
|
-
ctx.pf_trace_pragmas = pragmas
|
|
55
|
-
return CodeGen(ctx).generate()
|
|
143
|
+
_gen, _ctx, cpp, _support = _generate(pine_source, check_support, filename, libraries)
|
|
144
|
+
return cpp
|
|
56
145
|
|
|
57
146
|
|
|
58
147
|
def transpile_full(pine_source: str, *, check_support: bool = True,
|
|
59
|
-
filename: str = "<input>"
|
|
148
|
+
filename: str = "<input>",
|
|
149
|
+
libraries: Mapping[str, str] | None = None) -> dict:
|
|
60
150
|
"""Transpile like :func:`transpile`, plus the host-UI input manifest.
|
|
61
151
|
|
|
62
|
-
Runs
|
|
63
|
-
CodeGen.generate)
|
|
152
|
+
Runs the pipeline (Lexer -> Parser -> support check -> Analyzer ->
|
|
153
|
+
CodeGen.generate) once, again while the C++ holds a
|
|
154
|
+
``session.<flag>[k]`` read of a function not yet cloned for it (see
|
|
155
|
+
``_generate``), and returns the generated C++ alongside the data the
|
|
64
156
|
cloud Studio needs to auto-build a backtest "override params" form:
|
|
65
157
|
|
|
66
158
|
- ``cpp``: the generated C++ source (identical to :func:`transpile`).
|
|
67
|
-
- ``inputs``: a list of ``InputDef`` dicts (one per
|
|
68
|
-
``
|
|
159
|
+
- ``inputs``: a list of ``InputDef`` dicts (one per global-scope
|
|
160
|
+
``input(...)`` / ``input.*(...)`` call, inline calls included, in
|
|
161
|
+
source order). Each has ``title`` / ``type`` /
|
|
69
162
|
``default`` and optionally ``min`` / ``max`` / ``step`` / ``options``
|
|
70
163
|
(omitted when the corresponding signature argument is absent or
|
|
71
164
|
references a non-const value). See
|
|
72
165
|
:meth:`CodeGen.extract_input_manifest`.
|
|
73
166
|
- ``strategyParams``: the literal ``strategy(...)`` kwargs the analyzer
|
|
74
167
|
surfaced (e.g. ``initial_capital``, ``pyramiding``).
|
|
168
|
+
- ``diagnostics``: the warnings (:class:`~pineforge_codegen.errors.Diagnostic`,
|
|
169
|
+
``Level.WARNING``) the support checker and the analyzer raised for a
|
|
170
|
+
script that transpiled -- e.g. an approximated ``ta.vwap`` anchor. An
|
|
171
|
+
error still raises ``CompileError``, which carries the warnings too.
|
|
75
172
|
|
|
76
173
|
Args mirror :func:`transpile`.
|
|
77
174
|
|
|
78
175
|
Returns:
|
|
79
|
-
``{"cpp": str, "inputs": list[dict], "strategyParams": dict
|
|
176
|
+
``{"cpp": str, "inputs": list[dict], "strategyParams": dict,
|
|
177
|
+
"diagnostics": list[Diagnostic]}``.
|
|
80
178
|
"""
|
|
81
|
-
|
|
82
|
-
tokens = Lexer(pine_source, filename=filename).tokenize()
|
|
83
|
-
ast = Parser(tokens, source=pine_source, filename=filename).parse()
|
|
84
|
-
if check_support:
|
|
85
|
-
check_support_or_raise(ast, filename=filename)
|
|
86
|
-
ast = expand_finite_choice_extrema_lengths(ast)
|
|
87
|
-
ctx = Analyzer(ast, filename=filename).analyze()
|
|
88
|
-
ctx.pf_trace_pragmas = pragmas
|
|
89
|
-
gen = CodeGen(ctx)
|
|
90
|
-
cpp = gen.generate()
|
|
179
|
+
gen, ctx, cpp, support_diagnostics = _generate(pine_source, check_support, filename, libraries)
|
|
91
180
|
return {
|
|
92
181
|
"cpp": cpp,
|
|
93
182
|
"inputs": gen.extract_input_manifest(),
|
|
94
183
|
"strategyParams": dict(ctx.strategy_params),
|
|
184
|
+
"diagnostics": [d for d in (*support_diagnostics, *ctx.diagnostics)
|
|
185
|
+
if d.level == Level.WARNING],
|
|
95
186
|
}
|
|
@@ -36,6 +36,7 @@ from .tables import (
|
|
|
36
36
|
TA_TUPLE_RETURNS,
|
|
37
37
|
TA_MULTI_CTOR,
|
|
38
38
|
TA_NO_CTOR,
|
|
39
|
+
TA_LENGTH_ONLY_DEFAULT_SOURCE,
|
|
39
40
|
BUILTIN_VARS,
|
|
40
41
|
BAR_FIELDS,
|
|
41
42
|
SKIP_FUNCS,
|
|
@@ -54,6 +55,7 @@ __all__ = [
|
|
|
54
55
|
"TA_TUPLE_RETURNS",
|
|
55
56
|
"TA_MULTI_CTOR",
|
|
56
57
|
"TA_NO_CTOR",
|
|
58
|
+
"TA_LENGTH_ONLY_DEFAULT_SOURCE",
|
|
57
59
|
"BUILTIN_VARS",
|
|
58
60
|
"BAR_FIELDS",
|
|
59
61
|
"SKIP_FUNCS",
|