Papierkorb / bindgen

Binding and wrapper generator for C/C++ libraries
GNU General Public License v3.0
179 stars 18 forks source link

Generate property methods / constants for static data members #98

Closed HertzDevil closed 4 years ago

HertzDevil commented 4 years ago

Generates getters and setters for static variables, just like instance variables. Considerable portions of the changes are just documentation changes to reflect that both static and non-static data members are supported.

struct Application {
  static Application *instance;
  static const int VERSION = 103;
};
Application *Application::instance = /* ... */;

produces:

extern "C" Application * bg_Application_instance_STATIC_GETTER_() {
  return Application::instance;
}

extern "C" void bg_Application_instance_STATIC_SETTER_Application_X(Application * instance) {
  Application::instance = instance;
}

extern "C" const int bg_Application_VERSION_STATIC_GETTER_() {
  return Application::VERSION;
}
module Test
  lib Binding
    alias Application = Void
    fun bg_Application_instance_STATIC_GETTER_() : Application*
    fun bg_Application_instance_STATIC_SETTER_Application_X(instance : Application*) : Void
    fun bg_Application_VERSION_STATIC_GETTER_() : Int32
  end

  class Application
    def self.instance() : Application
      Application.new(unwrap: Binding.bg_Application_instance_STATIC_GETTER_())
    end

    def self.instance=(instance : Application) : Void
      Binding.bg_Application_instance_STATIC_SETTER_Application_X(instance)
    end

    def self.version() : Int32
      Binding.bg_Application_VERSION_STATIC_GETTER_()
    end
  end
end

This PR does not map constant members to Crystal constants yet, because in general the constant itself may be altered in Crystal if it is a wrapper (which inherits from Reference, and only prevents assignments):

struct Point {
    int x, y;
    static const Point ORIGIN;
};
const Point Point::ORIGIN = {0, 0};
class Point
  ORIGIN = Binding.bg_Point_ORIGIN_STATIC_GETTER_()
  # ORIGIN is actually a *copy* of the C++ constant on the heap
end

Point::ORIGIN.x = 4 # should not be allowed

Interestingly, Qt uses almost no static members, and the only differences after this PR are QObject::staticMetaObject. (Integral constants used anonymous enums a bit.)

Papierkorb commented 4 years ago

Hello,

Cool stuff as always :) Wouldn't it make sense to copy constants which are of primitive type? Pointer-types and non-primitives should stay as they are with this PR, I think they're great :+1:

HertzDevil commented 4 years ago

My concern is that even for primitive types their initializers must be available to the parser. Consider

// test.h
struct T {
  static const int X = 1;
  static const int Y;
};

// test.cpp (not parsed)
const int T::Y = 2;
class T
  X = 1
  Y = Binding.bg_T_Y_STATIC_GETTER_() # ???

  # do we still need these?
  def self.x : Int32 end
  def self.y : Int32 end
end
Papierkorb commented 4 years ago

That's a fair point (Gotta love C++!). I think the Crystal part of your sample looks good. I'd then drop the methods for these cases. This hopefully allows the user to e.g. use meta-programming in combination of this in some cases. I think it also makes sense as I've seen libs using constants as replacements for enums - For reasons unknown :)

HertzDevil commented 4 years ago

I did a small test and unfortunately T::Y is not a constant expression:

extern "C" int f() { return 1; }
lib Binding
  fun f : Int32
end

class Wrapper
  A = 4
  B = Binding.f
#     ^-----
# Error: invalid constant value
end

x = StaticArray(Int32, Wrapper::A).new(0)
y = StaticArray(Int32, Wrapper::B).new(0) # triggered by this line

The Crystal constant is still lazily constructed so it behaves like a constant during run-time, but compile-time programming with these constants will be impossible. Thus there is little point in having constants like T::Y if their initializers are unavailable:

class T
  X = 1

  # do not define `T.x`
  # corresponding C++ static getter will not be generated at all

  def self.y() : Int32 # as before
    Binding.bg_T_Y_STATIC_GETTER_()
  end
end

Should this part go into a separate PR?

Papierkorb commented 4 years ago

Ah that's unfortunate, but I think even with that caveat it's a good feature. I'll leave that up to you if you want to do a split PR or not.

HertzDevil commented 4 years ago

Static constants are in.

Default values (C++11 NSDMI) for non-static fields are also parsed, since they share the same JSON structure as static fields.

Papierkorb commented 4 years ago

Good stuff, thank you!