| 
407
 | 
     1 package IMPL::lang;
 | 
| 
 | 
     2 use strict;
 | 
| 
 | 
     3 use warnings;
 | 
| 
 | 
     4 
 | 
| 
 | 
     5 use parent qw(Exporter);
 | 
| 
 | 
     6 use IMPL::_core::version;
 | 
| 
 | 
     7 use IMPL::clone qw(clone);
 | 
| 
 | 
     8 use Scalar::Util qw(blessed);
 | 
| 
408
 | 
     9 use POSIX;
 | 
| 
407
 | 
    10 use Carp qw(carp);
 | 
| 
 | 
    11 
 | 
| 
409
 | 
    12 our @EXPORT      = qw(&is &isclass &typeof &coarsen &coarsen_dt);
 | 
| 
407
 | 
    13 our %EXPORT_TAGS = (
 | 
| 
 | 
    14     base => [
 | 
| 
 | 
    15         qw(
 | 
| 
 | 
    16           &is
 | 
| 
 | 
    17           &clone
 | 
| 
 | 
    18           &isclass
 | 
| 
 | 
    19           &typeof
 | 
| 
 | 
    20           )
 | 
| 
 | 
    21     ],
 | 
| 
 | 
    22 
 | 
| 
 | 
    23     declare => [
 | 
| 
 | 
    24         qw(
 | 
| 
 | 
    25           &public
 | 
| 
 | 
    26           &protected
 | 
| 
 | 
    27           &private
 | 
| 
 | 
    28           &property
 | 
| 
 | 
    29           &static
 | 
| 
 | 
    30           &property
 | 
| 
 | 
    31           &_direct
 | 
| 
 | 
    32           &ACCESS_PUBLIC
 | 
| 
 | 
    33           &ACCESS_PROTECTED
 | 
| 
 | 
    34           &ACCESS_PRIVATE
 | 
| 
 | 
    35           &PROP_GET
 | 
| 
 | 
    36           &PROP_SET
 | 
| 
 | 
    37           &PROP_OWNERSET
 | 
| 
 | 
    38           &PROP_LIST
 | 
| 
 | 
    39           &PROP_ALL
 | 
| 
 | 
    40           &PROP_RO
 | 
| 
 | 
    41           &PROP_RW
 | 
| 
 | 
    42           &PROP_DIRECT
 | 
| 
 | 
    43           )
 | 
| 
 | 
    44     ],
 | 
| 
 | 
    45     compare => [
 | 
| 
 | 
    46         qw(
 | 
| 
 | 
    47           &equals
 | 
| 
 | 
    48           &equals_s
 | 
| 
 | 
    49           &hashCompare
 | 
| 
 | 
    50           )
 | 
| 
 | 
    51     ],
 | 
| 
 | 
    52     hash => [
 | 
| 
 | 
    53         qw(
 | 
| 
 | 
    54           &hashApply
 | 
| 
 | 
    55           &hashMerge
 | 
| 
 | 
    56           &hashDiff
 | 
| 
 | 
    57           &hashCompare
 | 
| 
 | 
    58           &hashParse
 | 
| 
 | 
    59           &hashSave
 | 
| 
 | 
    60           )
 | 
| 
 | 
    61     ]
 | 
| 
 | 
    62 );
 | 
| 
 | 
    63 
 | 
| 
 | 
    64 our @EXPORT_OK = keys %{ { map (($_,1) , map (@{$_}, values %EXPORT_TAGS) ) } };
 | 
| 
 | 
    65 
 | 
| 
 | 
    66 use IMPL::Const qw(:all);
 | 
| 
 | 
    67 
 | 
| 
 | 
    68 sub is($$) {
 | 
| 
 | 
    69     carp "A typename can't be undefined" unless $_[1];
 | 
| 
 | 
    70     blessed($_[0]) and $_[0]->isa( $_[1] );
 | 
| 
 | 
    71 }
 | 
| 
 | 
    72 
 | 
| 
 | 
    73 sub isclass {
 | 
| 
 | 
    74     carp "A typename can't be undefined" unless $_[1];
 | 
| 
 | 
    75     local $@;
 | 
| 
 | 
    76     eval {not ref $_[0] and $_[0]->isa( $_[1] ) };
 | 
| 
 | 
    77 }
 | 
| 
 | 
    78 
 | 
| 
 | 
    79 sub typeof(*) {
 | 
| 
 | 
    80 	local $@;
 | 
| 
 | 
    81     eval { $_[0]->_typeof } || blessed($_[0]) || ref($_[0]);
 | 
| 
 | 
    82 }
 | 
| 
 | 
    83 
 | 
| 
 | 
    84 sub public($) {
 | 
| 
 | 
    85     my $info = shift;
 | 
| 
 | 
    86     $info->{access} = ACCESS_PUBLIC;
 | 
| 
 | 
    87     my $implementor = delete $info->{implementor};
 | 
| 
 | 
    88     $implementor->Implement($info);
 | 
| 
 | 
    89 }
 | 
| 
 | 
    90 
 | 
| 
 | 
    91 sub private($) {
 | 
| 
 | 
    92     my $info = shift;
 | 
| 
 | 
    93     $info->{access} = ACCESS_PRIVATE;
 | 
| 
 | 
    94     my $implementor = delete $info->{implementor};
 | 
| 
 | 
    95     $implementor->Implement($info);
 | 
| 
 | 
    96 }
 | 
| 
 | 
    97 
 | 
| 
 | 
    98 sub protected($) {
 | 
| 
 | 
    99     my $info = shift;
 | 
| 
 | 
   100     $info->{access} = ACCESS_PROTECTED;
 | 
| 
 | 
   101     my $implementor = delete $info->{implementor};
 | 
| 
 | 
   102     $implementor->Implement($info);
 | 
| 
 | 
   103 }
 | 
| 
 | 
   104 
 | 
| 
 | 
   105 sub _direct ($) {
 | 
| 
 | 
   106     my $info = shift;
 | 
| 
 | 
   107     $info->{direct} = 1;
 | 
| 
 | 
   108     return $info;
 | 
| 
 | 
   109 }
 | 
| 
 | 
   110 
 | 
| 
 | 
   111 sub property($$) {
 | 
| 
 | 
   112     my ($propName,$attributes) = @_;
 | 
| 
 | 
   113     
 | 
| 
 | 
   114     $attributes = {
 | 
| 
 | 
   115         get => $attributes & PROP_GET,
 | 
| 
 | 
   116         set => $attributes & PROP_SET,
 | 
| 
 | 
   117         isList => $attributes & PROP_LIST
 | 
| 
 | 
   118     } unless ref $attributes;
 | 
| 
 | 
   119     
 | 
| 
 | 
   120     my $class = caller;
 | 
| 
 | 
   121      
 | 
| 
 | 
   122     return hashMerge (
 | 
| 
 | 
   123         $attributes,
 | 
| 
 | 
   124         {
 | 
| 
 | 
   125             implementor => $class->ClassPropertyImplementor,
 | 
| 
 | 
   126             name => $propName,
 | 
| 
 | 
   127             class => scalar(caller),
 | 
| 
 | 
   128         }
 | 
| 
 | 
   129     );
 | 
| 
 | 
   130 }
 | 
| 
 | 
   131 
 | 
| 
 | 
   132 sub static($$) {
 | 
| 
 | 
   133     my ( $name, $value ) = @_;
 | 
| 
 | 
   134     my $class = caller;
 | 
| 
 | 
   135     $class->static_accessor( $name, $value );
 | 
| 
 | 
   136 }
 | 
| 
 | 
   137 
 | 
| 
408
 | 
   138 sub coarsen {
 | 
| 
 | 
   139 	my ( $value, $resolution ) = @_;
 | 
| 
 | 
   140 	return $resolution ? ceil( $value / $resolution ) * $resolution : $value;
 | 
| 
 | 
   141 }
 | 
| 
 | 
   142 
 | 
| 
409
 | 
   143 # datetime is DateTime object
 | 
| 
 | 
   144 # resolution is DateTime::Duration object, the resulting time will be aligned to it
 | 
| 
 | 
   145 sub coarsen_dt {
 | 
| 
 | 
   146     my ( $datetime, $resolution ) = @_;
 | 
| 
 | 
   147 
 | 
| 
 | 
   148     return $datetime unless $resolution;
 | 
| 
 | 
   149 
 | 
| 
 | 
   150     my $date = $datetime->clone()->truncate( to => "day" );
 | 
| 
 | 
   151 
 | 
| 
 | 
   152     return $date->add(
 | 
| 
 | 
   153         minutes => coarsen(
 | 
| 
 | 
   154             $datetime->subtract_datetime($date)->in_units('minutes'),
 | 
| 
 | 
   155             $resolution->in_units('minutes')
 | 
| 
 | 
   156         )
 | 
| 
 | 
   157     );
 | 
| 
 | 
   158 }
 | 
| 
 | 
   159 
 | 
| 
407
 | 
   160 sub equals {
 | 
| 
 | 
   161     if (defined $_[0]) {
 | 
| 
 | 
   162         return 0 if (not defined $_[1]);
 | 
| 
 | 
   163         
 | 
| 
 | 
   164         return $_[0] == $_[1];
 | 
| 
 | 
   165     }  else {
 | 
| 
 | 
   166         return 0 if defined $_[1];
 | 
| 
 | 
   167         
 | 
| 
 | 
   168         return 1;
 | 
| 
 | 
   169     }
 | 
| 
 | 
   170 }
 | 
| 
 | 
   171 
 | 
| 
 | 
   172 sub equals_s {
 | 
| 
 | 
   173     if (defined $_[0]) {
 | 
| 
 | 
   174         return 0 if (not defined $_[1]);
 | 
| 
 | 
   175         
 | 
| 
 | 
   176         return $_[0] eq $_[1];
 | 
| 
 | 
   177     }  else {
 | 
| 
 | 
   178         return 0 if defined $_[1];
 | 
| 
 | 
   179         
 | 
| 
 | 
   180         return 1;
 | 
| 
 | 
   181     }
 | 
| 
 | 
   182 }
 | 
| 
 | 
   183 
 | 
| 
 | 
   184 sub hashDiff {
 | 
| 
 | 
   185     my ($src,$dst) = @_;
 | 
| 
 | 
   186     
 | 
| 
 | 
   187     $dst = $dst ? { %$dst } : {} ;
 | 
| 
 | 
   188     $src ||= {};
 | 
| 
 | 
   189     
 | 
| 
 | 
   190     my %result;
 | 
| 
 | 
   191     
 | 
| 
 | 
   192     foreach my $key ( keys %$src ) {
 | 
| 
 | 
   193         if (exists $dst->{$key}) {
 | 
| 
 | 
   194             $result{"+$key"} = $dst->{$key} unless equals_s($dst->{$key}, $src->{$key});
 | 
| 
 | 
   195             delete $dst->{$key};
 | 
| 
 | 
   196         } else {
 | 
| 
 | 
   197             $result{"-$key"} = 1;
 | 
| 
 | 
   198         }
 | 
| 
 | 
   199     }
 | 
| 
 | 
   200     
 | 
| 
 | 
   201     $result{"+$_"} = $dst->{$_} foreach keys %$dst;
 | 
| 
 | 
   202     
 | 
| 
 | 
   203     return \%result;
 | 
| 
 | 
   204 }
 | 
| 
 | 
   205 
 | 
| 
 | 
   206 sub hashMerge {
 | 
| 
 | 
   207     return hashApply( { %{$_[0] || {}} }, $_[1] );
 | 
| 
 | 
   208 }
 | 
| 
 | 
   209 
 | 
| 
 | 
   210 sub hashApply {
 | 
| 
 | 
   211     my ($target,$diff) = @_;
 | 
| 
 | 
   212     
 | 
| 
 | 
   213     return $target unless ref $diff eq 'HASH';
 | 
| 
 | 
   214     
 | 
| 
 | 
   215     while ( my ($key,$value) = each %$diff) {
 | 
| 
 | 
   216         $key =~ /^(\+|-)?(.*)$/;
 | 
| 
 | 
   217         my $op = $1 || '+';
 | 
| 
 | 
   218         $key = $2;
 | 
| 
 | 
   219         
 | 
| 
 | 
   220         if ($op eq '-') {
 | 
| 
 | 
   221             delete $target->{$key};
 | 
| 
 | 
   222         } else {
 | 
| 
 | 
   223             $target->{$key} = $value;
 | 
| 
 | 
   224         }
 | 
| 
 | 
   225     }
 | 
| 
 | 
   226     
 | 
| 
 | 
   227     return $target;
 | 
| 
 | 
   228 }
 | 
| 
 | 
   229 
 | 
| 
 | 
   230 sub hashCompare {
 | 
| 
 | 
   231     my ($l,$r,$cmp) = @_;
 | 
| 
 | 
   232     
 | 
| 
 | 
   233     $cmp ||= \&equals_s;
 | 
| 
 | 
   234     
 | 
| 
 | 
   235     return 0 unless scalar keys %$l == scalar keys %$r;
 | 
| 
 | 
   236     &$cmp($l->{$_},$r->{$_}) || return 0 foreach keys %$l;
 | 
| 
 | 
   237     
 | 
| 
 | 
   238     return 1;
 | 
| 
 | 
   239 }
 | 
| 
 | 
   240 
 | 
| 
 | 
   241 sub hashParse {
 | 
| 
 | 
   242     my ($s,$p,$d) = @_;
 | 
| 
 | 
   243     
 | 
| 
 | 
   244     $p = $p ? qr/$p/ : qr/\n+/;
 | 
| 
 | 
   245     $d = $d ? qr/$d/ : qr/\s*=\s*/;
 | 
| 
 | 
   246     
 | 
| 
 | 
   247     return {
 | 
| 
 | 
   248         map split($d,$_,2), split($p,$s)
 | 
| 
 | 
   249     };
 | 
| 
 | 
   250 }
 | 
| 
 | 
   251 
 | 
| 
 | 
   252 sub hashSave {
 | 
| 
 | 
   253     my ($hash,$p,$d) = @_;
 | 
| 
 | 
   254     
 | 
| 
 | 
   255     return "" unless ref $hash eq 'HASH';
 | 
| 
 | 
   256     
 | 
| 
 | 
   257     $p ||= "\n";
 | 
| 
 | 
   258     $d ||= " = ";
 | 
| 
 | 
   259     
 | 
| 
 | 
   260     return
 | 
| 
 | 
   261         join(
 | 
| 
 | 
   262             $p,
 | 
| 
 | 
   263             map(
 | 
| 
 | 
   264                 join(
 | 
| 
 | 
   265                     $d,
 | 
| 
 | 
   266                     $_,
 | 
| 
 | 
   267                     $hash->{$_}
 | 
| 
 | 
   268                 ),
 | 
| 
 | 
   269                 keys %$hash
 | 
| 
 | 
   270             )
 | 
| 
 | 
   271         );
 | 
| 
 | 
   272 }
 | 
| 
 | 
   273 
 | 
| 
 | 
   274 1;
 |