@@ -5,9 +5,7 @@ Copyright :
5License : BSD3
5License : BSD3
6Maintainer : The Idris Community.
6Maintainer : The Idris Community.
8−{-# LANGUAGE CPP, ConstraintKinds, FlexibleInstances,
8+{-# LANGUAGE ConstraintKinds, FlexibleContexts #-}
9− GeneralizedNewtypeDeriving, PatternGuards, StandaloneDeriving,
10− TypeSynonymInstances #-}
11module Idris.Parser.Helpers where
9module Idris.Parser.Helpers where
13import Idris.AbsSyntax
11import Idris.AbsSyntax
@@ -23,71 +21,58 @@ import Prelude hiding (pi)
23import Control.Applicative
21import Control.Applicative
24import Control.Monad
22import Control.Monad
25import Control.Monad.State.Strict
23import Control.Monad.State.Strict
26−import qualified Data.ByteString.UTF8 as UTF8
27import Data.Char
24import Data.Char
28import qualified Data.HashSet as HS
25import qualified Data.HashSet as HS
29import Data.List
26import Data.List
27+import qualified Data.List.NonEmpty as NonEmpty
30import qualified Data.Map as M
28import qualified Data.Map as M
31import Data.Maybe
29import Data.Maybe
32import qualified Data.Text as T
30import qualified Data.Text as T
31+import Data.Void (Void(..))
33import System.FilePath
32import System.FilePath
33+import Text.Megaparsec ((<?>))
34−import qualified Text.Parser.Char as Chr
34+import qualified Text.Megaparsec as P
35−import Text.Parser.LookAhead
35+import qualified Text.Megaparsec.Char as P
36−import qualified Text.Parser.Token as Tok
36+import qualified Text.Megaparsec.Char.Lexer as P hiding (space)
37import qualified Text.PrettyPrint.ANSI.Leijen as PP
37import qualified Text.PrettyPrint.ANSI.Leijen as PP
38−import Text.Trifecta ((<?>))
38+
39−import qualified Text.Trifecta as P
40−import qualified Text.Trifecta.Delta as P
42-- | Idris parser with state used during parsing
40-- | Idris parser with state used during parsing
43−type IdrisParser = StateT IState P.Parser
41+type IdrisParser = StateT IState IdrisInnerParser
44−
42+type IdrisInnerParser = P.Parsec Void String
45−#if MIN_VERSION_base(4,9,0)
43+type ParseState = P.State String
46−instance {-# OVERLAPPING #-} P.DeltaParsing IdrisParser where
44+data ParseError = ParseError String (P.ParseError (P.Token String) Void)
47− line = lift P.line
45+
48− {-# INLINE line #-}
46+parseErrorPretty :: ParseError -> String
49− position = lift P.position
47+parseErrorPretty (ParseError s err) = P.parseErrorPretty' s err
50− {-# INLINE position #-}
48+
51− slicedWith f (StateT m) = StateT $ \s -> P.slicedWith (\(a,s') b -> (f a b, s')) $ m s
49+parseErrorDoc :: ParseError -> PP.Doc
52− {-# INLINE slicedWith #-}
50+parseErrorDoc = PP.string . parseErrorPretty
55− restOfLine = lift P.restOfLine
56− {-# INLINE restOfLine #-}
59−someSpace' :: MonadicParsing m => m ()
60−someSpace' = many (simpleWhiteSpace <|> singleLineComment <|> multiLineComment) *> pure ()
62−instance {-# OVERLAPPING #-} P.TokenParsing IdrisParser where
63− someSpace = someSpace'
64− token p = do s <- get
65− (FC fn (sl, sc) _) <- getFC --TODO: Update after fixing getFC
68− (FC fn _ (el, ec)) <- getFC
70− put (s { lastTokenSpan = Just (FC fn (sl, sc) (el, ec)) })
73-- | Generalized monadic parsing constraint type
52-- | Generalized monadic parsing constraint type
74−type MonadicParsing m = (P.DeltaParsing m, LookAheadParsing m, Monad m)
53+type MonadicParsing m = (P.MonadParsec Void String m)
76−class HasLastTokenSpan m where
55+someSpace :: MonadicParsing m => m ()
77− getLastTokenSpan :: m (Maybe FC)
56+someSpace = many (simpleWhiteSpace <|> singleLineComment <|> multiLineComment) *> pure ()
79−instance HasLastTokenSpan IdrisParser where
58+tokenFC :: MonadicParsing m => m a -> m (a, FC)
80− getLastTokenSpan = lastTokenSpan <$> get
59+tokenFC 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))
82−newtype ParseError = ParseError { parseErrorDoc :: PP.Doc }
66+token :: MonadicParsing m => m a -> m a
67+token p = p <* whiteSpace
84-- | Helper to run Idris inner parser based stateT parsers
69-- | Helper to run Idris inner parser based stateT parsers
85−runparser :: StateT st P.Parser res -> st -> String -> String -> Either ParseError res
70+runparser :: StateT st IdrisInnerParser res -> st -> String -> String -> Either ParseError res
86runparser p i inputname s =
71runparser p i inputname s =
87− case P.parseString (evalStateT p i)
72+ case P.parse (evalStateT p i) inputname s of
88− (P.Directed (UTF8.fromString inputname) 0 0 0 0) s of
89− P.Failure (P.ErrInfo doc _) -> Left (ParseError doc)
73+ Left err -> Left $ ParseError s err
90− P.Success value -> Right value
74+ Right v -> Right v
92highlightP :: FC -> OutputAnnotation -> IdrisParser ()
77highlightP :: FC -> OutputAnnotation -> IdrisParser ()
93highlightP fc annot = do ist <- get
78highlightP fc annot = do ist <- get
@@ -136,7 +121,7 @@ isEol _ = False
137-- | A parser that succeeds at the end of the line
122-- | A parser that succeeds at the end of the line
138eol :: MonadicParsing m => m ()
123eol :: MonadicParsing m => m ()
139−eol = () <$ P.satisfy isEol <|> lookAhead P.eof <?> "end of line"
124+eol = () <$ P.satisfy isEol <|> P.lookAhead P.eof <?> "end of line"
141{- | Consumes a single-line comment
126{- | Consumes a single-line comment
@@ -145,10 +130,7 @@ eol = () <$ P.satisfy isEol <|> lookAhead P.eof <?> "end of line"
147singleLineComment :: MonadicParsing m => m ()
132singleLineComment :: MonadicParsing m => m ()
148−singleLineComment = (string "--" *>
133+singleLineComment = P.hidden (() <$ string "--" *> many (P.satisfy (not . isEol)) *> eol)
149− many (P.satisfy (not . isEol)) *>
153{- | Consumes a multi-line comment
135{- | Consumes a multi-line comment
@@ -168,9 +150,8 @@ singleLineComment = (string "--" *>
170multiLineComment :: MonadicParsing m => m ()
152multiLineComment :: MonadicParsing m => m ()
171−multiLineComment = P.try (string "{-" *> (string "-}") *> pure ())
153+multiLineComment = P.hidden $ P.try (string "{-" *> string "-}" *> pure ())
172− <|> string "{-" *> inCommentChars
154+ <|> string "{-" *> inCommentChars
174 where inCommentChars :: MonadicParsing m => m ()
155 where inCommentChars :: MonadicParsing m => m ()
175 inCommentChars = string "-}" *> pure ()
156 inCommentChars = string "-}" *> pure ()
176 <|> P.try (multiLineComment) *> inCommentChars
157 <|> P.try (multiLineComment) *> inCommentChars
@@ -199,56 +180,47 @@ docComment = do dc <- pushIndent *> docCommentLine
199 map (\(n, d) -> (n, parseDocstring (T.pack d))) args)
180 map (\(n, d) -> (n, parseDocstring (T.pack d))) args)
201 where docCommentLine :: MonadicParsing m => m String
182 where docCommentLine :: MonadicParsing m => m String
202− docCommentLine = P.try (do string "|||"
183+ docCommentLine = P.hidden $ P.try $ do
203− many (P.satisfy (==' '))
185+ many (P.satisfy (==' '))
204− contents <- P.option "" (do first <- P.satisfy (\c -> not (isEol c || c == '@'))
186+ contents <- P.option "" (do first <- P.satisfy (\c -> not (isEol c || c == '@'))
205− res <- many (P.satisfy (not . isEol))
187+ res <- many (P.satisfy (not . isEol))
206− return $ first:res)
188+ return $ first:res)
207− eol ; someSpace'
189+ eol ; someSpace
208− return contents)
190+ return contents
210−
192+ argDocCommentLine :: IdrisParser (Name, String)
211− argDocCommentLine = do string "|||"
193+ argDocCommentLine = do P.string "|||"
212− many (P.satisfy isSpace)
194+ P.many (P.satisfy isSpace)
213− char '@'
195+ P.char '@'
214− many (P.satisfy isSpace)
196+ P.many (P.satisfy isSpace)
215 n <- fst <$> name
197 n <- fst <$> name
216− many (P.satisfy isSpace)
198+ P.many (P.satisfy isSpace)
217− docs <- many (P.satisfy (not . isEol))
199+ docs <- P.many (P.satisfy (not . isEol))
218− eol ; someSpace'
200+ P.eol ; someSpace
219 return (n, docs)
201 return (n, docs)
221-- | Parses some white space
203-- | Parses some white space
222whiteSpace :: MonadicParsing m => m ()
204whiteSpace :: MonadicParsing m => m ()
223−whiteSpace = Tok.whiteSpace
205+whiteSpace = someSpace <|> pure ()
225-- | Parses a string literal
207-- | Parses a string literal
226−stringLiteral :: (MonadicParsing m, HasLastTokenSpan m) => m (String, FC)
208+stringLiteral :: MonadicParsing m => m (String, FC)
227−stringLiteral = do str <- Tok.stringLiteral
209+stringLiteral = tokenFC . P.try $ P.char '"' *> P.manyTill P.charLiteral (P.char '"')
228− fc <- getLastTokenSpan
229− return (str, fromMaybe NoFC fc)
231-- | Parses a char literal
211-- | Parses a char literal
232−charLiteral :: (MonadicParsing m, HasLastTokenSpan m) => m (Char, FC)
212+charLiteral :: IdrisParser (Char, FC)
233−charLiteral = do ch <- Tok.charLiteral
213+charLiteral = tokenFC . P.try $ P.char '\'' *> P.charLiteral <* P.char '\''
234− fc <- getLastTokenSpan
235− return (ch, fromMaybe NoFC fc)
237-- | Parses a natural number
215-- | Parses a natural number
238−natural :: (MonadicParsing m, HasLastTokenSpan m) => m (Integer, FC)
216+natural :: IdrisParser (Integer, FC)
239−natural = do n <- Tok.natural
217+natural = tokenFC ( P.try (P.char '0' *> P.char' 'x' *> P.hexadecimal)
240− fc <- getLastTokenSpan
218+ <|> P.try (P.char '0' *> P.char' 'o' *> P.octal)
241− return (n, fromMaybe NoFC fc)
219+ <|> P.try P.decimal)
243−-- | Parses an integral number
244−integer :: MonadicParsing m => m Integer
245−integer = Tok.integer
247-- | Parses a floating point number
221-- | Parses a floating point number
248−float :: (MonadicParsing m, HasLastTokenSpan m) => m (Double, FC)
222+float :: IdrisParser (Double, FC)
249−float = do f <- Tok.double
223+float = tokenFC . P.try $ P.float
250− fc <- getLastTokenSpan
251− return (f, fromMaybe NoFC fc)
253{- * Symbols, identifiers, names and operators -}
225{- * Symbols, identifiers, names and operators -}
@@ -264,30 +236,29 @@ reservedIdentifiers = HS.fromList
266identifierOrReserved :: MonadicParsing m => m String
238identifierOrReserved :: MonadicParsing m => m String
267−identifierOrReserved = P.token $ P.try $ do
239+identifierOrReserved = token $ P.try $ do
268 c <- P.satisfy isAlpha <|> P.oneOf "_"
240 c <- P.satisfy isAlpha <|> P.oneOf "_"
269 cs <- P.many (P.satisfy isAlphaNum <|> P.oneOf "_'.")
241 cs <- P.many (P.satisfy isAlphaNum <|> P.oneOf "_'.")
270 return $ c : cs
242 return $ c : cs
272char :: MonadicParsing m => Char -> m Char
244char :: MonadicParsing m => Char -> m Char
273−char = Chr.char
245+char = P.char
275string :: MonadicParsing m => String -> m String
247string :: MonadicParsing m => String -> m String
276−string = Chr.string
248+string = P.string
278-- | Parses a character as a token
250-- | Parses a character as a token
279lchar :: MonadicParsing m => Char -> m Char
251lchar :: MonadicParsing m => Char -> m Char
280−lchar = P.token . P.char
252+lchar = token . P.char
282-- | Parses a character as a token, returning the source span of the character
254-- | Parses a character as a token, returning the source span of the character
283lcharFC :: MonadicParsing m => Char -> m FC
255lcharFC :: MonadicParsing m => Char -> m FC
284lcharFC ch = do (FC file (l, c) _) <- getFC
256lcharFC ch = do (FC file (l, c) _) <- getFC
285− _ <- P.token (char ch)
257+ _ <- token (P.char ch)
286 return $ FC file (l, c) (l, c+1)
258 return $ FC file (l, c) (l, c+1)
288−-- | Parses string as a token
289symbol :: MonadicParsing m => String -> m ()
260symbol :: MonadicParsing m => String -> m ()
290−symbol = void . Tok.symbol
261+symbol = void . P.symbol someSpace
292symbolFC :: MonadicParsing m => String -> m FC
263symbolFC :: MonadicParsing m => String -> m FC
293symbolFC str = do (FC file (l, c) _) <- getFC
264symbolFC str = do (FC file (l, c) _) <- getFC
@@ -296,7 +267,7 @@ symbolFC str = do (FC file (l, c) _) <- getFC
297-- | Parses a reserved identifier
268-- | Parses a reserved identifier
298reserved :: MonadicParsing m => String -> m ()
269reserved :: MonadicParsing m => String -> m ()
299−reserved name = P.token $ P.try $ do
270+reserved name = token $ P.try $ do
300 P.string name
271 P.string name
301 P.notFollowedBy (P.satisfy isAlphaNum <|> P.oneOf "_'.") <?> "end of " ++ name
272 P.notFollowedBy (P.satisfy isAlphaNum <|> P.oneOf "_'.") <?> "end of " ++ name
@@ -314,7 +285,7 @@ reservedHL str = reservedFC str >>= flip highlightP AnnKeyword
314-- Taken from Parsec (c) Daan Leijen 1999-2001, (c) Paolo Martini 2007
285-- Taken from Parsec (c) Daan Leijen 1999-2001, (c) Paolo Martini 2007
315-- | Parses a reserved operator
286-- | Parses a reserved operator
316reservedOp :: MonadicParsing m => String -> m ()
287reservedOp :: MonadicParsing m => String -> m ()
317−reservedOp name = P.token $ P.try $
288+reservedOp name = token $ P.try $
318 do string name
289 do string name
319 P.notFollowedBy operatorLetter <?> ("end of " ++ show name)
290 P.notFollowedBy operatorLetter <?> ("end of " ++ show name)
@@ -328,8 +299,8 @@ identifier :: (MonadicParsing m) => m (String, FC)
328identifier = P.try $ do
299identifier = P.try $ do
329 (FC f (l, c) _) <- getFC
300 (FC f (l, c) _) <- getFC
330 ident <- identifierOrReserved
301 ident <- identifierOrReserved
331− when (ident `HS.member` reservedIdentifiers) $ P.unexpected $ "reserved " ++ ident
302+ when (ident `HS.member` reservedIdentifiers) $ P.unexpected . P.Label . NonEmpty.fromList $ "reserved " ++ ident
332− when (ident == "_") $ P.unexpected "wildcard"
303+ when (ident == "_") $ P.unexpected . P.Label . NonEmpty.fromList $ "wildcard"
333 return (ident, FC f (l, c) (l, c + length ident))
304 return (ident, FC f (l, c) (l, c + length ident))
335-- | Parses an identifier with possible namespace as a name
306-- | Parses an identifier with possible namespace as a name
@@ -340,8 +311,8 @@ iName bad = maybeWithNS identifier False bad <?> "name"
340maybeWithNS :: (MonadicParsing m) => m (String, FC) -> Bool -> [String] -> m (Name, FC)
311maybeWithNS :: (MonadicParsing m) => m (String, FC) -> Bool -> [String] -> m (Name, FC)
341maybeWithNS parser ascend bad = do
312maybeWithNS parser ascend bad = do
342 fc <- getFC
313 fc <- getFC
343− i <- P.option "" (lookAhead (fst <$> identifier))
314+ i <- P.option "" (P.lookAhead (fst <$> identifier))
344− when (i `elem` bad) $ P.unexpected "reserved identifier"
315+ when (i `elem` bad) $ P.unexpected . P.Label . NonEmpty.fromList $ "reserved identifier"
345 let transf = if ascend then id else reverse
316 let transf = if ascend then id else reverse
346 (x, xs, fc) <- P.choice (transf (parserNoNS parser : parsersNS parser i))
317 (x, xs, fc) <- P.choice (transf (parserNoNS parser : parsersNS parser i))
347 return (mkName (x, xs), fc)
318 return (mkName (x, xs), fc)
@@ -410,7 +381,7 @@ invalidOperators = [":", "=>", "->", "<-", "=", "?=", "|", "**", "==>", "\\", "%
411-- | Parses an operator
382-- | Parses an operator
412symbolicOperator :: MonadicParsing m => m String
383symbolicOperator :: MonadicParsing m => m String
413−symbolicOperator = do op <- P.token . some $ operatorLetter
384+symbolicOperator = do op <- token . some $ operatorLetter
414 when (op `elem` (invalidOperators ++ commentMarkers)) $
385 when (op `elem` (invalidOperators ++ commentMarkers)) $
415 fail $ op ++ " is not a valid operator"
386 fail $ op ++ " is not a valid operator"
416 return op
387 return op
@@ -422,32 +393,17 @@ symbolicOperatorFC = do (FC f (l, c) _) <- getFC
422 return (op, FC f (l, c) (l, c + length op))
393 return (op, FC f (l, c) (l, c + length op))
424{- * Position helpers -}
395{- * Position helpers -}
425−{- | Get filename from position (returns "(interactive)" when no source file is given) -}
426−fileName :: P.Delta -> String
427−fileName (P.Directed fn _ _ _ _) = UTF8.toString fn
428−fileName _ = "(interactive)"
430−{- | Get line number from position -}
431−lineNum :: P.Delta -> Int
432−lineNum (P.Lines l _ _ _) = fromIntegral l + 1
433−lineNum (P.Directed _ l _ _ _) = fromIntegral l + 1
436−{- | Get column number from position -}
437−columnNum :: P.Delta -> Int
438−columnNum pos = fromIntegral (P.column pos) + 1
441{- | Get file position as FC -}
396{- | Get file position as FC -}
442getFC :: MonadicParsing m => m FC
397getFC :: MonadicParsing m => m FC
443−getFC = do s <- P.position
398+getFC = do pos <- P.getPosition
399+ let columnNumber = P.unPos . P.sourceColumn $ pos
400+ let lineNumber = P.unPos . P.sourceLine $ pos
444− let (dir, file) = splitFileName (fileName s)
401+ let (dir, file) = splitFileName (P.sourceName pos)
445− let f = if dir == addTrailingPathSeparator "." then file else fileName s
402+ let f = if dir == addTrailingPathSeparator "." then file else P.sourceName pos
446− return $ FC f (lineNum s, columnNum s) (lineNum s, columnNum s) -- TODO: Change to actual spanning
403+ return $ FC f (lineNumber, columnNumber) (lineNumber, columnNumber) -- TODO: Change to actual spanning
447 -- Issue #1594 on the Issue Tracker.
404 -- Issue #1594 on the Issue Tracker.
448 -- https://github.com/idris-lang/Idris-dev/issues/1594
405 -- https://github.com/idris-lang/Idris-dev/issues/1594
451{-* Syntax helpers-}
407{-* Syntax helpers-}
452-- | Bind constraints to term
408-- | Bind constraints to term
453bindList :: (RigCount -> Name -> FC -> PTerm -> PTerm -> PTerm) -> [(RigCount, Name, FC, PTerm)] -> PTerm -> PTerm
409bindList :: (RigCount -> Name -> FC -> PTerm -> PTerm -> PTerm) -> [(RigCount, Name, FC, PTerm)] -> PTerm -> PTerm
@@ -457,15 +413,15 @@ bindList b ((r, n, fc, t):bs) sc = b r n fc t (bindList b bs sc)
457{- | @commaSeparated p@ parses one or more occurences of `p`,
413{- | @commaSeparated p@ parses one or more occurences of `p`,
458 separated by commas and optional whitespace. -}
414 separated by commas and optional whitespace. -}
459commaSeparated :: MonadicParsing m => m a -> m [a]
415commaSeparated :: MonadicParsing m => m a -> m [a]
460−commaSeparated p = p `P.sepBy1` (P.spaces >> P.char ',' >> P.spaces)
416+commaSeparated p = p `P.sepBy1` (P.space >> P.char ',' >> P.space)
462{- * Layout helpers -}
418{- * Layout helpers -}
464-- | Push indentation to stack
420-- | Push indentation to stack
465pushIndent :: IdrisParser ()
421pushIndent :: IdrisParser ()
466−pushIndent = do pos <- P.position
422+pushIndent = do columnNumber <- indent
467 ist <- get
423 ist <- get
468− put (ist { indent_stack = (fromIntegral (P.column pos) + 1) : indent_stack ist })
424+ put (ist { indent_stack = columnNumber : indent_stack ist })
470-- | Pops indentation from stack
426-- | Pops indentation from stack
471popIndent :: IdrisParser ()
427popIndent :: IdrisParser ()
@@ -476,8 +432,8 @@ popIndent = do ist <- get
478-- | Gets current indentation
434-- | Gets current indentation
479−indent :: IdrisParser Int
435+indent :: MonadicParsing m => m Int
480−indent = liftM ((+1) . fromIntegral . P.column) P.position
436+indent = P.unPos . P.sourceColumn <$> P.getPosition
482-- | Gets last indentation
438-- | Gets last indentation
483lastIndent :: IdrisParser Int
439lastIndent :: IdrisParser Int
@@ -520,8 +476,7 @@ indentedBlockS p = do openBlock
521-- | Checks if the following character matches provided parser
477-- | Checks if the following character matches provided parser
522lookAheadMatches :: MonadicParsing m => m a -> m Bool
478lookAheadMatches :: MonadicParsing m => m a -> m Bool
523−lookAheadMatches p = do match <- lookAhead (optional p)
479+lookAheadMatches p = isJust <$> P.lookAhead (P.optional p)
524− return $ isJust match
526-- | Parses a start of block
481-- | Parses a start of block
527openBlock :: IdrisParser ()
482openBlock :: IdrisParser ()
@@ -565,7 +520,7 @@ terminator = do lchar ';'; popIndent
565 if c <= l then popIndent else fail "not a terminator"
520 if c <= l then popIndent else fail "not a terminator"
566 <|> do isParen <- lookAheadMatches (P.oneOf ")}")
521 <|> do isParen <- lookAheadMatches (P.oneOf ")}")
567 if isParen then popIndent else fail "not a terminator"
522 if isParen then popIndent else fail "not a terminator"
568− <|> lookAhead P.eof
523+ <|> P.lookAhead P.eof
570-- | Parses and keeps a terminator
525-- | Parses and keeps a terminator
571keepTerminator :: IdrisParser ()
526keepTerminator :: IdrisParser ()
@@ -575,7 +530,7 @@ keepTerminator = () <$ lchar ';'
575 <|> do isParen <- lookAheadMatches (P.oneOf ")}|")
530 <|> do isParen <- lookAheadMatches (P.oneOf ")}|")
576 isIn <- lookAheadMatches (reserved "in")
531 isIn <- lookAheadMatches (reserved "in")
577 unless (isIn || isParen) $ fail "not a terminator"
532 unless (isIn || isParen) $ fail "not a terminator"
578− <|> lookAhead P.eof
533+ <|> P.lookAhead P.eof
580-- | Checks if application expression does not end
535-- | Checks if application expression does not end
581notEndApp :: IdrisParser ()
536notEndApp :: IdrisParser ()
@@ -587,7 +542,7 @@ notEndBlock :: IdrisParser ()
587notEndBlock = do ist <- get
542notEndBlock = do ist <- get
588 case brace_stack ist of
543 case brace_stack ist of
589 Just lvl : xs -> do i <- indent
544 Just lvl : xs -> do i <- indent
590− isParen <- lookAheadMatches (char ')')
545+ isParen <- lookAheadMatches (P.char ')')
591 when (i < lvl || isParen) (fail "end of block")
546 when (i < lvl || isParen) (fail "end of block")
592 _ -> return ()
547 _ -> return ()