|
| 1 | +// SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | + |
| 3 | +#include "../libnetdata.h" |
| 4 | +#include "eval-internal.h" |
| 5 | + |
| 6 | +// Global registry for dynamic functions |
| 7 | +EVAL_DYNAMIC_FUNCTION *eval_function_registry = NULL; |
| 8 | + |
| 9 | +// Next available operator ID for dynamic functions (starting after hardcoded operators) |
| 10 | +EVAL_OPERATOR next_function_op = EVAL_OPERATOR_CUSTOM_FUNCTION_START; // Start from a safe value |
| 11 | + |
| 12 | +void str2lower(char *dst, const char *src) { |
| 13 | + while (*src) { |
| 14 | + *dst = (char)tolower((uint8_t)*src); |
| 15 | + dst++; |
| 16 | + src++; |
| 17 | + } |
| 18 | + *dst = '\0'; |
| 19 | +} |
| 20 | + |
| 21 | +// Register a new function in the registry |
| 22 | +bool eval_register_function(const char *name, eval_function_cb callback, int min_params, int max_params) { |
| 23 | + if (!name || !callback || min_params < 0) |
| 24 | + return 0; |
| 25 | + |
| 26 | + // make it lowercase |
| 27 | + char n[strlen(name) + 1]; |
| 28 | + str2lower(n, name); |
| 29 | + name = n; |
| 30 | + |
| 31 | + // Check if a function with this name already exists |
| 32 | + EVAL_DYNAMIC_FUNCTION *func = eval_function_lookup(name); |
| 33 | + if (!func) { |
| 34 | + func = callocz(1, sizeof(EVAL_DYNAMIC_FUNCTION)); |
| 35 | + func->name = string_strdupz(name); |
| 36 | + |
| 37 | + // Assign a unique operator ID |
| 38 | + func->operator = __atomic_fetch_add(&next_function_op, 1, __ATOMIC_RELAXED); |
| 39 | + |
| 40 | + // Add to registry |
| 41 | + func->next = eval_function_registry; |
| 42 | + eval_function_registry = func; |
| 43 | + } |
| 44 | + |
| 45 | + // Create new function registry entry |
| 46 | + func->callback = callback; |
| 47 | + func->min_params = min_params; |
| 48 | + func->max_params = max_params; |
| 49 | + |
| 50 | + // Register the function operator in the operators' table |
| 51 | + operators[func->operator].print_as = string2str(func->name); |
| 52 | + operators[func->operator].precedence = eval_precedence(EVAL_OPERATOR_FUNCTION); |
| 53 | + operators[func->operator].parameters = 0; // Will be set dynamically when the function is called |
| 54 | + operators[func->operator].isfunction = 1; |
| 55 | + operators[func->operator].eval = eval_execute_function; |
| 56 | + |
| 57 | + return 1; |
| 58 | +} |
| 59 | + |
| 60 | +// Lookup a function by name (case-insensitive) |
| 61 | +EVAL_DYNAMIC_FUNCTION *eval_function_lookup(const char *name) { |
| 62 | + if (!name) |
| 63 | + return NULL; |
| 64 | + |
| 65 | + // make it lowercase |
| 66 | + char n[strlen(name) + 1]; |
| 67 | + str2lower(n, name); |
| 68 | + name = n; |
| 69 | + |
| 70 | + // Create a temporary STRING for case-insensitive comparison |
| 71 | + STRING *lookup_name = string_strdupz(name); |
| 72 | + |
| 73 | + EVAL_DYNAMIC_FUNCTION *func = eval_function_registry; |
| 74 | + while (func) { |
| 75 | + // Compare names case-insensitively |
| 76 | + if (func->name == lookup_name) { |
| 77 | + string_freez(lookup_name); |
| 78 | + return func; |
| 79 | + } |
| 80 | + func = func->next; |
| 81 | + } |
| 82 | + |
| 83 | + string_freez(lookup_name); |
| 84 | + return NULL; |
| 85 | +} |
| 86 | + |
| 87 | +// Function to evaluate a dynamic function |
| 88 | +NETDATA_DOUBLE eval_execute_function(struct eval_expression *exp, EVAL_NODE *op, int *error) { |
| 89 | + if (!op) { |
| 90 | + *error = EVAL_ERROR_INVALID_NUMBER_OF_OPERANDS; |
| 91 | + return NAN; |
| 92 | + } |
| 93 | + |
| 94 | + // Find the function in the registry |
| 95 | + EVAL_DYNAMIC_FUNCTION *func = NULL; |
| 96 | + for (func = eval_function_registry; func; func = func->next) { |
| 97 | + if (func->operator == op->operator) |
| 98 | + break; |
| 99 | + } |
| 100 | + |
| 101 | + if (!func) { |
| 102 | + buffer_sprintf(exp->error_msg, "unknown function with operator %d", (int)op->operator); |
| 103 | + *error = EVAL_ERROR_UNKNOWN_OPERAND; |
| 104 | + return NAN; |
| 105 | + } |
| 106 | + |
| 107 | + // Check parameter count |
| 108 | + if (op->count < func->min_params) { |
| 109 | + buffer_sprintf(exp->error_msg, "function %s requires at least %d parameters, but %d provided", |
| 110 | + string2str(func->name), func->min_params, op->count); |
| 111 | + *error = EVAL_ERROR_INVALID_NUMBER_OF_OPERANDS; |
| 112 | + return NAN; |
| 113 | + } |
| 114 | + |
| 115 | + if (func->max_params >= 0 && op->count > func->max_params) { |
| 116 | + buffer_sprintf(exp->error_msg, "function %s accepts at most %d parameters, but %d provided", |
| 117 | + string2str(func->name), func->max_params, op->count); |
| 118 | + *error = EVAL_ERROR_INVALID_NUMBER_OF_OPERANDS; |
| 119 | + return NAN; |
| 120 | + } |
| 121 | + |
| 122 | + // Call the function |
| 123 | + return func->callback(exp, op->count, op->ops, error); |
| 124 | +} |
| 125 | + |
| 126 | +// Define abs function using the dynamic system |
| 127 | +static NETDATA_DOUBLE abs_function(struct eval_expression *exp, int param_count, EVAL_VALUE *params, int *error) { |
| 128 | + if (param_count != 1) { |
| 129 | + *error = EVAL_ERROR_INVALID_NUMBER_OF_OPERANDS; |
| 130 | + return NAN; |
| 131 | + } |
| 132 | + |
| 133 | + // Evaluate the parameter |
| 134 | + NETDATA_DOUBLE n = eval_value(exp, ¶ms[0], error); |
| 135 | + if (*error != EVAL_ERROR_OK) |
| 136 | + return NAN; |
| 137 | + |
| 138 | + return ABS(n); |
| 139 | +} |
| 140 | + |
| 141 | +// Function to initialize the eval subsystem |
| 142 | +// This is defined as a constructor so it runs automatically when the library is loaded |
| 143 | +__attribute__((constructor)) |
| 144 | +static void eval_functions_constructor(void) { |
| 145 | + // Initialize the function registry |
| 146 | + eval_register_function("abs", abs_function, 1, 1); |
| 147 | +} |
0 commit comments