orrence has asked for the wisdom of the Perl Monks concerning the following question:

Hi!

I have ascii files describing network topology like this:

network:north = { ip = 10.1.1.0; mask = 255.255.255.0; host:asterix = {ip = 10.1.1.10;} host:obelix = {ip = 10.1.1.11;} host:idefix = {ip = 10.1.1.12;} host:ix_13_20 = {range = 10.1.1.13 - 10.1.1.20;} }
Now I am trying to use Parse::RecDescent to extract relevant information and visualize it in a nice way. My problem is, that I can't seem to get the host and host-range alternatives to work at the same time.
The range-alternative is for hosts with several ip-adresses in order to avoid having to type all ip-adresses in the host-specification-line like so:
host:ix_13_20 = {ip = 10.1.1.13; ip = 10.1.1.14; ..... ; ip = 10.1.1.2 +0 }
Here is part of my grammar:
startrule: network_object(s) ip: 'ip' '=' /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ ip_nosemi: 'ip' '=' /\d{1,3}\.\d{1,3}\.\d{1,3}\.d{1,3}/ mask: /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3};/ host: /host:\w+/ '=' '{' ip(s) '}' host_range: /host:\w+/ '=' '{' 'range' '=' ip_nosemi '-' ip '}' hosts_or_range: host(s?) | host_range #hosts_or_range: host(s?) <score: 2> | host_range <score: 1> network_object: /network:(\w+)/ '=' '{' ip 'mask' '=' mask hosts_or_range '}' { print "Found network object named $item[1] " . "with IP $item{ip} and MASK $item{mask}\n"; }
The host rule matches for the host-range as well with 0 matches for subrule ip and host_range isn't considered anymore. I thought I could change this behaviour with the <score: ...> directive, but I can't get it to try the host_range part of the grammar if a range is specified and I therefor get 0 matches for subrule ip for the "host"-part of the grammar.

I hope I explained precisely enough what I am trying to achieve here ... anyone any hint?

Thanks, Daniel.

update (broquaint): fixed formatting

Replies are listed 'Best First'.
Re: Parse::RecDescent and <score: ...> directive
by tachyon (Chancellor) on Sep 22, 2003 at 13:03 UTC

    I think perhaps you are using an Atom Bomb to split a walnut. If your files are well formed and as simple as shown then something like this may be more appropriate:

    #!/usr/bin/perl -w use strict; my $n; local $/; my $data =<DATA>; for my $network( split /(?=network:\w+)/, $data ) { my ( $net_loc ) = $network =~ m/network:(\w+)/; my ( $ip ) = $network =~ m/ip\s*=\s*([\d\.]+)/; my ( $mask ) = $network =~ m/mask\s*=\s*([\d\.]+)/; my %hosts = $network =~ m/host:(\w+)\s*=\s*{\s*([^;}]+)/g; $n->{$net_loc} = { ip => $ip, mask => $mask }; $n->{$net_loc}->{$_} = $hosts{$_} for keys %hosts; } use Data::Dumper; print Dumper $n; __DATA__ network:north = { ip = 10.1.1.0; mask = 255.255.255.0; host:asterix = {ip = 10.1.1.10;} host:obelix = {ip = 10.1.1.11;} host:idefix = {ip = 10.1.1.12;} host:ix_13_20 = {range = 10.1.1.13 - 10.1.1.20;} } network:south = { ip = 10.1.1.0; mask = 255.255.255.0; host:asterix = {ip = 10.1.1.10;} host:obelix = {ip = 10.1.1.11;} host:idefix = {ip = 10.1.1.12;} host:ix_13_20 = {range = 10.1.1.13 - 10.1.1.20;} } __END__ $VAR1 = { 'north' => { 'ip' => '10.1.1.0', 'asterix' => 'ip = 10.1.1.10', 'obelix' => 'ip = 10.1.1.11', 'idefix' => 'ip = 10.1.1.12', 'mask' => '255.255.255.0', 'ix_13_20' => 'range = 10.1.1.13 - 10.1.1.20' }, 'south' => { 'ip' => '10.1.1.0', 'asterix' => 'ip = 10.1.1.10', 'obelix' => 'ip = 10.1.1.11', 'idefix' => 'ip = 10.1.1.12', 'mask' => '255.255.255.0', 'ix_13_20' => 'range = 10.1.1.13 - 10.1.1.20' } };

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: Parse::RecDescent and <score: ...> directive
by kabel (Chaplain) on Sep 22, 2003 at 16:15 UTC

    well, i do not feel very comfortable with grammars yet - you'll note this when my explanations start to get wacky ;) (to be honest, i hope i get some upvotes for formatting reasons ...)

    the rule 'ip' nicely encloses a key=value pair, the same sort of rule can be applied to the mask. further, the name 'ip' is IMO not very well chosen, i rename it to 'ip_def'. finally, break the first regexp into its pieces, so you can specify whitespaces. the first rule gets:

    network_object: 'network' ':' /(\w+)/ '=' '{' ip_def mask_def host(s) +'}'

    your problem with hosts_or_range is that you want to do too much within one rule. at first, it is irrelevant if a host has one ip or a whole range. so you can pull 'host:' one level up.

    host : 'host' ':' /\w+/ '=' '{' ip_or_range '}'

    if a host has an ip, it starts with the string 'ip'. if a host has an associated ip range, it starts with 'range' - and your problem disappears

    ip_or_range : 'ip' '=' ip ';' | 'range' '=' ip '-' ip ';'

    in your grammar, the rule 'hosts_or_range' tries to do two jobs:

    • there can be possible zero to inf hosts and
    • each hosts associates either an ip or a range.

    the whole thing gets (plus added return values): HTH
Re: Parse::RecDescent and <score: ...> directive
by jryan (Vicar) on Sep 22, 2003 at 17:37 UTC

    You have a number of errors in your grammar:

    1. In the rule ip_nosemi, you forgot to backslash the last "</code>\d</code>".
    2. In hosts_or_range, you have host(s?), when you probably meant to put the (s?) in network object.
    3. You can't use $item as both a hash and an array in the same production!

    I also reorganized your grammar a bit. It should work now, although I'm on a machine without PRD installed at the moment so you shouldn't just take my word on that. However, tachyon is right on this one; PRD is far too large of a hammer to use parse data like this. :)

    startrule: network_object(s) network_object: /network:(\w+)/ '=' '{' 'ip' '=' ip 'mask' '=' ip hosts_or_range(s?) '}' { print "Found network object named $item[1] ", "with IP $item[6] and MASK $item[9]\n" } hosts_or_range: host | host_range host_range: /host:\w+/ '=' '{' 'range' '=' ip '-' ip '}' host: /host:\w+/ '=' '{' 'ip' '=' ip(s) '}' ip: / \d{1,3} (?:\.\d{1,3}){3} ;? /x