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 8     8   124147 use common::sense;
  8         12  
  8         48  
4 8     8   631 use warnings FATAL => 'recursion';
  8         24  
  8         534  
5             #use warnings 'recursion';
6              
7 8     8   365 use Aion::Meta::Util qw//;
  8         29  
  8         167  
8 8     8   2860 use Aion::Type::Lim;
  8         25  
  8         264  
9 8     8   43 use List::Util qw//;
  8         11  
  8         117  
10 8     8   27 use Scalar::Util qw//;
  8         12  
  8         2419  
11              
12 213     213 1 666 sub true {1}
13              
14             use overload
15             "fallback" => 1,
16             "&{}" => sub {
17 2     2   402 my ($self) = @_;
18 2     2   7 sub { $self->test }
19 2         7 },
20             '""' => "stringify",
21 48     48   4034 "|" => sub { Aion::Types::Union([@_[0, 1]]) },
22 103     103   3069 "&" => sub { Aion::Types::Intersection([@_[0, 1]]) },
23 467     467   10454 "~" => 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 8         173 "cmp" => "compare",
33             "<=>" => "compare",
34             "==" => "equals",
35             "!=" => "differs",
36             ">=" => "superset",
37             "<=" => "subset",
38             ">" => "superproper",
39             "<" => "subproper",
40 8     8   74 ;
  8         11  
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 3536     3536 1 304329 my $cls = shift;
77 3536         14003 my $self = bless {@_}, $cls;
78 3536   100     8730 $self->{test} //= \&test;
79 3536   100     7600 $self->{coerce} //= [];
80 3536         7575 $self
81             }
82              
83             # Клонировать тип
84             sub clone {
85 293     293 1 365 my $self = shift;
86 293         1824 $self = bless { %$self, @_ }, ref $self;
87 293         644 delete @$self{qw/key as_test_cache/};
88 293         724 $self
89             }
90              
91             # Инициализировать тип
92             sub init {
93 2669     2669 1 4027 my ($self) = @_;
94            
95             # Есть параметрические типы – не инициализируем
96 2669 100 100 6857   10728 return $self if $self->{args} && List::Util::first { UNIVERSAL::isa($_, __PACKAGE__) && exists $_->{is_param} } @{$self->{args}};
  6857 100       20163  
  2661         6934  
97              
98 2656         7246 local $Aion::Type::SELF = $self;
99 2656         3342 $_->() for @{$self->{init}};
  2656         7914  
100              
101 2655         15932 $self
102             }
103              
104             #@category strings
105              
106             # Строковое представление
107             sub stringify {
108 17683     17683 1 459416 my ($self) = @_;
109              
110 17683         21532 my @args = map Aion::Meta::Util::val_to_str($_), @{$self->{args}};
  17683         35392  
111              
112             $self->is_union? join "", "( ", join(" | ", @args), " )":
113             $self->is_intersection? join "", "( ", join(" & ", @args), " )":
114             $self->is_exclude? "~$args[0]":
115 17683 100       28756 join("", $self->{name}, @args? ("[", join(", ", @args), "]") : ());
    100          
    100          
    100          
116             }
117              
118             # Сообщение об ошибке
119             sub detail {
120 15     15 1 78 (my $self, local $_, my $name) = @_;
121 15         29 local $Aion::Type::SELF = $self;
122 15 100       66 $self->{message}? do { local $self->{property} = $name; $self->{message}->() }:
  3         11  
  3         11  
123             "$name must have the type $self. The it is ${\
124 12         69 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         4 Aion::Meta::Util::val_to_str($val)
132             }
133              
134             #@category test
135              
136             # Строит кеш для вызова только для примитивного типа
137             sub _build_as_test_cache {
138 392     392   752 my ($self) = @_;
139              
140 392         573 my @as;
141 392         1327 for(my $i = $self->{as}; $i; $i = $i->{as}) {
142 1169 100       2002 return "" if $i->is_set_theoretic;
143 1047 100       3952 unshift @as, $i if $i->{test} != \&true;
144             }
145            
146 270         913 \@as;
147             }
148              
149             # Это - примитивный тип, то есть тот, в иерархии которого нет множественно-теоритических операторов
150             sub is_primitive {
151 2     2 1 3 my ($self) = @_;
152 2   66     11 !!($self->{as_test_cache} //= $self->_build_as_test_cache);
153             }
154              
155             # Тестировать значение в $_
156             sub test {
157 1521     1521 1 2829 my ($self) = @_;
158              
159 1521 100 100     5233 if($self->{as_test_cache} //= $self->_build_as_test_cache) {
160 1189         1423 local $Aion::Type::SELF;
161 1189         1244 for $Aion::Type::SELF (@{$self->{as_test_cache}}) {
  1189         2508  
162 2045 100       4459 return "" unless $Aion::Type::SELF->{test}->();
163             }
164             } else {
165 332 100 66     697 return "" if $self->{as} && !$self->{as}->test;
166             }
167              
168 1442         3130 local $Aion::Type::SELF = $self;
169 1442         3408 $self->{test}->();
170             }
171              
172             # Является элементом множества описываемого типом
173             sub include {
174 740     740 1 9105 (my $self, local $_) = @_;
175 740         1758 $self->test
176             }
177              
178             # Не является элементом множества описываемого типом
179             sub exclude {
180 59     59 1 1260 (my $self, local $_) = @_;
181 59         119 !$self->test
182             }
183              
184             # Валидировать значение в параметре
185             sub validate {
186 164     164 1 1021 (my $self, local $_, my $name) = @_;
187 164 100       330 die $self->detail($_, $name) unless $self->test;
188 151         2320 $_
189             }
190              
191             # Преобразовать значение в параметре и вернуть преобразованное
192             sub coerce {
193 30     30 1 1325 local ($Aion::Type::SELF, $_) = @_;
194              
195 30         44 for my $coerce (@{$Aion::Type::SELF->{coerce}}) {
  30         94  
196 34 100       123 return $coerce->[1]() if $coerce->[0]->test;
197             }
198             $_
199 3         39 }
200              
201             #@category compare
202              
203             #my $_any; my $_none;
204 3     3 1 19 sub Any() { *Any = \&Aion::Types::Any; &Any }
  3         93  
205 3     3 1 12 sub None() { *None = \&Aion::Types::None; &None }
  3         11  
206              
207             # refaddr coerce => минимальная нижняя граница. У Range она -Inf, а у остальных – 0
208             our %range_lbound;
209              
210             # Определяет, что тип – множественно-теоретический оператор
211             my $set_theoretic = [qw/Union Intersection Exclude/];
212 2659     2659 1 7620 sub is_set_theoretic { shift->{name} ~~ $set_theoretic }
213 22716     22716 1 53802 sub is_union { shift->{name} eq 'Union' }
214 27506     27506 1 58921 sub is_intersection { shift->{name} eq 'Intersection' }
215 43306     43306 1 126320 sub is_exclude { shift->{name} eq 'Exclude' }
216 23244     23244 1 49388 sub is_enum { shift->{name} eq 'Enum' }
217 304     304 1 916 sub is_range_type { exists $range_lbound{Scalar::Util::refaddr shift->{coerce}} }
218 60     60 1 270 sub range_lbound { $range_lbound{Scalar::Util::refaddr shift->{coerce}} }
219 28     28 1 58 sub is_range { shift->range_lbound == '-Inf' }
220              
221             # Формирует ключ с отсортированными типизированными параметрами
222             sub typed_sorted_args_key {
223 996     996 1 1288 my ($self) = @_;
224 996         1211 my $coerceaddr = Scalar::Util::refaddr $self->{coerce};
225 996         1276 join "-", $coerceaddr, join(",", map { join ":", length($_), $_ } sort map $_->key, @{$self->{args}});
  2753         8422  
  996         1820  
226             }
227              
228             # Формирует ключ с отсортированными нетипизированными параметрами
229             sub sorted_args_key {
230 28     28 1 39 my ($self) = @_;
231 28         57 my $coerceaddr = Scalar::Util::refaddr $self->{coerce};
232 28         37 join "-", $coerceaddr, join(",", map { join ":", length($_), $_ } sort @{$self->{args}});
  55         354  
  28         71  
233             }
234              
235             # Возвращает уникальный ключ для типа, использующийся в хешах и сравнения
236             # Должен быть заменён на созданные типы
237             my %keyfn;
238             my $undefined = [];
239             sub key {
240 30335     30335 1 38512 my ($self) = @_;
241 30335   66     79467 $self->{key} //= do {
242 1447         1937 my $coerceaddr = Scalar::Util::refaddr $self->{coerce};
243 1447         2045 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     1393 my $key = UNIVERSAL::isa($_, __PACKAGE__)? $_->key: "" . ($_ // $undefined);
249 498         2510 join ":", length($key), $key
250 1447 100 66     2828 } @{$self->{args}})
  389 100       570  
251             : ();
252             };
253             }
254              
255             # Устанавливает/возвращает функцию построения ключа для типа как класса
256             sub keyfn {
257 26     26 1 55 my ($self, $fn) = @_;
258 26 50       54 if(@_>1) {
259 26         74 $keyfn{Scalar::Util::refaddr $self->{coerce}} = $fn;
260 26         62 $self
261             } else {
262 0         0 $keyfn{Scalar::Util::refaddr $self->{coerce}};
263             }
264             }
265              
266             # Возвращает цепочку предков
267             sub asen {
268 16     16 1 19 my ($self) = @_;
269 16         16 my @as;
270 16         35 for(my $i=$self->{as}; $i; $i = $i->{as}) { unshift @as, $i }
  56         108  
271 16 50 33     255 unshift @as, Any unless @as && $as[0] eq Any;
272             @as
273 16         41 }
274              
275             # Ключ для сравнения типов в <=> и cmp
276             sub ckey {
277 18     18 1 20 my ($self) = @_;
278 18   66     58 $self->{ckey} //= join " <- ", map $_->stringify, $self->asen, $self;
279             }
280              
281             # Сравнение для сортировки
282             sub compare {
283 9     9 1 15 my ($self, $other) = @_;
284 9         16 $self->ckey cmp $other->ckey;
285             }
286              
287             # A потомок B
288             sub instanceof {
289 6     6 1 807 my ($self, $name) = @_;
290              
291 6         11 my @S = $self;
292 6         18 while(@S) {
293 16         23 my $x = pop @S;
294 16 100       53 return 1 if $x->{name} eq $name;
295 11 100       31 if($x->is_intersection) { push @S, @{$x->{args}} }
  3 100       8  
  3         13  
296             elsif($x->is_set_theoretic) {}
297 7 100       21 else { push @S, $x->{as} if $x->{as} }
298             }
299              
300             ""
301 1         2 }
302              
303             # A потомок B
304             sub is_descendant {
305 33     33 1 39 my ($self, $other, $is_strict) = @_;
306            
307 33 100 33     103 return 1 if $is_strict && $self eq $other
      66        
      66        
308             || !$is_strict && $self->like($other);
309              
310 29 100       34 if ($self->is_intersection) {
311 2     3   5 return List::Util::any { $_->is_descendant($other, $is_strict) } @{$self->{args}};
  3         4  
  2         6  
312             }
313 27 100       31 if ($self->is_union) {
314 3     6   9 return List::Util::all { $_->is_descendant($other, $is_strict) } @{$self->{args}};
  6         12  
  3         7  
315             }
316 24 50       28 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       32 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   40 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   41 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     40 return $self->{args}[0]->like($other->{args}[0]) if $self->is_exclude && $other->is_exclude;
330 34 100 66     37 return "" if $self->is_set_theoretic || $other->is_set_theoretic;
331 29         88 $self->{coerce} == $other->{coerce};
332             }
333              
334             # Тождество
335             sub identical {
336 225     225 1 5027 my ($self, $other) = @_;
337              
338 225 100       3003 return 1 if Scalar::Util::refaddr $self == Scalar::Util::refaddr $other;
339             return "" unless UNIVERSAL::isa($other, __PACKAGE__)
340 159 100 100     2319 && $self->{coerce} == $other->{coerce};
341              
342 46         160 $self->key eq $other->key
343             }
344              
345             # Нетождественно
346             sub distinct {
347 2     2 1 385 my ($self, $other) = @_;
348 2         5 !$self->identical($other);
349             }
350              
351             # Превращает выражение в ДНФ
352 56     56   197 sub _simplify { shift->_unfolding->_pushing->_distribute }
353              
354             # Упрощает выражение
355             # TODO: использовать алгоритм Espresso для свёртки DNF
356             sub simplify {
357 2     2 1 12 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   686 my ($self) = @_;
365            
366 449         483 my @u;
367 449         868 for(my $i=$self; $i; $i = $i->{as}) {
368 1419 100       2024 unshift(@u, $i->clone(args => [map $_->_unfolding, @{$i->{args}}])), last if $i->is_set_theoretic;
  222         589  
369 1197 100       3869 unshift @u, $i if $i->{test} != \&true;
370             }
371              
372 449 100       6511 @u == 0? Any:
    100          
373             @u == 1? $u[0]: Aion::Types::Intersection(\@u);
374             }
375              
376             # Проталкивание исключений к термам, заодно уменьшает размерность с приведением
377             sub _pushing {
378 1147     1147   1615 my ($self) = @_;
379            
380 1147 100       1579 if($self->is_exclude) {
381 449         734 my $inner = $self->{args}[0];
382             # ~(~A) => A
383 449 100       708 return $inner->{args}[0]->_pushing if $inner->is_exclude;
384             # ~(A | B) => ~A & ~B
385 448 100       814 return _intersection(map { (~$_)->_pushing } @{$inner->{args}}) if $inner->is_union;
  60         113  
  30         89  
386             # ~(A & B) => ~A | ~B
387 418 100       694 return _union(map { (~$_)->_pushing } @{$inner->{args}}) if $inner->is_intersection;
  339         555  
  116         210  
388             # Range[A, B] => Range[-Inf, Invert[A]] | Range[Invert[B], Inf]
389 302 100       546 if($inner->is_range_type) {
390 25         41 my ($min, $max) = @{$inner->{args}};
  25         79  
391 25 100       68 if($inner->is_range) {
392 11 50 33     57 return None if $min == '-Inf' && $max == 'Inf';
393 11 50       36 return $inner->clone(args => [Aion::Type::Lim->from($max)->inc, 'Inf']) if $min == '-Inf';
394 11 100       56 return $inner->clone(args => ['-Inf', Aion::Type::Lim->from($min)->dec]) if $max == 'Inf';
395 7         43 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     46 return None if $min == 0 && $max == 'Inf';
399 13 100       41 return $inner->clone(args => [$max+1, 'Inf']) if $min == 0;
400 5 100       32 return $inner->clone(args => [0, $min-1]) if $max == 'Inf';
401 4         14 return $inner->clone(args => [0, $min-1]) | $inner->clone(args => [$max+1, 'Inf']);
402             }
403 277         678 return $self;
404             }
405              
406 698 100       962 return _intersection(map $_->_pushing, @{$self->{args}}) if $self->is_intersection;
  225         540  
407 473 100       684 return _union(map $_->_pushing, @{$self->{args}}) if $self->is_union;
  46         145  
408              
409 427         856 $self
410             }
411              
412             # Сжимает в ДНФ
413             sub _distribute {
414 886     886   1057 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       1229 if($self->is_intersection) {
418 124 100       162 my @disjuncts = map { my $x = $_->_distribute; $x->is_union? [@{$x->{args}}]: [$x] } @{$self->{args}};
  443         662  
  443         740  
  121         611  
  124         217  
419            
420             my $dnf = List::Util::reduce {
421 443     443   670 [ map { my $p = $_; map { [@$p, $_] } @$b } @$a ]
  2184         2302  
  2184         2562  
  4083         8722  
422 124         893 } [[]], @disjuncts;
423            
424 124         639 return _union(map _intersection(@$_), @$dnf);
425             }
426              
427 762 100       1065 return _union(map $_->_distribute, @{$self->{args}}) if $self->is_union;
  121         364  
428            
429 641         1084 $self
430             }
431              
432             # Объединение интервалов
433             sub _union_ranges {
434 51     51   110 my ($ranges) = @_;
435              
436             # Отсекаем пустые
437 51         269 my @ranges = grep $_->{args}[0] <= $_->{args}[1], @$ranges;
438              
439             # Сортируем в порядке возрастания нижней границы
440 51         167 (my $range, @ranges) = sort { $a->{args}[0] <=> $b->{args}[0] } @ranges;
  22         85  
441              
442             @ranges = map {
443 51         85 my ($min1, $max1) = @{$range->{args}};
  22         36  
  22         57  
444 22         37 my ($min2, $max2) = @{$_->{args}};
  22         55  
445 22 50       47 if($max1 > $min2) { $range = $range->clone(args => [$min1, List::Util::max($max1, $max2)]); () }
  0         0  
  0         0  
446 22         42 else { my $arange = $range; $range = $_; $arange }
  22         35  
  22         57  
447             } @ranges;
448 51         96 push @ranges, $range;
449              
450 51 100       106 if(@ranges == 1) {
451 29         46 my ($min, $max) = @{$range->{args}};
  29         64  
452 29 50 66     57 return Any if $min == $range->range_lbound && $max == 'Inf';
453             }
454              
455             @ranges
456 51         224 }
457              
458             # Обрабатывает пересечение границ однотипных диапазонов
459             sub _intersection_ranges($) {
460 1318     1318   2021 my ($ranges) = @_;
461              
462             # Пустой диапазон - это None
463 1318 50       4427 return None if 0 == grep $_->{args}[0] <= $_->{args}[1], @$ranges;
464            
465             # Сортируем в порядке возрастания нижней границы
466 1318         2618 my ($range, @ranges) = sort { $a->{args}[0] <=> $b->{args}[0] } @$ranges;
  78         351  
467              
468 1318         1956 for my $arange (@ranges) {
469             # Если хотя бы у одного нет пересечений – это None
470 78         104 my ($min1, $max1) = @{$range->{args}};
  78         180  
471 78         101 my ($min2, $max2) = @{$arange->{args}};
  78         186  
472 78         307 my $max = List::Util::min($max1, $max2);
473 78 100       183 return None if $min2 > $max;
474 25         127 $range = $range->clone(args => [$min2, $max]);
475             }
476              
477             $range
478 1265         5633 }
479              
480             # Объединение перечислений
481             sub _union_enums($,$) {
482 3     3   8 my ($enums, $exclude_enums) = @_;
483            
484 3         4 my %enum = map {($_=>$_)} map @{$_->{args}}, @$enums;
  8         33  
  6         14  
485 3 50       42 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   28 my ($enums, $exclude_enums) = @_;
504            
505 16         22 my %exclude_enum = map {($_=>$_)} map @{$_->{args}}, @$exclude_enums;
  29         58  
  15         36  
506 16 100       46 return ~$exclude_enums->[0]->clone(args => [sort values %exclude_enum])->init unless @$enums;
507            
508 14         22 my $first_enum = shift(@$enums);
509 14         18 my %enum = map {($_=>$_)} @{$first_enum->{args}};
  30         49  
  14         23  
510              
511 14         23 for my $enum (@$enums) {
512 3         6 delete @enum{grep { !($_ ~~ $enum->{args}) } keys %enum};
  7         19  
513 3 100       32 return None unless keys %enum;
514             }
515              
516 13         28 delete @enum{keys %exclude_enum};
517              
518 13 100       37 return None unless keys %enum;
519              
520 5         18 $first_enum->clone(args => [sort values %enum])->init;
521             }
522              
523             # Обрабатывает пересечение границ диапазонов
524             sub _ranges_bag(@) {
525 2685     2685   3641 my $ranges_fn = shift;
526 2685         2923 my $enums_fn = shift;
527 2685         6185 my %bag; my @any; my @enums; my @exclude_enums;
  2685         0  
  2685         0  
528 2685         4088 for my $candidate (@_) {
529 18377         25481 my $addr = Scalar::Util::refaddr $candidate->{coerce};
530 18377 100 100     33715 if(exists $range_lbound{$addr}) { push @{$bag{$addr}}, $candidate }
  1469 100       1738  
  1469 100       3883  
531 23         40 elsif($candidate->is_enum) { push @enums, $candidate }
532 15         29 elsif($candidate->is_exclude && $candidate->{args}[0]->is_enum) { push @exclude_enums, $candidate->{args}[0] }
533 16870         27080 else { push @any, $candidate }
534             }
535            
536 2685 100 100     10552 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   5060 my %x = map {($_->key => $_)} _ranges_bag \&_intersection_ranges, \&_intersection_enums, map { $_->is_intersection? @{$_->{args}}: $_ } @_;
  14920         22196  
  8606         12638  
  2731         7349  
542             # ~Any & A = ~Any
543 2278 100       7261 return None if exists $x{None->key};
544             # Any & A = A
545 2216         48977 delete $x{Any->key};
546             # Intersection[A] = A
547 2216 100       4438 return (values %x)[0] if 1 == keys %x;
548             # Intersection[] = Any
549 2183 100       3677 return Any if 0 == keys %x;
550             # A & ~A = ~Any
551 2182 100   6942   11589 return None if List::Util::first { $_->is_exclude && exists $x{$_->{args}[0]->key} } values %x;
  6942 100       11215  
552 988         17655 Aion::Types::Intersection([values %x]);
553             }
554              
555             # Создание объединения с приведением
556             sub _union(@) {
557 407 100   407   983 my %x = map {($_->key => $_)} _ranges_bag \&_union_ranges, \&_union_enums, map { $_->is_union? @{$_->{args}}: $_ } @_;
  3360         5922  
  2841         4103  
  78         302  
558             # Any | A = Any
559 407 50       9371 return Any if exists $x{Any->key};
560             # ~Any | A = A
561 407         946 delete $x{None->key};
562             # Union[A] = A
563 407 100       1096 return (values %x)[0] if 1 == keys %x;
564             # Union[] = None
565 352 100       723 return None if 0 == keys %x;
566             # A | ~A = Any
567 332 100   1859   1973 return Any if List::Util::first { $_->is_exclude && exists $x{$_->{args}[0]->key} } values %x;
  1859 50       2731  
568 332         6550 Aion::Types::Union([values %x]);
569             }
570              
571             # A <= B <=> A & ~B = ∅
572             sub subset {
573 63     63 1 1163 my ($self, $other) = @_;
574              
575 63 100 100     150 return 1 if $self eq $other or $other eq Any;
576              
577 50         139 ($self & ~$other)->_simplify eq None;
578             }
579              
580             # A < B (Строгое включение: подтип, но не равен) = A <= B && !(B <= A)
581             sub subproper {
582 13     13 1 905 my ($self, $other) = @_;
583 13 100       32 $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         4 $other->subproper($self);
596             }
597              
598             # A == B (Эквивалентность типов: A является подтипом B И B является подтипом A) = A <= B && B <= A
599             sub equals {
600 7     7 1 21 my ($self, $other) = @_;
601 7 100 33     21 $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 4 my ($self, $other) = @_;
613 2         7 !$self->disjoint($other);
614             }
615              
616             # Не пересекаются
617             sub disjoint {
618 4     4 1 9 my ($self, $other) = @_;
619 4         13 ($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 379     379 1 663 my ($self, $pkg) = @_;
659            
660 379 100       778 die "init_where won't work in $self->{name}" if $self->{init};
661            
662 378         618 my $var = "\$$self->{name}";
663              
664 378         644 my $code = "package $pkg {
665             my $var = \$self;
666             sub $self->{name} () { $var }
667             }";
668 378     2742 1 52064 eval $code;
  2742     4 1 14377  
  4     27 1 6684  
  27     8 1 8316  
  8     15 1 5954  
  15     3 1 7872  
  3     42 1 5319  
  42     40 1 3511  
  40     20 1 277  
  20     4 1 4611  
  4     3 1 5048  
  3     5 1 8806  
  5     4 1 4693  
  4     2 1 9644  
  2     6 1 3770  
  6     20 1 5021  
  20     4 1 25368  
  4     2 1 7503  
  2     108 1 3328  
  108     47 1 248822  
  47     6 1 425  
  6     47 1 9085  
  47     12 1 6312  
  12     3 1 17066  
  3     86 1 9207  
  86     8 0 478732  
  8     19 0 70  
  19     3 1 6067  
  3     13 1 16999  
  13     4 1 15671  
  4     10 1 22030  
  10     122 1 6715  
  122     6 1 8584  
  6     2 1 7897  
  2     4 1 3290  
  4     202 1 19216  
  202     2 1 437079  
  2     2 1 7955  
  2     11 1 13858  
  11     18 1 4991  
  18     15 1 9566  
  15     20 1 29295  
  20     3 1 17508  
  3     2 1 6764  
  2     21 1 10337  
  21     7 1 4891  
  7     2   18670  
  2         2673  
669 378 100       2221 die if $@;
670              
671 377         3735 $self
672             }
673              
674             # Создаёт функцию для типа c аргументом
675             sub make_arg {
676 249     249 1 510 my ($self, $pkg, $is_arg) = @_;
677              
678 249         390 my $hash = "%$self->{name}";
679 249 100       434 my $proto = $is_arg? '$': '';
680              
681 249 100       410 if($is_arg) {
682 240 100       442 my $init = $self->{init}? '->init': '';
683 240         459 my $code = "package $pkg {
684             my $hash = %\$self;
685             sub $self->{name} (\$) { Aion::Type->new($hash, args => \$_[0])$init }
686             }";
687 240     15 0 39574 eval $code;
  15     4 0 32678  
  4     4 0 4247  
  4     7 0 26  
  7     2 0 49  
  2     48 0 8517  
  48     471 0 21585  
  471     6 0 1675  
  6     5 0 5307  
  5     3 0 5048  
  3     1354 0 3986  
  1354     5 0 5219  
  5     10 0 23  
  10     30 0 4638  
  30     30 0 10556  
  30     20 0 27155  
  20     5 0 6594  
  5     6 0 25  
  6     3 0 56  
  3     4 0 3614  
  4     15 0 37  
  15     3 0 19638  
  3     129 0 3660  
  129     12 0 25037  
  12     3 0 6145  
  3     4 0 12900  
  4     24 0 9485  
  24     391 0 7024  
  391     1 0 1654  
  1         6  
688 240 50       1301 die if $@;
689 240         5983 return $self;
690             }
691            
692 9         23 my $code = "package $pkg {
693             my $hash = %\$self;
694             sub $self->{name} () { Aion::Type->new($hash)->init }
695             }";
696 9     8 1 1344 eval $code;
  8         5045  
697 9 100       385 die if $@;
698              
699 8         34 $self
700             }
701              
702             # Создаёт функцию для типа c аргументом или без.
703             # init вызывается только для типа с аргументами. Без аргументов возвращается один и тот же тип
704             sub make_maybe_arg {
705 84     84 1 145 my ($self, $pkg) = @_;
706              
707 84         144 my $var = "\$$self->{name}";
708 84         122 my $hash = "%$self->{name}";
709 84 100       172 my $init = $self->{init}? '->init': '';
710              
711 84         186 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 84 100   17 0 14452 eval $code or die;
  17 100   41 1 5949  
  41 100   32 0 3652  
  32 100   25 0 23717  
  25 100   56 0 4165  
  56 100   14 0 8090  
  14 100   4 0 5452  
  4 100   4 0 3438  
  4 100   14 0 3286  
  14 100   21 0 9273  
  21 100       3338  
726            
727 83         8742 $self
728             }
729              
730              
731             1;
732              
733             __END__