blob: 6961f5ef4c78c0b0b0538b7860f1b700136baa46 [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::Method;
use Clownfish::Util qw( a_isa_b );
use Carp;
sub method_def {
my ( undef, %args ) = @_;
my ( $method, $class ) = @args{qw( method class )};
confess("Not a Method")
unless a_isa_b( $method, "Clownfish::Method" );
confess("Not a Class")
unless a_isa_b( $class, "Clownfish::Class" );
if ( $method->final ) {
return _final_method_def( $method, $class );
}
else {
return _virtual_method_def( $method, $class );
}
}
sub _virtual_method_def {
my ( $method, $class ) = @_;
my $cnick = $class->get_cnick;
my $param_list = $method->get_param_list;
my $invoker_struct = $class->full_struct_sym;
my $common_struct = $method->self_type->get_specifier;
my $full_method_sym = $method->full_method_sym($cnick);
my $full_offset_sym = $method->full_offset_sym($cnick);
my $typedef = $method->full_typedef;
my $arg_names = $param_list->name_list;
$arg_names =~ s/\s*\w+/self/;
# Prepare the parameter list for the inline function.
my $params = $param_list->to_c;
$params =~ s/^.*?\*\s*\w+/const $invoker_struct *self/
or confess("no match: $params");
# Prepare a return statement... or not.
my $return_type = $method->get_return_type->to_c;
my $maybe_return = $method->get_return_type->is_void ? '' : 'return ';
return <<END_STUFF;
extern size_t $full_offset_sym;
static CHY_INLINE $return_type
$full_method_sym($params) {
char *const method_address = *(char**)self + $full_offset_sym;
const $typedef method = *(($typedef*)method_address);
${maybe_return}method(($common_struct*)$arg_names);
}
END_STUFF
}
# Create a macro definition that aliases to a function name directly, since
# this method may not be overridden.
sub _final_method_def {
my ( $method, $class ) = @_;
my $cnick = $class->get_cnick;
my $macro_sym = $method->get_macro_sym;
my $self_type = $method->self_type->to_c;
my $full_method_sym = $method->full_method_sym($cnick);
my $full_func_sym = $method->full_func_sym;
my $arg_names = $method->get_param_list->name_list;
return <<END_STUFF;
#define $full_method_sym($arg_names) \\
$full_func_sym(($self_type)$arg_names)
END_STUFF
}
sub typedef_dec {
my ( undef, $method ) = @_;
my $params = $method->get_param_list->to_c;
my $return_type = $method->get_return_type->to_c;
my $typedef = $method->full_typedef;
return <<END_STUFF;
typedef $return_type
(*$typedef)($params);
END_STUFF
}
sub callback_dec {
my ( undef, $method ) = @_;
my $callback_sym = $method->full_callback_sym;
return qq|extern cfish_Callback $callback_sym;\n|;
}
sub callback_obj_def {
my ( undef, %args ) = @_;
my $method = $args{method};
my $offset = $args{offset};
my $macro_sym = $method->get_macro_sym;
my $len = length($macro_sym);
my $func_sym = $method->full_override_sym;
my $callback_sym = $method->full_callback_sym;
return qq|cfish_Callback $callback_sym = |
. qq|{"$macro_sym", $len, (cfish_method_t)$func_sym, $offset};\n|;
}
sub callback_def {
my ( undef, $method ) = @_;
my $return_type = $method->get_return_type;
my $params = _callback_params($method);
if ( !$params ) {
# Can't map vars, because there's at least one type in the argument
# list we don't yet support. Return a callback wrapper that throws an
# error error.
return _invalid_callback_def( $method, $params );
}
elsif ( $return_type->is_void ) {
return _void_callback_def( $method, $params );
}
elsif ( $return_type->is_object ) {
return _obj_callback_def( $method, $params );
}
else {
return _primitive_callback_def( $method, $params );
}
}
# Return a string which maps arguments to various arg wrappers conforming
# to Host's callback interface. For instance, (int32_t foo, Obj *bar)
# produces the following:
#
# CFISH_ARG_I32("foo", foo),
# CFISH_ARG_OBJ("bar", bar)
#
sub _callback_params {
my $method = shift;
my $micro_sym = $method->micro_sym;
my $param_list = $method->get_param_list;
my $num_params = $param_list->num_vars - 1;
my $arg_vars = $param_list->get_variables;
my @params;
# Iterate over arguments, mapping them to various arg wrappers which
# conform to Host's callback interface.
for my $var ( @$arg_vars[ 1 .. $#$arg_vars ] ) {
my $name = $var->micro_sym;
my $type = $var->get_type;
my $param;
if ( $type->is_string_type ) {
$param = qq|CFISH_ARG_STR("$name", $name)|;
}
elsif ( $type->is_object ) {
$param = qq|CFISH_ARG_OBJ("$name", $name)|;
}
elsif ( $type->is_integer ) {
my $width = $type->get_width;
if ($width) {
if ( $width <= 4 ) {
$param = qq|CFISH_ARG_I32("$name", $name)|;
}
else {
$param = qq|CFISH_ARG_I64("$name", $name)|;
}
}
else {
my $c_type = $type->to_c;
$param = qq|CFISH_ARG_I($c_type, "$name", $name)|;
}
}
elsif ( $type->is_floating ) {
$param = qq|CFISH_ARG_F64("$name", $name)|;
}
else {
# Can't map variable type. Signal to caller.
return undef;
}
push @params, $param;
}
return join( ', ', 'self', qq|"$micro_sym"|, $num_params, @params );
}
# Return a function which throws a runtime error indicating which variable
# couldn't be mapped. TODO: it would be better to resolve all these cases at
# compile-time.
sub _invalid_callback_def {
my ( $method, $callback_params ) = @_;
my $full_method_sym
= $method->full_method_sym( $method->get_class_cnick );
my $override_sym = $method->full_override_sym;
my $params = $method->get_param_list->to_c;
my $unused = '';
for my $var ( @{ $method->get_param_list->get_variables } ) {
my $var_name = $var->micro_sym;
$unused .= "CHY_UNUSED_VAR($var_name); ";
}
return <<END_CALLBACK_DEF;
void
$override_sym($params) {
$unused;
CFISH_THROW(CFISH_ERR, "Can't override $full_method_sym via binding");
}
END_CALLBACK_DEF
}
# Create a callback for a method which operates in a void context.
sub _void_callback_def {
my ( $method, $callback_params ) = @_;
my $override_sym = $method->full_override_sym;
my $params = $method->get_param_list->to_c;
return <<END_CALLBACK_DEF;
void
$override_sym($params) {
cfish_Host_callback($callback_params);
}
END_CALLBACK_DEF
}
# Create a callback which returns a primitive type.
sub _primitive_callback_def {
my ( $method, $callback_params ) = @_;
my $override_sym = $method->full_override_sym;
my $params = $method->get_param_list->to_c;
my $return_type = $method->get_return_type;
my $return_type_str = $return_type->to_c;
my $nat_func
= $return_type->is_floating ? "cfish_Host_callback_f64"
: $return_type->is_integer ? "cfish_Host_callback_i64"
: $return_type_str eq 'void*' ? "cfish_Host_callback_host"
: confess("unrecognized type: $return_type_str");
return <<END_CALLBACK_DEF;
$return_type_str
$override_sym($params) {
return ($return_type_str)$nat_func($callback_params);
}
END_CALLBACK_DEF
}
# Create a callback which returns an object type -- either a generic object or
# a string.
sub _obj_callback_def {
my ( $method, $callback_params ) = @_;
my $override_sym = $method->full_override_sym;
my $params = $method->get_param_list->to_c;
my $return_type = $method->get_return_type;
my $return_type_str = $return_type->to_c;
my $cb_func_name
= $return_type->is_string_type
? "cfish_Host_callback_str"
: "cfish_Host_callback_obj";
my $nullable_check = "";
if ( !$return_type->nullable ) {
my $macro_sym = $method->get_macro_sym;
$nullable_check
= qq|if (!retval) { CFISH_THROW(CFISH_ERR, |
. qq|"$macro_sym() for class '%o' cannot return NULL", |
. qq|Cfish_Obj_Get_Class_Name((cfish_Obj*)self)); }\n |;
}
my $decrement = "";
if ( !$return_type->incremented ) {
$decrement = "LUCY_DECREF(retval);\n ";
}
return <<END_CALLBACK_DEF;
$return_type_str
$override_sym($params) {
$return_type_str retval = ($return_type_str)$cb_func_name($callback_params);
${nullable_check}${decrement}return retval;
}
END_CALLBACK_DEF
}
# Create a function which throws a runtime error indicating that a method is
# abstract. This serves as the implementation for methods which are
# declared as "abstract" in a Clownfish header file.
sub abstract_method_def {
my ( undef, $method ) = @_;
my $params = $method->get_param_list->to_c;
my $full_func_sym = $method->full_func_sym;
my $vtable = uc( $method->self_type->get_specifier );
my $return_type = $method->get_return_type;
my $return_type_str = $return_type->to_c;
my $macro_sym = $method->get_macro_sym;
# Build list of unused params and create an unreachable return statement
# if necessary, in order to thwart compiler warnings.
my $param_vars = $method->get_param_list->get_variables;
my $unused = "";
for ( my $i = 1; $i < @$param_vars; $i++ ) {
my $var_name = $param_vars->[$i]->micro_sym;
$unused .= "\n CHY_UNUSED_VAR($var_name);";
}
my $ret_statement = '';
if ( !$return_type->is_void ) {
$ret_statement = "\n CHY_UNREACHABLE_RETURN($return_type_str);";
}
return <<END_ABSTRACT_DEF;
$return_type_str
$full_func_sym($params) {
cfish_CharBuf *klass = self ? Cfish_Obj_Get_Class_Name((cfish_Obj*)self) : $vtable->name;$unused
CFISH_THROW(CFISH_ERR, "Abstract method '$macro_sym' not defined by %o", klass);$ret_statement
}
END_ABSTRACT_DEF
}
1;
__END__
__POD__
=head1 NAME
Clownfish::Binding::Core::Method - Generate core C code for a method.
=head1 DESCRIPTION
Clownfish::Method is an abstract specification; this class generates C code
which implements the specification.
=head1 METHODS
=head2 method_def
my $c_code = Clownfish::Binding::Core::Method->method_def(
method => $method,
$class => $class,
);
Return C code for the static inline vtable method invocation function.
=over
=item * B<method> - A L<Clownfish::Method>.
=item * B<class> - The L<Clownfish::Class> which will be invoking the method -
LobsterClaw needs its own method invocation function even if the method was
defined in Claw.
=back
=head2 typedef_dec
my $c_code = Clownfish::Binding::Core::Method->typedef_dec($method);
Return C code expressing a typedef declaration for the method.
=head2 callback_dec
my $c_code = Clownfish::Binding::Core::Method->callback_dec($method);
Return C code declaring the Callback object for this method.
=head2 callback_obj_def
my $c_code
= Clownfish::Binding::Core::Method->callback_obj_def($method);
Return C code defining the Callback object for this method, which stores
introspection data and a pointer to the callback function.
=head2 callback_def
my $c_code = Clownfish::Binding::Core::Method->callback_def($method);
Return C code implementing a callback to the Host for this method. This code
is used when a Host method has overridden a method in a Clownfish class.
=head2 abstract_method_def
my $c_code
= Clownfish::Binding::Core::Method->abstract_method_def($method);
Return C code implementing a version of the method which throws an "abstract
method" error at runtime.
=cut