require 5.002;
$debug = 0;
$errs = $totalerrs = 0;
$goodfiles = $badfiles = $skipfiles = 0;
$filename = '';
%safemacros = (
'TH' => 5,
'B' => -1, 'BI' => -1, 'BR' => -1,
'I' => -1, 'IB' => -1, 'IR' => -1,
'RB' => -1, 'RI' => -1, 'SB' => -1, 'SM' => -1,
'SH' => 1,
'LP' => 0, 'P' => 0,
'PP' => 0,
'RS' => 1, 'RE' => 0,
'HP' => 1, 'IP' => 2, 'TP' => 1,
'DT' => 0, 'PD' => 1, 'SS' => 1,
'IX' => -1,
'UR' => 1,
'UN' => 1,
'UE' => 0,
'\\"' => -1, 'ps' => 1, 'ft' => 1, 'hy' => 1, 'bp' => 0, 'ne' => 1, 'br' => 0,
'nf' => 0, 'fi' => 0,
'ig' => 1,
'.' => 0, 'ce' => 1, 'ad' => 1,
'na' => 0,
'if' => -1, 'ie' => -1, 'el' => -1,
'so' => 1, 'sp' => 1, 'de' => 1, 'ds' => -1, 'in' => 1, 'ti' => 1, 'hy' => 1, 'nh' => 1, 'tr' => 1, );
%allowed_ft_parameter = (
'1' => 1,
'2' => 1,
'3' => 1,
'4' => 1,
'R' => 1,
'I' => 1,
'B' => 1,
'P' => 1,
'CW' => 1,
'' => 1,
);
%allowed_tr = (
'\\(ts"' => 1,
'\\(is\'' => 1,
'\\(if`' => 1,
'\\(pd"' => 1,
'\\(*W-|\(bv\*(Tr' => 1,
'\\*(Tr' => 1,
);
sub problem($) {
my $message = shift;
print "${ARGV}: $message\n";
$errs++;
}
sub clean_state {
%defined_macros = ();
$is_skipped = 0;
}
sub process_line {
my $macro;
my $parameters;
if (m/^[.']\s*([^\s]+)\s*(.*)?/) {
$macro=$1;
$parameters=$2;
$macro =~ s/\s//g;
print "Found macro: #${macro}#\n" if $debug;
if ($macro =~ m/Dd/) { # Is this the BSD macro set and not a tmac.an set?
problem("Uses BSD mandoc conventions instead of tmac.an");
$errs--; # Patch up error count.
# print "${ARGV}: Uses BSD mandoc conventions instead of tmac.an.\n";
close(ARGV); # Skip the rest of this file.
$is_skipped = 1;
return;
}
if ($macro =~ m/\\"/) {return;} # Skip troff comments.
if (exists($defined_macros{$macro})) {
return; # ??? Should examine the macro parameters.
}
if (exists($safemacros{$macro}) ) {
# ??? Check parameter count.
# ??? Check that .TH is the first macro (note: bash.1, etc., break this)
if ( ($macro eq 'if') || ($macro eq 'ie' )) {
# Only permit checking 't' or 'n' for now.
if ($parameters =~ m/^[tn]\s/) {
$_ = $parameters;
s/^[tn]\s+//;
process_line(); # Re-examine line without the if statement.
} else {
problem("unsafe use of if/ie");
}
# ??? sp: only no-parameter or positive values.
} elsif ($macro eq 'de') {
$parameters =~ m/^([^\s]+)/;
$is_defining = $1;
$defined_macros{$is_defining} = 1;
} elsif ($macro eq 'so') {
$parameters =~ m/^([^\s]+)/;
$new_file = $1;
while (<$new_file>) { process_line(); }
} elsif (($macro eq 'ft') && (defined($parameters))
&& (! exists($allowed_ft_parameter{$parameters}))) {
problem("forbidden ft parameter $parameters");
} elsif (($macro eq 'tr') && (defined($parameters))
&& (! exists($allowed_tr{$parameters}))) {
problem("forbidden tr parameter $parameters");
}
# ??? 'in': Require that every indent be paired with a negative indent.
# ??? For macros with text after them, check their text's escapes.
} else {
problem("unsafe macro $macro");
}
} else {
}
}
clean_state();
while (<>) {
if ($ARGV ne $filename) {
print "Processing $ARGV; up to now good=$goodfiles bad=$badfiles skip=$skipfiles\n";
$filename=$ARGV;
}
process_line();
} continue {
if (eof) { close ARGV; $totalerrs += $errs;
if ($errs) { $badfiles++ } else {
if ($is_skipped) {$skipfiles++} else {$goodfiles++};
}
$errs = 0;
clean_state();
}
}
print "Number of good files = $goodfiles\n";
print "Number of bad files = $badfiles\n";
print "Number of skipped files = $skipfiles\n";
exit $errs;