From 278efd7dbebd04dac6e8b6a63f06855d6b809f38 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Tue, 18 Jun 2019 22:52:45 +0000 Subject: [PATCH] Implemented all number formats for TOML. --- toml.go | 14 ++++++++------ value_number.go | 42 +++++++++++++++++++++++++++++++++++++----- value_number_test.go | 27 ++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/toml.go b/toml.go index 00f3f52..af3405b 100644 --- a/toml.go +++ b/toml.go @@ -14,12 +14,14 @@ type cmdType string // Command types that are emitted by the parser. const ( - cComment cmdType = "comment" // a # comment at the end of the line - cKey = "key" // set key name - cKeyDot = "keydot" // new key stack level - cAssign = "assign" // assign a value - csetStrVal = "string" // set a string value - csetIntVal = "integer" // set an integer value + cComment cmdType = "comment" // a # comment at the end of the line + cKey = "key" // set key name + cKeyDot = "keydot" // new key stack level + cAssign = "assign" // assign a value + csetStrVal = "string" // set a string value + csetIntVal = "integer" // set an integer value + csetFloatVal = "float" // set a float value + csetBoolVal = "boolean" // set a boolean value ) type parser struct { diff --git a/value_number.go b/value_number.go index 2249274..6d73169 100644 --- a/value_number.go +++ b/value_number.go @@ -12,7 +12,7 @@ var ( // 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())) + integerPrefix = a.Signed(a.DigitNotZero.Then(a.Digits.Optional())).Or(a.Signed(zero)) underscoreDigits = m.Drop(a.Underscore).Then(a.Digits) integerSuffix = c.ZeroOrMore(underscoreDigits) integer = integerPrefix.Then(integerSuffix) @@ -39,18 +39,50 @@ var ( 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) + + // 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. + infinity = a.Signed(a.Str("inf")) + nan = a.Signed(a.Str("nan")) ) -func (t *parser) startInteger(p *parse.API) { +func (t *parser) startNumber(p *parse.API) { switch { + case p.Accept(float): + f, err := strconv.ParseFloat(p.Result().String(), 64) + if err != nil { + p.Error("Cannot parse value 0%s: %s", p.Result().String(), err) + } else { + t.emitCommand(csetFloatVal, f) + } + case p.Accept(zeroFloat): + f, _ := strconv.ParseFloat(p.Result().String(), 64) + t.emitCommand(csetFloatVal, f) case p.Accept(zero): p.Handle(t.startIntegerStartingWithZero) - case p.Accept(plusZero.Or(minusZero)): - t.emitCommand(csetIntVal, int64(0)) case p.Accept(tok.Int64(nil, integer)): t.emitCommand(csetIntVal, p.Result().Value(0).(int64)) default: - p.Expected("an integer value") + p.Expected("a number") } } diff --git a/value_number_test.go b/value_number_test.go index 34ac515..fd71760 100644 --- a/value_number_test.go +++ b/value_number_test.go @@ -6,7 +6,7 @@ import ( func TestInteger(t *testing.T) { for _, test := range []parseTest{ - {``, []string{`Error: unexpected end of file (expected an integer value) at start of file`}}, + {``, []string{`Error: unexpected end of file (expected a number) at start of file`}}, // Decimal {`0`, []string{`integer(0)`}}, {`+0`, []string{`integer(0)`}}, @@ -16,7 +16,7 @@ func TestInteger(t *testing.T) { {`+99`, []string{`integer(99)`}}, {`-17`, []string{`integer(-17)`}}, {`1234`, []string{`integer(1234)`}}, - {`_`, []string{`Error: unexpected input (expected an integer value) at start of file`}}, + {`_`, []string{`Error: unexpected input (expected a number) at start of file`}}, {`1_`, []string{`integer(1)`, `Error: unexpected input (expected end of file) at line 1, column 2`}}, {`1_000`, []string{`integer(1000)`}}, {`5_349_221`, []string{`integer(5349221)`}}, @@ -75,6 +75,27 @@ func TestInteger(t *testing.T) { `value out of range at line 1, column 74`}}, } { p := &parser{} - testParseHandler(t, p, p.startInteger, test) + testParseHandler(t, p, p.startNumber, test) + } +} + +func TestFloat(t *testing.T) { + for _, test := range []parseTest{ + {``, []string{`Error: unexpected end of file (expected a number) at start of file`}}, + {`0.0`, []string{`float(0)`}}, + {`+0.0`, []string{`float(0)`}}, + {`-0.0`, []string{`float(-0)`}}, + {`+1.0`, []string{`float(1)`}}, + {`3.1415`, []string{`float(3.1415)`}}, + {`-0.01`, []string{`float(-0.01)`}}, + {`5e+22`, []string{`float(5e+22)`}}, + {`1E6`, []string{`float(1e+06)`}}, + {`-2E-2`, []string{`float(-0.02)`}}, + {`6.626e-34`, []string{`float(6.626e-34)`}}, + {`224_617.445_991_228`, []string{`float(224617.445991228)`}}, + {`12_345.111_222e+1_2_3`, []string{`float(1.2345111222e+127)`}}, + } { + p := &parser{} + testParseHandler(t, p, p.startNumber, test) } }