diff --git a/pineforge_codegen/codegen/base.py b/pineforge_codegen/codegen/base.py index 8e90f59..7b1f037 100644 --- a/pineforge_codegen/codegen/base.py +++ b/pineforge_codegen/codegen/base.py @@ -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] diff --git a/pineforge_codegen/codegen/visit_expr.py b/pineforge_codegen/codegen/visit_expr.py index eae35ec..494c3b8 100644 --- a/pineforge_codegen/codegen/visit_expr.py +++ b/pineforge_codegen/codegen/visit_expr.py @@ -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): diff --git a/tests/test_codegen_string_escape.py b/tests/test_codegen_string_escape.py new file mode 100644 index 0000000..5b25f96 --- /dev/null +++ b/tests/test_codegen_string_escape.py @@ -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