Написание интерпретатора Haskell на C++ (используя ghc или hugs в качестве библиотеки)
Я пишу приложение на C++, которое должно интерпретировать и оценивать код haskell. Этот код не известен во время компиляции, но задан пользователем. Есть ли способ использовать компилятор/интерпретатор haskell (например, GHCi или hugs) в качестве библиотеки?
- Я нашел FFI, но это, похоже, работает только для кода haskell, который известен во время компиляции.
- Я нашел API GHC и подсказку, но они, похоже, работают только тогда, когда я хочу интерпретировать код haskell из хаскель.
2 ответов
вместо использования api GHC я бы предложил привязку к подсказка для этого конкретного подхода, который является просто упрощенной оболочкой вокруг api GHC. Причина, по которой я бы рекомендовал это, заключается в том, что api GHC имеет немного крутой кривой обучения.
но в любом случае, как я сказал в своем комментарии, в зависимости от того, насколько глубоко вы хотите, чтобы это произошло, потребуется удивительно мало вызовов 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
так как мы должны выйти из земли Хаскелла, мы должны иметь какой-то способ ссылаться на контекст, мы можем сделать это с StablePtr
и я просто заворачиваю его в IORef
чтобы сделать его изменчивым, если вы хотите изменить вещи в будущем. Обратите внимание, что API GHC не поддерживает проверку типа буфера в памяти, поэтому необходимо сохранить код вы хотите интерпретировать временный файл перед его загрузкой.
на -- @@
аннотации для моего инструмента 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++, но нет причин, по которым это не должно работать. Это очень простой пример, если вы скомпилируете его в динамическую lib, вы, вероятно, захотите перенаправить stdout, stderr и стандартный ввод.
поскольку GHC написан в Haskell, его API доступен исключительно от Haskell. Написание необходимых интерфейсов в Haskell и привязка их к C с помощью FFI, как предложил Даниэль Вагнер, будет самым простым путем. Это, вероятно, проще, чем использовать прямую привязку API GHC к C; вы можете использовать сильные стороны Haskell для создания необходимых интерфейсов, и только интерфейс с ними на C++ на верхнем уровне.
обратите внимание, что FFI Haskell будет экспортировать только в C; если вы хотите, чтобы вокруг него была оболочка c++ - ish, вам придется написать ее как другой слой.
(кстати, объятия древние и не занавешены.)