packages feed

agda2train (empty) → 0.0.1.0

raw patch · 8 files changed

+1035/−0 lines, 8 filesdep +Agdadep +aesondep +aeson-pretty

Dependencies added: Agda, aeson, aeson-pretty, async, base, bytestring, containers, deepseq, directory, file-embed, filepath, mtl, pretty, unordered-containers

Files

+ LICENSE view
@@ -0,0 +1,22 @@+Copyright (c) 2023, Orestis Melkonian+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+   list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright notice,+   this list of conditions and the following disclaimer in the documentation+   and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,10 @@+agda2train: An Agda backend to generate training data for machine learning+===========================================================================++This is a work in progress.++## Relevant Agda issues++- [Type-on-hover #516](https://github.com/agda/agda/issues/516)+- [Step-by-step evaluation #4747](https://github.com/agda/agda/issues/4747)+- [Reverting projection-like optimization #5142](https://github.com/agda/agda/pull/5142/)
+ agda2train.cabal view
@@ -0,0 +1,62 @@+cabal-version:      2.4+name:               agda2train+version:            0.0.1.0+synopsis:           Agda backend to generate training data for machine learning purposes.+description:        -+category:           Agda, machine learning+author:             Orestis Melkonian+maintainer:         melkon.or@gmail.com+stability:          experimental+homepage:           http://github.com/omelkonian/agda2train/+bug-reports:        https://github.com/omelkonian/agda2train/issues+license:            BSD-3-Clause+license-file:       LICENSE+copyright:          (c) 2023 Orestis Melkonian++extra-source-files:+    data/defsToSkip.txt+    README.md+    LICENSE++source-repository head+    type: git+    location: git://github.com/omelkonian/agda2train.git++++executable agda2train+    main-is:             Main.hs+    other-modules:       ToTrain, Output, AgdaInternals+    build-depends:       base >=4.12.0.0 && <4.20+                       , Agda >=2.6.3 && <2.6.4+                       , pretty >=1.1.3.3 && <1.2+                       , containers >=0.5.11.0 && <0.8+                       , unordered-containers >=0.2.9.0 && <0.3+                       , aeson >=2.0 && <2.3+                       , aeson-pretty == 0.8.10+                       , bytestring >=0.10.8.1 && <0.13+                       , mtl >=2.2.1 && <2.4+                       , directory >=1.2.6.2 && <1.4+                       , filepath >=1.4.1.0 && <1.5+                       , deepseq >=1.4.2.0 && <1.6+                       , async >=2.2 && <2.3+                       , file-embed == 0.0.15.0+    hs-source-dirs:      src+    default-language:    Haskell2010+    default-extensions:  LambdaCase+                       , NamedFieldPuns+                       , OverloadedStrings+                       , RecordWildCards+                       , TupleSections+                       , DeriveGeneric+                       , DeriveAnyClass+                       , DeriveFunctor+                       , DeriveFoldable+                       , DeriveTraversable+                       , DerivingVia+                       , StandaloneDeriving+                       , PatternSynonyms+                       , TypeFamilies+                       , TypeOperators+                       , FunctionalDependencies+    ghc-options: -threaded -rtsopts -with-rtsopts=-N
+ data/defsToSkip.txt view
@@ -0,0 +1,47 @@+Data.Relation.Binary.Subset.Propositional.Properties.filter⁺+foundation.homotopies._.compute-ind-htpy+foundation.equivalences.coh-unit-laws-equiv+foundation.functoriality-propositional-truncation.id-map-trunc-Prop+foundation.functoriality-coproduct-types._.equiv-mutually-exclusive-coprod+trees.polynomial-endofunctors.coh-refl-htpy-polynomial-endofunctor+foundation.uniqueness-image._.htpy-map-hom-equiv-slice-uniqueness-im+foundation.uniqueness-image._.tetrahedron-hom-equiv-slice-uniqueness-im+foundation.functoriality-set-truncation._.htpy-map-hom-slice-trunc-im-Set+foundation.functoriality-set-truncation._.tetrahedron-map-hom-slice-trunc-im-Set+univalent-combinatorics.2-element-types._.is-not-identity-swap-2-Element-Type+foundation.unordered-pairs._.eq-Eq-refl-unordered-pair+MGS.Equivalence-Induction.𝔾-≃-equation+MGS.More-FunExt-Consequences.EM-is-subsingleton+foundation.unordered-pairs.preserves-refl-htpy-unordered-pair+MGS.Equivalence-Constructions.Eq-Eq-cong+foundation.unordered-pairs.id-equiv-standard-unordered-pair+MGS.Embeddings.Emb→fun+trees.morphisms-algebras-polynomial-endofunctors._.refl-htpy-hom-algebra-polynomial-endofunctor+MGS.Universe-Lifting._.q+UF.Equiv-FunExt.prop-=-≃-⇔+trees.morphisms-algebras-polynomial-endofunctors._.is-contr-total-htpy-hom-algebra-polynomial-endofunctor+trees.w-types.htpy-hom-𝕎-Alg+UF.FunExt-Properties.naive-funext-gives-funext₀+graph-theory.morphisms-undirected-graphs._.refl-htpy-hom-Undirected-Graph+UF.UA-FunExt._._.h+graph-theory.morphisms-undirected-graphs._.is-contr-total-htpy-hom-Undirected-Graph+UF.PairFun.pair-fun-embedding+graph-theory.equivalences-undirected-graphs._.edge-standard-unordered-pair-vertices-id-equiv-Undirected-Graph+UF.UniverseEmbedding.hSet-embeddings-are-embeddings+graph-theory.equivalences-undirected-graphs._.is-contr-total-htpy-equiv-Undirected-Graph+UF.Size._.Set-Replacement+Lifting.IdentityViaSIP.𝓛-Id+foundation.partitions._.is-section-map-inv-compute-block-partition+Lifting.IdentityViaSIP.𝓛-Id·+foundation.partitions._.is-retraction-map-inv-compute-block-partition+Lifting.Size._.e+foundation.partitions._.is-emb-inhabited-subtype-block-partition+InjectiveTypes.Blackboard.injective._.e+foundation.partitions._.compute-is-in-block-partition+foundation.partitions._.is-contr-block-containing-element-partition+foundation.partitions._.is-in-block-class-partition+foundation.partitions._.compute-total-block-partition+TypeTopology.SquashedCantor._.fg+foundation.partitions.partition-Set-Indexed-Σ-Decomposition+foundation.set-quotients._.is-section-equivalence-class-set-quotient+foundation.set-quotients._.is-retraction-equivalence-class-set-quotient
+ src/AgdaInternals.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}+-- | Missing instances for WriterT in Agda's internals.+--+-- NB: we are not exporting any definitions, just typeclass instances.+module AgdaInternals () where++import Data.String++import Control.Monad+import Control.Monad.Writer+import Control.Monad.State++import Agda.TypeChecking.Monad+import Agda.TypeChecking.Warnings+import Agda.TypeChecking.Pretty++instance (Monoid r, Monad m, Semigroup (m Doc)) => Semigroup (WriterT r m Doc) where+  (<>) = liftM2 (<>)+instance (Monoid r, Monad m, IsString (m a)) => IsString (WriterT r m a) where+  fromString s = WriterT $ fmap (,mempty) $ fromString s+instance (Monoid r, MonadStConcreteNames m) => MonadStConcreteNames (WriterT r m) where+  runStConcreteNames m = WriterT $ runStConcreteNames $ StateT $ \s -> do+    ((x,s'),ns) <- runWriterT $ runStateT m s+    return ((x,ns),s')+instance (Monoid r, MonadInteractionPoints m) => MonadInteractionPoints (WriterT r m)+instance (Monoid r, MonadFresh i m) => MonadFresh i (WriterT r m)+instance (Monoid r, MonadWarning m) => MonadWarning (WriterT r m) where+instance (Monoid r, MonadBlock m) => MonadBlock (WriterT r m) where+  catchPatternErr h m = WriterT $ catchPatternErr (runWriterT . h) (runWriterT m)+instance (Monoid r, MonadTCM m, MonadConstraint m) => MonadConstraint (WriterT r m) where+  addConstraint x = lift . addConstraint x+  addAwakeConstraint x = lift . addAwakeConstraint x+  solveConstraint = lift . solveConstraint+  solveSomeAwakeConstraints x = lift . solveSomeAwakeConstraints x+  wakeConstraints = lift . wakeConstraints+  stealConstraints = lift . stealConstraints+  modifyAwakeConstraints = lift . modifyAwakeConstraints+  modifySleepingConstraints = lift . modifySleepingConstraints+instance (Monoid r, MonadTCM m, MonadMetaSolver m) => MonadMetaSolver (WriterT r m) where+  newMeta' x y z k l = lift . newMeta' x y z k l+  assignV x y z w = lift . assignV x y z w+  assignTerm' x y = lift . assignTerm' x y+  etaExpandMeta x = lift . etaExpandMeta x+  updateMetaVar x = lift . updateMetaVar x+  speculateMetas x = WriterT . runWriterT . speculateMetas x
+ src/Main.hs view
@@ -0,0 +1,234 @@+{-# LANGUAGE TypeApplications #-}+-- | Main driver for the @agda2train@ executable.+module Main where++import GHC.Generics++import System.Environment ( getArgs, withArgs )+import System.Directory ( createDirectoryIfMissing, doesFileExist )+import System.FilePath ( (</>) )+import System.Console.GetOpt ( OptDescr(..), ArgDescr(..) )++import qualified Data.List as L+import qualified Data.Set as S+import qualified Data.HashMap.Strict as HM+import qualified Data.ByteString.Lazy as BL+import Data.Aeson ( ToJSON )+import Data.Aeson.Encode.Pretty+  ( encodePretty', Config(..), defConfig, Indent(..), keyOrder )++import Control.Monad+import Control.Monad.IO.Class ( liftIO )+import Control.DeepSeq ( NFData )++import Agda.Main ( runAgda )+import Agda.Compiler.Backend hiding (Reduced)+import Agda.Compiler.Common ( curDefs )++import Agda.Utils.Pretty+import Agda.Utils.Maybe+import Agda.Utils.Monad++import Agda.Syntax.Scope.Base+import Agda.Syntax.Scope.Monad+import Agda.Syntax.TopLevelModuleName++import Agda.TypeChecking.Reduce+import qualified Agda.TypeChecking.Pretty as P++import Agda.Compiler.Common ( curIF )++import ToTrain+import Output++-- | The main entrypoint for the @agda2train@ executable.+main :: IO ()+main = do+  as <- getArgs+  let extraFlags = [] -- ["--no-projection-like"]+  withArgs (extraFlags ++ as) $+    runAgda [Backend $ mkBackend train]++-- | Make an Agda backend from a given training function (c.f. 'ToTrain.TrainF').+mkBackend :: TrainF ->+  Backend' Options Options ([ScopeEntry], [ScopeEntry]) () (String, [Sample])+mkBackend trainF = Backend'+  { backendName      = "agda2train"+  , backendVersion   = Nothing+  , options          = defaultOptions+  , commandLineFlags =+      [ Option ['r'] ["recurse"] (NoArg recOpt)+        "Recurse into imports/dependencies."+      , Option ['x'] ["no-json"] (NoArg noJsonOpt)+        "Skip generation of JSON files. (just debug print)"+      , Option [] ["ignore-existing-json"] (NoArg ignoreJsonOpt)+        "Ignore existing JSON files. (i.e. always overwrite)"+      , Option [] ["print-json"] (NoArg printJsonOpt)+        "Print JSON output. (for debugging)"+      , Option [] ["no-terms"] (NoArg includeDefsOpt)+        "Do not include definitions of things in scope"+      , Option [] ["no-privates"] (NoArg includePrivsOpt)+        "Do not include private definitions"+      , Option ['o'] ["out-dir"] (ReqArg outDirOpt "DIR")+        "Generate data at DIR. (default: project root)"+      ]+  , isEnabled             = \ _ -> True+  , scopeCheckingSuffices = False+  , mayEraseType          = \ _ -> return True+  , preCompile  = return+  , postCompile = \ _ _ _ -> return ()+  , preModule   = \ opts isMain md _ ->+      let+        processScopeEntry :: QName -> TCM (Maybe ScopeEntry)+        processScopeEntry qn =+          caseMaybeM (tryMaybe $ do+            def <- getConstInfo qn+            case theDef def of+              GeneralizableVar -> fail "not handling `variable` definitions"+              DataOrRecSig _   -> fail "not handling `DataOrRecSig` definitions"+              _ -> typeOfConst qn) (pure Nothing) $ \ty -> do+            rty  <- mkReduced ty+            pty  <- ppm ty+            rty' <- traverse convert rty++            def  <- getConstInfo qn+            pdef <- ppm def+            def' <- convert def++            report 20 $ ppm (pp qn) <> " : " <> ppm ty+            report 30 $ "      *pretty: " <> P.text (pp ty)+            report 20 $ ppm (pp qn) <> " = " <> ppm def+            report 30 $ "      *pretty: " <> P.text (pp def)+            reportReduced rty+            report 20 ""++            return $ Just $ ppName qn :~ ScopeEntry+              { _type      = prender pty :> rty'+              , definition = boolToMaybe (includeDefs opts)+                           $ prender pdef :> def'+              , holes = Nothing+              }+      in+        ifM (skip opts isMain md) (return $ Skip ()) $ do+          report 10 $ "************************ "+                   <> ppm md <> " (" <> ppm (show isMain)+                   <> ") ***********************************"+          -- printScope "" 1 ""+          setScope . iInsideScope =<< curIF+          -- ** public+          NameSpace _ _ ns <- allThingsInScope <$> getCurrentScope+          scopeEntries <- mapMaybeM processScopeEntry (S.toList ns)+          -- ** private+          privs <- HM.filterWithKey (\k _ -> k `S.notMember` ns) <$> liftTCM curDefs+          privScopeEntries <- mapMaybeM processScopeEntry (HM.keys privs)+          report 10 "******************************************************************"+          return $ Recompile (scopeEntries, privScopeEntries)+  , compileDef = \ _ _ _ def -> (ppName (defName def) ,) <$> runC (forEachHole trainF def)+  , postModule = \ opts (scopeEntries, privScopeEntries) isMain md samples ->+    let mn = pp md in+    unlessM (skip opts isMain md) $ do+      whenJust (outDir opts) (liftIO . createDirectoryIfMissing True)+      unless (noJson opts) $ liftIO $ do+        let+          isGlobal = \(n :~ _) -> isNothing $ lookup n samples+          (globals, locals') = L.partition isGlobal scopeEntries+          locals = flip map locals' $ \(n :~ l) ->+              n :~ l { holes = Just $ fromJust $ lookup n samples }+          plocals = flip map privScopeEntries $ \(n :~ l) ->+              n :~ l { holes = Just $ fromJust $ lookup n samples }+          fileData = mn :~ TrainData+            { scopeGlobal  = globals+            , scopeLocal   = locals+            , scopePrivate = boolToMaybe (includePrivs opts) plocals+            }++        encodeFile (getOutFn opts mn) fileData+        when (printJson opts) $ do+          putStrLn "************************ JSON ************************"+          BL.putStr (encode fileData)+          putStrLn "******************************************************"+  }+  where+    skip :: Options -> IsMain -> TopLevelModuleName -> TCM Bool+    skip opts@Options{..} isMain md = do+      jsonExists <- liftIO $ doesFileExist (getOutFn opts $ pp md)+      return $ (not recurse && (isMain == NotMain))+            || (not noJson && jsonExists && not ignoreJson)++-- * Command-line flags++-- | Available options to configure how @agda2train@ generates training data:+--+-- ['recurse'] whether to recursively generate data from all transitive dependencies+--+-- ['noJson'] mock run without generating any actual JSON files+--+-- ['ignoreJson'] compile everything from scratch+--+-- ['includeDefs'] whether to include definitions in scope, or just their type+--+-- ['includePrivs'] whether to include private definitions as well+--+-- (run @agda2train -h/--help@ for a human-readable description of all options)+data Options = Options+  { recurse, noJson, ignoreJson, printJson, includeDefs, includePrivs :: Bool+  , outDir  :: Maybe FilePath+  } deriving (Generic, NFData)++-- | The default options.+defaultOptions = Options+  { recurse      = False+  , noJson       = False+  , ignoreJson   = False+  , printJson    = False+  , includeDefs  = True+  , includePrivs = True+  , outDir       = Nothing+  }++recOpt, noJsonOpt, ignoreJsonOpt, printJsonOpt, includeDefsOpt, includePrivsOpt+  :: Monad m => Options -> m Options+recOpt          opts = return $ opts { recurse      = True }+noJsonOpt       opts = return $ opts { noJson       = True }+printJsonOpt    opts = return $ opts { printJson    = True }+ignoreJsonOpt   opts = return $ opts { ignoreJson   = True }+includeDefsOpt  opts = return $ opts { includeDefs  = False }+includePrivsOpt opts = return $ opts { includePrivs = False }++outDirOpt :: Monad m => FilePath -> Options -> m Options+outDirOpt fp opts = return $ opts { outDir = Just fp }++getOutDir :: Options -> FilePath+getOutDir opts = case outDir opts of+  Just fp -> fp+  Nothing -> "."++getOutFn :: Options -> String -> FilePath+getOutFn opts mn = getOutDir opts </> mn <> ".json"++-- * JSON encoding++-- | Uses "Aeson.Pretty" to order the JSON fields.+encode :: ToJSON a => a -> BL.ByteString+encode = encodePretty' $ defConfig+  { confIndent = Spaces 2+  , confCompare = keyOrder+      [ "pretty"+      , "tag"+      , "name"+      , "original", "simplified", "reduced", "normalised"+      , "telescope", "patterns", "fields"+      , "domain", "codomain"+      , "abstraction", "body"+      , "sort", "level", "literal"+      , "head", "arguments"+      , "variants", "reference", "variant"+      , "index"+      , "scopeGlobal", "scopeLocal"+      , "type", "definition", "holes"+      , "ctx", "goal", "term", "premises"+      ]+  }++encodeFile :: ToJSON a => FilePath -> a -> IO ()+encodeFile = \fn -> BL.writeFile fn . encode
+ src/Output.hs view
@@ -0,0 +1,429 @@+{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}+-- | Defines the structure of the training data, as well as how to translate+-- internal Agda definition to this format.+module Output where++import Control.Arrow ( second )+import Control.Applicative ( (<|>) )+import GHC.Generics ( Generic )+import Data.List ( notElem, elemIndex )+import Data.String ( fromString )+import Data.Aeson+import qualified Data.Aeson as JSON+import qualified Data.Aeson.KeyMap as KM++import Agda.Syntax.Common ( unArg )+import qualified Agda.Syntax.Common as A+import qualified Agda.Syntax.Internal as A+import qualified Agda.Syntax.Literal as A+import Agda.Syntax.Internal+  ( QName, absName, qnameName, qnameModule, unAbs, unEl, unDom+  , nameId, conName, dbPatVarIndex, pDom, telToList, telFromList )+import Agda.Syntax.Translation.InternalToAbstract ( NamedClause(..) )+import qualified Agda.TypeChecking.Monad as A+import Agda.TypeChecking.Monad+  ( TCM, MonadTCM, liftTCM, typeOfConst, theDef, defName, getConstInfo+  , reportSDoc, VerboseLevel )++import qualified Agda.Utils.Pretty as P+import Agda.TypeChecking.Pretty+  ( PrettyTCM(..), MonadPretty, fsep, punctuate, braces, parens, Doc )+import qualified Agda.TypeChecking.Pretty as P+  hiding (text)++-- * Types++-- | Name identifiers.+type Name = String+-- | DeBruijn indices.+type DB   = Int+-- | A head of a λ-application can either be a defined name in the global scope,+-- or a DeBruijn index into the local context.+type Head = Either Name DB++-- * Generic constructions++infixr 4 :>; pattern x :> y = Pretty {pretty = x, thing = y}+-- | Bundle a thing with its "pretty" version.+--+-- NB: In JSON format, we follow a /shallow/ encoding with "pretty" being+-- an additional field and "thing" just inlined in the top-level record.+data Pretty a = Pretty+  { pretty :: String+  , thing  :: a+  } deriving Generic+deriving instance Show a => Show (Pretty a)+instance ToJSON a => ToJSON (Pretty a) where+  toJSON (Pretty{..}) = let pretty' = toJSON pretty in+    case toJSON thing of+      (Object fs)  -> object ("pretty" .= pretty' : KM.toList fs)+      t@(Array xs) -> object ["pretty" .= pretty', "telescope" .= t]+      t            -> object ["pretty" .= pretty', "thing" .= toJSON t]+instance FromJSON a => FromJSON (Pretty a) where+  parseJSON = withObject "Pretty" $ \v -> Pretty+    <$> v .: "pretty"+    <*> (v .: "thing" <|> parseJSON (Object v))++-- | Bundle a term with (several of) its normalised forms.+--+-- We do not repeat identical elements in subsequent evaluations+-- (in the order simp/red/norm) and some fields may not be populated due to+-- the evaluation taking to long and leading to a timeout (currently 2 seconds).+--+-- NB: Like 'Named', encoded in a /shallow/ JSON.+data Reduced a = Reduced+  { original   :: a+  , simplified :: Maybe a+  , reduced    :: Maybe a+  , normalised :: Maybe a+  } deriving (Generic, Functor, Foldable, Traversable)+deriving instance Show a => Show (Reduced a)+instance ToJSON a => ToJSON (Reduced a) where+  toJSON r@(Reduced{..})+    | Nothing <- simplified <|> reduced <|> normalised+    = toJSON original+    | otherwise+    = genericToJSON jsonOpts r+instance FromJSON a => FromJSON (Reduced a) where+  parseJSON = withObject "Object" $ \v -> Reduced+    <$> (v .: "original" <|> parseJSON (Object v))+    <*> v .:? "simplified"+    <*> v .:? "reduced"+    <*> v .:? "normalised"++infixr 4 :~; pattern x :~ y = Named {name = x, item = y}++-- | Bundle a thing with its name.+--+-- NB: Like 'Named' and 'Reduced', encoded in a /shallow/ JSON.+data Named a = Named+  { name :: Name+  , item :: a+  } deriving (Generic, Show)+instance ToJSON a => ToJSON (Named a) where+  toJSON (Named{..}) = let name' = toJSON name in+    case toJSON item of+      (Object fs) -> object ("name" .= name' : KM.toList fs)+      x           -> object ["name" .= name', "item" .= toJSON x]+instance FromJSON a => FromJSON (Named a) where+  parseJSON = withObject "Named" $ \v -> Named+    <$> v .: "name"+    <*> (v .: "item" <|> parseJSON (Object v))++-- * Concrete types (~ JSON schema)++-- | Data for a file include the filename and its training data.+type FileData = Named TrainData+-- | The training data for a module, divided into three parts.+data TrainData = TrainData+  { scopeGlobal  :: [ScopeEntry]+  -- ^ The /global/ scope, giving the types and definitions of all @import@ statements.+  --+  -- NB: does not contain any /holes/ for subterms.+  , scopeLocal   :: [ScopeEntry]+  -- ^ The /local/ scope, containing the types, definitions, and training data+  -- for each of this module's definitions.+  , scopePrivate :: Maybe [ScopeEntry]+  -- ^ The /private/ scope, containing private definitions not exported to the public,+  -- as well as system-generated definitions stemming from @where@ or @with@.+  } deriving Generic+instance ToJSON   TrainData where toJSON    = genericToJSON jsonOpts+instance FromJSON TrainData where parseJSON = genericParseJSON jsonOpts++-- | Every 'ScopeEntry'' is /named/.+type ScopeEntry = Named ScopeEntry'+-- | An entry in the scope: type, definitions, and holes.+data ScopeEntry' = ScopeEntry+  { _type      :: Pretty (Reduced Type)+  -- ^ The entry's type.+  , definition :: Maybe (Pretty Definition)+  -- ^ The actual body of this entry's definition.+  , holes      :: Maybe [Sample]+  -- ^ Training data for each of the subterms in this entry's 'definition'.+  } deriving (Generic, Show)+instance ToJSON   ScopeEntry' where toJSON    = genericToJSON    jsonOpts+instance FromJSON ScopeEntry' where parseJSON = genericParseJSON jsonOpts++-- | The training sample for each sub-hole.+data Sample = Sample+  { ctx      :: Pretty Telescope+  -- ^ The current context, as a /binding telescope/.+  , goal     :: Pretty (Reduced Type)+  -- ^ The current goal, i.e. type of the sub-term.+  --+  -- NB: DeBruijn indices here refer to the 'ctx'.+  , term     :: Pretty (Reduced Term)+  -- ^ The term that successfully fills the current 'goal'.+  , premises :: [Name]+  -- ^ Definitions used in this "proof", intended to be used for /premise selection/.+  } deriving (Generic, Show, ToJSON, FromJSON)+++-- | Agda definitions: datatypes, records, functions, postulates and primitives.+data Definition+  = ADT {variants :: [Type]}+  -- ^ e.g.+  -- data ℕ : Set where+  --   zero : ℕ+  --   suc  : ℕ → ℕ+  | Constructor {reference :: Name, variant :: Integer}+  -- ^ e.g. `(ℕ, 0) ~ zero` or `(ℕ, 1) ~ suc`+  | Record {telescope :: Telescope, fields :: [Type]}+  -- ^ e.g.+  -- record X : Set where+  --   field x : ℕ+  --         y : ℕ+  | Function {clauses :: [Clause]}+  -- ^ e.g.+  -- f []       = []+  -- f (x ∷ xs) = x ∷ x ∷ xs+  | Postulate {}+  -- ^ e.g. `postulate pred : ℕ → ℕ`+  | Primitive {}+  -- ^ e.g. `primitive primShowNat : ℕ → String`+  deriving (Generic, Show, ToJSON, FromJSON)++-- | Function clauses.+data Clause = Clause+  { _telescope :: Telescope+  -- ^ the telescope induced by the clause's context and patterns+  , patterns  :: [Pattern]+  -- ^ the actual patterns of this function clause+  , body      :: Maybe Term+  -- ^ the right hand side of the clause (@Nothing@ for absurd clauses)+  } deriving (Generic, Show)+instance ToJSON   Clause where toJSON    = genericToJSON jsonOpts+instance FromJSON Clause where parseJSON = genericParseJSON jsonOpts++-- | A telescope is a sequence of (named) types, a.k.a. bindings.+type Telescope = [Named (Pretty Type)]+-- | We under-approximate patterns as terms,+-- e.g. losing information about forced patterns.+type Pattern   = Term+-- | Types are the same as terms.+type Type      = Term++-- | The AST of Agda terms.+data Term+  = Pi Bool (Named Term) Term -- ^ e.g. @∀ {A : Set}. A → A@+  | Lam (Named Term)          -- ^ e.g. @λ x. x@+  | App Head [Term]           -- ^ e.g. @f x (x + x)@ or @@0 (λ x. x)@+  | Lit String   -- ^ e.g. @42@ or @"something"@+  | Sort String  -- ^ e.g. @Set@+  | Level String -- ^ e.g. @0ℓ@+  | UnsolvedMeta -- ^ i.e. @{!!}@+  deriving (Generic, Show, FromJSON)++instance ToJSON Term where+  toJSON = \case+    (Pi isDep (n :~ dom) codom) -> object+      [ tag "Pi"+      , "name"     .= toJSON n -- T0D0: remove if "(nothing) (or _)?"+      , "domain"   .= toJSON dom+      , "codomain" .= toJSON codom+      ]+    (Lam (n :~ f)) -> object+      [ tag "Lambda"+      , "abstraction" .= toJSON n+      , "body"        .= toJSON f+      ]+    (App f xs) ->+      if null xs then+        refHead+      else+        object [tag "Application", "head" .= refHead, "arguments" .= toJSON xs]+      where+        refHead = object $ case f of+          (Left n)  -> [tag "ScopeReference", "name"  .= toJSON n]+          (Right i) -> [tag "DeBruijn",       "index" .= toJSON i]+    (Lit s)   -> object [tag "Literal", "literal" .= toJSON s]+    (Sort s)  -> object [tag "Sort",    "sort"   .= toJSON s]+    (Level s) -> object [tag "Level",   "level"  .= toJSON s]+    UnsolvedMeta -> object [tag "UnsolvedMetavariable"]+    where tag s = "tag" .= JSON.String s++-- * Conversion from Agda's internal syntax++-- | Converting between two types @a@ and @b@ under Agda's typechecking monad.+--+-- NB: 'go' is only used internally to de-clutter the recursive calls.+class (~>) a b | a -> b where+  convert, go :: a -> TCM b+  convert = go++instance A.Definition ~> Definition where+  go = go . theDef++instance A.Defn ~> Definition where+  go = \case+    A.AbstractDefn defn -> go defn+    A.Function{..} -> let cls = takeWhile isNotCubical funClauses in+      -- NB: handle funWith and funExtLam+      Function <$> traverse go cls+    A.Datatype{..} -> do+      -- NB: what is a dataClause???+      tys <- fmap unEl <$> traverse typeOfConst dataCons+      ADT <$> traverse go tys+    A.Record{..} -> do+      -- NB: incorporate conHead/namedCon in the future for accuracy+      --     + to solve the issue with private (non-public) fields+      (tel, fs) <- splitAt recPars <$> go recTel+      return $ Record tel (thing . item <$> fs)+    A.Constructor{..} -> do+      let cn = conName conSrcCon+      d <- theDef <$> getConstInfo conData+      return $ case d of+        A.Datatype{..} ->+          let Just ix = elemIndex (unqualify cn) (unqualify <$> dataCons)+          in  Constructor (pp conData) (toInteger ix)+        A.Record{..} -> Constructor (pp conData) 0+    A.Primitive{..}      -> return Primitive+    A.PrimitiveSort{..}  -> return Primitive+    A.Axiom{..}          -> return Postulate+    d@A.DataOrRecSig{..} -> panic "dataOrRecSig" d+    d@A.GeneralizableVar -> panic "generalizable variable" d++instance A.Clause ~> Clause where+  go A.Clause{..} =+    Clause <$> go clauseTel+           <*> traverse go (A.namedThing . unArg <$> namedClausePats)+           -- ^ drop visibility and name information+           <*> traverse go clauseBody++instance A.DeBruijnPattern ~> Pattern where+  go = \case+    A.VarP _ v -> return $ App (Right $ dbPatVarIndex v) []+    A.DotP _ t -> go t+    A.ConP c _ ps -> do+      App (Left $ pp c) <$> traverse go (A.namedThing . unArg <$> ps)+    A.LitP _ lit -> return $ Lit (pp lit)+    A.ProjP _ qn -> return $ App (Left $ pp qn) []+    p@(A.IApplyP _ _ _ _) -> panic "pattern (cubical)" p+    p@(A.DefP _ _ _)      -> panic "pattern (cubical)" p++instance A.Telescope ~> Telescope where+  go = traverse action . A.telToList+    where+    action :: A.Dom (Name, A.Type) -> TCM (Named (Pretty Type))+    action dty = do+      let (n, ty) = unDom dty+      pty <- ppm ty+      ty' <- go ty+      let pdty = prender $ pDom dty $ P.text $ n <> " : " <> prender pty+      return $ n :~ pdty :> ty'++instance A.Type ~> Type where+  go = go . A.unEl++instance A.Term ~> Term where+  go = flip (.) A.unSpine $ \case+    -- ** abstractions+    (A.Pi ty ab) -> do+      let nameUsed = pp (A.domName ty) `notElem` ["_", "(nothing)"]+      ty' <- go (unEl $ unDom ty)+      ab' <- go (unEl $ unAbs ab)+      return $ Pi nameUsed (absName ab :~ ty') ab'+    (A.Lam _ ab) -> do+      ab' <- go (unAbs ab)+      return $ Lam (pp (absName ab) :~ ab')+    -- ** applications+    (A.Var i   xs) -> App (Right i)                   <$> (traverse go xs)+    (A.Def f   xs) -> App (Left $ ppName f)           <$> (traverse go xs)+    (A.Con c _ xs) -> App (Left $ ppName $ conName c) <$> (traverse go xs)+    -- ** other constants+    (A.Lit   x)   -> return $ Lit   $ pp x+    (A.Level x)   -> return $ Level $ pp x+    (A.Sort  x)   -> return $ Sort  $ pp x+    (A.MetaV _ _) -> return UnsolvedMeta+    -- ** there are some occurrences of `DontCare` in the standard library+    (A.DontCare t) -> go t+    -- ** crash on the rest (should never be encountered)+    t@(A.Dummy _ _) -> panic "term" t++instance A.Elim ~> Term where+  go = \case+    (A.Apply x)      -> go (unArg x)+    (A.Proj _ qn)    -> return $ App (Left $ ppName qn) []+    (A.IApply _ _ x) -> go x++-- * Utilities++pp :: P.Pretty a => a -> String+pp = P.prettyShow++ppm :: (MonadPretty m, P.PrettyTCM a) => a -> m Doc+ppm = P.prettyTCM++prender :: Doc -> String+prender = P.renderStyle (P.Style P.OneLineMode 0 0.0)++pinterleave :: (Applicative m, Semigroup (m Doc)) => m Doc -> [m Doc] -> m Doc+pinterleave sep = fsep . punctuate sep++pbindings :: (MonadPretty m, PrettyTCM a) => [(String, a)] -> [m Doc]+pbindings = map $ \(n, ty) -> parens $ ppm n <> " : " <> ppm ty++report :: MonadTCM m => VerboseLevel -> TCM Doc -> m ()+report n x = liftTCM $ reportSDoc "agda2train" n x++panic :: (P.Pretty a, Show a) => String -> a -> b+panic s t = error $+  "[PANIC] unexpected " <> s <> ": " <> pp t <> "\n show: " <> pp (show t)++ppName :: A.QName -> String+ppName qn = pp qn <> "<" <> show (fromEnum $ nameId $ qnameName qn) <> ">"++unqualify :: A.QName -> String+unqualify = pp . qnameName++(\/) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)+(f \/ g) x = f x || g x++isNotCubical :: A.Clause -> Bool+isNotCubical A.Clause{..}+  | Just (A.Def qn _) <- clauseBody+  = pp (qnameModule qn) /= "Agda.Primitive.Cubical"+  | otherwise+  = True++-- | Configure JSON to omit empty (optional) fields and switch+-- from camelCase to kebab-case.+jsonOpts :: JSON.Options+jsonOpts = defaultOptions+  { omitNothingFields = True+  , fieldLabelModifier = \case+      ('_' : s) -> s+      "scopeGlobal"  -> "scope-global"+      "scopeLocal"   -> "scope-local"+      "scopePrivate" -> "scope-private"+      s -> s+  }++instance P.PrettyTCM A.Definition where+  prettyTCM d = go (theDef d)+   where+    go = \case+      A.AbstractDefn defn -> go defn+      A.Function{..} -> let cls = takeWhile isNotCubical funClauses in+        fsep $ punctuate " |"+             $ ppm . NamedClause (defName d) True <$> cls+      A.Datatype{..} -> do+        tys <- fmap unEl <$> traverse typeOfConst dataCons+        pinterleave " |" $ pbindings $ zip (unqualify <$> dataCons) tys+      A.Record{..} ->+        let (tel, fs) = splitAt recPars $ telToList recTel in+        (if null tel then "" else ppm (telFromList tel) <> " |- ")+          <> (braces $ pinterleave " ;" $ pbindings $ unDom <$> fs)+      A.Constructor{..} -> do+        let cn = conName conSrcCon+        d <- theDef <$> getConstInfo conData+        case d of+          A.Datatype{..} ->+            let Just ix = elemIndex (unqualify cn) (unqualify <$> dataCons)+            in  ppm conData <> "@" <> ppm ix+          A.Record{..} -> ppm conData <> "@0"+      A.Primitive{..}     -> "<Primitive>"+      A.PrimitiveSort{..} -> "<PrimitiveSort>"+      A.Axiom{..}         -> "<Axiom>"+      A.DataOrRecSig{..}  -> "<DataOrRecSig>"+      A.GeneralizableVar  -> "<GeneralizableVar>"
+ src/ToTrain.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE TemplateHaskell #-}+-- | This module contains everything related to the generation of the training data.+module ToTrain where++import Data.Maybe ( isJust )+import Data.List ( isPrefixOf, isInfixOf, find, nub )+import qualified Data.Set as S+import Data.FileEmbed ( embedStringFile )++import Control.Monad ( forM_, void, when, unless )+import Control.Monad.Writer ( WriterT(runWriterT) )+import Control.Monad.Error.Class ( catchError )+import Control.Monad.IO.Class ( liftIO )+import Control.Concurrent ( threadDelay )+import Control.Concurrent.Async ( race )++import Agda.Syntax.Common+  ( unArg, defaultArg, defaultArgInfo, namedArg )+import Agda.Syntax.Internal+import Agda.Syntax.Internal.Names ( namesIn )+import Agda.Syntax.Literal ( Literal(..) )+import Agda.Syntax.Internal.Generic ( TermLike, foldTerm )+import Agda.Syntax.Scope.Base ( nsInScope, allThingsInScope )+import Agda.Syntax.Scope.Monad ( getCurrentScope )++import Agda.Utils.Monad ( whenM, tell1 )+import Agda.Utils.Either ( caseEitherM )++import Agda.TypeChecking.Monad hiding (Reduced)+import Agda.TypeChecking.Reduce+  ( Simplify, simplify, Normalise, normalise, Reduce, reduce )+import Agda.TypeChecking.CheckInternal ( Action(..), defaultAction, checkInternal' )+import Agda.TypeChecking.Pretty ( PrettyTCM )+++import AgdaInternals ()+import Output hiding ( Definition(..), Clause(..), Term(..), Type(..) )++-- * Wrapper around Agda's typechecking monad 'TCM'++-- | Additionally records/outputs training samples.+type C = WriterT [Sample] TCM++runC :: C () -> TCM [Sample]+runC = (snd <$>) . runWriterT++noop :: C ()+noop = return ()++silently :: C a -> C ()+silently k = void k `catchError` \ _ -> noop++-- * Training data generation++-- | A training function generates training data for each typed sub-term,+-- with access to the local context via the typechecking monad.+type TrainF = Type -> Term -> C ()++-- | An example training function that records a 'Output.Sample'+-- (i.e. context, type, and term) for a given subterm.+train :: TrainF+train ty t = do+  let ns = names t+  allNs <- nsInScope . allThingsInScope <$> liftTCM getCurrentScope+  unless (null ns) $+    when (S.fromList ns `S.isSubsetOf` allNs) $ do+      ctx <- getContextTelescope+      pctx <- liftTCM $ ppm ctx; pty <- liftTCM $ ppm ty; pt <- liftTCM $ ppm t+      rty <- mkReduced ty+      report 30 $ "rty: " <> ppm (original rty)++      rt <- mkReduced t++      ctx' <- liftTCM $ convert ctx+      rty' <- liftTCM $ traverse convert rty+      rt'  <- liftTCM $ traverse convert rt+      tell1 $ Sample+        { ctx      = prender pctx :> ctx'+        , goal     = prender pty  :> rty'+        , term     = prender pt   :> rt'+        , premises = map ppName ns+        }+      report 20 "{"+      report 20 $ " ctx: " <> ppm (pp ctx)+      report 30 $ "      *pretty: " <> pure pctx+      report 20 $ " goal: " <> ppm (pp ty)+      report 30 $ "      *pretty: " <> pure pty+      reportReduced rty+      report 20 $ " term: " <> ppm (pp t)+      report 30 $ "      *pretty: " <> pure pt+      reportReduced rt+      report 20 $ " premises: " <> ppm ns+      report 20 "}"++-- | Run the training function on each subterm of a definition.+forEachHole :: TrainF -> Definition -> C ()+forEachHole trainF def@Defn{..} = unless (ignoreDef def) $ do+  report 10 $ "------ definition: " <> ppm (pp defName) <> " -------"+  sc <- getScope+  case theDef of+    (Function{..}) ->+      forM_ funClauses $ \c@(Clause{..}) -> addContext clauseTel $+        case (clauseBody, unArg <$> clauseType) of+          (Just t, Just ty) -> go ty t+          _ -> noop+    _ -> noop+    -- TODO: get data from other places other than clause bodies+  where+    ignoreDef :: Definition -> Bool+    ignoreDef Defn{..}+        = False+       || tooSlow (pp defName)+      -- || defCopy+      -- || defNoCompilation+      -- || null (inverseScopeLookupName defName sc)+      -- || isAnonymousModuleName (qnameModule defName)+      -- || ("._." `isInfixOf` pp defName)+      -- || (getOrigin defName /= UserWritten)+      -- || ( ("with-" `isPrefixOf` pp (qnameName defName))++    ignore :: Type -> C Bool+    ignore ty = do+      ctx <- fmap (snd . unDom) <$> getContext+      return (ignoreType ty || any ignoreCtxType ctx)++    ignoreType, ignoreCtxType :: Type -> Bool+    ignoreType    = any cubicalRelated . map pp . names . unEl+    ignoreCtxType = any cubicalRelated . map pp . names . unEl++    cubicalRelated, tooSlow :: String -> Bool+    cubicalRelated = ("Agda.Primitive.Cubical.I" `isInfixOf`)+    tooSlow+       = ("Data.Rational.Properties" `isPrefixOf`)+      \/ ("Prelude.Solvers" `isPrefixOf`)+      \/ ("foundation.partitions" `isPrefixOf`)+      \/ (`elem` defsToSkip)++    go :: Type -> Term -> C ()+    go ty t = whenM (not <$> ignore ty)+            $ silently (checkInternal' act t CmpLeq ty)++    act :: Action C+    act = defaultAction {preAction = pre}++    pre :: Type -> Term -> C Term+    pre ty t = trainF ty t >> return t++-- | Read a list of definitions to skip from @data/defsToSkip.txt@.+defsToSkip :: [String]+defsToSkip = lines $ $(embedStringFile "data/defsToSkip.txt")++-- | Gathering names from terms.+names :: Term -> [QName]+names = nub . namesIn++-- * Reduction++-- | The hard limit on how much time can be spent on normalising a single term.+maxDuration = 2 -- seconds++withTimeout :: TCM a -> TCM (Maybe a)+withTimeout k = getTC >>= \ s -> liftIO $+  caseEitherM+    (race (threadDelay (maxDuration * 1000000))+          (fst <$> runSafeTCM k s))+    (\() -> pure Nothing)+    (pure . Just)++mkReduced ::+  ( MonadFail m, MonadTCM m, PrettyTCM a+  , Simplify a, Reduce a, Normalise a, Eq a+  ) => a -> m (Reduced a)+mkReduced t = do+  -- try different reductions (with timeout)+  [_, simplified, reduced, normalised] <- compressList . (Just t :)+    <$> mapM (liftTCM . withTimeout) [simplify t, reduce t, normalise t]+  return $ Reduced {original = t, ..}+  where+    compressList :: Eq a => [Maybe a] -> [Maybe a]+    compressList xs = find ((`notElem` xs) . Just) <$> xs++reportReduced :: (MonadTCM m, PrettyTCM a) => Reduced a -> m ()+reportReduced Reduced{..} = do+  report 30 $ "  *simplified: " <> ppm simplified+  report 30 $ "     *reduced: " <> ppm reduced+  report 30 $ "  *normalised: " <> ppm normalised