Skip to content

Commit 98e94ae

Browse files
pelletierclaude
andauthored
Update bundled toml.abnf to TOML 1.1.0 and pin spec-corner behaviour (#1082)
* docs: update bundled toml.abnf to TOML 1.1.0 The bundled grammar was still the TOML 1.0.0 ABNF and also contained an error that contradicted both the spec and the implementation. It is used only as documentation (no code references it), but the README advertises 1.1.0 support, so the reference should match. - non-eol: %x20-7F -> %x20-7E, so DEL (0x7F) is excluded from comments. The parser already rejects 0x7F in comments; the grammar was the only thing claiming otherwise. - escape-seq-char: add \e (U+001B) and \xHH (#790, #796). - partial-time: make seconds optional (#894). - inline-table: allow whitespace/comments/newlines and a trailing comma (#904). These four rules now match the official TOML 1.1.0 toml.abnf. All four features were already implemented by the parser; this only corrects the documentation. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * test: pin spec-corner behaviour and TOML 1.1.0 features Add native regression coverage for spec corners that the upstream toml-test suite intentionally leaves undefined or under-tests, so a future refactor cannot silently change behaviour while the generated toml_testgen_test.go stays green: - leap seconds (:60) rejected (not representable by time.Time); - float overflow (1e400) rejected rather than decoded to +Inf (implementation-defined per #1058); - fractional seconds without seconds rejected (#894); - \xHH requires exactly two hex digits (#796); - DEL (0x7F) rejected in comments and string bodies; - multiline basic string quote-counting (content may end with up to two quotes). Also keeps native coverage of the four 1.1.0 grammar features (optional seconds, \e, \xHH, inline-table newlines/trailing commas) independent of the generated suite. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 35f78d5 commit 98e94ae

2 files changed

Lines changed: 107 additions & 9 deletions

File tree

compliance_corners_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package toml_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/pelletier/go-toml/v2"
7+
"github.com/pelletier/go-toml/v2/internal/assert"
8+
)
9+
10+
// This file pins go-toml's behaviour in TOML spec corners that the upstream
11+
// toml-test conformance suite intentionally leaves undefined or under-tests.
12+
// Without these, a future refactor could silently change behaviour while the
13+
// generated toml_testgen_test.go stays green.
14+
//
15+
// It also keeps native regression coverage for the four TOML 1.1.0 grammar
16+
// changes, independent of the generated suite.
17+
18+
// TOML 1.1.0 grammar features that must be accepted.
19+
func TestCorners_TOML11Accepted(t *testing.T) {
20+
cases := map[string]string{
21+
// #894 — seconds are optional in time and datetime.
22+
"local-time-no-seconds": "x = 13:37\n",
23+
"offset-datetime-no-seconds": "x = 1979-05-27T07:32Z\n",
24+
"offset-datetime-no-sec-num": "x = 1979-05-27T07:32-07:00\n",
25+
"local-datetime-no-seconds": "x = 1979-05-27T07:32\n",
26+
// #790 — \e is the escape character (U+001B).
27+
"esc-e": "x = \"\\e\"\n",
28+
// #796 — \xHH is a two-digit hex escape for codepoints <= 0xFF.
29+
"esc-x-lower": "x = \"\\xff\"\n",
30+
"esc-x-upper": "x = \"\\xFF\"\n",
31+
// #904 — newlines and trailing commas in inline tables.
32+
"inline-trailing-comma": "x = {a = 1,}\n",
33+
"inline-newline": "x = {\n\ta = 1,\n}\n",
34+
}
35+
for name, input := range cases {
36+
t.Run(name, func(t *testing.T) {
37+
var v map[string]interface{}
38+
assert.NoError(t, toml.Unmarshal([]byte(input), &v))
39+
})
40+
}
41+
}
42+
43+
// Gray-area corners where go-toml deliberately rejects input that the
44+
// conformance suite does not pin. These assertions document the intent.
45+
func TestCorners_DeliberateRejections(t *testing.T) {
46+
cases := map[string]string{
47+
// Leap seconds (:60) are grammar-permitted but not representable by
48+
// time.Time without rolling over, so go-toml rejects them. toml-test
49+
// only pins :61 as invalid, never :60.
50+
"leap-second-local": "x = 23:59:60\n",
51+
"leap-second-offset": "x = 1979-05-27T23:59:60Z\n",
52+
53+
// Float overflow: 1e400 is rejected rather than decoded to +Inf.
54+
// TOML 1.1.0 #1058 makes float size implementation-defined, so either
55+
// behaviour is conformant; this pins go-toml's choice.
56+
"float-overflow": "x = 1e400\n",
57+
58+
// #894 — fractional seconds may only appear when seconds are present.
59+
"fraction-without-seconds": "x = 07:32.5\n",
60+
61+
// #796 — \xHH requires exactly two hex digits.
62+
"esc-x-one-digit": "x = \"\\xf\"\n",
63+
"esc-x-bad-hex": "x = \"\\xg0\"\n",
64+
65+
// DEL (0x7F) is excluded from comments (non-eol = %x09 / %x20-7E /
66+
// non-ascii) and from every string body.
67+
"del-in-comment": "x = 1 # a\x7fb\n",
68+
"del-in-basic-str": "x = \"a\x7fb\"\n",
69+
70+
// #904 — a leading comma in an inline table is still invalid.
71+
"inline-leading-comma": "x = {,a = 1}\n",
72+
}
73+
for name, input := range cases {
74+
t.Run(name, func(t *testing.T) {
75+
var v map[string]interface{}
76+
assert.Error(t, toml.Unmarshal([]byte(input), &v))
77+
})
78+
}
79+
}
80+
81+
// Multiline basic string quote-counting: content may end with up to two
82+
// quotation marks before the closing delimiter, but no more.
83+
func TestCorners_MultilineQuoteCounting(t *testing.T) {
84+
t.Run("two-trailing-quotes", func(t *testing.T) {
85+
var v struct {
86+
X string `toml:"x"`
87+
}
88+
assert.NoError(t, toml.Unmarshal([]byte("x = \"\"\"a\"\"\"\"\"\n"), &v))
89+
assert.Equal(t, `a""`, v.X)
90+
})
91+
t.Run("too-many-trailing-quotes", func(t *testing.T) {
92+
var v map[string]interface{}
93+
assert.Error(t, toml.Unmarshal([]byte("x = \"\"\"a\"\"\"\"\"\"\"\n"), &v))
94+
})
95+
}

toml.abnf

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ newline =/ %x0D.0A ; CRLF
3636

3737
comment-start-symbol = %x23 ; #
3838
non-ascii = %x80-D7FF / %xE000-10FFFF
39-
non-eol = %x09 / %x20-7F / non-ascii
39+
non-eol = %x09 / %x20-7E / non-ascii
4040

4141
comment = comment-start-symbol *non-eol
4242

@@ -74,12 +74,14 @@ escape = %x5C ; \
7474
escape-seq-char = %x22 ; " quotation mark U+0022
7575
escape-seq-char =/ %x5C ; \ reverse solidus U+005C
7676
escape-seq-char =/ %x62 ; b backspace U+0008
77+
escape-seq-char =/ %x65 ; e escape U+001B
7778
escape-seq-char =/ %x66 ; f form feed U+000C
7879
escape-seq-char =/ %x6E ; n line feed U+000A
7980
escape-seq-char =/ %x72 ; r carriage return U+000D
8081
escape-seq-char =/ %x74 ; t tab U+0009
81-
escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX
82-
escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX
82+
escape-seq-char =/ %x78 2HEXDIG ; xHH U+00HH
83+
escape-seq-char =/ %x75 4HEXDIG ; uHHHH U+HHHH
84+
escape-seq-char =/ %x55 8HEXDIG ; UHHHHHHHH U+HHHHHHHH
8385

8486
;; Multiline Basic String
8587

@@ -174,7 +176,7 @@ time-secfrac = "." 1*DIGIT
174176
time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
175177
time-offset = "Z" / time-numoffset
176178

177-
partial-time = time-hour ":" time-minute ":" time-second [ time-secfrac ]
179+
partial-time = time-hour ":" time-minute [ ":" time-second [ time-secfrac ] ]
178180
full-date = date-fullyear "-" date-month "-" date-mday
179181
full-time = partial-time time-offset
180182

@@ -221,13 +223,14 @@ std-table-close = ws %x5D ; ] Right square bracket
221223

222224
;; Inline Table
223225

224-
inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close
226+
inline-table = inline-table-open [ inline-table-keyvals ] ws-comment-newline inline-table-close
225227

226-
inline-table-open = %x7B ws ; {
227-
inline-table-close = ws %x7D ; }
228-
inline-table-sep = ws %x2C ws ; , Comma
228+
inline-table-open = %x7B ; {
229+
inline-table-close = %x7D ; }
230+
inline-table-sep = %x2C ; , Comma
229231

230-
inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ]
232+
inline-table-keyvals = ws-comment-newline keyval ws-comment-newline inline-table-sep inline-table-keyvals
233+
inline-table-keyvals =/ ws-comment-newline keyval ws-comment-newline [ inline-table-sep ]
231234

232235
;; Array Table
233236

0 commit comments

Comments
 (0)