{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
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, intercalate )
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, Eq str, Eq a, Show 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
varNameCollisions = verifyNamesUnique guide lbn inp
letPart = SAtom $ letMaker guide "let"
(lbvdefs, subsInp) = substLBRefs guide lbn annotInp
in if null varNameCollisions
then if null lbn
then inp
else SCons letPart $ SCons lbvdefs (SCons subsInp SNil)
else error $ verificationFailureReport locs varNameCollisions
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, (numRemaining-1, e:binds))
skipE = (skip-1, (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 :: (Show a, Eq a, IsString str, Monoid str) =>
DiscoveryGuide a str -> SExpr a -> [Location a] -> [NamedLoc a]
assignLBNames guide inp = snd . mapAccumL mkNamedLoc (1::Int, 0::Int)
where mkNamedLoc (i,t) l = let nm = labelMaker guide suggestedName $ locExpr l
suggestedName = "var" <> fromString (show i)
in case F.find ((==) nm) inp of
Nothing -> ((i+1,0), NamedLoc { nlocId = locId l
, nlocVar = SAtom nm
})
Just _ -> if t < 100
then mkNamedLoc (i+1,t+1) l
else error $ "Too many failed attempts \
\to generate a unique let var name: " <> show nm
type UniquenessResult a = [(NamedLoc a, [Either (NamedLoc a) (SExpr a)])]
verifyNamesUnique :: (IsString str, Eq str, Eq a) =>
DiscoveryGuide a str
-> [NamedLoc a]
-> SExpr a
-> UniquenessResult a
verifyNamesUnique guide names sexpr =
foldr checkUniqueInExpr (checkUniqueNames names) names
where
varname (SAtom a) = atom2str a
varname _ = Nothing
atom2str = extractStr guide
checkUniqueInExpr nloc dups =
let locname = varname $ nlocVar nloc
addDup [] otherexp = [(nloc, [Right otherexp])]
addDup ((l,dl):dls) subexp = if nlocId l == nlocId nloc
then (nloc, Right subexp : dl) : dls
else addDup dls subexp
matchExpHead s e@(SAtom a) = if Just s == atom2str a
then Just e
else Nothing
matchExpHead s e@(SCons (SAtom a) r) = if Just s == atom2str a
then Just e
else matchExpHead s r
matchExpHead _ SNil = Nothing
matchExpHead s (SCons l r) = matchExpHead s l <|> matchExpHead s r
in case locname of
Nothing -> dups
Just nstr -> maybe dups (addDup dups) $ matchExpHead nstr sexpr
checkUniqueNames = fmap (fmap (fmap Left)) . snd
. splitDups . foldr combineDups []
combineDups nloc [] = [(nloc, [])]
combineDups nloc ((d,ls):ds) = if nlocVar nloc == nlocVar d
then (d,nloc:ls):ds
else (d,ls) : combineDups nloc ds
splitDups = let isDup (nloc, []) (u,d) = (nloc:u, d)
isDup e (u,d) = (u, e:d)
in foldr isDup ([],[])
verificationFailureReport :: Show a => [Location a] -> UniquenessResult a -> String
verificationFailureReport locs = intercalate "\n" . fmap vfRep
where vfRep (l, vf) =
let fs = fmap fl vf
fl (Left nloc) = var nloc
fl (Right e) = "other portion of S-expression: "
<> (show $ truncateExpr 4 e)
var v = "let variable \"" <> (show $ nlocVar v)
<> "\" ["
<> (show $ (truncateExpr 2 . locExpr) <$>
F.find ((==) (nlocId v) . locId) locs)
<> " ...]"
in intercalate "\n " $
("ERR: duplicated " <> (var l) <> " at: ") : fs
truncateExpr :: Int -> SExpr a -> SExpr a
truncateExpr _ SNil = SNil
truncateExpr _ e@(SAtom _) = e
truncateExpr 0 _ = SNil
truncateExpr n (SCons l r) = let trunc = truncateExpr (n - 1)
in SCons (trunc l) (trunc r)
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