2Module : Idris.Parser.Helpers
3Description : Utilities for Idris' parser.
6Maintainer : The Idris Community.
8{-# LANGUAGE ConstraintKinds, FlexibleContexts #-}
9module Idris.Parser.Helpers where
12import Idris.Core.Evaluate
14import Idris.Delaborate (pprintErr)
15import Idris.Docstrings
17import Idris.Output (iWarn)
19import Prelude hiding (pi)
21import Control.Applicative
23import Control.Monad.State.Strict
25import qualified Data.HashSet as HS
27import qualified Data.List.NonEmpty as NonEmpty
28import qualified Data.Map as M
30import qualified Data.Text as T
31import Data.Void (Void(..))
33import Text.Megaparsec ((<?>))
34import qualified Text.Megaparsec as P
35import qualified Text.Megaparsec.Char as P
36import qualified Text.Megaparsec.Char.Lexer as P hiding (space)
37import qualified Text.PrettyPrint.ANSI.Leijen as PP
40-- | Idris parser with state used during parsing
41type IdrisParser = StateT IState IdrisInnerParser
42type IdrisInnerParser = P.Parsec Void String
43type ParseState = P.State String
44data ParseError = ParseError String (P.ParseError (P.Token String) Void)
46parseErrorPretty :: ParseError -> String
47parseErrorPretty (ParseError s err) = P.parseErrorPretty' s err
49parseErrorDoc :: ParseError -> PP.Doc
50parseErrorDoc = PP.string . parseErrorPretty
52-- | Generalized monadic parsing constraint type
53type MonadicParsing m = (P.MonadParsec Void String m)
55someSpace :: MonadicParsing m => m ()
56someSpace = many (simpleWhiteSpace <|> singleLineComment <|> multiLineComment) *> pure ()
58tokenFC :: MonadicParsing m => m a -> m (a, FC)
59tokenFC p = do (FC fn (sl, sc) _) <- getFC --TODO: Update after fixing getFC
62 (FC fn _ (el, ec)) <- getFC
64 return (r, FC fn (sl, sc) (el, ec))
66token :: MonadicParsing m => m a -> m a
67token p = p <* whiteSpace
69-- | Helper to run Idris inner parser based stateT parsers
70runparser :: StateT st IdrisInnerParser res -> st -> String -> String -> Either ParseError res
71runparser p i inputname s =
72 case P.parse (evalStateT p i) inputname s of
73 Left err -> Left $ ParseError s err
77highlightP :: FC -> OutputAnnotation -> IdrisParser ()
78highlightP fc annot = do ist <- get
79 put ist { idris_parserHighlights = (fc, annot) : idris_parserHighlights ist}
81noDocCommentHere :: String -> IdrisParser ()
83 optional (do fc <- getFC
86 put ist { parserWarnings = (fc, Msg msg) : parserWarnings ist}) *>
89clearParserWarnings :: Idris ()
90clearParserWarnings = do ist <- getIState
91 putIState ist { parserWarnings = [] }
93reportParserWarnings :: Idris ()
94reportParserWarnings = do ist <- getIState
96 (map (\ (fc, err) -> (fc, pprintErr ist err)) .
98 nubBy (\(fc, err) (fc', err') ->
99 FC' fc == FC' fc' && err == err') $
104parserWarning :: FC -> Maybe Opt -> Err -> IdrisParser ()
105parserWarning fc warnOpt warnErr = do
107 let cmdline = opt_cmdline (idris_options ist)
108 unless (maybe False (`elem` cmdline) warnOpt) $
109 put ist { parserWarnings = (fc, warnErr) : parserWarnings ist }
111{- * Space, comments and literals (token/lexing like parsers) -}
113-- | Consumes any simple whitespace (any character which satisfies Char.isSpace)
114simpleWhiteSpace :: MonadicParsing m => m ()
115simpleWhiteSpace = () <$ P.satisfy isSpace
117-- | Checks if a charcter is end of line
122-- | A parser that succeeds at the end of the line
123eol :: MonadicParsing m => m ()
124eol = () <$ P.satisfy isEol <|> P.lookAhead P.eof <?> "end of line"
126{- | Consumes a single-line comment
129 SingleLineComment_t ::= '--' ~EOL_t* EOL_t ;
132singleLineComment :: MonadicParsing m => m ()
133singleLineComment = P.hidden (() <$ string "--" *> many (P.satisfy (not . isEol)) *> eol)
135{- | Consumes a multi-line comment
138 MultiLineComment_t ::=
140 | '{ -' InCommentChars_t
147 | MultiLineComment_t InCommentChars_t
148 | ~'- }'+ InCommentChars_t
152multiLineComment :: MonadicParsing m => m ()
153multiLineComment = P.hidden $ P.try (string "{-" *> string "-}" *> pure ())
154 <|> string "{-" *> inCommentChars
155 where inCommentChars :: MonadicParsing m => m ()
156 inCommentChars = string "-}" *> pure ()
157 <|> P.try (multiLineComment) *> inCommentChars
158 <|> string "|||" *> many (P.satisfy (not . isEol)) *> eol *> inCommentChars
159 <|> P.skipSome (P.noneOf startEnd) *> inCommentChars
160 <|> P.oneOf startEnd *> inCommentChars
165{-| Parses a documentation comment
168 DocComment_t ::= '|||' ~EOL_t* EOL_t
172docComment :: IdrisParser (Docstring (), [(Name, Docstring ())])
173docComment = do dc <- pushIndent *> docCommentLine
174 rest <- many (indented docCommentLine)
175 args <- many $ do (name, first) <- indented argDocCommentLine
176 rest <- many (indented docCommentLine)
177 return (name, concat (intersperse "\n" (first:rest)))
179 return (parseDocstring $ T.pack (concat (intersperse "\n" (dc:rest))),
180 map (\(n, d) -> (n, parseDocstring (T.pack d))) args)
182 where docCommentLine :: MonadicParsing m => m String
183 docCommentLine = P.hidden $ P.try $ do
185 many (P.satisfy (==' '))
186 contents <- P.option "" (do first <- P.satisfy (\c -> not (isEol c || c == '@'))
187 res <- many (P.satisfy (not . isEol))
192 argDocCommentLine :: IdrisParser (Name, String)
193 argDocCommentLine = do P.string "|||"
194 P.many (P.satisfy isSpace)
196 P.many (P.satisfy isSpace)
198 P.many (P.satisfy isSpace)
199 docs <- P.many (P.satisfy (not . isEol))
203-- | Parses some white space
204whiteSpace :: MonadicParsing m => m ()
205whiteSpace = someSpace <|> pure ()
207-- | Parses a string literal
208stringLiteral :: MonadicParsing m => m (String, FC)
209stringLiteral = tokenFC . P.try $ P.char '"' *> P.manyTill P.charLiteral (P.char '"')
211-- | Parses a char literal
212charLiteral :: IdrisParser (Char, FC)
213charLiteral = tokenFC . P.try $ P.char '\'' *> P.charLiteral <* P.char '\''
215-- | Parses a natural number
216natural :: IdrisParser (Integer, FC)
217natural = tokenFC ( P.try (P.char '0' *> P.char' 'x' *> P.hexadecimal)
218 <|> P.try (P.char '0' *> P.char' 'o' *> P.octal)
221-- | Parses a floating point number
222float :: IdrisParser (Double, FC)
223float = tokenFC . P.try $ P.float
225{- * Symbols, identifiers, names and operators -}
227reservedIdentifiers :: HS.HashSet String
228reservedIdentifiers = HS.fromList
230 , "abstract", "case", "class", "codata", "constructor", "corecord", "data"
231 , "do", "dsl", "else", "export", "if", "implementation", "implicit"
232 , "import", "impossible", "in", "infix", "infixl", "infixr", "instance"
233 , "interface", "let", "mutual", "namespace", "of", "parameters", "partial"
234 , "postulate", "private", "proof", "public", "quoteGoal", "record"
235 , "rewrite", "syntax", "then", "total", "using", "where", "with"
238identifierOrReserved :: MonadicParsing m => m String
239identifierOrReserved = token $ P.try $ do
240 c <- P.satisfy isAlpha <|> P.oneOf "_"
241 cs <- P.many (P.satisfy isAlphaNum <|> P.oneOf "_'.")
244char :: MonadicParsing m => Char -> m Char
247string :: MonadicParsing m => String -> m String
250-- | Parses a character as a token
251lchar :: MonadicParsing m => Char -> m Char
252lchar = token . P.char
254-- | Parses a character as a token, returning the source span of the character
255lcharFC :: MonadicParsing m => Char -> m FC
256lcharFC ch = do (FC file (l, c) _) <- getFC
257 _ <- token (P.char ch)
258 return $ FC file (l, c) (l, c+1)
260symbol :: MonadicParsing m => String -> m ()
261symbol = void . P.symbol someSpace
263symbolFC :: MonadicParsing m => String -> m FC
264symbolFC str = do (FC file (l, c) _) <- getFC
266 return $ FC file (l, c) (l, c + length str)
268-- | Parses a reserved identifier
269reserved :: MonadicParsing m => String -> m ()
270reserved name = token $ P.try $ do
272 P.notFollowedBy (P.satisfy isAlphaNum <|> P.oneOf "_'.") <?> "end of " ++ name
274-- | Parses a reserved identifier, computing its span. Assumes that
275-- reserved identifiers never contain line breaks.
276reservedFC :: MonadicParsing m => String -> m FC
277reservedFC str = do (FC file (l, c) _) <- getFC
279 return $ FC file (l, c) (l, c + length str)
281-- | Parse a reserved identfier, highlighting its span as a keyword
282reservedHL :: String -> IdrisParser ()
283reservedHL str = reservedFC str >>= flip highlightP AnnKeyword
285-- Taken from Parsec (c) Daan Leijen 1999-2001, (c) Paolo Martini 2007
286-- | Parses a reserved operator
287reservedOp :: MonadicParsing m => String -> m ()
288reservedOp name = token $ P.try $
290 P.notFollowedBy operatorLetter <?> ("end of " ++ show name)
292reservedOpFC :: MonadicParsing m => String -> m FC
293reservedOpFC name = do (FC f (l, c) _) <- getFC
295 return (FC f (l, c) (l, c + length name))
297-- | Parses an identifier as a token
298identifier :: (MonadicParsing m) => m (String, FC)
299identifier = P.try $ do
300 (FC f (l, c) _) <- getFC
301 ident <- identifierOrReserved
302 when (ident `HS.member` reservedIdentifiers) $ P.unexpected . P.Label . NonEmpty.fromList $ "reserved " ++ ident
303 when (ident == "_") $ P.unexpected . P.Label . NonEmpty.fromList $ "wildcard"
304 return (ident, FC f (l, c) (l, c + length ident))
306-- | Parses an identifier with possible namespace as a name
307iName :: (MonadicParsing m) => [String] -> m (Name, FC)
308iName bad = maybeWithNS identifier False bad <?> "name"
310-- | Parses an string possibly prefixed by a namespace
311maybeWithNS :: (MonadicParsing m) => m (String, FC) -> Bool -> [String] -> m (Name, FC)
312maybeWithNS parser ascend bad = do
314 i <- P.option "" (P.lookAhead (fst <$> identifier))
315 when (i `elem` bad) $ P.unexpected . P.Label . NonEmpty.fromList $ "reserved identifier"
316 let transf = if ascend then id else reverse
317 (x, xs, fc) <- P.choice (transf (parserNoNS parser : parsersNS parser i))
318 return (mkName (x, xs), fc)
319 where parserNoNS :: MonadicParsing m => m (String, FC) -> m (String, String, FC)
320 parserNoNS parser = do startFC <- getFC
321 (x, nameFC) <- parser
322 return (x, "", spanFC startFC nameFC)
323 parserNS :: MonadicParsing m => m (String, FC) -> String -> m (String, String, FC)
324 parserNS parser ns = do startFC <- getFC
326 lchar '.'; (x, nameFC) <- parser
327 return (x, xs, spanFC startFC nameFC)
328 parsersNS :: MonadicParsing m => m (String, FC) -> String -> [m (String, String, FC)]
329 parsersNS parser i = [P.try (parserNS parser ns) | ns <- (initsEndAt (=='.') i)]
332name :: IdrisParser (Name, FC)
333name = (<?> "name") $ do
334 keywords <- syntax_keywords <$> get
335 aliases <- module_aliases <$> get
336 (n, fc) <- iName keywords
337 return (unalias aliases n, fc)
339 unalias :: M.Map [T.Text] [T.Text] -> Name -> Name
340 unalias aliases (NS n ns) | Just ns' <- M.lookup ns aliases = NS n ns'
341 unalias aliases name = name
343{- | List of all initial segments in ascending order of a list. Every
344such initial segment ends right before an element satisfying the given
347initsEndAt :: (a -> Bool) -> [a] -> [[a]]
349initsEndAt p (x:xs) | p x = [] : x_inits_xs
350 | otherwise = x_inits_xs
351 where x_inits_xs = [x : cs | cs <- initsEndAt p xs]
354{- | Create a `Name' from a pair of strings representing a base name and its
357mkName :: (String, String) -> Name
358mkName (n, "") = sUN n
359mkName (n, ns) = sNS (sUN n) (reverse (parseNS ns))
360 where parseNS x = case span (/= '.') x of
362 (x, '.':y) -> x : parseNS y
365opChars = ":!#$%&*+./<=>?@\\^|-~"
367operatorLetter :: MonadicParsing m => m Char
368operatorLetter = P.oneOf opChars
370-- | Parse a package name
371packageName :: MonadicParsing m => m String
372packageName = (:) <$> P.oneOf firstChars <*> many (P.oneOf remChars)
373 where firstChars = ['a'..'z'] ++ ['A'..'Z']
374 remChars = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ ['-','_']
376commentMarkers :: [String]
377commentMarkers = [ "--", "|||" ]
379invalidOperators :: [String]
380invalidOperators = [":", "=>", "->", "<-", "=", "?=", "|", "**", "==>", "\\", "%", "~", "?", "!", "@"]
382-- | Parses an operator
383symbolicOperator :: MonadicParsing m => m String
384symbolicOperator = do op <- token . some $ operatorLetter
385 when (op `elem` (invalidOperators ++ commentMarkers)) $
386 fail $ op ++ " is not a valid operator"
389-- | Parses an operator
390symbolicOperatorFC :: MonadicParsing m => m (String, FC)
391symbolicOperatorFC = do (FC f (l, c) _) <- getFC
392 op <- symbolicOperator
393 return (op, FC f (l, c) (l, c + length op))
395{- * Position helpers -}
396{- | Get file position as FC -}
397getFC :: MonadicParsing m => m FC
398getFC = do pos <- P.getPosition
399 let columnNumber = P.unPos . P.sourceColumn $ pos
400 let lineNumber = P.unPos . P.sourceLine $ pos
401 let (dir, file) = splitFileName (P.sourceName pos)
402 let f = if dir == addTrailingPathSeparator "." then file else P.sourceName pos
403 return $ FC f (lineNumber, columnNumber) (lineNumber, columnNumber) -- TODO: Change to actual spanning
404 -- Issue #1594 on the Issue Tracker.
405 -- https://github.com/idris-lang/Idris-dev/issues/1594
408-- | Bind constraints to term
409bindList :: (RigCount -> Name -> FC -> PTerm -> PTerm -> PTerm) -> [(RigCount, Name, FC, PTerm)] -> PTerm -> PTerm
411bindList b ((r, n, fc, t):bs) sc = b r n fc t (bindList b bs sc)
413{- | @commaSeparated p@ parses one or more occurences of `p`,
414 separated by commas and optional whitespace. -}
415commaSeparated :: MonadicParsing m => m a -> m [a]
416commaSeparated p = p `P.sepBy1` (P.space >> P.char ',' >> P.space)
418{- * Layout helpers -}
420-- | Push indentation to stack
421pushIndent :: IdrisParser ()
422pushIndent = do columnNumber <- indent
424 put (ist { indent_stack = columnNumber : indent_stack ist })
426-- | Pops indentation from stack
427popIndent :: IdrisParser ()
428popIndent = do ist <- get
429 case indent_stack ist of
430 [] -> error "The impossible happened! Tried to pop an indentation level where none was pushed (underflow)."
431 (x : xs) -> put (ist { indent_stack = xs })
434-- | Gets current indentation
435indent :: MonadicParsing m => m Int
436indent = P.unPos . P.sourceColumn <$> P.getPosition
438-- | Gets last indentation
439lastIndent :: IdrisParser Int
440lastIndent = do ist <- get
441 case indent_stack ist of
445-- | Applies parser in an indented position
446indented :: IdrisParser a -> IdrisParser a
447indented p = notEndBlock *> p <* keepTerminator
449-- | Applies parser to get a block (which has possibly indented statements)
450indentedBlock :: IdrisParser a -> IdrisParser [a]
451indentedBlock p = do openBlock
453 res <- many (indented p)
458-- | Applies parser to get a block with at least one statement (which has possibly indented statements)
459indentedBlock1 :: IdrisParser a -> IdrisParser [a]
460indentedBlock1 p = do openBlock
462 res <- some (indented p)
467-- | Applies parser to get a block with exactly one (possibly indented) statement
468indentedBlockS :: IdrisParser a -> IdrisParser a
469indentedBlockS p = do openBlock
477-- | Checks if the following character matches provided parser
478lookAheadMatches :: MonadicParsing m => m a -> m Bool
479lookAheadMatches p = isJust <$> P.lookAhead (P.optional p)
481-- | Parses a start of block
482openBlock :: IdrisParser ()
483openBlock = do lchar '{'
485 put (ist { brace_stack = Nothing : brace_stack ist })
488 -- if we're not indented further, it's an empty block, so
489 -- increment lvl to ensure we get to the end
490 let lvl = case brace_stack ist of
492 if lvl' <= lvl_old then lvl_old+1
494 [] -> if lvl' == 1 then 2 else lvl'
496 put (ist { brace_stack = Just lvl : brace_stack ist })
499-- | Parses an end of block
500closeBlock :: IdrisParser ()
501closeBlock = do ist <- get
502 bs <- case brace_stack ist of
504 Nothing : xs -> lchar '}' >> return xs <?> "end of block"
505 Just lvl : xs -> (do i <- indent
506 isParen <- lookAheadMatches (char ')')
507 isIn <- lookAheadMatches (reserved "in")
508 if i >= lvl && not (isParen || isIn)
509 then fail "not end of block"
511 <|> (do notOpenBraces
514 put (ist { brace_stack = bs })
516-- | Parses a terminator
517terminator :: IdrisParser ()
518terminator = do lchar ';'; popIndent
519 <|> do c <- indent; l <- lastIndent
520 if c <= l then popIndent else fail "not a terminator"
521 <|> do isParen <- lookAheadMatches (P.oneOf ")}")
522 if isParen then popIndent else fail "not a terminator"
523 <|> P.lookAhead P.eof
525-- | Parses and keeps a terminator
526keepTerminator :: IdrisParser ()
527keepTerminator = () <$ lchar ';'
528 <|> do c <- indent; l <- lastIndent
529 unless (c <= l) $ fail "not a terminator"
530 <|> do isParen <- lookAheadMatches (P.oneOf ")}|")
531 isIn <- lookAheadMatches (reserved "in")
532 unless (isIn || isParen) $ fail "not a terminator"
533 <|> P.lookAhead P.eof
535-- | Checks if application expression does not end
536notEndApp :: IdrisParser ()
537notEndApp = do c <- indent; l <- lastIndent
538 when (c <= l) (fail "terminator")
540-- | Checks that it is not end of block
541notEndBlock :: IdrisParser ()
542notEndBlock = do ist <- get
543 case brace_stack ist of
544 Just lvl : xs -> do i <- indent
545 isParen <- lookAheadMatches (P.char ')')
546 when (i < lvl || isParen) (fail "end of block")
549indentGt :: IdrisParser ()
553 when (i <= li) $ fail "Wrong indention: should be greater than context indentation"
555-- | Checks that there are no braces that are not closed
556notOpenBraces :: IdrisParser ()
557notOpenBraces = do ist <- get
558 when (hasNothing $ brace_stack ist) $ fail "end of input"
559 where hasNothing :: [Maybe a] -> Bool
560 hasNothing = any isNothing
562{- | Parses an accessibilty modifier (e.g. public, private) -}
563accessibility' :: IdrisParser Accessibility
565 = do reserved "public";
566 gotexp <- optional (reserved "export")
572 put ist { parserWarnings =
573 (fc, Msg "'public' is deprecated. Use 'public export' instead.")
574 : parserWarnings ist }
576 <|> do reserved "abstract";
579 put ist { parserWarnings =
580 (fc, Msg "The 'abstract' keyword is deprecated. Use 'export' instead.")
581 : parserWarnings ist }
583 <|> do reserved "export"; return Frozen
584 <|> do reserved "private"; return Private
585 <?> "accessibility modifier"
587accessibility :: IdrisParser Accessibility
588accessibility = do acc <- optional accessibility'
591 Nothing -> do ist <- get
592 return (default_access ist)
594-- | Adds accessibility option for function
595addAcc :: Name -> Accessibility -> IdrisParser ()
596addAcc n a = do i <- get
597 put (i { hide_list = addDef n a (hide_list i) })
599{- | Add accessbility option for data declarations
600 (works for interfaces too - 'abstract' means the data/interface is visible but members not) -}
601accData :: Accessibility -> Name -> [Name] -> IdrisParser ()
602accData Frozen n ns = do addAcc n Public -- so that it can be used in public definitions
603 mapM_ (\n -> addAcc n Private) ns -- so that they are invisible
604accData a n ns = do addAcc n a
605 mapM_ (`addAcc` a) ns
608{- * Error reporting helpers -}
609{- | Error message with possible fixes list -}
610fixErrorMsg :: String -> [String] -> String
611fixErrorMsg msg fixes = msg ++ ", possible fixes:\n" ++ (concat $ intersperse "\n\nor\n\n" fixes)
613-- | Collect 'PClauses' with the same function name
614collect :: [PDecl] -> [PDecl]
615collect (c@(PClauses _ o _ _) : ds)
616 = clauses (cname c) [] (c : ds)
617 where clauses :: Maybe Name -> [PClause] -> [PDecl] -> [PDecl]
618 clauses j@(Just n) acc (PClauses fc _ _ [PClause fc' n' l ws r w] : ds)
619 | n == n' = clauses j (PClause fc' n' l ws r (collect w) : acc) ds
620 clauses j@(Just n) acc (PClauses fc _ _ [PWith fc' n' l ws r pn w] : ds)
621 | n == n' = clauses j (PWith fc' n' l ws r pn (collect w) : acc) ds
622 clauses (Just n) acc xs = PClauses (fcOf c) o n (reverse acc) : collect xs
623 clauses Nothing acc (x:xs) = collect xs
624 clauses Nothing acc [] = []
626 cname :: PDecl -> Maybe Name
627 cname (PClauses fc _ _ [PClause _ n _ _ _ _]) = Just n
628 cname (PClauses fc _ _ [PWith _ n _ _ _ _ _]) = Just n
629 cname (PClauses fc _ _ [PClauseR _ _ _ _]) = Nothing
630 cname (PClauses fc _ _ [PWithR _ _ _ _ _]) = Nothing
632 fcOf (PClauses fc _ _ _) = fc
633collect (PParams f ns ps : ds) = PParams f ns (collect ps) : collect ds
634collect (POpenInterfaces f ns ps : ds) = POpenInterfaces f ns (collect ps) : collect ds
635collect (PMutual f ms : ds) = PMutual f (collect ms) : collect ds
636collect (PNamespace ns fc ps : ds) = PNamespace ns fc (collect ps) : collect ds
637collect (PInterface doc f s cs n nfc ps pdocs fds ds cn cd : ds')
638 = PInterface doc f s cs n nfc ps pdocs fds (collect ds) cn cd : collect ds'
639collect (PImplementation doc argDocs f s cs pnames acc opts n nfc ps pextra t en ds : ds')
640 = PImplementation doc argDocs f s cs pnames acc opts n nfc ps pextra t en (collect ds) : collect ds'
641collect (d : ds) = d : collect ds
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list