2009-06-17 17 views
3

Ein Perl-Skript, das ich schreibe, muss eine Datei parsen, die Fortsetzungszeilen wie ein Makefile hat. h. Zeilen, die mit Leerzeichen beginnen, sind Teil der vorherigen Zeile.Cleanster Perl-Parser für Makefile-ähnliche Fortsetzungszeilen

ich den Code unten geschrieben, aber nicht das Gefühl, wie es ist sehr sauber oder Perl-ish

Es gibt viele Grenzfälle (Heck, es auch nicht verwenden „Redo“!): EOF bei ungeraden Orte, Einzeilendateien, Dateien, die mit einer leeren Zeile (oder einer leeren Zeile oder einer Fortsetzungszeile) beginnen oder enden, leere Dateien. Alle meine Testfälle (und Code) sind hier: http://whatexit.org/tal/flatten.tar

Können Sie sauberer, Perl-ish Code schreiben, der alle meine Tests besteht?

#!/usr/bin/perl -w 

use strict; 

sub process_file_with_continuations { 
    my $processref = shift @_; 
    my $nextline; 
    my $line = <ARGV>; 

    $line = '' unless defined $line; 
    chomp $line; 

    while (defined($nextline = <ARGV>)) { 
     chomp $nextline; 
     next if $nextline =~ /^\s*#/; # skip comments 
     $nextline =~ s/\s+$//g; # remove trailing whitespace 
     if (eof()) { # Handle EOF 
      $nextline =~ s/^\s+/ /; 
      if ($nextline =~ /^\s+/) { # indented line 
       &$processref($line . $nextline); 
      } 
      else { 
       &$processref($line); 
       &$processref($nextline) if $nextline ne ''; 
      } 
      $line = ''; 
     } 
     elsif ($nextline eq '') { # blank line 
      &$processref($line); 
      $line = ''; 
     } 
     elsif ($nextline =~ /^\s+/) { # indented line 
      $nextline =~ s/^\s+/ /; 
      $line .= $nextline; 
     } 
     else { # non-indented line 
      &$processref($line) unless $line eq ''; 
      $line = $nextline; 
     } 
    } 
    &$processref($line) unless $line eq ''; 
} 

sub process_one_line { 
    my $line = shift @_; 
    print "$line\n"; 
} 

process_file_with_continuations \&process_one_line; 

Antwort

6

Wie schlürfen Sie die ganze Datei in den Speicher und verarbeiten sie mit regulären Ausdrücken. Viel mehr 'Perl'. Dies geht Ihre Tests und ist viel kleiner und übersichtlicher:

#!/usr/bin/perl 

use strict; 
use warnings; 

$/ = undef;    # we want no input record separator. 
my $file = <>;   # slurp whole file 

$file =~ s/^\n//;  # Remove newline at start of file 
$file =~ s/\s+\n/\n/g; # Remove trailing whitespace. 
$file =~ s/\n\s*#[^\n]+//g;  # Remove comments. 
$file =~ s/\n\s+/ /g; # Merge continuations 

# Done 
print $file; 
+1

Eine Sache im Auge zu tragen sowohl mit mir und Antworten Mirod ist, dass es eine gute Idee wäre, die speziellen Variablen zu lokalisieren, wenn Sie dies in einem größeren Stück Code einbetten (zB ‚local $ /‘) –

+0

Ich habe gerade codiert, um die Tests zu bestehen ;-) Du hast aber Recht. – mirod

+0

@mirod - heh. Das ist fast identisch mit einer Übung im Perl-Intro-Kurs, den ich lehre (es geht darum, Mail-Header zu entwickeln). Es ist wahrscheinlich ein ziemlich häufiges Problem mit * so * vielen Möglichkeiten, es zu tun :) –

3

Wenn Sie nichts dagegen haben die gesamte Datei in den Speicher zu laden, dann unterhalb der Code die Tests bestanden. Es speichert die Zeilen in einem Array und fügt jede Zeile entweder zur vorherigen (Fortsetzung) oder am Ende des Arrays (andere) hinzu.

#!/usr/bin/perl 

use strict; 
use warnings; 

my @out; 

while(<>) 
    { chomp; 
    s{#.*}{};    # suppress comments 
    next unless(m{\S}); # skip blank lines 
    if(s{^\s+}{ })  # does the line start with spaces? 
     { $out[-1] .= $_; } # yes, continuation, add to last line 
    else 
     { push @out, $_; } # no, add as new line 
    } 

$, = "\n";    # set output field separator 
$\ = "\n";    # set output record separator 
print @out;   
+1

Ihr Algorithmus funktioniert natürlich auch, wenn Sie einfach die (gefügten) Zeilen einzeln verarbeiten möchten. Mach einfach die Verarbeitung (oder Ausdruck) statt auf @out zu drücken. Dann ist es nicht notwendig, die gesamte Datei gleichzeitig im Speicher zu haben. – user55400

+0

@blixtor: In der Tat können Sie @out durch $ last_line ersetzen, ändern Sie das innere if durch if (s {^ \ s +} {}) {$ last_line. = $ _; } else {print $ last_line, "\ n"; $ last_line = $ _; } und die letzten 3 Zeilen durch Drucken $ last_line, "\ n" wenn $ last_line. Ich nahm an, Makefile-Typ-Zeilen wären nicht zu groß, um in den Speicher zu passen. – mirod

+0

Ja, ich würde es lieber tun, ohne alles in den Speicher zu lesen. Diese Dateien können riesig sein! – TomOnTime