#! /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
our $opt_W = 0; # Make warnings errors.
# 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:ivlLsdwtW');
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.
-W Warnings are errors.
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 $parts_re = '';
my %parts = ( '{' => '(',
'}' => ')',
',' => '||',
'-' => '&&',
'|' => '|',
'&' => '&',
'(' => '(',
')' => ')',
'!' => '!');
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;
}
##
# Source-code chunk
#
package Chunk;
our %sections = ();
sub new
{
my ($class, $pos, $type, $code, %opts) = @_;
my $section = $pos->{section};
my $current_part = $pos->{part};
my $c = {
part => $current_part,
part_ext => $opt_s ? $pos->{part_ext} : $current_part,
printed => 0,
section => $section,
src_pos => $pos,
string => $code,
type => $type,
line => $pos->line,
file => $pos->file,
class => '',
%opts
};
push @{$sections{$section}}, $c;
return bless $c, __PACKAGE__
}
sub pos
{ return $_[0]->{src_pos}; }
sub is_member
{ return $_[0]->{class} ne ''; }
sub full_name
{
my $o = $_[0];
return ($o->{class} ? ($o->{class} . "::") : ""). $o->{name};
}
sub in_interface
{ return $_[0]->{src_pos}->in_interface; }
sub in_implementation
{ return $_[0]->{src_pos}->in_implementation; }
sub line
{ return $_[0]->{src_pos}->line; }
sub file
{ return $_[0]->{src_pos}->file; }
package main;
#
# Variable initializations
#
my %visibility_spec = (
PUBLIC => { visibility => 'public' },
PRIVATE => { visibility => 'private' },
PROTECTED => { visibility => 'protected' },
# Use a visibility attribute that is never used in adding
# declarations to classes in print_classdecl.
IMPLEMENT => { visibility => 'implementation_only' },
IMPLEMENT_DEFAULT => { visibility => 'implementation_only', default_impl => 1 },
IMPLEMENT_OVERRIDE => { visibility => 'implementation_only', override_impl => 1 },
);
my @member_visibility_spec = ( keys %visibility_spec );
my @global_visibility_spec = qw(IMPLEMENT IMPLEMENT_DEFAULT IMPLEMENT_OVERRIDE);
parse_init();
our $print_indent = 0;
clear_head();
my $num_errors = 0;
my $num_warnings = 0;
my %classes = ();
my %functions = ();
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'} @{$Chunk::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'}
@{$Chunk::sections{"INTERFACE"}}),
(grep {$_->{type} ne 'classdef' || $_->{syntax} ne 'forwarddecl'}
@{$Chunk::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}}
@{$Chunk::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
|| ($_->is_member # or member func of public or published class
&& ($classes{$_->{class}}->in_interface
|| defined $public_inline{$classes{$_->{class}}})))
}
@{$Chunk::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}) }
@{$Chunk::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' }
@{$Chunk::sections{"IMPLEMENTATION"}})
{
print_code $i;
}
foreach my $i
( (grep {$_->{type} eq 'classdef' && $_->{syntax} eq 'forwarddecl'}
@{$Chunk::sections{"IMPLEMENTATION"}}), # first all forward declarations,
(grep {$_->{type} eq 'classdef' && $_->{syntax} ne 'forwarddecl'}
@{$Chunk::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'
|| ($_->is_member
&& (!$classes{$_->{class}}->in_interface &&
!defined $public_inline{$classes{$_->{class}}})))
} @{$Chunk::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' && ! $_->is_member
&& $_->{static}}
grep {$_->{part_ext} eq $part} @{$Chunk::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'}
@{$Chunk::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'}
@{$Chunk::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;
}
if ($num_errors > 0)
{
print STDERR "error: found $num_errors error(s) and $num_warnings warning(s)\n";
exit 128;
}
if ($num_warnings > 0)
{
print STDERR "warning: found $num_warnings warning(s)\n";
exit 128 if $opt_W;
}
exit 0;
#############################################################################
#
# Parser code.
#
##
# Source-Code position Class
#
package Src_pos;
use overload
'+' => sub { return bless ({ %{$_[0]}, line => $_[0]->{line} + 1 }, __PACKAGE__); },
'-' => sub { return bless ({ %{$_[0]}, line => $_[0]->{line} - 1 }, __PACKAGE__); },
'++' => sub { ++$_[0]->{line}; },
'--' => sub { --$_[0]->{line}; },
'+=' => sub { $_[0]->{line} += $_[1]; },
'-=' => sub { $_[0]->{line} -= $_[1]; },
'=' => sub { return bless ({ %{$_[0]} }, __PACKAGE__); };
sub new
{
my ($class, $arg0, $arg1) = @_;
return bless { %$arg0 }, $class
if ref($arg0) eq __PACKAGE__;
$arg0 = '' unless defined $arg0;
$arg1 = 0 unless defined $arg1;
return bless {
file => $arg0,
line => $arg1,
part => '',
part_ext => '',
section => '',
}, $class;
}
sub clone
{ return bless { %{$_[0]} }, __PACKAGE__; }
sub line
{ return $_[0]->{line}; }
sub file
{ return $_[0]->{file}; }
sub to_string
{ return $_[0]->{file}.':'.$_[0]->{line}; }
sub to_line_directive
{ return "\n#line " . $_[0]->{line} . " \"" . $_[0]->{file} . "\"\n"; }
sub in_implementation
{ return $_[0]->{section} eq 'IMPLEMENTATION'; }
sub in_interface
{ return $_[0]->{section} eq 'INTERFACE'; }
package main;
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 $exception_spec; # exception spec for function declarations
my $ctor_initializer; # ctor initializer ' : foo(), bar() '
my $attribute_spec; # attribute specification
my $src_pos; # current source position (file and line)
my $block_pos; # file and start position of the currently parsed block
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};
my $dyn_exception_spec = qr {\b throw $s \( $paren_expr \) }sx;
my $noexcept_spec = qr {\b noexcept (?:$s \( $paren_expr \))? }sx;
$exception_spec = qr { (?:$dyn_exception_spec) | (?:$noexcept_spec) }sx;
my $member_initializer = qr { $s $identifier (?:$s $template_arg)? $s \( $paren_expr \) }sx;
$ctor_initializer = qr { (? $cp = $match\n";
if (!defined $match)
{
error($src_pos, "syntax error in tag '$tag'");
}
if (($verbose || $opt_d) && (!defined $match || !$match))
{
print "Drop SECTION: [$orig_tag] from file " . $src_pos->file . "\n";
}
return $match;
}
sub parse_file
{
$src_pos = Src_pos->new($ARGV[0], 0);
@ifstack = ();
my $current_section = "IMPLEMENTATION";
my $current_part = "";
my $skip_to_next_section = 0;
NEXTLINE:
while (1)
{
#print "PARSED: $_\n";
$_ = '';
$block_pos = $src_pos + 1;
$block_pos->{section} = $current_section;
$block_pos->{part} = $current_part;
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;
$impl_parts{$current_part} = 1 unless $opt_s;
if ($opt_e ne '')
{
$skip_to_next_section = !match_e_opt($current_part);
next NEXTLINE if $skip_to_next_section;
}
}
else
{
$current_part = '';
$impl_parts{$current_part} = 1 unless $opt_s;
}
}
else
{
if (defined $3 && $opt_h ne $3)
{
error($src_pos, "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;
}
# top-level namespace declaration
if (/^$s (?:inline $s)? namespace $s/sx)
{
handle_source_code ();
next NEXTLINE;
}
# 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)
{
handle_classdef(name => $name,
syntax => $syntax,
pretext => $1,
posttext => $2);
#print "CLASS " . $class->{name} . ">" .$class->{pretext} . "###" . $class->{posttext};
}
}
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)
{
handle_classdef(syntax => 'forwarddecl',
name => $1);
}
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;
handle_classdef(syntax => 'typedef',
name => $1);
}
else
{
error($src_pos, "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})
{
error($src_pos, "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} .= $block_pos->to_line_directive;
}
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;
}
# Function definition ?
if (/^([^\{;]*)((?:\{.*)|(?:;[^\{;]*))$/sx)
{
#print "\n\nMATCH:<$1><$2>\n";
my $pretext = $1;
my %func_decl = (
posttext => $2,
class => '',
class_templateargs => '',
func_templateargs => '',
virt_spec => ''
);
if ($func_decl{posttext} =~ s/^$s;//sx)
{
# put pure-viretual default and delete specs into posttext
if ($pretext =~ s/(.*)(?: = $s )(0|default|delete)$s$/$1/sx)
{
$func_decl{assign_spec} = $2;
}
else
{
goto not_a_function; # skip non-definitions
}
}
# move ctor initializer list to posttext
$func_decl{posttext} = $2 . $func_decl{posttext}
if $pretext =~ s/^(.*) ($ctor_initializer $s)$/$1/sx;
my $func_specifiers = '';
# move final and override specifiers into func_specifiers
$func_decl{virt_spec} = ' ' . $2 .$func_decl{virt_spec}
while $pretext =~ s/(.*)(\b $s (?:final|override))($s)$/$1$3/sx;
# move attribute spec into func_specifiers
$func_specifiers = $2 . $func_specifiers
while $pretext =~ s/(.*)($s $attribute_spec $s)$/$1/sx;
# move throw and noexcept specifiers into func_specifiers
$func_specifiers = $2 . $func_specifiers
if $pretext =~ s/(.*)($s $exception_spec $s)$/$1/sx;
my $name;
if ($pretext =~ s/^(.*?)\b
($operator_name | ~? $identifier) # member name
($s $template_arg)? # member template args
($s \( $paren_expr \)) # args
([^()]*) # remainder
$/$1/sx)
{
$name = $2;
$func_decl{func_templateargs} = $3 if defined $3;
$func_decl{args} = $4 . $5 . $func_specifiers;
# handle dtors
$name = '~' . $name
if $pretext =~ s/~$//sx;
}
print "\nFUNC 0:\nPRETEXT\n<$pretext>\n".
"CLASS\n<$func_decl{class}>\nCLASS_TMPL<$func_decl{class_templateargs}>\n::\n" .
"NAME\n<$name>\nFUNC_TMPL\n<$func_decl{func_templateargs}>\nARGS\n<$func_decl{args}>\n" .
"POSTTEXT\n<$func_decl{posttext}>\n"
if $verbose;
goto not_a_function unless defined $name;
goto not_a_function
unless ($pretext =~ /\btemplate\b/sx) || ! ($pretext =~ /=/sx);
if ($pretext =~ s/^
(.*?) \b
((?:$identifier (?:$s $template_arg)? $s :: $s)* $identifier) ($s $template_arg)? $s :: $s
$/$1/sx)
{
$func_decl{class} = $2 if defined $2;
$func_decl{class_templateargs} = $3 if defined $3;
}
goto not_a_function
if $func_decl{class} eq '' && $pretext =~ /^$s$/;
$name =~ s/(?<=\w)(?:$s_once)+(?=\W)//gs;
$name =~ s/(?<=\W)(?:$s_once)+(?=\w)//gs;
$name =~ s/(?:$s_once)+/ /gs;
$func_decl{class} =~ s/$s//gs;
handle_function(%func_decl, pretext => $pretext, name => $name);
next NEXTLINE;
not_a_function:
}
handle_source_code ();
next NEXTLINE;
}
}
if (! /^$s$/s)
{
$verbose && print "EOF: " . $_ . "\n";
error($block_pos, "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();
do
{
my $file;
return 0 unless $file = shift @ARGV;
$src_pos = Src_pos->new($file, 0);
open(INPUT, $file) || die "Cannot open $file for reading!";
if ($opt_s)
{
# print "FILE: $file\n";
my $part_ext = '';
if ($file =~ /^(?:.*\/)?(.+)$/ && $1 =~ /(?:[^-]*)-(.*)\..*/)
{
$part_ext = $1;
}
$src_pos->{part_ext} = $part_ext;
$impl_parts{$part_ext} = 1;
# print "PART: '$part_ext'\n";
}
print "read file: '$file'\n" if $verbose;
}
while(eof(INPUT));
}
++$src_pos;
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 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, pos => $src_pos };
$verbose && print "IF 0: " . ignoring() . "\n";
return;
}
elsif (@ifstack && /^$s\#\s*if(def|ndef)?\s/)
{
push @ifstack, { value => 0, pos => $src_pos };
$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 = Chunk->new($block_pos, 'include', $_, name => $1, inline => 0);
$includes{$codeblock->{name}} = $codeblock;
$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->{pos}->to_string."\n";
$indent .= ' ';
}
}
sub check_empty_ifstack
{
if ($#ifstack >= 0)
{
my $i = pop @ifstack;
error($src_pos, "missing endif for " . $i->{pos}->to_string);
}
}
sub handle_source_code
{
return if /^[\s\n]*$/;
my $codeblock = Chunk->new($block_pos, 'code', $_);
$verbose && print "UNKNOWN: " . $codeblock->{string};
}
sub handle_classdef
{
my $class = Chunk->new($block_pos, '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 = Chunk->new($block_pos, 'function', undef, @_);
if ($func->is_member)
{
# Nested class or namespace hacks
if ($func->{class} =~ /::/
&& ! defined $classes{$func->{class}})
{
# Define class along the way -- the dirty way.
my ($topclass, $rest) = split (/::/, $func->{class});
my $save_pos = $block_pos->clone;
$block_pos->{section} = $classes{$topclass}->{section}
if defined $classes{$topclass};
handle_classdef(name => $func->{class},
syntax => "class",
nested_class => 1);
$block_pos = $save_pos;
}
# handle visibility specifiers for member functions
my $visibility_re = join('|', keys %visibility_spec);
$func->{visibility} = "private";
if (s/^($s)($visibility_re)([\s\n])/$1$3/s)
{
my $vis_spec = $2;
my $vis = $visibility_spec{$vis_spec};
foreach my $x (keys %$vis)
{
$func->{$x} = $vis->{$x};
}
$func->{pretext} =~ s|$vis_spec[ \t]*||s;
}
if ($func->{class} =~ /::/
&& $func->{visibility} ne "implementation_only")
{
error($src_pos, "Limitation: Only predeclared members " .
"supported for nested classes. Use IMPLEMENT");
}
if (! defined $classes{$func->{class}})
{
error($src_pos, "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->is_member) # 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;
}
}
error($src_pos, "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->is_member)
{
$func->{visibility} = "static";
$func->{hide} = 1;
}
next;
}
if (! $func->is_member)
{
# handle visibility specifiers for global functions (IMPLEMENT*)
my $visibility_re = join('|', @global_visibility_spec);
if (s/^($s)($visibility_re)([\s\n])/$1$3/s)
{
my $vis_spec = $2;
$func->{pretext} =~ s/$vis_spec[ \t]*//s;
my $vis = $visibility_spec{$vis_spec};
foreach my $x (keys %$vis)
{
$func->{$x} = $vis->{$x};
}
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])/)
{
error($block_pos,
"only one visibility attribute allowed at start of declaration");
}
last;
}
$func->{string} = $_;
if ($src_pos->in_interface)
{
error($src_pos, "Function " . $func->full_name . " in INTERFACE section");
}
my $func_name = $func->full_name;
if (defined $functions{$func_name})
{
my $f = $functions{$func_name};
if (defined $f->{default_impl})
{
$f->{printed} = 1; # skip the default implementation
$func->{overrides_default} = $f;
unless (defined $func->{override_impl})
{
warning($src_pos, "Function $func_name overrides an"
." IMPLEMENT_DEFAULT function but does not use"
." IMPLEMENT_OVERRIDE");
}
}
elsif (defined $func->{default_impl})
{
$func->{printed} = 1;
$f->{overrides_default} = $func;
$functions{$func_name} = $func; # remember ths default for later
unless (defined $f->{override_impl})
{
warning($f->pos, "Function ".$f->full_name." overrides an "
."IMPLEMENT_DEFAULT function but does "
."not use IMPLEMENT_OVERRIDE");
}
}
}
else
{
$functions{$func_name} = $func;
}
push @{$classes{$func->{class}}->{funcs}}, $func;
$verbose && print "FUNC: " . ($func->is_member
? ($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->{virt_spec};
$e .= defined $func->{assign_spec} ? " = " . $func->{assign_spec} : '';
$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};
}
}
sub print_funcdef($)
{
my $function = $_[0];
my $func_name = $function->{name};
$func_name = "$function->{class}::$func_name" if $function->is_member;
error($function->pos, "No default implementation for $func_name found")
if defined $function->{override_impl} &&
!defined $function->{overrides_default};
return if defined $function->{assign_spec}; # skip pure virtual, or = default, or = delete
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->is_member)
{
$pretext =~ s/^($s)/${1}static /s;
}
my $expr = qr { [^<>,]* (?:$template_arg)? [^<>,]* }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};
$expr = qr{ [^(),]* (?:$paren_group)? [^(),]* }x;
$args =~ s/$s = $expr//gx;
$args =~ s/($s(?:override|final))+($s)$/$2/g;
print_expand "\n";
print_lineno $function;
print_expand $pretext
. ($function->is_member
? $function->{class} . $function->{class_templateargs}
. "::" . $function->{name}
: $function->{name})
. $function->{func_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}};
error($context->pos, "Cannot find $item")
if (scalar @xgrepresult == 0);
$wno_inline && warning($context->pos, "Cannot find inline code for $item");
}
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})
{
error($function->pos, "Nonhidden function " . $function->full_name .
" depends on hidden function " . $item->full_name . " (" .
($item->{visibility} eq 'static' ? "static" : "NOEXPORT") . ")");
}
return exists $public_inline{$item}
|| (($function->{visibility} eq 'private'
|| ($function->is_member
&& $classes{$function->{class}}->in_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 " . $function->full_name . " 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->is_member)
{
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 = " " . $function->full_name . "\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 .= " " . $j->full_name . "\n";
$prev = $j;
last if $j == $function;
}
error($object->pos, "Function " . $object->full_name . " NEEDS "
. $i->full_name .", which circularly depends on this function:\n"
. $callstack);
}
unshift @needed, $i;
next NEEDEDLOOP;
}
$verbose && print $object->full_name . " ";
if ($function->{hide})
{
$unit_inline{$object} = 1;
push @inline_order_unit, $object;
}
elsif ($function->{visibility} eq 'private'
|| ($class ne ''
&& $classes{$class}->in_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->in_interface)
{
print_classdecl $object;
}
}
foreach my $object (grep {$_->{type} eq "function"} @_)
{
print_funcdef $object;
}
}
######################################################################
#
# Utilities
#
sub warning
{
my ($pos, $text) = @_;
print STDERR $pos->to_string . ": warning: $text\n";
$num_warnings++;
}
sub error
{
my ($pos, $text) = @_;
print STDERR $pos->to_string . ": error: $text\n";
$num_errors++;
exit 128;
}