# gtkmm - GtkDefs module # # Copyright 2001 Free Software Foundation # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # package GtkDefs; use strict; use warnings; use open IO => ":utf8"; use Util; use Enum; use Object; use Property; use FunctionBase; # # Public functions # read_defs(path, file) # # @ get_methods() # @ get_signals() # @ get_properties() # @ get_child_properties() # @ get_unwrapped() # # $ lookup_enum(c_type) # $ lookup_object(c_name) # $ lookup_method_dont_mark(c_name) # $ lookup_method_set_weak_mark(c_name) # $ lookup_method(c_name) # $ lookup_function(c_name) # $ lookup_property(object, c_name) # $ lookup_child_property(object, c_name) # $ lookup_signal(object, c_name) # BEGIN { use Exporter (); our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); # set the version for version checking $VERSION = 1.00; @ISA = qw(Exporter); @EXPORT = ( ); %EXPORT_TAGS = ( ); # your exported package globals go here, # # as well as any optionally exported functions @EXPORT_OK = ( ); } our @EXPORT_OK; ##################################### use strict; use warnings; ##################################### %GtkDefs::enums = (); #Enum %GtkDefs::objects = (); #Object %GtkDefs::methods = (); #GtkDefs::Function %GtkDefs::signals = (); #GtkDefs::Signal %GtkDefs::properties = (); #Property %GtkDefs::child_properties = (); #Property @GtkDefs::read = (); @GtkDefs::file = (); ##################################### #prototype to get rid of warning sub read_defs($$;$); sub read_defs($$;$) { my ($path, $filename, $restrict) = @_; $restrict = "" if ($#_ < 2); # check that the file is there. if ( ! -r "$path/$filename") { print "Error: can't read defs file $filename\n"; return; } # break the tokens into lisp phrases up to three levels deep. # WARNING: reading the following perl statement may induce seizures, # please flush eyes with water immediately, and consult a mortician. # # this regexp is weak - it does not work on multiple and/or unpaired parens # inside double quotes - those shouldn't be ever considered. i replaced this # splitting with my own function, which does the job very well - krnowak. # my @tokens = split( # m/( # \( # (?: # [^()]* # \( # (?: # [^()]* # \( # [^()]* # \) # )* # [^()]* # \) # )* # [^()]* # \) # )/x, # read_file($path, $filename)); my @tokens = split_tokens(read_file($path, $filename)); # scan through top level tokens while ($#tokens > -1) { my $token = shift @tokens; next if ($token =~ /^\s*$/); if ($token =~ /\(include (\S+)\)/) { read_defs($path,$1,$restrict); next; } elsif ($token =~ /^\(define-flags-extended.*\)$/) { on_enum($token); } elsif ($token =~ /^\(define-enum-extended.*\)$/) { on_enum($token); } elsif ($token =~ /^\(define-flags.*\)$/) { } elsif ($token =~ /^\(define-enum.*\)$/) { } elsif ($token =~ /^\(define-object.*\)$/) { on_object($token); } elsif ($token =~ /^\(define-function.*\)$/) { on_function($token); } elsif ($token =~ /^\(define-method.*\)$/) { on_method($token); } elsif ($token =~ /^\(define-property.*\)$/) { on_property($token); } elsif ($token =~ /^\(define-child-property.*\)$/) { on_child_property($token); } elsif ($token =~ /^\(define-signal.*\)$/) { on_signal($token); } elsif ($token =~ /^\(define-vfunc.*\)$/) { on_vfunc($token); } else { if ( $token =~ /^\(define-(\S+) (\S+)/) { # FIXME need to figure out the line number. print STDERR "Broken lisp definition for $1 $2.\n"; } else { print "unknown token $token \n"; } } } } sub split_tokens($) { my ($token_string) = @_; my @tokens = (); # whether we are inside double quotes. my $inside_dquotes = 0; # whether we are inside double and then single quotes (for situations like # "'"'"). my $inside_squotes = 0; # number of yet unpaired opening parens. my $parens = 0; # whether previous char was a backslash - important only when being between # double quotes. my $backslash = 0; # index of first opening paren - beginning of a new token. my $begin_token = 0; # Isolate characters with special significance for the token split. my @substrings = split(/([\\"'()])/, $token_string); my $index = -1; for my $substring (@substrings) { $index++; # if we are inside double quotes. if ($inside_dquotes) { # if prevous char was backslash, then current char is not important - # we are still inside double or double/single quotes anyway. if ($backslash) { $backslash = 0; } # if current char is backslash. elsif ($substring eq '\\') { $backslash = 1; } # if current char is unescaped double quotes and we are not inside single # ones - means, we are going outside string. elsif ($substring eq '"' and not $inside_squotes) { $inside_dquotes = 0; } # if current char is unescaped single quote, then we have two cases: # 1. it just plain apostrophe. # 2. it is a piece of a C code: # a) opening quotes, # b) closing quotes. # if there is near (2 or 3 indexes away) second quote, then it is 2a, # if 2a occured earlier, then it is 2b. # otherwise is 1. elsif ($substring eq '\'') { # if we are already inside single quotes, it is 2b. if ($inside_squotes) { $inside_squotes = 0; } else { # if there is closing quotes near, it is 2a. if (join('', @substrings[$index .. min($#substrings, $index+3)]) =~ /^'\\?.'/) { $inside_squotes = 1; } # else it is just 1. } } } # double quotes - beginning of a string. elsif ($substring eq '"') { $inside_dquotes = 1; } # opening paren - if paren count is 0 then this is a beginning of a token. elsif ($substring eq '(') { unless ($parens) { $begin_token = $index; } $parens++; } # closing paren - if paren count is 1 then this is an end of a token, so we # extract it from token string and push into token list. elsif ($substring eq ')') { $parens--; unless ($parens) { my $token = join('', @substrings[$begin_token .. $index]); push(@tokens, $token); } } # do nothing on other chars. } return @tokens; } sub min($$) { return ($_[0] < $_[1]) ? $_[0] : $_[1]; } sub read_file($$) { my ($path, $filename)=@_; my @buf = (); # don't read a file twice foreach (@GtkDefs::read) { return "" if ($_ eq "$path/$filename"); } push @GtkDefs::read, "$path/$filename"; # read file while stripping comments open(FILE, "$path/$filename"); while () { s/^;.*$//; # remove comments chop; # remove new lines push(@buf, $_); } close(FILE); $_ = join("", @buf); s/\s+/ /g; return $_; } sub on_enum($) { my $thing = Enum::new(shift(@_)); $GtkDefs::enums{$$thing{c_type}} = $thing; } sub on_object($) { my $thing = Object::new(shift(@_)); $GtkDefs::objects{$$thing{c_name}} = $thing; } sub on_function($) { my $thing = GtkDefs::Function::new(shift(@_)); $GtkDefs::methods{$$thing{c_name}} = $thing; } sub on_method($) { my $thing = GtkDefs::Function::new(shift(@_)); $GtkDefs::methods{$$thing{c_name}} = $thing if ($thing); } sub on_property($) { my $thing = Property::new(shift(@_)); $GtkDefs::properties{"$$thing{class}::$$thing{name}"} = $thing; } sub on_child_property($) { my $thing = Property::new(shift(@_)); $GtkDefs::child_properties{"$$thing{class}::$$thing{name}"} = $thing; } sub on_signal($) { my $thing = GtkDefs::Signal::new(shift(@_)); $GtkDefs::signals{"$$thing{class}::$$thing{name}"} = $thing; } sub on_vfunc($) { my $thing = GtkDefs::Signal::new(shift(@_)); $GtkDefs::signals{"$$thing{class}::$$thing{name}"} = $thing; } ########################## sub get_enums { return sort {$$a{c_type} cmp $$b{c_type}} values %GtkDefs::enums; } sub get_methods { return sort {$$a{c_name} cmp $$b{c_name}} values %GtkDefs::methods; } sub get_signals { return sort {$$a{name} cmp $$b{name}} values %GtkDefs::signals; } sub get_properties { return sort {$$a{name} cmp $$b{name}} values %GtkDefs::properties; } sub get_child_properties { return sort {$$a{name} cmp $$b{name}} values %GtkDefs::child_properties; } sub get_marked { no warnings; return grep {$$_{mark}==1} values %GtkDefs::methods; } # This searches for items wrapped by this file and then tries to locate # other methods/signals/properties which may have been left unmarked. sub get_unwrapped { # find methods which were used in a _WRAP or _IGNORE. my @targets; push @targets,grep {$$_{entity_type} eq "method" && $$_{mark}==1} values %GtkDefs::methods; push @targets,grep {$$_{mark}==1} values %GtkDefs::signals; push @targets,grep {$$_{mark}==1} values %GtkDefs::properties; push @targets,grep {$$_{mark}==1} values %GtkDefs::child_properties; # find the classes which used them. my @classes = unique(map { $$_{class} } @targets); # find methods/signals/properties which are in those classes which didn't get marked. my @unwrapped; my $class; foreach $class (@classes) { # if this class's parent is defined then don't put its properties as unwrapped. # this may not work if parent is from other library (GtkApplication's parent # is GApplication, so all its properties will be marked as unwrapped) my $detailed = 0; my $parent = undef; if (exists $GtkDefs::objects{$class}) { my $object = $GtkDefs::objects{$class}; if (defined $object) { $parent = $object->{parent}; # may be empty for some classes deriving a GInterface? if ($parent) { $detailed = 1; } } } if ($detailed) { push @unwrapped, grep {$$_{class} eq $class && $$_{mark}==0 && not exists $GtkDefs::properties{$parent . '::' . $_->{name}}} values %GtkDefs::properties; push @unwrapped, grep {$$_{class} eq $class && $$_{mark}==0 && not exists $GtkDefs::child_properties{$parent . '::' . $_->{name}}} values %GtkDefs::child_properties; } else { push @unwrapped, grep {$$_{class} eq $class && $$_{mark}==0} values %GtkDefs::properties; push @unwrapped, grep {$$_{class} eq $class && $$_{mark}==0} values %GtkDefs::child_properties; } push @unwrapped, grep {$$_{class} eq $class && $$_{mark}==0} values %GtkDefs::methods; push @unwrapped, grep {$$_{class} eq $class && $$_{mark}==0} values %GtkDefs::signals; } return @unwrapped; } ########################## sub lookup_enum($) { no warnings; my ($c_type) = @_; my $obj = $GtkDefs::enums{$c_type}; return 0 if(!$obj); $$obj{mark} = 1; return $obj; } sub lookup_object($) { no warnings; my $c_name = $_[0]; my $result = $GtkDefs::objects{$c_name}; if (not defined($result)) { # We do not print this error because it's not always an error, # because the caller will often try several object names, # while guessing an object name prefix from a function name. # # print "GtkDefs:lookup_object(): can't find object with name=" . $c_name . "\n"; # debug output: # foreach my $key (keys %GtkDefs::objects) # { # print " possible name=" . $key . "\n"; # } } return $result; } # $objProperty lookup_property($name, $parent_object_name) sub lookup_property($$) { no warnings; my ($parent_object_name, $name) = @_; $name =~ s/-/_/g; my $obj = $GtkDefs::properties{"${parent_object_name}::${name}"}; return 0 if ($obj eq ""); $$obj{mark} = 1; return $obj; } # $objChildProperty lookup_child_property($name, $parent_object_name) sub lookup_child_property($$) { no warnings; my ($parent_object_name, $name) = @_; $name =~ s/-/_/g; my $obj = $GtkDefs::child_properties{"${parent_object_name}::${name}"}; return 0 if ($obj eq ""); $$obj{mark} = 1; return $obj; } sub lookup_method_dont_mark($) { no warnings; my ($c_name) = @_; $c_name =~ s/-/_/g; my $obj = $GtkDefs::methods{$c_name}; return 0 if ($obj eq ""); return $obj; } sub lookup_method($) { my $obj = lookup_method_dont_mark($_[0]); $$obj{mark} = 1 if($obj); return $obj; } sub lookup_function($) { return lookup_method($_[0]); } sub lookup_method_set_weak_mark($) { my $obj = lookup_method_dont_mark($_[0]); # A constructor or a static method may be listed in the .defs file as a method # of another class, if its first parameter is a pointer to a class instance. # Examples: # GVariantIter* g_variant_iter_new(GVariant* value) # GtkWidget* gtk_application_window_new(GtkApplication* application) # GSocketConnection* g_socket_connection_factory_create_connection(GSocket* socket) # # The use of gtk_application_window_new() in Gtk::ApplicationWindow shall # not cause get_unwrapped() to list all methods, signals and properties of # GtkApplication as unwrapped in applicationwindow.hg. # Therefore mark=2 instead of mark=1. $$obj{mark} = 2 if ($obj && $$obj{mark} == 0); return $obj; } sub lookup_signal($$) { no warnings; my ($parent_object_name, $name) = @_; $name =~ s/-/_/g; my $obj = $GtkDefs::signals{"${parent_object_name}::${name}"}; return 0 if ($obj eq ""); $$obj{mark} = 1; return $obj; } sub error { my $format = shift @_; printf STDERR "GtkDefs.pm: $format\n", @_; } ######################################################################## package GtkDefs::Function; BEGIN { @GtkDefs::Function::ISA=qw(FunctionBase); } # class Function : FunctionBase # { # string name; e.g. function: gtk_accelerator_valid, method: clicked # string c_name; e.g. gtk_accelerator_valid, gtk_button_clicked # string class; e.g. GtkButton # # string rettype; # string array param_types; # string array param_names; # # string entity_type; e.g. method or function # # bool varargs; # bool mark; # } # "new" can't have prototype sub new { my ($def) = @_; my $whole = $def; my $self = {}; bless $self; #Remove first and last braces: $def =~ s/^\(//; $def =~ s/\)$//; #In rare cases a method can be nameless (g_iconv). #Don't interpret the following "(of-object" as the method's name. $def =~ s/^\s*define-([^\s\(]+)\s*([^\s\(]*)\s*//; $$self{entity_type} = $1; $$self{name} = $2; $$self{name} =~ s/-/_/g; # change - to _ # init variables $$self{mark} = 0; $$self{rettype} = "none"; $$self{param_types} = []; $$self{param_names} = []; $$self{class} = ""; # snarf down lisp fields $$self{c_name} = $1 if ($def=~s/\(c-name "(\S+)"\)//); $$self{class} = $1 if ($def=~s/\(of-object "(\S+)"\)//); if ($def =~ s/\(return-type "(\S+)"\)//) { $$self{rettype} = $1; $$self{rettype} =~ s/-/ /g; #e.g. replace const-gchar* with const gchar*. Otherwise it will be used in code. } $$self{varargs} = 1 if ($def=~s/\(varargs\s+#t\)//); $$self{rettype} = "void" if ($$self{rettype} eq "none"); # methods have a parameter not stated in the defs file if ($$self{entity_type} eq "method") { push( @{$$self{param_types}}, "$$self{class}*" ); push( @{$$self{param_names}}, "self" ); } # parameters are compound lisp statement if ($def =~ s/\(parameters(( '\("\S+" "\S+"\))*) \)//) { $self->parse_param($1); } # is-constructor-of: if ($def =~ s/\(is-constructor-of "(\S+)"\)//) { #Ignore them. } # of-object if ($def =~ s/\(of-object "(\S+)"\)//) { #Ignore them. } GtkDefs::error("Unhandled function parameter ($def) in $$self{c_name}\n") if ($def !~ /^\s*$/); return $self; } sub parse_param($$) { my ($self, $param) = @_; # break up the parameter statements foreach (split(/\s*'*[()]\s*/, $param)) { next if ($_ eq ""); if (/^"(\S+)" "(\S+)"$/) { my ($p1, $p2) = ($1,$2); $p1 =~ s/-/ /; push( @{$$self{param_types}}, $p1); push( @{$$self{param_names}}, $p2); } else { GtkDefs::error("Unknown parameter statement ($_) in $$self{c_name}\n"); } } } # $string get_return_type_for_methods(). # Changes gchar* (not const-gchar*) to return-gchar* so that _CONVERT knows that it needs to be freed. sub get_return_type_for_methods($) { my ($self) = @_; my $rettype = $$self{rettype}; if($rettype eq "gchar*" || $rettype eq "char*") { $rettype = "return-" . $rettype; } return $rettype; } sub get_param_names { my ($self) = @_; return @$self{param_names}; } ###################################################################### package GtkDefs::Signal; BEGIN { @GtkDefs::Signal::ISA=qw(GtkDefs::Function); } # class Signal : Function # { # string name; e.g. gtk_accelerator_valid # string class e.g. GtkButton ( == of-object.) # # string rettype; # # string flags. e.g. Run Last, No Hooks # string entity_type. e.g. vfunc or signal # bool detailed; # optional # bool deprecated; # optional # } # "new" can't have prototype sub new { my ($def) = @_; my $whole = $def; my $self = {}; bless $self; #Remove first and last braces: $def =~ s/^\(//; $def =~ s/\)$//; $def =~ s/^\s*define-(\S+)\s+(\S+)\s*//; $$self{entity_type} = $1; $$self{name} = $2; $$self{name} =~ s/-/_/g; #change - to _ # init variables $$self{mark}=0; $$self{rettype} = "none"; $$self{param_types} = []; $$self{param_names} = []; $$self{flags} = ""; $$self{class} = ""; # snarf down lisp fields if($def =~ s/\(of-object "(\S+)"\)//) { $$self{class} = $1; } else { GtkDefs::error("define-signal/define-vfunc without of-object (entity type: $$self{entity_type}): $whole"); } if($def =~ s/\(return-type "(\S+)"\)//) { $$self{rettype} = $1; $$self{rettype} =~ s/-/ /g; #e.g. replace const-gchar* with const gchar*. Otherwise it will be used in code. } if ($def =~ s/\(flags "(.*?)"\)//) { $$self{flags} = $1; } elsif ($def =~ s/\(when "(\S+)"\)//) { # "when" is a deprecated alternative to "flags". # when eq "none", "first", "last", or "both". if ($1 eq "first") { $$self{flags} = "Run First"; } elsif ($1 eq "last") { $$self{flags} = "Run Last"; } elsif ($1 eq "both") { $$self{flags} = "Run First, Run Last"; } } if($$self{rettype} eq "none") { $$self{rettype} = "void" } $$self{detailed} = ($1 eq "#t") if ($def =~ s/\(detailed (\S+)\)//); $$self{deprecated} = ($1 eq "#t") if ($def =~ s/\(deprecated (\S+)\)//); # signals always have a parameter push(@{$$self{param_types}}, "$$self{class}*"); push(@{$$self{param_names}}, "self"); # parameters are compound lisp statement if ($def =~ s/\(parameters(( '\("\S+" "\S+"\))+) \)//) { $self->parse_param($1); } if ($def!~/^\s*$/) { GtkDefs::error("Unhandled signal/vfunc def ($def) in $$self{class}::$$self{name}"); } return $self; } # bool get_detailed() sub get_detailed($) { my ($self) = @_; return $$self{detailed}; # undef, 0 or 1 } # bool get_deprecated() sub get_deprecated($) { my ($self) = @_; return $$self{deprecated}; # undef, 0 or 1 } # bool has_same_types($objFunction) # Compares return types and argument types sub has_same_types($$) { my ($self, $objFuncOther) = @_; #Compare return types: if($self->types_are_equal($$self{rettype}, $$objFuncOther{rettype}) ne 1) { # printf("debug: different return types: %s, %s\n", $$self{rettype}, $$objFuncOther{rettype}); return 0; #Different types found. } #Compare arguement types: my $i = 0; my $param_types = $$self{param_types}; my $param_types_other = $$objFuncOther{param_types}; for ($i = 1; $i < $#$param_types + 1; $i++) { my $type_a = $$param_types[$i]; my $type_b = $$param_types_other[$i-1]; if($self->types_are_equal($type_a, $type_b) ne 1) { # printf("debug: different arg types: %s, %s\n", $type_a, $type_b); return 0; #Different types found. } } return 1; #They must all be the same for it to get this far. } # bool types_are_equal($a, $b) # Compares types, ignoring gint/int differences, etc. sub types_are_equal($$$) { #TODO: Proper method of getting a normalized type name. my ($self, $type_a, $type_b) = @_; if($type_a ne $type_b) { #Try adding g to one of them: if( ("g" . $type_a) ne $type_b ) { #Try adding g to the other one: if( $type_a ne ("g" . $type_b) ) { #After all these checks it's still not equal: return 0; #not equal. } } } # printf("DEBUG: types are equal: %s, %s\n", $$type_a, $$type_b); return 1; #They must be the same for it to get this far. } 1; # indicate proper module load.