org-parser-0.1.0.0: src/Org/Parser/Common.hs
module Org.Parser.Common where
import Data.Char (digitToInt, isAsciiLower, isAsciiUpper)
import Data.Text qualified as T
import Org.Parser.Definitions
import Prelude hiding (State, many, some)
-- | Read the start of a header line, return the header level
headingStart :: OrgParser Int
headingStart =
try $
(T.length <$> takeWhile1P (Just "heading bullets") (== '*'))
<* char ' '
<* skipSpaces
parseTime :: OrgParser Time
parseTime = do
hour <- (number 2 <|> number 1) <* char ':'
minute <- number 2
pure (hour, minute)
-- | The same as 'string'', but cheaper (?)
string'' :: MonadParser m => Text -> m Text
string'' = tokens ((==) `on` T.toLower)
{-# INLINE string'' #-}
digitIntChar :: MonadParser m => m Int
digitIntChar = digitToInt <$> digitChar
digits :: MonadParser m => m Text
digits = takeWhileP (Just "digits") isDigit
digits1 :: MonadParser m => m Text
digits1 = takeWhile1P (Just "digits") isDigit
integer :: MonadParser m => m Int
integer = try $ do
digits' <- reverse <$> some digitIntChar
let toInt (x : xs) = 10 * toInt xs + x
toInt [] = 0
pure $ toInt digits'
number ::
Int ->
OrgParser Int
number 1 = digitIntChar
number n | n > 1 = try $ do
d <- digitIntChar
(10 ^ (n - 1) * d +) <$> number (n - 1)
number _ = error "Number of digits to parse must be positive!"
-- * ASCII alphabet character classes
isAsciiAlpha :: Char -> Bool
isAsciiAlpha c = isAsciiLower c || isAsciiUpper c
upperAscii' :: MonadParser m => m Int
upperAscii' = do
c <- upperAscii
pure $ ord c - ord 'A' + 1
lowerAscii' :: MonadParser m => m Int
lowerAscii' = do
c <- lowerAscii
pure $ ord c - ord 'a' + 1
asciiAlpha' :: MonadParser m => m Int
asciiAlpha' = lowerAscii' <|> upperAscii'
upperAscii :: MonadParser m => m Char
upperAscii =
satisfy isAsciiUpper
<?> "uppercase A-Z character"
lowerAscii :: MonadParser m => m Char
lowerAscii =
satisfy isAsciiLower
<?> "lowercase a-z character"
asciiAlpha :: MonadParser m => m Char
asciiAlpha =
satisfy isAsciiAlpha
<?> "a-z or A-Z character"
manyAsciiAlpha :: OrgParser Text
manyAsciiAlpha =
takeWhileP
(Just "a-z or A-Z characters")
isAsciiAlpha
someAsciiAlpha :: MonadParser m => m Text
someAsciiAlpha =
takeWhile1P
(Just "a-z or A-Z characters")
isAsciiAlpha
someNonSpace :: OrgParser Text
someNonSpace = takeWhile1P (Just "not whitespace") (not . isSpace)
isSpaceOrTab :: Char -> Bool
isSpaceOrTab c = c == ' ' || c == '\t'
spaceOrTab :: OrgParser Char
spaceOrTab = satisfy isSpaceOrTab <?> "space or tab character"
countSpaces :: Int -> Text -> Int
countSpaces tabWidth = T.foldr go 0
where
go ' ' = (+ 1)
go '\t' = (+ tabWidth)
go _ = id
spacesOrTabs :: OrgParser Int
spacesOrTabs = do
tw <- getsO orgSrcTabWidth
countSpaces tw <$> skipSpaces
spacesOrTabs1 :: OrgParser Int
spacesOrTabs1 = do
tw <- getsO orgSrcTabWidth
countSpaces tw <$> skipSpaces1
-- | Skips one or more spaces or tabs.
skipSpaces1 :: MonadParser m => m Text
skipSpaces1 = takeWhile1P (Just "at least one space or tab whitespace") isSpaceOrTab
-- | Skips zero or more spaces or tabs.
skipSpaces :: MonadParser m => m Text
skipSpaces = takeWhileP (Just "spaces or tabs") isSpaceOrTab
-- | Makes sure a value is Just, else fail with a custom
-- error message.
guardMaybe :: (MonadFail m, MonadParser m) => String -> Maybe a -> m a
guardMaybe _ (Just x) = pure x
guardMaybe err _ = fail err
-- | Parse a newline or EOF. Consumes no input at EOF!
newline' :: MonadParser m => m ()
newline' = void newline <|> eof
-- | Parse the rest of line, returning the contents without the final newline.
anyLine :: MonadParser m => m (Tokens Text)
anyLine =
takeWhileP (Just "rest of line") (/= '\n')
<* newline
{-# INLINE anyLine #-}
-- | Parse the rest of line, returning the contents without the final newline or EOF.
-- Consumes no input at EOF!
anyLine' :: MonadParser m => m (Tokens Text)
anyLine' =
takeWhileP (Just "rest of line") (/= '\n')
<* newline'
-- | Consumes the rest of input
takeInput :: MonadParser m => m Text
takeInput = takeWhileP Nothing (const True)
-- | Parse a line with whitespace contents, and consume a newline at the end.
blankline :: MonadParser m => m ()
blankline = try $ hspace <* newline
-- | Parse a line with whitespace contents, line may end with EOF. CAUTION: this
-- function may consume NO INPUT! Be mindful of infinite loops!
blankline' :: MonadParser m => m ()
blankline' = try $ hspace <* newline'
parseFromText :: FullState -> Text -> OrgParser b -> OrgParser b
parseFromText (prevPS, prevOS) txt parser = do
(cPS, cOS) <- getFullState
setFullState
( prevPS {stateInput = txt},
-- NOTE: using cOS instead of prevOS
-- is an implementation quirk. We
-- don't want neither the changes of
-- state done by the end parser in
-- markupContext nor the ones in the
-- fromText parser to be lost. But
-- this will have the effect of
-- commuting the change of state: the
-- end changes will be registered
-- before the body ones. This is not a
-- problem because most of state
-- building is commutative and most
-- querying is done in Future anyway.
-- The problematic ones are either
-- irrelevant to a paragraph (like the
-- order in which title keywords are
-- concatenated) or must be handled
-- manually like affiliated keywords.
cOS
{ orgStateLastChar = orgStateLastChar prevOS
}
)
result <- parser
(aPS, aOS) <- getFullState
setFullState
( cPS
{ stateParseErrors =
stateParseErrors cPS
++ stateParseErrors aPS
},
aOS
{ orgStateLastChar =
orgStateLastChar cOS
}
)
pure result