#!/usr/bin/perl # # gapi2xml.pl : Generates an XML representation of GObject based APIs. # # Author: Mike Kestner # # Copyright (c) 2001-2003 Mike Kestner # Copyright (c) 2003-2009 Novell, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # 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, write to the # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. ############################################################## $parser_version = 2; $debug=$ENV{'GAPI_DEBUG'}; use XML::LibXML; if (!$ARGV[2]) { die "Usage: gapi_pp.pl | gapi2xml.pl \n"; } $ns = $ARGV[0]; $libname = $ARGV[2]; # Used by name mangling sub %num2txt = ('1', "One", '2', "Two", '3', "Three", '4', "Four", '5', "Five", '6', "Six", '7', "Seven", '8', "Eight", '9', "Nine", '0', "Zero"); ############################################################## # Check if the filename provided exists. We parse existing files into # a tree and append the namespace to the root node. If the file doesn't # exist, we create a doc tree and root node to work with. ############################################################## if (-e $ARGV[1]) { #parse existing file and get root node. $doc = XML::LibXML->new->parse_file($ARGV[1]); $root = $doc->getDocumentElement(); if ($root->getAttribute ('parser_version') != $parser_version) { die "The version of the file does not match the version of the parser"; } } else { $doc = XML::LibXML::Document->new(); $root = $doc->createElement('api'); $root->setAttribute('parser_version', $parser_version); $doc->setDocumentElement($root); $warning_node = XML::LibXML::Comment->new ("\n\n This file was automatically generated.\n Please DO NOT MODIFY THIS FILE, modify .metadata files instead.\n\n"); $root->appendChild($warning_node); } $ns_elem = $doc->createElement('namespace'); $ns_elem->setAttribute('name', $ns); $ns_elem->setAttribute('library', $libname); $root->appendChild($ns_elem); ############################################################## # First we parse the input for typedefs, structs, enums, and class_init funcs # and put them into temporary hashes. ############################################################## while ($line = ) { if ($line =~ /typedef\s+(struct\s+\w+\s+)\*+(\w+);/) { $ptrs{$2} = $1; } elsif ($line =~ /typedef\s+(struct\s+\w+)\s+(\w+);/) { next if ($2 =~ /Private$/); # fixme: siiigh $2 = "GdkDrawable" if ($1 eq "_GdkDrawable"); $types{$2} = $1; } elsif ($line =~ /typedef\s+struct/) { $sdef = $line; while ($line = ) { $sdef .= $line; last if ($line =~ /^(deprecated)?}/); } $sdef =~ s!/\*.*?(\*/|\n)!!g; $sdef =~ s/\n\s*//g; $types{$1} = $sdef if ($sdef =~ /.*\}\s*(\w+);/); } elsif ($line =~ /typedef\s+(unsigned\s+\w+)\s+(\**)(\w+);/) { $types{$3} = $1 . $2; } elsif ($line =~ /typedef\s+(\w+)\s+(\**)(\w+);/) { $types{$3} = $1 . $2; } elsif ($line =~ /typedef\s+enum\s+(\w+)\s+(\w+);/) { $etypes{$1} = $2; } elsif ($line =~ /^((deprecated)?typedef\s+)?\benum\b/) { $edef = $line; while ($line = ) { $edef .= $line; last if ($line =~ /^(deprecated)?}\s*(\w+)?;/); } $edef =~ s/\n\s*//g; $edef =~ s|/\*.*?\*/||g; if ($edef =~ /typedef.*}\s*(\w+);/) { $ename = $1; } elsif ($edef =~ /^(deprecated)?enum\s+(\w+)\s*{/) { $ename = $2; } else { print "Unexpected enum format\n$edef"; next; } $edefs{$ename} = $edef; } elsif ($line =~ /typedef\s+(const\s+)?\w+\s*\**\s*\(\s*\*\s*(\w+)\s*\)\s*\(/) { $fname = $2; $fdef = ""; while ($line !~ /;/) { $fdef .= $line; $line = ; } $fdef .= $line; $fdef =~ s/\n\s+//g; $fpdefs{$fname} = $fdef; } elsif ($line =~ /^(private|deprecated)?struct\s+(\w+)/) { next if ($line =~ /;/); $sname = $2; $sdef = $line; while ($line = ) { $sdef .= $line; last if ($line =~ /^(deprecated)?}/); } $sdef =~ s!/\*[^<].*?(\*/|\n)!!g; $sdef =~ s/\n\s*//g; $sdefs{$sname} = $sdef if (!exists ($sdefs{$sname})); } elsif ($line =~ /^(\w+)_(class|base|default)_init\b/) { $class = StudlyCaps($1); $pedef = $line; while ($line = ) { $pedef .= $line; last if ($line =~ /^(deprecated)?}/); } $pedefs{lc($class)} = $pedef; } elsif ($line =~ /^(\w+)_get_type\b/) { $class = StudlyCaps($1); $pedef = $line; while ($line = ) { $pedef .= $line; if ($line =~ /g_boxed_type_register_static/) { $boxdef = $line; while ($line !~ /;/) { $boxdef .= ($line = ); } $boxdef =~ s/\n\s*//g; $boxdef =~ /\(\"(\w+)\"/; my $boxtype = $1; $boxtype =~ s/($ns)Type(\w+)/$ns$2/; $boxdefs{$boxtype} = $boxdef; } elsif ($line =~ /g_(enum|flags)_register_static/) { $pedef =~ /^(\w+_get_type)/; $enum_gtype{$class} = $1; } last if ($line =~ /^(deprecated)?}/); } $typefuncs{lc($class)} = $pedef; } elsif ($line =~ /^G_DEFINE_TYPE_(EXTENDED|WITH_CODE)\s*\(\s*(\w+)/) { $typefuncs{lc($2)} = $line; } elsif ($line =~ /^G_DEFINE_BOXED_TYPE\s*\(\s*(\w+)/) { $boxdefs{$1} = $line; } elsif ($line =~ /^G_DEFINE_INTERFACE\s*\(\s*(\w+)\s*,\s*(\w+)/) { if (exists($get_types{"$2_get_type"})) { $typedef = $get_types{"$2_get_type"}; if (!exists($ifaces{$typedef})) { $ifaces{$typedef} = "$1Interface"; } } } elsif ($line =~ /^(deprecated)?(const|G_CONST_RETURN)?\s*(struct\s+)?\w+\s*\**(\s*(const|G_CONST_RETURN)\s*\**)?\s*(\w+)\s*\(/) { $fname = $6; $fdef = ""; while ($line !~ /;/) { $fdef .= $line; $line = ; } $fdef .= $line; $fdef =~ s/\n\s*//g; if ($fdef !~ /^_/) { $fdefs{$fname} = $fdef; } } elsif ($line =~ /CHECK_(\w*)CAST/) { $cast_macro = $line; while ($line =~ /\\$/) { $line = ; $cast_macro .= $line; } $cast_macro =~ s/\\\n\s*//g; $cast_macro =~ s/\s+/ /g; if ($cast_macro =~ /G_TYPE_CHECK_(\w+)_CAST.*,\s*(\w+),\s*(\w+)\)/) { if ($1 eq "INSTANCE") { $objects{$2} = $3 . $objects{$2}; } else { $objects{$2} .= ":$3"; } } elsif ($cast_macro =~ /G_TYPE_CHECK_(\w+)_CAST.*,\s*([a-zA-Z0-9]+)_(\w+)_get_type\s*\(\),\s*(\w+)\)/) { $typename = uc ("$2_type_$3"); if ($1 eq "INSTANCE") { $objects{$typename} = $4 . $objects{$typename}; } else { $objects{$typename} .= ":$4"; } } elsif ($cast_macro =~ /GTK_CHECK_CAST.*,\s*(\w+),\s*(\w+)/) { $objects{$1} = $2 . $objects{$1}; } elsif ($cast_macro =~ /GTK_CHECK_CLASS_CAST.*,\s*(\w+),\s*(\w+)/) { $objects{$1} .= ":$2"; } elsif ($cast_macro =~ /GST_IMPLEMENTS_INTERFACE_CHECK_INSTANCE_CAST.*,\s*(\w+),\s*(\w+)/) { $objects{$1} = $2 . $objects{$1}; } } elsif ($line =~ /INSTANCE_GET_INTERFACE.*,\s*(\w+),\s*(\w+)/) { $ifaces{$1} = $2; } elsif ($line =~ /^BUILTIN\s*\{\s*\"(\w+)\".*GTK_TYPE_BOXED/) { $boxdefs{$1} = $line; } elsif ($line =~ /^BUILTIN\s*\{\s*\"(\w+)\".*GTK_TYPE_(ENUM|FLAGS)/) { # ignoring these for now. } elsif ($line =~ /^(deprecated)?\#\s*define/) { my $test_ns = uc ($ns); if ($line =~ /^deprecated\#\s*define\s+(\w+)\s+\"(.*)\"/) { $defines{"deprecated$1"} = $2; } elsif ($line =~ /\#\s*define\s+(\w+)\s+\"(.*)\"/) { $defines{$1} = $2; } elsif ($line =~ /\#\s*define\s+(\w+)\s+\(?\s*(\w+_get_type)/) { $get_types{$2} = $1; } } elsif ($line !~ /\/\*/) { print $line; } } ############################################################## # Produce the enum definitions. ############################################################## %enums = (); foreach $cname (sort(keys(%edefs))) { $ecnt++; $def = $edefs{$cname}; $cname = $etypes{$cname} if (exists($etypes{$cname})); $enums{lc($cname)} = $cname; $enum_elem = addNameElem($ns_elem, 'enum', $cname, $ns); if ($def =~ /^deprecated/) { $enum_elem->setAttribute("deprecated", "1"); $def =~ s/deprecated//g; } if ($enum_gtype{$cname}) { $enum_elem->setAttribute("gtype", $enum_gtype{$cname}); } if ($def =~ /<setAttribute('type', "flags"); } else { $enum_elem->setAttribute('type', "enum"); } $def =~ /\{(.*\S)\s*\}/; @vals = split(/,\s*/, $1); $vals[0] =~ s/^\s+//; @nameandval = split(/=/, $vals[0]); @v0 = split(/_/, $nameandval[0]); if (@vals > 1) { $done = 0; for ($idx = 0, $regex = ""; $idx < @v0; $idx++) { $regex .= ($v0[$idx] . "_"); foreach $val (@vals) { $done = 1 if ($val !~ /$regex/); } last if $done; } $common = join("_", @v0[0..$idx-1]); } else { $common = join("_", @v0[0..$#v0-1]); } foreach $val (@vals) { $val =~ s/=\s*\(\s*(.*\S)\s*\)\s*/= \1/; if ($val =~ /$common\_?(\w+)\s*=\s*(.*)$/) { $name = $1; $enumval = $2; if ($enumval =~ /^(\d+|0x[0-9A-Fa-f]+)u?\s*<<\s*(\d+)$/) { $enumval = "$1 << $2"; } elsif ($enumval =~ /^$common\_?(\w+)$/) { $enumval = StudlyCaps(lc($1)) } } elsif ($val =~ /$common\_?(\w+)/) { $name = $1; $enumval = ""; } else { die "Unexpected enum value: $val for common value $common\n"; } $val_elem = addNameElem($enum_elem, 'member'); $val_elem->setAttribute('cname', "$common\_$name"); $val_elem->setAttribute('name', StudlyCaps(lc($name))); if ($enumval) { $val_elem->setAttribute('value', $enumval); } } } ############################################################## # Parse the callbacks. ############################################################## foreach $cbname (sort(keys(%fpdefs))) { next if ($cbname =~ /^_/); $cbcnt++; $fdef = $cb = $fpdefs{$cbname}; $cb_elem = addNameElem($ns_elem, 'callback', $cbname, $ns); $cb =~ /typedef\s+(.*)\(.*\).*\((.*)\);/; $ret = $1; $params = $2; addReturnElem($cb_elem, $ret); if ($params && ($params ne "void")) { addParamsElem($cb_elem, split(/,/, $params)); } } ############################################################## # Parse the interfaces list. ############################################################## foreach $type (sort(keys(%ifaces))) { $iface = $ifaces{$type}; ($inst, $dontcare) = split(/:/, delete $objects{$type}); $initfunc = $pedefs{lc($inst)}; $ifacetype = delete $types{$iface}; delete $types{$inst}; $ifacecnt++; $iface_el = addNameElem($ns_elem, 'interface', $inst, $ns); $elem_table{lc($inst)} = $iface_el; if ($ifacetype =~ /struct\s+(\w+)/) { $classdef = $sdefs{$1}; } else { $classdef = 0; } if ($classdef) { my @signal_vms; if ($initfunc) { @signal_vms = parseInitFunc($iface_el, $initfunc, $classdef); } else { warn "Don't have an init func for $inst.\n" if $debug; # my @signal_vms; } addClassElem ($iface_el, $classdef, @signal_vms); } else { $iface_el->setAttribute("consume_only", "1"); } } ############################################################## # Parse the classes by walking the objects list. ############################################################## foreach $type (sort(keys(%objects))) { ($inst, $class) = split(/:/, $objects{$type}); $class = $inst . "Class" if (!$class); $initfunc = $pedefs{lc($inst)}; $typefunc = $typefuncs{lc($inst)}; $insttype = delete $types{$inst}; $classtype = delete $types{$class}; $instdef = $classdef = ""; $instdef = $sdefs{$1} if ($insttype =~ /struct\s+(\w+)/); $classdef = $sdefs{$1} if ($classtype =~ /struct\s+(\w+)/); $classdef =~ s/deprecated//g; $instdef =~ s/\s+(\*+)([^\/])/\1 \2/g; warn "Strange Class $inst\n" if (!$instdef && $debug); $classcnt++; $obj_el = addNameElem($ns_elem, 'object', $inst, $ns); $elem_table{lc($inst)} = $obj_el; # Check if the object is deprecated if ($instdef =~ /^deprecatedstruct/) { $obj_el->setAttribute("deprecated", "1"); $instdef =~ s/deprecated//g; } # Extract parent and fields from the struct if ($instdef =~ /^struct/) { $instdef =~ /\{(.*)\}/; $fieldstr = $1; $fieldstr =~ s|/\*[^<].*?\*/||g; @fields = split(/;/, $fieldstr); addFieldElems($obj_el, 'private', @fields); $obj_el->setAttribute('parent', $obj_el->firstChild->getAttribute('type')); $obj_el->removeChild($obj_el->firstChild); } elsif ($instdef =~ /privatestruct/) { # just get the parent for private structs $instdef =~ /\{\s*(\w+)/; $obj_el->setAttribute('parent', "$1"); } # Get the props from the class_init func. if ($initfunc) { @signal_vms = parseInitFunc($obj_el, $initfunc, $classdef); } else { warn "Don't have an init func for $inst.\n" if $debug; } addClassElem ($obj_el, $classdef, @signal_vms) if ($classdef); # Get the interfaces from the class_init func. if ($typefunc) { if ($typefunc =~ /G_DEFINE_TYPE_(EXTENDED|WITH_CODE)/) { parseTypeFuncMacro($obj_el, $typefunc); } else { parseTypeFunc($obj_el, $typefunc); } } else { warn "Don't have a GetType func for $inst.\n" if $debug; } } ############################################################## # Parse the remaining types. ############################################################## foreach $key (sort (keys (%types))) { $lasttype = $type = $key; while ($type && ($types{$type} !~ /struct/)) { $lasttype = $type; $type = $types{$type}; } if ($types{$type} =~ /struct\s+(\w+)/) { $type = $1; if (exists($sdefs{$type})) { $def = $sdefs{$type}; } else { $def = "privatestruct"; } } elsif ($types{$type} =~ /struct/ && $type =~ /^$ns/) { $def = $types{$type}; } else { $elem = addNameElem($ns_elem, 'alias', $key, $ns); $elem->setAttribute('type', $lasttype); warn "alias $key to $lasttype\n" if $debug; next; } # fixme: hack if ($key eq "GdkBitmap") { $struct_el = addNameElem($ns_elem, 'object', $key, $ns); } elsif (exists($boxdefs{$key})) { $struct_el = addNameElem($ns_elem, 'boxed', $key, $ns); } else { $struct_el = addNameElem($ns_elem, 'struct', $key, $ns); } if ($def =~ /^deprecated/) { $struct_el->setAttribute("deprecated", "1"); $def =~ s/deprecated//g; } $elem_table{lc($key)} = $struct_el; $def =~ s/\s+/ /g; if ($def =~ /privatestruct/) { $struct_el->setAttribute('opaque', 'true'); } else { $def =~ /\{(.+)\}/; addFieldElems($struct_el, 'public', split(/;/, $1)); } } # really, _really_ opaque structs that aren't even defined in sources. Lovely. foreach $key (sort (keys (%ptrs))) { next if $ptrs{$key} !~ /struct\s+(\w+)/; $type = $1; $struct_el = addNameElem ($ns_elem, 'struct', $key, $ns); $struct_el->setAttribute('opaque', 'true'); $elem_table{lc($key)} = $struct_el; } addFuncElems(); addStaticFuncElems(); # This should probably be done in a more generic way foreach $define (sort (keys (%defines))) { next if $define !~ /[A-Z]_STOCK_/; if ($stocks{$ns}) { $stock_el = $stocks{$ns}; } else { $stock_el = addNameElem($ns_elem, "object", $ns . "Stock", $ns); $stocks{$ns} = $stock_el; } $string_el = addNameElem ($stock_el, "static-string", $define); $string_name = lc($define); $string_name =~ s/\w+_stock_//; $string_el->setAttribute('name', StudlyCaps($string_name)); $string_el->setAttribute('value', $defines{$define}); } ############################################################## # Output the tree ############################################################## if ($ARGV[1]) { open(XMLFILE, ">$ARGV[1]") || die "Couldn't open $ARGV[1] for writing.\n"; print XMLFILE $doc->toString(); close(XMLFILE); } else { print $doc->toString(); } ############################################################## # Generate a few stats from the parsed source. ############################################################## $scnt = keys(%sdefs); $fcnt = keys(%fdefs); $tcnt = keys(%types); print "structs: $scnt enums: $ecnt callbacks: $cbcnt\n"; print "funcs: $fcnt types: $tcnt classes: $classcnt\n"; print "props: $propcnt childprops: $childpropcnt signals: $sigcnt\n\n"; sub addClassElem { my ($obj_el, $classdef, @signal_vms) = @_; my %is_signal_vm; for (@signal_vms) { $is_signal_vm{$_} = 1; } if ($classdef =~ /struct\s+_?(\w+)\s*{(.*)};/) { my $elem = $doc->createElement('class_struct'); $elem->setAttribute('cname', $1); $obj_el->insertBefore($elem, $obj_el->firstChild); $fields = $2; $fields =~ s!/\*.*?\*/!!g; # Remove comments foreach $field (split (/;/, $fields)) { if ($field =~ /\s*(G_CONST_RETURN\s+)?(\S+\s*\**)\s*\(\s*\*\s*(\w+)\s*\)\s*(\((.*?)\))?/) { $ret = $1 . $2; $cname = $3; $parms = $5; $class_elem = $doc->createElement('method'); $elem->appendChild($class_elem); if ($is_signal_vm{$cname}) { $class_elem->setAttribute('signal_vm', $cname); } else { $class_elem->setAttribute('vm', $cname); $vm_elem = $doc->createElement('virtual_method'); $obj_el->appendChild($vm_elem); $vm_elem->setAttribute('name', StudlyCaps($cname)); $vm_elem->setAttribute('cname', $cname); addReturnElem($vm_elem, $ret); if ($parms && ($parms ne "void")) { # if there are any parameters @parm_arr = split(/,/, $parms); $parms =~ /\s*(\w+)/; # Get type of first parameter if ($1 ne $obj_el->getAttribute ('cname')) { $vm_elem->setAttribute('shared', 'true'); # First parameter is not of the type of the declaring class -> static vm } else { ($dump, @parm_arr) = @parm_arr; } addParamsElem($vm_elem, @parm_arr); } else { $vm_elem->setAttribute('shared', 'true'); } if ($cname =~ /reserved_?[0-9]+$/ || $cname =~ /padding_?[0-9]+$/ || $cname =~ /recent_?[0-9]+$/) { $vm_elem->setAttribute('padding', 'true'); } } } elsif ($field =~ /(unsigned\s+)?(const\s+)?(\S+)\s+(.+)/) { my $unsigned = $1; $const = $2; $name = $3; $symb = $4; $const =~ s/const\s+/const\-/g; my $type = $unsigned . $const . $name; foreach $tok (split (/,\s*/, $symb)) { # multiple field defs may occur in one line; like int xrange, yrange; $tok =~ /(\*)?(\w+)\s*(.*)/; my $field_type = $type . $1; my $cname = $2; my $modifiers = $3; $fld_elem = addNameElem($elem, 'field', $cname, ""); $fld_elem->setAttribute('type', "$field_type"); if ($modifiers =~ /\[(.*)\]/) { $fld_elem->setAttribute('array_len', "$1"); } elsif ($modifiers =~ /\:\s*(\d+)/) { $fld_elem->setAttribute('bits', "$1"); } } } elsif ($field =~ /\S+/) { print "***** Unmatched class struct field $field\n"; } } } else { print "***** Unmatched $classdef\n"; } } sub addFieldElems { my ($parent, $defaultaccess, @fields) = @_; my $access = $defaultaccess; foreach $field (@fields) { if ($field =~ m!/\*< (public|private) >.*\*/(.*)$!) { $access = $1; $field = $2; } next if ($field !~ /\S/); $field =~ s/GSEAL\s*\((.*)\)/\1/g; $field =~ s/\bvolatile\s+//g; $field =~ s/\s+(\*+)/\1 /g; $field =~ s/(const\s+)?(\w+)\*\s+const\*/const \2\*/g; $field =~ s/(\w+)\s+const\s*\*/const \1\*/g; $field =~ s/const /const\-/g; $field =~ s/struct /struct\-/g; $field =~ s/.*\*\///g; next if ($field !~ /\S/); if ($field =~ /(\S+\s+\*?)\(\*\s*(.+)\)\s*\((.*)\)/) { $elem = addNameElem($parent, 'callback', $2); addReturnElem($elem, $1); addParamsElem($elem, $3); } elsif ($field =~ /(unsigned )?(\S+)\s+(.+)/) { my $type = $1 . $2; $symb = $3; foreach $tok (split (/,\s*/, $symb)) { if ($tok =~ /(\w+)\s*\[(.*)\]/) { $elem = addNameElem($parent, 'field', $1, ""); $elem->setAttribute('array_len', "$2"); } elsif ($tok =~ /(\w+)\s*\:\s*(\d+)/) { $elem = addNameElem($parent, 'field', $1, ""); $elem->setAttribute('bits', "$2"); } else { $elem = addNameElem($parent, 'field', $tok, ""); } $elem->setAttribute('type', "$type"); if ($access ne $defaultaccess) { $elem->setAttribute('access', "$access"); } } } else { die "$field\n"; } } } sub addFuncElems { my ($obj_el, $inst, $prefix); $fcnt = keys(%fdefs); foreach $mname (sort (keys (%fdefs))) { next if ($mname =~ /^_/); $obj_el = ""; $prefix = $mname; $prepend = undef; while ($prefix =~ /(\w+)_/) { $prefix = $key = $1; $key =~ s/_//g; # FIXME: lame Gdk API hack if ($key eq "gdkdraw") { $key = "gdkdrawable"; $prepend = "draw_"; } if (exists ($elem_table{$key})) { $prefix .= "_"; $obj_el = $elem_table{$key}; $inst = $key; last; } elsif (exists ($enums{$key}) && ($mname =~ /_get_type/)) { delete $fdefs{$mname}; last; } } next if (!$obj_el); $mdef = delete $fdefs{$mname}; if ($mname =~ /$prefix(new)/) { $el = addNameElem($obj_el, 'constructor', $mname); if ($mdef =~ /^deprecated/) { $el->setAttribute("deprecated", "1"); $mdef =~ s/deprecated//g; } $drop_1st = 0; } else { $el = addNameElem($obj_el, 'method', $mname, $prefix, $prepend); if ($mdef =~ /^deprecated/) { $el->setAttribute("deprecated", "1"); $mdef =~ s/deprecated//g; } $mdef =~ /(.*?)\w+\s*\(/; addReturnElem($el, $1); $mdef =~ /\(\s*(const)?\s*(\w+)/; if (lc($2) ne $inst) { $el->setAttribute("shared", "true"); $drop_1st = 0; } else { $drop_1st = 1; } } parseParms ($el, $mdef, $drop_1st); # Don't add "free" to this regexp; that will wrongly catch all boxed types if ($mname =~ /$prefix(new|destroy|ref|unref)/ && ($obj_el->nodeName eq "boxed" || $obj_el->nodeName eq "struct") && $obj_el->getAttribute("opaque") ne "true") { $obj_el->setAttribute("opaque", "true"); for my $field ($obj_el->getElementsByTagName("field")) { if (!$field->getAttribute("access")) { $field->setAttribute("access", "public"); $field->setAttribute("writeable", "true"); } } } } } sub parseParms { my ($el, $mdef, $drop_1st) = @_; $fmt_args = 0; if ($mdef =~ /G_GNUC_PRINTF.*\((\d+,\s*\d+)\s*\)/) { $fmt_args = $1; $mdef =~ s/\s*G_GNUC_PRINTF.*\)//; } if (($mdef =~ /\((.*)\)/) && ($1 ne "void")) { @parms = (); $parm = ""; $pcnt = 0; foreach $char (split(//, $1)) { if ($char eq "(") { $pcnt++; } elsif ($char eq ")") { $pcnt--; } elsif (($pcnt == 0) && ($char eq ",")) { @parms = (@parms, $parm); $parm = ""; next; } $parm .= $char; } if ($parm) { @parms = (@parms, $parm); } # @parms = split(/,/, $1); ($dump, @parms) = @parms if $drop_1st; if (@parms > 0) { addParamsElem($el, @parms); } if ($fmt_args != 0) { $fmt_args =~ /(\d+),\s*(\d+)/; $fmt = $1; $args = $2; ($params_el, @junk) = $el->getElementsByTagName ("parameters"); (@params) = $params_el->getElementsByTagName ("parameter"); $offset = 1 + $drop_1st; $params[$fmt-$offset]->setAttribute ("printf_format", "true"); $params[$args-$offset]->setAttribute ("printf_format_args", "true"); } } } sub addStaticFuncElems { my ($global_el, $ns_prefix); @mnames = sort (keys (%fdefs)); $mcount = @mnames; return if ($mcount == 0); $ns_prefix = ""; $global_el = ""; for ($i = 0; $i < $mcount; $i++) { $mname = $mnames[$i]; $prefix = $mname; next if ($prefix =~ /^_/); if ($ns_prefix eq "") { my (@toks) = split(/_/, $prefix); for ($j = 0; $j < @toks; $j++) { if (join ("", @toks[0 .. $j]) eq lc($ns)) { $ns_prefix = join ("_", @toks[0 .. $j]); last; } } next if ($ns_prefix eq ""); } next if ($mname !~ /^$ns_prefix/); if ($mname =~ /($ns_prefix)_([a-zA-Z]+)_\w+/) { $classname = $2; $key = $prefix = $1 . "_" . $2 . "_"; $key =~ s/_//g; $cnt = 1; if (exists ($enums{$key})) { $cnt = 1; } elsif ($classname ne "set" && $classname ne "get" && $classname ne "scan" && $classname ne "find" && $classname ne "add" && $classname ne "remove" && $classname ne "free" && $classname ne "register" && $classname ne "execute" && $classname ne "show" && $classname ne "parse" && $classname ne "paint" && $classname ne "string") { while ($mnames[$i+$cnt] =~ /$prefix/) { $cnt++; } } if ($cnt == 1) { $mdef = delete $fdefs{$mname}; if (!$global_el) { $global_el = $doc->createElement('class'); $global_el->setAttribute('name', "Global"); $global_el->setAttribute('cname', $ns . "Global"); $ns_elem->appendChild($global_el); } $el = addNameElem($global_el, 'method', $mname, $ns_prefix); if ($mdef =~ /^deprecated/) { $el->setAttribute("deprecated", "1"); $mdef =~ s/deprecated//g; } $mdef =~ /(.*?)\w+\s*\(/; addReturnElem($el, $1); $el->setAttribute("shared", "true"); parseParms ($el, $mdef, 0); next; } else { $class_el = $doc->createElement('class'); $class_el->setAttribute('name', StudlyCaps($classname)); $class_el->setAttribute('cname', StudlyCaps($prefix)); $ns_elem->appendChild($class_el); for ($j = 0; $j < $cnt; $j++) { $mdef = delete $fdefs{$mnames[$i+$j]}; $el = addNameElem($class_el, 'method', $mnames[$i+$j], $prefix); if ($mdef =~ /^deprecated/) { $el->setAttribute("deprecated", "1"); $mdef =~ s/deprecated//g; } $mdef =~ /(.*?)\w+\s*\(/; addReturnElem($el, $1); $el->setAttribute("shared", "true"); parseParms ($el, $mdef, 0); } $i += ($cnt - 1); next; } } } } sub addNameElem { my ($node, $type, $cname, $prefix, $prepend) = @_; my $elem = $doc->createElement($type); $node->appendChild($elem); if (defined $prefix) { my $match; if ($cname =~ /$prefix(\w+)/) { $match = $1; } else { $match = $cname; } if ($prepend) { $name = $prepend . $match; } else { $name = $match; } $elem->setAttribute('name', StudlyCaps($name)); } if ($cname) { $elem->setAttribute('cname', $cname); } return $elem; } sub addParamsElem { my ($parent, @params) = @_; my $parms_elem = $doc->createElement('parameters'); $parent->appendChild($parms_elem); my $parm_num = 0; foreach $parm (@params) { $parm_num++; $parm =~ s/\s+(\*+)/\1 /g; my $out = $parm =~ s/G_CONST_RETURN/const/g; $parm =~ s/(const\s+)?(\w+)\*\s+const\*/const \2\*/g; $parm =~ s/(\*+)\s*const\s+/\1 /g; $parm =~ s/(\w+)\s+const\s*\*/const \1\*/g; $parm =~ s/const\s+/const-/g; $parm =~ s/unsigned\s+/unsigned-/g; $parm =~ s/\bvolatile\s+//g; if ($parm =~ /(.*)\(\s*\**\s*(\w+)\)\s+\((.*)\)/) { my $ret = $1; my $cbn = $2; my $params = $3; my $type = $parent->getAttribute('name') . StudlyCaps($cbn); $cb_elem = addNameElem($ns_elem, 'callback', $type, $ns); addReturnElem($cb_elem, $ret); if ($params && ($params ne "void")) { addParamsElem($cb_elem, split(/,/, $params)); my $data_parm = $cb_elem->lastChild()->lastChild(); if ($data_parm && $data_parm->getAttribute('type') eq "gpointer") { $data_parm->setAttribute('name', 'data'); } } $parm_elem = $doc->createElement('parameter'); $parm_elem->setAttribute('type', $type); $parm_elem->setAttribute('name', $cbn); $parms_elem->appendChild($parm_elem); next; } elsif ($parm =~ /\.\.\./) { $parm_elem = $doc->createElement('parameter'); $parms_elem->appendChild($parm_elem); $parm_elem->setAttribute('ellipsis', 'true'); next; } $parm_elem = $doc->createElement('parameter'); $parms_elem->appendChild($parm_elem); my $name = ""; if ($parm =~ /struct\s+(\S+)\s+(\S+)/) { $parm_elem->setAttribute('type', $1); $name = $2; }elsif ($parm =~ /(unsigned )?(\S+)\s+(\S+)/) { $parm_elem->setAttribute('type', $1 . $2); $name = $3; } elsif ($parm =~ /(\w+\*)(\w+)/) { $parm_elem->setAttribute('type', $1); $name = $2; } elsif ($parm =~ /(\S+)/) { $parm_elem->setAttribute('type', $1); $name = "arg" . $parm_num; } if ($name =~ /(\w+)\[.*\]/) { $name = $1; $parm_elem->setAttribute('array', "true"); } if ($out) { $parm_elem->setAttribute('pass_as', "out"); } $parm_elem->setAttribute('name', $name); } } sub addReturnElem { my ($parent, $ret) = @_; $ret =~ s/(\w+)\s+const\s*\*/const \1\*/g; $ret =~ s/const|G_CONST_RETURN/const-/g; $ret =~ s/\s+//g; $ret =~ s/(const-)?(\w+)\*(const-)\*/const-\2\*\*/g; my $ret_elem = $doc->createElement('return-type'); $parent->appendChild($ret_elem); $ret_elem->setAttribute('type', $ret); if ($parent->getAttribute('name') eq "Copy" && $ret =~ /\*$/) { $ret_elem->setAttribute('owned', 'true'); } return $ret_elem; } sub addPropElem { my ($spec, $node, $is_child) = @_; my ($name, $mode, $docs); $spec =~ /g_param_spec_(\w+)\s*\((.*)\s*\)\s*\)/; my $type = $1; my @params = split(/,/, $2); $name = $params[0]; if ($defines{$name}) { $name = $defines{$name}; } else { $name =~ s/\s*\"//g; } $mode = $params[$#params]; if ($type =~ /boolean|float|double|^u?int|pointer|unichar/) { $type = "g$type"; } elsif ($type =~ /string/) { $type = "gchar*"; } elsif ($type =~ /boxed|object/) { $type = $params[$#params-1]; $type =~ s/TYPE_//; $type =~ s/\s+//g; $type = StudlyCaps(lc($type)); } elsif ($type =~ /enum|flags/) { $type = $params[$#params-2]; $type =~ s/TYPE_//; $type =~ s/\s+//g; $type = StudlyCaps(lc($type)); } $prop_elem = $doc->createElement($is_child ? "childprop" : "property"); $node->appendChild($prop_elem); $prop_elem->setAttribute('name', StudlyCaps($name)); $prop_elem->setAttribute('cname', $name); $prop_elem->setAttribute('type', $type); $prop_elem->setAttribute('readable', "true") if ($mode =~ /READ/); $prop_elem->setAttribute('writeable', "true") if ($mode =~ /WRIT/); $prop_elem->setAttribute('construct', "true") if ($mode =~ /CONSTRUCT(?!_)/); $prop_elem->setAttribute('construct-only', "true") if ($mode =~ /CONSTRUCT_ONLY/); } sub addPropElem2 { my ($spec, $node, $is_child) = @_; my ($name, $mode, $docs); $spec =~ /g_param_spec_(\w+)\s*\((.*)\s*\);/; my $type = $1; my @params = split(/,/, $2); $name = $params[0]; if ($defines{$name}) { $name = $defines{$name}; } else { $name =~ s/\s*\"//g; } $mode = $params[$#params]; if ($type =~ /boolean|float|double|^u?int|pointer|unichar/) { $type = "g$type"; } elsif ($type =~ /string/) { $type = "gchar*"; } elsif ($type =~ /boxed|object/) { $type = $params[$#params-1]; $type =~ s/TYPE_//; $type =~ s/\s+//g; $type = StudlyCaps(lc($type)); } elsif ($type =~ /enum|flags/) { $type = $params[$#params-2]; $type =~ s/TYPE_//; $type =~ s/\s+//g; $type = StudlyCaps(lc($type)); } $prop_elem = $doc->createElement($is_child ? "childprop" : "property"); $node->appendChild($prop_elem); $prop_elem->setAttribute('name', StudlyCaps($name)); $prop_elem->setAttribute('cname', $name); $prop_elem->setAttribute('type', $type); $prop_elem->setAttribute('readable', "true") if ($mode =~ /READ/); $prop_elem->setAttribute('writeable', "true") if ($mode =~ /WRIT/); $prop_elem->setAttribute('construct', "true") if ($mode =~ /CONSTRUCT(?!_)/); $prop_elem->setAttribute('construct-only', "true") if ($mode =~ /CONSTRUCT_ONLY/); } sub parseTypeToken { my ($tok) = @_; if ($tok =~ /G_TYPE_(\w+)/) { my $type = $1; if ($type eq "NONE") { return "void"; } elsif ($type eq "INT") { return "gint32"; } elsif ($type eq "UINT") { return "guint32"; } elsif ($type eq "ENUM" || $type eq "FLAGS") { return "gint32"; } elsif ($type eq "STRING") { return "gchar*"; } elsif ($type eq "OBJECT") { return "GObject*"; } else { return "g" . lc ($type); } } else { $tok =~ s/_TYPE//; $tok =~ s/\|.*STATIC_SCOPE//; $tok =~ s/\W+//g; return StudlyCaps (lc($tok)); } } sub addSignalElem { my ($spec, $class, $node) = @_; $spec =~ s/\n\s*//g; $class =~ s/\n\s*//g; $sig_elem = $doc->createElement('signal'); $node->appendChild($sig_elem); if ($spec =~ /\(\"([\w\-]+)\"/) { $sig_elem->setAttribute('name', StudlyCaps($1)); $sig_elem->setAttribute('cname', $1); } $sig_elem->setAttribute('when', $1) if ($spec =~ /_RUN_(\w+)/); $sig_elem->setAttribute('manual', 'true') if ($spec =~ /G_TYPE_POINTER/); if ($spec =~ /_OFFSET\s*\(\w+,\s*(\w+)\)/) { my $method = $1; $sig_elem->setAttribute('field_name', $method); if ($class =~ /;\s*(\/\*< (public|protected|private) >\s*\*\/)?(G_CONST_RETURN\s+)?(\w+\s*\**)\s*\(\s*\*\s*$method\s*\)\s*\((.*?)\);/) { $ret = $4; $parms = $5; addReturnElem($sig_elem, $ret); if ($parms && ($parms ne "void")) { my ($dump, @parm_arr) = split (/,/, $parms); addParamsElem($sig_elem, @parm_arr); } return $method; } else { die "ERROR: Failed to parse method $method from class definition:\n$class"; } } else { @args = split(/,/, $spec); my $rettype = parseTypeToken ($args[7]); addReturnElem($sig_elem, $rettype); $parmcnt = $args[8]; $parmcnt =~ s/.*(\d+).*/\1/; $parms_elem = $doc->createElement('parameters'); $sig_elem->appendChild($parms_elem); for (my $idx = 0; $idx < $parmcnt; $idx++) { my $argtype = parseTypeToken ($args[9+$idx]); $parm_elem = $doc->createElement('parameter'); $parms_elem->appendChild($parm_elem); $parm_elem->setAttribute('name', "p$idx"); $parm_elem->setAttribute('type', $argtype); } return ""; } } sub addImplementsElem { my ($spec, $node) = @_; $spec =~ s/\n\s*//g; if ($spec =~ /,\s*(\w+)_TYPE_(\w+),/) { $impl_elem = $doc->createElement('interface'); $name = StudlyCaps (lc ("$1_$2")); $impl_elem->setAttribute ("cname", "$name"); $node->appendChild($impl_elem); } } sub parseInitFunc { my ($obj_el, $initfunc, $classdef) = @_; my @init_lines = split (/\n/, $initfunc); my @signal_vms = (); my $linenum = 0; my $pspec = ""; my $pspec_use = 0; while ($linenum < @init_lines) { my $line = $init_lines[$linenum]; if ($line =~ /#define/) { # FIXME: This ignores the bool helper macro thingie. } elsif ($line =~ /g_object_(class|interface)_install_prop/) { my $prop = $line; $pspec_use = 1; while ($prop !~ /\)\s*;/) { $prop .= $init_lines[++$linenum]; if ($init_lines[$linenum] =~ /g_param_spec_/) { $pspec_use = 0; } } if ($pspec_use) { # addPropElem2 ($prop.$pspec, $obj_el, 0); } else { addPropElem ($prop, $obj_el, 0); $propcnt++; } } elsif ($line =~ /gtk_container_class_install_child_property/) { my $prop = $line; do { $prop .= $init_lines[++$linenum]; } until ($init_lines[$linenum] =~ /\)\s*;/); addPropElem ($prop, $obj_el, 1); $childpropcnt++; } elsif ($line =~ /\bg.*_signal_new/) { my $sig = $line; do { $sig .= $init_lines[++$linenum]; } until ($init_lines[$linenum] =~ /;/); $signal_vm = addSignalElem ($sig, $classdef, $obj_el); push (@signal_vms, $signal_vm) if $signal_vm; $sigcnt++; } elsif ($line =~ /g_param_spec_/) { $pspec = $line; do { $pspec .= $init_lines[++$linenum]; } until ($init_lines[$linenum] =~ /\)\s*;/); addPropElem2 ($pspec, $obj_el, 0); $propcnt++; } $linenum++; } return @signal_vms; } sub parseTypeFuncMacro { my ($obj_el, $typefunc) = @_; $impls_node = undef; while ($typefunc =~ /G_IMPLEMENT_INTERFACE\s*\(\s*(\w+)/) { $iface = $1; if (not $impls_node) { $impls_node = $doc->createElement ("implements"); $obj_el->appendChild ($impls_node); } addImplementsElem ($prop, $impl_node); if ($iface =~ /(\w+)_TYPE_(\w+)/) { $impl_elem = $doc->createElement('interface'); $name = StudlyCaps (lc ("$1_$2")); $impl_elem->setAttribute ("cname", "$name"); $impls_node->appendChild($impl_elem); } $typefunc =~ s/G_IMPLEMENT_INTERFACE\s*\(.*?\)//; } } sub parseTypeFunc { my ($obj_el, $typefunc) = @_; my @type_lines = split (/\n/, $typefunc); my $linenum = 0; $impl_node = undef; while ($linenum < @type_lines) { my $line = $type_lines[$linenum]; if ($line =~ /#define/) { # FIXME: This ignores the bool helper macro thingie. } elsif ($line =~ /g_type_add_interface_static/) { my $prop = $line; do { $prop .= $type_lines[++$linenum]; } until ($type_lines[$linenum] =~ /;/); if (not $impl_node) { $impl_node = $doc->createElement ("implements"); $obj_el->appendChild ($impl_node); } addImplementsElem ($prop, $impl_node); } $linenum++; } } ############################################################## # Converts a dash or underscore separated name to StudlyCaps. ############################################################## sub StudlyCaps { my ($symb) = @_; $symb =~ s/^([a-z])/\u\1/; $symb =~ s/^(\d)/\1_/; $symb =~ s/[-_]([a-z])/\u\1/g; $symb =~ s/[-_](\d)/\1/g; $symb =~ s/^2/Two/; $symb =~ s/^3/Three/; return $symb; }