#!/usr/pkg/bin/perl -w
#
# Copyright 2000 Patrik Stridvall
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#

use strict;

my $name0=$0;
$name0 =~ s%^.*/%%;

my $invert = 0;
my $pattern;
my @files = ();
my $usage;

while(defined($_ = shift)) {
    if (/^-v$/) {
        $invert = 1;
    } elsif (/^--?(\?|h|help)$/) {
        $usage=0;
    } elsif (/^-/) {
        print STDERR "$name0:error: unknown option '$_'\n";
        $usage=2;
        last;
    } elsif(!defined($pattern)) {
        $pattern = $_;
    } else {
        push @files, $_;
    }
}
if (defined $usage)
{
    print "Usage: $name0 [--help] [-v] pattern files...\n";
    print "where:\n";
    print "--help    Prints this help message\n";
    print "-v        Return functions that do not match pattern\n";
    print "pattern   A regular expression for the function name\n";
    print "files...  A list of files to search the function in\n";
    exit $usage;
}

foreach my $file (@files) {
    open(IN, "< $file") || die "Error: Can't open $file: $!\n";

    my $level = 0;
    my $extern_c = 0;

    my $again = 0;
    my $lookahead = 0;
    while($again || defined(my $line = <IN>)) {
	if(!$again) {
	    chomp $line;
	    if($lookahead) {
		$lookahead = 0;
		$_ .= "\n" . $line;
	    } else {
		$_ = $line;
	    }
	} else {
	    $again = 0;
	}

	# remove C comments
	if(s/^(|.*?[^\/])(\/\*.*?\*\/)(.*)$/$1 $3/s) {
	    $again = 1;
	    next;
	} elsif(/^(.*?)\/\*/s) {
	    $lookahead = 1;
	    next;
	}

	# remove C++ comments
	while(s/^(.*?)\/\/.*?$/$1\n/s) { $again = 1; }
	if($again) { next; }

	# remove empty rows
	if(/^\s*$/) { next; }

	# remove preprocessor directives
	if(s/^\s*\#/\#/m) {
	    if(/^\#[.\n\r]*?\\$/m) {
		$lookahead = 1;
		next;
	    } elsif(s/^\#\s*(.*?)(\s+(.*?))?\s*$//m) {
		next;
	    }
	}

	# Remove extern "C"
	if(s/^\s*extern[\s\n]+"C"[\s\n]+\{//m) {
	    $extern_c = 1;
	    $again = 1;
	    next;
	} elsif(m/^\s*extern[\s\n]+"C"/m) {
	    $lookahead = 1;
	    next;
	}

	if($level > 0)
	{
	    my $line = "";
	    while(/^[^\{\}]/) {
		s/^([^\{\}\'\"]*)//s;
		$line .= $1;
	        if(s/^\'//) {
		    $line .= "\'";
		    while(/^./ && !s/^\'//) {
			s/^([^\'\\]*)//s;
			$line .= $1;
			if(s/^\\//) {
			    $line .= "\\";
			    if(s/^(.)//s) {
				$line .= $1;
				if($1 eq "0") {
				    s/^(\d{0,3})//s;
				    $line .= $1;
				}
			    }
			}
		    }
		    $line .= "\'";
		} elsif(s/^\"//) {
		    $line .= "\"";
		    while(/^./ && !s/^\"//) {
			s/^([^\"\\]*)//s;
			$line .= $1;
			if(s/^\\//) {
			    $line .= "\\";
			    if(s/^(.)//s) {
				$line .= $1;
				if($1 eq "0") {
				    s/^(\d{0,3})//s;
				    $line .= $1;
				}
			    }
			}
		    }
		    $line .= "\"";
		}
	    }

	    if(s/^\{//) {
		$_ = $'; $again = 1;
		$line .= "{";
		$level++;
	    } elsif(s/^\}//) {
		$_ = $'; $again = 1;
		$line .= "}" if $level > 1;
		$level--;
		if($level == -1 && $extern_c) {
		    $extern_c = 0;
		    $level = 0;
		}
	    }

	    next;
	} elsif(/^class[^\}]*{/) {
	    $_ = $'; $again = 1;
	    $level++;
	    next;
	} elsif(/^class[^\}]*$/) {
	    $lookahead = 1;
	    next;
	} elsif(/^typedef[^\}]*;/) {
	    next;
        } elsif(/(extern\s+|static\s+)?
		(?:__inline__\s+|__inline\s+|inline\s+)?
		((struct\s+|union\s+|enum\s+)?(?:\w+(?:\:\:(?:\s*operator\s*[^\)\s]+)?)?)+((\s*(?:\*|\&))+\s*|\s+))
		((__cdecl|__stdcall|CDECL|VFWAPIV|VFWAPI|WINAPIV|WINAPI|CALLBACK)\s+)?
		((?:\w+(?:\:\:)?)+(\(\w+\))?)\s*\(([^\)]*)\)\s*
		(?:\w+(?:\s*\([^\)]*\))?\s*)*\s*
                (\{|\;)/sx)
	{
	    $_ = $'; $again = 1;
	    if($11 eq "{")  {
		$level++;
	    }

	    my $linkage = $1;
            my $return_type = $2;
            my $calling_convention = $7;
            my $name = $8;
            my $arguments = $10;

            if(!defined($linkage)) {
                $linkage = "";
            }

            if(!defined($calling_convention)) {
                $calling_convention = "";
            }

            $linkage =~ s/\s*$//;

            $return_type =~ s/\s*$//;
            $return_type =~ s/\s*\*\s*/*/g;
            $return_type =~ s/(\*+)/ $1/g;

            $arguments =~ y/\t\n/  /;
            $arguments =~ s/^\s*(.*?)\s*$/$1/;
            if($arguments eq "") { $arguments = "void" }

            my @argument_types;
            my @argument_names;
            my @arguments = split(/,/, $arguments);
            foreach my $n (0..$#arguments) {
                my $argument_type = "";
                my $argument_name = "";
                my $argument = $arguments[$n];
                $argument =~ s/^\s*(.*?)\s*$/$1/;
                # print "  " . ($n + 1) . ": '$argument'\n";
                $argument =~ s/^(IN OUT(?=\s)|IN(?=\s)|OUT(?=\s)|\s*)\s*//;
                $argument =~ s/^(const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s)|\s*)\s*//;
                if($argument =~ /^\.\.\.$/) {
                    $argument_type = "...";
                    $argument_name = "...";
                } elsif($argument =~ /^
                        ((?:struct\s+|union\s+|enum\s+|(?:signed\s+|unsigned\s+)
                          (?:short\s+(?=int)|long\s+(?=int))?)?(?:\w+(?:\:\:)?)+)\s*
                        ((?:const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s))?\s*(?:\*\s*?)*)\s*
                        (?:const(?=\s)|CONST(?=\s)|__const(?=\s)|__restrict(?=\s))?\s*
			(\w*)\s*
			(?:\[\]|\s+OPTIONAL)?/x)
                {
                    $argument_type = "$1";
                    if($2 ne "") {
                        $argument_type .= " $2";
                    }
                    $argument_name = $3;

                    $argument_type =~ s/\s*const\s*/ /;
                    $argument_type =~ s/^\s*(.*?)\s*$/$1/;

                    $argument_name =~ s/^\s*(.*?)\s*$/$1/;
                } else {
                    die "$file: $.: syntax error: '$argument'\n";
                }
                $argument_types[$n] = $argument_type;
                $argument_names[$n] = $argument_name;
                # print "  " . ($n + 1) . ": '$argument_type': '$argument_name'\n";
            }
            if($#argument_types == 0 && $argument_types[0] =~ /^void$/i) {
                $#argument_types = -1;
                $#argument_names = -1;
            }

	    @arguments = ();
            foreach my $n (0..$#argument_types) {
		if($argument_names[$n] && $argument_names[$n] ne "...") {
		    if($argument_types[$n] !~ /\*$/) {
			$arguments[$n] = $argument_types[$n] . " " . $argument_names[$n];
		    } else {
			$arguments[$n] = $argument_types[$n] . $argument_names[$n];
		    }
		} else {
		    $arguments[$n] = $argument_types[$n];
		}
	    }

	    $arguments = join(", ", @arguments);
	    if(!$arguments) { $arguments = "void"; }

	    if((!$invert && $name =~ /$pattern/) || ($invert && $name !~ /$pattern/)) {
		if($calling_convention) {
		    print "$return_type $calling_convention $name($arguments)\n";
		} else {
		    if($return_type =~ /\*$/) {
			print "$return_type$name($arguments)\n";
		    } else {
			print "$return_type $name($arguments)\n";
		    }
		}
	    }
        } elsif(/\'(?:[^\\\']*|\\.)*\'/s) {
            $_ = $'; $again = 1;
        } elsif(/\"(?:[^\\\"]*|\\.)*\"/s) {
            $_ = $'; $again = 1;
        } elsif(/;/s) {
            $_ = $'; $again = 1;
        } elsif(/extern\s+"C"\s+{/s) {
            $_ = $'; $again = 1;
        } elsif(/\{/s) {
            $_ = $'; $again = 1;
            $level++;
        } else {
            $lookahead = 1;
        }
    }
    close(IN);
}
