bff-0.3.1: bff-cgi.hs
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" +++
"."
)
) +++
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 ("<![CDATA[\n"++ s ++ "\n]]>")
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/xml; 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; }"
]