tock-mirror/frontends/RainTypes.hs
Adam Sampson 3283b7db41 Remove the Type/AbbrevMode information from Actual*.
It's redundant, since you can always compute them from the variable, and it
makes the code that deals with actuals rather cleaner.

On the other hand, it slightly complicates some of the tests, because any names
you use in an Actual need to be defined...
2008-03-26 18:16:09 +00:00

413 lines
20 KiB
Haskell

{-
Tock: a compiler for parallel languages
Copyright (C) 2007 University of Kent
This program 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 2 of the License, or (at your
option) any later version.
This program 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/>.
-}
module RainTypes where
import Control.Monad.State
import Data.Generics
import qualified AST as A
import CompState
import Errors
import EvalConstants
import Metadata
import Pass
import ShowCode
import Types
-- | A pass that records inferred types. Currently the only place where types are inferred is in seqeach\/pareach loops.
recordInfNameTypes :: Data t => t -> PassM t
recordInfNameTypes = everywhereM (mkM recordInfNameTypes')
where
recordInfNameTypes' :: A.Replicator -> PassM A.Replicator
recordInfNameTypes' input@(A.ForEach m n e)
= do arrType <- typeOfExpression e
innerT <- case arrType of
A.List t -> return t
_ -> diePC m $ formatCode "Cannot do a foreach loop over a non-list type: %" arrType
defineName n A.NameDef {A.ndMeta = m, A.ndName = A.nameName n, A.ndOrigName = A.nameName n,
A.ndNameType = A.VariableName, A.ndType = (A.Declaration m innerT),
A.ndAbbrevMode = A.Abbrev, A.ndPlacement = A.Unplaced}
return input
recordInfNameTypes' r = return r
-- | Folds all constants.
constantFoldPass :: Data t => t -> PassM t
constantFoldPass = applyDepthM doExpression
where
doExpression :: A.Expression -> PassM A.Expression
doExpression = (liftM (\(x,_,_) -> x)) . constantFold
-- | Annotates all integer literal types
annotateIntLiteralTypes :: Data t => t -> PassM t
annotateIntLiteralTypes = applyDepthM doExpression
where
--Function is separated out to easily provide the type description of Integer
powOf2 :: Integer -> Integer
powOf2 x = 2 ^ x
doExpression :: A.Expression -> PassM A.Expression
doExpression (A.Literal m t (A.IntLiteral m' s))
= do t' <-
if (t == A.Int64) then --it's a signed literal
(if (n >= powOf2 63 || n < (-(powOf2 63)))
then dieP m $ "Signed integer literal too large to fit into 64 bits: " ++ s
else
if (n < (-(powOf2 31)) || n >= powOf2 31)
then return A.Int64
else
if (n < (-(powOf2 15)) || n >= powOf2 15)
then return A.Int32
else
if (n < (-(powOf2 7)) || n >= powOf2 7)
then return A.Int16
else return A.Int8
)
else
dieP m $ "Unsigned literals currently unsupported"
return $ A.Literal m t' (A.IntLiteral m' s)
where
n :: Integer
n = read s
doExpression e = return e
-- | Annotates all list literals and list ranges with their type
annotateListLiteralTypes :: Data t => t -> PassM t
annotateListLiteralTypes = applyDepthM doExpression
where
doExpression :: A.Expression -> PassM A.Expression
doExpression (A.Literal m _ (A.ListLiteral m' es))
= do ts <- mapM typeOfExpression es
sharedT <- case (ts, leastGeneralSharedTypeRain ts) of
(_, Just t) -> return t
([], Nothing) -> return A.Any
(_, Nothing) -> diePC m'
$ formatCode
"Can't determine a common type for the list literal from: %"
ts
es' <- mapM (coerceIfNecessary sharedT) (zip ts es)
return $ A.Literal m (A.List sharedT) $ A.ListLiteral m' es'
doExpression (A.ExprConstr m (A.RangeConstr m' t b e))
= do bt <- typeOfExpression b
et <- typeOfExpression e
sharedT <- case leastGeneralSharedTypeRain [bt, et] of
Just t -> return t
Nothing -> diePC m'
$ formatCode
"Can't determine a common type for the range from: % %"
bt et
b' <- coerceIfNecessary sharedT (bt, b)
e' <- coerceIfNecessary sharedT (et, e)
return $ A.ExprConstr m $ A.RangeConstr m' (A.List sharedT) b' e'
doExpression e = return e
coerceIfNecessary :: A.Type -> (A.Type, A.Expression) -> PassM A.Expression
coerceIfNecessary to (from, e)
| to == from = return e
| otherwise = coerceType " in list literal" to from e
-- | A pass that finds all the 'A.ProcCall' and 'A.FunctionCall' in the
-- AST, and checks that the actual parameters are valid inputs, given
-- the 'A.Formal' parameters in the process's type
matchParamPass :: Data t => t -> PassM t
matchParamPass = everywhereM ((mkM matchParamPassProc) `extM` matchParamPassFunc)
where
--Picks out the parameters of a process call, checks the number is correct, and maps doParam over them
matchParamPassProc :: A.Process -> PassM A.Process
matchParamPassProc (A.ProcCall m n actualParams)
= do def <- lookupNameOrError n $ dieP m ("Process name is unknown: \"" ++ (show $ A.nameName n) ++ "\"")
case A.ndType def of
A.Proc _ _ expectedParams _ ->
if (length expectedParams) == (length actualParams)
then do transActualParams <- mapM (doParam m (A.nameName n)) (zip3 [0..] expectedParams actualParams)
return $ A.ProcCall m n transActualParams
else dieP m $ "Wrong number of parameters given to process call; expected: " ++ show (length expectedParams) ++ " but found: " ++ show (length actualParams)
_ -> dieP m $ "You cannot run things that are not processes, such as: \"" ++ (show $ A.nameName n) ++ "\""
matchParamPassProc p = return p
--Picks out the parameters of a function call, checks the number is correct, and maps doExpParam over them
matchParamPassFunc :: A.Expression -> PassM A.Expression
matchParamPassFunc (A.FunctionCall m n actualParams)
= do def <- lookupNameOrError n $ dieP m ("Function name is unknown: \"" ++ (show $ A.nameName n) ++ "\"")
case A.ndType def of
A.Function _ _ _ expectedParams _ ->
if (length expectedParams) == (length actualParams)
then do transActualParams <- mapM (doExpParam m (A.nameName n)) (zip3 [0..] expectedParams actualParams)
return $ A.FunctionCall m n transActualParams
else dieP m $ "Wrong number of parameters given to function call; expected: " ++ show (length expectedParams) ++ " but found: " ++ show (length actualParams)
_ -> dieP m $ "Attempt to make a function call with something"
++ " that is not a function: \"" ++ A.nameName n
++ "\"; is actually: " ++ showConstr (toConstr $
A.ndType def)
matchParamPassFunc e = return e
--Checks the type of a parameter (A.Actual), and inserts a cast if it is safe to do so
doParam :: Meta -> String -> (Int,A.Formal, A.Actual) -> PassM A.Actual
doParam m n (index, A.Formal formalAbbrev formalType formalName, A.ActualVariable v)
= do actualType <- typeOfVariable v
if (actualType == formalType)
then return $ A.ActualVariable v
else (liftM A.ActualExpression) $ doCast index formalType actualType (A.ExprVariable (findMeta v) v )
doParam m n (index, for@(A.Formal _ formalType _), A.ActualExpression e)
= (liftM A.ActualExpression) $ doExpParam m n (index, for, e)
--Checks the type of a parameter (A.Expression), and inserts a cast if it is safe to do so
doExpParam :: Meta -> String -> (Int, A.Formal, A.Expression) -> PassM A.Expression
doExpParam m n (index, A.Formal formalAbbrev formalType formalName, e)
= do actualType <- typeOfExpression e
if (actualType == formalType)
then return e
else doCast index formalType actualType e
doCast :: Int -> A.Type -> A.Type -> A.Expression -> PassM A.Expression
doCast index = coerceType $ " for parameter (zero-based): " ++ (show index)
--Adds a cast between two types if it is safe to do so, otherwise gives an error
coerceType :: String -> A.Type -> A.Type -> A.Expression -> PassM A.Expression
coerceType customMsg to from item
= if isImplicitConversionRain from to
then return $ A.Conversion (findMeta item) A.DefaultConversion to item
else diePC (findMeta item) $ (liftM concat) $ sequence [formatCode "Could not perform implicit cast from supplied type: % to expected type: %" from to, return customMsg]
-- | Checks the types in expressions
checkExpressionTypes :: Data t => t -> PassM t
checkExpressionTypes = applyDepthM checkExpression
where
-- | Checks the types of an expression where at least one type involved
-- is Time.
checkTimeExpression :: Meta -> A.DyadicOp -> (A.Type, A.Expression) ->
(A.Type, A.Expression) -> PassM A.Expression
checkTimeExpression m op (tlhs, lhs) (trhs, rhs)
= case (validOpWithTime op tlhs trhs) of
Nothing -> diePC m $ formatCode
"Operator: \"%\" is not valid on types: \"%\" and \"%\"" op tlhs trhs
Just (destLHS, destRHS) ->
if (isImplicitConversionRain tlhs destLHS)
&& (isImplicitConversionRain trhs destRHS)
then return $ A.Dyadic m op (convert destLHS tlhs lhs)
(convert destRHS trhs rhs)
else diePC m $ formatCode
"Operator: \"%\" is not valid on types: \"%\" and \"%\" (implicit conversions not possible)"
op tlhs trhs
checkExpression :: A.Expression -> PassM A.Expression
checkExpression e@(A.Dyadic m op lhs rhs)
= do tlhs <- typeOfExpression lhs
trhs <- typeOfExpression rhs
if (tlhs == A.Time || trhs == A.Time)
-- Expressions with times can have asymmetric types,
-- so we handle them specially:
then checkTimeExpression m op (tlhs, lhs) (trhs, rhs)
else
if (tlhs == trhs)
then
-- Types identical. At this point we consider whether the
-- user is adding two lists (in which case, correct the
-- operator), otherwise we just need to check the operator
-- is valid on the types (to avoid two channels of the same
-- type being added, for example)
case (tlhs, op) of
(A.List _, A.Plus) -> return $ A.Dyadic m A.Concat lhs rhs
_ -> if validOpSameType op tlhs
then return e
else diePC m $ formatCode
"Operator: \"%\" is not valid on type: \"%\""
op tlhs
-- Types differ. If they are integers, we can look for
-- a common (more general) type for both of them to be cast
-- up into in order to perform the operation.
else if (isIntegerType tlhs && isIntegerType trhs)
then case (leastGeneralSharedTypeRain [tlhs,trhs]) of
Nothing -> diePC m $ formatCode "Cannot find a suitable type to convert expression to, types are: % and %" tlhs trhs
Just t -> if validOpSameType op t then return $ A.Dyadic m op (convert t tlhs lhs) (convert t trhs rhs) else diePC m $
formatCode "Operator: \"%\" is not valid on type: \"%\"" op tlhs
else --The operands are not equal, and are not integers, and neither of them is a time type. Therefore this must be an error:
diePC m $ formatCode "Mis-matched types; no operator applies to types: % and %" tlhs trhs
checkExpression e@(A.Monadic m op rhs)
= do trhs <- typeOfExpression rhs
if (op == A.MonadicMinus)
then case trhs of
A.Byte -> return $ A.Monadic m op $ convert A.Int16 trhs rhs
A.UInt16 -> return $ A.Monadic m op $ convert A.Int32 trhs rhs
A.UInt32 -> return $ A.Monadic m op $ convert A.Int64 trhs rhs
A.UInt64 -> diePC m $ formatCode "Cannot apply unary minus to type: % because there is no type large enough to safely contain the result" trhs
_ -> if (isIntegerType trhs) then return e else diePC m $ formatCode "Trying to apply unary minus to non-integer type: %" trhs
else if (op == A.MonadicNot)
then
case trhs of
A.Bool -> return e
_ -> diePC m $ formatCode "Cannot apply unary not to non-boolean type: %" trhs
else dieP m $ "Invalid Rain operator: \"" ++ show op ++ "\""
checkExpression e@(A.Conversion m cm dest rhs)
= do src <- typeOfExpression rhs
if (src == dest)
then return e
else if isImplicitConversionRain src dest
then return e
else diePC m $ formatCode "Invalid cast from: % to: %"
src dest
checkExpression e = return e
convert :: A.Type -> A.Type -> A.Expression -> A.Expression
convert dest src e = if (dest == src)
then e
else A.Conversion (findMeta e) A.DefaultConversion dest e
validOpSameType :: A.DyadicOp -> A.Type -> Bool
validOpSameType A.Plus t = isIntegerType t
validOpSameType A.Minus t = isIntegerType t
validOpSameType A.Times t = isIntegerType t && t /= A.Time
validOpSameType A.Div t = isIntegerType t && t /= A.Time
validOpSameType A.Rem t = isIntegerType t && t /= A.Time
validOpSameType A.Eq _ = True
validOpSameType A.NotEq _ = True
validOpSameType A.Less t = haveOrder t
validOpSameType A.LessEq t = haveOrder t
validOpSameType A.More t = haveOrder t
validOpSameType A.MoreEq t = haveOrder t
validOpSameType A.And A.Bool = True
validOpSameType A.Or A.Bool = True
validOpSameType _ _ = False
-- | Takes an operator, the types of LHS and RHS, and returns Nothing if no cast will fix it,
-- or Just (needTypeLHS,needTypeRHS) for what types will be okay
validOpWithTime :: A.DyadicOp -> A.Type -> A.Type -> Maybe (A.Type,A.Type)
validOpWithTime A.Times A.Time _ = Just (A.Time, A.Int64)
validOpWithTime A.Times _ A.Time = Just (A.Int64, A.Time)
validOpWithTime A.Div A.Time _ = Just (A.Time, A.Int64)
--Any other operators involving Time are symmetric:
validOpWithTime op tlhs trhs = if (tlhs == trhs && validOpSameType op tlhs) then Just (tlhs,trhs) else Nothing
haveOrder :: A.Type -> Bool
haveOrder t = (isIntegerType t) || (t == A.Time)
-- | Checks the types in assignments
checkAssignmentTypes :: Data t => t -> PassM t
checkAssignmentTypes = applyDepthM checkAssignment
where
checkAssignment :: A.Process -> PassM A.Process
checkAssignment ass@(A.Assign m [v] (A.ExpressionList m' [e]))
= do trhs <- typeOfExpression e
tlhs <- typeOfVariable v
am <- abbrevModeOfVariable v
when (am == A.ValAbbrev) $
diePC m $ formatCode "Cannot assign to a constant variable: %" v
if (tlhs == trhs)
then return ass
else do rhs' <- coerceType " in assignment" tlhs trhs e
return $ A.Assign m [v] (A.ExpressionList m' [rhs'])
checkAssignment (A.Assign m _ _) = dieInternal (Just m,"Rain checker found occam-style assignment")
checkAssignment st = return st
-- | Checks the types in if and while conditionals
checkConditionalTypes :: Data t => t -> PassM t
checkConditionalTypes = applyDepthM2 checkWhile checkIf
where
checkWhile :: A.Process -> PassM A.Process
checkWhile w@(A.While m exp _)
= do t <- typeOfExpression exp
if (t == A.Bool)
then return w
else dieP m "Expression in while conditional must be of boolean type"
checkWhile p = return p
checkIf :: A.Choice -> PassM A.Choice
checkIf c@(A.Choice m exp _)
= do t <- typeOfExpression exp
if (t == A.Bool)
then return c
else dieP m "Expression in if conditional must be of boolean type"
-- | Checks the types in inputs and outputs, including inputs in alts
checkCommTypes :: Data t => t -> PassM t
checkCommTypes = applyDepthM2 checkInputOutput checkAltInput
where
checkInput :: A.Variable -> A.Variable -> Meta -> a -> PassM a
checkInput chanVar destVar m p
= do chanType <- typeOfVariable chanVar
destType <- typeOfVariable destVar
case chanType of
A.Chan dir _ innerType ->
if (dir == A.DirOutput)
then dieP m $ "Tried to input from the writing end of a channel: " ++ show chanVar
else
if (innerType == destType)
then return p
else diePC m $ formatCode "Mis-matching types; channel: \"%\" has inner-type: % but destination variable: \"%\" has type: %"
chanVar innerType destVar destType
_ -> dieP m $ "Tried to input from a variable that is not of type channel: " ++ show chanVar
checkWait :: A.InputMode -> PassM ()
checkWait (A.InputTimerFor m exp)
= do t <- typeOfExpression exp
when (t /= A.Time) $
diePC m $ formatCode "Tried to wait for something that was not of time type: %"
t
checkWait (A.InputTimerAfter m exp)
= do t <- typeOfExpression exp
when (t /= A.Time) $
diePC m $ formatCode "Tried to wait for something that was not of time type: %"
t
checkWait (A.InputTimerRead m (A.InVariable _ v))
= do t <- typeOfVariable v
when (t /= A.Time) $
diePC m $ formatCode "Tried to wait for something that was not of time type: %"
t
checkWait _ = return ()
checkInputOutput :: A.Process -> PassM A.Process
checkInputOutput p@(A.Input m chanVar (A.InputSimple _ [A.InVariable _ destVar]))
= checkInput chanVar destVar m p
checkInputOutput p@(A.Input _ _ im@(A.InputTimerFor {}))
= do checkWait im
return p
checkInputOutput p@(A.Input _ _ im@(A.InputTimerAfter {}))
= do checkWait im
return p
checkInputOutput p@(A.Input _ _ im@(A.InputTimerRead {}))
= do checkWait im
return p
checkInputOutput p@(A.Output m chanVar [A.OutExpression m' srcExp])
= do chanType <- typeOfVariable chanVar
srcType <- typeOfExpression srcExp
case chanType of
A.Chan dir _ innerType ->
if (dir == A.DirInput)
then dieP m $ "Tried to output to the reading end of a channel: " ++ show chanVar
else
if (innerType == srcType)
then return p
else do castExp <- coerceType " for writing to channel" innerType srcType srcExp
return $ A.Output m chanVar [A.OutExpression m' castExp]
_ -> dieP m $ "Tried to output to a variable that is not of type channel: " ++ show chanVar
checkInputOutput p = return p
checkAltInput :: A.Alternative -> PassM A.Alternative
checkAltInput a@(A.Alternative m chanVar (A.InputSimple _ [A.InVariable _ destVar]) body)
= checkInput chanVar destVar m a
checkAltInput a@(A.Alternative m _ im@(A.InputTimerFor {}) _)
= do checkWait im
return a
checkAltInput a@(A.Alternative m _ im@(A.InputTimerAfter {}) _)
= do checkWait im
return a
checkAltInput a = return a