packages feed

cg-0.0.9.0: CG/ToAgda.hs

{-# LANGUAGE RecordWildCards, FlexibleContexts #-}
module CG.ToAgda (Result(..),writeAgdaFile) where


import           CG.Base
import           Control.Monad.State (State,get,put,runState)
import           Data.Char (isSpace,toUpper,toLower)
import           Data.List (intersperse,intercalate)
import           Data.Map (Map)
import qualified Data.Map as M
import           Data.Maybe (fromMaybe)
import           Data.Void (Void)
import           System.IO
import           Text.Printf (printf)



toCtxt :: System c -> String -> String
toCtxt sys@System{..} sent
  | structural = unwords (intersperse "," (words sent ++ ["∅"]))
  | otherwise  = unwords (intersperse "," (words sent))

toAgdaName :: System c -> String
toAgdaName System{..} = case agdaName of
  Just n -> n
  _      -> error "Must set agda_name to export to Agda."

toAgdaModule :: System c -> String
toAgdaModule System{..} = case agdaModule of
  Just n -> n
  _      -> error "Must set agda_module to export to Agda."

toBaseName :: Result -> String
toBaseName (Solved     g _) = defaultName g
toBaseName (Parsed s _ _ _) = map toLower (sanitise s)

sanitise [] = []
sanitise (' ':' ': xs) = sanitise (' ':xs)
sanitise (' '    : xs) = '_' : sanitise xs
sanitise ('.'    : xs) = sanitise xs
sanitise (':'    : xs) = sanitise xs
sanitise ( c     : xs) = toUpper c : sanitise xs

sanitiseForLaTeX [] = []
sanitiseForLaTeX ('_':xs) = '\\' : '_' : sanitiseForLaTeX xs
sanitiseForLaTeX ( c :xs) =         c  : sanitiseForLaTeX xs

defaultName :: (Show v, ToString v) => Term ConId v -> String
defaultName = map repl . filter (not . isSpace) . show
  where
    repl '(' = '['
    repl ')' = ']'
    repl  c  =  c

withType :: (Show v, ToString v) => Term ConId v -> String -> String
withType = printf "id {A = ⟦ %s ⟧ᵀ} (%s)" . show

agdaList :: [String] -> String
agdaList = unwords . intersperse "\n  ∷" . (++["[]"])

agdaList' :: [String] -> String
agdaList' = unwords . intersperse " ∷" . (++["[]"])

toAgdaDefn :: System c -> Term ConId Void -> Result -> State (Map String Int) String
toAgdaDefn sys goalType ret = case ret of
  (Solved     g p) ->
    return $ unlines
      [ baseName++" : "++dataTypeName++" "++show g
      , baseName++" = "++show p
      , ""
      ]
  (Parsed s e g p) -> do
    n <- next s; let subn = sub n
    return . unlines $
      [ synBaseName++subn++" : "++dataTypeName++" "++show g
      , synBaseName++subn++" \n  = "++show p
      , semBaseName++subn++" : ⟦ "++show goalType++" ⟧ᵀ"
      , semBaseName++subn++" \n  = [ "++synBaseName++subn++" ]ᵀ ("++toCtxt sys s++")"
      , ""
      ]
      ++
      (if null e then [] else
      [ listBaseName++subn++" : List Term"
      , listBaseName++subn++" \n  = "++agdaList (map (\x -> "quoteTerm ("++withType goalType x++")") e)
      , testBaseName++subn++" : Assert (quoteTerm "++semBaseName++subn++" ∈ "++listBaseName++subn++")"
      , testBaseName++subn++" = _"
      , ""
      ])
      ++
      [""]
  where
    baseName     = toBaseName ret
    synBaseName  = map toUpper baseName
    semBaseName  = map toLower baseName
    listBaseName = "LIST_"++baseName
    testBaseName = "TEST_"++baseName
    dataTypeName = toAgdaName sys


-- |Return a valid Agda file given a sequence of proofs.
writeAgdaFile :: Handle -> String -> System c -> [Result] -> Term ConId Void -> IO [String]
writeAgdaFile h moduleName sys@System{..} prfs goalType = do

  let (proofDefn, nameMap) =
        runState (mapM (toAgdaDefn sys goalType) prfs) M.empty
  let nameList = toNameList nameMap
  let fileList = map (\(i,s) -> map toLower (sanitise s) ++ "_" ++ show i) nameList

  mapM_ (hPutStrLn h) $
    [ "------------------------------------------------------------------------"
    , "-- The Lambek Calculus in Agda"
    , "------------------------------------------------------------------------"
    , ""
    , "open import Data.Colist using (fromList)"
    , "open import Data.Bool   using (Bool; true; false; _∧_; _∨_)"
    , "open import Data.List   using (List; _∷_; [])"
    , "open import Data.String using (String)"
    , "open import Data.Vec    using (_∷_; [])"
    , "open import Function    using (id)"
    , "open import IO          using (IO; mapM′; run)"
    , "open import Reflection  using (Term)"
    , "open import "++toAgdaModule sys
    , ""
    , "module "++moduleName++" where"
    , ""
    , unlines proofDefn
    , ""
    , "proofList : List Proof"
    , "proofList\n  = " ++ (agdaList (proofList structural nameList))
    , ""
    , "main : _"
    , "main = run (mapM′ writeProof (fromList proofList))"
    ]

  return fileList


-- |Convert a given map of sentences to max IDs, to a list of names.
toNameList :: Map String Int -> [(Int, String)]
toNameList sentenceIds =
  [(j,s) | (s,i) <- M.toList sentenceIds, j <- [0..i-1]]


-- |Return a single list which contains all proofs as LaTeX strings.
proofList :: Bool -> [(Int, String)] -> [String]
proofList structural nameList = do
  (i,s) <- nameList
  let
    fileName = map toLower (sanitise s) ++ "_" ++ show i
    termName = map toUpper (sanitise s) ++ sub i
    sentence = sanitiseForLaTeX (toUpper (head s) : map toLower (tail s) ++ ".")
    wordList = if structural
               then agdaList' (map (show . sanitiseForLaTeX) (words s))
               else agdaList' [show (sanitiseForLaTeX (sanitise s))]
  return $
    printf "proof %s %s (toLaTeX %s) (toLaTeXTerm (%s) (toTerm %s))"
    (show fileName) (show sentence) termName wordList termName


-- |Compute the next number for this String.
next :: String -> State (Map String Int) Int
next s = do m <- get
            let n = fromMaybe 0 (M.lookup s m)
            put (M.insert s (n + 1) m)
            return n


-- |Show a number in subscript.
sub :: Int -> String
sub = map toSub . show
  where
    toSub '0' = '₀'; toSub '5' = '₅'
    toSub '1' = '₁'; toSub '6' = '₆'
    toSub '2' = '₂'; toSub '7' = '₇'
    toSub '3' = '₃'; toSub '8' = '₈'
    toSub '4' = '₄'; toSub '9' = '₉'