用C++编写Haskell解释器(使用ghc或hugs作为库)

Hei*_*nzi 8 c++ haskell ghc ghci hugs

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

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

Phy*_*hyx 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
Run Code Online (Sandbox Code Playgroud)

因为我们必须退出haskell land我们必须有一些方法来引用Context,我们可以用a来做这个,StablePtr我只是将它包装成一个IORef可变的,以防你想在将来改变它.请注意,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 (*)
Run Code Online (Sandbox Code Playgroud)

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

*FFIInterpreter> session <- createContext "Test"
*FFIInterpreter> runExpr session "bar 5"
"25"
Run Code Online (Sandbox Code Playgroud)

所以,是的,它适用于Haskell,现在让它在haskell之外工作.

只需在文件顶部添加一些Hs2lib关于如何编组的指令,ModuleName因为该类型是在没有源代码的文件中定义的.

{- @@ INSTANCE ModuleName 0                 @@ -}
{- @@ HS2HS ModuleName CWString             @@ -}
{- @@ IMPORT "Data.IORef"                   @@ -}
{- @@ IMPORT "Language.Haskell.Interpreter" @@ -}
{- @@ HS2C  ModuleName "wchar_t*@4"         @@ -}
Run Code Online (Sandbox Code Playgroud)

要么

{- @@ HS2C  ModuleName "wchar_t*@8"         @@ -}
Run Code Online (Sandbox Code Playgroud)

如果是64位架构,

并只是调用 Hs2lib

PS Haskell\FFIInterpreter> hs2lib .\FFIInterpreter.hs -n "HsInterpreter"
Linking main.exe ...
Done.
Run Code Online (Sandbox Code Playgroud)

而你最终会得到一个Include文件

#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
Run Code Online (Sandbox Code Playgroud)

我没有测试过C++方面,但没有理由不应该这样做.这是一个非常简单的例子,如果你将它编译成动态库你可能想要重定向stdout,stderr和stdin.