packages feed

runGhcBWrap-core-0.1.0.0: src/RunGhc/UserInput.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TupleSections #-}
module RunGhc.UserInput where

{-
A collection of helper functions for providing something our API can understand which is ultimately just the Executable type
-}

import Scrappy.Scrape
import Text.Parsec
import RunGhc.Executable
import RunGhc.LocatedModule
import RunGhc.Locate
import RunGhc.SystemModule
import Data.Bifunctor
import Text.IStr
import Data.Default
import qualified Data.Text as T
import Data.Char (toUpper, toLower)

data UserModuleSettings = UserModuleSettings
  { _location :: [PathSegment]
  , _allowImports :: Bool
  , _overrideMake_If_main_exists :: Bool
  , _mkSystemModule :: Expressions -> SystemModule
  }
instance Default UserModuleSettings where
  def = UserModuleSettings
    { _location = [PathSegment "UserModule"]
    , _allowImports = True
    , _overrideMake_If_main_exists = False
    , _mkSystemModule = ExpressionsOnly
    }

mkUserModule' :: UserModuleSettings -> T.Text -> LocatedUserModule
mkUserModule' cfg input =
  LocatedUserModule $ mkUserModule 
  (_location cfg)
  (_allowImports cfg)
  (_overrideMake_If_main_exists cfg)
  (_mkSystemModule cfg)
  input

handleUserInputMultipleFiles :: (FilePath, Script) -> [(FilePath, T.Text)] -> Executable
handleUserInputMultipleFiles main@(targetPath, Script mainSrc) txtsWithPath =
  let
    f (fp, script) =
      FromLocatedScript $ LocatedScript (pathSegsFromFilePath fp) $ Script script
    fMain (fp, Script script) =
      FromLocatedScript $ LocatedScript (pathSegsFromFilePath fp) $ Script script
  in
    Executable (fMain main) (f <$> txtsWithPath)

-- tryHandleUserInput 
parseUserScript
  :: T.Text -- Raw user input 
  -> Bool -- can contain imports?
  -> Bool -- can contain main?
  -> Either Script Expressions
parseUserScript txt canContainImports cancelWhenMainExists =  
  let
    moduleHeadPieces = findModuleHeadPieces txt
    mainExists = exists mainFuncParser $ T.unpack txt -- (string "main :: IO ()"
    x = case moduleHeadPieces of
      Just _ -> Left $ Script txt 
      Nothing -> Right $ Expressions txt

  in case moduleHeadPieces of
       Just _ ->
         -- We cannot interact with this script
         Left $ Script txt 
       Nothing ->
         case mainExists && cancelWhenMainExists of
           True -> Left $ Script txt
           False -> Right $ Expressions txt

handleUserInputSingleFile
  :: T.Text
  -> LocatedUserModule
handleUserInputSingleFile txt =
  LocatedUserModule
  $ FromLocatedScript
  $ LocatedScript [PathSegment "UserLibrary"]
  $ Script txt

    
-- We only build if they gave just an Expressions
unsafeMkSystemModuleFromExpressionsOnly
  :: Expressions
  -> Imports
  -> Extensions
  -> SystemModule
unsafeMkSystemModuleFromExpressionsOnly exprs imports extensions =
  -- LocatedUserModule
  -- FromSystemModuele [PathSegment "UserLibrary"]
  addExtensions extensions
  $ addImports imports 
  $ ExpressionsOnly
  $ exprs

-- Super naive function
unsafeMkLocatedUserModule
  :: Expressions
  -> LocatedUserModule
unsafeMkLocatedUserModule expr =
  LocatedUserModule
  $ FromSystemModule [PathSegment "UserLibrary"]
  $ ExpressionsOnly
  $ expr

(<+>) :: Applicative m => m a -> m b -> m (a, b)
ma <+> mb = (,) <$> ma <*> mb


-- 99% of cases are
-- Text -> LocatedUserModule
-- 


-- We should also have functionality to check if the Module exports main

-- case exportsMain { True -> Exe ; False -> Library }
-- ;
-- A general theme i am noticing is that if we want user freedom -> their code is Main.main (eg user playground/ first program)
-- but if we need to give any feedback or interaction at all, then their code
-- must be under <UserModuleName>.* (eg. Hackerrank style, large projects, chapter exercises)
--
--   Otherwise, even if they give main we should still write Main.hs ourselves. Why?
--       - We may need to affect the environment, which is actually more likely if they give `main :: IO ()`

-- We either have just an expression or something more
-- but either way we are trying to normalize it

-- Note that on a LocatedModule we should always be able to apply
-- a function (f :: SystemModule -> SystemModule) and if it is actually
-- a LocatedScript then we do nothing
-- f `apply` lMod = lMod ; when lMod is lScript
-- tryHandleUserInputExpressionsOnly
--   :: T.Text
--   -- ^ User Input direct
--   -> Imports
--   -- ^ Configured imports
--   -> Extensions
--   -- ^ ConfiguredExtensions
--   -> (LocatedUserModule, Bool) -- or is this unlocated???
-- tryHandleUserInputExpressionsOnly txt imports extensions =
--   let
--     locate_ :: forall a. Locatable a => a -> LocatedModule
--     locate_ = locate [PathSegment "UserLibrary"]
--     moduleHeadPieces = findModuleHeadPieces txt
--     mainExists = exists mainFuncParser $ T.unpack txt -- (string "main :: IO ()"
--   in
--     (,mainExists)
--     $ LocatedUserModule
--     $ case moduleHeadPieces of
--       Just ("module":"where":xs) -> locate_ $ Script txt
--       Just xs | "import" `elem` xs -> locate_ $ Script txt
--       Just something -> locate_ $ Script txt
--       Nothing ->
--         locate_ 
--         $ addExtensions extensions
--         $ addImports imports
--         $ ExpressionsOnly $ Expressions txt
        -- is as expected a function or set of functions and types
    --FromSystemModule ["UserLibrary"] $ ExpressionsOnly 

-- the theoretical reason why existence of main, may matter is that
-- in some cases, we want to allow that to override the mkExe logic
--
-- In those cases, if `main` exists then we should (give option to) avoid mkModule
  -- this would most likely be when main is not in Symbols expected by Test script

-- ( importsExist @Bool -> mainExists @Bool -> choice_runMkModule @Bool )
  -- implicitly the existence of Main or Extensions |absolutely| negates
  -- any user choices

-- x :: LocatedModule
-- x =
--   -- either id id
--   -- $ bimap
--   -- (locate pathSegs)
--   fromLocatableSource [PathSegment "UserModule"]
--   $
--   (fmap mkLocatedUserModule :: SystemModule -> SystemModule) --LocatedModule)    
--   $ tryHandleUserInput "input"


--f :: Bool -> Bool -> (SystemModule -> SystemModule) -> T.Text -> LocatedModule

--TODO: system module 

-- wouldnt it always be an Expressions type?
  
-- TODO: give module name as string arg
mkUserModule
  :: [PathSegment] -- location
  -> Bool -- allow imports?
  -> Bool -- main exists?
  -> (Expressions -> SystemModule) -- mkModule (eg addImports, addExts, ...)
  -> T.Text -- User input
  -> LocatedModule
mkUserModule pathSegs allowImports mainExists mutModule input = 
  locateUserSource pathSegs
  $ fmap mutModule
  $ parseUserScript input allowImports mainExists 

-- Handle if user wrote their own full module,
-- or if we can make a SystemModule freely
locateUserSource
  :: [PathSegment]
  -> Either Script SystemModule
  -> LocatedModule
locateUserSource pathSegs userScript =
  either id id
  $ bimap
  (locate pathSegs) 
  (locate pathSegs) 
  userScript
          
    -- case targetPath `List.lookup` $ txtsWithPath of
    --   Nothing
    -- f <$> (main : txtsWithPath)
  --FromLocatedScript $ LocatedScript ["UserLibrary"] $ Script txt


--- Parsers used for scraping + analysis

caseInsensitiveString
  :: forall s u m
  . Stream s m Char
  => String
  -> ParsecT s u m String
caseInsensitiveString s = do
  let
    f :: Stream s m Char => Char -> ParsecT s u m Char
    f chr = char (toUpper chr) <|> char (toLower chr)
    --fs = f <$> s
  mapM f s


mainFuncParser :: Stream s m Char => ParsecT s u m ()
mainFuncParser = try a <|> b
  where
    a = do
      _ <- string "main"
      _ <- many space
      _ <- string "::"
      _ <- many space
      _ <- string "IO"
      _ <- many space
      _ <- string "()"
      pure ()
    b = do
      _ <- string "main"
      _ <- many space
      _ <- string "="
      pure ()
      
  
findModuleHeadPieces :: T.Text -> Maybe [String]
findModuleHeadPieces txt = scrape moduleHeadPiece $ T.unpack txt
    
moduleHeadPiece :: Stream s m Char => ParsecT s u m String
moduleHeadPiece = do
  try (string "module")
    <|> try (string "where")
    <|> string "import"
    <|> ( string "{-#"
          >> many space
          >> caseInsensitiveString "language"
        )
  -- or if there exists import



  
  -- \case
  -- _

-- -- attachLibraryToUserModule :: LocatedUserModule -> Located-> [LocatedModule] -> Executable
-- -- attachLibraryToUserModule 
-- addUserLibraryToExecutable :: LocatedUserModule -> Executable -> Executable
-- addUserLibraryToExecutable (LocatedUserModule loc) exe = Executable (addImports (Imports [toSimpleImport $ getPathSegments loc]) (_main exe)) [loc]


-- addSystemLibraryToExecutable :: LocatedTestModule -> Executable -> Executable
-- addSystemLibraryToExecutable locs exe = Executable (_main exe) $ locs : (_library exe)

-- mkUserExecutableNoLibrary :: LocatedUserModule -> Executable
-- mkUserExecutableNoLibrary (LocatedUserModule loc) = Executable loc []
-- run the user script 'as is' with zero processing

-- we want the writer of the script to pass the qualified name
      
-- test_1Input :: FunctionName -> Expression -> Expressions
-- test_1Input fname (Expression expr)  = Expressions $ fname <> " " <> expr

-- test_card1 :: FunctionName -> Expression -> Expressions

-- test_card2 :: FunctionName -> Expression -> Expression -> Expressions

-- test_card2 :: FunctionName -> Expression -> Expression -> Expressions

  
-- testThisFunction
--   :: FunctionName
--   -> Expression
--   -> Executable



-- fromDependentSystemModule
--   :: Qualifiable a
--   => [PathSegment]
--   -> Imports
--   -> Extensions
--   -> a -- FunctionName for instance
--   -> LocatedModule -- The user module
--   -> SystemModule
--   -> LocatedModule
-- fromDependentSystemModule pathLoc imps exts symbols userModule sysModule = 
--   addExtensions exts
--   $ addImports (Imports [localQualifiedImport "UserModule" userModule])
--   $ addImports imps 
--   $ FromSystemModule pathLoc sysModule  
--
  ---  $ ExpressionsOnly $ withF (qualify "UserModule" symbols)
-- fromDependentSystemModule'
--   :: Qualifiable a
--   => [PathSegment]
--   -> a -- FunctionName for instance
--   -> LocatedModule -- The user module
--   -> SystemModule
--   -> LocatedModule
-- fromDependentSystemModule' pathLoc symbols userModule sysModule = 
--   addImports (Imports [localQualifiedImport "UserModule" userModule])
--   $ FromSystemModule pathLoc sysModule  
-- | TODO:
-- 
--   instead this adding of localQualifiedImport
--   should be during a system module creation
--

  


-- let secondLine =
--       if "main :: IO ()" `isInfixOf` userMod
--       then \f -> f -- "#{f}"
--       else \f -> "mapM_ (print . " <> f <> ") $ toColor [1..10]"
-- (\f -> [istr|
-- main :: IO ()
-- main = do
--   setupEnv
--   (#{secondLine hasMain f}) 
--   --mapM_ (print . #{f}) $ toColor [1..10]
--   runChecks
  

-- data Color = Red | Blue | Green | Orange | Violet | Black | Gold deriving (Eq, Ord, Enum)

-- toColor = fromEnum
-- |]
  
-- )




-- chooseTestScriptBuild
--   :: (LocatedUserModule, Bool)
--   -> (LocatedUserModule -> symbols -> (symbols -> Expressions))
--   -> Expressions 
--   -> LocatedTestModule
-- chooseTestScriptBuild (userModule, hasMain) whenNoMain whenMain = undefined


  
    
-- setup
-- run Either Main (MkScript :: symbols -> Expressions)
-- Value-based tests
  

  -- mkMainExeWithDefaultHead user "main" $ \main_ ->
  -- Expressions $ [istr| main = #{main_} |]


--   if they gave a UserModule.main :: IO () , specifically "main :: IO ()" exists
--   then write the environment? with bracket

-- bracket f (setupEnv) (reportBenchmarks)


-- bracket UserModule.main setupEnv (runEqualityChecks <+> runBenchmarks)

  
  -- makeTestScriptWithImportsExtensions
  -- [PathSegment "Main"]
  -- (fst $ defaultHeadMainModule)
  -- (snd $ defaultHeadMainModule)
  -- symbols
  -- userModule
  -- mkExpr

  
-- mk userModule = makeTestScriptWithImportsExtensions
--   [PathSegment "Main"]
--   mempty
--   mempty
--   (Symbol "f", Symbol "g", Symbol "Maybee")
--   userModule
--   (\(Symbol fname, Symbol gname, Symbol typeName) -> [istr|main = #{gname} @#{typeName} $ #{fname} <$> [1..10]|])

-- mk2 userModule = makeTestScriptWithImportsExtensions
--   [PathSegment "Main"]
--   (Imports [ Import (Just "T") ["Data", "Text"]
--            , Import (Just "Json") ["Data", "Aeson"]
--            ])
--   (Extensions ["OverloadedStrings"])
--   "f"
--   -- In theory we could pass a reftype
--   userModule
--   (\fname -> [istr|
-- main = #{fname} <$> (fmap T.show [1..10])
-- |] <> dataTypeExists "Maybee")


-- makeTestScriptWithDefaultHead_1ref
--   :: LocatedModule -- The user module
--   -> Symbol
--   -> (FunctionName -> Expressions)
--   -> LocatedModule
-- makeTestScriptWithDefaultHead userModule fname symbol mkExpr =

-- makeTestScriptWithDefaultHead_1ref
--   :: LocatedModule -- The user module
--   -> Symbol
--   -> (FunctionName -> Expressions)
--   -> LocatedModule
-- makeTestScriptWithDefaultHead_1ref userModule fname symbol mkExpr =
 
-- makeTestScriptWithDefaultHead_2ref
--   :: LocatedModule -- The user module
--   -> FunctionName
--   -> (Symbol, Symbol)
--   -> ((Symbol, Symbol) -> Expressions)
--   -> LocatedModule
-- makeTestScriptWithDefaultHead_2ref userModule (symbol1, symbol2) mkExpr =
--   let (imports, exts) = defaultHeadMainModule
--   in makeTestScriptWithImportsExtensions
--      [PathSegment "Main"]
--      imports
--      exts
--      (symbol1, symbol2)
--      userModule
--      mkExpr
     
-- -- [istr|

-- |]
-- [istr|
-- module Main where
-- #{importLine}
-- main = #{qname}.#{fname}
-- |]

  
  
-- mkUserExecutableAsLibrary
--   :: LocatedUserModule
--   -> Executable
-- mkUserExecutableAsLibrary (LocatedUserModule loc) = Executable (mod qname_) [loc]
--   where
--     qname_ = "UserModule"
--     importLine = showImportLine $ localQualifiedImport qname loc --Import Nothing [getPathSegments loc
--     mod qname = FromLocatedScript $ LocatedScript [PathSegment "Main"] $ Script    
--       --""
--       $ [istr|
-- module Main where
-- #{importLine}
-- main = #{qname}.main
         
--          |]