ReviewOS

also looking at this

home-lang/home

fix(interpreter): decode escape sequences in interpolated strings

#934
Open mkantautas wants to merge fix/interpreter-interpolation-escapes into main
2 files +35 -5
packages/interpreter/src/interpreter.zigmodified+13-5
Changes to packages/interpreter/src/interpreter.zig
@@ -1200,7 +1200,7 @@ pub const Interpreter = struct {
12001200 },
12011201 .StringLiteral => |lit| {
12021202 // Process escape sequences in string literals
1203 const processed = try self.processStringEscapes(lit.value);
1203 const processed = try self.processStringEscapes(lit.value, false);
12041204 return Value{ .String = processed };
12051205 },
12061206 .InterpolatedString => |interp| {
@@ -1208,8 +1208,9 @@ pub const Interpreter = struct {
12081208 var result = std.ArrayList(u8).empty;
12091209 defer result.deinit(self.arena.allocator());
12101210
1211 // Add first part
1212 try result.appendSlice(self.arena.allocator(), interp.parts[0]);
1211 // Add first part. Parts keep their escape sequences as
1212 // written, like string literals, so decode them the same way.
1213 try result.appendSlice(self.arena.allocator(), try self.processStringEscapes(interp.parts[0], true));
12131214
12141215 // Interleave expressions and remaining parts
12151216 for (interp.expressions, 0..) |interp_expr, i| {
@@ -1219,7 +1220,7 @@ pub const Interpreter = struct {
12191220
12201221 // Add next part if it exists
12211222 if (i + 1 < interp.parts.len) {
1222 try result.appendSlice(self.arena.allocator(), interp.parts[i + 1]);
1223 try result.appendSlice(self.arena.allocator(), try self.processStringEscapes(interp.parts[i + 1], true));
12231224 }
12241225 }
12251226
@@ -12075,7 +12076,9 @@ pub const Interpreter = struct {
1207512076
1207612077 /// Process escape sequences in a string literal
1207712078 /// Converts \n, \t, \r, \\, \", \', \0, \xNN to their actual character values
12078 fn processStringEscapes(self: *Interpreter, input: []const u8) InterpreterError![]const u8 {
12079 /// `interpolated` decodes `\$`, which only interpolated strings accept as
12080 /// an escape (so a literal `${` can be written).
12081 fn processStringEscapes(self: *Interpreter, input: []const u8, interpolated: bool) InterpreterError![]const u8 {
1207912082 // Quick check: if no backslashes, return as-is
1208012083 var has_escape = false;
1208112084 for (input) |c| {
@@ -12118,6 +12121,11 @@ pub const Interpreter = struct {
1211812121 try result.append(self.arena.allocator(), '\'');
1211912122 i += 2;
1212012123 },
12124 '$' => {
12125 if (!interpolated) try result.append(self.arena.allocator(), '\\');
12126 try result.append(self.arena.allocator(), '$');
12127 i += 2;
12128 },
1212112129 '0' => {
1212212130 try result.append(self.arena.allocator(), 0);
1212312131 i += 2;