=encoding utf8

=head1 NAME

Math::Formula - expressions on steroids

=head1 SYNOPSIS

  my $formula = Math::Formula->new('size', '42k + 324', %options);
  my $formula = Math::Formula->new(π => 3.14);
  my $size    = $formula->evaluate;

  # For a bit more complex formulas, you need a context object.
  my $context = Math::Formula::Context->new(name => 'example');
  $context->add( { size => '42k', header => '324', total => 'size + header' });
  my $total   = $context->value('total');

  my age = $context->value('(#system.now.date - 1966-04-05).years');

  # To build connectors to objects in your program, interfaces.
  # See Math::Formula::Context.
  my $formula = Math::Formula->new(size => \&own_sub, %options);

=head1 DESCRIPTION

B<WARNING:> This is not a programming language: it lacks control
structures, like loops and blocks.  This module can be used
to offer (very) flexible configuration (files) for users of your
application. See L<Math::Formula::Context|Math::Formula::Context> and L<Math::Formula::Config|Math::Formula::Config>.

B<What makes Math::Formula special?> Zillions of expression evaluators
have been written in the past.  The application where this module was
written for, has special needs which were not served by them.
This expression evaluator can do things which are usually hidden behind
library calls.

B<Why do I need it?> I<My> application has many kinds of configurable
rules, from different sources.  Those rules often use times and durations
in them, to organize processing activities.  Each line in my configuration
can now be a smart expression.  Declarative programming.

B<Interested?> Read more in the L</"DETAILS"> section below.

=head1 METHODS

=head2 Constructors

=over 4

=item $class-E<gt>B<new>($name, $expression, %options)

The expression needs a C<$name>.  Expressions can refer to each other via this name.

The C<$expression> is usually a (utf8) string, which will get parsed and
evaluated on demand.  The C<$expression> may also be a prepared node (any
<L<Math::Formula::Type|Math::Formula::Type>> object).

As special hook, you may also provide a CODE as C<$expression>.  This will
be called as

  $expression->($context, $this_formula, %options);

Optimally, the expression returns any L<Math::Formula::Type|Math::Formula::Type> object.  Otherwise,
auto-detection of the computed result kicks in.  The C<%options> are passed to
L<evaluate()|Math::Formula/"Running">  More details below in L<Math::Formula::Context/"CODE as expression">.

 -Option --Default
  returns  undef

=over 2

=item returns => $type

Enforce that the type produced by the calculation of this C<$type>.  Otherwise, it may
be different when other people are permitted to configure the formulas... people can
make mistakes.

=back

=back

=head2 Accessors

=over 4

=item $obj-E<gt>B<expression>()

Returns the expression, which was given at creation. Hence, it can be a string
to be evaluated, a type-object, or a CODE reference.

=item $obj-E<gt>B<name>()

Returns the name of this expression.

=item $obj-E<gt>B<returns>()

Set when the expression promises to produce a certain type.

=item $obj-E<gt>B<tree>($expression)

Returns the Abstract Syntax Tree of the C<$expression>. Some of the types
are only determined at the first run, for optimal laziness.  Used for
debugging purposes only.

=back

=head2 Running

=over 4

=item $obj-E<gt>B<evaluate>( [ $context, %options ] )

Calculate the value for this expression given the C<$context>.  The Context groups the expressions
together so they can refer to each other.  When the expression does not contain Names, than you
may go without context.

 -Option--Default
  expect  <any ::Type>

=over 2

=item expect => $type

When specified, the result will be of the expected C<$type> or C<undef>.  This overrules
L<new(returns)|Math::Formula/"Constructors">.  Without either, the result type depends on the evaluation of the
expression.

=back

=item $obj-E<gt>B<toType>($data)

Convert internal Perl data into a L<Math::Formula|Math::Formula> internal types.  For most
times, this guess cannot go wrong. In other cases a mistake is not problematic.

In a small number of cases, auto-detection may break: is C<'true'> a
boolean or a string?  Gladly, this types will be cast into a string when
used as a string; a wrong guess without consequences.  It is preferred
that your CODE expressions return explicit types: for optimal safety and
performance.

See L<Math::Formula::Context/"CODE as expression"> for details.

=back

=head1 DETAILS

This module handles formulas.  Someone (your application user) gets more power
in configuring its settings.  Very simple example:

  # In a back-up script, configured in JSON
  "daily_backups"  : "/var/tmp/backups/daily",
  "weekly_backups" : "/var/tmp/backups/weekly",

  # With Math::Formula::Config::JSON
  "backup_dir"     : "/var/tmp/backups/",
  "daily_backups"  : "= backup_dir ~ 'daily'",
  "weekly_backups" : "= backup_dir ~ 'weekly'",

The more configuration your application needs, the more useful this module
gets.  Especially when you need to work with timestamps.

=head2 Data-types

Examples for all data-types which C<Math::Formula> supports:

  true and false               # real booleans
  "abc"  'abc'                 # the usual strings, WARNING: read below
  7  89k  5Mibi                # integers with multiplier support
  =~ "c$"                      # regular expression matching
  like "*c"                    # pattern matching
  2023-02-18T01:28:12+0300     # date-times
  2023-02-18+0100              # dates
  01:18:12                     # times
  -0600                        # time-zone
  P2Y3DT2H                     # duration
  name                         # outcome of other expressions

And constructs

  (1 + 2) * -3                 # operations and parenthesis
  "abc".length                 # attributes
  #unit.owner                  # fragments (nested context, namespaces)

B<Warning:> in your code, all these above are place between quotes.
This makes it inconvenient to B<use strings>, which are also usually
between quotes.  So: strings should stand-out from expressions.
Use any of the following syntaxes:

  "\"string\""   '"string"'    "'$string'"   # $string with escaped quotes!
  \"string"       \'string'    \$string      # or, use a SCALAR reference

When you use a L<Math::Formula::Context|Math::Formula::Context> (preferred), you can select your
own solution via L<Math::Formula::Context::new(lead_expressions)|Math::Formula::Context/"Constructors">.  It is
possible to configure that all strings get the normal quotes, and expressions
start with C<=> (or any other leading string).

Your expressions can look like this:

  my_age   => '(#system.now.date - 1966-05-04).years',
  is_adult => 'my_age >= 18',

Expressions can refer to values computed by other expressions.  Also,
external objects can maintain libraries of formulas or produce compatible
data.

=head3 Sets of formulas

Let's start with a large group of related formulas, and the types they produce:

  birthday: 1966-04-05      # DATE
  os_lib: #system           # other context is a FRAGMENT
  now: os_lib.now           # DATETIME 'now' is an attribute of system
  today: now.date           # DATE 'today' is an attribute of DATETIME
  alive: today - birthday   # DURATION
  age: alive.years          # INTEGER 'years' is an attr of DURATION

  # this can also be written in one line:

  age: (#system.now.date - 1966-04-05).years

Or some backup configuration lines:

  backup_needed: #system.now.day_of_week <= 5    # Monday = 1
  backup_start: 23:00:00
  backup_max_duration: PT2H30M
  backup_dir: "/var/tmp/backups"
  backup_name: backup_dir ~ '/' ~ "backup-" ~ weekday ~ ".tgz"

The application which uses this configuration, will run the expressions with
the names as listed.  It may also provide some own formulas, fragments, and
helper methods as features.

=head2 Operators

As B<prefix> operator, you can use C<not>, C<->, C<+>, and C<exists>
on applicable data types.  The C<#> (fragment) and C<.> (attributes)
prefixes are weird cases: see L<Math::Formula::Context|Math::Formula::Context>.

Operators only work on specific data types, but some types will
automatically convert.  For instance, all types can be cast into
a string to support regular expression and pattern matching.

Of course, you can use parenthesis for grouping.

B<Prefix> operators always have the highest priority, and work right
to left (RTL) The B<infix> and B<ternary> operators have the following
priorities: (from low to higher, each like with equivalent priority)

  LTR       ?:                             # if ? then : else
  NOCHAIN   ->                             # if-then, substitute
  LTR       or   xor  //                   # (excl)or, defaults to
  LTR       and                            # and
  NOCHAIN   <    >    <=   ==   !=   <=>   # numeric comparison
  NOCHAIN   lt   gt   le   eq   ne   cmp   # string comparison
  LTR       +    -    ~                    # plus, minus, concat
  LTR       *    /    %                    # mul, div, modulo
  NOCHAIN   =~   !~   like  unlike         # regexps and patterns
  LTR       #    .                         # fragments and attributes

The first value is a constant representing associativity.  Either the constant
LTR (compute left to right), RTL (right to left), or NOCHAIN (non-stackable
operator).

=head3 Comparison operators

Some data types support numeric comparison (implement C<< <=> >>, the
spaceship operator), other support textual comparison (implement C< cmp >),
where also some types have no intrinsic order.

The C<< <=> >> and C< cmp > return an integer: -1, 0, or 1, representing
smaller, equal, larger.

  :num: :text:
    <     lt      less than/before
    <=    le      less-equal
    ==    eq      equal/the same
    !-    ne      unequal/different
    >=    ge      greater-equal
    >     gt      greater/larger

String comparison uses L<Unicode::Collate>, which might be a bit expensive,
but at least a better attempt to order UTF-8 correctly.

=head1 DIAGNOSTICS

=over 4

=item Error: expression '$name', expected ':' in '?:', but got '$token'

Z<>

=item Error: expression '$name', expected infix operator but found '$type'

Z<>

=item Error: expression '$name', failed at '$where'

Z<>

=item Error: expression '$name', infix operator '$op' requires right-hand argument

Z<>

=item Error: expression '$name', monadic '$op' not followed by anything useful

Z<>

=item Error: expression '$name', parenthesis do not match

Z<>

=item Error: not an expression (string needs \\ ) for '$data'

Z<>

=back

=head1 SEE ALSO

This module is part of Math-Formula version 0.17,
built on August 17, 2025. Website: F<http://perl.overmeer.net/CPAN/>

=head1 LICENSE

For contributors see file ChangeLog.

This software is copyright (c) 2023-2025 by Mark Overmeer.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

