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