aihc-parser-4.0.0.0: src/Aihc/Parser/Lex/Layout.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
module Aihc.Parser.Lex.Layout
( applyLayoutTokens,
layoutTransition,
closeImplicitLayoutContext,
)
where
import Aihc.Parser.Lex.Types
import Aihc.Parser.Syntax (Extension, SourceSpan)
import Data.Maybe (fromMaybe)
ordinaryLayout :: ImplicitLayoutSpec
ordinaryLayout =
ImplicitLayoutSpec
{ implicitLayoutSemicolons = LayoutEmitSemicolons,
implicitLayoutIndentPolicy = LayoutAllowNondecreasingIndent,
implicitLayoutBaseline = LayoutCurrentIndent,
implicitLayoutChildBaseline = LayoutCurrentIndent,
implicitLayoutThenElseDepth = Nothing,
implicitLayoutFlushEmptyAtEOF = False
}
strictLayout :: ImplicitLayoutSpec
strictLayout = ordinaryLayout {implicitLayoutIndentPolicy = LayoutStrictIndent}
caseAlternativeLayout :: LayoutState -> ImplicitLayoutSpec
caseAlternativeLayout st =
strictLayout
{ implicitLayoutBaseline = currentChildBaseline (layoutContexts st),
implicitLayoutFlushEmptyAtEOF = True
}
multiWayIfLayout :: ImplicitLayoutSpec
multiWayIfLayout =
strictLayout
{ implicitLayoutSemicolons = LayoutSuppressSemicolons
}
thenElseDoLayout :: ImplicitLayoutSpec
thenElseDoLayout =
ordinaryLayout
{ implicitLayoutThenElseDepth = Just 0
}
thDeclQuoteLayout :: ImplicitLayoutSpec
thDeclQuoteLayout =
strictLayout
{ implicitLayoutChildBaseline = LayoutColumnZero
}
applyLayoutTokens :: Bool -> [Extension] -> [LexToken] -> [LexToken]
applyLayoutTokens enableModuleLayout exts =
go (mkInitialLayoutState enableModuleLayout exts)
where
go st toks =
case toks of
[] ->
-- The lexer ends every token list with 'TkEOF', and that transition
-- closes every open context, so normally nothing is left here. A
-- list that ends without 'TkEOF' anchors any remaining virtual
-- tokens to the last source token; without one there is nothing to
-- anchor them to.
case layoutPrevTokenEndSpan st of
Nothing -> []
Just eofAnchor ->
case finalizeModuleLayoutAtEOF st eofAnchor of
(moduleInserted, stAfterModule) ->
case flushPendingCaseLayoutAtEOF stAfterModule eofAnchor of
(pendingInserted, stAfterPending) ->
moduleInserted <> pendingInserted <> closeAllImplicit (layoutContexts stAfterPending) eofAnchor
tok : rest ->
case layoutTransition st tok of
(emitted, stNext) -> emitted <> go stNext rest
finalizeModuleLayoutAtEOF :: LayoutState -> SourceSpan -> ([LexToken], LayoutState)
finalizeModuleLayoutAtEOF st anchor =
case layoutModuleMode st of
mode
| mode == ModuleLayoutSeekStart || mode == ModuleLayoutAwaitBody ->
( [virtualSymbolToken "{" anchor, virtualSymbolToken "}" anchor],
st {layoutModuleMode = ModuleLayoutDone, layoutPendingLayout = Nothing}
)
_ -> ([], st)
{-# INLINE noteModuleLayoutBeforeToken #-}
noteModuleLayoutBeforeToken :: LayoutState -> LexToken -> LayoutState
noteModuleLayoutBeforeToken st tok =
case layoutModuleMode st of
ModuleLayoutAwaitBody -> st {layoutModuleMode = ModuleLayoutDone}
ModuleLayoutSeekStart ->
case lexTokenKind tok of
TkPragma _ -> st
TkLineComment -> st
TkBlockComment -> st
TkKeywordModule -> st {layoutModuleMode = ModuleLayoutAwaitWhere}
_ -> st {layoutModuleMode = ModuleLayoutDone, layoutPendingLayout = Just (PendingImplicitLayout ordinaryLayout)}
_ -> st
{-# INLINE noteModuleLayoutAfterToken #-}
noteModuleLayoutAfterToken :: LayoutState -> LexToken -> LayoutState
noteModuleLayoutAfterToken st tok =
case layoutModuleMode st of
ModuleLayoutAwaitWhere
| lexTokenKind tok == TkKeywordWhere ->
st {layoutModuleMode = ModuleLayoutAwaitBody, layoutPendingLayout = Just (PendingImplicitLayout ordinaryLayout)}
_ -> st
{-# INLINE openPendingLayout #-}
openPendingLayout :: LayoutState -> LexToken -> ([LexToken], LayoutState, Bool)
openPendingLayout st tok =
case layoutPendingLayout st of
Nothing -> ([], st, False)
Just pending ->
case pending of
PendingMaybeMultiWayIf ->
case lexTokenKind tok of
TkReservedPipe -> openImplicitLayout multiWayIfLayout st tok
_ -> ([], noteClassicIfInAfterThenElse (st {layoutPendingLayout = Nothing}), False)
PendingMaybeLambdaCases ->
case lexTokenKind tok of
TkSpecialLBrace -> ([], st {layoutPendingLayout = Nothing}, False)
tkKind
| closesDelimiter tkKind ->
let anchor = lexTokenSpan tok
openTok = virtualSymbolToken "{" anchor
closeTok = virtualSymbolToken "}" anchor
in ([openTok, closeTok], st {layoutPendingLayout = Nothing}, False)
_ -> openImplicitLayout (caseAlternativeLayout st) st tok
PendingImplicitLayout spec ->
case lexTokenKind tok of
TkSpecialLBrace -> ([], st {layoutPendingLayout = Nothing}, False)
tkKind
| closesDelimiter tkKind ->
-- Closing delimiter immediately after layout-opener (e.g. [d| |])
-- produces empty implicit layout {}, then the closer proceeds normally.
let anchor = lexTokenSpan tok
openTok = virtualSymbolToken "{" anchor
closeTok = virtualSymbolToken "}" anchor
in ([openTok, closeTok], st {layoutPendingLayout = Nothing}, False)
_ -> openImplicitLayout spec st tok
openImplicitLayout :: ImplicitLayoutSpec -> LayoutState -> LexToken -> ([LexToken], LayoutState, Bool)
openImplicitLayout spec st tok =
let col = tokenStartCol tok
parentIndent =
case implicitLayoutBaseline spec of
LayoutCurrentIndent -> currentLayoutIndent (layoutContexts st)
LayoutColumnZero -> 0
openTok = virtualSymbolToken "{" (lexTokenSpan tok)
closeTok = virtualSymbolToken "}" (lexTokenSpan tok)
newContext =
LayoutImplicit
LayoutFrame
{ layoutFrameIndent = col,
layoutFrameSemicolons = implicitLayoutSemicolons spec,
layoutFrameChildBaseline = implicitLayoutChildBaseline spec,
layoutFrameThenElseDepth = implicitLayoutThenElseDepth spec
}
opensEmpty =
case implicitLayoutIndentPolicy spec of
LayoutAllowNondecreasingIndent
| layoutNondecreasingIndent st -> col < parentIndent
_ -> col <= parentIndent
in if opensEmpty
then ([openTok, closeTok], st {layoutPendingLayout = Nothing}, False)
else
( [openTok],
st
{ layoutPendingLayout = Nothing,
layoutContexts = newContext : layoutContexts st
},
True
)
{-# INLINE closeBeforeToken #-}
closeBeforeToken :: LayoutState -> LexToken -> ([LexToken], LayoutState)
closeBeforeToken st tok =
case lexTokenKind tok of
kind
| closesImplicitBeforeDelimiter kind ->
case flushPendingImplicitLayout st anchor of
(pendingInserted, st0) ->
case closeImplicitLayouts anchor (\_ _ -> True) (layoutContexts st0) of
(inserted, ctxs') -> (pendingInserted <> inserted, st0 {layoutContexts = ctxs'})
| closesImplicitBeforeLayoutKeyword kind ->
closeBeforeLayoutKeyword
_ -> ([], st)
where
anchor = lexTokenSpan tok
closeBeforeLayoutKeyword =
let !col = tokenStartCol tok
in case flushPendingImplicitLayout st anchor of
(pendingInserted, st0) ->
case closeImplicitLayouts anchor (shouldClose col) (layoutContexts st0) of
(inserted, ctxs') -> (pendingInserted <> inserted, st0 {layoutContexts = ctxs'})
shouldClose col indent kind =
col < indent || (col == indent && closesSameColumnLayout kind)
closesSameColumnLayout kind =
case (lexTokenKind tok, kind) of
(TkKeywordThen, LayoutFrame {layoutFrameThenElseDepth = Just nestedIfs}) -> nestedIfs == 0
(TkKeywordElse, LayoutFrame {layoutFrameThenElseDepth = Just nestedIfs}) -> nestedIfs == 0
(TkKeywordThen, _) -> False
(TkKeywordElse, _) -> False
_ -> True
flushPendingImplicitLayout :: LayoutState -> SourceSpan -> ([LexToken], LayoutState)
flushPendingImplicitLayout st anchor =
case layoutPendingLayout st of
Just (PendingImplicitLayout _) ->
( [virtualSymbolToken "{" anchor, virtualSymbolToken "}" anchor],
st {layoutPendingLayout = Nothing}
)
_ -> ([], st)
flushPendingCaseLayoutAtEOF :: LayoutState -> SourceSpan -> ([LexToken], LayoutState)
flushPendingCaseLayoutAtEOF st anchor =
case layoutPendingLayout st of
Just (PendingImplicitLayout spec)
| implicitLayoutFlushEmptyAtEOF spec ->
( [virtualSymbolToken "{" anchor, virtualSymbolToken "}" anchor],
st {layoutPendingLayout = Nothing}
)
Just PendingMaybeLambdaCases ->
( [virtualSymbolToken "{" anchor, virtualSymbolToken "}" anchor],
st {layoutPendingLayout = Nothing}
)
_ -> ([], st)
{-# INLINE bolLayout #-}
bolLayout :: LayoutState -> LexToken -> ([LexToken], LayoutState)
bolLayout st tok
| not (isBOL st tok) = ([], st)
| otherwise =
case closeImplicitLayouts (lexTokenSpan tok) (\indent _ -> col < indent) (layoutContexts st) of
(inserted, contexts') ->
let semiAnchor = fromMaybe (lexTokenSpan tok) (layoutPrevTokenEndSpan st)
eqSemi =
case currentLayoutIndentMaybe contexts' of
Just indent
| col == indent,
currentLayoutAllowsSemicolon contexts',
lexTokenKind tok /= TkKeywordWhere ->
[virtualSymbolToken ";" semiAnchor]
_ -> []
in (inserted <> eqSemi, st {layoutContexts = contexts'})
where
!col = tokenStartCol tok
currentLayoutAllowsSemicolon :: [LayoutContext] -> Bool
currentLayoutAllowsSemicolon contexts =
case contexts of
LayoutImplicit LayoutFrame {layoutFrameSemicolons = LayoutSuppressSemicolons} : _ -> False
LayoutImplicit _ : _ -> True
_ -> False
closeImplicitLayouts :: SourceSpan -> (Int -> LayoutFrame -> Bool) -> [LayoutContext] -> ([LexToken], [LayoutContext])
closeImplicitLayouts anchor shouldClose = go []
where
closeTok = virtualSymbolToken "}" anchor
go acc contexts =
case contexts of
LayoutImplicit frame : rest
| shouldClose (layoutFrameIndent frame) frame -> go (closeTok : acc) rest
_ -> (reverse acc, contexts)
closeAllImplicit :: [LayoutContext] -> SourceSpan -> [LexToken]
closeAllImplicit contexts anchor =
[virtualSymbolToken "}" anchor | ctx <- contexts, isImplicitLayoutContext ctx]
{-# INLINE stepTokenContext #-}
stepTokenContext :: LayoutState -> LexToken -> LayoutState
stepTokenContext st tok =
case lexTokenKind tok of
TkKeywordDo
| layoutPrevTokenKind st == Just TkKeywordThen
|| layoutPrevTokenKind st == Just TkKeywordElse ->
st {layoutPendingLayout = Just (PendingImplicitLayout thenElseDoLayout)}
| otherwise -> st {layoutPendingLayout = Just (PendingImplicitLayout ordinaryLayout)}
TkKeywordMdo -> st {layoutPendingLayout = Just (PendingImplicitLayout ordinaryLayout)}
TkQualifiedDo {}
| layoutPrevTokenKind st == Just TkKeywordThen
|| layoutPrevTokenKind st == Just TkKeywordElse ->
st {layoutPendingLayout = Just (PendingImplicitLayout thenElseDoLayout)}
| otherwise -> st {layoutPendingLayout = Just (PendingImplicitLayout ordinaryLayout)}
TkQualifiedMdo {} -> st {layoutPendingLayout = Just (PendingImplicitLayout ordinaryLayout)}
TkKeywordOf -> st {layoutPendingLayout = Just (PendingImplicitLayout (caseAlternativeLayout st))}
TkKeywordCase
| layoutPrevTokenKind st == Just TkReservedBackslash ->
st {layoutPendingLayout = Just (PendingImplicitLayout (caseAlternativeLayout st))}
| otherwise -> st
TkVarId "cases"
| layoutLambdaCase st && layoutPrevTokenKind st == Just TkReservedBackslash ->
st {layoutPendingLayout = Just PendingMaybeLambdaCases}
| otherwise -> st
TkKeywordLet -> st {layoutPendingLayout = Just (PendingImplicitLayout strictLayout)}
TkKeywordRec -> st {layoutPendingLayout = Just (PendingImplicitLayout ordinaryLayout)}
TkKeywordWhere -> st {layoutPendingLayout = Just (PendingImplicitLayout strictLayout)}
TkKeywordElse -> decrementAfterThenElseClassicIfDepth st
TkKeywordIf -> st {layoutPendingLayout = Just PendingMaybeMultiWayIf}
TkTHDeclQuoteOpen ->
st
{ layoutContexts = LayoutDelimiter : layoutContexts st,
layoutPendingLayout = Just (PendingImplicitLayout thDeclQuoteLayout)
}
kind
| opensDelimiter kind ->
st {layoutContexts = LayoutDelimiter : layoutContexts st}
kind
| closesDelimiter kind ->
st {layoutContexts = popToDelimiter (layoutContexts st)}
TkSpecialLBrace -> st {layoutContexts = LayoutExplicit : layoutContexts st}
TkSpecialRBrace -> st {layoutContexts = popOneContext (layoutContexts st)}
_ -> st
mapAfterThenElse :: (Int -> Int) -> [LayoutContext] -> [LayoutContext]
mapAfterThenElse f = go
where
go contexts =
case contexts of
LayoutImplicit frame@LayoutFrame {layoutFrameThenElseDepth = Just nestedIfs} : rest ->
LayoutImplicit frame {layoutFrameThenElseDepth = Just (f nestedIfs)} : rest
ctx : rest -> ctx : go rest
[] -> []
noteClassicIfInAfterThenElse :: LayoutState -> LayoutState
noteClassicIfInAfterThenElse st = st {layoutContexts = mapAfterThenElse (+ 1) (layoutContexts st)}
decrementAfterThenElseClassicIfDepth :: LayoutState -> LayoutState
decrementAfterThenElseClassicIfDepth st = st {layoutContexts = mapAfterThenElse (max 0 . subtract 1) (layoutContexts st)}
popToDelimiter :: [LayoutContext] -> [LayoutContext]
popToDelimiter contexts =
case contexts of
LayoutDelimiter : rest -> rest
_ : rest -> popToDelimiter rest
[] -> []
popOneContext :: [LayoutContext] -> [LayoutContext]
popOneContext contexts =
case contexts of
_ : rest -> rest
[] -> []
currentLayoutIndent :: [LayoutContext] -> Int
currentLayoutIndent contexts = fromMaybe 0 (currentLayoutIndentMaybe contexts)
currentLayoutIndentMaybe :: [LayoutContext] -> Maybe Int
currentLayoutIndentMaybe contexts =
case contexts of
LayoutImplicit LayoutFrame {layoutFrameIndent = indent} : _ -> Just indent
_ -> Nothing
currentChildBaseline :: [LayoutContext] -> LayoutBaseline
currentChildBaseline contexts =
case contexts of
LayoutImplicit LayoutFrame {layoutFrameChildBaseline = baseline} : _ -> baseline
_ -> LayoutCurrentIndent
isImplicitLayoutContext :: LayoutContext -> Bool
isImplicitLayoutContext ctx =
case ctx of
LayoutImplicit _ -> True
LayoutExplicit -> False
LayoutDelimiter -> False
-- | Tokens that open a delimiter context (parens, brackets, TH quotes,
-- unboxed parens). These push 'LayoutDelimiter' to suppress implicit-layout
-- closures inside the delimited group.
--
-- Note: 'TkTHDeclQuoteOpen' is intentionally absent here because it is handled
-- explicitly in 'stepTokenContext', where it both pushes 'LayoutDelimiter' and
-- sets up a pending implicit layout for the declaration splice body.
opensDelimiter :: LexTokenKind -> Bool
opensDelimiter kind =
case kind of
TkSpecialLParen -> True
TkSpecialLBracket -> True
TkTHExpQuoteOpen -> True
TkTHTypedQuoteOpen -> True
TkTHTypeQuoteOpen -> True
TkTHPatQuoteOpen -> True
TkSpecialUnboxedLParen -> True
_ -> False
-- | Tokens that close a delimiter context (parens, brackets, TH quotes,
-- unboxed parens). These pop the context stack back to the matching
-- 'LayoutDelimiter' via 'popToDelimiter' in 'stepTokenContext'.
--
-- Related: 'closesImplicitBeforeDelimiter' determines which closing tokens
-- also force all intervening implicit layouts to emit virtual @}@ tokens
-- before the delimiter is popped. 'TkSpecialRBrace' is present there (but
-- absent here) because it closes 'LayoutExplicit', not 'LayoutDelimiter'.
closesDelimiter :: LexTokenKind -> Bool
closesDelimiter kind =
case kind of
TkSpecialRParen -> True
TkSpecialUnboxedRParen -> True
TkSpecialRBracket -> True
TkTHExpQuoteClose -> True
TkTHTypedQuoteClose -> True
_ -> False
-- | Tokens before which all intervening implicit layouts must be closed
-- (emitting virtual @}@ tokens). See 'closesDelimiter' for the relationship
-- between these two predicates.
closesImplicitBeforeDelimiter :: LexTokenKind -> Bool
closesImplicitBeforeDelimiter kind =
case kind of
TkSpecialRParen -> True
TkSpecialUnboxedRParen -> True
TkSpecialRBracket -> True
TkTHExpQuoteClose -> True
TkTHTypedQuoteClose -> True
TkSpecialRBrace -> True
_ -> False
-- | Reserved words that cannot start an item in the currently open layout list.
-- At the same column as that layout they trigger the report's parse-error rule:
-- inserting @}@ makes the keyword legal where inserting @;@ would not.
closesImplicitBeforeLayoutKeyword :: LexTokenKind -> Bool
closesImplicitBeforeLayoutKeyword kind =
case kind of
TkKeywordWhere -> True
TkKeywordIn -> True
TkKeywordThen -> True
TkKeywordElse -> True
_ -> False
isBOL :: LayoutState -> LexToken -> Bool
isBOL _ = lexTokenAtLineStart
layoutTransition :: LayoutState -> LexToken -> ([LexToken], LayoutState)
layoutTransition st tok =
case lexTokenKind tok of
TkLineComment -> ([tok], st)
TkBlockComment -> ([tok], st)
TkEOF ->
let !eofAnchor = fromMaybe (lexTokenSpan tok) (layoutPrevTokenEndSpan st)
in case finalizeModuleLayoutAtEOF st eofAnchor of
(moduleInserted, stAfterModule) ->
case flushPendingCaseLayoutAtEOF stAfterModule eofAnchor of
(pendingInserted, stAfterPending) ->
( moduleInserted <> pendingInserted <> closeAllImplicit (layoutContexts stAfterPending) eofAnchor <> [tok],
stAfterPending {layoutContexts = [], layoutBuffer = []}
)
_ ->
-- Every intermediate result is matched with 'case' rather than a lazy
-- tuple pattern. A lazy pattern binding here would allocate the pair
-- plus a selector thunk per component, on every token of every file;
-- matching strictly lets GHC unbox the pairs away entirely.
case closeBeforeToken (noteModuleLayoutBeforeToken st tok) tok of
(preInserted, stBeforePending) ->
case openPendingLayout stBeforePending tok of
(pendingInserted, stAfterPending, skipBOL) ->
case (if skipBOL then ([], stAfterPending) else bolLayout stAfterPending tok) of
(bolInserted, stAfterBOL) ->
let !stAfterToken = noteModuleLayoutAfterToken (stepTokenContext stAfterBOL tok) tok
newEndSpan =
if lexTokenOrigin tok == FromSource
then Just (lexTokenSpan tok)
else layoutPrevTokenEndSpan stAfterToken
!stNext =
stAfterToken
{ layoutPrevTokenKind = Just (lexTokenKind tok),
layoutPrevTokenEndSpan = newEndSpan,
layoutBuffer = []
}
in (prependInserted preInserted pendingInserted bolInserted tok, stNext)
{-# INLINE layoutTransition #-}
-- | The emitted token list for one ordinary token: the virtual tokens the
-- transition inserted, in order, followed by the token itself.
--
-- The overwhelmingly common case is that no virtual token was inserted at all,
-- and spelling that case out avoids building three @(++)@ thunks per token.
prependInserted :: [LexToken] -> [LexToken] -> [LexToken] -> LexToken -> [LexToken]
prependInserted preInserted pendingInserted bolInserted tok =
case (preInserted, pendingInserted, bolInserted) of
([], [], []) -> [tok]
_ -> preInserted <> (pendingInserted <> (bolInserted <> [tok]))
{-# INLINE prependInserted #-}
closeImplicitLayoutContext :: LayoutState -> Maybe LayoutState
closeImplicitLayoutContext st =
case (layoutContexts st, layoutPrevTokenEndSpan st) of
-- Only a source token opens an implicit context, so an open context always
-- has a preceding source token to anchor the virtual brace to.
(LayoutImplicit _ : rest, Just anchor) ->
Just
st
{ layoutContexts = rest,
layoutBuffer = virtualSymbolToken "}" anchor : layoutBuffer st
}
_ -> Nothing