packages feed

aihc-parser 3.0.1.1 → 4.0.0.0

raw patch · 22 files changed

+1166/−608 lines, 22 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Aihc.Parser.Syntax: NoSourceSpan :: SourceSpan
- Aihc.Parser.Syntax: SourceSpan :: !FilePath -> !Int -> !Int -> !Int -> !Int -> !Int -> !Int -> SourceSpan
- Aihc.Parser.Syntax: [sourceSpanEndCol] :: SourceSpan -> !Int
- Aihc.Parser.Syntax: [sourceSpanEndLine] :: SourceSpan -> !Int
- Aihc.Parser.Syntax: [sourceSpanEndOffset] :: SourceSpan -> !Int
- Aihc.Parser.Syntax: [sourceSpanSourceName] :: SourceSpan -> !FilePath
- Aihc.Parser.Syntax: [sourceSpanStartCol] :: SourceSpan -> !Int
- Aihc.Parser.Syntax: [sourceSpanStartLine] :: SourceSpan -> !Int
- Aihc.Parser.Syntax: [sourceSpanStartOffset] :: SourceSpan -> !Int
- Aihc.Parser.Syntax: noSourceSpan :: SourceSpan
+ Aihc.Parser.Syntax: pattern SourceSpan :: Text -> Int -> Int -> Int -> Int -> Int -> Int -> SourceSpan
+ Aihc.Parser.Syntax: sourceSpanEndCol :: SourceSpan -> Int
+ Aihc.Parser.Syntax: sourceSpanEndLine :: SourceSpan -> Int
+ Aihc.Parser.Syntax: sourceSpanEndOffset :: SourceSpan -> Int
+ Aihc.Parser.Syntax: sourceSpanSourceName :: SourceSpan -> Text
+ Aihc.Parser.Syntax: sourceSpanStartCol :: SourceSpan -> Int
+ Aihc.Parser.Syntax: sourceSpanStartLine :: SourceSpan -> Int
+ Aihc.Parser.Syntax: sourceSpanStartOffset :: SourceSpan -> Int

Files

CHANGELOG.md view
@@ -6,6 +6,105 @@  ## [Unreleased] +## [4.0.0.0] - 2026-09-17++### Fixed++- A `{-# LINE n "file" #-}` pragma now applies its file name. The file name was+  dropped for every well-formed LINE pragma, so spans after one kept the source+  name the caller passed in while taking their line and column from the pragma.+  The equivalent `#line` directive was already handled correctly.++### Removed++- The CPP preprocessor benchmark. `BENCHMARKS.md` no longer carries a "CPP+  Performance" table comparing `aihc-cpp` against `clang -E` and `cpphs`, and+  `aihc-parser-bench` no longer depends on `cpphs` or shells out to `clang`.+  `aihc-cpp` is still used to preprocess the parser corpus before measurement;+  only its benchmark is gone.++### Changed++- Remove the `NoSourceSpan` constructor and `noSourceSpan`. A `SourceSpan` is+  now always a concrete span. Parsed syntax gets the span of the tokens it+  consumed, or the zero-width span where the parser stands when it consumed+  none. Every parse error has a span too: an error that names no token, such+  as one raised with `fail`, is located at the token the parser stood on when+  it was raised.+- `sourceSpanSourceName` is now a `Text` rather than a `FilePath`. Callers+  that read the field get a `Text`; callers that build a `SourceSpan` by hand+  pass a `Text`. The name given as `parserSourceName` is still a `FilePath`+  and is converted once when lexing starts.+- `SourceSpan` is now a bidirectional record pattern synonym over a packed+  representation. Constructing a span, matching on one, reading a field and+  record-update syntax all work exactly as before. Two things change: import+  it as `SourceSpan, pattern SourceSpan` plus the field names you use, since+  `SourceSpan (..)` no longer brings the fields into scope, and the `Data`+  instance sees three `Word64` fields rather than six `Int` ones.+- `applyImpliedExtensions` returns its result in `Extension` constructor order+  and without duplicates when it has anything to add, rather than in+  most-recently-enabled-first order. Only membership was ever meaningful; a+  list that is already closed is still returned untouched.++### Performance++- A `SourceSpan` holds its six positions in three unboxed `Word64` fields+  rather than six `Int` ones, two positions to a word. It is the most numerous+  object in a parse tree, so a third off its size shows up in peak heap as+  well as in allocation.+- Expression parsing dispatches the block forms (`do`, `mdo`, qualified `do`,+  `if`, `case`, `let`, `proc`, `\`) and prefix negation on the next token+  instead of trying them in turn. Each block form starts with its own+  keyword, so at most one could ever match, but the old chain of alternatives+  allocated a continuation for all nine plus a backtracking negation at every+  expression position.+- The type-atom parser's fallback branch tries only the three alternatives+  whose leading token is not already dispatched, instead of re-running the+  full eleven-way chain.+- The implied-extension fixpoint runs on the `ExtensionSet` bitset instead of+  on lists, so closing a set costs word operations rather than a `filter` per+  implication and a pair of `sort`s per round. It ran once per file.++Together these cut the `bench-aihc-base` benchmark's allocation by about 20%+(2.93 GB to 2.34 GB over five iterations), its wall time by about 12%+(152 ms to 134 ms per iteration) and its peak heap by about 20%+(21.4 MB to 17.1 MB).++A second round of the same kind:++- The layout engine builds one `LayoutState` per token instead of several.+  Its intermediate results were bound with lazy tuple patterns, so every token+  of every file allocated a pair and a selector thunk per component of it+  before anything was looked at. The emitted token list also skips its three+  `(<>)` thunks in the usual case, where no virtual token was inserted.+- Peeking at the next token is a Megaparsec primitive (`peekToken` and+  friends) that reads the stream's memoized successor, rather than+  `lookAhead anySingle`. Every dispatch point in the parser used the latter,+  and paid for a state save and restore, the `token` machinery and a monadic+  bind each time.+- Optional tokens are decided on the peeked token — `optionalTok` for a token+  on its own, `optionalTokThen` for one that introduces a parser — instead of+  by running a parser and recovering from its failure. This replaced 45 uses+  of `MP.optional (expectedTok …)`, including the record braces and record dot+  that used to be tried after every atom in the file.+- The infix-operator chain, parenthesized operator sections such as `(+)`, the+  record-construction base and type applications as function arguments are all+  now chosen on the next token rather than by trying and backtracking.+- A pragma declaration is recognized from the pending-pragma list on the+  parser state, so an ordinary declaration no longer starts with a failed+  pragma parse.+- A declaration that starts with a variable identifier only tries the pattern+  binding parser when the token after it is `@`, a constructor operator or a+  backtick. Nothing else can make such a declaration a pattern binding, and a+  function definition with arguments is the most common declaration there is.+- `label` is a primitive too: a successful parse now goes straight to the+  caller's continuation, with no `Either` box from `MP.observing` and no bind,+  and the found token comes from the state the failure happened in.++Together these cut the benchmark's allocation by a further 19% (2.52 GB to+2.05 GB over five iterations) and its wall time by about 11% (150 ms to+134 ms per iteration), at unchanged peak heap.+ ## [3.0.1.1] - 2026-09-15  ### Fixed
aihc-parser.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.8 name: aihc-parser-version: 3.0.1.1+version: 4.0.0.0 build-type: Simple license: Unlicense license-file: LICENSE
src/Aihc/Parser.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}  -- | -- Module      : Aihc.Parser@@ -30,7 +31,7 @@   ) where -import Aihc.Parser.Internal.Common (drainParseErrors, eofTok)+import Aihc.Parser.Internal.Common (TokParser, drainParseErrors, eofTok) import Aihc.Parser.Internal.Decl (declParser) import Aihc.Parser.Internal.Errors (parseErrorBundleToSpannedText, parseErrorsToSpannedText) import Aihc.Parser.Internal.Expr (exprParser)@@ -38,7 +39,7 @@ import Aihc.Parser.Internal.Pattern (patternParser) import Aihc.Parser.Internal.Type (typeParser, typeSignatureParser) import Aihc.Parser.Pretty ()-import Aihc.Parser.Syntax (Decl, Expr, Module (..), Pattern, SourceSpan (..), Type, applyImpliedExtensions)+import Aihc.Parser.Syntax (Decl, Expr, Extension, Module (..), Pattern, SourceSpan, Type, applyImpliedExtensions, sourceSpanEndCol, sourceSpanSourceName, sourceSpanStartCol, sourceSpanStartLine, sourceSpanStartOffset, pattern SourceSpan) import Aihc.Parser.Types import Data.ByteString qualified as BS import Data.List qualified as List@@ -85,11 +86,7 @@ -- >>> case parseExpr defaultConfig "1 +" of { ParseErr _ -> "error"; ParseOk _ -> "ok" } -- "error" parseExpr :: ParserConfig -> Text -> ParseResult Expr-parseExpr cfg input =-  let ts = mkTokStream (parserSourceName cfg) (applyImpliedExtensions (parserExtensions cfg)) input-   in case runTokStreamParser (exprParser <* eofTok) (parserSourceName cfg) ts of-        Left bundle -> ParseErr (parseErrorBundleToSpannedText bundle)-        Right expr -> ParseOk expr+parseExpr = runEntry mkTokStream (exprParser <* eofTok)  -- | Parse a Haskell pattern. --@@ -99,11 +96,7 @@ -- >>> shorthand $ parsePattern defaultConfig "Just x" -- ParseOk (PCon "Just" [PVar "x"]) parsePattern :: ParserConfig -> Text -> ParseResult Pattern-parsePattern cfg input =-  let ts = mkTokStream (parserSourceName cfg) (applyImpliedExtensions (parserExtensions cfg)) input-   in case runTokStreamParser (patternParser <* eofTok) (parserSourceName cfg) ts of-        Left bundle -> ParseErr (parseErrorBundleToSpannedText bundle)-        Right pat -> ParseOk pat+parsePattern = runEntry mkTokStream (patternParser <* eofTok)  -- | Parse a Haskell signature type. --@@ -113,11 +106,7 @@ -- >>> case parseSignatureType defaultConfig "_ :: _" of { ParseErr _ -> "error"; ParseOk _ -> "ok" } -- "error" parseSignatureType :: ParserConfig -> Text -> ParseResult Type-parseSignatureType cfg input =-  let ts = mkTokStream (parserSourceName cfg) (applyImpliedExtensions (parserExtensions cfg)) input-   in case runTokStreamParser (typeSignatureParser <* eofTok) (parserSourceName cfg) ts of-        Left bundle -> ParseErr (parseErrorBundleToSpannedText bundle)-        Right ty -> ParseOk ty+parseSignatureType = runEntry mkTokStream (typeSignatureParser <* eofTok)  -- | Parse a Haskell type in the general declaration RHS context. --@@ -130,22 +119,14 @@ -- >>> shorthand $ parseType defaultConfig "_ :: _" -- ParseOk (TKindSig (TWildcard) (TWildcard)) parseType :: ParserConfig -> Text -> ParseResult Type-parseType cfg input =-  let ts = mkTokStream (parserSourceName cfg) (applyImpliedExtensions (parserExtensions cfg)) input-   in case runTokStreamParser (typeParser <* eofTok) (parserSourceName cfg) ts of-        Left bundle -> ParseErr (parseErrorBundleToSpannedText bundle)-        Right ty -> ParseOk ty+parseType = runEntry mkTokStream (typeParser <* eofTok)  -- | Parse a single Haskell declaration. -- -- >>> shorthand $ parseDecl defaultConfig "f x = x + 1" -- ParseOk (DeclValue (FunctionBind "f" [Match {MatchHeadPrefix, [PVar "x"], EInfix (EVar "x") "+" (EInt 1 TInteger)}])) parseDecl :: ParserConfig -> Text -> ParseResult Decl-parseDecl cfg input =-  let ts = mkTokStream (parserSourceName cfg) (applyImpliedExtensions (parserExtensions cfg)) input-   in case runTokStreamParser (declParser <* eofTok) (parserSourceName cfg) ts of-        Left bundle -> ParseErr (parseErrorBundleToSpannedText bundle)-        Right decl -> ParseOk decl+parseDecl = runEntry mkTokStream (declParser <* eofTok)  -- | Parse a complete Haskell module. --@@ -162,25 +143,40 @@ -- Nothing parseModule :: ParserConfig -> Text -> ([(SourceSpan, Text)], Module) parseModule cfg input =-  let ts = mkTokStreamModule (parserSourceName cfg) (applyImpliedExtensions (parserExtensions cfg)) input-      parser = do-        modu <- moduleParser-        errs <- drainParseErrors-        pure (errs, modu)-   in case runTokStreamParser parser (parserSourceName cfg) ts of-        Left bundle ->-          ( parseErrorBundleToSpannedText bundle,-            Module-              { moduleAnns = [],-                moduleHead = Nothing,-                moduleLanguagePragmas = [],-                moduleImports = [],-                moduleDecls = []-              }-          )-        Right (errs, modu) ->-          (parseErrorsToSpannedText errs, modu)+  case runTokStreamParser parser sourceName (mkTokStreamModule sourceName exts input) of+    Left bundle ->+      ( parseErrorBundleToSpannedText sourceName errorStream bundle,+        Module+          { moduleAnns = [],+            moduleHead = Nothing,+            moduleLanguagePragmas = [],+            moduleImports = [],+            moduleDecls = []+          }+      )+    Right (errs, modu) ->+      (parseErrorsToSpannedText sourceName errorStream errs, modu)+  where+    sourceName = parserSourceName cfg+    exts = applyImpliedExtensions (parserExtensions cfg)+    errorStream = rebuildStream (\(name, es, src) -> mkTokStreamModule name es src) (sourceName, exts, input)+    parser = do+      modu <- moduleParser+      errs <- drainParseErrors+      pure (errs, modu) +-- | Run a parser over freshly lexed input. Errors are located on a stream+-- built again from the input, so the parse itself does not retain the token+-- chain (see 'rebuildStream').+runEntry :: (FilePath -> [Extension] -> Text -> TokStream) -> TokParser a -> ParserConfig -> Text -> ParseResult a+runEntry mkStream parser cfg input =+  case runTokStreamParser parser sourceName (mkStream sourceName exts input) of+    Left bundle -> ParseErr (parseErrorBundleToSpannedText sourceName (rebuildStream (\(name, es, src) -> mkStream name es src) (sourceName, exts, input)) bundle)+    Right parsed -> ParseOk parsed+  where+    sourceName = parserSourceName cfg+    exts = applyImpliedExtensions (parserExtensions cfg)+ -- | Pretty-print a list of spanned parse errors with source context. formatParseErrors :: FilePath -> Maybe Text -> [(SourceSpan, Text)] -> String formatParseErrors sourceName mSource errs =@@ -191,8 +187,8 @@               renderString                 ( layoutPretty opts $                     case (srcSpan, mSource) of-                      (ss@SourceSpan {}, Just source) ->-                        vcat [renderSourceReference sourceName source ss, pretty msg]+                      (ss, Just source) ->+                        vcat [renderSourceReference source ss, pretty msg]                       _ ->                         vcat [pretty sourceName, pretty msg]                 )@@ -200,27 +196,20 @@           errs    in List.intercalate "\n\n" blocks --- renderSourceReference "<input>" "x = 1" (SourceSpan 1 5 1 6) = """+-- renderSourceReference "x = 1" (SourceSpan "<input>" 1 5 1 6 4 5) = """ -- <input>:1:5: -- 1 | x = 1 --   |     ^ -- """--- renderSourceReference "<input>" "module where" (SourceSpan 1 8 1 13) = """--- <input>:1:5:+-- renderSourceReference "module where" (SourceSpan "<input>" 1 8 1 13 7 12) = """+-- <input>:1:8: -- 1 | module where --   |        ^^^^^ -- """-renderSourceReference :: String -> Text -> SourceSpan -> Doc ann-renderSourceReference origin source srcSpan =-  let (renderedOrigin, lineNo, colNo, endCol, srcLine) = case srcSpan of-        SourceSpan {sourceSpanSourceName, sourceSpanStartLine, sourceSpanStartCol, sourceSpanEndCol, sourceSpanStartOffset} ->-          ( sourceSpanSourceName,-            sourceSpanStartLine,-            sourceSpanStartCol,-            sourceSpanEndCol,-            extractSourceLineByOffset source sourceSpanStartOffset-          )-        NoSourceSpan -> (origin, 1, 1, 1, "")+renderSourceReference :: Text -> SourceSpan -> Doc ann+renderSourceReference source srcSpan =+  let SourceSpan {sourceSpanSourceName = renderedOrigin, sourceSpanStartLine = lineNo, sourceSpanStartCol = colNo, sourceSpanEndCol = endCol, sourceSpanStartOffset} = srcSpan+      srcLine = extractSourceLineByOffset source sourceSpanStartOffset       lineNoText = show lineNo       markerPrefix = replicate (length lineNoText) ' ' ++ " | "       markerStart = max 0 (colNo - 1)
src/Aihc/Parser/Internal/Cmd.hs view
@@ -12,7 +12,7 @@ import Aihc.Parser.Lex (LexTokenKind (..), lexTokenKind) import Aihc.Parser.Syntax import Aihc.Parser.Types (ParserErrorComponent (..), mkFoundToken)-import Text.Megaparsec (anySingle, lookAhead, (<|>))+import Text.Megaparsec ((<|>)) import Text.Megaparsec qualified as MP  -- | Parse a command (the body of a @proc@ abstraction).@@ -27,7 +27,7 @@ -- @ cmdParser :: TokParser Cmd cmdParser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordDo -> cmd0Parser     TkKeywordIf -> cmd0Parser@@ -52,7 +52,7 @@  cmd10Parser :: TokParser Cmd cmd10Parser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordDo -> cmdDoParser     TkKeywordIf -> cmdIfParser@@ -145,7 +145,7 @@ -- | Parse a do-statement in command context (arrow do). cmdStmtParser :: TokParser (DoStmt Cmd) cmdStmtParser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordLet -> MP.try cmdLetStmtParser <|> cmdBodyStmtParser     TkKeywordRec -> cmdRecStmtParser@@ -174,17 +174,17 @@ cmdBindOrBodyStmtParser = withSpanAnn (DoAnn . mkAnnotation) $ do   -- Arrow tails (-<, -<<) belong to the command level, not the expression.   expr <- exprParser-  mArrow <- MP.optional (expectedTok TkReservedLeftArrow)-  case mArrow of-    Just () -> DoBind <$> liftCheck (checkPattern expr) <*> cmdParser-    Nothing -> do+  hasArrow <- optionalTok TkReservedLeftArrow+  if hasArrow+    then DoBind <$> liftCheck (checkPattern expr) <*> cmdParser+    else do       -- No bind arrow: this is a body statement.  Check for arrow tail.       mArrTail <- MP.optional cmdArrTailParser       case mArrTail of         Just (appType, rhs) ->           pure (DoExpr (CmdArrApp expr appType rhs))         Nothing -> do-          mTok <- MP.optional (lookAhead anySingle)+          mTok <- peekTokenMaybe           MP.customFailure             UnexpectedTokenExpecting               { unexpectedFound = mkFoundToken <$> mTok,
src/Aihc/Parser/Internal/Common.hs view
@@ -5,6 +5,13 @@     label,     region,     expectedTok,+    peekToken,+    peekTokenMaybe,+    peekTokenKind,+    nextTokenIs,+    tokenKindDispatch,+    optionalTok,+    optionalTokThen,     eofTok,     varIdTok,     tokenSatisfy,@@ -32,6 +39,7 @@     operatorTextParser,     constructorInfixOperatorNameParser,     stringTextParser,+    consumedSpan,     inputStartSpan,     withSpan,     withSpanAnn,@@ -72,6 +80,7 @@     isConLikeNameType,     liftCheck,     infixOperatorParser,+    startsInfixOperator,     foldInfixL,     foldInfixR,   )@@ -79,7 +88,7 @@  import Aihc.Parser.Lex (LayoutState (..), LexToken (..), LexTokenKind (..), TokenOrigin (..), closeImplicitLayoutContext) import Aihc.Parser.Syntax-import Aihc.Parser.Types (ParserErrorComponent (..), TokStream (..), mkFoundToken, setTokStreamLayout, setTokStreamPendingPragmas, tokStreamExtensionSet)+import Aihc.Parser.Types (ParserErrorComponent (..), TokStream (..), mkFoundToken, setTokStreamLayout, setTokStreamPendingPragmas, sourcePosSpan, tokStreamExtensionSet) import Control.Monad (guard) import Data.Char (isUpper) import Data.Functor (($>))@@ -91,32 +100,43 @@ import Text.Megaparsec (Parsec, anySingle, lookAhead, (<|>)) import Text.Megaparsec qualified as MP import Text.Megaparsec.Error qualified as MPE+import Text.Megaparsec.Internal qualified as MPI  type TokParser = Parsec ParserErrorComponent TokStream +-- | Replace whatever error a parser reports with one that names what was+-- expected here and what was found instead.+--+-- Written as a primitive rather than as 'MP.observing' followed by a case:+-- the labelled parsers are the hot ones — every expression, every right-hand+-- side, every type — and this way a successful parse hands its result+-- straight to the caller's continuation, with no 'Either' box and no bind.+-- The failure continuations see the state the failure happened in, so the+-- token that was found is read from that state rather than looked ahead for. label :: Text -> TokParser a -> TokParser a-label expected parser = do-  outcome <- MP.observing parser-  case outcome of-    Right parsed -> pure parsed-    Left err ->+label expected parser =+  MPI.ParsecT $ \s cok cerr eok eerr ->+    MPI.unParser parser s cok (relabel cerr) eok (relabel eerr)+  where+    relabel report err errState =       case err of-        MPE.TrivialError off _ _ -> do-          mTok <- MP.optional (lookAhead anySingle)-          let mFound = mkFoundToken <$> mTok-          MP.parseError $-            MPE.FancyError-              off-              ( Set.singleton-                  ( MPE.ErrorCustom-                      UnexpectedTokenExpecting-                        { unexpectedFound = mFound,-                          unexpectedExpecting = expected,-                          unexpectedContext = []-                        }-                  )-              )-        _ -> MP.parseError err+        MPE.TrivialError off _ _ ->+          report+            ( MPE.FancyError+                off+                ( Set.singleton+                    ( MPE.ErrorCustom+                        UnexpectedTokenExpecting+                          { unexpectedFound =+                              mkFoundToken . fst <$> tokStreamNext (MP.stateInput errState),+                            unexpectedExpecting = expected,+                            unexpectedContext = []+                          }+                    )+                )+            )+            errState+        _ -> report err errState  region :: Text -> TokParser a -> TokParser a region context =@@ -142,6 +162,84 @@     if lexTokenKind tok == expected then Just () else Nothing {-# INLINE expectedTok #-} +-- | The next token, without consuming it.+--+-- Fails at the end of the stream with the same error as @lookAhead+-- anySingle@, which is what every dispatch point used before: this is that+-- parser with the 'MP.lookAhead' state save and restore and the 'MP.token'+-- machinery replaced by a read of the stream's memoized successor.+peekToken :: TokParser LexToken+peekToken =+  MPI.ParsecT $ \s _ _ eok eerr ->+    case tokStreamNext (MP.stateInput s) of+      Just (tok, _) -> eok tok s mempty+      Nothing -> eerr (MPE.TrivialError (MP.stateOffset s) (Just MPE.EndOfInput) Set.empty) s+{-# INLINE peekToken #-}++-- | The next token, or 'Nothing' at the end of the stream.+peekTokenMaybe :: TokParser (Maybe LexToken)+peekTokenMaybe =+  MPI.ParsecT $ \s _ _ eok _ ->+    eok (fst <$> tokStreamNext (MP.stateInput s)) s mempty+{-# INLINE peekTokenMaybe #-}++-- | The kind of the next token, without consuming it and without building a+-- parse error when there is none.+peekTokenKind :: TokParser (Maybe LexTokenKind)+peekTokenKind = MPI.ParsecT $ \s _ _ eok _ -> eok (nextTokenKind s) s mempty+{-# INLINE peekTokenKind #-}++-- | Whether the next token has the given kind, without consuming it.+nextTokenIs :: LexTokenKind -> TokParser Bool+nextTokenIs expected =+  MPI.ParsecT $ \s _ _ eok _ -> eok (nextTokenKind s == Just expected) s mempty+{-# INLINE nextTokenIs #-}++-- | Run the parser that the next token's kind selects.+--+-- 'Nothing' means the stream is exhausted, which only happens once 'TkEOF'+-- has been consumed.+tokenKindDispatch :: (Maybe LexTokenKind -> TokParser a) -> TokParser a+tokenKindDispatch select =+  MPI.ParsecT $ \s cok cerr eok eerr ->+    MPI.unParser (select (nextTokenKind s)) s cok cerr eok eerr+{-# INLINE tokenKindDispatch #-}++nextTokenKind :: MP.State TokStream e -> Maybe LexTokenKind+nextTokenKind s =+  case tokStreamNext (MP.stateInput s) of+    Just (tok, _) -> Just (lexTokenKind tok)+    Nothing -> Nothing+{-# INLINE nextTokenKind #-}++-- | Consume the next token if it has the given kind, reporting whether it+-- did.+--+-- Behaves exactly like @MP.optional (expectedTok expected)@ — including at+-- the end of the stream, where both leave the input alone — but decides on+-- the peeked token instead of recovering from a failed parse.+optionalTok :: LexTokenKind -> TokParser Bool+optionalTok expected =+  tokenKindDispatch $ \mKind ->+    if mKind == Just expected+      then True <$ expectedTok expected+      else pure False+{-# INLINE optionalTok #-}++-- | Like @MP.optional (expectedTok expected *> parser)@, but decided on the+-- peeked token.+--+-- The behaviour is identical, including when @parser@ fails after the token+-- was consumed: the failure propagates, because input was consumed either+-- way.  Pass @pure ()@ for a bare optional token.+optionalTokThen :: LexTokenKind -> TokParser a -> TokParser (Maybe a)+optionalTokThen expected parser =+  tokenKindDispatch $ \mKind ->+    if mKind == Just expected+      then Just <$> (expectedTok expected *> parser)+      else pure Nothing+{-# INLINE optionalTokThen #-}+ -- | Match the end-of-file token. -- -- The lexer emits a 'TkEOF' token at the end of input. This parser consumes@@ -463,35 +561,72 @@       TkString txt -> Just txt       _ -> Nothing -withSpanAnn :: (SourceSpan -> a -> a) -> TokParser a -> TokParser a+-- | The span of the tokens consumed between two stream positions, each given+-- as the stream and its offset: from the first token at the start position to+-- the last token consumed before the end position. The result is lazy in both+-- positions, so a caller can attach the span of a deferred parse (see 'lazy')+-- without forcing that parse.+--+-- A parser that consumed nothing gets the zero-width span at the point where+-- it stands. A stream with no tokens at all has no token to stand at; the+-- lexer never builds one, since it always ends the stream with 'TkEOF', but a+-- stream built from an explicit token list can be empty. The span is then the+-- zero-width span at the given start of the input, which the parser state+-- knows along with the source name.+consumedSpan :: MP.SourcePos -> TokStream -> Int -> TokStream -> Int -> SourceSpan+consumedSpan inputStart startInput startOffset endInput endOffset =+  case (inputStartSpan startInput, lexTokenSpan <$> tokStreamPrevToken endInput) of+    (Just next, Just prev)+      | endOffset > startOffset -> mergeSourceSpans next prev+      | otherwise -> emptySpanAtStart next+    (Just next, Nothing) -> emptySpanAtStart next+    (Nothing, Just prev) -> emptySpanAtEnd prev+    (Nothing, Nothing) -> sourcePosSpan inputStart+  where+    emptySpanAtStart sp =+      sp+        { sourceSpanEndLine = sourceSpanStartLine sp,+          sourceSpanEndCol = sourceSpanStartCol sp,+          sourceSpanEndOffset = sourceSpanStartOffset sp+        }+    emptySpanAtEnd sp =+      sp+        { sourceSpanStartLine = sourceSpanEndLine sp,+          sourceSpanStartCol = sourceSpanEndCol sp,+          sourceSpanStartOffset = sourceSpanEndOffset sp+        }++-- | Run a parser and combine its result with the span of the consumed tokens.+--+-- The parser state is read once at each end, and the fields the span needs+-- are taken out strictly, so the span thunk holds positions rather than the+-- state. The state's position state references the initial input, and holding+-- it would keep every token of the file alive until the tree is forced.+withSpanAnn :: (SourceSpan -> a -> b) -> TokParser a -> TokParser b withSpanAnn f parser = do-  startInput <- MP.getInput+  startState <- MP.getParserState+  let !startInput = MP.stateInput startState+      !startOffset = MP.stateOffset startState+      !inputStart = MP.pstateSourcePos (MP.statePosState startState)   out <- parser-  endInput <- MP.getInput-  let startSpan = inputStartSpan startInput-      endSpan = maybe noSourceSpan lexTokenSpan (tokStreamPrevToken endInput)-      parserSpan = mergeSourceSpans startSpan endSpan-  pure $ f parserSpan out+  endState <- MP.getParserState+  let !endInput = MP.stateInput endState+      !endOffset = MP.stateOffset endState+  pure (f (consumedSpan inputStart startInput startOffset endInput endOffset) out) {-# INLINE withSpanAnn #-} --- FIXME: Remove.+-- | Run a parser whose result takes the span of the consumed tokens. withSpan :: TokParser (SourceSpan -> a) -> TokParser a-withSpan parser = do-  startInput <- MP.getInput-  out <- parser-  endInput <- MP.getInput-  let startSpan = inputStartSpan startInput-      endSpan = maybe noSourceSpan lexTokenSpan (tokStreamPrevToken endInput)-      parserSpan = mergeSourceSpans startSpan endSpan-  pure (out parserSpan)+withSpan = withSpanAnn (\parserSpan out -> out parserSpan) {-# INLINE withSpan #-} -inputStartSpan :: TokStream -> SourceSpan+-- | The span of the next token, if there is one.+inputStartSpan :: TokStream -> Maybe SourceSpan inputStartSpan ts-  | tokStreamEOFEmitted ts = noSourceSpan-  | tok : _ <- tokStreamBuffer ts = lexTokenSpan tok-  | rawTok : _ <- tokStreamRawTokens ts = lexTokenSpan rawTok-  | otherwise = noSourceSpan+  | tokStreamEOFEmitted ts = Nothing+  | tok : _ <- tokStreamBuffer ts = Just (lexTokenSpan tok)+  | rawTok : _ <- tokStreamRawTokens ts = Just (lexTokenSpan rawTok)+  | otherwise = Nothing {-# INLINE inputStartSpan #-}  optionalSuffix :: TokParser b -> (a -> b -> a) -> TokParser a -> TokParser a@@ -678,7 +813,7 @@       first <- constraintTypeAppParser       rest <- MP.many ((,) <$> constraintTypeInfixOperatorParser <*> constraintTypeAppParser)       let baseType = foldInfixR buildInfixType first rest-      mRhs <- MP.optional (expectedTok TkReservedRightArrow *> kindTypeParser)+      mRhs <- optionalTokThen TkReservedRightArrow kindTypeParser       case mRhs of         Just rhs ->           pure (TFun ArrowUnrestricted baseType rhs)@@ -719,13 +854,19 @@ contextItemsParserWith typeParser typeAtomParser =   MP.try parenthesizedContextItemsParser <|> fmap pure (contextItemParserWith typeParser typeAtomParser)   where-    parenthesizedContextItemsParser = do+    parenthesizedContextItemsParser = withSpanAnn annotateSingleItem $ do       items <- parens (listContextItemParser `MP.sepEndBy` expectedTok TkSpecialComma)       guardNotFollowedByConstraintInfixOp       case items of         [] -> fail "empty constraint list in parens"-        [item] -> pure [typeAnnSpan NoSourceSpan (TParen item)]+        [item] -> pure [TParen item]         _ -> pure items+    -- A single item keeps its parentheses as a node that spans the whole+    -- parenthesized list.+    annotateSingleItem sp items =+      case items of+        [item] -> [typeAnnSpan sp item]+        _ -> items     listContextItemParser =       MP.try quantifiedContextItemParser <|> contextItemParserWith typeParser typeAtomParser     -- \| Extension form (QuantifiedConstraints):@@ -816,7 +957,7 @@   TokParser a typedBindingOrSignatureParser typeParser signatureCtor bindingCtor singleBinderMsg = do   (names, ty) <- typedSignaturePrefixParser typeParser-  nextKind <- lexTokenKind <$> lookAhead anySingle+  nextKind <- lexTokenKind <$> peekToken   if nextKind == TkReservedEquals || nextKind == TkReservedPipe     then case names of       [name] -> bindingCtor name ty@@ -972,11 +1113,11 @@   fields <- fieldsParser   if rwcEnabled     then do-      mDotDot <- MP.optional (expectedTok TkReservedDotDot)-      case mDotDot of-        Nothing -> pure (fields, False)-        Just _ -> do-          _ <- MP.optional (expectedTok TkSpecialComma)+      hasDotDot <- optionalTok TkReservedDotDot+      if not hasDotDot+        then pure (fields, False)+        else do+          _ <- optionalTok TkSpecialComma           pure (fields, True)     else pure (fields, False) @@ -1200,6 +1341,21 @@ liftCheck :: Either Text a -> TokParser a liftCheck (Right a) = pure a liftCheck (Left msg) = fail (T.unpack msg)++-- | Whether a token can start an infix operator, symbolic or backticked.+-- Mirrors the token cases of 'infixOperatorParser'.+startsInfixOperator :: LexTokenKind -> Bool+startsInfixOperator kind =+  case kind of+    TkVarSym {} -> True+    TkConSym {} -> True+    TkPrefixPercent -> True+    TkQVarSym {} -> True+    TkQConSym {} -> True+    TkMinusOperator -> True+    TkReservedColon -> True+    TkSpecialBacktick -> True+    _ -> False  -- | Parse an infix operator. infixOperatorParser :: TokParser Name
src/Aihc/Parser/Internal/Decl.hs view
@@ -16,11 +16,11 @@ import Aihc.Parser.Internal.Type (arrowKindParser, forallTelescopeParser, typeAppParser, typeAtomParser, typeInfixOperatorParser, typeInfixParser, typeParser, typeSignatureParser) import Aihc.Parser.Lex (LexTokenKind (..), lexTokenKind, pattern TkVarFamily, pattern TkVarRole) import Aihc.Parser.Syntax-import Aihc.Parser.Types (ParserErrorComponent (..), mkFoundToken)+import Aihc.Parser.Types (ParserErrorComponent (..), TokStream (..), mkFoundToken) import Control.Monad (when) import Data.Char (isLower) import Data.Functor (($>))-import Data.Maybe (fromMaybe, isJust)+import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as T import Text.Megaparsec (anySingle, lookAhead, (<|>))@@ -39,8 +39,16 @@ -- -- > decl -> gendecl -- >      | (funlhs | pat) rhs+-- | A pragma declaration is only possible when the lexer has actually set a+-- hidden pragma aside, so the state says so directly.  Trying+-- 'pragmaDeclParser' first instead made every ordinary declaration pay for a+-- parser failure, with the error and hints that go with it. declParser :: TokParser Decl-declParser = pragmaDeclParser <|> ordinaryDeclParser+declParser = do+  pst <- MP.getParserState+  case tokStreamPendingPragmas (MP.stateInput pst) of+    [] -> ordinaryDeclParser+    _ -> pragmaDeclParser <|> ordinaryDeclParser  ordinaryDeclParser :: TokParser Decl ordinaryDeclParser = do@@ -94,9 +102,32 @@         TkReservedDoubleColon -> sigOrValueDecl         TkSpecialComma -> sigOrValueDecl         TkReservedEquals -> valueDecl-        _ -> nonBareVarPatternBindDeclParser <|> valueDecl+        _+          | patternBindMayFollowVarId nextTokKind ->+              nonBareVarPatternBindDeclParser <|> valueDecl+          | otherwise -> valueDecl     _ -> fallbackDecl +-- | Whether a declaration whose first token is a variable identifier can+-- still be a pattern binding, given the token after it.+--+-- A variable identifier on its own is a bare variable pattern, which+-- 'nonBareVarPatternBindDeclParser' rejects, so the only pattern bindings+-- that start with one are an as-pattern (@x\@p = ...@) and an infix+-- constructor pattern (@x : xs = ...@, @x \`Cons\` y = ...@).  Every other+-- second token means the declaration is a function binding, and trying the+-- pattern parser first only to backtrack was pure cost: a function+-- definition with arguments is the most common declaration there is.+patternBindMayFollowVarId :: LexTokenKind -> Bool+patternBindMayFollowVarId kind =+  case kind of+    TkReservedAt -> True+    TkConSym {} -> True+    TkQConSym {} -> True+    TkReservedColon -> True+    TkSpecialBacktick -> True+    _ -> False+ -- | Like 'patternBindDeclParser' but rejects bare variable patterns. -- When the leading token is a variable identifier, a bare @x = 5@ must be -- parsed as a zero-argument function bind, not a pattern bind.  This parser@@ -215,7 +246,7 @@     )     <|> do       (context, head') <- declHeadWithOptionalContext headParser-      inlineKind <- MP.optional (expectedTok TkReservedDoubleColon *> typeParser)+      inlineKind <- optionalTokThen TkReservedDoubleColon typeParser       pure (context, head', inlineKind)  -- | Parse a declaration head that may be preceded by a context.@@ -240,7 +271,7 @@ -- | Parse an optional unnamed @:: Kind@ result signature for a family head. familyResultKindParser :: TokParser (Maybe Type) familyResultKindParser =-  MP.optional (expectedTok TkReservedDoubleColon *> typeParser)+  optionalTokThen TkReservedDoubleColon typeParser  -- | Parse an optional type family result signature. GHC admits either an unnamed -- @:: Kind@ annotation or a named result variable with optional injectivity annotation,@@ -474,7 +505,7 @@ instanceTypeFamilyInstParser :: TokParser InstanceDeclItem instanceTypeFamilyInstParser = withSpanAnn (InstanceItemAnn . mkAnnotation) $ do   expectedTok TkKeywordType-  _ <- MP.optional (expectedTok TkKeywordInstance)+  _ <- optionalTok TkKeywordInstance   forallBinders <- MP.option [] explicitForallParser   (headForm, lhs) <- typeFamilyLhsParser   expectedTok TkReservedEquals@@ -493,7 +524,7 @@ instanceDataFamilyInstParser :: TokParser InstanceDeclItem instanceDataFamilyInstParser = withSpanAnn (InstanceItemAnn . mkAnnotation) $ do   expectedTok TkKeywordData-  _ <- MP.optional (expectedTok TkKeywordInstance)+  _ <- optionalTok TkKeywordInstance   (_, head') <- typeFamilyLhsParser   kind <- familyResultKindParser   (constructors, derivingClauses) <- gadtDataDeclParser <|> traditionalDataDeclParser@@ -513,7 +544,7 @@ instanceNewtypeFamilyInstParser :: TokParser InstanceDeclItem instanceNewtypeFamilyInstParser = withSpanAnn (InstanceItemAnn . mkAnnotation) $ do   expectedTok TkKeywordNewtype-  _ <- MP.optional (expectedTok TkKeywordInstance)+  _ <- optionalTok TkKeywordInstance   (_, head') <- typeFamilyLhsParser   kind <- familyResultKindParser   expectedTok TkReservedEquals@@ -769,7 +800,7 @@ instanceDeclItemParser =   instancePragmaItemParser     <|> do-      tok <- lookAhead anySingle+      tok <- peekToken       typeSigPrefix <- startsWithTypeSig       case lexTokenKind tok of         TkKeywordInfix -> instanceFixityItemParser@@ -868,7 +899,7 @@  traditionalDataDeclParser :: TokParser ([DataConDecl], [DerivingClause]) traditionalDataDeclParser = do-  constructors <- MP.optional (expectedTok TkReservedEquals *> dataConDeclParser `MP.sepBy1` expectedTok TkReservedPipe)+  constructors <- optionalTokThen TkReservedEquals (dataConDeclParser `MP.sepBy1` expectedTok TkReservedPipe)   derivingClauses <- MP.many derivingClauseParser   pure (fromMaybe [] constructors, derivingClauses) @@ -912,7 +943,7 @@   -- type data may not have a datatype context   typeHead <- typeDeclHeadParser   -- Parse optional inline kind signature: @:: Kind@-  inlineKind <- MP.optional (expectedTok TkReservedDoubleColon *> typeParser)+  inlineKind <- optionalTokThen TkReservedDoubleColon typeParser   -- GADT syntax starts with `where`, traditional syntax starts with `=` or nothing   constructors <- gadtStyleTypeDataDecl <|> traditionalStyleTypeDataDecl   -- type data may not have a deriving clause@@ -928,18 +959,18 @@         }   where     traditionalStyleTypeDataDecl =-      fromMaybe [] <$> MP.optional (expectedTok TkReservedEquals *> typeDataConDeclParser `MP.sepBy1` expectedTok TkReservedPipe)+      fromMaybe [] <$> optionalTokThen TkReservedEquals (typeDataConDeclParser `MP.sepBy1` expectedTok TkReservedPipe)      gadtStyleTypeDataDecl = gadtTypeDataWhereClauseParser  -- | Parse constructors for type data (traditional style, after `=`) -- No labelled fields, no strictness annotations typeDataConDeclParser :: TokParser DataConDecl-typeDataConDeclParser = withSpan $ do+typeDataConDeclParser = withSpanAnn (DataConAnn . mkAnnotation) $ do   (_forallVars, context) <- dataConQualifiersParser   MP.try (typeDataConPrefixParser context) <|> typeDataConInfixParser context -typeDataConPrefixParser :: [Type] -> TokParser (SourceSpan -> DataConDecl)+typeDataConPrefixParser :: [Type] -> TokParser DataConDecl typeDataConPrefixParser context = do   conName <- constructorUnqualifiedNameParser <|> parens constructorOperatorUnqualifiedNameParser   -- Parse arguments (no strictness, no records).@@ -948,14 +979,13 @@   args <- MP.many $ BangType [] [] False False <$> typeAtomParser   -- If a constructor operator follows, this declaration is actually infix.   MP.notFollowedBy constructorOperatorParser-  pure $ \span' -> DataConAnn (mkAnnotation span') (PrefixCon [] context conName args)+  pure (PrefixCon [] context conName args) -typeDataConInfixParser :: [Type] -> TokParser (SourceSpan -> DataConDecl)+typeDataConInfixParser :: [Type] -> TokParser DataConDecl typeDataConInfixParser context = do   lhs <- typeDataConArgParser   op <- constructorOperatorUnqualifiedNameParser <|> backtickConstructorUnqualifiedParser-  rhs <- typeDataConArgParser-  pure $ \span' -> DataConAnn (mkAnnotation span') (InfixCon [] context lhs op rhs)+  InfixCon [] context lhs op <$> typeDataConArgParser   where     backtickConstructorUnqualifiedParser = do       expectedTok TkSpecialBacktick@@ -974,7 +1004,7 @@ -- | Parse a GADT constructor for type data -- Only equality constraints permitted, no strictness, no records gadtTypeDataConDeclParser :: TokParser DataConDecl-gadtTypeDataConDeclParser = withSpan $ do+gadtTypeDataConDeclParser = withSpanAnn (DataConAnn . mkAnnotation) $ do   -- Parse constructor names (can be multiple separated by commas)   names <- gadtConNameParser `MP.sepBy1` expectedTok TkSpecialComma   expectedTok TkReservedDoubleColon@@ -983,8 +1013,7 @@   -- Parse context (only equality constraints permitted, but we parse generally)   context <- contextPrefixDispatchList   -- Parse the body (prefix only for type data - no record style)-  body <- gadtTypeDataBodyParser-  pure $ \span' -> DataConAnn (mkAnnotation span') (GadtCon forallBinders context names body)+  GadtCon forallBinders context names <$> gadtTypeDataBodyParser  -- | Parse the body of a GADT constructor for type data -- Only prefix style allowed (no records), no strictness annotations@@ -1005,9 +1034,9 @@        in pure (GadtPrefixBody argsWithKinds resultTy)  dataConDeclParser :: TokParser DataConDecl-dataConDeclParser = withSpan $ do+dataConDeclParser = withSpanAnn (DataConAnn . mkAnnotation) $ do   (forallVars, context) <- dataConQualifiersParser-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     -- `(#` is either the LHS arg of an infix constructor (e.g. @(# #) :. Int@) or a     -- standalone unboxed tuple/sum constructor (e.g. @(# Int, Bool #)@).@@ -1025,20 +1054,19 @@         <|> MP.try (boxedTupleConDeclParser forallVars context)         <|> dataConRecordOrPrefixParser forallVars context -listConDeclParser :: [TyVarBinder] -> [Type] -> TokParser (SourceSpan -> DataConDecl)+listConDeclParser :: [TyVarBinder] -> [Type] -> TokParser DataConDecl listConDeclParser forallVars context = do   expectedTok TkSpecialLBracket   expectedTok TkSpecialRBracket-  pure $ \span' -> DataConAnn (mkAnnotation span') (ListCon forallVars context)+  pure (ListCon forallVars context) -boxedTupleConDeclParser :: [TyVarBinder] -> [Type] -> TokParser (SourceSpan -> DataConDecl)+boxedTupleConDeclParser :: [TyVarBinder] -> [Type] -> TokParser DataConDecl boxedTupleConDeclParser forallVars context = do   expectedTok TkSpecialLParen-  mClose <- MP.optional (expectedTok TkSpecialRParen)-  case mClose of-    Just () ->-      pure $ \span' -> DataConAnn (mkAnnotation span') (TupleCon forallVars context Boxed [])-    Nothing -> do+  closedImmediately <- optionalTok TkSpecialRParen+  if closedImmediately+    then pure (TupleCon forallVars context Boxed [])+    else do       firstField <- constructorArgParser       -- A comma is mandatory: boxed 1-tuples don't exist in Haskell       -- (e.g. @data C = (Int)@ is invalid). Without this, a@@ -1047,16 +1075,15 @@       expectedTok TkSpecialComma       rest <- constructorArgParser `MP.sepBy1` expectedTok TkSpecialComma       expectedTok TkSpecialRParen-      pure $ \span' -> DataConAnn (mkAnnotation span') (TupleCon forallVars context Boxed (firstField : rest))+      pure (TupleCon forallVars context Boxed (firstField : rest)) -unboxedConDeclParser :: [TyVarBinder] -> [Type] -> TokParser (SourceSpan -> DataConDecl)+unboxedConDeclParser :: [TyVarBinder] -> [Type] -> TokParser DataConDecl unboxedConDeclParser forallVars context = do   expectedTok TkSpecialUnboxedLParen-  mClose <- MP.optional (expectedTok TkSpecialUnboxedRParen)-  case mClose of-    Just () ->-      pure $ \span' -> DataConAnn (mkAnnotation span') (TupleCon forallVars context Unboxed [])-    Nothing -> do+  closedImmediately <- optionalTok TkSpecialUnboxedRParen+  if closedImmediately+    then pure (TupleCon forallVars context Unboxed [])+    else do       leadingPipes <- MP.many (MP.try (expectedTok TkReservedPipe))       if not (null leadingPipes)         then do@@ -1065,23 +1092,23 @@           expectedTok TkSpecialUnboxedRParen           let pos = length leadingPipes + 1               arity = length leadingPipes + 1 + length trailingPipes-          pure $ \span' -> DataConAnn (mkAnnotation span') (UnboxedSumCon forallVars context pos arity field)+          pure (UnboxedSumCon forallVars context pos arity field)         else do           firstField <- constructorArgParser           mSep <- MP.optional (MP.try ((Left () <$ expectedTok TkSpecialComma) <|> (Right () <$ expectedTok TkReservedPipe)))           case mSep of             Nothing -> do               expectedTok TkSpecialUnboxedRParen-              pure $ \span' -> DataConAnn (mkAnnotation span') (TupleCon forallVars context Unboxed [firstField])+              pure (TupleCon forallVars context Unboxed [firstField])             Just (Left ()) -> do               rest <- constructorArgParser `MP.sepBy1` expectedTok TkSpecialComma               expectedTok TkSpecialUnboxedRParen-              pure $ \span' -> DataConAnn (mkAnnotation span') (TupleCon forallVars context Unboxed (firstField : rest))+              pure (TupleCon forallVars context Unboxed (firstField : rest))             Just (Right ()) -> do               trailingPipes <- MP.many (expectedTok TkReservedPipe)               expectedTok TkSpecialUnboxedRParen               let arity = 1 + 1 + length trailingPipes-              pure $ \span' -> DataConAnn (mkAnnotation span') (UnboxedSumCon forallVars context 1 arity firstField)+              pure (UnboxedSumCon forallVars context 1 arity firstField)  -- | Report core: --@@ -1105,7 +1132,7 @@         }   where     traditionalStyleNewtypeDecl = do-      constructor <- MP.optional (expectedTok TkReservedEquals *> dataConDeclParser)+      constructor <- optionalTokThen TkReservedEquals dataConDeclParser       derivingClauses <- MP.many derivingClauseParser       pure (constructor, derivingClauses) @@ -1124,7 +1151,7 @@  -- | Parse a GADT constructor declaration: @Con1, Con2 :: forall a. Ctx => Type@ gadtConDeclParser :: TokParser DataConDecl-gadtConDeclParser = withSpan $ do+gadtConDeclParser = withSpanAnn (DataConAnn . mkAnnotation) $ do   -- Parse constructor names (can be multiple separated by commas)   names <- gadtConNameParser `MP.sepBy1` expectedTok TkSpecialComma   expectedTok TkReservedDoubleColon@@ -1133,8 +1160,7 @@   -- Parse optional context   context <- contextPrefixDispatchList   -- Parse the body (record or prefix style)-  body <- gadtBodyParser-  pure $ \span' -> DataConAnn (mkAnnotation span') (GadtCon forallBinders context names body)+  GadtCon forallBinders context names <$> gadtBodyParser  -- | Parse constructor name for GADT - can be regular or operator in parens gadtConNameParser :: TokParser UnqualifiedName@@ -1407,7 +1433,7 @@   expectedTok TkKeywordType   explicitFamilyKeyword <- case familyKeywordMode of     FamilyKeywordRequired -> expectedTok TkVarFamily $> True-    FamilyKeywordOptional -> isJust <$> MP.optional (expectedTok TkVarFamily)+    FamilyKeywordOptional -> optionalTok TkVarFamily   (headForm, headType, params) <- typeFamilyHeadParser   resultSig <- typeFamilyResultSigParser explicitFamilyKeyword   equations <-@@ -1431,19 +1457,19 @@   expectedTok (TkVarSym ".")   pure binders -dataConRecordOrPrefixParser :: [TyVarBinder] -> [Type] -> TokParser (SourceSpan -> DataConDecl)+dataConRecordOrPrefixParser :: [TyVarBinder] -> [Type] -> TokParser DataConDecl dataConRecordOrPrefixParser forallVars context = do   name <- constructorUnqualifiedNameParser <|> parens operatorUnqualifiedNameParser   mRecordFields <- MP.optional (MP.try recordFieldsParserAfterLayoutSemicolon)   case mRecordFields of-    Just fields -> pure (\span' -> DataConAnn (mkAnnotation span') (RecordCon forallVars context name fields))+    Just fields -> pure (RecordCon forallVars context name fields)     Nothing -> do       args <- MP.many constructorArgParser       -- Ensure we're not leaving a constructor operator unconsumed.       -- If there's a constructor operator next, this is actually an infix form       -- and we should backtrack to let dataConInfixParser handle it.       MP.notFollowedBy constructorOperatorParser-      pure (\span' -> DataConAnn (mkAnnotation span') (PrefixCon forallVars context name args))+      pure (PrefixCon forallVars context name args)   where     -- Layout may inject a virtual ';' before a newline-started record field block.     -- Accept it as part of the constructor declaration.@@ -1451,12 +1477,11 @@       recordFieldsParser         <|> (expectedTok TkSpecialSemicolon *> recordFieldsParser) -dataConInfixParser :: [TyVarBinder] -> [Type] -> TokParser (SourceSpan -> DataConDecl)+dataConInfixParser :: [TyVarBinder] -> [Type] -> TokParser DataConDecl dataConInfixParser forallVars context = do   lhs <- infixConstructorArgParser   op <- constructorOperatorUnqualifiedNameParser <|> backtickConstructorUnqualifiedParser-  rhs <- infixConstructorArgParser-  pure (\span' -> DataConAnn (mkAnnotation span') (InfixCon forallVars context lhs op rhs))+  InfixCon forallVars context lhs op <$> infixConstructorArgParser   where     backtickConstructorUnqualifiedParser = do       expectedTok TkSpecialBacktick@@ -1647,7 +1672,7 @@               Just matches -> pure (PatSynExplicitBidirectional matches, pat)         )     <|> do-      mTok <- MP.optional (lookAhead anySingle)+      mTok <- peekTokenMaybe       MP.customFailure         UnexpectedTokenExpecting           { unexpectedFound = mkFoundToken <$> mTok,
src/Aihc/Parser/Internal/Errors.hs view
@@ -5,8 +5,8 @@ where  import Aihc.Parser.Lex (LexToken (..), TokenOrigin (..))-import Aihc.Parser.Syntax (SourceSpan (..))-import Aihc.Parser.Types (FoundToken (..), ParseErrorBundle, ParserErrorComponent (..), TokStream)+import Aihc.Parser.Syntax (SourceSpan, sourceSpanEndCol, sourceSpanEndLine, sourceSpanEndOffset, sourceSpanStartCol, sourceSpanStartLine, sourceSpanStartOffset)+import Aihc.Parser.Types (FoundToken (..), ParseErrorBundle, ParserErrorComponent (..), TokStream (..), sourcePosSpan) import Data.List qualified as List import Data.List.NonEmpty qualified as NE import Data.Maybe (fromMaybe)@@ -19,17 +19,51 @@ import Text.Megaparsec.Error (ErrorFancy (..), ErrorItem (..)) import Text.Megaparsec.Error qualified as MPE -parseErrorBundleToSpannedText :: ParseErrorBundle -> [(SourceSpan, Text)]-parseErrorBundleToSpannedText bundle =-  parseErrorsToSpannedText (NE.toList (MPE.bundleErrors bundle))+-- | Render the errors of a failed parse, each with a source span.+--+-- The stream is a fresh stream over the same input, positioned at offset 0.+-- It must not be the stream that was parsed: holding that one keeps its+-- memoized successor chain alive for the whole parse (see+-- 'Aihc.Parser.Types.runTokStreamParser').+parseErrorBundleToSpannedText :: FilePath -> TokStream -> ParseErrorBundle -> [(SourceSpan, Text)]+parseErrorBundleToSpannedText sourceName stream bundle =+  parseErrorsToSpannedText sourceName stream (NE.toList (MPE.bundleErrors bundle)) -parseErrorsToSpannedText :: [MPE.ParseError TokStream ParserErrorComponent] -> [(SourceSpan, Text)]-parseErrorsToSpannedText errs =-  [ (fromMaybe NoSourceSpan mSpan, RText.renderStrict (layoutPretty defaultLayoutOptions doc))+-- | Render parse errors, each with a source span. See+-- 'parseErrorBundleToSpannedText' for the stream argument.+parseErrorsToSpannedText :: FilePath -> TokStream -> [MPE.ParseError TokStream ParserErrorComponent] -> [(SourceSpan, Text)]+parseErrorsToSpannedText sourceName stream errs =+  [ (fromMaybe (spanAtOffset sourceName stream (MP.errorOffset err)) mSpan, RText.renderStrict (layoutPretty defaultLayoutOptions doc))   | err <- List.sortOn MP.errorOffset errs,     (mSpan, doc) <- renderParseErrors err   ] +-- | The span of the token at an offset of a stream that starts at offset 0.+-- This is where the parser stood when it raised an error at that offset, so+-- it locates errors that carry no token of their own, such as one raised with+-- 'fail'. Past the last token the span is the zero-width end of that token; a+-- stream with no tokens at all gives the zero-width start of the input.+spanAtOffset :: FilePath -> TokStream -> Int -> SourceSpan+spanAtOffset sourceName = go+  where+    go stream n =+      case tokStreamNext stream of+        Just (tok, rest)+          | n > 0 -> go rest (n - 1)+          | otherwise -> lexTokenSpan tok+        Nothing ->+          case tokStreamPrevToken stream of+            Just prev -> spanEnd (lexTokenSpan prev)+            Nothing -> sourcePosSpan (MP.initialPos sourceName)+    spanEnd sp =+      sp+        { sourceSpanStartLine = sourceSpanEndLine sp,+          sourceSpanStartCol = sourceSpanEndCol sp,+          sourceSpanStartOffset = sourceSpanEndOffset sp+        }++-- | Render an error's messages, each with the span of the token it names, if+-- it names one. renderParseErrors :: MPE.ParseError TokStream ParserErrorComponent -> [(Maybe SourceSpan, Doc ann)] renderParseErrors err =   case err of
src/Aihc/Parser/Internal/Expr.hs view
@@ -21,9 +21,10 @@ import Aihc.Parser.Internal.Type (typeAtomParser, typeParser, typeSignatureParser) import Aihc.Parser.Lex (LexToken (..), LexTokenKind (..), lexTokenKind, lexTokenSpan, lexTokenText) import Aihc.Parser.Syntax-import Aihc.Parser.Types (ParserErrorComponent (..), TokStream (..), mkFoundToken)+import Aihc.Parser.Types (ParserErrorComponent (..), mkFoundToken) import Control.Monad (guard) import Data.Functor (($>))+import Data.Maybe (fromMaybe) import Data.Text (Text) import Text.Megaparsec (anySingle, lookAhead, (<|>)) import Text.Megaparsec qualified as MP@@ -49,15 +50,19 @@     exprOrPattern = do       expr <- exprP       -- An expression can stop before a pattern-only argument or suffix.-      MP.notFollowedBy $-        expectedTok TkReservedAt-          <|> expectedTok TkPrefixBang-          <|> expectedTok TkPrefixTilde-          <|> expectedTok TkReservedDoubleColon-      mArrow <- MP.optional (expectedTok TkReservedLeftArrow)-      case mArrow of-        Just () -> Left <$> liftCheck (checkPattern expr)-        Nothing -> pure (Right expr)+      -- The four tokens that mean it did are recognized on the peeked token+      -- rather than by four alternatives under 'MP.notFollowedBy'.+      mKind <- peekTokenKind+      case mKind of+        Just TkReservedAt -> MP.empty+        Just TkPrefixBang -> MP.empty+        Just TkPrefixTilde -> MP.empty+        Just TkReservedDoubleColon -> MP.empty+        _ -> pure ()+      hasArrow <- optionalTok TkReservedLeftArrow+      if hasArrow+        then Left <$> liftCheck (checkPattern expr)+        else pure (Right expr)      patternBind = do       pat <- patternParser@@ -126,10 +131,10 @@ -- no @->@ follows. maybeViewPattern :: Expr -> TokParser Expr maybeViewPattern lhs = do-  mArrow <- MP.optional (expectedTok TkReservedRightArrow)-  case mArrow of-    Just () -> EViewPat lhs <$> texprParser-    Nothing -> pure lhs+  hasArrow <- optionalTok TkReservedRightArrow+  if hasArrow+    then EViewPat lhs <$> texprParser+    else pure lhs  -- | Like 'exprParser' but also allows the view-pattern arrow @->@ at the -- top level.  This corresponds to GHC\'s @texp@ production, which is used@@ -211,7 +216,7 @@  doStmtParser :: TokParser (DoStmt Expr) doStmtParser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordLet -> MP.try doLetStmtParser <|> doBindOrExprStmtParser     TkKeywordRec -> doRecStmtParser@@ -247,16 +252,26 @@  -- | Shared infix-chain parser used by the report core and contextual -- expression variants such as TransformListComp.+--+-- The operator is looked for on the peeked token first.  Every expression in+-- the file ends by failing to find one more operator, and letting+-- 'infixOperatorParser' fail made that failure cost a parse error and its+-- hints. exprInfixChainParser :: TokParser Expr -> TokParser Expr exprInfixChainParser lexp = do   lhs <- lexp-  rest <--    MP.many-      ( (,)-          <$> infixOperatorParser-          <*> region "after infix operator" lexp-      )+  rest <- MP.many infixOperatorAndOperand   pure (foldInfixL buildInfix lhs rest)+  where+    infixOperatorAndOperand = do+      mKind <- peekTokenKind+      case mKind of+        Just kind+          | startsInfixOperator kind ->+              (,)+                <$> infixOperatorParser+                <*> region "after infix operator" lexp+        _ -> MP.empty  -- | Report core: --@@ -278,11 +293,24 @@     Just sccPragma -> EPragma sccPragma <$> lexpParserWith atomContext     Nothing -> lexpBaseParserWith atomContext (appExprParserWith (atomOrRecordExprParserWith atomContext)) +-- | Every block form starts with a distinct keyword and prefix negation with a+-- minus token, so the next token decides which alternative can possibly match.+-- Dispatching on it rather than running the alternatives in turn matters: this+-- parser runs at every expression position, and the chain used to attempt —+-- and allocate continuations for — nine block parsers plus a backtracking+-- negation before reaching the application parser that almost always wins. lexpBaseParserWith :: AtomContext -> TokParser Expr -> TokParser Expr-lexpBaseParserWith atomContext appParser =-  lexpBlockParserWith atomContext-    <|> MP.try negateExprParser-    <|> appParser+lexpBaseParserWith atomContext appParser = do+  tok <- peekToken+  case lexTokenKind tok of+    kind+      | Just blockParser <- lexpBlockParserForToken atomContext kind -> blockParser+    TkVarSym "-" -> negateOrApp+    TkMinusOperator -> negateOrApp+    TkPrefixMinus -> negateOrApp+    _ -> appParser+  where+    negateOrApp = MP.try negateExprParser <|> appParser  -- | The Haskell report's @lexp@ production: lambda, let, if, case, do, and -- function application.  GHC extensions add more forms at the same grammar@@ -305,21 +333,26 @@ lexpBlockParser = lexpBlockParserWith NormalExprAtom  lexpBlockParserWith :: AtomContext -> TokParser Expr-lexpBlockParserWith atomContext =-  doExprParser-    <|> mdoExprParser-    <|> qualifiedDoExprParser-    <|> qualifiedMdoExprParser-    <|> ifExprParser-    <|> caseExprParser-    <|> letExprParser-    <|> procBlockParser-    <|> lambdaExprParser-  where-    procBlockParser-      | atomContext == NormalExprAtom = procExprParser-      | otherwise = MP.empty+lexpBlockParserWith atomContext = do+  tok <- peekToken+  fromMaybe MP.empty (lexpBlockParserForToken atomContext (lexTokenKind tok)) +-- | The block parser a token can start, if any.  Each block form begins with+-- its own keyword, so at most one alternative is ever viable.+lexpBlockParserForToken :: AtomContext -> LexTokenKind -> Maybe (TokParser Expr)+lexpBlockParserForToken atomContext kind =+  case kind of+    TkKeywordDo -> Just doExprParser+    TkKeywordMdo -> Just mdoExprParser+    TkQualifiedDo {} -> Just qualifiedDoExprParser+    TkQualifiedMdo {} -> Just qualifiedMdoExprParser+    TkKeywordIf -> Just ifExprParser+    TkKeywordCase -> Just caseExprParser+    TkKeywordLet -> Just letExprParser+    TkKeywordProc | atomContext == NormalExprAtom -> Just procExprParser+    TkReservedBackslash -> Just lambdaExprParser+    _ -> Nothing+ getSCCPragma :: Pragma -> Maybe Pragma getSCCPragma p = case pragmaType p of   PragmaSCC _ -> Just p@@ -381,29 +414,34 @@ -- variants.  The caller chooses the @aexp@-like atom parser. appExprParserWith :: TokParser Expr -> TokParser Expr appExprParserWith atomParser = do-  startInput <- MP.getInput+  startState <- MP.getParserState+  let !startInput = MP.stateInput startState+      !startOffset = MP.stateOffset startState+      !inputStart = MP.pstateSourcePos (MP.statePosState startState)   first <- atomParser   rest <- MP.many appArg   case rest of     [] -> pure first     _ -> do-      endInput <- MP.getInput-      let startSpan = inputStartSpan startInput-          endSpan = maybe noSourceSpan lexTokenSpan (tokStreamPrevToken endInput)-          appSpan = mergeSourceSpans startSpan endSpan+      endState <- MP.getParserState+      let !endInput = MP.stateInput endState+          !endOffset = MP.stateOffset endState+          appSpan = consumedSpan inputStart startInput startOffset endInput endOffset       pure (EAnn (mkAnnotation appSpan) (foldl applyArg first rest))   where     appArg :: TokParser (Either Type Expr)     appArg = (Left <$> typeAppArg) <|> (Right <$> appExprArgParser)      typeAppArg :: TokParser Type-    typeAppArg = MP.try $ do-      expectedTok TkTypeApp-      typeAtomParser+    typeAppArg = do+      hasAt <- nextTokenIs TkTypeApp+      if hasAt+        then MP.try (expectedTok TkTypeApp *> typeAtomParser)+        else MP.empty      appExprArgParser :: TokParser Expr     appExprArgParser = do-      tok <- lookAhead anySingle+      tok <- peekToken       case lexTokenKind tok of         -- GHC rejects bare explicit namespace syntax as a function argument:         -- @f type T@ is invalid, while @f (type T)@ is accepted syntactically.@@ -429,20 +467,35 @@ atomOrRecordExprParser :: TokParser Expr atomOrRecordExprParser = atomOrRecordExprParserWith NormalExprAtom +-- | The record-base atom parser accepts exactly the @aexp@ forms, so for any+-- other leading token it fails without consuming input and the atom parser+-- has to be run instead.  Which of the two applies is decided by the next+-- token, rather than by trying the record path and letting it fail: this+-- parser runs at every atom position, and the failing alternative allocated a+-- continuation and an error for each one. atomOrRecordExprParserWith :: AtomContext -> TokParser Expr-atomOrRecordExprParserWith atomContext =-  recordExprParser <|> atomExprParserWith atomContext+atomOrRecordExprParserWith atomContext = do+  tok <- peekToken+  if startsAtomOnlyForm atomContext (lexTokenKind tok)+    then atomExprParserWith atomContext+    else recordExprParser   where     recordExprParser :: TokParser Expr     recordExprParser = do       base <- recordBaseAtomExprParserWith atomContext       applyRecordSuffixes base +    -- Record braces and record dots are suffixes on an atom that almost+    -- never follow it, so both are decided on the peeked token.  Running+    -- 'recordBracesParser' under 'MP.optional' meant every atom in the file+    -- paid for a failed brace parse.     applyRecordSuffixes :: Expr -> TokParser Expr     applyRecordSuffixes e = do-      mRecordFields <- MP.optional recordBracesParser-      case mRecordFields of-        Just (fields, hasWildcard) -> do+      hasBrace <- nextTokenIs TkSpecialLBrace+      if not hasBrace+        then applyRecordDotSuffixes e+        else do+          (fields, hasWildcard) <- recordBracesParser           let result = case peelExprAnn e of                 EVar name                   | isConLikeName name ->@@ -450,18 +503,18 @@                 _ ->                   ERecordUpd e (map normalizeField fields)           applyRecordSuffixes result-        Nothing -> applyRecordDotSuffixes e      applyRecordDotSuffixes :: Expr -> TokParser Expr     applyRecordDotSuffixes e = do-      recordDotEnabled <- isExtensionEnabled OverloadedRecordDot-      if not recordDotEnabled || not (recordDotMayFollow e)+      hasDot <- nextTokenIs TkRecordDot+      if not hasDot || not (recordDotMayFollow e)         then pure e         else do-          mDot <- MP.optional (expectedTok TkRecordDot)-          case mDot of-            Nothing -> pure e-            Just () -> do+          recordDotEnabled <- isExtensionEnabled OverloadedRecordDot+          if not recordDotEnabled+            then pure e+            else do+              expectedTok TkRecordDot               fieldName <- recordFieldNameParser               applyRecordSuffixes (EGetField e fieldName) @@ -489,7 +542,7 @@ recordFieldBindingParser :: TokParser (Name, Maybe Expr, SourceSpan) recordFieldBindingParser = withSpan $ do   fieldName <- recordFieldNameParser-  mAssign <- MP.optional (expectedTok TkReservedEquals *> exprParser)+  mAssign <- optionalTokThen TkReservedEquals exprParser   pure (fieldName,mAssign,)  -- | Parse the expression forms that correspond to the report's @aexp@@@ -509,7 +562,7 @@ -- >      | aexp<qcon> '{' fbind_1 ',' ... ',' fbind_n '}' recordBaseAtomExprParserWith :: AtomContext -> TokParser Expr recordBaseAtomExprParserWith atomContext = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkImplicitParam {} -> implicitParamExprParser     _ -> simpleAtomExprParserWith atomContext@@ -520,12 +573,29 @@ -- -- This variant also admits extension-only atoms such as block arguments and -- explicit namespace syntax when the corresponding extensions are enabled.+-- | Whether a token starts an expression form that is not an @aexp@, and so+-- can never be a record construction or update base.+startsAtomOnlyForm :: AtomContext -> LexTokenKind -> Bool+startsAtomOnlyForm atomContext kind =+  case kind of+    TkKeywordType -> True+    TkReservedBackslash -> True+    TkKeywordLet -> True+    TkKeywordDo -> True+    TkKeywordMdo -> True+    TkQualifiedDo {} -> True+    TkQualifiedMdo {} -> True+    TkKeywordCase -> True+    TkKeywordIf -> True+    TkKeywordProc -> atomContext == NormalExprAtom+    _ -> False+ atomExprParser :: TokParser Expr atomExprParser = atomExprParserWith NormalExprAtom  atomExprParserWith :: AtomContext -> TokParser Expr atomExprParserWith atomContext = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkImplicitParam {} -> implicitParamExprParser     TkKeywordType -> do@@ -554,10 +624,19 @@  simpleAtomExprParserWith :: AtomContext -> TokParser Expr simpleAtomExprParserWith atomContext = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkPrefixMinus -> prefixNegateAtomExprParserWith atomContext-    TkSpecialLParen -> MP.try parenOperatorExprParser <|> parenExprParser+    -- @(+)@ and @(x + y)@ both start with @(@, and only the second token+    -- tells them apart.  Without that check every parenthesized expression+    -- paid for a failed 'parenOperatorExprParser' parse first.+    TkSpecialLParen -> do+      mAfterParen <- lookAhead (MP.optional (anySingle *> anySingle))+      case lexTokenKind <$> mAfterParen of+        Just afterParen+          | startsParenOperator afterParen ->+              MP.try parenOperatorExprParser <|> parenExprParser+        _ -> parenExprParser     TkSpecialUnboxedLParen -> parenExprParser     TkSpecialLBracket -> listExprParser     TkInteger {} -> intExprParser@@ -627,6 +706,20 @@       TkPrefixMinus -> Just ()       _ -> Nothing +-- | Whether a token can be the operator of a parenthesized operator+-- expression such as @(+)@; see 'operatorExprNameParser'.+startsParenOperator :: LexTokenKind -> Bool+startsParenOperator kind =+  case kind of+    TkVarSym {} -> True+    TkConSym {} -> True+    TkQVarSym {} -> True+    TkQConSym {} -> True+    TkMinusOperator -> True+    TkReservedColon -> True+    TkReservedAt -> True+    _ -> False+ parenOperatorExprParser :: TokParser Expr parenOperatorExprParser =   withSpanAnn (EAnn . mkAnnotation) $@@ -683,7 +776,7 @@  rhsParserWithBodyParser :: RhsArrowKind -> TokParser body -> TokParser (Rhs body) rhsParserWithBodyParser arrowKind bodyParser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkReservedPipe -> guardedRhssParserWithBodyParser arrowKind bodyParser     TkReservedRightArrow | RhsArrowCase <- arrowKind -> unguardedRhsParserWithBodyParser arrowKind bodyParser@@ -734,7 +827,7 @@ -- >       | infixexp guardQualifierParser :: RhsArrowKind -> TokParser GuardQualifier guardQualifierParser arrowKind = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordLet -> MP.try guardLetParser <|> guardBindOrExprParser arrowKind     _ -> guardBindOrExprParser arrowKind@@ -795,10 +888,10 @@ parenExprParser :: TokParser Expr parenExprParser = withSpanAnn (EAnn . mkAnnotation) $ do   (tupleFlavor, closeTok) <- tupleDelimsParser-  mClosed <- MP.optional (expectedTok closeTok)-  case mClosed of-    Just () -> pure (ETuple tupleFlavor [])-    Nothing ->+  closedImmediately <- optionalTok closeTok+  if closedImmediately+    then pure (ETuple tupleFlavor [])+    else       if tupleFlavor == Boxed         then MP.try (parseNegateParen closeTok) <|> parseBoxedContent closeTok         else MP.try (parseUnboxedSumExprLeadingBars closeTok) <|> parseTupleOrParen tupleFlavor closeTok@@ -823,7 +916,7 @@               )           )       let withInfix = foldInfixL buildInfix negBase rest-      mTypeSig <- MP.optional (expectedTok TkReservedDoubleColon *> typeSignatureParser)+      mTypeSig <- optionalTokThen TkReservedDoubleColon typeSignatureParser       let typed = case mTypeSig of             Just ty -> ETypeSig withInfix ty             Nothing -> withInfix@@ -862,7 +955,7 @@                     Just op ->                       pure (EParen (ESectionL base op))                     Nothing -> do-                      mTypeSig <- MP.optional (expectedTok TkReservedDoubleColon *> typeSignatureParser)+                      mTypeSig <- optionalTokThen TkReservedDoubleColon typeSignatureParser                       let typed = case mTypeSig of                             Just ty -> ETypeSig base ty                             Nothing -> base@@ -870,11 +963,10 @@                       finalExpr <- maybeViewPattern typed                       finishBoxed closeTok (Just finalExpr)                 Just op -> do-                  mClose <- MP.optional (expectedTok closeTok)-                  case mClose of-                    Just () ->-                      pure (EParen (ESectionL base op))-                    Nothing -> do+                  closedAfterOperator <- optionalTok closeTok+                  if closedAfterOperator+                    then pure (EParen (ESectionL base op))+                    else do                       rhs <- region "after infix operator" lexpParser                       more <-                         MP.many@@ -891,7 +983,7 @@                           expectedTok closeTok                           pure (EParen (ESectionL fullInfix trailOp))                         Nothing -> do-                          mTypeSig <- MP.optional (expectedTok TkReservedDoubleColon *> typeSignatureParser)+                          mTypeSig <- optionalTokThen TkReservedDoubleColon typeSignatureParser                           let typed = case mTypeSig of                                 Just ty -> ETypeSig fullInfix ty                                 Nothing -> fullInfix@@ -924,49 +1016,49 @@               _ -> Nothing      finishBoxed closeTok mFirst = do-      mComma <- MP.optional (expectedTok TkSpecialComma)-      case (mFirst, mComma) of-        (Just e, Nothing) -> do+      hasComma <- optionalTok TkSpecialComma+      case (mFirst, hasComma) of+        (Just e, False) -> do           expectedTok closeTok           pure (EParen e)-        (_, Just ()) -> do+        (_, True) -> do           rest <- parseTupleElems closeTok           pure (ETuple Boxed (mFirst : rest))-        (Nothing, Nothing) ->+        (Nothing, False) ->           fail "expected expression or closing paren"      parseTupleOrParen tupleFlavor closeTok = do       first <- MP.optional texprParser-      mComma <- MP.optional (expectedTok TkSpecialComma)-      case (first, mComma) of-        (Just e, Nothing) ->+      hasComma <- optionalTok TkSpecialComma+      case (first, hasComma) of+        (Just e, False) ->           case tupleFlavor of             Boxed -> do               expectedTok closeTok               pure (EParen e)             Unboxed -> do-              mPipe <- MP.optional (expectedTok TkReservedPipe)-              case mPipe of-                Just () -> do+              hasPipe <- optionalTok TkReservedPipe+              if hasPipe+                then do                   trailingBars <- MP.many (expectedTok TkReservedPipe)                   expectedTok closeTok                   let arity = 2 + length trailingBars                   pure (EUnboxedSum 0 arity e)-                Nothing -> do+                else do                   expectedTok closeTok                   pure (ETuple Unboxed [Just e])-        (_, Just ()) -> do+        (_, True) -> do           rest <- parseTupleElems closeTok           pure (ETuple tupleFlavor (first : rest))-        (Nothing, Nothing) ->+        (Nothing, False) ->           fail "expected expression or closing paren"      parseTupleElems closeTok = do       e <- MP.optional texprParser-      mComma <- MP.optional (expectedTok TkSpecialComma)-      case mComma of-        Just () -> (e :) <$> parseTupleElems closeTok-        Nothing -> do+      hasComma <- optionalTok TkSpecialComma+      if hasComma+        then (e :) <$> parseTupleElems closeTok+        else do           expectedTok closeTok           pure [e] @@ -983,10 +1075,10 @@ listExprParser :: TokParser Expr listExprParser = withSpanAnn (EAnn . mkAnnotation) $ do   expectedTok TkSpecialLBracket-  mClose <- MP.optional (expectedTok TkSpecialRBracket)-  case mClose of-    Just () -> pure (EList [])-    Nothing -> do+  closedImmediately <- optionalTok TkSpecialRBracket+  if closedImmediately+    then pure (EList [])+    else do       first <- exprParser       parseListTail first @@ -1039,7 +1131,7 @@  compStmtParser :: TokParser CompStmt compStmtParser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordLet -> MP.try compLetStmtParser <|> compGenOrGuardParser     TkKeywordThen -> compTransformStmtParser <|> compGenOrGuardParser@@ -1054,7 +1146,7 @@   guard enabled   expectedTok TkKeywordThen   -- Check for 'group' forms first-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkVarId "group" -> compGroupStmtParser     _ -> compThenStmtParser@@ -1063,7 +1155,7 @@ compGroupStmtParser :: TokParser CompStmt compGroupStmtParser = do   varIdTok "group"-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordBy -> do       expectedTok TkKeywordBy@@ -1081,10 +1173,10 @@ compThenStmtParser :: TokParser CompStmt compThenStmtParser = do   f <- compTransformExprParser-  mBy <- MP.optional (expectedTok TkKeywordBy)-  case mBy of-    Just () -> CompThenBy f <$> exprParser-    Nothing -> pure (CompThen f)+  hasBy <- optionalTok TkKeywordBy+  if hasBy+    then CompThenBy f <$> exprParser+    else pure (CompThen f)  -- | Expression parser for TransformListComp context. -- Parses an expression but treats bare 'by' and 'using' as terminators@@ -1171,7 +1263,7 @@ -- These are treated as contextual keywords in TransformListComp context. compTransformAtomOrRecordExprParser :: TokParser Expr compTransformAtomOrRecordExprParser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordBy -> MP.empty     TkKeywordUsing -> MP.empty@@ -1277,7 +1369,7 @@ localMultiplicityTagParser :: TokParser MultiplicityTag localMultiplicityTagParser = do   expectedTok TkPrefixPercent-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkInteger 1 _ -> anySingle $> LinearMultiplicityTag     _ -> ExplicitMultiplicityTag <$> typeAtomParser@@ -1355,7 +1447,7 @@  compactSpliceBodyParser :: TokParser Expr compactSpliceBodyParser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkReservedBackslash -> MP.empty     TkKeywordLet -> MP.empty
src/Aihc/Parser/Internal/FromTokens.hs view
@@ -37,7 +37,7 @@ runParserFromTokens :: TokParser a -> FilePath -> [LexToken] -> ParseResult a runParserFromTokens parser sourceName toks =   case runTokStreamParser parser sourceName (mkTokStreamFromTokens toks) of-    Left bundle -> ParseErr (parseErrorBundleToSpannedText bundle)+    Left bundle -> ParseErr (parseErrorBundleToSpannedText sourceName (rebuildStream mkTokStreamFromTokens toks) bundle)     Right parsed -> ParseOk parsed  parseFromTokens :: TokParser a -> FilePath -> [LexToken] -> ParseResult a
src/Aihc/Parser/Internal/Import.hs view
@@ -98,9 +98,9 @@      parseDotDotFirst = do       expectedTok TkReservedDotDot-      MP.optional (expectedTok TkSpecialComma) >>= \case-        Nothing -> pure MembersAll-        Just _ -> do+      optionalTok TkSpecialComma >>= \case+        False -> pure MembersAll+        True -> do           trailingMembers <- memberNameParser `MP.sepBy` expectedTok TkSpecialComma           pure (MembersListAll 0 trailingMembers) @@ -109,18 +109,18 @@       parseMemberSegments [firstMember]      parseMemberSegments members =-      MP.optional (expectedTok TkSpecialComma) >>= \case-        Nothing -> pure (MembersList members)-        Just _ ->+      optionalTok TkSpecialComma >>= \case+        False -> pure (MembersList members)+        True ->           (expectedTok TkReservedDotDot >> parseWildcardTail members)             <|> do               nextMember <- memberNameParser               parseMemberSegments (members <> [nextMember])      parseWildcardTail members =-      MP.optional (expectedTok TkSpecialComma) >>= \case-        Nothing -> pure (MembersListAll (length members) members)-        Just _ -> do+      optionalTok TkSpecialComma >>= \case+        False -> pure (MembersListAll (length members) members)+        True -> do           trailingMembers <- memberNameParser `MP.sepBy` expectedTok TkSpecialComma           pure (MembersListAll (length members) (members <> trailingMembers)) @@ -163,7 +163,7 @@           unexpectedExpecting = "import declaration without duplicate 'qualified'",           unexpectedContext = []         }-  importAlias <- MP.optional (expectedTok TkVarAs *> moduleNameParser)+  importAlias <- optionalTokThen TkVarAs moduleNameParser   importSpec <- MP.optional importSpecParser   let isQualified = preQualified || isJust postQualified   pure $ \span' ->
src/Aihc/Parser/Internal/Module.hs view
@@ -13,17 +13,17 @@ import Aihc.Parser.Internal.Common   ( TokParser,     closeAndExpectRBrace,+    consumedSpan,     eofTok,     expectedTok,-    inputStartSpan,     lazy,+    optionalTok,     skipSemicolons,   ) import Aihc.Parser.Internal.Decl (declParser) import Aihc.Parser.Internal.Import (importDeclParser, languagePragmaParser, moduleHeaderParser)-import Aihc.Parser.Lex (LexToken (lexTokenSpan), LexTokenKind (..), lexTokenKind)-import Aihc.Parser.Syntax (Decl, ImportDecl, Module (..), mergeSourceSpans, mkAnnotation, noSourceSpan)-import Aihc.Parser.Types (TokStream (tokStreamPrevToken))+import Aihc.Parser.Lex (LexTokenKind (..), lexTokenKind)+import Aihc.Parser.Syntax (Decl, ImportDecl, Module (..), mkAnnotation) import Control.Monad (void) import Text.Megaparsec qualified as MP @@ -36,7 +36,10 @@ -- the whole-module span suspended until the corresponding result is demanded. moduleParser :: TokParser Module moduleParser = do-  startInput <- MP.getInput+  startState <- MP.getParserState+  let !startInput = MP.stateInput startState+      !startOffset = MP.stateOffset startState+      !inputStart = MP.pstateSourcePos (MP.statePosState startState)   languagePragmas <- MP.many (languagePragmaParser <* MP.many (expectedTok TkSpecialSemicolon))   mHeader <- MP.optional (moduleHeaderParser <* MP.many (expectedTok TkSpecialSemicolon))   expectedTok TkSpecialLBrace@@ -49,8 +52,7 @@           <* MP.lookAhead eofTok       )   MP.updateParserState (\state -> state {MP.stateParseErrors = MP.stateParseErrors finalState})-  let endSpan = maybe noSourceSpan lexTokenSpan (tokStreamPrevToken (MP.stateInput finalState))-      moduleSpan = mergeSourceSpans (inputStartSpan startInput) endSpan+  let moduleSpan = consumedSpan inputStart startInput startOffset (MP.stateInput finalState) (MP.stateOffset finalState)   pure     Module       { moduleAnns = [mkAnnotation moduleSpan],@@ -99,4 +101,4 @@           let kind = lexTokenKind tok            in kind /= TkSpecialSemicolon && kind /= TkSpecialRBrace       )-  void (MP.optional (expectedTok TkSpecialSemicolon))+  void (optionalTok TkSpecialSemicolon)
src/Aihc/Parser/Internal/Pattern.hs view
@@ -102,14 +102,8 @@ buildPatternApp :: Pattern -> Pattern -> Pattern buildPatternApp lhs rhs =   case peelPatternAnn lhs of-    PCon name typeArgs args ->-      PAnn-        (mkAnnotation NoSourceSpan)-        (PCon name typeArgs (args <> [rhs]))-    PBuiltinCon con typeArgs args ->-      PAnn-        (mkAnnotation NoSourceSpan)-        (PBuiltinCon con typeArgs (args <> [rhs]))+    PCon name typeArgs args -> PCon name typeArgs (args <> [rhs])+    PBuiltinCon con typeArgs args -> PBuiltinCon con typeArgs (args <> [rhs])     _ -> lhs  -- | Parse an atomic pattern (@apat@ in the Haskell Report).@@ -132,7 +126,7 @@  nonAsApatParser :: TokParser Pattern nonAsApatParser = do-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkTypeApp -> do       typeAbstractionsEnabled <- isExtensionEnabled TypeAbstractions@@ -273,7 +267,7 @@ thSplicePatternParser :: TokParser Pattern thSplicePatternParser = withSpanAnn (PAnn . mkAnnotation) $ do   expectedTok TkTHSplice-  tok <- lookAhead anySingle+  tok <- peekToken   case lexTokenKind tok of     TkKeywordType -> MP.empty     _ -> PSplice <$> atomExprParser@@ -297,15 +291,15 @@ varOrConPatternParser :: TokParser Pattern varOrConPatternParser = do   (tok, name) <- identifierNameWithTokenParser-  mNextTok <- MP.optional (lookAhead anySingle)+  mNextTok <- peekTokenMaybe   let ann = mkAnnotation (lexTokenSpan tok)   case mNextTok of     Just nextTok       | isConLikeName name && lexTokenKind nextTok == TkSpecialLBrace -> do           (fields, hasWildcard) <- braces recordPatternFieldListParser           endInput <- MP.getInput-          let endSpan = maybe noSourceSpan lexTokenSpan (tokStreamPrevToken endInput)-              recordSpan = mergeSourceSpans (lexTokenSpan tok) endSpan+          let startSpan = lexTokenSpan tok+              recordSpan = maybe startSpan (mergeSourceSpans startSpan . lexTokenSpan) (tokStreamPrevToken endInput)           pure (PAnn (mkAnnotation recordSpan) (PRecord name fields hasWildcard))     _ ->       pure $@@ -317,12 +311,12 @@ recordFieldPatternParser :: TokParser (RecordField Pattern) recordFieldPatternParser = do   field <- recordFieldNameParser-  mEq <- MP.optional (expectedTok TkReservedEquals)-  case mEq of-    Just () -> do+  hasEquals <- optionalTok TkReservedEquals+  if hasEquals+    then do       pat <- subpatternWithBareViewParser       pure (RecordField field pat False)-    Nothing -> do+    else       -- NamedFieldPuns: just "field" means "field = field"       pure (RecordField field (PVar (nameToUnqualified field)) True) @@ -358,7 +352,7 @@ subpatternWithBareViewParser = do   mResult <- MP.optional . MP.try $ do     expr <- exprParser-    tok <- lookAhead anySingle+    tok <- peekToken     case lexTokenKind tok of       TkReservedRightArrow -> pure (Left expr)       TkSpecialComma -> Right <$> liftCheck (checkPattern expr)@@ -378,7 +372,7 @@ parenOrTuplePatternParser :: TokParser Pattern parenOrTuplePatternParser = withSpanAnn (PAnn . mkAnnotation) $ do   (tupleFlavor, closeTok) <- tupleDelimsParser-  mNextTok <- MP.optional (lookAhead anySingle)+  mNextTok <- peekTokenMaybe   case fmap lexTokenKind mNextTok of     Just nextKind       | nextKind == closeTok -> unitPatternParser tupleFlavor closeTok@@ -465,7 +459,7 @@     -- the surrounding parens serve as prefix notation rather than grouping.     parenPatElementParser :: TokParser (Bool, Pattern)     parenPatElementParser = do-      tok <- lookAhead anySingle+      tok <- peekToken       case lexTokenKind tok of         TkPrefixBang -> (False,) <$> patternParser         TkPrefixTilde -> (False,) <$> patternParser@@ -490,7 +484,7 @@           -- Look ahead to check what comes after the operator           mNext <- MP.optional . lookAhead . MP.try $ do             _ <- anySingle -- skip the operator token itself-            lookAhead anySingle+            peekToken           case fmap lexTokenKind mNext of             -- If followed by closing delimiters, parse as operator pattern             Just TkSpecialRParen -> (True,) <$> operatorPatternParser@@ -521,28 +515,28 @@      tupleOrParenPatternParser tupleFlavor closeTok = do       (isBareOp, first) <- parenPatElementParser-      mComma <- MP.optional (expectedTok TkSpecialComma)-      case mComma of-        Nothing -> do+      hasComma <- optionalTok TkSpecialComma+      if hasComma+        then do+          (_, second) <- parenPatElementParser+          more <- MP.many (expectedTok TkSpecialComma *> (snd <$> parenPatElementParser))+          expectedTok closeTok+          pure (PTuple tupleFlavor (first : second : more))+        else do           -- Check for pipe (unboxed sum: pattern in first slot)-          mPipe <- if tupleFlavor == Unboxed then MP.optional (expectedTok TkReservedPipe) else pure Nothing-          case mPipe of-            Just () -> do+          hasPipe <- if tupleFlavor == Unboxed then optionalTok TkReservedPipe else pure False+          if hasPipe+            then do               -- (# pat | ... #) - pattern in first slot of sum               trailingBars <- MP.many (expectedTok TkReservedPipe)               expectedTok closeTok               let arity = 2 + length trailingBars               pure (PUnboxedSum 0 arity first)-            Nothing -> do+            else do               expectedTok closeTok               if tupleFlavor == Boxed                 then parenOrSymConParser isBareOp first                 else pure (PTuple Unboxed [first])-        Just () -> do-          (_, second) <- parenPatElementParser-          more <- MP.many (expectedTok TkSpecialComma *> (snd <$> parenPatElementParser))-          expectedTok closeTok-          pure (PTuple tupleFlavor (first : second : more))      parseUnboxedSumPatLeadingBars closeTok = do       -- Parse (# | | ... | pat | ... | #) where pattern is not in first slot
src/Aihc/Parser/Internal/Type.hs view
@@ -129,7 +129,7 @@     ( do         expectedTok TkSpecialLBrace         ident <- tyVarNameParser-        mKind <- MP.optional (expectedTok TkReservedDoubleColon *> typeParser)+        mKind <- optionalTokThen TkReservedDoubleColon typeParser         expectedTok TkSpecialRBrace         pure (\span' -> TyVarBinder [mkAnnotation span'] ident mKind TyVarBInferred TyVarBVisible)     )@@ -395,10 +395,9 @@     TkConId {} -> typeIdentifierParser     TkQVarId {} -> typeIdentifierParser     TkQConId {} -> typeIdentifierParser-    _ -> do-      thAny <- thAnyEnabled-      ipEnabled <- isExtensionEnabled ImplicitParams-      typeAtomParserAlternatives thAny ipEnabled+    -- Every other alternative in 'typeAtomParserAlternatives' begins with a+    -- token kind already dispatched above, so only these three can match.+    _ -> MP.try promotedTypeParser <|> typeStarParser <|> typeIdentifierParser  typeAtomParserAlternatives :: Bool -> Bool -> TokParser Type typeAtomParserAlternatives thAny ipEnabled =@@ -510,10 +509,10 @@ typeListParser :: TokParser Type typeListParser = withSpanAnn (TAnn . mkAnnotation) $ do   expectedTok TkSpecialLBracket-  mClosed <- MP.optional (expectedTok TkSpecialRBracket)-  case mClosed of-    Just () -> pure (TBuiltinCon BuiltinList Unpromoted)-    Nothing -> do+  closedImmediately <- optionalTok TkSpecialRBracket+  if closedImmediately+    then pure (TBuiltinCon BuiltinList Unpromoted)+    else do       elems <- typeParser `MP.sepBy1` expectedTok TkSpecialComma       expectedTok TkSpecialRBracket       pure (TList Unpromoted elems)@@ -521,11 +520,10 @@ typeParenOrTupleParser :: TokParser Type typeParenOrTupleParser = withSpanAnn (TAnn . mkAnnotation) $ do   (tupleFlavor, closeTok) <- tupleDelimsParser-  mClosed <- MP.optional (expectedTok closeTok)-  case mClosed of-    Just () -> pure (TTuple tupleFlavor Unpromoted [])-    Nothing -> do-      MP.try (tupleConstructorParser tupleFlavor closeTok) <|> parenthesizedTypeOrTupleParser tupleFlavor closeTok+  closedImmediately <- optionalTok closeTok+  if closedImmediately+    then pure (TTuple tupleFlavor Unpromoted [])+    else MP.try (tupleConstructorParser tupleFlavor closeTok) <|> parenthesizedTypeOrTupleParser tupleFlavor closeTok   where     tupleConstructorParser tupleFlavor closeTok = do       _ <- expectedTok TkSpecialComma@@ -536,33 +534,33 @@      parenthesizedTypeOrTupleParser tupleFlavor closeTok = do       first <- typeParser-      mKind <- if tupleFlavor == Boxed then MP.optional (expectedTok TkReservedDoubleColon *> typeParser) else pure Nothing+      mKind <- if tupleFlavor == Boxed then optionalTokThen TkReservedDoubleColon typeParser else pure Nothing       case mKind of         Just kind -> do           expectedTok closeTok           pure (TParen (TKindSig first kind))         Nothing -> do-          mComma <- MP.optional (expectedTok TkSpecialComma)-          case mComma of-            Nothing -> do+          hasComma <- optionalTok TkSpecialComma+          if hasComma+            then do+              second <- typeParser+              more <- MP.many (expectedTok TkSpecialComma *> typeParser)+              expectedTok closeTok+              pure (TTuple tupleFlavor Unpromoted (first : second : more))+            else do               -- Check for pipe (unboxed sum type)-              mPipe <- if tupleFlavor == Unboxed then MP.optional (expectedTok TkReservedPipe) else pure Nothing-              case mPipe of-                Just () -> do+              hasPipe <- if tupleFlavor == Unboxed then optionalTok TkReservedPipe else pure False+              if hasPipe+                then do                   -- (# Type1 | Type2 | ... #) - unboxed sum type                   rest <- typeParser `MP.sepBy1` expectedTok TkReservedPipe                   expectedTok closeTok                   pure (TUnboxedSum (first : rest))-                Nothing -> do+                else do                   expectedTok closeTok                   case tupleFlavor of                     Boxed -> pure (TParen first)                     Unboxed -> pure (TTuple Unboxed Unpromoted [first])-            Just () -> do-              second <- typeParser-              more <- MP.many (expectedTok TkSpecialComma *> typeParser)-              expectedTok closeTok-              pure (TTuple tupleFlavor Unpromoted (first : second : more))  markTypePromoted :: Type -> Maybe Type markTypePromoted ty =
src/Aihc/Parser/Lex.hs view
@@ -123,8 +123,8 @@     SkipDone st       | T.null (lexerInput st) -> [eofToken st]       | otherwise ->-          let (tok, st') = nextToken env st-           in tok : scanTokens env (recordScannedToken tok st')+          case nextToken env st of+            (tok, st') -> tok : scanTokens env (recordScannedToken tok st')  data SkipResult = SkipDone !LexerState | SkipToken !LexToken !LexerState @@ -147,8 +147,8 @@             _               | Just rest <- T.stripPrefix "--" inp,                 isLineComment rest ->-                  let (tok, st') = consumeLineCommentToken st-                   in SkipToken tok (markHadTrivia st')+                  case consumeLineCommentToken st of+                    (tok, st') -> SkipToken tok (markHadTrivia st')             -- Check {-# before {- so control pragmas are handled first and             -- block comment handler does not eat pragma tokens.             _@@ -261,11 +261,11 @@       case scanOneToken env lexSt of         Nothing -> Nothing         Just (rawTok, lexSt') ->-          let (allToks, laySt') = layoutTransition laySt rawTok-           in case allToks of-                [] -> Just (rawTok, lexSt', laySt')-                [first] -> Just (first, lexSt', laySt')-                first : rest -> Just (first, lexSt', laySt' {layoutBuffer = rest})+          case layoutTransition laySt rawTok of+            (allToks, laySt') -> case allToks of+              [] -> Just (rawTok, lexSt', laySt')+              [first] -> Just (first, lexSt', laySt')+              first : rest -> Just (first, lexSt', laySt' {layoutBuffer = rest})  scanOneToken :: LexerEnv -> LexerState -> Maybe (LexToken, LexerState) scanOneToken env st0 =@@ -281,8 +281,8 @@                   st' = st {lexerPrevTokenKind = Just TkEOF, lexerHadTrivia = False}                in Just (tok, st')       | otherwise ->-          let (tok, st') = nextToken env st-           in Just (tok, recordScannedToken tok st')+          case nextToken env st of+            (tok, st') -> Just (tok, recordScannedToken tok st')  scanAllTokens :: LexerEnv -> LexerState -> [LexToken] scanAllTokens env st =@@ -299,23 +299,25 @@               !firstChunkLen = utf8CharWidth c + identTailBytes hasMagicHash rest               firstChunk = TU.takeWord8 firstChunkLen (lexerInput st)               rest0 = TU.dropWord8 firstChunkLen (lexerInput st)-              (consumed, rest1, isQualified) = gatherQualified hasMagicHash False firstChunk rest0-           in case (isQualified || isConIdStart c, rest1) of-                (True, '.' :< dotRest@(opChar :< _))-                  | isSymbolicOpChar opChar ->-                      let opChars = T.takeWhile isSymbolicOpChar dotRest-                          fullOp = consumed <> "." <> opChars-                          (modName, opName) = splitQualified (consumed <> ".") opChars-                          kind =-                            if opChar == ':'-                              then TkQConSym modName opName-                              else TkQVarSym modName opName-                          st' = advanceChars fullOp st-                       in Just (mkToken st st' fullOp kind, st')-                _ ->-                  let kind = classifyIdentifier c isQualified consumed-                      st' = advanceChars consumed st-                   in Just (mkToken st st' consumed kind, st')+           in case gatherQualified hasMagicHash False firstChunk rest0 of+                (consumed, rest1, isQualified) ->+                  case rest1 of+                    '.' :< dotRest@(opChar :< _)+                      | isQualified || isConIdStart c,+                        isSymbolicOpChar opChar ->+                          let opChars = T.takeWhile isSymbolicOpChar dotRest+                              fullOp = consumed <> "." <> opChars+                              modName = consumed+                              kind =+                                if opChar == ':'+                                  then TkQConSym modName opChars+                                  else TkQVarSym modName opChars+                              !st' = advanceChars fullOp st+                           in Just (mkToken st st' fullOp kind, st')+                    _ ->+                      let kind = classifyIdentifier c isQualified consumed+                          !st' = advanceChars consumed st+                       in Just (mkToken st st' consumed kind, st')     _ -> Nothing   where     -- The Bool accumulator records whether a qualifier segment was added.@@ -326,30 +328,26 @@           | isIdentStart c',             not (T.isSuffixOf "#" acc),             isConIdStart (T.head acc) ->-              let (seg, rest) = consumeIdentTail hasMH more-                  segWithHead = TU.takeWord8 (utf8CharWidth c' + TU.lengthWord8 seg) dotRest-               in gatherQualified hasMH True (acc <> "." <> segWithHead) rest+              case consumeIdentTail hasMH more of+                (seg, rest) ->+                  let segWithHead = TU.takeWord8 (utf8CharWidth c' + TU.lengthWord8 seg) dotRest+                   in gatherQualified hasMH True (acc <> "." <> segWithHead) rest         _ -> (acc, chars, qualified) -    -- Split a qualified identifier into (module part, name part).-    -- E.g. "Data.Maybe." ++ "++" -> ("Data.Maybe", "++")-    splitQualified :: Text -> Text -> (Text, Text)-    splitQualified modWithDot name =-      (T.dropEnd 1 modWithDot, name)-     classifyIdentifier firstChar isQualified ident       | isQualified =           let rev = T.reverse ident-              (revName, revRest) = T.span (/= '.') rev-              modName = T.reverse (T.drop 1 revRest)-              name = T.reverse revName-           in case T.uncons name of-                Just (c', _)-                  | isConIdStart c' -> TkQConId modName name-                  | name == "do" && hasExt QualifiedDo env -> TkQualifiedDo modName-                  | name == "mdo" && hasExt QualifiedDo env && hasExt RecursiveDo env -> TkQualifiedMdo modName-                Just _ -> TkQVarId modName name-                Nothing -> TkQVarId modName name+           in case T.span (/= '.') rev of+                (revName, revRest) ->+                  let modName = T.reverse (T.drop 1 revRest)+                      name = T.reverse revName+                   in case T.uncons name of+                        Just (c', _)+                          | isConIdStart c' -> TkQConId modName name+                          | name == "do" && hasExt QualifiedDo env -> TkQualifiedDo modName+                          | name == "mdo" && hasExt QualifiedDo env && hasExt RecursiveDo env -> TkQualifiedMdo modName+                        Just _ -> TkQVarId modName name+                        Nothing -> TkQVarId modName name       | otherwise =           case keywordTokenKind (lexerExtensions env) ident of             Just kw -> kw@@ -458,18 +456,11 @@         other -> other      extendSpanLeft sp =-      case sp of-        SourceSpan {sourceSpanSourceName, sourceSpanEndLine = endLine, sourceSpanEndCol = endCol, sourceSpanEndOffset} ->-          SourceSpan-            { sourceSpanSourceName = sourceSpanSourceName,-              sourceSpanStartLine = lexerLine stBefore,-              sourceSpanStartCol = lexerCol stBefore,-              sourceSpanEndLine = endLine,-              sourceSpanEndCol = endCol,-              sourceSpanStartOffset = lexerByteOffset stBefore,-              sourceSpanEndOffset = sourceSpanEndOffset-            }-        NoSourceSpan -> NoSourceSpan+      sp+        { sourceSpanStartLine = lexerLine stBefore,+          sourceSpanStartCol = lexerCol stBefore,+          sourceSpanStartOffset = lexerByteOffset stBefore+        }  -- | Does the token kind represent a primitive (unboxed) numeric literal? -- These are MagicHash types (Int#, Word#, Float#, Double#) and ExtendedLiterals
src/Aihc/Parser/Lex/Layout.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-}  module Aihc.Parser.Lex.Layout@@ -8,7 +9,7 @@ where  import Aihc.Parser.Lex.Types-import Aihc.Parser.Syntax (Extension, SourceSpan (..))+import Aihc.Parser.Syntax (Extension, SourceSpan) import Data.Maybe (fromMaybe)  ordinaryLayout :: ImplicitLayoutSpec@@ -57,13 +58,22 @@     go st toks =       case toks of         [] ->-          let eofAnchor = NoSourceSpan-              (moduleInserted, stAfterModule) = finalizeModuleLayoutAtEOF st eofAnchor-              (pendingInserted, stAfterPending) = flushPendingCaseLayoutAtEOF stAfterModule eofAnchor-           in moduleInserted <> pendingInserted <> closeAllImplicit (layoutContexts stAfterPending) eofAnchor+          -- 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 ->-          let (emitted, stNext) = layoutTransition st tok-           in emitted <> go stNext rest+          case layoutTransition st tok of+            (emitted, stNext) -> emitted <> go stNext rest  finalizeModuleLayoutAtEOF :: LayoutState -> SourceSpan -> ([LexToken], LayoutState) finalizeModuleLayoutAtEOF st anchor =@@ -171,9 +181,10 @@   case lexTokenKind tok of     kind       | closesImplicitBeforeDelimiter kind ->-          let (pendingInserted, st0) = flushPendingImplicitLayout st anchor-              (inserted, ctxs') = closeImplicitLayouts anchor (\_ _ -> True) (layoutContexts st0)-           in (pendingInserted <> inserted, st0 {layoutContexts = ctxs'})+          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)@@ -181,10 +192,11 @@     anchor = lexTokenSpan tok      closeBeforeLayoutKeyword =-      let col = tokenStartCol tok-          (pendingInserted, st0) = flushPendingImplicitLayout st anchor-          (inserted, ctxs') = closeImplicitLayouts anchor (shouldClose col) (layoutContexts st0)-       in (pendingInserted <> inserted, st0 {layoutContexts = ctxs'})+      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)@@ -225,18 +237,20 @@ bolLayout st tok   | not (isBOL st tok) = ([], st)   | otherwise =-      let col = tokenStartCol tok-          (inserted, contexts') = closeImplicitLayouts (lexTokenSpan tok) (\indent _ -> col < indent) (layoutContexts st)-          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'})+      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 =@@ -428,50 +442,60 @@     TkLineComment -> ([tok], st)     TkBlockComment -> ([tok], st)     TkEOF ->-      let eofAnchor = fromMaybe (lexTokenSpan tok) (layoutPrevTokenEndSpan st)-          (moduleInserted, stAfterModule) = finalizeModuleLayoutAtEOF st eofAnchor-          (pendingInserted, stAfterPending) = flushPendingCaseLayoutAtEOF stAfterModule eofAnchor-       in ( moduleInserted <> pendingInserted <> closeAllImplicit (layoutContexts stAfterPending) eofAnchor <> [tok],-            stAfterPending {layoutContexts = [], layoutBuffer = []}-          )+      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 = []}+                  )     _ ->-      let stModule = noteModuleLayoutBeforeToken st tok-          (preInserted, stBeforePending) = closeBeforeToken stModule tok-          (pendingInserted, stAfterPending, skipBOL) = openPendingLayout stBeforePending tok-          (bolInserted, stAfterBOL) = if skipBOL then ([], stAfterPending) else bolLayout stAfterPending tok-          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 (preInserted <> pendingInserted <> bolInserted <> [tok], stNext)+      -- 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 of-    LayoutImplicit _ : rest -> Just (closeWith rest)+  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-  where-    anchor = fromMaybe noSpan (layoutPrevTokenEndSpan st)-    noSpan =-      SourceSpan-        { sourceSpanSourceName = "",-          sourceSpanStartLine = 0,-          sourceSpanStartCol = 0,-          sourceSpanEndLine = 0,-          sourceSpanEndCol = 0,-          sourceSpanStartOffset = 0,-          sourceSpanEndOffset = 0-        }-    closeWith rest =-      st-        { layoutContexts = rest,-          layoutBuffer = virtualSymbolToken "}" anchor : layoutBuffer st-        }
src/Aihc/Parser/Lex/Pragmas.hs view
@@ -306,8 +306,8 @@     _       | Just rest <- T.stripPrefix "LINE" upperBody,         isPragmaBodyEnd rest ->-          let bodyAfter = dropPragmaName "LINE" trimmed-              ws = T.words bodyAfter+          let afterName = T.stripStart (dropPragmaName "LINE" trimmed)+              ws = T.words afterName            in case ws of                 lineNo : _                   | T.all isDigit lineNo ->@@ -319,7 +319,7 @@                                 DirectiveUpdate                                   { directiveLine = Just parsedLine,                                     directiveCol = Just 1,-                                    directiveSourceName = parseDirectiveSourceName (T.dropWhile isSpace (T.drop (T.length lineNo) bodyAfter))+                                    directiveSourceName = parseDirectiveSourceName (T.drop (T.length lineNo) afterName)                                   }                             )                         Nothing -> Just ("{-#" <> body <> "#-}", Left "malformed LINE pragma")@@ -342,14 +342,14 @@                 _ -> Just ("{-#" <> body <> "#-}", Left "malformed COLUMN pragma")     _ -> Nothing -parseDirectiveSourceName :: Text -> Maybe FilePath+parseDirectiveSourceName :: Text -> Maybe Text parseDirectiveSourceName rest =   let rest' = T.dropWhile isSpace rest    in case rest' of         '"' :< more ->           let (name, trailing) = T.break (== '"') more            in case trailing of-                '"' :< _ -> Just (T.unpack name)+                '"' :< _ -> Just name                 _ -> Nothing         _ -> Nothing 
src/Aihc/Parser/Lex/Trivia.hs view
@@ -195,14 +195,14 @@         c :< _ | isDigit c -> True         _ -> "line" `T.isPrefixOf` afterHash -parseDirectiveSourceName :: Text -> Maybe FilePath+parseDirectiveSourceName :: Text -> Maybe Text parseDirectiveSourceName rest =   let rest' = T.dropWhile isSpace rest    in case rest' of         '"' :< more ->           let (name, trailing) = T.break (== '"') more            in case trailing of-                '"' :< _ -> Just (T.unpack name)+                '"' :< _ -> Just name                 _ -> Nothing         _ -> Nothing 
src/Aihc/Parser/Lex/Types.hs view
@@ -226,7 +226,7 @@  data LexerState = LexerState   { lexerInput :: !Text,-    lexerLogicalSourceName :: !FilePath,+    lexerLogicalSourceName :: !Text,     lexerLine :: !Int,     lexerCol :: !Int,     lexerByteOffset :: !Int,@@ -307,7 +307,7 @@ data DirectiveUpdate = DirectiveUpdate   { directiveLine :: !(Maybe Int),     directiveCol :: !(Maybe Int),-    directiveSourceName :: !(Maybe FilePath)+    directiveSourceName :: !(Maybe Text)   }   deriving (Eq, Show) @@ -325,7 +325,7 @@   ( mkLexerEnv exts,     LexerState       { lexerInput = input,-        lexerLogicalSourceName = sourceName,+        lexerLogicalSourceName = T.pack sourceName,         lexerLine = 1,         lexerCol = 1,         lexerByteOffset = 0,@@ -456,10 +456,7 @@    in go 0 (lexerLine st) (lexerCol st) (lexerByteOffset st) (lexerAtLineStart st)  tokenStartCol :: LexToken -> Int-tokenStartCol tok =-  case lexTokenSpan tok of-    SourceSpan {sourceSpanStartCol = col} -> col-    NoSourceSpan -> 1+tokenStartCol tok = sourceSpanStartCol (lexTokenSpan tok)  virtualSymbolToken :: Text -> SourceSpan -> LexToken virtualSymbolToken sym span' =
src/Aihc/Parser/Syntax.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}  -- | --@@ -85,7 +87,15 @@     Role (..),     RoleAnnotation (..),     Rhs (..),-    SourceSpan (..),+    SourceSpan,+    pattern SourceSpan,+    sourceSpanSourceName,+    sourceSpanStartLine,+    sourceSpanStartCol,+    sourceSpanEndLine,+    sourceSpanEndCol,+    sourceSpanStartOffset,+    sourceSpanEndOffset,     StandaloneDerivingDecl (..),     Type (..),     TupleFlavor (..),@@ -120,7 +130,6 @@     gadtBodyResultType,     languageEditionExtensions,     editionFromExtensionSettings,-    noSourceSpan,     mergeSourceSpans,     mkName,     mkUnqualifiedName,@@ -160,14 +169,13 @@ where  import Control.DeepSeq (NFData (..))-import Data.Bits (setBit, testBit)+import Data.Bits (clearBit, countTrailingZeros, setBit, shiftL, shiftR, testBit, (.&.), (.|.)) import Data.Char (GeneralCategory (..), generalCategory) import Data.Data (Constr, Data (..), DataType, Fixity (Prefix), mkConstr, mkDataType) import Data.Dynamic (Dynamic, Typeable, fromDynamic, toDyn)-import Data.List (sort) import Data.List qualified as List import Data.Map qualified as Map-import Data.Maybe (fromMaybe, mapMaybe)+import Data.Maybe (fromMaybe) import Data.String (IsString (..)) import Data.Text (Text) import Data.Text qualified as T@@ -344,14 +352,37 @@   deriving anyclass (NFData)  mkExtensionSet :: [Extension] -> ExtensionSet-mkExtensionSet = List.foldl' insertExtension (ExtensionSet 0 0 0)+mkExtensionSet = List.foldl' (flip insertExtension) emptyExtensionSet++emptyExtensionSet :: ExtensionSet+emptyExtensionSet = ExtensionSet 0 0 0++insertExtension :: Extension -> ExtensionSet -> ExtensionSet+insertExtension ext (ExtensionSet low middle high) =+  case fromEnum ext `quotRem` 64 of+    (0, bit) -> ExtensionSet (setBit low bit) middle high+    (1, bit) -> ExtensionSet low (setBit middle bit) high+    (2, bit) -> ExtensionSet low middle (setBit high bit)+    _ -> error "insertExtension: extension enum exceeds bitset capacity"++deleteExtension :: Extension -> ExtensionSet -> ExtensionSet+deleteExtension ext (ExtensionSet low middle high) =+  case fromEnum ext `quotRem` 64 of+    (0, bit) -> ExtensionSet (clearBit low bit) middle high+    (1, bit) -> ExtensionSet low (clearBit middle bit) high+    (2, bit) -> ExtensionSet low middle (clearBit high bit)+    _ -> error "deleteExtension: extension enum exceeds bitset capacity"++-- | The members of the set, in 'Extension' constructor order.+extensionSetToList :: ExtensionSet -> [Extension]+extensionSetToList (ExtensionSet low middle high) =+  wordMembers 0 low (wordMembers 64 middle (wordMembers 128 high []))   where-    insertExtension (ExtensionSet low middle high) ext =-      case fromEnum ext `quotRem` 64 of-        (0, bit) -> ExtensionSet (setBit low bit) middle high-        (1, bit) -> ExtensionSet low (setBit middle bit) high-        (2, bit) -> ExtensionSet low middle (setBit high bit)-        _ -> error "mkExtensionSet: extension enum exceeds bitset capacity"+    wordMembers base word rest+      | word == 0 = rest+      | otherwise =+          let bit = countTrailingZeros word+           in toEnum (base + bit) : wordMembers base (clearBit word bit) rest  memberExtension :: Extension -> ExtensionSet -> Bool memberExtension ext (ExtensionSet low middle high) =@@ -655,12 +686,32 @@ impliedExtensionMap :: Map.Map Extension [ExtensionSetting] impliedExtensionMap = Map.fromList impliedExtensions +-- | Close a set of extensions under 'impliedExtensions'.+--+-- The fixpoint runs on the 'ExtensionSet' bitset rather than on the list:+-- enabling or disabling one extension is then a word operation instead of a+-- filter over every extension already enabled, and comparing two rounds is a+-- comparison of three words instead of sorting two lists.  The result is+-- returned in 'Extension' constructor order; only membership is meaningful,+-- and the caller's own list is handed back untouched when it is already+-- closed. applyImpliedExtensions :: [Extension] -> [Extension]-applyImpliedExtensions extensions =-  let settings = concat $ mapMaybe (`Map.lookup` impliedExtensionMap) extensions-      newExtensions = foldr applyExtensionSetting extensions settings-   in if sort newExtensions == sort extensions then extensions else applyImpliedExtensions newExtensions+applyImpliedExtensions extensions+  | closure == initial = extensions+  | otherwise = extensionSetToList closure+  where+    initial = mkExtensionSet extensions+    closure = go initial+    go current =+      let settings = concatMap impliedBy (extensionSetToList current)+          next = foldr applySettingToSet current settings+       in if next == current then current else go next+    impliedBy ext = Map.findWithDefault [] ext impliedExtensionMap +applySettingToSet :: ExtensionSetting -> ExtensionSet -> ExtensionSet+applySettingToSet (EnableExtension ext) = insertExtension ext+applySettingToSet (DisableExtension ext) = deleteExtension ext+ -- | Apply 'LANGUAGE' settings left to right, the order GHC applies them in, -- so that a later setting overrides an earlier one. Implications are applied -- immediately after each enable, so a later explicit disable can override an@@ -671,25 +722,96 @@     applyOne extensions setting@(EnableExtension _) = applyImpliedExtensions (applyExtensionSetting setting extensions)     applyOne extensions setting@(DisableExtension _) = applyExtensionSetting setting extensions --- | Source location metadata for parsed syntax.--- Example: the span covering @map@ in @map f xs@.+-- | Source location metadata for parsed syntax, such as the token range for+-- @map@ in @map f xs@.+--+-- A span is always concrete. Syntax without a location carries no+-- 'SourceSpan' annotation at all, so consumers read a span with+-- 'fromAnnotation' and treat its absence as the missing case.+--+-- A span is flat: the source name is a 'Text' that every span from the same+-- file shares, and the positions are unboxed, so a span in weak head normal+-- form is already in normal form and 'rnf' on it is constant time.+--+-- Line, column and byte-offset numbers are non-negative and far below @2^32@+-- even for machine-generated sources, so the six of them are packed two to a+-- word.  A span is a third smaller as a result, which matters because it is+-- the single most numerous heap object in a parse tree.  The packing is an+-- implementation detail: the 'SourceSpan' pattern synonym below constructs+-- and matches spans in terms of the same seven fields as before.+--+-- Six @{-\# UNPACK \#-} !Word32@ fields would give exactly the same five-word+-- closure, because GHC packs unpacked sub-word fields two to a machine word,+-- and would need no shifting here.  It was measured and is about 3% slower on+-- the @bench-aihc-base@ benchmark at byte-identical allocation, so the+-- explicit packing stays. data SourceSpan-  = -- | No location information is available.-    NoSourceSpan-  | -- | A concrete span such as the token range for @map@ in @map f xs@.-    SourceSpan-      { sourceSpanSourceName :: !FilePath,-        sourceSpanStartLine :: !Int,-        sourceSpanStartCol :: !Int,-        sourceSpanEndLine :: !Int,-        sourceSpanEndCol :: !Int,-        sourceSpanStartOffset :: !Int,-        sourceSpanEndOffset :: !Int-      }-  deriving (Data, Eq, Ord, Generic, NFData)+  = PackedSourceSpan+      -- | The file the span refers to, as given to the parser or by a+      -- @LINE@ pragma or @#line@ directive.+      !Text+      -- | Start line in the high half, start column in the low half.+      {-# UNPACK #-} !Word64+      -- | End line in the high half, end column in the low half.+      {-# UNPACK #-} !Word64+      -- | Start byte offset in the high half, end byte offset in the low half.+      {-# UNPACK #-} !Word64+  deriving (Data, Eq, Ord, Generic) +-- | A concrete span, in terms of its seven logical fields.+--+-- Deriving 'Ord' on the packed representation gives the same ordering as+-- deriving it on these fields, because each pair is packed most-significant+-- component first and in the same order.+pattern SourceSpan ::+  Text -> Int -> Int -> Int -> Int -> Int -> Int -> SourceSpan+pattern SourceSpan+  { sourceSpanSourceName,+    sourceSpanStartLine,+    sourceSpanStartCol,+    sourceSpanEndLine,+    sourceSpanEndCol,+    sourceSpanStartOffset,+    sourceSpanEndOffset+  } <-+  PackedSourceSpan+    sourceSpanSourceName+    (unpackPositions -> (sourceSpanStartLine, sourceSpanStartCol))+    (unpackPositions -> (sourceSpanEndLine, sourceSpanEndCol))+    (unpackPositions -> (sourceSpanStartOffset, sourceSpanEndOffset))+  where+    SourceSpan name startLine startCol endLine endCol startOffset endOffset =+      PackedSourceSpan+        name+        (packPositions startLine startCol)+        (packPositions endLine endCol)+        (packPositions startOffset endOffset)++{-# COMPLETE SourceSpan #-}++packPositions :: Int -> Int -> Word64+packPositions high low =+  (fromIntegral high `shiftL` 32) .|. (fromIntegral low .&. 0xffffffff)+{-# INLINE packPositions #-}++unpackPositions :: Word64 -> (Int, Int)+unpackPositions word = (highPosition word, lowPosition word)+{-# INLINE unpackPositions #-}++highPosition :: Word64 -> Int+highPosition word = fromIntegral (word `shiftR` 32)+{-# INLINE highPosition #-}++lowPosition :: Word64 -> Int+lowPosition word = fromIntegral (word .&. 0xffffffff)+{-# INLINE lowPosition #-}++-- | Every field is strict and none of them holds a thunk once the span is in+-- weak head normal form, so forcing the span is all there is to do.+instance NFData SourceSpan where+  rnf span' = span' `seq` ()+ instance Show SourceSpan where-  show NoSourceSpan = "NoSourceSpan"   show SourceSpan {sourceSpanStartLine, sourceSpanStartCol, sourceSpanEndLine, sourceSpanEndCol} =     "SourceSpan "       ++ show sourceSpanStartLine@@ -700,18 +822,17 @@       ++ " "       ++ show sourceSpanEndCol -noSourceSpan :: SourceSpan-noSourceSpan = NoSourceSpan-+-- | The span from the start of the first span to the end of the second.+-- The source name comes from the first span. mergeSourceSpans :: SourceSpan -> SourceSpan -> SourceSpan-mergeSourceSpans left right =-  case (left, right) of-    ( SourceSpan name l1 c1 _ _ startOffset _,-      SourceSpan _ _ _ l2 c2 _ endOffset-      ) ->-        SourceSpan name l1 c1 l2 c2 startOffset endOffset-    (NoSourceSpan, span') -> span'-    (span', NoSourceSpan) -> span'+mergeSourceSpans+  (PackedSourceSpan name start _ startOffsets)+  (PackedSourceSpan _ _ end endOffsets) =+    PackedSourceSpan+      name+      start+      end+      (packPositions (highPosition startOffsets) (lowPosition endOffsets))  -- | A qualified or unqualified name with type information. --@@ -1987,7 +2108,7 @@  instance Data Annotation where   gfoldl _ z = z-  gunfold _ z _ = z (SourceSpanAnnotation NoSourceSpan)+  gunfold _ z _ = z (DynamicAnnotation (toDyn ()))   toConstr _ = annotationConstr   dataTypeOf _ = annotationDataType 
src/Aihc/Parser/Types.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE TypeFamilies #-}  module Aihc.Parser.Types@@ -17,6 +18,8 @@     mkFoundToken,     ParseErrorBundle,     ParseResult (..),+    rebuildStream,+    sourcePosSpan,     ParserConfig (..),   ) where@@ -33,7 +36,7 @@     readModuleHeaderExtensions,     scanAllTokens,   )-import Aihc.Parser.Syntax (Extension, ExtensionSet, SourceSpan, applyExtensionSetting, applyImpliedExtensions, mkExtensionSet)+import Aihc.Parser.Syntax (Extension, ExtensionSet, SourceSpan, applyExtensionSetting, applyImpliedExtensions, mkExtensionSet, sourceSpanEndCol, sourceSpanEndLine, sourceSpanEndOffset, sourceSpanSourceName, sourceSpanStartCol, sourceSpanStartLine, sourceSpanStartOffset, pattern SourceSpan) import Control.DeepSeq (NFData (..)) import Data.Text (Text) import Data.Text qualified as T@@ -252,8 +255,8 @@           case rawTokens' of             [] -> finish [] layoutState' [] pendingPragmas'             rawTok : rawRest ->-              let (allToks, laySt') = layoutTransition layoutState' rawTok-               in go rawRest laySt' allToks pendingPragmas'+              case layoutTransition layoutState' rawTok of+                (allToks, laySt') -> go rawRest laySt' allToks pendingPragmas'  -- | Step one token from the stream. This is the core primitive used by all -- Stream methods; its result is memoized in 'tokStreamNext'.@@ -300,6 +303,31 @@            in Just (tok, next)         [] ->           Nothing++-- | Build a stream a second time, for locating errors after a parse.+--+-- Error rendering walks a stream from offset 0. Reusing the stream that was+-- parsed would keep its memoized successor chain alive for the whole parse,+-- which 'runTokStreamParser' is careful to avoid. This function is not+-- inlined so that GHC cannot share the rebuilt stream with the parsed one by+-- common-subexpression elimination.+rebuildStream :: (a -> TokStream) -> a -> TokStream+rebuildStream build = build+{-# NOINLINE rebuildStream #-}++-- | The zero-width span at a Megaparsec source position, such as the initial+-- position of a parse. Used when a stream has no token to locate something at.+sourcePosSpan :: MP.SourcePos -> SourceSpan+sourcePosSpan pos =+  SourceSpan+    { sourceSpanSourceName = T.pack (MP.sourceName pos),+      sourceSpanStartLine = MP.unPos (MP.sourceLine pos),+      sourceSpanStartCol = MP.unPos (MP.sourceColumn pos),+      sourceSpanEndLine = MP.unPos (MP.sourceLine pos),+      sourceSpanEndCol = MP.unPos (MP.sourceColumn pos),+      sourceSpanStartOffset = 0,+      sourceSpanEndOffset = 0+    }  -- | Run a token parser from the start of a stream. --
test/Spec.hs view
@@ -240,6 +240,7 @@             testCase "lexes string gaps before a closing quote" test_stringGapBeforeClosingQuoteLexes,             testCase "pretty-prints overloaded labels with delimiter spacing" test_overloadedLabelPrettyPrintsWithDelimiterSpacing,             testCase "applies LINE pragmas to subsequent tokens" test_linePragmaUpdatesSpan,+            testCase "applies a LINE pragma file name to subsequent tokens" test_linePragmaUpdatesSourceName,             testCase "applies COLUMN pragmas to subsequent tokens" test_columnPragmaUpdatesSpan,             testCase "applies COLUMN pragmas in the middle of a line" test_inlineColumnPragmaUpdatesSpan,             testCase "sets lexTokenAtLineStart correctly" test_tokenAtLineStartWithoutDirective,@@ -261,6 +262,7 @@             testCase "shrunk standalone kind signatures shrink binder kinds" test_shrunkStandaloneKindSignaturesShrinkBinderKinds,             testCase "shrunk module headers without warnings make progress" test_shrunkModuleHeaderWithoutWarningMakesProgress,             testCase "syntax utility functions cover public edge cases" test_syntaxUtilityFunctions,+            testCase "parse errors without a token are located at the parser offset" test_parseErrorOffsetSpan,             testCase "shrunk class default pattern binds make progress" test_shrunkClassDefaultPatternBindMakesProgress,             testCase "parsed binders carry source spans" test_parsedBindersCarrySourceSpans,             testCase "every top-level declaration carries a source span" test_everyTopLevelDeclarationCarriesSourceSpan,@@ -532,6 +534,13 @@       assertSourceSpan "<input>" 17 1 17 2 16 17 span'     other -> assertFailure ("expected identifier at line 17, got: " <> show other) +test_linePragmaUpdatesSourceName :: Assertion+test_linePragmaUpdatesSourceName =+  case lexTokens "{-# LINE 14 \"Demo.hsc\" #-}\nx" of+    [LexToken {lexTokenKind = TkVarId "x", lexTokenSpan = span'}, LexToken {lexTokenKind = TkEOF}] ->+      assertSourceSpan "Demo.hsc" 14 1 14 2 27 28 span'+    other -> assertFailure ("expected identifier at Demo.hsc line 14, got: " <> show other)+ test_columnPragmaUpdatesSpan :: Assertion test_columnPragmaUpdatesSpan =   case lexTokens "x\n{-# COLUMN 7 #-}y" of@@ -733,20 +742,17 @@       ] -> pure ()     other -> assertFailure ("expected indented '# line' to lex as operator + identifier, got: " <> show other) -assertSourceSpan :: FilePath -> Int -> Int -> Int -> Int -> Int -> Int -> SourceSpan -> Assertion-assertSourceSpan expectedName expectedStartLine expectedStartCol expectedEndLine expectedEndCol expectedStartOffset expectedEndOffset span' =-  case span' of-    SourceSpan {sourceSpanSourceName, sourceSpanStartLine, sourceSpanStartCol, sourceSpanEndLine, sourceSpanEndCol, sourceSpanStartOffset, sourceSpanEndOffset} -> do-      assertEqual "source name" expectedName sourceSpanSourceName-      assertEqual "start line" expectedStartLine sourceSpanStartLine-      assertEqual "start col" expectedStartCol sourceSpanStartCol-      assertEqual "end line" expectedEndLine sourceSpanEndLine-      assertEqual "end col" expectedEndCol sourceSpanEndCol-      assertEqual "start offset" expectedStartOffset sourceSpanStartOffset-      assertEqual "end offset" expectedEndOffset sourceSpanEndOffset-    NoSourceSpan -> assertFailure "expected SourceSpan, got NoSourceSpan"+assertSourceSpan :: Text -> Int -> Int -> Int -> Int -> Int -> Int -> SourceSpan -> Assertion+assertSourceSpan expectedName expectedStartLine expectedStartCol expectedEndLine expectedEndCol expectedStartOffset expectedEndOffset SourceSpan {sourceSpanSourceName, sourceSpanStartLine, sourceSpanStartCol, sourceSpanEndLine, sourceSpanEndCol, sourceSpanStartOffset, sourceSpanEndOffset} = do+  assertEqual "source name" expectedName sourceSpanSourceName+  assertEqual "start line" expectedStartLine sourceSpanStartLine+  assertEqual "start col" expectedStartCol sourceSpanStartCol+  assertEqual "end line" expectedEndLine sourceSpanEndLine+  assertEqual "end col" expectedEndCol sourceSpanEndCol+  assertEqual "start offset" expectedStartOffset sourceSpanStartOffset+  assertEqual "end offset" expectedEndOffset sourceSpanEndOffset -assertUnqualifiedNameSpan :: String -> FilePath -> Int -> Int -> Int -> Int -> Int -> Int -> UnqualifiedName -> Assertion+assertUnqualifiedNameSpan :: String -> Text -> Int -> Int -> Int -> Int -> Int -> Int -> UnqualifiedName -> Assertion assertUnqualifiedNameSpan label expectedName expectedStartLine expectedStartCol expectedEndLine expectedEndCol expectedStartOffset expectedEndOffset name =   case mapMaybe (fromAnnotation :: Annotation -> Maybe SourceSpan) (unqualifiedNameAnns name) of     span' : _ -> assertSourceSpan expectedName expectedStartLine expectedStartCol expectedEndLine expectedEndCol expectedStartOffset expectedEndOffset span'@@ -757,6 +763,16 @@   mapM_ $ \value ->     assertEqual (label <> ": " <> show value) (Just value) (readMaybe (show value)) +-- A `fail` inside the parser raises an error that names no token. Its span+-- is recovered from the error offset: the token the parser stood on.+test_parseErrorOffsetSpan :: Assertion+test_parseErrorOffsetSpan =+  case parseModule defaultConfig "{-# LANGUAGE TransformListComp #-}\nx = [y | y <- ys, then group z]" of+    ([(span', message)], _) -> do+      assertEqual "message" "expected 'by' or 'using' after 'group'" message+      assertSourceSpan "<input>" 2 30 2 31 64 65 span'+    (errs, _) -> assertFailure ("expected exactly one parse error, got: " <> show errs)+ test_syntaxUtilityFunctions :: Assertion test_syntaxUtilityFunctions = do   assertEqual "known extensions" ([minBound .. maxBound] :: [Extension]) allKnownExtensions@@ -794,11 +810,8 @@   let spanA = SourceSpan "A.hs" 1 2 1 4 0 2       spanB = SourceSpan "A.hs" 2 1 2 5 3 7       merged = SourceSpan "A.hs" 1 2 2 5 0 7-  assertEqual "show no source span" "NoSourceSpan" (show noSourceSpan)   assertEqual "show source span" "SourceSpan 1 2 2 5" (show merged)   assertEqual "merge source spans" merged (mergeSourceSpans spanA spanB)-  assertEqual "merge left missing source span" spanB (mergeSourceSpans NoSourceSpan spanB)-  assertEqual "merge right missing source span" spanA (mergeSourceSpans spanA NoSourceSpan)   assertBool "source span ordering" (spanA < spanB)   assertBool "source span nfdata" (rnf merged `seq` True) 
test/Test/Properties/NoExceptions.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-}  module Test.Properties.NoExceptions@@ -31,7 +32,7 @@     parsePatternFromTokens,     parseTypeFromTokens,   )-import Aihc.Parser.Syntax (ExtensionSetting (..), FloatType (..), NumericType (..), SourceSpan (..))+import Aihc.Parser.Syntax (ExtensionSetting (..), FloatType (..), NumericType (..), SourceSpan, pattern SourceSpan) import Aihc.Parser.Syntax qualified as Syntax import Control.DeepSeq (NFData (..), force) import Control.Exception (SomeException, evaluate, try)@@ -326,36 +327,30 @@     ]  genSourceSpan :: Gen SourceSpan-genSourceSpan =-  oneof-    [ pure NoSourceSpan,-      do-        sourceName <- elements ["<input>", "source", "generated.h"]-        startLine <- chooseInt (1, 200)-        startCol <- chooseInt (1, 200)-        endLine <- chooseInt (startLine, startLine + 5)-        endCol <--          if endLine == startLine-            then chooseInt (startCol, startCol + 10)-            else chooseInt (1, 200)-        startOffset <- chooseInt (0, 4000)-        endOffset <- chooseInt (startOffset, startOffset + 200)-        pure (SourceSpan sourceName startLine startCol endLine endCol startOffset endOffset)-    ]+genSourceSpan = do+  sourceName <- elements ["<input>", "source", "generated.h"]+  startLine <- chooseInt (1, 200)+  startCol <- chooseInt (1, 200)+  endLine <- chooseInt (startLine, startLine + 5)+  endCol <-+    if endLine == startLine+      then chooseInt (startCol, startCol + 10)+      else chooseInt (1, 200)+  startOffset <- chooseInt (0, 4000)+  endOffset <- chooseInt (startOffset, startOffset + 200)+  pure (SourceSpan sourceName startLine startCol endLine endCol startOffset endOffset)  shrinkSourceSpan :: SourceSpan -> [SourceSpan] shrinkSourceSpan span' =   case span' of-    NoSourceSpan -> []     SourceSpan sourceName sl sc el ec startOffset endOffset ->-      [NoSourceSpan]-        <> [ SourceSpan sourceName sl' sc' el' ec' startOffset' endOffset'-           | (sl', sc', el', ec', startOffset', endOffset') <- shrink (sl, sc, el, ec, startOffset, endOffset),-             sl' >= 1,-             sc' >= 1,-             el' >= sl',-             ec' >= 1,-             el' > sl' || ec' >= sc',-             startOffset' >= 0,-             endOffset' >= startOffset'-           ]+      [ SourceSpan sourceName sl' sc' el' ec' startOffset' endOffset'+      | (sl', sc', el', ec', startOffset', endOffset') <- shrink (sl, sc, el, ec, startOffset, endOffset),+        sl' >= 1,+        sc' >= 1,+        el' >= sl',+        ec' >= 1,+        el' > sl' || ec' >= sc',+        startOffset' >= 0,+        endOffset' >= startOffset'+      ]