gval

repository·master·Indexed 21 days ago

https://github.com/paesslerag/gval

A Go library for evaluating arbitrary, Go-like expressions. It supports arithmetic, logical, string, and float64 operations, as well as JSON structures and ternary operators. gval is fully customizable, allowing developers to define custom functions, constants, and infix operators. It provides features for optimizing performance through pre-parsing expressions and reusing language objects, and supports complex parameter access via dot selectors, bracket selectors, and a jsonpath extension.

Tokens
5.2K
Snippets
23
Records
39
Agent score
73%

What's inside gval

  1. Access parameters via selectors

    master

    Variables provided in the parameters can be accessed using several selector methods:

    • String Literals: Access variables directly by name (e.g., foo).
    • Bracket Selector []: Access elements in maps, arrays, or specific struct fields (e.g., foo[0] or foo["bar"]).
    • Dot Selector .: Access nested variables where the name contains only letters and underscores (e.g., foo.bar).
    • Fields and Methods: If a parameter is a struct, you can access its fields and methods directly (e.g., foo.Hello or foo.World()).

    Note on Performance: Using accessors (fields/methods) on structs is approximately four times slower than using direct parameters. For better performance, define logic as functions rather than relying on struct method access.

  2. Use JSON Path for complex parameter names

    master

    If your parameter names contain characters that interfere with standard operators (for example, response-time which would be interpreted as response minus time), you can use the jsonpath extension to access them using bracket notation.

    Example syntax: $["response-time"]

    $["response-time"]
  3. Evaluate expressions with Gval

    master

    Gval allows you to evaluate arbitrary Go-like expressions including arithmetic, logical, string, and float64 operations. You can pass parameters to these expressions to make them dynamic.

    To optimize performance, you should parse an expression once using gval.Parse (or similar parsing methods) and then reuse the resulting gval.Expression object multiple times with different parameters. Parsing is the most compute-intensive phase.

    // Example of the pattern: Parse once, evaluate many times
    expr, err := gval.Parse("foo > 0")
    if err != nil {
        // handle error
    }
    
    // Evaluate with different parameters
    result, err := expr.Evaluate(map[string]interface{}{"foo": 10})
  4. Optimize Gval performance

    master

    To ensure maximum performance when using Gval:

    1. Reuse Languages: Avoid calling gval.Evaluate("expression", ...) repeatedly with the same functions/constants, as this recreates the gval.Language every time. Instead, create the language once and reuse it.
    2. Pre-parse Expressions: Parse expressions into gval.Expression objects once and reuse them for multiple evaluations.
    3. Prefer Functions over Struct Accessors: Accessing struct fields and methods via reflection is significantly slower than using predefined functions or direct parameters.

    You can run the built-in benchmarks using go test -bench=. to measure performance on your specific hardware.

  5. Implement a custom selector

    master
    To provide custom logic for how paths are resolved, implement the SelectGVal(ctx context.Context, k string) (interface{}, error) method on your struct. The function receives the next part of the path (k) and should return a value that can be evaluated by standard Gval procedures.
  6. Customize the Gval language

    master

    Gval is fully customizable. You can define your own constants, functions, and operators, or reuse existing sub-languages. The default full language (gval.Full) includes:

    • Modifiers: +, -, /, *, &, |, ^, **, %, >>, <<
    • Comparators: >, >=, <, <=, ==, !=, =~, !~
    • Logical ops: ||, &&
    • Constants: Numeric (64-bit float), String (double quotes), Boolean (true, false)
    • Date function: Date(x) (supports RFC3339, ISO8601, ruby date, or unix date)
    • JSON structures: Arrays [1, 2] and Objects {"a":1}
    • Control flow: Parentheses (), Ternary ? :, Null coalescence ??
    • Prefixes: !, -, ~
  7. Define custom Prefix and Postfix Operators

    master

    Use these functions to extend the language with operators that appear before or after a single operand:

    • PrefixOperator(name string, e Evaluable): Takes an Evaluable and applies it to the next expression parsed.
    • PostfixOperator(name string, ext func(context.Context, *Parser, Evaluable) (Evaluable, error)): Extends the language with a postfix operation.
  8. PropositionalLogic language module

    master

    The PropositionalLogic() language provides boolean logic. Operators expect bool operands.

    Type Conversion Rules:

    • Numbers other than 0 and strings "TRUE" or "true" are interpreted as true.
    • 0 and strings "FALSE" or "false" are interpreted as false.

    Supported Operators:

    • ! (not)
    • && (and)
    • || (or)
    • ==, != (equality)
  9. Evaluate expressions with Evaluate()

    master

    Use Evaluate to run a string expression against a provided parameter. By default, it uses the full language (a union of arithmetic, bitmask, text, logic, ternary, and JSON).

    result, err := gval.Evaluate("a + 1", 10)
    // result is 11
  10. Parse an expression with ParseExpression

    master

    Use ParseExpression(c context.Context) to scan an expression into an Evaluable object. This is the primary method for converting a raw string expression into a structure that can be evaluated against a context.

    It handles operator precedence and complex expressions by scanning tokens and building an evaluation tree.

    eval, err := parser.ParseExpression(ctx)
  11. Access variables with Parser.Var

    master

    The Var(path ...Evaluable) method creates an Evaluable that retrieves a value from a data structure following the provided path.

    By default, the variable selector supports:

    • map[interface{}]interface{} and map[string]interface{} (via key lookup)
    • []interface{} (via integer index strings)
    • struct fields (via reflection)
    • struct methods (via reflection)
    • slices (via integer index strings)
    • Custom Selector implementations if configured on the Parser.

    Each element in the path slice represents one step in the traversal.

    // Example: accessing a nested field
    // path: user -> profile -> name
    path := []gval.Evaluable{
        p.Var("user"),
        p.Var("profile"),
        p.Var("name"),
    }
    userVar := p.Var(path...)
    name, err := userVar.EvalString(ctx, data)