#! /usr/bin/perl -w
# -*- perl -*-
#
# Please find extensive documentation of this program at
#
#
# Things this script does:
#
# - Expand class declarations as necessary using member-function
# definitions found in the file. Function labelled PUBLIC,
# PROTECTED and PRIVATE are put into the corresponding section of
# the class.
#
# - Put "INTERFACE:" regions in public header file.
#
# - Put "inline" functions and all types they need into public header
# file as well.
#
# - Put all remaining, private types and their inline functions into a
# separate header file. This file can be used by a debugger
# interface to display the data.
#
# - Place include directives to top of public header file and private
# header file.
#
#
# Declarations
#
require 5.006;
use Getopt::Std;
use strict;
sub print_expand($);
sub print_funcdecl($);
sub print_funcdef($);
sub print_classdecl($);
sub print_code($);
sub func_prototype($);
#
# Get options
#
our $opt_c = ''; # Base name for generated include directives
our $opt_o = ''; # Base name of output files (defaults to -c,
# overrides -p)
our $opt_p = ''; # Prepend to base name of output files (-c)
our $opt_h = ''; # Name of public header; overrides -c
our $opt_i = 0; # Doing inlines?
our $opt_v = 0; # Verboseness?
our $opt_l = 0; # Avoid generating #line directives?
our $opt_L = 0; # Avoid generatung #line dirs in headers only?
# Support for new style FIASCO config
our $opt_e = ''; # List of part tags
our $opt_s = 0;
our $opt_d = 0; # verbose drop
our $opt_w = 0; # warn if no inline code for needs is found
# Added 2003.01.12 by RCB
# Support for changing the names of headers
our $opt_H = "h"; # Default extenstion for header files
our $opt_C = "cc"; # Default extention for source files
our $opt_t = 0;
getopts('e:o:c:p:h:H:C:ivlLsdwt');
sub usage
{
print < [-C ]
[-e ] [-h ] [-H ]
[-o ] [-p ]
OPTIONS
-c Base name for generated include directives, generated
header files, and generated source files.
-C File extension for generated source files (default 'cc').
-d Verbose drop sections (only in conjunction with -e).
-e Use explicit section selection, and set the given tag
list. is a qouted and space separated list of
tags that should be enabled. (Useful in conjunction
with -s)
-h Name of generated public header (overrides -c)
-H File extension for generated header files (default 'h').
-i Do inlines, make inline functions real inline.
-l Avoid generating #line directives.
-L Avoid generating #line directives in headers only.
-o Base name for generated source files (defaults to -c, and
overrides -p)
-p Prefix for names of output files.
-s Generate a single source file per .cpp file (not a file
per section).
-t Truncate empty implementation files to zero length (so
that even the includ directives are ommitted)
-v Be verbose (very).
-w Do warnings.
EOF
}
if ($opt_c eq '')
{
usage;
die "Need to specify option -c Classfile_basename;";
}
my $incfile_base = $opt_c;
my $public_base = (($opt_h eq '') ? $incfile_base : $opt_h);
my $outfile_base;
my $headerfile_base;
my $doing_inlines = $opt_i;
my $verbose = $opt_v;
my $doing_linenumbers = (! $opt_l) && (! $opt_L);
my $wno_inline = $opt_w;
my $status = 0;
my $parts_re = '';
my %parts = ( '{' => '(',
'}' => ')',
',' => '||',
'-' => '&&',
'|' => '|',
'&' => '&',
'(' => '(',
')' => ')',
'!' => '!');
my $ARGV = $ARGV[0];
if ($opt_e ne '')
{
foreach my $p (split(' ',$opt_e))
{
$parts{$p} = '1';
}
}
# Added 2003.01.12 by RCB
# Support for changing the names of headers
my $source_ext = $opt_C;
my $header_ext = $opt_H;
if ($opt_o eq '')
{
$outfile_base = $opt_p . $incfile_base;
$headerfile_base = $opt_p . $public_base;
}
else
{
$outfile_base = $opt_o;
$headerfile_base = $outfile_base;
}
#
# Variable initializations
#
parse_init();
our $print_indent = 0;
clear_head();
my %classes = ();
my %functions = ();
my %sections = ();
my %impl_parts = ();
my %includes = ();
my @comments = ();
my %public_inline = ();
my %private_inline = ();
my %unit_inline = ();
my @inline_order_public = ();
my @inline_order_private = ();
my @inline_order_unit = ();
#
# Parse input file
#
parse_file ();
#
# Print header file
#
# Fixup incfile_base preproc macro if it contains invalid chars.
my $incfile_base_macro;
$incfile_base_macro = $incfile_base;
$incfile_base_macro =~ s/[+-]/_/g;
open(OUT, ">${headerfile_base}.$header_ext")
|| die "Cannot open ${headerfile_base}.$header_ext for writing!";
print OUT "// AUTOMATICALLY GENERATED -- DO NOT EDIT! -*- c++ -*-\n\n";
print OUT "#ifndef ${incfile_base_macro}_$header_ext\n" .
"#define ${incfile_base_macro}_$header_ext\n";
foreach my $i (grep {$_->{type} eq 'include'} @{$sections{"INTERFACE"}})
{
print_code $i;
}
print_head ("\n" .
"//\n" .
"// INTERFACE definition follows \n" .
"//\n\n");
foreach my $i ( (grep {$_->{type} eq 'classdef'
&& $_->{syntax} eq 'forwarddecl'}
@{$sections{"INTERFACE"}}),
(grep {$_->{type} ne 'classdef' || $_->{syntax} ne 'forwarddecl'}
@{$sections{"INTERFACE"}}) )
{
if ($i->{type} eq 'code')
{
print_code $i;
}
elsif ($i->{type} eq 'classdef')
{
print_classdecl ($i);
}
}
foreach my $i (grep {$_->{type} eq 'function' && $_->{class} eq ''
&& ! $_->{static}}
@{$sections{"IMPLEMENTATION"}})
{
print_funcdecl $i;
}
my @public_templates = grep
{
$_->{type} eq 'function' && $_->{template} ne '' # template func
&& $_->{fully_specialized_template} eq ''
&& ! defined $public_inline{$_} # not public inline -- handled elsewhere
&& ($_->{visibility} eq "free" # free func
|| ($_->{class} ne '' # or member func of public or published class
&& ($classes{$_->{class}}->{section} eq 'INTERFACE'
|| defined $public_inline{$classes{$_->{class}}})))
}
@{$sections{"IMPLEMENTATION"}};
my $impl_includes_imported = 0;
if (scalar keys %public_inline || scalar @public_templates)
{
if (scalar @public_templates)
{
$impl_includes_imported = 1;
}
clear_head();
print_head
("\n" .
"//\n" .
"// IMPLEMENTATION includes follow " .
"(for use by inline functions/templates)\n" .
"//\n\n");
foreach my $i (grep { $_->{type} eq 'include'
&& ($impl_includes_imported || $_->{inline}) }
@{$sections{"IMPLEMENTATION"}})
{
print_code $i;
}
clear_head();
print_head
("\n" .
"//\n" .
"// IMPLEMENTATION of inline functions (and needed classes)\n" .
"//\n\n");
print_inlines (@inline_order_public);
}
clear_head();
print_head ("\n" .
"//\n" .
"// IMPLEMENTATION of function templates\n" .
"//\n\n");
foreach my $i (@public_templates)
{
print_funcdef $i;
}
clear_head();
print OUT "\n#endif // ${incfile_base_macro}_$header_ext\n";
close OUT;
#
# Print "internal data structures" header file
#
open(OUT, ">${outfile_base}_i.$header_ext")
|| die "Cannot open ${outfile_base}_i.$header_ext for writing!";
print OUT "// AUTOMATICALLY GENERATED -- DO NOT EDIT! -*- c++ -*-\n\n";
print OUT "#ifndef ${incfile_base_macro}_i_$header_ext\n" .
"#define ${incfile_base_macro}_i_$header_ext\n";
foreach my $i (grep { $_->{type} eq 'include' }
@{$sections{"IMPLEMENTATION"}})
{
print_code $i;
}
foreach my $i
( (grep {$_->{type} eq 'classdef' && $_->{syntax} eq 'forwarddecl'}
@{$sections{"IMPLEMENTATION"}}), # first all forward declarations,
(grep {$_->{type} eq 'classdef' && $_->{syntax} ne 'forwarddecl'}
@{$sections{"IMPLEMENTATION"}}) ) # then all other class / type decls
{
print_classdecl ($i);
}
# XXX should we print #defines here?
print_head ("\n" .
"//\n" .
"// IMPLEMENTATION of inline functions follows\n".
"//\n\n");
print_inlines (@inline_order_private);
clear_head();
print_head ("\n" .
"//\n" .
"// IMPLEMENTATION of function templates\n" .
"//\n\n");
foreach my $i (grep
{
$_->{type} eq 'function' && $_->{template} ne ''
&& $_->{fully_specialized_template} eq ''
&& ! defined $public_inline{$_}
&& ! defined $private_inline{$_}
&& ($_->{visibility} eq 'static'
|| ($_->{class} ne ''
&& ($classes{$_->{class}}->{section} ne 'INTERFACE' &&
!defined $public_inline{$classes{$_->{class}}})))
} @{$sections{"IMPLEMENTATION"}})
{
print_funcdef $i;
}
clear_head();
print OUT "\n#endif // ${incfile_base_macro}_i_$header_ext\n";
close OUT;
$doing_linenumbers = (! $opt_l);
#
# Print implementation file(s)
#
foreach my $part (keys %impl_parts)
{
my $filename = $outfile_base.($part eq '' ? '' : ('-' . $part)) . ".$source_ext";
my $empty = 1;
#print "==> $filename\n";
open(OUT, ">$filename") || die "Could not open $filename for writing!";
print OUT "// AUTOMATICALLY GENERATED -- DO NOT EDIT! -*- c++ -*-\n\n";
print OUT "#include \"${public_base}.$header_ext\"\n" .
"#include \"${incfile_base}_i.$header_ext\"\n\n";
foreach my $i (grep {$_->{type} eq 'function' && $_->{class} eq ''
&& $_->{static}}
grep {$_->{part_ext} eq $part} @{$sections{"IMPLEMENTATION"}})
{
print_funcdecl $i;
}
# Print unparsed code first -- make private inline functions see
# static variables
foreach my $i (grep {$_->{part_ext} eq $part
&& $_->{type} eq 'code'}
@{$sections{"IMPLEMENTATION"}})
{
print_code $i;
$empty = 0;
}
print_inlines (grep {$_->{part_ext} eq $part} @inline_order_unit);
foreach my $i (grep {$_->{part_ext} eq $part
&& $_->{type} eq 'function'}
@{$sections{"IMPLEMENTATION"}})
{
next if $i->{template} ne ''
&& $i->{fully_specialized_template} eq '';
print_funcdef $i;
$empty = 0;
}
truncate OUT,0 if $empty && $opt_t;
close OUT;
}
exit 0;
#############################################################################
#
# Parser code.
#
my $s_once; # Regexp for whitespace; the \001 stuff is for comments
my $s; # Zero or more whitespace
my $identifier; # Identifier
my $operator_name; # Operator name
my $paren_group;
my $paren_expr;
my $tparen_group;
my $template; # Template declaration tag
my $template_arg; # Template argument list
my $lineno; # current line to be parsed
my $blockfile; # file that contains the currently pares block
my $blockstart; # first line of the current block
my $current_section; # current section
my $current_part;
my $current_part_ext;
my @ifstack; # stack of #if clauses
sub parse_init # Initialize parser variables.
{
# Regexp for whitespace; the \001 stuff is for comments
$s_once = '(?:[\n\s]|\001[0-9]+\001)';
# Zero or more whitespace
$s = $s_once . '*';
# Identifier
$identifier = "(?:[A-Za-z_][A-Za-z_0-9]*)";
# Operator name
$operator_name = "(?:operator$s(?:"
.'(?:[~,]|[+\-*/%^&|!=<>]=?|[<>]{2}=?|[&|+\-]{2}|->\*?'
.'|\(\)|(?:new|delete)?'.$s.'\[\])'."|$identifier))";
$paren_group = qr{
\(
(?:
(?> [^()]+ ) # Non-parens without backtracking
|
(??{ $paren_group }) # Group with matching parens
)*
\)
}x;
$paren_expr = qr{ (?> $paren_group | [^()]+ )* }x;
# Template argument list -- similar to paren_group above
$tparen_group = qr { < (?: (?> [^<>]+ ) | (??{ $tparen_group }) )* > }x;
# Template argument list
$template_arg = qr{ (?> $tparen_group) }x;
# Template declaration tag
$template = qr{template$s$template_arg$s};
}
sub match_e_opt
{
my $tag = shift;
my $cp = '';
my $t = '\(\)&|,\{\}!-';
my $orig_tag = $tag;
while ($tag =~ /^\s*([$t]|(?:[^\s$t]+))\s*(.*?)$/)
{
my $r = $parts{$1};
$cp .= defined $r ? $r : 0;
$tag = $2;
}
my $match = eval $cp;
#print "TAG: $tag -> $cp = $match\n";
if (!defined $match)
{
die "${ARGV}:$lineno: error: syntax error in tag '$tag'\n";
}
if (($verbose || $opt_d) && (!defined $match || !$match))
{
print "Drop SECTION: [$orig_tag] from".
" file ${ARGV}\n";
}
return $match;
}
sub parse_file
{
$lineno = 0;
@ifstack = ();
$current_section = "IMPLEMENTATION";
$current_part = "";
my $skip_to_next_section = 0;
NEXTLINE:
while (1)
{
#print "PARSED: $_\n";
$_ = '';
$blockstart = $lineno + 1;
$blockfile = $ARGV;
MORE:
while (1)
{
if (! read_more())
{
last NEXTLINE;
}
if (/^$s\/\/-[\s\n]*$/s)
{
handle_source_code ();
next NEXTLINE;
}
if (s/^($s)
(?:(INTERFACE
| IMPLEMENTATION)
(?:$s \[ $s ([A-Za-z0-9_,\{\}!\s&|\(\)-]+) $s \] $s)? : )
/$1/sx)
{
check_empty_ifstack();
$skip_to_next_section = 0;
$current_section = $2;
if ($opt_e ne '' || $current_section eq "IMPLEMENTATION")
{
if (defined $3)
{
$current_part = $3;
if ($opt_e ne '')
{
$skip_to_next_section = !match_e_opt($current_part);
next NEXTLINE if $skip_to_next_section;
}
}
else
{
$current_part = '';
}
}
else
{
if (defined $3 && $opt_h ne $3)
{
die "${ARGV}:${lineno}: all INTERFACE arguments and "
. "option -h must be consistent;"
if ($opt_h ne '');
$public_base = $3;
$opt_h = $3;
if ($opt_o eq '')
{
$headerfile_base = $opt_p . $public_base
}
}
}
handle_source_code ();
next NEXTLINE;
}
elsif ($skip_to_next_section)
{
next NEXTLINE;
}
# Preprocessor directive?
if (/^$s\#/s)
{
while (/\\\n$/s)
{
last NEXTLINE if ! read_more();
}
handle_preproc();
next NEXTLINE;
}
next NEXTLINE if ignoring();
# Read until we can decide what we have: Read till next block end
# or semicolon.
if (/\{/)
{
# Have a block.
my $foo = $_;
do {} while ($foo =~ s/\{[^\{\}]*\}//sg); # kill blocks
if ($foo =~ /\{/)
{
#print "MORE: $foo\n";
next MORE; # Still unfinished blocks.
}
}
elsif (! /;/) # no unclosed blocks & semicolon?
{
next MORE;
}
# Type declaration?
if (/^$s(?:$template)?(enum|struct|class|typedef)/s)
{
my $syntax = $1;
if (/^$s(?:$template)?
(?:enum|struct|class)
$s ($identifier (?:$s $template_arg)?)
$s (?::(?!:)|\{)/sx)
{
# Have a block -> this is a definition.
my $name = $1;
if (/^(.*)(\}.*)$/s)
{
my $class = { name => $name,
syntax => $syntax,
pretext => $1,
posttext => $2 };
#print "CLASS " . $class->{name} . ">" .$class->{pretext} . "###" . $class->{posttext};
handle_classdef ($class);
}
}
else
{
# No block or no name -- handle as declaration.
if (/^$s(?:$template)?(?:enum|struct|class)$s\{/s)
{
# no name but block -- make this a global variable decl.
handle_source_code ();
}
elsif (/^$s(?:$template)?(?:enum|struct|class)/s)
{
# no block -- this seems to be a forward
# decl. or a variable decl.
if (/^$s(?:$template)?(?:enum|struct|class)$s
($identifier) $s ;/sx)
{
my $class = { syntax => 'forwarddecl',
name => $1 };
handle_classdef ($class);
}
else
{
handle_source_code ();
}
}
elsif (/^${s} typedef \b /sx) # It's a typedef
{
# strip off function args and array spec
my $l = $_;
$l =~ s/; $s $//sx;
$l =~ s/\([^\)]*\) (?:${s}const)? $s $//sx;
$l =~ s/(?: \[ [^\[]* \] $s | \) $s )+ $//sx;
$l =~ m/($identifier) $s $/sx;
my $class = { syntax => 'typedef',
name => $1 };
handle_classdef ($class);
}
else
{
die "${ARGV}:$lineno: Parse error";
}
}
next NEXTLINE;
}
# Type declaration extension?
if (/^$s EXTENSION $s (?:struct|class) $s ($identifier)
$s (?::(?!:) $s ([^\{]*))?\{ (.*) \} $s ; $s $/sx)
{
my $super = $2;
my $name = $1;
my $string = $3;
if (! exists $classes{$name})
{
die "${ARGV}:$lineno: Class extension for undefined class " .
$name;
}
# XXX XXX we should not handle line directives here --
# this is the job of the output functions. However, as
# we don't generate a new codechunk for this extension,
# we just add the extension's line number here.
if ($doing_linenumbers)
{
$classes{$name}->{pretext} .=
"\n#line " . $blockstart . " \"" . $ARGV . "\"\n";
}
my $txt = \($classes{$name}->{pretext});
if (defined $super)
{
if ($$txt =~ /^([^\{]*)/sx)
{
my $pre = $1;
if ($pre =~ /^.*:(?!:)(.*)$/sx)
{
$$txt =~ s/^$pre/$pre\n, $super/s;
# print "ADD super classes: , $super\n";
}
else
{
$$txt =~ s/^$pre/$pre\n: $super/s;
}
}
}
$classes{$name}->{pretext} .= "private:\n" . $string;
next NEXTLINE;
}
# Member function definition?
# if (/^([^\{\(]*?) # pretext, maybe w template decl tag
if (/^((?:[^\{\(]|\(\()*?) # pretext, maybe w template decl tag
\b ($identifier (?: $s :: $s $identifier)*) # class name
($s $template_arg)? # optional class-template args
$s :: $s
((?:$operator_name | (?: ~? $identifier )) # member name
(?:$s $template_arg)?) # optional member-template args
$s ( \( (?: [^\)] | \([^\)]*\) )* \) [^:\{=]* ) # arg list
((?:\{|:.*\{).* | (?:$s = $s 0 $s ;))$/sx) # initializer ':' and body '{' or pure virtual
{
my ($pretext, $class, $templateargs, $name, $args, $posttext)
= ($1, $2, (defined $3 ? $3 : ''), $4, $5, $6);
#print "Member: P<$pretext> C<$class> T<$templateargs> N<$name> A<$args> P<$posttext>\n";
# Canonify operator names
$name =~ s/(?<=\w)(?:$s_once)+(?=\W)//gs;
$name =~ s/(?<=\W)(?:$s_once)+(?=\w)//gs;
$name =~ s/(?:$s_once)+/ /gs;
# Canonify class name
$class =~ s/$s//gs;
my $memberfunction = { class => $class,
name => $name,
templateargs => $templateargs,
pretext => $pretext,
args => $args,
posttext => $posttext };
handle_function ($memberfunction);
next NEXTLINE;
}
# Free function definition?
if (/^([^\{]*) # pretext, maybe w template decl tag
\b ($operator_name | $identifier) # function name
($s $template_arg)? # optional template args
$s( \( $paren_expr \) [^:\{\(\)=]*) # arg list
(\{.*)$/sx) # body
{
#print "Free function: $2 T(" . (defined $3 ? $3 : "") . ") PRE($1) ARGS($4) POST($5)\n";
my $function = { class => '',
name => $2,
templateargs => (defined $3
? $3 : ''),
pretext => $1,
args => $4,
posttext => $5 };
handle_function ($function);
next NEXTLINE;
}
handle_source_code ();
next NEXTLINE;
}
}
if (! /^$s$/s)
{
$verbose && print "EOF: " . $_ . "\n";
die "${blockfile}:$blockstart: Unexpected end of file in block starting here;";
}
}
sub read_more () # Read one more line of code. Stow away
# comments and character constants
{
# Get a line without comments.
while (1)
{
if (eof(INPUT)) # Reset line numbering.
{
check_empty_ifstack();
$lineno = 0;
do
{
my $file;
return 0 unless $file = shift @ARGV;
$ARGV = $file;
open(INPUT, $ARGV) || die "Cannot open $ARGV for reading!";
if ($opt_s)
{
# print "FILE: $ARGV\n";
my $part_ext = '';
if ($ARGV =~ /^(?:.*\/)?(.+)$/ && $1 =~ /(?:[^-]*)-(.*)\..*/)
{
$part_ext = $1;
}
$current_part_ext = $part_ext;
$impl_parts{$part_ext} = 1;
# print "PART: '$part_ext'\n";
}
print "read file: '$ARGV'\n" if $verbose;
}
while(eof(INPUT));
}
$lineno++;
my $line = ;
if (! defined $line)
{
return 0;
}
$_ .= $line;
# Save comments and strings in @comments array. Save strings
# first to catch strings with comment-like contents.
my $number = @comments;
# We don't touch strings in NEEDS[], neither #includes! Save now --
# restore later.
my $saved = '';
if (s/(^$s \# $s include.*$
| NEEDS $s \[[^\]]* )
/\003/sx)
{
$saved = $1;
}
while (s,(\'(?:\\.|[^\']|\\[0-7]+)\'),\002$number\002,s)
{
push @comments, $1;
$number++;
}
# while (s,(\"(?:[^\"]|(?<=\\)\")*\"),\002$number\002,s)
while (s,(\"(?:[^\\\"]|\\.)*\"),\002$number\002,s)
{
push @comments, $1;
$number++;
}
if ($saved ne '')
{
s/\003/$saved/s;
}
while (s|(//(?!-\s*\n).*\n)|\001$number\001|m) # Do not match magic "//-"
{ # The \001 signifies whitespace.
push @comments, $1;
$number++;
}
while (s|(/\*.*\*/)|\001$number\001|s)
{
push @comments, $1;
$number++;
}
if (! /\/\*/)
{
last;
}
}
return 1;
}
sub label_chunk
{
my ($codechunk,$type) = @_;
$codechunk->{type} = $type;
$codechunk->{section} = $current_section;
$codechunk->{string} = $_;
$codechunk->{part} = $current_part;
$codechunk->{part_ext} = $opt_s ? $current_part_ext : $current_part;
$impl_parts{$current_part} = 1 unless $opt_s;
$codechunk->{line} = $blockstart;
$codechunk->{file} = $ARGV;
$codechunk->{printed} = 0;
push @{$sections{$current_section}}, $codechunk;
}
sub ignoring
{
foreach my $i (@ifstack)
{
if ($i->{value} == 1)
{
return 1;
}
}
return 0;
}
sub handle_preproc
{
# if ($codeblock->{string} =~ /^$s\#\s*(if|endif|else|elif)/)
# {
# die "${ARGV}:${lineno}: Conditional compilation not supported;";
# }
if (/^$s\#\s*if\s+0${s}$/)
{
push @ifstack, { value => 1, file => ${ARGV}, line => $lineno };
$verbose && print "IF 0: " . ignoring() . "\n";
return;
}
elsif (@ifstack && /^$s\#\s*if(def|ndef)?\s/)
{
push @ifstack, { value => 0, file => ${ARGV}, line => $lineno };
$verbose && print "IF: " . ignoring() . "\n";
return if ignoring();
}
elsif (@ifstack && /^$s\#\s*(else|elif)/)
{
my $ignoring = ignoring();
my $i = pop @ifstack;
$i->{value} = -$i->{value};
push @ifstack, $i;
$verbose && print "ELSE/ELIF: " . ignoring() . " ($ignoring)\n";
return if $ignoring;
}
elsif (@ifstack && /^$s\#\s*endif/)
{
my $ignoring = pop @ifstack;
$verbose && print "ENDIF: " . ignoring() . "\n";
return if ignoring() || $ignoring->{value};
}
elsif (/^$s\#\s*include${s}([\"<][^\">]+[\">])/)
{
my $codeblock;
$codeblock->{name} = $1;
$codeblock->{inline} = 0;
$includes{$codeblock->{name}} = $codeblock;
label_chunk ($codeblock, "include");
$verbose && print "INCLUDE: " . $codeblock->{name} . "\n";
return;
}
# XXX: For now, treat preprocessor stuff besides #include, #if 0 as code.
handle_source_code ();
}
sub dump_ifstack
{
my $indent = '';
foreach my $i (@ifstack)
{
print "$indent$i->{value}: $i->{file}:$i->{line}\n";
$indent .= ' ';
}
}
sub check_empty_ifstack
{
if ($#ifstack >= 0)
{
my $i = pop @ifstack;
print STDERR "${ARGV}:${lineno}: missing endif for $i->{file}:$i->{line}\n";
die;
$status = -1;
}
}
sub handle_source_code
{
return if /^[\s\n]*$/;
my $codeblock = {};
label_chunk ($codeblock, "code");
$verbose && print "UNKNOWN: " . $codeblock->{string};
}
sub handle_classdef
{
my $class = $_[0];
label_chunk ($class, "classdef");
$class->{funcs} = [];
if ($class->{syntax} ne 'forwarddecl')
{
$classes{$class->{name}} = $class;
}
$verbose && print "CLASSDEF: " . $class->{name} . " ["
. $class->{syntax} . "]\n";
}
sub handle_function
{
my $func = $_[0];
$func->{purevirtual} = 0;
if ($func->{class} ne '')
{
# Nested class hacks
if ($func->{class} =~ /::/
&& ! defined $classes{$func->{class}})
{
# Define class along the way -- the dirty way.
my $class = { name => $func->{class},
syntax => "class",
nested_class => 1 };
my ($topclass, $rest) = split (/::/, $func->{class});
my $save_sec = $current_section;
$current_section = $classes{$topclass}->{section};
handle_classdef ($class);
$current_section = $save_sec;
}
$func->{visibility} = "private";
if (s/^($s)PRIVATE([\s\n])/$1$2/s)
{
$func->{visibility} = "private";
$func->{pretext} =~ s|PRIVATE[ \t]*||s;
}
elsif (s/^($s)PUBLIC([\s\n])/$1$2/s)
{
$func->{visibility} = "public";
$func->{pretext} =~ s|PUBLIC[ \t]*||s;
}
elsif (s/^($s)PROTECTED([\s\n])/$1$2/s)
{
$func->{visibility} = "protected";
$func->{pretext} =~ s|PROTECTED[ \t]*||s;
}
elsif (s/^($s)IMPLEMENT([\s\n])/$1$2/s)
{
# Use a visibility attribute that is never used in adding
# declarations to classes in print_classdecl.
$func->{visibility} = "implementation_only";
$func->{pretext} =~ s|IMPLEMENT[ \t]*||s;
}
elsif (s/^($s)IMPLEMENT_DEFAULT([\s\n])/$1$2/s)
{
# Use a visibility attribute that is never used in adding
# declarations to classes in print_classdecl.
$func->{visibility} = "implementation_only";
$func->{default_impl} = 1;
$func->{pretext} =~ s|IMPLEMENT_DEFAULT[ \t]*||s;
}
if ($func->{posttext} =~ s/$s=${s}0$s;$s$//s)
{
$func->{purevirtual} = 1;
}
if ($func->{class} =~ /::/
&& $func->{visibility} ne "implementation_only")
{
die "${ARGV}:${lineno}: Limitation: Only predeclared members " .
"supported for nested classes. Use IMPLEMENT;";
}
if (! defined $classes{$func->{class}})
{
die "${ARGV}:${lineno}: Class " . $func->{class}
. " has not been declared;";
}
}
else
{
$func->{visibility} = "free";
}
# Interprete more type attributes.
$func->{inline} = 0;
$func->{always_inline} = 0;
$func->{static} = 0;
$func->{hide} = 0;
$func->{virtual} = 0;
$func->{explicit} = 0;
$func->{classtemplate} = '';
$func->{funtemplate} = '';
$func->{template} = '';
$func->{fully_specialized_template} = '';
while (1)
{
if (s/^($s)((?:$template)+)([\s\n])/$1$3/s)
{
my $match = $2;
my @specs = split(/(?<= \>)(?= $s template)/sx, $match, 3);
if ($func->{class} eq '') # Free function?
{
$func->{funtemplate} = shift @specs;
}
else # Have a class
{
my $class = $classes{$func->{class}};
my $istemplateclass = ($class->{pretext} =~ /^[^\{]*template/s);
if ($istemplateclass)
{
$func->{classtemplate} = shift @specs;
$func->{funtemplate} = shift @specs if scalar @specs;
}
else # Not a class template
{
$func->{funtemplate} = shift @specs;
}
}
die "${ARGV}:$lineno: Too many template specs"
if scalar @specs;
$func->{template} = 'yes';
$func->{fully_specialized_template} = 'yes'
if ($match =~ /^(?:${s}template$s<${s}>)+${s}$/s);
$func->{pretext} =~ s/\Q$match//s;
next;
}
if (s/^($s)inline([\s\n])/$1$2/si) # "inline" is case-insensitive.
{
$func->{inline} = 1 if $doing_inlines
|| $func->{fully_specialized_template} ne '';
$func->{pretext} =~ s|inline[ \t]*||si;
@{$func->{needs}} = ();
while (1)
{
if (s/^($s)NEEDS\s*\[([^\]]+)\]([\s\n])/$1$3/s)
{
@{$func->{needs}} = split (/\s*,\s*/, $2);
# Delete NEEDS directive, but keep newlines
while ($func->{pretext} =~
s|NEEDS \s* \[ ( (?:[^\n\]]*\n)* )
[^\n\]]+ \n (\n*)
[^\n\]]* \]
|NEEDS[$1\n$2\]|sx) {}
$func->{pretext} =~ s|NEEDS\s*\[ (\n*) [^\n\]]*\]|$1|sx;
next;
}
if (s/^($s)NOEXPORT([\s\n])/$1$2/si)
{
$func->{hide} = 1;
$func->{pretext} =~ s|NOEXPORT[ \t]*||s;
next;
}
if (s/^($s)ALWAYS_INLINE([\s\n])/$1$2/si)
{
$func->{inline} = 1;
$func->{always_inline} = 1;
$func->{pretext} =~ s|ALWAYS_INLINE[ \t]*||s;
next;
}
last;
}
# Reset inline data if inline handling was not enabled by -i
# or ALWAYS_INLINE.
if (! $func->{inline})
{
undef $func->{needs};
}
next;
}
if (s/^($s)static([\s\n])/$1$2/s)
{
$func->{static} = 1;
$func->{pretext} =~ s/static[ \t]*//s;
if ($func->{class} eq '')
{
$func->{visibility} = "static";
$func->{hide} = 1;
}
next;
}
if (s/^($s)IMPLEMENT([\s\n])/$1$2/s)
{
$func->{pretext} =~ s/IMPLEMENT[ \t]*//s;
if ($func->{class} eq '')
{
$func->{visibility} = "implementation_only";
}
next;
}
if (s/^($s)IMPLEMENT_DEFAULT([\s\n])/$1$2/s)
{
$func->{pretext} =~ s/IMPLEMENT_DEFAULT[ \t]*//s;
if ($func->{class} eq '')
{
$func->{visibility} = "implementation_only";
$func->{default_impl} = 1;
}
next;
}
if (s/^($s)explicit([\s\n])/$1$2/s)
{
$func->{explicit} = 1;
$func->{pretext} =~ s|explicit[ \t]*||s;
next;
}
if (s/^($s)virtual([\s\n])/$1$2/s)
{
$func->{virtual} = 1;
$func->{pretext} =~ s|virtual[ \t]*||s;
next;
}
if (/^($s)(PRIVATE|PUBLIC|PROTECTED)([\s\n])/)
{
die "${blockfile}:$blockstart: only one visibility attribute allowed at start of declaration;";
}
last;
}
label_chunk ($func, "function");
if ($current_section eq 'INTERFACE')
{
die "${ARGV}:${lineno}: Function " . $func->{name}
. " in INTERFACE section;";
}
if (defined $functions{"$func->{class}::$func->{name}"})
{
my $f = $functions{"$func->{class}::$func->{name}"};
if (defined $f->{default_impl})
{
$f->{printed} = 1;
}
elsif (defined $func->{default_impl})
{
$func->{printed} = 1;
}
}
else
{
$functions{"$func->{class}::$func->{name}"} = $func;
}
push @{$classes{$func->{class}}->{funcs}}, $func;
$verbose && print "FUNC: " . ($func->{class} ne ''
? ($func->{class} . "::")
: "")
. $func->{name}
. ($func->{classtemplate} ne ''
? " T: " . $func->{classtemplate} : "")
. ($func->{funtemplate} ne ''
? " M: " . $func->{funtemplate} : "")
. ($func->{fully_specialized_template} ne ''
? " FULLY_SPEC" : "")
. "\n";
}
#############################################################################
#
# Printing code.
#
my $saved_head;
my $saved_indent;
sub print_head # Save header. Print it only if a
# print_expand() follows
{
$saved_head .= $_[0];
$saved_indent = $print_indent;
}
sub clear_head
{
$saved_head = '';
}
sub print_expand($) # Expands comments and prints to OUT.
{
my $str = $_[0];
if ($saved_head ne '')
{
local $print_indent = $saved_indent;
my $str = $saved_head;
$saved_head = '';
print_expand $str; # Recurse.
}
$str =~ s/\n(?:[ \t]*\n)+/\n\n/sg if ! $doing_linenumbers;
while ( $str =~ s/([\001\002])([0-9]+)\1/$comments[$2]/sg )
{}
if ($print_indent)
{
my $istr = " " x $print_indent;
$str =~ s/^/$istr/mg;
}
print OUT $str;
}
sub print_lineno($)
{
return if ! $doing_linenumbers;
my $object = $_[0];
print_expand ''; # print headers we accumulated
print OUT "#line " . $object->{line} . " \"" . $object->{file} . "\"\n";
}
sub print_lineno_sans_empty_lines($)
{
return if ! $doing_linenumbers;
my $object = $_[0];
my $start_of_code = $object->{string};
$start_of_code =~ s/^([\s\n]+).*$/$1/s;
my @startcomments = split /\n/, " $start_of_code ";
print OUT "#line " . ($object->{line} + @startcomments - 1)
. " \"" . $object->{file} . "\"\n";
}
sub weedout_whitespace # Delete whitespace except on lines w/comments
{
my $str = $_[0];
$str =~ s/^[\s\n]+//s;
if (! $doing_linenumbers) # more cosmetic changes if we do not
{ # have to be correct line-number-wise
my @lines = split /\n/, $str;
my $foundcode = 0;
$str = '';
foreach my $line (@lines)
{
$line =~ s/^\s+//;
$line =~ s/\s+$//;
if ($line =~ /\001/ || $line =~ /^\s*$/)
{
$line .= "\n";
}
else
{
if (! $foundcode)
{
$foundcode = 1;
# Found something like code: Remove trailing whitespace
# from $str,
$str =~ s/\s+$//s;
$str .= "\n" if $str ne '';
}
$line =~ s/\s+/ /g;
$line .= ' ';
}
$str .= $line;
}
}
$str =~ s/\s+$//;
return $str;
}
sub func_prototype($) # Return a function declaration from
# func head.
{
my $func = $_[0];
my $pretext = $func->{pretext};
if ($func->{inline})
{
$pretext =~ s/^($s)/${1}inline /s;
}
if ($func->{explicit})
{
$pretext =~ s/^($s)/${1}explicit /s;
}
if ($func->{static})
{
$pretext =~ s/^($s)/${1}static /s;
}
if ($func->{virtual})
{
$pretext =~ s/^($s)/${1}virtual /s;
}
if ($func->{funtemplate} ne '')
{
$pretext =~ s/^($s)/${1}$func->{funtemplate} /s;
}
my $func_header = weedout_whitespace($pretext .
$func->{name} . $func->{args});
# Insert ; at the correct place, that is, before any comments.
my $e = $func->{purevirtual} ? ' = 0' : '';
$func_header =~ s/($s)$/$e;$1/s;
return $func_header;
}
sub print_funcdecl($)
{
my $function = $_[0];
if ($function->{visibility} ne "implementation_only")
{
print_expand "\n";
print_lineno_sans_empty_lines $function;
print_expand func_prototype($function) . "\n";
}
# Handle inlines.
if ($function->{inline})
{
handle_inline ($function);
}
}
sub print_classdecl($)
{
my $class = $_[0];
return if check_if_printed ($class);
print_lineno $class;
if (defined $class->{nested_class})
{
# (This will not actually print anything, but do other processing.)
foreach my $function (@{$class->{funcs}})
{
die "Assert failed"
if $function->{visibility} ne "implementation_only";
print_funcdecl $function;
}
}
elsif ($class->{syntax} =~ /^(?:struct|class)$/)
{
if (! $doing_inlines)
{
$class->{pretext} =~ s/\binline\b[ \t]*//g;
}
print_expand $class->{pretext};
print_head "\npublic:";
$print_indent += 2;
foreach my $function (grep {$_->{visibility} eq "public"}
@{$class->{funcs}})
{
print_funcdecl $function;
}
$print_indent -= 2;
clear_head();
print_head "\nprotected:";
$print_indent += 2;
foreach my $function (grep {$_->{visibility} eq "protected"}
@{$class->{funcs}})
{
print_funcdecl $function;
}
$print_indent -= 2;
clear_head();
print_head "\nprivate:";
$print_indent += 2;
foreach my $function (grep {$_->{visibility} eq "private"}
@{$class->{funcs}})
{
print_funcdecl $function;
}
$print_indent -= 2;
clear_head();
# Also, don't forget to "print" already-declared functions.
# (This will not actually print anything, but do other processing.)
foreach my $function (grep {$_->{visibility} eq "implementation_only"}
@{$class->{funcs}})
{
print_funcdecl $function;
}
print_expand $class->{posttext};
}
else
{
print_expand $class->{string};
}
}
my $parengroup;
sub print_funcdef($)
{
my $function = $_[0];
return if $function->{purevirtual};
return if check_if_printed ($function);
my $pretext = $function->{pretext};
if ($function->{inline})
{
if ($function->{always_inline})
{
$pretext =~ s/^($s)/${1}ALWAYS_INLINE /s;
}
$pretext =~ s/^($s)/${1}inline /s;
}
if ($function->{static} && $function->{class} eq '')
{
$pretext =~ s/^($s)/${1}static /s;
}
$parengroup = qr{ # Matches correctly-nested groups of <> parens
<
(?:
(?> [^<>]* ) # Non-parens without backtracking
|
(??{ $parengroup }) # Backtrack: Group with parens
)*
>
}x;
my $expr = qr{ [^<>,]* (?:$parengroup)? [^<>,]* }x;
if ($function->{funtemplate} ne '')
{
my $templ = $function->{funtemplate};
$templ =~ s/$s = $expr//gx;
$pretext =~ s/^($s)/${1}$templ /s;
}
if ($function->{classtemplate} ne '')
{
my $templ = $function->{classtemplate};
$templ =~ s/$s = $expr//gx;
$pretext =~ s/^($s)/${1}$templ /s;
}
# Remove default arguments from argument list
my $args = $function->{args};
$parengroup = qr{ # Matches correctly-nested groups of () parens
\(
(?:
(?> [^()]* ) # Non-parens without backtracking
|
(??{ $parengroup }) # Backtrack: Group with parens
)*
\)
}x;
$expr = qr{ [^(),]* (?:$parengroup)? [^(),]* }x;
$args =~ s/$s = $expr//gx;
print_expand "\n";
print_lineno $function;
print_expand $pretext
. ($function->{class} ne ''
? $function->{class} . $function->{templateargs}
. "::" . $function->{name}
: $function->{name} . $function->{templateargs})
. $args . $function->{posttext};
}
sub print_code($)
{
my $codeblock = $_[0];
return if check_if_printed ($codeblock);
print_lineno $codeblock;
print_expand $codeblock->{string};
}
sub check_if_printed
{
my $codeblock = $_[0];
return 1 if $codeblock->{printed};
$codeblock->{printed} = 1;
return 0;
}
#############################################################################
#
# Inline-function bookkeeping.
#
sub lookup_by_name # Return (list of) item(s) matching name.
{
my ($item, $context) = @_;
# Is it a class name?
if (defined $classes{$item})
{
return $classes{$item};
}
# Is it an include file?
if (defined $includes{$item})
{
$includes{$item}->{inline} = 1;
return $includes{$item};
}
# Must be a function name!
my ($classname, $funcname);
if ($item =~ /::/)
{
($classname, $funcname) = split /::/, $item;
}
else
{
($classname, $funcname) = ('' , $item);
}
my @grepresult = grep {$_->{name} eq $funcname && $_->{inline}}
@{$classes{$classname}->{funcs}};
return shift @grepresult
if (scalar @grepresult == 1);
if (scalar @grepresult == 0)
{
my @xgrepresult = grep {$_->{name} eq $funcname}
@{$classes{$classname}->{funcs}};
die $context->{file} . ":" . $context->{line} . ": Cannot find $item;"
if (scalar @xgrepresult == 0);
$wno_inline && print STDERR $context->{file} . ":" . $context->{line} .
": warning: Cannot find inline code ".
"for $item;\n";
}
return @grepresult; # Return list of matching function names.
}
# Check if Function $function can already see Object $item in its context.
sub inline_known
{
my ($item, $function) = @_;
if ($item->{type} eq "function"
&& $item->{hide}
&& ! $function->{hide})
{
die $function->{file} . ":" . $function->{line} .
": Nonhidden function " . funcname($function) .
" depends on hidden function " . funcname($item) . " (" .
($item->{visibility} eq 'static' ? "static" : "NOEXPORT") . ")";
}
return exists $public_inline{$item}
|| (($function->{visibility} eq 'private'
|| ($function->{class} ne ''
&& $classes{$function->{class}}->{section} eq "IMPLEMENTATION"))
&& exists $private_inline{$item})
|| ($function->{hide}
&& exists $unit_inline{$item});
}
# Put inline function $1 and all its dependencies (given by NEEDS
# directives) into @inline_order_[public/private/unit], depending on
# visibility of $1. Function handle_inline is called when printing
# inline-function declarations, so the sequence of handle_inline calls
# is determined by declaration-printing order.
sub handle_inline
{
my $function = $_[0];
my $class = $function->{class};
my @needed = ();
$verbose &&
print "INLINE " . funcname($function) . " NEEDS ";
# Add all needed items, then add my own name as well as my class
# name for good measure.
foreach my $item (@{$function->{needs}})
{
push @needed, lookup_by_name ($item, $function);
}
push @needed, $function;
unshift @needed, lookup_by_name ($class, $function)
if ($class ne '');
NEEDEDLOOP:
while (@needed)
{
my $object = $needed[0];
if (inline_known ($object, $function))
{
shift @needed;
next;
}
# Check for further dependencies.
my @moreneeded = ();
if ($object->{type} eq "function" && $object->{class} ne '')
{
my $class = lookup_by_name ($object->{class}, $object);
push @moreneeded, $class;
}
if (defined $object->{needs})
{
foreach my $item (@{$object->{needs}})
{
my $o = lookup_by_name ($item, $object);
next if ! ref $o; # Skip referenced but noninline objects
push @moreneeded, $o;
}
}
# Check if we have everything that's needed for $item.
foreach my $i (@moreneeded)
{
if (inline_known ($i, $function))
{
next;
}
if ($i == $function) # Function depends on itself!
{
my $callstack = " " . funcname ($function) . "\n";;
my $prev = $function;
push @needed, $function;
foreach my $j (@needed)
{
# $j is not part of call stack if it does not need $prev
next if ! grep {lookup_by_name ($_, $object) == $prev}
@{$j->{needs}};
$callstack .= " " . funcname ($j) . "\n";
$prev = $j;
last if $j == $function;
}
die $object->{file} . ":" . $object->{line} . ": Function " .
funcname ($object) . " NEEDS " . funcname ($i) .
", which circularly depends on this function:\n" .
$callstack;
}
unshift @needed, $i;
next NEEDEDLOOP;
}
$verbose && print &funcname ($object) . " ";
if ($function->{hide})
{
$unit_inline{$object} = 1;
push @inline_order_unit, $object;
}
elsif ($function->{visibility} eq 'private'
|| ($class ne ''
&& $classes{$class}->{section} eq "IMPLEMENTATION"))
{
$private_inline{$object} = 1;
push @inline_order_private, $object;
}
else
{
$public_inline{$object} = 1;
push @inline_order_public, $object;
}
shift @needed;
}
$verbose && print "\n";
}
sub print_inlines
{
foreach my $object (grep {$_->{type} eq "classdef"} @_)
{
if ($object->{section} ne 'INTERFACE')
{
print_classdecl $object;
}
}
foreach my $object (grep {$_->{type} eq "function"} @_)
{
print_funcdef $object;
}
}
######################################################################
#
# Utilities
#
sub funcname
{
my $function = $_[0];
return ($function->{class} ? ($function->{class} . "::") : "")
. $function->{name};
}