NAME
Math::Formula - expressions on steroids
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');
# To build connectors to objects in your program, interfaces.
# See Math::Formula::Context.
my $formula = Math::Formula->new(size => \&own_sub, %options);
DESCRIPTION
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 Math::Formula::Context and Math::Formula::Config.
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.
Why do I need it? 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.
Interested? Read more in the "DETAILS" section below.
METHODS
Constructors
- Math::Formula->new($name, $expression, %options)
-
The expression needs a $name. Expressions can refer to each other via this name.
The $expression is usually a (utf8) string, which will get parsed and evaluated on demand. The $expression may also be a prepared node (any <Math::Formula::Type> object).
As special hook, you may also provide a CODE as $expression. This will be called as
$expression->($context, $this_formula, %options);
Optimally, the expression returns any Math::Formula::Type object. Otherwise, auto-detection of the computed result kicks in. The %options are passed to evaluate() More details below in "CODE as expression" in Math::Formula::Context.
-Option --Default returns undef
Accessors
- $obj->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.
- $obj->name()
-
Returns the name of this expression.
- $obj->returns()
-
Set when the expression promises to produce a certain type.
- $obj->tree($expression)
-
Returns the Abstract Syntax Tree of the $expression. Some of the types are only determined at the first run, for optimal laziness. Used for debugging purposes only.
Running
- $obj->evaluate( [ $context, %options ] )
-
Calculate the value for this expression given the $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>
- expect => $type
-
When specified, the result will be of the expected $type or
undef
. This overrules new(returns). Without either, the result type depends on the evaluation of the expression.
- $obj->toType($data)
-
Convert internal Perl data into a 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
'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 "CODE as expression" in Math::Formula::Context for details.
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.
Data-types
Examples for all data-types which 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)
Warning: in your code, all these above are place between quotes. This makes it inconvenient to 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 Math::Formula::Context (preferred), you can select your own solution via Math::Formula::Context::new(lead_expressions). It is possible to configure that all strings get the normal quotes, and expressions start with =
(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.
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.
Operators
As prefix operator, you can use not
, -
, +
, and exists
on applicable data types. The #
(fragment) and .
(attributes) prefixes are weird cases: see 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.
Prefix operators always have the highest priority, and work right to left (RTL) The infix and 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).
Comparison operators
Some data types support numeric comparison (implement <=>
, the spaceship operator), other support textual comparison (implement cmp
), where also some types have no intrinsic order.
The <=>
and 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 Unicode::Collate, which might be a bit expensive, but at least a better attempt to order UTF-8 correctly.
SEE ALSO
This module is part of Math-Formula distribution version 0.16, built on March 14, 2023. Website: http://perl.overmeer.net/CPAN/
LICENSE
Copyrights 2023 by [Mark Overmeer <markov@cpan.org>]. For other contributors see ChangeLog.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See http://dev.perl.org/licenses/