2011-12-27 3 views
8

Estoy escribiendo una aplicación C++ que necesita interpretar y evaluar el código haskell. Este código no se conoce en tiempo de compilación, pero lo da el usuario. ¿Hay alguna manera de usar un compilador/intérprete haskell (como GHCi o abrazos) como biblioteca?Escribiendo el intérprete Haskell en C++ (usando ghc o abrazos como biblioteca)

  • Encontré FFI pero esto parece funcionar solo para el código haskell que se conoce en tiempo de compilación.
  • Encontré la API y la sugerencia de GHC, pero parecen funcionar solo cuando quiero interpretar el código haskell desde fuera de haskell.
+1

Creo que encontraste las dos piezas importantes, ¡pero tienes que combinarlas! Escriba algún Haskell en tiempo de compilación que configure y use la API de GHC, y llame a ese código desde su C++ a través de la FFI. Pero nunca lo he hecho, así que no estoy seguro de hacer de esto una respuesta real. –

+0

Espero que haya una solución más fácil ... – Heinzi

+0

La solución tonta: en lugar de usarla como biblioteca, puede usar GHC (i) como código nativo a través de llamadas al sistema. –

Respuesta

7

En lugar de utilizar la API de GHC Yo sugeriría unión a Hint para este enfoque particular, que es sólo un envoltorio alrededor de la API simplificada GHC. La razón por la que recomendaría esto es porque la API de GHC tiene una curva de aprendizaje un poco empinada.

Pero de todos modos, como he dicho En mi comentario, dependiendo de qué tan profundo desee que vaya, requerirá sorprendentemente pocas llamadas FFI. A continuación doy un ejemplo sobre cómo ejecutar expresiones desde un archivo cargado y devolver los resultados (solo si hay una instancia de show). Esto es solo lo básico, devolviendo los resultados como una estructura si también es posible.

module FFIInterpreter where 

import Language.Haskell.Interpreter 

import Data.IORef 
import Foreign.StablePtr 

type Session = Interpreter() 
type Context = StablePtr (IORef Session) 

-- @@ Export 
-- | Create a new empty Context to be used when calling any functions inside 
-- this class. 
-- . 
-- String: The path to the module to load or the module name 
createContext :: ModuleName -> IO Context 
createContext name 
    = do let session = newModule name 
     _ <- runInterpreter session 
     liftIO $ newStablePtr =<< newIORef session 

newModule :: ModuleName -> Session 
newModule name = loadModules [name] >> setTopLevelModules [name] 

-- @@ Export 
-- | free a context up 
freeContext :: Context -> IO() 
freeContext = freeStablePtr 

-- @@ Export = evalExpression 
runExpr :: Context -> String -> IO String 
runExpr env input 
    = do env_value <- deRefStablePtr env 
     tcs_value <- readIORef env_value 
     result <- runInterpreter (tcs_value >> eval input) 
     return $ either show id result 

Ya que tenemos para salir de la tierra Haskell tenemos que tener alguna manera para referirse al contexto podemos hacer esto con un StablePtr y acabo de envolverlo en una IORef para que sea mutable en caso de que quiera cambiar las cosas en el futuro Tenga en cuenta que la API de GHC no es compatible con el tipo de comprobación de un búfer en memoria, por lo que debe guardar el código que desea interpretar en un archivo temporal antes de cargarlo.

Las anotaciones -- @@ son para mi herramienta Hs2lib, no les moleste si no las usa.

Mi archivo de prueba es

module Test where 

import Control.Monad 
import Control.Monad.Instances 

-- | This function calculates the value \x->x*x 
bar :: Int -> Int 
bar = join (*) 

y podemos probar esto mediante una prueba sencilla

*FFIInterpreter> session <- createContext "Test" 
*FFIInterpreter> runExpr session "bar 5" 
"25" 

Así que sí, funciona en Haskell, ahora para hacer que funcione fuera de Haskell.

Simplemente agregue a la parte superior del archivo algunas instrucciones para Hs2lib sobre cómo ordenar ModuleName porque ese tipo está definido en un archivo que no tiene la fuente.

{- @@ INSTANCE ModuleName 0     @@ -} 
{- @@ HS2HS ModuleName CWString    @@ -} 
{- @@ IMPORT "Data.IORef"     @@ -} 
{- @@ IMPORT "Language.Haskell.Interpreter" @@ -} 
{- @@ HS2C ModuleName "wchar_t*@4"   @@ -} 

o

{- @@ HS2C ModuleName "wchar_t*@8"   @@ -} 

si en una arquitectura de 64 bits,

y Just invocar Hs2lib

PS Haskell\FFIInterpreter> hs2lib .\FFIInterpreter.hs -n "HsInterpreter" 
Linking main.exe ... 
Done. 

Y usted va a terminar con, entre otros, un archivo con Incluir

#ifdef __cplusplus 
extern "C" { 
#endif 
// Runtime control methods 
// HsStart :: IO() 
extern CALLTYPE(void) HsStart (void); 

// HsEnd :: IO() 
extern CALLTYPE(void) HsEnd (void); 

// createContext :: ModuleName -> IO (StablePtr (IORef (Interpreter()))) 
// 
// Create a new empty Context to be used when calling any functionsinside this class. 
// String: The path to the module to load or themodule name 
// 
extern CALLTYPE(void*) createContext (wchar_t* arg1); 

// freeContext :: StablePtr (IORef (Interpreter())) -> IO() 
// 
// free a context up 
// 
extern CALLTYPE(void) freeContext (void* arg1); 

// evalExpression :: StablePtr (IORef (Interpreter())) -> String -> IO String 
extern CALLTYPE(wchar_t*) evalExpression (void* arg1, wchar_t* arg2); 

#ifdef __cplusplus 
} 
#endif 

No he probado el lado de C++, pero no hay ninguna razón por la que no debería funcionar. Este es un ejemplo muy escueto, si lo compila en una lib dinámica probablemente desee redirigir stdout, stderr y stdin.

+0

Muchas gracias por sus ejemplos. Desafortunadamente necesito esto en Linux. ¿Qué puedo hacer para poner tu ejemplo allí? – Heinzi

+0

El código de Haskell en el ejemplo es independiente de la plataforma, por lo que lo mismo funcionaría en Linux. El código haskell hs2lib genera * debería * funcionar bien en Linux. Es solo que por diversas razones nunca he conseguido que ghc compile libs dinámicas en Linux. Ver http://stackoverflow.com/questions/7652799/compiling-ghc-with-fpic-support. Lo que PUEDE ayudarlo es generar el código de Marshalling y FFI para usted, simplemente páselo por la bandera -T para guardar los archivos temporales y puede obtenerlo desde allí. A partir de ahí depende de usted compilarlo en una lib estática/compartida. – Phyx

+1

Alternativamente, podría usar algún tipo de IPC y simplemente tener un "servidor" de Haskell y un cliente de C++ que se comuniquen a través de sockets o tuberías. y simplemente serializar la información. Esa sería otra forma de abordarlo. Pero, una vez más, el código Haskell anterior es independiente de la plataforma. – Phyx

4

Dado que GHC está escrito en Haskell, su API está disponible exclusivamente en Haskell. Escribir las interfaces que necesita en Haskell y vincularlas a C con la FFI, como sugirió Daniel Wagner, será la ruta más simple. Esto es probablemente más fácil que usar un enlace directo de la API de GHC a C; puedes usar las fortalezas de Haskell para construir las interfaces que necesitas, y solo interactuar con ellas en C++ en la capa superior.

Tenga en cuenta que la FFI de Haskell solo exportará a C; si quieres un envoltorio C++ - ish alrededor, tendrás que escribirlo como otra capa.

(Por cierto, abrazos es antigua y sin mantenimiento.)

Cuestiones relacionadas