Friday, January 30, 2009

Stateful Parser in Perl

Here is a Perl implementation of the Python parser from my previous post. This is a direct port, so if you know one language and not the other then you can probably use these parsers to learn a little bit about the other language.

Some differences that I notice are that Python has a cleaner syntax. Also python favors string/list manipulation functions that return a new string as opposed to changing the string in place. That fact cleans up the code a lot, by allowing me to chain multiple functions together. A bonus for Perl is that it uses the same syntax for opening a file and opening a pipe to a subprocess.

Here is the code:

#! /usr/bin/env perl -w
use strict;

{
package Parser;

sub new {
my ($class) = @_;
my $self = {};
$self->{state} = sub{$self->outside(@_)};
$self->{interfaces} = [];
bless $self, $class;
return $self;
}

sub outside {
my ($self, $line) = @_;
if ($line =~ /^Network:/) {
$self->{state} = sub{$self->network(@_)};
}
}

sub network {
my ($self, $line) = @_;
if ($line =~ /^[^\s]/) {
$self->{state} = sub{$self->outside(@_)};
} else {
if ($line =~ /^\s{4}\S/) {
$line =~ s/^\s+//;
$line =~ s/:\s+$//;
push(@{$self->{interfaces}}, [$line]);
} elsif ($line =~ /^\s*MAC Address:/) {
$line =~ s/\s+$//;
my $interface = pop(@{$self->{interfaces}});
my @words = split(/: /, $line);
push(@$interface, $words[1]);
push(@{$self->{interfaces}}, $interface);
}
}
}
}

package main;

sub main {
my ($filename) = @_;
if (! $filename) {
$filename = "/usr/sbin/system_profiler |";
}
open(FILE, $filename) or die $!;
my $parser = new Parser();
while (<FILE>) {
$parser->{state}($_);
}
foreach (@{$parser->{interfaces}}) {
print join(" ", @$_) . "\n";
}
}

main($ARGV[1]);


Here is the output:

py$ ./system_profiler.pl system_profiler.txt
Bluetooth
Ethernet 00:22:41:21:0b:77
FireWire 00:21:e9:ff:fe:ce:eb:1a
AirPort 00:21:e9:e1:10:0f
py$

No comments: