packages feed

literatex-0.4.0.0: src/LiterateX/Parser.hs

------------------------------------------------------------------------------
-- |
-- Module      : LiterateX.Parser
-- Description : source parser
-- Copyright   : Copyright (c) 2021-2025 Travis Cardwell
-- License     : MIT
--
-- This module implements the source parser.
------------------------------------------------------------------------------

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module LiterateX.Parser
  ( -- * API
    parse
  ) where

-- https://hackage.haskell.org/package/conduit
import qualified Data.Conduit as C
import Data.Conduit (ConduitT)

-- https://hackage.haskell.org/package/text
import qualified Data.Text as T
import Data.Text (Text)

-- (literatex)
import LiterateX.Types (SourceFormat, SourceLine)
import qualified LiterateX.Types.SourceFormat as SourceFormat
import qualified LiterateX.Types.SourceLine as SourceLine

------------------------------------------------------------------------------
-- $API

-- | Create a "Conduit" transformer that parses the specified source format
--
-- The transformer consumes lines of the input and produces a 'SourceLine' for
-- each line of input.
--
-- @since 0.0.1.0
parse
  :: Monad m
  => SourceFormat
  -> ConduitT Text SourceLine m ()
parse sourceFormat = do
    let parseLine' = parseLine sourceFormat
    mLine <- C.await
    case mLine of
      Just line -> do
        C.yield $ if "#!" `T.isPrefixOf` line
          then SourceLine.Shebang line
          else parseLine' line
        C.awaitForever $ C.yield . parseLine'
      Nothing -> return ()

------------------------------------------------------------------------------
-- $Internal

-- | Parse a source line according to the source format
parseLine :: SourceFormat -> Text -> SourceLine
parseLine = \case
    SourceFormat.DoubleDash      -> parseLineCommentLine '-' 2
    SourceFormat.DoubleSlash     -> parseLineCommentLine '/' 2
    SourceFormat.Hash            -> parseLineCommentLine '#' 1
    SourceFormat.LiterateHaskell -> parseLiterateHaskellLine
    SourceFormat.Percent         -> parseLineCommentLine '%' 1
    SourceFormat.LispSemicolons  -> parseLispCommentLine

------------------------------------------------------------------------------

-- | Parse a source line using line-based comments
parseLineCommentLine
  :: Char  -- ^ comment character
  -> Int   -- ^ number of comment characters to create line comment
  -> Text  -- ^ source line
  -> SourceLine
parseLineCommentLine char count line
    | T.null line = SourceLine.CodeBlank
    | otherwise = case T.uncons <$> T.span (== char) line of
        ("", _) -> SourceLine.Code line
        (_, Nothing) -> case T.compareLength line count of
          EQ -> SourceLine.DocBlank
          GT -> SourceLine.Rule
          LT -> SourceLine.Code line
        (l, Just (' ', r)) | T.compareLength l count == EQ -> SourceLine.Doc r
        _otherwise -> SourceLine.Code line

------------------------------------------------------------------------------

-- | Parse a source line using Lisp-style comments
--
-- Lisp-style comments begin with one or more semicolons.
parseLispCommentLine
  :: Text  -- ^ source line
  -> SourceLine
parseLispCommentLine line
    | T.null line = SourceLine.CodeBlank
    | otherwise = case T.uncons <$> T.span (== ';') line of
        ("", _) -> SourceLine.Code line
        (_, Nothing)
          | T.compareLength line 4 == GT -> SourceLine.Rule
          | otherwise -> SourceLine.DocBlank
        (_, Just (' ', r)) -> SourceLine.Doc r
        _otherwise -> SourceLine.Code line

------------------------------------------------------------------------------

-- | Parse a Literate Haskell source line
parseLiterateHaskellLine
  :: Text  -- ^ source line
  -> SourceLine
parseLiterateHaskellLine line = case T.uncons line of
    Nothing -> SourceLine.DocBlank
    Just ('>', r1) -> case T.uncons r1 of
      Nothing -> SourceLine.CodeBlank
      Just (' ', r2) -> SourceLine.Code r2
      _otherwise -> SourceLine.Doc line
    _otherwise -> SourceLine.Doc line