Data-Annotation

 view release on metacpan or  search on metacpan

lib/Data/Annotation/Expression.pm  view on Meta::CPAN


   my %copy = $definition->%*;

   # if the definition contains "type" then it's expected to *almost* in
   # normalized form. We might admit that args were not provided in
   # subs and set them to an empty array reference in this case.
   if (exists($copy{type})) { # should be mostly fine
      my $type = $copy{type};
      if (! defined($type)) { die "undefined type in definition\n" }
      elsif ($type eq 'data') {
         die "missing value for data request in definition\n"
            unless exists($copy{value});
         return \%copy;
      }
      elsif ($type eq 'sub') {
         die "missing locator of required sub in definition\n"
            unless exists($copy{name});
         $copy{args} //= [];
      }
      else { } # nothing to check, normalization complete
      return \%copy;
   }

   if (scalar(keys(%copy)) == 1) { # we're in DWIM land here
      my ($key, $value) = %copy;
      return { type  => 'data', value => $value }
         if $key eq 'data';
      return { type => 'sub', name => $value, args => [] }
         if $key eq 'sub';
      if ($key eq 'context') {
         $value = 'run' . $value
            if length($value // '') && substr($value, 0, 1) eq '.';
         return { type => 'context', path => $value };
      }
      return { type => 'sub', package => $1, name => $2, args => $value }
         if $key =~ m{\A (.+) (?: :: | /) (.+)}mxs; 
      return { type => 'sub', name => $key, args => $value };
   }
   else {
      die "cannot normalize definition\n";
   }

   # should never be reached
   ...
}

sub generate_function ($parse_ctx, $definition) {
   my $normalizer = exists($parse_ctx->{'definition-normalizer'})
      ? $parse_ctx->{'definition-normalizer'}
      : __PACKAGE__->can('default_definition_normalizer');
   $definition = $normalizer->($parse_ctx, $definition)
      if defined($normalizer);

   my $type = $definition->{type};
   my $parser = __PACKAGE__->can("generate_function_$type")
      or die "no parser for function type '$type'\n";
   return $parser->($parse_ctx, $definition);
}

sub generate_function_data ($parse_ctx, $definition) {
   return sub ($overlay) { return $definition->{value} };
}

sub generate_function_context ($parse_ctx, $definition) {
   my $path = $definition->{path} // '';
   my ($entry, @crumbs) = crumble($path)->@*;

   # the runtime context is a Data::Annotation::Overlay instance. For
   # 'run' we plug directly into it, otherwise we use its access options
   # for other data, but without the overlay/caching
   return sub ($overlay) { $overlay->get(\@crumbs) } if $entry eq 'run';
   my $other = { definition => $definition, parse => $parse_ctx };
   return sub ($overlay) {
      return $overlay->get_external([$entry, @crumbs], $other);
   };
}

sub generate_function_sub ($parse_ctx, $definition) {
   my ($name, $package) = $definition->@{qw< name package >};
   my $function = resolve_function($parse_ctx, $name, $package);
   my @args = map { generate_function($parse_ctx, $_) }
      ($definition->{args} // [])->@*;
   return sub ($overlay) { $function->(map { $_->($overlay) } @args) };
}

sub resolve_function ($parse_ctx, $name, $package) {
   die "undefined sub name\n" unless defined($name);
   die "empty sub name\n"     unless length($name);

   my $suffix = $package //= '';
   my $is_absolute = $suffix =~ s{\A /}{}mxs;
   my $relative_prefixes = $parse_ctx->{'locator-relative-prefixes'};
   my @prefixes = $is_absolute ? ('') : (($relative_prefixes // [])->@*);

   my $function;
   PREFIX:
   for my $prefix (@prefixes) {
      my $module = join('::', grep { length } ($prefix, $suffix));
      #warn "module<$module> name<$name>";

      for (1 .. 2) { # first try directly, then require $module
         if (my $factory = $module->can('factory')) {
            $function = $factory->($parse_ctx, $name);
            return $function if defined($function);
            next PREFIX; # if a factory exists, no more attempts anyway
         }
         elsif ($function = $module->can($name)) {
            return $function;
         }
         else { # prepare for next attempt, if we still have one
            #warn "Loading module <$module>";
            eval { require_module($module) } or do {
               #warn "error: $@";
               next PREFIX;
            };
            #warn "Loaded module <$module>";
         }
      }
   }

   die "cannot find sub for '$name'\n";
}

sub require_module ($module) {
   my $path = "$module.pm" =~ s{::}{/}rgmxs;
   require $path;
}

1;



( run in 4.124 seconds using v1.01-cache-2.11-cpan-524268b4103 )