NAME

Data::Annotation::Expression

SYNOPSIS

use Data::Annotation::Expression qw< evaluator_factory >;

my $evaluator = evaluator_factory(\%definition, \%parse_context);
my $retval = $evaluator->($data);

DESCRIPTION

This module exports "evaluator_factory", a factory function that produces sub references based on an expression definition and additional context. These sub references can later be used to evaluate the expression based on additional data that is provided as input.

Example

To make an example, suppose that we have the following target function (from a behavioural point of view, at least):

my $expression = sub ($data) {
   return ($data->{foo} eq 'bar')
      && ($data->{baz}  ne $data->{galook});
}

It's a boolean expression that makes use of some parts taken from $data (keys foo, baz, and baz) as well as a constant (string bar), also leveraging boolean operators like && and comparison operators like eq and ne.

The first step is to represent the expression as data, e.g. in YAML it would be:

and:
   - eq: [ '.foo', '=bar' ]
   - ne: [ '.baz', '.galook' ]

In this module's conventions, strings starting with a dot represent access to the input data (like .foo), while strings starting with an equal sign represent verbatim data (like =baz). When we have this expression parsed as a Perl data structure, we can generate our equivalent function:

my $equivalent_expression = evaluator_factory($definition);

Definition format(s)

This section will use YAML format to represent data structure, although this module does only accept Perl data structures (i.e. parsing of YAML or any other representation format is supposed to be performed elsewhere).

A definition is a hierarchical data structure composed of nodes, that are hash references, with some specific fields that indicate what the node does. Some of these nodes represent functions/operators that take parameters, in the form of array references.

In its most verbose form, the representation is very explicit, like this that represents $input->{foo} eq 'bar':

type: sub
name: eq
args:
   - type: context
     path: run.foo
   - type: data
     value: bar

There are three node types:

  • sub: indicate a sub or an operator. This will be looked around based on its name and invoked with the provided args, after they have been evaluated. It is also possible to set a package where the name function will be searched, otherwise the "Parse context" will be used.

  • data: indicate verbatim data, provided as value. This can itself be some complicated data structure, but it will be used as-is.

  • context: indicate data that has to be taken from the available context, using a path that is evaluated using Data::Annotation::Traverse functions. The top-most key in the path can be run, meaning whatever is passed to the evaluation function at runtime, as well as parse, meaning the "Parse context", as well as definition, which means the expression definition itself. Most of the times you will probably want to use run, possibly parse (to keep some less-dynamic data like configurations), almost never definition (it's there mostly for debugging reasons).

The fully explicit form above is the normal form. As it is quite verbose, it's possible to use shortcuts as we already saw in the example. First of all, plain strings can serve as expressions, like this:

&sub_name     -->    { type: sub, name: sub_name, args: [] }
=foo_bar      -->    { type: data, value: foo_bar }
run.place     -->    { type: context, path: run.place }
.place        -->    { type: context, path: run.place }

Then, we can have hash-references that contain a single key/value pair (like in the first examples). Keys data, sub, and context do the right thing much like the string versions above; other keys always lead to sub nodes, where it's possible to also specify the package by setting the value to a fully qualified Perl function (i.e. including the package with the usual :: notation).

Parse context

When calling "evalutor_function" it's possible to pass an optional second parameter with a hash reference of options. These are used to set the behaviour of parsing, as well as some runtime behaviour (e.g. where to find functions or retrieving values from the "context").

The normalization process described in the previous section can be completely overridden by passing option definition-normalizer, which is a sub reference with the following signature:

sub normalizer ($parse_ctx, $definition)  --> $normalized_definition

You can find the default one in the Data::Annotation::Expression code (function default_definition_normalizer), which does what explained before.

The parse context can be used at runtime to retrieve values using a context node type. In this case, the path must start with string parse.

The parse context is also used in sub node types. In particular, key locator-relative-prefixes is (if present) an array reference holding a list of package prefixes that will be tried to find each function. As an example, suppose that it is set like this:

locator-relative-prefixes:
   - Foo::Bar
   - Baz::Galook

The following applies:

{ type: sub, name: blorg }
   candidates: package Foo::Bar           function blorg
               package Baz::Galook        function blorg

{ type: sub, name: blorg, package: Barf }
   candidates: package Foo::Bar::Barf     function blorg
               package Baz::Galook::Barf  function blorg

{ type: sub, name: blorg, package: '/Barf' }
   candidates: package Barf               function blorg

Builtin functions

This module comes with a set of wrappers around Perl most common operators which are used as default built-ins available in expressions. These are contained in Data::Annotation::Expression::Builtin and this module is injected by default inside "Parse context" option locator-relative-prefixes.

It is possible to avoid the injection of these built-ins by passing a true value to "Parse context" option no-builtin when calling the factory function.

INTERFACE

evaluator_factory

my $sub1 = evaluator_factory(\%definition); # OR
my $sub2 = evaluator_factory(\%definition, \%parse_context);

Generate an evaluator function based on a %definition and optional %parse_context. See "DESCRIPTION" for everything.

ANYTHING ELSE (INCLUDING AUTHOR, COPYRIGHT AND LICENSE)

See documentation for Data::Annotation.