diff --git a/zcl/json/parser.go b/zcl/json/parser.go index 953092b..7e13674 100644 --- a/zcl/json/parser.go +++ b/zcl/json/parser.go @@ -3,6 +3,7 @@ package json import ( "encoding/json" "fmt" + "math/big" "github.com/apparentlymart/go-zcl/zcl" ) @@ -207,7 +208,41 @@ func parseArray(p *peeker) (node, zcl.Diagnostics) { } func parseNumber(p *peeker) (node, zcl.Diagnostics) { - return nil, nil + tok := p.Read() + + // Use encoding/json to validate the number syntax. + // TODO: Do this more directly to produce better diagnostics. + var num json.Number + err := json.Unmarshal(tok.Bytes, &num) + if err != nil { + return nil, zcl.Diagnostics{ + { + Severity: zcl.DiagError, + Summary: "Invalid JSON number", + Detail: fmt.Sprintf("There is a syntax error in the given JSON number."), + Subject: &tok.Range, + }, + } + } + + f, _, err := (&big.Float{}).Parse(string(num), 10) + if err != nil { + // Should never happen if above passed, since JSON numbers are a subset + // of what big.Float can parse... + return nil, zcl.Diagnostics{ + { + Severity: zcl.DiagError, + Summary: "Invalid JSON number", + Detail: fmt.Sprintf("There is a syntax error in the given JSON number."), + Subject: &tok.Range, + }, + } + } + + return &numberVal{ + Value: f, + SrcRange: tok.Range, + }, nil } func parseString(p *peeker) (node, zcl.Diagnostics) { diff --git a/zcl/json/parser_test.go b/zcl/json/parser_test.go index 001dc93..78021db 100644 --- a/zcl/json/parser_test.go +++ b/zcl/json/parser_test.go @@ -1,6 +1,7 @@ package json import ( + "math/big" "reflect" "testing" @@ -111,6 +112,82 @@ func TestParse(t *testing.T) { nil, 1, }, + { + `1`, + &numberVal{ + Value: mustBigFloat("1"), + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 2, Byte: 1}, + }, + }, + 0, + }, + { + `1.2`, + &numberVal{ + Value: mustBigFloat("1.2"), + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 4, Byte: 3}, + }, + }, + 0, + }, + { + `-1`, + &numberVal{ + Value: mustBigFloat("-1"), + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 3, Byte: 2}, + }, + }, + 0, + }, + { + `1.2e5`, + &numberVal{ + Value: mustBigFloat("120000"), + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + }, + 0, + }, + { + `1.2e+5`, + &numberVal{ + Value: mustBigFloat("120000"), + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + }, + 0, + }, + { + `1.2e-5`, + &numberVal{ + Value: mustBigFloat("1.2e-5"), + SrcRange: zcl.Range{ + Start: zcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: zcl.Pos{Line: 1, Column: 7, Byte: 6}, + }, + }, + 0, + }, + { + `.1`, + nil, + 1, + }, + { + `+2`, + nil, + 1, + }, // Objects { @@ -259,3 +336,11 @@ func TestParse(t *testing.T) { }) } } + +func mustBigFloat(s string) *big.Float { + f, _, err := (&big.Float{}).Parse(s, 10) + if err != nil { + panic(err) + } + return f +}