| # 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::Type; |
| use Clownfish; |
| use Clownfish::Parcel; |
| use Clownfish::Util qw( verify_args a_isa_b ); |
| use Scalar::Util qw( blessed ); |
| use Carp; |
| |
| our %new_PARAMS = ( |
| const => undef, |
| specifier => undef, |
| indirection => undef, |
| parcel => undef, |
| c_string => undef, |
| void => undef, |
| object => undef, |
| primitive => undef, |
| integer => undef, |
| floating => undef, |
| string_type => undef, |
| va_list => undef, |
| arbitrary => undef, |
| composite => undef, |
| ); |
| |
| sub new { |
| my ( $either, %args ) = @_; |
| my $package = ref($either) || $either; |
| verify_args( \%new_PARAMS, %args ) or confess $@; |
| |
| my $flags = 0; |
| $flags |= CONST if $args{const}; |
| $flags |= NULLABLE if $args{nullable}; |
| $flags |= VOID if $args{void}; |
| $flags |= OBJECT if $args{object}; |
| $flags |= PRIMITIVE if $args{primitive}; |
| $flags |= INTEGER if $args{integer}; |
| $flags |= FLOATING if $args{floating}; |
| $flags |= STRING_TYPE if $args{string_type}; |
| $flags |= VA_LIST if $args{va_list}; |
| $flags |= ARBITRARY if $args{arbitrary}; |
| $flags |= COMPOSITE if $args{composite}; |
| |
| my $parcel |
| = $args{parcel} |
| ? Clownfish::Parcel->acquire( $args{parcel} ) |
| : $args{parcel}; |
| |
| my $indirection = $args{indirection} || 0; |
| my $specifier = $args{specifier} || ''; |
| my $c_string = $args{c_string} || ''; |
| |
| return $package->_new( $flags, $parcel, $specifier, $indirection, |
| $c_string ); |
| } |
| |
| our %new_integer_PARAMS = ( |
| const => undef, |
| specifier => undef, |
| ); |
| |
| sub new_integer { |
| my ( $either, %args ) = @_; |
| verify_args( \%new_integer_PARAMS, %args ) or confess $@; |
| my $flags = 0; |
| $flags |= CONST if $args{const}; |
| my $package = ref($either) || $either; |
| return $package->_new_integer( $flags, $args{specifier} ); |
| } |
| |
| our %new_float_PARAMS = ( |
| const => undef, |
| specifier => undef, |
| ); |
| |
| sub new_float { |
| my ( $either, %args ) = @_; |
| verify_args( \%new_float_PARAMS, %args ) or confess $@; |
| my $flags = 0; |
| $flags |= CONST if $args{const}; |
| my $package = ref($either) || $either; |
| return $package->_new_float( $flags, $args{specifier} ); |
| } |
| |
| our %new_object_PARAMS = ( |
| const => undef, |
| specifier => undef, |
| indirection => 1, |
| parcel => undef, |
| incremented => 0, |
| decremented => 0, |
| nullable => 0, |
| ); |
| |
| sub new_object { |
| my ( $either, %args ) = @_; |
| verify_args( \%new_object_PARAMS, %args ) or confess $@; |
| my $flags = 0; |
| $flags |= INCREMENTED if $args{incremented}; |
| $flags |= DECREMENTED if $args{decremented}; |
| $flags |= NULLABLE if $args{nullable}; |
| $flags |= CONST if $args{const}; |
| $args{indirection} = 1 unless defined $args{indirection}; |
| my $parcel = Clownfish::Parcel->acquire( $args{parcel} ); |
| my $package = ref($either) || $either; |
| confess("Missing required param 'specifier'") |
| unless defined $args{specifier}; |
| return $package->_new_object( $flags, $parcel, $args{specifier}, |
| $args{indirection} ); |
| } |
| |
| our %new_composite_PARAMS = ( |
| child => undef, |
| indirection => undef, |
| array => undef, |
| nullable => undef, |
| ); |
| |
| sub new_composite { |
| my ( $either, %args ) = @_; |
| verify_args( \%new_composite_PARAMS, %args ) or confess $@; |
| my $flags = 0; |
| $flags |= NULLABLE if $args{nullable}; |
| my $indirection = $args{indirection} || 0; |
| my $array = defined $args{array} ? $args{array} : ""; |
| my $package = ref($either) || $either; |
| return $package->_new_composite( $flags, $args{child}, $indirection, |
| $array ); |
| } |
| |
| our %new_void_PARAMS = ( const => undef, ); |
| |
| sub new_void { |
| my ( $either, %args ) = @_; |
| verify_args( \%new_void_PARAMS, %args ) or confess $@; |
| my $package = ref($either) || $either; |
| return $package->_new_void( !!$args{const} ); |
| } |
| |
| sub new_va_list { |
| my $either = shift; |
| verify_args( {}, @_ ) or confess $@; |
| my $package = ref($either) || $either; |
| return $either->_new_va_list(); |
| } |
| |
| our %new_arbitrary_PARAMS = ( |
| parcel => undef, |
| specifier => undef, |
| ); |
| |
| sub new_arbitrary { |
| my ( $either, %args ) = @_; |
| verify_args( \%new_arbitrary_PARAMS, %args ) or confess $@; |
| my $package = ref($either) || $either; |
| my $parcel = Clownfish::Parcel->acquire( $args{parcel} ); |
| return $package->_new_arbitrary( $parcel, $args{specifier} ); |
| } |
| |
| 1; |
| |
| __END__ |
| |
| __POD__ |
| |
| =head1 NAME |
| |
| Clownfish::Type - A variable's type. |
| |
| =head1 METHODS |
| |
| =head2 new |
| |
| my $type = MyType->new( |
| specifier => 'char', # default undef |
| indirection => undef, # default 0 |
| const => 1, # default undef |
| parcel => undef, # default undef |
| c_string => undef, # default undef |
| ); |
| |
| Generic constructor. |
| |
| =over |
| |
| =item * |
| |
| B<specifier> - The C name for the type, not including any indirection or array |
| subscripts. |
| |
| =item * |
| |
| B<indirection> - integer indicating level of indirection. Example: the C type |
| "float**" has a specifier of "float" and indirection 2. |
| |
| =item * |
| |
| B<const> - should be true if the type is const. |
| |
| =item * |
| |
| B<parcel> - A Clownfish::Parcel or a parcel name. |
| |
| =item * |
| |
| B<c_string> - The C representation of the type. |
| |
| =back |
| |
| =head2 new_integer |
| |
| my $type = Clownfish::Type->new_integer( |
| const => 1, # default: undef |
| specifier => 'char', # required |
| ); |
| |
| Return a Type representing an integer primitive. |
| |
| Support is limited to a subset of the standard C integer types: |
| |
| int8_t |
| int16_t |
| int32_t |
| int64_t |
| uint8_t |
| uint16_t |
| uint32_t |
| uint64_t |
| char |
| short |
| int |
| long |
| size_t |
| |
| Many others are not supported: "signed" or "unsigned" anything, "long long", |
| "ptrdiff_t", "off_t", etc. |
| |
| The following Charmonizer typedefs are supported: |
| |
| bool_t |
| |
| =over |
| |
| =item * B<const> - Should be true if the type is const. |
| |
| =item * B<specifier> - Must match one of the supported types. |
| |
| =back |
| |
| =head2 new_float |
| |
| my $type = Clownfish::Type->new_float( |
| const => 1, # default: undef |
| specifier => 'double', # required |
| ); |
| |
| Return a Type representing a floating point primitive. |
| |
| Two specifiers are supported: |
| |
| float |
| double |
| |
| =over |
| |
| =item * B<const> - Should be true if the type is const. |
| |
| =item * B<specifier> - Must match one of the supported types. |
| |
| =back |
| |
| =cut |
| |
| =head2 new_composite |
| |
| my $type = Clownfish::Type->new_composite( |
| child => $char_type, # required |
| indirection => undef, # default 0 |
| array => '[]', # default undef, |
| const => 1, # default undef |
| ); |
| |
| Constructor for a composite type which is made up of repetitions of a single, |
| uniform subtype. |
| |
| =over |
| |
| =item * |
| |
| B<child> - The Type which the composite is comprised of. |
| |
| =item * |
| |
| B<indirection> - integer indicating level of indirection. Example: the C type |
| "float**" has indirection 2. |
| |
| =item * |
| |
| B<array> - A string describing an array postfix. |
| |
| =item * |
| |
| B<const> - should be 1 if the type is const. |
| |
| =back |
| |
| =head2 new_object |
| |
| my $type = Clownfish::Type->new_object( |
| specifier => "Lobster", # required |
| parcel => "Crustacean", # default: the default Parcel. |
| const => undef, # default undef |
| indirection => 1, # default 1 |
| incremented => 1, # default 0 |
| decremented => 0, # default 0 |
| nullable => 1, # default 0 |
| ); |
| |
| Create a Type representing an object. The Type's C<specifier> must match the |
| last component of the class name -- i.e. for the class "Crustacean::Lobster" |
| it must be "Lobster". |
| |
| =over |
| |
| =item * B<specifier> - Required. Must follow the rules for |
| L<Clownfish::Class> class name components. |
| |
| =item * B<parcel> - A L<Clownfish::Parcel> or a parcel name. |
| |
| =item * B<const> - Should be true if the Type is const. Note that this refers |
| to the object itself and not the pointer. |
| |
| =item * B<indirection> - Level of indirection. Must be 1 if supplied. |
| |
| =item * B<incremented> - Indicate whether the caller must take responsibility |
| for an added refcount. |
| |
| =item * B<decremented> - Indicate whether the caller must account for |
| for a refcount decrement. |
| |
| =item * B<nullable> - Indicate whether the object specified by this type may |
| be NULL. |
| |
| =back |
| |
| The Parcel's prefix will be prepended to the specifier by new_object(). |
| |
| =head2 new_void |
| |
| my $type = Clownfish::Type->new_void( |
| specifier => 'void', # default: void |
| const => 1, # default: undef |
| ); |
| |
| Return a Type representing a the 'void' keyword in C. It can be used either |
| for a void return type, or in conjuction with with new_composite() to support |
| the C<void*> opaque pointer type. |
| |
| =over |
| |
| =item * B<specifier> - Must be "void" if supplied. |
| |
| =item * B<const> - Should be true if the type is const. (Useful in the |
| context of C<const void*>). |
| |
| =back |
| |
| =head2 new_va_list |
| |
| my $type = Clownfish::Type->new_va_list( |
| specifier => 'va_list', # default: va_list |
| ); |
| |
| Create a Type representing C's va_list, from stdarg.h. |
| |
| =over |
| |
| =item * B<specifier>. Must be "va_list" if supplied. |
| |
| =back |
| |
| =head2 new_arbitrary |
| |
| my $type = Clownfish::Type->new_arbitrary( |
| specifier => 'floatint_t', # required |
| parcel => 'Crustacean', # default: undef |
| ); |
| |
| "Arbitrary" types are a hack that spares us from having to support C types |
| with complex declaration syntaxes -- such as unions, structs, enums, or |
| function pointers -- from within Clownfish itself. |
| |
| The only constraint is that the C<specifier> must end in "_t". This allows us |
| to create complex types in a C header file... |
| |
| typedef union { float f; int i; } floatint_t; |
| |
| ... pound-include the C header, then use the resulting typedef in a Clownfish |
| header file and have it parse as an "arbitrary" type. |
| |
| floatint_t floatint; |
| |
| =over |
| |
| =item * B<specifier> - The name of the type, which must end in "_t". |
| |
| =item * B<parcel> - A L<Clownfish::Parcel> or a parcel name. |
| |
| =back |
| |
| If C<parcel> is supplied and C<specifier> begins with a capital letter, the |
| Parcel's prefix will be prepended to the specifier: |
| |
| foo_t -> foo_t # no prefix prepending |
| Lobster_foo_t -> crust_Lobster_foo_t # prefix prepended |
| |
| =cut |
| |
| =head2 equals |
| |
| do_stuff() if $type->equals($other); |
| |
| Returns true if two Clownfish::Type objects are equivalent. |
| |
| =head2 similar |
| |
| do_stuff() if $type->similar($other_type); |
| |
| Weak checking of type which allows for covariant return types. Calling this |
| method on anything other than an object type is an error. |
| |
| =head2 to_c |
| |
| # Declare variable "foo". |
| print $type->to_c . " foo;\n"; |
| |
| Return the C representation of the type. |
| |
| =head2 set_c_string |
| |
| Set the C representation of the type. |
| |
| =head2 get_specifier get_parcel get_indirection get_array const nullable set_specifier set_nullable |
| |
| Accessors. |
| |
| =head2 is_object is_primitive is_integer is_floating is_composite is_void |
| |
| do_stuff() if $type->is_object; |
| |
| Identify the flavor of Type, which is determined by the constructor which was |
| used to create it. |
| |
| =over |
| |
| =item * is_object: Clownfish::Type->new_object |
| |
| =item * is_primitive: Either Clownfish::Type->new_integer or |
| Clownfish::Type->new_float |
| |
| =item * is_integer: Clownfish::Type->new_integer |
| |
| =item * is_floating: Clownfish::Type->new_float |
| |
| =item * is_void: Clownfish::Type->new_void |
| |
| =item * is_composite: Clownfish::Type->new_composite |
| |
| =back |
| |
| =head2 is_string_type |
| |
| Returns true if $type represents a Clownfish type which holds unicode |
| strings. |
| |
| =head2 incremented |
| |
| Returns true if the Type is incremented. Only applicable to object Types. |
| |
| =head2 decremented |
| |
| Returns true if the Type is decremented. Only applicable to object Types. |
| |
| =cut |
| |