ta0kira / zeolite

Zeolite is a statically-typed, general-purpose programming language.
Apache License 2.0
18 stars 0 forks source link

Streamline C++ extensions. #50

Closed ta0kira closed 3 years ago

ta0kira commented 4 years ago

I'm leaning toward generating most of the .cpp file so that the user's .cpp file looks something like this:

// Category_MyExtension.cpp

// Initially generated with --templates, then hand-edited by the user.

#include "Category_MyExtension.hpp"

INCLUDE_BASE(
  "CategoryBase_MyExtension.cpp",    // Generated. Contains Value_MyExtension, etc.
  ,                                  // Extra members for the category. (None.)
  int counter; S<TypeValue> parent;  // Extra members for values.
)

ReturnTuple Value_MyExtension::Call_myFunc(const S<TypeValue>& Var_self, 
                                           const ParamTuple& params, 
                                           const ValueTuple& args) {
  TRACE_FUNCTION("MyExtension.myFunc")
  // Hand-written function definition.
}

This approach will make maintenance easier (especially adding/removing functions), but it has a few complications that need to be worked out:

ta0kira commented 4 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.

ta0kira commented 4 years ago

Just to avoid having compile-time errors in generated code, a good approach might be:

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.

ta0kira commented 4 years ago

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.

ta0kira commented 3 years ago

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.

ta0kira commented 3 years ago

Summary of the solution:

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.

ta0kira commented 3 years ago

As it turns out, the chosen solution is compatible with the built-in literal types.