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