on master help sign in
write parse error messages specific to JSON
d635edeEvan Czaplicki
compiler/src/Json/Decode.hs+28−34
@@ -24,6 +24,7 @@ module Json.Decode
24 , Problem(..)
24 , Problem(..)
25 , DecodeExpectation(..)
25 , DecodeExpectation(..)
26 , ParseError(..)
26 , ParseError(..)
27+ , StringProblem(..)
27 )
28 )
28 where
29 where
29
30
@@ -49,10 +50,10 @@ fromByteString :: Decoder x a -> B.ByteString -> Either (Error x) a
49fromByteString (Decoder decode) src =
50fromByteString (Decoder decode) src =
50 case P.fromByteString pFile BadEnd src of
51 case P.fromByteString pFile BadEnd src of
51 Right ast ->
52 Right ast ->
52− decode ast Right (Left . DecodeProblem)
53+ decode ast Right (Left . DecodeProblem src)
53
54
54 Left problem ->
55 Left problem ->
55− Left (ParseProblem problem)
56+ Left (ParseProblem src problem)
56
57
57
58
58
59
@@ -75,8 +76,8 @@ newtype Decoder x a =
75
76
76
77
77data Error x
78data Error x
78− = DecodeProblem (Problem x)
79+ = DecodeProblem B.ByteString (Problem x)
79− | ParseProblem ParseError
80+ | ParseProblem B.ByteString ParseError
80
81
81
82
82
83
@@ -460,21 +461,14 @@ type Parser a =
460
461
461
462
462data ParseError
463data ParseError
463− = ObjectStart Row Col
464+ = Start Row Col
464− | ObjectMore Row Col
465+ | ObjectField Row Col
466+ | ObjectColon Row Col
465 | ObjectEnd Row Col
467 | ObjectEnd Row Col
466− | ArrayStart Row Col
467− | ArrayMore Row Col
468 | ArrayEnd Row Col
468 | ArrayEnd Row Col
469− | StringStart Row Col
470 | StringProblem StringProblem Row Col
469 | StringProblem StringProblem Row Col
471− | IntStart Row Col
472 | NoLeadingZeros Row Col
470 | NoLeadingZeros Row Col
473 | NoFloats Row Col
471 | NoFloats Row Col
474− | Bool Row Col
475− | Null Row Col
476− | Value Row Col
477− | Colon Row Col
478 | BadEnd Row Col
472 | BadEnd Row Col
479
473
480-- PIndex Int ParseError Row Col
474-- PIndex Int ParseError Row Col
@@ -502,14 +496,14 @@ pFile =
502
496
503pValue :: Parser AST
497pValue :: Parser AST
504pValue =
498pValue =
505− P.oneOf Value
499+ P.oneOf Start
506− [ String <$> pString
500+ [ String <$> pString Start
507 , pObject
501 , pObject
508 , pArray
502 , pArray
509 , pInt
503 , pInt
510− , K.k4 0x74 0x72 0x75 0x65 Bool >> return TRUE
504+ , K.k4 0x74 0x72 0x75 0x65 Start >> return TRUE
511− , K.k5 0x66 0x61 0x6C 0x73 0x65 Bool >> return FALSE
505+ , K.k5 0x66 0x61 0x6C 0x73 0x65 Start >> return FALSE
512− , K.k4 0x6E 0x75 0x6C 0x6C Null >> return NULL
506+ , K.k4 0x6E 0x75 0x6C 0x6C Start >> return NULL
513 ]
507 ]
514
508
515
509
@@ -519,9 +513,9 @@ pValue =
519
513
520pObject :: Parser AST
514pObject :: Parser AST
521pObject =
515pObject =
522− do P.word1 0x7B {- { -} ObjectStart
516+ do P.word1 0x7B {- { -} Start
523 spaces
517 spaces
524− P.oneOf ObjectMore
518+ P.oneOf ObjectField
525 [ do entry <- pField
519 [ do entry <- pField
526 spaces
520 spaces
527 pObjectHelp [entry]
521 pObjectHelp [entry]
@@ -532,9 +526,9 @@ pObject =
532
526
533pObjectHelp :: [(P.Snippet, AST)] -> Parser AST
527pObjectHelp :: [(P.Snippet, AST)] -> Parser AST
534pObjectHelp revEntries =
528pObjectHelp revEntries =
535− P.oneOf ObjectMore
529+ P.oneOf ObjectEnd
536 [
530 [
537− do P.word1 0x2C {-,-} ObjectMore
531+ do P.word1 0x2C {-,-} ObjectEnd
538 spaces
532 spaces
539 entry <- pField
533 entry <- pField
540 spaces
534 spaces
@@ -547,9 +541,9 @@ pObjectHelp revEntries =
547
541
548pField :: Parser (P.Snippet, AST)
542pField :: Parser (P.Snippet, AST)
549pField =
543pField =
550− do key <- pString
544+ do key <- pString ObjectField
551 spaces
545 spaces
552− P.word1 0x3A {-:-} Colon
546+ P.word1 0x3A {-:-} ObjectColon
553 spaces
547 spaces
554 value <- pValue
548 value <- pValue
555 return (key, value)
549 return (key, value)
@@ -561,9 +555,9 @@ pField =
561
555
562pArray :: Parser AST
556pArray :: Parser AST
563pArray =
557pArray =
564− do P.word1 0x5B {-[-} ArrayStart
558+ do P.word1 0x5B {-[-} Start
565 spaces
559 spaces
566− P.oneOf ArrayMore
560+ P.oneOf Start
567 [ do entry <- pValue
561 [ do entry <- pValue
568 spaces
562 spaces
569 pArrayHelp 1 [entry]
563 pArrayHelp 1 [entry]
@@ -574,9 +568,9 @@ pArray =
574
568
575pArrayHelp :: Int -> [AST] -> Parser AST
569pArrayHelp :: Int -> [AST] -> Parser AST
576pArrayHelp !len revEntries =
570pArrayHelp !len revEntries =
577− P.oneOf ArrayMore
571+ P.oneOf ArrayEnd
578 [
572 [
579− do P.word1 0x2C {-,-} ArrayMore
573+ do P.word1 0x2C {-,-} ArrayEnd
580 spaces
574 spaces
581 entry <- pValue
575 entry <- pValue
582 spaces
576 spaces
@@ -591,8 +585,8 @@ pArrayHelp !len revEntries =
591-- STRING
585-- STRING
592
586
593
587
594−pString :: Parser P.Snippet
588+pString :: (Row -> Col -> ParseError) -> Parser P.Snippet
595−pString =
589+pString start =
596 P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
590 P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
597 if pos < end && P.unsafeIndex pos == 0x22 {-"-} then
591 if pos < end && P.unsafeIndex pos == 0x22 {-"-} then
598
592
@@ -617,7 +611,7 @@ pString =
617 cerr newRow newCol (StringProblem problem)
611 cerr newRow newCol (StringProblem problem)
618
612
619 else
613 else
620− eerr row col StringStart
614+ eerr row col start
621
615
622
616
623data StringStatus
617data StringStatus
@@ -726,12 +720,12 @@ pInt :: Parser AST
726pInt =
720pInt =
727 P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
721 P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
728 if pos >= end then
722 if pos >= end then
729− eerr row col IntStart
723+ eerr row col Start
730
724
731 else
725 else
732 let !word = P.unsafeIndex pos in
726 let !word = P.unsafeIndex pos in
733 if not (isDecimalDigit word) then
727 if not (isDecimalDigit word) then
734− eerr row col IntStart
728+ eerr row col Start
735
729
736 else if word == 0x30 {-0-} then
730 else if word == 0x30 {-0-} then
737
731
compiler/src/Reporting/Error/Json.hs+194−82
@@ -1,112 +1,225 @@
1{-# OPTIONS_GHC -Wall #-}
1{-# OPTIONS_GHC -Wall #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3module Reporting.Error.Json
3module Reporting.Error.Json
4− ( Error(..)
5− , Problem(..)
6− , DecodeExpectation(..)
7− , ParseError(..)
8− , toReport
4+ ( toReport
9 )
5 )
10 where
6 where
11
7
12
8
13−import qualified Data.Utf8 as Utf8
9+import qualified Data.Char as Char
14−import Data.Word (Word16)
10+import qualified Data.Map as Map
15
11
12+import Json.Decode (Error(..), Problem(..), DecodeExpectation(..), ParseError(..), StringProblem(..))
13+import qualified Reporting.Annotation as A
16−import qualified Reporting.Report as Report
14+import qualified Reporting.Doc as D
15+import qualified Reporting.Exit.Help as Help
16+import qualified Reporting.Render.Code as Code
17
17
18
18
19
19
20−-- ERROR
20+-- TO REPORT
21−
22−
23−data Error e
24− = DecodeProblem (Problem e)
25− | ParseProblem ParseError
26−
27−
28−
29−-- DECODE PROBLEMS
30
21
31
22
32−data Problem e
23+toReport :: FilePath -> Error x -> Help.Report
33− = Field Utf8.String (Problem e)
24+toReport path err =
34− | Index Int (Problem e)
25+ case err of
35− | OneOf (Problem e) [Problem e]
26+ DecodeProblem bytes problem ->
36− | Failure e
27+ problemToReport (Code.toSource bytes) problem
37− | Expecting DecodeExpectation
38
28
29+ ParseProblem bytes parseError ->
30+ parseErrorToReport path (Code.toSource bytes) parseError
39
31
40−data DecodeExpectation
41− = TObject
42− | TArray
43− | TString
44− | TBool
45− | TInt
46− | TObjectWith Utf8.String
47− | TArrayPair Int
48
32
49
33
34+-- PARSE ERROR TO REPORT
50
35
51−-- PARSE ERROR
52
36
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+ )
53
62
54−data ParseError
63+ ObjectField row col ->
55− = PIndex Int ParseError Row Col
64+ toSnippet "UNFINISHED OBJECT" row col
56− | PField Utf8.String ParseError Row Col
65+ (
57− | ObjectStart Row Col
66+ D.reflow $ "I was partway through parsing a JSON object when I got stuck here:"
58− | ObjectMore Row Col
67+ ,
59− | ObjectEnd Row Col
68+ D.stack
60− | ArrayStart Row Col
69+ [ D.reflow $ "I was expecting to see a field name next."
61− | ArrayMore Row Col
70+ , objectNote
62− | ArrayEnd Row Col
71+ ]
63− | StringStart Row Col
72+ )
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
73
73
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+ )
74
84
75−type Row = Word16
85+ ObjectEnd row col ->
76−type Col = Word16
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+ )
77
99
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+ ]
78
212
79
213
80−-- TO REPORT
81
214
215+-- PROBLEM TO REPORT
82
216
83−toReport :: Error e -> Report.Report
84−toReport err =
85− error "TODO Reporting.Error.Json.toReport" err
86
217
218+problemToReport :: Code.Source -> Problem x -> Help.Report
219+problemToReport source problem =
220+ error "TODO problemToReport" source problem
87
221
88{-
222{-
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−
110 BadContent jsonError ->
223 BadContent jsonError ->
111 case flatten jsonError of
224 case flatten jsonError of
112 [] ->
225 [] ->
@@ -312,9 +425,9 @@ addName name err subErrors =
312 Map.alter subAdd name subErrors
425 Map.alter subAdd name subErrors
313
426
314
427
315−fieldToName :: Text.Text -> String
428+fieldToName :: [Char] -> String
316fieldToName field =
429fieldToName field =
317− case Text.unpack field of
430+ case field of
318 [] ->
431 [] ->
319 "['']"
432 "['']"
320
433
@@ -328,5 +441,4 @@ fieldToName field =
328indexToName :: Int -> String
441indexToName :: Int -> String
329indexToName index =
442indexToName index =
330 "[" ++ show index ++ "]"
443 "[" ++ show index ++ "]"
331−
332-}
444-}