1package ini
2
3import (
4	"fmt"
5	"strconv"
6	"strings"
7)
8
9var (
10	runesTrue  = []rune("true")
11	runesFalse = []rune("false")
12)
13
14var literalValues = [][]rune{
15	runesTrue,
16	runesFalse,
17}
18
19func isBoolValue(b []rune) bool {
20	for _, lv := range literalValues {
21		if isLitValue(lv, b) {
22			return true
23		}
24	}
25	return false
26}
27
28func isLitValue(want, have []rune) bool {
29	if len(have) < len(want) {
30		return false
31	}
32
33	for i := 0; i < len(want); i++ {
34		if want[i] != have[i] {
35			return false
36		}
37	}
38
39	return true
40}
41
42// isNumberValue will return whether not the leading characters in
43// a byte slice is a number. A number is delimited by whitespace or
44// the newline token.
45//
46// A number is defined to be in a binary, octal, decimal (int | float), hex format,
47// or in scientific notation.
48func isNumberValue(b []rune) bool {
49	negativeIndex := 0
50	helper := numberHelper{}
51	needDigit := false
52
53	for i := 0; i < len(b); i++ {
54		negativeIndex++
55
56		switch b[i] {
57		case '-':
58			if helper.IsNegative() || negativeIndex != 1 {
59				return false
60			}
61			helper.Determine(b[i])
62			needDigit = true
63			continue
64		case 'e', 'E':
65			if err := helper.Determine(b[i]); err != nil {
66				return false
67			}
68			negativeIndex = 0
69			needDigit = true
70			continue
71		case 'b':
72			if helper.numberFormat == hex {
73				break
74			}
75			fallthrough
76		case 'o', 'x':
77			needDigit = true
78			if i == 0 {
79				return false
80			}
81
82			fallthrough
83		case '.':
84			if err := helper.Determine(b[i]); err != nil {
85				return false
86			}
87			needDigit = true
88			continue
89		}
90
91		if i > 0 && (isNewline(b[i:]) || isWhitespace(b[i])) {
92			return !needDigit
93		}
94
95		if !helper.CorrectByte(b[i]) {
96			return false
97		}
98		needDigit = false
99	}
100
101	return !needDigit
102}
103
104func isValid(b []rune) (bool, int, error) {
105	if len(b) == 0 {
106		// TODO: should probably return an error
107		return false, 0, nil
108	}
109
110	return isValidRune(b[0]), 1, nil
111}
112
113func isValidRune(r rune) bool {
114	return r != ':' && r != '=' && r != '[' && r != ']' && r != ' ' && r != '\n'
115}
116
117// ValueType is an enum that will signify what type
118// the Value is
119type ValueType int
120
121func (v ValueType) String() string {
122	switch v {
123	case NoneType:
124		return "NONE"
125	case DecimalType:
126		return "FLOAT"
127	case IntegerType:
128		return "INT"
129	case StringType:
130		return "STRING"
131	case BoolType:
132		return "BOOL"
133	}
134
135	return ""
136}
137
138// ValueType enums
139const (
140	NoneType = ValueType(iota)
141	DecimalType
142	IntegerType
143	StringType
144	QuotedStringType
145	BoolType
146)
147
148// Value is a union container
149type Value struct {
150	Type ValueType
151	raw  []rune
152
153	integer int64
154	decimal float64
155	boolean bool
156	str     string
157}
158
159func newValue(t ValueType, base int, raw []rune) (Value, error) {
160	v := Value{
161		Type: t,
162		raw:  raw,
163	}
164	var err error
165
166	switch t {
167	case DecimalType:
168		v.decimal, err = strconv.ParseFloat(string(raw), 64)
169	case IntegerType:
170		if base != 10 {
171			raw = raw[2:]
172		}
173
174		v.integer, err = strconv.ParseInt(string(raw), base, 64)
175	case StringType:
176		v.str = string(raw)
177	case QuotedStringType:
178		v.str = string(raw[1 : len(raw)-1])
179	case BoolType:
180		v.boolean = runeCompare(v.raw, runesTrue)
181	}
182
183	// issue 2253
184	//
185	// if the value trying to be parsed is too large, then we will use
186	// the 'StringType' and raw value instead.
187	if nerr, ok := err.(*strconv.NumError); ok && nerr.Err == strconv.ErrRange {
188		v.Type = StringType
189		v.str = string(raw)
190		err = nil
191	}
192
193	return v, err
194}
195
196// Append will append values and change the type to a string
197// type.
198func (v *Value) Append(tok Token) {
199	r := tok.Raw()
200	if v.Type != QuotedStringType {
201		v.Type = StringType
202		r = tok.raw[1 : len(tok.raw)-1]
203	}
204	if tok.Type() != TokenLit {
205		v.raw = append(v.raw, tok.Raw()...)
206	} else {
207		v.raw = append(v.raw, r...)
208	}
209}
210
211func (v Value) String() string {
212	switch v.Type {
213	case DecimalType:
214		return fmt.Sprintf("decimal: %f", v.decimal)
215	case IntegerType:
216		return fmt.Sprintf("integer: %d", v.integer)
217	case StringType:
218		return fmt.Sprintf("string: %s", string(v.raw))
219	case QuotedStringType:
220		return fmt.Sprintf("quoted string: %s", string(v.raw))
221	case BoolType:
222		return fmt.Sprintf("bool: %t", v.boolean)
223	default:
224		return "union not set"
225	}
226}
227
228func newLitToken(b []rune) (Token, int, error) {
229	n := 0
230	var err error
231
232	token := Token{}
233	if b[0] == '"' {
234		n, err = getStringValue(b)
235		if err != nil {
236			return token, n, err
237		}
238
239		token = newToken(TokenLit, b[:n], QuotedStringType)
240	} else if isNumberValue(b) {
241		var base int
242		base, n, err = getNumericalValue(b)
243		if err != nil {
244			return token, 0, err
245		}
246
247		value := b[:n]
248		vType := IntegerType
249		if contains(value, '.') || hasExponent(value) {
250			vType = DecimalType
251		}
252		token = newToken(TokenLit, value, vType)
253		token.base = base
254	} else if isBoolValue(b) {
255		n, err = getBoolValue(b)
256
257		token = newToken(TokenLit, b[:n], BoolType)
258	} else {
259		n, err = getValue(b)
260		token = newToken(TokenLit, b[:n], StringType)
261	}
262
263	return token, n, err
264}
265
266// IntValue returns an integer value
267func (v Value) IntValue() int64 {
268	return v.integer
269}
270
271// FloatValue returns a float value
272func (v Value) FloatValue() float64 {
273	return v.decimal
274}
275
276// BoolValue returns a bool value
277func (v Value) BoolValue() bool {
278	return v.boolean
279}
280
281func isTrimmable(r rune) bool {
282	switch r {
283	case '\n', ' ':
284		return true
285	}
286	return false
287}
288
289// StringValue returns the string value
290func (v Value) StringValue() string {
291	switch v.Type {
292	case StringType:
293		return strings.TrimFunc(string(v.raw), isTrimmable)
294	case QuotedStringType:
295		// preserve all characters in the quotes
296		return string(removeEscapedCharacters(v.raw[1 : len(v.raw)-1]))
297	default:
298		return strings.TrimFunc(string(v.raw), isTrimmable)
299	}
300}
301
302func contains(runes []rune, c rune) bool {
303	for i := 0; i < len(runes); i++ {
304		if runes[i] == c {
305			return true
306		}
307	}
308
309	return false
310}
311
312func runeCompare(v1 []rune, v2 []rune) bool {
313	if len(v1) != len(v2) {
314		return false
315	}
316
317	for i := 0; i < len(v1); i++ {
318		if v1[i] != v2[i] {
319			return false
320		}
321	}
322
323	return true
324}
325