|
#include <array> |
|
// #include <compare> |
|
#include <map> |
|
#include <optional> |
|
#include <set> |
|
#include <vector> |
|
|
|
#include <emscripten/bind.h> |
|
|
|
using namespace emscripten; |
|
|
|
// SPOG |
|
struct IdentifierQuad { |
|
uint32_t subject; |
|
uint32_t predicate; |
|
uint32_t object; |
|
uint32_t graph; |
|
|
|
//[[nodiscard]] constexpr auto operator<=>(const IdentifierQuad & other) const noexcept; |
|
|
|
[[nodiscard]] constexpr bool operator<(const IdentifierQuad & other) const noexcept { |
|
if (subject < other.subject ) return true; if (subject > other.subject ) return false; |
|
if (predicate < other.predicate) return true; if (predicate > other.predicate) return false; |
|
if (object < other.object ) return true; if (object > other.object ) return false; |
|
if (graph < other.graph ) return true; if (graph > other.graph ) return false; |
|
|
|
return false; |
|
} |
|
|
|
[[nodiscard]] constexpr std::array<uint32_t, 4> to_array() const noexcept { |
|
return std::array<uint32_t, 4> { subject, predicate, object, graph }; |
|
} |
|
|
|
[[nodiscard]] static IdentifierQuad from_identifier_list(const std::vector<uint32_t> & id_list, size_t i) { |
|
return IdentifierQuad{ id_list[i], id_list[i + 1], id_list[i + 2], id_list[i + 3] }; |
|
} |
|
}; |
|
|
|
// S? P? O? G? |
|
struct QuadMatcher { |
|
std::optional<uint32_t> subject; |
|
std::optional<uint32_t> predicate; |
|
std::optional<uint32_t> object; |
|
std::optional<uint32_t> graph; |
|
|
|
constexpr QuadMatcher(std::optional<uint32_t> s, std::optional<uint32_t> p, std::optional<uint32_t> o, std::optional<uint32_t> g) |
|
: subject(s), predicate(p), object(o), graph(g) {} |
|
|
|
// uint32_t only interface -> 0 == ? |
|
[[nodiscard]] constexpr static std::optional<uint32_t> map(uint32_t js_parameter) { |
|
if (js_parameter == 0) return std::nullopt; |
|
return js_parameter; |
|
} |
|
|
|
constexpr QuadMatcher(uint32_t s, uint32_t p, uint32_t o, uint32_t g) |
|
: subject(map(s)), predicate(map(p)), object(map(o)), graph(map(g)) {} |
|
|
|
|
|
// Returns true if the passed quad complies with this S? P? O? G? pattern |
|
[[nodiscard]] constexpr bool operator()(const IdentifierQuad & quad) const noexcept { |
|
if (subject && *subject != quad.subject ) return false; |
|
if (predicate && *predicate != quad.predicate) return false; |
|
if (object && *object != quad.object ) return false; |
|
if (graph && *graph != quad.graph ) return false; |
|
|
|
return true; |
|
} |
|
}; |
|
|
|
// A forest of trees... with only one tree which is a SPOG tree. |
|
class QuadForest { |
|
std::set<IdentifierQuad> spog_tree; |
|
|
|
public: |
|
QuadForest() = default; |
|
|
|
// std::vector as a parameter is supported |
|
static QuadForest from(const std::vector<uint32_t> & identifier_list) { |
|
QuadForest retval; |
|
for (size_t i = 0 ; i != identifier_list.size() ; i += 4) { |
|
retval.spog_tree.insert(IdentifierQuad::from_identifier_list(identifier_list, i)); |
|
} |
|
return retval; |
|
} |
|
|
|
void insert(uint32_t s, uint32_t p, uint32_t o, uint32_t g) { |
|
spog_tree.insert(IdentifierQuad{ s, p , o, g }); |
|
} |
|
|
|
// match : returns the list of quads complying with the pattern in the format |
|
// [s1, p1, o1, g1, s2, o2, o2, g2, ... sn, pn, on, gn] |
|
|
|
// optional is impossible |
|
// std::vector<uint32_t> match(std::optional<uint32_t> s, std::optional<uint32_t> p, std::optional<uint32_t> o, std::optional<uint32_t> g) { |
|
[[nodiscard]] std::vector<uint32_t> match(uint32_t s, uint32_t p, uint32_t o, uint32_t g) const { |
|
std::vector<uint32_t> retval; |
|
|
|
const QuadMatcher quad_matcher(s, p, o, g); |
|
|
|
for (const auto & identifier_quad : spog_tree) { |
|
if (quad_matcher(identifier_quad)) { |
|
for (auto id : identifier_quad.to_array()) { |
|
retval.push_back(id); |
|
} |
|
} |
|
} |
|
|
|
return retval; |
|
} |
|
|
|
// optional is kind of possible with emscripten::val, but some extra mapping could be offered |
|
[[nodiscard]] std::vector<uint32_t> match_emsval(emscripten::val s, emscripten::val p, emscripten::val o, emscripten::val g) const { |
|
std::vector<uint32_t> retval; |
|
|
|
constexpr auto to_optional = [](const emscripten::val & v) -> std::optional<uint32_t> { |
|
if (v.isNull() || v.isUndefined()) { |
|
return std::nullopt; |
|
} else if (v.isNumber()) { |
|
return v.as<uint32_t>(); |
|
} else { |
|
// ??? |
|
return std::nullopt; |
|
} |
|
}; |
|
|
|
const QuadMatcher quad_matcher( |
|
to_optional(s), |
|
to_optional(p), |
|
to_optional(o), |
|
to_optional(g) |
|
); |
|
|
|
for (const auto & identifier_quad : spog_tree) { |
|
if (quad_matcher(identifier_quad)) { |
|
for (auto id : identifier_quad.to_array()) { |
|
retval.push_back(id); |
|
} |
|
} |
|
} |
|
|
|
return retval; |
|
} |
|
}; |
|
|
|
// Binding code |
|
EMSCRIPTEN_BINDINGS(cpp_quad_forest) { |
|
class_<QuadForest>("CppQuadForest") |
|
.constructor<>() |
|
.function("insert", &QuadForest::insert) |
|
.function("matchNumbers", &QuadForest::match) |
|
.function("match", &QuadForest::match_emsval) |
|
.class_function("fromIdentifierList", &QuadForest::from) |
|
; |
|
|
|
register_vector<uint32_t>("vector<uint32_t>"); |
|
} |
|
|
|
// It could have been attributes (we have these in C++, see nodiscard attribute which is heavily used in this code) |
|
// instead of a macro |
|
// But it is not intrusive so it is nice (we can export classes we don't own) |
|
// + typically a class is declared several times, while I guess the binding only needs to happen once? |