10000 GitHub - Alessandro-Salerno/Forflex: Flexible expression evaluation library
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Alessandro-Salerno/Forflex

Forflex

Simple, flexible, and extensible expression parseing and evaluation library for Java

Contributors Forks Stargazers Issues MIT License

Features

  • Java function invocation inside expressions
  • Expression parameters
  • Real and string algebras included
  • Custom algebric structures other than real numbers
  • Parse tree caching
  • Error handling
  • Can be used in both FOSS and proprietary software

Syntax and grammar

The grammar is defined in pseudo-BNF as follows:

Grammar:
    <symbol>     := anything in UTF-8
    <digit>      := 0|1|2|3|4|5|6|7|8|9
    <integer>    := <digit>{<digit>}*
    <num>        := <integer>[.<integer>]
    <lowercase>  := a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z
    <uppercase>  := A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z
    <letter>     := <lowercase>|<uppercase>
    <string>     := "{<symbol>}*"
    <identifier> := <letter>|_{<letter>|<digit>|_}*
    <expr>       := <term>[{+|-}<term>]
    <params>     := [<expr>{, <expr>}*]
    <call>       := <identifier>(<params>)
    <term>       := <factor>[{*|/}<factor>]
    <factor>     := -<factor>|(<expr>)|<number>|<identifier>|<call>|<string>

For example, this is a valid statement:

round((a + b + c - d) / (5.47 * sin(1)) + priceof("AAPL", "2025-02-26"))

However, the round functions and the other symbols must be defied by the Java program, otherwise an exception is thrown:

1:1 ERROR: Undeclared identifier "round"
 1 | round((a + b + c - d) / (5.47 * sin(1)) + priceof("AAPL", "2025-02-26"))
   | ~~~~~

Structure

Forflex has six main concepts:

  • Algebric structures
  • Evaluables
  • Expressions
  • Functions
  • Parameters
  • Parser

Algebric structures

Algebric structures in Forflex define what operations can be applied to a number and how these operations behave. Algebric structures are classes that implement ForflexAlgebra and define methods for addition, subtraction, multiplication, and division. If a given algebric structure does not support one or more of these operations, it can throw a ForflexUnsupportedOperationError. The default algebric structure is ForflexRealNumber.

Evaluables

Evaluables in Forflex are essentially nodes in the parse tree. Evaluables implement the ForflexEvaluable interface and its method evaluate which returns an instance of a ForflexAlgebra when called. Forflex ships with four main evaluables:

  • Identity (ForflexIdentityNode - returns the same instance of ForflexAlgebra that was passed)
  • Binary (ForflexBinaryNode - holds two other evaluables on the left and right in order to make the binary tree)
  • Function call (ForflexFunctionCallNode - used to represent a function call in the tree)
  • Parameter (ForflexParameterNode - used to store references to parameters)

Expressions

Expressions in Forflex are a special type of evaluable which stands outside the tree and is used to cahce the tree itself in order to reuse it. This removes the need to reparse the expression every time it has to be evaluated with a given set of parameters.

Functions

Functions in Forflex are implementations of the ForflexFunction interface which, once added to a Parser, can be invoked from within an expression.

Parameters

Parameters in Forflex are named values that reside in the host Java program but can be access in read-only mode within expressions.

Parser

The Parser (ForflexParser) is the component responsible for constructing the tree. It first tokenizes the expression using the Lexer and then recursively scrolls the token list to build the tree.

Example

import alessandrosalerno.forflex.*;
import alessandrosalerno.forflex.errors.preprocessor.ForflexPreprocessorError;
import alessandrosalerno.forflex.algebra.ForflexAlgebra;
import alessandrosalerno.forflex.algebra.ForflexRealNumber;
import alessandrosalerno.forflex.errors.runtime.ForflexArgumentCountError;
import alessandrosalerno.forflex.errors.runtime.ForflexArgumentTypeError;
import alessandrosalerno.forflex.errors.runtime.ForflexUnassignedParametersError;

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        try {
            String formula = "round((a + b + c - d) / (5.47 * sin(1)) + priceof(\"AAPL\", \"2025-02-26\"))";
            ForflexParameterSpec parameterSpec = new ForflexParameterSpec()
                                                        .addParam("a")
                                                        .addParam("b")
                                                        .addParam("c")
                                                        .addParam("d");

            ForflexParser parser = new ForflexParser()
                                            .addFunctions(ForflexUtils.DEFAULT_FUNCTIONS)
                                            .addFunction("priceof", new ForflexFunction<ForflexRealNumber>() {
                @Override
                public ForflexRealNumber run(ForflexAlgebra<?>[] args) {
                    String symbol = ForflexUtils.requireArgumentPrimitiveType(args, 0, String.class);
                    String date = ForflexUtils.requireArgumentPrimitiveType(args, 1, String.class);
                    // Do some magic stock market stuff
                    return new ForflexRealNumber(200.5);
                }
            });

            ForflexExpression expr = parser.parse(formula, parameterSpec);
            ForflexParameterAssignment parameterAssignment = new ForflexParameterAssignment(parameterSpec)
                                                                    .assign("a", new ForflexRealNumber(1))
                                                                    .assign("b", new ForflexRealNumber(2))
                                                                    .assign("c", new ForflexRealNumber(3))
                                                                    .assign("d", new ForflexRealNumber(4));
            ForflexRealNumber result = (ForflexRealNumber) expr.evaluate(parameterAssignment);
            System.out.println(result.getPrimitive());
        } catch (ForflexPreprocessorError e) {
            e.printErrorMessage();
        } catch (ForflexArgumentCountError
                 | ForflexArgumentTypeError
                 | ForflexUnassignedParametersError e) {
            e.printStackTrace();
        }
    }
}

Installing Forflex with Maven

You can install Forflex using the official GitHub Package.

Alternatively, you can download and install it manually. After downloading the JAR and placing it in some project directory (e.g., resources), use the following dependency structure in your pom.xml file:

<dependency>
    <groupId>alessandrosalerno.forflex</groupId>
    <artifactId>forflex</artifactId>
    <version>1.0.2</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/src/main/resources/forflex-1.0.2.jar</systemPath>
</dependency>

License

Forflex is distributed under the Apache License 2.0.

Credits

0