#!/usr/bin/perl -w # # See the file $PREFIX/lib/matwrap/matwrap.pod for # extensive documentation. # # Copyright (c) 1997 Gary R. Holt. This is distributed under the terms of the # perl artistic license (http://language.perl.com/misc/Artistic.html). # require 5.002; # # Some stuff needed by the automatic install: # use Cwd; $PREFIX = &Cwd::cwd(); # This is the location of our source tree. unshift(@INC, "$PREFIX"."/matwrap"); # Add our library directory. # # Parse the command line arguments: # @cpp_ignore_dirs = ('/usr/include/', '/usr/local/include/'); # Directory hierarchies to ignore when the # -cpp switch is active. These should be # followed by a trailing slash to avoid # matching something like /usr/include_others. %cpp_ignore_files = (); # Files we're specifically supposed to ignore. $include_str = ''; # A list of files to #include. ($progname = $0) =~ s@.*/@@; # Format program name for error messages. $dim_type = 'int'; # The C type to use for array indices and # dimensions. @files = (); # No files to parse. $debug = 0; # Not in debug mode. $cpp_flag = 0; # Default to not running the C preprocessor. #@comments = (); # Where we store the comments. @strings = (); # Where we store quoted strings, etc. @brace_strs = (); # Where we store strings surrounded by braces # which we removed. %variables = (); # Type of each global non-static variable. %functions = (); # The prototype, etc., of each global # function, indexed by function name. Elements # are associative arrays (see the .pod file # for more details). %classes = (); # Elements are indexed by class names and are # associative arrays of members of each class. # If the members are themselves an associative # array, then they are functions (see the .pod # file for details on format); otherwise # they are fields and the type is given by the # value. %derived_classes = (); # For each class, contains an array of names # of classes that derive from this class. @basic_type_keywords = (qw(short long signed unsigned float double int char complex Complex const)); @basic_types{@basic_type_keywords} = @basic_type_keywords; %typedef = # Contains definitions of types. (%basic_types, # Fill out the types with the known types. # Unknown words in the type field will be # assumed to be argument names (for function # arguments) and will be ignored. We also # ignore some keywords like 'inline' and # 'virtual' and 'extern'. 'void' => 'void', # Other type words relevant for functions: 'static' => 'static', 'const' => 'const', # Keep 'const' around. 'inline' => '' # Delete occurences of 'inline'. ); %novectorize_types = (); # Non-zero for all types which we don't want # to vectorize at all, even if we could. # # Search the argument list for the -language option: # for (0..(@ARGV-2)) { if ($ARGV[$_] eq '-language') { # Is the language specified? $language = $ARGV[$_+1]; # Get the language name. splice(@ARGV, $_, 2); # Remove the elements. require "wrap_$language.pl"; # Load the language file. &{"${language}::parse_argv"}(\@ARGV); # Let it see the options first. last; # Stop scannining @ARGV for -language. } } defined($language) or die("$progname: must specify output language\n"); while ($_ = shift(@ARGV)) { # Get the next argument: if (/^-cpp$/) { # Run the C preprocessor? $cpp_flag = 1; # Remember to run the C preprocessor. last; # Remember the arguments. } elsif (/^-cpp_ignore$/) { # More things for the C preprocessor to ignore? $_ = shift(@ARGV); # Get the next argument. if (-d $_) { # Is this a directory? push(@cpp_ignore_dirs, "$_/"); # Remember the directory. } else { $cpp_ignore_files{$_} = 1; # Remember to ignore this file. } } elsif (/^-debug$/) { # Dump out definitions? $debug = 1; } elsif (/^-language$/) { # A repeated -language qualifier? die("$progname: two -languages specified\n"); } elsif (/^-wrap_?only$/) { # Specify explicitly what to wrap: $wraponly_classes = {}; # Indicate that we're only supposed to wrap $wraponly_globals = {}; # selected things. while ($_ = shift(@ARGV)) { # Get the next argument. if ($_ eq 'class') { # Wrap the whole class? $wraponly_classes->{shift(@ARGV)} = 1; # Wrap this class. } else { $wraponly_globals->{$_} = 1; # Wrap this function/variable. } } } elsif (/^-o$/) { # Specify output file? $outfile = shift(@ARGV); # Next parameter is output file. } # # Unrecognized switch: # elsif (/^-$/) { # Is it an option? die("$progname: illegal option $_\n"); } else { # Not an option, must be a file name. push(@files, $_); } } unless (defined($outfile)) { # Was an output file name explicitly specified? if ($cpp_flag) { die("$progname: -o must be explicitly specified before the -cpp flag\n"); } else { $outfile = &{"${language}::get_outfile"}(\@files); # Get the file name. print STDERR "$progname: output file is $outfile\n"; } } if (@files) { # Any files explicitly named? local ($/) = undef; # Slurp in files all at once. # This makes parsing much simpler. foreach $file (@files) { # Parse our list of files. if ($file =~ /\.[hH]{1,2}$/) { $include_str .= "#include \"$file\"\n"; # We'll probably need to include # this file in the generated code. } else { warn("$progname: $file is not an include file\n"); } open(INFILE, $file) || die("$progname: can't open $file--$!\n"); $_ = ; # Read the whole file. close(INFILE); # Done with the file. eval { &parse_str($_); }; # Parse this string. $@ and warn("In file $file:\n$@"); } } if ($cpp_flag) { # Run the C preprocessor? grep($_ eq '-E', @ARGV) or # If -E isn't already present somewhere, splice(@ARGV, 1, 0, '-E'); # add -E after the first word. # # Start up the child process. Don't use the simple OPEN() because that's # likely to get fouled up if any of the arguments had to be quoted because # they contained special characters. Instead, we want to use exec() # with a list argument. # my $pid = open(CPP, "-|"); # Fork and open a process. if (!defined($pid)) { # Did the fork succede? die("$progname: can't fork C preprocessor--$!\n"); } if ($pid == 0) { # We're the child? exec @ARGV; # Execute the preprocessor. die("Exec of C preprocessor failed--$!\n"); } # # We're the parent--read from the C preprocessor and parse the stuff as we # see it. # my $accum_str = ''; my $fname = ''; my $remember_defs_in_file = 1; # True if we're reading input from a file # for which we want to remember function # definitions. while (defined($_ = )) { # Read another line. if (/^\#(?:line)?\s+(?:\d+)\s+\"(\S+?)\"/) { # Switching to a different include file? $1 eq $fname and next; # Skip if from the same file. if ($remember_defs_in_file) { # Supposed to remember definitions we read? parse_str($accum_str); # Parse them. } else { parse_for_typedefs($accum_str); # Just look for typedefs and other # simple things, ignoring function defs. } # # Try to figure out whether we need to include this file in the wrapper # code or not. Our rule is that if it is a .h file which was included # from the top level .c or .cxx file, then we want to include it in the # wrapper file. Otherwise, it was included by some other .h file and we # don't need to include it explicitly. This may lead to our including # more than we really need to but it's likely to make code that will work. # my $last_fname = $fname; # Remember what file we used to be in. $fname = $1; # Remember the new file name. if ($fname =~ /\.[hH]{1,2}$/ && # Is this a .h file? $last_fname !~ /\.[hH]{1,2}$/) { # Last file was a .c or .cxx file? # Then it must have been included at the # top level. Add it to our list. my $incstr = $fname; # Assume we include the file as is. if ($incstr =~ s@^/usr/include/@@ || # Is it a system include file? $incstr =~ s@^/usr/local/include/@@ || $incstr =~ s@.*/gcc-lib/.*/include/@@) { # Is it a system include # file that was fixed by gcc? $include_str .= "#include <$incstr>\n"; # Use a different syntax. } else { $include_str .= "#include \"$incstr\"\n"; # Include it normally. } } if ($cpp_ignore_files{$fname} || # An explicitly disqualified file? grep(substr($fname, 0, length($_)) eq $_, @cpp_ignore_dirs) || # Or does it begin with the list of forbidden # directories? $fname =~ m@/gcc-lib/@) { # Somewhere in gcc fixed includes? $remember_defs_in_file = 0; # We're not really interested in this file. } else { $remember_defs_in_file = 1; # This is a file we are actually # interested in. } $accum_str = ''; # Start accumulating from scratch now. } else { $accum_str .= $_; } } close(CPP); # Done with the C preprocessor. ($? >> 8) and die("$progname: C preprocessor exited with error status\n"); $remember_defs_in_file and parse_str($accum_str); # Parse the remaining stuff. } if ($debug) { # # DEBUG: dump out the definitions. # print "Typedefs:\n"; foreach (sort keys %typedef) { print " $_ => $typedef{$_}\n"; } print "\nVariables:\n"; foreach (sort keys %variables) { print " $variables{$_} $_\n"; # Print the type and name. } print "\nFunctions:\n"; foreach (sort keys %functions) { dump_function(" ", $functions{$_}); } print "\nClasses:\n"; foreach $cls (sort keys %classes) { print " $cls:\n"; my $members = $classes{$cls}; foreach (sort keys %$members) { if (ref($members->{$_}) eq '') { # Is it a member field? print " $members->{$_} $_;\n"; # Print it as such. } else { # It's a member function: dump_function(" ", $members->{$_}); } } } } # # Now write the output file: # $fh = &{"${language}::initialize"}($outfile, \@files, \@ARGV, $include_str); # Initialize the output file. select($fh); # Make the output file be the default file # handle. &output_vectorizing_subs; # Output a couple of special C functions for # vectorizing. if (%wraponly_classes) { # Only wrapping a few classes? output_class_conversion_func(keys %wraponly_classes); # Only allow inheritance relationships between # them. print &{"${language}::pointer_conversion_functions"}; # We'll need the functions to # convert pointers. } elsif (keys %classes) { # Do we know about any classes? output_class_conversion_func(keys %classes); # Handle all inheritance # relationships among all known classes. print &{"${language}::pointer_conversion_functions"}; # We'll need the functions to # convert pointers. } foreach (sort keys %variables) { # First wrap all the global variables. wrap_variable($variables{$_}, $_, "") if (!%wraponly_globals || $wraponly_globals{$_}); } foreach (sort keys %functions) { # Now wrap all the global functions. wrap_function($functions{$_}) if (!%wraponly_globals || $wraponly_globals{$_}); } # # Now wrap all the classes: # foreach $cls (sort keys %classes) { if (!defined($wraponly_classes) || # Wrap all classes? defined($wraponly_classes->{$cls})) { # Supposed to wrap this class? my $members = $classes{$cls}; # Access member table. foreach (sort keys %$members) { # Look at each member: if (ref($members->{$_}) eq '') { # Is it a member field? wrap_variable($members->{$_}, $_, $cls); # Wrap as a variable. } else { # It's a member function: wrap_function($members->{$_}); # Wrap it appropriately. } } } } &{"${language}::finish"}(); # We're done! ############################################################################### # # Code to parse the input files: # # # Just extract the typedefs from a string. Arguments: # 1) The string. # # Side effects: # New types may be added to the %typedefs array. # # Function and variable definitions are ignored. # sub parse_for_typedefs { local ($_) = @_; # Access the argument. s{\\[\0-\377]}{}g; # Delete all backslash escapes. s{/\* [\0-\377]*? \*/ | # Old C-style comment. //.* | # New C++ style comments. \" .*? \" | # Double quoted string. \' .*? \' # Single quoted string. }{}xg; # Delete comments and quoted strings. s/\bextern\s*\"[^\"]*\"//g; # Remove all the "extern "C"" declarations. # Note that this will leave an extra trailing # brace. I don't care. 1 while s{\{[^\{\}]*\}}{}g; # Now remove expressions in braces. s{\b(?:struct|class)\s+(\w+)}{ # Pick out all the "class xyz", "struct xyz", # and "typedef struct {} xyz" declarations. $typedef{$1} ||= $1; # Call these a fundamental type from now on, # unless we already knew a different name for # it. ""; # Delete the typedef. }eg; s{\btypedef\s+(\w[\w<>\*\[\]\&\s]*?)\s*\b(\w+)\s*;}{ # Find a typedef. $typedef{$2} = canonicalize_type($1, 1); # Remember it. ""; }eg; } # # Handle all the definitions contained in a string. Arguments: # 1) The string. # # Side effects: adds entries to the following arrays: # $variables{name} Contains type of variable. # $functions{name} Points to an array containing (as element 0) # the return type of the function, and # (as elements 1-n) type types of its arguments. # $classes{name} Points to an associative array containing # the member functions and their types, encoded # like the elements of the $function array. # sub parse_str { local ($_) = @_; # Access the argument. # # Replace all things that could confuse a simple-minded parser by a tag. # We want to make sure that our brace and parenthesis matching is accurate, # so we remove all comments and quoted strings. This is a little tricky # to do accurately because there could be quotes inside of comments or # (partial) comments inside of quoted strings. We also should handle \" and # \' properly. The algorithm for doing this is: # 1) Remove all backslash escapes. # 2) Remove all comments and quoted strings at once using an ugly and # slow regular expression (which seems to work). # Comments and quoted strings are removed and are replaced by a tag value which # is just some binary characters surrounding a number. The number is the # index into the array where we stored the string. # s@//(%\w+)@\n$1@g; # Convert //%input into %input. This allows # declarations to be put into C files. s{\\[\0-\377]}{push(@strings, $&); # First remove all backslash escapes. "\01$#strings\02"; }eg; # Leave a tag. s{/\* [\0-\377]*? \*/ | # Old C-style comment. //.* (?:\n[ \t]*//.*)* | # Several lines of new C++ style comments. \" .*? \" | # Double quoted string. \' .*? \' # Single quoted string. }{ if (substr($&, 0, 1) eq '/') { # Was this a comment? # push(@comments, $&); # Save it. # "\03$#comments\04"; # Leave the tag. ""; # Strip out the comments. } else { # No, it must have been a string. push(@strings, $&); # Save it in a different place. "\01$#strings\02"; # Leave a different tag. } }xeg; s{%novectorize_type\s+(.*)}{ # Any types we're not supposed to vectorize? my @types = map(canonicalize_type($_), split(',', $1)); # Get the types. @novectorize_types{@types} = (1) x (@types); # Mark these types as used. ''; # Replace the %novectorize_type declaration # with nothing. }eg; # # Now pull out all expressions in braces. This has to be done in several # scans so we handle nested braces. Because we have protected comments # and quoted strings, there shouldn't be any problem with braces inside # quotes. # s/\bextern\s+\01\d+\02\s*\{?//g; # Remove all the "extern "C"" declarations. # (We don't have to worry about these; the # C++ compiler will handle them. Note that # this may leave an extra brace. I don't care. 1 while s{\{[^\{\}]*\}}{ # Now remove expressions in braces. push(@brace_strs, $&); # Save the expression.n "\05$#brace_strs\06"; # Replace it by a tag. }eg; # This has to be done in a loop because we # remove the innermost braces first, followed # by the next, etc. s{ template \s+ <.*?> .*?[;\06] }{}xg; # Strip out template definitions. s{__attribute__.*?([;\06])}{$1}g; # Strip out attribute declarations. # # At this point we have parsed the file so that all comments and expressions # of braces have been removed. Now go through sequentially and try to parse # all #defines, typedefs, etc. # # Currently we only handle simple typedefs, where the type name is the last # expression before the semicolon. Maybe later we'll handle function and # array typedefs. # s/^\s*\#.*\n//mg; # Remove all preprocessor directives. s{\btypedef\s+(\w[\w<>\s\*\&]+?)(\w+)\s*;}{ $typedef{$2} = canonicalize_type($1); # Store the type name. ""; # Remove the typedef from the file. }eg; s{\b(?:class|struct|typedef\s+struct\s+\05\d+\06)\s+(\w+)\s*;}{ $typedef{$1} or # Do we already know of this type? $typedef{$1} = $1; # Remember that we know this type. ""; # Delete the definition. }eg; # Strip out forward class definitions. # # Look for variable definitions: # 1 while s{ (^|[;\06]) # Match beginning of statement (end of last). \s* # Whitespace. (\w[\w<>\s\*\&]+?) # The type of the variable. ([\w:]+(?:\s*,\s*\w+)*)\s* # The name of the variable(s). (?:=[^;]+)? # An optional assignment expression. ; # The final semicolon. }{ my $delim = $1; my $orig_type = $2; # Remember the original type. my @vars = split(/\s*,\s*/, $3); # Get a list of variables. if (!defined($wraponly_globals) || # Are we supposed to wrap any of these grep($wraponly_globals->{$_}, @vars)) { # variables? my $type = canonicalize_type($orig_type); # Get the type. unless ($type =~ /^static /) { # Skip static variables. foreach (@vars) { # Look at each variable: next if /:/; # Skip static member data, since these will # be handled when we see the class definition. $variables{$_} = $type; # Remember the variable. } } } $delim; # Remove the whole definition. }xeg; # # Look for function declarations: # 1 while s{ (?:^|\G|[;\}\06]) # Match beginning of statement (end of last). \s* # Whitespace between statements. (\w[\w<>\s\*\&]*?)? # The return type of the function. (\w+)\s* # The name of the function. # Note that this does not match member # functions, whose prototypes are given in # the class declaration. \(([:<>\w\s\*\&,]*)\)\s* # The function arguments. (?:; | # The trailing semicolon, for a prototype. \05\d+\06)\s* # The body of the function. ((?:\s*%.*\n)+)? # Any additional modifiers. }{ my ($fdef, $fname); if (!defined($wraponly_globals) || # Wrap all functions? defined($wraponly_globals->{$2})) { # This is a function we want? eval { ($fdef, $fname) = parse_function($1, "", $2, $3, split(/\n\s*/, $4 || "")); # Parse the function definition. }; if ($@) { # Was there an error? print STDERR "$progname: error parsing definition of $1 $2:\n$@\n"; } else { defined($fdef) and $functions{$fname} = $fdef; # If it wasn't a static function, remember it. } } ''; # Just remove the whole statement. }xeg; # # Look for class or structure definitions: # 1 while s{ (?:^|\G|[;\06]) # End of last statement. \s* # Whitespace separating statements. (class|struct) \s+ (\w+) \s* # The name of the class. (:[\w\s,]+)? # The inheritance list. \05(\d+)\06 \s* # The body of the class definition. ; # The trailing semicolon. }{ parse_class($1, $2, $3, $brace_strs[$4]); # Parse the class definition. # We parse the class definition even if # it was not specified in a -wraponly # declaration, because some other listed class # may inherit from it. ''; # Just remove the whole statement. }xeg; # # Strip out member function definitions, so we don't give bogus error messages: # s{ (?:^|\G|[;\06]) # End of last statement. \s* # Whitespace separating statements. (?:[\w\s\*\&]+?) # The return type of the function. (?:\w+::\~?\w+)\s* # The name of the function. \((?:[:\w\s\*\&,]*)\)\s* # The function arguments. (?:const\s*)? # An optional const modifier. (?:; | # The trailing semicolon, for a prototype. \05\d+\06) # The body of the function. }{ }xg; if (/\w/) { # Some non-punctuation that we didn't recognize? s/(?:[ \t]*\n)+/\n/g; # Collapse multiple newlines into 1. s/\05\d+\06/{ ... }/g; # Put braces back in understandable form. 1 while s/\01(\d+)\02/$strings[$1]/g; # Put quoted strings back too. die "Warning: unrecognized text:\n$_\n"; } } # # Parse a function prototype. Arguments: # 1) The return type of the function. # 2) The class of the function. # 3) The name of the function. # 4) The argument list for the function (not including the THIS argument for # member functions). # 5-n) Additional declarations (%input, etc.), if any. # # Returns a reference to the %function_def array appropriate to this function. # Returns undef if it was a static function. # # Also returns the name of the function, which will be different from the name # passed if there was a %name directive. # sub parse_function { my ($ftype, $class, $fname, $arglist, @addl_decls) = @_; # Access the arguments. $ftype = canonicalize_type($ftype); # Get the type of the function. my $static_flag = ($ftype =~ s/\bstatic\s*//); # Is the function static? # (This also removes "static" from the type.) $static_flag and $class eq '' and return undef; # Don't try to make an entry for static # functions since we can't access them # anyway. # # Process the argument list. First, we pretty up the list of printable # arguments, and then we convert that to our internal types. # $arglist =~ s/^\s*void\s*$//; # Change argument of "void" to "". if ($arglist =~ /[\(\)]/) { # Does it have stuff we don't understand? warn("$progname: function pointers and other complex types not accepted in definition of function $fname, arglist $arglist\n"); return undef; # Skip this function. } my @args = split(/,/, $arglist); # Access the argument list. $class and !$static_flag and # If this is a non-static member function, unshift(@args, "$class *THIS"); # pass the class pointer as the first # argument. $ftype ne 'void' and # Pretend the return value is the first unshift(@args, "$ftype retval"); # argument for the moment. We'll take # it off later. my @canon_args = map { canonicalize_type($_) } @args; # Get the canonical types. # # Try to infer as much of the rest of the definition as possible. We can # infer everything if there are no pointer or reference types. # # First give names to all arguments that don't have any: # my $script_name; # The name of the function in the scripting # language, if different. my $vectorize; # Whether or not to vectorize this function. my @argnames = map { # Get names for each argument to C function. (($args[$_] =~ /(\w+)\s*(?:=|$)/ && # Take last word in type as !exists($typedef{$1})) ? # arg name if it's not a type. $1 : # Use the name if it was there. "_arg$_"); # Generate a name for the argument. } 0..(@args-1); # Get the specified names for each argument. my %args; # This array will become the "args" field of # the %function_def array. # # Process the argument declarations: # my $argidx; foreach $argidx (0 .. (@argnames-1)) { my $argname = $argnames[$argidx]; # Access the argument name. my $argtype = $canon_args[$argidx]; # Access its type. my $decl = ($args{$argname} = {}); # Create a declaration for this arg. $decl->{vectorize} = !$novectorize_types{$argtype}; # Vectorize unless this type is not supposed to # be vectorized. (We may turn off the # vectorize flag for several reasons below.) $decl->{c_var_name} = "_arg_$argname"; # Generate a unique name. # Default to passing by value. $decl->{type} = $argtype; # Remember the type. $argtype =~ s/\bconst\b\s*//g; # Strip out const to avoid multiplicities of # types. $argtype =~ s/ ?\&$//; # Strip off passsing by C++ reference. # # If there's an extra '*' on the end of a type we recognize, we assume that # we pass it by reference and put a & in front of the variable. # if ($argtype =~ /^(.*?)\s*\*$/ && $argtype ne 'char *' && # Is this a pointer? is_basic_type($1)) { # And it's not a structure? $argtype = $1; # Strip off the trailing *. $decl->{pass_by_pointer_reference} = 1; # Remember to put & in front of # call. } else { $decl->{pass_by_pointer_reference} = 0; # Don't put & in front of call. } $decl->{basic_type} = $argtype; # Store the modified type. } $ftype ne 'void' and $args{'retval'}{source} = 'output'; # "retval" is always an output var. # # Look at the additional declarations and convert things like # %input x(a,b), y(a,b) # into two separate declarations: # %input x(a,b) # %input y(a,b) # @paren_expr = (); # No parenthesized subexpressions known yet. # Note that this is a global variable, because # it's used in parse_dimension_decl. my @decl_copy; foreach (@addl_decls) { 1 while s{(\([^()]*\))}{ # Get rid of parenthesized sub-expressions # since they can cause problems. push(@paren_expr, $1); # Save the parenthesized expression. "\01$#paren_expr\02"; }eg; # Replace it with a tag. # Convert "%input x(a), y" into two # separate declaraions, "%input x(a)" and # "%input y". push(@decl_copy, "%$1 $2") while (s/^\s*\%\s*(input|modify|output)\s+(\w+(?:\s*\01\d+\02)?)\s*,\s*(.*)/%$1 $3/); push(@decl_copy, $_); # Save what's left. } # # Now parse all of the % declarations: # foreach (@decl_copy) { if (/^\s*%\s*(input|modify|output)\s+(\w+)(?:\s*\01(\d+)\02)?\s*$/) { # Input argument? my $arg = $args{$2}; # Point to the argument description. defined($arg) || die("In definition of ${class}::$fname:\n Illegal argument name $2\n"); $arg->{source} and die("In definition of ${class}::$fname:\n Illegal reuse of argument $2\n"); $arg->{source} = $1; # Remember the variable type. if (defined($3)) { # Is this a vector? $arg->{dimension} = parse_dimension_decl($paren_expr[$3], \%args); $arg->{basic_type} =~ s/\s*\*$// # If this was declared as a pointer, change # the basic type by taking off a '*'. Thus # char * goes into char, and float ** goes # into float. unless $arg->{pass_by_pointer_reference}; # If we already marked it to pass by reference, # then we already took off the '*'. } elsif ($2 ne 'retval' && # Can't alter the type of retval. $1 ne 'input' && # Is this a modify/output variable and it's substr($args{$2}{basic_type}, -1) eq '*' && # being passed as a # pointer? E.g., char * when passed as modify # output should have a basic type of char. !$arg->{pass_pointer_by_reference}) { # We didn't already strip off the '*'? $arg->{pass_by_pointer_reference} = 1; # Pass a reference. $arg->{basic_type} =~ s/\s*\*//; # Strip off the *. } } elsif (/^\s*%\s*name\s+(\w+)\s*$/) { # Name of function in scripting language? $script_name = $1; # Remember that. } elsif (/^\s*%\s*(no)?vectorize\s*$/) { # Vectorize or not? $vectorize = !defined($1); # Remember the value. } elsif (/^\s*%\s*nowrap\s*$/) { # Don't wrap this function? return undef; # Quit now. } elsif (/^\s*%\s*name\s+(\w+)\s*$/) { # Change the name of the function: $fname = $1; # Remember the new name. } else { 1 while s/\01(\d+)\02/$paren_expr[$1]/; # Put all the parenthesized # sub-expressions back to print it out properly. die("In definition of function ${class}::$fname: unrecognized declaration $_\n"); } } # # Now for each of the input/modify variables whose dimension is given by # a C expression, see if we can find a way to compute the variable in the # expression. If so, we can eliminate the dimension variable from the # argument list. # foreach $argname (@argnames) { my $arg = $args{$argname}; # Get this argument. $arg->{source} ||= 'input'; # Make all unspecified arguments input args. $arg->{dimension} ||= []; # Default to a dimensionless variable. next unless @{$arg->{dimension}}; # Skip if not an array argument. $arg->{pass_by_pointer_reference} = 0; # If it's an array argument, we # want this to be 0. next unless ($arg->{source} eq 'input' || # Skip if not an argument whose $arg->{source} eq 'modify'); # value we are given. my $dimidx = 0; foreach (@{$arg->{dimension}}) { # Look at the expression for each dimension. # # See if we can invert this expression to determine the value of a # dimensional variable. If so, then we can remove the argument from the # argument list. # # We can only invert simple arithmetic expressions, i.e., things in which # only one argument is present, and which are of the form # arg # arg+1 # arg-1 # 2*arg # 2*arg-1 # Expressions may not be substituted for the '1' and '2', though any other # integer may be. # # Other forms we can't handle, so we require that the value be specified. # if (/^_arg_(\w+)$/) { # Just the argument word by itself? $args{$1}{calculate} = "dim($argname, $dimidx)"; $args{$1}{source} = 'dimension'; # Mark as a dimensional variable. } elsif (/^_arg_(\w+)\s*([-+])\s*(\d+)$/) { # First or second form? $args{$1}{calculate} ||= "dim($argname, $dimidx)" . ($2 eq '-' ? '+' : '-') . $3; $args{$1}{source} = 'dimension'; # Mark as a dimensional variable. } elsif (/^(\d+)\s*\*\s*_arg_(\w+)$/) { # Simple multiplication? $args{$2}{calculate} ||= "dim($argname, $dimidx)/$1"; $args{$2}{source} = 'dimension'; # Mark as a dimensional variable. } elsif (/^(\d+)\s*\*\s*_arg_(\w+)\s*([-+])\s*\d+$/) { $args{$2}{calculate} ||= "(dim($argname, $dimidx)" . ($3 eq '-' ? '+' : '-') . "$4)/$1"; $args{$2}{source} = 'dimension'; # Mark as a dimensional variable. } $dimidx++; } } # # Now form the list of input/output/modify arguments in order, removing # dimensional arguments: # my (@inputs, @modifies, @outputs); # Array of argument names that will be # the input/modify/output variables. foreach $argname (@argnames) { next if exists($args{$argname}{calculate}); # Do we know how to calculate # this variable from the others? if ($args{$argname}{source} =~ /^input|dimension$/) { # It will be 'dimension' if this is an argument # that specifies another argument's dimension # but we couldn't actually calculate the # argument because the expression wasn't # invertible, e.g., %input a((b > 0) ? b : -b) # defines b as a dimensional variable but # b cannot be calculated so it must be # explicitly specified. push(@inputs, $argname); } elsif ($args{$argname}{source} eq 'modify') { push(@modifies, $argname); } elsif ($args{$argname}{source} eq 'output') { push(@outputs, $argname); } else { die("internal error, invalid argument source '$args{$argname}{source}'"); } } if ($ftype ne 'void') { # Was there a return type? shift(@argnames); # Remove the return value from the argument shift(@canon_args); # list since it is handled separately. } unless (defined($vectorize)) { # Did we get a %(no)vectorize? if ((@outputs != 0 && @inputs != 0 || @modifies != 0) && # Don't try to vectorize it if there aren't # any output arguments or any input args. grep($_->{source} ne 'output' && $_->{vectorize} != 0, values %args)) { # Don't try to vectorize this function if # none of its arguments can be vectorized. $vectorize = 1; } else { $vectorize = 0; } } if (!$vectorize) { # Not vectorizing this function? foreach $arg (values %args) { $arg->{vectorize} = 0; # Mark each of the arguments as not vectorized. } } # # Now we've generated all the pieces for the %function_def array. Fill in # all of the fields: # ({ name => $fname, class => $class, script_name => $script_name, static => $static_flag, inputs => \@inputs, modifies => \@modifies, outputs => \@outputs, returns => $ftype, args => \%args, argnames => \@argnames, vectorize => $vectorize }, $fname); } # # The following subroutine parses a dimension declaration, e.g., # %output varname(dim1, dim2) # Arguments: # 1) The dimension string (including parentheses). # 2) A reference to an associative array where we store the names of dimension # variables. # # Returns: a reference to a list which will become the "dimension" field # of the "args" subfield of the %function_def array, i.e., # [dim1, dim2] # where dim1 and dim2 are expressions which are the dimensional values. # These expressions may contain the parameter names or other C expressions. # The parameter names are substituted to their C equivalents, and any # arguments which appear in them are declared not vectorized. # # Global variable inputs: @paren_expr contains all parenthesized expressions # that were removed to facilitate parsing. # sub parse_dimension_decl { my ($dimstr, $args) = @_; # Name the arguments. $dimstr =~ s/^\((.*)\)$/$1/; # Strip the parentheses. my @dims = split(/,/, $dimstr || ""); # Split into components. foreach (@dims) { 1 while s/\01(\d+)\02/$paren_expr[$1]/; # Replace parenthesized # expressions; now commas in parentheses can't # hurt us since we've already done the split. s/^\s+//; # Remove leading whitespace. s/\s+$//; # Remove trailing whitespace. # # Find any parameter names in this dimension declaration. # my @expr_tokens = split(/(\W+)/, $_); # Split it on non-words (operators), # but put the operators into the array. my $idx; my $n_params = 0; # The number of parameters that were contained # in this expression. for ($idx = 0; $idx < @expr_tokens; ++$idx) { # Look at each token: my $arg = $args->{$expr_tokens[$idx]}; # See if this word is in the # argument list. next unless defined($arg); # Skip if it's an operator or some other # word. $arg->{vectorize} = 0; # This argument may not be vectorized, since # it determines the dimensions of other args. $arg->{source} = 'dimension'; # Remember this is a dimension variable. $expr_tokens[$idx] = $arg->{c_var_name}; # Replace it in the expression so that we # know how to do the dimension checking. } if (@expr_tokens == 1) { # Only one thing? $_ = $expr_tokens[0]; # Put it back (in case we changed it). } else { $_ = '(' . join('', @expr_tokens) . ')'; # Put the expression in # parentheses. } } return \@dims; } # # The following subroutine parses a class definition. Arguments: # 1) "class" or "struct" (so we know what's private and public). # 2) The name of the class. # 3) The inheritance list (with a leading colon). # 4) The body of the function. # # Fills out the following global variables: # $classes{name} Points to an associative array containing # the member functions and their types. Each # entry is a list where the first element is # the type of the function and the remaining # elements are the types of its arguments. # sub parse_class { my ($class_struct, $classname, $inh_list, $class_def) = @_; # Name the arguments. local ($_); # Don't mess up caller's $_. my %members; # Where we store member function info. if ($typedef{$classname}) { # Is another name already known for this $classname = $typedef{$classname}; # class? Change the name if so. } $classes{$classname} = \%members; # Make a null associative array. $typedef{$classname} = $classname; # Remember that we know of this type. $derived_classes{$classname} = []; # Currently this class is not a base class # for anything yet. # # First parse the inheritance list. Note that since we're parsing classes # in the same order that the C++ compiler sees them, all the preceding # classes should be defined. # if (defined($inh_list)) { # Is there an inheritance list? my @base_classes = split(/\s*,\s*/, substr($inh_list, 1)); # Extract them. # The substr skips the leading colon. foreach (@base_classes) { s/^\s+//; # Strip leading spaces. s/\s+$//; # Strip trailing spaces. s/\s*virtual\s+//; # Remove the virtual keyword. next if /^private\b/ || /^protected\b/; # Not interested in protected members. s/^public\s+// or # public not explicitly specified? $class_struct = 'struct' # public is assumed if a struct. or next; # Skip it--it's private. unless (defined($derived_classes{$_})) { # Do we understand this base class? warn("$progname: warning: in class $classname I don't understand base class $_, skipping its member functions\n"); next; # Skip this class. } push(@{$derived_classes{$_}}, $classname); # Remember that this class is # derived from this base class. } } # # Now we've dealt with the inheritance. Parse this class. First get rid # of all the private and protected members: # $_ = $class_def; # Access the class definition. $class_struct eq 'class' and $_ = "; private: $_"; # Everything in a class is private up until # the first "public:" declaration. Note that # we put a semicolon in so we can anchor # statements. my $private_members = ''; # No private members known yet. 1 while s{\b(?:private|protected):(.*?)\bpublic:}{ $private_members .= $1; # Remember the private members. "public:"; }esg; # Delete everything between any # private: and public:. The loop is necessary # to handle a sequence like # protected: private: public:; the first # iteration will turn it into protected:public: # and the second will eliminate the protected # section. s{\b(?:private|protected):(.*)}{ $private_members .= $1; ""; }es; # Delete everything after the last private: # or protected:. s/\bpublic://g; # Strip out any extra public: declarations. # # Now parse the member functions of the class. At this point we know that the # body of the class begins with ";" or "{". # 1 while s/([\{;\06])\s*typedef\s.*?;/$1/g; # Remove any typedefs. 1 while s/([\{;\06])\s*(class|struct).*?[;\06]/$1/g; # Remove any nested classes. 1 while s/([\{;\06])\s*friend\s.*?;/$1/g; # Remove any friends. 1 while s/([\{;\06])[^;\06]*\boperator\b[^;\06]*[;\06]/$1/g; # Remove any definition of operators. 1 while s{ ([\{;\06]) # Match beginning of statement (end of last). # (Note that we stuck a semicolon at the # beginning so this will work even for the # first definition.) \s* # Whitespace between statements. ([<>\w\s\*\&]*?)? # The return type of the function. (\~?\w+)\s* # The name of the function. # Note that this does not match member # functions, whose prototypes are given in # the class declaration. \(([^\)\(]*)\)\s* # The function arguments. (?:const\s*)? # Optional const qualifier. (?::[^;\05]+)? # Initializers (for constructors). (?:; | # The trailing semicolon, for a prototype. \05\d+\06)\s* # The body of the function. ((?:%.*\n\s*)+)? # Any additional modifiers. }{ my ($funcname, $functype) = ($3, $2); if ($funcname eq $classname) { # Is this a constructor? $funcname = "new"; # Change it to the new function. $functype = "static $classname *THIS"; # Change the return type. } elsif ($funcname eq "~$classname") { # Is this a destructor? $funcname = "delete"; # Change its name $functype = "void"; # and its return type. } my $fdef; eval { ($fdef, $funcname) = parse_function($functype, $classname, $funcname, $4, split(/\n\s*/, $5 || "")); # Parse it. }; if ($@) { # Was there an error? print STDERR "$progname: error parsing definition of $functype ${classname}::$funcname:\n$@\n"; } else { defined($fdef) and $members{$funcname} = $fdef; # Remember definition # unless it was marked nowrap. } $1; # Remove the member function definition. }xeg; # # Parse member fields: # 1 while s{ ([\{;\06]) # Match beginning of statement (end of last). \s* # Whitespace. ([\w\s\*\&]+?) # The type of the variable. (\w+(?:\s*,\s*\w+)*)\s* # The name of the variable(s). (?:=[^;]+)? # An optional assignment expression. ; # The final semicolon. }{ my $delim = $1; my $type = canonicalize_type($2); foreach (split(/\s*,\s*/, $3)) { # Look at each variable. $members{$_} = $type; # Remember this type. } $delim; # Remove the whole definition. }xeg; /\w/ and print STDERR "Warning: unrecognized text in definition of class $classname:\n$_\n"; # # Add a new and a delete to this class if there isn't one, because that's the # only way to create and destroy members of the class: # unless ($members{"new"} || # Already a new function? $private_members =~ /\b$classname\s*\(/) { # Constructor is private? $members{"new"} = (parse_function("static $classname *", $classname, "new",""))[0]; } unless ($members{"delete"} || # Already a delete function? $private_members =~ /\~$classname\s*\(/) { # Destructor is private? $members{"delete"} = (parse_function("void", $classname, "delete", ""))[0]; } } # # The following function is called to convert a type into a canonical format. # It handles typedefs and puts the '*' and '&' in the appropriate locations. # Arguments: # 1) The type name to canonicalize. # 2) True if unrecognized words should be understood as builtin types that we # don't understand. # sub canonicalize_type { my ($type, $new_type_flag) = @_; # Access the argument. my $oldval = $type; $type =~ s/=.*//; # A default value can be specified, and we # should ignore it. if ($new_type_flag) { # Add unrecognized words to the basic type list? $type =~ s{\w+}{$typedef{$&} ||= $&}eg; } else { $type =~ s{\w+}{$typedef{$&} || ''}eg; # Translate the typedefs, and delete # any words that we don't care about, like # 'inline', or function arguments names. } $type =~ s/\[\]/\*/; # Convert float[] into float *. $type =~ s// > /g; $type =~ s/\s+/ /g; # Convert whitespace into spaces. $type =~ s/^ //; # Strip leading whitespace. $type =~ s/ $//; # Strip trailing whitespace. $type =~ s/ ([\*\&])/$1/g; # Remove spaces between '*' and '&'. $type =~ s/[\*\&]/ $&/; # Put a space before the first one. if ($type eq ''){ $oldval =~ s/\s+/ /g; # Pretty-print the type. die("unrecognized type '$oldval'\n"); } # print STDERR "Canonicalizing $oldval => $type\n"; $type; } # # Dump out the definition of a function (for debug purposes). Arguments: # 1) A string used to prefix each line so the indentation looks right. # 2) The %function_def array. # sub dump_function { my ($indent_str, $faa) = @_; # Name the arguments. printf("%s%s%s %s::%s(%s)\n", $indent_str, $faa->{static} ? "static " : "", $faa->{returns}, $faa->{class}, $faa->{name}, join(", ", map({ $faa->{args}{$_}{type} . " " . $_ } @{$faa->{argnames}}))); # Print out the C++ function prototype. printf("%s [%s] = %s(%s)\n", $indent_str, join(", ", @{$faa->{outputs}}, @{$faa->{modifies}}), $faa->{script_name} || ($faa->{class} ? $faa->{class} . "_" : "" ) . $faa->{name}, join(", ", @{$faa->{inputs}})); # Print out the scripting language prototype. foreach (@{$faa->{outputs}}, @{$faa->{modifies}}, @{$faa->{inputs}}) { printf("%s %s %s: basic type = %s, vectorize = %d, dimension = [%s]\n", $indent_str, $faa->{args}{$_}{source}, $_, $faa->{args}{$_}{basic_type}, $faa->{args}{$_}{vectorize}, join(", ", @{$faa->{args}{$_}{dimension}})); if (exists($faa->{args}{$_}{calculate})) { # A dimension argument? printf("%s Calculate from %s\n", $indent_str, $faa->{args}{$_}{calculate}); } } printf("%s %svectorized\n", $indent_str, $faa->{vectorize} ? "" : "not "); } # # Return true if the type is a basic type that can be freely and easily # copied. # sub is_basic_type { my ($typename) = @_; # Access the argument. if ($typename =~ /\*$/) { # Is it a pointer type? return 1; # Pointers can be freely copied. } foreach (split(' ', $typename)) { # Look at all the words: return 0 unless exists($basic_types{$_}); # Skip if not a basic type word. } return 1; # It's a basic type. } ############################################################################### # # Code to produce the wrappers: # # All subroutines below this point may output C code to the default file handle # which has been redirected to the appropriate place. # # # Output a C++ function which allows a derived class to be substituted for # a base class in a function argument. This function is called whenver # the type does not match exactly. # # Arguments to the perl function: # 1-n) The names of the classes to allow inheritance relationships between. # Classes outside this list are simply not handled. # sub output_class_conversion_func { print("\n" . "/*\n" . " * Convert between classes, handling inheritance relationships.\n" . " * Arguments:\n" . " * 1) The pointer.\n" . " * 2) The type code for its class.\n" . " * 3) The type code for the class you want.\n" . " *\n" . " * Returns 0 if the conversion is illegal, or else returns the\n" . " * desired pointer.\n" . " * We assume that you have already verified that the type code does\n". " * not match, so the only valid possibility is an inheritance\n" . " * relationship.\n" . " */\n"); # # See if in fact we know about any inheritance relationships: # my $is_inh = 0; # Assume there is no inheritance. foreach (@_) { $is_inh = 1, last if @{$derived_classes{$_}} != 0; # Quit if we found one } # inheritance relationship. if ($is_inh) { # Is there an inheritance relationship? print("static void *\n" . "__cvt_type(void *ptr, unsigned ptr_type, unsigned goal_type)\n" . "{\n" . " switch (goal_type)\n" . # Look at the class we want: " {\n"); # Output the function header. my $baseclass; foreach $baseclass (sort @_) { # Look at each of the classes: my @derived_classes = all_derived_classes($baseclass); # Get a list of all classes that are derived # from this one. next if @derived_classes == 0; # Nothing to do if no one inherits from us. print (" case @{[pointer_type_code($baseclass . ' *')]}: /* $baseclass */\n" . " switch (ptr_type)\n" . " {\n"); # Now look at the type of class we hae. my $derived_class; foreach $derived_class (@derived_classes) { print(" case @{[pointer_type_code($derived_class . ' *')]}: /* $derived_class */\n" . " return ($baseclass *)($derived_class *)ptr;\n"); } print (" default:\n" . " return 0;\n" . # Not derived from the goal class. " }\n"); } print(" default:\n" . # Goal class has nothing derived from it. " return 0;\n" . " }\n" . "}\n" . "\n"); } else { # No inheritance relationships: print("static void *\n" . "__cvt_type(void *, unsigned, unsigned)\n" . # Don't list the "{\n" . # parameter names, because gcc gives warning " return 0;\n" . # messages about unused parameters. "}\n\n"); } } # # Output the functions to set up the arrays for vectorizing. # sub output_vectorizing_subs { print qq{ /* * Check to see if the vectorizing dimensions on an input argument are * ok. Arguments: * 1) The input argument. * 2) The number of vectorizing dimensions we have so far. This is updated * if we add more vectorizing dimensions. * 3) An array containing the existing vectorizing dimensions. * 4) The number of explicitly declared dimensions, i.e., 0 if this was * declared as a scalar, 1 if a vector. We vectorize only the dimensions * higher than the explicitly declared ones. * 5) A value which is set to 0 if this argument is not vectorized. This * value is left unaltered if the argument is vectorized. * * Returns 0 if there was a problem, 1 if the dimensions were ok. */ int _check_input_vectorize(@{[&{"${language}::arg_declare"}('arg')]}, $dim_type *n_vec_dim, $dim_type _d[${"${language}::max_dimensions"}], $dim_type explicit_dims, $dim_type *vec_stride) { int v_idx; $dim_type n_dims = _n_dims(arg); if (n_dims > explicit_dims) /* Any additional dimensions? */ { if (*n_vec_dim == 0) /* No vectorizing dimensions seen yet? */ { /* This defines the vectorizing dimensions. */ *n_vec_dim = n_dims - explicit_dims; /* Remember the # of dims. */ for (v_idx = 0; v_idx < ${"${language}::max_dimensions"}-explicit_dims; ++v_idx) _d[v_idx] = _dim(arg, v_idx+explicit_dims); /* Remember this dim. */ } else /* Already had some vectorizing dimensions. */ { /* These must match exactly. */ for (v_idx = 0; v_idx < ${"${language}::max_dimensions"}-explicit_dims; ++v_idx) if (_d[v_idx] != _dim(arg, v_idx+explicit_dims)) /* Wrong size? */ return 0; /* Error! */ } } /* else if (n_dims < explicit_dims) */ /* Too few dimensions? */ /* return 0; */ /* We don't do this check because there's no way to * distinguish between a vector and a 3x1 matrix. */ else *vec_stride = 0; /* Vectorization not required. */ return 1; } /* * Same thing except for modify variables. Arguments: * 1) The input argument. * 2) The number of vectorizing dimensions we have so far. * 3) An array containing the existing vectorizing dimensions. * 4) The number of explicitly declared dimensions, i.e., 0 if this was * declared as a scalar, 1 if a vector. We vectorize only the dimensions * higher than the explicitly declared ones. * 5) A flag indicating whether this is the first modify variable. This * flag is passed by reference and updated by this subroutine. * * The vectorizing dimensions of modify arguments must exactly match those * specified for input variables. The difference between this subroutine * and _check_input_vectorize is that only the first modify variable may * specify additional vectorizing dimensions. * * Returns 0 if there was a problem, 1 if the dimensions were ok. */ int _check_modify_vectorize(@{[&{"${language}::arg_declare"}('arg')]}, $dim_type *n_vec_dim, $dim_type _d[${"${language}::max_dimensions"}], $dim_type explicit_dims, int *first_modify_flag) { int v_idx; $dim_type n_dims = _n_dims(arg); if (n_dims > explicit_dims) /* Any additional dimensions? */ { if (*n_vec_dim == 0 && *first_modify_flag) /* No vectorizing dimensions seen yet? */ { /* This defines the vectorizing dimensions. */ *n_vec_dim = n_dims - explicit_dims; /* Remember the # of dims. */ for (v_idx = 0; v_idx < ${"${language}::max_dimensions"}-explicit_dims; ++v_idx) _d[v_idx] = _dim(arg, v_idx+explicit_dims); /* Remember this dim. */ } else /* Already had some vectorizing dimensions. */ { /* These must match exactly. */ for (v_idx = 0; v_idx < ${"${language}::max_dimensions"}-explicit_dims; ++v_idx) if (_d[v_idx] != _dim(arg, v_idx+explicit_dims)) /* Wrong size? */ return 0; /* Error! */ } } /* else if (n_dims < explicit_dims) */ /* Too few dimensions? */ /* return 0; */ /* We don't do this check because there's no way to * distinguish between a vector and a 3x1 matrix. */ *first_modify_flag = 0; /* Next modify variable will not be first. */ return 1; } }; } # # Returns a unique type code for a given pointer type. Arguments: # 1) The type of the pointer. # sub pointer_type_code { my ($type) = @_; # Name the arguments. # # In order to guarantee that the same type has the same type code even in # different wrapper files, we just use a hash of the type as the type code. # It's very unlikely, though possible, that two unrelated types will have the # same type code. Maybe we'll fix this later. # my $hash_code = 0; foreach (split(//, $type)) { # Look at each character. $hash_code = ($hash_code * 29 + ord($_)) & 0x7ffff; # This assumes a 32- or 64-bit architecture. # We used to AND with 0x7fffffff but perl 5.005 # seems to handle integer overflow quite # differently from 5.004, so that doesn't work # any more. } # # Try to detect the case where hash codes might conflict, and give a warning: # if (exists($hash_code_to_type{$hash_code})) { # Already seen this one? if ($hash_code_to_type{$hash_code} ne $type) { # Conflicting types? unless ($already_warned{$type, $hash_code_to_type{$hash_code}}) { warn("matwrap: hash codes for type $type and $hash_code_to_type{$hash_code} conflict;\n These types will not be distinguishable.\n"); $already_warned{$type, $hash_code_to_type{$hash_code}} = 1; # Don't give this warning twice. } } } else { # Remember this type to check for future $hash_code_to_type{$hash_code} = $type; # conflicts. } return $hash_code; } # # The following subroutine returns all classes which are derived from a given # class. Arguments: # 1) The name of the class. # # Returns a list of classes as an array. # sub all_derived_classes { my $class = $_[0]; # Access the argument. my @derived_classes = @{$derived_classes{$class}}; # Get the classes which # are immediately derived from that class. foreach (@{$derived_classes{$class}}) { # Now find what is derived from those. push(@derived_classes, all_derived_classes($_)); } @derived_classes; } # # Wrap a variable or a constant. Arguments: # 1) The variable type. # 2) The variable name. # 3) The class the variable is in. Blank if global. # sub wrap_variable { my ($type, $name, $class) = @_; if ($type =~ /^\bconst\b/ && $type !~ /\*/) { # Is this a constant? &{"${language}::declare_const"}($name, $class, $type, ""); } else { my $sflag = ($type =~ s/^static //) ? "static " : ""; my $fdef = (parse_function("$sflag$type", $class, "___get_$name", "", "%name " . ($class ? "${class}_" : "") . "get_$name"))[0]; # The name ___get is treated specially by # wrap_function. wrap_function($fdef); # Wrap it. $fdef = # Make a set function. (parse_function("${sflag}void", $class, "___set_$name", "$type newval", "%name " . ($class ? "${class}_" : "") . "set_$name"))[0]; # The name ___set is treated specially by # wrap_function. wrap_function($fdef); # Wrap it. } } # # Wrap a function definition. Arguments: # 1) The %function_def array for this function. # sub wrap_function { my $faa = bless $_[0], $language; # Access the argument. # Bless it into the language class so we # can access functions whose first argument # is this array using member function syntax. my $retstr; # Where we accumulate the C code. We don't # output the C code immediately because # if the language module die()'s during # execution of this function, we want to # skip it and move to the next. my $args = $faa->{args}; # Argument definitions. my $arg; eval { # Protect from die: $retstr = $faa->function_start(); # Begin the function declaration. # # Figure out whether we can vectorize this function. It may be tagged to # vectorize, but if all arguments are either dimensional arguments or # tensors of the maximum dimension, then we can't vectorize it. (For example, # this would be the case in octave for a function that takes only full # matrix arguments.) # my $max_dimensions = 0; # Assume we won't be able to vectorize. if ($faa->{vectorize}) { # Supposed to vectorize this function? foreach $argname (@{$faa->{inputs}}, @{$faa->{modifies}}) { # Look at the arguments to make # sure we can actually vectorize this many # dimensions. $arg = $faa->{args}{$argname}; next unless $arg->{vectorize}; # Ignore non-vectorizable arguments. if (@{$arg->{dimension}} < ${"${language}::max_dimensions"}) { # Room to vectorize here? $max_dimensions = ${"${language}::max_dimensions"}; # Turn on the vectorizing. last; # Other arguments are irrelevant for maximum # vectorizing dimension. } } foreach $argname (@{$faa->{outputs}}) { # Make sure the outputs don't $arg = $faa->{args}{$argname}; # have too high dimension. if ($arg->{vectorize} == 0 || # Not able to vectorize this? @{$arg->{dimension}} >= ${"${language}::max_dimensions"}) { $max_dimensions = 0; # Too many output dimensions--no room for last; # vectorization. } } } # # Try to declare all variables at the top so this has a chance of working # with a C compiler as well as a C++ compiler. # if ($max_dimensions) { # Are we vectorizing? $retstr .= " $dim_type _d[$max_dimensions] = { " . # Allocate space for join(",", (1) x $max_dimensions) . " };\n"; # dimensions. $retstr .= " $dim_type _vec_n = 0;\n"; # The number of vectorizing dims. $retstr .= " $dim_type _vidx;\n"; # An index we use in various places. $retstr .= " $dim_type _vec_sz;\n"; # The product of the vectorized # dimensions. $retstr .= " int first_modify_flag = 1;\n" # Add the modify flag if if (@{$faa->{modifies}}); # there are any modify arguments. } foreach $argname (@{$faa->{argnames}}, # Look at the arguments. ($faa->{returns} eq 'void' ? () : ("retval"))) { # Also include the return value here. # Declare space to hold argument values # and the return from the function, if there # is one. $arg = $faa->{args}{$argname}; if ($arg->{vectorize} && $max_dimensions || # Is this argument supposed to be vectorized? @{$arg->{dimension}}) { # Is it an array? $retstr .= " $arg->{basic_type} *$arg->{c_var_name};\n"; # Pointer. } else { $retstr .= " $arg->{basic_type} $arg->{c_var_name};\n"; # Scalar. } } # # Calculate all of the dimensional arguments: # my (%dims_calculated, %dimvar); foreach $arg (grep(exists($_->{calculate}), values %$args)) { my $calc_str = "($arg->{calculate})"; # Put the string in parentheses. $calc_str =~ s{dim\((\w+), (\d+)\)}{ $dims_calculated{$1, $2} = 1; # Remember that we got this dim. $dimvar{$1} = 1; # We handled this dimension. $faa->get_size($1, $2); # Replace dim(varname, n) with the appropriate }eg; # C expression to get the dimension. $retstr .= " $arg->{c_var_name} = $calc_str;\n"; # Set the value of this dimensional variable. } # # Now calculate any other arguments which are used as dimensional indices # but we could not calculate from the given dimensions. # foreach $argname (grep($args->{$_}{source} eq 'dimension' && !defined($dimvar{$_}), @{$faa->{inputs}})) { $retstr .= $faa->get_c_arg_scalar($argname); # Get this argument value. $dimvar{$argname} = 1; # Remember that we got this one. } # # Declare the vectorizing "stride". Virtually all matlab clones store # multidimensional data using the same layout: a single dimension array. # Since we can vectorize array arguments, we assume that the least # significant (fastest varying) dimension(s) is the vector that is # passed on each successive call to the C function. The stride is the # product of the least significant dimensions (the ones that the C # function wanted). To get to the next C function call, the index into # the serial array is incremented by the vector stride. Note that if the # object is a scalar or is not vectorized, the vector stride is 0. # # We don't need to declare vector strides for dimensional variables, since # by definition they can't be vectorized. # foreach $argname (@{$faa->{inputs}}) { $arg = $args->{$argname}; # Look at the non-dimensional variables, # including the output: $retstr .= " $dim_type _vecstride_$argname = " . (@{$arg->{dimension}} == 0 ? 1 : join("*", @{$arg->{dimension}})) . ";\n" if $max_dimensions > 0 && $faa->{args}{$argname}{vectorize}; # Assume this argument will be vectorized. # This will be set to 0 by check_input_vectorize # if it is not vectorized. } # # Now verify that the dimension of all arguments are compatible, set up the # vectorization, and get the pointer to the first argument value. # foreach $argname (@{$faa->{inputs}}, @{$faa->{modifies}}) { # Look at the input arguments: $arg = $args->{$argname}; # Access the description of argument. my $dim = @{$arg->{dimension}}; # Get the minimum dimension of the argument. my @conds; foreach (0 .. $dim-1) { # Look at the dimension specs. next if $dims_calculated{$argname, $_}; # Skip if we used this to # calculate a dimension variable. push(@conds, " ($dim_type)(" . $faa->get_size($argname, $_) . ") != ($dim_type)(" . $arg->{dimension}[$_] . ")"); # Make sure this dimension matches. } # # See if any additional dimensions are specified. If so, we'll use them # for vectorizing. All modify arguments must have the additional # vectorizing dimensions. Input arguments may be either scalars or vectors, # but if they are vectorized, their dimensions must match. # if ($arg->{vectorize} && $dim < $max_dimensions) { if ($arg->{source} eq 'input') { # Input args # may or may not have vectorizing dims. push(@conds, "!_check_input_vectorize(@{[$faa->arg_pass($argname)]}, &_vec_n, _d, " . scalar(@{$arg->{dimension}}) . ", &_vecstride_$argname)"); } else { push(@conds, "!_check_modify_vectorize(@{[$faa->arg_pass($argname)]}, &_vec_n, _d, " . scalar(@{$arg->{dimension}}) . ", &first_modify_flag)"); } } else { # Not a vectorizable argument? push(@conds, $faa->n_dimensions($argname) . " > $dim"); # Make sure it has exactly the right number of # dimensions. Unfortunately, octave and matlab # can't distinguish between a vector and a # n by 1 matrix, so we have to check for less # than or equal to the number of dimensions. } if (@conds) { # Any dimension conditions? $retstr .= (" if (" . join(" ||\n ", @conds) . ")\n" . " " . $faa->error_dimension($argname) . "\n"); # Blow up if there's a problem. } if ($arg->{vectorize} && $max_dimensions > 0 || @{$arg->{dimension}}) { $retstr .= $faa->get_c_arg_ptr($argname); # Get a pointer to this arg. } else { $retstr .= $faa->get_c_arg_scalar($argname) # Get this argument. unless $dimvar{$argname}; # Unless we had to get it above because it # was a dimensional variable. } $retstr .= "\n"; # Put an extra blank line in to make it # more readable. } # # So much for the input arguments. Now handle the output arguments. These # matrices must be allocated to be the appropriate size: # foreach $argname (@{$faa->{outputs}}) { $arg = $args->{$argname}; # Point to description of argument. if ($max_dimensions > 0) { # Are we vectorizing? $retstr .= $faa->make_output_ptr($argname, "(" . @{$arg->{dimension}} . " + _vec_n)", # Number of dims. @{$arg->{dimension}}, # Explicit dimensions. map({ "_d[$_] " } # Vectorized dims. 0 .. ($max_dimensions-@{$arg->{dimension}}-1))); } else { # Not vectorizing: if (@{$arg->{dimension}}) { # Is this a vector? $retstr .= $faa->make_output_ptr($argname, scalar(@{$arg->{dimension}}), @{$arg->{dimension}}); # Make it as a vector. } else { # It's a scalar: $retstr .= $faa->make_output_scalar($argname); } } } # # Now actually call the C function. Get each of the arguments in a variable # and then pass it off to the function: # $retstr .= (" _vec_sz = " . join('*', map { "_d[$_]" } 0..$max_dimensions-1) . ";\n" . " for (_vidx = 0; _vidx < _vec_sz; ++_vidx) {\n") if $max_dimensions; # Add a loop if we're vectorizing. # # Get an expression for each argument: # my @fargs = map { $arg = $faa->{args}{$_}; # Access this argument. my $cexp = $arg->{c_var_name}; # Assume we just use the variable name. if ($max_dimensions > 0 && $arg->{vectorize}) { # Vectorizing? if ($arg->{source} eq 'input') { # Do we have a vector stride? $cexp .= "[_vecstride_$_*_vidx]"; # Add the index. } else { $cexp .= "[" . (@{$arg->{dimension}} == 0 ? "" : join("*", @{$arg->{dimension}}) . "*") . "_vidx]"; } if (@{$arg->{dimension}} || $arg->{pass_by_pointer_reference}) { "&$cexp"; # Need to pass an address? } else { $cexp; } } else { # Not a vectorized parameter: if ($arg->{pass_by_pointer_reference}) { # Pass by reference? "&$cexp"; } else { $cexp; } } } @{$faa->{argnames}}; if ($faa->{returns} ne 'void') { # Is there a return code? if ($max_dimensions) { # Are we vectorizing this? $retstr .= " $args->{retval}{c_var_name}" . "[_vidx] = ($args->{retval}{basic_type})\n "; # Store return value in an array. } else { $retstr .= " $args->{retval}{c_var_name} = ($args->{retval}{basic_type})\n "; # Store return value in a scalar. } } my $fcallstr; if ($faa->{class}) { # Is this a member function? if ($faa->{static}) { # Is it a static member function? if ($faa->{name} eq 'new') { # Is this the new function? $fcallstr = " new $faa->{class}(" . join(", ", @fargs) . ");\n"; } else { $fcallstr = " $faa->{class}::$faa->{name}(" . # Specify the class join(", ", @fargs) . ");\n"; # name explicitly. } } else { # It's a member function. First argument is # actually the class pointer. if ($faa->{name} eq 'delete') { # Delete the field? $fcallstr = " delete $fargs[0];\n"; } else { $fcallstr = " ($fargs[0])->$faa->{name}(" . join(", ", @fargs[1 .. (@fargs-1)]) . ");\n"; } } } else { # It's a boring global function: $fcallstr = " $faa->{name}(" . join(", ", @fargs) . ");\n"; } $fcallstr =~ s/___set_(.*?)\((.*)\)/$1 = $2/; # Handle the variable set. $fcallstr =~ s/___get_(.*?)\(\)/$1/; # Handle the variable get. $retstr .= $fcallstr; # Call the function. $retstr .= " }\n" if $max_dimensions; # Terminate the vectorizing loop. # # Now we've called the function. Put back all the output and modify variables. # foreach $argname (@{$faa->{modifies}}, @{$faa->{outputs}}) { if ($max_dimensions > 0 || # Vectorizing? @{$args->{$argname}{dimension}} > 0) { # It's an array of some sort? $retstr .= $faa->put_val_ptr($argname); # Put back as vector. } else { $retstr .= $faa->put_val_scalar($argname); # It's guaranteed to be } # a scalar. } $retstr .= $faa->function_end(); # We're done! }; # End of eval. if ($@) { # Was there a problem? print(STDERR "While wrapping function ", ($faa->{script_name} || $faa->{class} . "::" . $faa->{name}), ":\n$@"); # Print the message. } else { print $retstr; # Output the result. } }