{-# LANGUAGE ScopedTypeVariables #-} module Yi.Syntax.Layout (layoutHandler, State) where import Yi.Syntax import Yi.Lexer.Alex import Yi.Prelude import Prelude () import Data.Maybe (isJust) import Data.List (dropWhile) data BlockOpen t = Indent Int -- block opened because of indentation; parameter is the column of it. | Paren t -- block opened because of parentheses deriving Show isParen :: BlockOpen t -> Bool isParen (Paren _) = True isParen _ = False data IState t = IState [BlockOpen t] -- current block nesting Bool -- should we open a compound now ? Int -- last line number deriving Show type State t lexState = (IState t, AlexState lexState) -- | Transform a scanner into a scanner that also adds opening, -- closing and "next" tokens to indicate layout. -- @isSpecial@ predicate indicates a token that starts a compound, -- like "where", "do", ... -- @isIgnore@ predicate indicates a token that is to be ignored for -- layout. (eg. pre-processor directive...) -- @parens@ is a list of couple of matching parenthesis-like tokens -- "()[]{}...". layoutHandler :: forall t lexState. (Show t, Eq t) => (t -> Bool) -> [(t,t)] -> (Tok t -> Bool) -> [t] -> Scanner (AlexState lexState) (Tok t) -> Scanner (State t lexState) (Tok t) layoutHandler isSpecial parens isIgnored [openT, closeT, nextT] lexSource = Scanner { scanLooked = scanLooked lexSource . snd, scanEmpty = error "layoutHandler: scanEmpty", scanInit = (IState [] True (-1), scanInit lexSource), scanRun = \st -> let result = parse (fst st) (scanRun lexSource (snd st)) in --trace ("toks = " ++ show (fmap snd result)) $ result } where dummyAlexState = AlexState { stLexer = error "dummyAlexState: should not be reused for restart!", lookedOffset = maxBound, -- setting this to maxBound ensures nobody ever uses it. stPosn = startPosn } deepestIndent [] = (-1) deepestIndent (Indent i:_) = i deepestIndent (_:levs) = deepestIndent levs deepestParen [] = nextT -- HACK: nextT must not be found in parens. deepestParen (Paren t:_) = t deepestParen (_:levs) = deepestParen levs findParen f t = find ((== t) . f) parens parse :: IState t -> [(AlexState lexState, Tok t)] -> [(State t lexState, Tok t)] parse iSt@(IState levels doOpen lastLine) toks@((aSt, tok @ Tok {tokPosn = Posn _nextOfs line col}) : tokss) -- ignore this token | isIgnored tok = (st, tok) : parse (IState levels doOpen line) tokss -- start a compound | doOpen = (st', tt openT) : parse (IState (Indent col:levels) (False) lastLine) toks -- close, or prepare to close, a paren block | isJust $ findParen id $ (deepestParen levels, tokT tok) -- check that the most nested paren matches. = case levels of Indent _:levs -> (st',tt closeT) : parse (IState levs False lastLine) toks -- close an indent level inside the paren block Paren _ :levs -> (st', tok) : parse (IState levs False line) tokss -- same as the otherwise case below. -- pop an indent block | col < deepestIndent levels = let (_lev:levs) = dropWhile isParen levels in (st', tt closeT) : parse (IState levs doOpen lastLine) toks -- drop all paren levels inside the indent -- next item | line > lastLine && col == deepestIndent levels = (st', tt nextT) : parse (IState (dropWhile isParen levels) doOpen line) toks -- drop all paren levels inside the indent -- open a paren block | isJust $ findParen fst $ (tokT tok) = (st', tok) : parse (IState (Paren (tokT tok):levels) False line) tokss -- prepare to open a compound | isSpecial (tokT tok) = (st', tok) : parse (IState levels True line) tokss | otherwise = (st', tok) : parse (IState levels doOpen line) tokss where st = (iSt, aSt) st' = (iSt, aSt {lookedOffset = max peeked (lookedOffset aSt)}) tt t = Tok t 0 (tokPosn tok) peeked = case tokss of [] -> maxBound (AlexState {lookedOffset = p},_):_ -> p -- This function checked the position and kind of the -- next token. We peeked further, and so must -- update the lookedOffset accordingly. -- finish by closing all the indent states. parse iSt@(IState (Indent _:levs) doOpen posn) [] = ((iSt,dummyAlexState), Tok closeT 0 maxPosn) : parse (IState levs doOpen posn) [] parse (IState (Paren _:levs) doOpen posn) [] = parse (IState levs doOpen posn) [] parse (IState [] _ _) [] = [] parse st _ = error $ "Parse: " ++ show st maxPosn :: Posn maxPosn = Posn (-1) (-1) 0 -- HACK! here we have collusion between using (-1) here and the parsing of -- OnlineTrees, which relies on the position of the last token to stop -- the parsing.