| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| use strict; |
| use warnings; |
| |
| package Clownfish::Binding::Core::Class; |
| use Clownfish::Util qw( a_isa_b verify_args ); |
| use Clownfish::Binding::Core::Method; |
| use Clownfish::Binding::Core::Function; |
| use File::Spec::Functions qw( catfile ); |
| |
| our %new_PARAMS = ( client => undef, ); |
| |
| sub new { |
| my $either = shift; |
| verify_args( \%new_PARAMS, @_ ) or confess $@; |
| my $self = bless { %new_PARAMS, @_, }, ref($either) || $either; |
| |
| # Validate. |
| my $client = $self->{client}; |
| confess("Not a Clownfish::Class") |
| unless a_isa_b( $client, "Clownfish::Class" ); |
| |
| return $self; |
| } |
| |
| sub _full_callbacks_var { shift->{client}->full_vtable_var . '_CALLBACKS' } |
| sub _full_name_var { shift->{client}->full_vtable_var . '_CLASS_NAME' } |
| sub _short_names_macro { shift->{client}->get_PREFIX . 'USE_SHORT_NAMES' } |
| |
| # C code defining the ZombieCharBuf which contains the class name for this |
| # class. |
| sub _name_var_definition { |
| my $self = shift; |
| my $full_var_name = _full_name_var($self); |
| my $class_name = $self->{client}->get_class_name; |
| my $class_name_len = length($class_name); |
| return <<END_STUFF; |
| cfish_ZombieCharBuf $full_var_name = { |
| CFISH_ZOMBIECHARBUF, |
| {1}, /* ref.count */ |
| "$class_name", |
| $class_name_len, |
| 0 |
| }; |
| |
| END_STUFF |
| } |
| |
| # Return C code defining the class's VTable. |
| sub _vtable_definition { |
| my $self = shift; |
| my $client = $self->{client}; |
| my $parent = $client->get_parent; |
| my $methods = $client->methods; |
| my $vt_type = $client->full_vtable_type; |
| my $cnick = $client->get_cnick; |
| my $vtable_var = $client->full_vtable_var; |
| my $struct_sym = $client->full_struct_sym; |
| my $vt = $vtable_var . "_vt"; |
| my $name_var = _full_name_var($self); |
| my $cb_var = _full_callbacks_var($self); |
| |
| # Create a pointer to the parent class's vtable. |
| my $parent_ref |
| = defined $parent |
| ? $parent->full_vtable_var |
| : "NULL"; # No parent, e.g. Obj or inert classes. |
| |
| # Spec functions which implement the methods, casting to quiet compiler. |
| my @implementing_funcs |
| = map { "(cfish_method_t)" . $_->full_func_sym } @$methods; |
| my $method_string = join( ",\n ", @implementing_funcs ); |
| my $num_methods = scalar @implementing_funcs; |
| |
| return <<END_VTABLE |
| |
| $vt_type $vt = { |
| CFISH_VTABLE, /* vtable vtable */ |
| {1}, /* ref.count */ |
| $parent_ref, /* parent */ |
| (cfish_CharBuf*)&$name_var, |
| 0, /* flags */ |
| NULL, /* "void *x" member reserved for future use */ |
| sizeof($struct_sym), /* obj_alloc_size */ |
| offsetof(cfish_VTable, methods) |
| + $num_methods * sizeof(cfish_method_t), /* vt_alloc_size */ |
| &$cb_var, /* callbacks */ |
| { |
| $method_string |
| } |
| }; |
| |
| END_VTABLE |
| } |
| |
| # Create the definition for the instantiable object struct. |
| sub _struct_definition { |
| my $self = shift; |
| my $struct_sym = $self->{client}->full_struct_sym; |
| my $member_declarations = join( "\n ", |
| map { $_->local_declaration } @{ $self->{client}->member_vars } ); |
| return <<END_STRUCT |
| struct $struct_sym { |
| $member_declarations |
| }; |
| END_STRUCT |
| } |
| |
| sub to_c_header { |
| my $self = shift; |
| my $client = $self->{client}; |
| my $cnick = $client->get_cnick; |
| my $functions = $client->functions; |
| my $methods = $client->methods; |
| my $novel_methods = $client->novel_methods; |
| my $inert_vars = $client->inert_vars; |
| my $vtable_var = $client->full_vtable_var; |
| my $short_vt_var = $client->short_vtable_var; |
| my $short_struct = $client->get_struct_sym; |
| my $full_struct = $client->full_struct_sym; |
| my $c_file_sym = "C_" . uc($full_struct); |
| my $struct_def = _struct_definition($self); |
| |
| # If class inherits from something, include the parent class's header. |
| my $parent_include = ""; |
| if ( my $parent = $client->get_parent ) { |
| $parent_include = $parent->include_h; |
| $parent_include = qq|#include "$parent_include"|; |
| } |
| |
| # Add a C function definition for each method and each function. |
| my $sub_declarations = ""; |
| for my $sub ( @$functions, @$novel_methods ) { |
| $sub_declarations |
| .= Clownfish::Binding::Core::Function->func_declaration($sub) |
| . "\n\n"; |
| } |
| |
| # Declare class (a.k.a. "inert") variables. |
| my $inert_var_defs = ""; |
| for my $inert_var (@$inert_vars) { |
| $inert_var_defs .= "extern " . $inert_var->global_c . ";\n"; |
| } |
| |
| # Declare typedefs for novel methods, to ease casting. |
| my $method_typedefs = ''; |
| for my $method (@$novel_methods) { |
| $method_typedefs |
| .= Clownfish::Binding::Core::Method->typedef_dec($method) . "\n"; |
| } |
| |
| # Define method invocation syntax. |
| my $method_defs = ''; |
| for my $method (@$methods) { |
| $method_defs .= Clownfish::Binding::Core::Method->method_def( |
| method => $method, |
| class => $self->{client}, |
| ) . "\n"; |
| } |
| |
| # Declare the virtual table singleton object. |
| my $vt_type = $self->{client}->full_vtable_type; |
| my $vt = "extern struct $vt_type ${vtable_var}_vt;"; |
| my $vtable_object |
| = "#define $vtable_var ((cfish_VTable*)&${vtable_var}_vt)"; |
| my $num_methods = scalar @$methods; |
| |
| # Declare cfish_Callback objects. |
| my $callback_declarations = ""; |
| for my $method (@$novel_methods) { |
| next unless $method->public || $method->abstract; |
| $callback_declarations |
| .= Clownfish::Binding::Core::Method->callback_dec($method); |
| } |
| |
| # Define short names. |
| my $short_names = ''; |
| my $short_names_macro = _short_names_macro($self); |
| for my $function (@$functions) { |
| my $short_func_sym = $function->short_sym; |
| my $full_func_sym = $function->full_sym; |
| $short_names .= " #define $short_func_sym $full_func_sym\n"; |
| } |
| for my $inert_var (@$inert_vars) { |
| my $short_sym = $inert_var->short_sym; |
| my $full_sym = $inert_var->full_sym; |
| $short_names .= " #define $short_sym $full_sym\n"; |
| } |
| if ( !$client->inert ) { |
| for my $method (@$novel_methods) { |
| if ( !$method->isa("Clownfish::Method::Overridden") ) { |
| my $short_typedef = $method->short_typedef; |
| my $full_typedef = $method->full_typedef; |
| $short_names .= " #define $short_typedef $full_typedef\n"; |
| } |
| my $short_func_sym = $method->short_func_sym; |
| my $full_func_sym = $method->full_func_sym; |
| $short_names .= " #define $short_func_sym $full_func_sym\n"; |
| } |
| for my $method (@$methods) { |
| my $short_method_sym = $method->short_method_sym($cnick); |
| my $full_method_sym = $method->full_method_sym($cnick); |
| $short_names .= " #define $short_method_sym $full_method_sym\n"; |
| } |
| } |
| |
| # Make the spacing in the file a little more elegant. |
| s/\s+$// for ( $method_typedefs, $method_defs, $short_names ); |
| |
| # Inert classes only output inert functions and member vars. |
| if ( $client->inert ) { |
| return <<END_INERT |
| #include "charmony.h" |
| #include "boil.h" |
| $parent_include |
| |
| $inert_var_defs |
| |
| $sub_declarations |
| |
| #ifdef $short_names_macro |
| $short_names |
| #endif /* $short_names_macro */ |
| |
| END_INERT |
| } |
| |
| # Instantiable classes get everything. |
| return <<END_STUFF; |
| |
| #include "charmony.h" |
| #include "boil.h" |
| $parent_include |
| |
| #ifdef $c_file_sym |
| $struct_def |
| #endif /* $c_file_sym */ |
| |
| $inert_var_defs |
| |
| $sub_declarations |
| $callback_declarations |
| |
| $method_typedefs |
| |
| $method_defs |
| |
| typedef struct $vt_type { |
| cfish_VTable *vtable; |
| cfish_ref_t ref; |
| cfish_VTable *parent; |
| cfish_CharBuf *name; |
| uint32_t flags; |
| void *x; |
| size_t obj_alloc_size; |
| size_t vt_alloc_size; |
| void *callbacks; |
| cfish_method_t methods[$num_methods]; |
| } $vt_type; |
| $vt |
| $vtable_object |
| |
| #ifdef $short_names_macro |
| #define $short_struct $full_struct |
| #define $short_vt_var $vtable_var |
| $short_names |
| #endif /* $short_names_macro */ |
| |
| END_STUFF |
| } |
| |
| sub to_c { |
| my $self = shift; |
| my $client = $self->{client}; |
| |
| return $client->get_autocode if $client->inert; |
| |
| my $include_h = $client->include_h; |
| my $autocode = $client->get_autocode; |
| my $offsets = ''; |
| my $abstract_funcs = ''; |
| my $callback_funcs = ''; |
| my $callbacks = ''; |
| my $vt_type = $client->full_vtable_type; |
| my $meth_num = 0; |
| my $cnick = $client->get_cnick; |
| my $class_name_def = _name_var_definition($self); |
| my $vtable_def = _vtable_definition($self); |
| my @class_callbacks; |
| |
| # Prepare to identify novel methods. |
| my %novel = map { ( $_->micro_sym => $_ ) } @{ $client->novel_methods }; |
| |
| for my $method ( @{ $client->methods } ) { |
| my $var_name = $method->full_offset_sym($cnick); |
| |
| # Create offset in bytes for the method from the top of the VTable |
| # object. |
| my $offset = "(offsetof($vt_type, methods)" |
| . " + $meth_num * sizeof(cfish_method_t))"; |
| $offsets .= "size_t $var_name = $offset;\n"; |
| |
| # Create a default implementation for abstract methods. |
| if ( $method->abstract ) { |
| if ( $novel{ $method->micro_sym } ) { |
| $callback_funcs |
| .= Clownfish::Binding::Core::Method->abstract_method_def( |
| $method) |
| . "\n"; |
| } |
| } |
| |
| # Define callbacks for methods that can be overridden via the |
| # host. |
| if ( $method->public or $method->abstract ) { |
| my $callback_sym = $method->full_callback_sym; |
| if ( $novel{ $method->micro_sym } ) { |
| $callback_funcs |
| .= Clownfish::Binding::Core::Method->callback_def($method) |
| . "\n"; |
| $callbacks |
| .= Clownfish::Binding::Core::Method->callback_obj_def( |
| method => $method, |
| offset => $offset, |
| ); |
| } |
| push @class_callbacks, "&$callback_sym"; |
| } |
| $meth_num++; |
| } |
| |
| # Create a NULL-terminated array of cfish_Callback vars. Since C89 |
| # doesn't allow us to initialize a pointer to an anonymous array inside a |
| # global struct, we have to give it a real symbol and then store a pointer |
| # to that symbol inside the VTable struct. |
| my $callbacks_var = _full_callbacks_var($self); |
| $callbacks .= "cfish_Callback *$callbacks_var" . "[] = {\n "; |
| $callbacks .= join( ",\n ", @class_callbacks, "NULL" ); |
| $callbacks .= "\n};\n"; |
| |
| return <<END_STUFF; |
| #include "$include_h" |
| |
| $offsets |
| $callback_funcs |
| $callbacks |
| $class_name_def |
| $vtable_def |
| $autocode |
| |
| END_STUFF |
| } |
| |
| 1; |
| |
| __END__ |
| |
| __POD__ |
| |
| =head1 NAME |
| |
| Clownfish::Binding::Core::Class - Generate core C code for a class. |
| |
| =head1 DESCRIPTION |
| |
| Clownfish::Class is an abstract specification for a class. This module |
| autogenerates the C code with implements that specification. |
| |
| =head1 METHODS |
| |
| =head2 new |
| |
| my $class_binding = Clownfish::Binding::Core::Class->new( |
| client => $class, |
| ); |
| |
| =over |
| |
| =item * B<client> - A L<Clownfish::Class>. |
| |
| =back |
| |
| =head2 to_c_header |
| |
| Return the .h file which contains autogenerated C code defining the class's |
| interface: all method invocation functions, etc... |
| |
| =head2 to_c |
| |
| Return the .c file which contains autogenerated C code necessary for the class |
| to function properly. |
| |
| =cut |