A Simple REPL¶
In the Embedding Hobbes chapter we built a simple Hobbes interpreter with two-way function bindings.
This allowed us to call C++ functions from within the Hobbes environment, and also, via the function pointer, to execute Hobbes code in the containing C++ code.
Here’s the full code listing. Afterwards we’ll dig into all the parts one by one:
#include <iostream>
#include <stdexcept>
#include <hobbes/hobbes.H>
int addTwo(int i){ return 2 + i; }
DEFINE_STRUCT(Writer,
(size_t, age),
(const hobbes::array<char>*, name)
);
typedef std::pair<int, const hobbes::array<char>*> Writer;
Writer* getWriter(){
return hobbes::make<Writer>(34, hobbes::makeString("Sam"));
}
hobbes::array<Writer>* getWriters(){
auto writers = hobbes::makeArray<Writer>(4);
writers->data[0].age = 21;
writers->data[0].name = hobbes::makeString("John");
writers->data[1].age = 22;
writers->data[1].name = hobbes::makeString("Paul");
writers->data[2].age = 22;
writers->data[2].name = hobbes::makeString("George");
writers->data[3].age = 42;
writers->data[3].name = hobbes::makeString("Sam");
return writers;
}
typedef hobbes::variant<int, const hobbes::array<char>*> CountOrMessage;
CountOrMessage* classify(int i){
if(i < 22){
return hobbes::make<CountOrMessage>(i);
}else{
return hobbes::make<CountOrMessage>(hobbes::makeString("haha!"));
}
}
int binaryIntFn(int (*pf)(int, int), int x){
return pf(x, x);
}
int main() {
hobbes::cc c;
c.bind("addTwo", &addTwo);
c.bind("getWriters", &getWriters);
c.bind("getWriter", &getWriter);
c.bind("classify", &classify);
c.bind("binaryIntFn", &binaryIntFn);
while (std::cin) {
std::cout << "> " << std::flush;
std::string line;
std::getline(std::cin, line);
if (line == ":q") break;
try {
c.compileFn<void()>("print(" + line + ")")();
} catch (std::exception& ex) {
std::cout << "*** " << ex.what();
}
std::cout << std::endl;
hobbes::resetMemoryPool();
}
return 0;
}
Sections¶
Prelude¶
#include <iostream>
#include <stdexcept>
#include <hobbes/hobbes.H>
First comes the include statements for the c++ pre-processor. The Hobbes header file is available in the GitHub repo under the include directory.
A simple function¶
int addTwo(int i){ return 2 + i; }
This simple c++ funciton takes an int and adds 2 to it, returning the result. It’s used later, as an example of binding a c++ funciton in to the hobbes environment.
Binding custom datatypes¶
DEFINE_STRUCT(Writer,
(size_t, age),
(const hobbes::array<char>*, name)
);
typedef std::pair<int, const hobbes::array<char>*> Writer;
Writer* getWriter(){
return hobbes::make<Writer>(34, hobbes::makeString("Sam"));
}
Here, we use the DEFINE_STRUCT macro to make Hobbes aware of a custom datatype, and use the make* functions for our custom type and for std::string. This makes Hobbes responsible for allocating space for this data, and allows it to deallocate the memory when it’s finished.
Arrays¶
hobbes::array<Writer>* getWriters(){
auto writers = hobbes::makeArray<Writer>(4);
writers->data[0].age = 21;
writers->data[0].name = hobbes::makeString("John");
writers->data[1].age = 22;
writers->data[1].name = hobbes::makeString("Paul");
writers->data[2].age = 22;
writers->data[2].name = hobbes::makeString("George");
writers->data[3].age = 42;
writers->data[3].name = hobbes::makeString("Sam");
return writers;
}
We’re extending the example to include makeArray, showing how Hobbes can interact with collections.
Variants¶
typedef hobbes::variant<int, const hobbes::array<char>*> CountOrMessage;
Firstly, we use a c++ typedef* to give a nice name to a Hobbes variant
CountOrMessage* classify(int i){
if(i < 22){
return hobbes::make<CountOrMessage>(i);
}else{
return hobbes::make<CountOrMessage>(hobbes::makeString("haha!"));
}
}
Hobbes creates factory methods for each subclass in the variant, and so, depending on some external factor, we’re able to initialise either a count or a message.
Function pointers¶
int binaryIntFn(int (*pf)(int, int), int x){
return pf(x, x);
}
The basic mechanism by which work is abstracted, and how we can externalise behaviour from Hobbes - allowing us to interact with Hobbes functionality from outside the environment. In this case we expect binaryIntFn to be called with two items - firstly, a function which takes two ints and returns an int, and secondly an int.
The result of the application of the function with the second argument twice is then returned to Hobbes as an int.
main()¶
Finally, the main method brings it all together:
int main() {
hobbes::cc c;
c.bind("addTwo", &addTwo);
c.bind("getWriters", &getWriters);
c.bind("getWriter", &getWriter);
c.bind("classify", &classify);
c.bind("binaryIntFn", &binaryIntFn);
while (std::cin) {
std::cout << "> " << std::flush;
std::string line;
std::getline(std::cin, line);
if (line == ":q") break;
try {
c.compileFn<void()>("print(" + line + ")")();
} catch (std::exception& ex) {
std::cout << "*** " << ex.what();
}
std::cout << std::endl;
hobbes::resetMemoryPool();
}
return 0;
}
- We initialise an instance of
hobbes::ccon the stack; - We bind five functions to it by their address;
- Then, in a loop, we print a prompt, accept a string from STDIN, and attempt to compile and execute the input string;
- Any failures are caught and reported;
- Finally, any unreferenced data left after the REPL loop is collected to avoid memory leaks.