From e1f7e3563fa84b08e707a3dcea610560d179c63d Mon Sep 17 00:00:00 2001 From: Oeistein Andersen Date: Wed, 5 Mar 2025 12:06:23 +0000 Subject: [PATCH] Allow \u escape in string literal to encode surrogate code point. --- doc/langref.html.in | 13 +++++++++++-- lib/compiler/Maker/Package/Manifest.zig | 2 +- lib/std/zig/string_literal.zig | 8 ++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 5392522d60b1adfaf94113e07f572b7465add0d5..67c2ba60f0d92560618d80e55963c5495dabece4 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -754,12 +754,21 @@ \u{NNNNNN} - hexadecimal Unicode scalar value UTF-8 encoded (1 or more digits) + hexadecimal Unicode code point (1 or more digits) -

Note that the maximum valid Unicode scalar value is {#syntax#}0x10ffff{#endsyntax#}.

+

+ Note that the highest Unicode code point is {#syntax#}0x10ffff{#endsyntax#}. Character + literals are integers ({#syntax#}'A'{#endsyntax#} = {#syntax#}65{#endsyntax#} and + {#syntax#}'\u{10000}'{#endsyntax#} = {#syntax#}0x10000{#endsyntax#} = {#syntax#}65536{#endsyntax#}), + and \u escapes in string literals are encoded as UTF-8 + ({#syntax#}"A"{#endsyntax#} = {#syntax#}"\u{41}"{#endsyntax#} = {#syntax#}"\x41"{#endsyntax#} + and {#syntax#}"\u{10000}"{#endsyntax#} = {#syntax#}"\xF0\x90\x80\x80"{#endsyntax#}). + \u escapes can be used for surrogate code points, which are not Unicode + scalar values or technically allowed in UTF-8 as defined by the Unicode standard, but can be useful in practice. +

{#header_close#} {#header_open|Multiline String Literals#}

diff --git a/lib/compiler/Maker/Package/Manifest.zig b/lib/compiler/Maker/Package/Manifest.zig index e9ce31626294a26bea9642c34827e5ae0b142cd3..b52156294e89cee603d31df157d340756b1fbc39 100644 --- a/lib/compiler/Maker/Package/Manifest.zig +++ b/lib/compiler/Maker/Package/Manifest.zig @@ -508,7 +508,7 @@ const Parse = struct { try p.appendErrorOff( token, offset + @as(u32, @intCast(bad_index)), - "unicode escape does not correspond to a valid unicode scalar value", + "unicode escape does not correspond to a unicode code point", .{}, ); }, diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index 3699636c7965aa2c7cad9389bfc51d5eaa636faa..24de0bb54fbe131f90e62345781702f19a28244d 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -1,6 +1,5 @@ const std = @import("../std.zig"); const assert = std.debug.assert; -const utf8Encode = std.unicode.utf8Encode; const Writer = std.Io.Writer; pub const ParseError = error{ @@ -63,7 +62,7 @@ pub const Error = union(enum) { .{self.raw_string[bad_index]}, ), .invalid_unicode_codepoint => try writer.writeAll( - "unicode escape does not correspond to a valid unicode scalar value", + "unicode escape does not correspond to a unicode code point", ), .expected_lbrace => |bad_index| try writer.print( "expected '{{', found '{c}'", @@ -334,8 +333,8 @@ pub fn parseWrite(writer: *Writer, bytes: []const u8) Writer.Error!Result { .success => |codepoint| { if (bytes[escape_char_index] == 'u') { var buf: [4]u8 = undefined; - const len = utf8Encode(codepoint, &buf) catch { - return .{ .failure = .{ .invalid_unicode_codepoint = escape_char_index + 1 } }; + const len = std.unicode.wtf8Encode(codepoint, &buf) catch |err| switch (err) { + error.CodepointTooLarge => unreachable, // Checked in `parseEscapeSequence`. }; try writer.writeAll(buf[0..len]); } else { @@ -385,4 +384,5 @@ test parseAlloc { try expect(eql(u8, "foo", try parseAlloc(alloc, "\"foo\""))); try expect(eql(u8, "foo", try parseAlloc(alloc, "\"f\x6f\x6f\""))); try expect(eql(u8, "f💯", try parseAlloc(alloc, "\"f\u{1f4af}\""))); + try expect(eql(u8, "\xed\xa0\x80", try parseAlloc(alloc, "\"\\u{d800}\""))); } -- 2.54.0