{-# LANGUAGE GeneralizedNewtypeDeriving, UndecidableInstances, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, NamedFieldPuns, TupleSections #-} {- Copyright (C) 2012-2013 Jimmy Liang, Kacper Bak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -} module Language.Clafer.Intermediate.GLPKScopeAnalyzer (glpkScopeAnalysis) where import Language.Clafer.Front.Absclafer hiding (Path) import qualified Language.Clafer.Intermediate.Intclafer as I import Language.Clafer.Intermediate.Analysis import Language.Clafer.Intermediate.ResolverType import Control.Applicative (Applicative(..), (<$>)) import Control.Monad import Control.Monad.List import Control.Monad.LPMonad import Control.Monad.Maybe import Control.Monad.Reader import Control.Monad.State import Data.Either import Data.LinearProgram hiding (constraints) import Data.List import Data.Map () import qualified Data.Map as Map import Data.Maybe import System.IO.Unsafe import Text.Parsec.Combinator import Text.Parsec.Error import Text.Parsec.Pos import Text.Parsec.Prim import Text.Parsec.String () {------------------------------------------------------------ ---------- Linear programming ------------------------------ ------------------------------------------------------------} -- | Compute scopes for clafers by solving a system of linear equations glpkScopeAnalysis :: I.IModule -> [(String, Integer)] glpkScopeAnalysis imodule = intScope ++ scopes where intScope = if bitwidth > 4 then return ("int", bitwidth) else fail "Bitwidth less than default." bitwidth = bitwidthAnalysis (constants ++ map snd scopes) scopes = removeZeroes $ removeRoot $ removeAux $ -- unsafePerformIO should be safe (?) -- We aren't modifying any global state. -- If we don't use unsafePerformIO, then we have to be inside the IO monad and -- makes things really ugly. Might as well contain the ugliness in here. case unsafePerformIO solution of (Success, Just (_, s)) -> Map.toList $ Map.map round s _ -> [] -- No solution ((_, constants), analysis) = runScopeAnalysis run $ gatherInfo imodule run = do setConstraints abstracts' <- clafers `suchThat` isAbstract constants' <- constantsAnalysis return (abstracts', constants') solution = {-trace (show $ unsafePerformIO $ writeLP "TESTTT" analysis) $-} glpSolveVars mipDefaults{msgLev = MsgOff} $ analysis -- Any scope that is 0 will take the global scope of 1 instead. removeZeroes = filter ((/= 0) . snd) -- The root is implied and not and not part of the actual solution. removeRoot = filter ((/= rootUid) . fst) -- Auxilary variables are only part of the computation, not the solution. removeAux = filter (not . (uniqNameSpace `isPrefixOf`) . fst) -- The scope for abstract clafers are removed. Alloy doesn't need it. Makes -- it easier use since user can increase the scope of subclafers without -- needing to increase the scope of the abstract Clafer. --removeAbstracts = filter (not . (`elem` map uid abstracts) . fst) bitwidthAnalysis :: [Integer] -> Integer bitwidthAnalysis constants = toInteger $ 1 + fromJust (findIndex (\x -> all (`within` x) constants) bitRange) where within a (minB, maxB) = a >= minB && a <= maxB bitRange = [(-2^i, 2^i-1) | i <- ([0..]::[Integer])] -- Returns all constant literals constantsAnalysis :: ScopeAnalysis [Integer] constantsAnalysis = do cons <- constraintsUnder anything `select` snd return $ mapMaybe integerConstant [I._exp sub | con <- cons, sub <- subexpressions con] where integerConstant (I.IInt i) = Just i integerConstant _ = Nothing -- (-1) for infinity data Between = Between Integer Integer deriving Show --atLeastOne :: Between -> Bool --atLeastOne (Between i _) = i >= 1 {- overlap :: Between -> Between -> Maybe Between overlap (Between l1 h1) (Between l2 h2) | l1 > h2 && h2 /= -1 = Nothing | l2 > h1 && h1 /= -1 = Nothing | otherwise = Just $ Between (maxx l1 l2) (minn h1 h2) where minn (-1) b = b minn a (-1) = a minn a b = min a b maxx (-1) _ = -1 maxx _ (-1) = -1 maxx a b = max a b overlapM :: Maybe Between -> Maybe Between -> Maybe Between overlapM a b = do a' <- a b' <- b overlap a' b' -} -- Multiplies two positive integers where -1=infinity mult :: Integer -> Integer -> Integer mult (-1) _ = -1 mult _ (-1) = -1 mult a b = a * b simpleAnalysis :: ScopeAnalysis [(String, Between)] simpleAnalysis = do root <- claferWithUid rootUid analysis <- simpleAnalysis' root (Between 1 1) --moreAnalysis <- simpleConstraintAnalysis analysis return analysis where simpleAnalysis' cur cb@(Between l h) = runListT $ return (uid cur, cb) `mplus` do child <- foreach $ (anything |^ cur) `select` fst let b | groupLow cur == 0 && groupHigh cur == -1 = Between (low child * l) (high child `mult` h) | otherwise = Between 0 (-1) foreach (simpleAnalysis' child b) {- mergeAnalysis analysis = [(n, fromJust x) | (n, b) <- combine analysis, let x = foldr1 overlapM $ map Just b, isJust x] simpleConstraintAnalysis :: [(String, Between)] -> ScopeAnalysis [(String, Between)] simpleConstraintAnalysis analysis = mergeAnalysis <$> simpleConstraintAnalysis' analysis simpleConstraintAnalysis' analysis = runListT $ do (curThis, cons) <- foreach $ constraintsUnder anything constraintBetween curThis (I._exp cons) where constraintBetween _ I.IDeclPExp {I._quant = I.ISome, I._oDecls = [], I._bpexp} = do let t = map tLexeme $ fromMaybe [] $ unfoldJoins bpexp guard (not $ null t) guard ("this" `notElem` t) guard ("parent" `notElem` t) guard ("ref" `notElem` t) msum $ map someStep t constraintBetween curThis I.IFunExp{I._op = "&&", I._exps = [exp1, exp2]} = constraintBetween curThis (I._exp exp1) `mplus` constraintBetween curThis (I._exp exp2) constraintBetween _ _ = mzero someStep step = do parent <- parentOf step let parentBetween = fromMaybe (error $ "Missing parent " ++ parent) $ lookup parent analysis guard $ atLeastOne parentBetween return (step, Between 1 $ -1) -} setConstraints :: ScopeAnalysis () setConstraints = do simpleAnalysis p <- flatten withExtraClafers p $ do optFormula colonConstraints refConstraints parentConstraints constraintConstraints (var rootUid) `equalTo` 1 optFormula :: ScopeAnalysis () optFormula = do setDirection Min c <- clafers let concretes = [uid concrete | concrete <- c, isConcrete concrete, isDerived concrete, not $ uniqNameSpace `isPrefixOf` uid concrete] setObjective $ varSum concretes parentConstraints :: ScopeAnalysis () parentConstraints = runListT_ $ do -- forall child under parent ... (child, parent) <- foreach $ anything |^ anything let uchild = uid child let uparent = uid parent if low child == high child -- Saves us one constraint then do var uchild `equal` (low child *^ var uparent) else do -- ... scope_this <= scope_parent * low-card(this) ... var uchild `geq` (low child *^ var uparent) -- ... scope_this >= scope_parent * high-card(this) ... -- high == -1 implies high card is unbounded if high child /= -1 then var uchild `leq` (high child *^ var uparent) {- - A - B * - [#B = 4] - - Need this constraint so that #A=1 -} else (smallM *^ var uchild) `leq` var uparent -- Use integer's not doubles setVarKind uchild IntVar setVarKind uparent IntVar refConstraints :: ScopeAnalysis () refConstraints = runListT_ $ do -- for all uids of any clafer the refs another uid ... (sub, sup) <- foreach $ (anything |-> anything) `suchThat` (isDerived . superClafers) let usub = uid sub let usup = uid sup aux <- testPositive usub -- scope_sup >= low-card(sub) var usup `geq` ((max 1 $ low sub) *^ var aux) colonConstraints :: ScopeAnalysis () colonConstraints = runListT_ $ do -- forall c in the set of clafers' uid ... c <- foreach $ clafers `suchThat` isDerived -- ... find all uids of any clafer that extends c (only colons) ... subs <- findAll $ (anything |: c) `select` (uid . subClafers) when (not $ null subs) $ -- ... then set the constraint scope_C = sum scope_subs var (uid c) `equal` varSum subs flatten :: ScopeAnalysis [SClafer] flatten = runListT $ do abs' <- clafers `suchThat` isAbstract (c, s) <- foreach $ anything |: anything ListT $ runReaderT (addChildren (map uid abs') (Part [uid c, uid s]) (Part [])) [] addChildren :: MonadAnalysis m => [String] -> Part -> Part -> m [SClafer] addChildren abs' (Part steps) ss@(Part supSteps) = do let parBase = last steps chis <- directChildrenOf parBase achis <- forM chis $ \chi -> do let chiP = Part $ init steps ++ [chi] let par = Part steps let supP = Part $ supSteps ++ [chi] chiC <- claferWithUid chi let s = SClafer (reifyPartName chiP) chi False (low chiC) (high chiC) (groupLow chiC) (groupHigh chiC) (Just $ reifyPartName par) (Just $ Colon $ reifyPartName supP) (constraints chiC) return s <:> addChildren abs' chiP ss col <- runMaybeT $ colonOf parBase case col of Just col' -> do acol <- addChildren abs' (Part $ steps ++ [col']) (Part $ supSteps ++ [parBase]) return $ concat achis ++ acol Nothing -> return $ concat achis where notAbs = not . (`elem` abs') reifyPartName (Part (t : target)) = reifyPartName' $ t : filter notAbs target reifyPartName (Part []) = error "Function reifyPartName from GLPKScopeAnalyzer expects a non empty Part, but was given one!" -- This should never happen reifyPartName' [target] = target reifyPartName' target = uniqNameSpace ++ "reify_" ++ intercalate "_" target data Path = Path {parts::[Part]} deriving (Eq, Ord, Show) data Part = Part {steps::[String]} deriving (Eq, Ord, Show) {-data Expr = This {path::Path, eType::I.IType} | Global {path::Path, eType::I.IType} | Const Integer | Concat {paths::[Expr], eType::I.IType} | Positive {allPaths :: [Path], num::Integer, eType::I.IType} deriving Show-} data Expr = This Path I.IType | Global Path I.IType | Const Integer | Concat [Expr] I.IType | Positive [Path] Integer I.IType deriving Show eType :: Expr -> I.IType eType (This _ e) = e eType (Global _ e) = e eType (Concat _ e) = e eType (Positive _ _ e) = e eType (Const _) = error "Function eType from GLPK did not expect a Const" isThis :: Expr -> Bool isThis This{} = True isThis _ = False isGlobal :: Expr -> Bool isGlobal Global{} = True isGlobal _ = False {-isConst :: Expr -> Bool isConst Const{} = True isConst _ = False-} parentOfPart :: MonadAnalysis m => Part -> m Part parentOfPart (Part s) = do s' <- parentOf $ last s cs' <- claferWithUid s' return $ if isAbstract cs' then Part $ init s else Part $ init s ++ [s'] {- - Turns constraints that look like: - - [ A in List - B in List ] - - to - - [ A, B in List ] -} optimizeInConstraints :: [I.PExp] -> [I.PExp] optimizeInConstraints constraints = noOpt ++ opt where (noOpt, toOpt) = partitionEithers (constraints >>= partitionConstraint) opt = [ unionPExpAll (map fst inSame) `inPExp` snd (head inSame) | inSame <- groupBy (testing' $ syntaxOf . snd) $ sortBy (comparing' snd) toOpt ] inPExp a b = I.PExp (Just I.TBoolean) "" noSpan $ I.IFunExp "in" [a, b] unionPExpAll es = foldr1 unionPExp es unionPExp a b = I.PExp (liftM2 (+++) (I._iType a) (I._iType b)) "" noSpan $ I.IFunExp "++" [a, b] partitionConstraint I.PExp{I._exp = I.IFunExp {I._op = "in", I._exps = [exp1, exp2]}} = return $ Right (exp1, exp2) partitionConstraint I.PExp{I._exp = I.IFunExp {I._op = "&&", I._exps = [exp1, exp2]}} = partitionConstraint exp1 `mplus` partitionConstraint exp2 partitionConstraint e = return $ Left e testing' f a b = f a == f b comparing' f a b = f a `compare` f b {- - Phone * - - [all p : Phone | ] - - becomes - - Phone * - [] -} optimizeAllConstraints :: MonadAnalysis m => SClafer -> [I.PExp] -> m [(SClafer, I.PExp)] optimizeAllConstraints curThis constraints = runListT $ partitionConstraint =<< foreachM constraints where partitionConstraint I.PExp{I._exp = I.IDeclPExp I.IAll [I.IDecl _ [decl] I.PExp{I._exp = I.IClaferId{I._sident}}] bpexp} = do under <- claferWithUid _sident return (under, rename decl bpexp) partitionConstraint I.PExp{I._exp = I.IFunExp {I._op = "&&", I._exps = [exp1, exp2]}} = partitionConstraint exp1 `mplus` partitionConstraint exp2 partitionConstraint e = return (curThis, e) rename :: String -> I.PExp -> I.PExp rename f p@I.PExp{I._exp = exp'} = p{I._exp = renameIExp exp'} where renameIExp (I.IFunExp op exps) = I.IFunExp op $ map (rename f) exps renameIExp (I.IDeclPExp quant oDecls bpexp) = I.IDeclPExp quant (map renameDecl oDecls) $ rename f bpexp renameIExp (I.IClaferId modName sident isTop) | f == sident = I.IClaferId modName "this" isTop | otherwise = I.IClaferId modName sident isTop renameIExp i = i renameDecl (I.IDecl isDisj decls body) | f `elem` decls = I.IDecl isDisj decls body -- Not a free variable | otherwise = I.IDecl isDisj decls $ rename f body -- Is a free variable optConstraintsUnder :: MonadAnalysis m => SClafer -> m [(SClafer, [I.PExp])] optConstraintsUnder clafer = do cons <- constraintsUnder clafer `select` snd allCons <- optimizeAllConstraints clafer cons let inCons = [(fst $ head c, optimizeInConstraints $ map snd c) | c <- groupBy (testing' $ uid . fst) $ sortBy (comparing' $ uid . fst) allCons] return inCons where testing' f a b = f a == f b comparing' f a b = f a `compare` f b constraintConstraints :: MonadScope m => m () constraintConstraints = do runListT_ $ do clafer <- foreach clafers (supThis, cons) <- foreach $ optConstraintsUnder clafer con <- foreachM cons curThis <- if isAbstract supThis then foreach $ colonsTo supThis else return supThis constraint <- foreach $ scopeConstraint curThis con oneConstraint curThis constraint where --base (Part steps) = last steps oneConstraint c (e1, con, e2) = void $ runMaybeT $ oneConstraintOneWay c e1 con e2 `mplus` oneConstraintOneWay c e2 (reverseCon con) e1 oneConstraintOneWay c@SClafer{uid} e1 con e2 = oneConstraint' e1 e2 where oneConstraint' _ (This (Path []) _) = mzero oneConstraint' _ (Global (Path []) _) = mzero oneConstraint' (This (Path []) _) (This (Path parts) _) = return (var uid) `comp` reifyVar (last parts) oneConstraint' (This (Path []) _) (Global (Path parts) _) = return (var uid) `comp` reifyVar (last parts) oneConstraint' (Positive [Path []] _ _) _ = mzero oneConstraint' _ (Positive [Path []] _ _) = mzero oneConstraint' (Global (Path gParts) _) (Positive allPaths claf _) = do aux <- testPositives (map (reifyVarName . last . parts) allPaths) reifyVar (last gParts) `comp` return (claf *^ var aux) oneConstraint' (This (Path parts) _) (Const constant) | con == EQU = oneConstraintOneWay c e1 LEQ e2 >> oneConstraintOneWay c e1 GEQ e2 | con `elem` [GTH, GEQ] = foldM_ mkCon 1 (reverse parts) | con `elem` [LTH, LEQ] = reifyVar (last parts) `comp` (return $ (fromInteger constant :: Double) *^ var uid) where mkCon :: MonadScope m => Integer -> Part -> m Integer mkCon multiplier part = do let frac = (1 / fromInteger multiplier) * fromInteger constant :: Double (reifyVar part) `comp` return (frac *^ var uid) mult multiplier <$> prod part oneConstraint' (Global (Path parts) _) (Const constant) | con == EQU = oneConstraintOneWay c e1 LEQ e2 >> oneConstraintOneWay c e1 GEQ e2 | con `elem` [GTH, GEQ] = do k <- testPositive uid foldM_ (mkCon k) 1 (reverse parts) | con `elem` [LTH, LEQ] = reifyVar (last parts) `compTo` (return $ fromInteger constant) where mkCon :: MonadScope m => String -> Integer -> Part -> m Integer mkCon pos (-1) part = do (reifyVar part) `comp` return (var pos) return (-1) mkCon pos multiplier part = do let frac = (1 / fromInteger multiplier) * fromInteger constant :: Double (reifyVar part) `comp` return (frac *^ var pos) mult multiplier <$> prod part oneConstraint' (This (Path parts1) _) (This (Path parts2) _) = reifyVar (last parts1) `comp` reifyVar (last parts2) oneConstraint' (Global (Path parts1) _) (Global (Path parts2) _) = reifyVar (last parts1) `comp` reifyVar (last parts2) oneConstraint' (Global (Path parts) _) (Concat exprs _) = if all isGlobal exprs then reifyVar (last parts) `comp` reifyVars [last p | Global (Path p) _ <- exprs] else mzero oneConstraint' (This (Path parts) _) (Concat exprs _) = if all isGlobal exprs then do let vs = [last p | Global (Path p) _ <- exprs] claf <- mapM (claferWithUid . last . steps) $ vs s <- mapM constantCard claf p <- parentOfPart $ last parts reifyVar (last parts) `comp` ((sum s *^) <$> reifyVar p) else if all isThis exprs then reifyVar (last parts) `comp` reifyVars [last p | This (Path p) _ <- exprs] else mzero oneConstraint' _ _ = mzero constantCard SClafer{low, high} | low == high = return low | otherwise = mzero prod (Part steps) = foldr1 mult <$> mapM (return . high <=< claferWithUid) steps comp x y = do x' <- x y' <- y case con of LTH -> (x' ^-^ y') `leqTo` (-smallM) LEQ -> x' `leq` y' EQU -> x' `equal` y' GTH -> (x' ^-^ y') `geqTo` smallM GEQ -> x' `geq` y' compTo x y = do x' <- x y' <- y case con of LTH -> x' `leqTo` (y' - smallM) LEQ -> x' `leqTo` y' EQU -> x' `equalTo` y' GTH -> x' `geqTo` (y' + smallM) GEQ -> x' `geqTo` y' reifyVar p = return (var $ reifyVarName p) reifyVars p = return (varSum $ map reifyVarName p) reifyVarName (Part [target]) = target reifyVarName (Part target) = uniqNameSpace ++ "reify_" ++ intercalate "_" target {- isAbstractPart (Part [_]) = False isAbstractPart _ = True reifiedSuper (Part steps) = do let (b : s : rest) = reverse steps ss <- colonOf s sss <- runMaybeT $ colonUid ss if isNothing sss then return $ Part $ reverse $ b : rest else return $ Part $ reverse $ b : ss : rest -- TODO: correct? siblingParts (Part (conc : abst)) = do conc' <- claferWithUid conc sup <- runMaybeT $ colonOf conc' case sup of Nothing -> return [Part $ conc : abst] Just sup' -> runListT $ do (sub, _) <- foreach $ anything |: sup' return $ Part $ uid sub : abst siblingParts [] = error "Function siblingParts from GLpkScopeAnalyzer expects a non empty list, given an empty one!" -- This should never happen reifyPart (Part steps) = do as <- claferWithUid (last steps) >>= nonTopAncestors forM as $ \a -> return $ Part $ init steps ++ [uid a] nonTopAncestors child = do parent <- parentOf child if uid parent == rootUid then return [] else (++ [child]) `fmap` nonTopAncestors parent -} data Con = EQU | LTH | LEQ | GTH | GEQ deriving (Eq, Ord, Show) reverseCon :: Con -> Con reverseCon EQU = EQU reverseCon LTH = GTH reverseCon LEQ = GEQ reverseCon GTH = LTH reverseCon GEQ = LEQ data Limit = Exact {lExpr::Expr} | AtLeast {lExpr::Expr} deriving Show scopeConstraint :: MonadScope m => SClafer -> I.PExp -> m [(Expr, Con, Expr)] scopeConstraint curThis pexp = runListT $ scopeConstraint' $ I._exp pexp where scopeConstraint' I.IFunExp {I._op = "&&", I._exps} = msum $ map (scopeConstraint' . I._exp) _exps scopeConstraint' I.IDeclPExp {I._quant = I.ISome, I._oDecls = [], I._bpexp} = parsePath curThis _bpexp `greaterThanEqual` constant (1::Integer) scopeConstraint' I.IDeclPExp {I._quant = I.ISome, I._oDecls} = msum $ map pathAndMultDecl _oDecls where pathAndMultDecl I.IDecl {I._isDisj = True, I._decls, I._body} = parsePath curThis _body `greaterThanEqual` constant (length _decls) pathAndMultDecl I.IDecl {I._isDisj = False, I._body} = parsePath curThis _body `greaterThanEqual` constant (1::Integer) scopeConstraint' I.IDeclPExp {I._quant = I.IOne, I._oDecls = [], I._bpexp} = parsePath curThis _bpexp `eqTo` constant (1::Integer) scopeConstraint' I.IDeclPExp {I._quant = I.IOne, I._oDecls} = do oDecl <- foreachM _oDecls parsePath curThis (I._body oDecl) `eqTo` constant (1::Integer) scopeConstraint' I.IFunExp {I._op, I._exps = [exp1, exp2]} | _op == "in" = inConstraint1 exp1 exp2 `mplus` inConstraint2 exp1 exp2 | _op == "=" = equalConstraint1 exp1 exp2 `mplus` equalConstraint2 exp1 exp2 | _op == "<" = scopeConstraintNum exp1 `lessThan` scopeConstraintNum exp2 | _op == "<=" = scopeConstraintNum exp1 `lessThanEqual` scopeConstraintNum exp2 | _op == ">" = scopeConstraintNum exp1 `greaterThan` scopeConstraintNum exp2 | _op == ">=" = scopeConstraintNum exp1 `greaterThanEqual` scopeConstraintNum exp2 | _op == "<=>" = (exp1 `implies` exp2) `mplus` (exp2 `implies` exp1) | _op == "=>" = exp1 `implies` exp2 scopeConstraint' _ = mzero implies exp1 exp2 = do e1 <- scopeConstraint' $ I._exp exp1 e2 <- scopeConstraint' $ I._exp exp2 case (e1, e2) of ((This thisPath t1, GEQ, Const 1), (Global globalPath t0, comp, Positive allPaths c t2)) -> return $ (Global globalPath t0, comp, Positive (thisPath : allPaths) c $ t1 +++ t2) ((This thisPath e1', GEQ, Const 1), (Global globalPath e2', comp, Const c)) -> return $ (Global globalPath e2', comp, Positive [thisPath] c e1') ((Global path1 t1, GEQ, Const 1), (Global path2 t0, comp, Positive allPaths c t2)) -> return $ (Global path2 t0, comp, Positive (path1 : allPaths) c $ t1 +++ t2) ((Global path1 e1', GEQ, Const 1), (Global path2 e2', comp, Const c)) -> return $ (Global path2 e2', comp, Positive [path1] c e1') ((t1@(This (Path [thisPart1]) _), GEQ, Const 1), (t2@(This (Path [_]) _), GEQ, Const 1)) -> do c <- claferWithUid $ last $ steps thisPart1 guard (high c == 1) return (t2, GEQ, t1) _ -> mzero equalConstraint1 exp1 exp2 = do l1 <- scopeConstraintSet exp1 l2 <- scopeConstraintSet exp2 case (l1, l2) of (Exact e1, Exact e2) -> return e1 `eqTo` return e2 (AtLeast e1, Exact e2) -> return e1 `greaterThanEqual` return e2 (Exact e1, AtLeast e2) -> return e1 `lessThanEqual` return e2 _ -> mzero equalConstraint2 exp1 exp2 = scopeConstraintNum exp1 `eqTo` scopeConstraintNum exp2 -- exp1 in exp2 inConstraint1 exp1 exp2 = do l1 <- scopeConstraintSet exp1 l2 <- scopeConstraintSet exp2 case l2 of Exact e2 -> return (lExpr l1) `lessThanEqual` return e2 _ -> mzero inConstraint2 exp1 exp2 = scopeConstraintNum exp1 `lessThanEqual` scopeConstraintNum exp2 scopeConstraintSet I.PExp {I._exp = I.IFunExp {I._op = "++", I._exps = [e1, e2]}} = do l1' <- scopeConstraintSet e1 l2' <- scopeConstraintSet e2 i <- intersects (eType $ lExpr l1') (eType $ lExpr l2') if i then return $ AtLeast $ lExpr l1' else return $ combineDisjoint l1' l2' scopeConstraintSet x = Exact <$> parsePath curThis x combineDisjoint (Exact e1) (Exact e2) = Exact (Concat ([e1, e2] >>= flattenConcat) $ eType e1 +++ eType e2) combineDisjoint l1 l2 = AtLeast (Concat ([e1, e2] >>= flattenConcat) $ eType e1 +++ eType e2) where e1 = lExpr l1 e2 = lExpr l2 flattenConcat (Concat es _) = es >>= flattenConcat flattenConcat e = [e] scopeConstraintNum I.PExp {I._exp = I.IInt const'} = constant const' scopeConstraintNum I.PExp {I._exp = I.IFunExp {I._op = "#", I._exps = [path]}} = parsePath curThis path scopeConstraintNum _ = mzero constant :: (Monad m, Integral i) => i -> m Expr constant = return . Const . toInteger greaterThan = liftM2 (,GTH,) greaterThanEqual = liftM2 (,GEQ,) lessThan = liftM2 (,LTH,) lessThanEqual = liftM2 (,LEQ,) eqTo = liftM2 (,EQU,) {- - We use the stack to push every abstraction we traverse through. - For example: - - abstract A - B ? - C : D ? - abstract D - E ? - F : A - G : A - H : A - - [some F.B.C.E] - [some G.B.C.E] - - The first constraint's final stack will look like ["C" ,"F"] - Hence the linear programming equation will look like: - - scope_F_C_E >= scope_root - - Adding the second constraint: - - scope_G_C_E >= scope_root - scope_E >= scope_F_C_E + scope_G_C_E (*) - - Solving the minimization should have scope_E = 2 in its solution. - The (*) equation is set in constraintConstraints -} parsePath :: MonadScope m => SClafer -> I.PExp -> m Expr parsePath start pexp = do start' <- claferWithUid (origUid start) parsePath2 start' pexp parsePath2 :: MonadScope m => SClafer -> I.PExp -> m Expr parsePath2 start pexp = do root <- claferWithUid rootUid case unfoldJoins pexp of Just unfold -> do match <- patternMatch parsePath' (ParseState root []) unfold either (fail . show) return match Nothing -> fail "Cannot unfold." where asPath :: [[String]] -> Path asPath parts = Path [Part part | part <- parts, not $ null part] parsePath' = (This <$> (asPath <$> parseThisPath) <*> getThisType) <|> (Global <$> (asPath <$> parseNonthisPath) <*> getThisType) getThisType = do t <- getThis return $ fromJust $ fromUnionType [uid t] parseThisPath = do t <- _this_ do many1 _parent_ return [[uid start]] <|> (follow t >> parseNonthisPath) parseNonthisPath = do paths <- many (step >>= follow) lifo <- popStack let end = if null paths then [] else [last paths] let result = reverse $ end ++ map uid lifo do _ref_ >>= follow -- recurse rec <- parseNonthisPath return $ result : rec <|> return [result] -- Step handles non-this token. step :: MonadScope m => ParseT m String step = _parent_ <|> _directChild_ <|> try (pushThis >> _indirectChild_) -- Update the state of where "this" is. -- Path is one step away from where "this" is. follow :: MonadScope m => String -> ParseT m String follow path = do curThis <- getThis case path of "this" -> putThis start "parent" -> lift (parentOf curThis) >>= putThis -- the parent is now "this" "ref" -> lift (refOf curThis) >>= putThis -- the ref'd Clafer is now "this" u -> lift (claferWithUid u) >>= putThis return path {------------------------------------------------------------ ---------- Internals --------------------------------------- ------------------------------------------------------------} newtype ScopeAnalysis a = ScopeAnalysis (VSupplyT (AnalysisT (LPM String Double)) a) deriving (Monad, Functor, MonadState (LP String Double), MonadSupply Var, MonadReader Info, MonadAnalysis) class (MonadAnalysis m, MonadState (LP String Double) m, MonadSupply Var m) => MonadScope m instance (MonadAnalysis m, MonadState (LP String Double) m, MonadSupply Var m) => MonadScope m runScopeAnalysis :: ScopeAnalysis a -> Info -> (a, LP String Double) runScopeAnalysis (ScopeAnalysis s) info = runLPM $ runAnalysisT (runVSupplyT s) info -- Unfold joins -- If the expression is a tree of only joins, then this function will flatten -- the joins into a list. -- Otherwise, returns an empty list. unfoldJoins :: Monad m => I.PExp -> m [Token] unfoldJoins pexp = unfoldJoins' pexp where unfoldJoins' I.PExp{I._exp = (I.IFunExp "." args)} = return $ args >>= (fromMaybe [] . unfoldJoins) unfoldJoins' I.PExp{I._inPos, I._exp = I.IClaferId{I._sident}} = return $ [Token (spanToSourcePos _inPos) _sident] unfoldJoins' _ = fail "not a join" -- Variables starting with "_aux_" are reserved for creating -- new variables at runtime. uniqNameSpace :: String uniqNameSpace = "_aux_" uniqVar :: MonadScope m => m String uniqVar = do c <- supplyNew return $ uniqNameSpace ++ show (varId c) {- - Create a new variable "aux". If - v == 0 -> aux == 0 - v > 0 -> aux == 1 - - pre: v >= 0 and v is integer -} testPositive :: MonadScope m => String -> m String testPositive v = do aux <- uniqVar var aux `leq` var v var aux `geq` (smallM *^ var v) var aux `leqTo` 1 setVarKind aux IntVar return aux {- - Create a new variable "aux". If - all v == 0 -> aux == 0 - all v > 0 -> aux == 1 - - pre: all v >= 0 and all v is integer -} testPositives :: MonadScope m => [String] -> m String testPositives [v] = testPositive v testPositives vs = do auxs <- mapM testPositive vs aux <- uniqVar (length vs *^ var aux) `equal` varSum auxs a <- uniqVar (var a ^-^ var aux) `geqTo` (-0.9999) -- Buffer for floating point inaccuracies (var a ^-^ var aux) `leqTo` 0.0001 -- Buffer for floating point inaccuracies setVarKind a IntVar return a {- - smallM cannot be too small. For example, with glpk - 0.000001 * 9 = 0 -} smallM :: Double smallM = 0.0005 -- 0.00001 {- - - Parsing - -} data Token = Token {tPos::SourcePos, tLexeme::String} deriving Show data ParseState = ParseState {psThis::SClafer, -- "this" psStack::[SClafer] -- the list of all the abstract Clafers traversed } deriving Show type ParseT = ParsecT [Token] ParseState -- Where "this" refers to. getThis :: MonadScope m => ParseT m SClafer getThis = do s <- getState return (psThis s) -- Update where "this" refers to. putThis :: MonadScope m => SClafer -> ParseT m () putThis newThis = do state' <- getState putState $ state'{psThis = newThis} popStack :: MonadScope m => ParseT m [SClafer] popStack = do state' <- getState let stack = psStack state' putState state'{psStack = []} return stack pushThis :: MonadScope m => ParseT m () pushThis = do state' <- getState putState $ state'{psStack = psThis state' : psStack state'} -- Parser combinator for "this" _this_ :: MonadScope m => ParseT m String _this_ = satisfy (== "this") -- Parser combinator for "parent" _parent_ :: MonadScope m => ParseT m String _parent_ = satisfy (== "parent") -- Parser combinator for "ref" _ref_ :: MonadScope m => ParseT m String _ref_ = satisfy (== "ref") -- Parser combinator for a uid that is not "this", "parent", or "ref" _child_ :: MonadScope m => ParseT m String _child_ = satisfy (not . (`elem` ["this", "parent", "ref"])) -- Parser combinator for a uid of direct child. _directChild_ :: MonadScope m => ParseT m String _directChild_ = try $ do curThis <- getThis clafer <- _child_ >>= lift . claferWithUid check <- lift $ isDirectChild clafer curThis when (not check) $ unexpected $ (uid clafer) ++ " is not a direct child of " ++ (uid curThis) return $ uid clafer -- Parser combinator for a uid of indirect child. _indirectChild_ :: MonadScope m => ParseT m String _indirectChild_ = try $ do curThis <- getThis clafer <- _child_ >>= lift . claferWithUid check <- lift $ isIndirectChild clafer curThis when (not check) $ unexpected $ (uid clafer) ++ " is not an indirect child of " ++ (uid curThis) return $ uid clafer satisfy :: MonadScope m => (String -> Bool) -> ParseT m String satisfy f = tLexeme <$> tokenPrim (tLexeme) (\_ c _ -> tPos c) (\c -> if f $ tLexeme c then Just c else Nothing) spanToSourcePos :: Span -> SourcePos spanToSourcePos (Span (Pos l c) _) = (newPos "" (fromInteger l) (fromInteger c)) patternMatch :: MonadScope m => ParseT m a -> ParseState -> [Token] -> m (Either ParseError a) patternMatch parse' state' = runParserT (parse' <* eof) state' "" {- - - Utility functions - -} subexpressions :: I.PExp -> [I.PExp] subexpressions p@I.PExp{I._exp = exp'} = p : subexpressions' exp' where subexpressions' I.IDeclPExp{I._oDecls, I._bpexp} = concatMap (subexpressions . I._body) _oDecls ++ subexpressions _bpexp subexpressions' I.IFunExp{I._exps} = concatMap subexpressions _exps subexpressions' _ = [] instance MonadSupply s m => MonadSupply s (ListT m) where supplyNew = lift supplyNew instance MonadSupply s m => MonadSupply s (MaybeT m) where supplyNew = lift supplyNew instance MonadSupply s m => MonadSupply s (ParsecT a b m) where supplyNew = lift supplyNew