8

我正在编写一个需要解释和评估 haskell 代码的 C++ 应用程序。此代码在编译时未知,但由用户提供。有没有办法使用 haskell 编译器/解释器(如 GHCi 或 hugs)作为库?

  • 我找到了 FFI,但这似乎只适用于编译时已知的 haskell 代码。
  • 我找到了 GHC API 和提示,但它们似乎只在我想从 haskell 中解释 haskell 代码时才起作用。
4

2 回答 2

8

我建议不要使用 GHC api,而是为这种特定方法绑定到Hint,这只是 GHC api 的简化包装。我推荐这个的原因是因为 GHC api 的学习曲线有点陡峭。

但无论如何,就像我在评论中所说的那样,取决于你希望它走多远,它需要的 FFI 调用很少。下面我将举例说明如何从加载的文件中运行表达式并返回结果(仅当存在 show 实例时)。这只是基础,也应该可以将结果作为结构返回。

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

由于我们必须退出 haskell 领域,我们必须有一些方法来引用上下文,我们可以使用 a 来做到这一点,StablePtr我只是将它包装在 anIORef中以使其可变,以防您将来想要更改内容。请注意,GHC API 不支持对内存缓冲区进行类型检查,因此您必须在加载之前将要解释的代码保存到临时文件中。

注释适用于我的-- @@工具 Hs2lib,如果您不使用它,请不要介意它们。

我的测试文件是

module Test where

import Control.Monad
import Control.Monad.Instances

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

我们可以用一个简单的测试来测试它

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

所以是的,它在 Haskell 中工作,现在让它在 Haskell 之外工作。

只需在文件顶部添加一些关于如何编组的 Hs2lib 说明,ModuleName因为该类型是在它没有源的文件中定义的。

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

或者

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

如果在 64 位架构上,

并调用Hs2lib

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

你最终会得到一个包含文件

#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

我还没有测试过 C++ 方面,但没有理由它不应该工作。这是一个非常简单的示例,如果您将其编译为动态库,您可能希望重定向 stdout、stderr 和 stdin。

于 2011-12-28T03:42:37.887 回答
4

由于 GHC 是用 Haskell 编写的,因此它的 API 只能从 Haskell 获得。正如 Daniel Wagner 所建议的那样,在 Haskell 中编写所需的接口并使用 FFI 将它们绑定到 C 将是最简单的路线。这可能比直接将 GHC API 绑定到 C 更容易;你可以利用 Haskell 的优势来构建你需要的接口,并且只在顶层用 C++ 与它们交互。

注意 Haskell 的 FFI 只会导出到 C;如果你想要一个 C++-ish 包装器,你必须把它写成另一个层。

(顺便说一句,拥抱是古老且无人维护的。)

于 2011-12-27T16:58:33.497 回答