on 83cd588 help sign in
787 lines 23 kB history raw
1{-# OPTIONS_GHC -Wall #-}
2{-# LANGUAGE OverloadedStrings #-}
3module Reporting.Error.Syntax
4 ( Error(..)
5 , ParseError(..)
6 , Problem(..), Theory(..)
7 , EscapeProblem(..)
8 , ContextStack, Context(..)
9 , BadOp(..), Next(..)
10 , toReport
11 )
12 where
15import Data.Monoid ((<>))
16import qualified Data.Name as Name
17import qualified Data.Set as Set
18import qualified Data.Text as Text
20import qualified Reporting.Doc as D
21import qualified Reporting.Region as R
22import qualified Reporting.Render.Code as Code
23import qualified Reporting.Report as Report
27-- ALL SYNTAX ERRORS
30data Error
31 = CommentOnNothing R.Region
32 | UnexpectedPort R.Region Name.Name
33 | TypeWithBadDefinition R.Region Name.Name Name.Name
34 | TypeWithoutDefinition R.Region Name.Name
35 | Parse R.Region (Maybe R.Region) Problem
39-- PARSE ERRORS
42data ParseError = ParseError !Int !Int !Problem
45data Problem
46 = Tab
47 | EndOfFile_Comment
48 | EndOfFile_Shader
49 | EndOfFile_String
50 | EndOfFile_MultiString
51 | EndOfFile_Char
52 | NewLineInString
53 | NewLineInChar
54 | BadEscape Int EscapeProblem
55 | BadChar Int
56 | BadNumberDot Int
57 | BadNumberEnd
58 | BadNumberExp
59 | BadNumberHex
60 | BadNumberZero
61 | FloatInPattern
62 | BadShader Text.Text
63 | BadUnderscore Int
64 | BadOp BadOp ContextStack
65 | Theories ContextStack [Theory]
68data EscapeProblem
69 = UnknownEscape
70 | UnicodeSyntax
71 | UnicodeRange
72 | UnicodeLength Int String
75data BadOp = HasType | Equals | Arrow | Pipe | Dot
78data Theory
79 = Expecting Next
80 | Keyword String
81 | Symbol String
82 | LowVar
83 | CapVar
84 | InfixOp
85 | Digit
86 | BadSpace
87 deriving (Eq, Ord)
90data Next
91 = Decl
92 | Expr
93 | AfterOpExpr Name.Name
94 | ElseBranch
95 | Arg
96 | Pattern
97 | Type
98 | Listing
99 | Exposing
100 deriving (Eq, Ord)
103type ContextStack = [(Context, R.Position)]
106data Context
107 = ExprIf
108 | ExprLet
109 | ExprFunc
110 | ExprCase
111 | ExprList
112 | ExprTuple
113 | ExprRecord
114 ----------------
115 | Definition Name.Name
116 | Annotation Name.Name
117 ----------------
118 | TypeTuple
119 | TypeRecord
120 ----------------
121 | PatternList
122 | PatternTuple
123 | PatternRecord
124 ----------------
125 | Module
126 | Import
127 | TypeUnion
128 | TypeAlias
129 | Infix
130 | Port
131 deriving (Eq, Ord)
135-- TO REPORT
138toReport :: Code.Source -> Error -> Report.Report
139toReport source err =
140 case err of
141 CommentOnNothing region ->
142 Report.Report "STRAY COMMENT" region [] $
143 Report.toCodeSnippet source region Nothing
144 (
145 "This documentation comment is not followed by anything."
146 ,
147 D.reflow $
148 "All documentation comments need to be right above the declaration they\
149 \ describe. Maybe some code got deleted or commented out by accident? Or\
150 \ maybe this comment is here by accident?"
151 )
153 UnexpectedPort region name ->
154 Report.Report "BAD PORT" region [] $
155 Report.toCodeSnippet source region Nothing
156 (
157 D.reflow $
158 "You are declaring port `" <> Name.toString name <> "` in a normal module."
159 ,
160 D.stack
161 [ "It needs to be in a `port` module."
162 , D.link "Hint"
163 "Ports are not a traditional FFI for calling JS functions directly. They need a different mindset! Read"
164 "ports"
165 "to learn how to use ports effectively."
166 ]
167 )
169 TypeWithBadDefinition region annName defName ->
170 Report.Report "ANNOTATION MISMATCH" region [] $
171 Report.toCodeSnippet source region Nothing
172 (
173 D.reflow $
174 "I see a `" <> Name.toString annName
175 <> "` annotation, but it is followed by a `"
176 <> Name.toString defName <> "` definition."
177 ,
178 D.fillSep
179 ["The","annotation","and","definition","names","must","match!"
180 ,"Is","there","a","typo","between"
181 , D.dullyellow (D.fromName annName)
182 ,"and"
183 , D.dullyellow (D.fromName defName) <> "?"
184 ]
185 )
187 TypeWithoutDefinition region name ->
188 Report.Report "MISSING DEFINITION" region [] $
189 Report.toCodeSnippet source region Nothing
190 (
191 D.reflow $
192 "There is a type annotation for `" <> Name.toString name
193 <> "` but there is no corresponding definition!"
194 ,
195 "Directly below the type annotation, put a definition like:\n\n"
196 <> " " <> D.fromName name <> " = 42"
197 )
199 Parse region subRegion problem ->
200 Report.Report "PARSE ERROR" (maybe region id subRegion) [] $
201 Report.toCodeSnippet source region subRegion $
202 problemToDocs problem
206-- PARSE ERROR TO DOCS
209problemToDocs :: Problem -> (D.Doc, D.Doc)
210problemToDocs problem =
211 case problem of
212 Tab ->
213 (
214 "I ran into a tab, but tabs are not allowed in Elm files."
215 ,
216 "Replace the tab with spaces."
217 )
219 EndOfFile_Comment ->
220 (
221 "I got to the end of the file while parsing a multi-line comment."
222 ,
223 D.stack
224 [ D.reflow $
225 "Multi-line comments look like {- comment -}, and it looks like\
226 \ you are missing the closing marker."
227 , D.toSimpleHint $
228 "Nested multi-line comments like {- this {- and this -} -} are allowed.\
229 \ The opening and closing markers must be balanced though, just\
230 \ like parentheses in normal code. Maybe that is the problem?"
231 ]
232 )
234 EndOfFile_Shader ->
235 (
236 "I got to the end of the file while parsing a GLSL block."
237 ,
238 D.reflow $
239 "A shader should be defined in a block like this: [glsl| ... |]"
240 )
242 EndOfFile_String ->
243 (
244 "I got to the end of the file while parsing a string."
245 ,
246 D.reflow $
247 "Strings look like \"this\" with double quotes on each end.\
248 \ Is the closing double quote missing in your code?"
249 )
251 EndOfFile_MultiString ->
252 (
253 "I got to the end of the file while parsing a multi-line string."
254 ,
255 D.reflow $
256 "Multi-line strings look like \"\"\"this\"\"\" with three double quotes on each\
257 \ end. Is the closing triple quote missing in your code?"
258 )
260 EndOfFile_Char ->
261 (
262 "I got to the end of the file while parsing a character."
263 ,
264 D.reflow $
265 "Characters look like 'c' with single quotes on each end.\
266 \ Is the closing single quote missing in your code?"
267 )
269 NewLineInString ->
270 (
271 "This string is missing the closing quote."
272 ,
273 D.stack
274 [ D.reflow $
275 "Elm strings like \"this\" cannot contain newlines."
276 , D.toSimpleHint $
277 "For strings that CAN contain newlines, say \"\"\"this\"\"\" for Elm’s\
278 \ multi-line string syntax. It allows unescaped newlines and double quotes."
279 ]
280 )
282 NewLineInChar ->
283 (
284 "This character is missing the closing quote."
285 ,
286 "Elm characters must start and end with a single quote. Valid examples\n\
287 \ include 'a', 'b', '\n', 'ø', and '\\u{00F8}' for unicode code points."
288 )
290 BadEscape _ escapeProblem ->
291 case escapeProblem of
292 UnknownEscape ->
293 (
294 "Backslashes always start escaped characters, but I do not recognize this one:"
295 , D.stack
296 [ "Maybe there is some typo?"
297 , D.toSimpleHint "Valid escape characters include:"
298 , D.indent 4 $ D.vcat $
299 [ "\\n"
300 , "\\r"
301 , "\\t"
302 , "\\\""
303 , "\\\'"
304 , "\\\\"
305 , "\\u{03BB}"
306 ]
307 , D.reflow $
308 "The last one lets encode ANY character by its Unicode code\
309 \ point, so use that for anything outside the ordinary six."
310 ]
311 )
313 UnicodeSyntax ->
314 (
315 "I ran into an invalid Unicode escape character:"
316 ,
317 D.stack
318 [ "Here are some examples of valid Unicode escape characters:"
319 , D.indent 4 $ D.vcat $
320 [ "\\u{0041}"
321 , "\\u{03BB}"
322 , "\\u{6728}"
323 , "\\u{1F60A}"
324 ]
325 , D.reflow $
326 "Notice that the code point is always surrounded by curly\
327 \ braces. They are required!"
328 ]
329 )
331 UnicodeRange ->
332 (
333 "This is not a real Unicode code point:"
334 ,
335 "All valid Unicode code points are between 0 and 10FFFF."
336 )
338 UnicodeLength numDigits badCode ->
339 if numDigits < 4 then
340 (
341 "Every Unicode code point needs at least four digits:"
342 ,
343 let
344 goodCode = replicate (4 - numDigits) '0' ++ badCode
345 escape = "\\u{" <> D.fromString goodCode <> "}"
346 in
347 D.hsep [ "Try", D.dullyellow escape, "instead?" ]
348 )
350 else
351 (
352 "This Unicode code point has too many digits:"
353 ,
354 D.fillSep
355 ["Valid","code","points","are","between"
356 , D.dullyellow "\\u{0000}", "and", D.dullyellow "\\u{10FFFF}"
357 ,"so","it","must","have","between","four","and","six","digits."
358 ]
359 )
361 BadChar _ ->
362 (
363 "Ran into a bad use of single quotes."
364 ,
365 D.stack
366 [ D.reflow $
367 "If you want to create a string, switch to double quotes:"
368 , D.indent 4 $
369 D.dullyellow "'this'" <> " => " <> D.green "\"this\""
370 , D.toSimpleHint $
371 "Unlike JavaScript, Elm distinguishes between strings like \"hello\"\
372 \ and individual characters like 'A' and '3'. If you really do want\
373 \ a character though, something went wrong and I did not find the\
374 \ closing single quote."
375 ]
376 )
378 BadNumberDot numberBeforeDot ->
379 (
380 "Numbers cannot end with a decimal points."
381 ,
382 let
383 number =
384 D.fromString (show numberBeforeDot)
385 in
386 "Saying " <> D.green number <> " or " <> D.green (number <> ".0") <> " will work though!"
387 )
389 BadNumberEnd ->
390 (
391 "Numbers cannot have letters or underscores in them."
392 ,
393 D.reflow $
394 "Maybe a space is missing between a number and a variable?"
395 )
397 BadNumberExp ->
398 (
399 "If you put the letter E in a number, it should followed by more digits."
400 ,
401 D.reflow $
402 "If you want to say 1000, you can also say 1e3.\
403 \ You cannot just end it with an E though!"
404 )
406 BadNumberHex ->
407 (
408 "I see the start of a hex number, but not the end."
409 ,
410 D.reflow $
411 "A hex number looks like 0x123ABC, where the 0x is followed by hexidecimal\
412 \ digits. Valid hexidecimal digits include: 0123456789abcdefABCDEF"
413 )
415 BadNumberZero ->
416 (
417 "Normal numbers cannot start with zeros. Take the zeros off the front."
418 ,
419 D.reflow $
420 "Only numbers like 0x0040 or 0.25 can start with a zero."
421 )
423 FloatInPattern ->
424 (
425 "I cannot pattern match with floating point numbers:"
426 ,
427 D.reflow $
428 "Equality on floats can be unreliable, so you usually want to check that they\
429 \ are nearby with some sort of (abs (actual - expected) < 0.001) check."
430 )
432 BadShader msg ->
433 (
434 "I ran into a problem while parsing this GLSL block."
435 ,
436 D.reflow (Text.unpack msg)
437 )
439 BadUnderscore _ ->
440 (
441 "A variable name cannot start with an underscore:"
442 ,
443 D.reflow $
444 "You can (1) use a wildcard like _ to ignore the value or you can (2) use\
445 \ a name that starts with a letter to access the value later. Pick one!"
446 )
448 BadOp op stack ->
449 case op of
450 HasType ->
451 badOp stack "A" "\"has type\" operator"
452 "type annotations and record types"
453 "Maybe you want :: instead? Or maybe something is indented too much?"
455 Equals ->
456 (
457 D.reflow $
458 "I was not expecting this equals sign"
459 <> contextToString " here" " while parsing " stack <> "."
460 ,
461 toBadEqualsHint stack
462 )
464 Arrow ->
465 if isCaseRelated stack then
466 (
467 "I ran into a stray arrow while parsing this `case` expression."
468 ,
469 D.reflow $
470 "All branches in a `case` must be indented the exact\
471 \ same amount, so the patterns are vertically\
472 \ aligned. Maybe this branch is indented too much?"
473 )
475 else
476 badOp stack "An" "arrow"
477 "cases expressions and anonymous functions"
478 "Maybe you want > or >= instead?"
480 Pipe ->
481 badOp stack "A" "vertical bar"
482 "type declarations"
483 "Maybe you want || instead?"
485 Dot ->
486 (
487 "I was not expecting this dot."
488 ,
489 D.reflow $
490 "Dots are for record access and decimal points, so\
491 \ they cannot float around on their own. Maybe\
492 \ there is some extra whitespace?"
493 )
495 Theories stack allTheories ->
496 (
497 D.reflow $
498 "Something went wrong while parsing " <> contextToString "your code" "" stack <> "."
499 ,
500 case Set.toList (Set.fromList allTheories) of
501 [] ->
502 D.stack
503 [ D.reflow $
504 "I do not have any suggestions though!"
505 , D.reflow $
506 "Can you get it down to a <http://sscce.org> and share it at\
507 \ <https://github.com/elm/error-message-catalog/issues>?\
508 \ That way we can figure out how to give better advice!"
509 ]
511 [theory] ->
512 D.reflow $
513 "I was expecting to see "
514 <> addPeriod (theoryToString stack theory)
516 theories ->
517 D.vcat $
518 [ "I was expecting:"
519 , ""
520 ]
521 ++ map (bullet . theoryToString stack) theories
522 )
526-- BAD OP HELPERS
529badOp :: ContextStack -> String -> String -> String -> String -> ( D.Doc, D.Doc )
530badOp stack article opName setting hint =
531 (
532 D.reflow $
533 "I was not expecting this " <> opName
534 <> contextToString " here" " while parsing " stack <> "."
535 ,
536 D.reflow $
537 article <> " " <> opName <> " should only appear in "
538 <> setting <> ". " <> hint
539 )
542toBadEqualsHint :: ContextStack -> D.Doc
543toBadEqualsHint stack =
544 case stack of
545 [] ->
546 D.reflow $
547 "Maybe you want == instead? Or maybe something is indented too much?"
549 (ExprRecord, _) : _ ->
550 D.reflow $
551 "Records look like { x = 3, y = 4 } with the equals sign right\
552 \ after the field name. Maybe you forgot a comma?"
554 (Definition _, _) : rest ->
555 D.reflow $
556 "Maybe this is supposed to be a separate definition? If so, it\
557 \ is indented too far. "
558 <>
559 if any ((==) ExprLet . fst) rest then
560 "All definitions in a `let` expression must be vertically aligned."
561 else
562 "Spaces are not allowed before top-level definitions."
564 _ : rest ->
565 toBadEqualsHint rest
569isCaseRelated :: ContextStack -> Bool
570isCaseRelated stack =
571 case stack of
572 [] ->
573 False
575 (context, _) : rest ->
576 context == ExprCase || isCaseRelated rest
580-- CONTEXT
583contextToString :: String -> String -> ContextStack -> String
584contextToString defaultString prefixString stack =
585 case stack of
586 [] ->
587 defaultString
589 (context, _) : rest ->
590 let anchor = getAnchor rest in
591 prefixString <>
592 case context of
593 ExprIf -> "an `if` expression" <> anchor
594 ExprLet -> "a `let` expression" <> anchor
595 ExprFunc -> "an anonymous function" <> anchor
596 ExprCase -> "a `case` expression" <> anchor
597 ExprList -> "a list" <> anchor
598 ExprTuple -> "an expression (in parentheses)" <> anchor
599 ExprRecord -> "a record" <> anchor
600 Definition name -> Name.toString name <> "'s definition"
601 Annotation name -> Name.toString name <> "'s type annotation"
602 TypeTuple -> "a type (in parentheses)" <> anchor
603 TypeRecord -> "a record type" <> anchor
604 PatternList -> "a list pattern" <> anchor
605 PatternTuple -> "a pattern (in parentheses)" <> anchor
606 PatternRecord -> "a record pattern" <> anchor
607 Module -> "a module declaration"
608 Import -> "an import"
609 TypeUnion -> "a union type"
610 TypeAlias -> "a type alias"
611 Infix -> "an infix declaration"
612 Port -> "a port declaration"
615getAnchor :: ContextStack -> String
616getAnchor stack =
617 case stack of
618 [] ->
619 ""
621 (context, _) : rest ->
622 case context of
623 Definition name ->
624 " in " <> Name.toString name <> "'s definition"
626 Annotation name ->
627 " in " <> Name.toString name <> "'s type annotation"
629 _ ->
630 getAnchor rest
634-- THEORY HELPERS
637bullet :: String -> D.Doc
638bullet point =
639 D.hang 4 (" - " <> D.fillSep (map D.fromString (words point)))
642addPeriod :: String -> String
643addPeriod msg =
644 if last msg `elem` ['`', ')', '.', '!', '?'] then
645 msg
646 else
647 msg <> "."
650theoryToString :: ContextStack -> Theory -> String
651theoryToString context theory =
652 case theory of
653 Keyword keyword ->
654 "the `" <> keyword <> "` keyword"
656 Symbol symbol ->
657 case symbol of
658 "=" -> equalsTheory context
659 "->" -> "an arrow (->) followed by an expression"
660 ":" -> "the \"has type\" symbol (:) followed by a type"
661 "," -> "a comma"
662 "|" -> barTheory context
663 "::" -> "the cons operator (::) followed by more list elements"
664 "." -> "a dot (.)"
665 "-" -> "a minus sign (-)"
666 "_" -> "an underscore"
667 "(" -> "a left paren, for grouping or starting tuples"
668 ")" -> "a closing paren"
669 "[" -> "a left square bracket, for starting lists"
670 "]" -> "a right square bracket, to end a list"
671 "{" -> "a left curly brace, for starting records"
672 "}" -> "a right curly brace, to end a record"
673 "{-|" -> "a doc comment, like {-| this -}"
674 _ -> "the (" <> symbol <> ") symbol"
676 LowVar ->
677 "a lower-case variable, like `x` or `user`"
679 CapVar ->
680 "an upper-case variable, like `Maybe` or `Just`"
682 InfixOp ->
683 "an infix operator, like (+) or (==)"
685 Digit ->
686 "a digit from 0 to 9"
688 BadSpace ->
689 badSpace context
691 Expecting next ->
692 case next of
693 Decl -> "a declaration, like `x = 5` or `type alias Model = { ... }`"
694 Expr -> "an expression, like x or 42"
695 AfterOpExpr op -> "an expression after that (" <> Name.toString op <> ") operator, like x or 42"
696 ElseBranch -> "an `else` branch. An `if` must handle both possibilities."
697 Arg -> "an argument, like `name` or `total`"
698 Pattern -> "a pattern, like `name` or (Just x)"
699 Type -> "a type, like Int or (List String)"
700 Listing -> "a list of exposed values and types, like (..) or (x,y,z)"
701 Exposing -> "something like `exposing (..)`"
704equalsTheory :: ContextStack -> String
705equalsTheory stack =
706 case stack of
707 [] ->
708 "an equals sign (=)"
710 (context, _) : rest ->
711 case context of
712 ExprRecord -> "an equals sign (=) followed by an expression"
713 Definition name -> "an equals sign (=) followed by " <> Name.toString name <> "'s definition"
714 TypeUnion -> "an equals sign (=) followed by the first union type constructor"
715 TypeAlias -> "an equals sign (=) followed by the aliased type"
716 _ -> equalsTheory rest
719barTheory :: ContextStack -> String
720barTheory stack =
721 case stack of
722 [] ->
723 "a vertical bar (|)"
725 (context, _) : rest ->
726 case context of
727 ExprRecord -> "a vertical bar (|) followed by the record fields you want to update"
728 TypeRecord -> "a vertical bar (|) followed by some record field types"
729 TypeUnion -> "a vertical bar (|) followed by more union type constructors"
730 _ -> barTheory rest
733badSpace :: ContextStack -> String
734badSpace stack =
735 case stack of
736 [] ->
737 "more indentation? I was not done with that last thing yet."
739 (context, _) : rest ->
740 case context of
741 ExprIf -> "the end of that `if`" <> badSpaceExprEnd rest
742 ExprLet -> "the end of that `let`" <> badSpaceExprEnd rest
743 ExprFunc -> badSpace rest
744 ExprCase -> "more of that `case`" <> badSpaceExprEnd rest
745 ExprList -> "the end of that list" <> badSpaceExprEnd rest
746 ExprTuple -> "a closing paren" <> badSpaceExprEnd rest
747 ExprRecord -> "the end of that record" <> badSpaceExprEnd rest
748 Definition name -> "the rest of " <> Name.toString name <> "'s definition" <> badSpaceExprEnd stack
749 Annotation name -> "the rest of " <> Name.toString name <> "'s type annotation" <> badSpaceEnd
750 TypeTuple -> "a closing paren" <> badSpaceEnd
751 TypeRecord -> "the end of that record" <> badSpaceEnd
752 PatternList -> "the end of that list" <> badSpaceEnd
753 PatternTuple -> "a closing paren" <> badSpaceEnd
754 PatternRecord -> "the end of that record" <> badSpaceEnd
755 Module -> "something like `module Main exposing (..)`"
756 Import -> "something like `import Html exposing (..)`"
757 TypeUnion -> "more of that union type" <> badSpaceEnd
758 TypeAlias -> "more of that type alias" <> badSpaceEnd
759 Infix -> "more of that infix declaration" <> badSpaceEnd
760 Port -> "more of that port declaration" <> badSpaceEnd
763badSpaceEnd :: String
764badSpaceEnd =
765 ". Maybe you forgot some code? Or you need more indentation?"
768badSpaceExprEnd :: ContextStack -> String
769badSpaceExprEnd stack =
770 case stack of
771 [] ->
772 badSpaceEnd
774 (Definition name, R.Position _ column) : _ ->
775 let
776 ending =
777 if column <= 1 then
778 "to be indented?"
779 else
780 "more indentation? (Try " <> show (column + 1) <> "+ spaces.)"
781 in
782 ". Maybe you forgot some code? Or maybe the body of `"
783 <> Name.toString name
784 <> "` needs " <> ending
786 _ : rest ->
787 badSpaceExprEnd rest