Skip to content

Commit e3477a2

Browse files
committed
Implement bridges
1 parent ccef888 commit e3477a2

File tree

8 files changed

+103
-33
lines changed

8 files changed

+103
-33
lines changed

src/cpp/bin/testml-cpp-tap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ testml-run-file() {
1515
set -x
1616
/usr/bin/env c++ \
1717
-o $testml_runner \
18-
-std=c++14 \
18+
-std=c++14 -Wall -Wextra \
19+
-g \
1920
$src_bin \
2021
$lib/run/tap.cpp \
2122
$lib/runtime.cpp \

src/cpp/lib/testml/bridge.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include "bridge.hpp"
2+
3+
namespace testml {
4+
5+
using json = nlohmann::json;
6+
7+
json Bridge::call(std::string const& name, std::vector<json> const& args) {
8+
auto it = _fns.find(name);
9+
if (it == _fns.end()) {
10+
throw std::runtime_error("Bridge method not found: " + name + ".");
11+
}
12+
return it->second->call(args);
13+
}
14+
15+
}

src/cpp/lib/testml/bridge.hpp

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,40 @@
55
#include <unordered_map>
66
#include <memory>
77
#include <stdexcept>
8+
#include <string>
9+
810
#include "../../ext/nlohmann/json.hpp"
911

12+
#include "wrapper.hpp"
13+
1014
namespace testml {
11-
15+
1216
namespace details {
1317

1418
using json = nlohmann::json;
19+
using wrapper::cook;
20+
using wrapper::uncook;
1521

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

28+
// the implementation of a FnHolder, which keeps the types around
2129
template<typename Ret, typename... Arg>
2230
class FnHolderImpl : public FnHolder {
23-
using Fn = Ret(*)(Arg...);
31+
using Fn = std::function<Ret(Arg...)>;
2432
Fn _fn;
2533

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

3038
// uncook each argument to its expected type, and call the function
3139
template<std::size_t... I>
3240
Ret call_impl(std::vector<json> const& args, std::index_sequence<I...>) {
33-
return _fn(uncook<ArgType<I>>(args[I]), ...);
41+
return _fn(uncook<ArgType<I>>(args[I])...);
3442
}
3543

3644
public:
@@ -40,10 +48,11 @@ namespace testml {
4048
// check arity and call the function using our little helper, before wrapping it back to json
4149
json call(std::vector<json> const& args) override {
4250
if (args.size() != sizeof...(Arg)) {
43-
throw new std::runtime_error("Bridge method call with wrong arity, expected " + sizeof...(Arg) + ", got " + args.size() + ".");
51+
throw std::runtime_error("Bridge method call with wrong arity, expected " + std::to_string(sizeof...(Arg)) + ", got " + std::to_string(args.size()) + ".");
4452
}
4553

46-
return cook(_call(args, std::make_index_sequence<sizeof...(Arg)>{}));
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)>{}));
4756
}
4857

4958
};
@@ -54,18 +63,19 @@ namespace testml {
5463
std::unordered_map<std::string, std::unique_ptr<details::FnHolder>> _fns;
5564

5665
public:
57-
template<typename Fn>
58-
void register(std::string const& name, Fn&& fn) {
59-
_fns[name] = std:make_unique<details::FnHolderImpl>(std::move(fn));
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));
6071
}
6172

62-
json call(std::string const& name, std::vector<json> const& args) override {
63-
auto it = _fns.find(name);
64-
if (it == _fns.end()) {
65-
throw new std::runtime_error("Bridge method not found: " + name + ".");
66-
}
67-
return it->call(args);
73+
template<typename Ret, typename... Arg>
74+
void bind(std::string const& name, Ret(*fn)(Arg...)) {
75+
bind(name, std::function<Ret(Arg...)>(fn));
6876
}
77+
78+
nlohmann::json call(std::string const& name, std::vector<nlohmann::json> const& args);
6979
};
7080

7181
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,9 @@ namespace run {
2929
std::cout << "\n";
3030
}
3131

32+
void TAP::testml_done() {
33+
std::cout << "1.." << count;
34+
}
35+
3236
}
3337
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ namespace run {
99

1010
class TAP : public Runtime {
1111
using json = nlohmann::json;
12+
1213
using Runtime::Runtime;
1314

1415
protected:
1516
void testml_eq(json want, json got, std::string const& label) override;
17+
void testml_done() override;
1618

1719
private:
1820
void tap_pass(std::string const& label);

src/cpp/lib/testml/runtime.cpp

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,44 @@
11
#include <string>
2+
#include <iterator>
23
#include <iomanip> // TODO remove
34
#include <fstream>
45
#include <stdexcept>
56
#include <iostream>
7+
#include <functional>
8+
69
#include "runtime.hpp"
10+
#include "bridge.hpp"
711

8-
static void NYI(std::string const& add = "") {
12+
[[noreturn]] static void NYI(std::string const& add = "") {
913
throw std::runtime_error("Not Yet Implemented, sorry! " + add);
1014
}
1115

1216
using json = nlohmann::json;
1317

1418
namespace testml {
1519

16-
Runtime::Runtime(std::string const& filename) {
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+
26+
Runtime::Runtime(std::string const& filename, Bridge& bridge)
27+
: _bridge{bridge} {
28+
1729
std::ifstream stream(filename);
1830
stream >> _ast;
1931

2032
_data = _ast["data"];
2133
}
2234

23-
json Runtime::exec_expr(json::array_t fragment) {
35+
json Runtime::exec_expr(json fragment) {
2436
if (!fragment.is_array() || fragment.size() == 0)
25-
return fragment;
37+
return json::array_t{fragment};
38+
39+
// TODO check if the first element is a string, otherwise return it unwrapped
2640

27-
auto opcode = fragment[0];
41+
std::string opcode = fragment[0];
2842
fragment.erase(fragment.begin()); // pop first arg
2943
json val;
3044
if (opcode == "%<>") {
@@ -33,23 +47,29 @@ namespace testml {
3347
each_pick(fragment[0], fragment[1]);
3448
return {}; // no return value
3549
} else if (opcode == "==") {
36-
assert_eq(fragment[0], fragment[1], fragment.size() == 3 ? fragment[2] : "(no reason)");
50+
assert_eq(fragment[0], fragment[1], fragment.size() == 3 ? fragment[2] : "");
3751
return {}; // no return value
3852
} else if (opcode == ".") {
3953
val = exec_dot(fragment);
4054
} else if (opcode == "*") {
4155
val = get_point(fragment[0]);
42-
} else if (opcode == "add") {
56+
} else if (is_all_lowercase(opcode)) {
4357
val = call_bridge(opcode, fragment);
4458
} else if (true) {
4559
NYI(opcode);
46-
return {}; // stupid compiler.
4760
} else {
4861
throw std::runtime_error("Can't resolve TestML function");
4962
}
5063
return val.is_null() ? json::array_t{} : json::array_t{val};
5164
}
5265

66+
json Runtime::call_bridge(std::string const& name, json::array_t args) {
67+
std::vector<json> transformed;
68+
std::transform(args.begin(), args.end(), std::back_inserter(transformed),
69+
[this](json& j) { return exec_expr(j)[0] /* TODO exec() */; });
70+
return _bridge.call(name, transformed);
71+
}
72+
5373
json Runtime::get_point(std::string const& name) {
5474
return _currentBlock["point"][name];
5575
}
@@ -60,11 +80,10 @@ namespace testml {
6080
for (auto call : fragment) {
6181
// add context right after the opcode
6282
call.insert(call.begin() + 1 /* after opcode */, context.begin(), context.end());
63-
std::cout << "call: " << call << " with context " << context << std::endl;
6483
// we now have the full argument list
6584
context = exec_expr(call);
6685
}
67-
return context;
86+
return context[0];
6887
}
6988

7089
void Runtime::each_pick(json::array_t list, json::array_t expr) {
@@ -116,6 +135,7 @@ namespace testml {
116135
for (auto& statement : _ast["code"]) {
117136
exec_expr(statement);
118137
}
138+
testml_done();
119139
}
120140

121141
Runtime::~Runtime() {

src/cpp/lib/testml/runtime.hpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
#pragma once
22

33
#include <string>
4+
45
#include "../../ext/nlohmann/json.hpp"
56

7+
#include "bridge.hpp"
8+
69
namespace testml {
710

811
class Runtime {
912
using json = nlohmann::json;
1013

1114
public:
12-
Runtime(std::string const& filename);
15+
Runtime(std::string const& filename, Bridge&);
1316
virtual ~Runtime() = 0;
1417

1518
void run();
@@ -21,7 +24,7 @@ namespace testml {
2124

2225
private:
2326
// other methods
24-
json exec_expr(json::array_t fragment);
27+
json exec_expr(json fragment);
2528
json exec_dot(json::array_t fragment);
2629
json call_bridge(std::string const& name, json::array_t args);
2730
json get_point(std::string const& name);
@@ -35,8 +38,11 @@ namespace testml {
3538
protected:
3639
// those methods are to be overriden by the runtime class implementing
3740
virtual void testml_eq(json want, json got, std::string const& label) = 0;
41+
virtual void testml_done() = 0;
3842

3943
private:
44+
Bridge& _bridge;
45+
4046
json _ast;
4147
json _data;
4248
json _currentBlock; /* TODO ptr or smth */

src/cpp/src/testml-run.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
#include <iostream>
2-
#include "../lib/testml/run/tap.hpp"
2+
#include <exception>
33

4+
#include "../lib/testml/run/tap.hpp"
45

5-
int main(int argc, char* argv[]) {
6+
int main(int, char* argv[]) {
67

7-
testml::run::TAP tap{argv[1]};
8-
tap.run();
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+
});
15+
try {
16+
testml::run::TAP tap{argv[1], bridge};
17+
tap.run();
18+
} catch (std::exception& e) {
19+
std::cout << "exception thrown: " << e.what() << std::endl;
20+
}
921

1022
return 0;
1123
}

0 commit comments

Comments
 (0)