Skip to content

Commit

Permalink
Hackery hackety hacks for PMFs
Browse files Browse the repository at this point in the history
  • Loading branch information
vendethiel committed Jul 23, 2018
1 parent 7c4eac7 commit b47ad45
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 63 deletions.
3 changes: 2 additions & 1 deletion src/cpp/bin/testml-cpp-tap
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ testml-run-file() {
/usr/bin/env c++ \
-o $testml_runner \
-std=c++14 -Wall -Wextra \
-g \
-I ext \
$src_bin \
$lib/run/tap.cpp \
$lib/runtime.cpp \
$lib/stdlib.cpp \
$lib/bridge.cpp \
$lib/wrapper.cpp \
$lib/utils.cpp \
$bridge_file || return

chmod +x $testml_runner
Expand Down
65 changes: 47 additions & 18 deletions src/cpp/lib/testml/bridge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
#include <stdexcept>
#include <string>

#include "../../ext/nlohmann/json.hpp"
#include "boost/callable_traits/args.hpp"
#include "boost/callable_traits/class_of.hpp"
#include "boost/callable_traits/return_type.hpp"
#include "nlohmann/json.hpp"

#include "utils.hpp"
#include "wrapper.hpp"

namespace testml {
Expand All @@ -19,60 +23,85 @@ namespace testml {
using wrapper::cook;
using wrapper::uncook;

namespace ct = boost::callable_traits;

// we need this details class so that we can have a non-templated value
// stored in the Bridge _fns map.
struct FnHolder {
virtual json call(std::vector<json> const&) = 0;
};

// the implementation of a FnHolder, which keeps the types around
template<typename Ret, typename... Arg>
template<typename BridgeT, typename Fn>
class FnHolderImpl : public FnHolder {
using Fn = std::function<Ret(Arg...)>;
Fn _fn;
BridgeT* _bridge;
static constexpr bool _is_pmf = std::is_member_function_pointer<Fn>::value;
using RawArg = ct::args_t<Fn>;
// in case of a PMF, remove the class type from the argument list
using Arg = std::conditional_t<_is_pmf, typename utils::remove_first_type<RawArg>::type, RawArg>;
using Ret = ct::return_type_t<Fn>;
static constexpr std::size_t _num_args = std::tuple_size<Arg>::value;

// type of the N-th argument that the stored function takes
template<std::size_t I>
using ArgType = typename std::tuple_element<I, std::tuple<Arg...>>::type;
using ArgType = typename std::tuple_element<I, Arg>::type;

// uncook each argument to its expected type, and call the function
// we do SFINAE in the return type, using comma+sizeof() to get a dependance on I.

// PMF case
template<std::size_t... I>
auto call_impl(std::vector<json> const& args, std::index_sequence<I...>)
-> typename std::enable_if<(sizeof...(I), _is_pmf), Ret>::type {
return (_bridge->*_fn)(uncook<ArgType<I>>(args[I])...);
}

// non-PMF case (BridgeT = nullptr_t)
template<std::size_t... I>
Ret call_impl(std::vector<json> const& args, std::index_sequence<I...>) {
auto call_impl(std::vector<json> const& args, std::index_sequence<I...>)
-> typename std::enable_if<(sizeof...(I), !_is_pmf), Ret>::type {
return _fn(uncook<ArgType<I>>(args[I])...);
}

public:
FnHolderImpl(Fn fn) : _fn{std::move(fn)} {
FnHolderImpl(BridgeT* bridge, Fn fn)
: _fn{std::move(fn)},
_bridge{bridge} {
}

// check arity and call the function using our little helper, before wrapping it back to json
json call(std::vector<json> const& args) override {
if (args.size() != sizeof...(Arg)) {
throw std::runtime_error("Bridge method call with wrong arity, expected " + std::to_string(sizeof...(Arg)) + ", got " + std::to_string(args.size()) + ".");
if (args.size() != _num_args) {
throw std::runtime_error("Bridge method call with wrong arity, expected " + std::to_string(_num_args) + ", got " + std::to_string(args.size()) + ".");
}

// generate an index_sequence so that the call_impl() can spread on each argument
return cook(call_impl(args, std::make_index_sequence<sizeof...(Arg)>{}));
return cook(call_impl(args, std::make_index_sequence<_num_args>{}));
}

};

}

class Bridge {
// store a wrapper FnHolder in the map, with FnHolderImpl to keep the correct types around and do FFI correctly
std::unordered_map<std::string, std::unique_ptr<details::FnHolder>> _fns;

public:
template<typename Ret, typename... Arg>
void bind(std::string const& name, std::function<Ret(Arg...)> fn) {
// store a wrapper FnHolder in the map, with FnHolderImpl to keep the correct types around and do FFI correctly
using HolderType = details::FnHolderImpl<Ret, Arg...>;
_fns[name] = std::make_unique<HolderType>(std::move(fn));
template<typename BridgeT, typename Fn>
auto bind(std::string const& name, BridgeT* obj, Fn fn)
-> typename std::enable_if<std::is_member_function_pointer<Fn>::value, void>::type {
static_assert(std::is_same<details::ct::class_of_t<Fn>, BridgeT>::value, "Bridge subclass must pass itself");

using HolderType = details::FnHolderImpl<BridgeT, Fn>;
_fns[name] = std::make_unique<HolderType>(obj, std::move(fn));
}

template<typename Ret, typename... Arg>
void bind(std::string const& name, Ret(*fn)(Arg...)) {
bind(name, std::function<Ret(Arg...)>(fn));
template<typename Fn>
auto bind(std::string const& name, Fn fn)
-> typename std::enable_if<!std::is_member_function_pointer<Fn>::value, void>::type {
using HolderType = details::FnHolderImpl<std::nullptr_t, Fn>;
_fns[name] = std::make_unique<HolderType>(nullptr, std::move(fn));
}

nlohmann::json call(std::string const& name, std::vector<nlohmann::json> const& args);
Expand Down
2 changes: 1 addition & 1 deletion src/cpp/lib/testml/run/tap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace run {
std::cout << "\n";
}

void TAP::testml_done() {
void TAP::testml_end() {
std::cout << "1.." << count;
}

Expand Down
2 changes: 1 addition & 1 deletion src/cpp/lib/testml/run/tap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace run {

protected:
void testml_eq(json want, json got, std::string const& label) override;
void testml_done() override;
void testml_end() override;

private:
void tap_pass(std::string const& label);
Expand Down
60 changes: 30 additions & 30 deletions src/cpp/lib/testml/runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "runtime.hpp"
#include "bridge.hpp"
#include "utils.hpp"

[[noreturn]] static void NYI(std::string const& add = "") {
throw std::runtime_error("Not Yet Implemented, sorry! " + add);
Expand All @@ -17,12 +18,6 @@ using json = nlohmann::json;

namespace testml {

namespace {
bool is_all_lowercase(std::string const& s) {
return std::all_of(s.begin(), s.end(), [](char c) { return std::islower(c); });
}
}

Runtime::Runtime(std::string const& filename, Bridge& bridge)
: _bridge{bridge} {

Expand All @@ -32,30 +27,35 @@ namespace testml {
_data = _ast["data"];
}

json Runtime::exec_expr(json fragment) {
if (!fragment.is_array() || fragment.size() == 0)
return json::array_t{fragment};
json Runtime::exec(json expr) {
json executed = exec_expr(expr);
return executed.size() > 0 ? executed[0] : static_cast<json>(nullptr);
}

// TODO check if the first element is a string, otherwise return it unwrapped
json Runtime::exec_expr(json expr) {
if (!expr.is_array() || expr.size() == 0 || !expr[0].is_string())
return json::array_t{expr};

std::string opcode = fragment[0];
fragment.erase(fragment.begin()); // pop first arg
std::string opcode = expr[0];
expr.erase(expr.begin()); // pop first arg
json val;
// TODO vtable
if (opcode == "%<>") {
// TODO bound check
// TODO std::unordered_map<std::string key, std::tuple<int arity, std::function call>>
each_pick(fragment[0], fragment[1]);
each_pick(expr[0], expr[1]);
return {}; // no return value
} else if (opcode == "==") {
assert_eq(fragment[0], fragment[1], fragment.size() == 3 ? fragment[2] : "");
assert_eq(expr[0], expr[1], expr.size() == 3 ? expr[2] : "");
return {}; // no return value
} else if (opcode == ".") {
val = exec_dot(fragment);
val = exec_dot(expr);
} else if (opcode == "*") {
val = get_point(fragment[0]);
} else if (is_all_lowercase(opcode)) {
val = call_bridge(opcode, fragment);
val = get_point(expr[0]);
} else if (utils::is_all_lowercase(opcode)) {
val = call_bridge(opcode, expr);
} else if (utils::is_all_uppercase(opcode)) {
NYI("std lib");
} else if (true) {
// TODO func
NYI(opcode);
} else {
throw std::runtime_error("Can't resolve TestML function");
Expand All @@ -66,18 +66,22 @@ namespace testml {
json Runtime::call_bridge(std::string const& name, json::array_t args) {
std::vector<json> transformed;
std::transform(args.begin(), args.end(), std::back_inserter(transformed),
[this](json& j) { return exec_expr(j)[0] /* TODO exec() */; });
[this](json& j) { return exec(j); });
return _bridge.call(name, transformed);
}

void Runtime::assert_eq(json::array_t left, json::array_t right, std::string const& label) {
testml_eq(exec_expr(left), exec_expr(right), label);
}

json Runtime::get_point(std::string const& name) {
return _currentBlock["point"][name];
}

json Runtime::exec_dot(json::array_t fragment) {
json Runtime::exec_dot(json::array_t calls) {
json context = {}; // result of last call
for (auto call : fragment) {

for (auto call : calls) {
// add context right after the opcode
call.insert(call.begin() + 1 /* after opcode */, context.begin(), context.end());
// we now have the full argument list
Expand Down Expand Up @@ -117,10 +121,6 @@ namespace testml {
}
}

void Runtime::assert_eq(json::array_t left, json::array_t right, std::string const& label) {
testml_eq(exec_expr(left), exec_expr(right), label);
}

bool Runtime::is_function(json value) {
if (!value.is_object()) {
return false;
Expand All @@ -131,11 +131,11 @@ namespace testml {
return value[0].is_string() && value[0] == "=>";
}

void Runtime::run() {
void Runtime::test() {
for (auto& statement : _ast["code"]) {
exec_expr(statement);
}
testml_done();
testml_end();
}

Runtime::~Runtime() {
Expand Down
11 changes: 7 additions & 4 deletions src/cpp/lib/testml/runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace testml {
Runtime(std::string const& filename, Bridge&);
virtual ~Runtime() = 0;

void run();
void test();

private:
// toplevel methods
Expand All @@ -24,21 +24,24 @@ namespace testml {

private:
// other methods
json exec_expr(json fragment);
json exec_dot(json::array_t fragment);
json exec(json expr);
json exec_expr(json expr);
json call_bridge(std::string const& name, json::array_t args);
json get_point(std::string const& name);

private:
void assert_eq(json::array_t got, json::array_t want, std::string const& label);

private:
json exec_dot(json::array_t calls);

private:
bool is_function(json value);

protected:
// those methods are to be overriden by the runtime class implementing
virtual void testml_eq(json want, json got, std::string const& label) = 0;
virtual void testml_done() = 0;
virtual void testml_end() = 0;

private:
Bridge& _bridge;
Expand Down
19 changes: 19 additions & 0 deletions src/cpp/lib/testml/utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "utils.hpp"

namespace testml {
namespace utils {

bool is_all_lowercase(std::string const& s) {
return std::all_of(s.begin(), s.end(),
[](char c) { return std::islower(c); }
);
}

bool is_all_uppercase(std::string const& s) {
return std::all_of(s.begin(), s.end(),
[](char c) { return std::isupper(c); }
);
}

}
}
29 changes: 29 additions & 0 deletions src/cpp/lib/testml/utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <string>

namespace testml {
namespace utils {

bool is_all_lowercase(std::string const& s);
bool is_all_uppercase(std::string const& s);

template<typename T>
struct remove_first_type
{
};

template<typename T, typename... Ts>
struct remove_first_type<std::tuple<T, Ts...>>
{
typedef std::tuple<Ts...> type;
};

template<>
struct remove_first_type<std::tuple<>>
{
typedef std::tuple<> type;
};

}
}
11 changes: 3 additions & 8 deletions src/cpp/src/testml-run.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@
#include <exception>

#include "../lib/testml/run/tap.hpp"
#include "../test/testml-bridge.hpp"

int main(int, char* argv[]) {

testml::Bridge bridge;
bridge.bind("add", +[](int a, int b) {
return a + b;
});
bridge.bind("sub", +[](int a, int b) {
return a - b;
});
TestMLBridge bridge;
try {
testml::run::TAP tap{argv[1], bridge};
tap.run();
tap.test();
} catch (std::exception& e) {
std::cout << "exception thrown: " << e.what() << std::endl;
}
Expand Down
13 changes: 13 additions & 0 deletions src/cpp/test/testml-bridge.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "testml-bridge.hpp"

TestMLBridge::TestMLBridge() {
bind("add", this, &TestMLBridge::add);
bind("sub", [](int a, int b) {
return a - b;
});
}

int TestMLBridge::add(int a, int b) {
return a + b;
}

Empty file removed src/cpp/test/testml-bridge.h
Empty file.
Loading

0 comments on commit b47ad45

Please sign in to comment.