| # 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. |
| package Clownfish::Build::Binding; |
| use strict; |
| use warnings; |
| |
| our $VERSION = '0.004001'; |
| $VERSION = eval $VERSION; |
| |
| sub bind_all { |
| my $class = shift; |
| $class->bind_clownfish; |
| $class->bind_test; |
| $class->bind_test_alias_obj; |
| $class->bind_bytebuf; |
| $class->bind_string; |
| $class->bind_err; |
| $class->bind_hash; |
| $class->bind_float32; |
| $class->bind_float64; |
| $class->bind_obj; |
| $class->bind_varray; |
| $class->bind_class; |
| $class->bind_stringhelper; |
| } |
| |
| sub bind_clownfish { |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish |
| |
| IV |
| _dummy_function() |
| CODE: |
| RETVAL = 1; |
| OUTPUT: |
| RETVAL |
| |
| SV* |
| to_clownfish(sv) |
| SV *sv; |
| CODE: |
| { |
| cfish_Obj *obj = XSBind_perl_to_cfish(sv); |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(obj); |
| } |
| OUTPUT: RETVAL |
| |
| SV* |
| to_perl(sv) |
| SV *sv; |
| CODE: |
| { |
| if (sv_isobject(sv) && sv_derived_from(sv, "Clownfish::Obj")) { |
| IV tmp = SvIV(SvRV(sv)); |
| cfish_Obj* obj = INT2PTR(cfish_Obj*, tmp); |
| RETVAL = XSBind_cfish_to_perl(obj); |
| } |
| else { |
| RETVAL = newSVsv(sv); |
| } |
| } |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish", |
| ); |
| $binding->append_xs($xs_code); |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_test { |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::Test |
| |
| bool |
| run_tests(package) |
| char *package; |
| CODE: |
| cfish_String *class_name = cfish_Str_newf("%s", package); |
| cfish_TestFormatter *formatter |
| = (cfish_TestFormatter*)cfish_TestFormatterTAP_new(); |
| cfish_TestSuite *suite = testcfish_Test_create_test_suite(); |
| bool result = CFISH_TestSuite_Run_Batch(suite, class_name, formatter); |
| CFISH_DECREF(class_name); |
| CFISH_DECREF(formatter); |
| CFISH_DECREF(suite); |
| |
| RETVAL = result; |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "TestClownfish", |
| class_name => "Clownfish::Test", |
| ); |
| $binding->append_xs($xs_code); |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_test_alias_obj { |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "TestClownfish", |
| class_name => "Clownfish::Test::AliasTestObj", |
| ); |
| $binding->bind_method( |
| alias => 'perl_alias', |
| method => 'Aliased', |
| ); |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_bytebuf { |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::ByteBuf |
| |
| SV* |
| new(either_sv, sv) |
| SV *either_sv; |
| SV *sv; |
| CODE: |
| { |
| STRLEN size; |
| char *ptr = SvPV(sv, size); |
| cfish_ByteBuf *self = (cfish_ByteBuf*)XSBind_new_blank_obj(either_sv); |
| cfish_BB_init(self, size); |
| CFISH_BB_Mimic_Bytes(self, ptr, size); |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(self); |
| } |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::ByteBuf", |
| ); |
| $binding->append_xs($xs_code); |
| $binding->exclude_constructor; |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_string { |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::String |
| |
| SV* |
| new(either_sv, sv) |
| SV *either_sv; |
| SV *sv; |
| CODE: |
| { |
| STRLEN size; |
| char *ptr = SvPVutf8(sv, size); |
| cfish_String *self = (cfish_String*)XSBind_new_blank_obj(either_sv); |
| cfish_Str_init_from_trusted_utf8(self, ptr, size); |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(self); |
| } |
| OUTPUT: RETVAL |
| |
| SV* |
| _clone(self) |
| cfish_String *self; |
| CODE: |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(CFISH_Str_Clone_IMP(self)); |
| OUTPUT: RETVAL |
| |
| SV* |
| to_perl(self) |
| cfish_String *self; |
| CODE: |
| RETVAL = XSBind_str_to_sv(self); |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::String", |
| ); |
| $binding->append_xs($xs_code); |
| $binding->exclude_constructor; |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_err { |
| my $pod_spec = Clownfish::CFC::Binding::Perl::Pod->new; |
| my $synopsis = <<'END_SYNOPSIS'; |
| package MyErr; |
| use base qw( Clownfish::Err ); |
| |
| ... |
| |
| package main; |
| use Scalar::Util qw( blessed ); |
| while (1) { |
| eval { |
| do_stuff() or MyErr->throw("retry"); |
| }; |
| if ( blessed($@) and $@->isa("MyErr") ) { |
| warn "Retrying...\n"; |
| } |
| else { |
| # Re-throw. |
| die "do_stuff() died: $@"; |
| } |
| } |
| END_SYNOPSIS |
| $pod_spec->set_synopsis($synopsis); |
| |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::Err |
| |
| SV* |
| trap(routine_sv, context_sv) |
| SV *routine_sv; |
| SV *context_sv; |
| CODE: |
| cfish_Err *error = XSBind_trap(routine_sv, context_sv); |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(error); |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::Err", |
| ); |
| $binding->bind_constructor( alias => '_new' ); |
| $binding->set_pod_spec($pod_spec); |
| $binding->append_xs($xs_code); |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_hash { |
| my @hand_rolled = qw( |
| Store |
| Next |
| ); |
| |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::Hash |
| SV* |
| _fetch(self, key) |
| cfish_Hash *self; |
| cfish_String *key; |
| CODE: |
| RETVAL = CFISH_OBJ_TO_SV(CFISH_Hash_Fetch_IMP(self, (cfish_Obj*)key)); |
| OUTPUT: RETVAL |
| |
| void |
| store(self, key, value); |
| cfish_Hash *self; |
| cfish_String *key; |
| cfish_Obj *value; |
| PPCODE: |
| { |
| if (value) { CFISH_INCREF(value); } |
| CFISH_Hash_Store_IMP(self, (cfish_Obj*)key, value); |
| } |
| |
| void |
| next(self) |
| cfish_Hash *self; |
| PPCODE: |
| { |
| cfish_Obj *key; |
| cfish_Obj *val; |
| |
| if (CFISH_Hash_Next(self, &key, &val)) { |
| SV *key_sv = (SV*)CFISH_Obj_To_Host(key); |
| SV *val_sv = (SV*)CFISH_Obj_To_Host(val); |
| |
| XPUSHs(sv_2mortal(key_sv)); |
| XPUSHs(sv_2mortal(val_sv)); |
| XSRETURN(2); |
| } |
| else { |
| XSRETURN_EMPTY; |
| } |
| } |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::Hash", |
| ); |
| $binding->exclude_method($_) for @hand_rolled; |
| $binding->append_xs($xs_code); |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_float32 { |
| my $float32_xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::Float32 |
| |
| SV* |
| new(either_sv, value) |
| SV *either_sv; |
| float value; |
| CODE: |
| { |
| cfish_Float32 *self = (cfish_Float32*)XSBind_new_blank_obj(either_sv); |
| cfish_Float32_init(self, value); |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(self); |
| } |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::Float32", |
| ); |
| $binding->append_xs($float32_xs_code); |
| $binding->exclude_constructor; |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_float64 { |
| my $float64_xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::Float64 |
| |
| SV* |
| new(either_sv, value) |
| SV *either_sv; |
| double value; |
| CODE: |
| { |
| cfish_Float64 *self = (cfish_Float64*)XSBind_new_blank_obj(either_sv); |
| cfish_Float64_init(self, value); |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(self); |
| } |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::Float64", |
| ); |
| $binding->append_xs($float64_xs_code); |
| $binding->exclude_constructor; |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_obj { |
| my @exposed = qw( |
| To_String |
| To_I64 |
| To_F64 |
| Equals |
| ); |
| my @hand_rolled = qw( |
| Is_A |
| ); |
| |
| my $pod_spec = Clownfish::CFC::Binding::Perl::Pod->new; |
| my $synopsis = <<'END_SYNOPSIS'; |
| package MyObj; |
| use base qw( Clownfish::Obj ); |
| |
| # Inside-out member var. |
| my %foo; |
| |
| sub new { |
| my ( $class, %args ) = @_; |
| my $foo = delete $args{foo}; |
| my $self = $class->SUPER::new(%args); |
| $foo{$$self} = $foo; |
| return $self; |
| } |
| |
| sub get_foo { |
| my $self = shift; |
| return $foo{$$self}; |
| } |
| |
| sub DESTROY { |
| my $self = shift; |
| delete $foo{$$self}; |
| $self->SUPER::DESTROY; |
| } |
| END_SYNOPSIS |
| my $description = <<'END_DESCRIPTION'; |
| Clownfish::Obj is the base class of the Clownfish object hierarchy. |
| |
| From the standpoint of a Perl programmer, all classes are implemented as |
| blessed scalar references, with the scalar storing a pointer to a C struct. |
| |
| =head2 Subclassing |
| |
| The recommended way to subclass Clownfish::Obj and its descendants is |
| to use the inside-out design pattern. (See L<Class::InsideOut> for an |
| introduction to inside-out techniques.) |
| |
| Since the blessed scalar stores a C pointer value which is unique per-object, |
| C<$$self> can be used as an inside-out ID. |
| |
| # Accessor for 'foo' member variable. |
| sub get_foo { |
| my $self = shift; |
| return $foo{$$self}; |
| } |
| |
| |
| Caveats: |
| |
| =over |
| |
| =item * |
| |
| Inside-out aficionados will have noted that the "cached scalar id" stratagem |
| recommended above isn't compatible with ithreads. |
| |
| =item * |
| |
| Overridden methods must not return undef unless the API specifies that |
| returning undef is permissible. (Failure to adhere to this rule currently |
| results in a segfault rather than an exception.) |
| |
| =back |
| |
| =head1 CONSTRUCTOR |
| |
| =head2 new() |
| |
| Abstract constructor -- must be invoked via a subclass. Attempting to |
| instantiate objects of class "Clownfish::Obj" directly causes an |
| error. |
| |
| Takes no arguments; if any are supplied, an error will be reported. |
| |
| =head1 DESTRUCTOR |
| |
| =head2 DESTROY |
| |
| All Clownfish classes implement a DESTROY method; if you override it in a |
| subclass, you must call C<< $self->SUPER::DESTROY >> to avoid leaking memory. |
| END_DESCRIPTION |
| $pod_spec->set_synopsis($synopsis); |
| $pod_spec->set_description($description); |
| $pod_spec->add_method( method => $_, alias => lc($_) ) for @exposed; |
| |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::Obj |
| |
| bool |
| is_a(self, class_name) |
| cfish_Obj *self; |
| cfish_String *class_name; |
| CODE: |
| { |
| cfish_Class *target = cfish_Class_fetch_class(class_name); |
| RETVAL = CFISH_Obj_Is_A(self, target); |
| } |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::Obj", |
| ); |
| $binding->bind_method( |
| alias => 'DESTROY', |
| method => 'Destroy', |
| ); |
| $binding->exclude_method($_) for @hand_rolled; |
| $binding->append_xs($xs_code); |
| $binding->set_pod_spec($pod_spec); |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_varray { |
| my @hand_rolled = qw( |
| Shallow_Copy |
| Shift |
| Pop |
| Delete |
| Store |
| Fetch |
| ); |
| |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::VArray |
| |
| SV* |
| shallow_copy(self) |
| cfish_VArray *self; |
| CODE: |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(CFISH_VA_Shallow_Copy(self)); |
| OUTPUT: RETVAL |
| |
| SV* |
| _clone(self) |
| cfish_VArray *self; |
| CODE: |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(CFISH_VA_Clone(self)); |
| OUTPUT: RETVAL |
| |
| SV* |
| shift(self) |
| cfish_VArray *self; |
| CODE: |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(CFISH_VA_Shift(self)); |
| OUTPUT: RETVAL |
| |
| SV* |
| pop(self) |
| cfish_VArray *self; |
| CODE: |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(CFISH_VA_Pop(self)); |
| OUTPUT: RETVAL |
| |
| SV* |
| delete(self, tick) |
| cfish_VArray *self; |
| uint32_t tick; |
| CODE: |
| RETVAL = CFISH_OBJ_TO_SV_NOINC(CFISH_VA_Delete(self, tick)); |
| OUTPUT: RETVAL |
| |
| void |
| store(self, tick, value); |
| cfish_VArray *self; |
| uint32_t tick; |
| cfish_Obj *value; |
| PPCODE: |
| { |
| if (value) { CFISH_INCREF(value); } |
| CFISH_VA_Store_IMP(self, tick, value); |
| } |
| |
| SV* |
| fetch(self, tick) |
| cfish_VArray *self; |
| uint32_t tick; |
| CODE: |
| RETVAL = CFISH_OBJ_TO_SV(CFISH_VA_Fetch(self, tick)); |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::VArray", |
| ); |
| $binding->exclude_method($_) for @hand_rolled; |
| $binding->append_xs($xs_code); |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_class { |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::Class |
| |
| SV* |
| _get_registry() |
| CODE: |
| if (cfish_Class_registry == NULL) { |
| cfish_Class_init_registry(); |
| } |
| RETVAL = (SV*)CFISH_Obj_To_Host((cfish_Obj*)cfish_Class_registry); |
| OUTPUT: RETVAL |
| |
| SV* |
| fetch_class(unused_sv, class_name_sv) |
| SV *unused_sv; |
| SV *class_name_sv; |
| CODE: |
| { |
| STRLEN size; |
| char *ptr = SvPVutf8(class_name_sv, size); |
| cfish_StackString *class_name = CFISH_SSTR_WRAP_UTF8(ptr, size); |
| cfish_Class *klass |
| = cfish_Class_fetch_class((cfish_String*)class_name); |
| CFISH_UNUSED_VAR(unused_sv); |
| RETVAL = klass ? (SV*)CFISH_Class_To_Host(klass) : &PL_sv_undef; |
| } |
| OUTPUT: RETVAL |
| |
| SV* |
| singleton(unused_sv, ...) |
| SV *unused_sv; |
| CODE: |
| { |
| cfish_String *class_name = NULL; |
| cfish_Class *parent = NULL; |
| cfish_Class *singleton = NULL; |
| bool args_ok |
| = XSBind_allot_params(&(ST(0)), 1, items, |
| ALLOT_OBJ(&class_name, "class_name", 10, true, |
| CFISH_STRING, alloca(cfish_SStr_size())), |
| ALLOT_OBJ(&parent, "parent", 6, false, |
| CFISH_CLASS, NULL), |
| NULL); |
| CFISH_UNUSED_VAR(unused_sv); |
| if (!args_ok) { |
| CFISH_RETHROW(CFISH_INCREF(cfish_Err_get_error())); |
| } |
| singleton = cfish_Class_singleton(class_name, parent); |
| RETVAL = (SV*)CFISH_Class_To_Host(singleton); |
| } |
| OUTPUT: RETVAL |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::Class", |
| ); |
| $binding->append_xs($xs_code); |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| sub bind_stringhelper { |
| my $xs_code = <<'END_XS_CODE'; |
| MODULE = Clownfish PACKAGE = Clownfish::Util::StringHelper |
| |
| =for comment |
| |
| Turn an SV's UTF8 flag on. Equivalent to Encode::_utf8_on, but we don't have |
| to load Encode. |
| |
| =cut |
| |
| void |
| utf8_flag_on(sv) |
| SV *sv; |
| PPCODE: |
| SvUTF8_on(sv); |
| |
| =for comment |
| |
| Turn an SV's UTF8 flag off. |
| |
| =cut |
| |
| void |
| utf8_flag_off(sv) |
| SV *sv; |
| PPCODE: |
| SvUTF8_off(sv); |
| |
| SV* |
| to_base36(num) |
| uint64_t num; |
| CODE: |
| { |
| char base36[cfish_StrHelp_MAX_BASE36_BYTES]; |
| size_t size = cfish_StrHelp_to_base36(num, &base36); |
| RETVAL = newSVpvn(base36, size); |
| } |
| OUTPUT: RETVAL |
| |
| IV |
| from_base36(str) |
| char *str; |
| CODE: |
| RETVAL = strtol(str, NULL, 36); |
| OUTPUT: RETVAL |
| |
| =for comment |
| |
| Upgrade a SV to UTF8, converting Latin1 if necessary. Equivalent to |
| utf::upgrade(). |
| |
| =cut |
| |
| void |
| utf8ify(sv) |
| SV *sv; |
| PPCODE: |
| sv_utf8_upgrade(sv); |
| |
| bool |
| utf8_valid(sv) |
| SV *sv; |
| CODE: |
| { |
| STRLEN len; |
| char *ptr = SvPV(sv, len); |
| RETVAL = cfish_StrHelp_utf8_valid(ptr, len); |
| } |
| OUTPUT: RETVAL |
| |
| =for comment |
| |
| Concatenate one scalar onto the end of the other, ignoring UTF-8 status of the |
| second scalar. This is necessary because $not_utf8 . $utf8 results in a |
| scalar which has been infected by the UTF-8 flag of the second argument. |
| |
| =cut |
| |
| void |
| cat_bytes(sv, catted) |
| SV *sv; |
| SV *catted; |
| PPCODE: |
| { |
| STRLEN len; |
| char *ptr = SvPV(catted, len); |
| if (SvUTF8(sv)) { CFISH_THROW(CFISH_ERR, "Can't cat_bytes onto a UTF-8 SV"); } |
| sv_catpvn(sv, ptr, len); |
| } |
| END_XS_CODE |
| |
| my $binding = Clownfish::CFC::Binding::Perl::Class->new( |
| parcel => "Clownfish", |
| class_name => "Clownfish::Util::StringHelper", |
| ); |
| $binding->append_xs($xs_code); |
| |
| Clownfish::CFC::Binding::Perl::Class->register($binding); |
| } |
| |
| 1; |