2011-12-27 2 views
8

저는 haskell 코드를 해석하고 평가해야하는 C++ 애플리케이션을 작성하고 있습니다. 이 코드는 컴파일시에는 알 수 없지만 사용자가 제공합니다. haskell 컴파일러/인터프리터 (예 : GHCi 또는 hugs)를 라이브러리로 사용하는 방법이 있습니까?Haskell 인터프리터를 C++로 작성하기 (ghc 또는 hugs를 라이브러리로 사용)

  • FFI가 발견되었지만 컴파일 타임에 알려진 haskell 코드에서만 작동합니다.
  • GHC API와 힌트를 찾았지만 haskell에서 haskell 코드를 해석하려는 경우에만 작동하는 것처럼 보입니다.
+1

두 개의 중요한 조각을 찾은 것 같습니다. 그러나 그것들을 결합해야합니다! GHC API를 설정하고 사용하는 컴파일 타임 하스켈을 작성하고 C++에서 해당 코드를 FFI를 통해 호출하십시오. 하지만 실제로 해본 적이 없으므로 나는 이것을 실제 답변으로 만드는 것에 자신이 없습니다. –

+0

더 쉬운 해결책이 있었으면 좋겠다. – Heinzi

+0

벙어리 솔루션 : 라이브러리로 사용하는 대신 시스템 호출을 통해 GHC (i)를 원시 코드로 사용할 수있다. –

답변

7

. 이유는 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 

우리가 상황을 참조 할 수있는 방법이 있어야, 우리는 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" 

그래서 그래, 그것은 하스켈에서 작동하는 간단한 테스트를 사용하여이를 테스트 할 수 있습니다.

소스가없는 파일에 해당 형식이 정의되어 있기 때문에 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++ 측을 테스트하지는 않았지만 작동하지 않아야하는 이유는 없습니다. 이것은 매우 베어 본 예제입니다. 동적 lib로 컴파일하면 stdout, stderr 및 stdin을 리디렉션하려는 것이 좋습니다.

+0

예제를 주셔서 감사합니다. 불행히도 나는 리눅스에서 이것을 필요로한다. 나는 그곳에서 당신의 모범을 보이기 위해 무엇을 할 수 있습니까? – Heinzi

+0

예제의 Haskell 코드는 플랫폼에 독립적이므로 linux에서도 동일하게 작동합니다. haskell 코드 hs2lib는 linux에서 * 잘 작동해야합니다. 여러 가지 이유 때문에 ghc가 리눅스에서 동적 인 libs를 컴파일하는 것을 결코 얻지 못했습니다. http://stackoverflow.com/questions/7652799/compiling-ghc-with-fpic-support를 참조하십시오. 도움이되는 것은 Marshalling과 FFI 코드를 생성하는 것입니다. 임시 파일을 보관하기 위해 -T 플래그를 전달하면 거기에서 가져올 수 있습니다. 거기서 정적/공유 라이브러리로 컴파일하는 것은 당신에게 달려 있습니다. – Phyx

+1

또는 IPC 형식을 사용할 수 있으며 소켓이나 파이프를 통해 통신하는 haskell "서버"와 C++ 클라이언트 만 있으면됩니다. 정보를 직렬화하면됩니다. 그것이 접근하는 또 다른 방법 일 것입니다. 그러나 위의 Haskell 코드는 플랫폼에 독립적입니다. – Phyx

4

GHC는 하스켈로 작성되었으므로 API는 하스켈에서만 사용할 수 있습니다. 다니엘 바그너 (Daniel Wagner)가 제안한 것처럼 하스켈에서 필요로하는 인터페이스를 작성하고이를 FFI와 함께 C에 바인딩하는 것이 가장 간단한 방법이 될 것입니다. 이것은 아마도 GHC API의 직접 바인딩을 사용하는 것보다 쉽습니다. 하스켈의 강점을 이용하여 필요한 인터페이스를 구현하고 최상층의 C++에서만 인터페이스를 사용하게됩니다.

하스켈의 FFI는 C로만 내보내집니다. 그 주위에 C++ - ish 래퍼가 필요하면 다른 레이어로 써야합니다.

(BTW, 포옹은 고대와 관리되지 않는 것입니다.) 대신 난 그냥 GHC API를 주위에 간단한 래퍼이 특정 접근 방식에 대한 Hint에 바인딩 제안 할 것 GHC API를 사용

관련 문제