simple-smt-0.9.9: exe/DiffSEXp.hs
module Main where
import Data.Map(Map)
import qualified Data.Map.Strict as Map
import Data.IntMap(IntMap)
import qualified Data.IntMap as IntMap
import Data.List(mapAccumL, foldl')
import Data.Char(isSpace)
import SimpleGetOpt
import SimpleSMT(SExpr(..), readSExpr)
data Options = Options {
optSplitOn :: [String],
optShowHelp :: Bool
}
options :: OptSpec Options
options = optSpec {
progDescription = ["Compute a diff on sub-expressions of S-expressions"],
progOptions = [
Option ['s'] ["split"]
"Split terms on the given symbol"
$ ReqArg "ATOM" $ \a s -> Right s { optSplitOn = a : optSplitOn s },
Option ['h'] ["help"]
"Show help"
$ NoArg $ \s -> Right s { optShowHelp = True }
]
}
defaultOptions :: Options
defaultOptions = Options {
optSplitOn = ["=", "bvEq"],
optShowHelp = False
}
main :: IO ()
main =
do
opts <- getOpts defaultOptions options
if optShowHelp opts then dumpUsage options else
interact $ \txt ->
case readSExpr txt of
Just (sexp,rest) | all isSpace rest ->
case fromSExp emptyCtx sexp of
(ctx, tm) | (_, d) <- diffEq (optSplitOn opts) ctx tm ->
toJS (toBinds mempty d) d
_ -> error "malformed S-expression"
diffEq :: [String] -> TermCtx -> Term -> (TermCtx, Term)
diffEq isEq = go
where
go ctx t =
case termF t of
Con {} -> (ctx, t)
Diff {} -> (ctx, t) -- shouldn't happen
App a args
| a `elem` isEq,
Just (as,x,y) <- lastTwo [] args,
let (ctx1, d) = diff ctx x y ->
fromShp ctx1 (App a (as ++ [d]))
| otherwise -> goMany ctx (App a) args
Tup args -> goMany ctx Tup args
goMany ctx f ts =
case mapAccumL go ctx ts of
(ctx1, ts1) -> fromShp ctx1 (f ts1)
lastTwo acc xs =
case xs of
[x,y] -> Just (reverse acc, x, y)
x : rest -> lastTwo (x : acc) rest
[] -> Nothing
data TermCtx = TermCtx {
terms :: !(Map TermF Term),
nextTerm :: !Int
}
emptyCtx :: TermCtx
emptyCtx = TermCtx { terms = mempty, nextTerm = 0 }
data Term = Term {
termId :: !Int,
termF :: TermF
} deriving Show
instance Eq Term where
x == y = termId x == termId y
instance Ord Term where
compare x y = compare (termId x) (termId y)
data TermF =
Con String
| App String [Term]
| Tup [Term]
| Diff Term Term
deriving (Eq,Ord,Show)
fromSExp :: TermCtx -> SExpr -> (TermCtx, Term)
fromSExp ctx0 sexp =
case sexp of
Atom a -> fromShp ctx0 (Con a)
List (Atom a : more) ->
let (ctx, ts) = mapAccumL fromSExp ctx0 more
in fromShp ctx (App a ts)
List es ->
let (ctx, ts) = mapAccumL fromSExp ctx0 es
in fromShp ctx (Tup ts)
fromShp :: TermCtx -> TermF -> (TermCtx, Term)
fromShp ctx shp =
case Map.lookup shp (terms ctx) of
Just t -> (ctx, t)
Nothing ->
let i = nextTerm ctx
t = Term { termId = i, termF = shp }
ts = Map.insert shp t (terms ctx)
ctx1 = TermCtx { nextTerm = i + 1, terms = ts }
in ctx1 `seq` (ctx1, t)
diffMany :: TermCtx -> [Term] -> [Term] -> Maybe (TermCtx, [Term])
diffMany ctx xs ys
| length xs == length ys =
Just (mapAccumL (\c (x,y) -> diff c x y) ctx (zip xs ys))
| otherwise = Nothing
diff :: TermCtx -> Term -> Term -> (TermCtx, Term)
diff ctx x y
| x == y = (ctx, x)
diff ctx Term { termF = App f xs } Term { termF = App g ys }
| f == g, Just (ctx1, ts) <- diffMany ctx xs ys = fromShp ctx1 (App f ts)
diff ctx Term { termF = Tup xs } Term { termF = Tup ys }
| Just (ctx1, ts) <- diffMany ctx xs ys = fromShp ctx1 (Tup ts)
-- We ignore Diff for the moment, as the plan is to use ths on fromSExp,
-- which shouldn't have any of these. We could handle `Diff` as if it
-- is an "or" and try to diff each option separately.
diff ctx x y = fromShp ctx (Diff x y)
data Bind = Bind {
def :: Term,
count :: !Int,
hasDiff :: Bool
}
type Binds = IntMap Bind
toBindsMany :: Binds -> [Term] -> (Binds, Bool)
toBindsMany bs as =
case mapAccumL toBinds' bs as of
(bs1, ds) -> (bs1, or ds)
toBinds :: Binds -> Term -> Binds
toBinds bs = fst . toBinds' bs
toBinds' :: Binds -> Term -> (Binds, Bool)
toBinds' bs t =
case IntMap.lookup (termId t) bs of
Just b -> (IntMap.insert (termId t) b { count = count b + 1 } bs, hasDiff b)
Nothing ->
let bs1 = IntMap.insert (termId t) Bind { def = t, count = 1, hasDiff = snd res } bs
res =
case termF t of
Con _ -> (bs1, False)
App _ as -> toBindsMany bs1 as
Tup as -> toBindsMany bs1 as
Diff x y -> (toBinds (toBinds bs1 x) y, True)
in res
type JS = String
toJS :: Binds -> Term -> JS
toJS bs t = unlines $ [
"{ \"root\": " ++ show (tid t) ++ ",",
" \"terms\": {"
] ++
[ l | let n = IntMap.size bs,
(b,isL) <- zip (replicate (n-1) False ++ [True])
(IntMap.elems bs),
l <- bind b isL ] ++
[
" }",
"}"
]
where
tid x = "t" ++ show (termId x)
bind isLast b =
let t = def b
in [
" " ++ show (tid t) ++ ": {",
" \"diff\": " ++ (if hasDiff b then "true" else "false") ++ ",",
" \"count\": " ++ show (count b) ++ ",",
" \"shape\": " ++ toShape t,
" }" ++ if isLast then "" else ","
]
toShape t =
case termF t of
Con a -> shape "con" a []
App f as -> shape "app" f as
Tup as -> shape "tup" "" as
Diff a b -> shape "diff" "" [a,b]
shape t f xs = "{ \"tag\": " ++ show t ++ concat [ ", \"fun\": " ++ show f | not (null f) ] ++
", \"args\": " ++ show (map tid xs) ++ "}"