- Minor fixes and reorganizations.

- Added checks for documentation inconsistencies.
This commit is contained in:
Patrik Stridvall 1999-10-31 02:08:38 +00:00 committed by Alexandre Julliard
parent 2c92835f7b
commit 659fcd81df
8 changed files with 338 additions and 92 deletions

View File

@ -8,14 +8,19 @@ sub new {
my $self = {};
bless ($self, $class);
my $output = \${$self->{OUTPUT}};
my $functions = \%{$self->{FUNCTIONS}};
my $conditionals = \%{$self->{CONDITIONALS}};
my $conditional_headers = \%{$self->{CONDITIONAL_HEADERS}};
my $conditional_functions = \%{$self->{CONDITIONAL_FUNCTIONS}};
$$output = shift;
my $api_file = shift;
my $configure_in_file = shift;
my $config_h_in_file = shift;
$$output->progress("$api_file");
open(IN, "< $api_file");
$/ = "\n";
while(<IN>) {
@ -27,6 +32,8 @@ sub new {
}
close(IN);
$$output->progress("$configure_in_file");
my $again = 0;
open(IN, "< $configure_in_file");
local $/ = "\n";
@ -46,8 +53,13 @@ sub new {
if(/^AC_CHECK_HEADERS\(\s*(.*?)\)\s*$/) {
my @arguments = split(/,/,$1);
foreach my $header (split(/\s+/, $arguments[0])) {
$$conditional_headers{$header}++;
foreach my $name (split(/\s+/, $arguments[0])) {
$$conditional_headers{$name}++;
}
} elsif(/^AC_CHECK_FUNCS\(\s*(.*?)\)\s*$/) {
my @arguments = split(/,/,$1);
foreach my $name (split(/\s+/, $arguments[0])) {
$$conditional_functions{$name}++;
}
} elsif(/^AC_FUNC_ALLOCA/) {
$$conditional_headers{"alloca.h"}++;
@ -56,10 +68,12 @@ sub new {
}
close(IN);
$$output->progress("$config_h_in_file");
open(IN, "< $config_h_in_file");
local $/ = "\n";
while(<IN>) {
if(/^\#undef (\S+)$/) {
if(/^\#undef\s+(\S+)$/) {
$$conditionals{$1}++;
}
}
@ -95,4 +109,13 @@ sub is_conditional_header {
return $$conditional_headers{$name};
}
sub is_conditional_function {
my $self = shift;
my $conditional_functions = \%{$self->{CONDITIONAL_FUNCTIONS}};
my $name = shift;
return $$conditional_functions{$name};
}
1;

View File

@ -70,12 +70,12 @@ sub parse_api_file {
if(!$forbidden) {
if(defined($module)) {
if($$allowed_modules_unlimited{$type}) {
print "$file: type ($type) already specificed as an unlimited type\n";
$$output->write("$file: type ($type) already specificed as an unlimited type\n");
} elsif(!$$allowed_modules{$type}{$module}) {
$$allowed_modules{$type}{$module} = 1;
$$allowed_modules_limited{$type} = 1;
} else {
print "$file: type ($type) already specificed\n";
$$output->write("$file: type ($type) already specificed\n");
}
} else {
$$allowed_modules_unlimited{$type} = 1;
@ -84,12 +84,12 @@ sub parse_api_file {
$$allowed_modules_limited{$type} = 1;
}
if(defined($$translate_argument{$type}) && $$translate_argument{$type} ne $kind) {
print "$file: type ($type) respecified as different kind ($kind != $$translate_argument{$type})\n";
$$output->write("$file: type ($type) respecified as different kind ($kind != $$translate_argument{$type})\n");
} else {
$$translate_argument{$type} = $kind;
}
} else {
print "$file: file must begin with %<type> statement\n";
$$output->write("$file: file must begin with %<type> statement\n");
exit 1;
}
}
@ -208,7 +208,7 @@ sub parse_spec_file {
if(defined($ordinal)) {
if($ordinals{$ordinal}) {
print "$file: ordinal redefined: $_\n";
$$output->write("$file: ordinal redefined: $_\n");
}
$ordinals{$ordinal}++;
}

View File

@ -6,6 +6,7 @@ use strict;
my $wine_dir;
my $winapi_check_dir;
BEGIN {
if($0 =~ /^((.*?)\/?tools\/winapi_check)\/winapi_check$/)
@ -24,6 +25,7 @@ BEGIN {
require "output.pm";
require "preprocessor.pm";
require "winapi.pm";
require "winapi_function.pm";
require "winapi_local.pm";
require "winapi_global.pm";
require "winapi_options.pm";
@ -33,6 +35,7 @@ BEGIN {
import output;
import preprocessor;
import winapi;
import winapi_function;
import winapi_local;
import winapi_global;
import winapi_options;
@ -51,7 +54,7 @@ my $win16api = 'winapi'->new($output, "win16", "$winapi_check_dir/win16api.dat",
my $win32api = 'winapi'->new($output, "win32", "$winapi_check_dir/win32api.dat", "$winapi_check_dir/win32");
'winapi'->read_spec_files($wine_dir, $win16api, $win32api);
my $nativeapi = 'nativeapi'->new("$winapi_check_dir/nativeapi.dat", "$wine_dir/configure.in", "$wine_dir/include/config.h.in");
my $nativeapi = 'nativeapi'->new($output, "$winapi_check_dir/nativeapi.dat", "$wine_dir/configure.in", "$wine_dir/include/config.h.in");
for my $name ($win32api->all_functions) {
my $module16 = $win16api->function_module($name);
@ -62,7 +65,7 @@ for my $name ($win32api->all_functions) {
$win32api->found_shared_function($name);
if($options->shared) {
print "*.spec: $name: is shared between $module16 (Win16) and $module32 (Win32)\n";
$output->write("*.spec: $name: is shared between $module16 (Win16) and $module32 (Win32)\n");
}
}
}
@ -86,19 +89,21 @@ my %includes;
}
}
my %functions;
my $progress_output;
my $progress_current=0;
my $progress_max=scalar($options->files);
foreach my $file ($options->files) {
my %functions;
$progress_current++;
if($options->progress) {
$output->progress("$file: file $progress_current of $progress_max");
}
my $file_dir = $file;
$file_dir =~ s/(.*?)\/[^\/]*$/$1/;
if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) {
$file_dir = ".";
}
my $file_type;
if($file_dir =~ /^(libtest|program|rc)/) {
@ -112,6 +117,7 @@ foreach my $file ($options->files) {
}
my $found_function = sub {
my $documentation = shift;
my $return_type = shift;
my $calling_convention = shift;
my $name = shift;
@ -135,16 +141,19 @@ foreach my $file ($options->files) {
my $module16 = $win16api->function_module($name);
my $module32 = $win32api->function_module($name);
my $module;
if(defined($module16) && defined($module32)) {
$module = "$module16 & $module32";
} elsif(defined($module16)) {
$module = $module16;
} elsif(defined($module32)) {
$module = $module32;
} else {
$module = "";
}
my $function = 'winapi_function'->new;
$functions{$name} = $function;
$function->documentation($documentation);
$function->file($file);
$function->return_type($return_type);
$function->calling_convention($calling_convention);
$function->name($name);
$function->arguments([@arguments]);
$function->statements($statements);
$function->module16($module16);
$function->module32($module32);
my $output_module = sub {
my $module = shift;
return sub {
@ -157,22 +166,13 @@ foreach my $file ($options->files) {
my $output16 = &$output_module($module16);
my $output32 = &$output_module($module32);
my $function = $functions{$name};
$$function{file} = $file;
$$function{return_type} = $return_type;
$$function{calling_convention} = $calling_convention;
$$function{arguments} = [@arguments];
$$function{module} = $module;
$$function{module16} = $module16;
$$function{module32} = $module32;
if($options->argument) {
if($options->win16 && $options->report_module($module16)) {
winapi_local::check_arguments $options, $output16,
winapi_local::check_function $options, $output16,
$return_type, $calling_convention, $name, [@arguments], $win16api;
}
if($options->win32 && $options->report_module($module32)) {
winapi_local::check_arguments $options, $output32,
winapi_local::check_function $options, $output32,
$return_type, $calling_convention, $name, [@arguments], $win32api;
}
}
@ -202,14 +202,42 @@ foreach my $file ($options->files) {
$_ = $';
my $called_name = $1;
if($called_name !~ /^if|for|while|switch|sizeof$/) {
$functions{$name}{called_function_names}{$called_name}++;
$functions{$called_name}{called_by_function_names}{$name}++;
$functions{$name}->function_called($called_name);
if(!defined($functions{$called_name})) {
$functions{$called_name} = 'function'->new;
}
$functions{$called_name}->function_called_by($name);
}
} else {
undef $_
}
}
}
if($options->documentation && (defined($module16) || defined($module32))) {
my $name2 = $name;
my $name3 = $name;
my $name4 = $name;
my $name5 = $name;
if(defined($module16) && !defined($module32)) {
$name2 =~ s/([AW])$/16$1/;
$name3 =~ s/16(([AW])?)$/$1/;
$name4 =~ s/^\U$module16\E\_//;
$name5 =~ s/^WIN16_//;
} elsif(!defined($module16) && defined($module32)) {
$name2 =~ s/([AW])$/32$1/;
$name3 =~ s/32(([AW])?)$/$1/;
$name4 =~ s/^\U$module32\E\_//;
$name5 =~ s/^WIN16_//;
}
if($name !~ /^SMapLS|SUnMapLS/ && $documentation !~ /($name|$name2|$name3|$name4|$name5)/) {
$output->write("$file: $name: \\\n");
$output->write("$documentation\n");
}
}
}
};
@ -278,7 +306,7 @@ foreach my $file ($options->files) {
foreach my $name (keys(%{$includes{"$file_dir/$header"}{includes}})) {
$includes{$name}{used}++;
}
} elsif(-e "include/$header") {
} elsif(-e "$wine_dir/include/$header") {
$includes{"include/$header"}{used}++;
foreach my $name (keys(%{$includes{"include/$header"}{includes}})) {
$includes{$name}{used}++;
@ -291,7 +319,7 @@ foreach my $file ($options->files) {
}
};
winapi_parser::parse_c_file $options, $file, $found_function, $found_preprocessor;
winapi_parser::parse_c_file $options, $output, $file, $found_function, $found_preprocessor;
if($options->config_unnessary) {
if($config && $conditional == 0) {
@ -299,26 +327,7 @@ foreach my $file ($options->files) {
}
}
if($options->cross_call) {
my @names = sort(keys(%functions));
for my $name (@names) {
my @called_names = sort(keys(%{$functions{$name}{called_function_names}}));
my @called_by_names = sort(keys(%{$functions{$name}{called_by_function_names}}));
my $module = $functions{$name}{module};
my $module16 = $functions{$name}{module16};
my $module32 = $functions{$name}{module32};
if($#called_names >= 0 && (defined($module16) || defined($module32)) ) {
$output->write("$file: $module: $name: \\\n");
for my $called_name (@called_names) {
my $function;
if($function = $functions{$called_name}) {
$output->write(" $called_name\n");
}
}
}
}
}
winapi_local::check_file $options, $output, $file, \%functions;
}
$output->hide_progress;
@ -327,12 +336,12 @@ if($options->global) {
foreach my $name (sort(keys(%includes))) {
if(!$includes{$name}{used}) {
if($options->include) {
print "$name: include file is never used\n";
$output->write("$name: include file is never used\n");
}
}
}
winapi_global::check $options, $win16api, $nativeapi if $options->win16;
winapi_global::check $options, $win32api, $nativeapi if $options->win32;
winapi_global::check $options, $output, $win16api, $nativeapi if $options->win16;
winapi_global::check $options, $output, $win32api, $nativeapi if $options->win32;
}

View File

@ -0,0 +1,161 @@
package winapi_function;
use strict;
sub new {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
bless ($self, $class);
return $self;
}
sub file {
my $self = shift;
my $file = \${$self->{FILE}};
local $_ = shift;
if(defined($_)) { $$file = $_; }
return $$file;
}
sub documentation {
my $self = shift;
my $documentation = \${$self->{DOCUMENTATION}};
local $_ = shift;
if(defined($_)) { $$documentation = $_; }
return $$documentation;
}
sub return_type {
my $self = shift;
my $return_type = \${$self->{RETURN_TYPE}};
local $_ = shift;
if(defined($_)) { $$return_type = $_; }
return $$return_type;
}
sub calling_convention {
my $self = shift;
my $calling_convention = \${$self->{CALLING_CONVENTION}};
local $_ = shift;
if(defined($_)) { $$calling_convention = $_; }
return $$calling_convention;
}
sub name {
my $self = shift;
my $name = \${$self->{NAME}};
local $_ = shift;
if(defined($_)) { $$name = $_; }
return $$name;
}
sub arguments {
my $self = shift;
my $arguments = \${$self->{ARGUMENTS}};
local $_ = shift;
if(defined($_)) { $$arguments = $_; }
return $$arguments;
}
sub module16 {
my $self = shift;
my $module16 = \${$self->{MODULE16}};
local $_ = shift;
if(defined($_)) { $$module16 = $_; }
return $$module16;
}
sub module32 {
my $self = shift;
my $module32 = \${$self->{MODULE32}};
local $_ = shift;
if(defined($_)) { $$module32 = $_; }
return $$module32;
}
sub statements {
my $self = shift;
my $statements = \${$self->{STATEMENTS}};
local $_ = shift;
if(defined($_)) { $$statements = $_; }
return $$statements;
}
sub module {
my $self = shift;
my $module16 = \${$self->{MODULE16}};
my $module32 = \${$self->{MODULE32}};
my $module;
if(defined($$module16) && defined($$module32)) {
$module = "$$module16 & $$module32";
} elsif(defined($$module16)) {
$module = $$module16;
} elsif(defined($$module32)) {
$module = $$module32;
} else {
$module = "";
}
}
sub function_called {
my $self = shift;
my $called_function_names = \%{$self->{CALLED_FUNCTION_NAMES}};
my $name = shift;
$$called_function_names{$name}++;
}
sub function_called_by {
my $self = shift;
my $called_by_function_names = \%{$self->{CALLED_BY_FUNCTION_NAMES}};
my $name = shift;
$$called_by_function_names{$name}++;
}
sub called_function_names {
my $self = shift;
my $called_function_names = \%{$self->{CALLED_FUNCTION_NAMES}};
return sort(keys(%$called_function_names));
}
sub called_by_function_names {
my $self = shift;
my $called_by_function_names = \%{$self->{CALLED_BY_FUNCTION_NAMES}};
return sort(keys(%$called_by_function_names));
}
1;

View File

@ -4,6 +4,7 @@ use strict;
sub check {
my $options = shift;
my $output = shift;
my $winapi = shift;
my $nativeapi = shift;
@ -11,9 +12,9 @@ sub check {
if($options->argument) {
foreach my $type ($winapi->all_declared_types) {
if(!$winapi->type_found($type) && !$winapi->is_type_limited($type) && $type ne "CONTEXT86 *") {
print "*.c: $winver: ";
print "type ($type) not used\n";
if(!$winapi->type_found($type) && !$winapi->is_limited_type($type) && $type ne "CONTEXT86 *") {
$output->write("*.c: $winver: ");
$output->write("type ($type) not used\n");
}
}
}
@ -22,8 +23,8 @@ sub check {
foreach my $name ($winapi->all_functions) {
if(!$winapi->function_found($name) && !$nativeapi->is_function($name)) {
my $module = $winapi->function_module($name);
print "*.c: $module: $name: ";
print "function declared but not implemented: " . $winapi->function_arguments($name) . "\n";
$output->write("*.c: $module: $name: ");
$output->write("function declared but not implemented: " . $winapi->function_arguments($name) . "\n");
}
}
}
@ -33,10 +34,9 @@ sub check {
foreach my $module (sort(keys(%$not_used))) {
foreach my $type (sort(keys(%{$$not_used{$module}}))) {
print "*.c: $module: type $type not used\n";
$output->write("*.c: $module: type $type not used\n");
}
}
}
}

View File

@ -2,7 +2,7 @@ package winapi_local;
use strict;
sub check_arguments {
sub check_function {
my $options = shift;
my $output = shift;
my $return_type = shift;
@ -163,5 +163,36 @@ sub check_arguments {
}
}
sub check_file {
my $options = shift;
my $output = shift;
my $file = shift;
my $functions = shift;
if($options->cross_call) {
my @names = sort(keys(%$functions));
for my $name (@names) {
my @called_names = $$functions{$name}->called_function_names;
my @called_by_names = $$functions{$name}->called_by_function_names;
my $module = $$functions{$name}->module;
my $module16 = $$functions{$name}->module16;
my $module32 = $$functions{$name}->module32;
if($#called_names >= 0 && (defined($module16) || defined($module32)) ) {
for my $called_name (@called_names) {
my $called_module16 = $$functions{$called_name}->module16;
my $called_module32 = $$functions{$called_name}->module32;
if(defined($module32) &&
defined($called_module16) && !defined($called_module32) &&
$name ne $called_name)
{
$output->write("$file: $module: $name: illegal call to $called_name (Win16)\n");
}
}
}
}
}
}
1;

View File

@ -59,6 +59,7 @@ my %options = (
"calling-convention" => { default => 0, parent => "local", description => "calling convention checking" },
"misplaced" => { default => 0, parent => "local", description => "check for misplaced functions" },
"cross-call" => { default => 0, parent => "local", description => "check for cross calling functions" },
"documentation" => { default => 0, parent => "local", description => "check for documentation inconsistances\n" },
"global" => { default => 1, description => "global checking" },
"declared" => { default => 1, parent => "global", description => "declared checking" },

View File

@ -4,10 +4,12 @@ use strict;
sub parse_c_file {
my $options = shift;
my $output = shift;
my $file = shift;
my $function_found_callback = shift;
my $preprocessor_found_callback = shift;
my $documentation;
my $return_type;
my $calling_convention;
my $function = "";
@ -15,6 +17,7 @@ sub parse_c_file {
my $statements;
my $function_begin = sub {
$documentation = shift;
$return_type= shift;
$calling_convention = shift;
$function = shift;
@ -23,11 +26,12 @@ sub parse_c_file {
$statements = "";
};
my $function_end = sub {
&$function_found_callback($return_type,$calling_convention,$function,$arguments,$statements);
&$function_found_callback($documentation,$return_type,$calling_convention,$function,$arguments,$statements);
$function = "";
};
my @comments = ();
my $level = 0;
my $again = 0;
my $lookahead = 0;
@ -54,8 +58,14 @@ sub parse_c_file {
$again = 0;
}
# Merge conflicts in file?
if(/^(<<<<<<<|=======|>>>>>>>)/) {
$output->write("$file: merge conflicts in file\n");
last;
}
# remove comments
if(s/^(.*?)\/\*.*?\*\/(.*)$/$1 $2/s) { $again = 1; next };
if(s/^(.*?)(\/\*.*?\*\/)(.*)$/$1 $3/s) { push @comments, $2; $again = 1; next };
if(/^(.*?)\/\*/s) {
$lookahead = 1;
next;
@ -80,6 +90,17 @@ sub parse_c_file {
}
}
my $documentation;
{
my $n = $#comments;
while($n >= 0 && $comments[$n] !~ /\/\*\*/) { $n-- }
if(defined($comments[$n]) && $n >= 0) {
$documentation = $comments[$n];
} else {
$documentation = "";
}
}
if($level > 0)
{
my $line;
@ -156,65 +177,65 @@ sub parse_c_file {
if($options->debug) {
print "$file: $return_type $calling_convention $name(" . join(",", @arguments) . ")\n";
}
&$function_begin($return_type,$calling_convention,$name,\@arguments);
&$function_begin($documentation,$return_type,$calling_convention,$name,\@arguments);
} elsif(/DC_(GET_X_Y|GET_VAL_16)\s*\(\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*\)/s) {
$_ = $'; $again = 1;
my @arguments = ("HDC16");
&$function_begin($2, "WINAPI", $3, \@arguments);
&$function_begin($documentation,$2, "WINAPI", $3, \@arguments);
&$function_end;
} elsif(/DC_(GET_VAL_32)\s*\(\s*(.*?)\s*,\s*(.*?)\s*,.*?\)/s) {
$_ = $'; $again = 1;
my @arguments = ("HDC");
&$function_begin($2, "WINAPI", $3, \@arguments);
&$function_begin($documentation,$2, "WINAPI", $3, \@arguments);
&$function_end;
} elsif(/DC_(GET_VAL_EX)\s*\(\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*\)/s) {
$_ = $'; $again = 1;
my @arguments16 = ("HDC16", "LP" . $5 . "16");
my @arguments32 = ("HDC", "LP" . $5);
&$function_begin("BOOL16", "WINAPI", $2 . "16", \@arguments16);
&$function_begin($documentation,"BOOL16", "WINAPI", $2 . "16", \@arguments16);
&$function_end;
&$function_begin("BOOL", "WINAPI", $2, \@arguments32);
&$function_begin($documentation,"BOOL", "WINAPI", $2, \@arguments32);
&$function_end;
} elsif(/DC_(SET_MODE)\s*\(\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*\)/s) {
$_ = $'; $again = 1;
my @arguments16 = ("HDC16", "INT16");
my @arguments32 = ("HDC", "INT");
&$function_begin("INT16", "WINAPI", $2 . "16", \@arguments16);
&$function_begin($documentation,"INT16", "WINAPI", $2 . "16", \@arguments16);
&$function_end;
&$function_begin("INT", "WINAPI", $2, \@arguments32);
&$function_begin($documentation,"INT", "WINAPI", $2, \@arguments32);
&$function_end;
} elsif(/WAVEIN_SHORTCUT_0\s*\(\s*(.*?)\s*,\s*(.*?)\s*\)/s) {
$_ = $'; $again = 1;
my @arguments16 = ("HWAVEIN16");
my @arguments32 = ("HWAVEIN");
&$function_begin("UINT16", "WINAPI", "waveIn" . $1 . "16", \@arguments16);
&$function_begin($documentation,"UINT16", "WINAPI", "waveIn" . $1 . "16", \@arguments16);
&$function_end;
&$function_begin("UINT", "WINAPI", "waveIn" . $1, \@arguments32);
&$function_begin($documentation,"UINT", "WINAPI", "waveIn" . $1, \@arguments32);
&$function_end;
} elsif(/WAVEOUT_SHORTCUT_0\s*\(\s*(.*?)\s*,\s*(.*?)\s*\)/s) {
$_ = $'; $again = 1;
my @arguments16 = ("HWAVEOUT16");
my @arguments32 = ("HWAVEOUT");
&$function_begin("UINT16", "WINAPI", "waveOut" . $1 . "16", \@arguments16);
&$function_begin($documentation,"UINT16", "WINAPI", "waveOut" . $1 . "16", \@arguments16);
&$function_end;
&$function_begin("UINT", "WINAPI", "waveOut" . $1, \@arguments32);
&$function_begin($documentation,"UINT", "WINAPI", "waveOut" . $1, \@arguments32);
&$function_end;
} elsif(/WAVEOUT_SHORTCUT_(1|2)\s*\(\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*\)/s) {
$_ = $'; $again = 1;
if($1 eq "1") {
my @arguments16 = ("HWAVEOUT16", $4);
my @arguments32 = ("HWAVEOUT", $4);
&$function_begin("UINT16", "WINAPI", "waveOut" . $2 . "16", \@arguments16);
&$function_begin($documentation,"UINT16", "WINAPI", "waveOut" . $2 . "16", \@arguments16);
&$function_end;
&$function_begin("UINT", "WINAPI", "waveOut" . $2, \@arguments32);
&$function_begin($documentation,"UINT", "WINAPI", "waveOut" . $2, \@arguments32);
&$function_end;
} elsif($1 eq 2) {
my @arguments16 = ("UINT16", $4);
my @arguments32 = ("UINT", $4);
&$function_begin("UINT16", "WINAPI", "waveOut". $2 . "16", \@arguments16);
&$function_begin($documentation,"UINT16", "WINAPI", "waveOut". $2 . "16", \@arguments16);
&$function_end;
&$function_begin("UINT", "WINAPI", "waveOut" . $2, \@arguments32);
&$function_begin($documentation,"UINT", "WINAPI", "waveOut" . $2, \@arguments32);
&$function_end;
}
} elsif(/;/s) {
@ -229,7 +250,7 @@ sub parse_c_file {
}
close(IN);
print STDERR "done\n" if $options->verbose;
print "$file: <>: not at toplevel at end of file\n" unless $level == 0;
$output->write("$file: not at toplevel at end of file\n") unless $level == 0;
}
1;