-- {-# 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