View Issue Details

IDProjectCategoryView StatusLast Update
0004152NoesisGUIC++ SDKpublic2026-04-16 13:21
ReporterMarco Pisanu Assigned Tojsantos  
PrioritynormalSeverityminor 
Status resolvedResolutionfixed 
Product Version3.2.7 
Target Version3.2.13Fixed in Version4.0 
Summary0004152: MathConverter not existing
Description

The MathConverter, used by Noesis Studio, does not seem to exist in the C++ SDK, causing UI elements created by Noesis Studio using the MathConverter to not function.

PlatformAny

Activities

jsantos

jsantos

2025-05-13 11:41

manager   ~0010635

We are working on this. As a workaround, I am attaching the implementation of MathConverter here.

ArithmeticParser.cpp (12,578 bytes)   
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////


#include "ArithmeticParser.h"

#include <NsCore/StringUtils.h>


using namespace Noesis;


namespace
{

struct Operator
{
    enum class Associativity
    {
        Right, Left, None
    };

    char op;
    uint32_t precedence;
    Associativity associativity;
    float (*func)(BaseVector<float>& stack);
};

}

////////////////////////////////////////////////////////////////////////////////////////////////////
static float Pop(BaseVector<float>& stack)
{
    if (!stack.Empty())
    {
        float v = stack.Back();
        stack.PopBack();
        return v;
    }

    return 0.0f;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
static Operator ParseFunc(const char* expr, uint32_t& parsed)
{
    if (StrStartsWith(expr, "min"))
    {
        parsed = (uint32_t)strlen("min");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float b = Pop(stack);
            float a = Pop(stack);
            return Min(a, b);
        }};
    }
    else if (StrStartsWith(expr, "max"))
    {
        parsed = (uint32_t)strlen("max");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float b = Pop(stack);
            float a = Pop(stack);
            return Max(a, b);
        }};
    }
    else if (StrStartsWith(expr, "floor"))
    {
        parsed = (uint32_t)strlen("floor");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float a = Pop(stack);
            return Floor(a);
        }};
    }
    else if (StrStartsWith(expr, "round"))
    {
        parsed = (uint32_t)strlen("round");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float a = Pop(stack);
            return (float)Round(a);
        }};
    }
    else if (StrStartsWith(expr, "ceil"))
    {
        parsed = (uint32_t)strlen("ceil");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float a = Pop(stack);
            return Ceil(a);
        } };
    }
    else if (StrStartsWith(expr, "sin"))
    {
        parsed = (uint32_t)strlen("sin");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float a = Pop(stack);
            return sinf(a);
        }};
    }
    else if (StrStartsWith(expr, "cos"))
    {
        parsed = (uint32_t)strlen("cos");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float a = Pop(stack);
            return cosf(a);
        }};
    }
    else if (StrStartsWith(expr, "pow"))
    {
        parsed = (uint32_t)strlen("pow");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float b = Pop(stack);
            float a = Pop(stack);
            return powf(a, b);
        }};
    }
    else if (StrStartsWith(expr, "abs"))
    {
        parsed = (uint32_t)strlen("abs");

        return Operator{ 'F', 10, Operator::Associativity::None, [](BaseVector<float>& stack)
        {
            float a = Pop(stack);
            return fabsf(a);
        } };
    }

    parsed = 0;
    return Operator{};
}

////////////////////////////////////////////////////////////////////////////////////////////////////
static bool ParseOperator(const char* expr, Operator& op, bool binaryAllowed)
{
    if (binaryAllowed)
    {
        switch (*expr)
        {
            case '*':
            {
                op = { '*', 8, Operator::Associativity::Left, [](BaseVector<float>& stack)
                {
                    float b = Pop(stack);
                    float a = Pop(stack);
                    return a * b;
                }};

                return true;
            }
            case '/':
            {
                op = { '/', 8, Operator::Associativity::Left, [](BaseVector<float>& stack)
                {
                    float b = Pop(stack);
                    float a = Pop(stack);
                    return a / b;
                }};

                return true;
            }
            case '%':
            {
                op = { '%', 8, Operator::Associativity::Left, [](BaseVector<float>& stack)
                {
                    float b = Pop(stack);
                    float a = Pop(stack);
                    return fmodf(a, b);
                }};

                return true;
            }
            case '+':
            {
                op = { '+', 6, Operator::Associativity::Left, [](BaseVector<float>& stack)
                {
                    float b = Pop(stack);
                    float a = Pop(stack);
                    return a + b;
                }};

                return true;
            }
            case '-':
            {
                op = { '-', 6, Operator::Associativity::Left, [](BaseVector<float>& stack)
                {
                    float b = Pop(stack);
                    float a = Pop(stack);
                    return a - b;
                }};

                return true;
            }
        }
    }

    switch (*expr)
    {
        case '+':
        {
            // Unary plus
            op = { '+', 9, Operator::Associativity::Right, [](BaseVector<float>& stack)
            {
                float a = Pop(stack);
                return a;
            }};

            return true;
        }
        case '-':
        {
            // Unary minus 
            op = { '-', 9, Operator::Associativity::Right, [](BaseVector<float>& stack)
            {
                float a = Pop(stack);
                return -a;
            }};

            return true;
        }
        case '(':
        {
            op= { '(', 0, Operator::Associativity::None, nullptr};
            return true;
        }
        case ')':
        {
            op= { ')', 0, Operator::Associativity::None, nullptr};
            return true;
        }
    }

    return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
static float ParseConstant(const char* expr, ArrayRef<float> inputs, float param, uint32_t& parsed)
{
    if (expr[0] == '{')
    {
        uint32_t charParsed;
        uint32_t N = StrToUInt32(expr + 1, &charParsed);

        if (N < inputs.Size() && charParsed > 0 && expr[charParsed + 1] == '}')
        {
            parsed = charParsed + 2;
            return inputs[N];
        }
    }

    if (StrStartsWith(expr, "{_}"))
    {
        parsed = 3;
        return param;
    }

    parsed = 0;
    return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
bool Parse(const char* expr, ArrayRef<float> inputs, float param, float& value)
{
    uint32_t numChars = (uint32_t)strlen(expr);
    uint32_t pos = 0;

    struct Out
    {
        enum Type
        {
            Value, Op
        };

        Type type;

        union
        {
            float v;
            Operator op;
        };
    };

    Vector<Out, 256> queue;
    Vector<Operator, 128> stack;

    bool binaryOpAllowed = false;

    while (pos < numChars)
    {
        // Skip spaces
        while (expr[pos] == ' ') pos++;
        if (pos == numChars) break;

        uint32_t parsed;

        // Function
        Operator func = ParseFunc(expr + pos, parsed);
        if (parsed > 0)
        {
            stack.PushBack(func);
            pos += parsed;
            binaryOpAllowed = false;
            continue;
        }

        Operator op;
        if (ParseOperator(expr + pos, op, binaryOpAllowed))
        {
            // Operator
            if (op.op != '(' && op.op != ')')
            {
                while (!stack.Empty() && stack.Back().op != '(')
                {
                    Operator op2 = stack.Back();

                    if (op2.precedence > op.precedence || (op2.precedence == op.precedence &&
                        op.associativity == Operator::Associativity::Left))
                    {
                        stack.PopBack();

                        Out out;
                        out.type = Out::Type::Op;
                        out.op = op2;

                        queue.PushBack(out);
                    }
                    else break;
                }

                stack.PushBack(op);
                pos++;
                binaryOpAllowed = false;
                continue;
            }

            // Left parenthesis
            if (op.op == '(')
            {
                stack.PushBack(op);
                pos++;
                binaryOpAllowed = false;
                continue;
            }

            // Right parenthesis
            if (op.op == ')')
            {
                while (!stack.Empty() && stack.Back().op != '(')
                {
                    Out out;
                    out.type = Out::Type::Op;
                    out.op = stack.Back();

                    stack.PopBack();
                    queue.PushBack(out);
                }

                if (stack.Empty() || stack.Back().op != '(')
                {
                    // Mismatched parentheses
                    return false;
                }

                NS_ASSERT(!stack.Empty() && stack.Back().op == '(');
                stack.PopBack();

                // If there is a function in the stack, move it to the queue
                if (!stack.Empty() && stack.Back().op == 'F')
                {
                    Out out;
                    out.type = Out::Type::Op;
                    out.op = stack.Back();

                    stack.PopBack();
                    queue.PushBack(out);
                }

                pos++;
                binaryOpAllowed = true;
                continue;
            }
        }

        // Comma
        if (expr[pos] == ',')
        {
            while (!stack.Empty() && stack.Back().op != '(')
            {
                Out out;
                out.type = Out::Type::Op;
                out.op = stack.Back();

                stack.PopBack();
                queue.PushBack(out);
            }

            pos++;
            binaryOpAllowed = false;
            continue;
        }

        // Number
        float v = StrToFloat(expr + pos, &parsed);
        if (parsed > 0)
        {
            queue.PushBack(Out{ Out::Type::Value, v });
            pos += parsed;
            binaryOpAllowed = true;
            continue;
        }

        // Constant
        v = ParseConstant(expr + pos, inputs, param, parsed);
        if (parsed > 0)
        {
            queue.PushBack(Out{ Out::Type::Value, v });
            pos += parsed;
            binaryOpAllowed = true;
            continue;
        }

        // Invalid expression
        return false;
    }

    // Pop the remaining items from the operator stack into the output queue
    while (!stack.Empty())
    {
        if (stack.Back().op == '(')
        {
            // Mismatched parentheses
            return false;
        }

        Out out;
        out.type = Out::Type::Op;
        out.op = stack.Back();

        stack.PopBack();
        queue.PushBack(out);
    }

    // RPN evaluation
    Vector<float, 128> values;
    for (const Out& o : queue)
    {
        if (o.type == Out::Type::Value)
        {
            values.PushBack(o.v);
        }
        else
        {
            values.PushBack(o.op.func(values));
        }
    }

    if (values.Size() != 1)
    {
        return false;
    }

    NS_ASSERT(values.Size() == 1);
    value = values.Back();
    return true;
}
ArithmeticParser.cpp (12,578 bytes)   
MathConverter.h (1,754 bytes)   
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////


#ifndef __APP_MATHCONVERTER_H__
#define __APP_MATHCONVERTER_H__


#include <NsCore/Noesis.h>
#include <NsCore/String.h>
#include <NsGui/BaseValueConverter.h>
#include <NsGui/IMultiValueConverter.h>


namespace NoesisApp
{

////////////////////////////////////////////////////////////////////////////////////////////////////
class MathConverter final : public Noesis::BaseValueConverter, public Noesis::IMultiValueConverter
{
public:
    MathConverter();

    const char* GetExpression() const;
    void SetExpression(const char* expr);

    /// From IValueConverter
    //@{
    bool TryConvert(Noesis::BaseComponent* value, const Noesis::Type* targetType,
        Noesis::BaseComponent* parameter, Noesis::Ptr<Noesis::BaseComponent>& result) final;
    //@}

    /// From IMultiValueConverter
    //@{
    bool TryConvert(Noesis::ArrayRef<Noesis::BaseComponent*> values, const Noesis::Type* targetType,
        Noesis::BaseComponent* parameter, Noesis::Ptr<Noesis::BaseComponent>& result) override;
    bool TryConvertBack(Noesis::BaseComponent* value,
        Noesis::ArrayRef<const Noesis::Type*> targetTypes, Noesis::BaseComponent* parameter,
        Noesis::BaseVector<Noesis::Ptr<Noesis::BaseComponent>>& results) override;
    //@}

    NS_IMPLEMENT_INTERFACE_FIXUP

private:
    Noesis::String mExpr;

    NS_DECLARE_REFLECTION(MathConverter, BaseValueConverter)
};

}

#endif
MathConverter.h (1,754 bytes)   
MathConverter.cpp (3,138 bytes)   
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////


#include "MathConverter.h"
#include "ArithmeticParser.h"

#include <NsCore/ReflectionImplement.h>
#ifdef NS_HAVE_STUDIO
#include <NsCore/Category.h>
#endif

#include <math.h>


using namespace Noesis;
using namespace NoesisApp;


////////////////////////////////////////////////////////////////////////////////////////////////////
MathConverter::MathConverter(): mExpr("{0}")
{
}

////////////////////////////////////////////////////////////////////////////////////////////////////
const char* MathConverter::GetExpression() const
{
    return mExpr.Str();
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void MathConverter::SetExpression(const char* expr)
{
    mExpr = expr;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
bool MathConverter::TryConvert(BaseComponent* value, const Type*, BaseComponent* parameter,
    Ptr<BaseComponent>& result)
{
    if (!mExpr.Empty())
    {
        float input = value ? (float)atof(value->ToString().Str()) : 0.0f;
        float param = parameter ? (float)atof(parameter->ToString().Str()) : 0.0f;
        float output;

        if (Parse(mExpr.Str(), input, param, output))
        {
            result.Reset(Boxing::Box(output));
            return true;
        }
    }

    return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
bool MathConverter::TryConvert(ArrayRef<BaseComponent*> values, const Type*,
    BaseComponent* parameter, Ptr<BaseComponent>& result)
{
    if (!mExpr.Empty())
    {
        Vector<float, 8> inputs;
        for (uint32_t i = 0; i < values.Size(); ++i)
        {
            inputs.PushBack(values[i] ? (float)atof(values[i]->ToString().Str()) : 0.0f);
        }
        float param = parameter ? (float)atof(parameter->ToString().Str()) : 0.0f;
        float output;

        if (Parse(mExpr.Str(), inputs, param, output))
        {
            result.Reset(Boxing::Box(output));
            return true;
        }
    }

    return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
bool MathConverter::TryConvertBack(BaseComponent*, ArrayRef<const Type*>, BaseComponent*,
    BaseVector<Ptr<BaseComponent>>&)
{
    return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
NS_BEGIN_COLD_REGION

NS_IMPLEMENT_REFLECTION(MathConverter, "NoesisGUIExtensions.MathConverter")
{
#ifdef NS_HAVE_STUDIO
    NsMeta<Category>("Converters");
#endif

    NsImpl<IMultiValueConverter>();

    NsProp("Expression", &MathConverter::mExpr);
}

NS_END_COLD_REGION
MathConverter.cpp (3,138 bytes)   
ArithmeticParser.h (829 bytes)   
////////////////////////////////////////////////////////////////////////////////////////////////////
// NoesisGUI - http://www.noesisengine.com
// Copyright (c) 2013 Noesis Technologies S.L. All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////


#ifndef __APP_ARITHMETICPARSER_H__
#define __APP_ARITHMETICPARSER_H__


#include <NsCore/ArrayRef.h>


// Evaluates the mathematical expression, substituting the provided input values for any variables
// in the expression. Variables in the expression are expected to be in the form {N}, where N is
// the index of the corresponding input value in the inputs array. {_} is used for the float param
bool Parse(const char* expr, Noesis::ArrayRef<float> inputs, float param, float& value);

#endif
ArithmeticParser.h (829 bytes)   
Marco Pisanu

Marco Pisanu

2025-05-13 23:49

reporter   ~0010644

Great to hear, and thanks for providing a workaround!

jsantos

jsantos

2026-04-16 13:21

manager   ~0012218

This is now available in the beta of 4.0

Issue History

Date Modified Username Field Change
2025-05-12 16:47 Marco Pisanu New Issue
2025-05-13 11:38 jsantos Assigned To => jsantos
2025-05-13 11:38 jsantos Status new => assigned
2025-05-13 11:38 jsantos Target Version => Studio_Beta
2025-05-13 11:39 jsantos Target Version Studio_Beta => 3.2.8
2025-05-13 11:41 jsantos Note Added: 0010635
2025-05-13 11:41 jsantos File Added: ArithmeticParser.cpp
2025-05-13 11:41 jsantos File Added: MathConverter.h
2025-05-13 11:41 jsantos File Added: MathConverter.cpp
2025-05-13 11:41 jsantos File Added: ArithmeticParser.h
2025-05-13 23:49 Marco Pisanu Note Added: 0010644
2025-05-28 10:37 jsantos Target Version 3.2.8 => 3.2.9
2025-10-02 00:49 jsantos Target Version 3.2.9 => 3.2.10
2025-10-20 18:25 jsantos Target Version 3.2.10 => 3.2.11
2026-01-20 19:32 jsantos Target Version 3.2.11 => 3.2.12
2026-03-04 00:39 jsantos Target Version 3.2.12 => 3.2.13
2026-04-16 13:20 jsantos Fixed in Version => 4.0
2026-04-16 13:21 jsantos Status assigned => resolved
2026-04-16 13:21 jsantos Resolution open => fixed
2026-04-16 13:21 jsantos Note Added: 0012218