Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pineforge_codegen/codegen/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1258,7 +1258,7 @@ def _resolve_known(self, arg_str: str) -> str:
if isinstance(val, (int, float)):
return str(val)
if isinstance(val, str):
return f'std::string("{val}")'
return f'std::string("{self._cpp_string_escape(val)}")'
# Also resolve bar field references
if arg_str in BAR_FIELDS:
return BAR_FIELDS[arg_str]
Expand Down
2 changes: 1 addition & 1 deletion pineforge_codegen/codegen/visit_expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def _visit_expr(self, node: ASTNode | None) -> str:
if isinstance(node, NumberLiteral):
return str(node.value)
if isinstance(node, StringLiteral):
return f'std::string("{node.value}")'
return f'std::string("{self._cpp_string_escape(node.value)}")'
if isinstance(node, BoolLiteral):
return "true" if node.value else "false"
if isinstance(node, NaLiteral):
Expand Down
25 changes: 25 additions & 0 deletions tests/test_codegen_string_escape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Regression: C++ string-literal escaping.

Pine strings may contain characters that are special inside a C++ string
literal — double quotes (common in JSON alert/webhook templates), backslashes,
and newlines. These must be escaped when emitted, or the generated C++ fails to
compile ("invalid suffix on literal" / unterminated string).
"""

from pineforge_codegen import transpile


def _cpp(body: str) -> str:
return transpile('//@version=6\nstrategy("t")\n' + body + "\n")


def test_string_with_embedded_double_quotes_is_escaped():
cpp = _cpp("msg = '{\"type\":\"bot\",\"id\":\"42\"}'\nplot(str.length(msg))")
# The raw inner quotes must be backslash-escaped in the C++ literal.
assert r'\"type\"' in cpp
assert 'std::string("{"type"' not in cpp # the broken (unescaped) form


def test_string_with_backslash_is_escaped():
cpp = _cpp("p = 'a\\\\b'\nplot(str.length(p))")
assert "\\\\" in cpp
Loading