json: parsing of keywords

This commit is contained in:
Martin Atkins 2017-05-15 19:37:20 -07:00
parent f754328a91
commit f6bd122f4b
4 changed files with 193 additions and 1 deletions

20
zcl/json/didyoumean.go Normal file
View File

@ -0,0 +1,20 @@
package json
import (
"github.com/agext/levenshtein"
)
var keywords = []string{"false", "true", "null"}
// keywordSuggestion tries to find a valid JSON keyword that is close to the
// given string and returns it if found. If no keyword is close enough, returns
// the empty string.
func keywordSuggestion(given string) string {
for _, kw := range keywords {
dist := levenshtein.Distance(given, kw, nil)
if dist < 3 { // threshold determined experimentally
return kw
}
}
return ""
}

View File

@ -0,0 +1,49 @@
package json
import "testing"
func TestKeywordSuggestion(t *testing.T) {
tests := []struct {
Input, Want string
}{
{"true", "true"},
{"false", "false"},
{"null", "null"},
{"bananas", ""},
{"NaN", ""},
{"Inf", ""},
{"Infinity", ""},
{"void", ""},
{"undefined", ""},
{"ture", "true"},
{"tru", "true"},
{"tre", "true"},
{"treu", "true"},
{"rtue", "true"},
{"flase", "false"},
{"fales", "false"},
{"flse", "false"},
{"fasle", "false"},
{"fasel", "false"},
{"flue", "false"},
{"nil", "null"},
{"nul", "null"},
{"unll", "null"},
{"nll", "null"},
}
for _, test := range tests {
t.Run(test.Input, func(t *testing.T) {
got := keywordSuggestion(test.Input)
if got != test.Want {
t.Errorf(
"wrong result\ninput: %q\ngot: %q\nwant: %q",
test.Input, got, test.Want,
)
}
})
}
}

View File

@ -1,6 +1,8 @@
package json
import (
"fmt"
"github.com/apparentlymart/go-zcl/zcl"
)
@ -180,5 +182,46 @@ func parseString(p *peeker) (node, zcl.Diagnostics) {
}
func parseKeyword(p *peeker) (node, zcl.Diagnostics) {
return nil, nil
tok := p.Read()
s := string(tok.Bytes)
switch s {
case "true":
return &booleanVal{
Value: true,
SrcRange: tok.Range,
}, nil
case "false":
return &booleanVal{
Value: false,
SrcRange: tok.Range,
}, nil
case "null":
return &nullVal{
SrcRange: tok.Range,
}, nil
case "undefined", "NaN", "Infinity":
return nil, zcl.Diagnostics{
{
Severity: zcl.DiagError,
Summary: "Invalid JSON keyword",
Detail: fmt.Sprintf("The JavaScript identifier %q cannot be used in JSON.", s),
Subject: &tok.Range,
},
}
default:
var dym string
if suggest := keywordSuggestion(s); suggest != "" {
dym = fmt.Sprintf(" Did you mean %q?", suggest)
}
return nil, zcl.Diagnostics{
{
Severity: zcl.DiagError,
Summary: "Invalid JSON keyword",
Detail: fmt.Sprintf("%q is not a valid JSON keyword.%s", s, dym),
Subject: &tok.Range,
},
}
}
}

80
zcl/json/parser_test.go Normal file
View File

@ -0,0 +1,80 @@
package json
import (
"reflect"
"testing"
"github.com/apparentlymart/go-zcl/zcl"
"github.com/davecgh/go-spew/spew"
)
func TestParse(t *testing.T) {
tests := []struct {
Input string
Want node
DiagCount int
}{
{
`true`,
&booleanVal{
Value: true,
SrcRange: zcl.Range{
Filename: "",
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 5, Byte: 4},
},
},
0,
},
{
`false`,
&booleanVal{
Value: false,
SrcRange: zcl.Range{
Filename: "",
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 6, Byte: 5},
},
},
0,
},
{
`null`,
&nullVal{
SrcRange: zcl.Range{
Filename: "",
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 5, Byte: 4},
},
},
0,
},
{
`undefined`,
nil,
1,
},
{
`flase`,
nil,
1,
},
}
for _, test := range tests {
t.Run(test.Input, func(t *testing.T) {
got, diag := parseFileContent([]byte(test.Input), "")
if len(diag) != test.DiagCount {
t.Errorf("got %d diagnostics; want %d\n%s", len(diag), test.DiagCount, spew.Sdump(diag))
}
if !reflect.DeepEqual(got, test.Want) {
t.Errorf(
"wrong result\ninput: %s\ngot: %#v\nwant: %#v",
test.Input, got, test.Want,
)
}
})
}
}