2011-12-27 22 views
8

Tôi đang viết một ứng dụng C++ cần giải thích và đánh giá mã haskell. Mã này không được biết lúc biên dịch nhưng do người dùng đưa ra. Có cách nào để sử dụng trình biên dịch/trình thông dịch haskell (như GHCi hoặc hugs) làm thư viện không?Viết trình thông dịch Haskell bằng C++ (sử dụng ghc hoặc ôm làm thư viện)

  • Tôi tìm thấy FFI nhưng điều này dường như chỉ hoạt động đối với mã haskell được biết đến lúc biên dịch.
  • Tôi đã tìm thấy API GHC và gợi ý, nhưng chúng dường như chỉ hoạt động khi tôi muốn giải thích mã haskell từ ngoài haskell.
+1

Tôi nghĩ bạn đã tìm thấy hai phần quan trọng, nhưng bạn cần phải kết hợp chúng! Viết một số Haskell biên dịch thời gian để thiết lập và sử dụng API GHC và gọi mã đó từ C++ của bạn thông qua FFI. Nhưng tôi chưa bao giờ thực sự làm điều đó, vì vậy tôi không tự tin làm cho câu trả lời này thành một câu trả lời thực sự. –

+0

Tôi đã hy vọng có một giải pháp dễ dàng hơn ... – Heinzi

+0

Giải pháp câm: thay vì sử dụng nó như một thư viện, bạn có thể sử dụng GHC (i) làm mã gốc thông qua các cuộc gọi hệ thống. –

Trả lời

7

Thay vì sử dụng api GHC tôi sẽ đề nghị ràng buộc để Hint cho cách tiếp cận đặc biệt này, mà chỉ là một wrapper đơn giản xung quanh api GHC. Lý do tôi muốn giới thiệu điều này là vì api GHC có một chút đường cong học tập dốc.

Nhưng dù sao, như tôi đã nói Trong bình luận của tôi, tùy thuộc vào mức độ sâu bạn muốn điều này đi nó sẽ đòi hỏi đáng ngạc nhiên vài cuộc gọi FFI. Dưới đây tôi đưa ra một ví dụ về cách chạy các biểu thức từ một tệp đã tải và trả lại kết quả (chỉ khi có một cá thể hiển thị). Đây chỉ là vấn đề cơ bản, trả lại kết quả dưới dạng cấu trúc cũng có thể là.

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 

Kể từ khi chúng ta phải thoát khỏi đất Haskell chúng ta phải có một số cách để đề cập đến bối cảnh, Chúng tôi có thể làm điều này với một StablePtr và tôi chỉ quấn nó trong một IORef để làm cho nó có thể thay đổi trong trường hợp bạn muốn thay đổi mọi thứ trong tương lai. Lưu ý rằng GHC API không hỗ trợ loại kiểm tra bộ đệm trong bộ nhớ, vì vậy bạn phải lưu mã bạn muốn giải thích vào một tệp tạm thời trước khi tải nó.

-- @@ Chú thích dành cho công cụ Hs2lib của tôi, đừng bận tâm nếu bạn không sử dụng.

tập tin thử nghiệm của tôi là

module Test where 

import Control.Monad 
import Control.Monad.Instances 

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

và chúng ta có thể kiểm tra điều này bằng cách sử dụng kiểm tra đơn giản

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

Vì vậy, yeah, nó hoạt động trong Haskell, bây giờ để làm cho nó làm việc bên ngoài của Haskell.

Chỉ cần thêm vào đầu tệp một vài hướng dẫn cho Hs2lib về cách soái ModuleName vì loại đó được xác định trong tệp mà tệp không có nguồn.

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

hoặc

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

nếu trên một kiến ​​trúc 64bit,

và Just gọi Hs2lib

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

Và bạn sẽ kết thúc với những người khác, Bao gồm tập tin với

#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 

Tôi chưa thử nghiệm mặt C++, nhưng không có lý do nào nó không hoạt động. Đây là một ví dụ rất phù hợp, nếu bạn biên dịch nó thành một lib động, bạn có thể muốn chuyển hướng stdout, stderr và stdin.

+0

Cảm ơn bạn rất nhiều vì các ví dụ của bạn. Thật không may tôi cần điều này trên Linux. Tôi có thể làm gì để chạy ví dụ của bạn ở đó? – Heinzi

+0

Mã Haskell trong ví dụ này là nền tảng độc lập, vì vậy điều tương tự sẽ hoạt động trên linux. Mã haskell hs2lib tạo * nên * hoạt động tốt trên linux. Nó chỉ là vì nhiều lý do tôi đã không bao giờ nhận được ghc để biên dịch libs năng động trên Linux. Xem http://stackoverflow.com/questions/7652799/compiling-ghc-with-fpic-support. Những gì nó có thể giúp bạn với là tạo ra các mã Marshalling và FFI cho bạn, chỉ cần vượt qua nó cờ -T để giữ các tập tin tạm thời và bạn chỉ có thể nhận được nó từ đó. Từ đó, bạn có thể biên dịch nó thành một lib tĩnh/chia sẻ. – Phyx

+1

Hoặc bạn có thể sử dụng một số dạng IPC và chỉ có một máy chủ "haskell" và máy khách C++ giao tiếp thông qua các ổ cắm hoặc đường ống. và chỉ cần tuần tự hóa thông tin. Đó sẽ là một cách khác để tiếp cận nó. Nhưng một lần nữa, mã Haskell ở trên là nền tảng độc lập. – Phyx

4

Vì GHC được viết bằng Haskell nên API của nó chỉ có sẵn từ Haskell. Viết các giao diện bạn cần trong Haskell và liên kết chúng với C với FFI, như Daniel Wagner đã gợi ý, sẽ là con đường đơn giản nhất. Điều này có lẽ dễ dàng hơn việc sử dụng một ràng buộc trực tiếp của GHC API cho C sẽ là; bạn có thể sử dụng các điểm mạnh của Haskell để xây dựng các giao diện bạn cần và chỉ giao diện với chúng trong C++ ở lớp trên cùng.

Lưu ý rằng FFI của Haskell sẽ chỉ xuất sang C; nếu bạn muốn có một wrapper C++ - ish xung quanh nó, bạn sẽ phải viết nó như là một lớp khác.

(BTW, Hugs là cổ và bỏ dở.)