{-# LANGUAGE OverloadedStrings #-}

module Text.TeXMath.TeX (TeX(..),
                         renderTeX,
                         isControlSeq,
                         escapeLaTeX)
where
import Data.Char (isLetter, isAlphaNum, isAscii)
import qualified Data.Text as T

-- | An intermediate representation of TeX math, to be used in rendering.
data TeX = ControlSeq T.Text
         | Token Char
         | Literal T.Text
         | Grouped [TeX]
         | Space
         deriving (Int -> TeX -> ShowS
[TeX] -> ShowS
TeX -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TeX] -> ShowS
$cshowList :: [TeX] -> ShowS
show :: TeX -> String
$cshow :: TeX -> String
showsPrec :: Int -> TeX -> ShowS
$cshowsPrec :: Int -> TeX -> ShowS
Show, TeX -> TeX -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TeX -> TeX -> Bool
$c/= :: TeX -> TeX -> Bool
== :: TeX -> TeX -> Bool
$c== :: TeX -> TeX -> Bool
Eq)

-- | Render a 'TeX' to a string, appending to the front of the given string.
renderTeX :: TeX -> T.Text -> T.Text
renderTeX :: TeX -> Text -> Text
renderTeX (Token Char
c) Text
cs     = Char -> Text -> Text
T.cons Char
c Text
cs
renderTeX (Literal Text
s) Text
cs
  | (Char -> Bool) -> Text -> Bool
endsWith (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isLetter) Text
s = Text
s forall a. Semigroup a => a -> a -> a
<> Text
cs
  | (Char -> Bool) -> Text -> Bool
startsWith Char -> Bool
isLetter Text
cs      = Text
s forall a. Semigroup a => a -> a -> a
<> Char -> Text -> Text
T.cons Char
' ' Text
cs
  | Bool
otherwise                   = Text
s forall a. Semigroup a => a -> a -> a
<> Text
cs
renderTeX (ControlSeq Text
s) Text
cs
  | Text
s forall a. Eq a => a -> a -> Bool
== Text
"\\ "               = Text
s forall a. Semigroup a => a -> a -> a
<> Text
cs
  | (Char -> Bool) -> Text -> Bool
startsWith (\Char
c -> Char -> Bool
isAlphaNum Char
c Bool -> Bool -> Bool
|| Bool -> Bool
not (Char -> Bool
isAscii Char
c)) Text
cs
                             = Text
s forall a. Semigroup a => a -> a -> a
<> Char -> Text -> Text
T.cons Char
' ' Text
cs
  | Bool
otherwise                = Text
s forall a. Semigroup a => a -> a -> a
<> Text
cs
renderTeX (Grouped [Grouped [TeX]
xs]) Text
cs  = TeX -> Text -> Text
renderTeX ([TeX] -> TeX
Grouped [TeX]
xs) Text
cs
renderTeX (Grouped [TeX]
xs) Text
cs     =
  Text
"{" forall a. Semigroup a => a -> a -> a
<> forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr TeX -> Text -> Text
renderTeX Text
"" ([TeX] -> [TeX]
trimSpaces [TeX]
xs) forall a. Semigroup a => a -> a -> a
<> Text
"}" forall a. Semigroup a => a -> a -> a
<> Text
cs
renderTeX TeX
Space Text
cs
  | Text
cs forall a. Eq a => a -> a -> Bool
== Text
""                   = Text
""
  | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text -> Text -> Bool
`T.isPrefixOf` Text
cs) [Text]
ps = Text
cs
  | Bool
otherwise                  = Char -> Text -> Text
T.cons Char
' ' Text
cs
  where
    -- No space before ^, _, or \limits, and no doubled up spaces
    ps :: [Text]
ps = [ Text
"^", Text
"_", Text
" ", Text
"\\limits" ]

trimSpaces :: [TeX] -> [TeX]
trimSpaces :: [TeX] -> [TeX]
trimSpaces = forall a. [a] -> [a]
reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TeX] -> [TeX]
go forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TeX] -> [TeX]
go
  where go :: [TeX] -> [TeX]
go = forall a. (a -> Bool) -> [a] -> [a]
dropWhile (forall a. Eq a => a -> a -> Bool
== TeX
Space)

startsWith :: (Char -> Bool) -> T.Text -> Bool
startsWith :: (Char -> Bool) -> Text -> Bool
startsWith Char -> Bool
p Text
t = case Text -> Maybe (Char, Text)
T.uncons Text
t of
  Just (Char
c, Text
_) -> Char -> Bool
p Char
c
  Maybe (Char, Text)
Nothing     -> Bool
False

endsWith :: (Char -> Bool) -> T.Text -> Bool
endsWith :: (Char -> Bool) -> Text -> Bool
endsWith Char -> Bool
p Text
t = case Text -> Maybe (Text, Char)
T.unsnoc Text
t of
  Just (Text
_, Char
c) -> Char -> Bool
p Char
c
  Maybe (Text, Char)
Nothing     -> Bool
False

isControlSeq :: T.Text -> Bool
isControlSeq :: Text -> Bool
isControlSeq Text
t = case Text -> Maybe (Char, Text)
T.uncons Text
t of
  Just (Char
'\\', Text
xs) -> Text -> Int
T.length Text
xs forall a. Eq a => a -> a -> Bool
== Int
1 Bool -> Bool -> Bool
&& Text
xs forall a. Eq a => a -> a -> Bool
/= Text
" "
                     Bool -> Bool -> Bool
|| (Char -> Bool) -> Text -> Bool
T.all Char -> Bool
isLetter Text
xs
  Maybe (Char, Text)
_               -> Bool
False

escapeLaTeX :: Char -> TeX
escapeLaTeX :: Char -> TeX
escapeLaTeX Char
c =
  case Char
c of
       Char
'~'   -> Text -> TeX
ControlSeq Text
"\\textasciitilde"
       Char
'^'   -> Text -> TeX
Literal Text
"\\textasciicircum"
       Char
'\\'  -> Text -> TeX
ControlSeq Text
"\\textbackslash"
       Char
'\x200B' -> Text -> TeX
Literal Text
"\\!"
       Char
'\x200A' -> Text -> TeX
Literal Text
"\\,"
       Char
'\x2006' -> Text -> TeX
Literal Text
"\\,"
       Char
'\xA0'   -> Text -> TeX
Literal Text
"~"
       Char
'\x2005' -> Text -> TeX
Literal Text
"\\:"
       Char
'\x2004' -> Text -> TeX
Literal Text
"\\;"
       Char
'\x2001' -> Text -> TeX
ControlSeq Text
"\\quad"
       Char
'\x2003' -> Text -> TeX
ControlSeq Text
"\\quad"
       Char
'\x2032' -> Text -> TeX
Literal Text
"'"
       Char
'\x2033' -> Text -> TeX
Literal Text
"''"
       Char
'\x2034' -> Text -> TeX
Literal Text
"'''"
       Char
_ | (Char -> Bool) -> Text -> Bool
T.any (forall a. Eq a => a -> a -> Bool
== Char
c) Text
"#$%&_{} " -> Text -> TeX
Literal (Text
"\\" forall a. Semigroup a => a -> a -> a
<> Char -> Text
T.singleton Char
c)
         | Bool
otherwise -> Char -> TeX
Token Char
c