module Data.SCargot.LetBind
(
discoverLetBindings
, DiscoveryGuide(..)
, nativeGuide
, letExpand
)
where
import Control.Applicative
import qualified Data.Foldable as F
import Data.Function (on)
import Data.List (sortBy)
import Data.Maybe
import Data.Monoid
import Data.SCargot.Repr
import Data.String
import Data.Traversable ( mapAccumL )
import Data.Tuple
data DiscoveryGuide a str = Guide
{ maxLetBinds :: Int -> Int
, minExprSize :: Int
, allowRecursion :: Bool
, weighting :: SExpr a -> Int -> Int
, letMaker :: (IsString str) => str -> a
, labelMaker :: (IsString str, Monoid str) => str -> SExpr a -> a
, extractStr :: (IsString str) => a -> Maybe str
}
nativeGuide :: (str -> a) -> (str -> SExpr a -> a) -> DiscoveryGuide a str
nativeGuide letMk labelMk = Guide { maxLetBinds = const 8
, minExprSize = 5
, allowRecursion = False
, weighting = defaultWeighting
, letMaker = letMk
, labelMaker = labelMk
, extractStr = const Nothing
}
defaultWeighting :: SExpr a -> Int -> Int
defaultWeighting subexpr cnt =
let h = F.length subexpr
baseline = case subexpr of
(SCons (SAtom _) _) -> 100
_ -> 0
in (baseline + h + (cnt * 4))
discoverLetBindings :: (Monoid str, IsString str, Show str, Eq a) =>
DiscoveryGuide a str -> SExpr a -> SExpr a
discoverLetBindings guide inp =
let (inpMap,annotInp) = explore guide startingLoc inp
locs = bestBindings guide annotInp $ points inpMap
lbn = assignLBNames guide inp locs
letPart = SAtom $ letMaker guide "let"
(lbvdefs, subsInp) = substLBRefs guide lbn annotInp
in SCons letPart $ SCons lbvdefs (SCons subsInp SNil)
alwaysBindWeight :: Int
alwaysBindWeight = 1000000
bestBindings :: DiscoveryGuide a str -> ExprInfo a -> [Location a] -> [Location a]
bestBindings guide exprs locs = getMaxBest
where getMaxBest = head $
sortBy (compare `on` length) $
fmap getBestSkipping [0..2]
getBestSkipping n = snd $ snd $
foldl bestB (n, (maxbinds, [])) $
reverse $
sortBy (compare `on` fst) $
filter ((/=) 0 . fst) $
fmap (\l -> (uncurry (weighting guide) $ lwi l, l)) $
locs
bestB :: (Int, (Int, [Location a]))
-> (Int, Location a)
-> (Int, (Int, [Location a]))
bestB acc@(_, (numRemaining, binds)) (w,e) =
let subs = subBindings e binds
in if numRemaining > 0 &&
(null subs || allowRecursion guide || w >= alwaysBindWeight)
then addUnlessSkipping acc w e
else acc
subBindings x = catMaybes . fmap (isSub x)
isSub x startingFrom = do sloc <- findLocation (locId startingFrom) exprs
findLocation (locId x) sloc
addUnlessSkipping (skip, (numRemaining, binds)) w e =
let addE = (minExprSize guide, (numRemaining1, e:binds))
skipE = (skip1, (numRemaining, binds))
in if w >= alwaysBindWeight
then addE
else if numRemaining > 0 && skip == 0
then addE
else skipE
lwi l = (locExpr l, locCount l)
maxbinds = maxLetBinds guide (length locs)
type LocationId = Int
data Location a = Location { locExpr :: SExpr a
, locCount :: Int
, locId :: LocationId
}
deriving Show
data NamedLoc a = NamedLoc { nlocId :: LocationId
, nlocVar :: SExpr a
}
deriving Show
data MyMap a = MyMap { points :: [Location a]
}
startingLoc :: MyMap a
startingLoc = MyMap []
data ExprInfo a = EINil | EIAtom a | EICons LocationId (ExprInfo a) (ExprInfo a)
explore :: Eq a => DiscoveryGuide a str -> MyMap a -> SExpr a -> (MyMap a, ExprInfo a)
explore _ mymap SNil = (mymap, EINil)
explore _ mymap (SAtom a) = (mymap, EIAtom a)
explore guide mymap h@(SCons l r) =
let (lc,le) = explore guide mymap l
(rc,re) = explore guide lc r
(hm,hi) = updateMap guide h rc
in (hm, EICons hi le re)
updateMap :: Eq a => DiscoveryGuide a str -> SExpr a -> MyMap a -> (MyMap a, LocationId)
updateMap guide point mymap =
let (p, i) = addOrUpdate (points mymap)
in (mymap { points = p }, i)
where addOrUpdate [] = ([ Location { locExpr=point, locCount=succCnt(0), locId=lId} ], lId)
addOrUpdate (p:ps) = let (sm,si) = addOrUpdate ps
in if locExpr p /= point
then (p : sm, si)
else (p { locCount = succCnt(locCount p) } : ps, locId p)
lId = length (points mymap)
succCnt n = if F.length point > (minExprSize guide) then n + 1 else n
findLocation :: LocationId -> ExprInfo a -> Maybe (ExprInfo a)
findLocation loc = fndLoc
where fndLoc EINil = Nothing
fndLoc (EIAtom _) = Nothing
fndLoc e@(EICons el l r) = if el == loc then Just e else fndLoc l <|> fndLoc r
assignLBNames :: (Eq a, IsString str, Monoid str) =>
DiscoveryGuide a str -> SExpr a -> [Location a] -> [NamedLoc a]
assignLBNames guide inp = snd . mapAccumL mkNamedLoc (1::Int)
where mkNamedLoc i l = let nm = labelMaker guide suggestedName $ locExpr l
suggestedName = "var" <> fromString (show i)
in case F.find ((==) nm) inp of
Nothing -> (i+1, NamedLoc { nlocId = locId l
, nlocVar = SAtom nm
})
Just _ -> mkNamedLoc (i+1) l
substLBRefs :: Eq a =>
DiscoveryGuide a str -> [NamedLoc a] -> ExprInfo a
-> (SExpr a, SExpr a)
substLBRefs _ nlocs = swap . fmap declVars . swap . subsRefs []
where subsRefs b EINil = (b, SNil)
subsRefs b (EIAtom a) = (b, SAtom a)
subsRefs b (EICons i l r) = let (b',l') = subsRefs b l
(c',r') = subsRefs b' r
here = SCons l' r'
in case hasBinding i of
Nothing -> (c', here)
Just loc -> (((nlocVar loc), here) : c', (SCons (nlocVar loc) SNil))
hasBinding i = F.find ((==) i . nlocId) nlocs
declVars = foldl addVar SNil . foldl addVarIfUnique []
addVarIfUnique vl v@(vn,_) = case lookup vn vl of
Nothing -> v : vl
Just _ -> vl
addVar vl (vn,vv) = SCons (SCons vn (SCons vv SNil)) vl
letExpand :: (Eq a, Show a, Eq str, IsString str) =>
(a -> Maybe str) -> SExpr a -> SExpr a
letExpand atomToText = findExpLet
where findExpLet (SCons (SAtom a) (SCons lbvdefs (SCons subsInp SNil))) =
if atomToText a == Just "let"
then expLet lbvdefs subsInp
else SCons (SAtom a) (SCons (findExpLet lbvdefs) (SCons (findExpLet subsInp) SNil))
findExpLet e = e
expLet lb = expandWith (bindings lb)
bindings = parseVar []
parseVar vdefs (SCons (SCons vn (SCons vv SNil)) r) = (vn, vv) : parseVar vdefs r
parseVar vdefs SNil = vdefs
parseVar _ e = error $ "Expected a var, got: " <> show e
expandWith _ SNil = SNil
expandWith vdefs e@(SCons v@(SAtom _) SNil) =
case lookup v vdefs of
Nothing -> e
Just vv -> expandWith vdefs vv
expandWith vdefs e@(SCons l r) =
case lookup e vdefs of
Nothing -> SCons (expandWith vdefs l) (expandWith vdefs r)
Just vv -> expandWith vdefs vv
expandWith _ e@(SAtom _) = e