diff --git a/map.go b/map.go index f0871cad..7042db83 100644 --- a/map.go +++ b/map.go @@ -57,12 +57,16 @@ func unquote(s string) (string, error) { s = s[1 : len(s)-1] var out strings.Builder for s != "" { - value, _, tail, err := strconv.UnquoteChar(s, quote) + value, multibyte, tail, err := strconv.UnquoteChar(s, quote) if err != nil { return "", err } s = tail - out.WriteRune(value) + if multibyte { + out.WriteRune(value) + } else { + out.WriteByte(byte(value)) //nolint:gosec // UnquoteChar returns a single byte when multibyte is false. + } } return out.String(), nil } diff --git a/map_test.go b/map_test.go index d5327d99..b25624e0 100644 --- a/map_test.go +++ b/map_test.go @@ -54,6 +54,30 @@ func TestUnquote(t *testing.T) { require.Equal(t, expected, actual) } +func TestUnquoteByteEscapes(t *testing.T) { + type grammar struct { + Text string `@String` + } + parser := mustTestParser[grammar](t, participle.Unquote()) + for _, tc := range []struct { + name, input, want string + }{ + {"hexadecimal byte", `"\xff"`, "\xff"}, + {"octal byte", `"\377"`, "\xff"}, + {"UTF-8 bytes", `"\xc3\xbf"`, "ÿ"}, + {"mixed byte and Unicode", `"a\xff\u00ff"`, "a\xffÿ"}, + {"Unicode escape", `"\u00ff"`, "ÿ"}, + {"literal Unicode", `"ÿ"`, "ÿ"}, + {"ASCII escapes", `"\x41\101"`, "AA"}, + } { + t.Run(tc.name, func(t *testing.T) { + actual, err := parser.ParseString("", tc.input) + require.NoError(t, err) + require.Equal(t, tc.want, actual.Text) + }) + } +} + func TestUnquoteShortToken(t *testing.T) { type grammar struct { Text string `@String`