#include "WorkflowNodes.hpp" #include "Utils/Macros.hpp" #include "Utils/RTTI.hpp" #include "Utils/Variant.hpp" #include #include #include WorkflowNode::Kind NumericOperationNode::OperationTypeToNodeKind(OperationType type) { switch (type) { case Addition: return KD_NumericAddition; case Subtraction: return KD_NumericSubtraction; case Multiplication: return KD_NumericMultiplication; case Division: return KD_NumericDivision; default: UNREACHABLE; } } NumericOperationNode::OperationType NumericOperationNode::NodeKindToOperationType(Kind kind) { switch (kind) { case KD_NumericAddition: return Addition; case KD_NumericSubtraction: return Subtraction; case KD_NumericMultiplication: return Multiplication; case KD_NumericDivision: return Division; default: UNREACHABLE; } } bool NumericOperationNode::IsInstance(const WorkflowNode* node) { return node->GetKind() >= KD_NumericAddition && node->GetKind() <= KD_NumericDivision; } NumericOperationNode::NumericOperationNode(OperationType type) : WorkflowNode(TransformType, OperationTypeToNodeKind(type)) , mType{ type } { mInputs.resize(2); mInputs[0].MatchingType = BaseValue::KD_Numeric; mInputs[1].MatchingType = BaseValue::KD_Numeric; mOutputs.resize(1); mOutputs[0].MatchingType = BaseValue::KD_Numeric; } void NumericOperationNode::Evaluate(WorkflowEvaluationContext& ctx) { auto lhsVal = dyn_cast(ctx.GetConnectionValue(mInputs[0])); if (!lhsVal) return; double lhs = lhsVal->GetValue(); auto rhsVal = dyn_cast(ctx.GetConnectionValue(mInputs[1])); if (!rhsVal) return; double rhs = rhsVal->GetValue(); double res; switch (mType) { case Addition: res = lhs + rhs; break; case Subtraction: res = lhs - rhs; break; case Multiplication: res = lhs * rhs; break; case Division: { if (rhs == 0.0) { // TODO localize ctx.ReportError("Error: division by 0", *this); return; } res = lhs / rhs; } break; default: return; } auto value = std::make_unique(); value->SetValue(res); ctx.SetConnectionValue(mOutputs[0], std::move(value)); } bool NumericExpressionNode::IsInstance(const WorkflowNode* node) { return node->GetKind() == KD_NumericExpression; } NumericExpressionNode::NumericExpressionNode() : WorkflowNode(TransformType, KD_NumericExpression) { } struct FormatTextNode::Argument { ArgumentType ArgumentType; int PinIdx; template static void ForArguments(std::vector::iterator begin, std::vector::iterator end, const TFunction& func) { for (auto it = begin; it != end; ++it) { auto& elm = *it; if (auto arg = std::get_if(&elm)) { func(*arg); } } } /// Find the pin index that the \c elmIdx -th element should have, based on the elements coming before it. static int FindPinForElement(const std::vector& vec, int elmIdx) { for (int i = elmIdx; i >= 0; --i) { auto& elm = vec[i]; if (auto arg = std::get_if(&elm)) { return arg->PinIdx + 1; } } return 0; } }; BaseValue::Kind FormatTextNode::ArgumentTypeToValueKind(FormatTextNode::ArgumentType arg) { switch (arg) { case NumericArgument: return BaseValue::KD_Numeric; case TextArgument: return BaseValue::KD_Text; case DateTimeArgument: return BaseValue::KD_DateTime; default: UNREACHABLE; } } bool FormatTextNode::IsInstance(const WorkflowNode* node) { return node->GetKind() == KD_TextFormatting; } FormatTextNode::FormatTextNode() : WorkflowNode(TransformType, KD_TextFormatting) { } int FormatTextNode::GetElementCount() const { return mElements.size(); } const FormatTextNode::Element& FormatTextNode::GetElement(int idx) const { return mElements[idx]; } void FormatTextNode::SetElement(int idx, std::string text) { assert(idx >= 0 && idx < mElements.size()); std::visit( Overloaded{ [&](const std::string& original) { mMinOutputChars -= original.size(); }, [&](const Argument& original) { PreRemoveElement(idx); }, }, mElements[idx]); mMinOutputChars += text.size(); mElements[idx] = std::move(text); } void FormatTextNode::SetElement(int idx, ArgumentType argument) { assert(idx >= 0 && idx < mElements.size()); std::visit( Overloaded{ [&](const std::string& original) { mMinOutputChars -= original.size(); mElements[idx] = Argument{ .ArgumentType = argument, .PinIdx = Argument::FindPinForElement(mElements, idx), }; /* `original` is invalid from this point */ }, [&](const Argument& original) { int pinIdx = original.PinIdx; // Create pin auto& pin = mInputs[pinIdx]; pin.MatchingType = ArgumentTypeToValueKind(argument); // Create element mElements[idx] = Argument{ .ArgumentType = argument, .PinIdx = pinIdx, }; /* `original` is invalid from this point */ }, }, mElements[idx]); } void FormatTextNode::InsertElement(int idx, std::string text) { assert(idx >= 0); if (idx >= mElements.size()) AppendElement(std::move(text)); mMinOutputChars += text.size(); mElements.insert(mElements.begin() + idx, std::move(text)); } void FormatTextNode::InsertElement(int idx, ArgumentType argument) { assert(idx >= 0); if (idx >= mElements.size()) AppendElement(argument); int pinIdx = Argument::FindPinForElement(mElements, idx); // Create pin auto& pin = InsertInputPin(pinIdx); pin.MatchingType = ArgumentTypeToValueKind(argument); // Create element mElements.insert( mElements.begin() + idx, Argument{ .ArgumentType = argument, .PinIdx = pinIdx, }); } void FormatTextNode::AppendElement(std::string text) { mMinOutputChars += text.size(); mElements.push_back(std::move(text)); } void FormatTextNode::AppendElement(ArgumentType argument) { int pinIdx = mInputs.size(); // Create pin mInputs.push_back(InputPin{}); mInputs.back().MatchingType = ArgumentTypeToValueKind(argument); // Creat eelement mElements.push_back(Argument{ .ArgumentType = argument, .PinIdx = pinIdx, }); } void FormatTextNode::RemoveElement(int idx) { assert(idx >= 0 && idx < mElements.size()); PreRemoveElement(idx); if (auto arg = std::get_if(&mElements[idx])) { RemoveInputPin(arg->PinIdx); } mElements.erase(mElements.begin() + idx); } void FormatTextNode::Evaluate(WorkflowEvaluationContext& ctx) { std::string result; result.reserve((size_t)(mMinOutputChars * 1.5f)); auto HandleText = [&](const std::string& str) { result += str; }; auto HandleArgument = [&](const Argument& arg) { switch (arg.ArgumentType) { case NumericArgument: { if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { result += val->GetString(); } else { // TODO localize ctx.ReportError("Non-numeric value connected to a numeric text format parameter.", *this); } } break; case TextArgument: { if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { result += val->GetValue(); } else { // TODO localize ctx.ReportError("Non-text value connected to a textual text format parameter.", *this); } } break; case DateTimeArgument: { if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { result += val->GetString(); } else { // TODO localize ctx.ReportError("Non-date/time value connected to a date/time text format parameter.", *this); } } break; } }; for (auto& elm : mElements) { std::visit(Overloaded{ HandleText, HandleArgument }, elm); } } void FormatTextNode::PreRemoveElement(int idx) { auto& elm = mElements[idx]; if (auto arg = std::get_if(&elm)) { RemoveInputPin(arg->PinIdx); Argument::ForArguments( mElements.begin() + idx + 1, mElements.end(), [&](Argument& arg) { arg.PinIdx--; }); } } bool DocumentTemplateExpansionNode::IsInstance(const WorkflowNode* node) { return node->GetKind() == KD_DocumentTemplateExpansion; } DocumentTemplateExpansionNode::DocumentTemplateExpansionNode() : WorkflowNode(TransformType, KD_DocumentTemplateExpansion) { } bool FormInputNode::IsInstance(const WorkflowNode* node) { return node->GetKind() == KD_FormInput; } FormInputNode::FormInputNode() : WorkflowNode(InputType, KD_FormInput) { } bool DatabaseRowsInputNode::IsInstance(const WorkflowNode* node) { return node->GetKind() == KD_DatabaseRowsInput; } DatabaseRowsInputNode::DatabaseRowsInputNode() : WorkflowNode(InputType, KD_DatabaseRowsInput) { }