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
orfalse
- 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
Step Operations: Path selection steps can be applied to any expression result, not just the root $
. This means you
can chain operations on function results, variables, or any expression that returns an array or object.
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
// Step operations on expressions
(array($.a, $.b, $.c))[0] // Array access on function result
(dict(x=$.value))[\"x\"] // Field access on dict expression
(case($.type == \"A\" => $.dataA, _ => $.dataB)).field // Field access on case result
// Nested combinations
$.users[0]["address.line1"] // Mix of array access and special fields
$.data[$.currentIndex].value // Use field value as array index
(grok($.raw, \"%{PATTERN}\")).extractedField // Field access on grok result
Operators and Precedence
Operators
Listed in order of precedence (highest to lowest):
- Unary Operations (highest)
- Unary minus (
-
) - Logical NOT (
not
)
- Multiplicative Operations
- Multiplication (
*
) - Division (
/
) - Modulo (
%
)
- Additive Operations
- Addition (
+
) - Subtraction (
-
)
- Relational Operations
- Less than (
<
) - Greater than (
>
) - Less than or equal (
<=
) - Greater than or equal (
>=
)
- Equality Operations
- Equal (
==
) - Not equal (
!=
)
-
Logical AND (
and
) -
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, ...)
Key formats supported:
- Simple identifiers: Start with letter or underscore, contain only letters, numbers, and underscores
- Namespaced identifiers: Use
::
separator for namespace qualification (e.g.,namespace::field
) - Quoted identifiers: Use single or double quotes for keys with special characters
Examples:
// Simple keys
dict(
city=$.address.city,
state=$.address.state
)
// Namespaced keys
dict(
user::name=$.user.name,
user::id=$.user.id,
system::timestamp=$.timestamp
)
// Quoted keys for special characters
dict(
"field.with.dots"=$.special_field,
'field with spaces'=$.another_field,
regular_field=$.normal_field
)
// Mixed key types
dict(
namespace::field=$.data,
"special.key"=$.value,
normalKey=$.other
)
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}")
)
dict_remove()
Removes specified keys from a dictionary and returns a new dictionary without those keys.
Syntax: dict_remove(dictionary, key1, key2, ...)
Input:
- Dictionary to remove keys from
- One or more string keys to remove
Returns: New dictionary with specified keys removed Throws: Error if first argument is not a dictionary or if any key argument is not a string
Examples:
// Remove single key
dict_remove($.user, "password")
// Returns user object without password field
// Remove multiple keys
dict_remove($.data, "temp", "cache", "debug")
// Returns data object without temp, cache, and debug fields
// Chain with other operations
dict_merge(
dict_remove($, "internal", "private"),
dict(processed=true, timestamp=now())
)
// Remove internal fields and add new ones
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:
range(count)
- Generates integers from 0 up to (but not including) 'count'range(start, end)
- Generates integers from 'start' up to (but not including) 'end'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, extracting all elements from nested arrays.
Syntax: arr_flatten(arrayOfArrays)
Input: An array where every element must be an array Returns: A flattened array with one level of nesting removed Throws: Error if any element is not an array
Behavior:
- Takes each nested array and extracts all its elements
- Combines all extracted elements into a single flat array
- Only performs shallow flattening (one level deep)
- Preserves original order of elements
Examples:
// Array of arrays with simple elements
// $.nested = [[1, 2], [3, 4], [5, 6]]
arr_flatten($.nested)
// Results: [1, 2, 3, 4, 5, 6]
// Array of arrays with mixed element types
// $.data = [["a", "b"], [1, 2], [true, false]]
arr_flatten($.data)
// Results: ["a", "b", 1, 2, true, false]
// Array of arrays with deeper nesting (nesting preserved)
// $.deep = [[1, [2, 3]], [4, 5], [[6, 7]]]
arr_flatten($.deep)
// 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. All top-level elements must be arrays or the function will throw an error.
String Operations
str_split()
Splits a string into an array of substrings based on a delimiter.
Syntax: str_split(string, delimiter)
Input:
- string: The string to split.
- delimiter: The literal string delimiter to split by. Special regex characters are automatically escaped and treated as literal characters.
Returns: An array of strings.
Examples:
str_split("a,b,c", ",") // Returns ["a", "b", "c"]
str_split("a+b+c", "+") // Returns ["a", "b", "c"] (+ treated as literal)
str_split("a.b.c", ".") // Returns ["a", "b", "c"] (. treated as literal)
str_split("a*b*c", "*") // Returns ["a", "b", "c"] (* treated as literal)
str_split("hello world", " ") // Returns ["hello", "world"]
Note: Unlike regex-based split functions, all characters in the delimiter are treated literally. You don't need to
escape special regex characters like +
, .
, *
, ?
, etc.
substr()
Extracts a substring from a string using SQL or Python style syntax.
Syntax:
substr(string, start)
- Extract from start position to end of stringsubstr(string, start, length)
- Extract specified number of characters
Parameters:
- string: The string to extract from
- start: Starting position (0-based index)
- Positive values: Count from beginning (0 is first character)
- Negative values: Count from end (-1 is last character)
- length (optional): Number of characters to extract
- If omitted, extracts to end of string
- Must be non-negative
Returns: String
Throws: Error if:
- Input
string
is null or not a string - Start or length are not numeric
- Length is negative
Edge cases:
- Start beyond string length: Returns empty string
""
- Start before beginning (e.g., -100 on 5-char string): Treated as 0
- Length exceeds remaining characters: Returns substring to end of string
Examples:
// Basic extraction
substr("hello world", 6) // Returns "world"
substr("hello world", 0, 5) // Returns "hello"
// Negative indices (Python-style)
substr("hello", -2) // Returns "lo" (last 2 chars)
substr("hello", -3, 2) // Returns "ll" (2 chars starting 3 from end)
// Edge cases
substr("hello", 10) // Returns "" (start beyond length)
substr("hello", -10) // Returns "hello" (clamped to start)
substr("hello", 2, 100) // Returns "llo" (length truncated)
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:
- Timestamp string
- Java SimpleDateFormat pattern
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:
- Epoch milliseconds (integer)
- Java SimpleDateFormat pattern
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)
now()
Returns the current time as epoch milliseconds.
Syntax: now()
Input: None Returns: Long integer (current epoch milliseconds)
Examples:
// Get current timestamp
now() // Returns current epoch milliseconds (e.g., 1699564800000)
// Use in data processing
dict(
processedAt=now(),
data=$.payload
)
// Calculate time difference
dict(
eventTime=$.timestamp,
currentTime=now(),
ageInMillis=now() - $.timestamp
)
// Use with epoch_to_ts_str for formatted output
epoch_to_ts_str(now(), "yyyy-MM-dd HH:mm:ss")
Type Conversion Functions
parse_int()
Parses a string into a long integer (equivalent to Java's Long.parseLong()).
Syntax:
parse_int(string)
- Parse with base 10parse_int(string, radix)
- Parse with specified base
Input:
- String containing an integer
- Optional radix (base) for number parsing (2-36)
Returns: Long integer Throws: Error if string is not a valid integer or radix is invalid
Examples:
parse_int("42") // Returns 42 (base 10)
parse_int("1010", 2) // Returns 10 (binary)
parse_int("FF", 16) // Returns 255 (hexadecimal)
parse_int("77", 8) // Returns 63 (octal)
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
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
Math Operations
floor()
Rounds down a floating-point number to the nearest integer.
Syntax: floor(number)
Input: Number (integer or floating-point) Returns: Long integer (rounded down) Throws: Error if input is not a number
Examples:
floor(123.45) // Returns 123
floor(-123.45) // Returns -124
floor(42) // Returns 42
floor(0.9) // Returns 0
ceil()
Rounds up a floating-point number to the nearest integer.
Syntax: ceil(number)
Input: Number (integer or floating-point) Returns: Long integer (rounded up) Throws: Error if input is not a number
Examples:
ceil(123.45) // Returns 124
ceil(-123.45) // Returns -123
ceil(42) // Returns 42
ceil(0.1) // Returns 1
Random Number Generation
random_long()
Generates a cryptographically secure random long integer.
Syntax: random_long()
Input: None Returns: Long integer (cryptographically secure random value)
Examples:
// Generate random ID
dict(
id=random_long(),
data=$.payload
)
// Create random seed
random_long() // Returns random long value (e.g., -3458764513820540928)
// Use for distributed tracing
dict(
traceId=random_long(),
spanId=random_long(),
timestamp=now()
)
// Generate multiple random values
array(random_long(), random_long(), random_long())
Note: This function uses Java's SecureRandom
class to generate cryptographically strong random numbers, suitable for security-sensitive applications.
Python Integration
python()
Executes a Python function defined within a script string. Requires GraalVM with Python language support.
Syntax: python(scriptString, arg1, arg2, ...)
Input:
- scriptString: String literal containing Python code with exactly one function definition
- arguments: Zero or more FEEL expressions to pass to the Python function
Returns: Value returned by the Python function, converted to FEEL data types Throws: Error if script contains zero or multiple functions, or if Python execution fails
Requirements:
- Script must define exactly one top-level function
- GraalVM with Python language support must be configured
- Python functions are pre-compiled for execution
Examples:
// Simple calculation
python("
def calculate(x, y):
return x * y + 10
", $.value1, $.value2)
// Data processing
python("
def process_items(items):
return [item.upper() for item in items if len(item) > 2]
", $.stringArray)
// Complex transformation
python("
def transform_data(data, multiplier):
result = {}
for key, value in data.items():
if isinstance(value, (int, float)):
result[key] = value * multiplier
else:
result[key] = str(value).upper()
return result
", $.inputData, 1.5)
Note: Python function execution is sandboxed and cached for performance. The same script string will reuse the compiled function.
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
-
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
floor("text") // floor() requires number
ceil(null) // ceil() doesn't accept null -
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 -
Function Arguments
// These throw errors:
size_of() // Missing argument
str_contains("a") // Missing second argument
upper(42) // Wrong argument type
parse_int("abc") // Invalid number format
parse_int("10", 1) // Invalid radix (must be 2-36)
arr_flatten([[1], 2]) // Mixed array/non-array elements
python("not a function") // Script must contain one function
Best Practices
-
Check for null values:
case(
$.amount != null => $.amount * 100,
_ => 0
) -
Provide default values:
dict(
name=case($.name != null => $.name, _ => "unknown"),
value=case($.value != null => $.value, _ => 0)
) -
Validate data structure:
case(
not array($.data) => dict(error="data must be array"),
_ => arr_foreach($.data, item, process(item))
) -
Bounds checking for dynamic array access:
case(
idx >= 0 and idx < size_of($.array) => $.array[idx],
_ => null
) -
Safe type conversion:
// With error handling
case(
str_contains($.input, "0x") => parse_int(substr($.input, 2), 16),
str_contains($.input, ".") => floor(parse_float($.input)),
_ => parse_int($.input)
)
// Safe string conversion
case(
$.value != null => to_str($.value),
_ => "N/A"
) -
Math operations with validation:
// Ensure positive result for floor/ceil
case(
$.value >= 0 => floor($.value),
_ => ceil($.value) // Different rounding for negative numbers
)