Bikeshedding the gnome-class mini-language



Hey, all!

I'm making very good progress with the rewrite of gnome-class.  I've
figured out how to port it gradually from lalrpop and the old proc-
macro scheme of things, to proc-macro2 and syn/quote.

As a reminder, gnome-class is a procedural macro that you call like
this:

gobject_gen! {
    class Counter {
        struct CounterPrivate {
            f: Cell<u32>
        }

        private_init() -> CounterPrivate {
            CounterPrivate {
                f: Cell::new(0)
            }
        }

        fn add(&self, x: u32) -> u32 {
            let private = self.get_priv();
            let v = private.f.get() + x;
            private.f.set(v);
            v
        }

        fn get(&self) -> u32 {
            self.get_priv().f.get()
        }
    }
}

This generates all the GObject boilerplate to register a Counter class,
its private data, the #[repr(C)] functions to call the add() and get()
methods from C, etc.

However, GObject supports a lot of features (signals, properties, etc.)
and I am trying to come up with suitable syntax.  I would love your
feedback on the following.

By default, the answer to many things here is "what does Vala do?" :) 
This is probably often the right thing syntax-wise.  We can make it
more Rust-like as needed.

Basic syntax
============

Here is a bare-bones class that derives from GObject; same as the
example above:

gobject_gen! {
    class Counter {
        struct CounterPrivate {
            f: Cell<u32>
        }

        private_init() -> CounterPrivate {
            CounterPrivate {
                f: Cell::new(0)
            }
        }

        fn add(&self, x: u32) -> u32 {
            let private = self.get_priv();
            let v = private.f.get() + x;
            private.f.set(v);
            v
        }

        fn get(&self) -> u32 {
            self.get_priv().f.get()
        }
    }
}

You can specify the parent class like "class Foo: Bar".  If it is not
specified, the code generator assumes that GObject is your parent class.

"struct CounterPrivate" is your private struct.  Each class must have
one and only one such structures.

private_init() is a mandatory function that gets called during
initialization; you return an initialized private struct, and *that*
becomes the initial value of what gets put in the
g_type_class_add_private() chunk.

As an alternative, I want to make it possible to specify
#[derive(Default)] for your private struct, and thus not having to
specify a private_init():

gobject_gen! {
    class Counter {
        #[derive(Default)] // <- note this thing
        struct CounterPrivate {
            f: Cell<u32>
        }
    }
}

Constructors
============

Vala supports a construct{} block, which gets called after all (construct-time?) 
properties have been set.  It uses GObjectClass::constructor.  I haven't decided
on this yet, nor on how the following from Vala would work:

    public MyConstructor (int a, int b, int c) {
        Object (some_construct_only_prop: a, some_other_prop: b); // construct superclass
        this.my_own_prop = c;
    }

Virtual vs. non-virtual methods
===============================

Virtual methods have a slot in FooClass; non-virtual ones don't.  They
are just C functions that happen to take a &self as the first
parameter.

Syntax-wise, we need a way to specify virtual / non-virtual (default
to non-virtual, like C# does), and a way to override virtual methods
from your parent class.  Some possibilities:

gobject_gen! {
    class Flarp: SuperClass {
        // ... private struct foo

        // static method
        fn florp(&self, x: i32) -> bool {
            // ... code
        }

        // virtual method, no default handler
        virtual fn virtual_method_1(&self, x: i32) -> bool;

        // virtual method with default handler
        virtual fn virtual_method_2(&self, y: i32) {
            // ... code
        }

        override fn foo(&self, z: i32) -> Bar {
            // do something with the superclass
        }
    }
}

For the public C API, these would generate the following prototypes in a
header file:

gboolean flarp_florp (Flarp *self, gint x);

gboolean flarp_virtual_method_1 (Flarp *self, gint x);

void flarp_virtual_method_2 (Flarp *self, gint y);

For the overriden method "foo", there presumably is a superclass_foo()
somewhere.

Problem: how would the code generator know which superclass struct
has the .foo field in order to override it?  Should we specify this
overriden method like

  override fn SuperClassName::foo(&self, ...);

?

Visibility: the code generator assumes that all the "fn" that you put
inside a class are public.  Should we instead allow "fn" and "pub fn"?
Should virtual methods be declared as "virtual pub fn" correspondingly?

Implementing interfaces
=======================

You specify which interfaces you implement like this:

  class Foo: Superclass, Iface1, Iface2 {
      // ...
  }

Now, how to specify the implementations?

First alternative, similar to our "override" above:

  class Foo: Superclass, Iface1, Iface2 {
      override fn Iface1::method_1(&self, ...) {
          // code
      }

      override fn Iface2::method_2(&self, ...) {
          // code
      }
  }

Second alternative, more Rust-like, with "impl" items inside the class:

gnome_class! {
    class Foo: Superclass, Iface1, Iface2 {
        impl Iface1 for Foo {
            fn method_1(&self, ...) {
                // code
            }
        }

        impl Iface2 for Foo {
            fn method_2(&self, ...) {
                // code
            }
        }
    }
}

Third alternative, also Rust-like, with "impl" items outside the class:

gnome_class! {
    class Foo: Superclass, Iface1, Iface2 {
        // ...
    }

    impl Iface1 for Foo {
        fn method_1(&self, ...) {
            // code
        }
    }

    impl Iface2 for Foo {
        fn method_2(&self, ...) {
            // code
        }
    }
}

Creating new interfaces
=======================

I haven't decided this yet.  Maybe something like

gnome_class! {
    interface Foo {
        virtual fn blah(&self, x: i32) -> bool;
        virtual fn bleh(&self, y: *const c_char);
    }
}

I.e. make it similar to "trait", except that the "virtual" keywords
are there to remind you that you are not in Rust land?

That would generate the appropriate #[repr(C)] structs so that
people can implement those interfaces later.

Signals
=======

Vala specifies signal flags like this:

    [Signal (action=true, detailed=true, run=true, no_recurse=true, no_hooks=true)]
    public signal void sig_1 ();

I think we can use function attributes:

        // no default handler, no return value
        signal no_default_handler(&self, x: u32);

        // with default handler, with return value
        signal with_default_handler(&self, e: &EventButton) -> bool {
            // default handler code here
        }

        // signal flags; pick the ones you need
        #[signal(run_first, run_last, run_cleanup, no_recurse, detailed, action, no_hooks, must_collect, 
deprecated)]
        signal foo(&self);

I haven't though of how to specify signal accumulators, or if we even
need them.  Is there a non-obscure case that needs them?

Properties
==========

I took some notes about this, but they are far from final.  Vala has
good examples on what the syntax may look like; it borrows from C#,
which is nice.

Feedback is appreciated!

  Federico


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]