module MagicHaskeller.ExpToHtml(QueryOptions(..), defaultQO,
review,
expToPlainString, expSigToString, refer, pprnn, annotateFree, annotateString, Language(..)) where
import Language.Haskell.TH as TH
import Language.Haskell.TH.PprLib(to_HPJ_Doc)
import Text.PrettyPrint
import Network.URI(escapeURIString, isUnreserved)
import Text.Html(stringToHtmlString)
import MagicHaskeller.LibTH(fromPrelude, fromDataList, fromDataChar, fromDataMaybe, Primitive, ords, prelOrdRelated, prelEqRelated, dataListOrdRelated, dataListEqRelated, fromPrelDouble, fromPrelRatio, fromDataRatio)
import Data.Char(isAlpha, ord, isDigit, isSpace, toUpper)
import qualified Data.Map
import qualified Data.IntSet as IS
import Data.Generics
import MagicHaskeller.CoreLang(stripByd_)
import Data.Hashable
import Data.List((\\))
import Control.Monad(mplus)
data QueryOptions = Q {depth :: Int, absents :: Bool} deriving (Read, Show)
defaultQO = Q {depth = 7, absents = False}
data Language = LHaskell | LExcel | LJavaScript deriving (Read, Show, Eq)
review :: String -> Either String (String,Bool)
review "" = return ("",False)
review xs = case lex xs of
[] | '"':_ <- dropWhile isSpace xs -> Left "<br><br>Lex error: maybe double-quotes are not balanced.<br>"
| otherwise -> Left "<br><br>Lex error!<br>"
[("let", _)] -> Left loopErrMsg
[("where", _)] -> Left loopErrMsg
[("=", rest)] -> do (zs,_) <- review rest
return ("~= "++zs, True)
[("&", rest)] -> do (zs,_) <- review rest
return ("&& "++zs, True)
[("NaN",rest)] -> do (zs,_) <- review rest
return ("(0/0) "++zs, True)
[("Infinity",rest)] -> do (zs,_) <- review rest
return ("(1/0) "++zs, True)
[(tkn, rest)] -> do (zs,repl) <- review rest
return (tkn++' ':zs, repl)
loopErrMsg = "<br><br>Error: <b>let</b> expressions and <b>where</b> clauses are prohibited here. You can still use " ++ refLink "case" "case" ++ " expressions without <b>where</b> clauses for non-recursive bindings.<br>"
expToPlainString, expToString :: Exp -> String
expToPlainString = ('\n':) . pprint
expToString = (\xs -> '(':xs++")<br>") . filter (/='\n') . annotateString LHaskell. pprnn
expSigToString = mkButton
pprnn = renderStyle style{mode=OneLineMode} . to_HPJ_Doc . pprExp 4
isAbsent :: TH.Exp -> Bool
isAbsent (LamE pats e) = any (==WildP) pats || isAbsent e
isAbsent (VarE name) = nameBase name == "const"
isAbsent _ = False
replaceRightArrow "" = ""
replaceRightArrow ('-':'>':xs) = "→"++replaceRightArrow xs
replaceRightArrow (x:xs) = x : replaceRightArrow xs
mkButton :: Language -> [Char] -> [Char] -> Exp -> [Char]
mkButton lang predStr sig expr | usesBlackListed expr = body ++ "<br>"
| otherwise = "<FORM"++ (if isAbsent expr then " class='absent'" else "") ++">"
++"<input type='submit' value='Exemplify'> f = <span draggable='True' ondragstart='dragStart(event)'>"++body
++"</span><input type=hidden name='predicate' value='" ++ concatMap escapeHTML predStr ++ "'><input type=hidden name='candidate' value='" ++ concatMap escapeHTML pprExp ++ sig ++ "'></FORM>"
where pprExp = pprnn expr
body = annotateString lang pprExp
usesBlackListed :: TH.Exp -> Bool
usesBlackListed = everything (||) (False `mkQ` (\name -> hash (nameBase name) `IS.member` partial))
partial :: IS.IntSet
partial = IS.fromList $ map hash ["div", "mod", "enumFromThenTo", "^", "head",
"init", "maximum", "minimum", "maximumBy", "minimumBy"]
escapeHTML '<' = "<"
escapeHTML '>' = ">"
escapeHTML '&' = "&"
escapeHTML '"' = """
escapeHTML '\'' = "'"
escapeHTML c = [c]
#ifdef RESPECTQUALIFICATIONS
annotateString lang xs = case lex xs of
[] -> error $ "parse error during annotateString: " ++ xs ++ "\nThis should not happen, when connected to the right server."
[("","")] -> ""
[(cs@(c:_),'.':rs@(r:_))] | isUpper c && not (isSpace r) -> annStr lang (cs++".") rs
[(cs,rs)] -> (if isSpace $ head xs then (' ':) else id) (annotateWord lang cs ++ annotateString lang rs)
annStr lang mod xs = case lex xs of
[] -> error $ "parse error during annStr: " ++ xs ++"\nThis should not happen, when connected to the right server."
[("","")] -> error $ "parse error during annStr: " ++ xs ++"\nThis should not happen, when connected to the right server."
[(cs@(c:_),".":rs@(r:_))] | isUpper c && not (isSpace r) -> annStr lang (mod++cs++".") rs
[(cs,rs)] -> (if isSpace $ head xs then (' ':) else id) (annotateWord lang cs ++ annotateString lang rs)
#else
annotateString lang xs = case lex xs of
[] -> error $ "parse error during annotateString: " ++ xs ++ "\nThis should not happen, when connected to the right server."
[("","")] -> ""
[(".",rs@(r:_))] | not $ isSpace (head xs) && isSpace r -> '.' : annotateString lang rs
[(cs,rs)] -> (if isSpace $ head xs then (' ':) else id) (annotateWord lang cs ++ annotateString lang rs)
#endif
annotateWord LHaskell cs@('\'':_) = mkLink cs "http://www.haskell.org/onlinereport/haskell2010/haskellch2.html#x7-200002.6" "literal"
annotateWord LHaskell cs@('"' :_) = mkLink cs "http://www.haskell.org/onlinereport/haskell2010/haskellch2.html#x7-200002.6" "literal"
annotateWord LHaskell cs@('_' :_) = mkLink cs "http://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-580003.17" "keyword"
annotateWord LHaskell cs@(c :_)
| isDigit c = mkLink cs "http://www.haskell.org/onlinereport/haskell2010/haskellch2.html#x7-190002.5" "literal"
| otherwise = case referMb cs of Just (cls, str) -> mkLink cs str cls
Nothing | isAlpha c -> "<span class=variable>"++cs++"</span>"
| otherwise -> cs
annotateWord LExcel cs = case Data.Map.lookup capcs xlmap of Nothing -> cs
Just filename -> "<a href='https://support.office.com/en-us/article/Excel"++filename++"?ui=en-US&rs=en-US&ad=US'>"
++ capcs ++ "</a>"
where capcs = map toUpper cs
annotateWord _ [] = []
xlmap :: Data.Map.Map String String
xlmap = Data.Map.fromList $ read $(fmap (LitE . StringL) $ runIO $ readFile "xlmap")
annotateEverywhere = everywhere (mkT annotateName)
annotateFree :: [String] -> TH.Exp -> TH.Exp
annotateFree names v@(VarE name) | show name `elem` names = v
| otherwise = VarE $ annotateName name
annotateFree _ (ConE name) = ConE $ annotateName name
annotateFree _ l@(LitE _) = l
annotateFree names (AppE f e) = annotateFree names f `AppE` annotateFree names e
annotateFree names (InfixE mbf op mbe) = InfixE (fmap (annotateFree names) mbf) (annotateFree names op) (fmap (annotateFree names) mbe)
annotateFree names (LamE pats e) = LamE pats $ annotateFree (patsToNames pats names) e
annotateFree names (TupE es) = TupE $ map (annotateFree names) es
annotateFree names (CondE b t f) = CondE (annotateFree names b) (annotateFree names t) (annotateFree names f)
annotateFree names (ListE es) = ListE $ map (annotateFree names) es
annotateFree names (SigE e t) = SigE (annotateFree names e) t
annotateFree names e = annotateEverywhere e
patsToNames [] = id
patsToNames (p:ps) = patToNames p . patsToNames ps
patToNames (VarP name) = (show name :)
patToNames (TupP ps) = patsToNames ps
patToNames (ConP _ ps) = patsToNames ps
patToNames (InfixP p _ q) = patsToNames [p,q]
patToNames (TildeP p) = patToNames p
patToNames (AsP name p) = (show name :) . patToNames p
patToNames (ListP ps) = patsToNames ps
patToNames (SigP p _) = patToNames p
patToNames _ = id
annotateName :: TH.Name -> TH.Name
annotateName name = case nameBase name of nameStr@(c:cs) | isAlpha c -> mkName $ c : refLink nameStr cs
| c `elem` "=+!@#$%^&*-\\|:/?<>.~" -> mkName $ refLink nameStr $ stringToHtmlString nameStr
_ -> name
refLink nameStr body = case refer nameStr of (cls, url) -> mkLink body url cls
refer nameStr = case referMb nameStr of Just tup -> tup
Nothing -> ("variable", referHoogle nameStr)
mkLink body url cls = "<a href='"++url++"' class="++cls++">"++body++"</a>"
referMb str = do (cls, f) <- Data.Map.lookup str mapNameModule `mplus` Data.Map.lookup (str++"By") mapNameModule
return (cls, f str)
mapNameModule :: Data.Map.Map String (String, String->String)
mapNameModule = Data.Map.fromList $
mkAssoc "base" "Prelude" preludeNameBases ++
mkAssoc "base" "Data-List" (["\\\\"] ++ primssToStrs fromDataList ++ [ stripByd_ nm | nm <- primssToStrs $ dataListOrdRelated ++ dataListEqRelated ]) ++
mkAssoc "base" "Data-Char" dataCharNameBases ++
mkAssoc "base" "Data-Maybe" (primssToStrs fromDataMaybe) ++
mkAssoc "base" "Data-Ratio" (primssToStrs fromDataRatio) ++
[ (kw, ("keyword", const $ repch3 ++ str)) | (kw, str) <- [
("@", "#x8-580003.17"),
("~", "#x8-580003.17"),
("..", "#x8-400003.10"),
("\\", "#x8-260003.3"),
("->", "#x8-260003.3"),
("if", "#x8-320003.6"),
("then","#x8-320003.6"),
("else","#x8-320003.6"),
(":", "#x8-340003.7"),
("let", ""),
("in", "#x8-440003.12"),
("case","#x8-460003.13"),
("of", "#x8-460003.13"),
("do", "#x8-470003.14"),
("::", "#x8-560003.16")
] ] ++
[ (kw, ("other", const $ repch3 ++ str)) | (kw, str) <- [
("[", ""),
("]", ""),
("|", ""),
("`", "#x8-240003.2"),
("-", "#x8-280003.4")
] ]
repch3 = "http://www.haskell.org/onlinereport/haskell2010/haskellch3.html"
mkAssoc package mod namebases = [ (str, ("variable", referHackage package mod)) | str <- namebases ]
preludeNameBases = ["iterate", "!!", "id", "$", "const", ".", "flip", "subtract", "maybe", "foldr", "foldl", "zipWith", "either", "last"] ++
(primssToStrs fromPrelude \\ [":","-"]) ++ primssToStrs fromPrelDouble ++ primssToStrs fromPrelRatio ++ [ stripByd_ nm | nm <- primssToStrs $ prelOrdRelated ++ prelEqRelated ]
dataCharNameBases = ["chr"] ++
primssToStrs fromDataChar
primssToStrs = primsToStrs . concat
primsToStrs = map TH.nameBase . primsToNames
primsToNames :: [Primitive] -> [TH.Name]
primsToNames ps = [ name | (_, VarE name, _) <- ps ] ++ [ name | (_, ConE name, _) <- ps ]
++ [ name | (_, _ `AppE` VarE name, _) <- ps ]
referHackage package modulename str = "http://hackage.haskell.org/packages/archive/"++package++"/latest/doc/html/"++modulename++".html#v:"++hackageEncode str
hackageEncode cs@(a:_) | isAlpha a = cs
| otherwise = concatMap (\c -> '-' : shows (ord c) "-") cs
referHoogle str = "http://www.haskell.org/hoogle/?hoogle=" ++ escapeURIString isUnreserved str