Writing Haskell interpreter in C++ (using ghc or hugs as library)
Asked Answered
M

2

8

I'm writing a C++ application that needs to interpret and evaluate haskell code. This code isn't known at compile time but given by the user. Is there a way to use a haskell compiler/interpreter (like GHCi or hugs) as a library?

  • I found FFI but this seems only to work for haskell code that is known at compile time.
  • I found the GHC API and hint, but they seem only to work when I want to interpret haskell code from out of haskell.
Matroclinous answered 27/12, 2011 at 15:22 Comment(5)
I think you found the two important pieces, but you need to combine them! Write some compile-time Haskell that sets up and uses the GHC API, and call that code from your C++ via the FFI. But I've never actually done it, so I'm not confident in making this an actual answer.Ardie
I did hope there was an easier solution...Matroclinous
The dumb solution: instead of using it as a library, you could use GHC(i) as native code via system calls.Andeee
Just out of curiosity, to what extend do you want to do this? The basic workflow depending on how advanced you want to make this would require surprisingly few FFI calls. If you want to just load a file and run expressions using that file, that's very easy to do. Also May I ask which platform you intend to run this on? If it's Windows you could try my library hs2lib for the FFI bindings.Sherleysherline
I too want to run a Haskell interpreter from C++ - since you've asked this question 5 years ago, can you share some experiences? Do you have some example project?Ellipsoid
S
8

Instead of using the GHC api I would suggest binding to Hint for this particular approach, which is just a simplified wrapper around the GHC api. The reason I would recommend this is because the GHC api has a bit of a steep learning curve.

But anyway, Like I said In my comment, depending on how deep you want this to go it would require surprisingly few FFI calls. Below I give an example on how to run expressions from a loaded file and return the results (only if there's a show instance). This is just the basics, returning the results as a structure should be possible too.

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

Since we have to exit haskell land we have to have some way to refer to the Context, We can do this with a StablePtr and I just wrap it in an IORef to make it mutable in case you want to change things in the future. Note that the GHC API does not support type checking an in-memory buffer, so you have to save the code you want to interpret to a temporary file before loading it.

The -- @@ Annotations are for my tool Hs2lib, don't mind them if you don't use it.

My test file is

module Test where

import Control.Monad
import Control.Monad.Instances

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

and we can test this using a simple test

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

So yeah, it works in Haskell, now to make it work outside of haskell.

Just add to the top of the file a few instructions for Hs2lib on how to marshal ModuleName because that type is defined in a file which it doesn't have the source to.

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

or

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

if on a 64bit architecture,

and Just invoke Hs2lib

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

And you'll end up with among others, an Include file with

#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

I haven't tested the C++ side, but there's no reason it shouldn't work. This is a very barebones example, if you compile it to a dynamic lib you probably want to redirect stdout, stderr and stdin.

Sherleysherline answered 28/12, 2011 at 3:42 Comment(6)
Thank you very much for your examples. Unfortunately I need this on linux. What can I do to run your example there?Matroclinous
The Haskell code in the example is platform independent, so the same would work on linux. The haskell code hs2lib generates should work fine on linux. It's just that for various reasons I've never gotten ghc to compile dynamic libs on linux. See #7653299 . What it CAN help you with is generating the Marshalling and FFI code for you, just pass it the -T flag to keep the temporary files and you can just get it from there. From there it's up to you to compile it to a static/shared lib.Sherleysherline
Alternatively you could use some form of IPC and just have a haskell "server" and a C++ client that communicate via sockets or pipes. and just serialize the information. That would be another way to approach it. But again, the Haskell code above is platform independent.Sherleysherline
Your haskell interpreter does work when running with ghci. So I just need a way to call it from C++ using FFI. I tried to use ghc directly, but got "undefined reference" errors. I posted another question for this: #8659486 (maybe you can help me there). This question here is solved. Thank you.Matroclinous
It seems that the manual and homepage for hs2lib linked from hackage are dead. Also, I couldn't build haskell-src-exts. Is hs2lib dead? Is there a successor?Illegal
Hi, the project is mostly in maintenance mode as I have been trying to rewrite it to solve a few issues with the design (like inability to support x64 GHC). That said, I am doing bug fixes I just forgotten to upload the new versions. I have uploaded 0.6.3 which will compile with recent GHCs and Haskell-src-exts along with various fixes. The manual can be found at scribd.com/doc/63918055/Hs2lib-Tutorial and the homepage will work again once I finish transferring all my DNS records correctly. If you have any questions or issues feel free to create a SO post and link me or mail me.Sherleysherline
C
4

Since GHC is written in Haskell, its API is exclusively available from Haskell. Writing the interfaces you need in Haskell and binding them to C with the FFI, as Daniel Wagner suggested, is going to be the simplest route. This is probably easier than using a direct binding of the GHC API to C would be; you get to use Haskell's strengths to build the interfaces you need, and only interface with them in C++ at the top layer.

Note that Haskell's FFI will only export to C; if you want a C++-ish wrapper around it, you'll have to write it as another layer.

(BTW, Hugs is ancient and unmaintained.)

Conglobate answered 27/12, 2011 at 16:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.