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
31
32
33
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
80
81
82
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
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
189
190
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
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
256
257
258
259
260
261
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
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
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
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
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
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
519
520
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
View as plain text