Skip to main content

Fleak Eval Expression Language (FEEL) Reference

Introduction

Fleak Eval Expression Language is a flexible expression language designed for data transformation and evaluation. It provides various operations for manipulating dictionaries, arrays, strings, and timestamps, with a focus on data processing efficiency.

Basic Syntax and Types

Data Types

Numbers

  • Integers: 0 or any sequence of digits not starting with 0 (e.g., 42, 123)
  • Floating-point numbers: Numbers with decimal points (e.g., -1.5, .5, 10.0)
  • Negative numbers are supported for both integer and floating-point values

Strings

  • Can be enclosed in either single quotes ('hello') or double quotes ("world")
  • Support escape sequences:
    • \' - Single quote in single-quoted strings
    • \" - Double quote in double-quoted strings
    • \\ - Backslash
  • Examples:
    "This is a 'string'"
    'This is a "string"'
    "String with \"quotes\""
    'String with \'quotes\''
    "Path with \\backslashes\\"

Booleans

  • Two possible values: true or false
  • Used in conditional expressions and comparisons

Null

  • Represented by the keyword: null
  • Used to represent missing or undefined values

Path Selection

The language provides flexible path selection syntax for accessing nested data structures:

  • $: References the root event/document
  • .fieldName: Accesses a field in a dictionary using dot notation (field name must be a valid identifier)
  • [expression]: Accesses an array element by index, where the expression evaluates to an integer (zero-based)
  • ["fieldName"]: Alternative syntax for accessing dictionary fields, required when:
    • Field names contain special characters or spaces
    • Field names are reserved words
    • Field names need to be computed dynamically

If using dot notation ($.user.name), a field name must be a valid identifier. Valid identifiers:

  • Start with letter or underscore
  • Contain only letters, numbers, and underscores
  • Case-sensitive

Examples:

// Basic field access
$.user.name // Access 'name' field in user object
$["user"]["name"] // Same access using bracket notation

// Static array access
$.items[0] // Access first element of items array
$.users[1].address // Combine array and field access

// Dynamic array access
$.items[idx] // Access element at variable index
$.data[i + 1] // Access element using arithmetic
$.array[parse_int($.index)] // Use function result as index
$.matrix[row][col] // Multi-dimensional array access with variables

// Special character handling
$["special.field"] // Access field containing a dot
$["user name"] // Access field containing a space

// Nested combinations
$.users[0]["address.line1"] // Mix of array access and special fields
$.data[$.currentIndex].value // Use field value as array index

Operators and Precedence

Operators

Listed in order of precedence (highest to lowest):

  1. Unary Operations (highest)

    • Unary minus (-)
    • Logical NOT (not)
  2. Multiplicative Operations

    • Multiplication (*)
    • Division (/)
    • Modulo (%)
  3. Additive Operations

    • Addition (+)
    • Subtraction (-)
  4. Relational Operations

    • Less than (<)
    • Greater than (>)
    • Less than or equal (<=)
    • Greater than or equal (>=)
  5. Equality Operations

    • Equal (==)
    • Not equal (!=)
  6. Logical AND (and)

  7. Logical OR (or) (lowest)

Examples:

// Unary has highest precedence
-3 * 4 // Evaluates as (-3) * 4 = -12
not true and false // Evaluates as (not true) and false = false

// Multiplication before addition
2 + 3 * 4 // Evaluates as 2 + (3 * 4) = 14

// Comparison before logical operations
x > 5 and y < 10 // Evaluates as (x > 5) and (y < 10)

// AND before OR
a or b and c // Evaluates as a or (b and c)

Use parentheses to override default precedence:

(2 + 3) * 4  // Evaluates as 5 * 4 = 20
a and (b or c) // Evaluates AND after OR

Control Flow

Case Expressions

Provides conditional logic with pattern matching syntax. The first matching condition's expression is evaluated and returned.

case(
condition_1 => expression_1,
condition_2 => expression_2,
...
_ => default_expression
)

Examples:

// Simple status mapping
case(
$.status == "active" => "Current",
$.status == "pending" => "In Progress",
_ => "Unknown"
)

// Data transformation
case(
size_of($.items) == 0 => dict(status="empty", count=0),
size_of($.items) > 10 => dict(
status="full",
count=size_of($.items),
firstItem=$.items[0]
),
_ => dict(status="partial", items=$.items)
)

// Type-based handling
case(
$.value == null => "missing",
str_contains(to_str($.value), "error") => "error",
$.value > 100 => "high",
_ => "normal"
)

Built-in Functions

Utility Functions

size_of()

Returns the size of the argument based on its type.

Syntax: size_of(expression)

Input Types:

  • Array: Returns number of elements
  • Dictionary/Object: Returns number of key-value pairs
  • String: Returns string length

Returns: Integer

Examples:

// Array size
size_of($.items) // Returns number of elements
size_of(array(1, 2, 3)) // Returns 3

// Dictionary size
size_of(dict(a=1, b=2)) // Returns 2
size_of($.user.properties) // Returns number of properties

// String size
size_of("hello") // Returns 5
size_of($.message) // Returns string length

Dictionary Operations

dict()

Creates a dictionary with specified key-value pairs.

Syntax: dict(key1=expression1, key2=expression2, ...)

Keys must be valid identifiers:

  • Start with letter or underscore
  • Contain only letters, numbers, and underscores
  • Case-sensitive

Returns: Dictionary

Examples:

dict(
city=$.address.city,
state=$.address.state,
details=dict(
zip=$.address.zip,
country=$.address.country
)
)

dict_merge()

Merges multiple dictionaries into one. Later dictionaries override earlier ones for duplicate keys.

Syntax: dict_merge(dict1, dict2, ...)

Input: Two or more dictionaries Returns: Dictionary

Example:

dict_merge(
$,
dict(newField="value"),
grok($.__raw__, "%{PATTERN}")
)

Array Operations

array()

Creates an array from the provided arguments. Arguments can be of any type and can be mixed.

Syntax: array([expression1, expression2, ...])

Returns: Array

Example:

array($.field1, $.field2, "constant", 42)

range()

Generates an array of integer numbers based on the provided arguments. The 'end' parameter is exclusive (the range goes up to, but does not include, 'end').

Syntax variations:

  1. range(count) - Generates integers from 0 up to (but not including) 'count'
  2. range(start, end) - Generates integers from 'start' up to (but not including) 'end'
  3. range(start, end, step) - Generates integers from 'start' up to (but not including) 'end', incrementing by 'step'

Input:

  • count: Integer (for single-argument form)
  • start, end: Integers
  • step: Integer (can be positive or negative, but not zero)

Returns: Array of integers

Examples:

// Single argument
range(5) // Returns [0, 1, 2, 3, 4]
range(0) // Returns []
range(-2) // Returns []

// Two arguments
range(2, 5) // Returns [2, 3, 4]
range(5, 2) // Returns [] (start >= end)

// Three arguments
range(0, 10, 2) // Returns [0, 2, 4, 6, 8]
range(10, 0, -2) // Returns [10, 8, 6, 4, 2]
range(0, 5, -1) // Returns [] (wrong step direction)

// Practical use with dynamic array access
arr_foreach(
range(size_of($.k1)), // Generate indices [0, 1, 2, ...]
idx,
dict(
k1=$.k1[idx], // Access arrays using the generated index
k2=$.k2[idx]
)
)

arr_foreach()

Applies an expression to each element of an array.

Syntax: arr_foreach(arrayExpression, elementVariable, transformExpression)

Parameters:

  • arrayExpression: Expression that evaluates to an array. If it points to an object, treat that object as a single element array.
  • elementVariable: Variable name to represent each array element
  • transformExpression: Expression to apply to each element

Returns: Array of transformed elements

Example:

arr_foreach(
$.users,
user,
dict(
name=user.name,
age=user.age,
type=$.userType
)
)

With dynamic array access, you can now use arr_foreach for indexed operations:

// Zip two arrays together using range and dynamic indexing
arr_foreach(
range(size_of($.arr1)),
idx,
dict(
k1=$.arr1[idx],
k2=$.arr2[idx],
position=idx
)
)

arr_flatten()

Flattens an array of arrays by one level.

Syntax: arr_flatten(arrayOfArrays)

Input: An array containing other arrays Returns: A flattened array with one level of nesting removed

Example:

// Given:
// $.nested = [[1, 2], [3, 4], [5, [6, 7]]]

arr_flatten($.nested)
// Results: [1, 2, 3, 4, 5, [6, 7]]

Note: This performs a shallow flatten operation, not a deep recursive flatten. Deeper nested arrays remain intact.

String Operations

str_split()

Splits a string into an array of substrings based on a delimiter.

Syntax: str_split(string, delimiter)

Input:

  • String to split
  • Delimiter string

Returns: Array of strings

Example:

str_split("a,b,c", ",")  // Returns ["a", "b", "c"]

to_str()

Converts a value to string.

Syntax: to_str(expression)

Input: Any value Returns: String, or null if input is null

Example:

to_str($.numericField)  // Converts number to string
to_str(null) // Returns null

upper()

Converts a string to uppercase.

Syntax: upper(expression)

Input: String Returns: String Throws: Error if input is not a string

Example:

upper($.name)  // "john" -> "JOHN"

lower()

Converts a string to lowercase.

Syntax: lower(expression)

Input: String Returns: String Throws: Error if input is not a string

Example:

lower($.name)  // "JOHN" -> "john"

str_contains()

Tests if a string contains a substring.

Syntax: str_contains(string, substring)

Input: Two strings (string to search, substring to find) Returns: Boolean Throws: Error if either input is not a string

Examples:

// Basic usage
str_contains($.message, "error") // Returns true if found

// In case expressions
case(
str_contains($.level, "ERROR") => "critical",
str_contains($.level, "WARN") => "warning",
_ => "info"
)

// With other functions
str_contains(lower($.text), $.searchTerm)

grok()

Applies a Grok pattern to extract structured data from a string.

Syntax: grok(string, pattern)

Input:

  • String to parse
  • Grok pattern string

Returns: Dictionary of extracted fields Throws: Error if pattern is invalid

Example:

grok($.rawLog, "%{TIMESTAMP:timestamp} %{WORD:level}: %{GREEDYDATA:message}")

Time and Date Operations

ts_str_to_epoch()

Converts a timestamp string to epoch milliseconds.

Syntax: ts_str_to_epoch(timestampString, formatPattern)

Input:

Returns: Integer (epoch milliseconds) Throws: Error if timestamp doesn't match pattern

Example:

ts_str_to_epoch($.timestamp, "yyyy-MM-dd HH:mm:ss")

epoch_to_ts_str()

Converts epoch milliseconds to formatted timestamp string.

Syntax: epoch_to_ts_str(epochMillis, formatPattern)

Input:

Returns: String Throws: Error if timestamp is invalid

Example:

epoch_to_ts_str($.epochTime, "yyyy-MM-dd HH:mm:ss")

duration_str_to_mills()

Converts a duration string to milliseconds.

Syntax: duration_str_to_mills(durationString)

Input: Duration string in "HH:mm:ss" format Returns: Integer (milliseconds) Throws: Error if duration format is invalid

Example:

duration_str_to_mills("01:30:00")  // Returns 5400000 (1.5 hours in milliseconds)

Number Operations

parse_int()

Parses a string into a long integer (equivalent to Java's Long.parseLong()).

Syntax: parse_int(string)

Input: String containing an integer Returns: Long integer Throws: Error if string is not a valid integer

Example:

parse_int("42")  // Returns 42

parse_float()

Parses a string into a floating-point number (equivalent to Java's Double.parseDouble()).

Syntax: parse_float(string)

Input: String that represents a number Returns: Double Throws: Error if string is not a valid number

Example:

parse_float("3.14")  // Returns 3.14
parse_float("-1.5") // Returns -1.5
parse_float("42") // Returns 42.0

Advanced Patterns with Dynamic Array Access

The addition of dynamic array access enables powerful new patterns:

Parallel Array Processing

Process multiple arrays in parallel by iterating over indices:

// Transform parallel arrays into structured data
dict(
results=arr_foreach(
range(size_of($.names)),
i,
dict(
name=$.names[i],
score=$.scores[i],
grade=case(
$.scores[i] >= 90 => "A",
$.scores[i] >= 80 => "B",
$.scores[i] >= 70 => "C",
_ => "F"
)
)
)
)

Matrix Operations

Access multi-dimensional arrays:

// Process a 2D matrix
arr_foreach(
range(size_of($.matrix)),
row,
arr_foreach(
range(size_of($.matrix[row])),
col,
$.matrix[row][col] * 2
)
)

Conditional Array Access

Use computed indices based on conditions:

// Access array element based on status
dict(
selectedItem=$.items[
case(
$.priority == "high" => 0,
$.priority == "medium" => 1,
_ => 2
)
]
)

Sliding Windows

Process arrays with sliding windows:

// Create pairs of consecutive elements
arr_foreach(
range(size_of($.data) - 1),
i,
dict(
current=$.data[i],
next=$.data[i + 1],
diff=$.data[i + 1] - $.data[i]
)
)

Error Handling

Null Safety

  • Operations on null values generally propagate null
  • Function-specific null handling is documented in each function
  • Use case expressions for explicit null handling:
    case(
    $.field == null => "missing",
    _ => to_str($.field)
    )

Type Safety

  • Type mismatches in operations throw errors
  • Explicit type conversion functions available:
    • to_str() for string conversion
    • parse_int() for integer conversion
    • parse_float() for floating-point conversion

Common Error Conditions

  1. Type Mismatches

    // These will throw errors:
    "string" * 5 // Can't multiply string
    upper(42) // upper() requires string
    size_of(true) // size_of() not valid for boolean
  2. Field Access

    // These return null:
    $.nonexistent.field // Missing field
    $.array[999] // Index out of bounds

    // These throw errors:
    $.string[0] // Array access on non-array
    $[true] // Invalid field access
  3. Function Arguments

    // These throw errors:
    size_of() // Missing argument
    str_contains("a") // Missing second argument
    upper(42) // Wrong argument type

Best Practices

  1. Check for null values:

    case(
    $.amount != null => $.amount * 100,
    _ => 0
    )
  2. Provide default values:

    dict(
    name=case($.name != null => $.name, _ => "unknown"),
    value=case($.value != null => $.value, _ => 0)
    )
  3. Validate data structure:

    case(
    not array($.data) => dict(error="data must be array"),
    _ => arr_foreach($.data, item, process(item))
    )
  4. Bounds checking for dynamic array access:

    case(
    idx >= 0 and idx < size_of($.array) => $.array[idx],
    _ => null
    )