File Coverage

blib/lib/Data/Sah/Normalize.pm
Criterion Covered Total %
statement 60 86 69.7
branch 40 82 48.7
condition 15 29 51.7
subroutine 5 5 100.0
pod 2 2 100.0
total 122 204 59.8


line stmt bran cond sub pod time code
1             package Data::Sah::Normalize;
2              
3 1     1   480 use 5.010001;
  1         7  
4 1     1   4 use strict;
  1         1  
  1         16  
5 1     1   4 use warnings;
  1         1  
  1         1190  
6              
7             our $DATE = '2018-09-10'; # DATE
8             our $VERSION = '0.050'; # VERSION
9              
10             require Exporter;
11             our @ISA = qw(Exporter);
12             our @EXPORT_OK = qw(
13             normalize_clset
14             normalize_schema
15              
16             $type_re
17             $clause_name_re
18             $clause_re
19             $attr_re
20             $funcset_re
21             $compiler_re
22             );
23              
24             our $type_re = qr/\A(?:[A-Za-z_]\w*::)*[A-Za-z_]\w*\z/;
25             our $clause_name_re = qr/\A[A-Za-z_]\w*\z/;
26             our $clause_re = qr/\A[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*\z/;
27             our $attr_re = $clause_re;
28             our $funcset_re = qr/\A(?:[A-Za-z_]\w*::)*[A-Za-z_]\w*\z/;
29             our $compiler_re = qr/\A[A-Za-z_]\w*\z/;
30             our $clause_attr_on_empty_clause_re = qr/\A(?:\.[A-Za-z_]\w*)+\z/;
31              
32             sub normalize_clset($;$) {
33 1     1 1 81 my ($clset0, $opts) = @_;
34 1   50     8 $opts //= {};
35              
36 1         2 my $clset = {};
37 1         6 for my $c (sort keys %$clset0) {
38 5         9 my $c0 = $c;
39              
40 5         7 my $v = $clset0->{$c};
41              
42             # ignore expression
43 5         6 my $expr;
44 5 100       11 if ($c =~ s/=\z//) {
45 1         2 $expr++;
46             # XXX currently can't disregard merge prefix when checking
47             # conflict
48 1 50       2 die "Conflict between '$c=' and '$c'" if exists $clset0->{$c};
49 1         3 $clset->{"$c.is_expr"} = 1;
50             }
51              
52 5         8 my $sc = "";
53 5         6 my $cn;
54             {
55 5         4 my $errp = "Invalid clause name syntax '$c0'"; # error prefix
  5         9  
56 5 100 100     48 if (!$expr && $c =~ s/\A!(?=.)//) {
    100 100        
    100 100        
    100 66        
    50 33        
57 1 50       6 die "$errp, syntax should be !CLAUSE"
58             unless $c =~ $clause_name_re;
59 1         3 $sc = "!";
60             } elsif (!$expr && $c =~ s/(?<=.)\|\z//) {
61 1 50       4 die "$errp, syntax should be CLAUSE|"
62             unless $c =~ $clause_name_re;
63 1         2 $sc = "|";
64             } elsif (!$expr && $c =~ s/(?<=.)\&\z//) {
65 1 50       6 die "$errp, syntax should be CLAUSE&"
66             unless $c =~ $clause_name_re;
67 1         2 $sc = "&";
68             } elsif (!$expr && $c =~ /\A([^.]+)(?:\.(.+))?\((\w+)\)\z/) {
69 1         4 my ($c2, $a, $lang) = ($1, $2, $3);
70 1 50 33     9 die "$errp, syntax should be CLAUSE(LANG) or C.ATTR(LANG)"
      33        
71             unless $c2 =~ $clause_name_re &&
72             (!defined($a) || $a =~ $attr_re);
73 1         2 $sc = "(LANG)";
74 1 50       3 $cn = $c2 . (defined($a) ? ".$a" : "") . ".alt.lang.$lang";
75             } elsif ($c !~ $clause_re &&
76             $c !~ $clause_attr_on_empty_clause_re) {
77 0         0 die "$errp, please use letter/digit/underscore only";
78             }
79             }
80              
81             # XXX can't disregard merge prefix when checking conflict
82 5 100       25 if ($sc eq '!') {
    100          
    100          
    100          
83             die "Conflict between clause shortcuts '!$c' and '$c'"
84 1 50       3 if exists $clset0->{$c};
85             die "Conflict between clause shortcuts '!$c' and '$c|'"
86 1 50       4 if exists $clset0->{"$c|"};
87             die "Conflict between clause shortcuts '!$c' and '$c&'"
88 1 50       4 if exists $clset0->{"$c&"};
89 1         2 $clset->{$c} = $v;
90 1         5 $clset->{"$c.op"} = "not";
91             } elsif ($sc eq '&') {
92             die "Conflict between clause shortcuts '$c&' and '$c'"
93 1 50       3 if exists $clset0->{$c};
94             die "Conflict between clause shortcuts '$c&' and '$c|'"
95 1 50       4 if exists $clset0->{"$c|"};
96 1 50       4 die "Clause 'c&' value must be an array"
97             unless ref($v) eq 'ARRAY';
98 1         2 $clset->{$c} = $v;
99 1         3 $clset->{"$c.op"} = "and";
100             } elsif ($sc eq '|') {
101             die "Conflict between clause shortcuts '$c|' and '$c'"
102 1 50       3 if exists $clset0->{$c};
103 1 50       4 die "Clause 'c|' value must be an array"
104             unless ref($v) eq 'ARRAY';
105 1         1 $clset->{$c} = $v;
106 1         3 $clset->{"$c.op"} = "or";
107             } elsif ($sc eq '(LANG)') {
108             die "Conflict between clause '$c' and '$cn'"
109 1 50       3 if exists $clset0->{$cn};
110 1         3 $clset->{$cn} = $v;
111             } else {
112 1         2 $clset->{$c} = $v;
113             }
114              
115             }
116 1 50       3 $clset->{req} = 1 if $opts->{has_req};
117              
118             # XXX option to recursively normalize clset, any's of, all's of, ...
119             #if ($clset->{clset}) {
120             # local $opts->{has_req};
121             # if ($clset->{'clset.op'} && $clset->{'clset.op'} =~ /and|or/) {
122             # # multiple clause sets
123             # $clset->{clset} = map { $self->normalize_clset($_, $opts) }
124             # @{ $clset->{clset} };
125             # } else {
126             # $clset->{clset} = $self->normalize_clset($_, $opts);
127             # }
128             #}
129              
130 1         10 $clset;
131             }
132              
133             sub normalize_schema($) {
134 2     2 1 5 my $s = shift;
135              
136 2         4 my $ref = ref($s);
137 2 50       7 if (!defined($s)) {
    50          
    0          
138              
139 0         0 die "Schema is missing";
140              
141             } elsif (!$ref) {
142              
143 2         7 my $has_req = $s =~ s/\*\z//;
144 2 50       13 $s =~ $type_re or die "Invalid type syntax $s, please use ".
145             "letter/digit/underscore only";
146 2 100       14 return [$s, $has_req ? {req=>1} : {}, {}];
147              
148             } elsif ($ref eq 'ARRAY') {
149              
150 0           my $t = $s->[0];
151 0   0       my $has_req = $t && $t =~ s/\*\z//;
152 0 0         if (!defined($t)) {
    0          
153 0           die "For array form, at least 1 element is needed for type";
154             } elsif (ref $t) {
155 0           die "For array form, first element must be a string";
156             }
157 0 0         $t =~ $type_re or die "Invalid type syntax $s, please use ".
158             "letter/digit/underscore only";
159              
160 0           my $clset0;
161             my $extras;
162 0 0         if (defined($s->[1])) {
163 0 0         if (ref($s->[1]) eq 'HASH') {
164 0           $clset0 = $s->[1];
165 0           $extras = $s->[2];
166 0 0         die "For array form, there should not be more than 3 elements"
167             if @$s > 3;
168             } else {
169             # flattened clause set [t, c=>1, c2=>2, ...]
170 0 0         die "For array in the form of [t, c1=>1, ...], there must be ".
171             "3 elements (or 5, 7, ...)"
172             unless @$s % 2;
173 0           $clset0 = { @{$s}[1..@$s-1] };
  0            
174             }
175             } else {
176 0           $clset0 = {};
177             }
178              
179             # check clauses and parse shortcuts (!c, c&, c|, c=)
180 0           my $clset = normalize_clset($clset0, {has_req=>$has_req});
181 0 0         if (defined $extras) {
182 0 0         die "For array form with 3 elements, extras must be hash"
183             unless ref($extras) eq 'HASH';
184             die "'def' in extras must be a hash"
185 0 0 0       if exists $extras->{def} && ref($extras->{def}) ne 'HASH';
186 0           return [$t, $clset, { %{$extras} }];
  0            
187             } else {
188 0           return [$t, $clset, {}];
189             }
190             }
191              
192 0           die "Schema must be a string or arrayref (not $ref)";
193             }
194              
195             1;
196             # ABSTRACT: Normalize Sah schema
197              
198             __END__