ABNF-Grammar
view release on metacpan or search on metacpan
lib/ABNF/Validator.pm view on Meta::CPAN
package ABNF::Validator;
=pod
=head1 NAME
ABNF::Validator - class to verify strings based on ABNF-grammars
=head1 DESCRIPTION
=head1 METHODS
=cut
use 5.014;
use strict;
use warnings;
use re 'eval';
use Carp;
use Readonly;
use Method::Signatures;
use Data::Dumper;
use Parse::ABNF;
use ABNF::Grammar qw(splitRule $BASIC_RULES);
use base qw(Exporter);
our @EXPORT_OK = qw(Validator);
Readonly my $ARGUMENTS_RULES => "generic_arguments_rule_for_";
Readonly my $CLASS_MAP => {
Choice => \&_choice,
Group => \&_group,
Range => \&_range,
Reference => \&_reference,
Repetition => \&_repetition,
Rule => \&_rule,
String => \&_string,
Literal => \&_literal,
ProseValue => \&_proseValue
};
=pod
=head1 ABNF::Validator->C<new>($grammar)
Creates a new B<ABNF::Validator> object.
$grammar isa B<ABNF::Grammar>.
=cut
method new(ABNF::Grammar $grammar) {
my $class = ref($self) || $self;
$self = { _grammar => $grammar };
bless($self, $class);
$self->_init();
return $self;
}
method _init() {
my $commands = $self->{_grammar}->commands();
$self->{_commandsPattern} = do {
my $pattern = join(" | ", @$commands);
qr/\A (?: $pattern ) \Z/ix;
};
$self->{_rules} = _value([
values($self->{_grammar}->rules()),
values($BASIC_RULES)
]);
$self->{_regexps} = do {
use Regexp::Grammars;
my %res = ();
foreach my $token ( @$commands ) {
# command
my $str = "
#<logfile: /dev/null>
^ <" . _fixRulename($token) . "> \$
$self->{_rules}
";
$res{$token} = qr{$str }ixs;
# arguments
my $value = $self->{_grammar}->rule($token);
my $name = _fixRulename($ARGUMENTS_RULES . $token);
my $rule = {class => "Rule", name => $name};
my $val = (splitRule($value))[-1];
if ( $value->{value} != $val ) {
$rule->{value} = $val;
my $converted = _value($rule);
$res{$name} = qr{
^ <$name> $
$converted
$self->{_rules}
}xis;
}
}
\%res;
};
}
func _value($val, $dent = 0) {
if ( UNIVERSAL::isa($val, 'ARRAY') ) {
return join('', map { _value($_ , $dent) } @$val);
} elsif ( UNIVERSAL::isa($val, 'HASH') && exists($CLASS_MAP->{ $val->{class} }) ) {
return $CLASS_MAP->{ $val->{class} }->($val, $dent);
} else {
croak "Unknown substance " . Dumper($val);
}
}
func _choice($val, $dent) {
return "(?: " . join(' | ', map { _value($_ , $dent + 1) } @{$val->{value}}) . ")";
}
func _group($val, $dent) {
return '(?: ' . _value($val->{value}, $dent + 1) . ' )';
}
func _reference($val, $dent) {
return "<" . _fixRulename($val->{name}) . ">";
}
func _repetition($val, $dent) {
no warnings 'uninitialized';
my %maxMin = (
# max min
"1 0" => '?',
" 0" => '*',
" 1" => '+',
);
if ( my $mm = $maxMin{"$val->{max} $val->{min}"} ) {
return " (?: " . _value($val->{value}, $dent + 1) . " )$mm ";
} elsif( $val->{min} == $val->{max} ){
return " (?: ". _value($val->{value}, $dent + 1) . " ){$val->{max}} ";
} else {
return " (?: " . _value($val->{value}, $dent+1) . " ){$val->{min}, $val->{max}} ";
}
}
func _rule($val, $dent) {
my $ret = "";
my $name = $val->{name};
if ( 'ws' eq lc($name) ) {
warn "Changing rule ws to token to avoid 'infinitely recursive unpleasantness.'\n";
$ret .= "<rule: ws>\n "; # may be token
} else {
$ret .= "<token: " . _fixRulename($val->{name}) . ">\n ";
}
$ret .= _value($val->{value}, $dent + 1);
$ret . "\n\n";
}
( run in 0.833 second using v1.01-cache-2.11-cpan-39bf76dae61 )