proof-assistant-bot 0.2.1 → 0.2.2
raw patch · 29 files changed
+489/−106 lines, 29 filesdep +telegram-bot-apidep ~Agdadep ~basedep ~mtl
Dependencies added: telegram-bot-api
Dependency ranges changed: Agda, base, mtl, telegram-bot-simple
Files
- CHANGELOG.md +10/−0
- README.md +157/−0
- proof-assistant-bot.cabal +8/−6
- src/Agda/Interaction/Command/EvalIn.hs +1/−1
- src/Agda/Interaction/Command/EvalTerm.hs +1/−1
- src/Agda/Interaction/Command/GiveMeta.hs +1/−1
- src/Agda/Interaction/Command/Internal/Parser.hs +4/−4
- src/Agda/Interaction/Command/RefineMeta.hs +1/−1
- src/Agda/Interaction/Command/Reload.hs +4/−3
- src/Agda/Interaction/Command/ShowConstraints.hs +1/−1
- src/Agda/Interaction/Command/ShowContext.hs +2/−2
- src/Agda/Interaction/Command/ShowMetas.hs +1/−1
- src/Agda/Interaction/Command/ShowScope.hs +1/−1
- src/Agda/Interaction/Command/TypeIn.hs +1/−1
- src/Agda/Interaction/Command/TypeOf.hs +1/−1
- src/Agda/Interaction/State.hs +2/−2
- src/Proof/Assistant/Agda.hs +4/−3
- src/Proof/Assistant/Alloy.hs +122/−0
- src/Proof/Assistant/Arend.hs +4/−4
- src/Proof/Assistant/Bot.hs +20/−9
- src/Proof/Assistant/Helpers.hs +4/−0
- src/Proof/Assistant/Idris.hs +5/−4
- src/Proof/Assistant/Interpreter.hs +9/−5
- src/Proof/Assistant/Lean.hs +17/−19
- src/Proof/Assistant/Response.hs +66/−24
- src/Proof/Assistant/Rzk.hs +7/−7
- src/Proof/Assistant/Settings.hs +27/−4
- src/Proof/Assistant/Transport.hs +2/−0
- src/Proof/Assistant/Version.hs +6/−1
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Revision history for proof-assistant-bot +## 0.2.2 -- 2024-02-07++- Support GHC 9.4 (see [#43](https://github.com/swamp-agr/proof-assistant-bot/pull/43)).+- Also various dependencies versions bumps:+ - base-4.17+ - Agda-2.6.4+ - mtl-2.3.1+ - rzk-0.7+ - telegram-bot-simple-0.13+ ## 0.2.1 -- 2023-02-07 - Agda bumped to 2.6.3, telegram-bot-simple bumped to 0.9 (see [#29](https://github.com/swamp-agr/proof-assistant-bot/pull/29)).
README.md view
@@ -8,6 +8,7 @@ 4. [Lean](#lean) 5. [Arend](#arend) 6. [Rzk](#rzk)+ 7. [Alloy](#alloy) 3. [Usage](#usage) 4. [Available instances](#available-instances) 5. [Acknowledgements](#acknowledgements)@@ -41,6 +42,12 @@ export PROOF_ASSISTANT_BOT_TOKEN="..." ``` +3. Another environmental variable that unfortunately needed is `NIX_PROFILE`. Please add it as ++```+export NIX_PROFILE="$HOME/.nix-profile"+```+ ### Coq 1. Install `opam >= 2.1.0`, e.g. [from here](https://ocaml.org/docs/up-and-running#installation-for-unix).@@ -165,6 +172,48 @@ No actions required. See `cabal.project` for more details. +### Alloy 6 / Alloy CLI Wrapper++You can read about CLI Wrapper here: https://github.com/AlloyTools/org.alloytools.alloy/issues/211++1. Get `nix` from [nixos.org](https://nixos.org/download.html#download-nix).++2. Get `java` and `openjdk17` via `nix`:++```bash+nix-env -i openjdk-17.0.4+8+```++3. Set `JAVA_HOME` environment variable to your openjdk location. You can use `readlink $HOME/.nix-profile/bin/java` and strip `/bin/java` from the end.++4. Get `alloy6` via `nix`:++```bash+nix-env -i alloy6+```++5. Create project directory to store Alloy projects (for different telegram chats) and set `ALLOY_PROJECT_DIR`.++6. Create directory for Alloy CLI wrapper and prepare Alloy CLI Wrapper:++```bash+mkdir -p $ALLOY_PROJECT_DIR/bin+cd $ALLOY_PROJECT_DIR+# Download Alloy CLI Wrapper+curl https://gist.githubusercontent.com/swamp-agr/560f0d9bf8dc034f99d6055a5a197285/raw/5b547616063ba834bfa2987bc1eb539f1ec8088d/Main.java > bin/Main.java+javac -cp "$NIX_PROFILE/share/alloy/*" -Xlint:all bin/Main.java+# Test: should be empty output and exit code 0+java -cp $NIX_PROFILE/share/alloy/alloy6.jar:$ALLOY_PROJECT_DIR/bin Main+```++7. Set up `graphviz` and `imagemagick` for generating plots based on Alloy CLI Wrapper output:++```bash+nix-env -i graphviz imagemagick+```++8. Set `ALLOY_PATH` environment variable to `$NIX_PROFILE/share/alloy/alloy6.jar`.+ ## Usage - Coq supports only typecheck of the user input via `/coq` command. Example:@@ -361,6 +410,114 @@ := \I -> \J -> \psi -> \zeta -> \X -> \chi -> \phi -> \f -> \k -> \(t, s) -> k t s ```++- Alloy is available via `/alloy` command.++ - Example:++ ```alloy+ /alloy open util/ordering[Id]++ sig Node {+ id : one Id,+ succ : one Node,+ var inbox : set Id,+ var outbox : set Id+ } + + sig Id {}+ + fact ring {+ all i : Id | lone id.i+ all n : Node | Node in n.^succ+ }+ + var sig elected in Node {}+ + fact elected {+ always {+ elected = {n : Node | once (n.id in n.inbox)}+ }+ }+ + enum Event { Send, Compute }+ + pred send [n : Node] {+ some i : n.outbox {+ n.outbox' = n.outbox - i+ n.succ.inbox' = n.succ.inbox + i+ }+ all m : Node - n.succ | m.inbox' = m.inbox+ all m : Node - n | m.outbox' = m.outbox+ }+ + fun send : Event -> Node {+ Send -> { n : Node | send[n] }+ }+ + pred compute [n : Node] {+ some i : n.inbox {+ n.inbox' = n.inbox - i+ n.outbox' = n.outbox + (i - n.id.*(~next))+ }+ all m : Node - n | m.inbox' = m.inbox+ all m : Node - n | m.outbox' = m.outbox+ }+ + fun compute : Event -> Node {+ Compute -> { n : Node | compute[n] }+ }+ + fun events : set Event {+ (send+compute).Node+ }+ + pred skip {+ inbox' = inbox+ outbox' = outbox+ }+ + fact init {+ no inbox+ outbox = id+ }+ + fact behaviour {+ always (skip or some n : Node | send[n] or compute[n])+ }+ + + run {} for 4 but exactly 4 Node, 10 steps+ run example {eventually some elected} for 3 but exactly 3 Node, 6 steps+ + assert safety {+ always lone elected+ }+ check safety for 3 but 15 steps+ + + pred sendEnabled [n : Node] {+ some n.outbox+ }+ + pred computeEnabled [n : Node] {+ some n.inbox+ }+ + pred fairness {+ always (all n : Node | (always sendEnabled[n] implies eventually send[n]))+ always (all n : Node | (always computeEnabled[n] implies eventually compute[n]))+ }+ + assert liveness {+ fairness and some Node implies eventually some elected+ }+ check liveness for 3+ ```++ - As result you can see:+ +  ## Available instances
proof-assistant-bot.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: proof-assistant-bot-version: 0.2.1+version: 0.2.2 synopsis: Telegram bot for proof assistants description: Bridge between Telegram Bot and several proof assistants. .@@ -22,23 +22,25 @@ hs-source-dirs: src ghc-options: -Wall default-language: Haskell2010- build-depends: base < 4.17+ build-depends: base < 4.18 , async , bytestring , dhall , directory , filepath- , mtl+ , mtl >= 2.3.1 , process , rzk , stm- , telegram-bot-simple >= 0.9+ , telegram-bot-api+ , telegram-bot-simple == 0.13 , text , unix , unordered-containers -- interpreters- , Agda >= 2.6.3+ , Agda >= 2.6.4 exposed-modules: Proof.Assistant.Agda+ , Proof.Assistant.Alloy , Proof.Assistant.Arend , Proof.Assistant.Idris , Proof.Assistant.Lean@@ -86,7 +88,7 @@ -- LANGUAGE extensions used by modules in this package. -- other-extensions:- build-depends: base ^>=4.14.3.0+ build-depends: base < 4.18 , proof-assistant-bot hs-source-dirs: app ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N
src/Agda/Interaction/Command/EvalIn.hs view
@@ -4,8 +4,8 @@ import Agda.Interaction.Base (ComputeMode (..)) import Agda.Interaction.BasicOps (evalInCurrent) import Agda.Syntax.Abstract.Pretty (prettyA)+import Agda.Syntax.Common.Pretty (prettyShow) import Agda.TypeChecking.Monad.Base (TCM)-import Agda.Utils.Pretty (prettyShow) import Data.ByteString (ByteString) import Agda.Interaction.Command.Internal.Parser
src/Agda/Interaction/Command/EvalTerm.hs view
@@ -4,9 +4,9 @@ import Agda.Interaction.Base (ComputeMode (..)) import Agda.Interaction.BasicOps hiding (parseExpr)+import Agda.Syntax.Common.Pretty (prettyShow) import Agda.TypeChecking.Monad.Base (TCM) import Agda.TypeChecking.Pretty (prettyTCM)-import Agda.Utils.Pretty (prettyShow) import Proof.Assistant.Helpers (toBS) import Data.ByteString (ByteString)
src/Agda/Interaction/Command/GiveMeta.hs view
@@ -1,9 +1,9 @@ {-# LANGUAGE OverloadedStrings #-} module Agda.Interaction.Command.GiveMeta where +import Agda.Syntax.Common.Pretty (prettyShow) import Agda.TypeChecking.Monad.Base (TCM) import Agda.TypeChecking.Pretty (prettyTCM)-import Agda.Utils.Pretty (prettyShow) import Data.ByteString (ByteString) import Agda.Interaction.Base (UseForce (..))
src/Agda/Interaction/Command/Internal/Parser.hs view
@@ -2,15 +2,15 @@ import Agda.Syntax.Abstract (Expr) import Agda.Syntax.Common (InteractionId (..))+import Agda.Syntax.Common.Pretty (text) import Agda.Syntax.Parser (exprParser, parse, parsePosString) import Agda.Syntax.Position (getRange, noRange, rStart) import Agda.Syntax.Translation.ConcreteToAbstract- (checkCohesionAttributes, concreteToAbstract, localToAbstract)+ (checkAttributes, concreteToAbstract, localToAbstract) import Agda.TypeChecking.Monad.Base import Agda.TypeChecking.Monad.MetaVars (lookupInteractionId, lookupLocalMeta, withInteractionId) import Agda.TypeChecking.Warnings (runPM) import Agda.Utils.Impossible (__IMPOSSIBLE__)-import Agda.Utils.Pretty (text) import Control.Monad.Except (MonadError(..)) import Data.ByteString (ByteString) import Data.Maybe (fromMaybe)@@ -26,7 +26,7 @@ -- liftIO $ putStrLn $ prettyShow scope let pos = fromMaybe (__IMPOSSIBLE__) (rStart r) (e, coh) <- runPM $ parsePosString exprParser pos (BS8.unpack s)- checkCohesionAttributes coh+ checkAttributes coh concreteToAbstract scope e actOnMeta :: [ByteString] -> (InteractionId -> Expr -> TCM a) -> TCM a@@ -40,7 +40,7 @@ parseExpr :: String -> TCM Expr parseExpr s = do (e, coh) <- runPM $ parse exprParser s- checkCohesionAttributes coh+ checkAttributes coh localToAbstract e return readM :: Read a => String -> TCM a
src/Agda/Interaction/Command/RefineMeta.hs view
@@ -1,8 +1,8 @@ module Agda.Interaction.Command.RefineMeta where +import Agda.Syntax.Common.Pretty (prettyShow) import Agda.TypeChecking.Monad.Base (TCM) import Agda.TypeChecking.Pretty (prettyTCM)-import Agda.Utils.Pretty (prettyShow) import Data.ByteString (ByteString) import Agda.Interaction.Base (UseForce (..))
src/Agda/Interaction/Command/Reload.hs view
@@ -8,13 +8,14 @@ import Agda.Interaction.FindFile (SourceFile (..)) import Agda.Interaction.Options (optOnlyScopeChecking) import Agda.Syntax.Translation.ConcreteToAbstract (importPrimitives)-import Agda.TypeChecking.Errors (applyFlagsToTCWarnings, prettyError)+import Agda.TypeChecking.Errors (applyFlagsToTCWarnings, renderError) import Agda.TypeChecking.Monad.Base (ModuleCheckMode (..), TypeError (..), TCM, commandLineOptions, iInsideScope, typeError) import Agda.TypeChecking.Monad.State (setScope) import Agda.Utils.FileName (AbsolutePath) import Agda.Utils.Null (unlessNullM)-import Control.Monad.Except (MonadError(..), unless)+import Control.Monad (unless)+import Control.Monad.Except (MonadError(..)) import Data.ByteString (ByteString) import Proof.Assistant.Helpers (toBS)@@ -27,7 +28,7 @@ _ <- importPrimitives pure "Type checked succesfully." `catchError` \e -> do- s <- prettyError e+ s <- renderError e pure $ "Failed. " <> toBS s checkFile :: AbsolutePath -> TCM CheckResult
src/Agda/Interaction/Command/ShowConstraints.hs view
@@ -3,8 +3,8 @@ module Agda.Interaction.Command.ShowConstraints where import Agda.Interaction.BasicOps (getConstraints)+import Agda.Syntax.Common.Pretty (prettyShow) import Agda.TypeChecking.Monad.Base (TCM)-import Agda.Utils.Pretty (prettyShow) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as BS8
src/Agda/Interaction/Command/ShowContext.hs view
@@ -4,14 +4,14 @@ import Agda.Interaction.Command.Internal.Parser import Agda.Syntax.Common (InteractionId (..), argNameToString)+import Agda.Syntax.Common.Pretty ((<+>), render, text) import Agda.TypeChecking.Monad.Base (TCM, getMetaInfo) import Agda.TypeChecking.Monad.Context (getContextTelescope) import Agda.TypeChecking.Monad.MetaVars (lookupInteractionId, lookupLocalMeta, withMetaInfo) import Agda.TypeChecking.Pretty (prettyTCM) import Agda.TypeChecking.Reduce (normalise) import Agda.TypeChecking.Substitute.Class (raise)-import Agda.Utils.Pretty ((<+>), render, text)-import Control.Monad.Except (zipWithM)+import Control.Monad (zipWithM) import Data.ByteString (ByteString) import qualified Agda.Syntax.Internal as I
src/Agda/Interaction/Command/ShowMetas.hs view
@@ -7,12 +7,12 @@ (outputFormId, typeOfMeta, typesOfHiddenMetas, typesOfVisibleMetas) import Agda.Syntax.Abstract.Pretty (prettyA) import Agda.Syntax.Common (InteractionId (..))+import Agda.Syntax.Common.Pretty (prettyShow, render) import Agda.Syntax.Position (noRange) import Agda.TypeChecking.Monad.Base (TCM, nmid) import Agda.TypeChecking.Monad.MetaVars (getInteractionRange, getMetaRange, withInteractionId, withMetaId) import Agda.Utils.Impossible (__IMPOSSIBLE__)-import Agda.Utils.Pretty (prettyShow, render) import Data.ByteString (ByteString) import Agda.Interaction.Command.Internal.Parser
src/Agda/Interaction/Command/ShowScope.hs view
@@ -1,8 +1,8 @@ module Agda.Interaction.Command.ShowScope where +import Agda.Syntax.Common.Pretty (prettyShow) import Agda.TypeChecking.Monad.Base (TCM) import Agda.TypeChecking.Monad.State (getScope)-import Agda.Utils.Pretty (prettyShow) import Data.ByteString (ByteString) import Proof.Assistant.Helpers (toBS)
src/Agda/Interaction/Command/TypeIn.hs view
@@ -3,9 +3,9 @@ import Agda.Interaction.Base (Rewrite (..)) import Agda.Interaction.BasicOps (typeInMeta)+import Agda.Syntax.Common.Pretty (prettyShow) import Agda.TypeChecking.Monad.Base (TCM) import Agda.TypeChecking.Pretty (prettyTCM)-import Agda.Utils.Pretty (prettyShow) import Data.ByteString (ByteString) import Agda.Interaction.Command.Internal.Parser
src/Agda/Interaction/Command/TypeOf.hs view
@@ -2,9 +2,9 @@ import Agda.Interaction.Base (Rewrite (..)) import Agda.Interaction.BasicOps (typeInCurrent)+import Agda.Syntax.Common.Pretty (prettyShow) import Agda.TypeChecking.Monad.Base (TCM) import Agda.TypeChecking.Pretty (prettyTCM)-import Agda.Utils.Pretty (prettyShow) import Data.ByteString (ByteString) import Agda.Interaction.Command.Internal.Parser
src/Agda/Interaction/State.hs view
@@ -4,7 +4,7 @@ import Agda.Interaction.Options import Agda.Syntax.Translation.ConcreteToAbstract (importPrimitives)-import Agda.TypeChecking.Errors (prettyError)+import Agda.TypeChecking.Errors (renderError) import Agda.TypeChecking.Monad.Base import Agda.TypeChecking.Monad.Options (setCommandLineOptions) import Control.Monad.Except (MonadError(..))@@ -67,7 +67,7 @@ -- | Catch error from Agda and make it looks pretty. catchAgdaError :: MonadTCM m => TCErr -> m ByteString catchAgdaError e = do- s <- prettyError e+ s <- renderError e return $ toBS s -- | Helper to modify 'TCEnv'.
src/Proof/Assistant/Agda.hs view
@@ -21,13 +21,14 @@ import Proof.Assistant.Helpers import Proof.Assistant.RefreshFile (validate) import Proof.Assistant.Request+import Proof.Assistant.Response import Proof.Assistant.Settings import Proof.Assistant.State import qualified Data.ByteString.Char8 as BS8 -- | Call Agda with its state. It will parse and execute command from the 'InterpreterRequest'.-callAgda :: AgdaState -> InterpreterRequest -> IO ByteString+callAgda :: AgdaState -> InterpreterRequest -> IO BotResponse callAgda currentAgdaState@AgdaState{..} InterpreterRequest{..} = do let InternalInterpreterSettings{..} = internal (settings interpreterState) asyncApi = withChat interpreterRequestTelegramChatId currentAgdaState $ do@@ -38,8 +39,8 @@ asyncTimer = asyncWait (coerce timeout) eresult <- race asyncTimer (handleErrorMaybe asyncApi) case eresult of- Left () -> pure "Time limit exceeded"- Right result -> pure result+ Left () -> pure (TextResponse "Time limit exceeded")+ Right result -> pure (TextResponse result) -- | Since every chat associated with its own Agda state, we need to modify 'TCEnv' -- by setting 'envCurrentPath' with an absolute path to the corresponding file.
+ src/Proof/Assistant/Alloy.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+module Proof.Assistant.Alloy where++import Control.Concurrent.Async (race)+import Control.Monad (forM)+import Data.Coerce (coerce)+import Data.Maybe (mapMaybe)+import Data.Text (unpack, pack)+import System.Directory+ (createDirectoryIfMissing, listDirectory, removeFile, withCurrentDirectory)+import System.Exit (ExitCode)+import System.FilePath+import System.Process (readProcessWithExitCode)+import Text.Read (readMaybe)++import Proof.Assistant.Helpers+import Proof.Assistant.RefreshFile+import Proof.Assistant.Request+import Proof.Assistant.Response+import Proof.Assistant.ResourceLimit+import Proof.Assistant.Settings+import Proof.Assistant.State++callAlloy :: InterpreterState AlloySettings -> InterpreterRequest -> IO BotResponse+callAlloy InterpreterState{..} ir = do+ let AlloySettings{..} = coerce settings+ s@ExternalInterpreterSettings{..} = externalAlloy+ projectDir = alloyProjectDir+ </> (tempFilePrefix <> chatIdToString (interpreterRequestTelegramChatId ir))+ createDirectoryIfMissing True projectDir+ (dir, path) <- refreshTmpFile s ir (Just projectDir)+ withCurrentDirectory dir $ do+ let run+ :: Executable -> CmdArgs -> FilePath -> Maybe [FilePath]+ -> IO (ExitCode, String, String)+ run exe arguments path' mPath = do+ readProcessWithExitCode (t2s exe) fullArgs ""+ where+ addPathAsArg "" = id+ addPathAsArg p = (<> [p])++ applyOutMaybe = case mPath of+ Nothing -> id+ Just extra -> (<> extra)+ fullArgs = applyOutMaybe $ addPathAsArg path' (unpack <$> coerce arguments)+ runProcess = run executable args path Nothing+ asyncExecutable = do+ setPriority priority+ (_exitCode, stdout, stderr) <- runProcess+ pure . validate path . toBS . unlines $ [stdout, stderr]+ asyncTimer = asyncWait time++ mkImagePath external file =+ let changeAbsolute = if external+ then (alloySharedDir </>) . takeFileName else id+ in changeAbsolute ((dropExtension file) <.> "png")+ mkGifPath file = (alloySharedDir </>) . takeFileName $ (dropExtension file) <.> "gif"++ eresult <- race asyncTimer (handleErrorMaybe asyncExecutable)+ result <- case eresult of+ Left () -> pure (TextResponse "Time limit exceeded")+ Right bs -> findDots dir >>= \case+ [] -> return (TextResponse bs)+ dot : [] -> do+ let imagePath = mkImagePath True dot+ _ <- run dotGraphExecutable dotGraphArgs dot $ Just ["-o" <> imagePath]+ pure (ImageResponse "image/png" imagePath Nothing Nothing)+ dots -> do+ images <- forM dots $ \dot -> do+ let imagePath = mkImagePath False dot+ _ <- run dotGraphExecutable dotGraphArgs dot $ Just ["-o" <> imagePath]+ pure imagePath++ let imagesAsArgs = CmdArgs (pack <$> images)+ fullImgHelperArgs = imageHelperArgs <> imagesAsArgs++ (_code, stdout, _stderr) <- run imageHelperExecutable fullImgHelperArgs "" Nothing++ let (mwidth, mheight, canvasArgs) = makeCanvasArgs stdout+ fullGifArgs = canvasArgs <> gifConverterArgs <> imagesAsArgs+ gifPath = mkGifPath path++ _ <- run gifConverterExecutable fullGifArgs gifPath Nothing+ pure (ImageResponse "image/gif" gifPath mwidth mheight)+ removeIntermediateFiles dir+ return result++makeCanvasArgs :: String -> (Maybe Int, Maybe Int, CmdArgs)+makeCanvasArgs stdout = (mwidth, mheight, args) + where+ splitOn c x = (takeWhile (/= c) x, reverse . takeWhile (/= c) . reverse $ x)+ parse = fmap (splitOn '-') . filter (/= mempty) . lines+ parseInts (w, h) = case (readMaybe w, readMaybe h) of+ (Just wn, Just hn) -> Just (wn, hn)+ (Just wn, Nothing) -> Just (wn, 0 :: Int)+ (Nothing, Just hn) -> Just (0 :: Int, hn)+ _ -> Nothing+ safeMaximum [] = 0+ safeMaximum xs = maximum xs+ textDimensions = parse stdout+ dimensions = mapMaybe parseInts textDimensions+ maxWidth = safeMaximum (fst <$> dimensions)+ maxHeight = safeMaximum (snd <$> dimensions)+ restoreMaybe 0 = Nothing+ restoreMaybe x = Just x++ mwidth = restoreMaybe maxWidth+ mheight = restoreMaybe maxHeight+ dimensionsAsArgs = s2t maxWidth <> "x" <> s2t maxHeight+ args = CmdArgs ["-size", dimensionsAsArgs, "-extent", dimensionsAsArgs, "-sample", dimensionsAsArgs]++findDots :: FilePath -> IO [FilePath]+findDots dir = do+ contents <- listDirectory dir+ pure . filter ((== ".dot") . takeExtension) $ contents++removeIntermediateFiles :: FilePath -> IO ()+removeIntermediateFiles dir+ = listDirectory dir >>= mapM_ removeFile . filter ((/= ".als") . takeExtension)
src/Proof/Assistant/Arend.hs view
@@ -3,7 +3,6 @@ module Proof.Assistant.Arend where import Control.Concurrent.Async (race)-import Data.ByteString (ByteString) import Data.Coerce (coerce) import Data.Text (unpack) import System.Directory (createDirectoryIfMissing, withCurrentDirectory)@@ -13,6 +12,7 @@ import Proof.Assistant.Helpers import Proof.Assistant.RefreshFile import Proof.Assistant.Request+import Proof.Assistant.Response import Proof.Assistant.ResourceLimit import Proof.Assistant.Settings import Proof.Assistant.State@@ -22,7 +22,7 @@ -- | Call Arend typechecker as CLI application. -- It prepares the CLI command, executes it and waits for response.-callArend :: InterpreterState ArendSettings -> InterpreterRequest -> IO ByteString+callArend :: InterpreterState ArendSettings -> InterpreterRequest -> IO BotResponse callArend InterpreterState{..} ir = do let ArendSettings{..} = coerce settings ExternalInterpreterSettings{..} = externalArend@@ -37,8 +37,8 @@ asyncTimer = asyncWait time eresult <- race asyncTimer (handleErrorMaybe asyncExecutable) case eresult of- Left () -> pure "Time limit exceeded"- Right bs -> pure bs+ Left () -> pure (TextResponse "Time limit exceeded")+ Right bs -> pure (TextResponse bs) -- | Arend requires a project for every file to typecheck. -- It reads project configuration, finds all files in a project directory
src/Proof/Assistant/Bot.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Proof.Assistant.Bot where@@ -15,12 +16,12 @@ import qualified Data.HashMap.Strict as HashMap +import Agda.Interaction.State import Proof.Assistant.Helpers import Proof.Assistant.Interpreter import Proof.Assistant.Request import Proof.Assistant.Response import Proof.Assistant.Settings-import Agda.Interaction.State import Proof.Assistant.Transport import Proof.Assistant.Version @@ -36,7 +37,7 @@ | Debug String -- ^ Write in STDOUT unknown input. -- | Supported backends.-data Backend = Agda | Arend | Idris | Coq | Lean | Rzk+data Backend = Agda | Arend | Idris | Coq | Lean | Rzk | Alloy -- | Initiate bot app based on a 'Model'. proofAssistantBot :: Model -> BotApp Model Action@@ -57,6 +58,7 @@ | isCommand "lean" update = Call <$> Just Lean <*> updateToRequest update | isCommand "arend" update = Call <$> Just Arend <*> updateToRequest update | isCommand "rzk" update = Call <$> Just Rzk <*> updateToRequest update+ | isCommand "alloy" update = Call <$> Just Alloy <*> updateToRequest update -- other | isCommand "help" update = Help <$> updateToRequest update | isCommand "version" update = Version <$> updateToRequest update@@ -80,33 +82,41 @@ Idris -> handle idris Lean -> handle lean Rzk -> handle rzk+ Alloy -> handle alloy handleAction (SendBack response) model = model <# sendResponseBack True response handleAction (Help req) model = model <# do let BotState {..} = model Settings{..} = botSettings case HashMap.lookup (bsToText $ interpreterRequestMessage req) helpMessages of- Nothing -> sendResponseBack False $ makeTelegramResponse req (textToBS help) + Nothing -> sendResponseBack False+ $ makeTelegramResponse req $ TextResponse $ textToBS help Just helpMessage -> sendResponseBack False- $ makeTelegramResponse req (textToBS helpMessage)+ $ makeTelegramResponse req $ TextResponse $ textToBS helpMessage handleAction (Version req) model = model <# do let BotState {..} = model Settings{..} = botSettings- sendResponseBack False $ makeTelegramResponse req (textToBS $ makeVersion version)+ sendResponseBack False+ $ makeTelegramResponse req $ TextResponse $ textToBS $ makeVersion version handleAction (Debug str) model = model <# (liftIO $ putStrLn str) -- | Helper that will try to deliver message even when Telegram failed to send it. sendResponseBack :: Bool -> InterpreterResponse -> BotM () sendResponseBack isMonospace response =- let req = toSendMessageRequest isMonospace response+ let req = toMessageRequest isMonospace response+ sendIt = \case+ TgMsg msgReq -> sendMessage msgReq+ TgPng imageReq -> sendPhoto imageReq+ TgGif gifReq -> sendAnimation gifReq waitAndRetry result = if responseOk result then pure () else case responseParameters result >>= responseParametersRetryAfter of Nothing -> pure () Just sec -> do liftIO . threadDelay $ coerce sec * 1000000- void (sendMessage req)+ void $ sendIt req+ in liftClientM $ do- result <- sendMessage req+ result <- sendIt req waitAndRetry result -- | Initiate Telegram Env, 'Model', start Bot, start backends concurrently.@@ -127,7 +137,8 @@ Concurrently (runInterpreter state coq) *> Concurrently (runInterpreter state idris) *> Concurrently (runInterpreter state lean) *>- Concurrently (runInterpreter state rzk)+ Concurrently (runInterpreter state rzk) *>+ Concurrently (runInterpreter state alloy) -- | Main function. run :: IO ()
src/Proof/Assistant/Helpers.hs view
@@ -27,6 +27,10 @@ t2s :: Coercible a Text => a -> String t2s = Text.unpack . coerce +-- | Cast something as 'Text'.+s2t :: Show a => a -> Text+s2t = Text.pack . show+ -- | Wait N seconds. asyncWait :: Time -> IO () asyncWait n = threadDelay (toInt @_ @Natural n * 1000000)
src/Proof/Assistant/Idris.hs view
@@ -9,6 +9,7 @@ import Idris.Interaction.Command import Proof.Assistant.Helpers import Proof.Assistant.Request+import Proof.Assistant.Response import Proof.Assistant.ResourceLimit import Proof.Assistant.Settings import Proof.Assistant.State@@ -17,10 +18,10 @@ -- | Call Idris 2 as CLI application. -- It prepares the CLI command, executes it and waits for response.-callIdris2 :: InterpreterState IdrisSettings -> InterpreterRequest -> IO ByteString+callIdris2 :: InterpreterState IdrisSettings -> InterpreterRequest -> IO BotResponse callIdris2 InterpreterState{..} ir@InterpreterRequest{..} = case parseRequest interpreterRequestMessage of- Left err -> pure err+ Left err -> pure (TextResponse err) Right (ecmd, request) -> do let s@ExternalInterpreterSettings{..} = coerce settings action <- chooseCommand s ir ecmd request@@ -32,8 +33,8 @@ asyncTimer = asyncWait time eresult <- race asyncTimer (handleErrorMaybe asyncExecutable) case eresult of- Left () -> pure "Time limit exceeded"- Right bs -> pure bs+ Left () -> pure (TextResponse "Time limit exceeded")+ Right bs -> pure (TextResponse bs) -- | Parse command. It could be unknown command or 'IdrisCommand' sub-command with its arguments -- or even expression to evaluate.
src/Proof/Assistant/Interpreter.hs view
@@ -15,7 +15,6 @@ import Control.Concurrent.Async import Control.Monad (forever)-import Data.ByteString (ByteString) import Data.Coerce (coerce) import Data.Text (unpack) import System.Directory@@ -24,6 +23,7 @@ import Agda.Interaction.State import Proof.Assistant.Agda+import Proof.Assistant.Alloy import Proof.Assistant.Arend import Proof.Assistant.Idris import Proof.Assistant.Lean@@ -51,7 +51,7 @@ -- Could be CLI, Haskell function, network, whatever. -- It should have the @state@ and associated @settings@ with the @state@. class Interpreter state settings | state -> settings where- interpretSafe :: state -> InterpreterRequest -> IO ByteString+ interpretSafe :: state -> InterpreterRequest -> IO BotResponse getSettings :: state -> InterpreterState settings instance Interpreter InternalState InternalInterpreterSettings where@@ -81,11 +81,15 @@ interpretSafe state request = callArend state request getSettings = id +instance Interpreter (InterpreterState AlloySettings) AlloySettings where+ interpretSafe state request = callAlloy state request+ getSettings = id+ -- ** External Interpreter -- | Call some external CLI application, probably Coq. callExternalInterpreter- :: ExternalInterpreterSettings -> (FilePath, FilePath) -> IO ByteString+ :: ExternalInterpreterSettings -> (FilePath, FilePath) -> IO BotResponse callExternalInterpreter ExternalInterpreterSettings{..} (dir, path) = withCurrentDirectory dir $ do contents <- readFile path@@ -96,5 +100,5 @@ asyncTimer = asyncWait time eresult <- race asyncTimer (handleErrorMaybe asyncExecutable) case eresult of- Left () -> pure "Time limit exceeded"- Right bs -> pure bs+ Left () -> pure (TextResponse "Time limit exceeded")+ Right bs -> pure (TextResponse bs)
src/Proof/Assistant/Lean.hs view
@@ -3,30 +3,27 @@ module Proof.Assistant.Lean where import Control.Concurrent.Async (race)-import Data.ByteString (ByteString) import Data.Coerce (coerce)-import Data.Foldable (foldl') import Data.Text (unpack) import System.Directory (withCurrentDirectory)+import System.Exit (ExitCode) import System.Process (readProcessWithExitCode) import Proof.Assistant.Helpers import Proof.Assistant.RefreshFile import Proof.Assistant.Request+import Proof.Assistant.Response import Proof.Assistant.ResourceLimit import Proof.Assistant.Settings import Proof.Assistant.State -import qualified Data.Text as Text--callLean :: InterpreterState LeanSettings -> InterpreterRequest -> IO ByteString+callLean :: InterpreterState LeanSettings -> InterpreterRequest -> IO BotResponse callLean InterpreterState{..} ir = do- let ls@LeanSettings{..} = coerce settings+ let LeanSettings{..} = coerce settings s@ExternalInterpreterSettings{..} = externalLean- (dir, path) <- refreshTmpFile s (validateLean ls ir) (Just projectDir)+ (dir, path) <- refreshTmpFile s ir (Just projectDir) withCurrentDirectory dir $ do- let fullArgs = (unpack <$> coerce args) <> [path]- runProcess = readProcessWithExitCode (t2s executable) fullArgs ""+ let runProcess = runWithSandboxMaybe sandbox executable args path asyncExecutable = do setPriority priority (_exitCode, stdout, stderr) <- runProcess@@ -34,15 +31,16 @@ asyncTimer = asyncWait time eresult <- race asyncTimer (handleErrorMaybe asyncExecutable) case eresult of- Left () -> pure "Time limit exceeded"- Right bs -> pure bs+ Left () -> pure (TextResponse "Time limit exceeded")+ Right bs -> pure (TextResponse bs) -validateLean :: LeanSettings -> InterpreterRequest -> InterpreterRequest-validateLean LeanSettings{..} ir@InterpreterRequest{..}- = ir { interpreterRequestMessage = validatedMsg }+runWithSandboxMaybe+ :: Maybe SandboxSettings -> Executable -> CmdArgs -> FilePath -> IO (ExitCode, String, String)+runWithSandboxMaybe Nothing exec arguments path+ = readProcessWithExitCode (t2s exec) ((unpack <$> coerce arguments) <> [path]) ""+runWithSandboxMaybe (Just SandboxSettings{..}) exec arguments path+ = readProcessWithExitCode (t2s sandboxExecutable) fullArgsList "" where- remove txt blockedPrefix = Text.replace blockedPrefix "" txt- removeAllFromLine x = foldl' remove x leanBlockList- removeUnsafeImports = textToBS . removeAllFromLine . bsToText-- validatedMsg = removeUnsafeImports interpreterRequestMessage+ sandboxArgsList = unpack <$> coerce sandboxArgs+ appArgsList = t2s exec : (unpack <$> coerce arguments)+ fullArgsList = concat [sandboxArgsList, ["--"], appArgsList, [path]]
src/Proof/Assistant/Response.hs view
@@ -5,42 +5,84 @@ import Data.ByteString (ByteString) import Data.Text.Encoding (decodeUtf8) import Telegram.Bot.API- (ChatId, MessageId, ParseMode (..), SendMessageRequest (..), SomeChatId (..))+ (ChatId, ContentType, MessageId+ , InputFile (..), ParseMode (..), PhotoFile (..)+ , SendAnimationRequest (..), SendMessageRequest (..), SendPhotoRequest (..), SomeChatId (..)+ , defSendAnimation, defSendMessage, defSendPhoto+ ) import Proof.Assistant.Request +-- | Bot response: either text message or picture (filepath and its content type).+data BotResponse+ = TextResponse !ByteString+ | ImageResponse+ { imgResponseCType :: !ContentType+ , imgResponsePath :: !FilePath+ , imgResponeWidth :: !(Maybe Int)+ , imgResponseHeight :: !(Maybe Int)+ }+ -- | Response for Telegram. data InterpreterResponse = InterpreterResponse { interpreterResponseTelegramChatId :: !ChatId -- ^ Telegram ChatId (for reply). , interpreterResponseTelegramMessageId :: !MessageId -- ^ Telegram MessageId (for reply).- , interpreterResponseResponse :: !ByteString -- ^ output data.+ , interpreterResponseResponse :: !BotResponse -- ^ output data. } --- | Cast 'InterpreterResponse' to 'SendMessageRequest'.--- If first argument is 'True'+data TelegramMessage+ = TgMsg SendMessageRequest+ | TgPng SendPhotoRequest+ | TgGif SendAnimationRequest++-- | Cast 'InterpreterResponse' to either 'SendMessageRequest' or 'SendPhotoRequest'.+-- For text messages:+-- - If first argument is 'True' -- then it will wrap message in Monospace font and mark it with @MarkdownV2@ parse mode.--- Otherwise, text message will be sent.--- For all responses from Backends 'True' should be specified.-toSendMessageRequest :: Bool -> InterpreterResponse -> SendMessageRequest-toSendMessageRequest isMonospace InterpreterResponse{..} = SendMessageRequest- { sendMessageChatId = SomeChatId interpreterResponseTelegramChatId- , sendMessageMessageThreadId = Nothing- , sendMessageText- = if isMonospace- then "```\n" <> decodeUtf8 interpreterResponseResponse <> "\n```\n"- else decodeUtf8 interpreterResponseResponse <> "\n"- , sendMessageParseMode = if isMonospace then Just MarkdownV2 else Nothing- , sendMessageEntities = Nothing- , sendMessageDisableWebPagePreview = Nothing- , sendMessageDisableNotification = Nothing- , sendMessageProtectContent = Nothing- , sendMessageReplyToMessageId = Just interpreterResponseTelegramMessageId- , sendMessageAllowSendingWithoutReply = Nothing- , sendMessageReplyMarkup = Nothing- }+-- - Otherwise, text message will be sent.+-- - For all responses from Backends 'True' should be specified.+-- +-- For images content type should be specified.+toMessageRequest+ :: Bool -> InterpreterResponse -> TelegramMessage+toMessageRequest isMonospace InterpreterResponse{..} = case interpreterResponseResponse of+ TextResponse bytes -> TgMsg $ SendMessageRequest+ { sendMessageChatId = SomeChatId interpreterResponseTelegramChatId+ , sendMessageMessageThreadId = Nothing+ , sendMessageText+ = if isMonospace+ then "```\n" <> decodeUtf8 bytes <> "\n```\n"+ else decodeUtf8 bytes <> "\n"+ , sendMessageParseMode = if isMonospace then Just MarkdownV2 else Nothing+ , sendMessageEntities = Nothing+ , sendMessageReplyParameters = Nothing+ , sendMessageLinkPreviewOptions = Nothing+ , sendMessageDisableNotification = Nothing+ , sendMessageProtectContent = Nothing+ , sendMessageReplyToMessageId = Just interpreterResponseTelegramMessageId+ , sendMessageReplyMarkup = Nothing+ }+ ImageResponse ctype imgPath mwidth mheight -> case ctype of+ "image/png" ->+ let reply = defSendPhoto (SomeChatId interpreterResponseTelegramChatId)+ $ MakePhotoFile $ InputFile imgPath ctype+ in TgPng (reply { sendPhotoReplyToMessageId = Just interpreterResponseTelegramMessageId })+ "image/gif" ->+ let reply = defSendAnimation (SomeChatId interpreterResponseTelegramChatId)+ $ InputFile imgPath "image/gif"+ in TgGif+ (reply { sendAnimationReplyToMessageId = Just interpreterResponseTelegramMessageId+ , sendAnimationWidth = mwidth+ , sendAnimationHeight = mheight+ }) + _ ->+ let reply =+ defSendMessage (SomeChatId interpreterResponseTelegramChatId) "Unsupported request"+ in TgMsg (reply { sendMessageReplyToMessageId = Just interpreterResponseTelegramMessageId })+ -- | Cast 'InterpreterRequest' and output data to 'InterpreterResponse'.-makeTelegramResponse :: InterpreterRequest -> ByteString -> InterpreterResponse+makeTelegramResponse :: InterpreterRequest -> BotResponse -> InterpreterResponse makeTelegramResponse InterpreterRequest{..} response = InterpreterResponse { interpreterResponseTelegramChatId = interpreterRequestTelegramChatId
src/Proof/Assistant/Rzk.hs view
@@ -3,30 +3,30 @@ module Proof.Assistant.Rzk where import Control.Concurrent.Async (race)-import Data.ByteString (ByteString) import Data.Coerce (coerce) import System.Mem -import Rzk.Polylingual+import Rzk.Main import Proof.Assistant.Helpers import Proof.Assistant.Request+import Proof.Assistant.Response import Proof.Assistant.Settings import Proof.Assistant.State -- | Call Polilyngual API. Rzk will do all the job and return response or an error as result.-callRzk :: InterpreterState InternalInterpreterSettings -> InterpreterRequest -> IO ByteString+callRzk :: InterpreterState InternalInterpreterSettings -> InterpreterRequest -> IO BotResponse callRzk InterpreterState{..} ir = do let InternalInterpreterSettings{..} = settings asyncApi = do enableAllocationLimit setAllocationCounter (fromIntegral allocations) let makeResult = pure- . either toBS (toBS . compileSomeModule)- . safeParseSomeModule . fromBS . dropCommand+ . either toBS toBS+ . typecheckString . fromBS . dropCommand makeResult (interpreterRequestMessage ir) asyncTimer = asyncWait (coerce timeout) eresult <- race asyncTimer (handleErrorMaybe asyncApi) case eresult of- Left () -> pure "Time limit exceeded"- Right result -> pure result+ Left () -> pure (TextResponse "Time limit exceeded")+ Right result -> pure (TextResponse result)
src/Proof/Assistant/Settings.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} module Proof.Assistant.Settings where @@ -17,6 +18,7 @@ , helpMessages :: HashMap Text Text -- ^ Help messages for sub-commands. , version :: Text -- ^ Version message. , interpretersSettings :: !InterpretersSettings -- ^ Settings for backend interpreters.+ , sharedDir :: !FilePath } deriving (Generic, FromDhall) data ExternalInterpreterSettings = ExternalInterpreterSettings@@ -28,6 +30,7 @@ , tempFilePrefix :: !FilePath -- ^ Prefix to filepath to avoid confusion between different interpreters. , fileExtension :: !FilePath -- ^ File extension used by interpreter. , inputSize :: !Natural -- ^ input size in bytes.+ , sandbox :: !(Maybe SandboxSettings) } deriving (Generic, FromDhall) data InternalInterpreterSettings = InternalInterpreterSettings@@ -44,7 +47,6 @@ data LeanSettings = LeanSettings { projectDir :: !FilePath -- ^ Lean requires project where to store source files for typechecking.- , leanBlockList :: [Text] , externalLean :: !ExternalInterpreterSettings } deriving (Generic, FromDhall) @@ -56,13 +58,25 @@ , externalArend :: !ExternalInterpreterSettings } deriving (Generic, FromDhall) +data AlloySettings = AlloySettings+ { alloyProjectDir :: !FilePath+ , dotGraphExecutable :: !Executable+ , dotGraphArgs :: !CmdArgs+ , imageHelperExecutable :: !Executable+ , imageHelperArgs :: !CmdArgs+ , gifConverterExecutable :: !Executable+ , gifConverterArgs :: !CmdArgs+ , alloySharedDir :: !FilePath+ , externalAlloy :: !ExternalInterpreterSettings+ } deriving (Generic, FromDhall)+ newtype IdrisSettings = IdrisSettings ExternalInterpreterSettings deriving newtype (FromDhall, ToInterpreterState) deriving stock Generic newtype CmdArgs = CmdArgs [Text] deriving stock Generic- deriving newtype FromDhall+ deriving newtype (FromDhall, Semigroup) newtype Executable = Executable Text deriving stock Generic@@ -91,6 +105,11 @@ , hard :: !Natural } deriving (Generic, FromDhall) +data SandboxSettings = SandboxSettings+ { sandboxExecutable :: !Executable+ , sandboxArgs :: !CmdArgs+ } deriving (Generic, FromDhall)+ -- | Combination of all supported interpreters settings. data InterpretersSettings = InterpretersSettings { agda :: !AgdaSettings@@ -99,6 +118,7 @@ , coq :: !ExternalInterpreterSettings , lean :: !LeanSettings , rzk :: !InternalInterpreterSettings+ , alloy :: !AlloySettings } deriving (Generic, FromDhall) -- | Load settings from file.@@ -114,10 +134,10 @@ getQueueSize :: settings -> Natural instance ToInterpreterState InternalInterpreterSettings where- getQueueSize = inputSize+ getQueueSize InternalInterpreterSettings{inputSize} = inputSize instance ToInterpreterState ExternalInterpreterSettings where- getQueueSize = inputSize+ getQueueSize ExternalInterpreterSettings{inputSize} = inputSize instance ToInterpreterState AgdaSettings where getQueueSize = getQueueSize . internal@@ -127,3 +147,6 @@ instance ToInterpreterState ArendSettings where getQueueSize = getQueueSize . externalArend++instance ToInterpreterState AlloySettings where+ getQueueSize = getQueueSize . externalAlloy
src/Proof/Assistant/Transport.hs view
@@ -22,6 +22,7 @@ , coq :: !ExternalState , lean :: !(InterpreterState LeanSettings) , rzk :: !InternalState+ , alloy :: !(InterpreterState AlloySettings) } -- | Initiate new interpreters from settings.@@ -33,6 +34,7 @@ coq <- newInterpreterState $ Settings.coq settings lean <- newInterpreterState $ Settings.lean settings rzk <- newInterpreterState $ Settings.rzk settings+ alloy <- newInterpreterState $ Settings.alloy settings pure Interpreters{..} type ExternalState = InterpreterState ExternalInterpreterSettings
src/Proof/Assistant/Version.hs view
@@ -8,6 +8,10 @@ import qualified Data.Text as Text +-- | Version of Agda.+agdaVersion :: String+agdaVersion = VERSION_Agda+ -- | Version of Rzk. rzkVersion :: String rzkVersion = VERSION_rzk@@ -19,6 +23,7 @@ -- | Helper to build version from template message. makeVersion :: Text -> Text makeVersion txt = Text.unlines- [ Text.replace "<bot_version>" (Text.pack proofAssistantBotVersion) txt+ [ Text.replace "<bot_version>" (Text.pack proofAssistantBotVersion)+ $ Text.replace "<agda_version>" (Text.pack agdaVersion) txt , "rzk v." <> Text.pack rzkVersion ]