Agda-2.2.10: src/full/Agda/Syntax/Parser/Layout.hs
{-# LANGUAGE CPP #-}
{-| This module contains the lex actions that handle the layout rules. The way
it works is that the 'Parser' monad keeps track of a stack of
'LayoutContext's specifying the indentation of the layout blocks in scope.
For instance, consider the following incomplete (Haskell) program:
> f x = x'
> where
> x' = case x of { True -> False; False -> ...
At the @...@ the layout context would be
> [NoLayout, Layout 4, Layout 0]
The closest layout block is the one containing the @case@ branches. This
block starts with an open brace (@\'{\'@) and so doesn't use layout. The
second closest block is the @where@ clause. Here, there is no open brace
so the block is started by the @x'@ token which has indentation 4. Finally
there is a top-level layout block with indentation 0.
-}
module Agda.Syntax.Parser.Layout
( openBrace, closeBrace
, withLayout
, offsideRule
, newLayoutContext
, emptyLayout
) where
import Agda.Syntax.Parser.Lexer
import Agda.Syntax.Parser.Alex
import Agda.Syntax.Parser.Monad
import Agda.Syntax.Parser.Tokens
import Agda.Syntax.Parser.LexActions
import Agda.Syntax.Position
-- | Executed upon lexing an open brace (@\'{\'@). Enters the 'NoLayout'
-- context.
openBrace :: LexAction Token
openBrace = token $ \_ ->
do pushContext NoLayout
i <- getParseInterval
return (TokSymbol SymOpenBrace i)
{-| Executed upon lexing a close brace (@\'}\'@). Exits the current layout
context. This might look a bit funny--the lexer will happily use a close
brace to close a context open by a virtual brace. This is not a problem
since the parser will make sure the braces are appropriately matched.
-}
closeBrace :: LexAction Token
closeBrace = token $ \_ ->
do popContext
i <- getParseInterval
return (TokSymbol SymCloseBrace i)
{-| Executed for the first token in each line (see 'Agda.Syntax.Parser.Lexer.bol').
Checks the position of the token relative to the current layout context.
If the token is
- /to the left/ :
Exit the current context and a return virtual close brace (stay in the
'Agda.Syntax.Parser.Lexer.bol' state).
- /same column/ :
Exit the 'Agda.Syntax.Parser.Lexer.bol' state and return a virtual semi
colon.
- /to the right/ :
Exit the 'Agda.Syntax.Parser.Lexer.bol' state and continue lexing.
If the current block doesn't use layout (i.e. it was started by
'openBrace') all positions are considered to be /to the right/.
-}
offsideRule :: LexAction Token
offsideRule inp _ _ =
do offs <- getOffside p
case offs of
LT -> do popContext
return (TokSymbol SymCloseVirtualBrace (Interval p p))
EQ -> do popLexState
return (TokSymbol SymVirtualSemi (Interval p p))
GT -> do popLexState
lexToken
where
p = lexPos inp
{-| This action is only executed from the 'Agda.Syntax.Parser.Lexer.empty_layout'
state. It will exit this state, enter the 'Agda.Syntax.Parser.Lexer.bol' state,
and return a virtual close brace (closing the empty layout block started
by 'newLayoutContext').
-}
emptyLayout :: LexAction Token
emptyLayout inp _ _ =
do popLexState
pushLexState bol
return (TokSymbol SymCloseVirtualBrace (Interval p p))
where
p = lexPos inp
{-| Start a new layout context. This is one of two ways to get out of the
'Agda.Syntax.Parser.Lexer.layout' state (the other is 'openBrace'). There are
two possibilities:
- The current token is to the right of the current layout context (or we're
in a no layout context).
- The current token is to the left of or in the same column as the current
context.
In the first case everything is fine and we enter a new layout context at
the column of the current token. In the second case we have an empty layout
block so we enter the 'Agda.Syntax.Parser.Lexer.empty_layout' state. In both
cases we return a virtual open brace without consuming any input.
Entering a new state when we know we want to generate a virtual @{}@ may
seem a bit roundabout. The thing is that we can only generate one token at
a time, so the way to generate two tokens is to generate the first one and
then enter a state in which the only thing you can do is generate the
second one.
-}
newLayoutContext :: LexAction Token
newLayoutContext inp _ _ =
do let offset = posCol p
ctx <- topContext
case ctx of
Layout prevOffs | prevOffs >= offset ->
do pushLexState empty_layout
return (TokSymbol SymOpenVirtualBrace (Interval p p))
_ ->
do pushContext (Layout offset)
return (TokSymbol SymOpenVirtualBrace (Interval p p))
where
p = lexPos inp
-- | Compute the relative position of a location to the
-- current layout context.
getOffside :: Position -> Parser Ordering
getOffside loc =
do ctx <- topContext
return $ case ctx of
Layout n -> compare (posCol loc) n
_ -> GT