gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

decode.go (12813B)


      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' && 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 }