【问题标题】:Writing Haskell interpreter in C++ (using ghc or hugs as library)用 C++ 编写 Haskell 解释器(使用 ghc 或 hugs 作为库)
【发布时间】:2012-01-28 14:17:48
【问题描述】:

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

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

【问题讨论】:

  • 我想你找到了两个重要的部分,但你需要把它们结合起来!编写一些设置和使用 GHC API 的编译时 Haskell,并通过 FFI 从您的 C++ 调用该代码。但我从来没有真正做过,所以我没有信心把它作为一个实际的答案。
  • 我确实希望有一个更简单的解决方案...
  • 愚蠢的解决方案:您可以通过系统调用将 GHC(i) 用作本机代码,而不是将其用作库。
  • 只是出于好奇,您想达到什么程度?基本工作流程取决于您想要实现的高级程度,需要的 FFI 调用很少。如果您只想加载一个文件并使用该文件运行表达式,那很容易做到。另外请问您打算在哪个平台上运行它?如果是 Windows,您可以尝试使用我的库 hs2lib 进行 FFI 绑定。
  • 我也想从 C++ 运行 Haskell 解释器——既然你 5 年前问过这个问题,你能分享一些经验吗?你有一些示例项目吗?

标签: c++ haskell ghc ghci hugs


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    我建议不要使用 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 领域,我们必须有一些方法来引用上下文,我们可以使用 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 (*)
    

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

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

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

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

    {- @@ 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。

    【讨论】:

    • 非常感谢您的示例。不幸的是,我在 linux 上需要这个。我该怎么做才能在那里运行您的示例?
    • 示例中的 Haskell 代码与平台无关,因此同样适用于 linux。 haskell 代码 hs2lib 生成 应该 在 linux 上可以正常工作。只是由于各种原因,我从来没有让 ghc 在 linux 上编译动态库。见stackoverflow.com/questions/7652799/…。它可以帮助您为您生成编组和 FFI 代码,只需将 -T 标志传递给它以保留临时文件,您就可以从那里获取它。从那里可以将其编译为静态/共享库。
    • 另外,您可以使用某种形式的 IPC,并且只需一个 haskell“服务器”和一个通过套接字或管道进行通信的 C++ 客户端。并将信息序列化。那将是另一种接近它的方法。但同样,上面的 Haskell 代码是平台无关的。
    • 使用 ghci 运行时,您的 haskell 解释器确实可以工作。所以我只需要一种使用 FFI 从 C++ 调用它的方法。我尝试直接使用 ghc,但出现“未定义引用”错误。我为此发布了另一个问题:stackoverflow.com/questions/8658986/…(也许你可以在那里帮助我)。这个问题到这里就解决了。谢谢。
    • 似乎从hackage链接的hs2lib的手册和主页已经死了。此外,我无法构建 haskell-src-exts。 hs2lib 死了吗?有继任者吗?
    猜你喜欢
    • 2012-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多