smcdel-1.2.0: exec/smcdel-web.hs
{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
module Main where
import Prelude
import Control.Monad (unless)
import Control.Monad.IO.Class (liftIO)
import Control.Arrow
import Control.DeepSeq (force)
import Control.Exception (evaluate, catch, SomeException)
import Data.FileEmbed
import Data.List (intercalate)
import Data.Maybe (fromMaybe)
import Data.Version (showVersion)
import Paths_smcdel (version)
import Web.Scotty
import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import qualified Data.Text.Lazy as TL
import Data.HasCacBDD.Visuals (svgGraph)
import qualified Language.Javascript.JQuery as JQuery
import Language.Haskell.TH.Syntax
import Network.Wai.Handler.Warp (defaultSettings, setHost, setPort)
import System.Environment (lookupEnv)
import Text.Read (readMaybe)
import SMCDEL.Internal.Lex
import SMCDEL.Internal.Parse
import SMCDEL.Internal.Sanity
import SMCDEL.Symbolic.S5
import SMCDEL.Internal.TexDisplay
import SMCDEL.Translations.S5
import SMCDEL.Language
main :: IO ()
main = do
putStrLn $ "SMCDEL " ++ showVersion version ++ " -- https://github.com/jrclogic/SMCDEL"
port <- fromMaybe 3000 . (readMaybe =<<) <$> lookupEnv "PORT"
putStrLn $ "Please open this link: http://127.0.0.1:" ++ show port ++ "/index.html"
let mySettings = Options 1 (setHost "127.0.0.1" $ setPort port defaultSettings)
scottyOpts mySettings $ do
get "" $ redirect "index.html"
get "/" $ redirect "index.html"
get "/index.html" . html . TL.fromStrict $ addVersionNumber $ embeddedFile "index.html"
get "/jquery.js" . html . TL.fromStrict $ embeddedFile "jquery.js"
get "/ace.js" . html . TL.fromStrict $ embeddedFile "ace.js"
get "/viz-lite.js" . html . TL.fromStrict $ embeddedFile "viz-lite.js"
get "/getExample" $ do
this <- param "filename"
html . TL.fromStrict $ embeddedFile this
post "/check" $ do
smcinput <- param "smcinput"
case alexScanTokensSafe smcinput of
Left pos -> webError Lex (Just pos) []
Right lexResult -> case parse lexResult of
Left pos -> webError Parse (Just pos) []
Right ci@(CheckInput vocabInts lawform obs jobs) -> case sanityCheck ci of
msgs@(_:_) -> do
webError Sanity Nothing msgs
[] -> do
let mykns = KnS (map P vocabInts) (boolBddOf lawform) (map (second (map P)) obs)
knstring <- liftIO $ showStructure mykns
results <- liftIO $ doJobsWebSafe mykns jobs
html $ mconcat
[ TL.pack knstring
, "<hr />\n"
, TL.pack results ]
post "/knsToKripke" $ do
smcinput <- param "smcinput"
case alexScanTokensSafe smcinput of
Left pos -> webError Lex (Just pos) []
Right lexResult -> case parse lexResult of
Left pos -> webError Parse (Just pos) []
Right ci@(CheckInput vocabInts lawform obs _) -> case sanityCheck ci of
msgs@(_:_) -> webError Sanity Nothing msgs
[] -> do
unless (null (sanityCheck ci)) (webError Sanity Nothing (sanityCheck ci))
let mykns = KnS (map P vocabInts) (boolBddOf lawform) (map (second (map P)) obs)
_ <- liftIO $ showStructure mykns -- this moves parse errors to scotty
if numberOfStates mykns > 32
then html . TL.pack $ "Sorry, I will not draw " ++ show (numberOfStates mykns) ++ " states!"
else do
let (myKripke, _) = knsToKripke (mykns, head $ statesOf mykns) -- ignore actual world
html $ TL.concat
[ TL.pack "<div id='here'></div>"
, TL.pack "<script>document.getElementById('here').innerHTML += Viz('"
, fixTeXinSVG $ textDot myKripke
, TL.pack "');</script>" ]
fixTeXinSVG :: TL.Text -> TL.Text
fixTeXinSVG = TL.replace "$" ""
. TL.replace "p_{" " "
. TL.replace "} " " "
myCatch :: String -> IO String
myCatch f = catch (evaluate (force f) :: IO String) (\e-> return ("ERROR: " ++ show (e :: SomeException)))
doJobsWebSafe :: KnowStruct -> [Job] -> IO String
doJobsWebSafe _ [] = return ""
doJobsWebSafe mykns (j:js) = do
result <- myCatch (doJobWeb mykns j)
rest <- doJobsWebSafe mykns js
return $ "<p>" ++ result ++ "</p>\n" ++ rest
doJobWeb :: KnowStruct -> Job -> String
doJobWeb mykns (TrueQ s f) = unlines
[ "\\( (\\mathcal{F}, " ++ sStr ++ " ) "
, if evalViaBdd (mykns, map P s) f then "\\vDash" else "\\not\\vDash"
, (texForm . simplify) f
, "\\)" ] where sStr = " \\{ " ++ intercalate "," (map (\i -> "p_{" ++ show i ++ "}") s) ++ " \\}"
doJobWeb mykns (ValidQ f) = unlines
[ "\\( \\mathcal{F} "
, if validViaBdd mykns f then "\\vDash" else "\\not\\vDash"
, (texForm . simplify) f
, "\\)" ]
doJobWeb mykns (WhereQ f) = unlines
[ "At which states is \\("
, (texForm . simplify) f
, "\\) true?<br /> \\("
, intercalate "," $ map tex (whereViaBdd mykns f)
, "\\)" ]
showStructure :: KnowStruct -> IO String
showStructure (KnS props lawbdd obs) = do
svgString <- svgGraph lawbdd
return $ "$$ \\mathcal{F} = \\left( \n"
++ tex props ++ ", "
++ " \\begin{array}{l} {"++ " \\href{javascript:toggleLaw()}{\\theta} " ++"} \\end{array}\n "
++ ", \\begin{array}{l}\n"
++ intercalate " \\\\\n " (map (\(i,os) -> "O_{"++i++"}=" ++ tex os) obs)
++ "\\end{array}\n"
++ " \\right) $$ \n <div class='lawbdd' style='display:none;'> where \\(\\theta\\) is this BDD:<br /><p align='center'>" ++ svgString ++ "</p></div>"
embeddedFile :: String -> T.Text
embeddedFile s = case s of
"index.html" -> E.decodeUtf8 $(embedFile "static/index.html")
"viz-lite.js" -> E.decodeUtf8 $(embedFile "static/viz-lite.js")
"ace.js" -> E.decodeUtf8 $(embedFile "static/ace.js")
"jquery.js" -> E.decodeUtf8 $(embedFile =<< runIO JQuery.file)
"MuddyChildren" -> E.decodeUtf8 $(embedFile "Examples/MuddyChildren.smcdel.txt")
"DiningCryptographers" -> E.decodeUtf8 $(embedFile "Examples/DiningCryptographers.smcdel.txt")
"DrinkingLogicians" -> E.decodeUtf8 $(embedFile "Examples/DrinkingLogicians.smcdel.txt")
"CherylsBirthday" -> E.decodeUtf8 $(embedFile "Examples/CherylsBirthday.smcdel.txt")
_ -> error "File not found."
addVersionNumber :: T.Text -> T.Text
addVersionNumber = T.replace "<!-- VERSION NUMBER -->" (T.pack $ showVersion version)
data WebErrorKind = Parse | Lex | Sanity deriving (Show)
webError :: WebErrorKind -> Maybe (Int,Int) -> [String] -> ActionM ()
webError kind mpos msgs = html $ TL.pack $ concat
[ "<p class='error'>", show kind, " error"
, if not (null msgs) then ": " ++ intercalate "<br />" msgs else ""
, case mpos of
Just (lin,col) -> concat
[ " in line ", show lin, ", column ", show col, "</p>\n"
, "<script>"
, "editor.clearSelection();"
, "editor.moveCursorTo(", show (lin - 1), ",", show col, ");"
, "editor.renderer.scrollCursorIntoView({row: ", show (lin - 1),", column: ", show col, "}, 0.5);"
, "editor.focus();"
, "</script>"
]
Nothing -> ""
]