Replace parser's int or float code with scanner

This commit is contained in:
Thomas Pelletier
2021-03-14 17:22:53 -04:00
parent 590d7faf65
commit 9a1cfcdd8e
2 changed files with 324 additions and 121 deletions
+190 -121
View File
@@ -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)
+134
View File
@@ -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