module Penny.Copper.Commodity (
lvl1Char,
lvl1Cmdty,
quotedLvl1Cmdty,
commandLineCmdty,
lvl2FirstChar,
lvl2OtherChars,
lvl2Cmdty,
lvl3FirstChar,
lvl3OtherChars,
lvl3Cmdty,
leftSideCmdty,
rightSideCmdty,
renderQuotedLvl1,
renderLvl2,
renderLvl3
) where
import Control.Applicative ((<*>), (<$>), (*>), (<|>))
import Control.Monad (guard)
import Data.Text ( pack, Text, cons, snoc, singleton )
import Text.Parsec ( satisfy, many, char, sepBy1, many1, (<?>),
between, option, sepBy )
import Text.Parsec.Text ( Parser )
import qualified Penny.Lincoln.Bits as B
import Data.List.NonEmpty (NonEmpty((:|)), fromList)
import Penny.Copper.Util (listIsOK, firstCharOfListIsOK)
import qualified Penny.Copper.Util as U
import qualified Penny.Lincoln.HasText as HT
import Penny.Lincoln.TextNonEmpty ( TextNonEmpty ( TextNonEmpty ),
unsafeTextNonEmpty )
lvl1Char :: Char -> Bool
lvl1Char c = (category || specific) && notBanned where
category = U.rangeLettersToSymbols c
specific = c == ' '
notBanned = not $ c `elem` ['"', ':']
lvl1SubCmdty :: Parser B.SubCommodity
lvl1SubCmdty = f <$> m <?> "sub commodity" where
m = many1 (satisfy lvl1Char)
f cs = B.SubCommodity (TextNonEmpty (head cs) (pack $ tail cs))
lvl1Cmdty :: Parser B.Commodity
lvl1Cmdty = (B.Commodity . fromList)
<$> sepBy1 lvl1SubCmdty (char ':')
<?> "commodity with spaces"
quotedLvl1Cmdty :: Parser B.Commodity
quotedLvl1Cmdty = between q q lvl1Cmdty
<?> "quoted commodity" where
q = char '"'
lvl2FirstChar :: Char -> Bool
lvl2FirstChar c = U.rangeLetters c || U.rangeMathCurrency c
lvl2OtherChars :: Char -> Bool
lvl2OtherChars c = category && notBanned where
category = U.rangeLettersToSymbols c
notBanned = not $ c `elem` ['"', ':']
lvl2FirstSubCmdty :: Parser B.SubCommodity
lvl2FirstSubCmdty = f <$> firstLet <*> restLet <?> e where
e = "sub commodity, first character is letter or symbol"
firstLet = satisfy lvl2FirstChar
restLet = many (satisfy lvl2OtherChars)
f l1 lr = B.SubCommodity (TextNonEmpty l1 (pack lr))
lvl2OtherSubCmdty :: Parser B.SubCommodity
lvl2OtherSubCmdty = f <$> ls <?> e where
e = "sub commodity"
ls = many1 (satisfy lvl2OtherChars)
f = B.SubCommodity . unsafeTextNonEmpty
lvl2Cmdty :: Parser B.Commodity
lvl2Cmdty = f <$> firstSub <*> restSubs <?> e where
e = "commodity, first character is letter or symbol"
firstSub = lvl2FirstSubCmdty
restSubs = option []
$ char ':'
*> sepBy1 lvl2OtherSubCmdty (char ':')
f s1 sr = B.Commodity (s1 :| sr)
lvl3OtherChars :: Char -> Bool
lvl3OtherChars c = U.rangeLetters c || U.rangeMathCurrency c
lvl3FirstChar :: Char -> Bool
lvl3FirstChar c = lvl3OtherChars c && (not $ c `elem` "+-")
lvl3FirstSubCmdty :: Parser B.SubCommodity
lvl3FirstSubCmdty = f <$> c <*> cs <?> e where
e = "first sub commodity, letters and symbols only, "
++ "first character not a + or -"
f c1 cr = B.SubCommodity (TextNonEmpty c1 (pack cr))
c = satisfy lvl3FirstChar
cs = many (satisfy lvl3OtherChars)
lvl3OtherSubCmdty :: Parser B.SubCommodity
lvl3OtherSubCmdty = f <$> ls <?> e where
e = "sub commodity, letters and symbols only"
f = B.SubCommodity . unsafeTextNonEmpty
ls = many1 (satisfy lvl3OtherChars)
lvl3Cmdty :: Parser B.Commodity
lvl3Cmdty = f <$> p1 <*> pr <?> e where
f cf cs = B.Commodity (cf :| cs)
p1 = lvl3FirstSubCmdty
pr = option [] $ char ':' *> sepBy lvl3OtherSubCmdty (char ':')
e = "commodity, letters and symbols only"
commandLineCmdty :: Parser B.Commodity
commandLineCmdty = lvl1Cmdty
leftSideCmdty :: Parser B.Commodity
leftSideCmdty =
quotedLvl1Cmdty
<|> lvl3Cmdty
<?> "commodity to the left of the quantity"
rightSideCmdty :: Parser B.Commodity
rightSideCmdty =
quotedLvl1Cmdty
<|> lvl2Cmdty
<?> "commodity to the right of the quantity"
renderQuotedLvl1 :: B.Commodity -> Maybe Text
renderQuotedLvl1 ca@(B.Commodity c) = do
guard $ listIsOK lvl1Char ca
return $ '"'
`cons` HT.text (HT.Delimited (singleton ':') (HT.textList c))
`snoc` '"'
renderLvl2 :: B.Commodity -> Maybe Text
renderLvl2 (B.Commodity c) = do
guard $ firstCharOfListIsOK lvl2FirstChar c
guard $ listIsOK lvl2OtherChars c
return $ HT.text (HT.Delimited (singleton ':') (HT.textList c))
renderLvl3 :: B.Commodity -> Maybe Text
renderLvl3 (B.Commodity c) = do
guard $ listIsOK lvl3OtherChars c
guard $ firstCharOfListIsOK lvl3FirstChar c
return $ HT.text (HT.Delimited (singleton ':') (HT.textList c))