github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/p/demo/json/parser.gno (about)

     1  package json
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"strconv"
     7  
     8  	el "gno.land/p/demo/json/eisel_lemire"
     9  )
    10  
    11  const (
    12  	absMinInt64 = 1 << 63
    13  	maxInt64    = absMinInt64 - 1
    14  	maxUint64   = 1<<64 - 1
    15  )
    16  
    17  const unescapeStackBufSize = 64
    18  
    19  // PaseStringLiteral parses a string from the given byte slice.
    20  func ParseStringLiteral(data []byte) (string, error) {
    21  	var buf [unescapeStackBufSize]byte
    22  
    23  	bf, err := Unescape(data, buf[:])
    24  	if err != nil {
    25  		return "", errors.New("invalid string input found while parsing string value")
    26  	}
    27  
    28  	return string(bf), nil
    29  }
    30  
    31  // ParseBoolLiteral parses a boolean value from the given byte slice.
    32  func ParseBoolLiteral(data []byte) (bool, error) {
    33  	switch {
    34  	case bytes.Equal(data, trueLiteral):
    35  		return true, nil
    36  	case bytes.Equal(data, falseLiteral):
    37  		return false, nil
    38  	default:
    39  		return false, errors.New("JSON Error: malformed boolean value found while parsing boolean value")
    40  	}
    41  }
    42  
    43  // PaseFloatLiteral parses a float64 from the given byte slice.
    44  //
    45  // It utilizes double-precision (64-bit) floating-point format as defined
    46  // by the IEEE 754 standard, providing a decimal precision of approximately 15 digits.
    47  func ParseFloatLiteral(bytes []byte) (float64, error) {
    48  	if len(bytes) == 0 {
    49  		return -1, errors.New("JSON Error: empty byte slice found while parsing float value")
    50  	}
    51  
    52  	neg, bytes := trimNegativeSign(bytes)
    53  
    54  	var exponentPart []byte
    55  	for i, c := range bytes {
    56  		if lower(c) == 'e' {
    57  			exponentPart = bytes[i+1:]
    58  			bytes = bytes[:i]
    59  			break
    60  		}
    61  	}
    62  
    63  	man, exp10, err := extractMantissaAndExp10(bytes)
    64  	if err != nil {
    65  		return -1, err
    66  	}
    67  
    68  	if len(exponentPart) > 0 {
    69  		exp, err := strconv.Atoi(string(exponentPart))
    70  		if err != nil {
    71  			return -1, errors.New("JSON Error: invalid exponent value found while parsing float value")
    72  		}
    73  		exp10 += exp
    74  	}
    75  
    76  	// for fast float64 conversion
    77  	f, success := el.EiselLemire64(man, exp10, neg)
    78  	if !success {
    79  		return 0, nil
    80  	}
    81  
    82  	return f, nil
    83  }
    84  
    85  func ParseIntLiteral(bytes []byte) (int64, error) {
    86  	if len(bytes) == 0 {
    87  		return 0, errors.New("JSON Error: empty byte slice found while parsing integer value")
    88  	}
    89  
    90  	neg, bytes := trimNegativeSign(bytes)
    91  
    92  	var n uint64 = 0
    93  	for _, c := range bytes {
    94  		if notDigit(c) {
    95  			return 0, errors.New("JSON Error: non-digit characters found while parsing integer value")
    96  		}
    97  
    98  		if n > maxUint64/10 {
    99  			return 0, errors.New("JSON Error: numeric value exceeds the range limit")
   100  		}
   101  
   102  		n *= 10
   103  
   104  		n1 := n + uint64(c-'0')
   105  		if n1 < n {
   106  			return 0, errors.New("JSON Error: numeric value exceeds the range limit")
   107  		}
   108  
   109  		n = n1
   110  	}
   111  
   112  	if n > maxInt64 {
   113  		if neg && n == absMinInt64 {
   114  			return -absMinInt64, nil
   115  		}
   116  
   117  		return 0, errors.New("JSON Error: numeric value exceeds the range limit")
   118  	}
   119  
   120  	if neg {
   121  		return -int64(n), nil
   122  	}
   123  
   124  	return int64(n), nil
   125  }
   126  
   127  // extractMantissaAndExp10 parses a byte slice representing a decimal number and extracts the mantissa and the exponent of its base-10 representation.
   128  // It iterates through the bytes, constructing the mantissa by treating each byte as a digit.
   129  // If a decimal point is encountered, the function keeps track of the position of the decimal point to calculate the exponent.
   130  // The function ensures that:
   131  // - The number contains at most one decimal point.
   132  // - All characters in the byte slice are digits or a single decimal point.
   133  // - The resulting mantissa does not overflow a uint64.
   134  func extractMantissaAndExp10(bytes []byte) (uint64, int, error) {
   135  	var (
   136  		man          uint64
   137  		exp10        int
   138  		decimalFound bool
   139  	)
   140  
   141  	for _, c := range bytes {
   142  		if c == dot {
   143  			if decimalFound {
   144  				return 0, 0, errors.New("JSON Error: multiple decimal points found while parsing float value")
   145  			}
   146  			decimalFound = true
   147  			continue
   148  		}
   149  
   150  		if notDigit(c) {
   151  			return 0, 0, errors.New("JSON Error: non-digit characters found while parsing integer value")
   152  		}
   153  
   154  		digit := uint64(c - '0')
   155  
   156  		if man > (maxUint64-digit)/10 {
   157  			return 0, 0, errors.New("JSON Error: numeric value exceeds the range limit")
   158  		}
   159  
   160  		man = man*10 + digit
   161  
   162  		if decimalFound {
   163  			exp10--
   164  		}
   165  	}
   166  
   167  	return man, exp10, nil
   168  }
   169  
   170  func trimNegativeSign(bytes []byte) (bool, []byte) {
   171  	if bytes[0] == minus {
   172  		return true, bytes[1:]
   173  	}
   174  
   175  	return false, bytes
   176  }
   177  
   178  func notDigit(c byte) bool {
   179  	return (c & 0xF0) != 0x30
   180  }
   181  
   182  // lower converts a byte to lower case if it is an uppercase letter.
   183  func lower(c byte) byte {
   184  	return c | 0x20
   185  }