content: {-# LANGUAGE FlexibleInstances          #-}
  {-# LANGUAGE MultiParamTypeClasses      #-}
  {-# LANGUAGE OverloadedStrings          #-}
  {- |
     Module      : Text.Pandoc.Parsing
     Copyright   : Copyright (C) 2006-2024 John MacFarlane
     License     : GPL-2.0-or-later
     Maintainer  : John MacFarlane <jgm@berkeley.edu>
  
  A default parser state with commonly used properties.
  -}
  
  module Text.Pandoc.Parsing.State
    ( ParserState (..)
    , ParserContext (..)
    , HeaderType (..)
    , NoteTable
    , NoteTable'
    , Key (..)
    , KeyTable
    , SubstTable
    , defaultParserState
    , toKey
    )
  where
  
  import Data.Default (Default (def))
  import Data.Text (Text)
  import Text.Parsec (SourcePos, getState, setState)
  import Text.Pandoc.Builder (Blocks, HasMeta (..), Inlines)
  import Text.Pandoc.Definition (Attr, Meta, Target, nullMeta)
  import Text.Pandoc.Logging (LogMessage)
  import Text.Pandoc.Options (ReaderOptions)
  import Text.Pandoc.Parsing.Capabilities
  import Text.Pandoc.Parsing.Future
  import Text.Pandoc.TeX (Macro)
  
  import qualified Data.Map as M
  import qualified Data.Set as Set
  import qualified Data.Text as T
  
  -- | Parsing options.
  data ParserState = ParserState
    { stateOptions         :: ReaderOptions -- ^ User options
    , stateParserContext   :: ParserContext -- ^ Inside list?
    , stateQuoteContext    :: QuoteContext  -- ^ Inside quoted environment?
    , stateAllowLinks      :: Bool          -- ^ Allow parsing of links
    , stateAllowLineBreaks :: Bool          -- ^ Allow parsing of line breaks
    , stateLastStrPos      :: Maybe SourcePos -- ^ Position after last str parsed
    , stateKeys            :: KeyTable      -- ^ List of reference keys
    , stateHeaderKeys      :: KeyTable      -- ^ List of implicit header ref keys
    , stateSubstitutions   :: SubstTable    -- ^ List of substitution references
    , stateNotes           :: NoteTable     -- ^ List of notes (raw bodies)
    , stateNotes'          :: NoteTable'    -- ^ List of notes (parsed bodies)
    , stateNoteRefs        :: Set.Set Text  -- ^ List of note references used
    , stateInNote          :: Bool          -- ^ True if parsing note contents
    , stateNoteNumber      :: Int           -- ^ Last note number for citations
    , stateMeta            :: Meta          -- ^ Document metadata
    , stateMeta'           :: Future ParserState Meta -- ^ Document metadata
    , stateCitations       :: M.Map Text Text -- ^ RST-style citations
    , stateHeaderTable     :: [HeaderType]  -- ^ Ordered list of header types used
    , stateIdentifiers     :: Set.Set Text  -- ^ Header identifiers used
    , stateNextExample     :: Int           -- ^ Number of next example
    , stateExamples        :: M.Map Text Int -- ^ Map from example labels to numbers
    , stateMacros          :: M.Map Text Macro -- ^ Table of macros defined so far
    , stateRstDefaultRole  :: Text          -- ^ Current rST default
                                             -- interpreted text role
    , stateRstHighlight    :: Maybe Text    -- ^ Current rST literal block
                                             -- language
    , stateRstCustomRoles  :: M.Map Text (Text, Maybe Text, Attr)
      -- ^ Current rST cust text roles;
      -- Triple represents:) Base role 2) Optional format (only for :raw:
      -- roles) 3) Addition classes (rest of Attr is unused)).
    , stateCaption         :: Maybe Inlines -- ^ Caption in current environment
    , stateInHtmlBlock     :: Maybe Text    -- ^ Tag type of HTML block being parsed
    , stateFencedDivLevel  :: Int           -- ^ Depth of fenced div
    , stateContainers      :: [Text]        -- ^ parent include files
    , stateLogMessages     :: [LogMessage]  -- ^ log messages
    , stateMarkdownAttribute :: Bool        -- ^ True if in markdown=1 context
    }
  
  instance Default ParserState where
    def = defaultParserState
  
  instance HasMeta ParserState where
    setMeta field val st =
      st{ stateMeta = setMeta field val $ stateMeta st }
    deleteMeta field st =
      st{ stateMeta = deleteMeta field $ stateMeta st }
  
  instance HasReaderOptions ParserState where
    extractReaderOptions = stateOptions
  
  instance Monad m => HasQuoteContext ParserState m where
    getQuoteContext = stateQuoteContext <$> getState
    withQuoteContext context parser = do
      oldState <- getState
      let oldQuoteContext = stateQuoteContext oldState
      setState oldState { stateQuoteContext = context }
      result <- parser
      newState <- getState
      setState newState { stateQuoteContext = oldQuoteContext }
      return result
  
  instance HasIdentifierList ParserState where
    extractIdentifierList     = stateIdentifiers
    updateIdentifierList f st = st{ stateIdentifiers = f $ stateIdentifiers st }
  
  instance HasMacros ParserState where
    extractMacros        = stateMacros
    updateMacros f st    = st{ stateMacros = f $ stateMacros st }
  
  instance HasLastStrPosition ParserState where
    setLastStrPos pos st = st{ stateLastStrPos = pos }
    getLastStrPos st     = stateLastStrPos st
  
  instance HasLogMessages ParserState where
    addLogMessage msg st = st{ stateLogMessages = msg : stateLogMessages st }
    getLogMessages st = reverse $ stateLogMessages st
  
  instance HasIncludeFiles ParserState where
    getIncludeFiles = stateContainers
    addIncludeFile f s = s{ stateContainers = f : stateContainers s }
    dropLatestIncludeFile s = s { stateContainers = drop 1 $ stateContainers s }
  
  data ParserContext
      = ListItemState   -- ^ Used when running parser on list item contents
      | NullState       -- ^ Default state
      deriving (Eq, Show)
  
  data HeaderType
      = SingleHeader Char  -- ^ Single line of characters underneath
      | DoubleHeader Char  -- ^ Lines of characters above and below
      deriving (Eq, Show)
  
  defaultParserState :: ParserState
  defaultParserState = ParserState
    { stateOptions         = def
    , stateParserContext   = NullState
    , stateQuoteContext    = NoQuote
    , stateAllowLinks      = True
    , stateAllowLineBreaks = True
    , stateLastStrPos      = Nothing
    , stateKeys            = M.empty
    , stateHeaderKeys      = M.empty
    , stateSubstitutions   = M.empty
    , stateNotes           = []
    , stateNotes'          = M.empty
    , stateNoteRefs        = Set.empty
    , stateInNote          = False
    , stateNoteNumber      = 0
    , stateMeta            = nullMeta
    , stateMeta'           = return nullMeta
    , stateCitations       = M.empty
    , stateHeaderTable     = []
    , stateIdentifiers     = Set.empty
    , stateNextExample     = 1
    , stateExamples        = M.empty
    , stateMacros          = M.empty
    , stateRstDefaultRole  = "title-reference"
    , stateRstHighlight    = Nothing
    , stateRstCustomRoles  = M.empty
    , stateCaption         = Nothing
    , stateInHtmlBlock     = Nothing
    , stateFencedDivLevel  = 0
    , stateContainers      = []
    , stateLogMessages     = []
    , stateMarkdownAttribute = False
    }
  
  type NoteTable = [(Text, Text)]
  
  type NoteTable' = M.Map Text (SourcePos, Future ParserState Blocks)
  -- used in markdown reader
  
  newtype Key = Key Text deriving (Show, Read, Eq, Ord)
  
  toKey :: Text -> Key
  toKey = Key . T.toLower . T.unwords . T.words . unbracket
    where unbracket t
            | Just ('[', t') <- T.uncons t
            , Just (t'', ']') <- T.unsnoc t'
            = t''
            | otherwise
            = t
  
  type KeyTable = M.Map Key (Target, Attr)
  
  type SubstTable = M.Map Key Blocks
  