File Coverage

blib/lib/Aion/Type.pm
Criterion Covered Total %
statement 435 472 92.1
branch 191 218 87.6
condition 53 76 69.7
subroutine 162 173 93.6
pod 96 136 70.5
total 937 1075 87.1


line stmt bran cond sub pod time code
1             package Aion::Type;
2             # Базовый класс для типов и преобразователей
3 7     7   129168 use common::sense;
  7         10  
  7         32  
4 7     7   462 use warnings FATAL => 'recursion';
  7         9  
  7         337  
5             #use warnings 'recursion';
6              
7 7     7   358 use Aion::Meta::Util qw//;
  7         31  
  7         169  
8 7     7   2106 use Aion::Type::Lim;
  7         18  
  7         200  
9 7     7   33 use List::Util qw//;
  7         7  
  7         77  
10 7     7   17 use Scalar::Util qw//;
  7         8  
  7         1720  
11              
12 207     207 1 538 sub true {1}
13              
14             use overload
15             "fallback" => 1,
16             "&{}" => sub {
17 2     2   454 my ($self) = @_;
18 2     2   7 sub { $self->test }
19 2         13 },
20             '""' => "stringify",
21 45     45   4020 "|" => sub { Aion::Types::Union([@_[0, 1]]) },
22 99     99   2295 "&" => sub { Aion::Types::Intersection([@_[0, 1]]) },
23 466     466   7782 "~" => sub { Aion::Types::Exclude([shift]) },
24             "~~" => "include",
25             ">>" => "coerce",
26             "eq" => "identical",
27             "ne" => "distinct",
28 0     0   0 "lt" => sub {die "lt do'nt used!"},
29 0     0   0 "gt" => sub {die "gt do'nt used!"},
30 0     0   0 "le" => sub {die "le do'nt used!"},
31 0     0   0 "ge" => sub {die "ge do'nt used!"},
32 7         143 "cmp" => "compare",
33             "<=>" => "compare",
34             "==" => "equals",
35             "!=" => "differs",
36             ">=" => "superset",
37             "<=" => "subset",
38             ">" => "superproper",
39             "<" => "subproper",
40 7     7   38 ;
  7         9  
41              
42             Aion::Meta::Util::create_getters(qw/name args as/);
43             Aion::Meta::Util::create_accessors(qw/message/);
44              
45             $Aion::Type::SELF = __PACKAGE__->new(
46             is_param_args => __PACKAGE__->new(name => "Argument_ARGS", is_param => -1024),
47             is_param => -256,
48             name => 'Argument_SELF',
49             args => [
50             __PACKAGE__->new(name => "Argument_A", is_param => 1),
51             __PACKAGE__->new(name => "Argument_B", is_param => 2),
52             __PACKAGE__->new(name => "Argument_C", is_param => 3),
53             __PACKAGE__->new(name => "Argument_D", is_param => 4),
54             ],
55             N => __PACKAGE__->new(name => "Argument_N", is_param => -1),
56             M => __PACKAGE__->new(name => "Argument_M", is_param => -2),
57             );
58              
59             # конструктор
60             # * name (Str) — Имя типа.
61             # * as (Object[Aion::Type]) — наследуемый тип.
62             # * args (ArrayRef) — Список аргументов.
63             # * init (ArrayRef[CodeRef]) — Инициализатор типа.
64             # * test (CodeRef) — Чекер.
65             # * a_test (CodeRef) — Используется для проверки типа с аргументами, если аргументы не указаны, то используется test.
66             # * coerce (ArrayRef) — Массив преобразователей в этот тип: [Type => sub {}]. Общий для экземплятов параметрического типа.
67             # * subset (CodeRef) - Проверка на подмножество типа A типу B.
68             # * message (CodeRef) — Сообщение об ошибке.
69             # * title (Str) — Заголовок.
70             # * description (Str) — Описание.
71             # * example (Any) — Пример.
72             # * is_option (Bool) – это Option[A].
73             # * is_wantarray (Bool) – это Wantarray[A, S].
74             # * ally (Bool) – вступать в союз для объединения ветвей наследования при пересечении типов.
75             sub new {
76 3416     3416 1 238630 my $cls = shift;
77 3416         10712 my $self = bless {@_}, $cls;
78 3416   100     6850 $self->{test} //= \&test;
79 3416   100     5187 $self->{coerce} //= [];
80 3416         6090 $self
81             }
82              
83             # Клонировать тип
84             sub clone {
85 293     293 1 319 my $self = shift;
86 293         1317 $self = bless { %$self, @_ }, ref $self;
87 293         530 delete @$self{qw/key as_test_cache/};
88 293         1935 $self
89             }
90              
91             # Инициализировать тип
92             sub init {
93 2647     2647 1 3254 my ($self) = @_;
94            
95             # Есть параметрические типы – не инициализируем
96 2647 100 100 6832   7427 return $self if $self->{args} && List::Util::first { UNIVERSAL::isa($_, __PACKAGE__) && exists $_->{is_param} } @{$self->{args}};
  6832 100       15104  
  2639         5260  
97              
98 2635         5757 local $Aion::Type::SELF = $self;
99 2635         2744 $_->() for @{$self->{init}};
  2635         5466  
100              
101 2634         19440 $self
102             }
103              
104             #@category strings
105              
106             # Строковое представление
107             sub stringify {
108 17160     17160 1 327164 my ($self) = @_;
109              
110 17160         16022 my @args = map Aion::Meta::Util::val_to_str($_), @{$self->{args}};
  17160         25500  
111              
112             $self->is_union? join "", "( ", join(" | ", @args), " )":
113             $self->is_intersection? join "", "( ", join(" & ", @args), " )":
114             $self->is_exclude? "~$args[0]":
115 17160 100       21357 join("", $self->{name}, @args? ("[", join(", ", @args), "]") : ());
    100          
    100          
    100          
116             }
117              
118             # Сообщение об ошибке
119             sub detail {
120 15     15 1 76 (my $self, local $_, my $name) = @_;
121 15         23 local $Aion::Type::SELF = $self;
122 15 100       40 $self->{message}? do { local $self->{property} = $name; $self->{message}->() }:
  3         8  
  3         9  
123             "$name must have the type $self. The it is ${\
124 12         37 Aion::Meta::Util::val_to_str($_)
125             }!"
126             }
127              
128             # Преобразовать значение в строку
129             sub val_to_str {
130 1     1 1 2 my ($self, $val) = @_;
131 1         3 Aion::Meta::Util::val_to_str($val)
132             }
133              
134             #@category test
135              
136             # Строит кеш для вызова только для примитивного типа
137             sub _build_as_test_cache {
138 384     384   536 my ($self) = @_;
139              
140 384         429 my @as;
141 384         920 for(my $i = $self->{as}; $i; $i = $i->{as}) {
142 1148 100       1467 return "" if $i->is_set_theoretic;
143 1029 100       3162 unshift @as, $i if $i->{test} != \&true;
144             }
145            
146 265         743 \@as;
147             }
148              
149             # Это - примитивный тип, то есть тот, в иерархии которого нет множественно-теоритических операторов
150             sub is_primitive {
151 2     2 1 4 my ($self) = @_;
152 2   66     10 !!($self->{as_test_cache} //= $self->_build_as_test_cache);
153             }
154              
155             # Тестировать значение в $_
156             sub test {
157 1500     1500 1 2465 my ($self) = @_;
158              
159 1500 100 100     3960 if($self->{as_test_cache} //= $self->_build_as_test_cache) {
160 1177         1211 local $Aion::Type::SELF;
161 1177         1251 for $Aion::Type::SELF (@{$self->{as_test_cache}}) {
  1177         2137  
162 2028 100       3740 return "" unless $Aion::Type::SELF->{test}->();
163             }
164             } else {
165 323 100 66     589 return "" if $self->{as} && !$self->{as}->test;
166             }
167              
168 1421         2402 local $Aion::Type::SELF = $self;
169 1421         2625 $self->{test}->();
170             }
171              
172             # Является элементом множества описываемого типом
173             sub include {
174 731     731 1 7452 (my $self, local $_) = @_;
175 731         1610 $self->test
176             }
177              
178             # Не является элементом множества описываемого типом
179             sub exclude {
180 59     59 1 905 (my $self, local $_) = @_;
181 59         95 !$self->test
182             }
183              
184             # Валидировать значение в параметре
185             sub validate {
186 160     160 1 757 (my $self, local $_, my $name) = @_;
187 160 100       278 die $self->detail($_, $name) unless $self->test;
188 147         2308 $_
189             }
190              
191             # Преобразовать значение в параметре и вернуть преобразованное
192             sub coerce {
193 30     30 1 1427 local ($Aion::Type::SELF, $_) = @_;
194              
195 30         55 for my $coerce (@{$Aion::Type::SELF->{coerce}}) {
  30         84  
196 34 100       89 return $coerce->[1]() if $coerce->[0]->test;
197             }
198             $_
199 3         38 }
200              
201             #@category compare
202              
203             #my $_any; my $_none;
204 3     3 1 15 sub Any() { *Any = \&Aion::Types::Any; &Any }
  3         57  
205 3     3 1 11 sub None() { *None = \&Aion::Types::None; &None }
  3         95  
206              
207             # refaddr coerce => минимальная нижняя граница. У Range она -Inf, а у остальных – 0
208             our %range_lbound;
209              
210             # Определяет, что тип – множественно-теоретический оператор
211             my $set_theoretic = [qw/Union Intersection Exclude/];
212 2638     2638 1 6056 sub is_set_theoretic { shift->{name} ~~ $set_theoretic }
213 22193     22193 1 41494 sub is_union { shift->{name} eq 'Union' }
214 27005     27005 1 43090 sub is_intersection { shift->{name} eq 'Intersection' }
215 42882     42882 1 92216 sub is_exclude { shift->{name} eq 'Exclude' }
216 23244     23244 1 36460 sub is_enum { shift->{name} eq 'Enum' }
217 304     304 1 648 sub is_range_type { exists $range_lbound{Scalar::Util::refaddr shift->{coerce}} }
218 60     60 1 225 sub range_lbound { $range_lbound{Scalar::Util::refaddr shift->{coerce}} }
219 28     28 1 43 sub is_range { shift->range_lbound == '-Inf' }
220              
221             # Формирует ключ с отсортированными типизированными параметрами
222             sub typed_sorted_args_key {
223 996     996 1 1041 my ($self) = @_;
224 996         974 my $coerceaddr = Scalar::Util::refaddr $self->{coerce};
225 996         907 join "-", $coerceaddr, join(",", map { join ":", length($_), $_ } sort map $_->key, @{$self->{args}});
  2753         6211  
  996         1512  
226             }
227              
228             # Формирует ключ с отсортированными нетипизированными параметрами
229             sub sorted_args_key {
230 28     28 1 31 my ($self) = @_;
231 28         33 my $coerceaddr = Scalar::Util::refaddr $self->{coerce};
232 28         32 join "-", $coerceaddr, join(",", map { join ":", length($_), $_ } sort @{$self->{args}});
  55         278  
  28         68  
233             }
234              
235             # Возвращает уникальный ключ для типа, использующийся в хешах и сравнения
236             # Должен быть заменён на созданные типы
237             my %keyfn;
238             my $undefined = [];
239             sub key {
240 30328     30328 1 29802 my ($self) = @_;
241 30328   66     57593 $self->{key} //= do {
242 1447         1474 my $coerceaddr = Scalar::Util::refaddr $self->{coerce};
243 1447         1624 my $keyfn = $keyfn{$coerceaddr};
244             $keyfn
245             ? $keyfn->($self)
246             : join "-", $coerceaddr, exists $self->{args} && @{$self->{args}} || exists $self->{N} || exists $self->{M}
247             ? join(",", map {
248 498 100 33     1136 my $key = UNIVERSAL::isa($_, __PACKAGE__)? $_->key: "" . ($_ // $undefined);
249 498         2144 join ":", length($key), $key
250 1447 100 66     2220 } @{$self->{args}})
  389 100       492  
251             : ();
252             };
253             }
254              
255             # Устанавливает/возвращает функцию построения ключа для типа как класса
256             sub keyfn {
257 23     23 1 66 my ($self, $fn) = @_;
258 23 50       45 if(@_>1) {
259 23         60 $keyfn{Scalar::Util::refaddr $self->{coerce}} = $fn;
260 23         38 $self
261             } else {
262 0         0 $keyfn{Scalar::Util::refaddr $self->{coerce}};
263             }
264             }
265              
266             # Возвращает цепочку предков
267             sub asen {
268 16     16 1 28 my ($self) = @_;
269 16         41 my @as;
270 16         42 for(my $i=$self->{as}; $i; $i = $i->{as}) { unshift @as, $i }
  56         126  
271 16 50 33     305 unshift @as, Any unless @as && $as[0] eq Any;
272             @as
273 16         42 }
274              
275             # Ключ для сравнения типов в <=> и cmp
276             sub ckey {
277 18     18 1 29 my ($self) = @_;
278 18   66     71 $self->{ckey} //= join " <- ", map $_->stringify, $self->asen, $self;
279             }
280              
281             # Сравнение для сортировки
282             sub compare {
283 9     9 1 21 my ($self, $other) = @_;
284 9         28 $self->ckey cmp $other->ckey;
285             }
286              
287             # A потомок B
288             sub instanceof {
289 6     6 1 939 my ($self, $name) = @_;
290              
291 6         12 my @S = $self;
292 6         15 while(@S) {
293 16         21 my $x = pop @S;
294 16 100       42 return 1 if $x->{name} eq $name;
295 11 100       17 if($x->is_intersection) { push @S, @{$x->{args}} }
  3 100       6  
  3         7  
296             elsif($x->is_set_theoretic) {}
297 7 100       20 else { push @S, $x->{as} if $x->{as} }
298             }
299              
300             ""
301 1         3 }
302              
303             # A потомок B
304             sub is_descendant {
305 33     33 1 43 my ($self, $other, $is_strict) = @_;
306            
307 33 100 33     78 return 1 if $is_strict && $self eq $other
      66        
      66        
308             || !$is_strict && $self->like($other);
309              
310 29 100       32 if ($self->is_intersection) {
311 2     3   5 return List::Util::any { $_->is_descendant($other, $is_strict) } @{$self->{args}};
  3         5  
  2         4  
312             }
313 27 100       33 if ($self->is_union) {
314 3     6   8 return List::Util::all { $_->is_descendant($other, $is_strict) } @{$self->{args}};
  6         10  
  3         8  
315             }
316 24 50       24 if ($self->is_exclude) {
317 0 0       0 return $self->{args}[0]->is_descendant($other->is_exclude? $other->{args}[0]: ~$other, $is_strict);
318             }
319 24 100       31 return $self->{as}->is_descendant($other, $is_strict) if $self->{as};
320              
321 2         13 ""
322             }
323              
324             # Сравнивает по прототипам
325             sub like {
326 34     34 1 35 my ($self, $other) = @_;
327 34 50 66 0   37 return List::Util::all { $_->[0]->like($_->[1]) } List::Util::zip $self->{args}, $other->{args} if $self->is_intersection && $other->is_intersection;
  0         0  
328 34 50 66 0   37 return List::Util::any { $_->[0]->like($_->[1]) } List::Util::zip $self->{args}, $other->{args} if $self->is_union && $other->is_union;
  0         0  
329 34 50 33     36 return $self->{args}[0]->like($other->{args}[0]) if $self->is_exclude && $other->is_exclude;
330 34 100 66     38 return "" if $self->is_set_theoretic || $other->is_set_theoretic;
331 29         87 $self->{coerce} == $other->{coerce};
332             }
333              
334             # Тождество
335             sub identical {
336 225     225 1 5195 my ($self, $other) = @_;
337              
338 225 100       1361 return 1 if Scalar::Util::refaddr $self == Scalar::Util::refaddr $other;
339             return "" unless UNIVERSAL::isa($other, __PACKAGE__)
340 159 100 100     1919 && $self->{coerce} == $other->{coerce};
341              
342 46         133 $self->key eq $other->key
343             }
344              
345             # Нетождественно
346             sub distinct {
347 2     2 1 406 my ($self, $other) = @_;
348 2         5 !$self->identical($other);
349             }
350              
351             # Превращает выражение в ДНФ
352 56     56   131 sub _simplify { shift->_unfolding->_pushing->_distribute }
353              
354             # Упрощает выражение
355             # TODO: использовать алгоритм Espresso для свёртки DNF
356             sub simplify {
357 2     2 1 16 my ($self) = @_;
358              
359 2 100       6 $self->_simplify eq None? None: $self;
360             }
361              
362             # A as B as C <=> A & B & C
363             sub _unfolding {
364 449     449   563 my ($self) = @_;
365            
366 449         427 my @u;
367 449         717 for(my $i=$self; $i; $i = $i->{as}) {
368 1419 100       1598 unshift(@u, $i->clone(args => [map $_->_unfolding, @{$i->{args}}])), last if $i->is_set_theoretic;
  222         473  
369 1197 100       3191 unshift @u, $i if $i->{test} != \&true;
370             }
371              
372 449 100       5213 @u == 0? Any:
    100          
373             @u == 1? $u[0]: Aion::Types::Intersection(\@u);
374             }
375              
376             # Проталкивание исключений к термам, заодно уменьшает размерность с приведением
377             sub _pushing {
378 1147     1147   1215 my ($self) = @_;
379            
380 1147 100       1324 if($self->is_exclude) {
381 449         525 my $inner = $self->{args}[0];
382             # ~(~A) => A
383 449 100       591 return $inner->{args}[0]->_pushing if $inner->is_exclude;
384             # ~(A | B) => ~A & ~B
385 448 100       624 return _intersection(map { (~$_)->_pushing } @{$inner->{args}}) if $inner->is_union;
  60         84  
  30         46  
386             # ~(A & B) => ~A | ~B
387 418 100       496 return _union(map { (~$_)->_pushing } @{$inner->{args}}) if $inner->is_intersection;
  339         437  
  116         155  
388             # Range[A, B] => Range[-Inf, Invert[A]] | Range[Invert[B], Inf]
389 302 100       370 if($inner->is_range_type) {
390 25         31 my ($min, $max) = @{$inner->{args}};
  25         64  
391 25 100       50 if($inner->is_range) {
392 11 50 33     39 return None if $min == '-Inf' && $max == 'Inf';
393 11 50       23 return $inner->clone(args => [Aion::Type::Lim->from($max)->inc, 'Inf']) if $min == '-Inf';
394 11 100       41 return $inner->clone(args => ['-Inf', Aion::Type::Lim->from($min)->dec]) if $max == 'Inf';
395 7         37 return $inner->clone(args => ['-Inf', Aion::Type::Lim->from($min)->dec]) | $inner->clone(args => [Aion::Type::Lim->from($max)->inc, 'Inf']);
396             }
397            
398 14 100 100     49 return None if $min == 0 && $max == 'Inf';
399 13 100       36 return $inner->clone(args => [$max+1, 'Inf']) if $min == 0;
400 5 100       15 return $inner->clone(args => [0, $min-1]) if $max == 'Inf';
401 4         12 return $inner->clone(args => [0, $min-1]) | $inner->clone(args => [$max+1, 'Inf']);
402             }
403 277         559 return $self;
404             }
405              
406 698 100       802 return _intersection(map $_->_pushing, @{$self->{args}}) if $self->is_intersection;
  225         437  
407 473 100       569 return _union(map $_->_pushing, @{$self->{args}}) if $self->is_union;
  46         126  
408              
409 427         712 $self
410             }
411              
412             # Сжимает в ДНФ
413             sub _distribute {
414 886     886   869 my ($self) = @_;
415              
416             # (A|B) & (C|D|E) & F => (A&C&F) | (A&D&F) | (A&E&F) | (B&C&F) | (B&D&F) | (B&E&F)
417 886 100       1001 if($self->is_intersection) {
418 124 100       137 my @disjuncts = map { my $x = $_->_distribute; $x->is_union? [@{$x->{args}}]: [$x] } @{$self->{args}};
  443         570  
  443         580  
  121         449  
  124         210  
419            
420             my $dnf = List::Util::reduce {
421 443     443   508 [ map { my $p = $_; map { [@$p, $_] } @$b } @$a ]
  1716         1359  
  1716         1532  
  3615         5641  
422 124         660 } [[]], @disjuncts;
423            
424 124         510 return _union(map _intersection(@$_), @$dnf);
425             }
426              
427 762 100       886 return _union(map $_->_distribute, @{$self->{args}}) if $self->is_union;
  121         279  
428            
429 641         863 $self
430             }
431              
432             # Объединение интервалов
433             sub _union_ranges {
434 51     51   69 my ($ranges) = @_;
435              
436             # Отсекаем пустые
437 51         191 my @ranges = grep $_->{args}[0] <= $_->{args}[1], @$ranges;
438              
439             # Сортируем в порядке возрастания нижней границы
440 51         119 (my $range, @ranges) = sort { $a->{args}[0] <=> $b->{args}[0] } @ranges;
  22         63  
441              
442             @ranges = map {
443 51         70 my ($min1, $max1) = @{$range->{args}};
  22         23  
  22         43  
444 22         29 my ($min2, $max2) = @{$_->{args}};
  22         37  
445 22 50       39 if($max1 > $min2) { $range = $range->clone(args => [$min1, List::Util::max($max1, $max2)]); () }
  0         0  
  0         0  
446 22         32 else { my $arange = $range; $range = $_; $arange }
  22         23  
  22         49  
447             } @ranges;
448 51         83 push @ranges, $range;
449              
450 51 100       88 if(@ranges == 1) {
451 29         28 my ($min, $max) = @{$range->{args}};
  29         50  
452 29 50 66     49 return Any if $min == $range->range_lbound && $max == 'Inf';
453             }
454              
455             @ranges
456 51         184 }
457              
458             # Обрабатывает пересечение границ однотипных диапазонов
459             sub _intersection_ranges($) {
460 1318     1318   1569 my ($ranges) = @_;
461              
462             # Пустой диапазон - это None
463 1318 50       3210 return None if 0 == grep $_->{args}[0] <= $_->{args}[1], @$ranges;
464            
465             # Сортируем в порядке возрастания нижней границы
466 1318         1978 my ($range, @ranges) = sort { $a->{args}[0] <=> $b->{args}[0] } @$ranges;
  78         206  
467              
468 1318         1522 for my $arange (@ranges) {
469             # Если хотя бы у одного нет пересечений – это None
470 78         84 my ($min1, $max1) = @{$range->{args}};
  78         139  
471 78         86 my ($min2, $max2) = @{$arange->{args}};
  78         141  
472 78         166 my $max = List::Util::min($max1, $max2);
473 78 100       149 return None if $min2 > $max;
474 25         63 $range = $range->clone(args => [$min2, $max]);
475             }
476              
477             $range
478 1265         3928 }
479              
480             # Объединение перечислений
481             sub _union_enums($,$) {
482 3     3   8 my ($enums, $exclude_enums) = @_;
483            
484 3         28 my %enum = map {($_=>$_)} map @{$_->{args}}, @$enums;
  8         16  
  6         15  
485 3 50       25 return $enums->[0]->clone(args => [sort values %enum])->init unless @$exclude_enums;
486              
487 0         0 my $first_exclude_enum = shift(@$exclude_enums);
488 0         0 my %exclude_enum = map {($_=>$_)} @{$first_exclude_enum->{args}};
  0         0  
  0         0  
489 0         0 for my $exclude_enum (@$exclude_enums) {
490 0         0 delete @exclude_enum{grep { !($_ ~~ $exclude_enum->{args}) } keys %exclude_enum};
  0         0  
491 0 0       0 return Any unless keys %exclude_enum;
492             }
493            
494 0         0 delete @exclude_enum{keys %enum};
495              
496 0 0       0 return Any unless keys %exclude_enum;
497              
498 0         0 ~$first_exclude_enum->clone(args => [sort values %exclude_enum])->init;
499             }
500              
501             # Пересечение перечислений
502             sub _intersection_enums($,$) {
503 16     16   23 my ($enums, $exclude_enums) = @_;
504            
505 16         28 my %exclude_enum = map {($_=>$_)} map @{$_->{args}}, @$exclude_enums;
  29         55  
  15         33  
506 16 100       47 return ~$exclude_enums->[0]->clone(args => [sort values %exclude_enum])->init unless @$enums;
507            
508 14         22 my $first_enum = shift(@$enums);
509 14         16 my %enum = map {($_=>$_)} @{$first_enum->{args}};
  30         67  
  14         22  
510              
511 14         25 for my $enum (@$enums) {
512 3         7 delete @enum{grep { !($_ ~~ $enum->{args}) } keys %enum};
  7         19  
513 3 100       11 return None unless keys %enum;
514             }
515              
516 13         28 delete @enum{keys %exclude_enum};
517              
518 13 100       122 return None unless keys %enum;
519              
520 5         14 $first_enum->clone(args => [sort values %enum])->init;
521             }
522              
523             # Обрабатывает пересечение границ диапазонов
524             sub _ranges_bag(@) {
525 2685     2685   2673 my $ranges_fn = shift;
526 2685         2598 my $enums_fn = shift;
527 2685         4826 my %bag; my @any; my @enums; my @exclude_enums;
  2685         0  
  2685         0  
528 2685         3177 for my $candidate (@_) {
529 18377         18887 my $addr = Scalar::Util::refaddr $candidate->{coerce};
530 18377 100 100     25527 if(exists $range_lbound{$addr}) { push @{$bag{$addr}}, $candidate }
  1469 100       1351  
  1469 100       2961  
531 23         58 elsif($candidate->is_enum) { push @enums, $candidate }
532 15         32 elsif($candidate->is_exclude && $candidate->{args}[0]->is_enum) { push @exclude_enums, $candidate->{args}[0] }
533 16870         20416 else { push @any, $candidate }
534             }
535            
536 2685 100 100     8328 return @any, @enums || @exclude_enums? $enums_fn->(\@enums, \@exclude_enums): (), map $ranges_fn->($_), values %bag;
537             }
538              
539             # Создание пересечения с приведением
540             sub _intersection(@) {
541 2278 100   2278   3432 my %x = map {($_->key => $_)} _ranges_bag \&_intersection_ranges, \&_intersection_enums, map { $_->is_intersection? @{$_->{args}}: $_ } @_;
  14920         15508  
  8606         9036  
  2731         5229  
542             # ~Any & A = ~Any
543 2278 100       4957 return None if exists $x{None->key};
544             # Any & A = A
545 2216         34547 delete $x{Any->key};
546             # Intersection[A] = A
547 2216 100       3211 return (values %x)[0] if 1 == keys %x;
548             # Intersection[] = Any
549 2183 100       2758 return Any if 0 == keys %x;
550             # A & ~A = ~Any
551 2182 100   6985   7813 return None if List::Util::first { $_->is_exclude && exists $x{$_->{args}[0]->key} } values %x;
  6985 100       8069  
552 988         12478 Aion::Types::Intersection([values %x]);
553             }
554              
555             # Создание объединения с приведением
556             sub _union(@) {
557 407 100   407   703 my %x = map {($_->key => $_)} _ranges_bag \&_union_ranges, \&_union_enums, map { $_->is_union? @{$_->{args}}: $_ } @_;
  3360         3590  
  2841         2929  
  78         186  
558             # Any | A = Any
559 407 50       6816 return Any if exists $x{Any->key};
560             # ~Any | A = A
561 407         705 delete $x{None->key};
562             # Union[A] = A
563 407 100       834 return (values %x)[0] if 1 == keys %x;
564             # Union[] = None
565 352 100       500 return None if 0 == keys %x;
566             # A | ~A = Any
567 332 100   1859   1400 return Any if List::Util::first { $_->is_exclude && exists $x{$_->{args}[0]->key} } values %x;
  1859 50       2117  
568 332         4868 Aion::Types::Union([values %x]);
569             }
570              
571             # A <= B <=> A & ~B = ∅
572             sub subset {
573 63     63 1 800 my ($self, $other) = @_;
574              
575 63 100 100     153 return 1 if $self eq $other or $other eq Any;
576              
577 50         133 ($self & ~$other)->_simplify eq None;
578             }
579              
580             # A < B (Строгое включение: подтип, но не равен) = A <= B && !(B <= A)
581             sub subproper {
582 13     13 1 1061 my ($self, $other) = @_;
583 13 100       35 $self->subset($other) && !$other->subset($self);
584             }
585              
586             # A >= B = B <= A
587             sub superset {
588 0     0 1 0 my ($self, $other) = @_;
589 0         0 $other->subset($self);
590             }
591              
592             # A > B = B < A
593             sub superproper {
594 1     1 1 3 my ($self, $other) = @_;
595 1         3 $other->subproper($self);
596             }
597              
598             # A == B (Эквивалентность типов: A является подтипом B И B является подтипом A) = A <= B && B <= A
599             sub equals {
600 7     7 1 18 my ($self, $other) = @_;
601 7 100 33     18 $self eq $other || $self->subset($other) && $other->subset($self);
602             }
603              
604             # A != B
605             sub differs {
606 0     0 1 0 my ($self, $other) = @_;
607 0         0 !$self->equals($other);
608             }
609              
610             # Пересекаются
611             sub joint {
612 2     2 1 5 my ($self, $other) = @_;
613 2         5 !$self->disjoint($other);
614             }
615              
616             # Не пересекаются
617             sub disjoint {
618 4     4 1 7 my ($self, $other) = @_;
619 4         8 ($self & $other)->_simplify eq None;
620             }
621              
622             #@category swagger
623              
624             # Заголовок
625             sub title {
626 0     0 1 0 my ($self, $title) = @_;
627 0 0       0 if(@_ == 1) {
628             $self->{title}
629 0         0 } else {
630 0         0 bless {%$self, title => $title}, ref $self
631             }
632             }
633              
634             # Описание
635             sub description {
636 0     0 1 0 my ($self, $description) = @_;
637 0 0       0 if(@_ == 1) {
638             $self->{description}
639 0         0 } else {
640 0         0 bless {%$self, description => $description}, ref $self
641             }
642             }
643              
644             # Описание
645             sub example {
646 0     0 1 0 my ($self, $description) = @_;
647 0 0       0 if(@_ == 1) {
648             $self->{example}
649 0         0 } else {
650 0         0 bless {%$self, example => $description}, ref $self
651             }
652             }
653              
654             #@category makers
655              
656             # Создаёт функцию для типа
657             sub make {
658 333     333 1 509 my ($self, $pkg) = @_;
659            
660 333 100       583 die "init_where won't work in $self->{name}" if $self->{init};
661            
662 332         449 my $var = "\$$self->{name}";
663              
664 332         479 my $code = "package $pkg {
665             my $var = \$self;
666             sub $self->{name} () { $var }
667             }";
668 332     2738 1 38002 eval $code;
  2738     4 1 12147  
  4     25 1 3959  
  25     8 1 6157  
  8     15 1 7828  
  15     3 1 3136  
  3     37 1 5166  
  37     35 1 3393  
  35     18 1 192  
  18     4 1 3133  
  4     3 1 4161  
  3     5 1 3549  
  5     4 1 6335  
  4     2 1 4768  
  2     6 1 3572  
  6     19 1 6556  
  19     4 1 9190  
  4     2 1 3991  
  2     105 1 4342  
  105     42 1 238316  
  42     6 1 282  
  6     42 1 8948  
  42     9 1 7745  
  9     3 1 7456  
  3     82 1 3601  
  82     7 0 285131  
  7     17 0 51  
  17     3 1 4701  
  3     13 1 3585  
  13     4 1 9479  
  4     9 1 4201  
  9     108 1 3840  
  108     6 1 6092  
  6     2 1 7812  
  2     4 1 4472  
  4     182 1 3922  
  182     2 1 346795  
  2     2 1 3185  
  2     10 1 3156  
  10     17 1 5309  
  17     15 1 6707  
  15     18 1 9164  
  18     3 1 6262  
  3     2 1 3554  
  2     19 1 3131  
  19     7 1 3569  
  7     2   5888  
  2         2789  
669 332 100       1734 die if $@;
670              
671 331         3220 $self
672             }
673              
674             # Создаёт функцию для типа c аргументом
675             sub make_arg {
676 219     219 1 369 my ($self, $pkg, $is_arg) = @_;
677              
678 219         313 my $hash = "%$self->{name}";
679 219 100       374 my $proto = $is_arg? '$': '';
680              
681 219 100       318 if($is_arg) {
682 211 100       310 my $init = $self->{init}? '->init': '';
683 211         340 my $code = "package $pkg {
684             my $hash = %\$self;
685             sub $self->{name} (\$) { Aion::Type->new($hash, args => \$_[0])$init }
686             }";
687 211     15 0 30304 eval $code;
  15     4 0 14788  
  4     4 0 5484  
  4     7 0 28  
  7     2 0 37  
  2     47 0 3325  
  47     470 0 19195  
  470     6 0 1275  
  6     5 0 6670  
  5     3 0 6046  
  3     1349 0 5106  
  1349     5 0 3698  
  5     9 0 35  
  9     29 0 4373  
  29     29 0 8105  
  29     19 0 17796  
  19     5 0 8337  
  5     6 0 34  
  6     3 0 42  
  3     4 0 3943  
  4     15 0 23  
  15     3 0 11226  
  3     121 0 4740  
  121     11 0 22404  
  11     3 0 5407  
  3     4 0 6405  
  4     24 0 3577  
  24     387 0 4690  
  387     1 0 1234  
  1         5  
688 211 50       923 die if $@;
689 211         4240 return $self;
690             }
691            
692 8         20 my $code = "package $pkg {
693             my $hash = %\$self;
694             sub $self->{name} () { Aion::Type->new($hash)->init }
695             }";
696 8     8 1 1114 eval $code;
  8         4463  
697 8 100       326 die if $@;
698              
699 7         25 $self
700             }
701              
702             # Создаёт функцию для типа c аргументом или без.
703             # init вызывается только для типа с аргументами. Без аргументов возвращается один и тот же тип
704             sub make_maybe_arg {
705 74     74 1 144 my ($self, $pkg) = @_;
706              
707 74         109 my $var = "\$$self->{name}";
708 74         107 my $hash = "%$self->{name}";
709 74 100       125 my $init = $self->{init}? '->init': '';
710              
711 74         165 my $code = "package $pkg;
712              
713             my $var = \$self;
714             my $hash = %\$self;
715              
716             sub $self->{name} (;\$) {
717             \@_==0? $var:
718             Aion::Type->new(
719             $hash,
720             args => \$_[0],
721             test => ${var}->{a_test},
722             )$init
723             }
724             ";
725 74 100   16 0 11173 eval $code or die;
  16 100   37 1 5817  
  37 100   30 0 4738  
  30 100   23 0 13914  
  23 100   52 0 5050  
  52 100   13 0 7872  
  13 100   4 0 6907  
  4 100   4 0 4302  
  4 100   14 0 4243  
  14 100   19 0 9455  
  19 100       4343  
726            
727 73         9625 $self
728             }
729              
730              
731             1;
732              
733             __END__