Skip to content

fix(codegen): escape C++ string literals (quotes/backslash/newline) - #23

Merged
luisleo526 merged 1 commit into
mainfrom
fix/cpp-string-literal-escaping
Jun 28, 2026
Merged

luisleo526 merged 1 commit into
mainfrom
fix/cpp-string-literal-escaping

Conversation

@luisleo526

Copy link
Copy Markdown
Contributor

Problem

StringLiteral was emitted as std::string("<raw value>") with no escaping. Pine strings that contain double quotes — extremely common in JSON alert_message / webhook templates (3commas, DCA bots, etc.), e.g. '{"message_type":"bot","bot_id":"42"}' — produced invalid C++:

std::string("{"message_type":"bot",...")   // error: invalid suffix on literal

Found while running real-world published strategies through the engine: a large fraction of webhook/DCA strategies failed to compile on this alone.

Fix

Route the main StringLiteral visitor (visit_expr.py) and the const-string resolution path (base.py) through the existing _cpp_string_escape helper, which escapes \, ", \n, \r.

Tests / regression

  • New tests/test_codegen_string_escape.py (embedded quotes + backslash).
  • No corpus regression: re-transpiling all 258 corpus probes yields byte-identical generated.cpp with vs without this change (corpus strings contain no escapable characters → no-op for them).

🤖 Generated with Claude Code

StringLiteral was emitted as std::string("<raw>") without escaping. Pine strings
containing double quotes — common in JSON alert_message / webhook templates,
e.g. '{"type":"bot"}' — produced invalid C++:

    std::string("{"type":"bot"}")   // -> error: invalid suffix on literal

breaking many real-world strategies (3commas/DCA/webhook bots). Route the main
StringLiteral visitor and the const-string resolution path through the existing
_cpp_string_escape helper (escapes \, ", \n, \r).

Adds regression tests. No corpus regression: re-transpiling all 258 corpus
probes is byte-identical with vs without this change (corpus strings contain no
escapable characters, so it is a no-op for them).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@luisleo526
luisleo526 merged commit 632c88d into main Jun 28, 2026
9 checks passed
@luisleo526
luisleo526 deleted the fix/cpp-string-literal-escaping branch June 28, 2026 08:17
luisleo526 added a commit that referenced this pull request Sep 23, 2026
… it (C3 W3)

Every get_input_*() read pasted the title verbatim between two quotes, so a
title holding " or \ (He said "fast", C:\bars) ended the literal early and
the TU did not compile -- in the member read, the TA runtime reset, the
static-input block, input.source's read and its precalculate() replay, and
for an inline call alike. The four sites now spell the key through one
helper, _input_key_literal(), over the existing _cpp_string_escape. The key
the engine sees is the title as Pine reads it, the same string the input
manifest lists, so an override keyed by the manifest title reaches it.

The same escape was missing where a known string constant is inlined at its
use (s = "say \"hi\"" read as std::string("say "hi"")). 632c88d (#23)
escaped string literals and the constant-folding path but not these five
sites; the title pastes date from the initial public release (5b92828), the
precalculate() replay copy from c9e1c72.

Titles and constants without a quote or backslash emit byte-identically: the
312 corpus probes re-transpile unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
luisleo526 added a commit that referenced this pull request Oct 4, 2026
The engine's Docker harness gains --symbol-feeds (PINEFORGE_SYMBOL_FEEDS
in the hub image; engine #327, pineforge-release #23), which installs
other symbols' bars for request.security through C ABI calls that exist
since 1.0.0, so the generated C++ does not change. Its report key
applied_runtime.symbol_feeds and its failure output join the report-key
notes. The documentation bullet names #162, merged since.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
luisleo526 added a commit that referenced this pull request Oct 4, 2026
The engine's Docker harness gains --symbol-feeds (PINEFORGE_SYMBOL_FEEDS
in the hub image; engine #327, pineforge-release #23), which installs
other symbols' bars for request.security through C ABI calls that exist
since 1.0.0, so the generated C++ does not change. Its report key
applied_runtime.symbol_feeds and its failure output join the report-key
notes. The documentation bullet names #162, merged since.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
luisleo526 added a commit that referenced this pull request Oct 4, 2026
* docs: changelog and version scoping for 1.1.0

1.1.0 pairs with engine v1.1.0, whose pineforge.h declares the six
checked settings functions (engine #317) that the C++ of codegen 1.1.0
defines (#159). The changelog section covers #159 and #160: the checked
settings functions, the exception handling at the C ABI and the latched
setter failure, the input.enum default that came out as the enum's first
member, the names reserved for the settings code, the pairing and the
migration step. The Python and JSON contract does not change; the
engine's Docker harness adds applied_runtime.syminfo and two failure
messages for syminfo.mincontract (engine #322).

The README, AGENTS.md / CLAUDE.md, CONTRIBUTING.md,
docs/PUBLIC_CONTRACT.md and docs/pine-cap-activation.md name 1.1.0 where
they named 1.0.1 as the current release or pair, and the pairing table
gets a 1.1.0 row. The release scoreboard sentence renders
releases[1.1.0] from the facts file; the active scoreboard moves to
baseline pineforge-parity-baseline-20261003-engine-dbd17b38. The
anomalies sentence no longer starts its line with a facts marker, so
GitHub keeps it out of a raw HTML block.

Measured for 1.1.0: the engine's 325 corpus sources and the 277 gate
fixtures transpile, with the settings code removed, to 1.0.1's C++ byte
for byte (13 refusals, same messages), each in under 0.1 s on CPython
3.14.6 on an Apple M4 Max. The tutorial run of the quick-start C++
against engine e9f78a6f (Linux x86_64, GCC 13) prints 9 trades and
+738.20, and 13 trades with the old sizing declared.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* docs: correct the 1.1.0 notes against the code

A review of every claim against #159's code and tests and the engine at
main found these:

- transpile_full()'s inputs and transpile_json report the folded enum
  member as the default of an input.enum whose default is a variable,
  where 1.0.1 reported null: the contract keeps its shape, one value
  changes;
- the enum fold needs a variable set to a literal member, and the C++
  beyond the settings code changes for the fold and the renamed names;
- the receipt gives min, max and step as numbers or null;
- #322 leaves a gridless report's bytes as they were apart from
  elapsed_seconds, but across 1.0.1 and 1.1.0 the fingerprint records the
  versions and the generated C++'s hash, and engine #315 and #316 can
  change trades; the second mincontract message is for a library without
  strategy_set_syminfo_metadata; the fingerprint field is
  fingerprint.provenance.runtime.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* docs: rewrap a changelog line

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* docs: the harness symbol feeds and #162 in the 1.1.0 notes

The engine's Docker harness gains --symbol-feeds (PINEFORGE_SYMBOL_FEEDS
in the hub image; engine #327, pineforge-release #23), which installs
other symbols' bars for request.security through C ABI calls that exist
since 1.0.0, so the generated C++ does not change. Its report key
applied_runtime.symbol_feeds and its failure output join the report-key
notes. The documentation bullet names #162, merged since.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* docs: render 1.1.0's grades from its re-mapped baseline

releases[1.1.0] now names pineforge-parity-baseline-20261004-engine-7b596622
(engine 7b596622, the merge of engine #325, with this repository at
c5d97ee): 7,951 excellent / 38 strong of 7,989. The README's markers are
re-rendered from that facts file (pineforge-release #24), and the
changelog's documentation bullet names the baseline in place of
20261003-engine-dbd17b38. The README's release sentence says the release
"grades", as the engine README does, since 1.1.0 is the current release.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* docs: fold request discovery (#164) into the 1.1.0 notes

#164 merged with an Unreleased changelog section and "(unreleased)"
wording; it ships in 1.1.0. Its Request discovery section moves into
1.1.0 with the pull request's link, and the lead names #164 and #162.
The compatibility note no longer says the Python and JSON contract keeps
its result keys: it is additive (transpile_full() and the JSON success
envelope gain requests, input.symbol manifest entries gain kind), nothing
is removed or renamed, and #159's changed enum default stays. #164's
own note joins it: its emitted C++ is unchanged over the engine's 325
corpus sources and the 540 test fixtures, each in a fresh process (51
refused with the same message), which this branch re-checked.

README.md and docs/PUBLIC_CONTRACT.md name 1.1.0 where #164 wrote
"(unreleased)" or "Development builds"; the contract's heading is now
"Request discovery", the anchor the changelog links. The contract's
result-key table, success envelope example and manifest paragraph list
requests and kind, which the error envelope does not carry.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant