├── .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