mida-0.4.3: src/Mida/Representation/Parser.hs
-- -*- Mode: Haskell; -*-
--
-- This module describes how to build syntax tree from textual
-- representation of MIDA statements.
--
-- Copyright © 2014, 2015 Mark Karpov
--
-- MIDA is free software: you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
-- Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- MIDA is distributed in the hope that it will be useful, but WITHOUT ANY
-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-- details.
--
-- You should have received a copy of the GNU General Public License along
-- with this program. If not, see <http://www.gnu.org/licenses/>.
{-# LANGUAGE OverloadedStrings #-}
module Mida.Representation.Parser
( Statement (..)
, probeMida
, parseMida )
where
import Control.Monad (void)
import Data.Functor.Identity
import Data.List (nub)
import qualified Data.Text.Lazy as T
import Text.Parsec
import Text.Parsec.Expr
import Text.Parsec.Language
import Text.Parsec.Text.Lazy
import qualified Text.Parsec.Token as Token
import Mida.Language.SyntaxTree (SyntaxTree, Sel (..))
import qualified Mida.Representation.Base as B
data Statement
= Definition String SyntaxTree
| Exposition SyntaxTree
deriving (Eq, Show)
probeMida :: T.Text -> Bool
probeMida txt = not $ or ["," `T.isSuffixOf` stripped
, f ("[", "]")
, f ("{", "}")
, f ("<", ">")
, f ("(", ")") ]
where stripped = T.strip txt
f (x, y) = (&&) <$> (> 0) <*> (/= g y) $ g x
g x = T.count x stripped
parseMida :: String -> T.Text -> Either String [Statement]
parseMida file txt =
case parse parser file txt of
Right x -> if null x
then Left $ '\"' : file ++ "\":\ninvalid definition syntax"
else Right x
Left x -> Left . show $ x
where parser = if T.pack B.defOp `T.isInfixOf` txt
then pSource
else return <$> pExposition
pSource :: Parser [Statement]
pSource = whiteSpace *> many pDefinition <* eof
pDefinition :: Parser Statement
pDefinition = Definition <$> identifier <* reservedOp B.defOp <*> pPrinciple
pExposition :: Parser Statement
pExposition = Exposition <$> (whiteSpace *> pPrinciple <* eof)
pPrinciple :: Parser SyntaxTree
pPrinciple = sepBy (pExpression <|> pElement) (optional comma)
pElement :: Parser Sel
pElement
= try pRange
<|> pValue
<|> try pReference
<|> pSection
<|> try pMulti
<|> pCMulti
<?> "element"
pRange :: Parser Sel
pRange = Range <$> pNatural <* reservedOp B.rangeOp <*> pNatural
pValue :: Parser Sel
pValue = Value <$> pNatural
pNatural :: Parser Int
pNatural = fromIntegral <$> natural <?> "literal value"
pReference :: Parser Sel
pReference = Reference <$> identifier <* notFollowedBy (reservedOp B.defOp)
pSection :: Parser Sel
pSection = Section <$> brackets pPrinciple
pMulti :: Parser Sel
pMulti = Multi <$> braces pPrinciple
pCMulti :: Parser Sel
pCMulti = CMulti <$> braces (many $ (,) <$> angles pPrinciple <*> pPrinciple)
pExpression :: Parser Sel
pExpression = buildExpressionParser optTable (parens pExpression <|> pElement)
<?> "expression"
optTable :: [[Operator T.Text () Identity Sel]]
optTable =
[[ Prefix (reservedOp B.reverseOp *> pure Reverse ) ]
, [ Infix (reservedOp B.productOp *> pure Product ) AssocLeft
, Infix (reservedOp B.divisionOp *> pure Division) AssocLeft
, Infix (reservedOp B.sumOp *> pure Sum ) AssocLeft
, Infix (reservedOp B.diffOp *> pure Diff ) AssocLeft
, Infix (reservedOp B.loopOp *> pure Loop ) AssocLeft
, Infix (reservedOp B.rotationOp *> pure Rotation) AssocLeft ]]
lang :: GenLanguageDef T.Text () Identity
lang = Token.LanguageDef
{ Token.commentStart = ""
, Token.commentEnd = ""
, Token.commentLine = B.commentLine
, Token.nestedComments = True
, Token.identStart = letter <|> char '_'
, Token.identLetter = alphaNum <|> char '_'
, Token.opStart = Token.opLetter lang
, Token.opLetter = oneOf . nub . concat $ langOps
, Token.reservedOpNames = langOps
, Token.reservedNames = []
, Token.caseSensitive = True }
langOps :: [String]
langOps =
[ B.productOp
, B.divisionOp
, B.sumOp
, B.diffOp
, B.loopOp
, B.rotationOp
, B.reverseOp
, B.rangeOp
, B.defOp ]
lexer :: Token.GenTokenParser T.Text () Identity
lexer = Token.makeTokenParser lang
angles :: Parser a -> Parser a
angles = Token.angles lexer
braces :: Parser a -> Parser a
braces = Token.braces lexer
brackets :: Parser a -> Parser a
brackets = Token.brackets lexer
comma :: Parser ()
comma = void $ Token.comma lexer
identifier :: Parser String
identifier = Token.identifier lexer
natural :: Parser Integer
natural = Token.natural lexer
parens :: Parser a -> Parser a
parens = Token.parens lexer
reservedOp :: String -> Parser ()
reservedOp = Token.reservedOp lexer
whiteSpace :: Parser ()
whiteSpace = Token.whiteSpace lexer