diff --git a/json/json.go b/json/json.go index 7c84f42..81b9523 100644 --- a/json/json.go +++ b/json/json.go @@ -1,23 +1,26 @@ package json -import ( - "errors" - - . "github.com/vektah/goparsify" -) +import "errors" +import . "github.com/vektah/goparsify" var ( - value Parser + _value Parser + _null = Bind("null", nil) + _true = Bind("true", true) + _false = Bind("false", false) + _string = StringLit(`"`) + _number = NumberLit() + _properties = Kleene(And(StringLit(`"`), ":", &_value), ",") - _array = Map(And("[", Kleene(&value, ","), "]"), func(n Node) Node { + _array = Map(And("[", Kleene(&_value, ","), "]"), func(n Node) Node { ret := []interface{}{} for _, child := range n.Children[1].Children { ret = append(ret, child.Result) } return Node{Result: ret} }) - properties = Kleene(And(StringLit(`"`), ":", &value), ",") - _object = Map(And("{", properties, "}"), func(n Node) Node { + + _object = Map(And("{", _properties, "}"), func(n Node) Node { ret := map[string]interface{}{} for _, prop := range n.Children[1].Children { @@ -26,20 +29,14 @@ var ( return Node{Result: ret} }) - - _null = Bind("null", nil) - _true = Bind("true", true) - _false = Bind("false", false) - _string = StringLit(`"`) - _number = NumberLit() ) func init() { - value = Any(_null, _true, _false, _string, _number, _array, _object) + _value = Any(_null, _true, _false, _string, _number, _array, _object) } func Unmarshal(input string) (interface{}, error) { - result, remaining, err := ParseString(value, input) + result, remaining, err := ParseString(_value, input) if err != nil { return result, err diff --git a/json/json_test.go b/json/json_test.go index 3cc7934..f020a40 100644 --- a/json/json_test.go +++ b/json/json_test.go @@ -41,8 +41,6 @@ func TestUnmarshal(t *testing.T) { }) } -const benchmarkString = `{"true":true, "false":false, "null": null}` - func BenchmarkUnmarshalParsec(b *testing.B) { bytes := []byte(benchmarkString) @@ -70,3 +68,93 @@ func BenchmarkUnmarshalStdlib(b *testing.B) { require.NoError(b, err) } } + +// This string was taken from http://json.org/example.html +const benchmarkString = `{"web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500}}, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2"}}, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet"}, + + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet"}, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true}}], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*"}, + + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld"}}}` diff --git a/json/profile/json.go b/json/profile/json.go index 0579fce..a61e966 100644 --- a/json/profile/json.go +++ b/json/profile/json.go @@ -31,10 +31,10 @@ func main() { } }() } - max := 1000000 + max := 100000 if *memprofile != "" { runtime.MemProfileRate = 1 - max = 100000 + max = 1000 defer func() { f, err := os.Create(*memprofile) if err != nil { @@ -47,9 +47,99 @@ func main() { } for i := 0; i < max; i++ { - _, err := json.Unmarshal(`{"true":true, "false":false, "null": null}`) + _, err := json.Unmarshal(benchmarkString) if err != nil { panic(err) } } } + +// This string was taken from http://json.org/example.html +const benchmarkString = `{"web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500}}, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2"}}, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet"}, + + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet"}, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true}}], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*"}, + + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld"}}}` diff --git a/literals.go b/literals.go index 5dd9fef..70832f9 100644 --- a/literals.go +++ b/literals.go @@ -67,9 +67,16 @@ func StringLit(allowedQuotes string) Parser { ps.Pos = end + 1 return Node{Result: buf.String()} default: - r, w := utf8.DecodeRuneInString(ps.Input[end:]) - end += w - if buf != nil { + if buf == nil { + if ps.Input[end] < 127 { + end++ + } else { + _, w := utf8.DecodeRuneInString(ps.Input[end:]) + end += w + } + } else { + r, w := utf8.DecodeRuneInString(ps.Input[end:]) + end += w buf.WriteRune(r) } } diff --git a/literals_test.go b/literals_test.go index 83e087b..e96f303 100644 --- a/literals_test.go +++ b/literals_test.go @@ -56,6 +56,12 @@ func TestStringLit(t *testing.T) { require.Equal(t, ``, p.Get()) }) + t.Run("test unicode chars", func(t *testing.T) { + result, p := runParser(`"hello 👺 my little goblin"`, parser) + require.Equal(t, `hello 👺 my little goblin`, result.Result) + require.Equal(t, ``, p.Get()) + }) + t.Run("test escaped unicode", func(t *testing.T) { result, p := runParser(`"hello \ubeef cake"`, parser) require.Equal(t, "", p.Error.Expected)