Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/vendor/github.com/pelletier/go-toml/v2/decode.go
2880 views
1
package toml
2
3
import (
4
"fmt"
5
"math"
6
"strconv"
7
"time"
8
9
"github.com/pelletier/go-toml/v2/unstable"
10
)
11
12
func parseInteger(b []byte) (int64, error) {
13
if len(b) > 2 && b[0] == '0' {
14
switch b[1] {
15
case 'x':
16
return parseIntHex(b)
17
case 'b':
18
return parseIntBin(b)
19
case 'o':
20
return parseIntOct(b)
21
default:
22
panic(fmt.Errorf("invalid base '%c', should have been checked by scanIntOrFloat", b[1]))
23
}
24
}
25
26
return parseIntDec(b)
27
}
28
29
func parseLocalDate(b []byte) (LocalDate, error) {
30
// full-date = date-fullyear "-" date-month "-" date-mday
31
// date-fullyear = 4DIGIT
32
// date-month = 2DIGIT ; 01-12
33
// date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
34
var date LocalDate
35
36
if len(b) != 10 || b[4] != '-' || b[7] != '-' {
37
return date, unstable.NewParserError(b, "dates are expected to have the format YYYY-MM-DD")
38
}
39
40
var err error
41
42
date.Year, err = parseDecimalDigits(b[0:4])
43
if err != nil {
44
return LocalDate{}, err
45
}
46
47
date.Month, err = parseDecimalDigits(b[5:7])
48
if err != nil {
49
return LocalDate{}, err
50
}
51
52
date.Day, err = parseDecimalDigits(b[8:10])
53
if err != nil {
54
return LocalDate{}, err
55
}
56
57
if !isValidDate(date.Year, date.Month, date.Day) {
58
return LocalDate{}, unstable.NewParserError(b, "impossible date")
59
}
60
61
return date, nil
62
}
63
64
func parseDecimalDigits(b []byte) (int, error) {
65
v := 0
66
67
for i, c := range b {
68
if c < '0' || c > '9' {
69
return 0, unstable.NewParserError(b[i:i+1], "expected digit (0-9)")
70
}
71
v *= 10
72
v += int(c - '0')
73
}
74
75
return v, nil
76
}
77
78
func parseDateTime(b []byte) (time.Time, error) {
79
// offset-date-time = full-date time-delim full-time
80
// full-time = partial-time time-offset
81
// time-offset = "Z" / time-numoffset
82
// time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
83
84
dt, b, err := parseLocalDateTime(b)
85
if err != nil {
86
return time.Time{}, err
87
}
88
89
var zone *time.Location
90
91
if len(b) == 0 {
92
// parser should have checked that when assigning the date time node
93
panic("date time should have a timezone")
94
}
95
96
if b[0] == 'Z' || b[0] == 'z' {
97
b = b[1:]
98
zone = time.UTC
99
} else {
100
const dateTimeByteLen = 6
101
if len(b) != dateTimeByteLen {
102
return time.Time{}, unstable.NewParserError(b, "invalid date-time timezone")
103
}
104
var direction int
105
switch b[0] {
106
case '-':
107
direction = -1
108
case '+':
109
direction = +1
110
default:
111
return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset character")
112
}
113
114
if b[3] != ':' {
115
return time.Time{}, unstable.NewParserError(b[3:4], "expected a : separator")
116
}
117
118
hours, err := parseDecimalDigits(b[1:3])
119
if err != nil {
120
return time.Time{}, err
121
}
122
if hours > 23 {
123
return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset hours")
124
}
125
126
minutes, err := parseDecimalDigits(b[4:6])
127
if err != nil {
128
return time.Time{}, err
129
}
130
if minutes > 59 {
131
return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset minutes")
132
}
133
134
seconds := direction * (hours*3600 + minutes*60)
135
if seconds == 0 {
136
zone = time.UTC
137
} else {
138
zone = time.FixedZone("", seconds)
139
}
140
b = b[dateTimeByteLen:]
141
}
142
143
if len(b) > 0 {
144
return time.Time{}, unstable.NewParserError(b, "extra bytes at the end of the timezone")
145
}
146
147
t := time.Date(
148
dt.Year,
149
time.Month(dt.Month),
150
dt.Day,
151
dt.Hour,
152
dt.Minute,
153
dt.Second,
154
dt.Nanosecond,
155
zone)
156
157
return t, nil
158
}
159
160
func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
161
var dt LocalDateTime
162
163
const localDateTimeByteMinLen = 11
164
if len(b) < localDateTimeByteMinLen {
165
return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]")
166
}
167
168
date, err := parseLocalDate(b[:10])
169
if err != nil {
170
return dt, nil, err
171
}
172
dt.LocalDate = date
173
174
sep := b[10]
175
if sep != 'T' && sep != ' ' && sep != 't' {
176
return dt, nil, unstable.NewParserError(b[10:11], "datetime separator is expected to be T or a space")
177
}
178
179
t, rest, err := parseLocalTime(b[11:])
180
if err != nil {
181
return dt, nil, err
182
}
183
dt.LocalTime = t
184
185
return dt, rest, nil
186
}
187
188
// parseLocalTime is a bit different because it also returns the remaining
189
// []byte that is didn't need. This is to allow parseDateTime to parse those
190
// remaining bytes as a timezone.
191
func parseLocalTime(b []byte) (LocalTime, []byte, error) {
192
var (
193
nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0}
194
t LocalTime
195
)
196
197
// check if b matches to have expected format HH:MM:SS[.NNNNNN]
198
const localTimeByteLen = 8
199
if len(b) < localTimeByteLen {
200
return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]")
201
}
202
203
var err error
204
205
t.Hour, err = parseDecimalDigits(b[0:2])
206
if err != nil {
207
return t, nil, err
208
}
209
210
if t.Hour > 23 {
211
return t, nil, unstable.NewParserError(b[0:2], "hour cannot be greater 23")
212
}
213
if b[2] != ':' {
214
return t, nil, unstable.NewParserError(b[2:3], "expecting colon between hours and minutes")
215
}
216
217
t.Minute, err = parseDecimalDigits(b[3:5])
218
if err != nil {
219
return t, nil, err
220
}
221
if t.Minute > 59 {
222
return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59")
223
}
224
if b[5] != ':' {
225
return t, nil, unstable.NewParserError(b[5:6], "expecting colon between minutes and seconds")
226
}
227
228
t.Second, err = parseDecimalDigits(b[6:8])
229
if err != nil {
230
return t, nil, err
231
}
232
233
if t.Second > 60 {
234
return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater 60")
235
}
236
237
b = b[8:]
238
239
if len(b) >= 1 && b[0] == '.' {
240
frac := 0
241
precision := 0
242
digits := 0
243
244
for i, c := range b[1:] {
245
if !isDigit(c) {
246
if i == 0 {
247
return t, nil, unstable.NewParserError(b[0:1], "need at least one digit after fraction point")
248
}
249
break
250
}
251
digits++
252
253
const maxFracPrecision = 9
254
if i >= maxFracPrecision {
255
// go-toml allows decoding fractional seconds
256
// beyond the supported precision of 9
257
// digits. It truncates the fractional component
258
// to the supported precision and ignores the
259
// remaining digits.
260
//
261
// https://github.com/pelletier/go-toml/discussions/707
262
continue
263
}
264
265
frac *= 10
266
frac += int(c - '0')
267
precision++
268
}
269
270
if precision == 0 {
271
return t, nil, unstable.NewParserError(b[:1], "nanoseconds need at least one digit")
272
}
273
274
t.Nanosecond = frac * nspow[precision]
275
t.Precision = precision
276
277
return t, b[1+digits:], nil
278
}
279
return t, b, nil
280
}
281
282
//nolint:cyclop
283
func parseFloat(b []byte) (float64, error) {
284
if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' {
285
return math.NaN(), nil
286
}
287
288
cleaned, err := checkAndRemoveUnderscoresFloats(b)
289
if err != nil {
290
return 0, err
291
}
292
293
if cleaned[0] == '.' {
294
return 0, unstable.NewParserError(b, "float cannot start with a dot")
295
}
296
297
if cleaned[len(cleaned)-1] == '.' {
298
return 0, unstable.NewParserError(b, "float cannot end with a dot")
299
}
300
301
dotAlreadySeen := false
302
for i, c := range cleaned {
303
if c == '.' {
304
if dotAlreadySeen {
305
return 0, unstable.NewParserError(b[i:i+1], "float can have at most one decimal point")
306
}
307
if !isDigit(cleaned[i-1]) {
308
return 0, unstable.NewParserError(b[i-1:i+1], "float decimal point must be preceded by a digit")
309
}
310
if !isDigit(cleaned[i+1]) {
311
return 0, unstable.NewParserError(b[i:i+2], "float decimal point must be followed by a digit")
312
}
313
dotAlreadySeen = true
314
}
315
}
316
317
start := 0
318
if cleaned[0] == '+' || cleaned[0] == '-' {
319
start = 1
320
}
321
if cleaned[start] == '0' && len(cleaned) > start+1 && isDigit(cleaned[start+1]) {
322
return 0, unstable.NewParserError(b, "float integer part cannot have leading zeroes")
323
}
324
325
f, err := strconv.ParseFloat(string(cleaned), 64)
326
if err != nil {
327
return 0, unstable.NewParserError(b, "unable to parse float: %w", err)
328
}
329
330
return f, nil
331
}
332
333
func parseIntHex(b []byte) (int64, error) {
334
cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])
335
if err != nil {
336
return 0, err
337
}
338
339
i, err := strconv.ParseInt(string(cleaned), 16, 64)
340
if err != nil {
341
return 0, unstable.NewParserError(b, "couldn't parse hexadecimal number: %w", err)
342
}
343
344
return i, nil
345
}
346
347
func parseIntOct(b []byte) (int64, error) {
348
cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])
349
if err != nil {
350
return 0, err
351
}
352
353
i, err := strconv.ParseInt(string(cleaned), 8, 64)
354
if err != nil {
355
return 0, unstable.NewParserError(b, "couldn't parse octal number: %w", err)
356
}
357
358
return i, nil
359
}
360
361
func parseIntBin(b []byte) (int64, error) {
362
cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:])
363
if err != nil {
364
return 0, err
365
}
366
367
i, err := strconv.ParseInt(string(cleaned), 2, 64)
368
if err != nil {
369
return 0, unstable.NewParserError(b, "couldn't parse binary number: %w", err)
370
}
371
372
return i, nil
373
}
374
375
func isSign(b byte) bool {
376
return b == '+' || b == '-'
377
}
378
379
func parseIntDec(b []byte) (int64, error) {
380
cleaned, err := checkAndRemoveUnderscoresIntegers(b)
381
if err != nil {
382
return 0, err
383
}
384
385
startIdx := 0
386
387
if isSign(cleaned[0]) {
388
startIdx++
389
}
390
391
if len(cleaned) > startIdx+1 && cleaned[startIdx] == '0' {
392
return 0, unstable.NewParserError(b, "leading zero not allowed on decimal number")
393
}
394
395
i, err := strconv.ParseInt(string(cleaned), 10, 64)
396
if err != nil {
397
return 0, unstable.NewParserError(b, "couldn't parse decimal number: %w", err)
398
}
399
400
return i, nil
401
}
402
403
func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) {
404
start := 0
405
if b[start] == '+' || b[start] == '-' {
406
start++
407
}
408
409
if len(b) == start {
410
return b, nil
411
}
412
413
if b[start] == '_' {
414
return nil, unstable.NewParserError(b[start:start+1], "number cannot start with underscore")
415
}
416
417
if b[len(b)-1] == '_' {
418
return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore")
419
}
420
421
// fast path
422
i := 0
423
for ; i < len(b); i++ {
424
if b[i] == '_' {
425
break
426
}
427
}
428
if i == len(b) {
429
return b, nil
430
}
431
432
before := false
433
cleaned := make([]byte, i, len(b))
434
copy(cleaned, b)
435
436
for i++; i < len(b); i++ {
437
c := b[i]
438
if c == '_' {
439
if !before {
440
return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores")
441
}
442
before = false
443
} else {
444
before = true
445
cleaned = append(cleaned, c)
446
}
447
}
448
449
return cleaned, nil
450
}
451
452
func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) {
453
if b[0] == '_' {
454
return nil, unstable.NewParserError(b[0:1], "number cannot start with underscore")
455
}
456
457
if b[len(b)-1] == '_' {
458
return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore")
459
}
460
461
// fast path
462
i := 0
463
for ; i < len(b); i++ {
464
if b[i] == '_' {
465
break
466
}
467
}
468
if i == len(b) {
469
return b, nil
470
}
471
472
before := false
473
cleaned := make([]byte, 0, len(b))
474
475
for i := 0; i < len(b); i++ {
476
c := b[i]
477
478
switch c {
479
case '_':
480
if !before {
481
return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores")
482
}
483
if i < len(b)-1 && (b[i+1] == 'e' || b[i+1] == 'E') {
484
return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore before exponent")
485
}
486
before = false
487
case '+', '-':
488
// signed exponents
489
cleaned = append(cleaned, c)
490
before = false
491
case 'e', 'E':
492
if i < len(b)-1 && b[i+1] == '_' {
493
return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after exponent")
494
}
495
cleaned = append(cleaned, c)
496
case '.':
497
if i < len(b)-1 && b[i+1] == '_' {
498
return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after decimal point")
499
}
500
if i > 0 && b[i-1] == '_' {
501
return nil, unstable.NewParserError(b[i-1:i], "cannot have underscore before decimal point")
502
}
503
cleaned = append(cleaned, c)
504
default:
505
before = true
506
cleaned = append(cleaned, c)
507
}
508
}
509
510
return cleaned, nil
511
}
512
513
// isValidDate checks if a provided date is a date that exists.
514
func isValidDate(year int, month int, day int) bool {
515
return month > 0 && month < 13 && day > 0 && day <= daysIn(month, year)
516
}
517
518
// daysBefore[m] counts the number of days in a non-leap year
519
// before month m begins. There is an entry for m=12, counting
520
// the number of days before January of next year (365).
521
var daysBefore = [...]int32{
522
0,
523
31,
524
31 + 28,
525
31 + 28 + 31,
526
31 + 28 + 31 + 30,
527
31 + 28 + 31 + 30 + 31,
528
31 + 28 + 31 + 30 + 31 + 30,
529
31 + 28 + 31 + 30 + 31 + 30 + 31,
530
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
531
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
532
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
533
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
534
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
535
}
536
537
func daysIn(m int, year int) int {
538
if m == 2 && isLeap(year) {
539
return 29
540
}
541
return int(daysBefore[m] - daysBefore[m-1])
542
}
543
544
func isLeap(year int) bool {
545
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
546
}
547
548
func isDigit(r byte) bool {
549
return r >= '0' && r <= '9'
550
}
551
552