Skip to content

Commit b47ad45

Browse files
committed
Hackery hackety hacks for PMFs
1 parent 7c4eac7 commit b47ad45

12 files changed

+163
-63
lines changed

src/cpp/bin/testml-cpp-tap

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ testml-run-file() {
1616
/usr/bin/env c++ \
1717
-o $testml_runner \
1818
-std=c++14 -Wall -Wextra \
19-
-g \
19+
-I ext \
2020
$src_bin \
2121
$lib/run/tap.cpp \
2222
$lib/runtime.cpp \
2323
$lib/stdlib.cpp \
2424
$lib/bridge.cpp \
2525
$lib/wrapper.cpp \
26+
$lib/utils.cpp \
2627
$bridge_file || return
2728

2829
chmod +x $testml_runner

src/cpp/lib/testml/bridge.hpp

+47-18
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
#include <stdexcept>
88
#include <string>
99

10-
#include "../../ext/nlohmann/json.hpp"
10+
#include "boost/callable_traits/args.hpp"
11+
#include "boost/callable_traits/class_of.hpp"
12+
#include "boost/callable_traits/return_type.hpp"
13+
#include "nlohmann/json.hpp"
1114

15+
#include "utils.hpp"
1216
#include "wrapper.hpp"
1317

1418
namespace testml {
@@ -19,60 +23,85 @@ namespace testml {
1923
using wrapper::cook;
2024
using wrapper::uncook;
2125

26+
namespace ct = boost::callable_traits;
27+
2228
// we need this details class so that we can have a non-templated value
2329
// stored in the Bridge _fns map.
2430
struct FnHolder {
2531
virtual json call(std::vector<json> const&) = 0;
2632
};
2733

2834
// the implementation of a FnHolder, which keeps the types around
29-
template<typename Ret, typename... Arg>
35+
template<typename BridgeT, typename Fn>
3036
class FnHolderImpl : public FnHolder {
31-
using Fn = std::function<Ret(Arg...)>;
3237
Fn _fn;
38+
BridgeT* _bridge;
39+
static constexpr bool _is_pmf = std::is_member_function_pointer<Fn>::value;
40+
using RawArg = ct::args_t<Fn>;
41+
// in case of a PMF, remove the class type from the argument list
42+
using Arg = std::conditional_t<_is_pmf, typename utils::remove_first_type<RawArg>::type, RawArg>;
43+
using Ret = ct::return_type_t<Fn>;
44+
static constexpr std::size_t _num_args = std::tuple_size<Arg>::value;
3345

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

3850
// uncook each argument to its expected type, and call the function
51+
// we do SFINAE in the return type, using comma+sizeof() to get a dependance on I.
52+
53+
// PMF case
54+
template<std::size_t... I>
55+
auto call_impl(std::vector<json> const& args, std::index_sequence<I...>)
56+
-> typename std::enable_if<(sizeof...(I), _is_pmf), Ret>::type {
57+
return (_bridge->*_fn)(uncook<ArgType<I>>(args[I])...);
58+
}
59+
60+
// non-PMF case (BridgeT = nullptr_t)
3961
template<std::size_t... I>
40-
Ret call_impl(std::vector<json> const& args, std::index_sequence<I...>) {
62+
auto call_impl(std::vector<json> const& args, std::index_sequence<I...>)
63+
-> typename std::enable_if<(sizeof...(I), !_is_pmf), Ret>::type {
4164
return _fn(uncook<ArgType<I>>(args[I])...);
4265
}
4366

4467
public:
45-
FnHolderImpl(Fn fn) : _fn{std::move(fn)} {
68+
FnHolderImpl(BridgeT* bridge, Fn fn)
69+
: _fn{std::move(fn)},
70+
_bridge{bridge} {
4671
}
4772

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

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

5882
};
5983

6084
}
6185

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

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

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

78107
nlohmann::json call(std::string const& name, std::vector<nlohmann::json> const& args);

src/cpp/lib/testml/run/tap.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace run {
2929
std::cout << "\n";
3030
}
3131

32-
void TAP::testml_done() {
32+
void TAP::testml_end() {
3333
std::cout << "1.." << count;
3434
}
3535

src/cpp/lib/testml/run/tap.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace run {
1414

1515
protected:
1616
void testml_eq(json want, json got, std::string const& label) override;
17-
void testml_done() override;
17+
void testml_end() override;
1818

1919
private:
2020
void tap_pass(std::string const& label);

src/cpp/lib/testml/runtime.cpp

+30-30
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "runtime.hpp"
1010
#include "bridge.hpp"
11+
#include "utils.hpp"
1112

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

1819
namespace testml {
1920

20-
namespace {
21-
bool is_all_lowercase(std::string const& s) {
22-
return std::all_of(s.begin(), s.end(), [](char c) { return std::islower(c); });
23-
}
24-
}
25-
2621
Runtime::Runtime(std::string const& filename, Bridge& bridge)
2722
: _bridge{bridge} {
2823

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

35-
json Runtime::exec_expr(json fragment) {
36-
if (!fragment.is_array() || fragment.size() == 0)
37-
return json::array_t{fragment};
30+
json Runtime::exec(json expr) {
31+
json executed = exec_expr(expr);
32+
return executed.size() > 0 ? executed[0] : static_cast<json>(nullptr);
33+
}
3834

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

41-
std::string opcode = fragment[0];
42-
fragment.erase(fragment.begin()); // pop first arg
39+
std::string opcode = expr[0];
40+
expr.erase(expr.begin()); // pop first arg
4341
json val;
42+
// TODO vtable
4443
if (opcode == "%<>") {
45-
// TODO bound check
46-
// TODO std::unordered_map<std::string key, std::tuple<int arity, std::function call>>
47-
each_pick(fragment[0], fragment[1]);
44+
each_pick(expr[0], expr[1]);
4845
return {}; // no return value
4946
} else if (opcode == "==") {
50-
assert_eq(fragment[0], fragment[1], fragment.size() == 3 ? fragment[2] : "");
47+
assert_eq(expr[0], expr[1], expr.size() == 3 ? expr[2] : "");
5148
return {}; // no return value
5249
} else if (opcode == ".") {
53-
val = exec_dot(fragment);
50+
val = exec_dot(expr);
5451
} else if (opcode == "*") {
55-
val = get_point(fragment[0]);
56-
} else if (is_all_lowercase(opcode)) {
57-
val = call_bridge(opcode, fragment);
52+
val = get_point(expr[0]);
53+
} else if (utils::is_all_lowercase(opcode)) {
54+
val = call_bridge(opcode, expr);
55+
} else if (utils::is_all_uppercase(opcode)) {
56+
NYI("std lib");
5857
} else if (true) {
58+
// TODO func
5959
NYI(opcode);
6060
} else {
6161
throw std::runtime_error("Can't resolve TestML function");
@@ -66,18 +66,22 @@ namespace testml {
6666
json Runtime::call_bridge(std::string const& name, json::array_t args) {
6767
std::vector<json> transformed;
6868
std::transform(args.begin(), args.end(), std::back_inserter(transformed),
69-
[this](json& j) { return exec_expr(j)[0] /* TODO exec() */; });
69+
[this](json& j) { return exec(j); });
7070
return _bridge.call(name, transformed);
7171
}
7272

73+
void Runtime::assert_eq(json::array_t left, json::array_t right, std::string const& label) {
74+
testml_eq(exec_expr(left), exec_expr(right), label);
75+
}
76+
7377
json Runtime::get_point(std::string const& name) {
7478
return _currentBlock["point"][name];
7579
}
7680

77-
json Runtime::exec_dot(json::array_t fragment) {
81+
json Runtime::exec_dot(json::array_t calls) {
7882
json context = {}; // result of last call
79-
80-
for (auto call : fragment) {
83+
84+
for (auto call : calls) {
8185
// add context right after the opcode
8286
call.insert(call.begin() + 1 /* after opcode */, context.begin(), context.end());
8387
// we now have the full argument list
@@ -117,10 +121,6 @@ namespace testml {
117121
}
118122
}
119123

120-
void Runtime::assert_eq(json::array_t left, json::array_t right, std::string const& label) {
121-
testml_eq(exec_expr(left), exec_expr(right), label);
122-
}
123-
124124
bool Runtime::is_function(json value) {
125125
if (!value.is_object()) {
126126
return false;
@@ -131,11 +131,11 @@ namespace testml {
131131
return value[0].is_string() && value[0] == "=>";
132132
}
133133

134-
void Runtime::run() {
134+
void Runtime::test() {
135135
for (auto& statement : _ast["code"]) {
136136
exec_expr(statement);
137137
}
138-
testml_done();
138+
testml_end();
139139
}
140140

141141
Runtime::~Runtime() {

src/cpp/lib/testml/runtime.hpp

+7-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace testml {
1515
Runtime(std::string const& filename, Bridge&);
1616
virtual ~Runtime() = 0;
1717

18-
void run();
18+
void test();
1919

2020
private:
2121
// toplevel methods
@@ -24,21 +24,24 @@ namespace testml {
2424

2525
private:
2626
// other methods
27-
json exec_expr(json fragment);
28-
json exec_dot(json::array_t fragment);
27+
json exec(json expr);
28+
json exec_expr(json expr);
2929
json call_bridge(std::string const& name, json::array_t args);
3030
json get_point(std::string const& name);
3131

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

35+
private:
36+
json exec_dot(json::array_t calls);
37+
3538
private:
3639
bool is_function(json value);
3740

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

4346
private:
4447
Bridge& _bridge;

src/cpp/lib/testml/utils.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "utils.hpp"
2+
3+
namespace testml {
4+
namespace utils {
5+
6+
bool is_all_lowercase(std::string const& s) {
7+
return std::all_of(s.begin(), s.end(),
8+
[](char c) { return std::islower(c); }
9+
);
10+
}
11+
12+
bool is_all_uppercase(std::string const& s) {
13+
return std::all_of(s.begin(), s.end(),
14+
[](char c) { return std::isupper(c); }
15+
);
16+
}
17+
18+
}
19+
}

src/cpp/lib/testml/utils.hpp

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace testml {
6+
namespace utils {
7+
8+
bool is_all_lowercase(std::string const& s);
9+
bool is_all_uppercase(std::string const& s);
10+
11+
template<typename T>
12+
struct remove_first_type
13+
{
14+
};
15+
16+
template<typename T, typename... Ts>
17+
struct remove_first_type<std::tuple<T, Ts...>>
18+
{
19+
typedef std::tuple<Ts...> type;
20+
};
21+
22+
template<>
23+
struct remove_first_type<std::tuple<>>
24+
{
25+
typedef std::tuple<> type;
26+
};
27+
28+
}
29+
}

src/cpp/src/testml-run.cpp

+3-8
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,14 @@
22
#include <exception>
33

44
#include "../lib/testml/run/tap.hpp"
5+
#include "../test/testml-bridge.hpp"
56

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

8-
testml::Bridge bridge;
9-
bridge.bind("add", +[](int a, int b) {
10-
return a + b;
11-
});
12-
bridge.bind("sub", +[](int a, int b) {
13-
return a - b;
14-
});
9+
TestMLBridge bridge;
1510
try {
1611
testml::run::TAP tap{argv[1], bridge};
17-
tap.run();
12+
tap.test();
1813
} catch (std::exception& e) {
1914
std::cout << "exception thrown: " << e.what() << std::endl;
2015
}

src/cpp/test/testml-bridge.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include "testml-bridge.hpp"
2+
3+
TestMLBridge::TestMLBridge() {
4+
bind("add", this, &TestMLBridge::add);
5+
bind("sub", [](int a, int b) {
6+
return a - b;
7+
});
8+
}
9+
10+
int TestMLBridge::add(int a, int b) {
11+
return a + b;
12+
}
13+

src/cpp/test/testml-bridge.h

Whitespace-only changes.

0 commit comments

Comments
 (0)