{-# LANGUAGE OverloadedStrings #-} -- | This module contains parsers for: -- -- * Catalog headers (@Name: Value@ lines, that are specified as translation for empty string in @po@ file); -- * Plural form selection expressions (specified in @Plural-Forms@ header). -- -- These parsers are already used by main module; but they can be useful for other libraries working with -- gettext's files. -- module Data.Gettext.Parsers ( -- * Types Header, Headers, -- * Parsing functions parseHeaders, parsePlural, -- * Parsec parsers pHeaders, pExpr, pPlural ) where import Control.Monad import Control.Monad.Identity import qualified Data.Text.Lazy as T import Text.Parsec import Text.Parsec.Text.Lazy import Text.Parsec.Language import qualified Text.Parsec.Token as Token import Text.Parsec.Expr import Data.Gettext.Plural -- | Catalog header, i.e. one @Name: Value@ line in @po@ file type Header = (T.Text, T.Text) -- | List of catalog headers type Headers = [Header] pHeader :: Parser Header pHeader = do name <- (many1 $ alphaNum <|> char '-') "Header name" char ':' many $ oneOf " \t" value <- many $ noneOf "\r\n" return (T.pack name, T.pack value) -- | Parsec parser for catalog headers pHeaders :: Parser Headers pHeaders = pHeader `sepEndBy` newline -- | Parse catalog headers. -- NB: for now this function does not use Parsec. parseHeaders :: T.Text -> Either String Headers parseHeaders t = do let lines = filter (not . T.null) $ T.splitOn "\n" t forM lines $ \line -> case T.splitOn ": " line of [name, value] -> return (name, value) (name:values) -> return (name, T.intercalate ": " values) _ -> Left $ "Invalid gettext file header: " ++ T.unpack line pSimpleExpr :: Parser Expr pSimpleExpr = buildExpressionParser table term "simple expression" where term = parens pExpr <|> (symbol "n" >> return N) <|> (Literal `fmap` natural) table = [ [prefix "-" Negate, prefix "!" Not], [binary "*" Multiply AssocLeft, binary "/" Divide AssocLeft, binary "%" Mod AssocLeft], [binary "+" Plus AssocLeft, binary "-" Minus AssocLeft], [binary "==" Equals AssocLeft, binary "!=" NotEquals AssocLeft, binary ">" Greater AssocLeft, binary "<=" NotGreater AssocLeft, binary "<" Less AssocLeft, binary ">=" NotLess AssocLeft], [binary "&&" And AssocLeft, binary "||" Or AssocLeft, binary "^" Xor AssocLeft] ] binary name fun assoc = Infix (do{ reservedOp name; return (Binary fun) }) assoc prefix name fun = Prefix (do{ reservedOp name; return fun }) -- postfix name fun = Postfix (do{ reservedOp name; return fun }) -- | Parse plural form selection expression. -- Note: this parses only part which goes after @plural=@. pExpr :: Parser Expr pExpr = do expr <- pSimpleExpr mbCont <- optionMaybe pTernary case mbCont of Nothing -> return expr Just (true, false) -> return $ If expr true false pTernary :: Parser (Expr, Expr) pTernary = do reservedOp "?" true <- pExpr colon false <- pExpr return (true, false) -- | Parse plural form selection definition. -- This parses the whole value of @Plural-Forms@ header, -- starting from @nplurals=@. pPlural :: Parser (Int, Expr) pPlural = do symbol "nplurals" reservedOp "=" n <- natural semi symbol "plural" reservedOp "=" expr <- pExpr return (n, expr) cStyle :: GenLanguageDef T.Text () Identity cStyle = Token.LanguageDef { Token.commentStart = "/*" , Token.commentEnd = "*/" , Token.commentLine = "//" , Token.nestedComments = True , Token.identStart = letter , Token.identLetter = alphaNum <|> oneOf "_'" , Token.opStart = Token.opLetter cStyle , Token.opLetter = oneOf ":!#$%&*+./<=>?@\\^|-~" , Token.reservedNames = [] , Token.reservedOpNames= [] , Token.caseSensitive = True } lexer :: Token.GenTokenParser T.Text () Identity lexer = Token.makeTokenParser cStyle natural :: Parser Int natural = fromIntegral `fmap` Token.natural lexer reservedOp :: String -> Parser () reservedOp = Token.reservedOp lexer symbol :: String -> Parser String symbol = Token.symbol lexer parens :: Parser a -> Parser a parens = Token.parens lexer semi :: Parser String semi = Token.semi lexer colon :: Parser String colon = Token.colon lexer -- | Parse plural form selection definition. -- Return value is (number of possible plural forms; selection expression). parsePlural :: Headers -> Either String (Int, Expr) parsePlural headers = case lookup (T.pack "Plural-Forms") headers of Nothing -> Left $ "Plural-Forms header not found: " ++ show headers Just str -> either (Left . show) Right $ parse pPlural "" str