packages feed

live-sequencer-0.0.1: src/Module.hs

module Module where

import IO ( Input, Output, input, output )
import Term ( Term, Identifier, lexer )
import Rule ( Rule )
import qualified ControlsBase as Controls
import qualified Type
import qualified Term
import qualified Rule

import qualified Exception
import qualified Control.Monad.Exception.Synchronous as Exc

import qualified Data.Set as S
import qualified Data.Map as M
import Data.Maybe ( mapMaybe )

import qualified Text.ParserCombinators.Parsec as Parsec
import qualified Text.ParserCombinators.Parsec.Pos as Pos
import qualified Text.ParserCombinators.Parsec.Token as Token
import Text.ParserCombinators.Parsec ( (<|>) )
import Text.ParserCombinators.Parsec.Token ( reserved, reservedOp )
import Text.ParserCombinators.Parsec.Expr
           ( Assoc(AssocLeft, AssocRight, AssocNone) )
import qualified Text.PrettyPrint.HughesPJ as Pretty
import Text.PrettyPrint.HughesPJ
           ( (<+>), ($$), hsep, sep, hang, punctuate,
             render, text, comma, vcat, parens )
import qualified Data.Char as Char

import qualified System.FilePath as FP
import Data.List.HT ( chop )

import Control.Functor.HT ( void )



indent :: Int
indent = 4

data Import = Import { qualified :: Bool
                     , source :: Name
                     , rename :: Maybe Identifier
                     }
--    deriving (Show)

parsePortList ::
    Parsec.GenParser Char () [Identifier]
parsePortList =
    Token.parens lexer $ flip Parsec.sepEndBy (Token.comma lexer) $
    (do ident <- input
        void $ Parsec.option [] $ Token.parens lexer $
            Token.commaSep lexer $ Token.identifier lexer
        return ident)
    <|>
    Term.parenOperator

{-
A semicolon behind an import statement is necessary when parsing

> import Prelude ;
>
> (+) :: a -> a -> a

otherwise the parentheses around the plus
would be interpreted as parentheses behind @Prelude@.
-}
instance Input Import where
    input = do
      reserved lexer "import"
      q <- Parsec.option False $ do reserved lexer "qualified" ; return True
      t <- input
      r <- Parsec.optionMaybe $ reserved lexer "as" >> input
      void $ Parsec.optionMaybe $ reserved lexer "hiding"
      void $ Parsec.optionMaybe $ parsePortList
      void $ Parsec.option "" $ Token.semi lexer
      return $ Import { qualified = q, source = t, rename = r }

instance Output Import where
    output i = hsep [ text "import"
                    , if qualified i then text "qualified" else Pretty.empty
                    , output $ source i
                    , case rename i of
                        Nothing -> Pretty.empty
                        Just r  -> text "as" <+> output r
                    ]


data TypeSig = TypeSig [Identifier] [Term] Term
    deriving (Show)

parseIdentList :: Parsec.CharParser () [Identifier]
parseIdentList =
    Token.commaSep lexer
        (input <|> Term.parenOperator)

instance Input TypeSig where
    input = do
        names <- parseIdentList
        reservedOp lexer "::"
        context <- Type.parseContext
        typeExpr <- Type.parseExpression
        void $ Token.semi lexer
        return $ TypeSig names context typeExpr

instance Output TypeSig where
    output (TypeSig names context typeExpr) =
        hang
            (hsep ( punctuate ( text "," ) $ map output names ) <+> text "::")
            indent
            (sep
                [if null context
                   then Pretty.empty
                   else parens ( hsep ( punctuate ( text "," ) $
                                 map output context ) ) <+> text "=>",
                 output typeExpr <+> text ";"])


data Data = Data { dataLhs :: Term
                 , dataRhs :: [ Term ]
                 }
    deriving (Show)

instance Input Data where
    input = do
        reserved lexer "data"
        l <- input
        reservedOp lexer "="
        rs <- Parsec.sepBy input ( reservedOp lexer "|" )
        void $ Token.semi lexer
        return $ Data { dataLhs = l, dataRhs = rs }

instance Output Data where
    output d = text "data" <+> output ( dataLhs d ) <+> text "="
        $$ hsep ( punctuate ( text "|" ) $ map output ( dataRhs d ) ) <+> text ";"


data Type = Type { typeLhs :: Term
                 , typeRhs :: Term
                 }
    deriving (Show)

instance Input Type where
    input = do
        reserved lexer "type"
        l <- input
        reservedOp lexer "="
        r <- input
        void $ Token.semi lexer
        return $ Type { typeLhs = l, typeRhs = r }

instance Output Type where
    output d =
        hang
            ( text "type" <+> output ( typeLhs d ) <+> text "=" )
            indent
            ( output ( typeRhs d ) <+> text ";" )


data Infix = Infix Assoc Int [ Identifier ]

showAssoc :: Assoc -> String
showAssoc AssocLeft  = "AssocLeft"
showAssoc AssocRight = "AssocRight"
showAssoc AssocNone  = "AssocNone"

instance Show Infix where
    showsPrec p (Infix assoc prec idents) =
       showParen (p>10) $
       showString "Infix " .
       showString (showAssoc assoc) .
       showString " " .
       shows prec .
       showString " " .
       shows idents

instance Input Infix where
    input = do
        assoc <-
           Parsec.try $
           Token.lexeme lexer $
           Parsec.string "infix" >>
              ((Parsec.char 'l' >> return AssocLeft)
               <|>
               (Parsec.char 'r' >> return AssocRight)
               <|>
               return AssocNone)
        prec <-
           fmap (\c -> Char.ord c - Char.ord '0') $
           Token.lexeme lexer Parsec.digit
        ops <- Parsec.sepBy1 Term.infixOperator (Token.comma lexer)
        void $ Parsec.option "" $ Token.semi lexer
        return $ Infix assoc prec ops

instance Output Infix where
    output (Infix assoc prec idents) =
        let assocStr =
                case assoc of
                    AssocLeft  -> "l"
                    AssocRight -> "r"
                    AssocNone  -> ""
        in  hang
                (text ( "infix" ++ assocStr ) <+> text ( show prec ))
                indent
                (hsep ( punctuate comma $ map output idents ) <+> text ";")


data Declaration = TypeSignature TypeSig
                 | RuleDeclaration Rule
                 | TypeDeclaration Type
                 | DataDeclaration Data
                 | InfixDeclaration Infix
    deriving (Show)

instance Input Declaration where
    input = fmap DataDeclaration input
        <|> fmap InfixDeclaration input
        <|> fmap TypeDeclaration input
        <|> fmap TypeSignature (do
                names <- Parsec.try $ do
                    names <- parseIdentList
                    reservedOp lexer "::"
                    return names
                context <-
                    Parsec.try Type.parseContext
                    <|>
                    return []
                typeExpr <- Type.parseExpression
                void $ Token.semi lexer
                return $ TypeSig names context typeExpr)
        <|> fmap RuleDeclaration input

instance Output Declaration where
    output decl = case decl of
        TypeSignature d -> output d
        DataDeclaration d -> output d
        TypeDeclaration d -> output d
        RuleDeclaration d -> output d
        InfixDeclaration d -> output d

-- | on module parsing:
-- identifiers contain information on their source location.
-- their sourceName (as used by Parsec) is the "show"
-- of the module name (which is an identifier).
-- So, sourceName is NOT the actual file name.
-- instead, the actual file name is kept in sourceLocation (defined here)

data Module =
    Module
        { name :: Name
        , imports :: [ Import ]
        , declarations :: [ Declaration ]
        , functions :: FunctionDeclarations
        , constructors :: ConstructorDeclarations
        , controls :: Controls.Assignments
        , sourceText :: String
        , sourceLocation :: FilePath
        }

newtype Name = Name {deconsName :: String}
    deriving (Eq, Ord)

instance Input Name where
    input = fmap Name Term.identifier

instance Output Name where
    output (Name n) = text n

tellName :: Name -> String
tellName (Name n) = "module " ++ n

nameFromIdentifier :: Identifier -> Name
nameFromIdentifier =
    Name . Pos.sourceName . Term.start . Term.range

{- |
Make a dummy Range if only the module name is known.
-}
nameRange :: Name -> Term.Range
nameRange (Name n) = Exception.dummyRange n

inoutExceptionMsg :: Module.Name -> String -> Exception.Message
inoutExceptionMsg moduleName msg =
    Exception.Message Exception.InOut (Module.nameRange moduleName) msg

makeFileName :: Name -> FilePath
makeFileName (Name n) =
    FP.addExtension (FP.joinPath $ chop ('.'==) n) "hs"


type FunctionDeclarations = M.Map Identifier [Rule]
type ConstructorDeclarations = S.Set Identifier


empty :: Name -> Module
empty moduleName =
    Module {
        name = moduleName,
        imports = [],
        sourceText = show $ outputModuleHead moduleName,
        sourceLocation = "/dev/null",
        functions = M.empty,
        constructors = S.empty,
        controls = M.empty,
        declarations = []
    }

-- | add, or replace (if rule with exact same lhs is already present)
addRule :: Rule -> Module -> Module
addRule rule@(Rule.Rule ident params _rhs) m =
    m { declarations =
            revUpdate
                (\d -> case d of
                    RuleDeclaration r' ->
                        ident == Rule.name r' &&
                        params == Rule.parameters r'
                    _ -> False)
                (RuleDeclaration rule) $
            declarations m,
        functions =
            M.insertWith
                (\_ -> revUpdate ((params ==) . Rule.parameters) rule)
                ident [rule] $
            functions m }

{- |
replace a matching element if it exists
and append the new element otherwise.
-}
update :: (a -> Bool) -> a -> [a] -> [a]
update matches x xs =
    let ( pre, post ) = span ( not . matches ) xs
    in  pre ++ x : drop 1 post

{- |
replace a matching element if it exists
and prepend the new element otherwise.
-}
revUpdate :: (a -> Bool) -> a -> [a] -> [a]
revUpdate p x = reverse . update p x . reverse

makeFunctions ::
    [Declaration] -> M.Map Identifier [Rule]
makeFunctions =
    M.fromListWith (flip (++)) .
    mapMaybe (\decl ->
        case decl of
            RuleDeclaration rule -> Just (Rule.name rule, [rule])
            _ -> Nothing)

makeConstructors ::
    [Declaration] -> S.Set Identifier
makeConstructors decls = S.fromList $ do
    DataDeclaration (Data {dataRhs = summands}) <- decls
    Term.Node ident _ <- summands
    return ident

makeControls ::
    [Declaration] ->
    Exc.Exceptional Exception.Message Controls.Assignments
makeControls decls =
    flip (foldr
        (\r go a -> Controls.collect r >>= Controls.union a >>= go)
        return) M.empty $ do
    Module.RuleDeclaration rule <- decls
    return $ Rule.rhs rule


{-
We do not define the instance Input Module,
because for proper module parsing
the caller should provide the source file path and content.

instance Input Module where
  input = do
-}
parser ::
    FilePath -> String ->
    Parsec.GenParser Char ()
        (Exc.Exceptional Exception.Message Module)
parser srcLoc srcText = do
    m <- Parsec.option (Name "Main") $ do
        reserved lexer "module"
        m <- input
        void $ Parsec.optionMaybe $ parsePortList
        reserved lexer "where"
        return m
    is <- Parsec.many input
    ds <- Parsec.many input
    return $ do
        ctrls <- makeControls ds
        return $ Module {
            name = m, imports = is, declarations = ds,
            functions = makeFunctions ds,
            constructors = makeConstructors ds,
            controls = ctrls,
            sourceText = srcText,
            sourceLocation = srcLoc
         }

parse ::
    String -> FilePath -> String ->
    Exc.Exceptional Exception.Message Module
parse srcName srcLoc srcText =
    let parserUntilEOF = do
            m <- parser srcLoc srcText
            Parsec.eof
            return m
    in  either (Exc.Exception . Exception.messageFromParserError) id $
        Parsec.parse parserUntilEOF srcName srcText



outputModuleHead :: Name -> Pretty.Doc
outputModuleHead nm =
    hsep [ text "module", output nm, text "where" ]

instance Output Module where
  output p = vcat
    [ outputModuleHead (name p)
    , vcat $ map output $ imports p
    , vcat $ map output $ declarations p
    ]

instance Show Module where show = render . output
-- instance Read Module where readsPrec = parsecReader