#!/usr/local/bin/perl # JPP - Java Pretty Printer # Simple parser and formatter for Java source code. No warranty. # Rolf Howarth -- Parallax Solutions Ltd. -- December 1996 # Email: rolf@parallax.co.uk # The following may be copied to a user's .jpp_prefs file and modified as required $newline = "\n"; $k_and_r = 0; $debug = 0; $tab = "\t"; $silent = 0; $flushLeftMethods = 0; if (-e "$ENV{'HOME'}/.jpp_prefs") { require "$ENV{'HOME'}/.jpp_prefs"; } while ($ARGV[0] =~ m,^-,) { $opt = shift @ARGV; if ($opt eq "-d") { $debug = 1; } elsif ($opt eq "--d") { $debug = 0; } elsif ($opt eq "-s") { $silent = 1; } elsif ($opt eq "-k") { $k_and_r = 1; } elsif ($opt eq "--k") { $k_and_r = 0; } elsif ($opt =~ m/^-t(\d?)$/) { if ($1) { $tab = " " x $1; } else { $tab = "\t"; } } elsif ($opt eq "-f") { $flushLeftMethods = 1; } elsif ($opt eq "--f") { $flushLeftMethods = 0; } elsif ($opt eq "-dos") { $newline = "\r\n"; } else { usage(); } } sub usage { die "Usage: jpp [options] [file.java ...] -k use K&R style indentation --k use Pascal-style (column aligned) indentation -f put class methods in column 0 --f indent class methods -s silent operation -t[n] indent with n spaces (use tab character if n is omitted) -dos use carriage return/line feed sequence Other options may be set in ~/.jpp_prefs\n"; } if ($#ARGV < 0) { open(DEST, ">-"); $indent = 0; $suppressIndent = 0; $multiLineComment = 0; $buffer = ""; @strings = (); $sn = 0; while() { processLine($_); } flush_buffer(); } foreach $file (@ARGV) { if (-e $file && ! -w $file) { print "$file is not writable - skipping\n"; next; } $indent = 0; $suppressIndent = 0; $multiLineComment = 0; $switchIndent = 0; $dbgIndent = ""; $tempIndent = 0; $buffer = ""; @strings = (); $sn = 0; open(SRC, $file) || die "Failed to open $file : $!\n"; open(DEST, ">$file.new") || die "Failed to create $file.new : $!\n"; while () { processLine($_); } flush_buffer(); close DEST; close SRC; $i = 1; if (`cmp $file $file.new`) { while (-e "$file.old.$i") { ++ $i; } print "$file formatted (previous version in $file.old.$i)\n" unless ($silent); rename($file, "$file.old.$i") || die "Failed to rename $file to $file.old.$i : $!\n"; rename("$file.new", $file) || die "Failed to rename $file.new to $file : $!\n"; } else { print "$file unchanged\n"; unlink("$file.new"); } } sub processLine { local($line) = @_; # Tokenise strings while ($line =~ m,([^"]*)("[^"]*")(.*),) { $line = "$1\001[S$sn]$3"; @strings[$sn++] = $2; } process($line); } # Process a line or part of line, called recursively sub process { local($line) = @_; local($got_cr); $got_cr = 0; if ($line =~ m,\n,) { $got_cr = 1; } # Remove trailing spaces, newline and ^M characters $line =~ s/\s*$//; print $dbgIndent."PROCESS: [$line]\n" if ($debug); # Do rest of processing with debug indent so we can see what's going on local($oldIndent) = ($dbgIndent); $dbgIndent .= " "; _process(); $dbgIndent = $oldIndent; } sub _process { my @match; # Process end and start of multi line comments if ($multiLineComment) { if ($line =~ m,\*/,) { $multiLineComment = 0; } outputline($line); return; } if ($line =~ m,/\*, && $line !~ m,/\*\*,) { print $dbgIndent."MULTILINE-COMMENT\n" if ($debug); outputline($line); $multiLineComment = 1 unless (m,/\*.*\*/,); return; } # remove leading space $line =~ s/^\s*//; # process javadoc comments if ($javadocComment) { print $dbgIndent."IN-JAVADOC-COMMENT\n" if ($debug); if ($line =~ m,^(.*[^*])?\*+/$,) { process($1) if ($1); indent(); outputline(" */"); $javadocComment = 0; return; } $line =~ s/^\*\s*//; indent(); output(" * "); outputline($line); return; } if ($line =~ m,/\*\*+(.*),) { print $dbgIndent."JAVADOC-COMMENT\n" if ($debug); $javadocComment = 1; indent(); outputline("/**"); process($1) if ($1); return; } # Process single line comments if (@match = $line =~ m,^(.*)(//.*)$,) { if ($match[0]) { process($match[0]); chop_prev(); output("\t"); } else { indent(); } outputline($match[1]); return; } # Don't indent methods in whole class if ($indent==0 && $flushLeftMethods && ($line =~ m,class,)) { print $dbgIndent."SUPPRESS INDENT\n" if ($debug); $suppressIndent = 1; } if (@match = $line =~ m,^(.*)({[^{}]*})(.*),) { print $dbgIndent."INLINE-BLOCK: [$match[0]] [$match[1]] [$match[2]]\n" if ($debug); process($match[0]); $tempIndent = 0; chop_prev(); if ($match[2] =~ m/^\s*$/) { outputline(" ".$match[1]); } else { output(" ".$match[1]); output(" ") if ($match[2] =~ m/^\s/); process($match[2]); } return; } # Process begin and end of statement blocks if (@match = $line =~ m,^([^}{]*)\s*([}{])\s*(.*),) { print $dbgIndent."BLOCK: [$match[0]] [$match[1]] [$match[2]]\n" if ($debug); $tempIndent = 0; if ($match[1] eq "{") { if ($k_and_r) { if ($match[0]) { $match[0] =~ s/\s*$//; # remove trailing spaces process($match[0]); $tempIndent = 0; chop_prev(); # unflush outputline(" {"); #} } else { chop_prev(); outputline(" {"); #} } } else { process($match[0]); $tempIndent = 0; indent(); outputline("{"); #} } if ($suppressIndent) { $suppressIndent = 0; } else { ++$indent; } process($match[2]); } else # close brace { process($match[0]); --$indent; if ($indent <= $switchIndent && $switchIndent > 0) { print $dbgIndent."Resetting switch indent \n" if ($debug); $switchIndent = 0; } indent(); #{ # leave semicolon after close brace, eg. after array initialisation if ($match[2] =~ m,^\s*;,) { outputline("}".$match[2]); } else { outputline("}"); process($match[2]); } } return; } # Process anything else, if we've got something if ($line) { # Outdent this line if it's a label or case statement $tempIndent = -1 if ($line =~ m,:$,); indent(); outputline($line); $switchIndent = $indent if ($line =~ m,^switch,); print $dbgIndent."Setting switch indent $indent\n" if ($debug && $line =~ m,^switch,); # Indent next line if it looks like a continuation (ie. this one isn't terminated) $tempIndent = (! ($line =~ m,[;:}]$,)) ? 1 : 0; print $dbgIndent."Setting temp indent $tempIndent [$line]\n" if ($debug && $tempIndent); return; } # Preserve blank lines if ($got_cr) { outputline(""); $got_cr = 0; } } sub indent { if ($buffer =~ m,$newline$, || $buffer eq "") { print $dbgIndent."INDENT: $indent + $tempIndent\n" if ($debug); print $dbgIndent."(SWITCH INDENT)\n" if ($switchIndent && $debug); flush_buffer() unless($k_and_r); # for k&r we need to undo newlines $buffer .= $tab x ($indent + $tempIndent); $buffer .= $tab if ($switchIndent>0 && $indent>$switchIndent); } else { print $dbgIndent."INDENT: suppressed, not at start of line\n" if ($debug); } } sub outputline { print $dbgIndent."OUTPUTLINE: [$_[0]]\n" if ($debug); $buffer .= $_[0].$newline; } sub output { print $dbgIndent."OUTPUT: [$_[0]]\n" if ($debug); $buffer .= $_[0]; } sub chop_prev { if ($buffer =~ m,//[^\n]*$newline$,) { print $dbgIndent."CHOP: [$buffer] - can't chop comment!\n" if ($debug); } else { $buffer =~ s,$newline$,,; print $dbgIndent."CHOP: [$buffer]\n" if ($debug); } } sub flush_buffer { #print $dbgIndent."FLUSH: $buffer" if ($debug); # Expand string tokens $buffer =~ s,\001\[S(\d+)\],$strings[$1],eg; print DEST $buffer; $buffer = ""; }