packages feed

penrose-0.1.1.0: src/Env.hs

-- | "Env" Contains all the shared code among substance and dsll:
--   AST, parser and typechecking functions
--   It also contains the environemt for the typechecker

{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}


module Env where
--module Main (main) where -- for debugging purposes

import Utils
import System.Process
import Control.Monad (void)
import Data.Void
import GHC.Generics
import System.IO -- read/write to file
import System.Environment
import Control.Arrow ((>>>))
import System.Random
import Debug.Trace
import Data.Functor.Classes
import Data.List
import Data.Maybe (fromMaybe)
import Control.Monad (void)
import Data.Typeable
import Text.Megaparsec
import Text.Megaparsec.Char
import Control.Monad.Combinators.Expr
import Data.Void
import Control.Monad.State.Lazy (StateT)
--import Text.PrettyPrint
--import Text.PrettyPrint.HughesPJClass hiding (colon, comma, parens, braces)
-- import qualified Data.Text 
import qualified Data.Set                   as S
import qualified Data.Map.Strict            as M
import qualified Text.Megaparsec.Char.Lexer as L
import qualified SubstanceTokenizer         as T
import qualified Data.Text as Text

--------------------------------------------------------------------------------
---- Lexer helper functions
-- TODO: separate reserved words and keywords for each of the DSLs

type BaseParser = Parsec ParserError String
type Parser = StateT (Maybe VarEnv) BaseParser
data ParserError
    = SubstanceError String
    | StyleError String
    deriving (Eq, Typeable, Ord, Read, Show)
instance ShowErrorComponent ParserError where
    showErrorComponent (SubstanceError msg) = "Substance Parser Error: " ++ msg

-- | custom source position type 
data SourcePosition = SourcePosition 
    { column :: Int -- ^ column number of the symbol
    , row :: Int  -- ^ row number of the symbol
    } deriving (Show, Eq)

rws, attribs, attribVs, shapes :: [String] -- list of reserved words
rws =     ["avoid", "as"] ++ dsll
-- ++ types ++ attribs ++ shapes ++ colors
attribs = ["shape", "color", "label", "scale", "position"]
attribVs = shapes
shapes =  ["Auto", "None", "Circle", "Box", "SolidArrow", "SolidDot",
           "HollowDot", "Cross"]
labelrws = ["Label", "AutoLabel", "NoLabel"]
dsll = ["tconstructor","vconstructor","operator","ExprNotation","StmtNotation",
        "forvars","fortypes","predicate", "Prop", "type", "<:", "->", "<->",
        "at level", "associativity"]
-- colors =  ["Random", "Black", "Red", "Blue", "Yellow"]

upperId, lowerId, identifier :: Parser String
identifier = (lexeme . try) (p >>= checkId)
  where p = (:) <$> letterChar <*> many validChar
upperId = (lexeme . try) (p >>= checkId)
  where p = (:) <$> upperChar <*> many validChar
lowerId = (lexeme . try) (p >>= checkId)
  where p = (:) <$> lowerChar <*> many validChar
validChar = alphaNumChar <|> char '_' <|> char '-'



transPattern :: Parser String
transPattern = (lexeme . try) (p >>= checkPattern)
  where p = many anySingle --(:) <$> letterChar <*> many validChar

checkPattern :: String -> Parser String
checkPattern x = if x == "\"" || x == "->"
            then fail $ "keyword " ++ show x ++ " cannot be an identifier"
            else return x

checkId :: String -> Parser String
checkId x = if x `elem` rws
          then fail $ "keyword " ++ show x ++ " cannot be an identifier"
          else return x

texExpr :: Parser String
texExpr = dollar >> manyTill asciiChar dollar

-- | 'lineComment' and 'blockComment' are the two styles of commenting in Penrose. Line comments start with @--@. Block comments are wrapped by @/*@ and @*/@.
lineComment, blockComment :: Parser ()
lineComment  = L.skipLineComment "--"
blockComment = L.skipBlockComment "/*" "*/"

-- | A strict space consumer. 'sc' only eats space and tab characters. It does __not__ eat newlines.
sc :: Parser ()
sc = L.space (void $ takeWhile1P Nothing f) lineComment empty
  where
    f x = x == ' ' || x == '\t'

-- | A normal space consumer. 'scn' consumes all whitespaces __including__ newlines.
scn :: Parser ()
scn = L.space space1 lineComment blockComment

lexeme :: Parser a -> Parser a
lexeme = L.lexeme sc

symbol :: String -> Parser String
symbol = L.symbol sc

symboln :: String -> Parser String
symboln = L.symbol scn

newline' :: Parser ()
newline' = newline >> scn

semi' :: Parser ()
semi' = semi >> scn

backticks :: Parser a -> Parser a
backticks = between (symbol "`") (symbol "`")

semi, def, lparen, rparen, lbrac, rbrac, colon, arrow, comma, dollar, question, dot :: Parser ()
aps = void (symbol "'")
quote = void (symbol "\"")
lbrac = void (symbol "{")
rbrac = void (symbol "}")
lparen = void (symbol "(")
rparen = void (symbol ")")
slparen = void (symbol "[")
srparen = void (symbol "]")
colon = void (symbol ":")
semi = void (symbol ";")
arrow = void (symbol "->")
comma = void (symbol ",")
dot = void (symbol ".")
eq = void (symbol "=")
def = void (symbol ":=")
dollar = void (symbol "$")
question = void (symbol "?")
tilde = void (symbol "~")
star = void (symbol "*")


dollars :: Parser a -> Parser a
dollars = between (symbol "$") (symbol "$")

braces :: Parser a -> Parser a
-- NOTE: symboln is used here because all usages of braces in our system allow newlines in the middle of a stmt
-- May wanna change this later once we have stricter use case of it
braces = between (symboln "{") (symbol "}")

parens :: Parser a -> Parser a
parens = between (symbol "(") (symbol ")")

brackets :: Parser a -> Parser a
brackets = between (symbol "[") (symbol "]")

-- | 'integer' parses an integer.
integer :: Parser Integer
unsignedInteger       = lexeme L.decimal
integer = L.signed sc unsignedInteger

-- | 'float' parses a floating point number.
float :: Parser Float
unsignedFloat = lexeme L.float
float = L.signed sc unsignedFloat

-- Reserved words
rword :: String -> Parser ()
rword w = lexeme (string w *> notFollowedBy alphaNumChar)

tryChoice :: [Parser a] -> Parser a
tryChoice list = choice $ map try list

----------------------------------- AST ----------------------------------------

data TypeName = TypeNameConst String  -- these are all names, e.g. “Set”
              | AllT              -- specifically for global selection in Style
                deriving (Show, Eq, Typeable)

data TypeVar = TypeVar { typeVarName :: String,
                         typeVarPos  :: SourcePos }
               deriving (Show, Typeable)

instance Eq TypeVar where
  (TypeVar n1 _) == (TypeVar n2 _) = n1 == n2

instance Ord TypeVar where
  (TypeVar s1 _) `compare` (TypeVar s2 _) = s1 `compare` s2

newtype Var = VarConst String
         deriving (Show, Eq, Typeable, Ord)
var2string (VarConst v) = v

data Y = TypeVarY TypeVar
       | VarY Var
         deriving (Show, Eq, Typeable, Ord)

data T = TTypeVar TypeVar
       | TConstr TypeCtorApp
       -- TODO: rename to TCtor. Less confusing, more consistent w/ Sty
         deriving (Show, Eq, Typeable, Ord)

data TypeCtorApp = TypeCtorApp { nameCons :: String,
                                 argCons  :: [Arg],
                                 constructorInvokerPos :: SourcePos }
                          deriving (Typeable)

instance Show TypeCtorApp where
  show (TypeCtorApp nameCons argCons posCons) = nString ++ "(" ++ aString ++ ")"
        where nString = show nameCons
              aString = show argCons

instance Eq TypeCtorApp where
  (TypeCtorApp n1 a1 _) == (TypeCtorApp n2 a2 _) = n1 == n2 && a1 == a2

instance Ord TypeCtorApp where
    (TypeCtorApp s1 _ _) `compare` (TypeCtorApp s2 _ _) = s1 `compare` s2

data Arg = AVar Var
         | AT T
           deriving (Show, Eq, Typeable, Ord)

data K = Ktype Type
       | KT T
       deriving (Show, Eq, Typeable)

data Type = Type { typeName :: String,
                   typePos  :: SourcePos }
            deriving (Show, Typeable)

instance Eq Type where
  (Type n1 _) == (Type n2 _) = n1 == n2

data Prop =  Prop { propName :: String,
                    propPos  :: SourcePos }
             deriving (Show, Typeable)

instance Eq Prop where
  (Prop n1 _) == (Prop n2 _) = n1 == n2

----------------------------------- Parser -------------------------------------

typeNameParser :: Parser TypeName
typeNameParser = TypeNameConst <$> identifier

typeParser :: Parser Type
typeParser = do
    rword "type"
    pos <- getSourcePos
    return Type{ typeName = "type", typePos = pos}

varParser :: Parser Var
varParser = VarConst <$> identifier

typeVarParser :: Parser TypeVar
typeVarParser = do
    aps
    i <- identifier
    pos <- getSourcePos
    return TypeVar { typeVarName =  i, typeVarPos = pos}

yParser, y1, y2 :: Parser Y
yParser = try y1 <|> y2
y1 = VarY <$> varParser
y2 = TypeVarY <$> typeVarParser

propParser :: Parser Prop
propParser = do
    rword "Prop"
    pos <- getSourcePos
    return Prop { propName = "Prop", propPos = pos}

tParser, tTypeCtorAppParser, typeVarParser' :: Parser T
tParser = try tTypeCtorAppParser <|> typeVarParser'
tTypeCtorAppParser = do
    i         <- identifier
    arguments <- option [] $ parens (argParser `sepBy1` comma)
    --try (parens (argParser `sepBy1` comma)) <|> emptyArgList --option [] $
    -- parens (argParser `sepBy1` comma)
    --try (parens (argParser `sepBy1` comma)) <|> emptyArgList
    pos <- getSourcePos
    return (TConstr (TypeCtorApp { nameCons = i, argCons = arguments,
                                   constructorInvokerPos = pos }))
typeVarParser' = TTypeVar <$> typeVarParser

argParser, varParser', tParser'  :: Parser Arg
argParser = try tParser' <|> varParser'
varParser' = AVar <$> varParser
tParser' = AT <$> tParser

kParser, kTypeParser, tParser'' :: Parser K
kParser = try kTypeParser <|> try tParser''
kTypeParser = do
     rword "type"
     pos <- getSourcePos
     return (Ktype (Type { typeName = "type", typePos = pos }))
tParser'' = KT <$> tParser

emptyArgList :: Parser [Arg]
emptyArgList = do
  lparen
  rparen
  return []

----------------------------------- Utility functions ------------------------------------------

-- Equality functions that don't compare SourcePos
-- TODO: use correct equality comparison in typechecker

argsEq :: Arg -> Arg -> Bool
argsEq (AVar v1) (AVar v2) = v1 == v2
argsEq (AT t1) (AT t2)     = typesEq t1 t2
argsEq _ _                 = False

typesEq :: T -> T -> Bool
typesEq (TTypeVar t1) (TTypeVar t2) = typeVarName t1 == typeVarName t2 -- TODO: better way to compare type vars
typesEq (TConstr t1) (TConstr t2) = nameCons t1 == nameCons t2 &&
                                    length (argCons t1) == length (argCons t2) &&
                                    (all (\(a1, a2) -> argsEq a1 a2) $ zip (argCons t1) (argCons t2))
typesEq _ _ = False

----------------------------------- Typechecker aux functions ------------------------------------------

-- | Compute the transitive closure of list of pairs
--   Useful for subtyping and equality subtyping checkings
transitiveClosure :: Eq a => [(a, a)] -> [(a, a)]
transitiveClosure closure
  | closure == closureAccum = closure
  | otherwise               = transitiveClosure closureAccum
  where closureAccum =
          nub $ closure ++ [(a, c) | (a, b) <- closure, (b', c) <- closure, b == b']

-- | Return whether a closure is cyclic (a, b) and (b, a) appears in the closure
isClosureNotCyclic :: Eq a => [(a,a)] -> Bool
isClosureNotCyclic lst = let c = [(a,a') | (a,a') <- lst, a == a']
                  in null c

firsts :: [(a, b)] -> [a]
firsts xs = [x | (x,_) <- xs]

seconds :: [(a, b)] -> [b]
seconds xs = [x | (_,x) <- xs]

second :: (a, b) -> b
second (a, b) = b

checkAndGet :: String -> M.Map String v -> SourcePos -> Either String v
checkAndGet k m pos = case M.lookup k m of
  Nothing -> Left ("Error in " ++ sourcePosPretty pos ++ " : " ++ k
                               ++ " Doesn't exist in the context \n")
  Just v ->  Right v

lookUpK :: VarEnv -> Arg -> K
lookUpK e (AT  (TTypeVar t))  = Ktype (typeVarMap e M.! t)
--(Ktype (Type {typeName = "type", typePos = typeVarPos t }))
lookUpK e (AT  (TConstr t)) =
        if nameCons t `elem` declaredNames e
        then lookUpK e (AVar (VarConst (nameCons t)))
        else Ktype (Type { typeName = "type", typePos = constructorInvokerPos t })
lookUpK e (AVar v) = KT (varMap e M.! v)

getTypesOfArgs :: VarEnv -> [Arg] -> [K]
getTypesOfArgs e = map (lookUpK e)

updateEnv :: VarEnv -> (Y, K) -> VarEnv
updateEnv e (TypeVarY y, Ktype t) = e { typeVarMap = M.insert y t $ typeVarMap e }
updateEnv e (VarY y, KT t)        = e { varMap = M.insert y t $ varMap e }
updateEnv e err                   = e { errors = errors e
                                  ++ "Problem in update: " ++ show err ++ "\n" }

addName :: String -> VarEnv -> VarEnv
addName a e = if a `elem` typeCtorNames e
              then e { errors = errors e ++ "Name " ++ a ++
               " already exists in the context \n" }
              else e { typeCtorNames = a : typeCtorNames e }

addValConstructor :: ValConstructor -> VarEnv -> VarEnv
addValConstructor v e = e { typeValConstructor = M.insert (tvc v) v $ typeValConstructor e }
-- Allow multiple value constructors for a type (e.g. List(`X) has Cons[`X] and Nil[`X]

addDeclaredName :: String -> VarEnv -> VarEnv
addDeclaredName a e = if a `elem` declaredNames e
                      then e { errors = errors e ++ "Name " ++ a
                                         ++ " already exsist in the context \n"}
                      else e { declaredNames = a : declaredNames e }

isSubtype :: T -> T -> VarEnv -> Bool
isSubtype t1 t2 e = typesEq t1 t2 -- A type is considered a subtype of itself
                    || (t1,t2) `elem` subTypes e

isSubtypeK :: K -> K -> VarEnv -> Bool
isSubtypeK (KT k1) (KT k2) e = isSubtype k1 k2 e

-- | For existing judgment G |- T1 <: T2,
-- | this rule (SUBTYPE-ARROW) checks if the first arrow type (i.e. function or value constructor type) is a subtype of the second
-- | The arrow types are contravariant in their arguments and covariant in their return type
-- | e.g. if Cat <: Animal, then Cat -> Cat <: Cat -> Animal, and Animal -> Cat <: Cat -> Cat
isSubtypeArrow :: [T] -> [T] -> VarEnv -> Bool
isSubtypeArrow [t] [s] e = isSubtype t s e
                       -- Covariant in return type (or simply the type for a nullary function)
isSubtypeArrow (t1:ts) (s1:ss) e = isSubtype s1 t1 e -- Contravariant in arguments
                                   && isSubtypeArrow ts ss e
isSubtypeArrow t s _ = False -- Functions have different numbers of arguments

--------------------------------------- Env Data Types ---------------------------------------

-- | Environment for the dsll semantic checker. As the 'check' function
-- executes, it accumulate information such as symbol tables in the environment.

-- | list of elements that might appear in the global context
data Ttype = Ttype { yt :: Y,
                     kt :: K }
             deriving (Show, Eq, Typeable)

data TypeConstructor = TypeConstructor { nametc :: String,
                                         kindstc  :: [K]}
                       deriving (Show, Eq, Typeable)

data ValConstructor = ValConstructor { namevc :: String,
                                       ylsvc  :: [Y],
                                       kindsvc  :: [K],
                                       nsvc   :: [Var],
                                       tlsvc  :: [T],
                                       tvc    :: T }
                      deriving (Show, Eq, Typeable)

data Operator = Operator { nameop :: String,
                             ylsop  :: [Y],
                             kindsop  :: [K],
                             tlsop  :: [T],
                             top  :: T}
                 deriving (Show, Eq, Typeable)

data PredicateEnv = Pred1 Predicate1
                  | Pred2 Predicate2
                  deriving (Show, Eq, Typeable)

data Predicate1 = Prd1 { namepred1 :: String,
                         ylspred1  :: [Y],
                         kindspred1  :: [K],
                         tlspred1  :: [T]}
                  deriving (Show, Eq, Typeable)

data Predicate2 = Prd2 { namepred2 :: String,
                         plspred2  :: [Prop]}
                  deriving (Show, Eq, Typeable)

data StmtNotationRule =
   StmtNotationRule { fromSnr :: [T.Token],
                      toSnr   :: [T.Token],
                      patternsSnr :: [T.Token],
                      entitiesSnr :: [T.Token] -- all the non pattern sugared entities
                    }
                    deriving (Show, Eq, Typeable)

data ExprNotationRule = ExprNotationRule {fromEnr          :: String,
                                          toEnr            :: String,
                                          associativityEnr :: String,
                                          precedenceEnr    :: Integer}
                  deriving (Show, Eq, Typeable)

data VarEnv = VarEnv
  { typeConstructors   :: M.Map String TypeConstructor
  , valConstructors    :: M.Map String ValConstructor
  , operators          :: M.Map String Env.Operator
  , predicates         :: M.Map String PredicateEnv
  , typeVarMap         :: M.Map TypeVar Type
  , typeValConstructor :: M.Map T ValConstructor
  , varMap             :: M.Map Var T
  , preludes           :: [(Var, T)]
  , subTypes           :: [(T, T)]
  , typeCtorNames      :: [String] -- a global list which contains all the names of types in that env
  , declaredNames      :: [String] -- a global list which contains all the names of elements declared in that env
  , stmtNotations      :: [StmtNotationRule] -- all the statement notations in the dsll
  , errors             :: String -- a string which accumulates all the errors founded during the run of the typechecker
  } deriving (Show, Eq, Typeable)

isDeclared :: String -> VarEnv -> Bool
isDeclared name varEnv = name `elem` typeCtorNames varEnv

checkTypeVar :: VarEnv -> TypeVar -> VarEnv
checkTypeVar e v = if M.member v (typeVarMap e)
                   then e
                   else e { errors = errors e ++ ("TypeVar " ++
                    show v ++ "is not in scope \n") }

checkVar :: VarEnv -> Var -> VarEnv
checkVar e v = if M.member v (varMap e)
               then e
               else e { errors = errors e ++ ("Var " ++ show v
               ++ "is not in scope \n") }


checkY :: VarEnv -> Y -> VarEnv
checkY e (TypeVarY y) = checkTypeVar e y
checkY e (VarY y)     = checkVar e y

checkArg :: VarEnv -> Arg -> VarEnv
checkArg e (AVar v) = checkVar e v
checkArg e (AT (TConstr i)) = if nameCons i `elem` declaredNames e
                              then checkVar e (VarConst (nameCons i))
                              else checkT e (TConstr i)
checkArg e (AT t) = checkT e t

checkT :: VarEnv -> T -> VarEnv
checkT e (TTypeVar t) = checkTypeVar e t
checkT e (TConstr c)  = let env1 = checkTypeCtorApp e c
                            env2 = checkDeclaredType env1 (TConstr c)
                        in env2

checkType :: VarEnv -> Type -> VarEnv
checkType e t = e

checkTypeCtorApp :: VarEnv -> TypeCtorApp -> VarEnv
checkTypeCtorApp e const =
  let name = nameCons const
      args = argCons const
      env1 = foldl checkArg e args
      kinds1 = getTypesOfArgs e args
  in case checkAndGet name (typeConstructors e) (constructorInvokerPos const) of
       Right val -> let kinds2 = kindstc val
                     in if kinds1 /= kinds2
                      then env1 { errors = errors env1
                      ++ ("Args do not match: " ++ show kinds1 ++
                      " != " ++ show kinds2 ++ "\n") }
                      else env1
       Left err -> env1 { errors = errors env1 ++ err }

checkDeclaredType :: VarEnv -> T -> VarEnv
checkDeclaredType e (TConstr t) =
   if nameCons t `elem` typeCtorNames e
     then e
     else e { errors = errors e ++ "Type " ++ nameCons t ++
      " does not exsist in the context \n" }

checkDeclaredType e _ = e { errors = errors e ++
 "checkDeclaredType should be called only with type constructors \n" }


checkK :: VarEnv -> K -> VarEnv
checkK e (Ktype t) = checkType e t
checkK e (KT t) = checkT e t