{-| Module : LayoutRule License : GPL Maintainer : helium@cs.uu.nl Stability : experimental Portability : portable -} module Helium.Parser.LayoutRule(layout) where import Helium.Parser.LexerToken(Token, Lexeme(..), lexemeLength) import Text.ParserCombinators.Parsec.Pos layout :: [Token] -> [Token] layout [] = [] layout input@((pos, lexeme):_) = optimise $ case lexeme of LexKeyword "module" -> lay dummyToken [] input LexSpecial '{' -> lay dummyToken [] input _ -> (pos, LexInsertedOpenBrace) : lay dummyToken [CtxLay (sourceColumn pos) False] input where zeroPos = setSourceColumn (setSourceLine pos 0) 0 dummyToken = (zeroPos, LexVar "") optimise :: [Token] -> [Token] optimise (token1@(_, LexInsertedOpenBrace) : (_, LexInsertedSemicolon) : ts) = optimise (token1 : ts) optimise (t:ts) = t : optimise ts optimise [] = [] data Context = CtxLay Int Bool {- let? -} | CtxBrace deriving (Eq,Show) -- previous token -- enclosing contexts lay :: Token -> [Context] -> [Token] -> [Token] -- If we're in a CtxBrace and we see a '}', we leave that context. -- If we see another token, we check to see if we need to add a -- new context. lay _ cc@(CtxBrace:cs) input@(t@(_, lexeme):ts) | lexeme == LexSpecial '}' = t : lay t cs ts | otherwise = t : addContext t cc input -- If we're in a let layout context, an 'in' can end -- the context, too. lay prevToken (CtxLay _ True:cs) (t@(_, LexKeyword "in"):ts) = (behind prevToken, LexInsertedCloseBrace) : t : lay t cs ts -- If we're in a layout context and the new token is not on the -- same line as the previous, we check the column against the -- context. If the new token is on the same line, we only need -- to check whether a context has to be added. lay prevToken@(prevPos, _) cc@(CtxLay ctxCol _:cs) input@(t@(pos, _):_) | sourceLine pos > sourceLine prevPos = -- token on next line? if sourceColumn pos > ctxCol then -- indent more => nothing t : addContext t cc input else if sourceColumn pos == ctxCol then -- indent same => insert ';' (behind prevToken, LexInsertedSemicolon) : t : addContext t cc input else -- indent less => insert brace, remove context and try again (behind prevToken, LexInsertedCloseBrace) : lay prevToken cs input | otherwise = -- token on same line t : addContext t cc input lay _ _ [] = [] lay _ [] input@(t@(_, _):_) = t : addContext t [] input behind :: Token -> SourcePos behind (pos, lexeme) = incSourceColumn pos (lexemeLength lexeme) addContext :: Token -> [Context] -> [Token] -> [Token] -- If we see a '{' we add a CtxBrace context addContext prevToken cs ((_, LexSpecial '{'):ts) = lay prevToken (CtxBrace : cs) ts -- If we see a 'do', 'where', 'of' or 'let' we add a context -- and a '{' only if the next token is not a '{' addContext prevToken cs ((_, LexKeyword keyword):t2@(pos2, lexeme2):ts) | keyword `elem` ["do", "where", "of","let"] = if lexeme2 == LexSpecial '{' then lay prevToken cs (t2:ts) else (pos2, LexInsertedOpenBrace) : lay prevToken (CtxLay (sourceColumn pos2) (keyword == "let") : cs) (t2:ts) | otherwise = lay prevToken cs (t2:ts) addContext prevToken cs (_:ts) = lay prevToken cs ts addContext _ _ [] = []