module Transfer.Syntax.Layout where import Transfer.Syntax.Lex import Data.Maybe (isNothing, fromJust) -- Generated by the BNF Converter -- local parameters topLayout = True layoutWords = ["let","where","of"] layoutStopWords = ["in"] -- layout separators layoutOpen = "{" layoutClose = "}" layoutSep = ";" -- | Replace layout syntax with explicit layout tokens. resolveLayout :: Bool -- ^ Whether to use top-level layout. -> [Token] -> [Token] resolveLayout tp = res Nothing [if tl then Implicit 1 else Explicit] where -- Do top-level layout if the function parameter and the grammar say so. tl = tp && topLayout res :: Maybe Token -- ^ The previous token, if any. -> [Block] -- ^ A stack of layout blocks. -> [Token] -> [Token] -- The stack should never be empty. res _ [] ts = error $ "Layout error: stack empty. Tokens: " ++ show ts res _ st (t0:ts) -- We found an open brace in the input, -- put an explicit layout block on the stack. -- This is done even if there was no layout word, -- to keep opening and closing braces. | isLayoutOpen t0 = moveAlong (Explicit:st) [t0] ts res _ st (t0:ts) -- Start a new layout block if the first token is a layout word | isLayout t0 = case ts of -- Explicit layout, just move on. The case above -- will push an explicit layout block. t1:_ | isLayoutOpen t1 -> moveAlong st [t0] ts -- at end of file, the start column doesn't matter _ -> let col = if null ts then column t0 else column (head ts) -- insert an open brace after the layout word b:ts' = addToken (nextPos t0) layoutOpen ts -- save the start column st' = Implicit col:st in moveAlong st' [t0,b] ts' -- If we encounter a closing brace, exit the first explicit layout block. | isLayoutClose t0 = let st' = drop 1 (dropWhile isImplicit st) in if null st' then error $ "Layout error: Found " ++ layoutClose ++ " at (" ++ show (line t0) ++ "," ++ show (column t0) ++ ") without an explicit layout block." else moveAlong st' [t0] ts -- We are in an implicit layout block res pt st@(Implicit n:ns) (t0:ts) -- End of implicit block by a layout stop word | isStop t0 = -- Insert a closing brace after the previous token. let b:t0':ts' = addToken (afterPrev pt) layoutClose (t0:ts) -- and exit the current block. -- NOTE: we don't care about the column of the -- stop word. in moveAlong ns [b,t0'] ts' -- End of an implicit layout block | column t0 < n = -- Insert a closing brace after the previous token. let b:t0':ts' = addToken (afterPrev pt) layoutClose (t0:ts) -- Repeat, with the current block removed from the stack in moveAlong ns [b] (t0':ts') -- Encounted a new line in an implicit layout block. | column t0 == n = -- Insert a semicolon after the previous token. -- unless we are the beginning of the file, -- or the previous token is a semicolon or open brace. if isNothing pt || isTokenIn [layoutSep,layoutOpen] (fromJust pt) then moveAlong st [t0] ts else let b:t0':ts' = addToken (afterPrev pt) layoutSep (t0:ts) in moveAlong st [b,t0'] ts' -- Nothing to see here, move along. res _ st (t:ts) = moveAlong st [t] ts -- We are at EOF, close all open implicit non-top-level layout blocks. res (Just t) st [] = addTokens (position t) [layoutClose | Implicit n <- st, not (tl && n == 1)] [] -- This should only happen if the input is empty. res Nothing st [] = [] -- | Move on to the next token. moveAlong :: [Block] -- ^ The layout stack. -> [Token] -- ^ Any tokens just processed. -> [Token] -- ^ the rest of the tokens. -> [Token] moveAlong st [] ts = error $ "Layout error: moveAlong got [] as old tokens" moveAlong st ot ts = ot ++ res (Just $ last ot) st ts data Block = Implicit Int -- ^ An implicit layout block with its start column. | Explicit deriving Show type Position = Posn -- | Check if s block is implicit. isImplicit :: Block -> Bool isImplicit (Implicit _) = True isImplicit _ = False -- | Insert a number of tokens at the begninning of a list of tokens. addTokens :: Position -- ^ Position of the first new token. -> [String] -- ^ Token symbols. -> [Token] -- ^ The rest of the tokens. These will have their -- positions updated to make room for the new tokens . -> [Token] addTokens p ss ts = foldr (addToken p) ts ss -- | Insert a new symbol token at the begninning of a list of tokens. addToken :: Position -- ^ Position of the new token. -> String -- ^ Symbol in the new token. -> [Token] -- ^ The rest of the tokens. These will have their -- positions updated to make room for the new token. -> [Token] addToken p s ts = sToken p s : map (incrGlobal p (length s)) ts -- | Get the position immediately to the right of the given token. -- If no token is given, gets the first position in the file. afterPrev :: Maybe Token -> Position afterPrev = maybe (Pn 0 1 1) nextPos -- | Get the position immediately to the right of the given token. nextPos :: Token -> Position nextPos t = Pn (g + s) l (c + s + 1) where Pn g l c = position t s = tokenLength t -- | Add to the global and column positions of a token. -- The column position is only changed if the token is on -- the same line as the given position. incrGlobal :: Position -- ^ If the token is on the same line -- as this position, update the column position. -> Int -- ^ Number of characters to add to the position. -> Token -> Token incrGlobal (Pn _ l0 _) i (PT (Pn g l c) t) = if l /= l0 then PT (Pn (g + i) l c) t else PT (Pn (g + i) l (c + i)) t incrGlobal _ _ p = error $ "cannot add token at " ++ show p -- | Create a symbol token. sToken :: Position -> String -> Token sToken p s = PT p (TS s) -- reserved word or symbol -- | Get the position of a token. position :: Token -> Position position t = case t of PT p _ -> p Err p -> p -- | Get the line number of a token. line :: Token -> Int line t = case position t of Pn _ l _ -> l -- | Get the column number of a token. column :: Token -> Int column t = case position t of Pn _ _ c -> c -- | Check if a token is one of the given symbols. isTokenIn :: [String] -> Token -> Bool isTokenIn ts t = case t of PT _ (TS r) | elem r ts -> True _ -> False -- | Check if a word is a layout start token. isLayout :: Token -> Bool isLayout = isTokenIn layoutWords -- | Check if a token is a layout stop token. isStop :: Token -> Bool isStop = isTokenIn layoutStopWords -- | Check if a token is the layout open token. isLayoutOpen :: Token -> Bool isLayoutOpen = isTokenIn [layoutOpen] -- | Check if a token is the layout close token. isLayoutClose :: Token -> Bool isLayoutClose = isTokenIn [layoutClose] -- | Get the number of characters in the token. tokenLength :: Token -> Int tokenLength t = length $ prToken t