Написание интерпретатора языка Haskell на C ++ (с использованием ghc или hugs в качестве библиотеки) - PullRequest
8 голосов
/ 27 декабря 2011

Я пишу приложение на C ++, которое должно интерпретировать и оценивать код на haskell.Этот код не известен во время компиляции, но предоставляется пользователем.Есть ли способ использовать компилятор / интерпретатор haskell (например, GHCi или hugs) в качестве библиотеки?

  • Я обнаружил FFI, но, похоже, это работает только для кода на Haskell, известного во время компиляции.
  • Я нашел GHC API и подсказку, но они, кажется, работают только тогда, когда я хочу интерпретировать код haskell из haskell.

Ответы [ 2 ]

8 голосов
/ 28 декабря 2011

Вместо использования 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

Так как мы должны покинуть землю в 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.

Просто добавьте в начало файла несколько инструкций для 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.

4 голосов
/ 27 декабря 2011

Поскольку GHC написан на Haskell, его API доступен исключительно на Haskell.Написание интерфейсов, которые вам нужны в Haskell, и привязка их к C с помощью FFI, как предположил Даниэль Вагнер, будет самым простым путем.Это, вероятно, проще, чем использование прямой привязки GHC API к C;вы можете использовать сильные стороны Haskell для создания необходимых вам интерфейсов и взаимодействовать с ними только в C ++ на верхнем уровне.

Обратите внимание, что FFI Haskell будет экспортировать только в C;если вам нужна оболочка C ++ - ish вокруг нее, вам придется написать ее как еще один слой.

(Кстати, Hugs древний и не поддерживается).

...