module Language.Clafer.Generator.Alloy where
import Control.Lens hiding (elements, mapping)
import Control.Monad.State
import Data.List
import Data.Maybe
import Language.Clafer.Common
import Language.Clafer.ClaferArgs
import Language.Clafer.Front.Absclafer
import Language.Clafer.Intermediate.Intclafer hiding (exp)
data Concat = CString String | Concat {
srcPos :: IrTrace,
nodes :: [Concat]
} deriving (Eq, Show)
type Position = ((LineNo, ColNo), (LineNo, ColNo))
data IrTrace =
IrPExp {pUid::String} |
LowerCard {pUid::String, isGroup::Bool} |
UpperCard {pUid::String, isGroup::Bool} |
ExactCard {pUid::String, isGroup::Bool} |
NoTrace
deriving (Eq, Show)
mkConcat :: IrTrace -> String -> Concat
mkConcat pos str = Concat pos [CString str]
iscPrimitive :: Concat -> Bool
iscPrimitive x = isPrimitive $ flatten x
flatten :: Concat -> String
flatten (CString x) = x
flatten (Concat _ nodes') = nodes' >>= flatten
(+++) :: Concat -> Concat -> Concat
(+++) (CString x) (CString y) = CString $ x ++ y
(+++) (CString "") y@Concat{} = y
(+++) x y@(Concat src ys)
| src == NoTrace = Concat NoTrace $ x : ys
| otherwise = Concat NoTrace $ [x, y]
(+++) x@Concat{} (CString "") = x
(+++) x@(Concat src xs) y
| src == NoTrace = Concat NoTrace $ xs ++ [y]
| otherwise = Concat NoTrace $ [x, y]
cconcat :: [Concat] -> Concat
cconcat = foldr (+++) (CString "")
cintercalate :: Concat -> [Concat] -> Concat
cintercalate xs xss = cconcat (intersperse xs xss)
filterNull :: [Concat] -> [Concat]
filterNull = filter (not.isNull)
isNull :: Concat -> Bool
isNull (CString "") = True
isNull (Concat _ []) = True
isNull _ = False
cunlines :: [Concat] -> Concat
cunlines xs = cconcat $ map (+++ (CString "\n")) xs
genModule :: ClaferArgs -> (IModule, GEnv) -> [(UID, Integer)] -> (Result, [(Span, IrTrace)])
genModule claferargs (imodule, _) scopes = (flatten output, filter ((/= NoTrace) . snd) $ mapLineCol output)
where
output = header claferargs scopes +++ (cconcat $ map (genDeclaration claferargs) (_mDecls imodule)) +++
if ((not $ skip_goals claferargs) && length goals_list > 0) then
CString "objectives o_global {\n" +++ (cintercalate (CString ",\n") goals_list) +++ CString "\n}"
else
CString ""
where
goals_list = filterNull (map (genDeclarationGoalsOnly claferargs) (_mDecls imodule))
header :: ClaferArgs -> [(UID, Integer)] -> Concat
header args scopes = CString $ unlines
[ "open util/integer"
, "pred show {}"
, if (validate args) || (noalloyruncommand args)
then ""
else "run show for 1" ++ genScopes scopes
, ""]
where
genScopes [] = ""
genScopes scopes' = " but " ++ intercalate ", " (map genScope scopes')
genScope :: (UID, Integer) -> String
genScope (uid', scope) = show scope ++ " " ++ uid'
genDeclarationGoalsOnly :: ClaferArgs -> IElement -> Concat
genDeclarationGoalsOnly claferargs x = case x of
IEClafer _ -> CString ""
IEConstraint _ _ -> CString ""
IEGoal _ (PExp _ _ _ innerexp) -> case innerexp of
IFunExp op' exps' -> if op' == iGMax || op' == iGMin then
mkMetric op' $ genPExp claferargs [] (head exps')
else
error "unary operator distinct from (min/max) at the topmost level of a goal element"
_ -> error "no unary operator (min/max) at the topmost level of a goal element."
genDeclaration :: ClaferArgs -> IElement -> Concat
genDeclaration claferargs x = case x of
IEClafer clafer' -> genClafer claferargs [] clafer'
IEConstraint _ pexp -> mkFact $ genPExp claferargs [] pexp
IEGoal _ (PExp _ _ _ innerexp) -> case innerexp of
IFunExp op' _ -> if op' == iGMax || op' == iGMin then
CString ""
else
error "unary operator distinct from (min/max) at the topmost level of a goal element"
_ -> error "no unary operator (min/max) at the topmost level of a goal element."
mkFact :: Concat -> Concat
mkFact xs = cconcat [CString "fact ", mkSet xs, CString "\n"]
mkMetric :: String -> Concat -> Concat
mkMetric goalopname xs = cconcat [ if goalopname == iGMax then CString "maximize" else CString "minimize", CString " ", xs, CString " "]
mkSet :: Concat -> Concat
mkSet xs = cconcat [CString "{ ", xs, CString " }"]
showSet :: Concat -> [Concat] -> Concat
showSet delim xs = showSet' delim $ filterNull xs
where
showSet' _ [] = CString "{}"
showSet' delim' xs' = mkSet $ cintercalate delim' xs'
optShowSet :: [Concat] -> Concat
optShowSet [] = CString ""
optShowSet xs = CString "\n" +++ showSet (CString "\n ") xs
genClafer :: ClaferArgs -> [String] -> IClafer -> Concat
genClafer claferargs resPath oClafer = (cunlines $ filterNull
[ cardFact +++ claferDecl clafer'
((showSet (CString "\n, ") $ genRelations claferargs clafer') +++
(optShowSet $ filterNull $ genConstraints claferargs resPath clafer'))
]) +++ CString "\n" +++ children'
where
clafer' = transPrimitive oClafer
children' = cconcat $ filterNull $ map
(genClafer claferargs ((_uid clafer') : resPath)) $
getSubclafers $ _elements clafer'
cardFact
| null resPath && (null $ flatten $ genOptCard clafer') =
case genCard (_uid clafer') $ _card clafer' of
CString "set" -> CString ""
c -> mkFact c
| otherwise = CString ""
transPrimitive :: IClafer -> IClafer
transPrimitive = super %~ toOverlapping
where
toOverlapping x@(ISuper _ [PExp _ _ _ (IClaferId _ id' _)])
| isPrimitive id' = x{_isOverlapping = True}
| otherwise = x
toOverlapping x = x
claferDecl :: IClafer -> Concat -> Concat
claferDecl c rest = cconcat [genOptCard c,
CString $ genAbstract $ _isAbstract c, CString "sig ",
Concat NoTrace [CString $ _uid c, genExtends $ _super c, CString "\n", rest]]
where
genAbstract isAbs = if isAbs then "abstract " else ""
genExtends (ISuper False [PExp _ _ _ (IClaferId _ "clafer" _)]) = CString ""
genExtends (ISuper False [PExp _ _ _ (IClaferId _ i _)]) = CString " " +++ Concat NoTrace [CString $ "extends " ++ i]
genExtends _ = CString ""
genOptCard :: IClafer -> Concat
genOptCard c
| glCard' `elem` ["lone", "one", "some"] = cardConcat (_uid c) False [CString glCard'] +++ (CString " ")
| otherwise = CString ""
where
glCard' = genIntervalCrude $ _glCard c
genRelations :: ClaferArgs -> IClafer -> [Concat]
genRelations claferargs c = maybeToList r ++ (map mkRel $ getSubclafers $ _elements c)
where
r = if _isOverlapping $ _super c
then
Just $ Concat NoTrace [CString $ genRel (if (noalloyruncommand claferargs) then (_uid c ++ "_ref") else "ref")
c {_card = Just (1, 1)} $
flatten $ refType claferargs c]
else
Nothing
mkRel c' = Concat NoTrace [CString $ genRel (genRelName $ _uid c') c' $ _uid c']
genRelName :: String -> String
genRelName name = "r_" ++ name
genRel :: String -> IClafer -> String -> String
genRel name c rType = genAlloyRel name (genCardCrude $ _card c) rType'
where
rType' = if isPrimitive rType then "Int" else rType
genAlloyRel :: String -> String -> String -> String
genAlloyRel name card' rType = concat [name, " : ", card', " ", rType]
refType :: ClaferArgs -> IClafer -> Concat
refType claferargs c = cintercalate (CString " + ") $ map ((genType claferargs).getTarget) $ _supers $ _super c
getTarget :: PExp -> PExp
getTarget x = case x of
PExp _ _ _ (IFunExp op' (_:pexp:_)) -> if op' == iJoin then pexp else x
_ -> x
genType :: ClaferArgs -> PExp -> Concat
genType claferargs x@(PExp _ _ _ y@(IClaferId _ _ _)) = genPExp claferargs []
x{_exp = y{_isTop = True}}
genType m x = genPExp m [] x
genConstraints :: ClaferArgs -> [String] -> IClafer -> [Concat]
genConstraints cargs resPath c = (genParentConst resPath c) :
(genGroupConst c) : genPathConst cargs (if (noalloyruncommand cargs) then (_uid c ++ "_ref") else "ref") resPath c : constraints
where
constraints = map genConst $ _elements c
genConst x = case x of
IEConstraint _ pexp -> genPExp cargs ((_uid c) : resPath) pexp
IEClafer c' ->
if genCardCrude (_card c') `elem` ["one", "lone", "some"]
then CString "" else mkCard ( _uid c') False (genRelName $ _uid c') $ fromJust (_card c')
IEGoal _ _ -> error "getConst function from Alloy generator was given a Goal, this function should only be given a Constrain or Clafer"
genParentConst :: [String] -> IClafer -> Concat
genParentConst [] _ = CString ""
genParentConst _ c = genOptParentConst c
genOptParentConst :: IClafer -> Concat
genOptParentConst c
| glCard' == "one" = CString ""
| glCard' == "lone" = Concat NoTrace [CString $ "one " ++ rel]
| otherwise = Concat NoTrace [CString $ "one @" ++ rel ++ ".this"]
where
rel = genRelName $ _uid c
glCard' = genIntervalCrude $ _glCard c
genGroupConst :: IClafer -> Concat
genGroupConst clafer'
| null children' || flatten card' == "" = CString ""
| otherwise = cconcat [CString "let children = ", brArg id $ CString children', CString" | ", card']
where
children' = intercalate " + " $ map (genRelName._uid) $
getSubclafers $ _elements clafer'
card' = mkCard (_uid clafer') True "children" $ _interval $ fromJust $ _gcard $ clafer'
mkCard :: String -> Bool -> String -> (Integer, Integer) -> Concat
mkCard constraintName group' element' crd
| crd' == "set" || crd' == "" = CString ""
| crd' `elem` ["one", "lone", "some"] = CString $ crd' ++ " " ++ element'
| otherwise = interval'
where
interval' = genInterval constraintName group' element' crd
crd' = flatten $ interval'
genPathConst :: ClaferArgs -> String -> [String] -> IClafer -> Concat
genPathConst claferargs name resPath c
| isRefPath c = cconcat [CString name, CString " = ",
cintercalate (CString " + ") $
map ((brArg id).(genPExp claferargs resPath)) $
_supers $ _super c]
| otherwise = CString ""
isRefPath :: IClafer -> Bool
isRefPath c = (c ^. super . isOverlapping) &&
((length s > 1) || (not $ isSimplePath s))
where
s = _supers $ _super c
isSimplePath :: [PExp] -> Bool
isSimplePath [PExp _ _ _ (IClaferId _ _ _)] = True
isSimplePath [PExp _ _ _ (IFunExp op' _)] = op' == iUnion
isSimplePath _ = False
genCard :: String -> Maybe Interval -> Concat
genCard element' crd = genInterval element' False element' $ fromJust crd
genCardCrude :: Maybe Interval -> String
genCardCrude crd = genIntervalCrude $ fromJust crd
genIntervalCrude :: Interval -> String
genIntervalCrude x = case x of
(1, 1) -> "one"
(0, 1) -> "lone"
(1, 1) -> "some"
_ -> "set"
genInterval :: String -> Bool -> String -> Interval -> Concat
genInterval constraintName group' element' x = case x of
(1, 1) -> cardConcat constraintName group' [CString "one"]
(0, 1) -> cardConcat constraintName group' [CString "lone"]
(1, 1) -> cardConcat constraintName group' [CString "some"]
(0, 1) -> CString "set"
(n, exinteger) ->
case (s1, s2) of
(Just c1, Just c2) -> cconcat [c1, CString " and ", c2]
(Just c1, Nothing) -> c1
(Nothing, Just c2) -> c2
(Nothing, Nothing) -> undefined
where
s1 = if n == 0 then Nothing else Just $ cardLowerConcat constraintName group' [CString $ concat [show n, " <= #", element']]
s2 =
do
result <- genExInteger element' x exinteger
return $ cardUpperConcat constraintName group' [CString result]
cardConcat :: String -> Bool -> [Concat] -> Concat
cardConcat constraintName = Concat . ExactCard constraintName
cardLowerConcat :: String -> Bool -> [Concat] -> Concat
cardLowerConcat constraintName = Concat . LowerCard constraintName
cardUpperConcat :: String -> Bool -> [Concat] -> Concat
cardUpperConcat constraintName = Concat . UpperCard constraintName
genExInteger :: String -> Interval -> Integer -> Maybe Result
genExInteger element' (y,z) x =
if (y==0 && z==0) then Just $ concat ["#", element', " = ", "0"] else
if x == 1 then Nothing else Just $ concat ["#", element', " <= ", show x]
genPExp :: ClaferArgs -> [String] -> PExp -> Concat
genPExp claferargs resPath x = genPExp' claferargs resPath $ adjustPExp resPath x
genPExp' :: ClaferArgs -> [String] -> PExp -> Concat
genPExp' claferargs resPath (PExp iType' pid' pos exp') = case exp' of
IDeclPExp q d pexp -> Concat (IrPExp pid') $
[ CString $ genQuant q, CString " "
, cintercalate (CString ", ") $ map ((genDecl claferargs resPath)) d
, CString $ optBar d, genPExp' claferargs resPath pexp]
where
optBar [] = ""
optBar _ = " | "
IClaferId _ "ref" _ -> CString "@ref"
IClaferId _ sid istop -> CString $
if head sid == '~' then sid else
if isNothing iType' then sid' else case fromJust $ iType' of
TInteger -> vsident
TReal -> vsident
TString -> vsident
_ -> sid'
where
sid' = (if istop then "" else '@' : genRelName "") ++ sid
vsident = if (noalloyruncommand claferargs) then sid' ++ ".@" ++ referredClaferUniqeuid ++ "_ref" else sid' ++ ".@ref"
where referredClaferUniqeuid = if sid == "this" then (head resPath) else sid
IFunExp _ _ -> case exp'' of
IFunExp _ _ -> genIFunExp pid' claferargs resPath exp''
_ -> genPExp' claferargs resPath $ PExp iType' pid' pos exp''
where
exp'' = transformExp exp'
IInt n -> CString $ show n
IDouble _ -> error "no real numbers allowed"
IStr _ -> error "no strings allowed"
transformExp :: IExp -> IExp
transformExp (IFunExp op' (e1:_))
| op' == iMin = IFunExp iMul [PExp (_iType e1) "" noSpan $ IInt (1), e1]
transformExp x@(IFunExp op' exps'@(e1:e2:_))
| op' == iXor = IFunExp iNot [PExp (Just TBoolean) "" noSpan (IFunExp iIff exps')]
| op' == iJoin && isClaferName' e1 && isClaferName' e2 &&
getClaferName e1 == this && head (getClaferName e2) == '~' =
IFunExp op' [e1{_iType = Just $ TClafer []}, e2]
| otherwise = x
transformExp x = x
genIFunExp :: String -> ClaferArgs -> [String] -> IExp -> Concat
genIFunExp pid' claferargs resPath (IFunExp op' exps') =
if (op' == iSumSet)
then genIFunExp pid' claferargs resPath (IFunExp iSumSet' [(removeright (head exps')), (getRight $ head exps')])
else if (op' == iSumSet')
then Concat (IrPExp pid') $ intl exps'' (map CString $ genOp (Alloy42 `elem` (mode claferargs)) iSumSet)
else Concat (IrPExp pid') $ intl exps'' (map CString $ genOp (Alloy42 `elem` (mode claferargs)) op')
where
intl
| op' == iSumSet' = flip $ interleave
| op' `elem` arithBinOps && length exps' == 2 = interleave
| otherwise = \xs ys -> reverse $ interleave (reverse xs) (reverse ys)
exps'' = map (optBrArg claferargs resPath) exps'
genIFunExp _ _ _ _ = error "Function genIFunExp from Alloy Generator expected a IFunExp as an argument but was given something else"
optBrArg :: ClaferArgs -> [String] -> PExp -> Concat
optBrArg claferargs resPath x = brFun (genPExp' claferargs resPath) x
where
brFun = case x of
PExp _ _ _ (IClaferId _ _ _) -> ($)
PExp _ _ _ (IInt _) -> ($)
_ -> brArg
interleave :: [Concat] -> [Concat] -> [Concat]
interleave [] [] = []
interleave (x:xs) [] = x:xs
interleave [] (x:xs) = x:xs
interleave (x:xs) ys = x : interleave ys xs
brArg :: (a -> Concat) -> a -> Concat
brArg f arg = cconcat [CString "(", f arg, CString ")"]
genOp :: Bool -> String -> [String]
genOp True op'
| op' == iPlus = [".plus[", "]"]
| op' == iSub = [".minus[", "]"]
| otherwise = genOp False op'
genOp _ op'
| op' == iSumSet = ["sum temp : "," | temp."]
| op' `elem` unOps = [op']
| op' == iPlus = [".add[", "]"]
| op' == iSub = [".sub[", "]"]
| op' == iMul = [".mul[", "]"]
| op' == iDiv = [".div[", "]"]
| op' `elem` logBinOps ++ relBinOps ++ arithBinOps = [" " ++ op' ++ " "]
| op' == iUnion = [" + "]
| op' == iDifference = [" - "]
| op' == iIntersection = [" & "]
| op' == iDomain = [" <: "]
| op' == iRange = [" :> "]
| op' == iJoin = ["."]
| op' == iIfThenElse = [" => ", " else "]
genOp _ _ = error "This should never happen"
adjustPExp :: [String] -> PExp -> PExp
adjustPExp resPath (PExp t pid' pos x) = PExp t pid' pos $ adjustIExp resPath x
adjustIExp :: [String] -> IExp -> IExp
adjustIExp resPath x = case x of
IDeclPExp q d pexp -> IDeclPExp q d $ adjustPExp resPath pexp
IFunExp op' exps' -> adjNav $ IFunExp op' $ map adjExps exps'
where
(adjNav, adjExps) = if op' == iJoin then (aNav, id)
else (id, adjustPExp resPath)
IClaferId _ _ _ -> aNav x
_ -> x
where
aNav = fst.(adjustNav resPath)
adjustNav :: [String] -> IExp -> (IExp, [String])
adjustNav resPath x@(IFunExp op' (pexp0:pexp:_))
| op' == iJoin = (IFunExp iJoin
[pexp0{_exp = iexp0},
pexp{_exp = iexp}], path')
| otherwise = (x, resPath)
where
(iexp0, path) = adjustNav resPath (_exp pexp0)
(iexp, path') = adjustNav path (_exp pexp)
adjustNav resPath x@(IClaferId _ id' _)
| id' == parent = (x{_sident = "~@" ++ (genRelName $ head resPath)}, tail resPath)
| otherwise = (x, resPath)
adjustNav _ _ = error "Function adjustNav Expect a IFunExp or IClaferID as one of it's argument but it was given a differnt IExp"
genQuant :: IQuant -> String
genQuant x = case x of
INo -> "no"
ILone -> "lone"
IOne -> "one"
ISome -> "some"
IAll -> "all"
genDecl :: ClaferArgs -> [String] -> IDecl -> Concat
genDecl claferargs resPath x = case x of
IDecl disj locids pexp -> cconcat [CString $ genDisj disj, CString " ",
CString $ intercalate ", " locids, CString " : ", genPExp claferargs resPath pexp]
genDisj :: Bool -> String
genDisj x = case x of
False -> ""
True -> "disj"
data AlloyEnv = AlloyEnv {
lineCol :: (LineNo, ColNo),
mapping :: [(Span, IrTrace)]
} deriving (Eq,Show)
mapLineCol :: Concat -> [(Span, IrTrace)]
mapLineCol code = mapping $ execState (mapLineCol' code) (AlloyEnv (firstLine, firstCol) [])
addCode :: MonadState AlloyEnv m => String -> m ()
addCode str = modify (\s -> s {lineCol = lineno (lineCol s) str})
mapLineCol' :: MonadState AlloyEnv m => Concat -> m ()
mapLineCol' (CString str) = addCode str
mapLineCol' c@(Concat srcPos' n) = do
posStart <- gets lineCol
_ <- mapM mapLineCol' n
posEnd <- gets lineCol
let flat = flatten c
raiseStart = countLeading "([" flat
deductEnd = (countTrailing ")]" flat)
modify (\s -> s {mapping = (Span (uncurry Pos $ posStart `addColumn` raiseStart) (uncurry Pos $ posEnd `addColumn` deductEnd), srcPos') : (mapping s)})
addColumn :: Interval -> Integer -> Interval
addColumn (x, y) c = (x, y + c)
countLeading :: String -> String -> Integer
countLeading c xs = toInteger $ length $ takeWhile (`elem` c) xs
countTrailing :: String -> String -> Integer
countTrailing c xs = countLeading c (reverse xs)
lineno :: (Integer, ColNo) -> String -> (Integer, ColNo)
lineno (l, c) str = (l + newLines, (if newLines > 0 then firstCol else c) + newCol)
where
newLines = toInteger $ length $ filter (== '\n') str
newCol = toInteger $ length $ takeWhile (/= '\n') $ reverse str
firstCol :: ColNo
firstCol = 1 :: ColNo
firstLine :: LineNo
firstLine = 1 :: LineNo
removeright :: PExp -> PExp
removeright (PExp _ _ _ (IFunExp _ (x : (PExp _ _ _ (IClaferId _ _ _)) : _))) = x
removeright (PExp t id' pos (IFunExp o (x1:x2:xs))) = (PExp t id' pos (IFunExp o (x1:(removeright x2):xs)))
removeright (PExp _ _ _ _) = error "Function removeright from the AlloyGenerator expects a PExp with a IFunExp inside, was given something else"
getRight :: PExp -> PExp
getRight (PExp _ _ _ (IFunExp _ (_:x:_))) = getRight x
getRight p = p