@pineforge/codegen-pyodide 1.3.0 → 1.4.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/package.json +1 -1
- package/pineforge_codegen/__init__.py +6 -5
- package/pineforge_codegen/analyzer/base.py +3 -3
- package/pineforge_codegen/analyzer/types.py +7 -3
- package/pineforge_codegen/codegen/base.py +26 -16
- package/pineforge_codegen/codegen/checked_settings.py +113 -2
- package/pineforge_codegen/codegen/collection_history.py +163 -12
- package/pineforge_codegen/codegen/emit_top.py +24 -8
- package/pineforge_codegen/codegen/helpers.py +19 -1
- package/pineforge_codegen/codegen/host_members.py +1 -0
- package/pineforge_codegen/codegen/input.py +23 -8
- package/pineforge_codegen/codegen/run_stops.py +52 -0
- package/pineforge_codegen/codegen/security.py +12 -5
- package/pineforge_codegen/codegen/tables.py +132 -28
- package/pineforge_codegen/codegen/tv_number_format.py +9 -2
- package/pineforge_codegen/codegen/types.py +85 -4
- package/pineforge_codegen/codegen/visit_call.py +174 -55
- package/pineforge_codegen/codegen/visit_expr.py +32 -6
- package/pineforge_codegen/codegen/visit_stmt.py +3 -0
- package/pineforge_codegen/collection_history.py +122 -20
- package/pineforge_codegen/diagnostics_catalog.json +1 -0
- package/pineforge_codegen/external_requests.py +30 -9
- package/pineforge_codegen/matrix_overloads.py +7 -0
- package/pineforge_codegen/parser.py +7 -2
- package/pineforge_codegen/signatures.py +2 -2
- package/pineforge_codegen/support_checker.py +82 -0
- package/pineforge_codegen-1.4.0.tar.gz +0 -0
- package/release.json +2 -2
- package/tables.json +7 -3
- package/pineforge_codegen-1.3.0.tar.gz +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pineforge/codegen-pyodide",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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",
|
|
@@ -164,11 +164,12 @@ def transpile_full(pine_source: str, *, check_support: bool = True,
|
|
|
164
164
|
- ``cpp``: the generated C++ source (identical to :func:`transpile`).
|
|
165
165
|
- ``inputs``: a list of ``InputDef`` dicts (one per global-scope
|
|
166
166
|
``input(...)`` / ``input.*(...)`` call, inline calls included, in
|
|
167
|
-
source order). Each has ``title`` / ``type`` /
|
|
168
|
-
``
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
``kind: "symbol"``.
|
|
167
|
+
source order). Each has ``title`` / ``type`` / ``default`` /
|
|
168
|
+
``supported`` (the checked-settings receipt's flag) and optionally
|
|
169
|
+
``min`` / ``max`` / ``step`` / ``options`` (omitted when the
|
|
170
|
+
corresponding signature argument is absent or is not a literal the
|
|
171
|
+
receipt holds); an ``input.symbol`` entry also has ``kind: "symbol"``.
|
|
172
|
+
See :meth:`CodeGen.extract_input_manifest`.
|
|
172
173
|
- ``strategyParams``: the literal ``strategy(...)`` kwargs the analyzer
|
|
173
174
|
surfaced (e.g. ``initial_capital``, ``pyramiding``).
|
|
174
175
|
- ``diagnostics``: the warnings (:class:`~pineforge_codegen.errors.Diagnostic`,
|
|
@@ -1758,7 +1758,7 @@ class Analyzer(CallHandlers, DiagnosticsHelper, TypeHelper):
|
|
|
1758
1758
|
for collection_owner, collection_names in persistent_collections.items():
|
|
1759
1759
|
if series_owner == collection_owner:
|
|
1760
1760
|
continue
|
|
1761
|
-
for exact_name in exact_series_names & collection_names:
|
|
1761
|
+
for exact_name in sorted(exact_series_names & collection_names):
|
|
1762
1762
|
raw_name = next(
|
|
1763
1763
|
(
|
|
1764
1764
|
name for name in names
|
|
@@ -4999,7 +4999,7 @@ class Analyzer(CallHandlers, DiagnosticsHelper, TypeHelper):
|
|
|
4999
4999
|
# ``exit_scope()`` can bind a same-named top-level collection
|
|
5000
5000
|
# and falsely turn a scalar UDF return into an array.
|
|
5001
5001
|
ret_spec = terminal_direct_return_spec
|
|
5002
|
-
if ret_spec is not None and ret_spec.kind
|
|
5002
|
+
if ret_spec is not None and ret_spec.kind in ("array", "matrix"):
|
|
5003
5003
|
self._func_return_type_specs[node.name] = ret_spec
|
|
5004
5004
|
if (terminal_direct_return_spec is not None
|
|
5005
5005
|
and terminal_direct_return_spec.kind == "map"):
|
|
@@ -5150,7 +5150,7 @@ class Analyzer(CallHandlers, DiagnosticsHelper, TypeHelper):
|
|
|
5150
5150
|
ret_type = self._statement_value_type(node.body[-1], ret_type)
|
|
5151
5151
|
if terminal_ret_expr is not None:
|
|
5152
5152
|
terminal_spec = self._type_spec_from_expr(terminal_ret_expr)
|
|
5153
|
-
if terminal_spec is not None and terminal_spec.kind
|
|
5153
|
+
if terminal_spec is not None and terminal_spec.kind in ("map", "matrix"):
|
|
5154
5154
|
return_type_spec = terminal_spec
|
|
5155
5155
|
if terminal_spec is not None and terminal_spec.kind == "udt":
|
|
5156
5156
|
# Methods may return ``self`` or another UDT-typed
|
|
@@ -45,6 +45,7 @@ from ..ast_nodes import (
|
|
|
45
45
|
SwitchStmt, TupleLiteral, UnaryOp,
|
|
46
46
|
)
|
|
47
47
|
from ..errors import Phase
|
|
48
|
+
from ..matrix_overloads import matrix_sum_has_rhs
|
|
48
49
|
from ..symbols import PineType, TypeSpec, method_receiver_type_name
|
|
49
50
|
|
|
50
51
|
# Drawing-objects-as-data type names (spec §4.1). Defined locally — the
|
|
@@ -537,8 +538,10 @@ class TypeHelper:
|
|
|
537
538
|
else:
|
|
538
539
|
elem = TypeSpec.primitive("float")
|
|
539
540
|
return TypeSpec.matrix(elem)
|
|
540
|
-
if ns == "matrix" and func in _MATRIX_RETURNING_METHODS
|
|
541
|
-
|
|
541
|
+
if ns == "matrix" and (func in _MATRIX_RETURNING_METHODS or (
|
|
542
|
+
func == "sum" and matrix_sum_has_rhs(value, namespace=True))):
|
|
543
|
+
receiver = (value.args[0] if value.args else
|
|
544
|
+
value.kwargs.get("id1", value.kwargs.get("id")))
|
|
542
545
|
receiver_spec = self._type_spec_from_expr(receiver)
|
|
543
546
|
if receiver_spec is not None and receiver_spec.kind == "matrix":
|
|
544
547
|
return receiver_spec
|
|
@@ -607,7 +610,8 @@ class TypeHelper:
|
|
|
607
610
|
if func == "size":
|
|
608
611
|
return TypeSpec.primitive("int")
|
|
609
612
|
if recv_spec is not None and recv_spec.kind == "matrix":
|
|
610
|
-
if func in _MATRIX_RETURNING_METHODS
|
|
613
|
+
if func in _MATRIX_RETURNING_METHODS or (
|
|
614
|
+
func == "sum" and matrix_sum_has_rhs(value)):
|
|
611
615
|
return recv_spec
|
|
612
616
|
if func in ("row", "col"):
|
|
613
617
|
return TypeSpec.array(recv_spec.element)
|
|
@@ -31,6 +31,7 @@ from ..symbols import PineType, TypeSpec, method_receiver_type_name
|
|
|
31
31
|
from .. import signatures as sigs
|
|
32
32
|
from ..errors import CompileError, Diagnostic, Level, Phase, SourceLocation
|
|
33
33
|
from ..limits import TimeBudget, iter_ast_nodes
|
|
34
|
+
from ..matrix_overloads import matrix_sum_has_rhs
|
|
34
35
|
from ..session_reads import emitted_session_reads
|
|
35
36
|
from ..block_locals import block_declarations, decl_key
|
|
36
37
|
from ..pine_spelling import (
|
|
@@ -1082,7 +1083,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
1082
1083
|
# (index, signature) of each TA variant computed inline, and
|
|
1083
1084
|
# (index, _SECURITY_THROUGH_GLOBAL) of a global's site a
|
|
1084
1085
|
# multi-statement helper reads, which the prologue computes.
|
|
1085
|
-
"inline_helper_ta_indices":
|
|
1086
|
+
"inline_helper_ta_indices": inline_helper_ta_indices,
|
|
1086
1087
|
"depends_on_mutable_globals": item.get("depends_on_mutable_globals", False),
|
|
1087
1088
|
"mutable_globals": list(item.get("mutable_globals", [])),
|
|
1088
1089
|
"is_lower_tf_array": bool(item.get("is_lower_tf_array", False)),
|
|
@@ -2156,6 +2157,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2156
2157
|
# to the analyzer's default ``double`` and the emitted C++
|
|
2157
2158
|
# fails to compile (``double = PineMatrix``).
|
|
2158
2159
|
or fn in MATRIX_RETURNING_METHODS
|
|
2160
|
+
or (fn == "sum" and matrix_sum_has_rhs(expr, namespace=True))
|
|
2159
2161
|
):
|
|
2160
2162
|
if fn == "new":
|
|
2161
2163
|
targs = self._template_args_from_call(expr) if hasattr(expr, "annotations") else []
|
|
@@ -2213,7 +2215,8 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
2213
2215
|
outer_callee = expr.callee
|
|
2214
2216
|
if (
|
|
2215
2217
|
isinstance(outer_callee, MemberAccess)
|
|
2216
|
-
and outer_callee.member in MATRIX_RETURNING_METHODS
|
|
2218
|
+
and (outer_callee.member in MATRIX_RETURNING_METHODS or (
|
|
2219
|
+
outer_callee.member == "sum" and matrix_sum_has_rhs(expr)))
|
|
2217
2220
|
):
|
|
2218
2221
|
recv_name2 = self._extract_receiver_name(expr)
|
|
2219
2222
|
if recv_name2 is not None and recv_name2 in self._matrix_specs:
|
|
@@ -4553,7 +4556,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4553
4556
|
"",
|
|
4554
4557
|
" Snapshot snapshot() {",
|
|
4555
4558
|
" if (_pf_generation_ == std::numeric_limits<uint64_t>::max()) {",
|
|
4556
|
-
'
|
|
4559
|
+
' _PF_ENGINE_INVARIANT("UDT checkpoint generation exhausted", std::overflow_error);',
|
|
4557
4560
|
" }",
|
|
4558
4561
|
" ++_pf_generation_;",
|
|
4559
4562
|
" _pf_undo_.clear();",
|
|
@@ -4565,13 +4568,13 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4565
4568
|
" bool empty() const { return _pf_undo_.empty(); }",
|
|
4566
4569
|
" void record(uint64_t generation, std::function<void()> undo) {",
|
|
4567
4570
|
" if (!_pf_active_ || generation != _pf_generation_) {",
|
|
4568
|
-
'
|
|
4571
|
+
' _PF_ENGINE_INVARIANT("invalid UDT undo generation", std::runtime_error);',
|
|
4569
4572
|
" }",
|
|
4570
4573
|
" _pf_undo_.push_back(std::move(undo));",
|
|
4571
4574
|
" }",
|
|
4572
4575
|
" void restore(const Snapshot& snapshot) {",
|
|
4573
4576
|
" if (!_pf_active_ || snapshot.generation != _pf_generation_) {",
|
|
4574
|
-
'
|
|
4577
|
+
' _PF_ENGINE_INVARIANT("invalid UDT coordinator checkpoint token", std::runtime_error);',
|
|
4575
4578
|
" }",
|
|
4576
4579
|
" for (auto entry = _pf_undo_.rbegin();",
|
|
4577
4580
|
" entry != _pf_undo_.rend(); ++entry) (*entry)();",
|
|
@@ -4595,13 +4598,13 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4595
4598
|
"",
|
|
4596
4599
|
" void capture(std::size_t index) {",
|
|
4597
4600
|
" if (!_pf_checkpoint_active_) return;",
|
|
4598
|
-
" auto& slot = _pf_records_
|
|
4601
|
+
" auto& slot = _PF_INVARIANT_AT(_pf_records_, index);",
|
|
4599
4602
|
" if (slot.logged_generation",
|
|
4600
4603
|
" == _pf_checkpoint_generation_) return;",
|
|
4601
4604
|
" auto snapshot = _PFRecordTraits::take(slot.value);",
|
|
4602
4605
|
" _pf_coordinator_->record(_pf_checkpoint_generation_,",
|
|
4603
4606
|
" [this, index, snapshot = std::move(snapshot)]() mutable {",
|
|
4604
|
-
" auto& restore_slot = _pf_records_
|
|
4607
|
+
" auto& restore_slot = _PF_INVARIANT_AT(_pf_records_, index);",
|
|
4605
4608
|
" _PFRecordTraits::restore(restore_slot.value, snapshot);",
|
|
4606
4609
|
" restore_slot.logged_generation = 0;",
|
|
4607
4610
|
" });",
|
|
@@ -4617,7 +4620,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4617
4620
|
f" {self._udt_undo_coordinator_cpp_name}* coordinator)",
|
|
4618
4621
|
" : _pf_coordinator_(coordinator) {",
|
|
4619
4622
|
" if (!_pf_coordinator_)",
|
|
4620
|
-
'
|
|
4623
|
+
' _PF_ENGINE_INVARIANT("UDT arena requires undo coordinator", std::invalid_argument);',
|
|
4621
4624
|
" }",
|
|
4622
4625
|
f" {self._udt_arena_template_cpp_name}(",
|
|
4623
4626
|
f" const {self._udt_arena_template_cpp_name}&) = delete;",
|
|
@@ -4639,7 +4642,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4639
4642
|
" _PFHandle create(_PFRecord value) {",
|
|
4640
4643
|
" if (_pf_records_.size() > static_cast<std::size_t>(",
|
|
4641
4644
|
" std::numeric_limits<int32_t>::max())) {",
|
|
4642
|
-
'
|
|
4645
|
+
' _PF_LIMIT_STOP("udt_objects", 2147483647LL, "UDT object-ID capacity exceeded");',
|
|
4643
4646
|
" }",
|
|
4644
4647
|
" const auto id = static_cast<int32_t>(_pf_records_.size());",
|
|
4645
4648
|
" _pf_records_.push_back(_PFSlot{std::move(value), 0});",
|
|
@@ -4652,7 +4655,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4652
4655
|
" _PFRecord& get(_PFHandle value) {",
|
|
4653
4656
|
" if (value.__pf_id < 0",
|
|
4654
4657
|
" || static_cast<std::size_t>(value.__pf_id) >= _pf_records_.size()) {",
|
|
4655
|
-
'
|
|
4658
|
+
' _PF_NA_STOP("udt_object", "UDT access on na or invalid object ID");',
|
|
4656
4659
|
" }",
|
|
4657
4660
|
" const auto index = static_cast<std::size_t>(value.__pf_id);",
|
|
4658
4661
|
" capture(index);",
|
|
@@ -4661,7 +4664,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4661
4664
|
" const _PFRecord& get(_PFHandle value) const {",
|
|
4662
4665
|
" if (value.__pf_id < 0",
|
|
4663
4666
|
" || static_cast<std::size_t>(value.__pf_id) >= _pf_records_.size()) {",
|
|
4664
|
-
'
|
|
4667
|
+
' _PF_NA_STOP("udt_object", "UDT access on na or invalid object ID");',
|
|
4665
4668
|
" }",
|
|
4666
4669
|
" return _pf_records_[static_cast<std::size_t>(value.__pf_id)].value;",
|
|
4667
4670
|
" }",
|
|
@@ -4671,14 +4674,14 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4671
4674
|
" std::size_t size() const { return _pf_records_.size(); }",
|
|
4672
4675
|
" _PFRecord& record_at(std::size_t index) {",
|
|
4673
4676
|
" capture(index);",
|
|
4674
|
-
" return _pf_records_
|
|
4677
|
+
" return _PF_INVARIANT_AT(_pf_records_, index).value;",
|
|
4675
4678
|
" }",
|
|
4676
4679
|
" const _PFRecord& record_at(std::size_t index) const {",
|
|
4677
|
-
" return _pf_records_
|
|
4680
|
+
" return _PF_INVARIANT_AT(_pf_records_, index).value;",
|
|
4678
4681
|
" }",
|
|
4679
4682
|
" Snapshot snapshot() {",
|
|
4680
4683
|
" if (!_pf_coordinator_->active()) {",
|
|
4681
|
-
'
|
|
4684
|
+
' _PF_ENGINE_INVARIANT("UDT coordinator checkpoint is not active", std::runtime_error);',
|
|
4682
4685
|
" }",
|
|
4683
4686
|
" _pf_checkpoint_generation_ = _pf_coordinator_->generation();",
|
|
4684
4687
|
" _pf_checkpoint_size_ = _pf_records_.size();",
|
|
@@ -4693,7 +4696,7 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4693
4696
|
" || snapshot.size != _pf_checkpoint_size_",
|
|
4694
4697
|
" || _pf_records_.size() < snapshot.size",
|
|
4695
4698
|
" || !_pf_coordinator_->empty()) {",
|
|
4696
|
-
'
|
|
4699
|
+
' _PF_ENGINE_INVARIANT("invalid UDT checkpoint token", std::runtime_error);',
|
|
4697
4700
|
" }",
|
|
4698
4701
|
" _pf_records_.resize(snapshot.size);",
|
|
4699
4702
|
" }",
|
|
@@ -4984,6 +4987,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
4984
4987
|
if name in self._global_collection_types
|
|
4985
4988
|
else self._callable_var_collection_spec(name)
|
|
4986
4989
|
)
|
|
4990
|
+
history_value_cpp = self._array_history_value_cpp_type(name)
|
|
4991
|
+
if history_value_cpp is not None:
|
|
4992
|
+
lines.append(f" {history_value_cpp} {safe};")
|
|
4993
|
+
continue
|
|
4987
4994
|
if callable_collection_spec is not None:
|
|
4988
4995
|
lines.append(
|
|
4989
4996
|
f" {self._type_spec_to_cpp(callable_collection_spec)} {safe};"
|
|
@@ -5182,7 +5189,10 @@ class CodeGen(CallVisitor, ExprVisitor, StmtVisitor, TopLevelEmitter, SecurityEm
|
|
|
5182
5189
|
seen_global.add(name)
|
|
5183
5190
|
safe = self._safe_name(name)
|
|
5184
5191
|
|
|
5185
|
-
|
|
5192
|
+
history_value_cpp = self._array_history_value_cpp_type(name)
|
|
5193
|
+
if history_value_cpp is not None:
|
|
5194
|
+
lines.append(f" {history_value_cpp} {safe};")
|
|
5195
|
+
elif name in self._matrix_specs:
|
|
5186
5196
|
lines.append(f" {self._type_spec_to_cpp(self._matrix_specs[name])} {safe};")
|
|
5187
5197
|
elif name in self._array_vars:
|
|
5188
5198
|
lines.append(f" {self._type_spec_to_cpp(self._array_spec_for_name(name))} {safe};")
|
|
@@ -7,8 +7,12 @@ from ..errors import CompileError
|
|
|
7
7
|
from .tables import ALERT_FREQ_VALUES, NAME_ECHO_STRING_MEMBERS, ORDER_DIRECTION_MAP
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
_QUOTED_STRING = r'"(?:[^"\\]
|
|
10
|
+
_QUOTED_STRING = r'"(?:[^"\\\x00\r\n]|\\(?:["\\nrt]|000))*"'
|
|
11
11
|
_STRING_LITERAL = re.compile(rf'(?:std::string\({_QUOTED_STRING}\)|{_QUOTED_STRING})')
|
|
12
|
+
_STRING_ESCAPE = re.compile(r'\\(?:["\\nrt]|000)')
|
|
13
|
+
_STRING_ESCAPE_VALUES = {
|
|
14
|
+
r'\"': '"', r'\\': '\\', r'\n': '\n', r'\r': '\r', r'\t': '\t', r'\000': '\0',
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
def _string_setting_arg(expr, lowered: str) -> str | None:
|
|
@@ -45,22 +49,97 @@ def _visit_setting_arg(emitter, expr) -> str:
|
|
|
45
49
|
return "0"
|
|
46
50
|
|
|
47
51
|
|
|
52
|
+
def _string_setting_value(lowered: str) -> str:
|
|
53
|
+
"""Decode the emitter's literals as native std::string(const char*) values.
|
|
54
|
+
|
|
55
|
+
Only the serializer's escape grammar is accepted; Unicode stays literal.
|
|
56
|
+
The receipt's string constructors stop at the first NUL, including one
|
|
57
|
+
followed by digits, while an escaped backslash remains ordinary data.
|
|
58
|
+
"""
|
|
59
|
+
if not _STRING_LITERAL.fullmatch(lowered):
|
|
60
|
+
raise ValueError("setting value is not an emitted C++ string literal")
|
|
61
|
+
literal = lowered[len("std::string("):-1] if lowered.startswith("std::string(") else lowered
|
|
62
|
+
decoded = _STRING_ESCAPE.sub(lambda match: _STRING_ESCAPE_VALUES[match.group()], literal[1:-1])
|
|
63
|
+
return decoded.partition("\0")[0]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
_NUMERIC_LITERAL = re.compile(r"[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?(?:LL|ULL|L|UL|[fF])?")
|
|
67
|
+
_NUMERIC_SUFFIX = re.compile(r"(?:LL|ULL|L|UL|[fF])$")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _numeric_setting_value(lowered: str) -> int | float | bool | None:
|
|
71
|
+
"""The number or bool a lowered C++ setting expression is, or None.
|
|
72
|
+
|
|
73
|
+
Only a plain literal, signed or in parentheses, is read: what the codegen
|
|
74
|
+
emits for a literal or for a named constant it inlines. Anything the C++
|
|
75
|
+
computes at run time (an arithmetic expression, a call, a series) stays
|
|
76
|
+
with the receipt, which evaluates it.
|
|
77
|
+
"""
|
|
78
|
+
text = lowered.strip()
|
|
79
|
+
while text.startswith("(") and text.endswith(")"):
|
|
80
|
+
text = text[1:-1].strip()
|
|
81
|
+
if text in ("true", "false"):
|
|
82
|
+
return text == "true"
|
|
83
|
+
if not _NUMERIC_LITERAL.fullmatch(text):
|
|
84
|
+
return None
|
|
85
|
+
text = _NUMERIC_SUFFIX.sub("", text)
|
|
86
|
+
return float(text) if re.search(r"[.eE]", text) else int(text)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _is_number(value) -> bool:
|
|
90
|
+
return isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
91
|
+
|
|
92
|
+
|
|
48
93
|
def emit_settings_members(emitter, lines: list[str], constructor: list[str]) -> None:
|
|
49
94
|
lines.extend([
|
|
50
95
|
" bool _pf_setting_failed_ = false;",
|
|
51
96
|
" std::string _pf_setting_failure_;",
|
|
97
|
+
"#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1",
|
|
98
|
+
" ::pineforge::RunFailureInfo _pf_setting_failure_info_{::pineforge::RunFailureCode::none, {}};",
|
|
99
|
+
"#ifdef PF_SETTINGS_API_VERSION",
|
|
100
|
+
" using _pf_setting_error_base = ::pineforge::checked_settings::LatchedSettingsFailure;",
|
|
101
|
+
"#else",
|
|
102
|
+
" using _pf_setting_error_base = std::runtime_error;",
|
|
103
|
+
"#endif",
|
|
104
|
+
" struct _pf_latched_setting_error : _pf_setting_error_base, ::pineforge::RunFailureInfo {",
|
|
105
|
+
" _pf_latched_setting_error(const std::string& text, const ::pineforge::RunFailureInfo& info)",
|
|
106
|
+
" : _pf_setting_error_base(text), ::pineforge::RunFailureInfo(info) {}",
|
|
107
|
+
" };",
|
|
108
|
+
"#endif",
|
|
52
109
|
" void _pf_record_failure(const char* entrypoint, const char* message) noexcept {",
|
|
110
|
+
"#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1",
|
|
111
|
+
" try { ::pineforge::note_run_failure_unknown(*this, entrypoint); } catch (...) {}",
|
|
112
|
+
"#else",
|
|
53
113
|
" try { last_error_ = entrypoint; last_error_ += \": \"; last_error_ += message; } catch (...) {}",
|
|
114
|
+
"#endif",
|
|
115
|
+
" }",
|
|
116
|
+
" void _pf_record_failure(const char* entrypoint, const std::exception& error) noexcept {",
|
|
117
|
+
"#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1",
|
|
118
|
+
" try { ::pineforge::note_run_failure(*this, entrypoint, error); } catch (...) {}",
|
|
119
|
+
"#else",
|
|
120
|
+
" _pf_record_failure(entrypoint, error.what());",
|
|
121
|
+
"#endif",
|
|
54
122
|
" }",
|
|
123
|
+
"#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1",
|
|
124
|
+
" template <typename FailureInfo>",
|
|
125
|
+
" void _pf_record_setting_failure(const char* entrypoint, const char* message, FailureInfo failure_info) noexcept {",
|
|
126
|
+
"#else",
|
|
55
127
|
" void _pf_record_setting_failure(const char* entrypoint, const char* message) noexcept {",
|
|
128
|
+
"#endif",
|
|
56
129
|
" if (!_pf_setting_failed_) {",
|
|
57
130
|
" _pf_setting_failed_ = true;",
|
|
58
131
|
' try { _pf_setting_failure_ = entrypoint; _pf_setting_failure_ += ": "; _pf_setting_failure_ += message; } catch (...) {}',
|
|
132
|
+
"#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1",
|
|
133
|
+
" try { _pf_setting_failure_info_ = failure_info(); }",
|
|
134
|
+
" catch (...) { _pf_setting_failure_info_ = ::pineforge::RunFailureInfo(::pineforge::RunFailureCode::out_of_memory, {}); }",
|
|
135
|
+
"#endif",
|
|
59
136
|
" }",
|
|
60
137
|
" _pf_refuse_failed_setting(nullptr);",
|
|
61
138
|
" }",
|
|
62
139
|
" void _pf_require_settings_ok() const {",
|
|
63
|
-
"#ifdef
|
|
140
|
+
"#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1",
|
|
141
|
+
' if (_pf_setting_failed_) throw _pf_latched_setting_error(_pf_setting_failure_.empty() ? "legacy strategy setter failed" : _pf_setting_failure_, _pf_setting_failure_info_);',
|
|
142
|
+
"#elif defined(PF_SETTINGS_API_VERSION)",
|
|
64
143
|
' if (_pf_setting_failed_) throw ::pineforge::checked_settings::LatchedSettingsFailure(_pf_setting_failure_.empty() ? "legacy strategy setter failed" : _pf_setting_failure_);',
|
|
65
144
|
"#else",
|
|
66
145
|
' if (_pf_setting_failed_) throw std::runtime_error(_pf_setting_failure_.empty() ? "legacy strategy setter failed" : _pf_setting_failure_);',
|
|
@@ -68,12 +147,17 @@ def emit_settings_members(emitter, lines: list[str], constructor: list[str]) ->
|
|
|
68
147
|
" }",
|
|
69
148
|
" bool _pf_refuse_failed_setting(ReportC* out) noexcept {",
|
|
70
149
|
" if (!_pf_setting_failed_) return false;",
|
|
150
|
+
"#ifdef PINEFORGE_HAS_RUN_FAILURE_CODES_V1",
|
|
151
|
+
' try { ::pineforge::note_run_failure(*this, _pf_setting_failure_.empty() ? "legacy strategy setter failed" : _pf_setting_failure_, _pf_setting_failure_info_.run_failure()); } catch (...) {}',
|
|
152
|
+
"#else",
|
|
71
153
|
' try { last_error_ = _pf_setting_failure_.empty() ? "legacy strategy setter failed" : _pf_setting_failure_; } catch (...) {}',
|
|
154
|
+
"#endif",
|
|
72
155
|
" if (out) *out = ReportC{};",
|
|
73
156
|
" return true;",
|
|
74
157
|
" }",
|
|
75
158
|
])
|
|
76
159
|
inputs = []
|
|
160
|
+
emitter._input_settings_metadata = {}
|
|
77
161
|
for node, binding in emitter._global_input_calls_with_names():
|
|
78
162
|
func_name, namespace = emitter._resolve_callee(node.callee)
|
|
79
163
|
name = emitter._get_input_title(node, var_name=binding)
|
|
@@ -90,6 +174,7 @@ def emit_settings_members(emitter, lines: list[str], constructor: list[str]) ->
|
|
|
90
174
|
}[getter]
|
|
91
175
|
kind = emitter._FORM_TYPE.get(func_name, value_type) if namespace == "input" else value_type
|
|
92
176
|
options = []
|
|
177
|
+
numeric_options = []
|
|
93
178
|
option_values = []
|
|
94
179
|
supported = "true"
|
|
95
180
|
default_serialized = None
|
|
@@ -146,6 +231,7 @@ def emit_settings_members(emitter, lines: list[str], constructor: list[str]) ->
|
|
|
146
231
|
options.append(option_cpp)
|
|
147
232
|
else:
|
|
148
233
|
options.append(f'::pineforge::checked_settings::number({option_cpp})')
|
|
234
|
+
numeric_options.append(option_cpp)
|
|
149
235
|
if getter == "get_input_string":
|
|
150
236
|
if arguments.get("options") is not None and declared is None:
|
|
151
237
|
supported = "false"
|
|
@@ -165,6 +251,31 @@ def emit_settings_members(emitter, lines: list[str], constructor: list[str]) ->
|
|
|
165
251
|
f'{64 if getter == "get_input_int64" else 32}, {supported}, '
|
|
166
252
|
f'{{{", ".join(option_values)}}}, "{kind}"}}')
|
|
167
253
|
inputs.append((metadata, effective))
|
|
254
|
+
manifest = {}
|
|
255
|
+
if getter == "get_input_string":
|
|
256
|
+
manifest["default"] = _string_setting_value(default_serialized)
|
|
257
|
+
if arguments.get("options") is not None or supported == "false":
|
|
258
|
+
manifest["options"] = [_string_setting_value(option) for option in options]
|
|
259
|
+
elif value_type == "source":
|
|
260
|
+
manifest["default"] = source
|
|
261
|
+
manifest["options"] = [_string_setting_value(option) for option in options]
|
|
262
|
+
elif value_type == "enum":
|
|
263
|
+
manifest["options"] = [_string_setting_value(option) for option in options]
|
|
264
|
+
elif namespace == "input" and kind in ("int", "float", "bool"):
|
|
265
|
+
# A typed numeric or bool input publishes the numbers the receipt
|
|
266
|
+
# holds, where its C++ is a plain literal.
|
|
267
|
+
default_value = _numeric_setting_value(default_cpp) if default is not None else None
|
|
268
|
+
if default_value is not None and isinstance(default_value, bool) == (kind == "bool"):
|
|
269
|
+
manifest["default"] = default_value
|
|
270
|
+
for field, lowered in zip(("min", "max", "step"), constraints):
|
|
271
|
+
bound = _numeric_setting_value(lowered)
|
|
272
|
+
if _is_number(bound):
|
|
273
|
+
manifest[field] = bound
|
|
274
|
+
values = [_numeric_setting_value(option) for option in numeric_options]
|
|
275
|
+
if values and all(_is_number(value) for value in values):
|
|
276
|
+
manifest["options"] = values
|
|
277
|
+
manifest["supported"] = supported == "true"
|
|
278
|
+
emitter._input_settings_metadata[id(node)] = manifest
|
|
168
279
|
|
|
169
280
|
overrides = [
|
|
170
281
|
("initial_capital", "float", "initial_capital", "initial_capital", "0.0"),
|