diff --git a/internal/unmarshaler/parser.go b/internal/unmarshaler/parser.go index 323adee..a9708bb 100644 --- a/internal/unmarshaler/parser.go +++ b/internal/unmarshaler/parser.go @@ -231,8 +231,8 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { b, err := p.parseInlineTable(&node, b) return node, b, err default: - // TODO - //return p.parseIntOrFloatOrDateTime(b) + b, err = p.parseIntOrFloatOrDateTime(&node, b) + return node, b, err } panic("parseVal not finished yet") return ast.Node{}, nil, nil @@ -614,28 +614,28 @@ func (p *parser) parseWhitespace(b []byte) []byte { return rest } -func (p *parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { +func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, error) { switch b[0] { case 'i': if !scanFollowsInf(b) { return nil, fmt.Errorf("expected 'inf'") } - //p.builder.FloatValue(math.Inf(1)) - // TODO + node.Kind = ast.Float + node.Data = b[:3] return b[3:], nil case 'n': if !scanFollowsNan(b) { return nil, fmt.Errorf("expected 'nan'") } - //p.builder.FloatValue(math.NaN()) - // TODO + node.Kind = ast.Float + node.Data = b[:3] return b[3:], nil case '+', '-': - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } if len(b) < 3 { - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } s := 5 if len(b) < s { @@ -652,7 +652,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) ([]byte, error) { return p.parseDateTime(b) } } - return p.parseIntOrFloat(b) + return p.scanIntOrFloat(node, b) } func digitsToInt(b []byte) int { @@ -925,135 +925,204 @@ func (p *parser) parseTime(b []byte) ([]byte, error) { return b[idx:], nil } -func (p *parser) parseIntOrFloat(b []byte) ([]byte, error) { +func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { i := 0 - r := b[0] - if r == '0' { - if len(b) >= 2 { - var isValidRune validRuneFn - var parseFn func([]byte) (int64, error) - switch b[1] { - case 'x': - isValidRune = isValidHexRune - parseFn = parseIntHex - case 'o': - isValidRune = isValidOctalRune - parseFn = parseIntOct - case 'b': - isValidRune = isValidBinaryRune - parseFn = parseIntBin - default: - if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { - return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) - } - parseFn = parseIntDec - } - if isValidRune != nil { - i = 2 - digitSeen := false - for { - if !isValidRune(b[i]) { - break - } - digitSeen = true - i++ - } + if len(b) > 2 && b[0] == '0' { + var isValidRune validRuneFn + switch b[1] { + case 'x': + isValidRune = isValidHexRune + case 'o': + isValidRune = isValidOctalRune + case 'b': + isValidRune = isValidBinaryRune + default: + return b, fmt.Errorf("unknown number base: %c. possible options are x (hex) o (octal) b (binary)", b[1]) + } - if !digitSeen { - return nil, fmt.Errorf("number needs at least one digit") - } - - v, err := parseFn(b[:i]) - if err != nil { - return nil, err - } - //p.builder.IntValue(v) - // TODO - v = v + i += 2 + for ; i < len(b); i++ { + if !isValidRune(b[i]) { + node.Kind = ast.Integer + node.Data = b[:i] return b[i:], nil } } } - if r == '+' || r == '-' { - b = b[1:] - if scanFollowsInf(b) { - if r == '+' { - //p.builder.FloatValue(plusInf) - // TODO - } else { - //p.builder.FloatValue(minusInf) - // TODO + isFloat := false + + for ; i < len(b); i++ { + c := b[i] + + if c >= '0' && c <= '9' || c == '+' || c == '-' || c == '_' { + continue + } + + if c == '.' || c == 'e' || c == 'E' { + isFloat = true + continue + } + + if c == 'i' { + if scanFollowsInf(b[i:]) { + node.Kind = ast.Float + node.Data = b[:i+3] + return b[i+3:], nil } - return b, nil + return nil, fmt.Errorf("unexpected character i while scanning for a number") } - if scanFollowsNan(b) { - //p.builder.FloatValue(nan) - // TODO - return b, nil + if c == 'n' { + if scanFollowsNan(b[i:]) { + node.Kind = ast.Float + node.Data = b[:i+3] + return b[i+3:], nil + } + return nil, fmt.Errorf("unexpected character n while scanning for a number") } + + break } - pointSeen := false - expSeen := false - digitSeen := false - for i < len(b) { - next := b[i] - if next == '.' { - if pointSeen { - return nil, fmt.Errorf("cannot have two dots in one float") - } - i++ - if i < len(b) && !isDigit(b[i]) { - return nil, fmt.Errorf("float cannot end with a dot") - } - pointSeen = true - } else if next == 'e' || next == 'E' { - expSeen = true - i++ - if i >= len(b) { - break - } - if b[i] == '+' || b[i] == '-' { - i++ - } - } else if isDigit(next) { - digitSeen = true - i++ - } else if next == '_' { - i++ - } else { - break - } - if pointSeen && !digitSeen { - return nil, fmt.Errorf("cannot start float with a dot") - } - } - - if !digitSeen { - return nil, fmt.Errorf("no digit in that number") - } - if pointSeen || expSeen { - f, err := parseFloat(b[:i]) - if err != nil { - return nil, err - } - //p.builder.FloatValue(f) - // TODO - f = f + if isFloat { + node.Kind = ast.Float } else { - v, err := parseIntDec(b[:i]) - if err != nil { - return nil, err - } - //p.builder.IntValue(v) - // TODO - v = v + node.Kind = ast.Integer } + node.Data = b[:i] return b[i:], nil } +//func (p *parser) parseIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { +// i := 0 +// r := b[0] +// if r == '0' { +// if len(b) >= 2 { +// var isValidRune validRuneFn +// var parseFn func([]byte) (int64, error) +// switch b[1] { +// case 'x': +// isValidRune = isValidHexRune +// parseFn = parseIntHex +// case 'o': +// isValidRune = isValidOctalRune +// parseFn = parseIntOct +// case 'b': +// isValidRune = isValidBinaryRune +// parseFn = parseIntBin +// default: +// if b[1] >= 'a' && b[1] <= 'z' || b[1] >= 'A' && b[1] <= 'Z' { +// return nil, fmt.Errorf("unknown number base: %s. possible options are x (hex) o (octal) b (binary)", string(b[1])) +// } +// parseFn = parseIntDec +// } +// +// if isValidRune != nil { +// i = 2 +// digitSeen := false +// for { +// if !isValidRune(b[i]) { +// break +// } +// digitSeen = true +// i++ +// } +// +// if !digitSeen { +// return nil, fmt.Errorf("number needs at least one digit") +// } +// +// v, err := parseFn(b[:i]) +// if err != nil { +// return nil, err +// } +// //p.builder.IntValue(v) +// // TODO +// v = v +// return b[i:], nil +// } +// } +// } +// +// if r == '+' || r == '-' { +// b = b[1:] +// if scanFollowsInf(b) { +// if r == '+' { +// //p.builder.FloatValue(plusInf) +// // TODO +// } else { +// //p.builder.FloatValue(minusInf) +// // TODO +// } +// return b, nil +// } +// if scanFollowsNan(b) { +// //p.builder.FloatValue(nan) +// // TODO +// return b, nil +// } +// } +// +// pointSeen := false +// expSeen := false +// digitSeen := false +// for i < len(b) { +// next := b[i] +// if next == '.' { +// if pointSeen { +// return nil, fmt.Errorf("cannot have two dots in one float") +// } +// i++ +// if i < len(b) && !isDigit(b[i]) { +// return nil, fmt.Errorf("float cannot end with a dot") +// } +// pointSeen = true +// } else if next == 'e' || next == 'E' { +// expSeen = true +// i++ +// if i >= len(b) { +// break +// } +// if b[i] == '+' || b[i] == '-' { +// i++ +// } +// } else if isDigit(next) { +// digitSeen = true +// i++ +// } else if next == '_' { +// i++ +// } else { +// break +// } +// if pointSeen && !digitSeen { +// return nil, fmt.Errorf("cannot start float with a dot") +// } +// } +// +// if !digitSeen { +// return nil, fmt.Errorf("no digit in that number") +// } +// if pointSeen || expSeen { +// f, err := parseFloat(b[:i]) +// if err != nil { +// return nil, err +// } +// //p.builder.FloatValue(f) +// // TODO +// f = f +// } else { +// v, err := parseIntDec(b[:i]) +// if err != nil { +// return nil, err +// } +// //p.builder.IntValue(v) +// // TODO +// v = v +// } +// return b[i:], nil +//} + func parseFloat(b []byte) (float64, error) { // TODO: inefficient tok := string(b) diff --git a/internal/unmarshaler/parser_test.go b/internal/unmarshaler/parser_test.go index 3052d9a..250da3e 100644 --- a/internal/unmarshaler/parser_test.go +++ b/internal/unmarshaler/parser_test.go @@ -7,6 +7,140 @@ import ( "github.com/stretchr/testify/require" ) +func TestParser_Numbers(t *testing.T) { + examples := []struct { + desc string + input string + kind ast.Kind + err bool + }{ + { + desc: "integer just digits", + input: `1234`, + kind: ast.Integer, + }, + { + desc: "integer zero", + input: `0`, + kind: ast.Integer, + }, + { + desc: "integer sign", + input: `+99`, + kind: ast.Integer, + }, + { + desc: "integer hex uppercase", + input: `0xDEADBEEF`, + kind: ast.Integer, + }, + { + desc: "integer hex lowercase", + input: `0xdead_beef`, + kind: ast.Integer, + }, + { + desc: "integer octal", + input: `0o01234567`, + kind: ast.Integer, + }, + { + desc: "integer binary", + input: `0b11010110`, + kind: ast.Integer, + }, + { + desc: "float pi", + input: `3.1415`, + kind: ast.Float, + }, + { + desc: "float negative", + input: `-0.01`, + kind: ast.Float, + }, + { + desc: "float signed exponent", + input: `5e+22`, + kind: ast.Float, + }, + { + desc: "float exponent lowercase", + input: `1e06`, + kind: ast.Float, + }, + { + desc: "float exponent uppercase", + input: `-2E-2`, + kind: ast.Float, + }, + { + desc: "float fractional with exponent", + input: `6.626e-34`, + kind: ast.Float, + }, + { + desc: "float underscores", + input: `224_617.445_991_228`, + kind: ast.Float, + }, + { + desc: "inf", + input: `inf`, + kind: ast.Float, + }, + { + desc: "inf negative", + input: `-inf`, + kind: ast.Float, + }, + { + desc: "inf positive", + input: `+inf`, + kind: ast.Float, + }, + { + desc: "nan", + input: `nan`, + kind: ast.Float, + }, + { + desc: "nan negative", + input: `-nan`, + kind: ast.Float, + }, + { + desc: "nan positive", + input: `+nan`, + kind: ast.Float, + }, + } + + for _, e := range examples { + t.Run(e.desc, func(t *testing.T) { + p := parser{} + err := p.parse([]byte(`A = ` + e.input)) + if e.err { + require.Error(t, err) + } else { + require.NoError(t, err) + + expected := ast.Root{ + ast.Node{ + Kind: ast.KeyValue, + Children: []ast.Node{ + {Kind: ast.Key, Data: []byte(`A`)}, + {Kind: e.kind, Data: []byte(e.input)}, + }, + }, + } + + require.Equal(t, expected, p.tree) + } + }) + } +} + func TestParser_AST(t *testing.T) { examples := []struct { desc string