packages feed

live-sequencer-0.0.6.1: src/Module.hs

module Module (
    Name (Name, deconsName), tellName, mainName, noName,
    Source (..), maybeEditor,
    Identifier (Identifier, deconsIdentifier),
    stripIdentifier,

    Module,
    name,
    version,
    sourceText,
    sourceLocation,
    constructors,
    controls,
    functions,
    imports,
    source,

    FunctionDeclarations,
    ConstructorDeclarations,

    Version(Version),
    noVersion,
    initVersion,
    nextVersion,
    equalVersion,

    empty,
    addRule,

    nameFromIdentifier,
    makeFileName,
    parse,
    ) where

import ModuleBase ( Name(Name, deconsName), tellName, mainName, noName,
                    Version(Version),
                    noVersion, initVersion, nextVersion, equalVersion,
                    Source(File, Editor), maybeEditor, formatSource )
import InOut ( Input, Output, input, output )
import Term ( Term )
import TermParser ( lexer )
import Rule ( Rule )
import SourceText
            ( ModuleRange, Range,
              extractModuleName, extractModuleRange, setRangeSourceName )
import qualified ControllerBase as Controller
import qualified Type
import qualified Term
import qualified Rule

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

import qualified Data.Map as Map
import Data.Map ( Map )
import Data.Maybe ( mapMaybe )

import qualified Text.ParserCombinators.Parsec as Parsec
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.Path as Path
import Data.List.HT ( chop )
import Data.Eq.HT ( equating )

import Control.Applicative ( (<$>) )
import Control.Functor.HT ( void )



indent :: Int
indent = 4

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

parsePortList :: Parsec.Parser [Term.Identifier Range]
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 [Term.Identifier Range] [Term Range] (Term Range)
    deriving (Show)

parseIdentList :: Parsec.CharParser () [Term.Identifier Range]
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 Range
                 , dataRhs :: [ Term Range ]
                 }
    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 Range
                 , typeRhs :: Term Range
                 }
    deriving (Show)

instance Input Type where
    input = do
        reserved lexer "type"
        l <- input
        reservedOp lexer "="
        r <- Type.parseExpression
        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 [ Term.Identifier Range ]

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 Range)
                 | 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
        , version :: Maybe Version
        , imports :: [ Import ]
        , declarations :: [ Declaration ]
        , functions :: FunctionDeclarations
        , constructors :: ConstructorDeclarations
        , controls :: Controller.Assignments
        , sourceText :: String
        , sourceLocation :: Path.AbsFile
        }

nameFromIdentifier :: Term.Identifier ModuleRange -> Name
nameFromIdentifier = extractModuleName . Term.range

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


newtype Identifier = Identifier { deconsIdentifier :: String }
    deriving (Eq, Ord)

stripIdentifier :: Term.Identifier range -> Identifier
stripIdentifier = Identifier . Term.name

type FunctionDeclarations =
        Map Identifier (Term.Identifier ModuleRange, [Rule ModuleRange])
type ConstructorDeclarations =
        Map Identifier (Term.Identifier ModuleRange)


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

-- | add, or replace (if rule with exact same lhs is already present)
addRule :: Rule ModuleRange -> Module -> Module
addRule rule@(Rule.Rule ident params _rhs) m =
  let matchParams ps = Term.equatingList Term.match ps . Rule.parameters
  in
    m { declarations =
            revUpdate
                (\d -> case d of
                    RuleDeclaration r' ->
                        equating Term.name (extractModuleRange <$> ident) (Rule.name r') &&
                        matchParams (map (fmap extractModuleRange) params) r'
                    _ -> False)
                (RuleDeclaration (extractModuleRange <$> rule)) $
            declarations m,
        functions =
            Map.insertWith
                (\_ -> (,) ident . revUpdate (matchParams params) rule . snd)
                (stripIdentifier ident) (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 ) = break 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 :: Name -> Version -> [Declaration] -> FunctionDeclarations
makeFunctions srcName vers =
    Map.fromListWith (\(nm,xs) (_,ys) -> (nm, ys++xs)) .
    mapMaybe (\decl ->
        case decl of
            RuleDeclaration rule -> Just $
                let nm = Rule.name rule
                in  (stripIdentifier nm,
                     (setRangeSourceName srcName vers <$> nm,
                      [setRangeSourceName srcName vers <$> rule]))
            _ -> Nothing)

makeConstructors :: Name -> Version -> [Declaration] -> ConstructorDeclarations
makeConstructors srcName vers decls = Map.fromList $ do
    DataDeclaration (Data {dataRhs = summands}) <- decls
    Term.Node ident _ <- summands
    return (stripIdentifier ident, setRangeSourceName srcName vers <$> ident)

makeControllers ::
    Name -> Version -> [Declaration] -> Exception.Monad Controller.Assignments
makeControllers srcName vers decls =
    flip (foldr
        (\r go a -> Controller.collect r >>= Controller.union a >>= go)
        return) Map.empty $ do
    Module.RuleDeclaration rule <- decls
    return $ setRangeSourceName srcName vers <$> Rule.rhs rule

{- |
Replace source names in all identifiers.
We can only do this after parsing
because if a parse error happens, module renaming will be skipped
and thus parse errors must refer
to the old module name or to the file name.
-}
fromDeclarations ::
    Path.AbsFile -> String ->
    Name -> Version -> [Import] -> [Declaration] ->
    Controller.Assignments -> Module
fromDeclarations srcLoc srcText moduleName vers imps decls ctrls =
    Module {
        name = moduleName,
        version = Just vers,
        imports = imps,
        declarations = decls,
        functions = makeFunctions moduleName vers decls,
        constructors = makeConstructors moduleName vers decls,
        controls = ctrls,
        sourceText = srcText,
        sourceLocation = srcLoc
    }

{-
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 :: Parsec.Parser (Name, [Import], [Declaration])
parser = do
    m <- Parsec.option mainName $ do
        reserved lexer "module"
        m <- input
        void $ Parsec.optionMaybe parsePortList
        reserved lexer "where"
        return m
    is <- Parsec.many input
    ds <- Parsec.many input
    Parsec.eof
    return (m,is,ds)

parse ::
    Version -> Maybe Name -> Path.AbsFile -> String -> Exception.Monad Module
parse vers srcName srcLoc srcText =
    let src = maybe (File srcLoc) Editor srcName
    in  either
            (ME.Exception . Exception.messageFromParserError src)
            (\(m,is,ds) ->
                fromDeclarations srcLoc srcText m vers is ds
                    <$> makeControllers m vers ds) $
        Parsec.parse parser (formatSource src) 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