325 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
package ini
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	runesTrue  = []rune("true")
 | 
						|
	runesFalse = []rune("false")
 | 
						|
)
 | 
						|
 | 
						|
var literalValues = [][]rune{
 | 
						|
	runesTrue,
 | 
						|
	runesFalse,
 | 
						|
}
 | 
						|
 | 
						|
func isBoolValue(b []rune) bool {
 | 
						|
	for _, lv := range literalValues {
 | 
						|
		if isLitValue(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
 | 
						|
}
 | 
						|
 | 
						|
// 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 = runeCompare(v.raw, runesTrue)
 | 
						|
	}
 | 
						|
 | 
						|
	// 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
 | 
						|
}
 | 
						|
 | 
						|
// Append will append values and change the type to a string
 | 
						|
// type.
 | 
						|
func (v *Value) Append(tok Token) {
 | 
						|
	r := tok.Raw()
 | 
						|
	if v.Type != QuotedStringType {
 | 
						|
		v.Type = StringType
 | 
						|
		r = tok.raw[1 : len(tok.raw)-1]
 | 
						|
	}
 | 
						|
	if tok.Type() != TokenLit {
 | 
						|
		v.raw = append(v.raw, tok.Raw()...)
 | 
						|
	} else {
 | 
						|
		v.raw = append(v.raw, r...)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 |