import MyInterpret import System.Directory import Network.CGI import Text.XHtml import Data.Maybe import Data.List import Data.ByteString.Lazy.UTF8 (fromString) page code pageContent = header << ( thetitle << "Bidirectionalization for Free! -- Demo" +++ style ! [ thetype "text/css" ] << cdata cssStyle ) +++ body << ( thediv ! [theclass "top"] << ( thespan ! [theclass "title"] << "Haskell" +++ thespan ! [theclass "subtitle"] << "Bidirectionalization for Free!" ) +++ maindiv << ( p << ("This tool allows you to experiment with the "+++ "method described in the paper “" +++ hotlink "http://www.iai.uni-bonn.de/~jv/popl09-2.pdf" << "Bidirectionalization for Free!" +++ "” (POPL'09) by " +++ hotlink "http://www.iai.uni-bonn.de/~jv/" << "Janis Voigtländer" +++ "." ) +++ p << ("You may also want to try another tool applying different bidirectionalization methods side by side: " +++ hotlink "http://www-ps.iai.uni-bonn.de/cgi-bin/b18n-combined-cgi" << "(Combining) Syntactic and Semantic Bidirectionalization" +++ "." ) ) +++ form ! [method "POST", action "#"] << ( maindiv << ( p << ( "Enter the Haskell function definitions or load an example. "+++ "You need to define " +++ tt << "source" +++ " and " +++ tt << "get" +++ ". The code is evaluated inside a " +++ tt << "let" +++ " block, so you can define functions by "+++ "pattern matching, but you cannot define new data types. "+++ "The type classes required by the bidirectionalization functions are defined for " +++ tt << "Maybe" +++ ", " +++ tt << "[]" +++ ", and this simple tree type:" +++ pre << "data Tree a = Leaf a | Node (Tree a) (Tree a)" ) +++ p << ( concatHtml (map (\(name,thisCode) -> radio "load" name ! (if thisCode == code then [checked] else []) +++ name +++ " " ) examples) +++ mkSubmit True Load +++ br +++ textarea ! [name "code", cols "80", rows "10"] << code ) ) +++ pageContent ) +++ maindiv << ( p << ( "The source code of this application and the underlying library can be found " +++ hotlink "http://hackage.haskell.org/cgi-bin/hackage-scripts/package/bff" << "here"+++ ".") +++ p << ("© 2008 Joachim Breitner <" +++ hotlink "mailto:mail@joachim-breitner.de" << "mail@joachim-breitner.de" +++ ">") ) ) cdata s = primHtml $ -- ("") maindiv = thediv ! [theclass "main"] examples = [ ("halve", unlines [ "source = [1,2,3,4,5,6,7,8,9,10]" , "" , "get as = take (length as `div` 2) as" ]) , ("flatten", unlines [ "source = Node (Leaf 'a') (Leaf 'b')" , "" , "get (Leaf a) = [a]" , "get (Node t1 t2) = get t1 ++ get t2" ]) , ("rmdups", unlines [ "source = \"abcbabcbaccba\"" , "" , "get = List.nub" ]) , ("top3", unlines [ "source = \"transformation\"" , "" , "get = take 3 . List.sort . List.nub" ]) , ("tail", unlines [ "source = \"abcd\"" , "" , "get = tail" ]) , ("sieve", unlines [ "source = \"abcdefg\"" , "" , "get (a:b:cs) = b:get cs" , "get _ = []" ]) , ("doubleList", unlines [ "source = \"a\"" , "" , "get = (\\s -> s ++ s)" ]) , ("tail/rmdups", unlines [ "source = \"abcbabcbaccba\"" , "" , "rmdups = List.nub" , "" , "get = tail . rmdups" ]) ] defaultCode = fromJust (lookup "halve" examples) outputErrors :: String -> Html outputErrors s = p << ( strong << "An error occurred:" +++ br +++ pre << s ) mkSubmit active what = submit (submitId what) (submitLabel what) ! if active then [] else [disabled] data Run = Get | Check | Load | Bff String submitId Get = "get source" submitId Check = "check" submitId Load = "load" submitId (Bff suffix) = "submitBff" ++ suffix submitCode Get = Just ("get source") submitCode Check = Nothing submitCode Load = Nothing submitCode (Bff suffix) = Just ("bff"++suffix++" get source view") submitLabel Check = "Re-check types" submitLabel Load = "Load example" submitLabel x = fromJust (submitCode x) main = do setCurrentDirectory "/tmp" runCGI (handleErrors cgiMain) -- This function will not work in all casses, but in most. delDefinition name code = unlines squashed where filtered = filter (not . defines name) (lines code) squash [] = [] squash ("":_) = [""] squash ("\r":_) = [""] squash ls = ls squashed = concat $ map squash $ group $ filtered addDefiniton name def code = unlines (squashed ++ pad ++ new_line) where squashed = lines (delDefinition name code) pad | last squashed == "" || last squashed == "\r" = [] | otherwise = [""] new_line = [name ++ " = " ++ def] defines "" (' ':_) = True defines "" ('=':_) = True defines "" "" = False defines "" _ = False defines _ "" = False defines (i:is) (x:xs) | i == x = defines is xs | i /= x = False cgiMain = do setHeader "Content-type" "text/html; charset=UTF-8" -- the next piece of code is not to be take seious todo <- (fromMaybe Check . listToMaybe . catMaybes) `fmap` mapM (\what -> (const what `fmap`) `fmap` getInput (submitId what)) [Get, Bff "", Bff "_Eq", Bff "_Ord", Check, Load] code <- fromMaybe defaultCode `fmap` getInput "code" (newCode, errors) <- case submitCode todo of Just expr -> do ret <- liftIO $ catchInterpreterErrors $ simpleInterpret code expr return $ case (ret,todo) of (Left err, Bff _) -> ( delDefinition "result" code , Just err) (Left err, Get) -> ( delDefinition "result" $ delDefinition "view" code , Just err) (Right dat, Bff suffix) -> ( addDefiniton "result" dat code , Nothing) (Right dat, Get) -> ( addDefiniton "view" dat $ delDefinition "result" code , Nothing) Nothing -> case todo of Load -> do loadWhat <- getInput "load" return ( fromMaybe code $ loadWhat >>= flip lookup examples , Nothing) Check -> return (code, Nothing) let hasView = any (defines "view") (lines newCode) mbType <- liftIO $ catchInterpreterErrors (simpleTypeOf newCode "get") mbTypeSrc <- liftIO $ catchInterpreterErrors (simpleTypeOf newCode "source") canBff <- liftIO $ either (const False) (const True) `fmap` catchInterpreterErrors (simpleTypeOf newCode "bff get source") canBffEq <- liftIO $ either (const False) (const True) `fmap` catchInterpreterErrors (simpleTypeOf newCode "bff_Eq get source") canBffOrd <- liftIO $ either (const False) (const True) `fmap` catchInterpreterErrors (simpleTypeOf newCode "bff_Ord get source") outputFPS $ fromString $ showHtml $ page newCode $ p << typeInfo mbType mbTypeSrc canBff canBffEq canBffOrd +++ maindiv << ( p << ( "You can use " +++ tt << "get source" +++ " to calculate "+++ "a view which you can then modify. If you have defined "+++ tt << "view" +++ ", then you can use the bidirectionalizer "+++ "to calculate the updated source. The result will be shown "+++ "in the code edit frame above." ) +++ p << ( "Evaluate " +++ mkSubmit True Get +++ " " +++ mkSubmit (hasView && canBff) (Bff "") +++" " +++ mkSubmit (hasView && canBffEq) (Bff "_Eq") +++" " +++ mkSubmit (hasView && canBffOrd) (Bff "_Ord") ) +++ maybe noHtml outputErrors errors ) typeInfo (Left err) _ _ _ _ = maindiv << p << ( "Your definitions do not typecheck:" +++ br +++ pre << err +++ br +++ mkSubmit True Check) typeInfo _ (Left err) _ _ _ = maindiv << p << ( "Your definitions do not typecheck:" +++ br +++ pre << err +++ br +++ mkSubmit True Check) typeInfo (Right getType) (Right sourceType) canBff canBffEq canBffOrd = maindiv << ( p << ( "Your definitions have the following types: " +++ pre << ("get :: " ++ getType ++ "\n"++ "source :: " ++ sourceType) +++ "Therefore, an updater can be derived by " +++ case (canBff, canBffEq, canBffOrd) of (True, _, _) -> tt << "bff" +++ ", " +++ tt << "bff_Eq" +++ ", and " +++ tt << "bff_Ord" +++ "." (False, True, _) -> tt << "bff_Eq" +++ " and " +++ tt << "bff_Ord" +++ "." (False, False, True) -> tt << "bff_Ord" +++ " only." (False, False, False) -> "none of the " +++ tt << "bff" +++ " functions." ) +++ p << mkSubmit True Check ) cssStyle = unlines [ "body { padding:0px; margin: 0px; }" , "div.top { margin:0px; padding:10px; margin-bottom:20px;" , " background-color:#efefef;" , " border-bottom:1px solid black; }" , "span.title { font-size:xx-large; font-weight:bold; }" , "span.subtitle { padding-left:30px; font-size:large; }" , "div.main { border:1px dotted black;" , " padding:10px; margin:10px; }" , "div.submain { padding:10px; margin:11px; }" , "p.subtitle { font-size:large; font-weight:bold; }" , "input.type { font-family:monospace; }" , "input[type=\"submit\"] { font-family:monospace; background-color:#efefef; }" , "span.mono { font-family:monospace; }" , "pre { margin:10px; margin-left:20px; padding:10px;" , " border:1px solid black; }" , "textarea { margin:10px; margin-left:20px; padding:10px; }" , "p { text-align:justify; }" ]