2Module : Idris.Parser.Helpers
3Description : Utilities for Idris' parser.
6Maintainer : The Idris Community.
8{-# LANGUAGE ConstraintKinds, FlexibleContexts, MultiParamTypeClasses #-}
9module Idris.Parser.Helpers
10 ( module Idris.Parser.Stack
39 , identifierWithExtraChars
45 -- * Warnings and errors
49 , reportParserWarnings
74import Idris.Core.Evaluate
76import Idris.Delaborate (pprintErr)
77import Idris.Docstrings
79import Idris.Output (iWarn)
80import Idris.Parser.Stack
82import Prelude hiding (pi)
84import Control.Applicative
86import Control.Monad.State.Strict
88import qualified Data.HashSet as HS
90import qualified Data.List.NonEmpty as NonEmpty
91import qualified Data.Map as M
93import qualified Data.Set as S
94import qualified Data.Text as T
95import Text.Megaparsec ((<?>))
96import qualified Text.Megaparsec as P
97import qualified Text.Megaparsec.Char as P
98import qualified Text.Megaparsec.Char.Lexer as P hiding (space)
99import qualified Text.PrettyPrint.ANSI.Leijen as PP
102-- | Idris parser with state used during parsing
103type IdrisParser = Parser IState
105parseErrorDoc :: ParseError -> PP.Doc
106parseErrorDoc = PP.string . prettyError
108someSpace :: Parsing m => m ()
109someSpace = many (simpleWhiteSpace <|> singleLineComment <|> multiLineComment) *> pure ()
111token :: Parsing m => m a -> m a
112token p = trackExtent p <* whiteSpace
114highlight :: (MonadState IState m, Parsing m) => OutputAnnotation -> m a -> m a
115highlight annot p = do
116 (result, fc) <- withExtent p
117 modify $ \ist -> ist { idris_parserHighlights = S.insert (FC' fc, annot) (idris_parserHighlights ist) }
120-- | Parse a reserved identfier, highlighting it as a keyword
121keyword :: (Parsing m, MonadState IState m) => String -> m ()
122keyword str = highlight AnnKeyword (reserved str)
124clearParserWarnings :: Idris ()
125clearParserWarnings = do ist <- getIState
126 putIState ist { parserWarnings = [] }
128reportParserWarnings :: Idris ()
129reportParserWarnings = do ist <- getIState
130 mapM_ (uncurry iWarn)
131 (map (\ (fc, err) -> (fc, pprintErr ist err)) .
133 nubBy (\(fc, err) (fc', err') ->
134 FC' fc == FC' fc' && err == err') $
139parserWarning :: FC -> Maybe Opt -> Err -> IdrisParser ()
140parserWarning fc warnOpt warnErr = do
142 let cmdline = opt_cmdline (idris_options ist)
143 unless (maybe False (`elem` cmdline) warnOpt) $
144 put ist { parserWarnings = (fc, warnErr) : parserWarnings ist }
146{- * Space, comments and literals (token/lexing like parsers) -}
148-- | Consumes any simple whitespace (any character which satisfies Char.isSpace)
149simpleWhiteSpace :: Parsing m => m ()
150simpleWhiteSpace = () <$ P.satisfy isSpace
152-- | Checks if a charcter is end of line
157-- | A parser that succeeds at the end of the line
158eol :: Parsing m => m ()
159eol = () <$ P.satisfy isEol <|> P.lookAhead P.eof <?> "end of line"
161{- | Consumes a single-line comment
164 SingleLineComment_t ::= '--' ~EOL_t* EOL_t ;
167singleLineComment :: Parsing m => m ()
168singleLineComment = P.hidden (() <$ string "--" *> many (P.satisfy (not . isEol)) *> eol)
170{- | Consumes a multi-line comment
173 MultiLineComment_t ::=
175 | '{ -' InCommentChars_t
182 | MultiLineComment_t InCommentChars_t
183 | ~'- }'+ InCommentChars_t
187multiLineComment :: Parsing m => m ()
188multiLineComment = P.hidden $ P.try (string "{-" *> string "-}" *> pure ())
189 <|> string "{-" *> inCommentChars
190 where inCommentChars :: Parsing m => m ()
191 inCommentChars = string "-}" *> pure ()
192 <|> P.try (multiLineComment) *> inCommentChars
193 <|> string "|||" *> many (P.satisfy (not . isEol)) *> eol *> inCommentChars
194 <|> P.skipSome (P.noneOf startEnd) *> inCommentChars
195 <|> P.oneOf startEnd *> inCommentChars
200{-| Parses a documentation comment
203 DocComment_t ::= DocCommentLine (ArgCommentLine DocCommentLine*)* ;
205 DocCommentLine ::= '|||' ~EOL_t* EOL_t ;
206 ArgCommentLine ::= '|||' '@' ~EOL_t* EOL_t ;
209docComment :: IdrisParser (Docstring (), [(Name, Docstring ())])
210docComment = do dc <- pushIndent *> docCommentLine
211 rest <- many (indented docCommentLine)
212 args <- many $ do (name, first) <- indented argDocCommentLine
213 rest <- many (indented docCommentLine)
214 return (name, concat (intersperse "\n" (first:rest)))
216 return (parseDocstring $ T.pack (concat (intersperse "\n" (dc:rest))),
217 map (\(n, d) -> (n, parseDocstring (T.pack d))) args)
219 where docCommentLine :: Parsing m => m String
220 docCommentLine = P.hidden $ P.try $ do
222 many (P.satisfy (==' '))
223 contents <- P.option "" (do first <- P.satisfy (\c -> not (isEol c || c == '@'))
224 res <- many (P.satisfy (not . isEol))
229 argDocCommentLine :: IdrisParser (Name, String)
230 argDocCommentLine = do P.string "|||"
231 P.many (P.satisfy isSpace)
233 P.many (P.satisfy isSpace)
235 P.many (P.satisfy isSpace)
236 docs <- P.many (P.satisfy (not . isEol))
240-- | Parses some white space
241whiteSpace :: Parsing m => m ()
242whiteSpace = someSpace <|> pure ()
244-- | Parses a string literal
245stringLiteral :: Parsing m => m String
246stringLiteral = token . P.try $ P.char '"' *> P.manyTill P.charLiteral (P.char '"')
248-- | Parses a char literal
249charLiteral :: Parsing m => m Char
250charLiteral = token . P.try $ P.char '\'' *> P.charLiteral <* P.char '\''
252-- | Parses a natural number
253natural :: Parsing m => m Integer
254natural = token ( P.try (P.char '0' *> P.char' 'x' *> P.hexadecimal)
255 <|> P.try (P.char '0' *> P.char' 'o' *> P.octal)
258-- | Parses a floating point number
259float :: Parsing m => m Double
260float = token . P.try $ P.float
262{- * Symbols, identifiers, names and operators -}
264reservedIdentifiers :: HS.HashSet String
265reservedIdentifiers = HS.fromList
267 , "case", "class", "codata", "constructor", "corecord", "data"
268 , "do", "dsl", "else", "export", "if", "implementation", "implicit"
269 , "import", "impossible", "in", "infix", "infixl", "infixr", "instance"
270 , "interface", "let", "mutual", "namespace", "of", "parameters", "partial"
271 , "postulate", "private", "proof", "public", "quoteGoal", "record"
272 , "rewrite", "syntax", "then", "total", "using", "where", "with"
275identifierOrReservedWithExtraChars :: Parsing m => String -> m String
276identifierOrReservedWithExtraChars extraChars = token $ P.try $ do
277 c <- P.satisfy isAlpha <|> P.oneOf "_"
278 cs <- P.many (P.satisfy isAlphaNum <|> P.oneOf extraChars)
281char :: Parsing m => Char -> m Char
284string :: Parsing m => String -> m String
287-- | Parses a character as a token
288lchar :: Parsing m => Char -> m Char
289lchar = token . P.char
291symbol :: Parsing m => String -> m ()
292symbol = void . token . P.string
294-- | Parses a reserved identifier
295reserved :: Parsing m => String -> m ()
296reserved name = token $ P.try $ do
298 P.notFollowedBy (P.satisfy isAlphaNum <|> P.oneOf "_'.") <?> "end of " ++ name
300-- | Parses an identifier as a token
301identifierWithExtraChars :: Parsing m => String -> m String
302identifierWithExtraChars extraChars = P.try $ do
303 ident <- identifierOrReservedWithExtraChars extraChars
304 when (ident `HS.member` reservedIdentifiers) $ P.unexpected . P.Label . NonEmpty.fromList $ "reserved " ++ ident
305 when (ident == "_") $ P.unexpected . P.Label . NonEmpty.fromList $ "wildcard"
308identifier :: Parsing m => m String
309identifier = identifierWithExtraChars "_'."
311-- | Parses an identifier with possible namespace as a name
312iName :: Parsing m => [String] -> m Name
313iName bad = maybeWithNS identifier bad <?> "name"
315-- | Parses an string possibly prefixed by a namespace
316maybeWithNS :: Parsing m => m String -> [String] -> m Name
317maybeWithNS parser bad = do
318 i <- P.option "" (P.lookAhead identifier)
319 when (i `elem` bad) $ P.unexpected . P.Label . NonEmpty.fromList $ "reserved identifier"
320 mkName <$> P.choice (reverse (parserNoNS parser : parsersNS parser i))
321 where parserNoNS :: Parsing m => m String -> m (String, String)
322 parserNoNS = fmap (\x -> (x, ""))
323 parserNS :: Parsing m => m String -> String -> m (String, String)
324 parserNS parser ns = do xs <- trackExtent (string ns)
328 parsersNS :: Parsing m => m String -> String -> [m (String, String)]
329 parsersNS parser i = [P.try (parserNS parser ns) | ns <- (initsEndAt (=='.') i)]
332name :: (Parsing m, MonadState IState m) => m Name
334 keywords <- syntax_keywords <$> get
335 aliases <- module_aliases <$> get
337 return (unalias aliases n)
340 unalias :: M.Map [T.Text] [T.Text] -> Name -> Name
341 unalias aliases (NS n ns) | Just ns' <- M.lookup ns aliases = NS n ns'
342 unalias aliases name = name
344{- | List of all initial segments in ascending order of a list. Every
345such initial segment ends right before an element satisfying the given
348initsEndAt :: (a -> Bool) -> [a] -> [[a]]
350initsEndAt p (x:xs) | p x = [] : x_inits_xs
351 | otherwise = x_inits_xs
352 where x_inits_xs = [x : cs | cs <- initsEndAt p xs]
355{- | Create a `Name' from a pair of strings representing a base name and its
358mkName :: (String, String) -> Name
359mkName (n, "") = sUN n
360mkName (n, ns) = sNS (sUN n) (reverse (parseNS ns))
361 where parseNS x = case span (/= '.') x of
363 (x, '.':y) -> x : parseNS y
365-- | Parse a package name
366packageName :: Parsing m => m String
367packageName = (:) <$> P.oneOf firstChars <*> many (P.oneOf remChars)
368 where firstChars = ['a'..'z'] ++ ['A'..'Z']
369 remChars = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ ['-','_']
371{- * Position helpers -}
374-- | Bind constraints to term
375bindList :: (RigCount -> Name -> FC -> PTerm -> PTerm -> PTerm) -> [(RigCount, Name, FC, PTerm)] -> PTerm -> PTerm
377bindList b ((r, n, fc, t):bs) sc = b r n fc t (bindList b bs sc)
379{- | @commaSeparated p@ parses one or more occurences of `p`,
380 separated by commas and optional whitespace. -}
381commaSeparated :: Parsing m => m a -> m [a]
382commaSeparated p = p `P.sepBy1` (P.space >> P.char ',' >> P.space)
384{- * Layout helpers -}
386-- | Push indentation to stack
387pushIndent :: IdrisParser ()
388pushIndent = do columnNumber <- indent
390 put (ist { indent_stack = columnNumber : indent_stack ist })
392-- | Pops indentation from stack
393popIndent :: IdrisParser ()
394popIndent = do ist <- get
395 case indent_stack ist of
396 [] -> error "The impossible happened! Tried to pop an indentation level where none was pushed (underflow)."
397 (x : xs) -> put (ist { indent_stack = xs })
400-- | Gets current indentation
401indent :: Parsing m => m Int
402indent = P.unPos . P.sourceColumn <$> P.getSourcePos
404-- | Gets last indentation
405lastIndent :: (MonadState IState m) => m Int
406lastIndent = do ist <- get
407 case indent_stack ist of
411-- | Applies parser in an indented position
412indented :: IdrisParser a -> IdrisParser a
413indented p = notEndBlock *> p <* keepTerminator
415-- | Applies parser to get a block (which has possibly indented statements)
416indentedBlock :: IdrisParser a -> IdrisParser [a]
417indentedBlock p = do openBlock
419 res <- many (indented p)
424-- | Applies parser to get a block with at least one statement (which has possibly indented statements)
425indentedBlock1 :: IdrisParser a -> IdrisParser [a]
426indentedBlock1 p = do openBlock
428 res <- some (indented p)
433-- | Applies parser to get a block with exactly one (possibly indented) statement
434indentedBlockS :: IdrisParser a -> IdrisParser a
435indentedBlockS p = do openBlock
443-- | Checks if the following character matches provided parser
444lookAheadMatches :: Parsing m => m a -> m Bool
445lookAheadMatches p = isJust <$> P.lookAhead (P.optional p)
447-- | Parses a start of block
448openBlock :: IdrisParser ()
449openBlock = do lchar '{'
451 put (ist { brace_stack = Nothing : brace_stack ist })
454 -- if we're not indented further, it's an empty block, so
455 -- increment lvl to ensure we get to the end
456 let lvl = case brace_stack ist of
458 if lvl' <= lvl_old then lvl_old+1
460 [] -> if lvl' == 1 then 2 else lvl'
462 put (ist { brace_stack = Just lvl : brace_stack ist })
465-- | Parses an end of block
466closeBlock :: IdrisParser ()
467closeBlock = do ist <- get
468 bs <- case brace_stack ist of
470 Nothing : xs -> lchar '}' >> return xs <?> "end of block"
471 Just lvl : xs -> (do i <- indent
472 isParen <- lookAheadMatches (char ')')
473 isIn <- lookAheadMatches (reserved "in")
474 if i >= lvl && not (isParen || isIn)
475 then fail "not end of block"
477 <|> (do notOpenBraces
480 put (ist { brace_stack = bs })
482-- | Parses a terminator
483terminator :: IdrisParser ()
484terminator = do lchar ';'; popIndent
485 <|> do c <- indent; l <- lastIndent
486 if c <= l then popIndent else fail "not a terminator"
487 <|> do isParen <- lookAheadMatches (P.oneOf ")}")
488 if isParen then popIndent else fail "not a terminator"
489 <|> P.lookAhead P.eof
491-- | Parses and keeps a terminator
492keepTerminator :: IdrisParser ()
493keepTerminator = () <$ lchar ';'
494 <|> do c <- indent; l <- lastIndent
495 unless (c <= l) $ fail "not a terminator"
496 <|> do isParen <- lookAheadMatches (P.oneOf ")}|")
497 isIn <- lookAheadMatches (reserved "in")
498 unless (isIn || isParen) $ fail "not a terminator"
499 <|> P.lookAhead P.eof
501-- | Checks if application expression does not end
502notEndApp :: IdrisParser ()
503notEndApp = do c <- indent; l <- lastIndent
504 when (c <= l) (fail "terminator")
506-- | Checks that it is not end of block
507notEndBlock :: IdrisParser ()
508notEndBlock = do ist <- get
509 case brace_stack ist of
510 Just lvl : xs -> do i <- indent
511 isParen <- lookAheadMatches (P.char ')')
512 when (i < lvl || isParen) (fail "end of block")
515indentGt :: (Parsing m, MonadState IState m) => m ()
519 when (i <= li) $ fail "Wrong indention: should be greater than context indentation"
521-- | Checks that there are no braces that are not closed
522notOpenBraces :: IdrisParser ()
523notOpenBraces = do ist <- get
524 when (hasNothing $ brace_stack ist) $ fail "end of input"
525 where hasNothing :: [Maybe a] -> Bool
526 hasNothing = any isNothing
528{- | Parses an accessibilty modifier (e.g. public, private) -}
529accessibility' :: IdrisParser Accessibility
530accessibility' = Public <$ reserved "public" <* reserved "export"
531 <|> Frozen <$ reserved "export"
532 <|> Private <$ reserved "private"
533 <?> "accessibility modifier"
535accessibility :: IdrisParser Accessibility
536accessibility = do acc <- optional accessibility'
539 Nothing -> do ist <- get
540 return (default_access ist)
542-- | Adds accessibility option for function
543addAcc :: Name -> Accessibility -> IdrisParser ()
544addAcc n a = do i <- get
545 put (i { hide_list = addDef n a (hide_list i) })
547{- | Add accessbility option for data declarations
548 (works for interfaces too - 'abstract' means the data/interface is visible but members not) -}
549accData :: Accessibility -> Name -> [Name] -> IdrisParser ()
550accData Frozen n ns = do addAcc n Public -- so that it can be used in public definitions
551 mapM_ (\n -> addAcc n Private) ns -- so that they are invisible
552accData a n ns = do addAcc n a
553 mapM_ (`addAcc` a) ns
556{- * Error reporting helpers -}
557{- | Error message with possible fixes list -}
558fixErrorMsg :: String -> [String] -> String
559fixErrorMsg msg fixes = msg ++ ", possible fixes:\n" ++ (concat $ intersperse "\n\nor\n\n" fixes)
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