packages feed

exon-1.7.3.0: lib/Exon/Parse.hs

{-# options_haddock prune #-}

-- | Description: The parser for the quasiquote body, using parsec.
module Exon.Parse where

import Data.Char (isSpace)
import Prelude hiding ((<|>))
import Text.Parsec as Parsec (
  Parsec,
  anyChar,
  char,
  choice,
  eof,
  getState,
  lookAhead,
  modifyState,
  putState,
  runParser,
  satisfy,
  string,
  try,
  (<|>),
  )

import Exon.Data.RawSegment (RawSegment (AutoExpSegment, ExpSegment, StringSegment, WsSegment))

type Parser = Parsec String Int

ws :: Parser Char
ws =
  satisfy isSpace

whitespace :: Parser RawSegment
whitespace =
  WsSegment <$> some ws

expr :: Parser String
expr =
  choice [try opening, try closing, anyChars]
  where
    opening = do
      char '{'
      modifyState (1 +)
      e <- expr
      pure ('{' : e)

    closing = do
      void $ lookAhead (char '}')
      getState >>= \case
        0 -> pure ""
        cur -> do
          putState (cur - 1)
          char '}'
          e <- expr
          pure ('}' : e)

    anyChars = do
      c <- anyChar
      e <- expr
      pure (c : e)

autoInterpolation :: Parser RawSegment
autoInterpolation =
  string "##{" *> (AutoExpSegment <$> expr) <* char '}'

verbatimInterpolation :: Parser RawSegment
verbatimInterpolation =
  string "#{" *> (ExpSegment <$> expr) <* char '}'

interpolations :: Parser RawSegment
interpolations =
  try autoInterpolation <|> try verbatimInterpolation

verbatimStep :: Bool -> Parser Bool
verbatimStep =
  lookAhead . \case
    True -> False <$ ws <|> basic
    False -> basic
    where
      basic =
        False <$ (try (string "##{") <|> try (string "#{"))
        <|>
        False <$ eof
        <|>
        pure True

verbatimWith :: Parser Bool -> Parser String
verbatimWith step =
  step >>= \case
    False -> fail "Empty verbatim segment"
    True -> go
  where
    go =
      step >>= \case
        False -> pure ""
        True -> do
          h <- anyChar
          t <- go
          pure (h : t)

verbatim :: Parser String
verbatim =
  verbatimWith (verbatimStep False)

verbatimWs :: Parser String
verbatimWs =
  verbatimWith (verbatimStep True)

text :: Parser RawSegment
text =
  StringSegment <$> verbatim

textWs :: Parser RawSegment
textWs =
  StringSegment <$> verbatimWs

segment :: Parser RawSegment
segment =
  interpolations <|> text

segmentWs :: Parser RawSegment
segmentWs =
  try whitespace <|> interpolations <|> textWs

parser :: Parser [RawSegment]
parser =
  many segment <* eof

parserWs :: Parser [RawSegment]
parserWs =
  many segmentWs <* eof

parseWith :: Parser [RawSegment] -> String -> Either Text [RawSegment]
parseWith p =
  first (unwords . lines . show) . runParser p 0 ""

parse :: String -> Either Text [RawSegment]
parse =
  parseWith parser

parseWs :: String -> Either Text [RawSegment]
parseWs =
  parseWith parserWs