337 lines
6.2 KiB
Go
337 lines
6.2 KiB
Go
package ini
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
var (
|
|
runesTrue = []rune("true")
|
|
runesFalse = []rune("false")
|
|
)
|
|
|
|
var literalValues = [][]rune{
|
|
runesTrue,
|
|
runesFalse,
|
|
}
|
|
|
|
func isBoolValue(b []rune) bool {
|
|
for _, lv := range literalValues {
|
|
if isCaselessLitValue(lv, b) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isLitValue(want, have []rune) bool {
|
|
if len(have) < len(want) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(want); i++ {
|
|
if want[i] != have[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// isCaselessLitValue is a caseless value comparison, assumes want is already lower-cased for efficiency.
|
|
func isCaselessLitValue(want, have []rune) bool {
|
|
if len(have) < len(want) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(want); i++ {
|
|
if want[i] != unicode.ToLower(have[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// isNumberValue will return whether not the leading characters in
|
|
// a byte slice is a number. A number is delimited by whitespace or
|
|
// the newline token.
|
|
//
|
|
// A number is defined to be in a binary, octal, decimal (int | float), hex format,
|
|
// or in scientific notation.
|
|
func isNumberValue(b []rune) bool {
|
|
negativeIndex := 0
|
|
helper := numberHelper{}
|
|
needDigit := false
|
|
|
|
for i := 0; i < len(b); i++ {
|
|
negativeIndex++
|
|
|
|
switch b[i] {
|
|
case '-':
|
|
if helper.IsNegative() || negativeIndex != 1 {
|
|
return false
|
|
}
|
|
helper.Determine(b[i])
|
|
needDigit = true
|
|
continue
|
|
case 'e', 'E':
|
|
if err := helper.Determine(b[i]); err != nil {
|
|
return false
|
|
}
|
|
negativeIndex = 0
|
|
needDigit = true
|
|
continue
|
|
case 'b':
|
|
if helper.numberFormat == hex {
|
|
break
|
|
}
|
|
fallthrough
|
|
case 'o', 'x':
|
|
needDigit = true
|
|
if i == 0 {
|
|
return false
|
|
}
|
|
|
|
fallthrough
|
|
case '.':
|
|
if err := helper.Determine(b[i]); err != nil {
|
|
return false
|
|
}
|
|
needDigit = true
|
|
continue
|
|
}
|
|
|
|
if i > 0 && (isNewline(b[i:]) || isWhitespace(b[i])) {
|
|
return !needDigit
|
|
}
|
|
|
|
if !helper.CorrectByte(b[i]) {
|
|
return false
|
|
}
|
|
needDigit = false
|
|
}
|
|
|
|
return !needDigit
|
|
}
|
|
|
|
func isValid(b []rune) (bool, int, error) {
|
|
if len(b) == 0 {
|
|
// TODO: should probably return an error
|
|
return false, 0, nil
|
|
}
|
|
|
|
return isValidRune(b[0]), 1, nil
|
|
}
|
|
|
|
func isValidRune(r rune) bool {
|
|
return r != ':' && r != '=' && r != '[' && r != ']' && r != ' ' && r != '\n'
|
|
}
|
|
|
|
// ValueType is an enum that will signify what type
|
|
// the Value is
|
|
type ValueType int
|
|
|
|
func (v ValueType) String() string {
|
|
switch v {
|
|
case NoneType:
|
|
return "NONE"
|
|
case DecimalType:
|
|
return "FLOAT"
|
|
case IntegerType:
|
|
return "INT"
|
|
case StringType:
|
|
return "STRING"
|
|
case BoolType:
|
|
return "BOOL"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// ValueType enums
|
|
const (
|
|
NoneType = ValueType(iota)
|
|
DecimalType
|
|
IntegerType
|
|
StringType
|
|
QuotedStringType
|
|
BoolType
|
|
)
|
|
|
|
// Value is a union container
|
|
type Value struct {
|
|
Type ValueType
|
|
raw []rune
|
|
|
|
integer int64
|
|
decimal float64
|
|
boolean bool
|
|
str string
|
|
}
|
|
|
|
func newValue(t ValueType, base int, raw []rune) (Value, error) {
|
|
v := Value{
|
|
Type: t,
|
|
raw: raw,
|
|
}
|
|
var err error
|
|
|
|
switch t {
|
|
case DecimalType:
|
|
v.decimal, err = strconv.ParseFloat(string(raw), 64)
|
|
case IntegerType:
|
|
if base != 10 {
|
|
raw = raw[2:]
|
|
}
|
|
|
|
v.integer, err = strconv.ParseInt(string(raw), base, 64)
|
|
case StringType:
|
|
v.str = string(raw)
|
|
case QuotedStringType:
|
|
v.str = string(raw[1 : len(raw)-1])
|
|
case BoolType:
|
|
v.boolean = isCaselessLitValue(runesTrue, v.raw)
|
|
}
|
|
|
|
// issue 2253
|
|
//
|
|
// if the value trying to be parsed is too large, then we will use
|
|
// the 'StringType' and raw value instead.
|
|
if nerr, ok := err.(*strconv.NumError); ok && nerr.Err == strconv.ErrRange {
|
|
v.Type = StringType
|
|
v.str = string(raw)
|
|
err = nil
|
|
}
|
|
|
|
return v, err
|
|
}
|
|
|
|
// NewStringValue returns a Value type generated using a string input.
|
|
func NewStringValue(str string) (Value, error) {
|
|
return newValue(StringType, 10, []rune(str))
|
|
}
|
|
|
|
// NewIntValue returns a Value type generated using an int64 input.
|
|
func NewIntValue(i int64) (Value, error) {
|
|
v := strconv.FormatInt(i, 10)
|
|
return newValue(IntegerType, 10, []rune(v))
|
|
}
|
|
|
|
func (v Value) String() string {
|
|
switch v.Type {
|
|
case DecimalType:
|
|
return fmt.Sprintf("decimal: %f", v.decimal)
|
|
case IntegerType:
|
|
return fmt.Sprintf("integer: %d", v.integer)
|
|
case StringType:
|
|
return fmt.Sprintf("string: %s", string(v.raw))
|
|
case QuotedStringType:
|
|
return fmt.Sprintf("quoted string: %s", string(v.raw))
|
|
case BoolType:
|
|
return fmt.Sprintf("bool: %t", v.boolean)
|
|
default:
|
|
return "union not set"
|
|
}
|
|
}
|
|
|
|
func newLitToken(b []rune) (Token, int, error) {
|
|
n := 0
|
|
var err error
|
|
|
|
token := Token{}
|
|
if b[0] == '"' {
|
|
n, err = getStringValue(b)
|
|
if err != nil {
|
|
return token, n, err
|
|
}
|
|
|
|
token = newToken(TokenLit, b[:n], QuotedStringType)
|
|
} else if isNumberValue(b) {
|
|
var base int
|
|
base, n, err = getNumericalValue(b)
|
|
if err != nil {
|
|
return token, 0, err
|
|
}
|
|
|
|
value := b[:n]
|
|
vType := IntegerType
|
|
if contains(value, '.') || hasExponent(value) {
|
|
vType = DecimalType
|
|
}
|
|
token = newToken(TokenLit, value, vType)
|
|
token.base = base
|
|
} else if isBoolValue(b) {
|
|
n, err = getBoolValue(b)
|
|
|
|
token = newToken(TokenLit, b[:n], BoolType)
|
|
} else {
|
|
n, err = getValue(b)
|
|
token = newToken(TokenLit, b[:n], StringType)
|
|
}
|
|
|
|
return token, n, err
|
|
}
|
|
|
|
// IntValue returns an integer value
|
|
func (v Value) IntValue() int64 {
|
|
return v.integer
|
|
}
|
|
|
|
// FloatValue returns a float value
|
|
func (v Value) FloatValue() float64 {
|
|
return v.decimal
|
|
}
|
|
|
|
// BoolValue returns a bool value
|
|
func (v Value) BoolValue() bool {
|
|
return v.boolean
|
|
}
|
|
|
|
func isTrimmable(r rune) bool {
|
|
switch r {
|
|
case '\n', ' ':
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// StringValue returns the string value
|
|
func (v Value) StringValue() string {
|
|
switch v.Type {
|
|
case StringType:
|
|
return strings.TrimFunc(string(v.raw), isTrimmable)
|
|
case QuotedStringType:
|
|
// preserve all characters in the quotes
|
|
return string(removeEscapedCharacters(v.raw[1 : len(v.raw)-1]))
|
|
default:
|
|
return strings.TrimFunc(string(v.raw), isTrimmable)
|
|
}
|
|
}
|
|
|
|
func contains(runes []rune, c rune) bool {
|
|
for i := 0; i < len(runes); i++ {
|
|
if runes[i] == c {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func runeCompare(v1 []rune, v2 []rune) bool {
|
|
if len(v1) != len(v2) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(v1); i++ {
|
|
if v1[i] != v2[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|