Writing Haskell interpreter in C++ (using ghc or hugs as library) - c++

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.

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.

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.)

Related

How do I add an OCaml library reference to a Reason code file?

Just began with Reason and OCaml today. I've started with the https://github.com/esy-ocaml/hello-reason sample. I want to make a HTTP API call so I've installed ocaml-cohttp with: esy add #opam/cohttp-lwt.
Now I want to use that library (or any that you may have as a suggestion) within the hello-reason getting started sample.
I can't find reference documentation on how to import it. I've tried:
open cohttp-lwt
Can I use OCaml libraries within in Reason code files?
Yes, the only difference is syntax. The client tutorial can be translated directly, and automatically into this:
open Lwt;
open Cohttp;
open Cohttp_lwt_unix;
let body =
Client.get(Uri.of_string("https://www.reddit.com/"))
>>= (
((resp, body)) => {
let code = resp |> Response.status |> Code.code_of_status;
Printf.printf("Response code: %d\n", code);
Printf.printf(
"Headers: %s\n",
resp |> Response.headers |> Header.to_string,
);
body
|> Cohttp_lwt.Body.to_string
>|= (
body => {
Printf.printf("Body of length: %d\n", String.length(body));
body;
}
);
}
);
let () = {
let body = Lwt_main.run(body);
print_endline("Received body\n" ++ body);
};
Edit: hello-reason uses ocaml-dune, so you also have to add cohttp-lwt-unix to the libraries stanza in the project's dune file, as shown in the client tutorial here.

Opening modules from the OCaml compiler without building a custom toplevel

I would like to have a few lines of code at the start of my OCaml input file to have toplevel remember the last expression typed all the time, under the name of it. I.e., I want to have:
# 3 + 4;;
val it : int = 7
# it;;
val it : int = 7
# let foo = 42;;
val foo : int = 42
# it + 130;;
val it : int = 137
#
But I don't want to build a custom toplevel or use camlp5 or anything fancy like that.
What I currently do (in OCaml version 4.02.3, I don't know why I have that version; but I hope the exact version doesn't matter?) is the following:
#directory "+compiler-libs";;
#load "/opt/src/ocaml-4.02.3/utils/warnings.cmo";;
#load "/opt/src/ocaml-4.02.3/parsing/location.cmo";;
let convert_phrase x =
match x with
| Parsetree.Ptop_def
[{Parsetree.pstr_desc = Parsetree.Pstr_eval (e, a)}] ->
Parsetree.Ptop_def
([{Parsetree.pstr_desc =
Parsetree.Pstr_value (Asttypes.Nonrecursive,
[{Parsetree.pvb_pat =
{Parsetree.ppat_desc =
Parsetree.Ppat_var (Location.mknoloc "it");
Parsetree.ppat_loc = Location.none;
Parsetree.ppat_attributes = []};
Parsetree.pvb_expr = e;
Parsetree.pvb_attributes = a;
Parsetree.pvb_loc = Location.none}]);
Parsetree.pstr_loc = Location.none}])
| x -> x;;
Toploop.parse_toplevel_phrase :=
let parse_toplevel_phrase = !Toploop.parse_toplevel_phrase in
fun x -> convert_phrase (parse_toplevel_phrase x);;
And that kind of works.
My question: if I just do the #directory "+compiler-libs";; thing, I can access the Toploop and Parsetree modules, but I cannot access the Location module! What is the reason for that? I find having to load .cmo files from my source directories very unattractive.
So is there a way to do what I want without having to have a source tree available?
Or, in other words: why the difference between Toploop and Location, in this respect?
In short, what you should load is not individual .cmo files but
#load "ocamlcommon.cma";;
which is in +compiler-libs directory.
The differences between Parsetree, Toploop and Location are subtle...
In OCaml, data types and their constructors become accessible only with adding its directory to the load path (by #directory "<dir>"). No object code loading (by #load) is required for them.
Parsetree is so called "mli only module": it has only data type definitions and no values are defined. Therefore everything in Parsetree is accessible only by putting it into the load path.
Location defines types and values. Its data types and constructors are accessible without loading the object file but values require the loading. In this case, the object location.cmo is loaded when you load ocamlcommon.cma which archives it.
Toploop is a tricky one. You can access the values of Toploop even without loading toploop.cmo, since Toploop is linked and already available in OCaml toplevel.

How to import Shakespearean Templates in Yesod?

I was using QuasiQuotations in Yesod, and everything worked fine. BUT my file became very large and not nice to look at. Also, my TextEditor does not highlight this syntax correctly. That is why is split my files like so:
getHomeR :: Handler Html
getHomeR = do
webSockets chatApp
defaultLayout $ do
$(luciusFile "templates/chat.lucius")
$(juliusFile "templates/chat.julius")
$(hamletFile "templates/chat.hamlet")
If this is wrong, please do tell. Doing runghc myFile.hs throws many errors like this:
chat_new.hs:115:9:
Couldn't match expected type ‘t0 -> Css’
with actual type ‘WidgetT App IO a0’
The lambda expression ‘\ _render_ajFK
-> (shakespeare-2.0.7:Text.Css.CssNoWhitespace . (foldr ($) ...))
...’
has one argument,
but its type ‘WidgetT App IO a0’ has none
In a stmt of a 'do' block:
\ _render_ajFK
...
And this.
chat_new.hs:116:9:
Couldn't match type ‘(url0 -> [(Text, Text)] -> Text)
-> Javascript’
with ‘WidgetT App IO a1’
Expected type: WidgetT App IO a1
Actual type: JavascriptUrl url0
Probable cause: ‘asJavascriptUrl’ is applied to too few arguments
...
And also one for the HTML (Hamlet).
Thus, one per template.
It seems that hamletFile and others treat templates as self-contained, while yours are referencing something from each other. You can play with order of *File calls, or use widgetFile* from Yesod.Default.Util module:
$(widgetFileNoReload def "chat")
The Reload variant is useful for development - it would make yesod devel to watch for file changes and reload them.

MirageOS - Http-fetch example

I'm trying to modify a bit the MirageOS http-fetch example (https://github.com/mirage/mirage-skeleton) that can be found inside mirage-skeleton but I'm having some problems understanding why I can't move some of the function executed inside the config.ml file to my unikernel.ml file. The original config.ml file follows (I'll copy just the interesting part) :
[...]
let client =
foreign "Unikernel.Client" ## console #-> resolver #-> conduit #-> job
let () =
add_to_ocamlfind_libraries ["mirage-http"];
add_to_opam_packages ["mirage-http"];
let sv4 = stack default_console in
let res_dns = resolver_dns sv4 in
let conduit = conduit_direct sv4 in
let job = [ client $ default_console $ res_dns $ conduit ] in
register "http-fetch" job
What I'm trying to do is move these two lines :
let res_dns = resolver_dns sv4 in
let conduit = conduit_direct sv4 in
into my unikernel.ml start method. Basically I want to pass to my module just the stack and let it create a dns resolver and a conduit. My start function follows:
let start c s =
C.log_s c (sprintf "Resolving in 1s using DNS server %s" ns) >>= fun () ->
OS.Time.sleep 1.0 >>= fun () ->
let res_dns = resolver_dns s in
let conduit = conduit_direct s in
http_fetch c res_dns conduit >>= fun (data) ->
Lwt.return(dump_to_db data);
Right now I'm getting this error at http_fetch parameters submission:
Error: This expression has type Mirage.resolver Mirage.impl
but an expression was expected of type Resolver_lwt.t
What I'm asking here is mostly a conceptual question because I'm clearly missing something. I'm not an expert in OCaml/MirageOS but this controversial behaviour of type mismatch is hard to understand considering that I'm just calling the same function from a different file.
config.ml is used to generate main.ml. You can copy the generated code from there if you want.

Passing more values into hspec tests cases with Yesod

I'm trying to enhance my current test fixtures with passing more than just foundation (of type App) into hspec test cases. In the example below I'm passing an additional Text value inside of a tuple (as IO (App, Text) ) as opposed to directly returning IO App
beforeOp :: IO (App, Text)
beforeOp = do
settings <- loadAppSettings
["config/test-settings.yml", "config/settings.yml"]
[]
ignoreEnv
foundation <- makeFoundation settings
wipeDB foundation
setUpFixtures foundation
return (foundation, "foo")
innerSpec :: SpecWith (App, Text)
innerSpec = do
describe "stuff" $ do
it "should work" $ do
post MyRouteR
statusIs 403
spec :: Spec
spec = before beforeOp innerSpec
I can't figure out how to correctly structure innerSpec in such a way that I can do my normal Yesod testing with functions such as post, statusIs etc, but also have the Text value be available to those specs for reading.
Without Yesod I can do something along the lines of the following:
innerSpec :: SpecWith (Int, Int)
innerSpec = do
describe "stuff" $ do
it "should work" $ \(x, y) -> do
x `shouldBe` y
and it will build just fine, but I can't quite get the types right as soon as Yesod comes into the mix. Advice?