113 lines
4.5 KiB
Go
113 lines
4.5 KiB
Go
package parse
|
|
|
|
// TODO move some definitions into parsekit, as far as they match Go standard formatting of numbers.
|
|
|
|
import (
|
|
"math"
|
|
"strconv"
|
|
|
|
"git.makaay.nl/mauricem/go-parsekit/parse"
|
|
"git.makaay.nl/mauricem/go-toml/ast"
|
|
)
|
|
|
|
var (
|
|
// Integers are whole numbers. Positive numbers may be prefixed with a
|
|
// plus sign. Negative numbers are prefixed with a minus sign. For large
|
|
// numbers, you may use underscores between digits to enhance readability.
|
|
// Each underscore must be surrounded by at least one digit on each side.
|
|
// Leading zeros are not allowed.
|
|
integerPrefix = a.Signed(a.DigitNotZero.Then(a.Digits.Optional())).Or(a.Signed(a.Zero))
|
|
underscoreDigits = m.Drop(a.Underscore).Then(a.Digits)
|
|
integerSuffix = c.ZeroOrMore(underscoreDigits)
|
|
integer = integerPrefix.Then(integerSuffix)
|
|
integerToken = tok.Int64(nil, integer)
|
|
|
|
// Integer values -0 and +0 are valid and identical to an unprefixed zero.
|
|
plusZero = a.Plus.Then(a.Zero)
|
|
minusZero = a.Minus.Then(a.Zero)
|
|
|
|
// Non-negative integer values may also be expressed in hexadecimal, octal,
|
|
// or binary. In these formats, leading + is not allowed and leading zeros
|
|
// are allowed (after the prefix). Hex values are case insensitive.
|
|
// Underscores are allowed between digits (but not between the prefix
|
|
// and the value).
|
|
// Hexadecimal with prefix `0x`.
|
|
hexDigits = c.OneOrMore(a.HexDigit)
|
|
underscoreHexDigits = m.Drop(a.Underscore).Then(hexDigits)
|
|
hexadecimal = a.Rune('x').Then(tok.Str("x", hexDigits.Then(c.ZeroOrMore(underscoreHexDigits))))
|
|
// Octal with prefix `0o`.
|
|
octalDigits = c.OneOrMore(a.RuneRange('0', '7'))
|
|
underscoreOctalDigits = m.Drop(a.Underscore).Then(octalDigits)
|
|
octal = a.Rune('o').Then(tok.Str("o", octalDigits.Then(c.ZeroOrMore(underscoreOctalDigits))))
|
|
// Binary with prefix `0b`.
|
|
binaryDigits = c.OneOrMore(a.RuneRange('0', '1'))
|
|
underscoreBinaryDigits = m.Drop(a.Underscore).Then(binaryDigits)
|
|
binary = a.Rune('b').Then(tok.Str("b", binaryDigits.Then(c.ZeroOrMore(underscoreBinaryDigits))))
|
|
|
|
// A fractional part is a decimal point followed by one or more digits.
|
|
// Similar to integers, you may use underscores to enhance readability.
|
|
// Each underscore must be surrounded by at least one digit.
|
|
fractionalPart = a.Dot.Then(a.Digits).Then(c.ZeroOrMore(underscoreDigits))
|
|
|
|
// An exponent part is an E (upper or lower case) followed by an integer
|
|
// part (which follows the same rules as decimal integer values).
|
|
exponentPart = a.Runes('e', 'E').Then(integer)
|
|
|
|
// Floats should be implemented as IEEE 754 binary64 values.
|
|
// A float consists of an integer part (which follows the same rules as
|
|
// decimal integer values) followed by a fractional part and/or an exponent
|
|
// part. If both a fractional part and exponent part are present, the
|
|
// fractional part must precede the exponent part.
|
|
fractAndOrExp = c.Any(fractionalPart.Then(c.Optional(exponentPart)), exponentPart)
|
|
float = integer.Then(fractAndOrExp)
|
|
floatToken = tok.Float64(nil, float)
|
|
|
|
// Float values -0.0 and +0.0 are valid and should map according to IEEE 754.
|
|
zeroFloat = a.Signed(a.Str("0.0"))
|
|
|
|
// Special float values can also be expressed. They are always lowercase.
|
|
inf = a.Signed(a.Str("inf"))
|
|
nan = a.Signed(a.Str("nan"))
|
|
)
|
|
|
|
func (t *parser) parseNumber(p *parse.API) (*ast.Value, bool) {
|
|
switch {
|
|
case p.Accept(floatToken):
|
|
return ast.NewValue(ast.TypeFloat, p.Result.Tokens[0].Value.(float64)), true
|
|
case p.Accept(nan):
|
|
return ast.NewValue(ast.TypeFloat, math.NaN()), true
|
|
case p.Accept(inf):
|
|
if p.Result.Runes[0] == '-' {
|
|
return ast.NewValue(ast.TypeFloat, math.Inf(-1)), true
|
|
}
|
|
return ast.NewValue(ast.TypeFloat, math.Inf(+1)), true
|
|
case p.Accept(a.Zero):
|
|
return t.parseIntegerStartingWithZero(p)
|
|
case p.Accept(integerToken):
|
|
return ast.NewValue(ast.TypeInteger, p.Result.Tokens[0].Value.(int64)), true
|
|
default:
|
|
p.Expected("a number")
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
func (t *parser) parseIntegerStartingWithZero(p *parse.API) (*ast.Value, bool) {
|
|
var value int64
|
|
var err error
|
|
switch {
|
|
case p.Accept(hexadecimal):
|
|
value, err = strconv.ParseInt(p.Result.Tokens[0].Value.(string), 16, 64)
|
|
case p.Accept(octal):
|
|
value, err = strconv.ParseInt(p.Result.Tokens[0].Value.(string), 8, 64)
|
|
case p.Accept(binary):
|
|
value, err = strconv.ParseInt(p.Result.Tokens[0].Value.(string), 2, 64)
|
|
default:
|
|
return ast.NewValue(ast.TypeInteger, int64(0)), true
|
|
}
|
|
if err == nil {
|
|
return ast.NewValue(ast.TypeInteger, value), true
|
|
}
|
|
p.SetError("invalid integer value 0%s: %s", p.Result.String(), err)
|
|
return nil, false
|
|
}
|