packages feed

aihc-parser-1.0.0.2: src/Aihc/Parser/Internal/Cmd.hs

{-# LANGUAGE OverloadedStrings #-}

module Aihc.Parser.Internal.Cmd
  ( cmdParser,
  )
where

import Aihc.Parser.Internal.CheckPattern (checkPattern)
import Aihc.Parser.Internal.Common
import {-# SOURCE #-} Aihc.Parser.Internal.Expr (atomExprParser, caseRhsParserWithBodyParser, cmdArrAppLhsParser, exprParser, parseLetDeclsParser, parseLetDeclsStmtParser)
import Aihc.Parser.Internal.Pattern (apatParser, caseAltPatternParser, patternParser)
import Aihc.Parser.Lex (LexTokenKind (..), lexTokenKind)
import Aihc.Parser.Syntax
import Aihc.Parser.Types (ParserErrorComponent (..), mkFoundToken)
import Text.Megaparsec (anySingle, lookAhead, (<|>))
import Text.Megaparsec qualified as MP

-- | Parse a command (the body of a @proc@ abstraction).
--
-- Grammar (simplified):
--
-- @
-- cmd   = exp10 -\< exp | exp10 -\<\< exp | cmd0
-- cmd0  = cmd10 (op cmd10)*
-- cmd10 = do { cstmts } | if … | case … | let … | \\pats -> cmd | fcmd
-- fcmd  = fcmd aexp | (cmd)
-- @
cmdParser :: TokParser Cmd
cmdParser = do
  tok <- lookAhead anySingle
  case lexTokenKind tok of
    TkKeywordDo -> cmd0Parser
    TkKeywordIf -> cmd0Parser
    TkKeywordCase -> cmd0Parser
    TkKeywordLet -> cmd0Parser
    TkReservedBackslash -> cmd0Parser
    TkSpecialLParen -> MP.try cmd0Parser <|> cmdArrAppParser
    _ -> MP.try cmdArrAppParser <|> cmd0Parser

cmd0Parser :: TokParser Cmd
cmd0Parser = do
  lhs <- cmd10Parser
  rest <-
    MP.many
      ( (,)
          <$> infixOperatorParser
          <*> cmd10Parser
      )
  pure (foldl buildCmdInfix lhs rest)
  where
    buildCmdInfix l (op, r) = CmdInfix l op r

cmd10Parser :: TokParser Cmd
cmd10Parser = do
  tok <- lookAhead anySingle
  case lexTokenKind tok of
    TkKeywordDo -> cmdDoParser
    TkKeywordIf -> cmdIfParser
    TkKeywordCase -> cmdCaseParser
    TkKeywordLet ->
      -- 'let decls in cmd' is a command; 'let decls' (without 'in') inside
      -- a do-block is a statement, not handled here.
      cmdLetParser
    TkReservedBackslash -> cmdLamParser
    _ -> cmdFcmdParser

cmdArrAppParser :: TokParser Cmd
cmdArrAppParser = do
  expr <- cmdArrAppLhsParser
  (appType, rhs) <- cmdArrTailParser
  pure (CmdArrApp expr appType rhs)

cmdFcmdParser :: TokParser Cmd
cmdFcmdParser = do
  headCmd <- cmdParenParser
  args <- MP.many atomExprParser
  pure (foldl CmdApp headCmd args)

-- | Parse an arrow tail operator in command context, returning the
-- application type and the right-hand expression.
cmdArrTailParser :: TokParser (ArrAppType, Expr)
cmdArrTailParser = do
  appType <- tokenSatisfy "arrow operator" $ \tok ->
    case lexTokenKind tok of
      TkArrowTail -> Just HsFirstOrderApp
      TkDoubleArrowTail -> Just HsHigherOrderApp
      _ -> Nothing
  rhs <- exprParser
  pure (appType, rhs)

-- | Parse a command do-block: @do { cstmt ; ... }@
cmdDoParser :: TokParser Cmd
cmdDoParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
  expectedTok TkKeywordDo
  CmdDo <$> bracedSemiSep1 cmdStmtParser

-- | Parse a command if-then-else: @if exp then cmd else cmd@
cmdIfParser :: TokParser Cmd
cmdIfParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
  expectedTok TkKeywordIf
  cond <- exprParser
  skipSemicolons
  expectedTok TkKeywordThen
  yes <- cmdParser
  skipSemicolons
  expectedTok TkKeywordElse
  CmdIf cond yes <$> cmdParser

-- | Parse a command case: @case exp of { calts }@
cmdCaseParser :: TokParser Cmd
cmdCaseParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
  expectedTok TkKeywordCase
  scrut <- region "while parsing case scrutinee" exprParser
  expectedTok TkKeywordOf
  alts <- bracedSemiSep1 cmdCaseAltParser
  pure (CmdCase scrut alts)

cmdCaseAltParser :: TokParser (CaseAlt Cmd)
cmdCaseAltParser = withSpan $ do
  pat <- caseAltPatternParser
  rhs <- caseRhsParserWithBodyParser cmdParser
  pure (\span' -> CaseAlt [mkAnnotation span'] pat rhs)

-- | Parse a command let: @let decls in cmd@
cmdLetParser :: TokParser Cmd
cmdLetParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
  decls <- parseLetDeclsParser
  expectedTok TkKeywordIn
  CmdLet decls <$> cmdParser

-- | Parse a command lambda: @\\pats -> cmd@
cmdLamParser :: TokParser Cmd
cmdLamParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
  expectedTok TkReservedBackslash
  pats <- MP.some apatParser
  expectedTok TkReservedRightArrow
  CmdLam pats <$> cmdParser

-- | Parse a parenthesised command: @( cmd )@
cmdParenParser :: TokParser Cmd
cmdParenParser =
  withSpanAnn (CmdAnn . mkAnnotation) $
    CmdPar <$> parens cmdParser

-- | Parse a do-statement in command context (arrow do).
cmdStmtParser :: TokParser (DoStmt Cmd)
cmdStmtParser = do
  tok <- lookAhead anySingle
  case lexTokenKind tok of
    TkKeywordLet -> MP.try cmdLetStmtParser <|> cmdBodyStmtParser
    TkKeywordRec -> cmdRecStmtParser
    -- Keyword commands: parse as command body statements.
    TkKeywordDo -> cmdBodyStmtParser
    TkKeywordIf -> cmdBodyStmtParser
    TkKeywordCase -> cmdBodyStmtParser
    TkReservedBackslash -> cmdBodyStmtParser
    TkSpecialLParen -> MP.try cmdBindOrBodyStmtParser <|> MP.try cmdBindStmtParser <|> cmdBodyStmtParser
    _ -> do
      isPatternBind <- startsWithPatternBind
      if isPatternBind
        then cmdBindStmtParser
        else cmdBindOrBodyStmtParser

startsWithPatternBind :: TokParser Bool
startsWithPatternBind =
  fmap (either (const False) (const True)) . MP.observing . MP.try . MP.lookAhead $ do
    _ <- patternParser
    expectedTok TkReservedLeftArrow

-- | Parse a command do-statement: @cmd@ or @pat <- cmd@.
-- Uses the expression-first approach: parse as expression, check for @<-@.
cmdBindOrBodyStmtParser :: TokParser (DoStmt Cmd)
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
      -- 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)
          MP.customFailure
            UnexpectedTokenExpecting
              { unexpectedFound = mkFoundToken <$> mTok,
                unexpectedExpecting = "arrow command (-< or -<<) in do statement",
                unexpectedContext = []
              }

-- | Parse a command bind statement where the pattern is unambiguously a
-- pattern (starts with !, ~, or x@).
cmdBindStmtParser :: TokParser (DoStmt Cmd)
cmdBindStmtParser = withSpanAnn (DoAnn . mkAnnotation) $ do
  pat <- patternParser
  expectedTok TkReservedLeftArrow
  cmd <- region "while parsing '<-' binding" cmdParser
  pure (DoBind pat cmd)

-- | Parse a body-only command statement (fallback from cmdStmtParser).
cmdBodyStmtParser :: TokParser (DoStmt Cmd)
cmdBodyStmtParser =
  withSpanAnn (DoAnn . mkAnnotation) $
    DoExpr <$> cmdParser

-- | Parse a command let-statement: @let decls@
cmdLetStmtParser :: TokParser (DoStmt Cmd)
cmdLetStmtParser =
  withSpanAnn (DoAnn . mkAnnotation) $
    DoLetDecls <$> parseLetDeclsStmtParser

-- | Parse a command rec-statement: @rec { cstmts }@
cmdRecStmtParser :: TokParser (DoStmt Cmd)
cmdRecStmtParser = withSpanAnn (DoAnn . mkAnnotation) $ do
  expectedTok TkKeywordRec
  DoRecStmt <$> bracedSemiSep1 cmdStmtParser