module Idris.IdeMode(parseMessage, convSExp, WhatDocs(..), IdeModeCommand(..), sexpToCommand, toSExp, SExp(..), SExpable, Opt(..), ideModeEpoch, getLen, getNChar, sExpToString) where
import Idris.Core.Binary ()
import Idris.Core.TT
import Control.Applicative hiding (Const)
import Control.Arrow (left)
import qualified Data.Binary as Binary
import qualified Data.ByteString.Base64 as Base64
import qualified Data.ByteString.Lazy as Lazy
import qualified Data.ByteString.UTF8 as UTF8
import Data.List
import Data.Maybe (isJust)
import qualified Data.Text as T
import Numeric
import System.IO
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P
import Text.Printf
getNChar :: Handle -> Int -> String -> IO (String)
getNChar _ 0 s = return (reverse s)
getNChar h n s = do c <- hGetChar h
getNChar h (n 1) (c : s)
getLen :: Handle -> IO (Either Err Int)
getLen h = do s <- getNChar h 6 ""
case readHex s of
((num, ""):_) -> return $ Right num
_ -> return $ Left . Msg $ "Couldn't read length " ++ s
data SExp = SexpList [SExp]
| StringAtom String
| BoolAtom Bool
| IntegerAtom Integer
| SymbolAtom String
deriving ( Eq, Show )
sExpToString :: SExp -> String
sExpToString (StringAtom s) = "\"" ++ escape s ++ "\""
sExpToString (BoolAtom True) = ":True"
sExpToString (BoolAtom False) = ":False"
sExpToString (IntegerAtom i) = printf "%d" i
sExpToString (SymbolAtom s) = ":" ++ s
sExpToString (SexpList l) = "(" ++ intercalate " " (map sExpToString l) ++ ")"
class SExpable a where
toSExp :: a -> SExp
instance SExpable SExp where
toSExp a = a
instance SExpable Bool where
toSExp True = BoolAtom True
toSExp False = BoolAtom False
instance SExpable String where
toSExp s = StringAtom s
instance SExpable Integer where
toSExp n = IntegerAtom n
instance SExpable Int where
toSExp n = IntegerAtom (toInteger n)
instance SExpable Name where
toSExp s = StringAtom (show s)
instance (SExpable a) => SExpable (Maybe a) where
toSExp Nothing = SexpList [SymbolAtom "Nothing"]
toSExp (Just a) = SexpList [SymbolAtom "Just", toSExp a]
instance (SExpable a) => SExpable [a] where
toSExp l = SexpList (map toSExp l)
instance (SExpable a, SExpable b) => SExpable (a, b) where
toSExp (l, r) = SexpList [toSExp l, toSExp r]
instance (SExpable a, SExpable b, SExpable c) => SExpable (a, b, c) where
toSExp (l, m, n) = SexpList [toSExp l, toSExp m, toSExp n]
instance (SExpable a, SExpable b, SExpable c, SExpable d) => SExpable (a, b, c, d) where
toSExp (l, m, n, o) = SexpList [toSExp l, toSExp m, toSExp n, toSExp o]
instance (SExpable a, SExpable b, SExpable c, SExpable d, SExpable e) =>
SExpable (a, b, c, d, e) where
toSExp (l, m, n, o, p) = SexpList [toSExp l, toSExp m, toSExp n, toSExp o, toSExp p]
instance SExpable NameOutput where
toSExp TypeOutput = SymbolAtom "type"
toSExp FunOutput = SymbolAtom "function"
toSExp DataOutput = SymbolAtom "data"
toSExp MetavarOutput = SymbolAtom "metavar"
toSExp PostulateOutput = SymbolAtom "postulate"
maybeProps :: SExpable a => [(String, Maybe a)] -> [(SExp, SExp)]
maybeProps [] = []
maybeProps ((n, Just p):ps) = (SymbolAtom n, toSExp p) : maybeProps ps
maybeProps ((n, Nothing):ps) = maybeProps ps
constTy :: Const -> String
constTy (I _) = "Int"
constTy (BI _) = "Integer"
constTy (Fl _) = "Double"
constTy (Ch _) = "Char"
constTy (Str _) = "String"
constTy (B8 _) = "Bits8"
constTy (B16 _) = "Bits16"
constTy (B32 _) = "Bits32"
constTy (B64 _) = "Bits64"
constTy _ = "Type"
namespaceOf :: Name -> Maybe String
namespaceOf (NS _ ns) = Just (intercalate "." $ reverse (map T.unpack ns))
namespaceOf _ = Nothing
instance SExpable OutputAnnotation where
toSExp (AnnName n ty d t) = toSExp $ [(SymbolAtom "name", StringAtom (show n)),
(SymbolAtom "implicit", BoolAtom False),
(SymbolAtom "key", StringAtom (encodeName n))] ++
maybeProps [("decor", ty)] ++
maybeProps [("doc-overview", d), ("type", t)] ++
maybeProps [("namespace", namespaceOf n)]
toSExp (AnnBoundName n imp) = toSExp [(SymbolAtom "name", StringAtom (show n)),
(SymbolAtom "decor", SymbolAtom "bound"),
(SymbolAtom "implicit", BoolAtom imp)]
toSExp (AnnConst c) = toSExp [(SymbolAtom "decor",
SymbolAtom (if constIsType c then "type" else "data")),
(SymbolAtom "type", StringAtom (constTy c)),
(SymbolAtom "doc-overview", StringAtom (constDocs c)),
(SymbolAtom "name", StringAtom (show c))]
toSExp (AnnData ty doc) = toSExp [(SymbolAtom "decor", SymbolAtom "data"),
(SymbolAtom "type", StringAtom ty),
(SymbolAtom "doc-overview", StringAtom doc)]
toSExp (AnnType name doc) = toSExp $ [(SymbolAtom "decor", SymbolAtom "type"),
(SymbolAtom "type", StringAtom "Type"),
(SymbolAtom "doc-overview", StringAtom doc)] ++
if not (null name) then [(SymbolAtom "name", StringAtom name)] else []
toSExp AnnKeyword = toSExp [(SymbolAtom "decor", SymbolAtom "keyword")]
toSExp (AnnFC fc) = toSExp [(SymbolAtom "source-loc", toSExp fc)]
toSExp (AnnTextFmt fmt) = toSExp [(SymbolAtom "text-formatting", SymbolAtom format)]
where format = case fmt of
BoldText -> "bold"
ItalicText -> "italic"
UnderlineText -> "underline"
toSExp (AnnLink url) = toSExp [(SymbolAtom "link-href", StringAtom url)]
toSExp (AnnTerm bnd tm)
| termSmallerThan 1000 tm = toSExp [(SymbolAtom "tt-term", StringAtom (encodeTerm bnd tm))]
| otherwise = SexpList []
toSExp (AnnSearchResult ordr) = toSExp [(SymbolAtom "doc-overview",
StringAtom ("Result type is " ++ descr))]
where descr = case ordr of
EQ -> "isomorphic"
LT -> "more general than searched type"
GT -> "more specific than searched type"
toSExp (AnnErr e) = toSExp [(SymbolAtom "error", StringAtom (encodeErr e))]
toSExp (AnnNamespace ns file) =
toSExp $ [(SymbolAtom "namespace", StringAtom (intercalate "." (map T.unpack ns)))] ++
[(SymbolAtom "decor", SymbolAtom $ if isJust file then "module" else "namespace")] ++
maybeProps [("source-file", file)]
toSExp AnnQuasiquote = toSExp [(SymbolAtom "quasiquotation", True)]
toSExp AnnAntiquote = toSExp [(SymbolAtom "antiquotation", True)]
toSExp (AnnSyntax c) = SexpList []
encodeName :: Name -> String
encodeName n = UTF8.toString . Base64.encode . Lazy.toStrict . Binary.encode $ n
encodeTerm :: [(Name, Bool)] -> Term -> String
encodeTerm bnd tm = UTF8.toString . Base64.encode . Lazy.toStrict . Binary.encode $
(bnd, tm)
decodeTerm :: String -> ([(Name, Bool)], Term)
decodeTerm = Binary.decode . Lazy.fromStrict . Base64.decodeLenient . UTF8.fromString
encodeErr :: Err -> String
encodeErr e = UTF8.toString . Base64.encode . Lazy.toStrict . Binary.encode $ e
decodeErr :: String -> Err
decodeErr = Binary.decode . Lazy.fromStrict . Base64.decodeLenient . UTF8.fromString
instance SExpable FC where
toSExp (FC f (sl, sc) (el, ec)) =
toSExp ((SymbolAtom "filename", StringAtom f),
(SymbolAtom "start", IntegerAtom (toInteger sl), IntegerAtom (toInteger sc)),
(SymbolAtom "end", IntegerAtom (toInteger el), IntegerAtom (toInteger ec)))
toSExp NoFC = toSExp ([] :: [String])
toSExp (FileFC f) = toSExp [(SymbolAtom "filename", StringAtom f)]
escape :: String -> String
escape = concatMap escapeChar
where
escapeChar '\\' = "\\\\"
escapeChar '"' = "\\\""
escapeChar c = [c]
type Parser a = P.Parsec () String a
sexp :: Parser SExp
sexp = SexpList <$> P.between (P.char '(') (P.char ')') (sexp `P.sepBy` (P.char ' '))
<|> atom
atom :: Parser SExp
atom = SexpList [] <$ P.string "nil"
<|> P.char ':' *> atomC
<|> StringAtom <$> P.between (P.char '"') (P.char '"') (P.many quotedChar)
<|> do ints <- some P.digitChar
case readDec ints of
((num, ""):_) -> return (IntegerAtom (toInteger num))
_ -> return (StringAtom ints)
atomC :: Parser SExp
atomC = BoolAtom True <$ P.string "True"
<|> BoolAtom False <$ P.string "False"
<|> SymbolAtom <$> many (P.noneOf " \n\t\r\"()")
quotedChar :: Parser Char
quotedChar = P.try ('\\' <$ P.string "\\\\")
<|> P.try ('"' <$ P.string "\\\"")
<|> P.noneOf "\""
data Opt = ShowImpl | ErrContext deriving Show
data WhatDocs = Overview | Full
data IdeModeCommand = REPLCompletions String
| Interpret String
| TypeOf String
| CaseSplit Int String
| AddClause Int String
| AddProofClause Int String
| AddMissing Int String
| MakeWithBlock Int String
| MakeCaseBlock Int String
| ProofSearch Bool Int String [String] (Maybe Int)
| MakeLemma Int String
| LoadFile String (Maybe Int)
| DocsFor String WhatDocs
| Apropos String
| GetOpts
| SetOpt Opt Bool
| Metavariables Int
| WhoCalls String
| CallsWho String
| BrowseNS String
| TermNormalise [(Name, Bool)] Term
| TermShowImplicits [(Name, Bool)] Term
| TermNoImplicits [(Name, Bool)] Term
| TermElab [(Name, Bool)] Term
| PrintDef String
| ErrString Err
| ErrPPrint Err
| GetIdrisVersion
sexpToCommand :: SExp -> Maybe IdeModeCommand
sexpToCommand (SexpList ([x])) = sexpToCommand x
sexpToCommand (SexpList [SymbolAtom "interpret", StringAtom cmd]) = Just (Interpret cmd)
sexpToCommand (SexpList [SymbolAtom "repl-completions", StringAtom prefix]) = Just (REPLCompletions prefix)
sexpToCommand (SexpList [SymbolAtom "load-file", StringAtom filename, IntegerAtom line]) = Just (LoadFile filename (Just (fromInteger line)))
sexpToCommand (SexpList [SymbolAtom "load-file", StringAtom filename]) = Just (LoadFile filename Nothing)
sexpToCommand (SexpList [SymbolAtom "type-of", StringAtom name]) = Just (TypeOf name)
sexpToCommand (SexpList [SymbolAtom "case-split", IntegerAtom line, StringAtom name]) = Just (CaseSplit (fromInteger line) name)
sexpToCommand (SexpList [SymbolAtom "add-clause", IntegerAtom line, StringAtom name]) = Just (AddClause (fromInteger line) name)
sexpToCommand (SexpList [SymbolAtom "add-proof-clause", IntegerAtom line, StringAtom name]) = Just (AddProofClause (fromInteger line) name)
sexpToCommand (SexpList [SymbolAtom "add-missing", IntegerAtom line, StringAtom name]) = Just (AddMissing (fromInteger line) name)
sexpToCommand (SexpList [SymbolAtom "make-with", IntegerAtom line, StringAtom name]) = Just (MakeWithBlock (fromInteger line) name)
sexpToCommand (SexpList [SymbolAtom "make-case", IntegerAtom line, StringAtom name]) = Just (MakeCaseBlock (fromInteger line) name)
sexpToCommand (SexpList (SymbolAtom "proof-search" : IntegerAtom line : StringAtom name : rest))
| [] <- rest = Just (ProofSearch True (fromInteger line) name [] Nothing)
| [SexpList hintexp] <- rest
, Just hints <- getHints hintexp = Just (ProofSearch True (fromInteger line) name hints Nothing)
| [SexpList hintexp, IntegerAtom depth] <- rest
, Just hints <- getHints hintexp = Just (ProofSearch True (fromInteger line) name hints (Just (fromInteger depth)))
where getHints = mapM (\h -> case h of
StringAtom s -> Just s
_ -> Nothing)
sexpToCommand (SexpList [SymbolAtom "make-lemma", IntegerAtom line, StringAtom name]) = Just (MakeLemma (fromInteger line) name)
sexpToCommand (SexpList [SymbolAtom "refine", IntegerAtom line, StringAtom name, StringAtom hint]) = Just (ProofSearch False (fromInteger line) name [hint] Nothing)
sexpToCommand (SexpList [SymbolAtom "docs-for", StringAtom name]) = Just (DocsFor name Full)
sexpToCommand (SexpList [SymbolAtom "docs-for", StringAtom name, SymbolAtom s])
| Just w <- lookup s opts = Just (DocsFor name w)
where opts = [("overview", Overview), ("full", Full)]
sexpToCommand (SexpList [SymbolAtom "apropos", StringAtom search]) = Just (Apropos search)
sexpToCommand (SymbolAtom "get-options") = Just GetOpts
sexpToCommand (SexpList [SymbolAtom "set-option", SymbolAtom s, BoolAtom b])
| Just opt <- lookup s opts = Just (SetOpt opt b)
where opts = [("show-implicits", ShowImpl), ("error-context", ErrContext)]
sexpToCommand (SexpList [SymbolAtom "metavariables", IntegerAtom cols]) = Just (Metavariables (fromIntegral cols))
sexpToCommand (SexpList [SymbolAtom "who-calls", StringAtom name]) = Just (WhoCalls name)
sexpToCommand (SexpList [SymbolAtom "calls-who", StringAtom name]) = Just (CallsWho name)
sexpToCommand (SexpList [SymbolAtom "browse-namespace", StringAtom ns]) = Just (BrowseNS ns)
sexpToCommand (SexpList [SymbolAtom "normalise-term", StringAtom encoded]) = let (bnd, tm) = decodeTerm encoded in
Just (TermNormalise bnd tm)
sexpToCommand (SexpList [SymbolAtom "show-term-implicits", StringAtom encoded]) = let (bnd, tm) = decodeTerm encoded in
Just (TermShowImplicits bnd tm)
sexpToCommand (SexpList [SymbolAtom "hide-term-implicits", StringAtom encoded]) = let (bnd, tm) = decodeTerm encoded in
Just (TermNoImplicits bnd tm)
sexpToCommand (SexpList [SymbolAtom "elaborate-term", StringAtom encoded]) = let (bnd, tm) = decodeTerm encoded in
Just (TermElab bnd tm)
sexpToCommand (SexpList [SymbolAtom "print-definition", StringAtom name]) = Just (PrintDef name)
sexpToCommand (SexpList [SymbolAtom "error-string", StringAtom encoded]) = Just . ErrString . decodeErr $ encoded
sexpToCommand (SexpList [SymbolAtom "error-pprint", StringAtom encoded]) = Just . ErrPPrint . decodeErr $ encoded
sexpToCommand (SymbolAtom "version") = Just GetIdrisVersion
sexpToCommand _ = Nothing
parseMessage :: String -> Either Err (SExp, Integer)
parseMessage x = case receiveString x of
Right (SexpList [cmd, (IntegerAtom id)]) -> Right (cmd, id)
Right x -> Left . Msg $ "Invalid message " ++ show x
Left err -> Left err
receiveString :: String -> Either Err SExp
receiveString = left (const $ Msg "parse failure") . P.parse sexp "(unknown)"
convSExp :: SExpable a => String -> a -> Integer -> String
convSExp pre s id =
let sex = SexpList [SymbolAtom pre, toSExp s, IntegerAtom id] in
let str = sExpToString sex in
(getHexLength str) ++ str
getHexLength :: String -> String
getHexLength s = printf "%06x" (1 + (length s))
ideModeEpoch :: Int
ideModeEpoch = 1