{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-|
Module      : Language.Alloy.Internal.Call
Copyright   : (c) Marcellus Siegburg, 2019 - 2025
License     : MIT

This module provides the basic internal functionality to retrieve the raw
results from calling Alloy.
It provides data types and functions to interact with Alloy.
-}
module Language.Alloy.Internal.Call (
  CallAlloyConfig (..),
  SatSolver (..),
  defaultCallAlloyConfig,
  getRawInstances,
  getRawInstancesWith,
  ) where

import qualified Data.ByteString                  as BS (
  intercalate,
  stripPrefix,
  )
import qualified Data.ByteString.Char8            as BS (
  hGetLine,
  putStrLn,
  unlines,
  )

import Control.Concurrent (
  threadDelay,
  )
import Control.Concurrent.Async (
  concurrently,
  mapConcurrently_,
  wait,
  withAsync
  )
import Control.Concurrent.Extra         (Lock, newLock, withLock)
import Control.Exception                (IOException, bracket, catch)
import Control.Monad                    (unless, when)
import Control.Monad.Extra              (whenJust)
import Data.ByteString                  (ByteString)
import Data.ByteString.Char8            (unpack)
import Data.IORef (
  IORef,
  atomicWriteIORef,
#ifdef mingw32_HOST_OS
  newIORef,
#endif
  readIORef,
  )
import Data.List                        (intercalate)
import Data.List.Split                  (splitOn)
import Data.Maybe                       (fromMaybe)
import System.Exit                      (ExitCode (..))
import System.FilePath
  (searchPathSeparator)
import System.IO (
#ifndef mingw32_HOST_OS
  BufferMode (..),
  hSetBuffering,
#endif
  Handle,
  hClose,
  hFlush,
  hIsEOF,
  hPutStr,
  hPutStrLn,
  stderr,
  )
import System.IO.Unsafe                 (unsafePerformIO)
import System.Process (
  CreateProcess (..), StdStream (..), ProcessHandle,
  cleanupProcess,
  createProcess, proc, terminateProcess, waitForProcess,
  )

import Language.Alloy.ResourceNames (
  className,
  classPackage,
  )
import Language.Alloy.Resources (
  alloyJar,
  commonsCliJar,
  slf4jJar,
  )
import Paths_call_alloy                 (getDataDir)

{-|
Available SAT solvers.

Note: solvers marked as not supported by default were supported
in earlier versions of Alloy, but got removed.
There might be a way of integrating those manually, but this has net been tried.
-}
data SatSolver
  = BerkMin
  -- ^ BerkMin
  --
  -- * not supported by default
  | Glucose
  -- ^ Glucose
  --
  -- * incremental
  | Glucose41
  -- ^ Glucose41
  --
  -- * not supported by default
  | Lingeling
  -- ^ Lingeling
  --
  -- * not incremental
  | MiniSat
  -- ^ MiniSat
  --
  -- * incremental
  | MiniSatProver
  -- ^ MiniSatProver
  --
  -- * incremental
  | PLingeling
  -- ^ PLingeling
  --
  -- * not supported by default
  | SAT4J
  -- ^ SAT4J
  --
  -- * incremental
  | SAT4JLight
  -- ^ SAT4J Light
  --
  -- * incremental
  | SAT4JPMax
  -- ^ SAT4J with Partial Maximum Satisfiability
  --
  -- * incremental
  | Spear
  -- ^ Spear
  --
  -- * not supported by default
  deriving (SatSolver
SatSolver -> SatSolver -> Bounded SatSolver
forall a. a -> a -> Bounded a
$cminBound :: SatSolver
minBound :: SatSolver
$cmaxBound :: SatSolver
maxBound :: SatSolver
Bounded, Int -> SatSolver
SatSolver -> Int
SatSolver -> [SatSolver]
SatSolver -> SatSolver
SatSolver -> SatSolver -> [SatSolver]
SatSolver -> SatSolver -> SatSolver -> [SatSolver]
(SatSolver -> SatSolver)
-> (SatSolver -> SatSolver)
-> (Int -> SatSolver)
-> (SatSolver -> Int)
-> (SatSolver -> [SatSolver])
-> (SatSolver -> SatSolver -> [SatSolver])
-> (SatSolver -> SatSolver -> [SatSolver])
-> (SatSolver -> SatSolver -> SatSolver -> [SatSolver])
-> Enum SatSolver
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: SatSolver -> SatSolver
succ :: SatSolver -> SatSolver
$cpred :: SatSolver -> SatSolver
pred :: SatSolver -> SatSolver
$ctoEnum :: Int -> SatSolver
toEnum :: Int -> SatSolver
$cfromEnum :: SatSolver -> Int
fromEnum :: SatSolver -> Int
$cenumFrom :: SatSolver -> [SatSolver]
enumFrom :: SatSolver -> [SatSolver]
$cenumFromThen :: SatSolver -> SatSolver -> [SatSolver]
enumFromThen :: SatSolver -> SatSolver -> [SatSolver]
$cenumFromTo :: SatSolver -> SatSolver -> [SatSolver]
enumFromTo :: SatSolver -> SatSolver -> [SatSolver]
$cenumFromThenTo :: SatSolver -> SatSolver -> SatSolver -> [SatSolver]
enumFromThenTo :: SatSolver -> SatSolver -> SatSolver -> [SatSolver]
Enum, SatSolver -> SatSolver -> Bool
(SatSolver -> SatSolver -> Bool)
-> (SatSolver -> SatSolver -> Bool) -> Eq SatSolver
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SatSolver -> SatSolver -> Bool
== :: SatSolver -> SatSolver -> Bool
$c/= :: SatSolver -> SatSolver -> Bool
/= :: SatSolver -> SatSolver -> Bool
Eq, ReadPrec [SatSolver]
ReadPrec SatSolver
Int -> ReadS SatSolver
ReadS [SatSolver]
(Int -> ReadS SatSolver)
-> ReadS [SatSolver]
-> ReadPrec SatSolver
-> ReadPrec [SatSolver]
-> Read SatSolver
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS SatSolver
readsPrec :: Int -> ReadS SatSolver
$creadList :: ReadS [SatSolver]
readList :: ReadS [SatSolver]
$creadPrec :: ReadPrec SatSolver
readPrec :: ReadPrec SatSolver
$creadListPrec :: ReadPrec [SatSolver]
readListPrec :: ReadPrec [SatSolver]
Read, Int -> SatSolver -> ShowS
[SatSolver] -> ShowS
SatSolver -> String
(Int -> SatSolver -> ShowS)
-> (SatSolver -> String)
-> ([SatSolver] -> ShowS)
-> Show SatSolver
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SatSolver -> ShowS
showsPrec :: Int -> SatSolver -> ShowS
$cshow :: SatSolver -> String
show :: SatSolver -> String
$cshowList :: [SatSolver] -> ShowS
showList :: [SatSolver] -> ShowS
Show)

toParameter :: SatSolver -> String
toParameter :: SatSolver -> String
toParameter = \case
  SatSolver
BerkMin -> String
"berkmin"
  SatSolver
Glucose -> String
"glucose"
  SatSolver
Glucose41 -> String
"glucose41"
  SatSolver
Lingeling -> String
"lingeling.parallel"
  SatSolver
MiniSat -> String
"minisat"
  SatSolver
MiniSatProver -> String
"minisat.prover"
  SatSolver
PLingeling -> String
"plingeling"
  SatSolver
SAT4J -> String
"sat4j"
  SatSolver
SAT4JLight -> String
"sat4j.light"
  SatSolver
SAT4JPMax -> String
"sat4j.pmax"
  SatSolver
Spear -> String
"spear"

{-|
Configuration for calling alloy.
-}
data CallAlloyConfig =
  -- | you should use 'defaultCallAlloyConfig' instead of this constructor
  -- as additional configuration parameters might be introduced in later
  -- releases.
  CallAlloyConfig {
  -- | maximal number of instances to retrieve ('Nothing' for all)
  CallAlloyConfig -> Maybe Integer
maxInstances :: !(Maybe Integer),
  -- | whether to not overflow when calculating numbers within Alloy
  CallAlloyConfig -> Bool
noOverflow   :: !Bool,
  -- | the 'SatSolver' to choose. Note that some are not incremental,
  --   i.e. will return only one solution, even if 'maxInstances' is set higher.
  CallAlloyConfig -> SatSolver
satSolver    :: !SatSolver,
  -- | the time in microseconds after which to forcibly kill Alloy
  --   ('Nothing' for never)
  CallAlloyConfig -> Maybe Int
timeout      :: !(Maybe Int)
  }

{-|
Default configuration for calling Alloy. Defaults to:

 * retrieve all instances
 * do not overflow
 * 'SAT4J'
-}
defaultCallAlloyConfig :: CallAlloyConfig
defaultCallAlloyConfig :: CallAlloyConfig
defaultCallAlloyConfig = CallAlloyConfig {
  maxInstances :: Maybe Integer
maxInstances = Maybe Integer
forall a. Maybe a
Nothing,
  noOverflow :: Bool
noOverflow   = Bool
True,
  satSolver :: SatSolver
satSolver    = SatSolver
SAT4J,
  timeout :: Maybe Int
timeout      = Maybe Int
forall a. Maybe a
Nothing
  }

{-# NOINLINE outLock #-}
outLock :: Lock
outLock :: Lock
outLock = IO Lock -> Lock
forall a. IO a -> a
unsafePerformIO IO Lock
newLock

putOutLn :: String -> IO ()
putOutLn :: String -> IO ()
putOutLn = Lock -> IO () -> IO ()
forall a. Lock -> IO a -> IO a
withLock Lock
outLock (IO () -> IO ()) -> (String -> IO ()) -> String -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> IO ()
putStrLn

putErrLn :: String -> IO ()
putErrLn :: String -> IO ()
putErrLn = Lock -> IO () -> IO ()
forall a. Lock -> IO a -> IO a
withLock Lock
outLock (IO () -> IO ()) -> (String -> IO ()) -> String -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Handle -> String -> IO ()
hPutStrLn Handle
stderr

{-|
This function may be used to get all raw model instances for a given Alloy
specification. It calls Alloy via a Java interface and splits the raw instance
answers before returning the resulting list of raw instances.
-}
getRawInstances
  :: Maybe Integer
  -- ^ How many instances to return; 'Nothing' for all.
  -> String
  -- ^ The Alloy specification which should be loaded.
  -> IO [ByteString]
getRawInstances :: Maybe Integer -> String -> IO [ByteString]
getRawInstances Maybe Integer
maxIs = CallAlloyConfig -> String -> IO [ByteString]
getRawInstancesWith CallAlloyConfig
defaultCallAlloyConfig {
  maxInstances = maxIs
  }

{-|
Creates an Alloy process using the given config.
-}
callAlloyWith
  :: CallAlloyConfig
  -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
callAlloyWith :: CallAlloyConfig
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
callAlloyWith CallAlloyConfig
config = do
  classPath <- IO String
getClassPath
  let callAlloy = String -> [String] -> CreateProcess
proc String
"java"
        ([String] -> CreateProcess) -> [String] -> CreateProcess
forall a b. (a -> b) -> a -> b
$ [
          String
"--enable-native-access=ALL-UNNAMED",
          String
"-cp", String
classPath, String
classPackage String -> ShowS
forall a. [a] -> [a] -> [a]
++ Char
'.' Char -> ShowS
forall a. a -> [a] -> [a]
: String
className,
          String
"-i", Integer -> String
forall a. Show a => a -> String
show (Integer -> String) -> Integer -> String
forall a b. (a -> b) -> a -> b
$ Integer -> Maybe Integer -> Integer
forall a. a -> Maybe a -> a
fromMaybe (-Integer
1) (Maybe Integer -> Integer) -> Maybe Integer -> Integer
forall a b. (a -> b) -> a -> b
$ CallAlloyConfig -> Maybe Integer
maxInstances CallAlloyConfig
config
          ]
        [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
"-o" | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ CallAlloyConfig -> Bool
noOverflow CallAlloyConfig
config]
        [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
"-s", SatSolver -> String
toParameter (CallAlloyConfig -> SatSolver
satSolver CallAlloyConfig
config)]
  createProcess callAlloy {
    std_out = CreatePipe,
    std_in  = CreatePipe,
    std_err = CreatePipe
  }

{-|
This function may be used to get all raw model instances for a given Alloy
specification. It calls Alloy via a Java interface and splits the raw instance
answers before returning the resulting list of raw instances.
Parameters are set using a t'CallAlloyConfig'.
-}
getRawInstancesWith
  :: CallAlloyConfig
  -- ^ The configuration to be used.
  -> String
  -- ^ The Alloy specification which should be loaded.
  -> IO [ByteString]
getRawInstancesWith :: CallAlloyConfig -> String -> IO [ByteString]
getRawInstancesWith CallAlloyConfig
config String
content
  = IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
    -> IO ())
-> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
    -> IO [ByteString])
-> IO [ByteString]
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket (CallAlloyConfig
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
callAlloyWith CallAlloyConfig
config) (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO ()
cleanupProcess (((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
  -> IO [ByteString])
 -> IO [ByteString])
-> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
    -> IO [ByteString])
-> IO [ByteString]
forall a b. (a -> b) -> a -> b
$ \(Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
p -> do
  (Just hin, Just hout, Just herr, ph) <- (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
p
#ifndef mingw32_HOST_OS
  hSetBuffering hin NoBuffering
  let abort = Maybe a
forall a. Maybe a
Nothing
#else
  abort <- Just <$> newIORef False
#endif
  let evaluateAlloy' = do
        Handle -> String -> IO ()
hPutStr Handle
hin String
content
        Handle -> IO ()
hFlush Handle
hin
        Handle -> IO ()
hClose Handle
hin
      evaluateAlloy = IO () -> (IOException -> IO ()) -> IO ()
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
catch IO ()
evaluateAlloy' ((IOException -> IO ()) -> IO ())
-> (IOException -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \IOException
e -> do
        let err :: String
err = IOException -> String
forall a. Show a => a -> String
show (IOException
e :: IOException)
            warn :: String
warn = String
"Maybe not complete instance was sent to Alloy "
            explain :: String
explain = String
"(Are timeouts set? Make sure they are not too small!): "
        String -> IO ()
putErrLn (String
"Warning: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
warn String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
explain String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
err)
  withTimeout hin hout herr ph abort (timeout config) $ do
    (out, err) <- fst <$> concurrently
      (concurrently (getOutput hout) (getOutput herr))
      evaluateAlloy
    printContentOnError out abort ph
    let err' = [ByteString] -> [ByteString]
removeInfoLines [ByteString]
err
    unless (null err') $ fail $ unpack $ BS.unlines err'
    return $ fmap (BS.intercalate "\n")
      $ filterLast ((/= partialInstance) . last)
      $ drop 1 $ splitOn [begin] out
  where
    begin :: ByteString
    begin :: ByteString
begin = ByteString
"---Trace---"
    filterLast :: (a -> Bool) -> [a] -> [a]
filterLast a -> Bool
_ []     = []
    filterLast a -> Bool
p x :: [a]
x@[a
_]  = (a -> Bool) -> [a] -> [a]
forall {a}. (a -> Bool) -> [a] -> [a]
filter a -> Bool
p [a]
x
    filterLast a -> Bool
p (a
x:[a]
xs) = a
xa -> [a] -> [a]
forall a. a -> [a] -> [a]
:(a -> Bool) -> [a] -> [a]
filterLast a -> Bool
p [a]
xs
    getOutput' :: Handle -> IO [ByteString]
getOutput' Handle
h = do
      eof <- Handle -> IO Bool
hIsEOF Handle
h
      if eof
        then return []
        else (:) <$> BS.hGetLine h <*> getOutput h
    getOutput :: Handle -> IO [ByteString]
getOutput Handle
h = IO [ByteString]
-> (IOException -> IO [ByteString]) -> IO [ByteString]
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
catch
      (Handle -> IO [ByteString]
getOutput' Handle
h)
      (\(IOException
_ :: IOException) -> [ByteString] -> IO [ByteString]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [ByteString
partialInstance])
    printContentOnError :: [ByteString] -> Maybe (IORef Bool) -> ProcessHandle -> IO ()
printContentOnError [ByteString]
out Maybe (IORef Bool)
abort ProcessHandle
ph = do
      code <- ProcessHandle -> IO ExitCode
waitForProcess ProcessHandle
ph
      aborted <- maybe (return False) readIORef abort
      when (code == ExitFailure 1 && not aborted)
        $ putOutLn $ "Failed parsing the Alloy code?:\n" <> content
      when (code == ExitFailure 2 && not aborted)
        $ withLock outLock $ BS.putStrLn $ BS.unlines out

partialInstance :: ByteString
partialInstance :: ByteString
partialInstance = ByteString
"---PARTIAL_INSTANCE---"

{-|
Removes lines starting with any of

@
[main] INFO
[main] WARN
[Finalizer] WARN
@

and partial instances

@
---PARTIAL_INSTANCE---
@

which seem to be appearing since Alloy-6.0.0
-}
removeInfoLines :: [ByteString] -> [ByteString]
removeInfoLines :: [ByteString] -> [ByteString]
removeInfoLines (ByteString
x:[ByteString]
xs)
  | Just ByteString
_ <- ByteString -> ByteString -> Maybe ByteString
BS.stripPrefix ByteString
"[main] INFO" ByteString
x
  = [ByteString] -> [ByteString]
removeInfoLines [ByteString]
xs
  | Just ByteString
_ <- ByteString -> ByteString -> Maybe ByteString
BS.stripPrefix ByteString
"[main] WARN" ByteString
x
  = [ByteString] -> [ByteString]
removeInfoLines [ByteString]
xs
  | Just ByteString
_ <- ByteString -> ByteString -> Maybe ByteString
BS.stripPrefix ByteString
"[Finalizer] WARN" ByteString
x
  = [ByteString] -> [ByteString]
removeInfoLines [ByteString]
xs
  | ByteString
x ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
partialInstance
  = [ByteString] -> [ByteString]
removeInfoLines [ByteString]
xs
removeInfoLines [ByteString]
xs = [ByteString]
xs

{-|
Start a new sub process that communicates with the worker process
if a timeout is provided.
Execution is aborted by closing all handles and
killing the underlying worker processes after the given amount of time
(if it has not finished by then).
The process will wait for the sub process to make the result available.

If the provided timeout is 'Nothing', evaluation happens without
scheduled interruption in the main thread.
-}
withTimeout
  :: Handle
  -- ^ the input handle (of the worker) to close
  -> Handle
  -- ^ the output handle (of the worker) to close
  -> Handle
  -- ^ the error handle (of the worker) to close
  -> ProcessHandle
  -- ^ the worker process handle
  -> Maybe (IORef Bool)
  -- ^ the IORef to communicate process abortion on Windows
  -> Maybe Int
  -- ^ the timeout (Nothing if no timeout)
  -> IO a
  -- ^ some action interacting with the worker and its handles
  -> IO a
withTimeout :: forall a.
Handle
-> Handle
-> Handle
-> ProcessHandle
-> Maybe (IORef Bool)
-> Maybe Int
-> IO a
-> IO a
withTimeout Handle
_ Handle
_ Handle
_ ProcessHandle
_  Maybe (IORef Bool)
_     Maybe Int
Nothing  IO a
p = IO a
p
withTimeout Handle
i Handle
o Handle
e ProcessHandle
ph Maybe (IORef Bool)
abort (Just Int
t) IO a
p = IO a -> (Async a -> IO a) -> IO a
forall a b. IO a -> (Async a -> IO b) -> IO b
withAsync IO a
p ((Async a -> IO a) -> IO a) -> (Async a -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$ \Async a
a -> do
  Int -> IO ()
threadDelay Int
t
  Maybe (IORef Bool) -> (IORef Bool -> IO ()) -> IO ()
forall (m :: * -> *) a.
Applicative m =>
Maybe a -> (a -> m ()) -> m ()
whenJust Maybe (IORef Bool)
abort (IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
`atomicWriteIORef` Bool
True)
  (IO () -> IO ()) -> [IO ()] -> IO ()
forall (f :: * -> *) a b. Foldable f => (a -> IO b) -> f a -> IO ()
mapConcurrently_ IO () -> IO ()
forall a. a -> a
id [
    Handle -> IO ()
hClose Handle
e,
    Handle -> IO ()
hClose Handle
o,
    ProcessHandle -> IO ()
terminateProcess ProcessHandle
ph
    ]
  Handle -> IO ()
hClose Handle
i
  Async a -> IO a
forall a. Async a -> IO a
wait Async a
a

{-|
Get the class path of all files in the data directory.

Returns the class path.
-}
getClassPath :: IO FilePath
getClassPath :: IO String
getClassPath =
  String -> String -> String -> ShowS
concatPaths (String -> String -> String -> ShowS)
-> IO String -> IO (String -> String -> ShowS)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO String
getDataDir IO (String -> String -> ShowS) -> IO String -> IO (String -> ShowS)
forall a b. IO (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> IO String
alloyJar IO (String -> ShowS) -> IO String -> IO ShowS
forall a b. IO (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> IO String
commonsCliJar IO ShowS -> IO String -> IO String
forall a b. IO (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> IO String
slf4jJar
  where
    concatPaths :: String -> String -> String -> ShowS
concatPaths String
w String
x String
y String
z = String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate
      [Char
searchPathSeparator]
      [String
w, String
x, String
y, String
z]