{-# LANGUAGE RecordWildCards #-} module Text.Layout.OffSides ( -- * Off-sides Rule Layout(..), layout, -- ** Utilities wrapToken, ) where import AlexTools import qualified Data.Text as T data Layout tok = Layout { beginsLayout :: tok -> Bool -- ^ True when this token begins layout , endsLayout :: tok -> Bool -- ^ True when this token explicitly ends layout , sep :: SourceRange -> Lexeme tok -- ^ The separator token , start :: SourceRange -> Lexeme tok -- ^ Layout block starting token , end :: SourceRange -> Lexeme tok -- ^ Layout block ending token } -- | Turn a single token into the form required by the 'Layout' type. wrapToken :: tok -> SourceRange -> Lexeme tok wrapToken lexemeToken lexemeRange = Lexeme { lexemeText = T.empty, .. } -- | The off-sides rule. layout :: Layout tok -> [Lexeme tok] -> [Lexeme tok] layout Layout { .. } = go Nothing [] where startCol SourceRange { sourceFrom = SourcePos { .. } } = sourceColumn currentLevel (loc : _) = startCol loc currentLevel [] = 0 -- a new layout level has been started, emit a starting token, and push the -- current level on the stack. go Just{} stack (tok : toks) = let loc = lexemeRange tok in start loc : tok : go Nothing (loc:stack) toks go (Just loc) stack [] = start loc : go Nothing (loc : stack) [] go Nothing stack ts@(tok : toks) -- when the next token would close the current level | startCol loc < currentLevel stack = end loc : go Nothing (tail stack) ts | beginsLayout (lexemeToken tok) = let sepToks | startCol loc == currentLevel stack = [sep loc] | otherwise = [] in sepToks ++ tok : go (Just loc) stack toks | endsLayout (lexemeToken tok) = end loc : tok : go Nothing (tail stack) toks | startCol loc == currentLevel stack = sep loc : tok : go Nothing stack toks | otherwise = tok : go Nothing stack toks where loc = lexemeRange tok go _ stack [] = [ end loc | loc <- stack ]