Skip to content

Resolve escape sequences in string literals - #86

Merged
revarbat merged 2 commits into
mainfrom
fix-string-escapes
Aug 10, 2026
Merged

Resolve escape sequences in string literals#86
revarbat merged 2 commits into
mainfrom
fix-string-escapes

Conversation

@revarbat

Copy link
Copy Markdown
Member

Nothing did.

The parser hands back a literal's source text with its backslashes intact — deliberately, since that is what lets a StringLiteral reproduce the source it came from, and what pretty-printing round-trips. But nothing cooked it afterwards, so every escape reached scripts raw:

source was now
"a\nb" [97, 92, 110, 98] [97, 10, 98]
"a\tb" [97, 92, 116, 98] [97, 9, 98]
"a\\b" [97, 92, 92, 98] [97, 92, 98]
"a\"b" [97, 92, 34, 98] [97, 34, 98]
"a\qb" [97, 92, 113, 98] [97, 113, 98]

unescapeStringLiteral() resolves them where the literal is evaluated — which is both evaluation paths: the tree walk in expr_eval and the constant pool in bytecode_compiler. A first attempt reached only one of them, so the tests exercise a literal at top level and one inside a function body.

It returns the input untouched when there is no backslash in it, which is nearly every string, so the common case allocates nothing.

Line continuations

A backslash escaping the end of a line contributes nothing at all; the string carries on with no break in it.

s = "a \
b";        // "a b", 3 characters

A CRLF goes whole — unlike the reference implementation, which drops only the LF and leaves the CR in the value, putting a stray control character into any string continued in a file written on Windows. A lone CR is still an ordinary escaped character.

This also picks up the parser's fix for the same feature (BelfrySCAD/openscad_cpp_parser#4), where a backslash before a newline matched no lexer rule and was printed to stdout by flex's default ECHO.

Testing

808 tests pass. Negative-controlled twice: making the unescaper a no-op, and making a continuation keep its newline, each fail their own tests.

🤖 Generated with Claude Code

revarbat and others added 2 commits August 9, 2026 22:26
Nothing did. The parser hands back a literal's source text with its
backslashes intact, deliberately -- that is what lets a StringLiteral
reproduce the source it came from, and what pretty-printing round-trips
-- but no one cooked it afterwards, so every escape reached scripts raw.
"a\nb" was four characters, a backslash and an 'n' among them, rather
than three. The same for \t, \r, \" and \\.

unescapeStringLiteral() resolves them where the literal is evaluated,
which is both evaluation paths: the tree walk in expr_eval and the
constant pool in bytecode_compiler. It returns the input untouched when
there is no backslash in it, which is nearly every string, so the common
case allocates nothing.

A backslash escaping the end of a line contributes nothing at all: the
string carries on with no break in it. A CRLF goes whole, unlike the
reference implementation, which drops only the LF and leaves the CR in
the value -- a stray control character in any string continued in a file
written on Windows. A lone CR is still an ordinary escaped character.

An unknown escape keeps its character and drops the backslash, matching
the reference.

Also picks up the parser's fix for the same feature, where a backslash
before a newline matched no lexer rule and was printed to stdout by
flex's default ECHO.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Windows CI turned red once escapes started being resolved: 43 import,
surface and DXF/SVG tests build a script by interpolating a temp path
into a string literal, and std::filesystem::path::string() hands back
backslashes there. `C:\temp\x.stl` was fine while backslashes passed
through untouched; now \t is a tab, which is exactly right and exactly
what breaks those paths.

generic_string() gives forward slashes, which Windows accepts, and this
is what a script has to do for real: a backslash in an OpenSCAD string
is an escape, so a Windows path in an import() is written with forward
slashes or doubled backslashes. The tests were relying on the bug.

Only the sites that interpolate into a quoted string literal changed.
`use <path>` and `include <path>` are lexed in a separate state that
does not process escapes at all, so those keep string(), and none of
them failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@revarbat
revarbat merged commit 0ed1951 into main Aug 10, 2026
3 checks passed
@revarbat
revarbat deleted the fix-string-escapes branch August 10, 2026 05:52
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