├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── TODO.md ├── common ├── any.cpp ├── cast.cpp ├── regex.cpp └── registry.cpp ├── images ├── 0.png └── icon.png ├── include ├── archimedes.hpp └── archimedes │ ├── access_specifier.hpp │ ├── any.hpp │ ├── basic.hpp │ ├── cast.hpp │ ├── ds.hpp │ ├── errors.hpp │ ├── invoke.hpp │ ├── regex.hpp │ ├── registry.hpp │ ├── serialize.hpp │ ├── type_id.hpp │ ├── type_info.hpp │ ├── type_kind.hpp │ └── types.hpp ├── plugin ├── compile.hpp ├── emit.cpp ├── emit.hpp ├── ext │ ├── LICENSE.txt │ ├── ast.cpp │ └── ast.hpp ├── implicit_function.hpp ├── invoker.cpp ├── invoker.hpp ├── plugin.cpp ├── plugin.hpp └── util.hpp ├── runtime └── archimedes.cpp └── test ├── abstract.test.cpp ├── abstract.test.hpp ├── access.test.cpp ├── access.test.hpp ├── alias.test.cpp ├── alias.test.hpp ├── align_size.test.cpp ├── align_size.test.hpp ├── annotations.test.cpp ├── annotations.test.hpp ├── anonymous.test.cpp ├── anonymous.test.hpp ├── any_add_ptr.test.cpp ├── any_add_ptr.test.hpp ├── array.test.cpp ├── array.test.hpp ├── basic.test.cpp ├── basic.test.hpp ├── builtin.test.cpp ├── builtin.test.hpp ├── constexpr.test.cpp ├── constexpr.test.hpp ├── converters.test.cpp ├── converters.test.hpp ├── ctor.test.cpp ├── ctor.test.hpp ├── decl_in_macro.test.cpp ├── decl_in_macro.test.hpp ├── defaults.test.cpp ├── defaults.test.hpp ├── empty.test.cpp ├── empty.test.hpp ├── enum.test.cpp ├── enum.test.hpp ├── example.test.cpp ├── example.test.hpp ├── explicit_enable.test.cpp ├── explicit_enable.test.hpp ├── explicit_enable_other.hpp ├── forward_decl.test.cpp ├── forward_decl.test.hpp ├── forward_decl2.hpp ├── implicit_ctor_dtor.test.cpp ├── implicit_ctor_dtor.test.hpp ├── invoke.test.cpp ├── invoke.test.hpp ├── lambda.test.cpp ├── lambda.test.hpp ├── local_struct.test.cpp ├── local_struct.test.hpp ├── member_ptr.test.cpp ├── member_ptr.test.hpp ├── namespaces.test.cpp ├── namespaces.test.hpp ├── nested_template_args.test.cpp ├── nested_template_args.test.hpp ├── no_reflect.test.cpp ├── no_reflect.test.hpp ├── operator.test.cpp ├── operator.test.hpp ├── overload.test.cpp ├── overload.test.hpp ├── parameter_pack.test.cpp ├── parameter_pack.test.hpp ├── ptr.test.cpp ├── ptr.test.hpp ├── ptr_in_template.test.cpp ├── ptr_in_template.test.hpp ├── record_typedef.test.cpp ├── record_typedef.test.hpp ├── reflect_type.test.cpp ├── reflect_type.test.hpp ├── requires.test.cpp ├── requires.test.hpp ├── specialize.test.cpp ├── specialize.test.hpp ├── static_field.test.cpp ├── static_field.test.hpp ├── static_method.test.cpp ├── static_method.test.hpp ├── stdlib.test.cpp ├── stdlib.test.hpp ├── template_method.test.cpp ├── template_method.test.hpp ├── template_params.test.cpp ├── template_params.test.hpp ├── test.hpp ├── tests.cpp ├── this.test.cpp ├── this.test.hpp ├── traverse.test.cpp ├── traverse.test.hpp ├── type_id.test.cpp ├── type_id.test.hpp ├── types.test.cpp ├── types.test.hpp ├── union.test.cpp ├── union.test.hpp ├── unique_ptr.test.cpp ├── unique_ptr.test.hpp ├── use_glm.test.cpp ├── use_glm.test.hpp ├── virtual.test.cpp ├── virtual.test.hpp ├── virtual2.test.cpp └── virtual2.test.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .idea/ 3 | 4 | # extension-less files 5 | * 6 | !/**/ 7 | !*.* 8 | !Makefile 9 | 10 | # archimedes files 11 | *.types.cpp 12 | 13 | .cache/ 14 | .vimrc 15 | 16 | *.icloud 17 | .DS_Store 18 | .vscode 19 | .ccls* 20 | compile_flags.txt 21 | compile_commands.json 22 | 23 | # Prerequisites 24 | *.d 25 | 26 | # Object files 27 | *.o 28 | *.ko 29 | *.obj 30 | *.elf 31 | 32 | # Linker output 33 | *.ilk 34 | *.map 35 | *.exp 36 | 37 | # Precompiled Headers 38 | *.gch 39 | *.pch 40 | 41 | # Libraries 42 | *.lib 43 | *.a 44 | *.la 45 | *.lo 46 | 47 | # Shared objects (inc. Windows DLLs) 48 | *.dll 49 | *.so 50 | *.so.* 51 | *.dylib 52 | 53 | # Executables 54 | *.exe 55 | *.out 56 | *.app 57 | *.i*86 58 | *.x86_64 59 | *.hex 60 | 61 | # Debug files 62 | *.dSYM/ 63 | *.su 64 | *.idb 65 | *.pdb 66 | 67 | # Kernel Module Compile Results 68 | *.mod* 69 | *.cmd 70 | .tmp_versions/ 71 | modules.order 72 | Module.symvers 73 | Mkfile.old 74 | dkms.conf 75 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/fmt"] 2 | path = lib/fmt 3 | url = https://github.com/fmtlib/fmt 4 | [submodule "lib/nameof"] 5 | path = lib/nameof 6 | url = https://github.com/Neargye/nameof 7 | [submodule "test/glm"] 8 | path = test/glm 9 | url = https://github.com/g-truc/glm 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 jdah 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | print-%:; @echo $($*) 2 | 3 | CC = clang++ 4 | LD = clang++ 5 | 6 | CCFLAGS += $(shell llvm-config --cppflags) 7 | CCFLAGS += -std=c++20 -stdlib=libc++ 8 | CCFLAGS += -O2 -g -Wall -Wextra -Wpedantic 9 | CCFLAGS += -Wno-vla-extension 10 | CCFLAGS += -Wno-c99-extensions 11 | CCFLAGS += -Wno-unused-parameter 12 | CCFLAGS += -Wno-gnu-zero-variadic-macro-arguments 13 | CCFLAGS += -Wno-gnu-anonymous-struct 14 | CCFLAGS += -iquoteplugin 15 | CCFLAGS += -Iinclude 16 | CCFLAGS += -Ilib/fmt/include 17 | CCFLAGS += -Ilib/nameof/include 18 | 19 | LDFLAGS = -lstdc++ 20 | LDFLAGS += $(shell llvm-config --ldflags --libs) 21 | LDFLAGS += $(shell llvm-config --libdir)/libclang-cpp.dylib 22 | 23 | BIN = bin 24 | 25 | PLUGIN_SRC = $(shell find plugin -name "*.cpp") 26 | PLUGIN_OBJ = $(PLUGIN_SRC:.cpp=.o) 27 | PLUGIN_DEP = $(PLUGIN_OBJ:.o=.d) 28 | PLUGIN = $(BIN)/libarchimedes-plugin.dylib 29 | 30 | LIB_SRC = $(shell find runtime -name "*.cpp") 31 | LIB_OBJ = $(LIB_SRC:.cpp=.o) 32 | LIB_DEP = $(LIB_OBJ:.o=.d) 33 | 34 | COMMON_SRC = $(shell find common -name "*.cpp") 35 | COMMON_OBJ = $(COMMON_SRC:.cpp=.o) 36 | COMMON_DEP = $(COMMON_OBJ:.o=.d) 37 | 38 | SHARED = $(BIN)/libarchimedes.dylib 39 | STATIC = $(BIN)/libarchimedes.a 40 | 41 | TEST_DIR = test 42 | 43 | TEST_SRC = $(shell find $(TEST_DIR) -name "*.test.cpp") 44 | TEST_DEP = $(TEST_SRC:.cpp=.d) 45 | TEST_OBJ = $(TEST_SRC:.cpp=.o) 46 | TEST_TYPES_OBJ = $(TEST_SRC:.cpp=.types.o) 47 | TEST_OUT = $(TEST_SRC:.test.cpp=) 48 | TEST_OUT_NAMES = $(notdir $(TEST_OUT)) 49 | 50 | TEST_RUNNER_SRC = $(TEST_DIR)/tests.cpp 51 | TEST_RUNNER_DEP = $(TEST_RUNNER_SRC:.cpp=.d) 52 | TEST_RUNNER_OUT = $(TEST_RUNNER_SRC:.cpp=) 53 | 54 | all: dirs plugin shared static 55 | 56 | $(BIN): 57 | mkdir -p $@ 58 | 59 | dirs: $(BIN) 60 | 61 | -include $(PLUGIN_DEP) $(LIB_DEP) $(TEST_DEP) $(TEST_RUNNER_DEP) 62 | 63 | %.test.o %.test.types.o: %.test.cpp %.test.hpp $(PLUGIN) 64 | $(CCACHE) $(CC) -o $@ -MMD -c $(CCFLAGS) \ 65 | -fplugin=$(PLUGIN) \ 66 | -fplugin-arg-archimedes-exclude-ns-std \ 67 | -fplugin-arg-archimedes-header-include/archimedes.hpp \ 68 | -fplugin-arg-archimedes-file-$< \ 69 | -fplugin-arg-archimedes-file-$(<:.cpp=.hpp) \ 70 | -fplugin-arg-archimedes-out-$(<:.cpp=.types.o) \ 71 | $< 72 | 73 | $(TEST_OUT): %: %.test.o %.test.types.o $(STATIC) 74 | $(LD) -o $@ $(filter %.o,$^) -Lbin -larchimedes $(LDFLAGS) 75 | 76 | $(TEST_RUNNER_OUT): %: %.cpp 77 | $(CCACHE) $(CC) -o $@ $(CCFLAGS) $< 78 | 79 | test: $(TEST_RUNNER_OUT) $(TEST_OUT) FORCE 80 | ./$(TEST_RUNNER_OUT) $(TEST_DIR) 81 | 82 | test-%: $(TEST_DIR)/% $(TEST_RUNNER_OUT) 83 | ./$(TEST_RUNNER_OUT) $(TEST_DIR) $(notdir $<) 84 | 85 | test-source-%: test/%.test.cpp test/%.test.hpp $(PLUGIN) 86 | $(CCACHE) $(CC) -o $(<:.cpp=.o) -MMD -c $(CCFLAGS) \ 87 | -fplugin=$(PLUGIN) \ 88 | -fplugin-arg-archimedes-exclude-ns-std \ 89 | -fplugin-arg-archimedes-header-include/archimedes.hpp \ 90 | -fplugin-arg-archimedes-file-$< \ 91 | -fplugin-arg-archimedes-file-$(<:.cpp=.hpp) \ 92 | -fplugin-arg-archimedes-out-$(<:.cpp=.types.cpp) \ 93 | -fplugin-arg-archimedes-emit-source \ 94 | -fplugin-arg-archimedes-emit-verbose \ 95 | -fplugin-arg-archimedes-verbose \ 96 | $< 97 | 98 | FORCE: 99 | 100 | $(PLUGIN): $(PLUGIN_OBJ) $(COMMON_OBJ) | dirs 101 | $(LD) -g -shared -o $(PLUGIN) $(filter %.o,$^) $(LDFLAGS) 102 | 103 | plugin: $(PLUGIN) 104 | 105 | $(SHARED): $(LIB_OBJ) $(COMMON_OBJ) | dirs 106 | $(LD) -g -shared -o $(SHARED) $(filter %.o,$^) $(LDFLAGS) 107 | 108 | shared: $(SHARED) 109 | 110 | $(STATIC): $(LIB_OBJ) $(COMMON_OBJ) | dirs 111 | ar rcs $(STATIC) $(filter %.o,$^) 112 | 113 | static: $(STATIC) 114 | 115 | deps: $(DEPS) $(PCHDEP) $(SHADERS_DEP) 116 | 117 | $(LIB_OBJ) $(PLUGIN_OBJ) $(COMMON_OBJ): %.o: %.cpp 118 | $(CCACHE) $(CC) -o $@ -MMD -c $(CCFLAGS) $< 119 | 120 | clean: 121 | cd test && find . -name "*.d" -delete 122 | cd test && find . -type f ! -name '*.*' -delete 123 | cd runtime && find . -name "*.d" -delete 124 | cd plugin && find . -name "*.d" -delete 125 | find . -name "*.o" -delete 126 | find . -name "*.types.cpp" -delete 127 | rm -rf $(BIN) 128 | 129 | time: dirs 130 | make clean && make -j 10 plugin static && time make -j 10 $(TEST_OUT) 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # archimedes - C++ reflection via code generation 2 | 3 |

4 | 5 |

6 | 7 | ### WARNING: this is ALPHA software. the code is highly unstable hot garbage. use at your own risk! 8 | 9 | ## Features 10 | * `template`s **are** supported - though only those which are naturally instantiated (used) by the program itself 11 | * Reflected `class`/`struct` types have: 12 | * `template` parameter names, types, and values 13 | * `typedef` and `using` values (i.e. `struct Foo { using F = int; /* ... */}`) 14 | * base classes 15 | * class memory layout including `virtual` bases, vtable pointers, etc. 16 | * fields and get/set values on live objects 17 | * static fields and their values 18 | * functions, member and static, which can be invoked on live objects 19 | * constructors (and destructors) which can be invoked on raw memory to (de)initialize objects 20 | * various type traits (`is_abstract/is_polymorphic/is_pod/...`) 21 | * inspect `private` members 22 | * Reflected `enum` types have: 23 | * `std::string` to value conversion 24 | * value to `std::string` conversion 25 | * underlying type information 26 | * Reflected functions: 27 | * can be `invoke(...)`'d with arbitrary arguments 28 | * list parameters; with types, names, and qualifiers 29 | 30 | ## Example 31 | The shortest demo which covers the most features (see `test/example.test.{cpp, hpp}`) 32 | 33 | #### TODO (just look through the tests for now!) 34 | 35 | ## How does it work? 36 | archimedes works via a clang plugin which runs on already compiled/template-instantiated ASTs. 37 | It traverses ASTs and gathers type information, serializing it and emitting it in an object file emitted alongside the actual compiler output. 38 | These object files can then be linked into any normal executable (or library) and their data loaded via `archimedes::load()` on program startup. 39 | In order to not bloat compile times, the majority of information (that which doesn't rely on function pointers, constexpr values, etc.) is 40 | serialized and embedded into the program via simple byte arrays which are deserialized at runtime. 41 | 42 | ## Usage 43 | Include `include/archimedes.hpp` for full access, and `include/archimedes/*.hpp` for submodules (`any`, `type_id`, etc.) 44 | See `Makefile` for complex usage. 45 | 46 | ``` 47 | $ clang++ 48 | -o main.o 49 | -fplugin= 50 | -fplugin-arg-archimedes-header-include/archimedes.hpp # path to archimedes header 51 | -fplugin-arg-archimedes-exclude-ns-std # use "exclude-ns-" to exclude namespaces from reflection 52 | -fplugin-arg-archimedes-file-main.cpp # list files in which types should be reflected 53 | -fplugin-arg-archimedes-file-main.hpp 54 | -fplugin-arg-archimedes-out-main.types.o # reflection output object file 55 | -c 56 | main.cpp 57 | $ clang++ -o main main.o main.types.o # link *.o and *.types.o to include reflection information 58 | ``` 59 | 60 | ## Building 61 | `$ make plugin static shared` 62 | 63 | ## TODO: Developing 64 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | * **CLEANUP** 3 | * Multithreading 4 | * NTTP support (non-type template parameters) 5 | * Allow `invoke()`ing functions with `requires` clauses 6 | * ~~convert from `std::type_info` -> archimedes types and vice versa (allow lookups for `dynamic_cast` with `any`s)~~ **DONE** 7 | * Check if types are serializable at compile time through `constexpr` arrays of registered `type_id`s 8 | * ~~Get fields independent on class location in inheritance hierarchy~~ **NOT GONNA HAPPEN** 9 | * `using enum ...;` support for string lookup 10 | * `constexpr` as much of the library as possible 11 | * Respect `using` declarations in the global namespace 12 | * ~~Generate object files directly (instead of source, remove need for extra compiler invoke)~~ **DONE** 13 | * Ad-hoc type checking for `any` types 14 | * Redesign top-level interface around ranges (or something like them) to avoid heap allocations 15 | * Template methods that don't act like template methods: see `test/requires.cpp` 16 | * ~~Generate constructor invokers for all possible constructors (excluding templates) for reflected types~~ **DONE** 17 | 18 | ## Never-gonna-happen-s 19 | | | Reason 20 | |-------------------------------------------|-------------------------------------------------------- 21 | | Invoke private methods | not supported by C++ 22 | | Values of `private` `constexpr` fields | can't `decltype` or get values outside of class 23 | | Anything in anonymous namespaces | too complex to resolve type names, not supported by C++ 24 | | Inspecting non-specialized `template`s | too complex, doesn't make a lot of sense at runtime? 25 | -------------------------------------------------------------------------------- /common/any.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace archimedes; 7 | 8 | any::~any() { 9 | if (this->is_data_owned) { 10 | // check if archimedes is still loaded - if it isn't, our dtor function 11 | // may have disappeared. 12 | if (archimedes::loaded() && this->dtor_fn) { 13 | (*this->dtor_fn)(this->data); 14 | } 15 | 16 | std::free(this->data); 17 | } 18 | } 19 | 20 | result any::make_for_id(type_id id) { 21 | const auto type = reflect(id); 22 | if (!type) { 23 | return any_error::COULD_NOT_REFLECT; 24 | } 25 | 26 | const auto kind = type->kind(); 27 | 28 | if (kind == VOID 29 | || kind == UNKNOWN 30 | || kind == FUNC) { 31 | return any_error::INVALID_TYPE; 32 | } else if (kind == PTR 33 | || kind == REF 34 | || kind == RREF 35 | || kind == MEMBER_PTR) { 36 | // return raw any, data is stored directly in pointer 37 | return any::make_ptr(id); 38 | } 39 | 40 | any a = any::make_of_size(id, type->size()); 41 | 42 | if (type->is_record()) { 43 | if (type->size() == 0) { 44 | return any_error::INVALID_TYPE; 45 | } 46 | 47 | const auto rec = type->as_record(); 48 | const auto ctor = rec.default_constructor(); 49 | if (!ctor) { 50 | return any_error::NO_DEFAULT_CTOR; 51 | } 52 | 53 | if (const auto dtor = rec.destructor()) { 54 | a.dtor_fn = 55 | [dtor = *dtor](void *p) { 56 | dtor.invoke(p); 57 | }; 58 | } 59 | 60 | if (const auto copy = rec.copy_constructor()) { 61 | a.copy_fn = 62 | [copy = *copy](void *p, void *o) { 63 | if (!copy.invoke(p, o)) { 64 | ARCHIMEDES_FAIL("failed to invoke copy ctor"); 65 | } 66 | }; 67 | } else if (const auto copy_assign = rec.copy_assignment()) { 68 | a.copy_fn = 69 | [ctor = *ctor, copy_assign = *copy_assign](void *p, void *o) { 70 | if (!ctor.invoke(p)) { 71 | ARCHIMEDES_FAIL("failed to invoke default ctor"); 72 | } 73 | 74 | if (!copy_assign.invoke(p, o)) { 75 | ARCHIMEDES_FAIL("failed to invoke copy assign"); 76 | } 77 | }; 78 | } 79 | 80 | if (!ctor->invoke(a.storage())) { 81 | ARCHIMEDES_FAIL("failed to invoke default ctor"); 82 | } 83 | } else if (type->is_numeric()) { 84 | // numeric types are stored on the heap with a simple memcpy copy 85 | a.copy_fn = 86 | [size = a.data_size](void *p, void *o) { 87 | std::memcpy(p, o, size); 88 | }; 89 | std::memset(a.data, 0, a.data_size); 90 | } 91 | 92 | return a; 93 | } 94 | 95 | result any::make_for_type( 96 | const reflected_type &type) { 97 | return any::make_for_id(type.id()); 98 | } 99 | -------------------------------------------------------------------------------- /common/cast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // TODO: remove 7 | #include 8 | 9 | using namespace archimedes; 10 | using namespace archimedes::detail; 11 | 12 | // get byte difference between a cast from a ptr_t to type "to" 13 | result archimedes::detail::cast_diff_impl( 14 | const any &ptr_t, 15 | const reflected_record_type &from, 16 | const reflected_record_type &to, 17 | bool only_up) { 18 | if (from == to) { 19 | // identity cast 20 | return 0; 21 | } 22 | 23 | void *ptr = ptr_t.as(); 24 | 25 | // check if to is a vbase of from 26 | const auto vbases = from.vbases(); 27 | auto it = 28 | std::find_if( 29 | vbases.begin(), vbases.end(), 30 | [&to](const reflected_base &vbase) { 31 | return vbase.type() == to; 32 | }); 33 | 34 | // cast directly to vbase 35 | if (it != vbases.end()) { 36 | return 37 | reinterpret_cast(it->cast_up(ptr)) 38 | - reinterpret_cast(ptr); 39 | } 40 | 41 | // try casting from regular bases, find the first one that succeeds 42 | for (const auto &base : from.bases()) { 43 | // try directly casting up and continuing with upcasts. add the offset 44 | // from this upcast to the total with *res. 45 | // 46 | // deliberately ignore errors, we'll just NOT FOUND in the end if 47 | // nothing is found this way. 48 | // 49 | // also ONLY UPCAST, otherwise we start to get some strange paths to 50 | // "to"... 51 | auto *ptr_base = base.cast_up(ptr); 52 | if (const auto res = 53 | cast_diff_impl( 54 | any::make(ptr_base), 55 | base.type(), 56 | to, 57 | true)) { 58 | return 59 | *res 60 | + (reinterpret_cast(ptr_base) 61 | - reinterpret_cast(ptr)); 62 | } 63 | } 64 | 65 | // only up cast and here, there is no cast 66 | if (only_up) { 67 | return cast_error::NOT_FOUND; 68 | } 69 | 70 | // check if from is above to in the inheritance hierarchy (downcast) 71 | // use a standard depth first search (reimplemented since traverse_bases is 72 | // not adequate) to find a path from to -> from 73 | vector path, bases; 74 | map visited; 75 | 76 | std::function dfs; 77 | dfs = 78 | [&](const reflected_base &base) { 79 | path.push_back(base); 80 | 81 | // check if already visited 82 | auto it = visited.find(base.type().id()); 83 | if (it == visited.end() || !it->second) { 84 | visited[base.type().id()] = true; 85 | } else { 86 | return; 87 | } 88 | 89 | if (base.type() == from) { 90 | bases = path; 91 | } else { 92 | for (const auto &b : base.type().bases()) { 93 | auto it = visited.find(b.type().id()); 94 | if (it == visited.end() || !it->second) { 95 | dfs(b); 96 | } 97 | } 98 | } 99 | 100 | visited[base.type().id()] = false; 101 | path.pop_back(); 102 | }; 103 | 104 | for (const auto &base : to.all_bases()) { 105 | dfs(base); 106 | } 107 | 108 | if (!bases.empty()) { 109 | // go backwards through path casting down 110 | void *out = const_cast(ptr); 111 | for (int i = bases.size() - 1; i >= 0; i--) { 112 | out = bases[i].cast_down(out); 113 | } 114 | return reinterpret_cast(out) - reinterpret_cast(ptr); 115 | } 116 | 117 | return cast_error::NOT_FOUND; 118 | } 119 | 120 | result archimedes::detail::make_cast_fn_impl( 121 | const any &ptr_t, 122 | const reflected_record_type &from, 123 | const reflected_record_type &to) { 124 | const auto diff = cast_diff_impl(ptr_t, from, to); 125 | if (diff) { 126 | return [diff = *diff](void *p) -> void* { 127 | return reinterpret_cast(p) + diff; 128 | }; 129 | } else { 130 | return diff.unwrap_error(); 131 | } 132 | } 133 | 134 | result archimedes::detail::cast_impl( 135 | const any &ptr_t, 136 | const reflected_record_type &from, 137 | const reflected_record_type &to) { 138 | const auto fn = make_cast_fn_impl(ptr_t, from, to); 139 | if (fn) { 140 | return (*fn)(ptr_t.as()); 141 | } else { 142 | return fn.unwrap_error(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /common/regex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define FMT_HEADER_ONLY 5 | #include "../lib/fmt/include/fmt/core.h" 6 | 7 | bool archimedes::regex_matches( 8 | std::string_view regex, 9 | std::string_view string) { 10 | return std::regex_match( 11 | std::string(string), 12 | std::regex( 13 | "^" + std::string(regex) + "$", 14 | std::regex_constants::ECMAScript)); 15 | } 16 | -------------------------------------------------------------------------------- /common/registry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace archimedes { 5 | namespace detail { 6 | registry ®istry::instance() { 7 | static registry instance; 8 | return instance; 9 | } 10 | 11 | void registry::load() { 12 | if (this->_loaded) { 13 | ARCHIMEDES_FAIL("load() called twice"); 14 | } 15 | 16 | this->_loaded = true; 17 | 18 | for (const auto &l : this->loaders) { 19 | l(); 20 | } 21 | 22 | // load typeid hash map 23 | for (auto &[_, t] : this->types_by_id) { 24 | if (t.type_id_hash == 0) { 25 | continue; 26 | } 27 | 28 | this->types_by_type_id_hashes[t.type_id_hash] = &t; 29 | } 30 | } 31 | 32 | vector registry::find_by_annotations( 33 | std::span annotations) { 34 | vector ts; 35 | for (const auto &[_, t] : this->types_by_id) { 36 | if (std::all_of( 37 | annotations.begin(), 38 | annotations.end(), 39 | [t = t](const auto &a) { 40 | return std::any_of( 41 | t.annotations.begin(), 42 | t.annotations.end(), 43 | [&](const auto &b) { 44 | return a == b; 45 | }); 46 | })) { 47 | ts.push_back(&t); 48 | } 49 | } 50 | return ts; 51 | } 52 | 53 | vector registry::all_types() const { 54 | return transform>( 55 | values(this->types_by_id), 56 | [](const auto &t) { 57 | return &t; 58 | }); 59 | } 60 | 61 | std::optional registry::type_from_type_id_hash(size_t hash) const { 62 | auto it = this->types_by_type_id_hashes.find(hash); 63 | return it == this->types_by_type_id_hashes.end() ? 64 | std::nullopt 65 | : std::make_optional(it->second); 66 | } 67 | 68 | std::optional registry::type_from_id( 69 | type_id id) const { 70 | auto it = this->types_by_id.find(id); 71 | return it == this->types_by_id.end() ? 72 | std::nullopt 73 | : std::make_optional(&it->second); 74 | } 75 | 76 | std::optional registry::type_from_name( 77 | std::string_view name) const { 78 | // expand any namespace aliases in the name 79 | auto expanded = expand_namespaces(name); 80 | 81 | std::optional result; 82 | this->permute_namespaces( 83 | expanded, 84 | [this, &result](std::string_view s) -> bool { 85 | // check if name is a typedef, if so return type directly 86 | for (const auto &t : this->typedefs) { 87 | if (s == t.name) { 88 | result = &*t.aliased_type.id; 89 | return true; 90 | } 91 | } 92 | 93 | // get by id 94 | result = this->type_from_id(type_id::from(s)); 95 | return static_cast(result); 96 | }); 97 | 98 | return result; 99 | } 100 | 101 | std::optional*> registry::function_by_name( 102 | std::string_view name) const { 103 | auto expanded = expand_namespaces(name); 104 | auto it = this->functions_by_name.find(std::string(expanded)); 105 | return it == this->functions_by_name.end() ? 106 | std::nullopt 107 | : std::make_optional(&it->second); 108 | } 109 | 110 | std::optional*> registry::function_by_type( 111 | type_id id) const { 112 | auto it = this->functions_by_type.find(id); 113 | return it == this->functions_by_type.end() ? 114 | std::nullopt 115 | : std::make_optional(&it->second); 116 | } 117 | 118 | void registry::set_collision_callback(collision_callback &&tcc) { 119 | this->tcc = tcc; 120 | } 121 | 122 | static void load_module_internal( 123 | registry ®istry, 124 | const vector> &dyncasts, 125 | const vector &constexpr_values, 126 | const vector &invokers, 127 | const vector &template_param_values, 128 | const vector &type_id_hashes, 129 | std::span functions_data, 130 | std::span types_data, 131 | std::span typedefs_data, 132 | std::span aliases_data, 133 | std::span usings_data) { 134 | // load invokers from array for fuction set 135 | const auto load_invokers = 136 | [&](function_overload_set &fos) { 137 | for (auto &f : fos.functions) { 138 | if (f.invoker_index != NO_ARRAY_INDEX) { 139 | f.invoker = invokers[f.invoker_index]; 140 | } 141 | } 142 | }; 143 | 144 | // std::move all of src onto dst 145 | const auto append = 146 | [](auto &dst, auto &src) { 147 | std::move( 148 | std::begin(src), 149 | std::end(src), 150 | std::back_inserter(dst)); 151 | src.clear(); 152 | }; 153 | 154 | // load src_data (serialized vector of same type as dst) and append data 155 | // onto dst 156 | const auto load_basic = 157 | [&append](auto &dst, auto &src_data) { 158 | using V = std::remove_reference_t; 159 | auto vec = vector(); 160 | auto is = bytestream(src_data); 161 | deserialize(is, vec); 162 | append(dst, vec); 163 | }; 164 | 165 | auto types = 166 | deserialize>(types_data); 167 | 168 | auto functions = 169 | deserialize>(functions_data); 170 | 171 | // patch indexed values for each type, function 172 | for (auto &t : types) { 173 | if (t.type_id_hash_index != NO_ARRAY_INDEX) { 174 | t.type_id_hash = type_id_hashes[t.type_id_hash_index]; 175 | } 176 | 177 | if (t.kind != STRUCT && t.kind != UNION) { 178 | continue; 179 | } 180 | 181 | for (auto &p : t.record.template_parameters) { 182 | if (p.value_index != NO_ARRAY_INDEX) { 183 | p.value = template_param_values[p.value_index]; 184 | } 185 | } 186 | 187 | for (auto &b : t.record.bases) { 188 | if (b.dyncast_down_index != NO_ARRAY_INDEX) { 189 | b.dyncast_down = dyncasts[b.dyncast_down_index]; 190 | } 191 | 192 | if (b.dyncast_up_index != NO_ARRAY_INDEX) { 193 | b.dyncast_up = dyncasts[b.dyncast_up_index]; 194 | } 195 | } 196 | 197 | for (auto &[_, f] : t.record.static_fields) { 198 | if (f.constexpr_value_index != NO_ARRAY_INDEX) { 199 | f.constexpr_value = 200 | constexpr_values[f.constexpr_value_index]; 201 | } 202 | } 203 | 204 | for (auto &[_, fos] : t.record.functions) { 205 | load_invokers(fos); 206 | } 207 | } 208 | 209 | for (auto &[_, fos] : functions) { 210 | load_invokers(fos); 211 | } 212 | 213 | registry.load_types(types); 214 | registry.load_functions(functions); 215 | 216 | /* auto vec = vector(); */ 217 | /* auto is = bytestream(typedefs_data); */ 218 | /* deserialize(is, vec); */ 219 | /* for (const auto &v : vec) { */ 220 | /* std::cout */ 221 | /* << "got typedef " */ 222 | /* << v.name */ 223 | /* << " <- " */ 224 | /* << v.aliased_type.id */ 225 | /* << std::endl; */ 226 | /* } */ 227 | 228 | // insert typedefs, aliases, usings directly 229 | load_basic(registry.typedefs, typedefs_data); 230 | load_basic(registry.namespace_aliases, aliases_data); 231 | load_basic(registry.namespace_usings, usings_data); 232 | } 233 | 234 | 235 | void registry::load_module( 236 | const vector> &dyncasts, 237 | const vector &constexpr_values, 238 | const vector &invokers, 239 | const vector &template_param_values, 240 | const vector &type_id_hashes, 241 | std::span functions_data, 242 | std::span types_data, 243 | std::span typedefs_data, 244 | std::span aliases_data, 245 | std::span usings_data) { 246 | const auto f = 247 | [=, &dyncasts, &constexpr_values, &invokers]() { 248 | load_module_internal( 249 | *this, 250 | dyncasts, 251 | constexpr_values, 252 | invokers, 253 | template_param_values, 254 | type_id_hashes, 255 | functions_data, 256 | types_data, 257 | typedefs_data, 258 | aliases_data, 259 | usings_data); 260 | }; 261 | 262 | this->loaders.push_back(f); 263 | } 264 | 265 | // load a set of types into the registry 266 | // TODO: std::move values 267 | void registry::load_types(const vector is) { 268 | // returns true if type_info i can collide legally with an existing type 269 | const auto can_collide = 270 | [](const type_info &i) { 271 | return i.kind != STRUCT && i.kind != UNION && i.kind != ENUM; 272 | }; 273 | 274 | for (const auto &i : is) { 275 | const auto it = this->types_by_id.find(i.id); 276 | if (it == this->types_by_id.end()) { 277 | this->types_by_id.emplace(i.id, i); 278 | } else { 279 | const auto &other = it->second; 280 | if (can_collide(i) && can_collide(other)) { 281 | // do nothing, collisions are fine here 282 | } else if (i.kind == UNKNOWN) { 283 | // do nothing, keep existing type 284 | } else if (other.kind == UNKNOWN) { 285 | // replace with new type 286 | // DOES THIS REPLACE?? 287 | this->types_by_id[i.id] = i; 288 | } else { 289 | // resolve type collision 290 | const auto *info = 291 | this->tcc( 292 | reflected_type(&other), 293 | reflected_type(&i)).info; 294 | 295 | this->types_by_id[info->id] = other; 296 | } 297 | } 298 | } 299 | } 300 | 301 | // load a set of function overloads into the registry 302 | // TODO: move from map 303 | void registry::load_functions( 304 | const name_map &fs) { 305 | for (const auto &[name, s] : fs) { 306 | const auto it_fbn = this->functions_by_name.find(name); 307 | 308 | vector *v_fbn; 309 | if (it_fbn == this->functions_by_name.end()) { 310 | v_fbn = 311 | &this->functions_by_name.emplace( 312 | name, 313 | vector()) 314 | .first->second; 315 | } else { 316 | v_fbn = &it_fbn->second; 317 | } 318 | 319 | // append set to list of sets with this name 320 | v_fbn->push_back(s); 321 | 322 | // add into set of functions by type 323 | // no need to resolve conflicts here as multiple functions can come 324 | // up for the same type (this is expected behavior) 325 | for (const auto &f : s.functions) { 326 | auto it = this->functions_by_type.find(f.id); 327 | auto &vec = 328 | it == this->functions_by_type.end() ? 329 | (this->functions_by_type.emplace( 330 | f.id, 331 | vector()) 332 | .first->second) 333 | : it->second; 334 | vec.push_back(f); 335 | } 336 | } 337 | } 338 | 339 | // expand namespaces according to namespace_aliases 340 | std::string registry::expand_namespaces(std::string_view name) const { 341 | auto expanded = std::string(name); 342 | expand: 343 | for (const auto &na : this->namespace_aliases) { 344 | if (expanded.starts_with(na.name)) { 345 | std::string old = expanded; 346 | expanded = 347 | na.aliased + expanded.substr(na.name.length()); 348 | goto expand; 349 | } 350 | } 351 | return expanded; 352 | } 353 | } // namespace detail 354 | } // namespace archimedes 355 | -------------------------------------------------------------------------------- /images/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/archimedes/ad2f4e8b70640ee54dc44c2858cf10053a83a629/images/0.png -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdah/archimedes/ad2f4e8b70640ee54dc44c2858cf10053a83a629/images/icon.png -------------------------------------------------------------------------------- /include/archimedes.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "archimedes/basic.hpp" 4 | 5 | // archimedes can be enabled/disabled per translation unit with 6 | // ARCHIMEDES_{ENABLE/DISABLE} 7 | #define ARCHIMEDES_ENABLE() ARCHIMEDES_ARG("enable") 8 | #define ARCHIMEDES_DISABLE() ARCHIMEDES_ARG("disable") 9 | 10 | // include/exclude namespaces per translation unit 11 | #define ARCHIMEDES_INCLUDE_NS(_ns) \ 12 | (ARCHIMEDES_ARG("include-ns-" ARCHIMEDES_STRINGIFY(_ns))) 13 | 14 | #define ARCHIMEDES_EXCLUDE_NS(_ns) \ 15 | (ARCHIMEDES_ARG("exclude-ns-" ARCHIMEDES_STRINGIFY(_ns))) 16 | 17 | #define _ARCHIMEDES_FORCE_FUNCTION_INSTANTIATION_IMPL(_f, _n) \ 18 | struct _n { _n(const void *p) { \ 19 | std::string _ = fmt::format("{}", fmt::ptr(p)); } }; \ 20 | auto CONCAT(_fi_, __COUNTER__) = _n(reinterpret_cast(&(_f))); 21 | 22 | #define ARCHIMEDES_FORCE_FUNCTION_INSTANTIATION(_f) \ 23 | _ARCHIMEDES_FORCE_FUNCTION_INSTANTIATION_IMPL( \ 24 | _f, CONCAT(_fi_t_, __COUNTER__)) 25 | 26 | #define _ARCHIMEDES_FORCE_TYPE_INSTANTIATION_IMPL(_n, ...) \ 27 | struct _n { \ 28 | __VA_ARGS__ t; \ 29 | void *p; _n() : p(foo()) {} \ 30 | void *foo() { return &t; }}; \ 31 | auto CONCAT(_ti_, __COUNTER__) = _n(); 32 | 33 | #define ARCHIMEDES_FORCE_TYPE_INSTANTIATION(...) \ 34 | _ARCHIMEDES_FORCE_TYPE_INSTANTIATION_IMPL( \ 35 | CONCAT(_ti_t_, __COUNTER__), \ 36 | __VA_ARGS__) 37 | 38 | // extern'd templates for compile performance 39 | #include "archimedes/ds.hpp" 40 | extern template class 41 | std::vector; 42 | 43 | #include "archimedes/type_id.hpp" 44 | #include "archimedes/any.hpp" 45 | #include "archimedes/errors.hpp" 46 | #include "archimedes/type_kind.hpp" 47 | #include "archimedes/access_specifier.hpp" 48 | #include "archimedes/types.hpp" 49 | #include "archimedes/registry.hpp" 50 | #include "archimedes/cast.hpp" 51 | 52 | namespace archimedes { 53 | // load type data if not already loaded 54 | inline void load() { 55 | detail::registry::instance().load(); 56 | } 57 | 58 | // returns true if archimedes is loaded 59 | inline bool loaded() { 60 | return detail::registry::instance().loaded(); 61 | } 62 | 63 | // set the type collision callback 64 | // a function which accepts two reflected_types and chooses between them 65 | // which type the type name should resolve to 66 | inline void set_collision_callback(collision_callback &&tcc) { 67 | detail::registry::instance() 68 | .set_collision_callback(std::move(tcc)); 69 | } 70 | 71 | // get reflected type by annotations 72 | inline vector reflect_by_annotations( 73 | std::span annotations) { 74 | return detail::transform>( 75 | detail::registry::instance().find_by_annotations(annotations), 76 | [](const auto *info) { 77 | return reflected_type(info); 78 | }); 79 | } 80 | 81 | // returns all registered types 82 | inline vector types() { 83 | return detail::transform>( 84 | detail::registry::instance().all_types(), 85 | [](const detail::type_info *info) { 86 | return reflected_type(info); 87 | }); 88 | } 89 | 90 | // get all children of some base class 91 | inline vector reflect_children( 92 | reflected_record_type rec) { 93 | return 94 | detail::transform>( 95 | detail::filter>( 96 | types(), 97 | [&](const reflected_type &type) { 98 | return 99 | type.is_record() 100 | && type.as_record().in_hierarchy(rec); 101 | }), 102 | [](const reflected_type &type) { 103 | return type.as_record(); 104 | }); 105 | } 106 | 107 | // get a reflected type by id 108 | inline std::optional reflect(type_id id) { 109 | const auto i = detail::registry::instance().type_from_id(id); 110 | return i ? std::make_optional(reflected_type(*i)) : std::nullopt; 111 | } 112 | 113 | // reflect a record (class/struct/union) type 114 | template 115 | requires std::is_class_v || std::is_union_v 116 | inline std::optional reflect() { 117 | const auto opt = reflect(type_id::from()); 118 | return opt ? std::make_optional(opt->as_record()) : std::nullopt; 119 | } 120 | 121 | // reflect an enum type 122 | template 123 | requires std::is_enum_v 124 | inline std::optional reflect() { 125 | const auto opt = reflect(type_id::from()); 126 | return opt ? std::make_optional(opt->as_enum()) : std::nullopt; 127 | } 128 | 129 | // reflect function type 130 | template 131 | requires std::is_function_v 132 | inline std::optional reflect() { 133 | const auto opt = reflect(type_id::from()); 134 | return opt ? std::make_optional(opt->as_function()) : std::nullopt; 135 | } 136 | 137 | // reflect array type 138 | template 139 | requires std::is_array_v 140 | inline std::optional reflect() { 141 | const auto opt = reflect(type_id::from()); 142 | return opt ? std::make_optional(opt->as_array()) : std::nullopt; 143 | } 144 | 145 | // reflect member ptr type 146 | template 147 | requires std::is_member_pointer_v 148 | inline std::optional reflect() { 149 | const auto opt = reflect(type_id::from()); 150 | return opt ? std::make_optional(opt->as_member_ptr()) : std::nullopt; 151 | } 152 | 153 | // reflect other types 154 | template 155 | requires ( 156 | !std::is_class_v 157 | && !std::is_union_v 158 | && !std::is_enum_v 159 | && !std::is_function_v 160 | && !std::is_array_v 161 | && !std::is_member_pointer_v) 162 | inline std::optional reflect() { 163 | return reflect(type_id::from()); 164 | } 165 | 166 | // reflect a type by name 167 | inline std::optional reflect(std::string_view name) { 168 | const auto i = detail::registry::instance().type_from_name(name); 169 | return i ? std::make_optional(reflected_type(*i)) : std::nullopt; 170 | } 171 | 172 | // reflect a function set 173 | inline vector reflect_functions( 174 | std::string_view name) { 175 | const auto info_opt = detail::registry::instance().function_by_name(name); 176 | if (!info_opt) { 177 | return {}; 178 | } 179 | 180 | vector fs; 181 | for (const auto &fos : *(*info_opt)) { 182 | fs.push_back(reflected_function_set(&fos)); 183 | } 184 | return fs; 185 | } 186 | 187 | // reflect a list of functions by type 188 | template 189 | std::optional> reflect_functions() { 190 | auto vec_opt = 191 | detail::registry::instance().function_by_type(type_id::from()); 192 | 193 | if (!vec_opt) { 194 | return std::nullopt; 195 | } 196 | 197 | return detail::transform>( 198 | **vec_opt, 199 | [](const detail::function_type_info &p) { 200 | return reflected_function(&p); 201 | }); 202 | } 203 | 204 | // reflect a single function, returning the first found if it resolves to 205 | // multiple functions 206 | inline std::optional reflect_function( 207 | std::string_view name) { 208 | const auto fs_opt = reflect_functions(name); 209 | return fs_opt.empty() ? std::nullopt : fs_opt.begin()->first(); 210 | } 211 | 212 | // reflect a field by qualified name 213 | // TODO: a little broken, field name lookup is by regex... 214 | inline std::optional reflect_field( 215 | std::string_view name) { 216 | auto pos_qual = name.find("::"); 217 | if (pos_qual == std::string::npos) { 218 | return std::nullopt; 219 | } 220 | 221 | // split into type name, field name 222 | const auto 223 | type_name = name.substr(0, pos_qual), 224 | field_name = name.substr(pos_qual + 2); 225 | 226 | if (type_name.empty() || field_name.empty()) { 227 | return std::nullopt; 228 | } 229 | 230 | const auto rec_opt = reflect(type_name); 231 | if (!rec_opt || !rec_opt->is_record()) { 232 | return std::nullopt; 233 | } 234 | 235 | return rec_opt->as_record().field(field_name); 236 | } 237 | 238 | // TODO: doc 239 | inline std::optional reflect_by_typeid(const std::type_info &ti) { 240 | const auto opt = 241 | detail::registry::instance().type_from_type_id_hash(ti.hash_code()); 242 | return opt ? std::make_optional(reflected_type(*opt)) : std::nullopt; 243 | } 244 | } // namespace archimedes 245 | -------------------------------------------------------------------------------- /include/archimedes/access_specifier.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace archimedes { 6 | enum AccessSpecifier : uint8_t { 7 | PUBLIC, 8 | PROTECTED, 9 | PRIVATE 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /include/archimedes/any.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "errors.hpp" 9 | #include "type_id.hpp" 10 | 11 | #include 12 | 13 | namespace archimedes { 14 | struct reflected_type; 15 | 16 | // generic storage for any type 17 | // TODO: optimize so types with size < sizeof(void*) don't heap allocate 18 | // TODO: store info (functions, etc.) in another structure that is not 19 | // duplicated per-any 20 | struct any { 21 | any() = default; 22 | 23 | any(const any &other) { 24 | *this = other; 25 | } 26 | 27 | any &operator=(const any& other) { 28 | // clean up existing data 29 | this->~any(); 30 | 31 | this->_id = other._id; 32 | this->data_size = other.data_size; 33 | this->data = 34 | this->data_size ? std::malloc(this->data_size) : other.data; 35 | this->is_data_owned = this->data_size != 0; 36 | this->copy_fn = other.copy_fn; 37 | this->dtor_fn = other.dtor_fn; 38 | 39 | if (this->data_size != 0) { 40 | if (!this->copy_fn) { 41 | ARCHIMEDES_FAIL("attempt to copy uncopyable type"); 42 | } 43 | 44 | (*this->copy_fn)(this->data, other.data); 45 | } 46 | 47 | return *this; 48 | } 49 | 50 | any(any&& other) { 51 | *this = std::move(other); 52 | } 53 | 54 | any &operator=(any&& other) { 55 | // clean up existing data 56 | this->~any(); 57 | 58 | this->_id = other._id; 59 | this->data_size = other.data_size; 60 | this->data = other.data; 61 | this->is_data_owned = other.is_data_owned; 62 | this->copy_fn = std::move(other.copy_fn); 63 | this->dtor_fn = std::move(other.dtor_fn); 64 | other.data = nullptr; 65 | other.is_data_owned = false; 66 | 67 | return *this; 68 | } 69 | 70 | ~any(); 71 | 72 | // get id of underlying type 73 | type_id id() const { 74 | return this->_id; 75 | } 76 | 77 | // TODO: remove 78 | // NOTE: this is dangerous as ptr/ref/etc. types are stored *directly* as a 79 | // void pointer. only use this if you know what you're doing! 80 | void *storage() const { 81 | return this->data; 82 | } 83 | 84 | // get data underlying any 85 | std::span bytes() const { 86 | if (this->data_size == 0) { 87 | return std::span( 88 | reinterpret_cast( 89 | const_cast(&this->data)), 90 | sizeof(this->data)); 91 | } else { 92 | return std::span( 93 | reinterpret_cast( 94 | const_cast(this->data)), 95 | this->data_size); 96 | } 97 | } 98 | 99 | // size of storage of this any in bytes 100 | size_t size() const { 101 | return this->data_size == 0 ? sizeof(this->data) : this->data_size; 102 | } 103 | 104 | // returns true if this any is an instance of T 105 | template 106 | bool is() const { 107 | return this->_id == type_id::from(); 108 | } 109 | 110 | // cast underlying value to T 111 | template 112 | T as() const { 113 | if constexpr (std::is_rvalue_reference_v) { 114 | return std::move( 115 | *reinterpret_cast< 116 | std::add_pointer_t< 117 | std::decay_t>>(this->data)); 118 | } else if constexpr (std::is_reference_v) { 119 | return 120 | *reinterpret_cast< 121 | std::add_pointer_t< 122 | std::remove_reference_t>>(this->data); 123 | } else if constexpr (std::is_pointer_v) { 124 | return reinterpret_cast(this->data); 125 | } else { 126 | return *reinterpret_cast(this->data); 127 | } 128 | } 129 | 130 | // get any which is pointer to this any 131 | // if this is a ptr/ref: actually make a void** which is the value 132 | // if this is a value: make an any void* which points to our data 133 | any ptr(type_id id = type_id::none()) const { 134 | return any::make( 135 | this->data_size == 0 ? 136 | reinterpret_cast(&this->data) 137 | : this->data, 138 | id ? id : this->id().add_pointer()); 139 | } 140 | 141 | // create for some reflected type 142 | // initialized with default constructor, returns nullopt if there is no 143 | // default ctor (see runtime/any.cpp for impl) 144 | static result make_for_id(type_id id); 145 | 146 | // create for some reflected type 147 | // initialized with default constructor, returns nullopt if there is no 148 | // default ctor 149 | static result make_for_type( 150 | const reflected_type &type); 151 | 152 | // create any from const reference via copy 153 | // value types which can't be std::move'd get copied 154 | template 155 | requires ( 156 | !std::is_array_v 157 | && !std::is_pointer_v 158 | && !(std::is_reference_v && std::is_pointer_v>) 159 | && !(std::is_reference_v && std::is_array_v>) 160 | && std::is_copy_constructible_v) 161 | static inline any make(const T &t, type_id id = type_id::none()) { 162 | any a = make_of_size( 163 | id ? id : type_id::from(), 164 | sizeof(T), 165 | get_copy(), 166 | get_dtor()); 167 | new (a.data) T(t); 168 | return a; 169 | } 170 | 171 | // create any from rvalue reference 172 | // value types which can be std::move'd get std::move'd 173 | template 174 | requires ( 175 | !std::is_array_v 176 | && !std::is_pointer_v 177 | && !(std::is_reference_v && std::is_pointer_v>) 178 | && !(std::is_reference_v && std::is_array_v>) 179 | && std::is_move_constructible_v 180 | && (std::is_rvalue_reference_v 181 | || std::is_same_v>)) 182 | static inline any make(T &&t, type_id id = type_id::none()) { 183 | using U = std::remove_const_t>; 184 | any a = make_of_size( 185 | id ? id : type_id::from(), 186 | sizeof(T), 187 | get_copy(), 188 | get_dtor()); 189 | new (a.data) T(std::move(t)); 190 | return a; 191 | } 192 | 193 | // create any from pointer 194 | template 195 | requires (std::is_pointer_v || std::is_array_v) 196 | static inline any make(T t, type_id id = type_id::none()) { 197 | if constexpr (std::is_array_v) { 198 | // arrays are converted implicitly to pointers 199 | return make(&t[0], id ? id : type_id::from()); 200 | } 201 | 202 | any a = make_ptr(id ? id : type_id::from()); 203 | a.data = const_cast(reinterpret_cast(t)); 204 | return a; 205 | } 206 | 207 | // references must be stored *explicitly* if the data value is to keep its 208 | // reference qualifiers. otherwise the value is std::move'd or copied 209 | template 210 | static inline any make_reference(T &t, type_id id = type_id::none()){ 211 | return make(&t, id ? id : type_id::from()); 212 | } 213 | 214 | // create reference any 215 | template 216 | static inline any make_reference(const T &t, type_id id = type_id::none()) { 217 | return make(&t, id ? id : type_id::from()); 218 | } 219 | 220 | // create reference any from ptr (creates a T& from T*, NOT T*& from T*) 221 | static inline any make_reference_from_ptr(void *ptr, type_id id) { 222 | return make(ptr, id); 223 | } 224 | 225 | // create none/nil value any 226 | static inline any none() { 227 | return any(); 228 | } 229 | 230 | protected: 231 | template 232 | static std::optional> get_copy() { 233 | if constexpr (std::is_copy_constructible_v) { 234 | return [](void *p, void *o) { 235 | new (p) T(*reinterpret_cast(o)); 236 | }; 237 | } else if constexpr ( 238 | std::is_copy_assignable_v 239 | && std::is_default_constructible_v) { 240 | return [](void *p, void *o) { 241 | new (p) T(); 242 | *reinterpret_cast(p) = *reinterpret_cast(o); 243 | }; 244 | } 245 | 246 | return std::nullopt; 247 | } 248 | 249 | template 250 | static std::optional> get_dtor() { 251 | if constexpr (!std::is_trivially_destructible_v) { 252 | return [](void *p) { reinterpret_cast(p)->~T(); }; 253 | } 254 | 255 | return std::nullopt; 256 | } 257 | 258 | // make any with arbitrarily sized storage 259 | static inline any make_of_size( 260 | type_id id, 261 | size_t data_size = 0, 262 | std::optional> copy = std::nullopt, 263 | std::optional> dtor = std::nullopt) { 264 | any a; 265 | a._id = id; 266 | a.data_size = data_size; 267 | a.copy_fn = copy; 268 | a.dtor_fn = dtor; 269 | 270 | if (a.data_size != 0) { 271 | a.data = std::malloc(data_size); 272 | a.is_data_owned = true; 273 | } else { 274 | a.is_data_owned = false; 275 | } 276 | 277 | return a; 278 | } 279 | 280 | // make any with only data in pointer 281 | static inline any make_ptr(type_id id) { 282 | any a; 283 | a._id = id; 284 | a.data_size = 0; 285 | a.copy_fn = std::nullopt; 286 | a.dtor_fn = std::nullopt; 287 | a.is_data_owned = false; 288 | return a; 289 | } 290 | 291 | // type_id of stored value 292 | type_id _id = type_id::none(); 293 | 294 | // size of data buffer, 0 if data is stored directly in ptr 295 | size_t data_size = 0; 296 | 297 | // data, may not be valid pointer 298 | void *data = nullptr; 299 | 300 | // if true, data must be free'd 301 | bool is_data_owned = false; 302 | 303 | // copy/move from arg 1 to arg 0 304 | std::optional> copy_fn; 305 | 306 | // destructor 307 | std::optional> dtor_fn; 308 | }; 309 | } // end namespace archimedes 310 | -------------------------------------------------------------------------------- /include/archimedes/basic.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // includes version string, basic control macros so that the whole 4 | // "archimedes.hpp" header does not need to be included for TUs which only need 5 | // access to control macros 6 | 7 | #define ARCHIMEDES_VERSION_MAJOR 0 8 | #define ARCHIMEDES_VERSION_MINOR 0 9 | #define ARCHIMEDES_VERSION_BUGFIX 1 10 | 11 | #define ARCHIMEDES_STRINGIFY_IMPL(x) #x 12 | #define ARCHIMEDES_STRINGIFY(x) ARCHIMEDES_STRINGIFY_IMPL(x) 13 | 14 | #define ARCHIMEDES_CONCAT_IMPL(x, y) x ## y 15 | #define ARCHIMEDES_CONCAT(x, y) ARCHIMEDES_CONCAT_IMPL(x, y) 16 | 17 | #define ARCHIMEDES_STRCAT(x, y) x y 18 | 19 | #define ARCHIMEDES_VERSION_STRING ( \ 20 | ARCHIMEDES_STRINGIFY(ARCHIMEDES_VERSION_MAJOR) "." \ 21 | ARCHIMEDES_STRINGIFY(ARCHIMEDES_VERSION_MINOR) "." \ 22 | ARCHIMEDES_STRINGIFY(ARCHIMEDES_VERSION_BUGFIX)) 23 | 24 | // pass symbol to ARCHIMEDES_PRAGMA to use as pragma 25 | // assumes NOT already prefixed with "archimedes" 26 | #define _ARCHIMEDES_PRAGMA_IMPL(...) _Pragma(#__VA_ARGS__) 27 | #define ARCHIMEDES_PRAGMA(x) _ARCHIMEDES_PRAGMA_IMPL(archimedes x) 28 | 29 | #define _ARCHIMEDES_STRING_PRAGMA_IMPL(x) _Pragma(x) 30 | #define ARCHIMEDES_STRING_PRAGMA(x) _ARCHIMEDES_STRING_PRAGMA_IMPL(x) 31 | 32 | // annotate types/functions 33 | #define ARCHIMEDES_ANNOTATE(_a) clang::annotate(_a) 34 | 35 | // annotate types/functions via attribute 36 | #define ARCHIMEDES_ANNOTATE_ATTR(_a) __attribute__((annotate(_a))) 37 | 38 | // types can be annotated with "ARCHIMEDES_NO_REFLECT" to avoid getting 39 | // reflected 40 | #define _ARCHIMEDES_NO_REFLECT_TEXT "_archimedes_no_reflect_" 41 | #define ARCHIMEDES_NO_REFLECT ARCHIMEDES_ANNOTATE(_ARCHIMEDES_NO_REFLECT_TEXT) 42 | 43 | // types can be annotated with "ARCHIMEDES_REFLECT" to ensure their being 44 | // reflected in "explicit enable" mode 45 | #define _ARCHIMEDES_REFLECT_TEXT "_archimedes_reflect_" 46 | #define ARCHIMEDES_REFLECT ARCHIMEDES_ANNOTATE(_ARCHIMEDES_REFLECT_TEXT) 47 | 48 | // types can be explicitly reflected from anywhere via this macro 49 | #define ARCHIMEDES_REFLECT_TYPE_NAME _archimedes_reflect_type_ 50 | #define ARCHIMEDES_REFLECT_TYPE(...) \ 51 | inline __attribute__((unused)) \ 52 | ARCHIMEDES_ANNOTATE_ATTR("_archimedes_reflect_type_") \ 53 | const auto \ 54 | ARCHIMEDES_CONCAT(ARCHIMEDES_REFLECT_TYPE_NAME, __COUNTER__) = \ 55 | archimedes::detail::template_arg<__VA_ARGS__>(); 56 | 57 | // types can be explicitly reflected via regex through this macro 58 | #define _ARCHIMEDES_REFLECT_TYPE_REGEX_TEXT \ 59 | ARCHIMEDES_STRINGIFY(_archimedes_reflect_regex_type_1) 60 | #define ARCHIMEDES_REFLECT_TYPE_REGEX(rx) \ 61 | ARCHIMEDES_PRAGMA(_archimedes_reflect_regex_type_1 rx) 62 | 63 | // use to force template instantiation 64 | #define ARCHIMEDES_FORCE_TEMPLATE_TYPE_INSTANTIATION(...) \ 65 | extern template <> struct __VA_ARGS__; 66 | 67 | // used to define arguments inside of individual files 68 | // fx. "-fplugin-arg-archimedes-exclude-ns-std" 69 | // -> ARCHIMEDES_ARG("exclude-ns-std") 70 | #define ARCHIMEDES_ARG_NAME _archimedes_arg_ 71 | #define ARCHIMEDES_ARG(_a) \ 72 | inline __attribute__((unused)) ARCHIMEDES_ANNOTATE_ATTR(_a) const auto \ 73 | ARCHIMEDES_CONCAT(ARCHIMEDES_ARG_NAME, __COUNTER__) = \ 74 | archimedes::detail::arg(); 75 | 76 | namespace archimedes { 77 | namespace detail { 78 | // used for ARCHIMEDES_ARG implementation 79 | struct arg {}; 80 | 81 | // used for some archimedes pragmas requiring a typename 82 | template 83 | struct template_arg {}; 84 | } // namespace detail 85 | } // namespace archimedes 86 | -------------------------------------------------------------------------------- /include/archimedes/cast.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "errors.hpp" 6 | #include "any.hpp" 7 | 8 | namespace archimedes { 9 | struct reflected_record_type; 10 | struct any; 11 | 12 | // a "baked" cast function which casts from one type to another 13 | // see archimedes::make_case_fn 14 | using cast_fn = std::function; 15 | 16 | namespace detail { 17 | // get byte difference between a cast from a ptr_t to type "to" 18 | result cast_diff_impl( 19 | const any &ptr_t, 20 | const reflected_record_type &from, 21 | const reflected_record_type &to, 22 | bool only_up = false); 23 | 24 | // make a function which, when called with a pointer of type ptr_t "from" will 25 | // cast it to "to" 26 | result make_cast_fn_impl( 27 | const any &ptr_t, 28 | const reflected_record_type &from, 29 | const reflected_record_type &to); 30 | 31 | // see cast() 32 | result cast_impl( 33 | const any &ptr_t, 34 | const reflected_record_type &from, 35 | const reflected_record_type &to); 36 | 37 | // TODO: RTTI optional 38 | // TODO: doc 39 | result dyncast_impl( 40 | const any &ptr_t, 41 | const std::type_info &ti, 42 | const reflected_record_type &to); 43 | 44 | } // namespace detail 45 | 46 | // get byte difference between a cast from a ptr_t to type "to" 47 | inline result cast_diff( 48 | const any &ptr_t, 49 | const reflected_record_type &from, 50 | const reflected_record_type &to) { 51 | return detail::cast_diff_impl(ptr_t, from, to); 52 | } 53 | 54 | // cast some pointer T of record type "from" to "to", dynamically, statically, 55 | // or however the inheritance tree needs to be traversed 56 | // NOTE: will break if multiple instances of the same (non-virtual) base are 57 | // found in the inheritance tree 58 | template < 59 | typename T, 60 | typename V = 61 | std::conditional_t< 62 | std::is_const_v>, 63 | const void *, 64 | void*>> 65 | requires std::is_pointer_v 66 | result cast( 67 | T ptr_t, 68 | const reflected_record_type &from, 69 | const reflected_record_type &to) { 70 | const auto res = detail::cast_impl(any::make(ptr_t), from, to); 71 | if (res) { 72 | return const_cast(*res); 73 | } 74 | return res.unwrap_error(); 75 | } 76 | 77 | inline result cast( 78 | const any &ptr, 79 | const reflected_record_type &from, 80 | const reflected_record_type &to) { 81 | const auto res = detail::cast_impl(ptr, from, to); 82 | return 83 | result::make( 84 | res, 85 | [&]() { 86 | return any::make(*res); 87 | }, 88 | [&]() { 89 | return res.unwrap_error(); 90 | }); 91 | } 92 | 93 | // make a function which will cast Ts (of type "from") to some type "to" 94 | template 95 | requires std::is_pointer_v 96 | result make_cast_fn( 97 | T ptr_t, 98 | const reflected_record_type &from, 99 | const reflected_record_type &to) { 100 | return detail::make_cast_fn_impl(any::make(ptr_t), from, to); 101 | } 102 | 103 | // make a function which will cast ptrs (of type "from") to some type "to" 104 | inline result make_cast_fn( 105 | const any &ptr, 106 | const reflected_record_type &from, 107 | const reflected_record_type &to) { 108 | return detail::make_cast_fn_impl(ptr, from, to); 109 | } 110 | } // end namespace archimedes 111 | -------------------------------------------------------------------------------- /include/archimedes/errors.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // exceptions can be enabled with (this replaces assert()-s) 4 | // #define ARCHIMEDES_USE_EXCEPTIONS 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef ARCHIMEDES_USE_EXCEPTIONS 12 | #include 13 | #include 14 | 15 | namespace archimedes { 16 | struct exception : public std::runtime_error { 17 | explicit exception(std::string_view message) 18 | : std::runtime_error(std::string(message)) {} 19 | }; 20 | } 21 | #endif 22 | 23 | // end user can define assert function is they so choose 24 | #ifndef ARCHIMEDES_ASSERT 25 | #define ARCHIMEDES_ASSERT assert 26 | #endif 27 | 28 | // ARCHIMEDES_FAIL to fail directly 29 | #ifdef ARCHIMEDES_USE_EXCEPTIONS 30 | #define ARCHIMEDES_FAIL(_m) throw new archimedes::exception((_m)) 31 | #else 32 | #define ARCHIMEDES_FAIL(_m) ARCHIMEDES_ASSERT(false && (_m)) 33 | #endif 34 | 35 | namespace archimedes { 36 | // static field constexpr error codes 37 | enum class constexpr_error { 38 | IS_NOT_CONSTEXPR 39 | }; 40 | 41 | // template parameter error codes 42 | enum class template_parameter_error { 43 | NO_VALUE 44 | }; 45 | 46 | // field get error codes 47 | enum class field_error { 48 | IS_NOT_BIT_FIELD, 49 | IS_BIT_FIELD, 50 | COULD_NOT_CAST, 51 | WRONG_TYPE 52 | }; 53 | 54 | // cast error codes 55 | enum class cast_error { 56 | NOT_FOUND, 57 | COULD_NOT_REFLECT 58 | }; 59 | 60 | // "any" error codes 61 | enum class any_error { 62 | COULD_NOT_REFLECT, 63 | NO_DEFAULT_CTOR, 64 | INVALID_TYPE 65 | }; 66 | 67 | // basic result type 68 | template 69 | struct result : public std::variant { 70 | using base = std::variant; 71 | using base::base; 72 | 73 | static constexpr auto 74 | INDEX_ERROR = 1, 75 | INDEX_VALUE = 0; 76 | 77 | // returns true if not error type 78 | operator bool() const { return this->index() == INDEX_VALUE; } 79 | 80 | // implicit conversion to optional 81 | operator std::optional() const { 82 | return this->is_value() ? std::make_optional(**this) : std::nullopt; 83 | } 84 | 85 | const T &operator*() const { 86 | if (this->is_error()) { 87 | ARCHIMEDES_FAIL("attempt to unwrap an error result"); 88 | } 89 | return std::get(*this); 90 | } 91 | 92 | T &operator*() { 93 | if (this->is_error()) { 94 | ARCHIMEDES_FAIL("attempt to unwrap an error result"); 95 | } 96 | return std::get(*this); 97 | } 98 | 99 | const T *operator->() const 100 | requires (!std::is_pointer_v) { 101 | return &**this; 102 | } 103 | 104 | const std::remove_pointer_t *operator->() const 105 | requires (std::is_pointer_v) { 106 | return **this; 107 | } 108 | 109 | T *operator->() 110 | requires (!std::is_pointer_v) { 111 | return &**this; 112 | } 113 | 114 | std::remove_pointer_t *operator->() 115 | requires (std::is_pointer_v) { 116 | return **this; 117 | } 118 | 119 | bool is_error() const { return this->index() == INDEX_ERROR; } 120 | bool is_value() const { return this->index() == INDEX_VALUE; } 121 | 122 | const T &unwrap() const { return **this; } 123 | T &unwrap() { return **this; } 124 | 125 | const E &unwrap_error() const { 126 | if (this->is_value()) { 127 | #ifdef ARCHIMEDES_USE_EXCEPTIONS 128 | throw new exception("attempt to unwrap a result as an error"); 129 | #else 130 | ARCHIMEDES_ASSERT(false&&"attempt to unwrap a result as an error"); 131 | #endif 132 | } 133 | 134 | return std::get(*this); 135 | } 136 | 137 | // make from function if true/error code if false 138 | template 139 | requires std::is_convertible_v, T> 140 | static result make(bool e, F if_true, E &&if_false) { 141 | return e ? 142 | result(T(if_true())) 143 | : result(E(std::forward(if_false))); 144 | } 145 | 146 | // make from function if true/function if false 147 | template 148 | requires 149 | (std::is_convertible_v, T> 150 | && std::is_convertible_v, E>) 151 | static result make(bool e, F if_true, G if_false) { 152 | return e ? result(T(if_true())) : result(E(if_false())); 153 | } 154 | 155 | // make from value if true/value if false 156 | template 157 | requires 158 | (std::is_convertible_v 159 | && std::is_convertible_v) 160 | static result make(bool e, U &&if_true, V &&if_false) { 161 | return e ? 162 | result(T(U(std::forward(if_true)))) 163 | : result(E(V(std::forward(if_false)))); 164 | } 165 | }; 166 | } 167 | -------------------------------------------------------------------------------- /include/archimedes/invoke.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "any.hpp" 5 | 6 | namespace archimedes { 7 | struct invoke_result; 8 | namespace detail { 9 | // signature for generated invoker functions 10 | using invoker_ptr = invoke_result(*)(std::span); 11 | } 12 | 13 | // result of function invoke 14 | struct invoke_result { 15 | enum Enum { 16 | SUCCESS = 0, 17 | BAD_ARG_COUNT = 1, 18 | NO_ACCESS = 2 19 | }; 20 | 21 | invoke_result() = default; 22 | 23 | // intentional implicit conversion 24 | invoke_result(Enum status, any &&result = any::none()) 25 | : _status(status), 26 | _result(std::move(result)) {} 27 | 28 | bool is_success() const { 29 | return this->_status == SUCCESS; 30 | } 31 | 32 | operator bool() const { 33 | return this->is_success(); 34 | } 35 | 36 | Enum status() const { 37 | return this->_status; 38 | } 39 | 40 | const any &result() const { 41 | return this->_result; 42 | } 43 | 44 | any &result() { 45 | return this->_result; 46 | } 47 | 48 | const any &operator*() const { 49 | return this->_result; 50 | } 51 | 52 | any &operator*() { 53 | return this->_result; 54 | } 55 | 56 | auto operator->() const { 57 | return &**this; 58 | } 59 | 60 | auto operator->() { 61 | return &**this; 62 | } 63 | 64 | // void success 65 | static inline auto success() { 66 | return invoke_result(SUCCESS); 67 | } 68 | 69 | static inline auto success(any &&a) { 70 | return invoke_result(SUCCESS, std::move(a)); 71 | } 72 | 73 | private: 74 | Enum _status; 75 | any _result; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /include/archimedes/regex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace archimedes { 7 | // escape a string literal for use in a regex 8 | inline std::string escape_for_regex(std::string_view s) { 9 | constexpr char meta_chars[] = R"(\.^$-+()[]{}|?*)"; 10 | std::string out; 11 | out.reserve(s.size()); 12 | for (auto ch : s) { 13 | if (std::strchr(meta_chars, ch)) { 14 | out.push_back('\\'); 15 | } 16 | out.push_back(ch); 17 | } 18 | return out; 19 | } 20 | 21 | // returns true if the regex matches the whole string 22 | bool regex_matches(std::string_view regex, std::string_view string); 23 | } 24 | -------------------------------------------------------------------------------- /include/archimedes/registry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "type_id.hpp" 7 | #include "type_info.hpp" 8 | #include "types.hpp" 9 | #include "serialize.hpp" 10 | #include "ds.hpp" 11 | 12 | namespace archimedes { 13 | // user registered callback for registering type collisions 14 | using collision_callback = 15 | std::function; 16 | 17 | namespace detail { 18 | // global type registry 19 | struct registry { 20 | // implemented in runtime/archimedes.cpp (must be linked!) 21 | static registry &instance(); 22 | 23 | // dtor 24 | ~registry() { 25 | this->_loaded = false; 26 | } 27 | 28 | // returns true if registry is loaded 29 | bool loaded() const { 30 | return this->_loaded; 31 | } 32 | 33 | // load all loaders 34 | void load(); 35 | 36 | // get matching types from annotations 37 | vector find_by_annotations( 38 | std::span annotations); 39 | 40 | // get all types 41 | vector all_types() const; 42 | 43 | // get type_info by typeid(...).hash_code() 44 | std::optional type_from_type_id_hash(size_t hash) const; 45 | 46 | // get type_info by id 47 | std::optional type_from_id(type_id id) const; 48 | 49 | // get type_info by name 50 | std::optional type_from_name( 51 | std::string_view name) const; 52 | 53 | // get function by name 54 | std::optional*> function_by_name( 55 | std::string_view name) const; 56 | 57 | // get function by type 58 | std::optional*> function_by_type( 59 | type_id id) const; 60 | 61 | // callback for type collision 62 | void set_collision_callback(collision_callback &&tcc); 63 | 64 | // NOTE: INTERNAL USE ONLY! 65 | // called from each archimedes translation unit to load stored data 66 | void load_module( 67 | const vector> &dyncasts, 68 | const vector &constexpr_values, 69 | const vector &invokers, 70 | const vector &template_param_values, 71 | const vector &type_id_hashes, 72 | std::span functions_data, 73 | std::span types_data, 74 | std::span typedefs_data, 75 | std::span aliases_data, 76 | std::span usings_data); 77 | 78 | // load a set of types into the registry 79 | // TODO: std::move values 80 | void load_types(const vector is); 81 | 82 | // load a set of function overloads into the registry 83 | // TODO: move from map 84 | void load_functions( 85 | const name_map &fs); 86 | 87 | // expand namespaces according to namespace_aliases 88 | std::string expand_namespaces(std::string_view name) const; 89 | 90 | // invoke a function F with all possible expansions of the specified name 91 | // fx. if we have namespace a::b which uses namespace a::b::c::d, f is 92 | // invoked for f("a::b::") and f("a::b::c::d::") if name is 93 | // prefixed by "a::b". 94 | // F returns true if it has found what it is looking for, function returns 95 | // true if and F returned true 96 | template 97 | requires std::is_invocable_r_v 98 | bool permute_namespaces(std::string_view name, F &&f) const { 99 | std::function g; 100 | g = [this, &f, &g](std::string_view n) { 101 | if (f(n)) { 102 | return true; 103 | } 104 | 105 | for (const auto &nu : this->namespace_usings) { 106 | if (n.starts_with(nu.containing) 107 | && !n.starts_with(nu.used)) { 108 | // replace with used namespace, try again 109 | auto replaced = 110 | nu.used 111 | + std::string(n.substr(nu.containing.length())); 112 | if (g(replaced)) { 113 | return true; 114 | } 115 | break; 116 | } 117 | } 118 | 119 | return false; 120 | }; 121 | return g(name); 122 | } 123 | 124 | mutable bool _loaded = false; 125 | vector> loaders; 126 | 127 | // backing storage containers 128 | map types_by_type_id_hashes; 129 | map types_by_id; 130 | map> functions_by_type; 131 | map> functions_by_name; 132 | vector namespace_aliases; 133 | vector namespace_usings; 134 | vector typedefs; 135 | 136 | // collision callbacks 137 | collision_callback tcc = 138 | [](auto _0, auto _1) -> reflected_type { 139 | std::cout 140 | << "collision between " 141 | << _0.name() 142 | << "/" 143 | << _0.mangled_name() 144 | << " " 145 | << _1.name() 146 | << "/" 147 | << _1.mangled_name() 148 | << std::endl; 149 | ARCHIMEDES_FAIL("type collision"); 150 | }; 151 | }; 152 | } // namespace detail 153 | } // namespace archimedes 154 | -------------------------------------------------------------------------------- /include/archimedes/type_id.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "errors.hpp" 8 | 9 | #if defined(__clang__) || defined(__GNUC__) 10 | #define ARCHIMEDES_PRETTY_FUNCTION __PRETTY_FUNCTION__ 11 | #elif defined(_MSC_VER) 12 | #define ARCHIMEDES_PRETTY_FUNCTION __FUNCSIG__ 13 | #else 14 | #error "unsupported compiler (pretty function)" 15 | #endif 16 | 17 | namespace archimedes { 18 | namespace detail { 19 | struct type_info; 20 | 21 | // type_name adapted from: 22 | // https://bitwizeshift.github.io/posts/2021/03/09/getting-an-unmangled-type-name-at-compile-time/ 23 | template 24 | constexpr auto substring_as_array( 25 | std::string_view s, 26 | std::index_sequence) { 27 | return std::array { s[Is]... }; 28 | } 29 | 30 | template 31 | constexpr auto type_name_array() { 32 | #if defined(__clang__) 33 | constexpr auto prefix = std::string_view{"[T = "}; 34 | constexpr auto suffix = std::string_view{"]"}; 35 | #elif defined(__GNUC__) 36 | constexpr auto prefix = std::string_view{"with T = "}; 37 | constexpr auto suffix = std::string_view{"]"}; 38 | #elif defined(_MSC_VER) 39 | constexpr auto prefix = std::string_view{"type_name_array<"}; 40 | constexpr auto suffix = std::string_view{">(void)"}; 41 | #else 42 | #error "unsupported compiler (type_name_array)" 43 | #endif 44 | 45 | constexpr auto function = std::string_view { ARCHIMEDES_PRETTY_FUNCTION }; 46 | constexpr auto start = function.find(prefix) + prefix.size(); 47 | constexpr auto end = function.rfind(suffix); 48 | 49 | static_assert(start < end); 50 | 51 | constexpr auto name = function.substr(start, (end - start)); 52 | return substring_as_array(name, std::make_index_sequence{}); 53 | } 54 | 55 | template 56 | struct type_name_holder { 57 | static inline constexpr auto value = type_name_array(); 58 | }; 59 | 60 | constexpr uint64_t fnv1a_partial(uint64_t partial, std::string_view s) { 61 | if (s.length() == 0) { 62 | return 0; 63 | } 64 | 65 | partial = (partial ^ s[0]) * 1099511628211u; 66 | return s.length() == 1 ? partial : fnv1a_partial(partial, s.substr(1)); 67 | } 68 | 69 | // constexpr FNV1a hash 70 | constexpr uint64_t fnv1a(std::string_view s) { 71 | return fnv1a_partial(14695981039346656037u, s); 72 | } 73 | 74 | constexpr uint64_t fnv1a_append(uint64_t partial, std::string_view s) { 75 | return fnv1a_partial(partial, s); 76 | } 77 | } // namespace detail 78 | 79 | // get non-mangled type name for T 80 | template 81 | constexpr auto type_name() { 82 | constexpr auto &value = detail::type_name_holder::value; 83 | return std::string_view { value.data(), value.size() }; 84 | } 85 | 86 | // represents a unique type id 87 | struct type_id { 88 | using internal = uint64_t; 89 | 90 | static constexpr auto NO_ID = 91 | std::numeric_limits::max(); 92 | 93 | // type id from type 94 | template 95 | static constexpr inline type_id from() { 96 | return type_id(detail::fnv1a(type_name())); 97 | } 98 | 99 | // type id from name 100 | static constexpr inline type_id from(std::string_view name) { 101 | return type_id(detail::fnv1a(name)); 102 | } 103 | 104 | template 105 | static constexpr inline type_id from() { 106 | return type_id(ID); 107 | } 108 | 109 | static constexpr inline type_id from(internal id) { 110 | return type_id(id); 111 | } 112 | 113 | static constexpr inline type_id none() { 114 | return type_id(NO_ID); 115 | } 116 | 117 | // must be public (sadly) for type to be "structural" for use in templates 118 | internal _id_internal = NO_ID; 119 | 120 | explicit constexpr type_id(internal i) 121 | : _id_internal(i) {} 122 | 123 | constexpr type_id() 124 | : _id_internal(NO_ID) {} 125 | 126 | constexpr internal value() const { 127 | return _id_internal; 128 | } 129 | 130 | // differing implementations according to runtime! plugin uses a lookup 131 | // though global context, runtime uses lookup in global type table 132 | const detail::type_info &operator*() const; 133 | 134 | const detail::type_info *operator->() const { 135 | return &**this; 136 | } 137 | 138 | constexpr bool operator==(const type_id &rhs) const { 139 | return this->_id_internal == rhs._id_internal; 140 | } 141 | 142 | constexpr auto operator<=>(const type_id &rhs) const { 143 | return this->_id_internal <=> rhs._id_internal; 144 | } 145 | 146 | operator bool() const; 147 | 148 | // abuse our hash function to append a pointer to the type id 149 | constexpr type_id add_pointer() const { 150 | return type_id(detail::fnv1a_append(this->_id_internal, " *")); 151 | } 152 | }; 153 | } // namespace archimedes 154 | 155 | // hash implementation 156 | namespace std { 157 | template<> 158 | struct hash { 159 | size_t operator() (const archimedes::type_id &t) const { 160 | return static_cast(t.value()); 161 | } 162 | }; 163 | } // namespace std 164 | -------------------------------------------------------------------------------- /include/archimedes/type_kind.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace archimedes { 7 | // TODO: VLAs? 8 | // TODO: long double 9 | // TODO: u128, i128 10 | enum type_kind : uint8_t { 11 | UNKNOWN = 0, 12 | VOID, 13 | BOOL, 14 | CHAR, 15 | U_CHAR, 16 | U_SHORT, 17 | U_INT, 18 | U_LONG, 19 | U_LONG_LONG, 20 | I_CHAR, 21 | I_SHORT, 22 | I_INT, 23 | I_LONG, 24 | I_LONG_LONG, 25 | FLOAT, 26 | DOUBLE, 27 | ENUM, 28 | FUNC, 29 | ARRAY, 30 | STRUCT, 31 | UNION, 32 | PTR, 33 | REF, 34 | RREF, 35 | MEMBER_PTR 36 | }; 37 | 38 | template 39 | type_kind get_kind() { 40 | if constexpr (std::is_pointer_v) { 41 | return PTR; 42 | } else if constexpr(std::is_rvalue_reference_v) { 43 | return RREF; 44 | } else if constexpr (std::is_reference_v) { 45 | return REF; 46 | } else if constexpr (std::is_member_pointer_v) { 47 | return MEMBER_PTR; 48 | } else if constexpr (std::is_array_v) { 49 | return ARRAY; 50 | } else if constexpr (std::is_class_v) { 51 | return STRUCT; 52 | } else if constexpr (std::is_function_v) { 53 | return FUNC; 54 | } else if constexpr (std::is_enum_v) { 55 | return ENUM; 56 | } else if constexpr (std::is_union_v) { 57 | return UNION; 58 | } else if constexpr (std::is_same_v) { 59 | return VOID; 60 | } else if constexpr (std::is_same_v) { 61 | return U_CHAR; 62 | } else if constexpr (std::is_same_v) { 63 | return U_SHORT; 64 | } else if constexpr (std::is_same_v) { 65 | return U_INT; 66 | } else if constexpr (std::is_same_v) { 67 | return U_LONG; 68 | } else if constexpr (std::is_same_v) { 69 | return U_LONG_LONG; 70 | } else if constexpr (std::is_same_v) { 71 | return I_CHAR; 72 | } else if constexpr (std::is_same_v) { 73 | return I_SHORT; 74 | } else if constexpr (std::is_same_v) { 75 | return I_INT; 76 | } else if constexpr (std::is_same_v) { 77 | return I_LONG; 78 | } else if constexpr (std::is_same_v) { 79 | return I_LONG_LONG; 80 | } else if constexpr (std::is_same_v) { 81 | return BOOL; 82 | } else if constexpr (std::is_same_v) { 83 | return FLOAT; 84 | } else if constexpr (std::is_same_v) { 85 | return DOUBLE; 86 | } else if constexpr (std::is_same_v) { 87 | return CHAR; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /plugin/compile.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "plugin.hpp" 20 | #include "util.hpp" 21 | 22 | namespace archimedes { 23 | // result of compile() compiler instance and module it has generated 24 | struct compiled_module { 25 | std::unique_ptr compiler; 26 | std::unique_ptr module; 27 | }; 28 | 29 | // invoke another compiler instance on some code 30 | // args_ is arguments to pass to compiler instance 31 | // out is final output path for object file 32 | // code is actual C++ code to compile 33 | // optional error stream errs in case std::nullopt is returned 34 | inline bool compile( 35 | const std::vector &args_, 36 | const std::filesystem::path &out, 37 | std::string_view code, 38 | llvm::raw_ostream &errs = llvm::errs()) { 39 | PLUGIN_LOG("compiling..."); 40 | 41 | // get temp filename in same directory as output 42 | const auto dir = out.parent_path(); 43 | auto tmp_name = 44 | fmt::format( 45 | "{:08X}_{}.tmp", 46 | hash(&dir), 47 | out.filename().string()); 48 | while (std::filesystem::exists(dir / tmp_name)) { 49 | tmp_name = char('A' + std::rand() % 52) + tmp_name; 50 | } 51 | 52 | // write code contents to file 53 | const auto tmp_path = dir / tmp_name; 54 | PLUGIN_LOG("writing code to {}", tmp_path.string()); 55 | std::FILE *f = std::fopen(tmp_path.c_str(), "w"); 56 | std::fputs(std::string(code).c_str(), f); 57 | std::fclose(f); 58 | 59 | // append output, inputs to args 60 | auto args = args_; 61 | args.push_back("-o"); 62 | args.push_back(out.c_str()); 63 | args.push_back(tmp_path.c_str()); 64 | PLUGIN_LOG("invoking with args {}", fmt::join(args, " ")); 65 | 66 | bool result = false; 67 | { 68 | // invoke compiler on temp file 69 | auto compiler = 70 | std::make_unique(); 71 | auto invocation = 72 | std::make_shared(); 73 | auto diags = 74 | llvm::makeIntrusiveRefCnt(); 75 | auto diag_options = 76 | llvm::makeIntrusiveRefCnt(); 77 | auto diag_printer = 78 | std::make_unique( 79 | errs, 80 | diag_options.get()); 81 | 82 | // allocate via new, ownership is transferred to compiler instance 83 | auto diag_engine = 84 | new clang::DiagnosticsEngine( 85 | diags, 86 | diag_options); 87 | diag_engine->setClient(diag_printer.get(), false); 88 | 89 | // TODO: why do these warnings come up in the first place? 90 | constexpr auto IGNORED_WARNINGS = 91 | make_array( 92 | "expansion-to-defined", 93 | "nullability-completeness"); 94 | 95 | for (const auto &w : IGNORED_WARNINGS) { 96 | diag_engine->setSeverityForGroup( 97 | clang::diag::Flavor::WarningOrError, 98 | w, 99 | clang::diag::Severity::Ignored); 100 | } 101 | 102 | PLUGIN_LOG("creating invocation"); 103 | clang::CompilerInvocation::CreateFromArgs( 104 | *invocation, 105 | detail::transform>( 106 | args, 107 | [](const auto &view) { 108 | return &view[0]; 109 | }), 110 | *diag_engine); 111 | 112 | compiler->setInvocation(invocation); 113 | compiler->setDiagnostics(diag_engine); 114 | 115 | // EmitObjAction will use the default specified "-o" argument as its 116 | // output path 117 | PLUGIN_LOG("invoking"); 118 | auto action = 119 | std::unique_ptr( 120 | new clang::EmitObjAction()); 121 | compiler->ExecuteAction(*action); 122 | result = static_cast(action->takeModule()); 123 | } 124 | 125 | std::filesystem::remove(tmp_path); 126 | return result; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /plugin/emit.cpp: -------------------------------------------------------------------------------- 1 | #include "emit.hpp" 2 | #include "plugin.hpp" 3 | #include "invoker.hpp" 4 | 5 | #include 6 | 7 | #include 8 | 9 | using namespace archimedes; 10 | using namespace archimedes::detail; 11 | 12 | // emit serialized data as an std::span 13 | // F(std::ostream) is called to populate data 14 | template 15 | requires (requires (F f, std::ostream &os) { f(os); }) 16 | static std::string emit_serialized( 17 | std::string_view name, 18 | F &&f) { 19 | struct outbuf : public std::streambuf { 20 | vector &buf; 21 | 22 | outbuf(vector &buf) : buf(buf) {} 23 | 24 | int_type overflow(int_type c) override { 25 | if (c == EOF) { 26 | return EOF; 27 | } 28 | 29 | buf.push_back(static_cast(c)); 30 | return c; 31 | } 32 | }; 33 | 34 | vector data; 35 | outbuf ob(data); 36 | std::ostream os(&ob); 37 | f(os); 38 | return fmt::format(R"( 39 | static const uint8_t {0}_internal[] = {1}; 40 | static const std::span {0} = {{ {0}_internal }}; 41 | )", 42 | name, 43 | emit_data(std::span { data }), 44 | data.size()); 45 | } 46 | 47 | // emit a vector of Ts with specified name and type name to module 48 | // F(T) is cpp-ifier for Ts 49 | template 50 | requires (requires (F f, T t) {{ f(t) } -> std::same_as; }) 51 | static std::string emit_module_vector( 52 | std::string_view name, 53 | std::string_view type_name, 54 | const vector &vs, 55 | F &&f) { 56 | return fmt::format(R"( 57 | static const archimedes::vector<{}> {} = {{{}}}; 58 | )", 59 | type_name, name, emit_vector(vs, std::forward(f))); 60 | } 61 | 62 | std::string archimedes::emit(Context &ctx) { 63 | std::string output; 64 | 65 | // TODO: optional RTTI 66 | // preamble 67 | output += 68 | fmt::format(R"( 69 | #include "{}" 70 | #include 71 | // ignored diagnostics in generated code 72 | #pragma clang diagnostic ignored "-Wmain" 73 | #pragma clang diagnostic ignored "-Wimplicitly-unsigned-literal" 74 | )", 75 | fs::relative( 76 | ctx.header_path, 77 | ctx.output_path.parent_path()).string()); 78 | 79 | // emit invokers 80 | for (const auto &i : ctx.invokers) { 81 | output += emit_invoker(ctx, *i); 82 | } 83 | 84 | // traverse context data and assign indices 85 | vector> dyncasts; 86 | vector constexpr_exprs; 87 | vector invokers; 88 | vector template_param_exprs; 89 | vector type_id_hash_exprs; 90 | 91 | // true if dyncasts can be generated between a and b 92 | const auto can_dyncast = 93 | [&ctx](const type_info &a, const type_info &b) { 94 | const auto 95 | *rd_a = clang::dyn_cast(a.internal->decl), 96 | *rd_b = clang::dyn_cast(b.internal->decl); 97 | return rd_a->isPolymorphic() 98 | && rd_b->isPolymorphic() 99 | && can_emit_type(ctx, a.internal->type) 100 | && can_emit_type(ctx, b.internal->type); 101 | }; 102 | 103 | // register invokers for the function set 104 | const auto register_invokers = 105 | [&](function_overload_set &fos) { 106 | for (auto &f : fos.functions) { 107 | if (!f.internal->invoker) { 108 | f.invoker_index = NO_ARRAY_INDEX; 109 | continue; 110 | } 111 | 112 | f.invoker_index = invokers.size(); 113 | invokers.push_back(f.internal->invoker); 114 | } 115 | }; 116 | 117 | // record types 118 | for (const auto &t : ctx.types) { 119 | if ((t->kind != STRUCT && t->kind != UNION) 120 | || !t->internal->is_resolved) { 121 | continue; 122 | } 123 | 124 | // only emit type_id(...) exprs for struct/union types 125 | if (can_emit_type(ctx, t->internal->type)) { 126 | t->type_id_hash_index = type_id_hash_exprs.size(); 127 | type_id_hash_exprs.push_back( 128 | fmt::format( 129 | "typeid({}).hash_code()", 130 | get_full_type_name( 131 | ctx, *t->internal->type, TNF_ARRAYS_TO_POINTERS))); 132 | } 133 | 134 | for (auto &p : t->record.template_parameters) { 135 | if (p.is_typename) { 136 | continue; 137 | } 138 | 139 | // TODO: do we need to register? 140 | ctx.register_emitted_type(*p.type.id->internal->type); 141 | p.value_index = template_param_exprs.size(); 142 | template_param_exprs.push_back(&p.internal->value_expr); 143 | } 144 | 145 | for (auto &b : t->record.bases) { 146 | if (!can_dyncast(*b.parent_id, *b.id)) { 147 | b.dyncast_up_index = NO_ARRAY_INDEX; 148 | b.dyncast_down_index = NO_ARRAY_INDEX; 149 | continue; 150 | } 151 | 152 | b.dyncast_up_index = dyncasts.size(); 153 | dyncasts.push_back( 154 | std::make_tuple( 155 | &*b.parent_id, 156 | &*b.id)); 157 | b.dyncast_down_index = dyncasts.size(); 158 | dyncasts.push_back( 159 | std::make_tuple( 160 | &*b.id, 161 | &*b.parent_id)); 162 | } 163 | 164 | for (auto &[_, f] : t->record.static_fields) { 165 | if (!f.is_constexpr 166 | || f.internal->constexpr_expr.empty()) { 167 | f.constexpr_value_index = NO_ARRAY_INDEX; 168 | continue; 169 | } 170 | 171 | ctx.register_emitted_type(*t->internal->type); 172 | f.constexpr_value_index = constexpr_exprs.size(); 173 | constexpr_exprs.push_back(&f.internal->constexpr_expr); 174 | } 175 | 176 | for (auto &[_, fos] : t->record.functions) { 177 | register_invokers(fos); 178 | } 179 | } 180 | 181 | // free functions 182 | for (auto &[_, fos] : ctx.functions) { 183 | register_invokers(fos); 184 | } 185 | 186 | constexpr auto 187 | DYNCASTS_NAME = "_module_dyncasts", 188 | CONSTEXPR_VALUES_NAME = "_module_constexpr_values", 189 | INVOKERS_NAME = "_module_invokers", 190 | TEMPLATE_PARAM_VALUES_NAME = "_module_template_param_values", 191 | TYPE_ID_HASHES_NAME = "_type_id_hashes"; 192 | 193 | // emit dyncast array 194 | output += 195 | emit_module_vector( 196 | DYNCASTS_NAME, 197 | "std::function", 198 | dyncasts, 199 | [&ctx](const auto &t) -> std::string { 200 | const auto &[from, to] = t; 201 | ctx.register_emitted_type(*from->internal->type); 202 | ctx.register_emitted_type(*to->internal->type); 203 | return 204 | fmt::format(R"( 205 | ([](void *p) -> void* {{ 206 | return dynamic_cast<{}*>( 207 | reinterpret_cast<{}*>(p)); 208 | }}) 209 | )", 210 | emit_type(ctx, *to->internal->type), 211 | emit_type(ctx, *from->internal->type)); 212 | }); 213 | 214 | // emit constexpr value array 215 | output += 216 | emit_module_vector( 217 | CONSTEXPR_VALUES_NAME, 218 | NAMEOF_TYPE(archimedes::any), 219 | constexpr_exprs, 220 | [](const std::string *s) -> std::string { 221 | return emit_any(*s); 222 | }); 223 | 224 | // emit invoker array 225 | output += 226 | emit_module_vector( 227 | INVOKERS_NAME, 228 | "archimedes::detail::invoker_ptr", 229 | invokers, 230 | [](const Invoker *i) -> std::string { 231 | return fmt::format("&{}", i->generated_name()); 232 | }); 233 | 234 | // emit template parameter value array 235 | output += 236 | emit_module_vector( 237 | TEMPLATE_PARAM_VALUES_NAME, 238 | NAMEOF_TYPE(archimedes::any), 239 | template_param_exprs, 240 | [](const std::string *s) -> std::string { 241 | return emit_any(*s); 242 | }); 243 | 244 | // TODO: optional RTTI 245 | // emit type index hashes array 246 | output += 247 | emit_module_vector( 248 | TYPE_ID_HASHES_NAME, 249 | "size_t", 250 | type_id_hash_exprs, 251 | [](const std::string &s) { return s; }); 252 | 253 | constexpr auto 254 | FUNCTIONS_NAME = "_module_functions", 255 | TYPES_NAME = "_module_types", 256 | TYPEDEFS_NAME = "_module_typedefs", 257 | NAMESPACE_ALIASES_NAME = "_module_aliases", 258 | NAMESPACE_USINGS_NAME = "_module_usings"; 259 | 260 | output += 261 | emit_serialized( 262 | FUNCTIONS_NAME, 263 | [&](std::ostream &os) { 264 | serialize(os, ctx.functions); 265 | }); 266 | 267 | output += 268 | emit_serialized( 269 | TYPES_NAME, 270 | [&](std::ostream &os) { 271 | serialize(os, ctx.types); 272 | }); 273 | 274 | output += 275 | emit_serialized( 276 | TYPEDEFS_NAME, 277 | [&](std::ostream &os) { 278 | serialize(os, ctx.typedefs); 279 | }); 280 | 281 | output += 282 | emit_serialized( 283 | NAMESPACE_ALIASES_NAME, 284 | [&](std::ostream &os) { 285 | serialize(os, ctx.namespace_aliases); 286 | }); 287 | 288 | output += 289 | emit_serialized( 290 | NAMESPACE_USINGS_NAME, 291 | [&](std::ostream &os) { 292 | serialize(os, ctx.namespace_usings); 293 | }); 294 | 295 | // emit loader 296 | output += fmt::format(R"( 297 | static const auto _module_loader = 298 | ({}::instance().load_module({}, {}, {}, {}, {}, {}, {}, {}, {}, {}), 0); 299 | )", 300 | NAMEOF_TYPE(archimedes::detail::registry), 301 | DYNCASTS_NAME, 302 | CONSTEXPR_VALUES_NAME, 303 | INVOKERS_NAME, 304 | TEMPLATE_PARAM_VALUES_NAME, 305 | TYPE_ID_HASHES_NAME, 306 | FUNCTIONS_NAME, 307 | TYPES_NAME, 308 | TYPEDEFS_NAME, 309 | NAMESPACE_ALIASES_NAME, 310 | NAMESPACE_USINGS_NAME); 311 | 312 | // add all emitted headers headers as #include-s at top of file 313 | std::string headers; 314 | // TODO: use emitted_headers 315 | for (const auto &h : ctx.found_headers) { 316 | headers += 317 | fmt::format( 318 | "#include \"{}\"\n", 319 | fs::relative(h, ctx.output_path.parent_path()) 320 | .string()); 321 | } 322 | 323 | output = headers + output; 324 | return output; 325 | } 326 | -------------------------------------------------------------------------------- /plugin/emit.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "plugin.hpp" 6 | #include "util.hpp" 7 | 8 | using namespace archimedes; 9 | using namespace archimedes::detail; 10 | 11 | namespace archimedes { 12 | // emit type name (clang type) to C++ type 13 | inline std::string emit_type(const Context &ctx, const clang::Type &type) { 14 | return get_full_type_name(ctx, type); 15 | } 16 | 17 | // emit type name (clang type) to C++ type 18 | inline std::string emit_type(const Context &ctx, const clang::QualType &type) { 19 | return get_full_type_name(ctx, type); 20 | } 21 | 22 | // emit vector as c++ initializer list 23 | template 24 | requires (requires (F f, T t) { { f(t) } -> std::same_as; }) 25 | inline std::string emit_vector( 26 | const vector &ts, 27 | F &&f, 28 | std::string_view sep = ",") { 29 | vector ss; 30 | for (const auto &t : ts) { 31 | ss.push_back(f(t)); 32 | } 33 | 34 | return fmt::format("{{{}}}", fmt::join(ss, sep)); 35 | } 36 | 37 | // emit any with constant value 38 | inline std::string emit_any(const std::string &value) { 39 | return 40 | fmt::format( 41 | "{}::make({})", 42 | NAMEOF_TYPE(archimedes::any), 43 | value); 44 | } 45 | 46 | // emit char array as array initializer 47 | inline std::string emit_data(std::span buffer) { 48 | std::string out; 49 | 50 | // reserve two curly braces + length(0xXX,) * size 51 | out.resize(2 + buffer.size() * 5); 52 | 53 | out[0] = '{'; 54 | for (size_t i = 0; i < buffer.size(); i++) { 55 | char cs[6]; 56 | sprintf(cs, "0x%02x,", static_cast(buffer[i])); 57 | std::memcpy(&out[1 + (i * 5)], &cs, 5); 58 | } 59 | out[out.length() - 1] = '}'; 60 | 61 | return out; 62 | } 63 | 64 | // emit context as c++ 65 | std::string emit(Context&); 66 | } 67 | -------------------------------------------------------------------------------- /plugin/ext/ast.hpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------*- C++ -*- 2 | // CLING - the C++ LLVM-based InterpreterG :) 3 | // author: Vassil Vassilev 4 | // 5 | // This file is dual-licensed: you can choose to license it under the University 6 | // of Illinois Open Source License or the GNU Lesser General Public License. See 7 | // LICENSE.TXT for details. 8 | //------------------------------------------------------------------------------ 9 | 10 | #ifndef CLING_UTILS_AST_H 11 | #define CLING_UTILS_AST_H 12 | 13 | #include "llvm/ADT/SmallSet.h" 14 | #include "llvm/ADT/DenseMap.h" 15 | #include "llvm/ADT/StringRef.h" 16 | 17 | namespace clang { 18 | class ASTContext; 19 | class Expr; 20 | class Decl; 21 | class DeclContext; 22 | class DeclarationName; 23 | class FunctionDecl; 24 | class GlobalDecl; 25 | class IntegerLiteral; 26 | class LookupResult; 27 | class NamedDecl; 28 | class NamespaceDecl; 29 | class NestedNameSpecifier; 30 | class QualType; 31 | class Sema; 32 | class TagDecl; 33 | class TemplateDecl; 34 | class Type; 35 | class TypedefNameDecl; 36 | class TemplateName; 37 | struct PrintingPolicy; 38 | } 39 | 40 | namespace cling { 41 | namespace utils { 42 | ///\brief Class containing static utility functions transforming AST nodes or 43 | /// types. 44 | /// 45 | namespace Transform { 46 | 47 | ///\brief Class containing the information on how to configure the 48 | /// transformation 49 | /// 50 | struct Config { 51 | typedef llvm::SmallSet SkipCollection; 52 | typedef const clang::Type cType; 53 | typedef llvm::DenseMap ReplaceCollection; 54 | 55 | SkipCollection m_toSkip; 56 | ReplaceCollection m_toReplace; 57 | 58 | bool empty() const { return m_toSkip.size()==0 && m_toReplace.empty(); } 59 | }; 60 | 61 | ///\brief Remove one layer of sugar, but only some kinds. 62 | bool SingleStepPartiallyDesugarType(clang::QualType& QT, 63 | const clang::ASTContext& C); 64 | 65 | ///\brief "Desugars" a type while skipping the ones in the set. 66 | /// 67 | /// Desugars a given type recursively until strips all sugar or until gets a 68 | /// sugared type, which is to be skipped. 69 | ///\param[in] Ctx - The ASTContext. 70 | ///\param[in] QT - The type to be partially desugared. 71 | ///\param[in] TypeConfig - The set of sugared types which shouldn't be 72 | /// desugared and those that should be replaced. 73 | ///\param[in] fullyQualify - if true insert Elaborated where needed. 74 | ///\returns Partially desugared QualType 75 | /// 76 | clang::QualType 77 | GetPartiallyDesugaredType(const clang::ASTContext& Ctx, clang::QualType QT, 78 | const Config& TypeConfig, 79 | bool fullyQualify = true); 80 | 81 | } 82 | 83 | namespace TypeName { 84 | ///\brief Convert the type into one with fully qualified template 85 | /// arguments. 86 | ///\param[in] QT - the type for which the fully qualified type will be 87 | /// returned. 88 | ///\param[in] Ctx - the ASTContext to be used. 89 | clang::QualType GetFullyQualifiedType(clang::QualType QT, 90 | const clang::ASTContext& Ctx); 91 | 92 | ///\brief Get the fully qualified name for a type. This includes full 93 | /// qualification of all template parameters etc. 94 | /// 95 | ///\param[in] QT - the type for which the fully qualified name will be 96 | /// returned. 97 | ///\param[in] Ctx - the ASTContext to be used. 98 | ///\param[in] Policy - NEW!!! 99 | std::string GetFullyQualifiedName(clang::QualType QT, 100 | const clang::ASTContext &Ctx, 101 | const clang::PrintingPolicy &Policy); 102 | 103 | ///\brief Create a NestedNameSpecifier for Namesp and its enclosing 104 | /// scopes. 105 | /// 106 | ///\param[in] Ctx - the AST Context to be used. 107 | ///\param[in] Namesp - the NamespaceDecl for which a NestedNameSpecifier 108 | /// is requested. 109 | clang::NestedNameSpecifier* 110 | CreateNestedNameSpecifier(const clang::ASTContext& Ctx, 111 | const clang::NamespaceDecl* Namesp); 112 | 113 | ///\brief Create a NestedNameSpecifier for TagDecl and its enclosing 114 | /// scopes. 115 | /// 116 | ///\param[in] Ctx - the AST Context to be used. 117 | ///\param[in] TD - the TagDecl for which a NestedNameSpecifier is 118 | /// requested. 119 | ///\param[in] FullyQualify - Convert all template arguments into fully 120 | /// qualified names. 121 | clang::NestedNameSpecifier* 122 | CreateNestedNameSpecifier(const clang::ASTContext& Ctx, 123 | const clang::TagDecl *TD, bool FullyQualify); 124 | 125 | ///\brief Create a NestedNameSpecifier for TypedefDecl and its enclosing 126 | /// scopes. 127 | /// 128 | ///\param[in] Ctx - the AST Context to be used. 129 | ///\param[in] TD - the TypedefDecl for which a NestedNameSpecifier is 130 | /// requested. 131 | ///\param[in] FullyQualify - Convert all template arguments (of possible 132 | /// parent scopes) into fully qualified names. 133 | clang::NestedNameSpecifier* 134 | CreateNestedNameSpecifier(const clang::ASTContext& Ctx, 135 | const clang::TypedefNameDecl *TD, 136 | bool FullyQualify); 137 | 138 | // MODIFIED 139 | bool GetFullyQualifiedTemplateName(const clang::ASTContext &Ctx, 140 | clang::TemplateName &tname); 141 | 142 | } // end namespace TypeName 143 | } // end namespace utils 144 | } // end namespace cling 145 | #endif // CLING_UTILS_AST_H 146 | -------------------------------------------------------------------------------- /plugin/implicit_function.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "util.hpp" 6 | 7 | namespace archimedes { 8 | enum class ImplicitFunction { 9 | DEFAULT_CTOR = 0, 10 | COPY_CTOR, 11 | MOVE_CTOR, 12 | COPY_ASSIGN, 13 | MOVE_ASSIGN, 14 | DTOR, 15 | COUNT 16 | }; 17 | 18 | inline std::string get_implicit_name( 19 | ImplicitFunction if_kind, 20 | std::string_view record_name) { 21 | switch (if_kind) { 22 | case ImplicitFunction::DEFAULT_CTOR: 23 | case ImplicitFunction::COPY_CTOR: 24 | case ImplicitFunction::MOVE_CTOR: 25 | return std::string(record_name); 26 | case ImplicitFunction::COPY_ASSIGN: 27 | case ImplicitFunction::MOVE_ASSIGN: 28 | return "operator="; 29 | case ImplicitFunction::DTOR: 30 | return "~" + std::string(record_name); 31 | default: ASSERT(false); 32 | } 33 | } 34 | } // namespace archimedes 35 | -------------------------------------------------------------------------------- /plugin/invoker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "implicit_function.hpp" 9 | #include "util.hpp" 10 | 11 | namespace archimedes { 12 | struct Context; 13 | 14 | // information needed to generate invoker for a function decl 15 | struct Invoker { 16 | const clang::FunctionProtoType *type = nullptr; 17 | const clang::FunctionDecl *decl = nullptr; 18 | const clang::CXXRecordDecl *record = nullptr; 19 | 20 | // only applicable if invoker is for implicit function (no decl) 21 | std::optional if_kind = std::nullopt; 22 | 23 | std::string generated_name() const { 24 | return fmt::format( 25 | "_invoker_{:08X}", 26 | hash( 27 | decl ? 28 | decl->getQualifiedNameAsString() 29 | : std::string(NAMEOF_ENUM(*if_kind)), 30 | reinterpret_cast(this), 31 | reinterpret_cast(this->decl))); 32 | } 33 | }; 34 | 35 | // returns true if an invoker can be emitted for the implicit function 36 | bool can_make_implicit_invoker( 37 | const Context &ctx, 38 | const clang::Type &type, 39 | const clang::CXXRecordDecl &parent_decl, 40 | ImplicitFunction function_kind); 41 | 42 | // returns true if invoker can be emitted for specified funciton decl 43 | bool can_make_invoker(const Context &ctx, const clang::FunctionDecl &decl); 44 | 45 | // emit invoker as function definition 46 | std::string emit_invoker(Context &ctx, const Invoker&); 47 | } // namespace archimedes 48 | -------------------------------------------------------------------------------- /plugin/util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define FMT_HEADER_ONLY 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | // undef to turn off logging messages 16 | // #define PLUGIN_VERBOSE 17 | 18 | #ifdef PLUGIN_VERBOSE 19 | #define PLUGIN_LOG(...) LOG(__VA_ARGS__) 20 | #else 21 | #define PLUGIN_LOG(...) if (Context::instance().verbose) { LOG(__VA_ARGS__); } 22 | #endif 23 | 24 | template 25 | static std::string _assert_fmt( 26 | const std::string &fmt = "", 27 | Args&&... args) { 28 | if (fmt.length() > 0) { 29 | return fmt::vformat( 30 | std::string_view(fmt), 31 | fmt::make_format_args(std::forward(args)...)); 32 | } 33 | 34 | return ""; 35 | } 36 | 37 | #define LOG_PREFIX() \ 38 | fmt::format( \ 39 | "[{}:{}][{}]",\ 40 | __FILE__, \ 41 | __LINE__, \ 42 | __FUNCTION__) 43 | 44 | #define LOG(_fmt, ...) \ 45 | std::cout \ 46 | << LOG_PREFIX() \ 47 | << " " \ 48 | << (fmt::format(_fmt, ## __VA_ARGS__))\ 49 | << std::endl; 50 | 51 | #define ASSERT(_e, ...) do { \ 52 | if (!(_e)) { \ 53 | const auto __msg = _assert_fmt(__VA_ARGS__); \ 54 | LOG( \ 55 | "ASSERTION FAILED{}{}", __msg.length() > 0 ? " " : "", __msg); \ 56 | std::exit(1); \ 57 | } \ 58 | } while (0) 59 | 60 | template 61 | constexpr auto make_array(T&&... values) -> 62 | std::array< 63 | typename std::decay< 64 | typename std::common_type::type>::type, 65 | sizeof...(T)> { 66 | return std::array< 67 | typename std::decay< 68 | typename std::common_type::type>::type, 69 | sizeof...(T)>{ std::forward(values)... }; 70 | } 71 | 72 | // hash arbitrary number of values with std::hash impl 73 | template struct hasher; 74 | 75 | template 76 | struct hasher : public std::hash { 77 | using std::hash::hash; 78 | }; 79 | 80 | template 81 | struct hasher { 82 | inline std::size_t operator()(const T& v, const Ts& ...ts) { 83 | auto seed = hasher{}(ts...); 84 | seed ^= hasher{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 85 | return seed; 86 | } 87 | }; 88 | 89 | template 90 | inline std::size_t hash(const Ts& ...ts) { 91 | return hasher{}(ts...); 92 | } 93 | 94 | namespace std { 95 | template <> 96 | struct hash { 97 | auto operator()(const std::filesystem::path &p) const { 98 | return std::filesystem::hash_value(p); 99 | } 100 | }; 101 | } 102 | 103 | template 104 | std::string f_ostream_to_string(F &&f) { 105 | std::string s; 106 | llvm::raw_string_ostream os(s); 107 | f(os); 108 | return s; 109 | } 110 | 111 | // TODO: doc 112 | inline std::string escape_for_fmt(std::string_view str) { 113 | std::stringstream res; 114 | for (const auto &c : str) { 115 | if (c == '{' || c == '}') { 116 | res << c; 117 | } 118 | 119 | res << c; 120 | } 121 | return res.str(); 122 | } 123 | -------------------------------------------------------------------------------- /runtime/archimedes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace archimedes; 5 | using namespace archimedes::detail; 6 | 7 | template struct 8 | archimedes::name_map; 9 | template struct 10 | archimedes::name_map; 11 | template struct 12 | archimedes::name_map; 13 | template struct 14 | archimedes::name_map; 15 | template class std::vector; 16 | 17 | const type_info &type_id::operator*() const { 18 | return **registry::instance().type_from_id(*this); 19 | } 20 | 21 | type_id::operator bool() const { 22 | return (*this) != none() 23 | && registry::instance().type_from_id(*this); 24 | } 25 | -------------------------------------------------------------------------------- /test/abstract.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "abstract.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | 7 | IAmNotAbstract foo = IAmNotAbstract(); 8 | 9 | const auto iaa = archimedes::reflect(); 10 | ASSERT(iaa); 11 | ASSERT(iaa->as_record().is_abstract()); 12 | 13 | const auto iaa_ctor = iaa->as_record().default_constructor(); 14 | ASSERT(iaa_ctor); 15 | ASSERT(!iaa_ctor->can_invoke()); 16 | 17 | ASSERT(iaa->as_record().function("some_method")); 18 | ASSERT(iaa->as_record().function("some_method")->can_invoke()); 19 | 20 | const auto iaavi = archimedes::reflect(); 21 | ASSERT(iaavi); 22 | ASSERT(iaavi->as_record().is_abstract()); 23 | 24 | const auto iaavi_ctor = iaavi->as_record().default_constructor(); 25 | ASSERT(iaavi_ctor); 26 | ASSERT(!iaavi_ctor->can_invoke()); 27 | 28 | const auto ianavi = archimedes::reflect(); 29 | ASSERT(ianavi); 30 | ASSERT(!ianavi->as_record().is_abstract()); 31 | 32 | const auto iana = archimedes::reflect(); 33 | ASSERT(iana); 34 | ASSERT(!iana->as_record().is_abstract()); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /test/abstract.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct IAmAbstract { 4 | int x; 5 | 6 | IAmAbstract() { } 7 | virtual void some_method() = 0; 8 | }; 9 | 10 | struct IAmAbstractViaInheritance : public IAmAbstract { 11 | IAmAbstractViaInheritance() : IAmAbstract() {} 12 | }; 13 | 14 | struct IAmNotAbstractViaInheritance : public IAmAbstractViaInheritance { 15 | IAmNotAbstractViaInheritance() : IAmAbstractViaInheritance() { } 16 | void some_method() override { /* ... */ } 17 | }; 18 | 19 | struct IAmNotAbstract : public IAmAbstract { 20 | IAmNotAbstract() : IAmAbstract() { } 21 | void some_method() override { /* ... */ } 22 | }; 23 | -------------------------------------------------------------------------------- /test/access.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "access.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto f = archimedes::reflect(); 7 | ASSERT(f); 8 | 9 | const auto b = archimedes::reflect("Foo::Bar"); 10 | ASSERT(b); 11 | 12 | // should be able to see function but not invoke it 13 | const auto amv = b->as_record().function("am_i_visible"); 14 | ASSERT(amv); 15 | ASSERT(amv->parameters().size() == 2); 16 | ASSERT(!amv->can_invoke()); 17 | 18 | // should be able to see x, y, and z 19 | ASSERT(f->as_record().field("x")->access() == archimedes::PUBLIC); 20 | ASSERT(f->as_record().field("z")->access() == archimedes::PROTECTED); 21 | ASSERT(f->as_record().field("y")->access() == archimedes::PRIVATE); 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /test/access.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo { 4 | private: 5 | struct Bar { 6 | int am_i_visible(int x, int y) { return x + y; } 7 | }; 8 | int y; 9 | public: 10 | int x; 11 | protected: 12 | int z; 13 | }; 14 | -------------------------------------------------------------------------------- /test/alias.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "alias.test.hpp" 3 | 4 | namespace arc = archimedes; 5 | 6 | int main(int argc, char *argv[]) { 7 | arc::load(); 8 | ASSERT(arc::reflect("char") == arc::reflect()); 9 | ASSERT(arc::reflect("MyChar") == arc::reflect()); 10 | ASSERT(arc::reflect("MyChar") == arc::reflect()); 11 | ASSERT(arc::reflect("MyChar2") == arc::reflect()); 12 | ASSERT(arc::reflect("MyChar2") == arc::reflect()); 13 | ASSERT(arc::reflect("std::string2") == arc::reflect>()); 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /test/alias.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef char MyChar; 6 | typedef MyChar MyChar2; 7 | 8 | namespace std { 9 | typedef basic_string string2; 10 | } 11 | -------------------------------------------------------------------------------- /test/align_size.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "align_size.test.hpp" 3 | 4 | FORCE_TYPE_INSTANTIATION(AllBuiltins) 5 | 6 | int main(int argc, char *argv[]) { 7 | archimedes::load(); 8 | 9 | AllBuiltins ab; 10 | ab.b = true; 11 | 12 | // test align is correct 13 | ASSERT( 14 | archimedes::reflect()->align() 15 | == std::alignment_of_v); 16 | ASSERT( 17 | archimedes::reflect()->align() 18 | == std::alignment_of_v); 19 | ASSERT( 20 | archimedes::reflect()->align() 21 | == std::alignment_of_v); 22 | ASSERT( 23 | archimedes::reflect()->align() 24 | == std::alignment_of_v); 25 | 26 | // test size is correct 27 | ASSERT( 28 | archimedes::reflect()->size() 29 | == sizeof(AllBuiltins)); 30 | ASSERT( 31 | archimedes::reflect()->size() 32 | == sizeof(int*)); 33 | ASSERT( 34 | archimedes::reflect()->size() 35 | == sizeof(double)); 36 | ASSERT( 37 | archimedes::reflect()->size() 38 | == sizeof(unsigned long long)); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /test/align_size.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct AllBuiltins { 4 | bool b; 5 | char c; 6 | unsigned char uc; 7 | unsigned short us; 8 | unsigned int ui; 9 | unsigned long ul; 10 | unsigned long long ull; 11 | signed char sc; 12 | signed short ss; 13 | signed int si; 14 | signed long sl; 15 | signed long long sll; 16 | float f; 17 | double d; 18 | bool *pb; 19 | int *psi; 20 | unsigned int *pui; 21 | }; 22 | -------------------------------------------------------------------------------- /test/annotations.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | 3 | #include "annotations.test.hpp" 4 | 5 | FORCE_TYPE_INSTANTIATION(Bar) 6 | FORCE_TYPE_INSTANTIATION(Bar) 7 | FORCE_TYPE_INSTANTIATION(Baz) 8 | 9 | int main(int argc, char *argv[]) { 10 | archimedes::load(); 11 | 12 | // check that template instantiations have annotations 13 | const auto 14 | baz = archimedes::reflect(), 15 | bar = archimedes::reflect>(); 16 | ASSERT(baz); 17 | ASSERT(baz->annotations().size() == 1); 18 | ASSERT(baz->annotations()[0] == bar->annotations()[0]); 19 | 20 | const auto foo = archimedes::reflect("Foo"); 21 | ASSERT(foo->annotations().size() == 2); 22 | 23 | const auto as = foo->annotations(); 24 | const auto it_abc = 25 | std::find( 26 | as.begin(), 27 | as.end(), 28 | "abc"); 29 | ASSERT(it_abc != as.end()); 30 | 31 | const auto it_xyz = 32 | std::find( 33 | as.begin(), 34 | as.end(), 35 | "xyz"); 36 | ASSERT(it_xyz != as.end()); 37 | 38 | ASSERT(foo->as_record().static_field("y")->annotations().front() == "bcd"); 39 | ASSERT(foo->as_record().field("x")->annotations().front() == "aaaa"); 40 | ASSERT(foo->as_record().field("z")->annotations().front() == "bbb"); 41 | ASSERT(foo->as_record().function("bar")->annotations().front() == "efg"); 42 | 43 | // ensure that fundamental types are not polluted with annotations 44 | ASSERT(archimedes::reflect()->annotations().empty()); 45 | ASSERT(archimedes::reflect()->annotations().empty()); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /test/annotations.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct __attribute__((annotate("abc"))) __attribute__((annotate("xyz"))) Foo { 4 | static int __attribute__((annotate("bcd"))) y; 5 | int __attribute__((annotate("aaaa"))) x; 6 | 7 | [[clang::annotate("bbb")]] int z; 8 | 9 | __attribute__((annotate("efg"))) int bar(int x) { return x; } 10 | }; 11 | 12 | template 13 | struct __attribute__((annotate("bar_annotation"))) Bar { 14 | int x; 15 | }; 16 | 17 | using Baz = Bar; 18 | -------------------------------------------------------------------------------- /test/anonymous.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "anonymous.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto f = archimedes::reflect()->as_record(); 7 | const auto some_fields = f.field("some_fields")->type()->as_record(); 8 | ASSERT(some_fields.field("x")); 9 | ASSERT(some_fields.field("y")); 10 | 11 | Foo foo; 12 | foo.some_fields.x = 12; 13 | foo.some_fields.y = 345; 14 | ASSERT(*some_fields.field("x")->get(foo.some_fields) == 12); 15 | ASSERT(*some_fields.field("y")->get(foo.some_fields) == 345); 16 | some_fields.field("x")->set(foo.some_fields, 444); 17 | ASSERT(foo.some_fields.x == 444); 18 | 19 | const auto r_x = f.field("x")->type()->as_record(); 20 | const auto r_u = r_x.field("u")->type()->as_record(); 21 | const auto r_u_h_l = r_u.field("h_l")->type()->as_record(); 22 | ASSERT(r_u.is_union()); 23 | 24 | foo.x.u.hl = 0xAA55; 25 | ASSERT(*r_u_h_l.field("l")->get(foo.x.u) == 0x55); 26 | ASSERT(*r_u_h_l.field("h")->get(foo.x.u) == 0xAA); 27 | 28 | r_x.field("f")->set(foo.x, &foo); 29 | ASSERT(foo.x.f == &foo); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /test/anonymous.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Foo { 6 | struct { 7 | int x, y; 8 | } some_fields; 9 | 10 | struct { 11 | union { 12 | struct { 13 | uint8_t l, h; 14 | } h_l; 15 | uint16_t hl; 16 | } u; 17 | int z; 18 | const Foo *f = nullptr; 19 | } x; 20 | }; 21 | -------------------------------------------------------------------------------- /test/any_add_ptr.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "any_add_ptr.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | int x = 2; 6 | auto a0 = archimedes::any::make(x); 7 | auto a1 = archimedes::any::make(&x); 8 | auto a2 = archimedes::any::make(&x, a0.id().add_pointer()); 9 | ASSERT(a1.id() == a2.id()); 10 | return 0; 11 | }; 12 | -------------------------------------------------------------------------------- /test/any_add_ptr.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /test/array.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "array.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | // should be able to find main using all possible array/pointer variants 7 | ASSERT(archimedes::reflect_functions()); 8 | ASSERT(archimedes::reflect_functions()); 9 | 10 | const auto foo = archimedes::reflect()->as_record(); 11 | ASSERT(foo.field("xs")->type()->as_array().length() == 2); 12 | ASSERT(foo.field("ys")->type()->as_array().length() == 222); 13 | ASSERT(foo.field("zs")->type()->as_array().length() == 0); 14 | ASSERT(foo.field("ws")->type()->as_array().length() == 224); 15 | 16 | // TODO: should be able to find length of array params 17 | 18 | const auto isma = 19 | archimedes::reflect_functions("i_sum_my_array").begin()->first(); 20 | int arr[8] = { 1, 1, 1, 1, 2, 2, 2, 2 }; 21 | ASSERT(isma->invoke(arr)->as() == 12); 22 | 23 | const auto ismca = 24 | archimedes::reflect_functions("i_sum_my_const_array").begin()->first(); 25 | arr[0] = 100; 26 | ASSERT(ismca->invoke(arr)->as() == 111); 27 | 28 | // should be able to find both functions via type 29 | ASSERT(archimedes::reflect_functions()->size() == 1); 30 | ASSERT(archimedes::reflect_functions()->size() == 1); 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /test/array.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo { 4 | static constexpr auto LENGTH = 222; 5 | 6 | int ws[LENGTH + 2]; 7 | int xs[2]; 8 | int ys[LENGTH]; 9 | int zs[]; 10 | }; 11 | 12 | inline int i_sum_my_array(int arr[8]) { 13 | int x = 0; 14 | for (int i = 0; i < 8; i++) { 15 | x += arr[i]; 16 | } 17 | return x; 18 | } 19 | 20 | inline int i_sum_my_const_array(int const arr[8]) { 21 | int x = 0; 22 | for (int i = 0; i < 8; i++) { 23 | x += arr[i]; 24 | } 25 | return x; 26 | } 27 | -------------------------------------------------------------------------------- /test/basic.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "basic.test.hpp" 3 | 4 | int free_func(Eeeee e) { 5 | LOG("here!"); 6 | return 135; 7 | } 8 | 9 | int free_func2(void) { 10 | LOG("here!"); 11 | return 135; 12 | } 13 | 14 | static int free_func3(void) { 15 | LOG("here!"); 16 | return 135; 17 | } 18 | 19 | FORCE_FUNCTION_INSTANTIATION(free_func2) 20 | FORCE_FUNCTION_INSTANTIATION(free_func3) 21 | 22 | int main(int argc, char *argv[]) { 23 | archimedes::load(); 24 | const auto opt_foo = archimedes::reflect(); 25 | ASSERT(opt_foo); 26 | const auto opt_glob = archimedes::reflect(); 27 | ASSERT(opt_glob); 28 | const auto opt_goob = archimedes::reflect>(); 29 | ASSERT(opt_goob); 30 | 31 | const auto fs = archimedes::reflect_functions(); 32 | ASSERT(fs); 33 | ASSERT(fs->size() == 2); 34 | 35 | decltype(fs)::value_type::const_iterator pos_ff2, pos_ff3; 36 | ASSERT( 37 | (pos_ff2 = std::find_if( 38 | fs->begin(), fs->end(), 39 | [](const archimedes::reflected_function &f) { 40 | return f.name() == "free_func2"; 41 | })) != fs->end()); 42 | ASSERT( 43 | (pos_ff3 = std::find_if( 44 | fs->begin(), fs->end(), 45 | [](const archimedes::reflected_function &f) { 46 | return f.name() == "free_func3"; 47 | })) != fs->end()); 48 | 49 | const auto ff2 = *pos_ff2, ff3 = *pos_ff3; 50 | ASSERT(ff2.can_invoke()); 51 | ASSERT(!ff3.can_invoke()); 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /test/basic.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace baz { 7 | struct Foo { 8 | static constexpr int baza = 12; 9 | int bazb; 10 | mutable int foob; 11 | 12 | std::string bar(const std::string &s) const noexcept(false) { 13 | std::string t = s + "asfsf"; 14 | return t; 15 | } 16 | }; 17 | } 18 | 19 | template 20 | struct Goob { 21 | int y; 22 | }; 23 | 24 | struct Glob : public baz::Foo, protected Goob { 25 | int Goob::*ptr; 26 | int z; 27 | unsigned int u : 5; 28 | unsigned int w : 4; 29 | 30 | Glob() 31 | : ptr(&Goob::y) {} 32 | }; 33 | 34 | using MyInt = uint8_t; 35 | 36 | enum Eeeee : MyInt { 37 | A = 0, 38 | B = 12, 39 | C = B, 40 | Z = (A + 1) 41 | }; 42 | 43 | int free_func(Eeeee e); 44 | -------------------------------------------------------------------------------- /test/builtin.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "builtin.test.hpp" 3 | 4 | template 5 | void test(AllBuiltins &ab, const archimedes::reflected_field &f, const T &t) { 6 | f.set(ab, t); 7 | ASSERT( 8 | *f.get(ab) == t, 9 | "failed for field {}", 10 | archimedes::reflect()->name()); 11 | } 12 | 13 | int main(int argc, char *argv[]) { 14 | archimedes::load(); 15 | const auto r = archimedes::reflect()->as_record(); 16 | 17 | AllBuiltins ab; 18 | test(ab, *r.field("b"), true); 19 | test(ab, *r.field("c"), 'a'); 20 | test(ab, *r.field("uc"), static_cast('a')); 21 | test(ab, *r.field("us"), static_cast(12)); 22 | test(ab, *r.field("ui"), static_cast(13)); 23 | test(ab, *r.field("ul"), static_cast(14)); 24 | test(ab, *r.field("ull"), static_cast(15)); 25 | test(ab, *r.field("sc"), static_cast(16)); 26 | test(ab, *r.field("ss"), static_cast(17)); 27 | test(ab, *r.field("si"), static_cast(-21414)); 28 | test(ab, *r.field("sl"), static_cast(-23535)); 29 | test(ab, *r.field("sll"), static_cast(-24586258)); 30 | test(ab, *r.field("f"), static_cast(1.34f)); 31 | test(ab, *r.field("d"), static_cast(1.555)); 32 | 33 | bool b = false; 34 | test(ab, *r.field("pb"), static_cast(&b)); 35 | 36 | int i = 12; 37 | test(ab, *r.field("psi"), static_cast(&i)); 38 | 39 | unsigned int x = 124; 40 | test(ab, *r.field("pui"), static_cast(&x)); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /test/builtin.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct AllBuiltins { 4 | bool b; 5 | char c; 6 | unsigned char uc; 7 | unsigned short us; 8 | unsigned int ui; 9 | unsigned long ul; 10 | unsigned long long ull; 11 | signed char sc; 12 | signed short ss; 13 | signed int si; 14 | signed long sl; 15 | signed long long sll; 16 | float f; 17 | double d; 18 | bool *pb; 19 | int *psi; 20 | unsigned int *pui; 21 | }; 22 | -------------------------------------------------------------------------------- /test/constexpr.test.cpp: -------------------------------------------------------------------------------- 1 | #include "constexpr.test.hpp" 2 | 3 | namespace arc = archimedes; 4 | 5 | FORCE_TYPE_INSTANTIATION(Foo) 6 | 7 | // TODO: nttp support 8 | // FORCE_TYPE_INSTANTIATION(Bar()>) 9 | 10 | int main(int argc, char *argv[]) { 11 | arc::load(); 12 | 13 | const auto iama = arc::reflect_functions("i_add_my_args"); 14 | ASSERT( 15 | iama.begin()->first() 16 | == (*arc::reflect_functions())[0]); 17 | ASSERT(iama.begin()->first()->invoke(11, 12)->as() == 23); 18 | const auto foo = arc::reflect(); 19 | ASSERT( 20 | foo->as_record().static_field("INTS") 21 | ->constexpr_value() 22 | ->as()[2] == 3); 23 | ASSERT( 24 | foo->static_field("INT") 25 | ->constexpr_value() 26 | ->as() == 3); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /test/constexpr.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "test.hpp" 4 | 5 | 6 | struct Foo { 7 | static constexpr int INTS[] = { 1, 2, 3 }; 8 | static constexpr int INT = 3; 9 | 10 | static constexpr auto lambda_result = 11 | []() { 12 | return INT + INTS[2]; 13 | }(); 14 | }; 15 | 16 | // TODO: NTTP support 17 | /* template */ 18 | /* struct Bar { */ 19 | /* using MyType = T; */ 20 | 21 | /* static constexpr auto L = */ 22 | /* []() { */ 23 | /* return ID == arc::type_id::none() ? arc::type_id::from() : ID; */ 24 | /* }(); */ 25 | /* }; */ 26 | 27 | 28 | constexpr inline int i_add_my_args(int x, int y) { 29 | return x + y; 30 | } 31 | -------------------------------------------------------------------------------- /test/converters.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "converters.test.hpp" 3 | 4 | FORCE_TYPE_INSTANTIATION(Foo) 5 | FORCE_TYPE_INSTANTIATION(Bar) 6 | 7 | int main(int argc, char *argv[]) { 8 | archimedes::load(); 9 | const auto foo = archimedes::reflect()->as_record(); 10 | const auto bar = archimedes::reflect()->as_record(); 11 | Foo f; 12 | Bar b; 13 | 14 | const auto foo_to_const_foo_ptr = 15 | foo.function(); 16 | ASSERT(foo_to_const_foo_ptr); 17 | ASSERT(foo_to_const_foo_ptr->is_explicit()); 18 | ASSERT(foo_to_const_foo_ptr->is_converter()); 19 | ASSERT(foo_to_const_foo_ptr->is_member()); 20 | ASSERT(foo_to_const_foo_ptr->type().is_const()); 21 | ASSERT(foo_to_const_foo_ptr->invoke(&f)->as() == &f); 22 | 23 | const auto bar_to_int_const = bar.function(); 24 | ASSERT(bar_to_int_const); 25 | ASSERT(bar_to_int_const->type().is_const()); 26 | ASSERT(bar_to_int_const->invoke(&b)->as() == 12); 27 | 28 | const auto bar_to_int = bar.function(); 29 | ASSERT(bar_to_int); 30 | ASSERT(!bar_to_int->type().is_const()); 31 | ASSERT(bar_to_int->invoke(&b)->as() == 13); 32 | 33 | const auto bar_to_double_l = bar.function(); 34 | ASSERT(bar_to_double_l); 35 | ASSERT(bar_to_double_l->type().is_ref()); 36 | ASSERT(bar_to_double_l->invoke(archimedes::ref(b))->as() == 1.0); 37 | 38 | const auto bar_to_double_cl = bar.function(); 39 | ASSERT(bar_to_double_cl); 40 | ASSERT(bar_to_double_cl->type().is_ref()); 41 | ASSERT(bar_to_double_cl->type().is_const()); 42 | ASSERT(bar_to_double_cl->invoke(archimedes::cref(b))->as() == 3.0); 43 | 44 | const auto bar_to_double_r = bar.function(); 45 | ASSERT(bar_to_double_r); 46 | ASSERT(bar_to_double_r->type().is_rref()); 47 | ASSERT(bar_to_double_r->invoke(Bar())->as() == 2.0); 48 | 49 | const auto bar_to_double_cr = bar.function(); 50 | ASSERT(bar_to_double_cr); 51 | ASSERT(bar_to_double_cr->type().is_rref()); 52 | ASSERT(bar_to_double_cr->type().is_const()); 53 | ASSERT(bar_to_double_cr->invoke(Bar())->as() == 4.0); 54 | 55 | const auto bar_to_foo = bar.function(); 56 | ASSERT(bar_to_foo); 57 | ASSERT(!bar_to_foo->type().is_const()); 58 | ASSERT(!bar_to_foo->type().is_ref()); 59 | ASSERT(!bar_to_foo->type().is_rref()); 60 | ASSERT(bar_to_foo->invoke(&b)->as().x == 101); 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /test/converters.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo { 4 | int x; 5 | 6 | explicit operator const Foo*() const { return this; } 7 | }; 8 | 9 | struct Bar { 10 | operator int() const { return 12; } 11 | operator int() { return 13; } 12 | operator double() & { return 1.0; } 13 | operator double() && { return 2.0; } 14 | operator double() const& { return 3.0; } 15 | operator double() const&& { return 4.0; } 16 | explicit operator Foo() { return Foo { .x = 101 }; } 17 | }; 18 | -------------------------------------------------------------------------------- /test/ctor.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "ctor.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto foo = archimedes::reflect()->as_record(); 7 | const auto foo_default = foo.default_constructor(); 8 | ASSERT(foo_default); 9 | ASSERT(foo_default->is_defaulted()); 10 | 11 | // should be able to find if defined and deleted but should not be able to 12 | // invoke 13 | const auto foo_copy = 14 | foo.constructors()->get(); 15 | ASSERT(foo_copy); 16 | ASSERT(foo_copy->is_deleted()); 17 | ASSERT(!foo_copy->can_invoke()); 18 | 19 | const auto foo_move = 20 | foo.constructors()->get(); 21 | ASSERT(foo_move); 22 | 23 | const auto foo_move_assign = 24 | foo.function(); 25 | ASSERT(foo_move_assign); 26 | 27 | const auto bar = archimedes::reflect()->as_record(); 28 | const auto bar_default = bar.default_constructor(); 29 | ASSERT(bar_default); 30 | ASSERT(!bar_default->is_defaulted()); 31 | 32 | Bar b; 33 | bar_default->invoke(&b); 34 | ASSERT(b.x == 12); 35 | bar_default->invoke(&b, 33); 36 | ASSERT(b.x == 33); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /test/ctor.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo { 4 | explicit Foo(int x) : x(x) {} 5 | 6 | Foo() = default; 7 | Foo(const Foo&) = delete; 8 | Foo(Foo&&) = default; 9 | Foo &operator=(const Foo&) = delete; 10 | Foo &operator=(Foo&&) = default; 11 | 12 | int x; 13 | }; 14 | 15 | struct Bar { 16 | Bar(int x = 12) : x(x) {} 17 | Bar(const Bar&) = default; 18 | Bar(Bar&&) = delete; 19 | Bar &operator=(const Bar&) = default; 20 | Bar &operator=(Bar&&) = delete; 21 | 22 | int x; 23 | }; 24 | -------------------------------------------------------------------------------- /test/decl_in_macro.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "decl_in_macro.test.hpp" 3 | 4 | FORCE_TYPE_INSTANTIATION(Foo); 5 | 6 | int main(int argc, char *argv[]) { 7 | archimedes::load(); 8 | const auto f = archimedes::reflect>(); 9 | ASSERT(f); 10 | 11 | const auto as = f->annotations(); 12 | auto it = std::find_if( 13 | as.begin(), 14 | as.end(), 15 | [](const auto &a) { 16 | return a.starts_with("_decl_me_"); 17 | }); 18 | ASSERT(it != as.end()); 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /test/decl_in_macro.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | struct Foo { 7 | T t; 8 | }; 9 | 10 | #define DECL_ME(_T) \ 11 | template <> \ 12 | struct [[ARCHIMEDES_ANNOTATE("_decl_me_" ARCHIMEDES_STRINGIFY(_T))]] Foo<_T> { \ 13 | int baz() { return archimedes::type_name<_T>().length(); } \ 14 | }; 15 | 16 | DECL_ME(int) 17 | DECL_ME(float) 18 | DECL_ME(double) 19 | DECL_ME(bool) 20 | -------------------------------------------------------------------------------- /test/defaults.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "defaults.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto ihzwd = 7 | *archimedes::reflect_functions("i_have_zero_with_defaults").begin()->first(); 8 | ASSERT(ihzwd.invoke()->as() == 6); 9 | ASSERT(ihzwd.invoke(4)->as() == 8); 10 | ASSERT(ihzwd.invoke(12, 12)->as() == 24); 11 | 12 | const auto ihd = 13 | *archimedes::reflect_functions("i_have_defaults").begin()->first(); 14 | ASSERT(!ihd.invoke().is_success()); 15 | ASSERT(ihd.invoke(4)->as() == 6); 16 | ASSERT(ihd.invoke(12, 12)->as() == 24); 17 | 18 | const auto ihs = 19 | *archimedes::reflect_functions("i_have_strings").begin()->first(); 20 | ASSERT(!ihs.invoke().is_success()); 21 | ASSERT(ihs.invoke(44)->as() == "x is: 44"); 22 | ASSERT(ihs.invoke(45, std::string("bbbbb"))->as() == "bbbbb45"); 23 | 24 | ASSERT(archimedes::reflect("Foo")); 25 | const auto f = archimedes::reflect("Foo")->as_record(); 26 | ASSERT(f.function("Foo")); 27 | const auto ctor = *f.function(); 28 | 29 | Foo foo; 30 | ASSERT(!ctor.invoke().is_success()); 31 | 32 | ctor.invoke(&foo); 33 | ASSERT(foo.x == 4); 34 | 35 | ctor.invoke(&foo, 12); 36 | ASSERT(foo.x == 14); 37 | 38 | const auto bar = *f.function("bar"); 39 | ASSERT(bar.invoke(&foo)->as() == 21); 40 | ASSERT(bar.invoke(&foo, 1)->as() == 32); 41 | ASSERT(bar.invoke(&foo, 1, 2)->as() == 1); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /test/defaults.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "test.hpp" 5 | 6 | inline int i_have_zero_with_defaults(int x = 2, int y = 4) { 7 | return x + y; 8 | } 9 | 10 | inline int i_have_defaults(int x, int y = 2) { 11 | return x + y; 12 | } 13 | 14 | inline std::string i_have_strings(int x, std::string s = "x is: ") { 15 | return s + std::to_string(x); 16 | } 17 | 18 | struct Foo { 19 | int x = 2; 20 | 21 | explicit Foo(int y = 2) { 22 | this->x += y; 23 | } 24 | 25 | int bar(int x = 12, int y = 33) { 26 | return y - x; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /test/empty.test.cpp: -------------------------------------------------------------------------------- 1 | int main(int argc, char *argv[]) { return 0; } 2 | -------------------------------------------------------------------------------- /test/empty.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /test/enum.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "enum.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto f = archimedes::reflect(); 7 | ASSERT(f); 8 | ASSERT(f->is_enum()); 9 | ASSERT(f->as_enum().value_of("A")); 10 | ASSERT(f->as_enum().names_of(Foo::D).size() == 2); 11 | 12 | const auto bar = archimedes::reflect("Bar"); 13 | ASSERT(bar); 14 | ASSERT(bar->is_enum()); 15 | ASSERT(bar->as_enum().values().size() == 4); 16 | ASSERT(bar->as_enum().value_of("FIRST") == 0); 17 | ASSERT(bar->as_enum().value_of("THIRD") == 2); 18 | ASSERT( 19 | bar->as_enum().base_type().type() == 20 | archimedes::reflect("unsigned short")); 21 | 22 | const auto baz = archimedes::reflect("names::Baz"); 23 | ASSERT(baz); 24 | ASSERT(baz->is_enum()); 25 | ASSERT(baz->as_enum().values().size() == 1); 26 | ASSERT(baz->as_enum().value_of("AAAA") == 3); 27 | 28 | const auto s_foo = archimedes::reflect("S::SFoo"); 29 | ASSERT(s_foo); 30 | ASSERT(s_foo->is_enum()); 31 | ASSERT(s_foo->as_enum().values().size() == 2); 32 | ASSERT(s_foo->as_enum().value_of("A") == 333); 33 | ASSERT(s_foo->as_enum().value_of("B") == 345); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /test/enum.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum Foo { 4 | A = 1, 5 | B = 2, 6 | C = A + 1, 7 | D = C + 12, 8 | E = D, 9 | Z = -1 10 | }; 11 | 12 | enum class Bar : unsigned short { 13 | FIRST = 0, 14 | SECOND = 1, 15 | THIRD, 16 | FOURTH 17 | }; 18 | 19 | namespace names { 20 | enum class Baz { 21 | AAAA = 3 22 | }; 23 | } 24 | 25 | struct S { 26 | enum SFoo { 27 | A = 333, 28 | B = 345 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /test/example.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "example.test.hpp" 3 | 4 | namespace arc = archimedes; 5 | 6 | Baz g_baz; 7 | 8 | int main(int argc, char *argv[]) { 9 | arc::load(); // must be run on program startup to load type information 10 | 11 | // we could also use arc::reflect("Foo") and arc::reflect("Baz") 12 | const auto 13 | foo = arc::reflect(), 14 | baz_int = arc::reflect>(); 15 | 16 | // like this! but then we need to manually "cast" it to a reflected enum 17 | const auto qux = arc::reflect("Qux")->as_enum(); 18 | 19 | // TODO 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /test/example.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct Foo { 7 | enum Qux : uint_fast32_t { QUX_A, QUX_B }; 8 | 9 | using string_type = std::string; 10 | 11 | int f; 12 | 13 | Foo() { 14 | this->f = 4545; 15 | } 16 | 17 | Foo(float flt) { 18 | this->f = static_cast(flt * 1284.124f); 19 | } 20 | 21 | virtual ~Foo() = default; 22 | 23 | string_type speak() { return "foo!"; } 24 | 25 | virtual string_type virtual_speak() { return "virtual foo!"; } 26 | }; 27 | 28 | template 29 | struct Baz : Foo { 30 | int x; 31 | std::string s; 32 | std::vector ts; 33 | 34 | string_type virtual_speak() override { return "virtual baz!"; } 35 | }; 36 | -------------------------------------------------------------------------------- /test/explicit_enable.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "explicit_enable.test.hpp" 3 | #include "explicit_enable_other.hpp" 4 | 5 | ARCHIMEDES_ARG("explicit-enable") 6 | ARCHIMEDES_ARG("enable") 7 | 8 | ARCHIMEDES_REFLECT_TYPE_REGEX("InAnotherHeader.*") 9 | 10 | static __attribute__((unused)) const auto my_var = InAnotherHeader(); 11 | 12 | int main(int argc, char *argv[]) { 13 | archimedes::load(); 14 | 15 | /* ASSERT(archimedes::reflect>()); */ 16 | /* TODO: ASSERT( */ 17 | /* archimedes::reflect>() */ 18 | /* ->function("some_function")); */ 19 | 20 | const auto foo = archimedes::reflect(); 21 | ASSERT(foo); 22 | const auto sf = foo->function("some_function"); 23 | ASSERT(sf); 24 | ASSERT(sf->can_invoke()); 25 | 26 | Foo f; 27 | ASSERT(sf->invoke(&f)->as() == 44); 28 | 29 | ASSERT(!archimedes::reflect_function("free_function")); 30 | ASSERT(archimedes::reflect_function("free_function2")); 31 | ASSERT(archimedes::reflect("ns::YouCanSeeMe")); 32 | ASSERT(!archimedes::reflect("ns2::YouCantSeeMe")); 33 | ASSERT(archimedes::reflect()->function("i_am_a_function")); 34 | 35 | ASSERT(archimedes::reflect("ns2::YouShouldBeAbleToSeeMe")); 36 | ASSERT( 37 | archimedes::reflect() 38 | ->function("i_am_a_function2")); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /test/explicit_enable.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct [[ARCHIMEDES_REFLECT]] Foo { 6 | int some_function() { return 44; } 7 | }; 8 | 9 | struct Bar { 10 | int x; 11 | }; 12 | 13 | inline int free_function(int x, int y) { return x * y; } 14 | 15 | [[ARCHIMEDES_REFLECT]] inline int free_function2(int x, int y) { return x * y; }; 16 | 17 | namespace [[ARCHIMEDES_REFLECT]] ns { 18 | struct YouCanSeeMe { int i_am_a_function() { return 12; } }; 19 | } 20 | 21 | namespace ns2 { 22 | struct YouCantSeeMe {}; 23 | 24 | struct [[ARCHIMEDES_REFLECT]] YouShouldBeAbleToSeeMe { 25 | int i_am_a_function2() { return 12; } 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /test/explicit_enable_other.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | struct InAnotherHeader { 5 | T t; 6 | 7 | int some_function() { return 12; } 8 | }; 9 | -------------------------------------------------------------------------------- /test/forward_decl.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "forward_decl.test.hpp" 3 | #include "forward_decl2.hpp" 4 | 5 | FORCE_TYPE_INSTANTIATION(Foo) 6 | 7 | int main(int argc, char *argv[]) { 8 | archimedes::load(); 9 | 10 | Foo f; 11 | Bar b; 12 | f.bar = &b; 13 | const auto foo = archimedes::reflect(); 14 | ASSERT(foo); 15 | const auto bar = archimedes::reflect(); 16 | ASSERT(bar); 17 | ASSERT(bar->kind() == archimedes::UNKNOWN); 18 | const auto fun = archimedes::reflect_function("fwd"); 19 | ASSERT(!fun); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /test/forward_decl.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Bar; 4 | int fwd(int,int); 5 | 6 | struct Foo { 7 | const Bar *bar = nullptr; 8 | }; 9 | -------------------------------------------------------------------------------- /test/forward_decl2.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Bar { 4 | int x, y; 5 | }; 6 | 7 | inline int fwd(int x, int y) { return x + y; } 8 | -------------------------------------------------------------------------------- /test/implicit_ctor_dtor.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "implicit_ctor_dtor.test.hpp" 3 | 4 | FORCE_TYPE_INSTANTIATION(Foo) 5 | FORCE_TYPE_INSTANTIATION(Bar) 6 | 7 | int main(int argc, char *argv[]) { 8 | archimedes::load(); 9 | const auto f = *archimedes::reflect(); 10 | ASSERT(f.constructors()); 11 | ASSERT(f.default_constructor()); 12 | ASSERT(f.copy_constructor()); 13 | ASSERT(f.move_constructor()); 14 | ASSERT(f.copy_assignment()); 15 | ASSERT(f.move_assignment()); 16 | ASSERT(f.destructor()); 17 | 18 | Bar b; 19 | WithAReference x = { b }; 20 | x.b.ints[0] = 3; 21 | 22 | // TODO: test bar 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /test/implicit_ctor_dtor.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Foo { 6 | union { 7 | int z; 8 | } u; 9 | 10 | int x; 11 | }; 12 | 13 | struct Bar { 14 | Bar() = default; 15 | 16 | // according to the standard, this *does not count* as a move constructor 17 | // unless it is instantiatd for Bar, therefore templated constructors MUST 18 | // be excluded from implicit function generation 19 | template 20 | explicit Bar(T &&t) { 21 | ints[0] = *t.begin(); 22 | } 23 | 24 | int ints[8]; 25 | }; 26 | 27 | struct WithAReference { 28 | Bar &b; 29 | }; 30 | -------------------------------------------------------------------------------- /test/invoke.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "invoke.test.hpp" 3 | 4 | std::string &i_return_my_argument(std::string &s) { 5 | s.append("aaa"); 6 | return s; 7 | } 8 | 9 | std::string &i_return_something_new(std::string &s) { 10 | static std::string q = s; 11 | q.append("aaa"); 12 | return q; 13 | } 14 | 15 | int lots_of_args(int x, int y, int z, const std::string &p, std::string &&q) { 16 | std::string f = std::move(q); 17 | return x + y + z + p.length() + f.length(); 18 | } 19 | 20 | int main(int argc, char *argv[]) { 21 | archimedes::load(); 22 | const auto f = archimedes::reflect(); 23 | ASSERT(f); 24 | const auto ssm = f->as_record().function("some_static_method"); 25 | ASSERT(ssm); 26 | ASSERT(ssm->invoke(4)->as() == 6); 27 | 28 | const auto wrap = [&](const int &x) { return ssm->invoke(x)->as(); }; 29 | ASSERT(wrap(12) == 14); 30 | 31 | const auto nsm = f->as_record().function("another_static_method"); 32 | ASSERT(nsm); 33 | auto iccm = ICountMyMoves(); 34 | ASSERT(nsm->invoke(archimedes::rref(std::move(iccm)))->as() == 1); 35 | 36 | const auto irma = 37 | archimedes::reflect_functions("i_return_my_argument").begin()->first(); 38 | ASSERT(irma); 39 | ASSERT(irma->can_invoke()); 40 | std::string s = "bbb"; 41 | std::string &t = irma->invoke(archimedes::ref(s))->as(); 42 | ASSERT(&s == &t); 43 | ASSERT(s == "bbbaaa"); 44 | ASSERT(t == "bbbaaa"); 45 | 46 | const auto irsn = 47 | archimedes::reflect_functions("i_return_something_new").begin()->first(); 48 | std::string q = "bbb"; 49 | std::string &r = irsn->invoke(archimedes::ref(q))->as(); 50 | ASSERT(&r != &q); 51 | ASSERT(q == "bbb"); 52 | ASSERT(r == "bbbaaa"); 53 | 54 | const auto loa = 55 | archimedes::reflect_functions("lots_of_args").begin()->first(); 56 | ASSERT(loa); 57 | 58 | std::string a = "aaa"; 59 | std::string b = "b"; 60 | ASSERT( 61 | loa->invoke( 62 | 1, 2, 3, 63 | archimedes::cref(a), 64 | archimedes::rref(std::move(b)))->as() 65 | == 10); 66 | 67 | const auto ial = archimedes::reflect_functions("i_am_inline").begin()->first(); 68 | ASSERT(ial); 69 | ASSERT(ial->can_invoke()); 70 | ASSERT(ial->invoke(4)->as() == 16); 71 | 72 | const auto maiv = archimedes::reflect_functions("my_arg_is_void").begin()->first(); 73 | ASSERT(maiv); 74 | ASSERT(maiv->can_invoke()); 75 | maiv->invoke(); 76 | ASSERT(maiv->invoke()->as() == "xyz"); 77 | 78 | Foo foo; 79 | const auto inc_w = f->as_record().function("inc_w"); 80 | const auto member_c = f->as_record().function("member_c"); 81 | const auto member_cr = f->as_record().function("member_cr"); 82 | const auto member_rr = f->as_record().function("member_rr"); 83 | 84 | foo.w = 200; 85 | ASSERT(inc_w->invoke(&foo, 3).is_success()); 86 | ASSERT(foo.w == 201); 87 | 88 | foo.w = 21; 89 | ASSERT(member_c->invoke(const_cast(&foo), 2)->as() == 23); 90 | ASSERT(member_cr->invoke(archimedes::cref(foo), 2)->as() == 25); 91 | ASSERT(member_rr->invoke(std::move(foo), 2)->as() == 27); 92 | 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /test/invoke.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "test.hpp" 6 | 7 | struct ICountMyMoves { 8 | int count = 0; 9 | 10 | ICountMyMoves() = default; 11 | ICountMyMoves(const ICountMyMoves&) = default; 12 | ICountMyMoves(ICountMyMoves &&other) { *this = std::move(other); } 13 | ICountMyMoves &operator=(const ICountMyMoves&) = delete; 14 | ICountMyMoves &operator=(ICountMyMoves &&other) { 15 | this->count = other.count + 1; 16 | return *this; 17 | } 18 | }; 19 | 20 | struct Foo { 21 | int w = 20; 22 | 23 | int inc_w(int x) { this->w++; return this->w; } 24 | int member_c(int x) const { return this->w + 2; } 25 | int member_cr(int x) const& { return this->w + 4; } 26 | int member_rr(int x) && { return this->w + 6; } 27 | 28 | static int some_static_method(const int &x) { 29 | return x + 2; 30 | } 31 | 32 | static int another_static_method(ICountMyMoves &&x) { 33 | ICountMyMoves i = std::move(x); 34 | return i.count; 35 | } 36 | }; 37 | 38 | std::string &i_return_my_argument(std::string &s); 39 | 40 | std::string &i_return_something_new(std::string &s); 41 | 42 | int lots_of_args(int x, int y, int z, const std::string &p, std::string &&q); 43 | 44 | inline int i_am_inline(int x) { return x * x; } 45 | 46 | inline std::string my_arg_is_void(void) { return "xyz"; } 47 | -------------------------------------------------------------------------------- /test/lambda.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "lambda.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | std::vector xs = { 1, 2, 3, 4, 5 }; 7 | int sum = 0; 8 | each(xs, [&](const auto &i) { sum += i; }); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /test/lambda.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | inline void each(const T &ts, F &&f) { 7 | for (const auto &t : ts) { 8 | f(t); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /test/local_struct.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "local_struct.test.hpp" 3 | 4 | struct SomeStruct { 5 | int x; 6 | }; 7 | 8 | int func(const SomeStruct &s) { return s.x; } 9 | 10 | int main(int argc, char *argv[]) { 11 | archimedes::load(); 12 | const auto r_func = archimedes::reflect_function("func"); 13 | ASSERT(r_func); 14 | ASSERT(!r_func->can_invoke()); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /test/local_struct.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /test/member_ptr.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "member_ptr.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto f = archimedes::reflect()->as_record(); 7 | Foo foo; 8 | Bar bar; 9 | 10 | ASSERT( 11 | *f.field("foo_member")->type() == 12 | archimedes::reflect()); 13 | ASSERT( 14 | *f.field("bar_member")->type() == 15 | archimedes::reflect()); 16 | ASSERT( 17 | *f.field("foo_ptr")->type() == 18 | archimedes::reflect()); 19 | 20 | 21 | f.field("foo_member")->set(foo, &Foo::x); 22 | foo.*f.field("foo_member")->get(foo)->get() = 12; 23 | ASSERT(foo.x == 12); 24 | 25 | f.field("foo_member")->set(foo, &Foo::y); 26 | foo.*f.field("foo_member")->get(foo)->get() = 20; 27 | ASSERT(foo.y == 20); 28 | 29 | f.field("bar_member")->set(foo, &Bar::f); 30 | bar.*f.field("bar_member")->get(foo)->get() = 0.125f; 31 | ASSERT(bar.f == 0.125f); 32 | 33 | bar.ptr0 = nullptr; 34 | bar.ptr1 = &foo; 35 | 36 | f.field("foo_ptr")->set(foo, &Bar::ptr0); 37 | ASSERT( 38 | bar.*f.field("foo_ptr")->get(foo)->get() 39 | == nullptr); 40 | f.field("foo_ptr")->set(foo, &Bar::ptr1); 41 | ASSERT( 42 | bar.*f.field("foo_ptr")->get(foo)->get() 43 | == &foo); 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /test/member_ptr.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Bar; 4 | 5 | struct Foo { 6 | int x, y; 7 | int Foo::*foo_member; 8 | float Bar::*bar_member; 9 | Foo *Bar::*foo_ptr; 10 | }; 11 | 12 | struct Bar { 13 | float f; 14 | Foo *ptr0, *ptr1; 15 | }; 16 | -------------------------------------------------------------------------------- /test/namespaces.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "namespaces.test.hpp" 3 | 4 | FORCE_TYPE_INSTANTIATION(Bar) 5 | FORCE_TYPE_INSTANTIATION(Foo) 6 | FORCE_TYPE_INSTANTIATION(a::InA) 7 | FORCE_TYPE_INSTANTIATION(a::b::InB) 8 | FORCE_TYPE_INSTANTIATION(a::b::c::InC) 9 | FORCE_FUNCTION_INSTANTIATION(a::i_use_func_in_anon_namespace) 10 | FORCE_TYPE_INSTANTIATION(a::IHaveEnumParamA) 11 | FORCE_TYPE_INSTANTIATION(ns::Foo) 12 | 13 | int main(int argc, char *argv[]) { 14 | archimedes::load(); 15 | const auto r_foo = archimedes::reflect(); 16 | // should be able to reflect because even though it's in an anonymous 17 | // namespace, the using decl gets resolved at compile time 18 | ASSERT(r_foo); 19 | 20 | // should *not* be able to reflect baz because it's not an alias and has its 21 | // definition in an anonymous namespace 22 | const auto r_baz = archimedes::reflect(); 23 | ASSERT(!r_baz); 24 | ASSERT(r_foo == archimedes::reflect("Bar")); 25 | const auto r_foob_str = archimedes::reflect("Foob"); 26 | ASSERT(r_foob_str); 27 | 28 | const auto abc_inc = archimedes::reflect("abc::InC"); 29 | ASSERT(archimedes::reflect()); 30 | ASSERT(abc_inc); 31 | ASSERT(abc_inc == archimedes::reflect()); 32 | 33 | const auto a_bc_inc = archimedes::reflect("a::bc::InC"); 34 | ASSERT(a_bc_inc); 35 | ASSERT(a_bc_inc == abc_inc); 36 | 37 | const auto ab_d_inc = archimedes::reflect("ab::d::InC"); 38 | ASSERT(ab_d_inc); 39 | ASSERT(ab_d_inc == abc_inc); 40 | 41 | ASSERT( 42 | abc_inc->as_record().bases()[0].type() == 43 | archimedes::reflect("ab::InB")); 44 | 45 | const auto ab_ind = archimedes::reflect("a::b::InD"); 46 | ASSERT(ab_ind); 47 | 48 | const auto abc_indinc = archimedes::reflect("abc::InDInC"); 49 | ASSERT(abc_indinc); 50 | ASSERT(abc_indinc == archimedes::reflect()); 51 | 52 | const auto ns_foo = archimedes::reflect(); 53 | ASSERT(ns_foo); 54 | ASSERT(ns_foo->function_set("Foo")); 55 | ASSERT(ns_foo->constructors()); 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /test/namespaces.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | struct Bar { T field; }; 5 | 6 | template 7 | int func(T x) { 8 | return x + 12; 9 | } 10 | 11 | // nothing in anonymous namespaces can be reflected 12 | namespace { 13 | using Foo = Bar; 14 | 15 | struct Baz : Foo { 16 | int y; 17 | }; 18 | } 19 | 20 | using Foob = Bar; 21 | 22 | namespace a { 23 | struct InA { }; 24 | 25 | namespace b { 26 | struct InB { }; 27 | 28 | namespace c { 29 | struct InC : public InB { }; 30 | 31 | namespace d { 32 | struct InD : public InC { }; 33 | } 34 | 35 | using InDInC = d::InD; 36 | 37 | enum EnumInC { 38 | ENUM_A, 39 | ENUM_B 40 | }; 41 | } 42 | 43 | using InAInB = InA; 44 | 45 | namespace d = c; 46 | using namespace d::d; 47 | } 48 | 49 | template 50 | struct IHaveEnumParam { 51 | static constexpr b::c::EnumInC E_VALUE = E; 52 | }; 53 | 54 | using IHaveEnumParamA = IHaveEnumParam; 55 | 56 | namespace { 57 | struct AnonymousInA {}; 58 | 59 | inline int func_in_anon_namespace(int bar) { 60 | return bar + 2; 61 | } 62 | } 63 | 64 | inline int i_use_func_in_anon_namespace() { 65 | return func_in_anon_namespace(2); 66 | } 67 | 68 | namespace bc = b::c; 69 | namespace cb = b::c; 70 | } 71 | 72 | namespace abc = a::b::c; 73 | namespace ab = a::b; 74 | 75 | namespace ns { 76 | struct Foo { 77 | int x; 78 | Foo() : x(12) {} 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /test/nested_template_args.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "nested_template_args.test.hpp" 3 | 4 | FORCE_TYPE_INSTANTIATION(Foo) 5 | namespace std { 6 | FORCE_TYPE_INSTANTIATION(Foo>>>>) 7 | } 8 | ARCHIMEDES_REFLECT_TYPE_REGEX(".*Foo.*") 9 | FORCE_TYPE_INSTANTIATION(q::Baz) 10 | 11 | int main(int argc, char *argv[]) { 12 | Foo f; 13 | ASSERT(f.foo(ns::Enum::A) == ns::Enum::A); 14 | 15 | Foo>>>> f2; 16 | f2.foo(Bar>>>()); 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /test/nested_template_args.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ns { 6 | enum Enum { 7 | A, 8 | B 9 | }; 10 | } 11 | 12 | template 13 | struct Foo { 14 | T foo(const std::optional &o) { return *o; } 15 | }; 16 | 17 | template 18 | struct Bar { 19 | T t; 20 | }; 21 | 22 | namespace q { 23 | using namespace std; 24 | struct Baz : Foo>>>> { 25 | void fn(int my_param) { } 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /test/no_reflect.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "no_reflect.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | Foo f; 7 | Bar b; 8 | b.w = f.x; 9 | const auto foo = archimedes::reflect(); 10 | ASSERT(!foo); 11 | const auto bar = archimedes::reflect(); 12 | ASSERT(bar); 13 | ASSERT(bar->as_record().field("z")); 14 | ASSERT(bar->as_record().field("w")); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /test/no_reflect.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct [[ARCHIMEDES_NO_REFLECT]] Foo { 6 | int x, y; 7 | }; 8 | 9 | struct Bar { 10 | int z, w; 11 | }; 12 | -------------------------------------------------------------------------------- /test/operator.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "operator.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto iho = archimedes::reflect("IHaveOperators"); 7 | ASSERT(iho); 8 | 9 | const auto p = iho->as_record().function("operator\\+"); 10 | ASSERT(p); 11 | 12 | const auto bsl = iho->as_record().function("operator<<"); 13 | ASSERT(bsl); 14 | 15 | IHaveOperators a { .x = 1 }, b { .x = 2 }; 16 | ASSERT(p->invoke(&a, archimedes::cref(b))->as().x == 3); 17 | 18 | ASSERT(bsl->invoke(&a, false)->as().x == -1); 19 | ASSERT(bsl->invoke(&a, true)->as().x == 1); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /test/operator.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct IHaveOperators { 4 | int x; 5 | 6 | IHaveOperators operator+(const IHaveOperators &rhs) const { 7 | auto t = *this; 8 | t.x += rhs.x; 9 | return t; 10 | } 11 | 12 | IHaveOperators operator<<(bool x) const { 13 | auto t = *this; 14 | t.x *= x ? 1 : -1; 15 | return t; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /test/overload.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "overload.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto bars = 7 | archimedes::reflect()->as_record().function_set("bar"); 8 | ASSERT(bars); 9 | ASSERT(bars->size() == 3); 10 | 11 | Foo f; 12 | ASSERT( 13 | bars->get()->invoke(&f, 1)->as() == 1); 14 | ASSERT( 15 | bars->get()->invoke(&f, 1, 2)->as() == 3); 16 | ASSERT( 17 | bars->get()->invoke(&f, 1, 2, 3)->as() == 6); 18 | 19 | const auto bazs = archimedes::reflect_functions("baz"); 20 | ASSERT(bazs.begin()->size() == 3); 21 | ASSERT( 22 | bazs.begin()->get()->invoke(1)->as() == 1); 23 | ASSERT( 24 | bazs.begin()->get()->invoke(1, 2)->as() == 3); 25 | ASSERT( 26 | bazs.begin()->get()->invoke(1, 2, 3)->as() == 6); 27 | 28 | const auto ns_baz = archimedes::reflect_functions("ns::baz"); 29 | ASSERT(ns_baz.size() == 1); 30 | ASSERT(ns_baz.begin()->get()->invoke(100)->as() == -100); 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /test/overload.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo { 4 | int bar(int x) { return x; } 5 | int bar(int x, int y) { return x + y; } 6 | int bar(int x, int y, int z) { return x + y + z; } 7 | }; 8 | 9 | inline int baz(int x) { return x; } 10 | inline int baz(int x, int y) { return x + y; } 11 | inline int baz(int x, int y, int z) { return x + y + z; } 12 | 13 | namespace ns { 14 | inline int baz(int x) { return -x; } 15 | } 16 | -------------------------------------------------------------------------------- /test/parameter_pack.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "parameter_pack.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | i_add_my_args(1, 2, 3, 4, 5); 7 | i_add_my_args(1, 2, 3); 8 | const auto iama = archimedes::reflect_functions("i_add_my_args"); 9 | ASSERT(iama.begin()->size() == 2); 10 | 11 | const auto iama3 = 12 | *std::find_if( 13 | iama.begin()->begin(), 14 | iama.begin()->end(), 15 | [](const auto &f) { 16 | return f.parameters().size() == 3; 17 | }); 18 | 19 | const auto iama5 = 20 | *std::find_if( 21 | iama.begin()->begin(), 22 | iama.begin()->end(), 23 | [](const auto &f) { 24 | return f.parameters().size() == 5; 25 | }); 26 | 27 | ASSERT(iama3.invoke(4, 5, 20)->as() == 29); 28 | ASSERT(iama5.invoke(10, 10, 33, 1, 2)->as() == 56); 29 | 30 | i_dont_collide(1, 2, 3, 4, 5, 6); 31 | const auto idc = archimedes::reflect_functions("i_dont_collide"); 32 | ASSERT(idc.begin()->size() == 1); 33 | ASSERT(idc.begin()->first()->parameters().size() == 6); 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /test/parameter_pack.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | int i_add_my_args(Ts&& ...ts) { 7 | int x = 0; 8 | ([&x](auto &&t){ 9 | x += t; 10 | }(std::forward(ts)), ...); 11 | return x; 12 | } 13 | 14 | template 15 | int i_dont_collide(int ts_, int ts_pp_0, int ts_pp______2, Ts&& ...ts) { 16 | int x; 17 | ([&x](auto &&t){ 18 | x += t; 19 | }(std::forward(ts)), ...); 20 | return x; 21 | } 22 | -------------------------------------------------------------------------------- /test/ptr.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "ptr.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | 7 | const auto foo = archimedes::reflect(); 8 | const auto bar = archimedes::reflect(); 9 | ASSERT(foo->field("foo")->type() != bar->field("foo")->type()); 10 | ASSERT(foo->field("foo")->type().type() != bar->field("foo")->type().type()); 11 | 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /test/ptr.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo; 4 | 5 | struct Bar { 6 | const Foo *foo; 7 | Bar *bar; 8 | }; 9 | 10 | struct Foo { 11 | const Bar *bar; 12 | Foo *foo; 13 | }; 14 | -------------------------------------------------------------------------------- /test/ptr_in_template.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "ptr_in_template.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto foo = archimedes::reflect(); 7 | ASSERT(foo); 8 | ASSERT(foo->as_record().function)>()); 9 | ASSERT(foo->as_record().function("Foo")); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /test/ptr_in_template.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Foo { 6 | std::shared_ptr bs; 7 | 8 | Foo(std::shared_ptr bs) 9 | : bs(bs) {} 10 | }; 11 | -------------------------------------------------------------------------------- /test/record_typedef.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "record_typedef.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto 7 | foo = archimedes::reflect(), 8 | in_foo = archimedes::reflect(), 9 | bar = archimedes::reflect(); 10 | 11 | ASSERT(foo->type_aliases().size() == 2); 12 | ASSERT(in_foo->type_aliases().size() == 1); 13 | ASSERT(bar->type_aliases().size() == 5); 14 | 15 | ASSERT( 16 | bar->type_alias("FooPtr1")->type().type() 17 | == archimedes::reflect()); 18 | ASSERT( 19 | bar->type_alias("FooPtr1A")->type().type() 20 | == archimedes::reflect()); 21 | ASSERT( 22 | bar->type_alias("FooPtr2")->type().type() 23 | == archimedes::reflect()); 24 | ASSERT( 25 | bar->type_alias("FooPtr2A")->type().type() 26 | == archimedes::reflect()); 27 | ASSERT( 28 | bar->type_alias("BarRef")->type().type() 29 | == archimedes::reflect()); 30 | ASSERT( 31 | in_foo->type_alias("Z")->type().type() 32 | == archimedes::reflect()); 33 | ASSERT( 34 | foo->type_alias("X")->type().type() 35 | == archimedes::reflect()); 36 | ASSERT( 37 | foo->type_alias("Y")->type().type() 38 | == archimedes::reflect()); 39 | 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /test/record_typedef.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo; 4 | 5 | struct Bar { 6 | using FooPtr1 = const Foo*; 7 | typedef const Foo* FooPtr1A; 8 | using FooPtr2 = Foo*; 9 | typedef Foo* FooPtr2A; 10 | using BarRef = Bar&; 11 | }; 12 | 13 | struct Foo { 14 | struct InFoo { 15 | using Z = Foo; 16 | }; 17 | 18 | using X = int; 19 | using Y = InFoo; 20 | }; 21 | -------------------------------------------------------------------------------- /test/reflect_type.test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "test.hpp" 4 | #include "reflect_type.test.hpp" 5 | 6 | ARCHIMEDES_ARG("explicit-enable") 7 | ARCHIMEDES_ENABLE() 8 | 9 | ARCHIMEDES_REFLECT_TYPE(Bar) 10 | 11 | ARCHIMEDES_FORCE_TYPE_INSTANTIATION(Baz) 12 | ARCHIMEDES_REFLECT_TYPE(Baz) 13 | 14 | ARCHIMEDES_REFLECT_TYPE_REGEX("Qux.*") 15 | ARCHIMEDES_REFLECT_TYPE_REGEX("ns::Qux.*") 16 | 17 | int main(int argc, char *argv[]) { 18 | Baz b; 19 | b.t = 3485; 20 | 21 | archimedes::load(); 22 | ASSERT(!archimedes::reflect()); 23 | ASSERT(archimedes::reflect()); 24 | ASSERT(archimedes::reflect()); 25 | ASSERT(archimedes::reflect()); 26 | ASSERT(archimedes::reflect>()); 27 | ASSERT(!archimedes::reflect()); 28 | ASSERT(archimedes::reflect()); 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /test/reflect_type.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo { 4 | int x; 5 | }; 6 | 7 | struct Bar { 8 | int y; 9 | }; 10 | 11 | template 12 | struct Baz { 13 | T t; 14 | }; 15 | 16 | struct Qux { 17 | int y; 18 | }; 19 | 20 | struct Quxical { 21 | int z; 22 | }; 23 | 24 | namespace ns { 25 | struct Buxtastic { int z; }; 26 | struct Quxtastic { int z; }; 27 | }; 28 | -------------------------------------------------------------------------------- /test/requires.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "requires.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | 7 | // TODO 8 | 9 | const auto f_f = archimedes::reflect>(); 10 | ASSERT(f_f->function("bar")); 11 | ASSERT(!f_f->function("baz")); 12 | // TODO: ASSERT(!f_f->function("boo")); 13 | ASSERT(f_f->function("qux")); 14 | 15 | const auto f_i = archimedes::reflect>(); 16 | ASSERT(!f_i->function("bar")); 17 | ASSERT(f_i->function("baz")); 18 | // TODO: ASSERT(f_i->function("boo")); 19 | ASSERT(f_i->function("qux")); 20 | 21 | Foo().bar(); 22 | Foo().baz(); 23 | Foo().boo(); 24 | Foo().qux(); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /test/requires.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | struct Foo { 7 | int bar() requires std::is_floating_point_v { 8 | return 12; 9 | } 10 | 11 | int baz() requires std::is_integral_v { 12 | return 14; 13 | } 14 | 15 | template 16 | auto boo() -> typename std::enable_if, int>::type { 17 | return 1334; 18 | } 19 | 20 | int qux() { return 123; } 21 | }; 22 | -------------------------------------------------------------------------------- /test/specialize.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "specialize.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | Bar b(2, 3, 4); 7 | Qux q(3); 8 | Foo f; 9 | Foo f_q; 10 | ASSERT(archimedes::reflect>()->default_constructor()); 11 | ASSERT(archimedes::reflect>()->copy_constructor()); 12 | ASSERT(archimedes::reflect>()->copy_assignment()); 13 | ASSERT(archimedes::reflect>()->default_constructor()); 14 | ASSERT(archimedes::reflect>()->copy_constructor()); 15 | ASSERT(archimedes::reflect>()->copy_assignment()); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /test/specialize.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Bar { 6 | float x; 7 | 8 | Bar() = default; 9 | Bar(float x, float y, float z) 10 | : x(x + y + z) {} 11 | }; 12 | 13 | struct Qux { 14 | virtual ~Qux() = default; 15 | 16 | template 17 | Qux(T &&t) : x(t) {} 18 | 19 | int x; 20 | }; 21 | 22 | template 23 | struct Foo; 24 | 25 | template 26 | requires std::is_class_v 27 | struct Foo { 28 | int x; 29 | 30 | Foo() = default; 31 | 32 | Foo &operator=(const Foo &foo) { 33 | this->x = 13; 34 | } 35 | 36 | Foo(const Foo &foo) { 37 | this->x = 12; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /test/static_field.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "static_field.test.hpp" 3 | 4 | FORCE_TYPE_INSTANTIATION(Baz) 5 | FORCE_TYPE_INSTANTIATION(Boo0) 6 | 7 | int main(int argc, char *argv[]) { 8 | archimedes::load(); 9 | const auto f = archimedes::reflect(); 10 | ASSERT(f); 11 | const auto x = f->as_record().static_field("x"); 12 | ASSERT(x); 13 | ASSERT(x->is_constexpr()); 14 | ASSERT(x->constexpr_value()->as() == 12); 15 | const auto s = f->as_record().static_field("s"); 16 | ASSERT(s->constexpr_value()->as() == Foo::s); 17 | 18 | const auto n = archimedes::reflect(); 19 | const auto y = n->as_record().static_field("y"); 20 | ASSERT(y); 21 | ASSERT(y->constexpr_value()->as() == 39); 22 | 23 | const auto b = archimedes::reflect>(); 24 | ASSERT(b); 25 | 26 | // TODO: for some reason template constexpr VarDecls do not have init 27 | // expressions :( 28 | /* const auto i = b->as_record().static_field("field"); */ 29 | /* ASSERT(i); */ 30 | /* ASSERT(i->type().type() == archimedes::reflect()); */ 31 | /* ASSERT(i->constexpr_value()->as() == 333); */ 32 | 33 | const auto iumn = archimedes::reflect("a::b::IUseMyNamespaces"); 34 | ASSERT(iumn); 35 | ASSERT( 36 | iumn->as_record().static_field("w") 37 | ->constexpr_value() 38 | ->as() == 26); 39 | ASSERT( 40 | iumn->as_record().static_field("p") 41 | ->constexpr_value() 42 | ->as().x == 4); 43 | ASSERT( 44 | archimedes::reflect("Boo0")->as_record() 45 | .static_field("E_VALUE") 46 | ->constexpr_value() 47 | ->as() 48 | == a::b::c::VALUE_0); 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /test/static_field.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace a { 4 | struct IntWrapper { int x; }; 5 | struct IntPairWrapper { int x, y; }; 6 | static constexpr int aa = 2; 7 | namespace b { 8 | static constexpr int bb = 2; 9 | 10 | namespace c { 11 | enum E { 12 | VALUE_0 = 0, 13 | VALUE_1 = 12 14 | }; 15 | 16 | static constexpr IntWrapper w = IntWrapper { .x = 22 }; 17 | } 18 | 19 | struct IUseMyNamespaces { 20 | static constexpr IntWrapper w = 21 | IntWrapper { .x = aa + bb + c::w.x }; 22 | static constexpr IntPairWrapper p = 23 | IntPairWrapper { .x = aa + bb, .y = aa - bb }; 24 | }; 25 | } 26 | } 27 | 28 | struct Foo { 29 | static constexpr int x = 12; 30 | static constexpr const char *s = "some string"; 31 | 32 | struct Nested { 33 | static constexpr int y = 39; 34 | }; 35 | }; 36 | 37 | template 38 | struct Baz { 39 | static constexpr T field = 333; 40 | int fff = 3; 41 | }; 42 | 43 | template 44 | struct Boo { 45 | static constexpr a::b::c::E E_VALUE = OPTION; 46 | }; 47 | 48 | using Boo0 = Boo; 49 | 50 | -------------------------------------------------------------------------------- /test/static_method.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "static_method.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | 7 | const auto f = archimedes::reflect(); 8 | ASSERT(f); 9 | 10 | const auto bars = f->as_record().function_set("bar"); 11 | ASSERT(bars); 12 | ASSERT(bars->size() == 1); 13 | 14 | const auto bazs = f->as_record().function_set("baz"); 15 | ASSERT(bazs); 16 | ASSERT(bazs->size() == 2); 17 | 18 | const auto f_m = bazs->get(); 19 | const auto f_s = bazs->get(); 20 | 21 | ASSERT(f_m); 22 | ASSERT(f_s); 23 | ASSERT(*f_m != *f_s); 24 | 25 | ASSERT(!f_m->is_static() && f_m->is_member()); 26 | ASSERT(f_s->is_static() && f_s->is_member()); 27 | 28 | Foo foo; 29 | ASSERT(f_m->invoke(&foo, 2)->as() == 4); 30 | ASSERT(f_s->invoke(2, 3)->as() == 5); 31 | 32 | const auto q = archimedes::reflect(); 33 | const auto f_q = q->as_record().function("qux_me"); 34 | ASSERT(f_q->invoke(44)->as() == 47); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /test/static_method.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo { 4 | struct Qux { 5 | int z; 6 | 7 | static int qux_me(int z) { 8 | return z + 3; 9 | } 10 | }; 11 | 12 | int bar(int x) { 13 | return x + 1; 14 | } 15 | 16 | int baz(int z) { 17 | return z + 2; 18 | } 19 | 20 | static int baz(int z, int w) { 21 | return z + w; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /test/stdlib.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "stdlib.test.hpp" 3 | 4 | namespace arc = archimedes; 5 | 6 | // TODO: test is broken 7 | // type name printers can't print nested stdlib types in parameter packs?? 8 | 9 | ARCHIMEDES_ARG("include-ns-std") 10 | ARCHIMEDES_ARG("include-path-regex-.*/string") 11 | ARCHIMEDES_REFLECT_TYPE_REGEX("std::string.*"); 12 | ARCHIMEDES_REFLECT_TYPE_REGEX("std::basic_string.*"); 13 | // TODO: broken ARCHIMEDES_ARG("include-path-regex-.*/iosfwd") 14 | 15 | using MyMap = std::unordered_map; 16 | 17 | int main(int argc, char *argv[]) { 18 | arc::load(); 19 | ASSERT((arc::reflect>())); 20 | ASSERT( 21 | arc::type_id::from() 22 | == arc::type_id::from>()); 23 | /* ASSERT(arc::reflect>()); */ 24 | /* ASSERT(arc::reflect()); */ 25 | /* ASSERT( */ 26 | /* arc::reflect()->id() */ 27 | /* == arc::type_id::from().value()); */ 28 | /* ASSERT( */ 29 | /* arc::reflect>()->id() */ 30 | /* == arc::type_id::from>().value()); */ 31 | // TODO: broken ASSERT(arc::reflect()); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /test/stdlib.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /test/template_method.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "template_method.test.hpp" 3 | 4 | struct Bar { 5 | 6 | }; 7 | 8 | using BooDouble = Boo; 9 | FORCE_TYPE_INSTANTIATION(BooDouble); 10 | 11 | int main(int argc, char *argv[]) { 12 | archimedes::load(); 13 | Foo f; 14 | ASSERT(f.bar(static_cast(12)) == 13); 15 | ASSERT(f.bar(std::move(12)) == 2); 16 | f.baz(); 17 | const auto fs = 18 | *archimedes::reflect()->function_set("bar"); 19 | ASSERT(fs.size() == 2); 20 | 21 | const auto bar_templated_opt = fs.get(); 22 | ASSERT(bar_templated_opt); 23 | const auto bar_templated = *bar_templated_opt; 24 | 25 | const auto bar_normal_opt = fs.get(); 26 | ASSERT(bar_normal_opt); 27 | const auto bar_normal = *bar_normal_opt; 28 | 29 | ASSERT(bar_templated != bar_normal); 30 | 31 | ASSERT(bar_templated.is_member()); 32 | ASSERT(bar_templated.parameter("t")); 33 | ASSERT( 34 | bar_templated.parameter("t")->type().type() 35 | == *archimedes::reflect()); 36 | 37 | ASSERT(bar_normal.is_member()); 38 | ASSERT(bar_normal.parameter("i")); 39 | ASSERT( 40 | bar_normal.parameter("i")->type().type() 41 | == *archimedes::reflect()); 42 | 43 | const auto bd = archimedes::reflect>(); 44 | ASSERT(bd); 45 | 46 | Quxical().qux(4, 55); 47 | Quxical().qux(4); 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /test/template_method.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | struct Boo { 7 | T t; 8 | 9 | T operator()(T t) { 10 | return t + 1; 11 | } 12 | }; 13 | 14 | using IntBoo = Boo; 15 | 16 | template 17 | struct Qux { 18 | 19 | }; 20 | 21 | template 22 | struct Qux { 23 | T operator()(T t) { 24 | return t + 1; 25 | } 26 | }; 27 | 28 | struct Foo { 29 | operator IntBoo() const { 30 | return IntBoo { .t = 144 }; 31 | } 32 | 33 | template 34 | int bar(const T &t) const { 35 | return static_cast(t) + 1; 36 | } 37 | 38 | int bar(int &&i) const { 39 | return 2; 40 | } 41 | 42 | template 43 | void baz() { 44 | T(); 45 | U(); 46 | } 47 | }; 48 | 49 | template <> 50 | struct Boo { 51 | double special_function() { return 4.0; } 52 | }; 53 | 54 | struct IntHolder { 55 | int i; 56 | 57 | IntHolder() = default; 58 | IntHolder(int x) : i(x) {} 59 | }; 60 | 61 | struct Quxical { 62 | template 63 | E &qux(int i, Args&& ...args) { 64 | return (*new E(std::forward(args)...)); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /test/template_params.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "template_params.test.hpp" 3 | 4 | using MyBar = Bar; 5 | FORCE_TYPE_INSTANTIATION(MyBar); 6 | 7 | int main(int argc, char *argv[]) { 8 | archimedes::load(); 9 | const auto ps = 10 | archimedes::reflect>()->template_parameters(); 11 | ASSERT(ps.size() == 2); 12 | ASSERT(ps[0].name() == "T"); 13 | ASSERT(ps[0].is_typename()); 14 | ASSERT(*ps[0].type() == archimedes::reflect()); 15 | ASSERT(ps[1].name() == "X"); 16 | ASSERT(!ps[1].is_typename()); 17 | ASSERT(*ps[1].type() == archimedes::reflect()); 18 | ASSERT(ps[1].value()->as() == 44); 19 | 20 | ASSERT(archimedes::reflect()->template_parameters().size() == 0); 21 | ASSERT(archimedes::reflect()->template_parameters().size() == 0); 22 | 23 | Foo foo; 24 | foo.t.x = 12; 25 | ASSERT(archimedes::reflect>()->template_parameters().size() == 1); 26 | 27 | ASSERT( 28 | archimedes::reflect()->bases()[0] 29 | .type() 30 | .template_parameters() 31 | .size() == 2); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /test/template_params.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | struct Foo { 5 | T t; 6 | }; 7 | 8 | template <> 9 | struct Foo { int something_special; }; 10 | 11 | template 12 | struct Bar { 13 | T t; 14 | }; 15 | 16 | struct Baz { 17 | int x, y; 18 | }; 19 | 20 | struct Qux : Bar {}; 21 | 22 | template 23 | struct Bar { 24 | int special() { return 12; } 25 | }; 26 | 27 | struct Foob : Bar {}; 28 | -------------------------------------------------------------------------------- /test/test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define FMT_HEADER_ONLY 8 | #include 9 | 10 | #include 11 | 12 | template 13 | static std::string _assert_fmt( 14 | const std::string &fmt = "", 15 | Args&&... args) { 16 | if (fmt.length() > 0) { 17 | return fmt::vformat( 18 | std::string_view(fmt), 19 | fmt::make_format_args(std::forward(args)...)); 20 | } 21 | 22 | return ""; 23 | } 24 | 25 | #define LOG_PREFIX() \ 26 | fmt::format( \ 27 | "[{}:{}][{}]",\ 28 | __FILE__, \ 29 | __LINE__, \ 30 | __FUNCTION__) 31 | 32 | #define LOG(_fmt, ...) \ 33 | std::cout \ 34 | << LOG_PREFIX() \ 35 | << " " \ 36 | << (fmt::format(_fmt, ## __VA_ARGS__))\ 37 | << std::endl; 38 | 39 | #define ASSERT(_e, ...) do { \ 40 | if (!(_e)) { \ 41 | const auto __msg = _assert_fmt(__VA_ARGS__); \ 42 | LOG( \ 43 | "ASSERTION FAILED{}{}", __msg.length() > 0 ? " " : "", __msg); \ 44 | std::exit(1); \ 45 | } \ 46 | } while (0) 47 | 48 | #define CONCAT_IMPL(x, y) x ## y 49 | #define CONCAT(x, y) CONCAT_IMPL(x, y) 50 | 51 | #define FORCE_FUNCTION_INSTANTIATION_IMPL(_f, _n) \ 52 | struct _n { _n(const void *p) { \ 53 | std::string _ = fmt::format("{}", fmt::ptr(p)); } }; \ 54 | auto CONCAT(_i_, __COUNTER__) = _n(reinterpret_cast(&(_f))); 55 | 56 | #define FORCE_FUNCTION_INSTANTIATION(_f) \ 57 | FORCE_FUNCTION_INSTANTIATION_IMPL(_f, CONCAT(_fi_t_, __COUNTER__)) 58 | 59 | #define FORCE_TYPE_INSTANTIATION_IMPL(_n, ...) \ 60 | struct _n { \ 61 | __VA_ARGS__ t; \ 62 | void *p; _n() : p(foo()) {} \ 63 | void *foo() { return &t; }}; \ 64 | auto CONCAT(_i_, __COUNTER__) = _n(); 65 | 66 | #define FORCE_TYPE_INSTANTIATION(...) \ 67 | FORCE_TYPE_INSTANTIATION_IMPL(CONCAT(_i_, __COUNTER__), __VA_ARGS__) 68 | 69 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | // archimedes test runner 2 | // usage: $ tests [tests directory] 3 | // looks for executables in test directory and runs them, failing if any of the 4 | // executables return 1 (error) 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define FMT_HEADER_ONLY 12 | #include 13 | 14 | template 15 | static std::string _assert_fmt( 16 | const std::string &fmt = "", 17 | Args&&... args) { 18 | if (fmt.length() > 0) { 19 | return fmt::vformat( 20 | std::string_view(fmt), 21 | fmt::make_format_args(std::forward(args)...)); 22 | } 23 | 24 | return ""; 25 | } 26 | 27 | #define ASSERT(_e, ...) do { \ 28 | if (!(_e)) { \ 29 | const auto __msg = _assert_fmt(__VA_ARGS__); \ 30 | fmt::print( \ 31 | "ASSERTION FAILED{}{}\n", \ 32 | __msg.length() > 0 ? " " : "", __msg); \ 33 | std::exit(1); \ 34 | } \ 35 | } while (0) 36 | 37 | namespace fs = std::filesystem; 38 | 39 | int main(int argc, char *argv[]) { 40 | ASSERT(argc >= 2, "expected argc >= 2"); 41 | 42 | std::vector explicit_tests; 43 | for (int i = 2; i < argc; i++) { 44 | explicit_tests.push_back(argv[i]); 45 | } 46 | 47 | const auto tests_dir = fs::path(argv[1]); 48 | ASSERT(fs::is_directory(tests_dir), "argv[1] must be directory"); 49 | 50 | // format strings for good things/bad things 51 | std::string fmt_good = "{}", fmt_bad = "{}"; 52 | 53 | // check if terminal probably supports ANSI color codes 54 | if (std::getenv("TERM") != nullptr) { 55 | fmt_good = "\033[32;1m{}\033[0m"; 56 | fmt_bad = "\033[31;1m{}\033[0m"; 57 | } 58 | 59 | // execute command, return (exit code, output) 60 | const auto exec = 61 | [](std::string_view cmd) -> std::tuple { 62 | FILE *pipe = popen(&cmd[0], "r"); 63 | 64 | if (!pipe) { 65 | return std::make_tuple(1, ""); 66 | } 67 | 68 | std::string out; 69 | char c; 70 | while ((c = std::fgetc(pipe)) != EOF) { 71 | out += c; 72 | } 73 | 74 | return std::make_tuple(pclose(pipe), out); 75 | }; 76 | 77 | // true if any test failed 78 | bool fail = false; 79 | 80 | for (const auto &e : fs::directory_iterator(tests_dir)) { 81 | const auto &p = e.path(); 82 | 83 | if (!fs::exists(p) 84 | || fs::is_directory(p) 85 | || p.has_extension() 86 | || ((fs::status(p).permissions() 87 | & (fs::perms::owner_exec 88 | | fs::perms::group_exec 89 | | fs::perms::others_exec)) == fs::perms::none) 90 | || fs::equivalent(fs::path(argv[0]), p)) { 91 | continue; 92 | } 93 | 94 | // check if explicit tests specified 95 | if (!explicit_tests.empty() 96 | && !std::any_of( 97 | explicit_tests.begin(), 98 | explicit_tests.end(), 99 | [&](const auto &name) { 100 | return name == p.filename().string(); 101 | })) { 102 | continue; 103 | } 104 | 105 | fmt::print("{} ... ", p.filename().string()); 106 | const auto [exit_code, out] = 107 | exec(fmt::format("./{} 2>&1", p.string())); 108 | 109 | if (!exit_code) { 110 | fmt::print( 111 | "{}\n{}", 112 | fmt::format( 113 | fmt::runtime(fmt_good), "[OK]"), 114 | (out.empty() 115 | || std::all_of( 116 | out.begin(), 117 | out.end(), 118 | [](char c) { return std::isspace(c); })) ? 119 | "" : out); 120 | } else { 121 | fmt::print( 122 | "{}\n{}", 123 | fmt::format( 124 | fmt::runtime(fmt_bad), 125 | "[FAIL]"), 126 | out); 127 | fail = true; 128 | } 129 | } 130 | 131 | if (fail) { 132 | fmt::print( 133 | "{}\n", 134 | fmt::format( 135 | fmt::runtime( 136 | fmt_bad), 137 | "TEST(S) FAILED")); 138 | return 1; 139 | } else { 140 | fmt::print( 141 | "{}\n", 142 | fmt::format( 143 | fmt::runtime( 144 | fmt_good), 145 | "ALL TESTS PASSED")); 146 | return 0; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /test/this.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "this.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto f = archimedes::reflect(); 7 | ASSERT(f); 8 | const auto bar = f->as_record().function_set("bar"); 9 | 10 | const auto bar_cl = 11 | *std::find_if( 12 | bar->begin(), bar->end(), 13 | [](const auto &f){ 14 | return f.type().is_const() && f.type().is_ref(); 15 | }); 16 | 17 | const auto bar_cr = 18 | *std::find_if( 19 | bar->begin(), bar->end(), 20 | [](const auto &f){ 21 | return f.type().is_const() && f.type().is_rref(); 22 | }); 23 | 24 | const auto bar_l = 25 | *std::find_if( 26 | bar->begin(), bar->end(), 27 | [](const auto &f){ 28 | return !f.type().is_const() && f.type().is_ref(); 29 | }); 30 | 31 | const auto bar_r = 32 | *std::find_if( 33 | bar->begin(), bar->end(), 34 | [](const auto &f){ 35 | return !f.type().is_const() && f.type().is_rref(); 36 | }); 37 | 38 | Foo foo; 39 | ASSERT(bar_cl.invoke(archimedes::cref(foo))->as() == 2); 40 | ASSERT(bar_cr.invoke(Foo())->as() == 3); 41 | ASSERT(bar_l.invoke(archimedes::ref(foo))->as() == 4); 42 | ASSERT(bar_r.invoke(Foo())->as() == 5); 43 | 44 | const auto baz = f->as_record().function_set("baz"); 45 | ASSERT(baz); 46 | 47 | const auto baz_const = 48 | *std::find_if( 49 | baz->begin(), baz->end(), 50 | [](const auto &f){ 51 | return f.type().is_const(); 52 | }); 53 | 54 | const auto baz_not_const = 55 | *std::find_if( 56 | baz->begin(), baz->end(), 57 | [](const auto &f){ 58 | return !f.type().is_const(); 59 | }); 60 | 61 | ASSERT(baz_const.invoke(const_cast(&foo))->as() == 6); 62 | ASSERT(baz_not_const.invoke(&foo)->as() == 7); 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /test/this.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Foo { 4 | int bar() const& { return 2; } 5 | int bar() const&& { return 3; } 6 | int bar() & { return 4; } 7 | int bar() && { return 5; } 8 | int baz() const { return 6; } 9 | int baz() { return 7; } 10 | }; 11 | -------------------------------------------------------------------------------- /test/traverse.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "traverse.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | // check that traversal order is correct 7 | bool got_z_w = false, got_z_y; 8 | archimedes::reflect()->traverse_bases( 9 | [&](const archimedes::reflected_base &base, 10 | const archimedes::any&) { 11 | const auto name = base.type().qualified_name(); 12 | if (name == "W") { 13 | ASSERT(!got_z_y && !got_z_w); 14 | got_z_w = true; 15 | } else if (name == "Y") { 16 | ASSERT(got_z_w); 17 | got_z_y = true; 18 | } else { 19 | ASSERT(false, "unknown base {}", name); 20 | } 21 | }); 22 | 23 | D d; 24 | archimedes::reflect()->traverse_bases( 25 | [&](const archimedes::reflected_base &base, 26 | const archimedes::any &any_ptr) { 27 | if (base.type().qualified_name() == "B") { 28 | ASSERT(any_ptr.as() == dynamic_cast(&d)); 29 | } else if (base.type().qualified_name() == "C") { 30 | ASSERT(any_ptr.as() == dynamic_cast(&d)); 31 | } else if (base.type().qualified_name() == "A") { 32 | ASSERT(any_ptr.as() == dynamic_cast(&d)); 33 | }else if (base.type().qualified_name() == "Z") { 34 | ASSERT(any_ptr.as() == dynamic_cast(&d)); 35 | }else if (base.type().qualified_name() == "W") { 36 | ASSERT(any_ptr.as() == dynamic_cast(&d)); 37 | }else if (base.type().qualified_name() == "Y") { 38 | ASSERT(any_ptr.as() == dynamic_cast(&d)); 39 | } 40 | }, &d); 41 | 42 | // check that upcasting works 43 | ASSERT( 44 | *archimedes::cast( 45 | &d, 46 | *archimedes::reflect(), 47 | *archimedes::reflect()) 48 | == dynamic_cast(&d)); 49 | ASSERT( 50 | *archimedes::cast( 51 | &d, 52 | *archimedes::reflect(), 53 | *archimedes::reflect()) 54 | == dynamic_cast(&d)); 55 | ASSERT( 56 | *archimedes::cast( 57 | dynamic_cast(&d), 58 | *archimedes::reflect(), 59 | *archimedes::reflect()) 60 | == dynamic_cast(&d)); 61 | 62 | // check that downcasting works 63 | ASSERT( 64 | *archimedes::cast( 65 | dynamic_cast(&d), 66 | *archimedes::reflect(), 67 | *archimedes::reflect()) 68 | == dynamic_cast(&d)); 69 | 70 | ASSERT( 71 | *archimedes::cast( 72 | dynamic_cast(&d), 73 | *archimedes::reflect(), 74 | *archimedes::reflect()) 75 | == &d); 76 | 77 | // check static casts 78 | X2 x2; 79 | ASSERT( 80 | *archimedes::cast( 81 | static_cast(&x2), 82 | *archimedes::reflect(), 83 | *archimedes::reflect()) 84 | == static_cast(&x2)); 85 | 86 | ASSERT( 87 | *archimedes::cast( 88 | &x2, 89 | *archimedes::reflect(), 90 | *archimedes::reflect()) 91 | == static_cast(&x2)); 92 | 93 | ASSERT( 94 | *archimedes::cast( 95 | static_cast(&x2), 96 | *archimedes::reflect(), 97 | *archimedes::reflect()) 98 | == static_cast(&x2)); 99 | 100 | // check that bogus casts are rejected 101 | ASSERT( 102 | !archimedes::cast( 103 | dynamic_cast(&d), 104 | *archimedes::reflect(), 105 | *archimedes::reflect())); 106 | 107 | // check that we can make cast functions and use them multiple times 108 | const auto fn = 109 | archimedes::make_cast_fn( 110 | &d, 111 | *archimedes::reflect(), 112 | *archimedes::reflect()); 113 | ASSERT(fn); 114 | 115 | D d2; 116 | ASSERT((*fn)(&d) == dynamic_cast(&d)); 117 | ASSERT((*fn)(&d2) == dynamic_cast(&d2)); 118 | ASSERT((*fn)(&d) != dynamic_cast(&d2)); 119 | 120 | ASSERT( 121 | !archimedes::make_cast_fn( 122 | dynamic_cast(&d), 123 | *archimedes::reflect(), 124 | *archimedes::reflect())); 125 | 126 | return 0; 127 | } 128 | -------------------------------------------------------------------------------- /test/traverse.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Y { 6 | uint64_t y_x = 7; 7 | virtual ~Y() {} 8 | }; 9 | 10 | struct W : virtual public Y { 11 | uint64_t w_x = 6; 12 | }; 13 | 14 | struct Z : public W { 15 | uint64_t z_x = 5; 16 | Z(): W() {} 17 | }; 18 | 19 | struct A { 20 | uint64_t a_x = 1; 21 | virtual ~A() {} 22 | }; 23 | 24 | struct B : virtual public A, virtual public Z { 25 | uint64_t b_y = 2; 26 | }; 27 | 28 | struct C : virtual public A, virtual public Z { 29 | uint64_t c_y = 3; 30 | }; 31 | 32 | struct D : public B, public C { 33 | uint64_t d_z = 4; 34 | 35 | D(): A(), Z(), B(), C() {} 36 | }; 37 | 38 | struct X0 { 39 | int x; 40 | }; 41 | 42 | struct X1 : public X0 { 43 | int y; 44 | }; 45 | 46 | struct X2 : public X1 { 47 | int z; 48 | }; 49 | -------------------------------------------------------------------------------- /test/type_id.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "type_id.test.hpp" 3 | 4 | FORCE_TYPE_INSTANTIATION(A) 5 | FORCE_TYPE_INSTANTIATION(A) 6 | 7 | int main(int argc, char *argv[]) { 8 | archimedes::load(); 9 | ASSERT( 10 | *archimedes::reflect()->type_id_hash() 11 | == typeid(Z).hash_code()); 12 | ASSERT( 13 | *archimedes::reflect()->type()->type().type_id_hash() 14 | == typeid(Z&).hash_code()); 15 | ASSERT( 16 | *archimedes::reflect>()->type_id_hash() 17 | == typeid(A).hash_code()); 18 | ASSERT( 19 | *archimedes::reflect>()->type_id_hash() 20 | == typeid(A).hash_code()); 21 | ASSERT( 22 | *archimedes::reflect>()->type_id_hash() 23 | == typeid(A).hash_code()); 24 | 25 | // const should not affect things 26 | ASSERT( 27 | *archimedes::reflect>()->type_id_hash() 28 | == typeid(const A).hash_code()); 29 | 30 | // check that everything works with dynamic types 31 | D1 d1; 32 | D0 *d0 = dynamic_cast(&d1); 33 | ASSERT( 34 | *archimedes::reflect()->type_id_hash() 35 | == typeid(*d0).hash_code()); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /test/type_id.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Y { 6 | uint64_t y_x = 7; 7 | virtual ~Y() {} 8 | }; 9 | 10 | struct W : virtual public Y { 11 | uint64_t w_x = 6; 12 | }; 13 | 14 | struct Z : public W { 15 | uint64_t z_x = 5; 16 | Z(): W() {} 17 | }; 18 | 19 | template 20 | struct A { 21 | T t; 22 | }; 23 | 24 | struct D0 { 25 | int x; 26 | virtual ~D0() = default; 27 | }; 28 | 29 | struct D1 : D0 { 30 | int y; 31 | }; 32 | -------------------------------------------------------------------------------- /test/types.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "types.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | ASSERT(archimedes::reflect()); 7 | const auto at = archimedes::reflect()->as_record(); 8 | ASSERT( 9 | at.static_field("FIELD") 10 | ->constexpr_value() 11 | ->as().x == 13); 12 | ASSERT( 13 | at.static_field("FIELD2") 14 | ->constexpr_value() 15 | ->as().x == 12); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /test/types.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ns { 4 | enum MyEnum { 5 | MyEnum0 = 0, 6 | MyEnum1 = 1 7 | }; 8 | 9 | template 10 | struct MyType { 11 | union { 12 | T ts[N]; 13 | 14 | struct { 15 | T x, y; 16 | }; 17 | }; 18 | 19 | constexpr MyType(T x, T y) : x(x), y(y) {} 20 | }; 21 | } 22 | 23 | namespace ns { 24 | using MyTypeInt2 = MyType; 25 | using MyTypeInt2X = MyType; 26 | } 27 | 28 | struct AnotherType { 29 | static constexpr auto FIELD = ns::MyTypeInt2 { 13, 12 }; 30 | static constexpr ns::MyTypeInt2X FIELD2 = 31 | { 12, 12 }; 32 | }; 33 | -------------------------------------------------------------------------------- /test/union.test.cpp: -------------------------------------------------------------------------------- 1 | #include "union.test.hpp" 2 | #include "test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto bar = *archimedes::reflect(); 7 | const auto baz = *archimedes::reflect(); 8 | const auto foo = *archimedes::reflect(); 9 | ASSERT(foo.is_record()); 10 | const auto r = foo.as_record(); 11 | const auto fields = r.fields(); 12 | ASSERT(fields.size() == 2); 13 | const auto 14 | f_bar = *r.field("bar"), 15 | f_baz = *r.field("baz"); 16 | ASSERT(f_bar.offset() == f_baz.offset()); 17 | 18 | const auto 19 | f_bar_0 = *f_bar.type().type().as_record().field("x"), 20 | f_baz_0 = *f_baz.type().type().as_record().field("z"); 21 | 22 | ASSERT(f_bar_0 == *bar.as_record().field("x")); 23 | ASSERT(f_baz_0 == *baz.as_record().field("z")); 24 | 25 | Foo f; 26 | f_bar_0.set(f.bar, 12); 27 | ASSERT(*f_bar_0.get(f.bar) == 12); 28 | ASSERT(*f_baz_0.get(f.baz) == 12); 29 | f_baz_0.set(f.baz, 66666); 30 | ASSERT(*f_bar_0.get(f.bar) == 66666); 31 | 32 | const auto 33 | f_bar_1 = *f_bar.type().type().as_record().field("y"), 34 | f_baz_1 = *f_baz.type().type().as_record().field("w"); 35 | f_baz_1.set(f.baz, 123.0f); 36 | 37 | union U { 38 | float f; 39 | int i; 40 | }; 41 | ASSERT(*f_bar_1.get(f.bar) == (U { .f = 123.0f }).i); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /test/union.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Bar { 4 | int x; 5 | int y; 6 | }; 7 | 8 | struct Baz { 9 | int z; 10 | float w; 11 | }; 12 | 13 | union Foo { 14 | Bar bar; 15 | Baz baz; 16 | }; 17 | -------------------------------------------------------------------------------- /test/unique_ptr.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "unique_ptr.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | 7 | Foo> f; 8 | f.t = std::make_unique(); 9 | f.t->x = 44; 10 | 11 | // TODO: test more than just compile 12 | 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /test/unique_ptr.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | struct Foo { 5 | T t; 6 | }; 7 | 8 | struct Bar { 9 | int x; 10 | }; 11 | -------------------------------------------------------------------------------- /test/use_glm.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "use_glm.test.hpp" 3 | 4 | ARCHIMEDES_ARG("explicit-enable") 5 | ARCHIMEDES_ARG("enable") 6 | 7 | ARCHIMEDES_REFLECT_TYPE(Struct0) 8 | ARCHIMEDES_REFLECT_TYPE(Struct1) 9 | 10 | // TODO: 11 | /* ARCHIMEDES_REFLECT_TYPE(glm::vec2) */ 12 | /* ARCHIMEDES_REFLECT_TYPE(glm::vec3) */ 13 | /* ARCHIMEDES_REFLECT_TYPE(glm::vec4) */ 14 | 15 | /* ARCHIMEDES_REFLECT_TYPE(glm::uvec2) */ 16 | /* ARCHIMEDES_REFLECT_TYPE(glm::uvec3) */ 17 | /* ARCHIMEDES_REFLECT_TYPE(glm::uvec4) */ 18 | 19 | /* ARCHIMEDES_REFLECT_TYPE(glm::ivec2) */ 20 | /* ARCHIMEDES_REFLECT_TYPE(glm::ivec3) */ 21 | /* ARCHIMEDES_REFLECT_TYPE(glm::ivec4) */ 22 | 23 | /* ARCHIMEDES_REFLECT_TYPE(glm::bvec2) */ 24 | /* ARCHIMEDES_REFLECT_TYPE(glm::bvec3) */ 25 | /* ARCHIMEDES_REFLECT_TYPE(glm::bvec4) */ 26 | 27 | using myvec3 = glm::vec3; 28 | 29 | int main(int argc, char *argv[]) { 30 | archimedes::load(); 31 | ASSERT( 32 | archimedes::reflect() 33 | ->as_record() 34 | .static_field("VEC1") 35 | ->constexpr_value() 36 | ->as() == uvec2(23, 23)); 37 | // TODO 38 | /* ASSERT(archimedes::reflect()); */ 39 | /* ASSERT(archimedes::reflect()); */ 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /test/use_glm.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "glm/glm/glm.hpp" 4 | 5 | namespace ns { 6 | using uvec2 = glm::uvec2; 7 | }; 8 | 9 | using namespace ns; 10 | 11 | struct Struct0 { 12 | static constexpr auto VEC = uvec2(12, 12); 13 | }; 14 | 15 | struct Struct1 { 16 | static constexpr auto VEC1 = (Struct0::VEC * 2u) - 1u; 17 | }; 18 | -------------------------------------------------------------------------------- /test/virtual.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "virtual.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto mf = archimedes::reflect("xy::MaybeFeathered"); 7 | ASSERT(mf); 8 | 9 | const auto animal = archimedes::reflect(); 10 | ASSERT(animal); 11 | 12 | const auto biped = archimedes::reflect(); 13 | ASSERT(biped); 14 | 15 | const auto fuzzy_animal = archimedes::reflect(); 16 | ASSERT(fuzzy_animal); 17 | 18 | const auto quadruped = archimedes::reflect(); 19 | ASSERT(quadruped); 20 | 21 | const auto cat = archimedes::reflect(); 22 | ASSERT(cat); 23 | 24 | const auto dog = archimedes::reflect(); 25 | ASSERT(dog); 26 | 27 | Dog d; 28 | const auto quadruped_num_legs = quadruped->as_record().function("num_legs"); 29 | ASSERT(quadruped_num_legs->invoke(&d)->as() == 4); 30 | 31 | const auto animal_speak = animal->as_record().function("speak"); 32 | ASSERT( 33 | animal_speak->invoke(dynamic_cast(&d))->as() 34 | == "woof"); 35 | } 36 | -------------------------------------------------------------------------------- /test/virtual.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace x { 6 | namespace y { 7 | struct MaybeFeathered { 8 | virtual ~MaybeFeathered() = default; 9 | virtual bool has_feathers() const = 0; 10 | }; 11 | } 12 | } 13 | 14 | namespace xy = x::y; 15 | 16 | struct Animal { 17 | virtual ~Animal() = default; 18 | 19 | virtual bool is_fuzzy() const = 0; 20 | 21 | virtual int num_legs() const = 0; 22 | 23 | virtual std::string speak() const = 0; 24 | }; 25 | 26 | struct Biped : virtual public Animal, public xy::MaybeFeathered { 27 | int num_legs() const override { return 2; } 28 | 29 | bool has_feathers() const override { 30 | return false; 31 | } 32 | }; 33 | 34 | struct FuzzyAnimal : virtual public Animal { 35 | bool is_fuzzy() const override { 36 | return true; 37 | } 38 | }; 39 | 40 | struct Quadruped : virtual public Animal { 41 | int num_legs() const override { 42 | return 4; 43 | } 44 | }; 45 | 46 | struct Cat : public FuzzyAnimal, public Quadruped { 47 | std::string speak() const { 48 | return "meow"; 49 | } 50 | }; 51 | 52 | struct Dog : public FuzzyAnimal, public Quadruped { 53 | Dog() 54 | : Animal(), 55 | FuzzyAnimal(), 56 | Quadruped() {} 57 | 58 | std::string speak() const { 59 | return "woof"; 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /test/virtual2.test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | #include "virtual2.test.hpp" 3 | 4 | int main(int argc, char *argv[]) { 5 | archimedes::load(); 6 | const auto d = *archimedes::reflect(); 7 | 8 | // d is dynamic (no functions but has virtual base) 9 | ASSERT(d.is_dynamic()); 10 | 11 | // 2 bases: B, C 12 | ASSERT(d.bases().size() == 2); 13 | 14 | // 1 vbase: A 15 | ASSERT(d.vbases().size() == 1); 16 | 17 | // try to dynamic cast from D to its base C 18 | D my_d; 19 | ASSERT( 20 | d.base("C")->cast_up(&my_d) == dynamic_cast(&my_d)); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /test/virtual2.test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct A { 6 | uint64_t x = 1; 7 | virtual ~A() = default; 8 | }; 9 | 10 | struct B : virtual public A { 11 | uint64_t y = 2; 12 | }; 13 | 14 | struct C : virtual public A { 15 | uint64_t y = 3; 16 | }; 17 | 18 | struct D : public B, public C { 19 | uint64_t z = 4; 20 | 21 | D(): A(), B(), C() {} 22 | }; 23 | --------------------------------------------------------------------------------