Closed ta0kira closed 3 years ago
After some prototyping, this might be a better approach:
struct CategoryBase_MyExtension {}
struct TypeBase_MyExtension{
ReturnTuple Call_create(const ParamTuple& params, const ValueTuple& args);
}
struct ValueBase_MyExtension {
ValueBase_MyExtension(int value) value_(value) {}
ReturnTuple Call_asInt(const S<TypeValue>& Var_self,
const ParamTuple& params,
const ValueTuple& args);
int value_;
}
// Category_MyExtension has CategoryBase_MyExtension as a member, etc.
DEFINE_BASE("Category_MyExtension.cpp",
CategoryBase_MyExtension,
TypeBase_MyExtension,
ValueBase_MyExtension)
// Functions are defined after DEFINE_BASE so that they can use Value_MyExtension, etc.
ReturnTuple ValueBase_MyExtension::Call_create(const ParamTuple& params,
const ValueTuple& args) {
TRACE_FUNCTION("MyExtension.create")
// Value_MyExtension passes extra args to ValueBase_MyExtension for init.
return ReturnTuple(S_get(new Value_MyExtension(CreateType_MyExtension(Params<0>::Type()))), 0);
}
ReturnTuple TypeBase_MyExtension::Call_asInt(const S<TypeValue>& Var_self,
const ParamTuple& params,
const ValueTuple& args) {
TRACE_FUNCTION("MyExtension.asInt")
return ReturnTuple(Box_Int(value_));
}
It might also be helpful to limit #include
visibility from base
so that only builtins can define functions like AsBool
.
Just to avoid having compile-time errors in generated code, a good approach might be:
#include
.
FAIL
as the default function behavior. Alternatively, generate these defaults in --templates
mode.This approach will require that C++-backed categories be generated differently than Zeolite-backed categories; therefore, category_source
(in .zeolite-module
) will need to be checked for categories that need extra code-gen. This should be simple however: Header generation won't differ, and such categories shouldn't already have a DefinedCategory
.
All of the generated abstract classes should mark all functions as final
except for the handlers for Zeolite functions.
Incidentally, this would preclude internal inheritance, since the module author couldn't override TypeInstance::TypeArgsForParent
. If the latter becomes necessary at some point, it could potentially be handled by adding fields to .zeolite-module
, since the generated code would also need to account for the additional inherited functions.
Separately, adding a new layer of virtual functions is going to add an extra vtable pointer. The trade-off (vs. using a data member) is being able to have compile-time errors for missing functions occur in the hand-written code rather than in the generated code.
This would be easier if output wasn't done using string concatenation. For example, maybe the output should be strongly-typed based on C++ syntax.
Summary of the solution:
concrete
categories and interface
s pretty much the same, but use out-of-line definitions for most generated class
functions.concrete
categories:
Category_Foo.hpp
the usual way.class
definitions in Streamlined_Foo.hpp
.Call_bar
naming convention for Foo.bar
, but make them abstract.reduce
calls.CanConvertFrom
) in Streamlined_Foo.cpp
.zeolite --templates
mode, generate Extension_Foo.cpp
.Streamlined_Foo.hpp
.fail
function definitions as before.The "registration" in this case is just implementing the category and type getters to return the hand-written implementations. There is no value registration, since values are only created by functions from the category itself.
As it turns out, the chosen solution is compatible with the built-in literal types.
I'm leaning toward generating most of the
.cpp
file so that the user's.cpp
file looks something like this:This approach will make maintenance easier (especially adding/removing functions), but it has a few complications that need to be worked out: