Przeglądaj źródła

feat: add reflection::Context for building anonymous contexts aggregates.
bugfix: fix test cases around casting
chore: don't instantiate CONCAT if already present

Sam Jaffe 3 lat temu
rodzic
commit
fc4bf4728b

+ 42 - 0
include/reflection/context.h

@@ -0,0 +1,42 @@
+//
+//  context.h
+//  reflection
+//
+//  Created by Sam Jaffe on 8/6/22.
+//  Copyright © 2022 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <initializer_list>
+#include <utility>
+
+#include "reflection/forward.h"
+#include "reflection/object.h"
+
+namespace reflection {
+class Context {
+public:
+  struct Arg : std::pair<std::string_view, Object> {
+    template <typename T> Arg(std::string_view id, T &&value)
+        : pair(id, Object(std::forward<T>(value), std::string(id))) {}
+    Arg(Object &&obj) : pair("", std::move(obj)) { first = second.name(); }
+  };
+  
+public:
+  explicit Context(std::initializer_list<Arg> data) : data_(data.begin(), data.end()) {}
+
+  Object get(std::string_view id) const { return data_.at(id); }
+
+  template <typename C,
+            typename = std::enable_if_t<std::is_constructible_v<
+                std::string_view, decltype(*std::begin(std::declval<C>()))>>>
+  Object get(C const & container) const {
+    return Object::get(get(*std::begin(container)), ++std::begin(container),
+                       std::end(container));
+  }
+
+private:
+  std::unordered_map<std::string_view, Object> data_;
+};
+}

+ 3 - 0
include/reflection/forward.h

@@ -16,6 +16,7 @@
 #include <unordered_map>
 
 namespace reflection {
+class Context;
 class Object;
 template <typename Obj, typename = void> class Reflection;
 template <typename T> class Proxy;
@@ -39,8 +40,10 @@ using Getter = std::function<Object(Obj const &, std::string)>;
   template TypeMap<std::function<T(Object const &)>> TypeCast<T>::get_;        \
   template TypeMap<std::function<void(Object &, T const &)>> TypeCast<T>::set_
 
+#if !defined(CONCAT)
 #define CONCAT_IMPL(A, B) A##B
 #define CONCAT(A, B) CONCAT_IMPL(A, B)
+#endif
 
 #define REFLECTION(T)                                                          \
   bool const CONCAT(reflection_, __LINE__) = ::reflection::Reflection<T>()

+ 1 - 1
include/reflection/object.h

@@ -55,7 +55,6 @@ public:
     return get(*this, std::begin(container), std::end(container));
   }
 
-private:
   template <typename It> static Object get(Object o, It it, It const end) {
     for (; it != end; ++it) {
       o = std::move(o).get(*it);
@@ -63,6 +62,7 @@ private:
     return o;
   }
 
+private:
   template <typename T> T cast() const;
 
   template <typename T> Object getter(std::string_view id) const;

+ 6 - 0
reflection.xcodeproj/project.pbxproj

@@ -9,6 +9,7 @@
 /* Begin PBXBuildFile section */
 		CD2FF9072310BE8500ABA548 /* reflection_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD5535251EEC689700108F81 /* reflection_test.cxx */; };
 		CD2FF9092310BE9200ABA548 /* GoogleMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD2FF8F32310BE6D00ABA548 /* GoogleMock.framework */; };
+		CD86DE33289FED0C00647685 /* context_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD86DE32289FED0C00647685 /* context_test.cxx */; };
 		CDA9561B28726949006ACEC2 /* reflection in Headers */ = {isa = PBXBuildFile; fileRef = CDA95611287266E5006ACEC2 /* reflection */; settings = {ATTRIBUTES = (Public, ); }; };
 		CDA9561E28731972006ACEC2 /* object_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDA9561D28731972006ACEC2 /* object_test.cxx */; };
 		CDA956D9287493FE006ACEC2 /* typecast_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CDA956D8287493FE006ACEC2 /* typecast_test.cxx */; };
@@ -50,6 +51,8 @@
 		CD2FF8FE2310BE7C00ABA548 /* reflection-test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "reflection-test.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD2FF9032310BE7C00ABA548 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		CD5535251EEC689700108F81 /* reflection_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = reflection_test.cxx; sourceTree = "<group>"; };
+		CD86DE2D289F55A100647685 /* context.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = context.h; sourceTree = "<group>"; };
+		CD86DE32289FED0C00647685 /* context_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = context_test.cxx; sourceTree = "<group>"; };
 		CDA9560A28726665006ACEC2 /* reflection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = reflection.h; sourceTree = "<group>"; };
 		CDA9560F287266C9006ACEC2 /* forward.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = forward.h; sourceTree = "<group>"; };
 		CDA95610287266CE006ACEC2 /* object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = object.h; sourceTree = "<group>"; };
@@ -134,6 +137,7 @@
 			children = (
 				CD5535251EEC689700108F81 /* reflection_test.cxx */,
 				CDA9561D28731972006ACEC2 /* object_test.cxx */,
+				CD86DE32289FED0C00647685 /* context_test.cxx */,
 				CDA956D8287493FE006ACEC2 /* typecast_test.cxx */,
 				CDA9561C28726A68006ACEC2 /* xcode_gtest_helper.h */,
 			);
@@ -153,6 +157,7 @@
 			children = (
 				CDA9560A28726665006ACEC2 /* reflection.h */,
 				CDA9560F287266C9006ACEC2 /* forward.h */,
+				CD86DE2D289F55A100647685 /* context.h */,
 				CDA9561228726723006ACEC2 /* proxy.h */,
 				CDA95610287266CE006ACEC2 /* object.h */,
 				CDA956D32873E1C7006ACEC2 /* typecast.h */,
@@ -299,6 +304,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CDA956D9287493FE006ACEC2 /* typecast_test.cxx in Sources */,
+				CD86DE33289FED0C00647685 /* context_test.cxx in Sources */,
 				CDA9561E28731972006ACEC2 /* object_test.cxx in Sources */,
 				CD2FF9072310BE8500ABA548 /* reflection_test.cxx in Sources */,
 			);

+ 72 - 0
test/context_test.cxx

@@ -0,0 +1,72 @@
+//
+//  context_test.cpp
+//  reflection-test
+//
+//  Created by Sam Jaffe on 8/7/22.
+//  Copyright © 2022 Sam Jaffe. All rights reserved.
+//
+
+#include "reflection/context.h"
+
+#include "xcode_gtest_helper.h"
+
+#include "reflection/reflection.h"
+
+namespace {
+struct Example {
+  int a;
+  int b;
+};
+}
+
+namespace reflection {
+INSTANTIATE_REFLECTION(Example);
+}
+
+REFLECTION(Example)
+    .bind("a", &Example::a)
+    .bind("b", &Example::b);
+
+TEST(Context, CanWrap) {
+  reflection::Context ctx{{"arg0", Example{.a = 0, .b = 1}}, {"arg1", Example{.a = 1, .b = 0}}};
+  
+  EXPECT_NO_THROW(ctx.get("arg0"));
+  EXPECT_NO_THROW(ctx.get("arg1"));
+  EXPECT_THROW(ctx.get("example"), std::out_of_range);
+}
+
+TEST(Context, CanBuildFromObjects) {
+  Example const arg0{.a = 0, .b = 1};
+  
+  reflection::Context ctx{reflect(arg0)};
+  EXPECT_NO_THROW(ctx.get("arg0"));
+}
+
+TEST(Context, CanAccessChildren) {
+  Example const arg0{.a = 0, .b = 1};
+  
+  reflection::Context ctx{reflect(arg0)};
+
+  reflection::Object b = ctx.get(std::vector{"arg0", "b"});
+  EXPECT_THAT(int(b), 1);
+}
+
+TEST(Context, CanProvideMutableAccess) {
+  Example arg0{.a = 0, .b = 1};
+  
+  reflection::Context ctx{reflect(arg0)};
+
+  reflection::Object b = ctx.get(std::vector{"arg0", "b"});
+  EXPECT_NO_THROW(b = 2);
+  EXPECT_THAT(arg0.b, 2);
+}
+
+TEST(Context, CanMutateWithPairCtor) {
+  Example arg0{.a = 0, .b = 1};
+  
+  reflection::Context ctx{{"arg0", arg0}};
+
+  reflection::Object b = ctx.get(std::vector{"arg0", "b"});
+  EXPECT_NO_THROW(b = 2);
+  EXPECT_THAT(arg0.b, 2);
+}

+ 9 - 2
test/object_test.cxx

@@ -72,11 +72,18 @@ TEST(ObjectTest, ThrowsWhenTryingToEditMoved) {
   EXPECT_THROW((void)static_cast<int &>(a), std::bad_cast);
 }
 
-TEST(ObjectTest, ThrowsWhenPerformingTypeCoersion) {
+TEST(ObjectTest, CanCoerceNumericTypes) {
   Example const ex{.a = 1};
 
   Object a = Object(ex).get("a");
-  EXPECT_THROW((void)double(a), std::bad_cast);
+  EXPECT_THAT(double(a), 1.0);
+}
+
+TEST(ObjectTest, ThrowsWhenPerformingIllegalTypeCoersion) {
+  Example const ex{.a = 1};
+
+  Object a = Object(ex).get("a");
+  EXPECT_THROW((void)std::string(a), std::bad_cast);
 }
 
 TEST(ObjectTest, CanFetchWithGetter) {