From 5794be6251311d7a3cbce8f2a24046cca823f6d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Fernandes?= Date: Wed, 11 Feb 2026 11:04:54 +0000 Subject: [PATCH] feat: add \xHH escape sequence support in basic strings TOML v1.1.0 introduces the \xHH escape notation for basic strings, allowing two-digit hex escapes for Unicode code points U+0000 to U+00FF. We keep emitting \u00XX for backwards compatibility. --- unmarshaler_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++ unstable/parser.go | 14 ++++++++++ 2 files changed, 82 insertions(+) diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 3e3b2a3..d832ea7 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -759,6 +759,62 @@ huey = 'dewey' } }, }, + { + desc: "basic string hex escape lowercase letter", + input: `A = "\x61"`, + gen: func() test { + type doc struct { + A string + } + + return test{ + target: &doc{}, + expected: &doc{A: "a"}, + } + }, + }, + { + desc: "basic string hex escape null byte", + input: `A = "\x00"`, + gen: func() test { + type doc struct { + A string + } + + return test{ + target: &doc{}, + expected: &doc{A: "\x00"}, + } + }, + }, + { + desc: "basic string hex escape max value", + input: `A = "\xFF"`, + gen: func() test { + type doc struct { + A string + } + + return test{ + target: &doc{}, + expected: &doc{A: "\u00FF"}, + } + }, + }, + { + desc: "multiline basic string hex escape", + input: `A = """\x61"""`, + gen: func() test { + type doc struct { + A string + } + + return test{ + target: &doc{}, + expected: &doc{A: "a"}, + } + }, + }, { desc: "spaces around dotted keys", input: "a . b = 1", @@ -3260,6 +3316,18 @@ world'`, desc: `invalid escape char basic multiline string`, data: `A = """\z"""`, }, + { + desc: `invalid hex escape non-hex character in basic string`, + data: `A = "\xGG"`, + }, + { + desc: `incomplete hex escape in basic string`, + data: `A = "\x6"`, + }, + { + desc: `invalid hex escape non-hex character in multiline basic string`, + data: `A = """\xGG"""`, + }, { desc: `invalid inf`, data: `A = ick`, diff --git a/unstable/parser.go b/unstable/parser.go index d48e07f..1a08397 100644 --- a/unstable/parser.go +++ b/unstable/parser.go @@ -785,6 +785,13 @@ func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er builder.WriteByte('\t') case 'e': builder.WriteByte(0x1B) + case 'x': + x, err := hexToRune(atmost(token[i+1:], 2), 2) + if err != nil { + return nil, nil, nil, err + } + builder.WriteRune(x) + i += 2 case 'u': x, err := hexToRune(atmost(token[i+1:], 4), 4) if err != nil { @@ -944,6 +951,13 @@ func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { builder.WriteByte('\t') case 'e': builder.WriteByte(0x1B) + case 'x': + x, err := hexToRune(token[i+1:len(token)-1], 2) + if err != nil { + return nil, nil, nil, err + } + builder.WriteRune(x) + i += 2 case 'u': x, err := hexToRune(token[i+1:len(token)-1], 4) if err != nil {