1 {-# LANGUAGE RecordWildCards #-}
6 import Data.ByteString.Lazy.UTF8 (fromString)
7 import qualified Data.ByteString.Lazy as BS
9 import Control.Applicative ((<$>),(<*>))
10 import Text.PrettyPrint.HughesPJ (render)
12 import System.IO.Error hiding ( catch )
13 import Text.ParserCombinators.Parsec (ParseError)
14 import System.Directory
15 import Prelude hiding ( catch )
16 import Control.Exception
17 import System.Posix.Files ( isDirectory, getSymbolicLinkStatus )
18 import System.Posix.Env
31 data PageInfo = PageInfo
33 , scrollX :: Maybe String
34 , scrollY :: Maybe String
35 , viewFunction :: String
36 , parseError :: Maybe String
37 , generatedModuleMB :: Maybe String
39 , playCodeMB :: Maybe String
40 , playErrorM :: Maybe String
43 page (PageInfo {..}) =
45 thetitle << "(Combining) Syntatic and Semantic Bidirectionalization" +++
46 style ! [ thetype "text/css" ] << cdata cssStyle +++
47 script ! [ thetype "text/javascript", src "?jquery" ] << noHtml +++
48 script ! [ thetype "text/javascript" ] << cdata jsCode
50 body ! [ strAttr "onload" "restoreScroll()" ] << (
51 thediv ! [theclass "top"] << (
52 thespan ! [theclass "title"] << "(Combining) Syntatic and Semantic Bidirectionalization" +++
53 thespan ! [theclass "subtitle"] << "Prototype implementation"
56 p << "This tool allows you to experiment with the bidirectionalization methods described in the following papers: " +++
60 hotlink "http://doi.acm.org/10.1145/1291151.1291162"
61 << "Bidirectionalization transformation based on automatic derivation of view complement functions" +++
63 hotlink "http://www.kb.ecei.tohoku.ac.jp/~kztk/"
64 << "Kazutaka Matsuda" +++ ", " +++
66 "Keisuke Nakano, " +++
67 "Makoto Hamana and " +++
72 hotlink "http://doi.acm.org/10.1145/1480881.1480904"
73 << "Bidirectionalization for free! (Pearl)" +++
75 hotlink "http://www.iai.uni-bonn.de/~jv/"
76 << "Janis Voigtländer"
86 form ! [method "post",
88 strAttr "onsubmit" "saveScroll()"
90 hidden "scrollx" (fromMaybe "0" scrollX) +++
91 hidden "scrolly" (fromMaybe "0" scrollY) +++
92 hidden "showCode" (show showCode) +++
95 "Please enter the view function. (TODO: Elaborate this text)"
99 concatHtml (map (\(name,thisCode) ->
100 radio "loadCode" name
101 ! (if thisCode == viewFunction then [checked] else [])
104 mkSubmit True Load +++
106 textarea ! [name "code", cols "120", rows "7"] << viewFunction
110 ( htmlMB parseError $ \err ->
112 "Can not parse your definition:" +++ br +++
113 pre << show err +++ br +++
116 -- p << astInfo mbAST +++
117 maindiv ! [ identifier "output" ]<< (
119 "You can calculate a derived put function with various options:" ) +++
120 p << ( "Output mode: " +++
121 concatHtml (map (\mode ->
122 radio "b18nMode" (show mode)
123 ! (guard (mode == b18nMode config) >> return checked)
124 +++ show mode +++ " "
125 ) [SyntacticB18n, SemanticB18n, CombinedB18n, NoB18n]) +++ br +++
126 "Show types " +++ checkbox "showTypes" "showTypes"
127 ! (guard (isShowType config) >> return checked)
131 ( htmlMB generatedModuleMB $ \ generatedModule ->
132 {- maybe noHtml outputErrors errors +++ -}
133 p << ("Result Code" +++
134 thespan ! [ identifier "hideShow"
135 , thestyle "display:none"] << (
136 " (" +++ hotlink "javascript:" << "Hide/Show" +++ ")"
138 pre ! [identifier "genCode" ] << generatedModule
144 ( htmlMB playCodeMB $ \playCode -> maindiv << (
145 p << ( "You can now play with the code. You can modify the " +++
146 tt << "source" +++ " and calculate the " +++
147 tt << "view" +++ ", or modify the " +++
148 tt << "view" +++ " and calculate an updated "+++
149 tt << "source" +++ "." +++ br +++
150 textarea ! [name "playCode", cols "120", rows "8" ] << playCode
152 p << ( "Evaluate " +++
153 mkSubmit True EvalGet +++ " " +++
154 mkSubmit True EvalPut
157 ( htmlMB playErrorM $ \playError -> maindiv << (
159 strong << "An error occurred while evaluating your code:" +++ br +++
166 "The source code of this application and the underlying library can be found " +++
167 hotlink "TODO" << "here"+++
169 "The code for the web interface is based on " +++
170 hotlink "http://www-ps.iai.uni-bonn.de/cgi-bin/bff.cgi" <<
171 "the demo interface from “Bidirectionalization for free!”"
173 p << ("© 2010 Joachim Breitner <" +++
174 hotlink "mailto:mail@joachim-breitner.de" << "mail@joachim-breitner.de" +++
180 cdata s = primHtml ("<![CDATA[\n"++ s ++ "\n]]>")
182 maindiv = thediv ! [theclass "main"]
187 , "init (Cons(a,Nil)) = Nil"
188 , "init (Cons(a,Cons(b,x))) = Cons(a,initWork(b,x))"
189 , "initWork(a,Nil) = Nil"
190 , "initWork(a,Cons(b,x)) = Cons(a,initWork(b,x))"
192 , ("initHalf", unlines
193 [ "initHalf(Nil) = Nil"
194 , "initHalf(Cons(a,x)) = Cons(a,initHalfWork(x,x))"
196 , "initHalfWork(xs, Nil) = Nil"
197 , "initHalfWork(xs, Cons(x,Nil)) = Nil"
198 , "initHalfWork(Cons(a,x), Cons(b,Cons(c,y)))"
199 , " = Cons(a,initHalfWork(x,y))"
202 [ "sieve (Nil) = Nil"
203 , "sieve (Cons(a,Nil)) = Nil"
204 , "sieve (Cons(a,Cons(b,x))) = Cons(b,sieve(x))"
207 [ "reverse(xs) = rev(xs,Nil)"
209 , "rev(Cons(a,x),y) = rev(x,Cons(a,y))"
213 defaultCode = fromJust (lookup "init" examples)
215 outputErrors :: String -> Html
218 strong << "An error occurred:" +++ br +++
222 mkSubmit active what = submit (submitId what) (submitLabel what)
223 ! if active then [] else [disabled]
225 data Run = Get | Check | Load | BiDi | EvalPut | EvalGet
228 submitId Get = "get source"
229 submitId Check = "check"
230 submitId Load = "load"
231 submitId BiDi = "submitBiDi"
232 submitId EvalPut = "evalPut"
233 submitId EvalGet = "evalGet"
235 submitCode Get = Just ("get source")
236 submitCode Check = Nothing
237 submitCode Load = Nothing
239 submitLabel Check = "Re-Parse definition"
240 submitLabel Load = "Load example"
241 submitLabel EvalGet = "view = get source"
242 submitLabel EvalPut = "result = put source view"
243 submitLabel BiDi = "bidirectionalize"
245 main = runCGI (handleErrors cgiMain)
247 -- This function will not work in all casses, but in most.
248 delDefinition name code = unlines squashed
249 where filtered = filter (not . defines name) (lines code)
252 squash ("\r":_) = [""]
254 squashed = concat $ map squash $ group $ filtered
256 addDefiniton name def code = unlines (squashed ++ pad ++ new_line)
257 where squashed = lines (delDefinition name code)
258 pad | last squashed == "" || last squashed == "\r" = []
260 new_line = [name ++ " = " ++ def]
262 defines "" (' ':_) = True
263 defines "" ('=':_) = True
264 defines "" "" = False
267 defines (i:is) (x:xs) | i == x = defines is xs
277 setHeader "Content-type" "text/javascript"
278 setHeader "Expires" "Fri, 01 Jan 2100 00:00:00 +0100"
279 setHeader "Cache-control" "max-age=36000000" -- 1000 h
280 outputFPS $ jQueryCode
282 defaultPlayCode (Config{..}) get =
284 [ "get s = Main." ++ get ++ " s"
285 , "put s v = " ++ put
287 , "source = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
289 where put | b18nMode == SyntacticB18n =
291 | b18nMode == SemanticB18n =
293 | b18nMode == CombinedB18n =
294 "fromMaybe (error \"Could not handle shape change.\") $ " ++
295 get ++ "_Bbd rear 42 s v"
298 setHeader "Content-type" "application/xhtml+xml; charset=UTF-8"
301 b18nMode' <- maybe CombinedB18n read <$> getInput "b18nMode"
302 isShowType' <- isJust <$> getInput "showTypes"
303 return $ defaultConfig
304 { isHaskellify = True
305 , b18nMode = b18nMode'
306 , execMode = ShapifyPlus
307 , isShowType = isShowType'
310 todo <- msum <$> sequence (
311 map (\what -> fmap (const what) <$> getInput (submitId what))
312 [ BiDi, Get, Check, Load, EvalPut, EvalGet])
314 code <- fromMaybe defaultCode <$> getInput "code"
317 Just Load -> do loadWhat <- getInput "loadCode"
318 return $ fromMaybe code $ loadWhat >>= flip lookup examples
321 let eAST = parseString code
324 let parseError = either (Just . show) (const Nothing) eAST
326 let (genCodeM,getM) = case (todo,eAST) of
327 (Just Load, _) -> (Nothing, Nothing)
328 (Just _, Right ast) ->
330 outputCode conf True ast (introNat $ shapify $ typeInference ast)
331 , firstDeclaredName ast
333 _ -> (Nothing, Nothing)
335 showCode <- maybe False read <$> getInput "showCode"
337 pcM <- getInput "playCode"
338 -- Playcode can only by used when the output is exMode
339 (playCode, playErrorM) <- -- if outMode /= HaskellCode then return (Nothing, Nothing) else
340 case (todo,getM,genCodeM,pcM) of
341 -- The user successfully generated code to play with, insert default playCode.
342 -- Do not use the user input, as he probably switched to a new example.
343 (Just BiDi, Just get, Just _, _) ->
344 return (defaultPlayCode conf get, Nothing)
345 -- The user played with the code
346 (Just EvalGet, Just get, Just genCode, Just pc) -> do
347 view <- liftIO $ evaluateWith genCode pc ("get source")
349 Left err -> return $ (Just pc, Just err)
350 Right dat -> return $ (\r -> (Just r, Nothing))
351 $ addDefiniton "view" dat
352 $ delDefinition "result"
354 (Just EvalGet, Just get, Just genCode, Nothing) -> do
355 return (defaultPlayCode conf get, Nothing)
356 (Just EvalPut, Just get, Just genCode, Just pc) -> do
357 view <- liftIO $ evaluateWith genCode pc ("put source view")
359 Left err -> return $ (Just pc, Just err)
360 Right dat -> return $ (\r -> (Just r, Nothing))
361 $ addDefiniton "result" dat
363 (Just EvalPut, Just get, Just _, Nothing) -> do
364 return (defaultPlayCode conf get, Nothing)
365 _ -> return (Nothing, Nothing)
367 scrollX <- getInput "scrollx"
368 scrollY <- getInput "scrolly"
370 outputFPS $ fromString $ showHtml $ page $
381 evaluateWith :: String -> String -> String -> IO (Either String String)
382 evaluateWith genCode playCode expr =
384 BS.writeFile "BUtil.hs" bUtilCode
385 writeFile "Main.hs" $ "module Main where\n" ++ genCode
386 liftIO $ catchInterpreterErrors $ simpleInterpret mods imports playCode expr
397 withFullSource genCode playCode = genCode' ++ "\n" ++ playCode
398 where genCode' = unlines . filter (not . isPrefixOf "import") . lines $ genCode
400 astInfo (Left err) = maindiv << p << (
401 "Can not parse your definition:" +++ br +++
402 pre << show err +++ br +++
405 astInfo (Right source) = maindiv << (
406 p << ("Definition parsed succesfully") +++
407 p << mkSubmit True Check
411 [ "body { padding:0px; margin: 0px; }"
412 , "div.top { margin:0px; padding:10px; margin-bottom:20px;"
413 , " background-color:#efefef;"
414 , " border-bottom:1px solid black; }"
415 , "span.title { font-size:xx-large; font-weight:bold; }"
416 , "span.subtitle { padding-left:30px; font-size:large; }"
417 , "div.main { border:1px dotted black;"
418 , " padding:10px; margin:10px; }"
419 , "div.submain { padding:10px; margin:11px; }"
420 , "p.subtitle { font-size:large; font-weight:bold; }"
421 , "input.type { font-family:monospace; }"
422 , "input[type=\"submit\"] { font-family:monospace; background-color:#efefef; }"
423 , "span.mono { font-family:monospace; }"
424 , "pre { margin:10px; margin-left:20px; padding:10px;"
425 , " border:1px solid black; }"
426 , "textarea { margin:10px; margin-left:20px; padding:10px; }"
427 , "p { text-align:justify; }"
431 [ "function saveScroll () {"
432 , " $('#scrolly').val($('html').scrollTop());"
434 , "function restoreScroll () {"
435 , " $('html').scrollTop($('#scrolly').val());"
437 , "$(document).ready(function () {"
438 , " $('#hideShow').show();"
439 , " if ($('#showCode').val() == 'False')"
440 , " { $('#genCode').hide(); };"
441 , " $('#hideShow a').click(function () {"
442 , " $('#showCode').val("
443 , " $('#genCode').is(':visible') ? 'False' : 'True'"
445 , " $('#genCode').toggle('slow');"
450 htmlMB Nothing f = noHtml
451 htmlMB (Just x) f = f x
453 readOnly = emptyAttr "readonly"
456 firstDeclaredName (AST []) = Nothing
457 firstDeclaredName (AST (Decl n _ _ _:_)) = Just (show n)
460 - Temp-Dir functions taken from XMonad/Lock.hs and simplified.
461 - It also changes TMP so that hint’s temporary files are stored within this directory
463 withinTmpDir :: IO a -> IO a
464 withinTmpDir job = do
465 absolute_name <- (++ "/sem_syn.cgi") <$> getTemporaryDirectory
466 formerdir <- getCurrentDirectory
467 formerTMP <- getEnv "TMPDIR"
468 bracket (do dir <- create_directory absolute_name 0
469 setEnv "TMPDIR" dir True
472 (\dir -> do setCurrentDirectory formerdir
473 maybe (unsetEnv "TMPDIR") (\p -> setEnv "TMPDIR" p True) formerTMP
476 where newname name 0 = name
477 newname name n = name ++ "-" ++ show n
478 create_directory :: FilePath -> Int -> IO FilePath
479 create_directory name n
480 = do createDirectory $ newname name n
481 setCurrentDirectory $ newname name n
483 `catch` (\e -> if isAlreadyExistsError e
484 then create_directory name (n+1)
487 rmRecursive :: FilePath -> IO ()
489 do isd <- isDirectory <$> getSymbolicLinkStatus d
492 else when isd $ do conts <- actual_dir_contents
493 withCurrentDirectory d $
494 mapM_ rmRecursive conts
496 where actual_dir_contents = -- doesn't include . or ..
497 do c <- getDirectoryContents d
498 return $ filter (/=".") $ filter (/="..") c
500 withCurrentDirectory :: FilePath -> IO r -> IO r
501 withCurrentDirectory name m =
503 (do cwd <- getCurrentDirectory
504 when (name /= "") (setCurrentDirectory name)
506 (\oldwd -> setCurrentDirectory oldwd)