on master help sign in
write parse error messages specific to JSON
commitd635ede92353587a77c5db0e23e012121e8b7887 authorEvan Czaplicki <evancz@users.noreply.github.com> dateTue 11 Jun 2019, 18:08 (7 years ago) parent7c2dedf
2 files changed+222−116
compiler/src/Json/Decode.hs+28−34 compiler/src/Reporting/Error/Json.hs+194−82
compiler/src/Json/Decode.hs+28−34
@@ -24,6 +24,7 @@ module Json.Decode
2424 , Problem(..)
2525 , DecodeExpectation(..)
2626 , ParseError(..)
27+ , StringProblem(..)
2728 )
2829 where
2930
@@ -49,10 +50,10 @@ fromByteString :: Decoder x a -> B.ByteString -> Either (Error x) a
4950fromByteString (Decoder decode) src =
5051 case P.fromByteString pFile BadEnd src of
5152 Right ast ->
52− decode ast Right (Left . DecodeProblem)
53+ decode ast Right (Left . DecodeProblem src)
5354
5455 Left problem ->
55− Left (ParseProblem problem)
56+ Left (ParseProblem src problem)
5657
5758
5859
@@ -75,8 +76,8 @@ newtype Decoder x a =
7576
7677
7778data Error x
78− = DecodeProblem (Problem x)
79− | ParseProblem ParseError
79+ = DecodeProblem B.ByteString (Problem x)
80+ | ParseProblem B.ByteString ParseError
8081
8182
8283
@@ -460,21 +461,14 @@ type Parser a =
460461
461462
462463data ParseError
463− = ObjectStart Row Col
464− | ObjectMore Row Col
464+ = Start Row Col
465+ | ObjectField Row Col
466+ | ObjectColon Row Col
465467 | ObjectEnd Row Col
466− | ArrayStart Row Col
467− | ArrayMore Row Col
468468 | ArrayEnd Row Col
469− | StringStart Row Col
470469 | StringProblem StringProblem Row Col
471− | IntStart Row Col
472470 | NoLeadingZeros Row Col
473471 | NoFloats Row Col
474− | Bool Row Col
475− | Null Row Col
476− | Value Row Col
477− | Colon Row Col
478472 | BadEnd Row Col
479473
480474-- PIndex Int ParseError Row Col
@@ -502,14 +496,14 @@ pFile =
502496
503497pValue :: Parser AST
504498pValue =
505− P.oneOf Value
506− [ String <$> pString
499+ P.oneOf Start
500+ [ String <$> pString Start
507501 , pObject
508502 , pArray
509503 , pInt
510− , K.k4 0x74 0x72 0x75 0x65 Bool >> return TRUE
511− , K.k5 0x66 0x61 0x6C 0x73 0x65 Bool >> return FALSE
512− , K.k4 0x6E 0x75 0x6C 0x6C Null >> return NULL
504+ , K.k4 0x74 0x72 0x75 0x65 Start >> return TRUE
505+ , K.k5 0x66 0x61 0x6C 0x73 0x65 Start >> return FALSE
506+ , K.k4 0x6E 0x75 0x6C 0x6C Start >> return NULL
513507 ]
514508
515509
@@ -519,9 +513,9 @@ pValue =
519513
520514pObject :: Parser AST
521515pObject =
522− do P.word1 0x7B {- { -} ObjectStart
516+ do P.word1 0x7B {- { -} Start
523517 spaces
524− P.oneOf ObjectMore
518+ P.oneOf ObjectField
525519 [ do entry <- pField
526520 spaces
527521 pObjectHelp [entry]
@@ -532,9 +526,9 @@ pObject =
532526
533527pObjectHelp :: [(P.Snippet, AST)] -> Parser AST
534528pObjectHelp revEntries =
535− P.oneOf ObjectMore
529+ P.oneOf ObjectEnd
536530 [
537− do P.word1 0x2C {-,-} ObjectMore
531+ do P.word1 0x2C {-,-} ObjectEnd
538532 spaces
539533 entry <- pField
540534 spaces
@@ -547,9 +541,9 @@ pObjectHelp revEntries =
547541
548542pField :: Parser (P.Snippet, AST)
549543pField =
550− do key <- pString
544+ do key <- pString ObjectField
551545 spaces
552− P.word1 0x3A {-:-} Colon
546+ P.word1 0x3A {-:-} ObjectColon
553547 spaces
554548 value <- pValue
555549 return (key, value)
@@ -561,9 +555,9 @@ pField =
561555
562556pArray :: Parser AST
563557pArray =
564− do P.word1 0x5B {-[-} ArrayStart
558+ do P.word1 0x5B {-[-} Start
565559 spaces
566− P.oneOf ArrayMore
560+ P.oneOf Start
567561 [ do entry <- pValue
568562 spaces
569563 pArrayHelp 1 [entry]
@@ -574,9 +568,9 @@ pArray =
574568
575569pArrayHelp :: Int -> [AST] -> Parser AST
576570pArrayHelp !len revEntries =
577− P.oneOf ArrayMore
571+ P.oneOf ArrayEnd
578572 [
579− do P.word1 0x2C {-,-} ArrayMore
573+ do P.word1 0x2C {-,-} ArrayEnd
580574 spaces
581575 entry <- pValue
582576 spaces
@@ -591,8 +585,8 @@ pArrayHelp !len revEntries =
591585-- STRING
592586
593587
594−pString :: Parser P.Snippet
595−pString =
588+pString :: (Row -> Col -> ParseError) -> Parser P.Snippet
589+pString start =
596590 P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
597591 if pos < end && P.unsafeIndex pos == 0x22 {-"-} then
598592
@@ -617,7 +611,7 @@ pString =
617611 cerr newRow newCol (StringProblem problem)
618612
619613 else
620− eerr row col StringStart
614+ eerr row col start
621615
622616
623617data StringStatus
@@ -726,12 +720,12 @@ pInt :: Parser AST
726720pInt =
727721 P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
728722 if pos >= end then
729− eerr row col IntStart
723+ eerr row col Start
730724
731725 else
732726 let !word = P.unsafeIndex pos in
733727 if not (isDecimalDigit word) then
734− eerr row col IntStart
728+ eerr row col Start
735729
736730 else if word == 0x30 {-0-} then
737731
compiler/src/Reporting/Error/Json.hs+194−82
@@ -1,112 +1,225 @@
11{-# OPTIONS_GHC -Wall #-}
22{-# LANGUAGE OverloadedStrings #-}
33module Reporting.Error.Json
4− ( Error(..)
5− , Problem(..)
6− , DecodeExpectation(..)
7− , ParseError(..)
8− , toReport
4+ ( toReport
95 )
106 where
117
128
13−import qualified Data.Utf8 as Utf8
14−import Data.Word (Word16)
9+import qualified Data.Char as Char
10+import qualified Data.Map as Map
1511
16−import qualified Reporting.Report as Report
12+import Json.Decode (Error(..), Problem(..), DecodeExpectation(..), ParseError(..), StringProblem(..))
13+import qualified Reporting.Annotation as A
14+import qualified Reporting.Doc as D
15+import qualified Reporting.Exit.Help as Help
16+import qualified Reporting.Render.Code as Code
1717
1818
1919
20−-- ERROR
21−
22−
23−data Error e
24− = DecodeProblem (Problem e)
25− | ParseProblem ParseError
26−
27−
28−
29−-- DECODE PROBLEMS
20+-- TO REPORT
3021
3122
32−data Problem e
33− = Field Utf8.String (Problem e)
34− | Index Int (Problem e)
35− | OneOf (Problem e) [Problem e]
36− | Failure e
37− | Expecting DecodeExpectation
23+toReport :: FilePath -> Error x -> Help.Report
24+toReport path err =
25+ case err of
26+ DecodeProblem bytes problem ->
27+ problemToReport (Code.toSource bytes) problem
3828
29+ ParseProblem bytes parseError ->
30+ parseErrorToReport path (Code.toSource bytes) parseError
3931
40−data DecodeExpectation
41− = TObject
42− | TArray
43− | TString
44− | TBool
45− | TInt
46− | TObjectWith Utf8.String
47− | TArrayPair Int
4832
4933
34+-- PARSE ERROR TO REPORT
5035
51−-- PARSE ERROR
5236
37+parseErrorToReport :: FilePath -> Code.Source -> ParseError -> Help.Report
38+parseErrorToReport path source parseError =
39+ let
40+ toSnippet title row col pair =
41+ let pos = A.Position row col in
42+ Help.jsonReport title (Just path) $
43+ Code.toSnippet source (A.Region pos pos) Nothing pair
44+ in
45+ case parseError of
46+ Start row col ->
47+ toSnippet "EXPECTING A VALUE" row col
48+ (
49+ D.reflow $ "I was expecting to see a JSON value next:"
50+ ,
51+ D.stack
52+ [ D.fillSep
53+ ["Try","something","like",D.dullyellow "\"this\"","or"
54+ ,D.dullyellow "42","to","move","on","to","better","hints!"
55+ ]
56+ , D.toSimpleNote $
57+ "The JSON specification does not allow trailing commas, so you can sometimes\
58+ \ get this error in arrays that have an extra comma at the end. In that case,\
59+ \ remove that last comma or add another array entry after it!"
60+ ]
61+ )
5362
54−data ParseError
55− = PIndex Int ParseError Row Col
56− | PField Utf8.String ParseError Row Col
57− | ObjectStart Row Col
58− | ObjectMore Row Col
59− | ObjectEnd Row Col
60− | ArrayStart Row Col
61− | ArrayMore Row Col
62− | ArrayEnd Row Col
63− | StringStart Row Col
64− | StringEnd Row Col
65− | IntStart Row Col
66− | NoLeadingZeros Row Col
67− | NoFloats Row Col
68− | Bool Row Col
69− | Null Row Col
70− | Value Row Col
71− | Colon Row Col
72− | EndOfFile Row Col
63+ ObjectField row col ->
64+ toSnippet "UNFINISHED OBJECT" row col
65+ (
66+ D.reflow $ "I was partway through parsing a JSON object when I got stuck here:"
67+ ,
68+ D.stack
69+ [ D.reflow $ "I was expecting to see a field name next."
70+ , objectNote
71+ ]
72+ )
7373
74+ ObjectColon row col ->
75+ toSnippet "EXPECTING COLON" row col
76+ (
77+ D.reflow $ "I was partway through parsing a JSON object when I got stuck here:"
78+ ,
79+ D.stack
80+ [ D.reflow $ "I was expecting to see a colon next."
81+ , objectNote
82+ ]
83+ )
7484
75−type Row = Word16
76−type Col = Word16
85+ ObjectEnd row col ->
86+ toSnippet "UNFINISHED OBJECT" row col
87+ (
88+ D.reflow $ "I was partway through parsing a JSON object when I got stuck here:"
89+ ,
90+ D.stack
91+ [ D.reflow $
92+ "I was expecting to see a comma or a closing curly brace next."
93+ , D.reflow $
94+ "Is a comma missing on the previous line? Is an array missing a closing square\
95+ \ bracket? It is often something tricky like that!"
96+ , objectNote
97+ ]
98+ )
7799
100+ ArrayEnd row col ->
101+ toSnippet "UNFINISHED ARRAY" row col
102+ (
103+ D.reflow $ "I was partway through parsing a JSON array when I got stuck here:"
104+ ,
105+ D.stack
106+ [ D.reflow $ "I was expecting to see a comma or a closing square bracket next."
107+ , D.reflow $
108+ "Is a comma missing on the previous line? It is often something like that!"
109+ ]
110+ )
111+
112+ StringProblem stringProblem row col ->
113+ case stringProblem of
114+ BadStringEnd ->
115+ toSnippet "ENDLESS STRING" row col
116+ (
117+ D.reflow $
118+ "I got to the end of the line without seeing the closing double quote:"
119+ ,
120+ D.fillSep $
121+ ["Strings","look","like",D.green "\"this\"","with","double"
122+ ,"quotes","on","each","end.","Is","the","closing","double"
123+ ,"quote","missing","in","your","code?"
124+ ]
125+ )
126+
127+ BadStringControlChar ->
128+ toSnippet "UNEXPECTED CONTROL CHARACTER" row col
129+ (
130+ D.reflow $
131+ "I ran into a control character unexpectedly:"
132+ ,
133+ D.reflow $
134+ "These are characters that represent tabs, backspaces, newlines, and\
135+ \ a bunch of other invisible characters. They all come before 20 in the\
136+ \ ASCII range, and they are disallowed by the JSON specificaiton. Maybe\
137+ \ a copy/paste added one of these invisible characters to your JSON?"
138+ )
139+
140+ BadStringEscapeChar ->
141+ toSnippet "UNKNOWN ESCAPE" row col
142+ (
143+ D.reflow $
144+ "Backslashes always start escaped characters, but I do not recognize this one:"
145+ ,
146+ D.stack
147+ [ D.reflow $
148+ "Valid escape characters include:"
149+ , D.dullyellow $ D.indent 4 $ D.vcat $
150+ ["\\\"","\\\\","\\/","\\b","\\f","\\n","\\r","\\t","\\u003D"]
151+ , D.reflow $
152+ "Do you want one of those instead? Maybe you need \\\\ to escape a backslash?"
153+ ]
154+ )
155+
156+ BadStringEscapeHex ->
157+ toSnippet "BAD HEX ESCAPE" row col
158+ (
159+ D.reflow $
160+ "This is not a valid hex escape:"
161+ ,
162+ D.fillSep $
163+ ["Valid","hex","escapes","in","JSON","are","between"
164+ ,D.green "\\u0000","and",D.green "\\uFFFF"
165+ ,"and","always","have","exactly","four","digits."
166+ ]
167+ )
168+
169+ NoLeadingZeros row col ->
170+ toSnippet "BAD NUMBER" row col
171+ (
172+ D.reflow $ "Numbers cannot start with zeros like this:"
173+ ,
174+ D.reflow $ "Try deleting the leading zeros?"
175+ )
176+
177+ NoFloats row col ->
178+ toSnippet "UNEXPECTED NUMBER" row col
179+ (
180+ D.reflow $ "I got stuck while trying to parse this number:"
181+ ,
182+ D.reflow $
183+ "I do not accept floating point numbers like 3.1415 right now. That kind\
184+ \ of JSON value is not needed for any of the uses that Elm has for now."
185+ )
186+
187+ BadEnd row col ->
188+ toSnippet "JSON PROBLEM" row col
189+ (
190+ D.reflow $ "I was partway through parsing some JSON when I got stuck here:"
191+ ,
192+ D.reflow $
193+ "I am not really sure what is wrong. This sometimes means there is extra\
194+ \ stuff after a valid JSON value?"
195+ )
196+
197+
198+objectNote :: D.Doc
199+objectNote =
200+ D.stack
201+ [ D.toSimpleNote $ "Here is an example of a valid JSON object for reference:"
202+ , D.vcat
203+ [ D.indent 4 $ "{"
204+ , D.indent 6 $ D.dullyellow "\"name\"" <> ": " <> D.dullyellow "\"Tom\"" <> ","
205+ , D.indent 6 $ D.dullyellow "\"age\"" <> ": " <> D.dullyellow "42"
206+ , D.indent 4 $ "}"
207+ ]
208+ , D.reflow $
209+ "Notice that (1) the field names are in double quotes and (2) there is no\
210+ \ trailing comma after the last entry. Both are strict requirements in JSON!"
211+ ]
78212
79213
80−-- TO REPORT
81214
215+-- PROBLEM TO REPORT
82216
83−toReport :: Error e -> Report.Report
84−toReport err =
85− error "TODO Reporting.Error.Json.toReport" err
86217
218+problemToReport :: Code.Source -> Problem x -> Help.Report
219+problemToReport source problem =
220+ error "TODO problemToReport" source problem
87221
88222{-
89−
90−-- ERROR
91−
92−
93−data Error e
94− = BadJson Syntax.Error
95− | BadContent (Json.Error e)
96−
97−
98−
99−-- TO DOC
100−
101−
102−toDoc :: String -> Code.Source -> (e -> [D.Doc]) -> Error e -> D.Doc
103−toDoc rootName source userErrorToDocs err =
104− case err of
105− BadJson syntaxError ->
106− case Syntax.toReport source syntaxError of
107− Report.Report _ _ _ doc ->
108− doc
109−
110223 BadContent jsonError ->
111224 case flatten jsonError of
112225 [] ->
@@ -312,9 +425,9 @@ addName name err subErrors =
312425 Map.alter subAdd name subErrors
313426
314427
315−fieldToName :: Text.Text -> String
428+fieldToName :: [Char] -> String
316429fieldToName field =
317− case Text.unpack field of
430+ case field of
318431 [] ->
319432 "['']"
320433
@@ -328,5 +441,4 @@ fieldToName field =
328441indexToName :: Int -> String
329442indexToName index =
330443 "[" ++ show index ++ "]"
331−
332444-}