blob: fc527230a0bcfea873cd1130b0b45775124da162 [file] [log] [blame]
# 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