use strict; use warnings; use Parse::RecDescent; use Data::Dumper; my $config_parser = Parse::RecDescent->new(<<'__END_OF_GRAMMAR__'); { # These pragmas affect the whole parser. use strict; use warnings; sub check_ip_nums { my ($ip) = @_; return !(grep $_ > 255, split /./, $ip); } sub dequote { my ($s) = @_; for ($s) { s/^"//; s/"\z//; s/\\(.)/$1/sg; return $_; } } } parse : line(s) /\Z/ { $item[1] } line : '' # Skip blank lines. # Don't treat newlines as whitespace. key_value /\n/ { $item[3] } key_value : server | key server : IDENT { $item[1] eq 'bind-server' } IP { [@item[0,3]] } key : IDENT { $item[1] eq 'tsig-key' } filename { [@item[0,3]] } filename : QSTRING | BAREWORD # Tokens IDENT : /[-\w]+/ QSTRING : /"(?:[^"\\]|\\.)*"/ { dequote($item[1]) } BAREWORD : /[^"\\\s]+/ IP : # This could be done more readably, but # the more is done by the regexp, the # faster it's going to be. A lot faster. /(?:[1-9][0-9]{0,2}|0)\.(?:[1-9][0-9]{0,2}|0)\.(?:[1-9][0-9]{0,2}|0)\.(?:[1-9][0-9]{0,2}|0)/ { check_ip_nums($item[1]) ? $item[1] : undef } __END_OF_GRAMMAR__ print Dumper $config_parser->parse(<<'__END_OF_CONFIG__'); bind-server 127.0.0.1 tsig-key "/etc/bind/rndc.key" __END_OF_CONFIG__ #### key_value : IDENT key_value_[ $item[1] ] key_value_ : server | key server : { $arg[0] eq 'bind-server' } IP { [ 'server', $item[2] ] } key : { $arg[0] eq 'tsig-key' } filename { [ 'key', $item[2] ] } filename : QSTRING | BAREWORD