Evaluates the math expression Expr with u (block input), t (current time), dt (step) and the diagram variables. The same expression engine drives the simulation and the C/Rust code generation, so simulated and generated behaviors match.
Supported grammar: constants pi, e, inf; unary functions abs, ceil, floor, round, sign, sqrt, exp, log, log10, log2, acos, asin, atan, cos, cosh, sin, sinh, tan, tanh, sinc; binary functions pow, atan2, min, max; ternary clamp; operators + - * / ^. Non-math constructs are rejected at code generation.
For arbitrary Nelson code (any function, handles), use the nelsonFunction block instead (simulation only).
Parameters
| Parameter |
Default value |
Expr
|
u |
Block Characteristics
| Block type |
expression |
| Family |
User-Defined Function blocks |
| Phases |
INIT, ALGEBRAIC |
| Direct feedthrough |
yes |
| Signal data type |
double, scalar |
| Code generation |
yes (C and Rust) |
Code generation: supported for C and Rust.
Manifestmodules/nflow_blocks/libraries/userdefined/library.json
{
"id": "builtin.userdefined",
"title": "User-Defined Functions",
"version": "1.0.0",
"format": "nflow-2",
"metadata": {
"author": "Allan CORNET",
"created": "2026-08-02",
"tool": "Nelson nflow"
},
"builtin": true,
"comment": "User-defined function blocks",
"license": "LGPL-3.0",
"blocks": [
{
"type": "expression",
"label": "Expression",
"icon": "expression.svg",
"phases": [
"INIT",
"ALGEBRAIC"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"Expr": "u"
},
"render": {
"type": "math",
"formula": "{params.Expr}",
"mathGroupClass": "userfunc-math",
"textSize": "16px",
"width": 80,
"height": 80
}
},
{
"type": "nelsonFunction",
"label": "Nelson Function",
"icon": "nelsonFunction.svg",
"phases": [
"INIT",
"ALGEBRAIC"
],
"width": 96,
"height": 48,
"inputs": [
{
"x": 0,
"y": 24,
"side": "left"
}
],
"outputs": [
{
"x": 96,
"y": 24,
"side": "right"
}
],
"defaultParams": {
"Fcn": "sin",
"OutputDimensions": "-1",
"SampleTime": "-1"
},
"render": {
"type": "math",
"formula": "\\mathtt{{params.Fcn}}",
"textSize": 14
}
}
]
}
Runtimemodules/nflow_blocks/src/cpp/userdefined/expression.cpp
//=============================================================================
// Copyright (c) 2016-present Allan CORNET (Nelson)
//=============================================================================
// This file is part of Nelson.
//=============================================================================
// LICENCE_BLOCK_BEGIN
// SPDX-License-Identifier: LGPL-3.0-or-later
// LICENCE_BLOCK_END
//=============================================================================
#include "SimEngineTypes.hpp"
#include "BlockRegistry.hpp"
#include "FieldNames.hpp"
#include "NFlowBlockDescriptor.hpp"
#include "ExprEngine.hpp"
#include <stdexcept>
#include "userdefined_blocks.hpp"
//=============================================================================
// expression: evaluates a restricted math expression of u (plus t, dt and the
// diagram variables) at every step, through the module's own ExprEngine - no
// interpreter involved, so this block simulates everywhere AND generates
// C/Rust code (the generators reconstruct the expression from its AST).
// Scalar signals only, one input, one output, stateless.
//=============================================================================
bool
Nelson::NFlow::handleExpression(SimCtx& ctx, const Block& b, Phase phase)
{
if (phase != Phase::INIT && phase != Phase::ALGEBRAIC) {
return false;
}
nflow::BlockDescriptor bd(b, ctx.variables);
const std::string expr = bd.paramStr(nflow::kExpr, "u");
if (phase == Phase::INIT) {
// Validate once so a bad expression fails with a clear diagnostic
// instead of erroring at every step.
std::unordered_map<std::string, double> vars = ctx.variables;
vars["u"] = 0.0;
vars["t"] = 0.0;
vars["dt"] = ctx.dt;
try {
(void)evalExpressionValue(expr, vars);
} catch (const std::exception& e) {
if (ctx.diag) {
SimDiagnostic d;
d.code = "expression_invalid";
d.blockId = b.id;
d.message = "expression block: invalid expression '" + expr + "': " + e.what();
d.time = ctx.t;
ctx.diag->fail(d);
}
}
return false;
}
if (!hasInput(ctx, b.nid, 0)) {
setOutput(ctx, b.nid, ctx.blockState[b.nid].output);
return false;
}
std::unordered_map<std::string, double> vars = ctx.variables;
vars["u"] = getInput(ctx, b.nid, 0, 0.0);
vars["t"] = ctx.t;
vars["dt"] = ctx.dt;
double y = 0.0;
try {
y = evalExpressionValue(expr, vars);
} catch (const std::exception&) {
y = 0.0; // validated at INIT; runtime failures degrade to 0
}
return emitElementwise(ctx, b.nid, [&](int) { return y; });
}
//=============================================================================