diff --git a/docs/superpowers/plans/2026-04-07-arraycalculator-memory-optimization.md b/docs/superpowers/plans/2026-04-07-arraycalculator-memory-optimization.md new file mode 100644 index 0000000000..a8db154391 --- /dev/null +++ b/docs/superpowers/plans/2026-04-07-arraycalculator-memory-optimization.md @@ -0,0 +1,1250 @@ +# ArrayCalculator Memory Optimization Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Eliminate unnecessary memory allocations in the ArrayCalculator evaluator by introducing a CalcBuffer RAII sentinel class, making the parser data-free, and enabling direct-write to the output array. + +**Architecture:** CalcBuffer wraps Float64Array references (borrowed or owned) with automatic cleanup via DataStructure::removeData(). The parser produces data-free RPN items (DataPath + metadata, no DataObject allocation). The evaluator creates a local DataStructure for temp arrays, uses a CalcBuffer stack where intermediates are freed via RAII when consumed, and the last operator writes directly into the output DataArray when the output type is float64. + +**Tech Stack:** C++20, simplnx DataStructure/DataArray/DataStore, Catch2 tests + +**Design spec:** `docs/superpowers/specs/2026-04-07-arraycalculator-memory-optimization-design.md` + +--- + +### Task 1: Establish Baseline — Verify All Existing Tests Pass + +**Files:** +- Read: `src/Plugins/SimplnxCore/test/ArrayCalculatorTest.cpp` + +This task ensures we have a clean starting point before making changes. + +- [ ] **Step 1: Build the project** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && cmake --build . --target SimplnxCore +``` + +Expected: Build succeeds with no errors. + +- [ ] **Step 2: Run existing ArrayCalculator tests** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && ctest -R "SimplnxCore::ArrayCalculatorFilter" --verbose +``` + +Expected: All test cases pass (Filter Execution, Tokenizer, Array Resolution, Built-in Constants, Modulo Operator, Tuple Component Indexing, Sub-expression Component Access, Multi-word Array Names, Sub-expression Tuple Component Extraction). + +--- + +### Task 2: Add CalcBuffer Class Declaration to Header + +**Files:** +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp` + +Add the CalcBuffer class declaration. This task adds new code only — nothing is removed or changed yet. + +- [ ] **Step 1: Add CalcBuffer class declaration after the OperatorDef struct (after line 133)** + +Insert the following class declaration between the `OperatorDef` struct and the `CalcValue` struct (before line 138): + +```cpp +// --------------------------------------------------------------------------- +// RAII sentinel for temporary Float64Arrays in the evaluator. +// Move-only. When an Owned CalcBuffer is destroyed, it removes its +// DataArray from the scratch DataStructure via removeData(). +// --------------------------------------------------------------------------- +class SIMPLNXCORE_EXPORT CalcBuffer +{ +public: + // --- Factory methods --- + + /** + * @brief Zero-copy reference to an existing Float64Array in the real DataStructure. + * Read-only. Destructor: no-op. + */ + static CalcBuffer borrow(const Float64Array& source); + + /** + * @brief Allocate a temp Float64Array in tempDS and convert source data from any numeric type. + * Owned. Destructor: removes the temp array from tempDS. + */ + static CalcBuffer convertFrom(DataStructure& tempDS, const IDataArray& source, const std::string& name); + + /** + * @brief Allocate a 1-element temp Float64Array with the given scalar value. + * Owned. Destructor: removes the temp array from tempDS. + */ + static CalcBuffer scalar(DataStructure& tempDS, float64 value, const std::string& name); + + /** + * @brief Allocate an empty temp Float64Array with the given shape. + * Owned. Destructor: removes the temp array from tempDS. + */ + static CalcBuffer allocate(DataStructure& tempDS, const std::string& name, std::vector tupleShape, std::vector compShape); + + /** + * @brief Wrap the output DataArray for direct writing. + * Not owned. Destructor: no-op. + */ + static CalcBuffer wrapOutput(DataArray& outputArray); + + // --- Move-only, non-copyable --- + CalcBuffer(CalcBuffer&& other) noexcept; + CalcBuffer& operator=(CalcBuffer&& other) noexcept; + ~CalcBuffer(); + + CalcBuffer(const CalcBuffer&) = delete; + CalcBuffer& operator=(const CalcBuffer&) = delete; + + // --- Element access --- + float64 read(usize index) const; + void write(usize index, float64 value); + void fill(float64 value); + + // --- Metadata --- + usize size() const; + usize numTuples() const; + usize numComponents() const; + std::vector tupleShape() const; + std::vector compShape() const; + bool isScalar() const; + bool isOwned() const; + bool isOutputDirect() const; + void markAsScalar(); + + // --- Access underlying array (for final copy to non-float64 output) --- + const Float64Array& array() const; + +private: + CalcBuffer() = default; + + enum class Storage + { + Borrowed, + Owned, + OutputDirect + }; + + Storage m_Storage = Storage::Owned; + + // Borrowed: const pointer to source Float64Array in real DataStructure + const Float64Array* m_BorrowedArray = nullptr; + + // Owned: pointer to temp Float64Array + reference to its DataStructure for cleanup + DataStructure* m_TempDS = nullptr; + DataObject::IdType m_ArrayId = 0; + Float64Array* m_OwnedArray = nullptr; + + // OutputDirect: writable pointer to output DataArray + DataArray* m_OutputArray = nullptr; + + bool m_IsScalar = false; +}; +``` + +- [ ] **Step 2: Build to verify the header compiles** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && cmake --build . --target SimplnxCore +``` + +Expected: Build succeeds. CalcBuffer is declared but not yet defined — the linker won't complain because nothing references it yet. + +- [ ] **Step 3: Commit** + +```bash +git add src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp +git commit -m "ENH: Add CalcBuffer RAII sentinel class declaration to ArrayCalculator.hpp" +``` + +--- + +### Task 3: Implement CalcBuffer in ArrayCalculator.cpp + +**Files:** +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp` + +Add the full CalcBuffer implementation. Place it after the anonymous namespace closing brace (after line 250) and before the `getOperatorRegistry()` function (line 255). This keeps it near the top of the file with other utility code. + +- [ ] **Step 1: Add CalcBuffer implementation** + +Insert the following after the anonymous namespace (after line 250, before `getOperatorRegistry()`): + +```cpp +// =========================================================================== +// CalcBuffer implementation +// =========================================================================== + +CalcBuffer::CalcBuffer(CalcBuffer&& other) noexcept +: m_Storage(other.m_Storage) +, m_BorrowedArray(other.m_BorrowedArray) +, m_TempDS(other.m_TempDS) +, m_ArrayId(other.m_ArrayId) +, m_OwnedArray(other.m_OwnedArray) +, m_OutputArray(other.m_OutputArray) +, m_IsScalar(other.m_IsScalar) +{ + other.m_TempDS = nullptr; + other.m_BorrowedArray = nullptr; + other.m_OwnedArray = nullptr; + other.m_OutputArray = nullptr; +} + +CalcBuffer& CalcBuffer::operator=(CalcBuffer&& other) noexcept +{ + if(this != &other) + { + // Clean up current state + if(m_Storage == Storage::Owned && m_TempDS != nullptr) + { + m_TempDS->removeData(m_ArrayId); + } + + m_Storage = other.m_Storage; + m_BorrowedArray = other.m_BorrowedArray; + m_TempDS = other.m_TempDS; + m_ArrayId = other.m_ArrayId; + m_OwnedArray = other.m_OwnedArray; + m_OutputArray = other.m_OutputArray; + m_IsScalar = other.m_IsScalar; + + other.m_TempDS = nullptr; + other.m_BorrowedArray = nullptr; + other.m_OwnedArray = nullptr; + other.m_OutputArray = nullptr; + } + return *this; +} + +CalcBuffer::~CalcBuffer() +{ + if(m_Storage == Storage::Owned && m_TempDS != nullptr) + { + m_TempDS->removeData(m_ArrayId); + } +} + +CalcBuffer CalcBuffer::borrow(const Float64Array& source) +{ + CalcBuffer buf; + buf.m_Storage = Storage::Borrowed; + buf.m_BorrowedArray = &source; + buf.m_IsScalar = false; + return buf; +} + +CalcBuffer CalcBuffer::convertFrom(DataStructure& tempDS, const IDataArray& source, const std::string& name) +{ + std::vector tupleShape = source.getTupleShape(); + std::vector compShape = source.getComponentShape(); + Float64Array* destArr = Float64Array::CreateWithStore(tempDS, name, tupleShape, compShape); + + usize totalElements = source.getSize(); + ExecuteDataFunction(CopyToFloat64Functor{}, source.getDataType(), source, *destArr); + + CalcBuffer buf; + buf.m_Storage = Storage::Owned; + buf.m_TempDS = &tempDS; + buf.m_ArrayId = destArr->getId(); + buf.m_OwnedArray = destArr; + buf.m_IsScalar = false; + return buf; +} + +CalcBuffer CalcBuffer::scalar(DataStructure& tempDS, float64 value, const std::string& name) +{ + Float64Array* arr = Float64Array::CreateWithStore(tempDS, name, std::vector{1}, std::vector{1}); + (*arr)[0] = value; + + CalcBuffer buf; + buf.m_Storage = Storage::Owned; + buf.m_TempDS = &tempDS; + buf.m_ArrayId = arr->getId(); + buf.m_OwnedArray = arr; + buf.m_IsScalar = true; + return buf; +} + +CalcBuffer CalcBuffer::allocate(DataStructure& tempDS, const std::string& name, std::vector tupleShape, std::vector compShape) +{ + Float64Array* arr = Float64Array::CreateWithStore(tempDS, name, tupleShape, compShape); + + CalcBuffer buf; + buf.m_Storage = Storage::Owned; + buf.m_TempDS = &tempDS; + buf.m_ArrayId = arr->getId(); + buf.m_OwnedArray = arr; + buf.m_IsScalar = false; + return buf; +} + +CalcBuffer CalcBuffer::wrapOutput(DataArray& outputArray) +{ + CalcBuffer buf; + buf.m_Storage = Storage::OutputDirect; + buf.m_OutputArray = &outputArray; + buf.m_IsScalar = false; + return buf; +} + +float64 CalcBuffer::read(usize index) const +{ + switch(m_Storage) + { + case Storage::Borrowed: + return m_BorrowedArray->at(index); + case Storage::Owned: + return m_OwnedArray->at(index); + case Storage::OutputDirect: + return m_OutputArray->at(index); + } + return 0.0; +} + +void CalcBuffer::write(usize index, float64 value) +{ + switch(m_Storage) + { + case Storage::Owned: + (*m_OwnedArray)[index] = value; + return; + case Storage::OutputDirect: + (*m_OutputArray)[index] = value; + return; + case Storage::Borrowed: + return; // read-only — should not be called + } +} + +void CalcBuffer::fill(float64 value) +{ + switch(m_Storage) + { + case Storage::Owned: + m_OwnedArray->fill(value); + return; + case Storage::OutputDirect: + m_OutputArray->fill(value); + return; + case Storage::Borrowed: + return; // read-only + } +} + +usize CalcBuffer::size() const +{ + switch(m_Storage) + { + case Storage::Borrowed: + return m_BorrowedArray->getSize(); + case Storage::Owned: + return m_OwnedArray->getSize(); + case Storage::OutputDirect: + return m_OutputArray->getSize(); + } + return 0; +} + +usize CalcBuffer::numTuples() const +{ + switch(m_Storage) + { + case Storage::Borrowed: + return m_BorrowedArray->getNumberOfTuples(); + case Storage::Owned: + return m_OwnedArray->getNumberOfTuples(); + case Storage::OutputDirect: + return m_OutputArray->getNumberOfTuples(); + } + return 0; +} + +usize CalcBuffer::numComponents() const +{ + switch(m_Storage) + { + case Storage::Borrowed: + return m_BorrowedArray->getNumberOfComponents(); + case Storage::Owned: + return m_OwnedArray->getNumberOfComponents(); + case Storage::OutputDirect: + return m_OutputArray->getNumberOfComponents(); + } + return 0; +} + +std::vector CalcBuffer::tupleShape() const +{ + switch(m_Storage) + { + case Storage::Borrowed: + return m_BorrowedArray->getTupleShape(); + case Storage::Owned: + return m_OwnedArray->getTupleShape(); + case Storage::OutputDirect: + return m_OutputArray->getTupleShape(); + } + return {}; +} + +std::vector CalcBuffer::compShape() const +{ + switch(m_Storage) + { + case Storage::Borrowed: + return m_BorrowedArray->getComponentShape(); + case Storage::Owned: + return m_OwnedArray->getComponentShape(); + case Storage::OutputDirect: + return m_OutputArray->getComponentShape(); + } + return {}; +} + +bool CalcBuffer::isScalar() const +{ + return m_IsScalar; +} + +bool CalcBuffer::isOwned() const +{ + return m_Storage == Storage::Owned; +} + +bool CalcBuffer::isOutputDirect() const +{ + return m_Storage == Storage::OutputDirect; +} + +void CalcBuffer::markAsScalar() +{ + m_IsScalar = true; +} + +const Float64Array& CalcBuffer::array() const +{ + switch(m_Storage) + { + case Storage::Borrowed: + return *m_BorrowedArray; + case Storage::Owned: + return *m_OwnedArray; + case Storage::OutputDirect: + return *m_OutputArray; + } + // Should never reach here; return owned as fallback + return *m_OwnedArray; +} +``` + +- [ ] **Step 2: Build to verify CalcBuffer compiles** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && cmake --build . --target SimplnxCore +``` + +Expected: Build succeeds. CalcBuffer is implemented but not yet used. + +- [ ] **Step 3: Run existing tests to verify no regressions** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && ctest -R "SimplnxCore::ArrayCalculatorFilter" --verbose +``` + +Expected: All tests pass (no change in behavior). + +- [ ] **Step 4: Commit** + +```bash +git add src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp +git commit -m "ENH: Implement CalcBuffer RAII sentinel for ArrayCalculator temp arrays" +``` + +--- + +### Task 4: Make ParsedItem and RpnItem Data-Free + +**Files:** +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp` (RpnItem) +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp` (ParsedItem) + +Replace CalcValue-based data in ParsedItem and RpnItem with metadata-only fields. This task changes the data structures but does not yet change the parser or evaluator logic — those come next. + +- [ ] **Step 1: Update RpnItem in ArrayCalculator.hpp** + +Replace the current `RpnItem` struct (lines 152-166) with the data-free version. Also delete the `CalcValue` struct (lines 138-147): + +Delete `CalcValue`: +```cpp +// DELETE the entire CalcValue struct (lines 138-147): +// struct SIMPLNXCORE_EXPORT CalcValue { ... }; +``` + +Replace `RpnItem`: +```cpp +// --------------------------------------------------------------------------- +// A single item in the RPN (reverse-polish notation) evaluation sequence. +// Data-free: stores DataPath references and scalar values, not DataObject IDs. +// --------------------------------------------------------------------------- +struct SIMPLNXCORE_EXPORT RpnItem +{ + enum class Type + { + Scalar, + ArrayRef, + Operator, + ComponentExtract, + TupleComponentExtract + } type; + + // Scalar + float64 scalarValue = 0.0; + + // ArrayRef + DataPath arrayPath; + DataType sourceDataType = DataType::float64; + + // Operator + const OperatorDef* op = nullptr; + + // ComponentExtract / TupleComponentExtract + usize componentIndex = std::numeric_limits::max(); + usize tupleIndex = std::numeric_limits::max(); +}; +``` + +- [ ] **Step 2: Update ParsedItem in the anonymous namespace of ArrayCalculator.cpp** + +Replace the current `ParsedItem` struct (lines 26-44) with the data-free version: + +```cpp +struct ParsedItem +{ + enum class Kind + { + Scalar, + ArrayRef, + Operator, + LParen, + RParen, + Comma, + ComponentExtract, + TupleComponentExtract + } kind; + + // Scalar + float64 scalarValue = 0.0; + + // ArrayRef: metadata for validation (no data allocated) + DataPath arrayPath; + DataType sourceDataType = DataType::float64; + std::vector arrayTupleShape; + std::vector arrayCompShape; + + // Operator + const OperatorDef* op = nullptr; + bool isNegativePrefix = false; + + // ComponentExtract / TupleComponentExtract + usize componentIndex = std::numeric_limits::max(); + usize tupleIndex = std::numeric_limits::max(); +}; +``` + +- [ ] **Step 3: Update isBinaryOp helper function** + +The `isBinaryOp` function (around line 139-142) references `ParsedItem::Kind::Operator` which stays the same. No change needed to this function. + +- [ ] **Step 4: Note — do NOT build yet** + +The parser and evaluator still reference the old CalcValue and old ParsedItem/RpnItem fields. They must be updated in Tasks 5 and 6 before the code compiles. Proceed directly to Task 5. + +--- + +### Task 5: Rewrite Parser to Be Data-Free + +**Files:** +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp` (remove parser members) +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp` (rewrite parse()) + +This is the largest task. The parser's `parse()` method is rewritten so it allocates zero data — it only produces RPN items with DataPath/scalar metadata. The helper methods `createScalarInTemp()`, `copyArrayToTemp()`, and `nextScratchName()` are removed from the parser class. The `m_TempDataStructure`, `m_IsPreflight`, and `m_ScratchCounter` members are removed. The `getTempDataStructure()` method is removed. + +- [ ] **Step 1: Remove parser-only members from ArrayCalculatorParser in the header** + +In `ArrayCalculator.hpp`, remove the following from the `ArrayCalculatorParser` class: + +Remove these private method declarations: +- `std::string nextScratchName();` (line 234) +- `DataObject::IdType copyArrayToTemp(const IDataArray& sourceArray);` (line 242) +- `DataObject::IdType createScalarInTemp(double value);` (line 249) + +Remove these private member variables: +- `DataStructure m_TempDataStructure;` (line 252) +- `bool m_IsPreflight;` (line 255) +- `usize m_ScratchCounter = 0;` (line 256) + +Remove this public method: +- `DataStructure& getTempDataStructure() { ... }` (lines 217-221) + +Update the constructor signature — remove `bool isPreflight` parameter: +```cpp +ArrayCalculatorParser(const DataStructure& dataStructure, const DataPath& selectedGroupPath, const std::string& infixEquation, const std::atomic_bool& shouldCancel); +``` + +- [ ] **Step 2: Update ArrayCalculatorParser constructor implementation in .cpp** + +Replace the constructor (lines 305-312) with: + +```cpp +ArrayCalculatorParser::ArrayCalculatorParser(const DataStructure& dataStructure, const DataPath& selectedGroupPath, const std::string& infixEquation, const std::atomic_bool& shouldCancel) +: m_DataStructure(dataStructure) +, m_SelectedGroupPath(selectedGroupPath) +, m_InfixEquation(infixEquation) +, m_ShouldCancel(shouldCancel) +{ +} +``` + +- [ ] **Step 3: Delete the old helper method implementations from .cpp** + +Delete the following function bodies from ArrayCalculator.cpp: +- `ArrayCalculatorParser::nextScratchName()` (lines 443-446) +- `ArrayCalculatorParser::createScalarInTemp()` (lines 449-454) +- `ArrayCalculatorParser::copyArrayToTemp()` (lines 457-476) + +- [ ] **Step 4: Rewrite parse() — token resolution (steps 3+4)** + +This is the core change. In the token resolution loop (starting around line 595), every place that previously called `createScalarInTemp()` or `copyArrayToTemp()` must instead store metadata in the ParsedItem. + +**For numeric literals** (the `TokenType::Number` case, around line 773): +Replace: +```cpp +DataObject::IdType id = createScalarInTemp(numValue); +ParsedItem pi; +pi.kind = ParsedItem::Kind::Value; +pi.value = CalcValue{CalcValue::Kind::Number, id}; +``` +With: +```cpp +ParsedItem pi; +pi.kind = ParsedItem::Kind::Scalar; +pi.scalarValue = numValue; +``` + +**For constants `pi` and `e`** (around line 841): +Replace: +```cpp +double constValue = (tok.text == "pi") ? std::numbers::pi : std::numbers::e; +DataObject::IdType id = createScalarInTemp(constValue); +ParsedItem pi; +pi.kind = ParsedItem::Kind::Value; +pi.value = CalcValue{CalcValue::Kind::Number, id}; +``` +With: +```cpp +float64 constValue = (tok.text == "pi") ? std::numbers::pi : std::numbers::e; +ParsedItem pi; +pi.kind = ParsedItem::Kind::Scalar; +pi.scalarValue = constValue; +``` + +**For array references found via selected group** (around line 876): +Replace: +```cpp +DataObject::IdType id = copyArrayToTemp(*dataArray); +ParsedItem pi; +pi.kind = ParsedItem::Kind::Value; +pi.value = CalcValue{CalcValue::Kind::Array, id}; +``` +With: +```cpp +ParsedItem pi; +pi.kind = ParsedItem::Kind::ArrayRef; +pi.arrayPath = arrayPath; +pi.sourceDataType = dataArray->getDataType(); +pi.arrayTupleShape = dataArray->getTupleShape(); +pi.arrayCompShape = dataArray->getComponentShape(); +``` + +Apply the same pattern to **all other array resolution sites**: +- Array found via `findArraysByName()` (around line 916): same change — store DataPath, DataType, shapes +- Quoted string path resolution (around line 969): same change + +**For bracket indexing `Array[C]`** (the block starting around line 635): + +Currently this block accesses `m_TempDataStructure` to get the temp array and extract component data. Replace the entire bracket handling block. When `prevItem.kind == ParsedItem::Kind::ArrayRef`: + +For `[C]` (single bracket number): +```cpp +usize numComponents = 1; +for(usize d : prevItem.arrayCompShape) +{ + numComponents *= d; +} + +// Validate component index +if(compIdx >= numComponents) +{ + return MakeErrorResult(static_cast(CalculatorErrorCode::ComponentOutOfRange), + fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComponents)); +} + +// Emit a ComponentExtract after the ArrayRef +ParsedItem ce; +ce.kind = ParsedItem::Kind::ComponentExtract; +ce.componentIndex = compIdx; +items.push_back(ce); +``` + +For `[T, C]` (two bracket numbers): +```cpp +usize numTuples = 1; +for(usize d : prevItem.arrayTupleShape) +{ + numTuples *= d; +} +usize numComponents = 1; +for(usize d : prevItem.arrayCompShape) +{ + numComponents *= d; +} + +if(tupleIdx >= numTuples) +{ + return MakeErrorResult(static_cast(CalculatorErrorCode::TupleOutOfRange), + fmt::format("Tuple index {} is out of range for array with {} tuples.", tupleIdx, numTuples)); +} +if(compIdx >= numComponents) +{ + return MakeErrorResult(static_cast(CalculatorErrorCode::ComponentOutOfRange), + fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComponents)); +} + +ParsedItem tce; +tce.kind = ParsedItem::Kind::TupleComponentExtract; +tce.tupleIndex = tupleIdx; +tce.componentIndex = compIdx; +items.push_back(tce); +``` + +The `(expr)[C]` and `(expr)[T,C]` paths (when `prevItem.kind == ParsedItem::Kind::RParen`) remain unchanged — they already emit ComponentExtract/TupleComponentExtract items. + +- [ ] **Step 5: Rewrite parse() — validation step 7b** + +The validation step 7b (starting around line 1316) currently queries `m_TempDataStructure.getDataAs()` for each array value. Replace it to query `ParsedItem::arrayTupleShape` and `ParsedItem::arrayCompShape` directly: + +```cpp +// 7b: Collect array-type values and verify consistent tuple/component info +std::vector arrayTupleShape; +std::vector arrayCompShape; +usize arrayNumTuples = 0; +bool hasArray = false; +bool hasNumericValue = false; +bool tupleShapesMatch = true; + +for(const auto& item : items) +{ + if(item.kind == ParsedItem::Kind::Scalar || item.kind == ParsedItem::Kind::ArrayRef) + { + hasNumericValue = true; + } + if(item.kind == ParsedItem::Kind::ArrayRef) + { + std::vector ts = item.arrayTupleShape; + std::vector cs = item.arrayCompShape; + usize nt = 1; + for(usize d : ts) + { + nt *= d; + } + + if(hasArray) + { + if(!arrayCompShape.empty() && arrayCompShape != cs) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InconsistentCompDims), + "Attribute Array symbols in the infix expression have mismatching component dimensions."); + } + if(arrayNumTuples != 0 && nt != arrayNumTuples) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InconsistentTuples), + "Attribute Array symbols in the infix expression have mismatching number of tuples."); + } + if(!arrayTupleShape.empty() && arrayTupleShape != ts) + { + tupleShapesMatch = false; + } + } + + hasArray = true; + arrayTupleShape = ts; + arrayCompShape = cs; + arrayNumTuples = nt; + } +} +``` + +- [ ] **Step 6: Rewrite parse() — shunting-yard conversion to RPN** + +The shunting-yard loop (starting around line 1416) currently converts ParsedItems to RpnItems. Update the `Value` case to handle the new `Scalar` and `ArrayRef` kinds: + +Replace the `ParsedItem::Kind::Value` case with two cases: + +```cpp +case ParsedItem::Kind::Scalar: { + RpnItem rpn; + rpn.type = RpnItem::Type::Scalar; + rpn.scalarValue = item.scalarValue; + m_RpnItems.push_back(rpn); + break; +} + +case ParsedItem::Kind::ArrayRef: { + RpnItem rpn; + rpn.type = RpnItem::Type::ArrayRef; + rpn.arrayPath = item.arrayPath; + rpn.sourceDataType = item.sourceDataType; + m_RpnItems.push_back(rpn); + break; +} +``` + +Update the `ComponentExtract` and `TupleComponentExtract` cases similarly — they already match the new RpnItem fields. Just ensure the RpnItem type assignment uses `RpnItem::Type::ComponentExtract` / `RpnItem::Type::TupleComponentExtract` and sets `componentIndex`/`tupleIndex`. + +- [ ] **Step 7: Update constructor calls in ArrayCalculatorFilter.cpp** + +In `ArrayCalculatorFilter.cpp`, update the two places where `ArrayCalculatorParser` is constructed: + +In `preflightImpl()` (around line 88), remove the `true` (isPreflight) argument: +```cpp +ArrayCalculatorParser parser(dataStructure, pInfixEquationValue.m_SelectedGroup, pInfixEquationValue.m_Equation, m_ShouldCancel); +``` + +In `ArrayCalculator::operator()()` in ArrayCalculator.cpp (around line 1789), remove the `false` (isPreflight) argument: +```cpp +ArrayCalculatorParser parser(m_DataStructure, m_InputValues->SelectedGroup, m_InputValues->InfixEquation, m_ShouldCancel); +``` + +- [ ] **Step 8: Note — do NOT build yet** + +The evaluator (`evaluateInto()`) still references the old CalcValue-based eval stack. It must be updated in Task 6 before the code compiles. + +--- + +### Task 6: Rewrite Evaluator to Use CalcBuffer Stack + +**Files:** +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp` + +Rewrite `evaluateInto()` to use a `std::stack` with a local `DataStructure` for temps. Add the last-operator OutputDirect optimization and the final result copy logic. + +- [ ] **Step 1: Rewrite evaluateInto()** + +Replace the entire `evaluateInto()` method (starting at line 1534) with the following implementation. This is the complete new evaluator: + +```cpp +Result<> ArrayCalculatorParser::evaluateInto(DataStructure& dataStructure, const DataPath& outputPath, NumericType scalarType, CalculatorParameter::AngleUnits units) +{ + // 1. Parse (populates m_RpnItems via shunting-yard) + Result<> parseResult = parse(); + if(parseResult.invalid()) + { + return parseResult; + } + + // 2. Create local temp DataStructure for intermediate arrays + DataStructure tempDS; + usize scratchCounter = 0; + auto nextScratchName = [&scratchCounter]() -> std::string { + return "_calc_" + std::to_string(scratchCounter++); + }; + + // 3. Pre-scan RPN to find the index of the last operator/extract item + // for the OutputDirect optimization + DataType outputDataType = ConvertNumericTypeToDataType(scalarType); + bool outputIsFloat64 = (outputDataType == DataType::float64); + int64 lastOpIndex = -1; + for(int64 idx = static_cast(m_RpnItems.size()) - 1; idx >= 0; --idx) + { + RpnItem::Type t = m_RpnItems[static_cast(idx)].type; + if(t == RpnItem::Type::Operator || t == RpnItem::Type::ComponentExtract || t == RpnItem::Type::TupleComponentExtract) + { + lastOpIndex = idx; + break; + } + } + + // 4. Walk the RPN items using a CalcBuffer evaluation stack + std::stack evalStack; + + for(usize rpnIdx = 0; rpnIdx < m_RpnItems.size(); ++rpnIdx) + { + if(m_ShouldCancel) + { + return {}; + } + + const RpnItem& rpnItem = m_RpnItems[rpnIdx]; + bool isLastOp = (static_cast(rpnIdx) == lastOpIndex); + + switch(rpnItem.type) + { + case RpnItem::Type::Scalar: { + evalStack.push(CalcBuffer::scalar(tempDS, rpnItem.scalarValue, nextScratchName())); + break; + } + + case RpnItem::Type::ArrayRef: { + if(rpnItem.sourceDataType == DataType::float64) + { + const auto& sourceArray = m_DataStructure.getDataRefAs(rpnItem.arrayPath); + evalStack.push(CalcBuffer::borrow(sourceArray)); + } + else + { + const auto& sourceArray = m_DataStructure.getDataRefAs(rpnItem.arrayPath); + evalStack.push(CalcBuffer::convertFrom(tempDS, sourceArray, nextScratchName())); + } + break; + } + + case RpnItem::Type::Operator: { + const OperatorDef* op = rpnItem.op; + if(op == nullptr) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidEquation), "Internal error: null operator in RPN evaluation."); + } + + if(op->numArgs == 1) + { + if(evalStack.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for unary operator."); + } + CalcBuffer operand = std::move(evalStack.top()); + evalStack.pop(); + + std::vector resultTupleShape = operand.tupleShape(); + std::vector resultCompShape = operand.compShape(); + usize totalSize = operand.size(); + + CalcBuffer result = (isLastOp && outputIsFloat64) + ? CalcBuffer::wrapOutput(dataStructure.getDataRefAs>(outputPath)) + : CalcBuffer::allocate(tempDS, nextScratchName(), resultTupleShape, resultCompShape); + + for(usize i = 0; i < totalSize; i++) + { + float64 val = operand.read(i); + + if(op->trigMode == OperatorDef::ForwardTrig && units == CalculatorParameter::AngleUnits::Degrees) + { + val = val * (std::numbers::pi / 180.0); + } + + float64 res = op->unaryOp(val); + + if(op->trigMode == OperatorDef::InverseTrig && units == CalculatorParameter::AngleUnits::Degrees) + { + res = res * (180.0 / std::numbers::pi); + } + + result.write(i, res); + } + + bool wasScalar = operand.isScalar(); + if(wasScalar) + { + result.markAsScalar(); + } + // operand destroyed here, RAII cleans up + evalStack.push(std::move(result)); + } + else if(op->numArgs == 2) + { + if(evalStack.size() < 2) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for binary operator."); + } + CalcBuffer right = std::move(evalStack.top()); + evalStack.pop(); + CalcBuffer left = std::move(evalStack.top()); + evalStack.pop(); + + // Determine output shape: use the array operand's shape (broadcast scalars) + std::vector outTupleShape; + std::vector outCompShape; + if(!left.isScalar()) + { + outTupleShape = left.tupleShape(); + outCompShape = left.compShape(); + } + else + { + outTupleShape = right.tupleShape(); + outCompShape = right.compShape(); + } + + usize totalSize = 1; + for(usize d : outTupleShape) + { + totalSize *= d; + } + for(usize d : outCompShape) + { + totalSize *= d; + } + + CalcBuffer result = (isLastOp && outputIsFloat64) + ? CalcBuffer::wrapOutput(dataStructure.getDataRefAs>(outputPath)) + : CalcBuffer::allocate(tempDS, nextScratchName(), outTupleShape, outCompShape); + + bool leftIsScalar = left.isScalar(); + bool rightIsScalar = right.isScalar(); + + for(usize i = 0; i < totalSize; i++) + { + float64 lv = left.read(leftIsScalar ? 0 : i); + float64 rv = right.read(rightIsScalar ? 0 : i); + result.write(i, op->binaryOp(lv, rv)); + } + + if(leftIsScalar && rightIsScalar) + { + result.markAsScalar(); + } + // left and right destroyed here, RAII cleans up owned temps + evalStack.push(std::move(result)); + } + else + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidEquation), + fmt::format("Internal error: operator '{}' has unsupported numArgs={}.", op->token, op->numArgs)); + } + break; + } + + case RpnItem::Type::ComponentExtract: { + if(evalStack.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for component extraction."); + } + CalcBuffer operand = std::move(evalStack.top()); + evalStack.pop(); + + usize numComps = operand.numComponents(); + usize numTuples = operand.numTuples(); + usize compIdx = rpnItem.componentIndex; + + if(compIdx >= numComps) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::ComponentOutOfRange), + fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComps)); + } + + CalcBuffer result = (isLastOp && outputIsFloat64) + ? CalcBuffer::wrapOutput(dataStructure.getDataRefAs>(outputPath)) + : CalcBuffer::allocate(tempDS, nextScratchName(), operand.tupleShape(), std::vector{1}); + + for(usize t = 0; t < numTuples; ++t) + { + result.write(t, operand.read(t * numComps + compIdx)); + } + + evalStack.push(std::move(result)); + break; + } + + case RpnItem::Type::TupleComponentExtract: { + if(evalStack.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for tuple+component extraction."); + } + CalcBuffer operand = std::move(evalStack.top()); + evalStack.pop(); + + usize numComps = operand.numComponents(); + usize numTuples = operand.numTuples(); + usize tupleIdx = rpnItem.tupleIndex; + usize compIdx = rpnItem.componentIndex; + + if(tupleIdx >= numTuples) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::TupleOutOfRange), + fmt::format("Tuple index {} is out of range for array with {} tuples.", tupleIdx, numTuples)); + } + if(compIdx >= numComps) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::ComponentOutOfRange), + fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComps)); + } + + float64 value = operand.read(tupleIdx * numComps + compIdx); + // operand destroyed, RAII cleans up + evalStack.push(CalcBuffer::scalar(tempDS, value, nextScratchName())); + break; + } + + } // end switch + } + + // 5. Final result + if(evalStack.size() != 1) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidEquation), + fmt::format("Internal error: evaluation stack has {} items remaining; expected exactly 1.", evalStack.size())); + } + + CalcBuffer finalResult = std::move(evalStack.top()); + evalStack.pop(); + + // 6. Copy/cast result into the output array (checked in order, first match wins) + if(finalResult.isScalar()) + { + // Fill entire output with the scalar value + float64 scalarVal = finalResult.read(0); + ExecuteDataFunction(CopyResultFunctor{}, outputDataType, dataStructure, outputPath, scalarVal); + } + else if(finalResult.isOutputDirect()) + { + // Data is already in the output array — nothing to do + } + else if(outputIsFloat64) + { + // Direct float64-to-float64 copy via operator[] (no type cast) + auto& outputArray = dataStructure.getDataRefAs>(outputPath); + usize totalSize = finalResult.size(); + for(usize i = 0; i < totalSize; i++) + { + outputArray[i] = finalResult.read(i); + } + } + else + { + // Type-casting copy via CopyResultFunctor + const Float64Array& resultArray = finalResult.array(); + ExecuteDataFunction(CopyResultFunctor{}, outputDataType, dataStructure, outputPath, &resultArray, false); + } + + return parseResult; +} +``` + +- [ ] **Step 2: Update CopyResultFunctor to support scalar fill** + +The scalar fill path now passes a `float64` value directly. Add an overload or update `CopyResultFunctor` in the anonymous namespace. Replace the existing `CopyResultFunctor` (lines 230-248) with: + +```cpp +struct CopyResultFunctor +{ + // Full array copy (non-float64 output) + template + void operator()(DataStructure& ds, const DataPath& outputPath, const Float64Array* resultArray, bool /*unused*/) + { + auto& output = ds.getDataRefAs>(outputPath).getDataStoreRef(); + for(usize i = 0; i < output.getSize(); i++) + { + output[i] = static_cast(resultArray->at(i)); + } + } + + // Scalar fill + template + void operator()(DataStructure& ds, const DataPath& outputPath, float64 scalarValue) + { + auto& output = ds.getDataRefAs>(outputPath); + output.fill(static_cast(scalarValue)); + } +}; +``` + +- [ ] **Step 3: Remove old CopyToFloat64Functor** + +The `CopyToFloat64Functor` (lines 122-134) is no longer needed at the top level since its logic is now inside `CalcBuffer::convertFrom()`. However, `CalcBuffer::convertFrom()` still calls it via `ExecuteDataFunction`. Keep `CopyToFloat64Functor` in the anonymous namespace — it is still referenced by `CalcBuffer::convertFrom()`. + +- [ ] **Step 4: Build the project** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && cmake --build . --target SimplnxCore +``` + +Expected: Build succeeds. Fix any compilation errors arising from ParsedItem/RpnItem field name mismatches — common issues: +- `item.kind == ParsedItem::Kind::Value` → split into `ParsedItem::Kind::Scalar` and `ParsedItem::Kind::ArrayRef` +- `item.value.kind == CalcValue::Kind::Array` → `item.kind == ParsedItem::Kind::ArrayRef` +- References to `item.value` → `item.scalarValue` or `item.arrayPath` + +- [ ] **Step 5: Run all ArrayCalculator tests** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && ctest -R "SimplnxCore::ArrayCalculatorFilter" --verbose +``` + +Expected: All test cases pass — identical behavior to baseline. If any fail, debug by comparing the error code or output value against the expected. Common issues: +- Bracket indexing `Array[C]` now emits ArrayRef + ComponentExtract, so the evaluator must handle ComponentExtract on borrowed arrays correctly +- Scalar detection: CalcBuffer created via `CalcBuffer::scalar()` has `m_IsScalar = true`, but CalcBuffers from binary operations where both operands are scalar should also be scalar. Check that the scalar fill path triggers correctly for all-scalar expressions. + +- [ ] **Step 6: Commit** + +```bash +git add src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp \ + src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp \ + src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ArrayCalculatorFilter.cpp +git commit -m "MEM: Rewrite ArrayCalculator parser and evaluator with CalcBuffer RAII + +Parser is now data-free: produces RPN items with DataPath/scalar metadata +instead of allocating temporary Float64Arrays. Evaluator uses a CalcBuffer +stack with RAII cleanup — intermediates are freed when consumed. Float64 +input arrays are borrowed (zero-copy). The last RPN operator writes +directly into the output DataArray when output type is float64." +``` + +--- + +### Task 7: Final Verification and Cleanup + +**Files:** +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp` (cleanup) +- Modify: `src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp` (cleanup) + +- [ ] **Step 1: Run clang-format on modified files** + +```bash +cd /Users/mjackson/Workspace7/simplnx && clang-format -i \ + src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp \ + src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp \ + src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ArrayCalculatorFilter.cpp +``` + +- [ ] **Step 2: Build the full project (not just SimplnxCore)** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && cmake --build . --target all +``` + +Expected: Full build succeeds — no other files reference CalcValue or the removed parser members. + +- [ ] **Step 3: Run the full SimplnxCore test suite** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && ctest -R "SimplnxCore::" --verbose +``` + +Expected: All SimplnxCore tests pass. This catches any accidental regressions in other filters. + +- [ ] **Step 4: Run ArrayCalculator tests specifically and verify all assertions pass** + +```bash +cd /Users/mjackson/Workspace2/DREAM3D-Build/simplnx-Rel && ctest -R "SimplnxCore::ArrayCalculatorFilter" --verbose +``` + +Expected: All 9 test cases pass with all assertions (the test output should show the same assertion count as the baseline from Task 1). + +- [ ] **Step 5: Commit formatting changes (if any)** + +```bash +git add src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp \ + src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp \ + src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ArrayCalculatorFilter.cpp +git commit -m "STY: Run clang-format on ArrayCalculator files after memory optimization" +``` + diff --git a/docs/superpowers/specs/2026-04-07-arraycalculator-memory-optimization-design.md b/docs/superpowers/specs/2026-04-07-arraycalculator-memory-optimization-design.md new file mode 100644 index 0000000000..177fadb63f --- /dev/null +++ b/docs/superpowers/specs/2026-04-07-arraycalculator-memory-optimization-design.md @@ -0,0 +1,289 @@ +# ArrayCalculator Memory Optimization Design + +## Problem + +The ArrayCalculator evaluator allocates temporary `Float64Array` objects in a scratch `DataStructure` (`m_TempDataStructure`) for every input array, every intermediate result, and the final result. None of these temporaries are freed until the parser is destroyed. For a simple expression like `a + b` with float64 inputs and float64 output, this produces 4 array-sized allocations when 0 would suffice. + +For large datasets with complex expressions, this causes memory usage to explode: an expression with N operations on an array of M elements allocates O(N * M * 8) bytes of temporaries that all stay alive simultaneously. + +### Specific waste patterns + +1. **Input float64 arrays are copied into temp Float64Arrays** even though the data is already float64 — a full O(M) allocation + O(M) copy per input reference. +2. **Every intermediate result allocates a new Float64Array** in `m_TempDataStructure`. None are freed until the parser destructs. +3. **The final result is copied element-by-element** from the temp Float64Array into the output DataArray, even when the output type is float64 — an unnecessary O(M) allocation + O(M) copy. + +## Constraints + +- **`m_TempDataStructure` must be retained.** Temporary arrays must live inside a `DataStructure` because the `DataArray`/`DataStore` abstraction is load-bearing: the project is moving to an out-of-core `DataStore` implementation where data may not be resident in memory. Raw `std::vector` or `float64*` buffers cannot replace `DataArray`. +- **No raw pointer access to DataArray data.** All element reads/writes must go through `DataArray::at()` or `operator[]`. Never use `T*` pointers obtained from `DataStore::data()` or similar. +- **Memory optimization is the first priority.** CPU computation performance is the second priority. +- **Backward compatibility.** All existing tests must continue to pass. Error codes, parameter keys, and pipeline JSON formats are unchanged by this work. + +## Solution: CalcBuffer — RAII Sentinel for Temp DataArrays + +### Overview + +Introduce a `CalcBuffer` class: a move-only RAII handle that wraps a `Float64Array` (either a temp array in `m_TempDataStructure` or a borrowed reference to an input array in the real `DataStructure`). When an owned `CalcBuffer` is destroyed, it removes its `DataArray` from the `DataStructure` via `DataStructure::removeData(DataObject::IdType)`. + +The evaluation stack changes from `std::stack` (with manual ID lookups) to `std::stack` (with automatic RAII cleanup). Intermediates are freed the instant they are consumed by an operator. + +### CalcBuffer class + +```cpp +class SIMPLNXCORE_EXPORT CalcBuffer +{ +public: + // --- Factory methods --- + + // Zero-copy reference to an existing Float64Array. Read-only. Destructor: no-op. + static CalcBuffer borrow(const Float64Array& source); + + // Allocate a temp Float64Array in tempDS, convert source data from any numeric type. + // Owned. Destructor: removes from tempDS. + static CalcBuffer convertFrom(DataStructure& tempDS, const IDataArray& source, + const std::string& name); + + // Allocate a 1-element temp Float64Array with the given scalar value. + static CalcBuffer scalar(DataStructure& tempDS, double value, const std::string& name); + + // Allocate an empty temp Float64Array with the given shape. + static CalcBuffer allocate(DataStructure& tempDS, const std::string& name, + std::vector tupleShape, std::vector compShape); + + // Wrap the output DataArray for direct writing. Destructor: no-op. + static CalcBuffer wrapOutput(DataArray& outputArray); + + // --- Move-only, non-copyable --- + CalcBuffer(CalcBuffer&& other) noexcept; + CalcBuffer& operator=(CalcBuffer&& other) noexcept; + ~CalcBuffer(); + + CalcBuffer(const CalcBuffer&) = delete; + CalcBuffer& operator=(const CalcBuffer&) = delete; + + // --- Element access --- + float64 read(usize index) const; + void write(usize index, float64 value); + void fill(float64 value); + + // --- Metadata --- + usize size() const; + usize numTuples() const; + usize numComponents() const; + std::vector tupleShape() const; + std::vector compShape() const; + bool isScalar() const; + bool isOwned() const; + bool isOutputDirect() const; + + // --- Access underlying array (for final copy to non-float64 output) --- + const Float64Array& array() const; + +private: + CalcBuffer() = default; + + enum class Storage + { + Borrowed, // const Float64Array* in real DataStructure, read-only + Owned, // Float64Array in m_TempDataStructure, read-write, cleaned up on destroy + OutputDirect // DataArray* output array, read-write, NOT cleaned up + }; + + Storage m_Storage = Storage::Owned; + + // Borrowed + const Float64Array* m_BorrowedArray = nullptr; + + // Owned + DataStructure* m_TempDS = nullptr; + DataObject::IdType m_ArrayId = 0; + Float64Array* m_OwnedArray = nullptr; + + // OutputDirect + DataArray* m_OutputArray = nullptr; + + bool m_IsScalar = false; +}; +``` + +#### Storage mode behavior + +| Mode | `read(i)` | `write(i, v)` | Destructor | +|------|-----------|---------------|------------| +| Borrowed | `m_BorrowedArray->at(i)` | assert/error | no-op | +| Owned | `m_OwnedArray->at(i)` | `(*m_OwnedArray)[i] = v` | `m_TempDS->removeData(m_ArrayId)` | +| OutputDirect | `m_OutputArray->at(i)` | `(*m_OutputArray)[i] = v` | no-op | + +#### Move semantics + +The move constructor transfers all fields and nulls out the source so the moved-from object's destructor is a no-op: + +```cpp +CalcBuffer::CalcBuffer(CalcBuffer&& other) noexcept +: m_Storage(other.m_Storage) +, m_BorrowedArray(other.m_BorrowedArray) +, m_TempDS(other.m_TempDS) +, m_ArrayId(other.m_ArrayId) +, m_OwnedArray(other.m_OwnedArray) +, m_OutputArray(other.m_OutputArray) +, m_IsScalar(other.m_IsScalar) +{ + other.m_TempDS = nullptr; // prevents other's destructor from removing the array + other.m_BorrowedArray = nullptr; + other.m_OwnedArray = nullptr; + other.m_OutputArray = nullptr; +} +``` + +### RPN item changes (data-free parser) + +`CalcValue` is deleted. `RpnItem` stores metadata only — no `DataObject::IdType`, no data allocation during parsing. + +```cpp +struct SIMPLNXCORE_EXPORT RpnItem +{ + enum class Type + { + Scalar, // Numeric literal or constant (pi, e) + ArrayRef, // Reference to a source array in the real DataStructure + Operator, // Math operator or function + ComponentExtract, // [C] on a sub-expression result + TupleComponentExtract // [T, C] on a sub-expression result + } type; + + // Scalar + float64 scalarValue = 0.0; + + // ArrayRef + DataPath arrayPath; + DataType sourceDataType = DataType::float64; + + // Operator + const OperatorDef* op = nullptr; + + // ComponentExtract / TupleComponentExtract + usize componentIndex = std::numeric_limits::max(); + usize tupleIndex = std::numeric_limits::max(); +}; +``` + +### Parser changes + +The parser becomes a pure validation + RPN construction pass with no data allocation: + +1. **`createScalarInTemp()` eliminated** — parser stores `float64 scalarValue` in RpnItem. +2. **`copyArrayToTemp()` eliminated** — parser stores `DataPath` + `DataType` from the source. +3. **`Array[C]` bracket indexing unified with `(expr)[C]`** — parser emits `ArrayRef` + `ComponentExtract` RPN items instead of extracting component data during parsing. +4. **`Array[T,C]` bracket indexing unified with `(expr)[T,C]`** — parser emits `ArrayRef` + `TupleComponentExtract`. +5. **Validation step 7b** queries source array shapes directly via `dataStructure.getDataRefAs(path)` instead of looking at temp arrays. +6. **`m_IsPreflight` flag eliminated** — parser is identical for preflight and execution. +7. **`m_TempDataStructure` removed from the parser** — it is created in the evaluator only. + +### Evaluator changes + +`m_TempDataStructure` becomes a local variable inside `evaluateInto()` (currently it is a member of `ArrayCalculatorParser`). Since the parser no longer needs it, the evaluator creates it on the stack and it is destroyed — along with any remaining temp arrays — when `evaluateInto()` returns. The eval stack is `std::stack`. + +#### Buffer creation by RPN type + +| RPN Type | CalcBuffer creation | +|----------|-------------------| +| `Scalar` | `CalcBuffer::scalar(tempDS, value, name)` | +| `ArrayRef` where `sourceDataType == float64` | `CalcBuffer::borrow(sourceFloat64Array)` | +| `ArrayRef` where `sourceDataType != float64` | `CalcBuffer::convertFrom(tempDS, source, name)` | + +#### RAII cleanup during operator evaluation + +When processing a binary operator, operands are moved out of the stack into locals. After the result is computed and pushed, the locals are destroyed at the closing brace, triggering `removeData()` for any owned temps: + +```cpp +{ + CalcBuffer right = std::move(evalStack.top()); + evalStack.pop(); + CalcBuffer left = std::move(evalStack.top()); + evalStack.pop(); + + CalcBuffer result = CalcBuffer::allocate(tempDS, name, outTupleShape, outCompShape); + + bool leftIsScalar = left.isScalar(); + bool rightIsScalar = right.isScalar(); + + for(usize i = 0; i < totalSize; i++) + { + float64 lv = left.read(leftIsScalar ? 0 : i); + float64 rv = right.read(rightIsScalar ? 0 : i); + result.write(i, op->binaryOp(lv, rv)); + } + + evalStack.push(std::move(result)); +} +// left and right destroyed here -> owned temp arrays removed from tempDS +``` + +#### Last-operator direct-write optimization + +For the last RPN operator, when the output type is `float64`: + +1. Pre-scan `m_RpnItems` to find the index of the last Operator/ComponentExtract/TupleComponentExtract. +2. When processing that item, use `CalcBuffer::wrapOutput(outputFloat64Array)` instead of `CalcBuffer::allocate()`. +3. Writes go directly into the output array via `operator[]`. +4. At the end, detect `isOutputDirect()` on the final CalcBuffer and skip the copy step. + +This eliminates the last temp array allocation entirely. + +#### Final result copy to output + +After the RPN loop, the stack has exactly one CalcBuffer: + +Checked in order (first match wins): + +| Final CalcBuffer | Output type | Action | +|-----------------|-------------|--------| +| isScalar() | any | Fill the output array with the scalar value via `DataArray::fill()` or equivalent loop | +| OutputDirect | float64 | No copy needed — data is already in the output | +| Owned or Borrowed | float64 | Element-by-element copy into output `DataArray` via `operator[]` (no type cast) | +| Owned or Borrowed | non-float64 | `CopyResultFunctor` cast-copy via `ExecuteDataFunction` | + +### CPU performance considerations + +- **`CalcBuffer::read()` branch on storage mode**: predictable per-CalcBuffer (same branch every call). Negligible cost compared to `std::function` dispatch through `op->binaryOp`/`op->unaryOp`. +- **Scalar broadcast check**: hoisted outside inner loops (`leftIsScalar`/`rightIsScalar` evaluated once before the loop). +- **OutputDirect adds a third branch to `write()`**: only applies to the single final-result CalcBuffer, so the branch predictor handles it trivially. +- **Borrowed reads via `Float64Array::at()` vs current temp array reads via `Float64Array::at()`**: identical per-element cost. Zero CPU regression for reads. + +### Memory impact analysis + +For expression `a + b + c + d + e + f` with float64 inputs and float64 output (M elements each): + +| Metric | Current | New | +|--------|---------|-----| +| Input copies | 6 arrays (6 * M * 8 bytes) | 0 (all borrowed) | +| Peak intermediate arrays | 5 (all alive simultaneously) | 1 (RAII frees consumed) | +| Final result temp | 1 array (M * 8 bytes) | 0 (OutputDirect writes to output) | +| **Total temp memory** | **12 * M * 8 bytes** | **1 * M * 8 bytes** (one intermediate) | + +For the same expression with non-float64 inputs (e.g., int32): + +| Metric | Current | New | +|--------|---------|-----| +| Input copies | 6 arrays | 6 arrays (conversion required) | +| Peak intermediate arrays | 5 | 1 | +| Final result temp | 1 | 0 (if output is float64) or 1 (if not) | +| **Total temp memory** | **12 * M * 8 bytes** | **7 * M * 8 bytes** (6 conversions + 1 intermediate) | + +### Files modified + +- `ArrayCalculator.hpp` — add `CalcBuffer` class, update `RpnItem`, remove `CalcValue`, remove `m_TempDataStructure`/`m_IsPreflight`/`createScalarInTemp`/`copyArrayToTemp` from parser, add `m_TempDataStructure` to evaluator or create locally +- `ArrayCalculator.cpp` — rewrite `parse()` to be data-free, rewrite `evaluateInto()` with CalcBuffer stack + RAII + OutputDirect, remove `CopyToFloat64Functor` (moved into `CalcBuffer::convertFrom`), update `CopyResultFunctor` for the non-float64 output path +- `ArrayCalculatorFilter.cpp` — no changes expected (parser/evaluator API stays the same) +- `ArrayCalculatorTest.cpp` — no changes expected (tests exercise the filter, not internal classes) + +### What is NOT changing + +- `OperatorDef` struct and operator registry — unchanged +- `Token` struct and `tokenize()` — unchanged +- `CalculatorErrorCode` / `CalculatorWarningCode` enums — unchanged +- `ArrayCalculatorInputValues` struct — unchanged +- `ArrayCalculator` algorithm class public API — unchanged +- `ArrayCalculatorParser::parseAndValidate()` signature — unchanged +- `ArrayCalculatorParser::evaluateInto()` signature — unchanged +- Filter parameter keys, UUID, pipeline JSON format — unchanged diff --git a/src/Plugins/SimplnxCore/CMakeLists.txt b/src/Plugins/SimplnxCore/CMakeLists.txt index 8175d378fe..a19eff9ce3 100644 --- a/src/Plugins/SimplnxCore/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/CMakeLists.txt @@ -371,71 +371,6 @@ set(PLUGIN_EXTRA_SOURCES "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/nanoflann.hpp" "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/StlUtilities.hpp" "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/StlUtilities.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CalculatorItem.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CalculatorItem.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ICalculatorArray.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ICalculatorArray.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CalculatorArray.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CalculatorOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CalculatorOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/UnaryOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/UnaryOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/BinaryOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/BinaryOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ABSOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ABSOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/PowOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/PowOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ExpOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ExpOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/SinOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/SinOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CosOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CosOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/TanOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/TanOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ASinOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ASinOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ACosOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ACosOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ATanOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/ATanOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/SqrtOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/SqrtOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/RootOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/RootOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/LogOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/LogOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/Log10Operator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/Log10Operator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/LnOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/LnOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/AdditionOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/AdditionOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/SubtractionOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/SubtractionOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/NegativeOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/NegativeOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/MultiplicationOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/MultiplicationOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/DivisionOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/DivisionOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/LeftParenthesisItem.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/LeftParenthesisItem.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/RightParenthesisItem.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/RightParenthesisItem.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CalculatorSeparator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CalculatorSeparator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CommaSeparator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CommaSeparator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/FloorOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/FloorOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/MinOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/MinOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/MaxOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/MaxOperator.cpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CeilOperator.hpp" - "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/CeilOperator.cpp" "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/AvizoWriter.hpp" "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/AvizoWriter.cpp" "${${PLUGIN_NAME}_SOURCE_DIR}/src/${PLUGIN_NAME}/utils/PythonPluginTemplateFile.hpp" diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp index 621911e38c..31f52bccdd 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp @@ -1,741 +1,2109 @@ #include "ArrayCalculator.hpp" -#include "SimplnxCore/utils/ABSOperator.hpp" -#include "SimplnxCore/utils/ACosOperator.hpp" -#include "SimplnxCore/utils/ASinOperator.hpp" -#include "SimplnxCore/utils/ATanOperator.hpp" -#include "SimplnxCore/utils/AdditionOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" -#include "SimplnxCore/utils/CalculatorOperator.hpp" -#include "SimplnxCore/utils/CeilOperator.hpp" -#include "SimplnxCore/utils/CommaSeparator.hpp" -#include "SimplnxCore/utils/CosOperator.hpp" -#include "SimplnxCore/utils/DivisionOperator.hpp" -#include "SimplnxCore/utils/ExpOperator.hpp" -#include "SimplnxCore/utils/FloorOperator.hpp" -#include "SimplnxCore/utils/LeftParenthesisItem.hpp" -#include "SimplnxCore/utils/LnOperator.hpp" -#include "SimplnxCore/utils/Log10Operator.hpp" -#include "SimplnxCore/utils/LogOperator.hpp" -#include "SimplnxCore/utils/MaxOperator.hpp" -#include "SimplnxCore/utils/MinOperator.hpp" -#include "SimplnxCore/utils/MultiplicationOperator.hpp" -#include "SimplnxCore/utils/NegativeOperator.hpp" -#include "SimplnxCore/utils/PowOperator.hpp" -#include "SimplnxCore/utils/RightParenthesisItem.hpp" -#include "SimplnxCore/utils/RootOperator.hpp" -#include "SimplnxCore/utils/SinOperator.hpp" -#include "SimplnxCore/utils/SqrtOperator.hpp" -#include "SimplnxCore/utils/SubtractionOperator.hpp" -#include "SimplnxCore/utils/TanOperator.hpp" - +#include "simplnx/Common/Result.hpp" #include "simplnx/Common/TypesUtility.hpp" #include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/DataStore.hpp" #include "simplnx/Utilities/DataGroupUtilities.hpp" #include "simplnx/Utilities/FilterUtilities.hpp" -#include "simplnx/Utilities/StringUtilities.hpp" -#include +#include +#include +#include +#include +#include using namespace nx::core; +// =========================================================================== +// Anonymous namespace: ParsedItem, helper functions, functors +// =========================================================================== namespace { -struct CreateCalculatorArrayFunctor + +// --------------------------------------------------------------------------- +// Intermediate representation used between parsing and shunting-yard. +// Includes parentheses and commas that the final RpnItem list does not. +// --------------------------------------------------------------------------- +struct ParsedItem { - template - CalculatorItem::Pointer operator()(DataStructure& dataStructure, bool allocate, const IDataArray* iDataArrayPtr) + enum class Kind { - const auto* inputDataArray = dynamic_cast*>(iDataArrayPtr); - CalculatorItem::Pointer itemPtr = CalculatorArray::New(dataStructure, inputDataArray, ICalculatorArray::Array, allocate); - return itemPtr; - } + Scalar, + ArrayRef, + Operator, + LParen, + RParen, + Comma, + ComponentExtract, + TupleComponentExtract + } kind; + + // Scalar + float64 scalarValue = 0.0; + + // ArrayRef: metadata for validation (no data allocated) + DataPath arrayPath; + DataType sourceDataType = DataType::float64; + std::vector arrayTupleShape; + std::vector arrayCompShape; + + // Operator + const OperatorDef* op = nullptr; + bool isNegativePrefix = false; + + // ComponentExtract / TupleComponentExtract + usize componentIndex = std::numeric_limits::max(); + usize tupleIndex = std::numeric_limits::max(); }; -struct CopyArrayFunctor +// --------------------------------------------------------------------------- +// The static unary negative OperatorDef (not in the registry) +// --------------------------------------------------------------------------- +const OperatorDef& getUnaryNegativeOp() { - template - void operator()(DataStructure& dataStructure, const DataPath& calculatedArrayPath, const Float64Array* inputArray) + static const OperatorDef s_UnaryNeg = {"neg", OperatorDef::UnaryPrefix, 4, 1, OperatorDef::Right, OperatorDef::None, [](double x) { return -x; }, nullptr}; + return s_UnaryNeg; +} + +// --------------------------------------------------------------------------- +// Look up an operator/function in the registry by exact token match +// --------------------------------------------------------------------------- +const OperatorDef* findOperatorByToken(const std::string& token) +{ + const auto& registry = getOperatorRegistry(); + for(const auto& opDef : registry) { - const auto& inputDataStore = inputArray->getDataStoreRef(); - if(nullptr != inputArray) + if(opDef.token == token) { - auto& convertedDataStore = dataStructure.getDataAsUnsafe>(calculatedArrayPath)->getDataStoreRef(); + return &opDef; + } + } + return nullptr; +} + +// --------------------------------------------------------------------------- +// Map single-character TokenType to the operator registry token string +// --------------------------------------------------------------------------- +const OperatorDef* operatorDefForSymbolToken(TokenType type) +{ + switch(type) + { + case TokenType::Plus: + return findOperatorByToken("+"); + case TokenType::Minus: + return findOperatorByToken("-"); + case TokenType::Star: + return findOperatorByToken("*"); + case TokenType::Slash: + return findOperatorByToken("/"); + case TokenType::Caret: + return findOperatorByToken("^"); + case TokenType::Percent: + return findOperatorByToken("%"); + default: + return nullptr; + } +} - const usize count = inputDataStore.getSize(); - for(usize i = 0; i < count; i++) +// --------------------------------------------------------------------------- +// Search the entire DataStructure for IDataArrays with a given name. +// Returns all DataPaths where a matching array is found. +// --------------------------------------------------------------------------- +std::vector findArraysByName(const DataStructure& ds, const std::string& name) +{ + std::vector results; + + // Search recursively from the root + auto allPaths = GetAllChildDataPathsRecursive(ds, DataPath{}, DataObject::Type::DataArray); + if(allPaths.has_value()) + { + for(const auto& path : allPaths.value()) + { + if(path.getTargetName() == name) { - if constexpr(std::is_same_v) - { - convertedDataStore[i] = inputDataStore[i]; - } - else - { - convertedDataStore[i] = static_cast(inputDataStore[i]); - } + results.push_back(path); } } } -}; -struct InitializeArrayFunctor + return results; +} + +// --------------------------------------------------------------------------- +// Functor to copy an IDataArray of any numeric type into a Float64Array +// --------------------------------------------------------------------------- +struct CopyToFloat64Functor { template - void operator()(DataStructure& dataStructure, const DataPath& calculatedArrayPath, const Float64Array* inputArray) + void operator()(const IDataArray& sourceArray, Float64Array& destArray) { - auto& convertedDataStore = dataStructure.getDataAsUnsafe>(calculatedArrayPath)->getDataStoreRef(); - if(nullptr != inputArray && inputArray->getSize() == 1) + const auto& typedSource = dynamic_cast&>(sourceArray); + const usize totalElements = typedSource.getSize(); + for(usize i = 0; i < totalElements; i++) { - if constexpr(std::is_same_v) - { - convertedDataStore.fill(inputArray->at(0)); - } - else - { - convertedDataStore.fill(static_cast(inputArray->at(0))); - } + destArray[i] = static_cast(typedSource.at(i)); } } }; -void WrapFunctionArguments(std::vector& tokens) +// --------------------------------------------------------------------------- +// Check whether the previous ParsedItem is a binary operator +// --------------------------------------------------------------------------- +bool isBinaryOp(const ParsedItem& item) { - std::vector out; - out.reserve(tokens.size() * 2); + return item.kind == ParsedItem::Kind::Operator && item.op != nullptr && item.op->kind == OperatorDef::BinaryInfix && !item.isNegativePrefix; +} - for(size_t i = 0; i < tokens.size(); ++i) +// --------------------------------------------------------------------------- +// WrapFunctionArguments: for each function call in the parsed item list, +// wrap each comma-separated argument in extra parentheses so that the +// shunting-yard algorithm processes them correctly. +// +// Example: sin(a + b) stays as sin((a + b)) +// log(a, b) becomes log((a), (b)) +// --------------------------------------------------------------------------- +void wrapFunctionArguments(std::vector& items) +{ + std::vector out; + out.reserve(items.size() * 2); + + for(size_t i = 0; i < items.size(); ++i) { - const auto& tok = tokens[i]; - // Function call start: an Identifier followed by '(' - if(dynamic_cast(tok.get()) != nullptr && i + 1 < tokens.size() && dynamic_cast(tokens[i + 1].get()) != nullptr) + const auto& item = items[i]; + + // Detect: Function operator followed by LParen + if(item.kind == ParsedItem::Kind::Operator && item.op != nullptr && item.op->kind == OperatorDef::Function && i + 1 < items.size() && items[i + 1].kind == ParsedItem::Kind::LParen) { - // Copy function name and '(' - out.push_back(tok); - out.push_back(tokens[++i]); + // Copy function and '(' + out.push_back(item); + out.push_back(items[++i]); int depth = 1; size_t argStart = out.size(); // Process until matching ')' - for(++i; i < tokens.size() && depth > 0; ++i) + for(++i; i < items.size() && depth > 0; ++i) { - auto cur = tokens[i]; - if(dynamic_cast(cur.get()) != nullptr) + const auto& cur = items[i]; + if(cur.kind == ParsedItem::Kind::LParen) { ++depth; out.push_back(cur); } - else if(dynamic_cast(cur.get()) != nullptr) + else if(cur.kind == ParsedItem::Kind::RParen) { --depth; if(depth == 0) { - // Close last argument - out.insert(out.begin() + argStart, LeftParenthesisItem::New()); - out.push_back(RightParenthesisItem::New()); + // Close last argument with wrapping parens + ParsedItem lp; + lp.kind = ParsedItem::Kind::LParen; + out.insert(out.begin() + static_cast(argStart), lp); + + ParsedItem rp; + rp.kind = ParsedItem::Kind::RParen; + out.push_back(rp); break; } out.push_back(cur); } - else if(dynamic_cast(cur.get()) != nullptr && depth == 1) + else if(cur.kind == ParsedItem::Kind::Comma && depth == 1) { - // Wrap end of this argument, copy comma, start next - out.push_back(RightParenthesisItem::New()); + // End this argument, copy comma, start next argument + ParsedItem rp; + rp.kind = ParsedItem::Kind::RParen; + out.push_back(rp); + out.push_back(cur); - out.push_back(LeftParenthesisItem::New()); + + ParsedItem lp; + lp.kind = ParsedItem::Kind::LParen; + out.push_back(lp); } else { out.push_back(cur); } } - --i; // we consumed the ')' + --i; // we consumed the ')' in the inner loop } else { - out.push_back(tok); + out.push_back(item); } } - tokens.swap(out); -} -} // namespace - -// ----------------------------------------------------------------------------- -ArrayCalculator::ArrayCalculator(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ArrayCalculatorInputValues* inputValues) -: m_DataStructure(dataStructure) -, m_InputValues(inputValues) -, m_ShouldCancel(shouldCancel) -, m_MessageHandler(mesgHandler) -{ -} - -// ----------------------------------------------------------------------------- -ArrayCalculator::~ArrayCalculator() noexcept = default; - -// ----------------------------------------------------------------------------- -const std::atomic_bool& ArrayCalculator::getCancel() -{ - return m_ShouldCancel; + items.swap(out); } -// ----------------------------------------------------------------------------- -Result<> ArrayCalculator::operator()() +// --------------------------------------------------------------------------- +// Functor to copy a Float64Array result into the output DataArray of any +// numeric type, performing static_cast on each element. +// --------------------------------------------------------------------------- +struct CopyResultFunctor { - Result<> results; - - // Parse the infix expression from the user interface - ArrayCalculatorParser parser(m_DataStructure, m_InputValues->SelectedGroup, m_InputValues->InfixEquation, false); - std::vector parsedInfix; - Result<> parsedEquationResults = parser.parseInfixEquation(parsedInfix); - results.warnings() = parsedEquationResults.warnings(); - if(parsedEquationResults.invalid()) - { - results.errors() = parsedEquationResults.errors(); - return results; - } - if(parsedInfix.empty()) - { - results.errors().push_back(Error{-6550, "Error while parsing infix expression."}); - return results; - } - - // Convert the parsed infix expression into RPN - Result rpnResults = ArrayCalculatorParser::ToRPN(m_InputValues->InfixEquation, parsedInfix); - std::vector rpn = rpnResults.value(); - if(rpnResults.invalid() || rpn.empty()) - { - results.errors().push_back(Error{-6551, "Error while converting parsed infix expression to postfix notation"}); - return results; - } - - // Execute the RPN expression - DataPath temporaryCalculatedArrayPath = DataPath({m_InputValues->CalculatedArray.getTargetName() + "_TEMPORARY"}); - std::stack executionStack; - int totalItems = rpn.size(); - for(int rpnCount = 0; rpnCount < totalItems; rpnCount++) + // Full array copy (non-float64 output) + template + void operator()(DataStructure& ds, const DataPath& outputPath, const Float64Array* resultArray, bool /*unused*/) { - m_MessageHandler({IFilter::Message::Type::Info, "Computing Operator " + StringUtilities::number(rpnCount + 1) + "/" + StringUtilities::number(totalItems)}); - - CalculatorItem::Pointer rpnItem = rpn[rpnCount]; - ICalculatorArray::Pointer calcArray = std::dynamic_pointer_cast(rpnItem); - if(nullptr != calcArray) - { - // This is an array - executionStack.push(calcArray); - } - else - { - // This is an operator - CalculatorOperator::Pointer rpnOperator = std::dynamic_pointer_cast(rpnItem); - - rpnOperator->calculate(parser.m_TemporaryDataStructure, m_InputValues->Units, temporaryCalculatedArrayPath, executionStack); - } - - if(getCancel()) + auto& output = ds.getDataRefAs>(outputPath).getDataStoreRef(); + for(usize i = 0; i < output.getSize(); i++) { - return results; + output[i] = static_cast(resultArray->at(i)); } } - // Grab the result from the stack - ICalculatorArray::Pointer arrayItem = ICalculatorArray::NullPointer(); - if(executionStack.size() != 1) - { - results.errors().push_back(Error{static_cast(CalculatorItem::ErrorCode::InvalidEquation), "The chosen infix equation is not a valid equation."}); - return results; - } - if(!executionStack.empty()) + // Scalar fill + template + void operator()(DataStructure& ds, const DataPath& outputPath, float64 scalarValue) { - arrayItem = executionStack.top(); - executionStack.pop(); + auto& output = ds.getDataRefAs>(outputPath); + output.fill(static_cast(scalarValue)); } +}; - if(arrayItem != ICalculatorArray::NullPointer()) - { - const Float64Array* resultArray = arrayItem->getArray(); - if(arrayItem->isNumber() && m_DataStructure.getDataAs(m_InputValues->CalculatedArray.getParent()) != nullptr) - { - ExecuteDataFunction(InitializeArrayFunctor{}, ConvertNumericTypeToDataType(m_InputValues->ScalarType), m_DataStructure, m_InputValues->CalculatedArray, resultArray); - } - else - { - ExecuteDataFunction(CopyArrayFunctor{}, ConvertNumericTypeToDataType(m_InputValues->ScalarType), m_DataStructure, m_InputValues->CalculatedArray, resultArray); - } - } - else - { - results.errors().push_back(Error{static_cast(CalculatorItem::ErrorCode::UnexpectedOutput), "Unexpected output item from chosen infix expression; the output item must be an array\n" - "Please contact the DREAM3D-NX developers for more information"}); - return results; - } +} // anonymous namespace - return {}; -} +// =========================================================================== +// CalcBuffer implementation +// =========================================================================== -ArrayCalculatorParser::ArrayCalculatorParser(const DataStructure& dataStruct, const DataPath& selectedGroupPath, const std::string& infixEquation, bool isPreflight) -: m_DataStructure(dataStruct) -, m_SelectedGroupPath(selectedGroupPath) -, m_InfixEquation(infixEquation) -, m_IsPreflight(isPreflight) +CalcBuffer::CalcBuffer(CalcBuffer&& other) noexcept +: m_Storage(other.m_Storage) +, m_BorrowedArray(other.m_BorrowedArray) +, m_TempDS(other.m_TempDS) +, m_ArrayId(other.m_ArrayId) +, m_OwnedArray(other.m_OwnedArray) +, m_OutputArray(other.m_OutputArray) +, m_IsScalar(other.m_IsScalar) { - createSymbolMap(); + other.m_TempDS = nullptr; + other.m_BorrowedArray = nullptr; + other.m_OwnedArray = nullptr; + other.m_OutputArray = nullptr; } -// ----------------------------------------------------------------------------- -Result<> ArrayCalculatorParser::parseInfixEquation(ParsedEquation& parsedInfix) +CalcBuffer& CalcBuffer::operator=(CalcBuffer&& other) noexcept { - parsedInfix.clear(); - Result<> results = {}; - std::vector itemList = getRegularExpressionMatches(); - - // Iterate through the QStringList and create the proper CalculatorItems - for(int i = 0; i < itemList.size(); i++) + if(this != &other) { - std::string strItem = itemList[i]; - CalculatorItem::Pointer itemPtr = nullptr; - - bool ok = true; - double num; - try - { - num = std::stod(strItem); - } catch(std::exception& e) + // Clean up current state + if(m_Storage == Storage::Owned && m_TempDS != nullptr) { - ok = false; + m_TempDS->removeData(m_ArrayId); } - if(ok) - { - // This is a numeric value - auto parsedNumericResults = parseNumericValue(strItem, parsedInfix, num); - results = MergeResults(results, parsedNumericResults); - if(results.invalid()) - { - parsedInfix.clear(); - return results; - } - } - else if(strItem == "-") - { - // This is a minus sign - auto parsedMinusResults = parseMinusSign(strItem, parsedInfix, i); - results = MergeResults(results, parsedMinusResults); - if(results.invalid()) - { - parsedInfix.clear(); - return results; - } - } - else if(StringUtilities::contains(strItem, "[") && StringUtilities::contains(strItem, "]")) - { - // This is an index operator - auto parsedIndexResults = parseIndexOperator(strItem, parsedInfix); - results = MergeResults(results, parsedIndexResults); - if(results.invalid()) - { - parsedInfix.clear(); - return results; - } - } - else - { - if(m_SymbolMap.find(strItem) != m_SymbolMap.end()) - { - itemPtr = m_SymbolMap[strItem]; - } - if(nullptr != itemPtr) - { - // This is another type of operator - std::string ss = fmt::format("Item '{}' in the infix expression is the name of an array in the selected Attribute Matrix, but it is currently being used as a mathematical operator", strItem); - auto checkNameResults = checkForAmbiguousArrayName(strItem, ss); - results = MergeResults(results, checkNameResults); - if(results.invalid()) - { - parsedInfix.clear(); - return results; - } - - parsedInfix.push_back(itemPtr); - } - // It doesn't matter which path we use for the selected attribute matrix since we are only checking the target names - else if(ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, strItem) || (!strItem.empty() && strItem[0] == '\"' && strItem[strItem.size() - 1] == '\"')) - { - auto parsedArrayResults = parseArray(strItem, parsedInfix); - results = MergeResults(results, parsedArrayResults); - if(results.invalid()) - { - parsedInfix.clear(); - return results; - } - } - else - { - parsedInfix.clear(); - std::string ss = fmt::format("An unrecognized or invalid item '{}' was found in the chosen infix expression", strItem); - return MakeErrorResult(static_cast(CalculatorItem::ErrorCode::UnrecognizedItem), ss); - } - } + m_Storage = other.m_Storage; + m_BorrowedArray = other.m_BorrowedArray; + m_TempDS = other.m_TempDS; + m_ArrayId = other.m_ArrayId; + m_OwnedArray = other.m_OwnedArray; + m_OutputArray = other.m_OutputArray; + m_IsScalar = other.m_IsScalar; + + other.m_TempDS = nullptr; + other.m_BorrowedArray = nullptr; + other.m_OwnedArray = nullptr; + other.m_OutputArray = nullptr; } - - ::WrapFunctionArguments(parsedInfix); - - // Return the parsed infix expression as a vector of CalculatorItems - return results; + return *this; } -// ----------------------------------------------------------------------------- -std::vector ArrayCalculatorParser::getRegularExpressionMatches() +CalcBuffer::~CalcBuffer() { - // Parse all the items into a QVector of strings using a regular expression - std::vector itemList; - // Match all array names that start with two alphabetical characters and have spaces. Match all numbers, decimal or integer. - // Match one letter array names. Match all special character operators. - std::regex regExp(R"lit(("((\[)?\d+(\.\d+)?(\])?|(\[)?\.\d+(\])?|\w{1,1}((\w|\s|\d)*(\w|\d){1,1})?|\S)")|(((\[)?\d+(\.\d+)?(\])?|(\[)?\.\d+(\])?|\w{1,1}((\w|\s|\d)*(\w|\d){1,1})?|\S)))lit"); - - auto regExpMatchBegin = std::sregex_iterator(m_InfixEquation.begin(), m_InfixEquation.end(), regExp); - auto regExpMatchEnd = std::sregex_iterator(); - for(std::sregex_iterator i = regExpMatchBegin; i != regExpMatchEnd; ++i) + if(m_Storage == Storage::Owned && m_TempDS != nullptr) { - const std::smatch& match = *i; - itemList.push_back(match.str()); + m_TempDS->removeData(m_ArrayId); } - - return itemList; } -// ----------------------------------------------------------------------------- -Result<> ArrayCalculatorParser::parseNumericValue(std::string token, std::vector& parsedInfix, double number) +CalcBuffer CalcBuffer::borrow(const Float64Array& source) { - // This is a number, so create an array with numOfTuples equal to 1 and set the value into it - Float64Array* ptr = Float64Array::CreateWithStore(m_TemporaryDataStructure, "INTERNAL_USE_ONLY_NumberArray" + StringUtilities::number(m_TemporaryDataStructure.getSize()), - std::vector{1}, std::vector{1}); - (*ptr)[0] = number; - CalculatorItem::Pointer itemPtr = CalculatorArray::New(m_TemporaryDataStructure, ptr, ICalculatorArray::Number, !m_IsPreflight); - parsedInfix.push_back(itemPtr); - - std::string ss = fmt::format("Item '{}' in the infix expression is the name of an array in the selected Attribute Matrix, but it is currently being used as a number", token); - return checkForAmbiguousArrayName(token, ss); + CalcBuffer buf; + buf.m_Storage = Storage::Borrowed; + buf.m_BorrowedArray = &source; + buf.m_IsScalar = false; + return buf; } -// ----------------------------------------------------------------------------- -Result<> ArrayCalculatorParser::parseMinusSign(std::string token, std::vector& parsedInfix, int loopIdx) +CalcBuffer CalcBuffer::convertFrom(DataStructure& tempDS, const IDataArray& source, const std::string& name) { - CalculatorItem::Pointer itemPtr = nullptr; + std::vector tupleShape = source.getTupleShape(); + std::vector compShape = source.getComponentShape(); + Float64Array* destArr = Float64Array::CreateWithStore(tempDS, name, tupleShape, compShape); + + ExecuteDataFunction(CopyToFloat64Functor{}, source.getDataType(), source, *destArr); + + CalcBuffer buf; + buf.m_Storage = Storage::Owned; + buf.m_TempDS = &tempDS; + buf.m_ArrayId = destArr->getId(); + buf.m_OwnedArray = destArr; + buf.m_IsScalar = false; + return buf; +} - // This could be either a negative sign or subtraction sign, so we need to figure out which one it is - if(loopIdx == 0 || (((nullptr != std::dynamic_pointer_cast(parsedInfix.back()) && - std::dynamic_pointer_cast(parsedInfix.back())->getOperatorType() == CalculatorOperator::Binary) || - nullptr != std::dynamic_pointer_cast(parsedInfix.back()) || nullptr != std::dynamic_pointer_cast(parsedInfix.back())) && - nullptr == std::dynamic_pointer_cast(parsedInfix.back()))) - { - // By context, this is a negative sign - itemPtr = NegativeOperator::New(); - parsedInfix.push_back(itemPtr); - } - else - { - // By context, this is a subtraction sign - if(m_SymbolMap.find(token) != m_SymbolMap.end()) - { - itemPtr = m_SymbolMap[token]; - } - parsedInfix.push_back(itemPtr); - } +CalcBuffer CalcBuffer::scalar(DataStructure& tempDS, float64 value, const std::string& name) +{ + Float64Array* arr = Float64Array::CreateWithStore(tempDS, name, std::vector{1}, std::vector{1}); + (*arr)[0] = value; + + CalcBuffer buf; + buf.m_Storage = Storage::Owned; + buf.m_TempDS = &tempDS; + buf.m_ArrayId = arr->getId(); + buf.m_OwnedArray = arr; + buf.m_IsScalar = true; + return buf; +} - std::string ss = fmt::format("Item '{}' in the infix expression is the name of an array in the selected attribute matrix, but it is currently being used as a mathematical operator", token); - return checkForAmbiguousArrayName(token, ss); +CalcBuffer CalcBuffer::allocate(DataStructure& tempDS, const std::string& name, std::vector tupleShape, std::vector compShape) +{ + Float64Array* arr = Float64Array::CreateWithStore(tempDS, name, tupleShape, compShape); + + CalcBuffer buf; + buf.m_Storage = Storage::Owned; + buf.m_TempDS = &tempDS; + buf.m_ArrayId = arr->getId(); + buf.m_OwnedArray = arr; + buf.m_IsScalar = false; + return buf; } -// ----------------------------------------------------------------------------- -Result<> ArrayCalculatorParser::parseIndexOperator(std::string token, std::vector& parsedInfix) +CalcBuffer CalcBuffer::wrapOutput(DataArray& outputArray) { - int idx = parsedInfix.size() - 1; + CalcBuffer buf; + buf.m_Storage = Storage::OutputDirect; + buf.m_OutputArray = &outputArray; + buf.m_IsScalar = false; + return buf; +} - std::string errorMsg = fmt::format("Index operator '{}' is not paired with a valid array name.", token); - int errCode = static_cast(CalculatorItem::ErrorCode::OrphanedComponent); - if(idx < 0) - { - return MakeErrorResult(errCode, errorMsg); - } - if(!parsedInfix[idx]->isICalculatorArray()) +float64 CalcBuffer::read(usize index) const +{ + switch(m_Storage) { - return MakeErrorResult(errCode, errorMsg); + case Storage::Borrowed: + return m_BorrowedArray->at(index); + case Storage::Owned: + return m_OwnedArray->at(index); + case Storage::OutputDirect: + return m_OutputArray->at(index); } - if(parsedInfix[idx]->isNumber()) + return 0.0; +} + +void CalcBuffer::write(usize index, float64 value) +{ + switch(m_Storage) { - return MakeErrorResult(errCode, errorMsg); + case Storage::Owned: + (*m_OwnedArray)[index] = value; + return; + case Storage::OutputDirect: + (*m_OutputArray)[index] = value; + return; + case Storage::Borrowed: + throw std::runtime_error("CalcBuffer::write() called on a read-only Borrowed buffer"); } +} - token = StringUtilities::replace(token, "[", ""); - token = StringUtilities::replace(token, "]", ""); - - int index = -1; - try - { - index = std::stoi(token); - } catch(std::exception& e) +void CalcBuffer::fill(float64 value) +{ + switch(m_Storage) { - std::string ss = "The chosen infix expression is not a valid expression"; - return MakeErrorResult(static_cast(CalculatorItem::ErrorCode::InvalidComponent), ss); + case Storage::Owned: + m_OwnedArray->fill(value); + return; + case Storage::OutputDirect: + m_OutputArray->fill(value); + return; + case Storage::Borrowed: + throw std::runtime_error("CalcBuffer::fill() called on a read-only Borrowed buffer"); } +} - ICalculatorArray::Pointer calcArray = std::dynamic_pointer_cast(parsedInfix.back()); - if(nullptr != calcArray && index >= calcArray->getArray()->getNumberOfComponents()) +usize CalcBuffer::size() const +{ + switch(m_Storage) { - std::string ss = fmt::format("'{}' has an component index that is out of range", calcArray->getArray()->getName()); - return MakeErrorResult(static_cast(CalculatorItem::ErrorCode::ComponentOutOfRange), ss); + case Storage::Borrowed: + return m_BorrowedArray->getSize(); + case Storage::Owned: + return m_OwnedArray->getSize(); + case Storage::OutputDirect: + return m_OutputArray->getSize(); } - - parsedInfix.pop_back(); - - Float64Array* reducedArray = calcArray->reduceToOneComponent(index, !m_IsPreflight); - CalculatorItem::Pointer itemPtr = CalculatorArray::New(m_TemporaryDataStructure, reducedArray, ICalculatorArray::Array, !m_IsPreflight); - parsedInfix.push_back(itemPtr); - - std::string ss = fmt::format("Item '{}' in the infix expression is the name of an array in the selected Attribute Matrix, but it is currently being used as an indexing operator", token); - return checkForAmbiguousArrayName(token, ss); + return 0; } -// ----------------------------------------------------------------------------- -Result<> ArrayCalculatorParser::parseArray(std::string token, std::vector& parsedInfix) +usize CalcBuffer::numTuples() const { - int firstArray_NumTuples = -1; - std::string firstArray_Name = ""; - - token = StringUtilities::replace(token, "\"", ""); - if(!ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, token)) + switch(m_Storage) { - std::string ss = fmt::format("The item '{}' is not the name of any valid array in the selected Attribute Matrix", token); - return MakeErrorResult(static_cast(CalculatorItem::ErrorCode::InvalidArrayName), ss); + case Storage::Borrowed: + return m_BorrowedArray->getNumberOfTuples(); + case Storage::Owned: + return m_OwnedArray->getNumberOfTuples(); + case Storage::OutputDirect: + return m_OutputArray->getNumberOfTuples(); } + return 0; +} - DataPath tokenArrayPath = m_SelectedGroupPath.empty() ? DataPath({token}) : m_SelectedGroupPath.createChildPath(token); - const auto* dataArray = m_DataStructure.getDataAs(tokenArrayPath); - if(firstArray_NumTuples < 0 && firstArray_Name.empty()) +usize CalcBuffer::numComponents() const +{ + switch(m_Storage) { - firstArray_NumTuples = dataArray->getNumberOfTuples(); - firstArray_Name = dataArray->getName(); + case Storage::Borrowed: + return m_BorrowedArray->getNumberOfComponents(); + case Storage::Owned: + return m_OwnedArray->getNumberOfComponents(); + case Storage::OutputDirect: + return m_OutputArray->getNumberOfComponents(); } - else if(dataArray->getNumberOfTuples() != firstArray_NumTuples) + return 0; +} + +std::vector CalcBuffer::tupleShape() const +{ + switch(m_Storage) { - std::string ss = fmt::format("Arrays '{}' and '{}' in the infix expression have an inconsistent number of tuples", firstArray_Name, dataArray->getName()); - return MakeErrorResult(static_cast(CalculatorItem::ErrorCode::InconsistentTuples), ss); + case Storage::Borrowed: + return m_BorrowedArray->getTupleShape(); + case Storage::Owned: + return m_OwnedArray->getTupleShape(); + case Storage::OutputDirect: + return m_OutputArray->getTupleShape(); } - - CalculatorItem::Pointer itemPtr = ExecuteDataFunction(CreateCalculatorArrayFunctor{}, dataArray->getDataType(), m_TemporaryDataStructure, !m_IsPreflight, dataArray); - parsedInfix.push_back(itemPtr); return {}; } -// ----------------------------------------------------------------------------- -Result<> ArrayCalculatorParser::checkForAmbiguousArrayName(const std::string& strItem, std::string warningMsg) +std::vector CalcBuffer::compShape() const { - if(m_IsPreflight && ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, strItem)) + switch(m_Storage) { - warningMsg.append("\nTo treat this item as an array name, please add double quotes around the item (i.e. \"" + strItem + "\")."); - return MakeWarningVoidResult(static_cast(CalculatorItem::WarningCode::AmbiguousNameWarning), warningMsg); + case Storage::Borrowed: + return m_BorrowedArray->getComponentShape(); + case Storage::Owned: + return m_OwnedArray->getComponentShape(); + case Storage::OutputDirect: + return m_OutputArray->getComponentShape(); } return {}; } -// ----------------------------------------------------------------------------- -void ArrayCalculatorParser::createSymbolMap() +bool CalcBuffer::isScalar() const { - // Insert all items into the symbol map to use during expression parsing - { - LeftParenthesisItem::Pointer symbol = LeftParenthesisItem::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - RightParenthesisItem::Pointer symbol = RightParenthesisItem::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - CommaSeparator::Pointer symbol = CommaSeparator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - AdditionOperator::Pointer symbol = AdditionOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - SubtractionOperator::Pointer symbol = SubtractionOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - MultiplicationOperator::Pointer symbol = MultiplicationOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - DivisionOperator::Pointer symbol = DivisionOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - PowOperator::Pointer symbol = PowOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - ABSOperator::Pointer symbol = ABSOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - SinOperator::Pointer symbol = SinOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - CosOperator::Pointer symbol = CosOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - TanOperator::Pointer symbol = TanOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - ASinOperator::Pointer symbol = ASinOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - ACosOperator::Pointer symbol = ACosOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - ATanOperator::Pointer symbol = ATanOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - SqrtOperator::Pointer symbol = SqrtOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - RootOperator::Pointer symbol = RootOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - Log10Operator::Pointer symbol = Log10Operator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - LogOperator::Pointer symbol = LogOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - ExpOperator::Pointer symbol = ExpOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - LnOperator::Pointer symbol = LnOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - FloorOperator::Pointer symbol = FloorOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - CeilOperator::Pointer symbol = CeilOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - MinOperator::Pointer symbol = MinOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } - { - MaxOperator::Pointer symbol = MaxOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; - } + return m_IsScalar; +} + +bool CalcBuffer::isOwned() const +{ + return m_Storage == Storage::Owned; +} + +bool CalcBuffer::isOutputDirect() const +{ + return m_Storage == Storage::OutputDirect; +} + +void CalcBuffer::markAsScalar() +{ + m_IsScalar = true; +} + +const Float64Array& CalcBuffer::array() const +{ + switch(m_Storage) { - NegativeOperator::Pointer symbol = NegativeOperator::New(); - m_SymbolMap[symbol->getInfixToken()] = symbol; + case Storage::Borrowed: + return *m_BorrowedArray; + case Storage::Owned: + return *m_OwnedArray; + case Storage::OutputDirect: + return *m_OutputArray; } + throw std::runtime_error("CalcBuffer::array() called on buffer with unknown storage mode"); } -// ----------------------------------------------------------------------------- -Result ArrayCalculatorParser::ToRPN(const std::string& unparsedInfixExpression, std::vector infixEquation) +// --------------------------------------------------------------------------- +// getOperatorRegistry +// --------------------------------------------------------------------------- +const std::vector& nx::core::getOperatorRegistry() { - std::stack itemStack; - std::vector rpnEquation; + static const std::vector s_Registry = []() { + std::vector reg; + reg.reserve(23); + + // ---- Binary infix operators ---- + reg.push_back({"+", OperatorDef::BinaryInfix, 1, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double a, double b) { return a + b; }}); + reg.push_back({"-", OperatorDef::BinaryInfix, 1, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double a, double b) { return a - b; }}); + reg.push_back({"*", OperatorDef::BinaryInfix, 2, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double a, double b) { return a * b; }}); + reg.push_back({"/", OperatorDef::BinaryInfix, 2, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double a, double b) { return a / b; }}); + reg.push_back({"%", OperatorDef::BinaryInfix, 2, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double a, double b) { return std::fmod(a, b); }}); + reg.push_back({"^", OperatorDef::BinaryInfix, 3, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double a, double b) { return std::pow(a, b); }}); + + // ---- Unary functions (1-arg) ---- + reg.push_back({"abs", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::None, [](double x) { return std::abs(x); }, nullptr}); + reg.push_back({"sqrt", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::None, [](double x) { return std::sqrt(x); }, nullptr}); + reg.push_back({"ceil", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::None, [](double x) { return std::ceil(x); }, nullptr}); + reg.push_back({"floor", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::None, [](double x) { return std::floor(x); }, nullptr}); + reg.push_back({"exp", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::None, [](double x) { return std::exp(x); }, nullptr}); + reg.push_back({"ln", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::None, [](double x) { return std::log(x); }, nullptr}); + reg.push_back({"log10", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::None, [](double x) { return std::log10(x); }, nullptr}); + + // ---- Trig functions (1-arg, ForwardTrig) ---- + reg.push_back({"sin", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::ForwardTrig, [](double x) { return std::sin(x); }, nullptr}); + reg.push_back({"cos", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::ForwardTrig, [](double x) { return std::cos(x); }, nullptr}); + reg.push_back({"tan", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::ForwardTrig, [](double x) { return std::tan(x); }, nullptr}); + + // ---- Inverse trig functions (1-arg, InverseTrig) ---- + reg.push_back({"asin", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::InverseTrig, [](double x) { return std::asin(x); }, nullptr}); + reg.push_back({"acos", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::InverseTrig, [](double x) { return std::acos(x); }, nullptr}); + reg.push_back({"atan", OperatorDef::Function, 5, 1, OperatorDef::Left, OperatorDef::InverseTrig, [](double x) { return std::atan(x); }, nullptr}); + + // ---- Binary functions (2-arg) ---- + // NOTE: log (2-arg) must come AFTER log10 so that prefix matching during + // identifier resolution checks "log10" before "log". + reg.push_back({"log", OperatorDef::Function, 5, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double base, double val) { return std::log(val) / std::log(base); }}); + reg.push_back({"root", OperatorDef::Function, 5, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double val, double n) { return std::pow(val, 1.0 / n); }}); + reg.push_back({"min", OperatorDef::Function, 5, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double a, double b) { return std::min(a, b); }}); + reg.push_back({"max", OperatorDef::Function, 5, 2, OperatorDef::Left, OperatorDef::None, nullptr, [](double a, double b) { return std::max(a, b); }}); + + return reg; + }(); + + return s_Registry; +} - bool* oneComponent = nullptr; +// --------------------------------------------------------------------------- +// ArrayCalculatorParser +// --------------------------------------------------------------------------- +ArrayCalculatorParser::ArrayCalculatorParser(const DataStructure& dataStructure, const DataPath& selectedGroupPath, const std::string& infixEquation, const std::atomic_bool& shouldCancel) +: m_DataStructure(dataStructure) +, m_SelectedGroupPath(selectedGroupPath) +, m_InfixEquation(infixEquation) +, m_ShouldCancel(shouldCancel) +{ +} - // Iterate through the infix expression items - for(int i = 0; i < infixEquation.size(); i++) - { - CalculatorItem::Pointer calcItem = infixEquation[i]; - if(nullptr != std::dynamic_pointer_cast(calcItem)) - { - ICalculatorArray::Pointer arrayItem = std::dynamic_pointer_cast(calcItem); +// --------------------------------------------------------------------------- +std::vector ArrayCalculatorParser::tokenize(const std::string& equation) +{ + std::vector tokens; + const size_t len = equation.size(); + size_t i = 0; - // This is a number or array, so push it onto the rpn expression output - rpnEquation.push_back(arrayItem); - } - else if(nullptr != std::dynamic_pointer_cast(calcItem)) - { - // This is a left parenthesis, so push it onto the item stack - itemStack.push(calcItem); - } - else if(nullptr != std::dynamic_pointer_cast(calcItem)) - { - // This is a right parenthesis, so push operators from the item stack onto the rpn expression output until we get to the left parenthesis - while(!itemStack.empty() && nullptr == std::dynamic_pointer_cast(itemStack.top())) - { - rpnEquation.push_back(itemStack.top()); - itemStack.pop(); - } + while(i < len) + { + const char c = equation[i]; - // Discard the left parenthesis that we found - itemStack.pop(); - } - else if(nullptr != std::dynamic_pointer_cast(calcItem)) + // 1. Skip whitespace + if(std::isspace(static_cast(c))) { - // This is a comma, so we want to continue without adding it to anything + ++i; continue; } - else - { - // This is an operator - CalculatorOperator::Pointer incomingOperator = std::dynamic_pointer_cast(calcItem); - if(!itemStack.empty()) + // 2. Numbers: starts with digit, or dot followed by a digit + if(std::isdigit(static_cast(c)) || (c == '.' && i + 1 < len && std::isdigit(static_cast(equation[i + 1])))) + { + size_t start = i; + bool hasDot = false; + while(i < len && (std::isdigit(static_cast(equation[i])) || equation[i] == '.')) { - /* If the operator's precedence is lower than the precedence of the operator on top of the item stack, push the operator at the top - of the item stack onto the rpn expression output. Keeping doing this until there isn't another operator at the top of the item - stack or the operator has a higher precedence than the one currently on top of the stack */ - CalculatorOperator::Pointer topOperator = std::dynamic_pointer_cast(itemStack.top()); - while(nullptr != topOperator && !incomingOperator->hasHigherPrecedence(topOperator)) + if(equation[i] == '.') { - rpnEquation.push_back(itemStack.top()); - itemStack.pop(); - if(!itemStack.empty()) + if(hasDot) { - topOperator = std::dynamic_pointer_cast(itemStack.top()); - } - else - { - topOperator = nullptr; + break; } + hasDot = true; } + ++i; } - - // Push the operator onto the rpn expression output. - itemStack.push(calcItem); + tokens.push_back({TokenType::Number, equation.substr(start, i - start), start}); + continue; } - } - /* After we are done iterating through the infix expression items, keep transferring items from the item stack to the - rpn expression output until the stack is empty. */ - while(!itemStack.empty()) - { - CalculatorItem::Pointer item = itemStack.top(); - itemStack.pop(); - if(nullptr != std::dynamic_pointer_cast(item)) + // 3. Identifiers: start with letter or underscore + if(std::isalpha(static_cast(c)) || c == '_') { - std::string ss = fmt::format("One or more parentheses are mismatched in the chosen infix expression '{}'", unparsedInfixExpression); - return MakeErrorResult(static_cast(CalculatorItem::ErrorCode::MismatchedParentheses), ss); + size_t start = i; + while(i < len && (std::isalnum(static_cast(equation[i])) || equation[i] == '_')) + { + ++i; + } + tokens.push_back({TokenType::Identifier, equation.substr(start, i - start), start}); + continue; + } + + // 4. Quoted strings + if(c == '"') + { + size_t start = i; + ++i; // skip opening quote + size_t contentStart = i; + while(i < len && equation[i] != '"') + { + ++i; + } + std::string content = equation.substr(contentStart, i - contentStart); + if(i < len) + { + ++i; // skip closing quote + } + tokens.push_back({TokenType::QuotedString, content, start}); + continue; + } + + // 5. Single-character operators + TokenType opType; + bool isOperator = true; + switch(c) + { + case '+': + opType = TokenType::Plus; + break; + case '-': + opType = TokenType::Minus; + break; + case '*': + opType = TokenType::Star; + break; + case '/': + opType = TokenType::Slash; + break; + case '^': + opType = TokenType::Caret; + break; + case '%': + opType = TokenType::Percent; + break; + case '(': + opType = TokenType::LParen; + break; + case ')': + opType = TokenType::RParen; + break; + case '[': + opType = TokenType::LBracket; + break; + case ']': + opType = TokenType::RBracket; + break; + case ',': + opType = TokenType::Comma; + break; + default: + isOperator = false; + break; + } + + if(isOperator) + { + tokens.push_back({opType, std::string(1, c), i}); + ++i; + continue; + } + + // 6. Unknown characters: produce an Identifier token + tokens.push_back({TokenType::Identifier, std::string(1, c), i}); + ++i; + } + + return tokens; +} + +// --------------------------------------------------------------------------- +// parse() -- the core parsing pipeline +// --------------------------------------------------------------------------- +Result<> ArrayCalculatorParser::parse() +{ + Result<> result; + + // === Step 1: Tokenize === + std::vector tokens = tokenize(m_InfixEquation); + if(tokens.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::EmptyEquation), "The infix expression is empty."); + } + + // === Step 2: Multi-word identifier merging === + // Walk the token list. When consecutive Identifier tokens appear, try + // merging them with spaces (greedy longest-first) and check if the + // merged name matches an array name. + { + std::vector merged; + merged.reserve(tokens.size()); + size_t i = 0; + while(i < tokens.size()) + { + if(tokens[i].type == TokenType::Identifier) + { + // Find the run of consecutive Identifier tokens + size_t runStart = i; + size_t runEnd = i + 1; + while(runEnd < tokens.size() && tokens[runEnd].type == TokenType::Identifier) + { + ++runEnd; + } + size_t runLen = runEnd - runStart; + + if(runLen > 1) + { + // Greedy: try longest merge first + bool foundMatch = false; + for(size_t len = runLen; len >= 2; --len) + { + for(size_t start = runStart; start + len <= runEnd; ++start) + { + // Build the merged name + std::string mergedName = tokens[start].text; + for(size_t k = start + 1; k < start + len; ++k) + { + mergedName += " " + tokens[k].text; + } + + // Check if this name matches an array + bool found = false; + if(!m_SelectedGroupPath.empty()) + { + found = ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, mergedName); + } + if(!found) + { + auto paths = findArraysByName(m_DataStructure, mergedName); + found = !paths.empty(); + } + + if(found) + { + // Add tokens before the match + for(size_t k = runStart; k < start; ++k) + { + merged.push_back(tokens[k]); + } + // Add the merged token + merged.push_back({TokenType::Identifier, mergedName, tokens[start].position}); + // Add tokens after the match + for(size_t k = start + len; k < runEnd; ++k) + { + merged.push_back(tokens[k]); + } + i = runEnd; + foundMatch = true; + break; + } + } + if(foundMatch) + { + break; + } + } + if(!foundMatch) + { + // No merge found; copy all identifiers as-is + for(size_t k = runStart; k < runEnd; ++k) + { + merged.push_back(tokens[k]); + } + i = runEnd; + } + } + else + { + merged.push_back(tokens[i]); + ++i; + } + } + else + { + merged.push_back(tokens[i]); + ++i; + } + } + tokens = std::move(merged); + } + + // === Steps 3+4: Identifier resolution, token conversion, and bracket indexing === + // These steps are combined so brackets can reference the array they follow. + std::vector items; + items.reserve(tokens.size()); + + // Combined Step 3+4: resolve identifiers and handle brackets inline + for(size_t i = 0; i < tokens.size(); ++i) + { + const Token& tok = tokens[i]; + + // Check if this token starts a bracket expression [...] + if(tok.type == TokenType::LBracket) + { + // Parse bracket contents: [Number] or [Number, Number] + if(items.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::OrphanedComponent), "Index operator '[' is not paired with a valid array name or closing parenthesis."); + } + + // Collect tokens until matching ']' + std::vector bracketNumbers; + size_t j = i + 1; + while(j < tokens.size() && tokens[j].type != TokenType::RBracket) + { + if(tokens[j].type == TokenType::Number) + { + bracketNumbers.push_back(tokens[j].text); + } + else if(tokens[j].type == TokenType::Comma) + { + // skip comma + } + else + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidComponent), fmt::format("Invalid content inside bracket index: '{}'.", tokens[j].text)); + } + ++j; + } + if(j >= tokens.size()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::MismatchedParentheses), "Missing closing bracket ']'."); + } + // j now points to ']' + + ParsedItem& prevItem = items.back(); + + if(prevItem.kind == ParsedItem::Kind::ArrayRef) + { + // Case A: Array[C] or Array[T, C] + usize numComponents = 1; + for(usize d : prevItem.arrayCompShape) + { + numComponents *= d; + } + usize numTuples = 1; + for(usize d : prevItem.arrayTupleShape) + { + numTuples *= d; + } + + if(bracketNumbers.size() == 1) + { + // [C]: component extraction + usize compIdx = 0; + try + { + auto parsed = std::stoull(bracketNumbers[0]); + compIdx = static_cast(parsed); + } catch(const std::exception&) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidComponent), fmt::format("Invalid component index '{}'.", bracketNumbers[0])); + } + + if(compIdx >= numComponents) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::ComponentOutOfRange), fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComponents)); + } + + // Emit a ComponentExtract after the ArrayRef + ParsedItem ce; + ce.kind = ParsedItem::Kind::ComponentExtract; + ce.componentIndex = compIdx; + items.push_back(ce); + } + else if(bracketNumbers.size() == 2) + { + // [T, C]: tuple+component extraction + usize tupleIdx = 0; + usize compIdx = 0; + try + { + tupleIdx = static_cast(std::stoull(bracketNumbers[0])); + compIdx = static_cast(std::stoull(bracketNumbers[1])); + } catch(const std::exception&) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidComponent), fmt::format("Invalid tuple/component index in '[{}, {}]'.", bracketNumbers[0], bracketNumbers[1])); + } + + if(tupleIdx >= numTuples) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::TupleOutOfRange), fmt::format("Tuple index {} is out of range for array with {} tuples.", tupleIdx, numTuples)); + } + if(compIdx >= numComponents) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::ComponentOutOfRange), fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComponents)); + } + + ParsedItem tce; + tce.kind = ParsedItem::Kind::TupleComponentExtract; + tce.tupleIndex = tupleIdx; + tce.componentIndex = compIdx; + items.push_back(tce); + } + else + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidComponent), "Bracket index must contain 1 or 2 numbers (e.g. [C] or [T, C])."); + } + } + else if(prevItem.kind == ParsedItem::Kind::RParen) + { + // Case B: )[C] or )[T, C] -- extraction on sub-expression result + if(bracketNumbers.size() == 1) + { + // )[C]: component extraction + usize compIdx = 0; + try + { + compIdx = static_cast(std::stoull(bracketNumbers[0])); + } catch(const std::exception&) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidComponent), fmt::format("Invalid component index '{}'.", bracketNumbers[0])); + } + + ParsedItem ce; + ce.kind = ParsedItem::Kind::ComponentExtract; + ce.componentIndex = compIdx; + items.push_back(ce); + } + else if(bracketNumbers.size() == 2) + { + // )[T, C]: tuple+component extraction (produces scalar) + usize tupleIdx = 0; + usize compIdx = 0; + try + { + tupleIdx = static_cast(std::stoull(bracketNumbers[0])); + compIdx = static_cast(std::stoull(bracketNumbers[1])); + } catch(const std::exception&) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidComponent), fmt::format("Invalid tuple/component index in '[{}, {}]'.", bracketNumbers[0], bracketNumbers[1])); + } + + ParsedItem tce; + tce.kind = ParsedItem::Kind::TupleComponentExtract; + tce.tupleIndex = tupleIdx; + tce.componentIndex = compIdx; + items.push_back(tce); + } + else + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidComponent), "Sub-expression index must be [C] or [T, C]."); + } + } + else + { + return MakeErrorResult(static_cast(CalculatorErrorCode::OrphanedComponent), fmt::format("Index operator '{}' is not paired with a valid array name or closing parenthesis.", tok.text)); + } + + i = j; // skip past ']' + continue; + } + + // Normal token processing (same as step 3 above but now with brackets handled separately) + switch(tok.type) + { + case TokenType::Number: { + double numValue = 0.0; + try + { + numValue = std::stod(tok.text); + } catch(const std::exception&) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidEquation), fmt::format("Invalid numeric value '{}'.", tok.text)); + } + + ParsedItem pi; + pi.kind = ParsedItem::Kind::Scalar; + pi.scalarValue = numValue; + items.push_back(pi); + + // Ambiguous name warning + { + bool arrayExists = false; + if(!m_SelectedGroupPath.empty()) + { + arrayExists = ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, tok.text); + } + if(!arrayExists) + { + arrayExists = !findArraysByName(m_DataStructure, tok.text).empty(); + } + if(arrayExists) + { + result.warnings().push_back(Warning{static_cast(CalculatorWarningCode::AmbiguousNameWarning), + fmt::format("Item '{}' in the infix expression is the name of an array, but it is currently being used as a number." + "\nTo treat this item as an array name, please add double quotes around the item (i.e. \"{}\").", + tok.text, tok.text)}); + } + } + break; + } + + case TokenType::Identifier: { + const OperatorDef* opDef = findOperatorByToken(tok.text); + if(opDef != nullptr) + { + { + bool arrayExists = false; + if(!m_SelectedGroupPath.empty()) + { + arrayExists = ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, tok.text); + } + if(!arrayExists) + { + arrayExists = !findArraysByName(m_DataStructure, tok.text).empty(); + } + if(arrayExists) + { + result.warnings().push_back(Warning{static_cast(CalculatorWarningCode::AmbiguousNameWarning), + fmt::format("Item '{}' in the infix expression is the name of an array, but it is currently being used as a mathematical operator." + "\nTo treat this item as an array name, please add double quotes around the item (i.e. \"{}\").", + tok.text, tok.text)}); + } + } + + ParsedItem pi; + pi.kind = ParsedItem::Kind::Operator; + pi.op = opDef; + items.push_back(pi); + } + else if(tok.text == "pi" || tok.text == "e") + { + float64 constValue = (tok.text == "pi") ? std::numbers::pi : std::numbers::e; + + ParsedItem pi; + pi.kind = ParsedItem::Kind::Scalar; + pi.scalarValue = constValue; + items.push_back(pi); + + { + bool arrayExists = false; + if(!m_SelectedGroupPath.empty()) + { + arrayExists = ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, tok.text); + } + if(!arrayExists) + { + arrayExists = !findArraysByName(m_DataStructure, tok.text).empty(); + } + if(arrayExists) + { + result.warnings().push_back(Warning{static_cast(CalculatorWarningCode::AmbiguousNameWarning), + fmt::format("Item '{}' in the infix expression is the name of an array, but it is currently being used as a built-in constant." + "\nTo treat this item as an array name, please add double quotes around the item (i.e. \"{}\").", + tok.text, tok.text)}); + } + } + } + else + { + // Try as array name + if(!m_SelectedGroupPath.empty() && ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, tok.text)) + { + DataPath arrayPath = m_SelectedGroupPath.createChildPath(tok.text); + const auto* dataArray = m_DataStructure.getDataAs(arrayPath); + if(dataArray == nullptr) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::UnrecognizedItem), fmt::format("Could not access array '{}' in selected group.", tok.text)); + } + + ParsedItem pi; + pi.kind = ParsedItem::Kind::ArrayRef; + pi.arrayPath = arrayPath; + pi.sourceDataType = dataArray->getDataType(); + pi.arrayTupleShape = dataArray->getTupleShape(); + pi.arrayCompShape = dataArray->getComponentShape(); + items.push_back(pi); + } + else + { + auto foundPaths = findArraysByName(m_DataStructure, tok.text); + if(foundPaths.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::UnrecognizedItem), fmt::format("An unrecognized or invalid item '{}' was found in the chosen infix expression.", tok.text)); + } + if(foundPaths.size() > 1) + { + std::string pathsList; + for(const auto& p : foundPaths) + { + if(!pathsList.empty()) + { + pathsList += ", "; + } + pathsList += p.toString(); + } + return MakeErrorResult(static_cast(CalculatorErrorCode::AmbiguousArrayName), fmt::format("Array name '{}' is ambiguous. Multiple arrays found: [{}]." + "\nPlease use double quotes with the full path (e.g. \"Path/To/{}\") to disambiguate.", + tok.text, pathsList, tok.text)); + } + const auto* dataArray = m_DataStructure.getDataAs(foundPaths[0]); + if(dataArray == nullptr) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::UnrecognizedItem), fmt::format("Could not access array '{}'.", tok.text)); + } + + ParsedItem pi; + pi.kind = ParsedItem::Kind::ArrayRef; + pi.arrayPath = foundPaths[0]; + pi.sourceDataType = dataArray->getDataType(); + pi.arrayTupleShape = dataArray->getTupleShape(); + pi.arrayCompShape = dataArray->getComponentShape(); + items.push_back(pi); + } + } + break; + } + + case TokenType::QuotedString: { + std::vector pathComponents; + { + std::string component; + for(char ch : tok.text) + { + if(ch == '/') + { + if(!component.empty()) + { + pathComponents.push_back(component); + component.clear(); + } + } + else + { + component += ch; + } + } + if(!component.empty()) + { + pathComponents.push_back(component); + } + } + + DataPath quotedPath(pathComponents); + + // If single component, try as child of selected group first + if(pathComponents.size() == 1 && !m_SelectedGroupPath.empty()) + { + DataPath childPath = m_SelectedGroupPath.createChildPath(pathComponents[0]); + if(m_DataStructure.getDataAs(childPath) != nullptr) + { + quotedPath = childPath; + } + } + + const auto* dataArray = m_DataStructure.getDataAs(quotedPath); + if(dataArray == nullptr) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidArrayName), fmt::format("The item '\"{}\"' is not a valid array path in the DataStructure.", tok.text)); + } + + ParsedItem pi; + pi.kind = ParsedItem::Kind::ArrayRef; + pi.arrayPath = quotedPath; + pi.sourceDataType = dataArray->getDataType(); + pi.arrayTupleShape = dataArray->getTupleShape(); + pi.arrayCompShape = dataArray->getComponentShape(); + items.push_back(pi); + break; + } + + case TokenType::Plus: + case TokenType::Minus: + case TokenType::Star: + case TokenType::Slash: + case TokenType::Caret: + case TokenType::Percent: { + const OperatorDef* opDef = operatorDefForSymbolToken(tok.type); + if(opDef == nullptr) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidSymbol), fmt::format("Unknown operator symbol '{}'.", tok.text)); + } + + // Check if the operator symbol is also the name of an array + { + bool arrayExists = false; + if(!m_SelectedGroupPath.empty()) + { + arrayExists = ContainsDataArrayName(m_DataStructure, m_SelectedGroupPath, tok.text); + } + if(!arrayExists) + { + arrayExists = !findArraysByName(m_DataStructure, tok.text).empty(); + } + if(arrayExists) + { + result.warnings().push_back(Warning{static_cast(CalculatorWarningCode::AmbiguousNameWarning), + fmt::format("Item '{}' in the infix expression is the name of an array, but it is currently being used as a mathematical operator." + "\nTo treat this item as an array name, please add double quotes around the item (i.e. \"{}\").", + tok.text, tok.text)}); + } + } + + ParsedItem pi; + pi.kind = ParsedItem::Kind::Operator; + pi.op = opDef; + items.push_back(pi); + break; + } + + case TokenType::LParen: { + ParsedItem pi; + pi.kind = ParsedItem::Kind::LParen; + items.push_back(pi); + break; + } + + case TokenType::RParen: { + ParsedItem pi; + pi.kind = ParsedItem::Kind::RParen; + items.push_back(pi); + break; + } + + case TokenType::Comma: { + ParsedItem pi; + pi.kind = ParsedItem::Kind::Comma; + items.push_back(pi); + break; + } + + case TokenType::LBracket: + case TokenType::RBracket: { + // Should not reach here since brackets are handled at the top of the loop + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidEquation), "Unexpected bracket token encountered."); + } + + } // end switch + } + + // === Step 5: Minus sign disambiguation === + for(size_t i = 0; i < items.size(); ++i) + { + auto& item = items[i]; + if(item.kind != ParsedItem::Kind::Operator || item.op == nullptr) + { + continue; + } + if(item.op->token != "-") + { + continue; + } + + // Determine if this is a unary negative + bool isUnary = false; + if(i == 0) + { + isUnary = true; + } + else + { + const auto& prev = items[i - 1]; + if(isBinaryOp(prev)) + { + isUnary = true; + } + else if(prev.kind == ParsedItem::Kind::Operator && prev.op != nullptr && prev.op->kind == OperatorDef::UnaryPrefix) + { + isUnary = true; + } + else if(prev.kind == ParsedItem::Kind::LParen) + { + isUnary = true; + } + else if(prev.kind == ParsedItem::Kind::Comma) + { + isUnary = true; + } + } + + if(isUnary) + { + item.op = &getUnaryNegativeOp(); + item.isNegativePrefix = true; + } + } + + // === Step 6: WrapFunctionArguments === + wrapFunctionArguments(items); + + // === Step 7: Validation === + + // 7a-1: Check for function/unary operators: opening/closing paren, argument count, empty args + for(size_t i = 0; i < items.size(); ++i) + { + const auto& item = items[i]; + if(item.kind == ParsedItem::Kind::Operator && item.op != nullptr && item.op->kind == OperatorDef::Function) + { + // A function operator must be followed by LParen + if(i + 1 >= items.size() || items[i + 1].kind != ParsedItem::Kind::LParen) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::OperatorNoOpeningParen), fmt::format("The operator/function '{}' does not have a valid opening parenthesis.", item.op->token)); + } + + // Find the matching RParen and count commas/values at depth 1 + int depth = 0; + bool foundClose = false; + size_t closeIdx = 0; + int commaCount = 0; + bool hasValueInside = false; + for(size_t j = i + 1; j < items.size(); ++j) + { + if(items[j].kind == ParsedItem::Kind::LParen) + { + ++depth; + } + else if(items[j].kind == ParsedItem::Kind::RParen) + { + --depth; + if(depth == 0) + { + foundClose = true; + closeIdx = j; + break; + } + } + else if(items[j].kind == ParsedItem::Kind::Comma && depth == 1) + { + ++commaCount; + } + else if(depth >= 1 && (items[j].kind == ParsedItem::Kind::Scalar || items[j].kind == ParsedItem::Kind::ArrayRef || (items[j].kind == ParsedItem::Kind::Operator && items[j].op != nullptr))) + { + hasValueInside = true; + } + } + if(!foundClose) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::OperatorNoClosingParen), fmt::format("The operator/function '{}' does not have a valid closing parenthesis.", item.op->token)); + } + + // Check for empty function call: func() with no values or commas inside + if(!hasValueInside && commaCount == 0) + { + // For 2-arg functions with empty parens: NotEnoughArguments + if(item.op->numArgs == 2) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), + fmt::format("The function '{}' requires {} arguments, but none were provided.", item.op->token, item.op->numArgs)); + } + // For 1-arg functions with empty parens: NoNumericArguments + return MakeErrorResult(static_cast(CalculatorErrorCode::NoNumericArguments), fmt::format("The function '{}' does not have any arguments that simplify down to a number.", item.op->token)); + } + + // Check for commas in the empty-value case: func(,) -- commas but no real values + if(!hasValueInside && commaCount > 0) + { + if(item.op->numArgs == 1) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::TooManyArguments), + fmt::format("The function '{}' requires {} argument, but more were provided.", item.op->token, item.op->numArgs)); + } + // For 2-arg functions: NoNumericArguments (commas but no values) + return MakeErrorResult(static_cast(CalculatorErrorCode::NoNumericArguments), fmt::format("The function '{}' does not have any arguments that simplify down to a number.", item.op->token)); + } + + // Argument count: numArgs from OperatorDef, commaCount gives (numArgs-1) + int providedArgs = commaCount + 1; + if(item.op->numArgs == 1 && commaCount > 0) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::TooManyArguments), + fmt::format("The function '{}' requires {} argument, but {} were provided.", item.op->token, item.op->numArgs, providedArgs)); + } + if(item.op->numArgs == 2 && commaCount < 1 && hasValueInside) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), + fmt::format("The function '{}' requires {} arguments, but only {} was provided.", item.op->token, item.op->numArgs, providedArgs)); + } + } + } + + // 7a-1b: Check for commas inside non-function parentheses (NoPrecedingUnaryOperator) + for(size_t i = 0; i < items.size(); ++i) + { + if(items[i].kind == ParsedItem::Kind::Comma) + { + // Walk backwards to find the opening paren at the same depth, and check if preceded by a function + int depth = 0; + bool foundFunction = false; + for(int j = static_cast(i) - 1; j >= 0; --j) + { + if(items[j].kind == ParsedItem::Kind::RParen) + { + ++depth; + } + else if(items[j].kind == ParsedItem::Kind::LParen) + { + if(depth == 0) + { + // Found the opening paren; check if preceded by a function + if(j > 0 && items[j - 1].kind == ParsedItem::Kind::Operator && items[j - 1].op != nullptr && items[j - 1].op->kind == OperatorDef::Function) + { + foundFunction = true; + } + break; + } + --depth; + } + } + if(!foundFunction) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NoPrecedingUnaryOperator), "A comma was found in parentheses without a preceding function operator."); + } + } + } + + // 7a-2: Check for binary operators missing left or right operands + for(size_t i = 0; i < items.size(); ++i) + { + const auto& item = items[i]; + if(!isBinaryOp(item)) + { + continue; + } + // Check left: the item before must be a value or RParen (something that produces a value) + bool hasLeft = false; + if(i > 0) + { + const auto& prev = items[i - 1]; + if(prev.kind == ParsedItem::Kind::Scalar || prev.kind == ParsedItem::Kind::ArrayRef || prev.kind == ParsedItem::Kind::RParen || prev.kind == ParsedItem::Kind::ComponentExtract || + prev.kind == ParsedItem::Kind::TupleComponentExtract) + { + hasLeft = true; + } + } + if(!hasLeft) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::OperatorNoLeftValue), fmt::format("The binary operator '{}' does not have a valid left-hand value.", item.op->token)); + } + // Check right: the item after must be a value, LParen, or unary operator (something that produces a value) + bool hasRight = false; + if(i + 1 < items.size()) + { + const auto& next = items[i + 1]; + if(next.kind == ParsedItem::Kind::Scalar || next.kind == ParsedItem::Kind::ArrayRef || next.kind == ParsedItem::Kind::LParen) + { + hasRight = true; + } + else if(next.kind == ParsedItem::Kind::Operator && next.op != nullptr) + { + hasRight = true; // Could be a unary prefix or function + } + } + if(!hasRight) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::OperatorNoRightValue), fmt::format("The binary operator '{}' does not have a valid right-hand value.", item.op->token)); + } + } + + // 7a-3: Check for unary negative with no right operand + for(size_t i = 0; i < items.size(); ++i) + { + const auto& item = items[i]; + if(item.isNegativePrefix) + { + bool hasRight = false; + if(i + 1 < items.size()) + { + const auto& next = items[i + 1]; + if(next.kind == ParsedItem::Kind::Scalar || next.kind == ParsedItem::Kind::ArrayRef || next.kind == ParsedItem::Kind::LParen) + { + hasRight = true; + } + else if(next.kind == ParsedItem::Kind::Operator && next.op != nullptr) + { + hasRight = true; // e.g. -sin(...) + } + } + if(!hasRight) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::OperatorNoRightValue), "The unary negative operator does not have a valid right-hand value."); + } + } + } + + // 7a-4: Check matched parentheses (generic, after operator-specific checks) + { + int parenDepth = 0; + for(const auto& item : items) + { + if(item.kind == ParsedItem::Kind::LParen) + { + ++parenDepth; + } + else if(item.kind == ParsedItem::Kind::RParen) + { + --parenDepth; + } + if(parenDepth < 0) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::MismatchedParentheses), + fmt::format("One or more parentheses are mismatched in the chosen infix expression '{}'.", m_InfixEquation)); + } + } + if(parenDepth != 0) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::MismatchedParentheses), fmt::format("One or more parentheses are mismatched in the chosen infix expression '{}'.", m_InfixEquation)); + } + } + + // 7b: Collect array-type values and verify consistent tuple/component info + std::vector arrayTupleShape; + std::vector arrayCompShape; + usize arrayNumTuples = 0; + bool hasArray = false; + bool hasNumericValue = false; + bool tupleShapesMatch = true; + + for(size_t vi = 0; vi < items.size(); ++vi) + { + const auto& item = items[vi]; + if(item.kind == ParsedItem::Kind::Scalar || item.kind == ParsedItem::Kind::ArrayRef) + { + hasNumericValue = true; + } + if(item.kind == ParsedItem::Kind::ArrayRef) + { + std::vector ts = item.arrayTupleShape; + std::vector cs = item.arrayCompShape; + + // If this ArrayRef is immediately followed by ComponentExtract or + // TupleComponentExtract, adjust the effective shape accordingly. + if(vi + 1 < items.size() && items[vi + 1].kind == ParsedItem::Kind::ComponentExtract) + { + cs = {1}; + } + else if(vi + 1 < items.size() && items[vi + 1].kind == ParsedItem::Kind::TupleComponentExtract) + { + // TupleComponentExtract reduces to a scalar — skip this array from + // shape consistency checks entirely (it won't contribute shape). + continue; + } + + usize nt = 1; + for(usize d : ts) + { + nt *= d; + } + + if(hasArray) + { + if(!arrayCompShape.empty() && arrayCompShape != cs) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InconsistentCompDims), "Attribute Array symbols in the infix expression have mismatching component dimensions."); + } + if(arrayNumTuples != 0 && nt != arrayNumTuples) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InconsistentTuples), "Attribute Array symbols in the infix expression have mismatching number of tuples."); + } + if(!arrayTupleShape.empty() && arrayTupleShape != ts) + { + tupleShapesMatch = false; + } + } + + hasArray = true; + arrayTupleShape = ts; + arrayCompShape = cs; + arrayNumTuples = nt; + } + } + + // 7c: Ensure at least one numeric argument exists + if(!hasNumericValue) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NoNumericArguments), "The expression does not have any arguments that simplify down to a number."); + } + + // Check if there is a ComponentExtract or TupleComponentExtract item in the parsed list. + // ComponentExtract produces a single-component array. + // TupleComponentExtract produces a scalar (single tuple, single component). + bool hasComponentExtract = false; + bool hasTupleComponentExtract = false; + for(const auto& item : items) + { + if(item.kind == ParsedItem::Kind::ComponentExtract) + { + hasComponentExtract = true; + } + if(item.kind == ParsedItem::Kind::TupleComponentExtract) + { + hasTupleComponentExtract = true; + } + } + + // Store the parsed shape info for use by parseAndValidate() + if(hasTupleComponentExtract) + { + // TupleComponentExtract produces a scalar + m_ParsedTupleShape = {1}; + m_ParsedComponentShape = {1}; + } + else if(hasArray) + { + if(tupleShapesMatch) + { + m_ParsedTupleShape = arrayTupleShape; + } + else + { + m_ParsedTupleShape = {arrayNumTuples}; + } + m_ParsedComponentShape = hasComponentExtract ? std::vector{1} : arrayCompShape; + } + else + { + // All scalars: output is {1} tuples, {1} components + m_ParsedTupleShape = {1}; + m_ParsedComponentShape = {1}; + } + + // === Convert ParsedItems to RPN using shunting-yard === + m_RpnItems.clear(); + std::vector opStack; + + for(const auto& item : items) + { + switch(item.kind) + { + case ParsedItem::Kind::Scalar: { + RpnItem rpn; + rpn.type = RpnItem::Type::Scalar; + rpn.scalarValue = item.scalarValue; + m_RpnItems.push_back(rpn); + break; + } + + case ParsedItem::Kind::ArrayRef: { + RpnItem rpn; + rpn.type = RpnItem::Type::ArrayRef; + rpn.arrayPath = item.arrayPath; + rpn.sourceDataType = item.sourceDataType; + m_RpnItems.push_back(rpn); + break; + } + + case ParsedItem::Kind::LParen: { + opStack.push_back(item); + break; + } + + case ParsedItem::Kind::RParen: { + // Pop operators to output until we find the matching LParen + while(!opStack.empty() && opStack.back().kind != ParsedItem::Kind::LParen) + { + const auto& top = opStack.back(); + RpnItem rpn; + rpn.type = RpnItem::Type::Operator; + rpn.op = top.op; + m_RpnItems.push_back(rpn); + opStack.pop_back(); + } + if(opStack.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::MismatchedParentheses), + fmt::format("One or more parentheses are mismatched in the chosen infix expression '{}'.", m_InfixEquation)); + } + // Discard the LParen + opStack.pop_back(); + break; + } + + case ParsedItem::Kind::Comma: { + // Pop operators to output until we find the LParen (but don't discard it) + while(!opStack.empty() && opStack.back().kind != ParsedItem::Kind::LParen) + { + const auto& top = opStack.back(); + RpnItem rpn; + rpn.type = RpnItem::Type::Operator; + rpn.op = top.op; + m_RpnItems.push_back(rpn); + opStack.pop_back(); + } + break; + } + + case ParsedItem::Kind::Operator: { + const OperatorDef* incomingOp = item.isNegativePrefix ? &getUnaryNegativeOp() : item.op; + int incomingPrec = incomingOp->precedence; + bool isLeftAssoc = (incomingOp->associativity == OperatorDef::Left); + + while(!opStack.empty() && opStack.back().kind == ParsedItem::Kind::Operator) + { + const auto& topItem = opStack.back(); + const OperatorDef* topOp = topItem.isNegativePrefix ? &getUnaryNegativeOp() : topItem.op; + int topPrec = topOp->precedence; + + if(topPrec > incomingPrec || (topPrec == incomingPrec && isLeftAssoc)) + { + RpnItem rpn; + rpn.type = RpnItem::Type::Operator; + rpn.op = topOp; + m_RpnItems.push_back(rpn); + opStack.pop_back(); + } + else + { + break; + } + } + + opStack.push_back(item); + break; + } + + case ParsedItem::Kind::ComponentExtract: { + RpnItem rpn; + rpn.type = RpnItem::Type::ComponentExtract; + rpn.componentIndex = item.componentIndex; + m_RpnItems.push_back(rpn); + break; + } + + case ParsedItem::Kind::TupleComponentExtract: { + RpnItem rpn; + rpn.type = RpnItem::Type::TupleComponentExtract; + rpn.tupleIndex = item.tupleIndex; + rpn.componentIndex = item.componentIndex; + m_RpnItems.push_back(rpn); + break; + } + + } // end switch + } + + // Pop remaining operators from the stack + while(!opStack.empty()) + { + const auto& top = opStack.back(); + if(top.kind == ParsedItem::Kind::LParen) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::MismatchedParentheses), fmt::format("One or more parentheses are mismatched in the chosen infix expression '{}'.", m_InfixEquation)); + } + const OperatorDef* topOp = top.isNegativePrefix ? &getUnaryNegativeOp() : top.op; + RpnItem rpn; + rpn.type = RpnItem::Type::Operator; + rpn.op = topOp; + m_RpnItems.push_back(rpn); + opStack.pop_back(); + } + + return result; +} + +// --------------------------------------------------------------------------- +Result<> ArrayCalculatorParser::parseAndValidate(std::vector& outTupleShape, std::vector& outComponentShape) +{ + Result<> parseResult = parse(); + if(parseResult.invalid()) + { + return parseResult; + } + + outTupleShape = m_ParsedTupleShape; + outComponentShape = m_ParsedComponentShape; + + return parseResult; +} + +// --------------------------------------------------------------------------- +Result<> ArrayCalculatorParser::evaluateInto(DataStructure& dataStructure, const DataPath& outputPath, NumericType scalarType, CalculatorParameter::AngleUnits units) +{ + // 1. Parse (populates m_RpnItems via shunting-yard) + Result<> parseResult = parse(); + if(parseResult.invalid()) + { + return parseResult; + } + + // 2. Create local temp DataStructure for intermediate arrays + DataStructure tempDS; + usize scratchCounter = 0; + auto nextScratchName = [&scratchCounter]() -> std::string { return "_calc_" + std::to_string(scratchCounter++); }; + + // 3. Pre-scan RPN to find the index of the last operator/extract item + // for the OutputDirect optimization + DataType outputDataType = ConvertNumericTypeToDataType(scalarType); + bool outputIsFloat64 = (outputDataType == DataType::float64); + int64 lastOpIndex = -1; + for(int64 idx = static_cast(m_RpnItems.size()) - 1; idx >= 0; --idx) + { + RpnItem::Type t = m_RpnItems[static_cast(idx)].type; + if(t == RpnItem::Type::Operator || t == RpnItem::Type::ComponentExtract || t == RpnItem::Type::TupleComponentExtract) + { + lastOpIndex = idx; + break; + } + } + + // 4. Walk the RPN items using a CalcBuffer evaluation stack + std::stack evalStack; + + for(usize rpnIdx = 0; rpnIdx < m_RpnItems.size(); ++rpnIdx) + { + if(m_ShouldCancel) + { + return {}; + } + + const RpnItem& rpnItem = m_RpnItems[rpnIdx]; + bool isLastOp = (static_cast(rpnIdx) == lastOpIndex); + + switch(rpnItem.type) + { + case RpnItem::Type::Scalar: { + evalStack.push(CalcBuffer::scalar(tempDS, rpnItem.scalarValue, nextScratchName())); + break; + } + + case RpnItem::Type::ArrayRef: { + if(rpnItem.sourceDataType == DataType::float64) + { + const auto& sourceArray = m_DataStructure.getDataRefAs(rpnItem.arrayPath); + evalStack.push(CalcBuffer::borrow(sourceArray)); + } + else + { + const auto& sourceArray = m_DataStructure.getDataRefAs(rpnItem.arrayPath); + evalStack.push(CalcBuffer::convertFrom(tempDS, sourceArray, nextScratchName())); + } + break; + } + + case RpnItem::Type::Operator: { + const OperatorDef* op = rpnItem.op; + if(op == nullptr) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidEquation), "Internal error: null operator in RPN evaluation."); + } + + if(op->numArgs == 1) + { + if(evalStack.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for unary operator."); + } + CalcBuffer operand = std::move(evalStack.top()); + evalStack.pop(); + + std::vector resultTupleShape = operand.tupleShape(); + std::vector resultCompShape = operand.compShape(); + usize totalSize = operand.size(); + + CalcBuffer result = (isLastOp && outputIsFloat64) ? CalcBuffer::wrapOutput(dataStructure.getDataRefAs>(outputPath)) : + CalcBuffer::allocate(tempDS, nextScratchName(), resultTupleShape, resultCompShape); + + for(usize i = 0; i < totalSize; i++) + { + float64 val = operand.read(i); + + if(op->trigMode == OperatorDef::ForwardTrig && units == CalculatorParameter::AngleUnits::Degrees) + { + val = val * (std::numbers::pi / 180.0); + } + + float64 res = op->unaryOp(val); + + if(op->trigMode == OperatorDef::InverseTrig && units == CalculatorParameter::AngleUnits::Degrees) + { + res = res * (180.0 / std::numbers::pi); + } + + result.write(i, res); + } + + bool wasScalar = operand.isScalar(); + if(wasScalar) + { + result.markAsScalar(); + } + // operand destroyed here, RAII cleans up + evalStack.push(std::move(result)); + } + else if(op->numArgs == 2) + { + if(evalStack.size() < 2) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for binary operator."); + } + CalcBuffer right = std::move(evalStack.top()); + evalStack.pop(); + CalcBuffer left = std::move(evalStack.top()); + evalStack.pop(); + + // Determine output shape: use the array operand's shape (broadcast scalars) + std::vector outTupleShape; + std::vector outCompShape; + if(!left.isScalar()) + { + outTupleShape = left.tupleShape(); + outCompShape = left.compShape(); + } + else + { + outTupleShape = right.tupleShape(); + outCompShape = right.compShape(); + } + + usize totalSize = 1; + for(usize d : outTupleShape) + { + totalSize *= d; + } + for(usize d : outCompShape) + { + totalSize *= d; + } + + CalcBuffer result = (isLastOp && outputIsFloat64) ? CalcBuffer::wrapOutput(dataStructure.getDataRefAs>(outputPath)) : + CalcBuffer::allocate(tempDS, nextScratchName(), outTupleShape, outCompShape); + + bool leftIsScalar = left.isScalar(); + bool rightIsScalar = right.isScalar(); + + for(usize i = 0; i < totalSize; i++) + { + float64 lv = left.read(leftIsScalar ? 0 : i); + float64 rv = right.read(rightIsScalar ? 0 : i); + result.write(i, op->binaryOp(lv, rv)); + } + + if(leftIsScalar && rightIsScalar) + { + result.markAsScalar(); + } + // left and right destroyed here, RAII cleans up owned temps + evalStack.push(std::move(result)); + } + else + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidEquation), fmt::format("Internal error: operator '{}' has unsupported numArgs={}.", op->token, op->numArgs)); + } + break; + } + + case RpnItem::Type::ComponentExtract: { + if(evalStack.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for component extraction."); + } + CalcBuffer operand = std::move(evalStack.top()); + evalStack.pop(); + + usize numComps = operand.numComponents(); + usize numTuples = operand.numTuples(); + usize compIdx = rpnItem.componentIndex; + + if(compIdx >= numComps) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::ComponentOutOfRange), fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComps)); + } + + CalcBuffer result = (isLastOp && outputIsFloat64) ? CalcBuffer::wrapOutput(dataStructure.getDataRefAs>(outputPath)) : + CalcBuffer::allocate(tempDS, nextScratchName(), operand.tupleShape(), std::vector{1}); + + for(usize t = 0; t < numTuples; ++t) + { + result.write(t, operand.read(t * numComps + compIdx)); + } + + evalStack.push(std::move(result)); + break; + } + + case RpnItem::Type::TupleComponentExtract: { + if(evalStack.empty()) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for tuple+component extraction."); + } + CalcBuffer operand = std::move(evalStack.top()); + evalStack.pop(); + + usize numComps = operand.numComponents(); + usize numTuples = operand.numTuples(); + usize tupleIdx = rpnItem.tupleIndex; + usize compIdx = rpnItem.componentIndex; + + if(tupleIdx >= numTuples) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::TupleOutOfRange), fmt::format("Tuple index {} is out of range for array with {} tuples.", tupleIdx, numTuples)); + } + if(compIdx >= numComps) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::ComponentOutOfRange), fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComps)); + } + + float64 value = operand.read(tupleIdx * numComps + compIdx); + // operand destroyed, RAII cleans up + evalStack.push(CalcBuffer::scalar(tempDS, value, nextScratchName())); + break; } - rpnEquation.push_back(item); + } // end switch + } + + // 5. Final result + if(evalStack.size() != 1) + { + return MakeErrorResult(static_cast(CalculatorErrorCode::InvalidEquation), fmt::format("Internal error: evaluation stack has {} items remaining; expected exactly 1.", evalStack.size())); + } + + CalcBuffer finalResult = std::move(evalStack.top()); + evalStack.pop(); + + // 6. Copy/cast result into the output array (checked in order, first match wins) + if(finalResult.isScalar()) + { + // Fill entire output with the scalar value + float64 scalarVal = finalResult.read(0); + ExecuteDataFunction(CopyResultFunctor{}, outputDataType, dataStructure, outputPath, scalarVal); + } + else if(finalResult.isOutputDirect()) + { + // Data is already in the output array — nothing to do + } + else if(outputIsFloat64) + { + // Direct float64-to-float64 copy via operator[] (no type cast) + auto& outputArray = dataStructure.getDataRefAs>(outputPath); + usize totalSize = finalResult.size(); + for(usize i = 0; i < totalSize; i++) + { + outputArray[i] = finalResult.read(i); + } + } + else + { + // Type-casting copy via CopyResultFunctor + const Float64Array& resultArray = finalResult.array(); + ExecuteDataFunction(CopyResultFunctor{}, outputDataType, dataStructure, outputPath, &resultArray, false); } - delete oneComponent; + return parseResult; +} + +// --------------------------------------------------------------------------- +// ArrayCalculator +// --------------------------------------------------------------------------- +ArrayCalculator::ArrayCalculator(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, ArrayCalculatorInputValues* inputValues) +: m_DataStructure(dataStructure) +, m_InputValues(inputValues) +, m_ShouldCancel(shouldCancel) +, m_MessageHandler(mesgHandler) +{ +} + +// --------------------------------------------------------------------------- +ArrayCalculator::~ArrayCalculator() noexcept = default; + +// --------------------------------------------------------------------------- +const std::atomic_bool& ArrayCalculator::getCancel() +{ + return m_ShouldCancel; +} - return {std::move(rpnEquation)}; +// --------------------------------------------------------------------------- +Result<> ArrayCalculator::operator()() +{ + ArrayCalculatorParser parser(m_DataStructure, m_InputValues->SelectedGroup, m_InputValues->InfixEquation, m_ShouldCancel); + return parser.evaluateInto(m_DataStructure, m_InputValues->CalculatedArray, m_InputValues->ScalarType, m_InputValues->Units); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp index 26020a5abe..63674307b9 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp @@ -1,18 +1,269 @@ #pragma once #include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/CalculatorItem.hpp" -#include "simplnx/DataStructure/AttributeMatrix.hpp" +#include "simplnx/DataStructure/DataArray.hpp" #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/DataStructure/DataStructure.hpp" +#include "simplnx/DataStructure/IDataArray.hpp" #include "simplnx/Filter/IFilter.hpp" #include "simplnx/Parameters/CalculatorParameter.hpp" #include "simplnx/Parameters/NumericTypeParameter.hpp" +#include +#include +#include +#include +#include + namespace nx::core { +// --------------------------------------------------------------------------- +// Error codes preserved from the legacy CalculatorItem::ErrorCode enum +// --------------------------------------------------------------------------- +enum class CalculatorErrorCode : int +{ + Success = 0, + InvalidEquation = -4009, + InvalidComponent = -4010, + EmptyEquation = -4011, + EmptyCalArray = -4012, + EmptySelMatrix = -4013, + LostAttrMatrix = -4014, + IncorrectTupleCount = -4015, + InconsistentTuples = -4016, + UnrecognizedItem = -4017, + MismatchedParentheses = -4018, + UnexpectedOutput = -4019, + ComponentOutOfRange = -4020, + InvalidArrayName = -4022, + InconsistentIndexing = -4023, + InconsistentCompDims = -4024, + AttrArrayZeroTuplesWarning = -4025, + OrphanedComponent = -4026, + OperatorNoLeftValue = -4027, + OperatorNoRightValue = -4028, + OperatorNoOpeningParen = -4029, + OperatorNoClosingParen = -4030, + NoNumericArguments = -4031, + MissingArguments = -4032, + NotEnoughArguments = -4033, + TooManyArguments = -4034, + InvalidSymbol = -4035, + NoPrecedingUnaryOperator = -4036, + InvalidOutputArrayType = -4037, + AttributeMatrixInsertionError = -4038, + AmbiguousArrayName = -4039, + TupleOutOfRange = -4040 +}; + +// --------------------------------------------------------------------------- +// Warning codes preserved from the legacy CalculatorItem::WarningCode enum +// --------------------------------------------------------------------------- +enum class CalculatorWarningCode : int +{ + None = 0, + NumericValueWarning = -5010, + AmbiguousNameWarning = -5011 +}; + +// --------------------------------------------------------------------------- +// Lexer token types +// --------------------------------------------------------------------------- +enum class TokenType +{ + Number, + Identifier, + QuotedString, + Plus, + Minus, + Star, + Slash, + Caret, + Percent, + LParen, + RParen, + LBracket, + RBracket, + Comma +}; + +// --------------------------------------------------------------------------- +// A single token produced by the lexer +// --------------------------------------------------------------------------- +struct SIMPLNXCORE_EXPORT Token +{ + TokenType type; + std::string text; + size_t position = 0; +}; + +// --------------------------------------------------------------------------- +// Definition of an operator or function in the calculator language +// --------------------------------------------------------------------------- +struct SIMPLNXCORE_EXPORT OperatorDef +{ + std::string token; + + enum Kind + { + BinaryInfix, + Function, + UnaryPrefix + } kind; + + int precedence; + int numArgs; + + enum Associativity + { + Left, + Right + } associativity = Left; + + enum TrigMode + { + None, + ForwardTrig, + InverseTrig + } trigMode = None; + + std::function unaryOp; + std::function binaryOp; +}; + +// --------------------------------------------------------------------------- +// RAII sentinel for temporary Float64Arrays in the evaluator. +// Move-only. When an Owned CalcBuffer is destroyed, it removes its +// DataArray from the scratch DataStructure via removeData(). +// --------------------------------------------------------------------------- +class SIMPLNXCORE_EXPORT CalcBuffer +{ +public: + // --- Factory methods --- + + /** + * @brief Zero-copy reference to an existing Float64Array in the real DataStructure. + * Read-only. Destructor: no-op. + */ + static CalcBuffer borrow(const Float64Array& source); + + /** + * @brief Allocate a temp Float64Array in tempDS and convert source data from any numeric type. + * Owned. Destructor: removes the temp array from tempDS. + */ + static CalcBuffer convertFrom(DataStructure& tempDS, const IDataArray& source, const std::string& name); + + /** + * @brief Allocate a 1-element temp Float64Array with the given scalar value. + * Owned. Destructor: removes the temp array from tempDS. + */ + static CalcBuffer scalar(DataStructure& tempDS, float64 value, const std::string& name); + + /** + * @brief Allocate an empty temp Float64Array with the given shape. + * Owned. Destructor: removes the temp array from tempDS. + */ + static CalcBuffer allocate(DataStructure& tempDS, const std::string& name, std::vector tupleShape, std::vector compShape); + + /** + * @brief Wrap the output DataArray for direct writing. + * Not owned. Destructor: no-op. + */ + static CalcBuffer wrapOutput(DataArray& outputArray); + + // --- Move-only, non-copyable --- + CalcBuffer(CalcBuffer&& other) noexcept; + CalcBuffer& operator=(CalcBuffer&& other) noexcept; + ~CalcBuffer(); + + CalcBuffer(const CalcBuffer&) = delete; + CalcBuffer& operator=(const CalcBuffer&) = delete; + + // --- Element access --- + float64 read(usize index) const; + void write(usize index, float64 value); + void fill(float64 value); + + // --- Metadata --- + usize size() const; + usize numTuples() const; + usize numComponents() const; + std::vector tupleShape() const; + std::vector compShape() const; + bool isScalar() const; + bool isOwned() const; + bool isOutputDirect() const; + void markAsScalar(); + + // --- Access underlying array (for final copy to non-float64 output) --- + const Float64Array& array() const; + +private: + CalcBuffer() = default; + + enum class Storage + { + Borrowed, + Owned, + OutputDirect + }; + + Storage m_Storage = Storage::Owned; + + // Borrowed: const pointer to source Float64Array in real DataStructure + const Float64Array* m_BorrowedArray = nullptr; + + // Owned: pointer to temp Float64Array + reference to its DataStructure for cleanup + DataStructure* m_TempDS = nullptr; + DataObject::IdType m_ArrayId = 0; + Float64Array* m_OwnedArray = nullptr; + + // OutputDirect: writable pointer to output DataArray + DataArray* m_OutputArray = nullptr; + + bool m_IsScalar = false; +}; + +// --------------------------------------------------------------------------- +// A single item in the RPN (reverse-polish notation) evaluation sequence. +// Data-free: stores DataPath references and scalar values, not DataObject IDs. +// --------------------------------------------------------------------------- +struct SIMPLNXCORE_EXPORT RpnItem +{ + enum class Type + { + Scalar, + ArrayRef, + Operator, + ComponentExtract, + TupleComponentExtract + } type; + + // Scalar + float64 scalarValue = 0.0; + + // ArrayRef + DataPath arrayPath; + DataType sourceDataType = DataType::float64; + + // Operator + const OperatorDef* op = nullptr; + + // ComponentExtract / TupleComponentExtract + usize componentIndex = std::numeric_limits::max(); + usize tupleIndex = std::numeric_limits::max(); +}; + +// --------------------------------------------------------------------------- +// Returns the global table of all supported operators and functions +// --------------------------------------------------------------------------- +SIMPLNXCORE_EXPORT const std::vector& getOperatorRegistry(); + +// --------------------------------------------------------------------------- +// Input values passed from ArrayCalculatorFilter to the algorithm +// --------------------------------------------------------------------------- struct SIMPLNXCORE_EXPORT ArrayCalculatorInputValues { DataPath SelectedGroup; @@ -22,42 +273,62 @@ struct SIMPLNXCORE_EXPORT ArrayCalculatorInputValues DataPath CalculatedArray; }; +// --------------------------------------------------------------------------- +// Parses and validates an infix calculator equation, then evaluates it +// --------------------------------------------------------------------------- class SIMPLNXCORE_EXPORT ArrayCalculatorParser { public: - using ParsedEquation = std::vector; + ArrayCalculatorParser(const DataStructure& dataStructure, const DataPath& selectedGroupPath, const std::string& infixEquation, const std::atomic_bool& shouldCancel); + ~ArrayCalculatorParser() noexcept = default; - ArrayCalculatorParser(const DataStructure& dataStruct, const DataPath& selectedGroupPath, const std::string& infixEquation, bool isPreflight); + ArrayCalculatorParser(const ArrayCalculatorParser&) = delete; + ArrayCalculatorParser(ArrayCalculatorParser&&) noexcept = delete; + ArrayCalculatorParser& operator=(const ArrayCalculatorParser&) = delete; + ArrayCalculatorParser& operator=(ArrayCalculatorParser&&) noexcept = delete; - Result<> parseInfixEquation(ParsedEquation& parsedInfix); + /** + * @brief Tokenises, parses, and validates the infix equation. + * On success the output tuple and component shapes are written to the + * out-parameters so the filter can create the output array in preflight. + */ + Result<> parseAndValidate(std::vector& outTupleShape, std::vector& outComponentShape); - static Result ToRPN(const std::string& unparsedInfixExpression, std::vector infixEquation); + /** + * @brief Evaluates the already-parsed equation and writes the result into + * the output array at @p outputPath inside @p dataStructure. + */ + Result<> evaluateInto(DataStructure& dataStructure, const DataPath& outputPath, NumericType scalarType, CalculatorParameter::AngleUnits units); - friend class ArrayCalculator; - -protected: - std::vector getRegularExpressionMatches(); - Result<> parseNumericValue(std::string token, std::vector& parsedInfix, double number); - Result<> parseMinusSign(std::string token, std::vector& parsedInfix, int loopIdx); - Result<> parseIndexOperator(std::string token, std::vector& parsedInfix); - Result<> parseArray(std::string token, std::vector& parsedInfix); - Result<> checkForAmbiguousArrayName(const std::string& strItem, std::string warningMsg); + /** + * @brief Pure lexer -- splits an equation string into tokens. + */ + static std::vector tokenize(const std::string& equation); private: + /** + * @brief Runs the full parsing pipeline (tokenize, merge identifiers, + * resolve, bracket indexing, minus disambiguation, wrap function args, + * validate) and populates m_RpnItems. + */ + Result<> parse(); + const DataStructure& m_DataStructure; - DataStructure m_TemporaryDataStructure; // data structure for holding the temporary calculator array items DataPath m_SelectedGroupPath; std::string m_InfixEquation; - bool m_IsPreflight; - std::map> m_SymbolMap; + // Populated by parse(); consumed by evaluateInto() + std::vector m_RpnItems; - void createSymbolMap(); + // Shape info determined during validation + std::vector m_ParsedTupleShape; + std::vector m_ParsedComponentShape; + const std::atomic_bool& m_ShouldCancel; }; -/** - * @class - */ +// --------------------------------------------------------------------------- +// Top-level algorithm class invoked by ArrayCalculatorFilter::executeImpl() +// --------------------------------------------------------------------------- class SIMPLNXCORE_EXPORT ArrayCalculator { public: diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ArrayCalculatorFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ArrayCalculatorFilter.cpp index 3111f2dee3..e32d10c8a5 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ArrayCalculatorFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ArrayCalculatorFilter.cpp @@ -1,17 +1,15 @@ #include "ArrayCalculatorFilter.hpp" #include "SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp" -#include "SimplnxCore/utils/ICalculatorArray.hpp" #include "simplnx/Common/TypesUtility.hpp" +#include "simplnx/DataStructure/AttributeMatrix.hpp" #include "simplnx/DataStructure/DataPath.hpp" #include "simplnx/Filter/Actions/CreateArrayAction.hpp" #include "simplnx/Parameters/ArrayCreationParameter.hpp" #include "simplnx/Parameters/CalculatorParameter.hpp" - -#include "simplnx/Utilities/SIMPLConversion.hpp" - #include "simplnx/Parameters/NumericTypeParameter.hpp" +#include "simplnx/Utilities/SIMPLConversion.hpp" using namespace nx::core; @@ -65,7 +63,7 @@ Parameters ArrayCalculatorFilter::parameters() const //------------------------------------------------------------------------------ IFilter::VersionType ArrayCalculatorFilter::parametersVersion() const { - return 1; + return 2; } //------------------------------------------------------------------------------ @@ -81,135 +79,46 @@ IFilter::PreflightResult ArrayCalculatorFilter::preflightImpl(const DataStructur auto pInfixEquationValue = filterArgs.value(k_CalculatorParameter_Key); auto pScalarTypeValue = filterArgs.value(k_ScalarType_Key); auto pCalculatedArrayPath = filterArgs.value(k_CalculatedArray_Key); - - auto pSelectedGroupPath = pInfixEquationValue.m_SelectedGroup; auto outputGroupPath = pCalculatedArrayPath.getParent(); - PreflightResult preflightResult; nx::core::Result resultOutputActions; - std::vector preflightUpdatedValues; - - // parse the infix expression - ArrayCalculatorParser parser(dataStructure, pSelectedGroupPath, pInfixEquationValue.m_Equation, true); - std::vector parsedInfix; - Result<> parsedEquationResults = parser.parseInfixEquation(parsedInfix); - resultOutputActions.warnings() = parsedEquationResults.warnings(); - if(parsedEquationResults.invalid()) - { - return {nonstd::make_unexpected(parsedEquationResults.errors())}; - } - if(parsedInfix.empty()) - { - return MakePreflightErrorResult(-7760, "Error while parsing infix expression."); - } - - // check individual infix expression items for validity - for(int i = 0; i < parsedInfix.size(); i++) - { - CalculatorItem::Pointer calcItem = parsedInfix[i]; - std::string errMsg = ""; - CalculatorItem::ErrorCode err = calcItem->checkValidity(parsedInfix, i, errMsg); - int errInt = static_cast(err); - if(errInt < 0) - { - return MakePreflightErrorResult(errInt, errMsg); - } - } - // collect calculated array dimensions, check for consistent array component dimensions in infix expression & make sure it yields a numeric result + // Parse and validate the expression + const std::atomic_bool m_ShouldCancel(false); + ArrayCalculatorParser parser(dataStructure, pInfixEquationValue.m_SelectedGroup, pInfixEquationValue.m_Equation, m_ShouldCancel); std::vector calculatedTupleShape; std::vector calculatedComponentShape; - usize calculatedNumOfTuples = 0; - bool tupleShapesMatch = true; - ICalculatorArray::ValueType resultType = ICalculatorArray::ValueType::Unknown; + Result<> parseResult = parser.parseAndValidate(calculatedTupleShape, calculatedComponentShape); - // We only check that the arrays have consistent tuple counts and determine the tuple shape based on whether all arrays have - // matching tuple shapes or not. We DO NOT take into account the operator at all; we assume that all operators that input an array - // also output an array of the same tuple size. Adding operators that can take in an array and output an array of a different size - // (like finding the minimum or maximum of a single array) will require a significant redesign of this filter since determining the - // final output tuple size/shape from an infix equation with those types of operators in it will be significantly more complicated. - for(const auto& item1 : parsedInfix) - { - if(item1->isICalculatorArray()) - { - ICalculatorArray::Pointer array1 = std::dynamic_pointer_cast(item1); - auto tupleShape = array1->getArray()->getTupleShape(); - auto compShape = array1->getArray()->getComponentShape(); - auto numTuples = array1->getArray()->getNumberOfTuples(); - if(item1->isArray()) - { - if(resultType == ICalculatorArray::ValueType::Array) - { - if(!calculatedComponentShape.empty() && calculatedComponentShape != array1->getArray()->getComponentShape()) - { - return MakePreflightErrorResult(static_cast(CalculatorItem::ErrorCode::InconsistentCompDims), - fmt::format("Attribute Array '{}' has component dimensions {} which do not match the previously encountered component dimensions {} in the expression.", - array1->getArray()->getName(), fmt::join(array1->getArray()->getComponentShape(), "x"), fmt::join(calculatedComponentShape, "x"))); - } - if(!calculatedTupleShape.empty() && calculatedNumOfTuples != array1->getArray()->getNumberOfTuples()) - { - return MakePreflightErrorResult(static_cast(CalculatorItem::ErrorCode::InconsistentTuples), - fmt::format("Attribute Array '{}' has {} tuples which does not match the previously encountered tuple count of {} in the expression.", - array1->getArray()->getName(), array1->getArray()->getNumberOfTuples(), calculatedNumOfTuples)); - } - if(!calculatedTupleShape.empty() && calculatedTupleShape != tupleShape) - { - tupleShapesMatch = false; - } - } + // Transfer warnings + resultOutputActions.warnings() = parseResult.warnings(); - resultType = ICalculatorArray::ValueType::Array; - calculatedComponentShape = compShape; - calculatedTupleShape = tupleShape; - calculatedNumOfTuples = numTuples; - } - else if(resultType == ICalculatorArray::ValueType::Unknown) - { - resultType = ICalculatorArray::ValueType::Number; - calculatedComponentShape = array1->getArray()->getComponentShape(); - calculatedTupleShape = {array1->getArray()->getNumberOfTuples()}; - } - } - } - if(resultType == ICalculatorArray::ValueType::Unknown) + if(parseResult.invalid()) { - return MakePreflightErrorResult(static_cast(CalculatorItem::ErrorCode::NoNumericArguments), "The expression does not have any arguments that simplify down to a number."); + return {nonstd::make_unexpected(parseResult.errors())}; } - if(resultType == ICalculatorArray::ValueType::Number) + // If the result is a scalar (1 tuple) and the output is in an AttributeMatrix, + // use the AttributeMatrix's shape instead + if(calculatedTupleShape.size() == 1 && calculatedTupleShape[0] == 1) { if(const auto* attributeMatrix = dataStructure.getDataAs(outputGroupPath); attributeMatrix != nullptr) { calculatedTupleShape = attributeMatrix->getShape(); } } - else if(!tupleShapesMatch) - { - calculatedTupleShape = {calculatedNumOfTuples}; - } - - // convert to postfix notation - Result rpnResults = ArrayCalculatorParser::ToRPN(pInfixEquationValue.m_Equation, parsedInfix); - std::vector rpn = rpnResults.value(); - if(rpnResults.invalid() || rpn.empty()) - { - return MakePreflightErrorResult(-7761, "Error while converting parsed infix expression to postfix notation"); - } - // create the destination array for the calculated results - { - auto createArrayAction = std::make_unique(ConvertNumericTypeToDataType(pScalarTypeValue), calculatedTupleShape, calculatedComponentShape, pCalculatedArrayPath); - resultOutputActions.value().appendAction(std::move(createArrayAction)); - } + // Create the output array + auto createArrayAction = std::make_unique(ConvertNumericTypeToDataType(pScalarTypeValue), calculatedTupleShape, calculatedComponentShape, pCalculatedArrayPath); + resultOutputActions.value().appendAction(std::move(createArrayAction)); - return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; -} // namespace nx::core + return {std::move(resultOutputActions)}; +} //------------------------------------------------------------------------------ Result<> ArrayCalculatorFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel, const ExecutionContext& executionContext) const { - ArrayCalculatorInputValues inputValues; auto pInfixEquationValue = filterArgs.value(k_CalculatorParameter_Key); inputValues.InfixEquation = pInfixEquationValue.m_Equation; diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ABSOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ABSOperator.cpp deleted file mode 100644 index 41b5b3bd31..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ABSOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/ABSOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ABSOperator::ABSOperator() -{ - setNumberOfArguments(1); - setInfixToken("abs"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ABSOperator::~ABSOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void ABSOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayStandardUnary(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return fabs(num); }); -} - -// ----------------------------------------------------------------------------- -ABSOperator::Pointer ABSOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ABSOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ABSOperator.hpp deleted file mode 100644 index 1048bb44db..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ABSOperator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT ABSOperator : public UnaryOperator -{ -public: - using Self = ABSOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new ABSOperator()); - } - - ~ABSOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - ABSOperator(); - -public: - ABSOperator(const ABSOperator&) = delete; // Copy Constructor Not Implemented - ABSOperator(ABSOperator&&) = delete; // Move Constructor Not Implemented - ABSOperator& operator=(const ABSOperator&) = delete; // Copy Assignment Not Implemented - ABSOperator& operator=(ABSOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ACosOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ACosOperator.cpp deleted file mode 100644 index 0cd8872ba1..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ACosOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/ACosOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ACosOperator::ACosOperator() -{ - setNumberOfArguments(1); - setInfixToken("acos"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ACosOperator::~ACosOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void ACosOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayArcTrig(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return acos(num); }); -} - -// ----------------------------------------------------------------------------- -ACosOperator::Pointer ACosOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ACosOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ACosOperator.hpp deleted file mode 100644 index 89e7a3e554..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ACosOperator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT ACosOperator : public UnaryOperator -{ -public: - using Self = ACosOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new ACosOperator()); - } - - ~ACosOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - ACosOperator(); - -public: - ACosOperator(const ACosOperator&) = delete; // Copy Constructor Not Implemented - ACosOperator(ACosOperator&&) = delete; // Move Constructor Not Implemented - ACosOperator& operator=(const ACosOperator&) = delete; // Copy Assignment Not Implemented - ACosOperator& operator=(ACosOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ASinOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ASinOperator.cpp deleted file mode 100644 index 924e62d393..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ASinOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/ASinOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ASinOperator::ASinOperator() -{ - setNumberOfArguments(1); - setInfixToken("asin"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ASinOperator::~ASinOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void ASinOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayArcTrig(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return asin(num); }); -} - -// ----------------------------------------------------------------------------- -ASinOperator::Pointer ASinOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ASinOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ASinOperator.hpp deleted file mode 100644 index 869dc07c9c..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ASinOperator.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ - -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT ASinOperator : public UnaryOperator -{ -public: - using Self = ASinOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new ASinOperator()); - } - - ~ASinOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - ASinOperator(); - -public: - ASinOperator(const ASinOperator&) = delete; // Copy Constructor Not Implemented - ASinOperator(ASinOperator&&) = delete; // Move Constructor Not Implemented - ASinOperator& operator=(const ASinOperator&) = delete; // Copy Assignment Not Implemented - ASinOperator& operator=(ASinOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ATanOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ATanOperator.cpp deleted file mode 100644 index 29e9dc8bd4..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ATanOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/ATanOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ATanOperator::ATanOperator() -{ - setNumberOfArguments(1); - setInfixToken("atan"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ATanOperator::~ATanOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void ATanOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayArcTrig(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return atan(num); }); -} - -// ----------------------------------------------------------------------------- -ATanOperator::Pointer ATanOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ATanOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ATanOperator.hpp deleted file mode 100644 index 71690e142f..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ATanOperator.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ - -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT ATanOperator : public UnaryOperator -{ -public: - using Self = ATanOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new ATanOperator()); - } - - ~ATanOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - ATanOperator(); - -public: - ATanOperator(const ATanOperator&) = delete; // Copy Constructor Not Implemented - ATanOperator(ATanOperator&&) = delete; // Move Constructor Not Implemented - ATanOperator& operator=(const ATanOperator&) = delete; // Copy Assignment Not Implemented - ATanOperator& operator=(ATanOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core \ No newline at end of file diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/AdditionOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/AdditionOperator.cpp deleted file mode 100644 index 759f0fafc6..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/AdditionOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/AdditionOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -AdditionOperator::AdditionOperator() -{ - setPrecedence(A_Precedence); - setInfixToken("+"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -AdditionOperator::~AdditionOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void AdditionOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [](double num1, double num2) -> double { return num1 + num2; }); -} - -// ----------------------------------------------------------------------------- -AdditionOperator::Pointer AdditionOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/AdditionOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/AdditionOperator.hpp deleted file mode 100644 index 63ebf83833..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/AdditionOperator.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/BinaryOperator.hpp" - -namespace nx::core -{ - -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT AdditionOperator : public BinaryOperator -{ -public: - using Self = AdditionOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new AdditionOperator()); - } - - ~AdditionOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - AdditionOperator(); - -public: - AdditionOperator(const AdditionOperator&) = delete; // Copy Constructor Not Implemented - AdditionOperator(AdditionOperator&&) = delete; // Move Constructor Not Implemented - AdditionOperator& operator=(const AdditionOperator&) = delete; // Copy Assignment Not Implemented - AdditionOperator& operator=(AdditionOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/BinaryOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/BinaryOperator.cpp deleted file mode 100644 index fde52052c1..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/BinaryOperator.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "SimplnxCore/utils/BinaryOperator.hpp" -#include "SimplnxCore/utils/LeftParenthesisItem.hpp" -#include "SimplnxCore/utils/RightParenthesisItem.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -BinaryOperator::BinaryOperator() -{ - setOperatorType(Binary); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -BinaryOperator::~BinaryOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void BinaryOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - // This should never be executed -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CalculatorItem::ErrorCode BinaryOperator::checkValidity(std::vector infixVector, int currentIndex, std::string& errMsg) -{ - int leftValue = currentIndex - 1; - int rightValue = currentIndex + 1; - - // Check that there is a valid value to the left of the operator - if(leftValue < 0 || - (nullptr != std::dynamic_pointer_cast(infixVector[leftValue]) && std::dynamic_pointer_cast(infixVector[leftValue])->getOperatorType() == Binary) || - nullptr != std::dynamic_pointer_cast(infixVector[leftValue])) - { - errMsg = fmt::format("The operator '{}' does not have a valid 'left' value.", getInfixToken()); - return CalculatorItem::ErrorCode::OperatorNoLeftValue; - } - - // Check that there is a valid value to the right of the operator - if(rightValue > infixVector.size() - 1 || - (nullptr != std::dynamic_pointer_cast(infixVector[rightValue]) && std::dynamic_pointer_cast(infixVector[rightValue])->getOperatorType() == Binary) || - nullptr != std::dynamic_pointer_cast(infixVector[rightValue])) - { - errMsg = fmt::format("The operator '{}' does not have a valid 'right' value.", getInfixToken()); - return CalculatorItem::ErrorCode::OperatorNoRightValue; - } - - return CalculatorItem::ErrorCode::Success; -} - -// ----------------------------------------------------------------------------- -BinaryOperator::Pointer BinaryOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/BinaryOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/BinaryOperator.hpp deleted file mode 100644 index 8f8b1fe93a..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/BinaryOperator.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "SimplnxCore/utils/CalculatorOperator.hpp" - -#include "simplnx/DataStructure/DataPath.hpp" - -#include -#include - -namespace nx::core -{ - -class SIMPLNXCORE_EXPORT BinaryOperator : public CalculatorOperator -{ -public: - using Self = BinaryOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new BinaryOperator()); - } - - ~BinaryOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - - CalculatorItem::ErrorCode checkValidity(std::vector infixVector, int currentIndex, std::string& msg) final; - -protected: - BinaryOperator(); - -public: - BinaryOperator(const BinaryOperator&) = delete; // Copy Constructor Not Implemented - BinaryOperator(BinaryOperator&&) = delete; // Move Constructor Not Implemented - BinaryOperator& operator=(const BinaryOperator&) = delete; // Copy Assignment Not Implemented - BinaryOperator& operator=(BinaryOperator&&) = delete; // Move Assignment Not Implemented -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CalculatorOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CalculatorOperator.cpp deleted file mode 100644 index b2b74d0100..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CalculatorOperator.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "SimplnxCore/utils/CalculatorOperator.hpp" - -#include "simplnx/Common/Numbers.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CalculatorOperator::CalculatorOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CalculatorOperator::~CalculatorOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -bool CalculatorOperator::hasHigherPrecedence(const CalculatorOperator::Pointer other) -{ - return m_Precedence > other->m_Precedence; -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CalculatorOperator::Precedence CalculatorOperator::getPrecedence() -{ - return m_Precedence; -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void CalculatorOperator::setPrecedence(Precedence precedence) -{ - m_Precedence = precedence; -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CalculatorOperator::OperatorType CalculatorOperator::getOperatorType() -{ - return m_OperatorType; -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void CalculatorOperator::setOperatorType(OperatorType type) -{ - m_OperatorType = type; -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -double CalculatorOperator::toDegrees(double radians) -{ - return radians * (180.0 / numbers::pi); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -double CalculatorOperator::toRadians(double degrees) -{ - return degrees * (numbers::pi / 180.0); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -double CalculatorOperator::root(double base, double root) -{ - if(root == 0) - { - return std::numeric_limits::infinity(); - } - - return pow(base, 1 / root); -} - -// ----------------------------------------------------------------------------- -CalculatorOperator::Pointer CalculatorOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} - -// ----------------------------------------------------------------------------- -void CalculatorOperator::CreateNewArrayTwoArguments(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, - std::stack& executionStack, std::function op) -{ - ICalculatorArray::Pointer iArray1 = executionStack.top(); - if(executionStack.size() >= 2 && nullptr != iArray1) - { - executionStack.pop(); - ICalculatorArray::Pointer iArray2 = executionStack.top(); - executionStack.pop(); - - calculatedArrayPath = GetUniquePathName(dataStructure, calculatedArrayPath); - - DataArray* array1 = iArray1->getArray(); - DataArray* array2 = iArray2->getArray(); - - Float64Array* newArray = nullptr; - if(iArray1->getType() == ICalculatorArray::Array) - { - newArray = Float64Array::CreateWithStore(dataStructure, calculatedArrayPath.getTargetName(), array1->getTupleShape(), array1->getComponentShape()); - } - else - { - newArray = Float64Array::CreateWithStore(dataStructure, calculatedArrayPath.getTargetName(), array2->getTupleShape(), array2->getComponentShape()); - } - - usize numComps = newArray->getNumberOfComponents(); - for(usize i = 0; i < newArray->getNumberOfTuples(); i++) - { - for(usize c = 0; c < newArray->getNumberOfComponents(); c++) - { - usize index = numComps * i + c; - float64 num1 = (iArray1->getType() == ICalculatorArray::Array) ? array1->getValue(index) : array1->getValue(0); - float64 num2 = (iArray2->getType() == ICalculatorArray::Array) ? array2->getValue(index) : array2->getValue(0); - (*newArray)[index] = op(num2, num1); - } - } - - if(iArray1->getType() == ICalculatorArray::Array || iArray2->getType() == ICalculatorArray::Array) - { - executionStack.push(CalculatorArray::New(dataStructure, newArray, ICalculatorArray::Array, true)); - } - else - { - executionStack.push(CalculatorArray::New(dataStructure, newArray, ICalculatorArray::Number, true)); - } - return; - } -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CalculatorOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CalculatorOperator.hpp deleted file mode 100644 index 21b1f4db31..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CalculatorOperator.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -#include "simplnx/DataStructure/DataArray.hpp" -#include "simplnx/DataStructure/DataPath.hpp" -#include "simplnx/Parameters/CalculatorParameter.hpp" - -#include -#include - -namespace nx::core -{ - -class SIMPLNXCORE_EXPORT CalculatorOperator : public CalculatorItem -{ -public: - enum OperatorType - { - Unary, - Binary - }; - - using Self = CalculatorOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static double toDegrees(double radians); - static double toRadians(double degrees); - - ~CalculatorOperator() override; - - bool hasHigherPrecedence(CalculatorOperator::Pointer other); - - virtual void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) = 0; - - OperatorType getOperatorType(); - -protected: - CalculatorOperator(); - - enum Precedence - { - Unknown_Precedence, - A_Precedence, - B_Precedence, - C_Precedence, - D_Precedence, - E_Precedence - }; - - double root(double base, double root); - - Precedence getPrecedence(); - void setPrecedence(Precedence precedence); - - void setOperatorType(OperatorType type); - -private: - Precedence m_Precedence = {Unknown_Precedence}; - OperatorType m_OperatorType; - -public: - CalculatorOperator(const CalculatorOperator&) = delete; // Copy Constructor Not Implemented - CalculatorOperator(CalculatorOperator&&) = delete; // Move Constructor Not Implemented - CalculatorOperator& operator=(const CalculatorOperator&) = delete; // Copy Assignment Not Implemented - CalculatorOperator& operator=(CalculatorOperator&&) = delete; // Move Assignment Not Implemented - - static void CreateNewArrayTwoArguments(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack, - std::function op); -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CeilOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CeilOperator.cpp deleted file mode 100644 index c06d6ed3b7..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CeilOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/CeilOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CeilOperator::CeilOperator() -{ - setNumberOfArguments(1); - setInfixToken("ceil"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CeilOperator::~CeilOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void CeilOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayStandardUnary(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return ceil(num); }); -} - -// ----------------------------------------------------------------------------- -CeilOperator::Pointer CeilOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CeilOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CeilOperator.hpp deleted file mode 100644 index 3e46d8df8c..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CeilOperator.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT CeilOperator : public UnaryOperator -{ -public: - using Self = CeilOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new CeilOperator()); - } - - ~CeilOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - CeilOperator(); - -public: - CeilOperator(const CeilOperator&) = delete; // Copy Constructor Not Implemented - CeilOperator(CeilOperator&&) = delete; // Move Constructor Not Implemented - CeilOperator& operator=(const CeilOperator&) = delete; // Copy Assignment Not Implemented - CeilOperator& operator=(CeilOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CommaSeparator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CommaSeparator.cpp deleted file mode 100644 index 57bb32cc06..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CommaSeparator.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "SimplnxCore/utils/CommaSeparator.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CommaSeparator::CommaSeparator() -{ - setInfixToken(","); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CommaSeparator::~CommaSeparator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CalculatorItem::ErrorCode CommaSeparator::checkValidity(std::vector infixVector, int currentIndex, std::string& errMsg) -{ - // Make sure that this comma has a valid 2-argument unary operator before it - bool foundUnaryOperator = false; - for(int i = currentIndex - 1; i >= 0; i--) - { - CalculatorItem::Pointer item = infixVector[i]; - UnaryOperator::Pointer unary = std::dynamic_pointer_cast(item); - if(unary != UnaryOperator::NullPointer() && unary->getNumberOfArguments() == 2) - { - foundUnaryOperator = true; - } - } - - if(!foundUnaryOperator) - { - errMsg = "A comma in the expression does not have a corresponding operator preceding it."; - return CalculatorItem::ErrorCode::NoPrecedingUnaryOperator; - } - - return CalculatorItem::ErrorCode::Success; -} - -// ----------------------------------------------------------------------------- -CommaSeparator::Pointer CommaSeparator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CommaSeparator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CommaSeparator.hpp deleted file mode 100644 index c58deb63f4..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CommaSeparator.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/CalculatorSeparator.hpp" - -namespace nx::core -{ -class SIMPLNXCORE_EXPORT CommaSeparator : public CalculatorSeparator -{ -public: - using Self = CommaSeparator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new CommaSeparator()); - } - - ~CommaSeparator() override; - - CalculatorItem::ErrorCode checkValidity(std::vector infixVector, int currentIndex, std::string& msg) override; - -protected: - CommaSeparator(); - -public: - CommaSeparator(const CommaSeparator&) = delete; // Copy Constructor Not Implemented - CommaSeparator(CommaSeparator&&) = delete; // Move Constructor Not Implemented - CommaSeparator& operator=(const CommaSeparator&) = delete; // Copy Assignment Not Implemented - CommaSeparator& operator=(CommaSeparator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CosOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CosOperator.cpp deleted file mode 100644 index 9638355e80..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CosOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/CosOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CosOperator::CosOperator() -{ - setNumberOfArguments(1); - setInfixToken("cos"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CosOperator::~CosOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void CosOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTrig(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return cos(num); }); -} - -// ----------------------------------------------------------------------------- -CosOperator::Pointer CosOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CosOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CosOperator.hpp deleted file mode 100644 index 4f94fcf2a7..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/CosOperator.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT CosOperator : public UnaryOperator -{ -public: - using Self = CosOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new CosOperator()); - } - - ~CosOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - CosOperator(); - -public: - CosOperator(const CosOperator&) = delete; // Copy Constructor Not Implemented - CosOperator(CosOperator&&) = delete; // Move Constructor Not Implemented - CosOperator& operator=(const CosOperator&) = delete; // Copy Assignment Not Implemented - CosOperator& operator=(CosOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/DivisionOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/DivisionOperator.cpp deleted file mode 100644 index 682d3b1333..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/DivisionOperator.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "SimplnxCore/utils/DivisionOperator.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -DivisionOperator::DivisionOperator() -{ - setPrecedence(B_Precedence); - setInfixToken("/"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -DivisionOperator::~DivisionOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void DivisionOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [](double num1, double num2) -> double { return num1 / num2; }); -} - -// ----------------------------------------------------------------------------- -DivisionOperator::Pointer DivisionOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/DivisionOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/DivisionOperator.hpp deleted file mode 100644 index d9256a6793..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/DivisionOperator.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/BinaryOperator.hpp" - -namespace nx::core -{ -class SIMPLNXCORE_EXPORT DivisionOperator : public BinaryOperator -{ -public: - using Self = DivisionOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new DivisionOperator()); - } - - ~DivisionOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - DivisionOperator(); - -public: - DivisionOperator(const DivisionOperator&) = delete; // Copy Constructor Not Implemented - DivisionOperator(DivisionOperator&&) = delete; // Move Constructor Not Implemented - DivisionOperator& operator=(const DivisionOperator&) = delete; // Copy Assignment Not Implemented - DivisionOperator& operator=(DivisionOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ExpOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ExpOperator.cpp deleted file mode 100644 index 216c202041..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ExpOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/ExpOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ExpOperator::ExpOperator() -{ - setNumberOfArguments(1); - setInfixToken("exp"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -ExpOperator::~ExpOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void ExpOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayStandardUnary(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return exp(num); }); -} - -// ----------------------------------------------------------------------------- -ExpOperator::Pointer ExpOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ExpOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ExpOperator.hpp deleted file mode 100644 index 73d718e92c..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/ExpOperator.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT ExpOperator : public UnaryOperator -{ -public: - using Self = ExpOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new ExpOperator()); - } - - ~ExpOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - ExpOperator(); - -public: - ExpOperator(const ExpOperator&) = delete; // Copy Constructor Not Implemented - ExpOperator(ExpOperator&&) = delete; // Move Constructor Not Implemented - ExpOperator& operator=(const ExpOperator&) = delete; // Copy Assignment Not Implemented - ExpOperator& operator=(ExpOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/FloorOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/FloorOperator.cpp deleted file mode 100644 index 6b6680f757..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/FloorOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/FloorOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -FloorOperator::FloorOperator() -{ - setNumberOfArguments(1); - setInfixToken("floor"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -FloorOperator::~FloorOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void FloorOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayStandardUnary(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return floor(num); }); -} - -// ----------------------------------------------------------------------------- -FloorOperator::Pointer FloorOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/FloorOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/FloorOperator.hpp deleted file mode 100644 index 93e935af96..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/FloorOperator.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT FloorOperator : public UnaryOperator -{ -public: - using Self = FloorOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new FloorOperator()); - } - - ~FloorOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - FloorOperator(); - -public: - FloorOperator(const FloorOperator&) = delete; // Copy Constructor Not Implemented - FloorOperator(FloorOperator&&) = delete; // Move Constructor Not Implemented - FloorOperator& operator=(const FloorOperator&) = delete; // Copy Assignment Not Implemented - FloorOperator& operator=(FloorOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LnOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LnOperator.cpp deleted file mode 100644 index 473c654367..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LnOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/LnOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -LnOperator::LnOperator() -{ - setNumberOfArguments(1); - setInfixToken("ln"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -LnOperator::~LnOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void LnOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayStandardUnary(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return log(num); }); -} - -// ----------------------------------------------------------------------------- -LnOperator::Pointer LnOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LnOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LnOperator.hpp deleted file mode 100644 index 992985eb04..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LnOperator.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT LnOperator : public UnaryOperator -{ -public: - using Self = LnOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new LnOperator()); - } - - ~LnOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - LnOperator(); - -public: - LnOperator(const LnOperator&) = delete; // Copy Constructor Not Implemented - LnOperator(LnOperator&&) = delete; // Move Constructor Not Implemented - LnOperator& operator=(const LnOperator&) = delete; // Copy Assignment Not Implemented - LnOperator& operator=(LnOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/Log10Operator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/Log10Operator.cpp deleted file mode 100644 index 64413029e6..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/Log10Operator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/Log10Operator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -Log10Operator::Log10Operator() -{ - setNumberOfArguments(1); - setInfixToken("log10"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -Log10Operator::~Log10Operator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void Log10Operator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayStandardUnary(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return log10(num); }); -} - -// ----------------------------------------------------------------------------- -Log10Operator::Pointer Log10Operator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/Log10Operator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/Log10Operator.hpp deleted file mode 100644 index ab053ffa10..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/Log10Operator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT Log10Operator : public UnaryOperator -{ -public: - using Self = Log10Operator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new Log10Operator()); - } - - ~Log10Operator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - Log10Operator(); - -public: - Log10Operator(const Log10Operator&) = delete; // Copy Constructor Not Implemented - Log10Operator(Log10Operator&&) = delete; // Move Constructor Not Implemented - Log10Operator& operator=(const Log10Operator&) = delete; // Copy Assignment Not Implemented - Log10Operator& operator=(Log10Operator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LogOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LogOperator.cpp deleted file mode 100644 index 3d2ed4d006..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LogOperator.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "SimplnxCore/utils/LogOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -LogOperator::LogOperator() -{ - setNumberOfArguments(2); - setInfixToken("log"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -LogOperator::~LogOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void LogOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [this](double num1, double num2) -> double { return log_arbitrary_base(num1, num2); }); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -double LogOperator::log_arbitrary_base(double base, double value) -{ - return log(value) / log(base); -} - -// ----------------------------------------------------------------------------- -LogOperator::Pointer LogOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LogOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LogOperator.hpp deleted file mode 100644 index 9409f158f7..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/LogOperator.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT LogOperator : public UnaryOperator -{ -public: - using Self = LogOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new LogOperator()); - } - - ~LogOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - LogOperator(); - -private: - double log_arbitrary_base(double base, double value); - -public: - LogOperator(const LogOperator&) = delete; // Copy Constructor Not Implemented - LogOperator(LogOperator&&) = delete; // Move Constructor Not Implemented - LogOperator& operator=(const LogOperator&) = delete; // Copy Assignment Not Implemented - LogOperator& operator=(LogOperator&&) = delete; // Move Assignment Not Implemented -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MaxOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MaxOperator.cpp deleted file mode 100644 index 3e230d9d9c..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MaxOperator.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "SimplnxCore/utils/MaxOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; -using namespace std; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -MaxOperator::MaxOperator() -{ - setNumberOfArguments(2); - setInfixToken("max"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -MaxOperator::~MaxOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void MaxOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [](double num1, double num2) -> double { return max(num1, num2); }); -} - -// ----------------------------------------------------------------------------- -MaxOperator::Pointer MaxOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MaxOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MaxOperator.hpp deleted file mode 100644 index 0b7431f91c..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MaxOperator.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT MaxOperator : public UnaryOperator -{ -public: - using Self = MaxOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new MaxOperator()); - } - - ~MaxOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - MaxOperator(); - -public: - MaxOperator(const MaxOperator&) = delete; // Copy Constructor Not Implemented - MaxOperator(MaxOperator&&) = delete; // Move Constructor Not Implemented - MaxOperator& operator=(const MaxOperator&) = delete; // Copy Assignment Not Implemented - MaxOperator& operator=(MaxOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MinOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MinOperator.cpp deleted file mode 100644 index f6221cd6c4..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MinOperator.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "SimplnxCore/utils/MinOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; -using namespace std; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -MinOperator::MinOperator() -{ - setNumberOfArguments(2); - setInfixToken("min"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -MinOperator::~MinOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void MinOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [](double num1, double num2) -> double { return min(num1, num2); }); -} - -// ----------------------------------------------------------------------------- -MinOperator::Pointer MinOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MinOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MinOperator.hpp deleted file mode 100644 index 15ff177642..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MinOperator.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT MinOperator : public UnaryOperator -{ -public: - using Self = MinOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new MinOperator()); - } - - ~MinOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - MinOperator(); - -public: - MinOperator(const MinOperator&) = delete; // Copy Constructor Not Implemented - MinOperator(MinOperator&&) = delete; // Move Constructor Not Implemented - MinOperator& operator=(const MinOperator&) = delete; // Copy Assignment Not Implemented - MinOperator& operator=(MinOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MultiplicationOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MultiplicationOperator.cpp deleted file mode 100644 index dc9bf707de..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MultiplicationOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/MultiplicationOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -MultiplicationOperator::MultiplicationOperator() -{ - setPrecedence(B_Precedence); - setInfixToken("*"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -MultiplicationOperator::~MultiplicationOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void MultiplicationOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [](double num1, double num2) -> double { return num1 * num2; }); -} - -// ----------------------------------------------------------------------------- -MultiplicationOperator::Pointer MultiplicationOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MultiplicationOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MultiplicationOperator.hpp deleted file mode 100644 index 1d32db39af..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/MultiplicationOperator.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/BinaryOperator.hpp" - -namespace nx::core -{ -class SIMPLNXCORE_EXPORT MultiplicationOperator : public BinaryOperator -{ -public: - using Self = MultiplicationOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new MultiplicationOperator()); - } - - ~MultiplicationOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - MultiplicationOperator(); - -public: - MultiplicationOperator(const MultiplicationOperator&) = delete; // Copy Constructor Not Implemented - MultiplicationOperator(MultiplicationOperator&&) = delete; // Move Constructor Not Implemented - MultiplicationOperator& operator=(const MultiplicationOperator&) = delete; // Copy Assignment Not Implemented - MultiplicationOperator& operator=(MultiplicationOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/NegativeOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/NegativeOperator.cpp deleted file mode 100644 index ddf23f2a95..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/NegativeOperator.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "SimplnxCore/utils/NegativeOperator.hpp" -#include "SimplnxCore/utils/BinaryOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" -#include "SimplnxCore/utils/LeftParenthesisItem.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -NegativeOperator::NegativeOperator() -{ - setOperatorType(Unary); - setPrecedence(D_Precedence); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -NegativeOperator::~NegativeOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void NegativeOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - if(!executionStack.empty()) - { - ICalculatorArray::Pointer arrayPtr = executionStack.top(); - executionStack.pop(); - calculatedArrayPath = GetUniquePathName(dataStructure, calculatedArrayPath); - - DataArray* array = arrayPtr->getArray(); - - Float64Array* newArray = Float64Array::CreateWithStore(dataStructure, calculatedArrayPath.getTargetName(), array->getTupleShape(), array->getComponentShape()); - - usize numComps = newArray->getNumberOfComponents(); - for(usize i = 0; i < newArray->getNumberOfTuples(); i++) - { - for(usize c = 0; c < newArray->getNumberOfComponents(); c++) - { - usize index = numComps * i + c; - double num = array->getValue(index); - (*newArray)[index] = -1 * num; - } - } - - executionStack.push(CalculatorArray::New(dataStructure, newArray, arrayPtr->getType(), true)); - return; - } -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CalculatorItem::ErrorCode NegativeOperator::checkValidity(std::vector infixVector, int currentIndex, std::string& errMsg) -{ - if(currentIndex - 1 >= 0 && (std::dynamic_pointer_cast(infixVector[currentIndex - 1]) == BinaryOperator::NullPointer() && - std::dynamic_pointer_cast(infixVector[currentIndex - 1]) == LeftParenthesisItem::NullPointer())) - { - // The symbol to the left of the negative sign is not a binary operator or left parenthesis - errMsg = fmt::format("The negative operator '{}' does not have a valid 'left' value.", getInfixToken()); - return NegativeOperator::ErrorCode::OperatorNoLeftValue; - } - if(currentIndex + 1 >= infixVector.size() || (currentIndex + 1 < infixVector.size() && (nullptr == std::dynamic_pointer_cast(infixVector[currentIndex + 1]) && - nullptr == std::dynamic_pointer_cast(infixVector[currentIndex + 1]) && - nullptr == std::dynamic_pointer_cast(infixVector[currentIndex + 1])))) - { - // The symbol to the right of the negative sign is not an array, left parenthesis, or unary operator - errMsg = fmt::format("The negative operator '{}' does not have a valid 'right' value.", getInfixToken()); - return NegativeOperator::ErrorCode::OperatorNoRightValue; - } - - return NegativeOperator::ErrorCode::Success; -} - -// ----------------------------------------------------------------------------- -NegativeOperator::Pointer NegativeOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/NegativeOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/NegativeOperator.hpp deleted file mode 100644 index d0d7419e2f..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/NegativeOperator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class SIMPLNXCORE_EXPORT NegativeOperator : public CalculatorOperator -{ -public: - using Self = NegativeOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new NegativeOperator()); - } - - ~NegativeOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - - CalculatorItem::ErrorCode checkValidity(std::vector infixVector, int currentIndex, std::string& errMsg) final; - -protected: - NegativeOperator(); - -public: - NegativeOperator(const NegativeOperator&) = delete; // Copy Constructor Not Implemented - NegativeOperator(NegativeOperator&&) = delete; // Move Constructor Not Implemented - NegativeOperator& operator=(const NegativeOperator&) = delete; // Copy Assignment Not Implemented - NegativeOperator& operator=(NegativeOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PowOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PowOperator.cpp deleted file mode 100644 index 3a6f96cacf..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PowOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/PowOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -PowOperator::PowOperator() -{ - setPrecedence(C_Precedence); - setInfixToken("^"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -PowOperator::~PowOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void PowOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [](double num1, double num2) -> double { return pow(num1, num2); }); -} - -// ----------------------------------------------------------------------------- -PowOperator::Pointer PowOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PowOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PowOperator.hpp deleted file mode 100644 index f9d89aa0ec..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/PowOperator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/BinaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT PowOperator : public BinaryOperator -{ -public: - using Self = PowOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new PowOperator()); - } - - ~PowOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - PowOperator(); - -public: - PowOperator(const PowOperator&) = delete; // Copy Constructor Not Implemented - PowOperator(PowOperator&&) = delete; // Move Constructor Not Implemented - PowOperator& operator=(const PowOperator&) = delete; // Copy Assignment Not Implemented - PowOperator& operator=(PowOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/RootOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/RootOperator.cpp deleted file mode 100644 index cf56bbfc4b..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/RootOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/RootOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -RootOperator::RootOperator() -{ - setNumberOfArguments(2); - setInfixToken("root"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -RootOperator::~RootOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void RootOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [this](double num1, double num2) -> double { return root(num1, num2); }); -} - -// ----------------------------------------------------------------------------- -RootOperator::Pointer RootOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/RootOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/RootOperator.hpp deleted file mode 100644 index 58b0868bd9..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/RootOperator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT RootOperator : public UnaryOperator -{ -public: - using Self = RootOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new RootOperator()); - } - - ~RootOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - RootOperator(); - -public: - RootOperator(const RootOperator&) = delete; // Copy Constructor Not Implemented - RootOperator(RootOperator&&) = delete; // Move Constructor Not Implemented - RootOperator& operator=(const RootOperator&) = delete; // Copy Assignment Not Implemented - RootOperator& operator=(RootOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SinOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SinOperator.cpp deleted file mode 100644 index 9484b3ce3e..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SinOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/SinOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -SinOperator::SinOperator() -{ - setNumberOfArguments(1); - setInfixToken("sin"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -SinOperator::~SinOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void SinOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTrig(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return sin(num); }); -} - -// ----------------------------------------------------------------------------- -SinOperator::Pointer SinOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SinOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SinOperator.hpp deleted file mode 100644 index c8e8d57cf1..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SinOperator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT SinOperator : public UnaryOperator -{ -public: - using Self = SinOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new SinOperator()); - } - - ~SinOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - SinOperator(); - -public: - SinOperator(const SinOperator&) = delete; // Copy Constructor Not Implemented - SinOperator(SinOperator&&) = delete; // Move Constructor Not Implemented - SinOperator& operator=(const SinOperator&) = delete; // Copy Assignment Not Implemented - SinOperator& operator=(SinOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SqrtOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SqrtOperator.cpp deleted file mode 100644 index 02d17072b1..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SqrtOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/SqrtOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -SqrtOperator::SqrtOperator() -{ - setNumberOfArguments(1); - setInfixToken("sqrt"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -SqrtOperator::~SqrtOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void SqrtOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayStandardUnary(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return sqrt(num); }); -} - -// ----------------------------------------------------------------------------- -SqrtOperator::Pointer SqrtOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SqrtOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SqrtOperator.hpp deleted file mode 100644 index 46a174f6f0..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SqrtOperator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT SqrtOperator : public UnaryOperator -{ -public: - using Self = SqrtOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new SqrtOperator()); - } - - ~SqrtOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - SqrtOperator(); - -public: - SqrtOperator(const SqrtOperator&) = delete; // Copy Constructor Not Implemented - SqrtOperator(SqrtOperator&&) = delete; // Move Constructor Not Implemented - SqrtOperator& operator=(const SqrtOperator&) = delete; // Copy Assignment Not Implemented - SqrtOperator& operator=(SqrtOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SubtractionOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SubtractionOperator.cpp deleted file mode 100644 index 6110ce750f..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SubtractionOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/SubtractionOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -SubtractionOperator::SubtractionOperator() -{ - setPrecedence(A_Precedence); - setInfixToken("-"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -SubtractionOperator::~SubtractionOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void SubtractionOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTwoArguments(dataStructure, units, calculatedArrayPath, executionStack, [](double num1, double num2) -> double { return num1 - num2; }); -} - -// ----------------------------------------------------------------------------- -SubtractionOperator::Pointer SubtractionOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SubtractionOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SubtractionOperator.hpp deleted file mode 100644 index 80b196435e..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/SubtractionOperator.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/BinaryOperator.hpp" - -namespace nx::core -{ -class SIMPLNXCORE_EXPORT SubtractionOperator : public BinaryOperator -{ -public: - using Self = SubtractionOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new SubtractionOperator()); - } - - ~SubtractionOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - SubtractionOperator(); - -public: - SubtractionOperator(const SubtractionOperator&) = delete; // Copy Constructor Not Implemented - SubtractionOperator(SubtractionOperator&&) = delete; // Move Constructor Not Implemented - SubtractionOperator& operator=(const SubtractionOperator&) = delete; // Copy Assignment Not Implemented - SubtractionOperator& operator=(SubtractionOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/TanOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/TanOperator.cpp deleted file mode 100644 index e39d1a2d44..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/TanOperator.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "SimplnxCore/utils/TanOperator.hpp" -#include "SimplnxCore/utils/CalculatorArray.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -TanOperator::TanOperator() -{ - setNumberOfArguments(1); - setInfixToken("tan"); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -TanOperator::~TanOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void TanOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - CreateNewArrayTrig(dataStructure, units, calculatedArrayPath, executionStack, [](double num) -> double { return tan(num); }); -} - -// ----------------------------------------------------------------------------- -TanOperator::Pointer TanOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/TanOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/TanOperator.hpp deleted file mode 100644 index 5ce8a5ca83..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/TanOperator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "SimplnxCore/SimplnxCore_export.hpp" -#include "SimplnxCore/utils/UnaryOperator.hpp" - -namespace nx::core -{ -class CalculatorNumber; - -class SIMPLNXCORE_EXPORT TanOperator : public UnaryOperator -{ -public: - using Self = TanOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new TanOperator()); - } - - ~TanOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - -protected: - TanOperator(); - -public: - TanOperator(const TanOperator&) = delete; // Copy Constructor Not Implemented - TanOperator(TanOperator&&) = delete; // Move Constructor Not Implemented - TanOperator& operator=(const TanOperator&) = delete; // Copy Assignment Not Implemented - TanOperator& operator=(TanOperator&&) = delete; // Move Assignment Not Implemented - -private: -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/UnaryOperator.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/UnaryOperator.cpp deleted file mode 100644 index 61cf7c7c99..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/UnaryOperator.cpp +++ /dev/null @@ -1,265 +0,0 @@ -#include "UnaryOperator.hpp" - -#include "SimplnxCore/Filters/ArrayCalculatorFilter.hpp" -#include "SimplnxCore/utils/BinaryOperator.hpp" -#include "SimplnxCore/utils/CommaSeparator.hpp" -#include "SimplnxCore/utils/LeftParenthesisItem.hpp" -#include "SimplnxCore/utils/NegativeOperator.hpp" -#include "SimplnxCore/utils/RightParenthesisItem.hpp" - -using namespace nx::core; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -UnaryOperator::UnaryOperator() -: m_NumOfArguments(-1) -{ - setPrecedence(E_Precedence); - setOperatorType(Unary); -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -UnaryOperator::~UnaryOperator() = default; - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void UnaryOperator::calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) -{ - // This should never be executed -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -CalculatorItem::ErrorCode UnaryOperator::checkValidity(std::vector infixVector, int currentIndex, std::string& errMsg) -{ - if(currentIndex - 1 >= 0) - { - // If the left value isn't a binary operator - if(nullptr == std::dynamic_pointer_cast(infixVector[currentIndex - 1])) - { - // If the left value isn't a left parenthesis - if(nullptr == std::dynamic_pointer_cast(infixVector[currentIndex - 1])) - { - // If the left value isn't a negative operator - if(nullptr == std::dynamic_pointer_cast(infixVector[currentIndex - 1])) - { - // If the left value isn't a comma - if(nullptr == std::dynamic_pointer_cast(infixVector[currentIndex - 1])) - { - errMsg = fmt::format("The operator '{}' does not have a valid 'left' value.", getInfixToken()); - return CalculatorItem::ErrorCode::OperatorNoLeftValue; - } - } - } - } - } - - int index = currentIndex + 1; - int commaCount = 0; - bool hasArray = false; - if(index < infixVector.size() && nullptr != std::dynamic_pointer_cast(infixVector[index])) - { - index++; - - // Iterate through the vector to find the matching right parenthesis - for(; index < infixVector.size(); index++) - { - if(nullptr != std::dynamic_pointer_cast(infixVector[index])) - { - // We found the matching right parenthesis - if(commaCount < m_NumOfArguments - 1) - { - errMsg = fmt::format("The operator '{}' needs {} arguments. {} arguments were found.", getInfixToken(), m_NumOfArguments, commaCount + 1); - return CalculatorItem::ErrorCode::NotEnoughArguments; - } - if(!hasArray) - { - errMsg = fmt::format("The operator '{}' does not have any arguments that simplify down to a number.", getInfixToken()); - return CalculatorItem::ErrorCode::NoNumericArguments; - } - - return CalculatorItem::ErrorCode::Success; - } - if(nullptr != std::dynamic_pointer_cast(infixVector[index])) - { - /* We found another left parenthesis, but we don't care what's inside this set of parentheses - (other operators' checkValidity functions will take care of these values), so just iterate - until we find the matching closing parenthesis for this opening parenthesis */ - int extraLeftPCount = 0; - index++; - while(index < infixVector.size() && (nullptr == std::dynamic_pointer_cast(infixVector[index]) || extraLeftPCount > 0)) - { - if(nullptr != std::dynamic_pointer_cast(infixVector[index])) - { - hasArray = true; - } - else if(nullptr != std::dynamic_pointer_cast(infixVector[index])) - { - extraLeftPCount++; - } - else if(nullptr != std::dynamic_pointer_cast(infixVector[index])) - { - extraLeftPCount--; - } - - index++; - } - } - else if(nullptr != std::dynamic_pointer_cast(infixVector[index])) - { - // We found a comma, so increase the comma count - commaCount++; - if(commaCount > m_NumOfArguments - 1) - { - // We found too many commas (meaning that there are too many arguments), so return false - errMsg = fmt::format("The operator '{}' needs {} arguments. {} arguments were found.", getInfixToken(), m_NumOfArguments, commaCount + 1); - return CalculatorItem::ErrorCode::TooManyArguments; - } - } - else if(nullptr != std::dynamic_pointer_cast(infixVector[index])) - { - hasArray = true; - } - } - } - else - { - errMsg = fmt::format("The operator '{}' does not have an opening parenthesis.", getInfixToken()); - return CalculatorItem::ErrorCode::OperatorNoOpeningParen; - } - - errMsg = fmt::format("The operator '{}' does not have a closing parenthesis.", getInfixToken()); - return CalculatorItem::ErrorCode::OperatorNoClosingParen; -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -int UnaryOperator::getNumberOfArguments() -{ - return m_NumOfArguments; -} - -// ----------------------------------------------------------------------------- -// -// ----------------------------------------------------------------------------- -void UnaryOperator::setNumberOfArguments(int numOfArguments) -{ - m_NumOfArguments = numOfArguments; -} - -// ----------------------------------------------------------------------------- -UnaryOperator::Pointer UnaryOperator::NullPointer() -{ - return Pointer(static_cast(nullptr)); -} - -// ----------------------------------------------------------------------------- -void UnaryOperator::CreateNewArrayStandardUnary(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, - std::stack& executionStack, std::function op) -{ - ICalculatorArray::Pointer arrayPtr = executionStack.top(); - if(!executionStack.empty() && nullptr != arrayPtr) - { - executionStack.pop(); - calculatedArrayPath = GetUniquePathName(dataStructure, calculatedArrayPath); - - Float64Array* newArray = - Float64Array::CreateWithStore(dataStructure, calculatedArrayPath.getTargetName(), arrayPtr->getArray()->getTupleShape(), arrayPtr->getArray()->getComponentShape()); - - usize numComps = newArray->getNumberOfComponents(); - for(usize i = 0; i < newArray->getNumberOfTuples(); i++) - { - for(usize c = 0; c < newArray->getNumberOfComponents(); c++) - { - usize index = numComps * i + c; - double num = arrayPtr->getValue(index); - (*newArray)[index] = op(num); - } - } - - executionStack.push(CalculatorArray::New(dataStructure, newArray, arrayPtr->getType(), true)); - return; - } -} - -// ----------------------------------------------------------------------------- -void UnaryOperator::CreateNewArrayTrig(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack, - std::function op) -{ - ICalculatorArray::Pointer arrayPtr = executionStack.top(); - if(!executionStack.empty() && nullptr != arrayPtr) - { - executionStack.pop(); - calculatedArrayPath = GetUniquePathName(dataStructure, calculatedArrayPath); - - DataArray* array = arrayPtr->getArray(); - - Float64Array* newArray = Float64Array::CreateWithStore(dataStructure, calculatedArrayPath.getTargetName(), array->getTupleShape(), array->getComponentShape()); - - usize numComps = newArray->getNumberOfComponents(); - for(usize i = 0; i < newArray->getNumberOfTuples(); i++) - { - for(usize c = 0; c < newArray->getNumberOfComponents(); c++) - { - usize index = numComps * i + c; - double num = array->getValue(index); - - if(units == CalculatorParameter::AngleUnits::Degrees) - { - (*newArray)[index] = op(toRadians(num)); - } - else - { - (*newArray)[index] = op(num); - } - } - } - - executionStack.push(CalculatorArray::New(dataStructure, newArray, arrayPtr->getType(), true)); - return; - } -} - -// ----------------------------------------------------------------------------- -void UnaryOperator::CreateNewArrayArcTrig(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack, - std::function op) -{ - ICalculatorArray::Pointer arrayPtr = executionStack.top(); - if(!executionStack.empty() && nullptr != arrayPtr) - { - executionStack.pop(); - calculatedArrayPath = GetUniquePathName(dataStructure, calculatedArrayPath); - - DataArray* array = arrayPtr->getArray(); - - Float64Array* newArray = Float64Array::CreateWithStore(dataStructure, calculatedArrayPath.getTargetName(), array->getTupleShape(), array->getComponentShape()); - - usize numComps = newArray->getNumberOfComponents(); - for(usize i = 0; i < newArray->getNumberOfTuples(); i++) - { - for(usize c = 0; c < newArray->getNumberOfComponents(); c++) - { - usize index = numComps * i + c; - double num = array->getValue(index); - - if(units == CalculatorParameter::AngleUnits::Degrees) - { - (*newArray)[index] = toDegrees(op(num)); - } - else - { - (*newArray)[index] = op(num); - } - } - } - - executionStack.push(CalculatorArray::New(dataStructure, newArray, arrayPtr->getType(), true)); - return; - } -} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/UnaryOperator.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/utils/UnaryOperator.hpp deleted file mode 100644 index a3f49a08d8..0000000000 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/utils/UnaryOperator.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "SimplnxCore/utils/CalculatorOperator.hpp" -#include "simplnx/DataStructure/DataPath.hpp" - -#include -#include - -namespace nx::core -{ -class SIMPLNXCORE_EXPORT UnaryOperator : public CalculatorOperator -{ -public: - using Self = UnaryOperator; - using Pointer = std::shared_ptr; - using ConstPointer = std::shared_ptr; - using WeakPointer = std::weak_ptr; - using ConstWeakPointer = std::weak_ptr; - static Pointer NullPointer(); - - static Pointer New() - { - return Pointer(new UnaryOperator()); - } - - ~UnaryOperator() override; - - void calculate(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack) override; - - CalculatorItem::ErrorCode checkValidity(std::vector infixVector, int currentIndex, std::string& msg) final; - - int getNumberOfArguments(); - -protected: - UnaryOperator(); - - void setNumberOfArguments(int numOfArguments); - -private: - int m_NumOfArguments; - -public: - UnaryOperator(const UnaryOperator&) = delete; // Copy Constructor Not Implemented - UnaryOperator(UnaryOperator&&) = delete; // Move Constructor Not Implemented - UnaryOperator& operator=(const UnaryOperator&) = delete; // Copy Assignment Not Implemented - UnaryOperator& operator=(UnaryOperator&&) = delete; // Move Assignment Not Implemented - - static void CreateNewArrayStandardUnary(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack, - std::function op); - static void CreateNewArrayTrig(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack, - std::function op); - static void CreateNewArrayArcTrig(DataStructure& dataStructure, CalculatorParameter::AngleUnits units, DataPath calculatedArrayPath, std::stack& executionStack, - std::function op); -}; - -} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/test/ArrayCalculatorTest.cpp b/src/Plugins/SimplnxCore/test/ArrayCalculatorTest.cpp index ea16fb20ea..d4dcae1591 100644 --- a/src/Plugins/SimplnxCore/test/ArrayCalculatorTest.cpp +++ b/src/Plugins/SimplnxCore/test/ArrayCalculatorTest.cpp @@ -1,6 +1,6 @@ +#include "SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp" #include "SimplnxCore/Filters/ArrayCalculatorFilter.hpp" #include "SimplnxCore/SimplnxCore_test_dirs.hpp" -#include "SimplnxCore/utils/CalculatorItem.hpp" #include "simplnx/Common/Numbers.hpp" #include "simplnx/DataStructure/AttributeMatrix.hpp" @@ -12,6 +12,7 @@ #include "simplnx/Utilities/StringUtilities.hpp" #include +#include using namespace nx::core; @@ -94,8 +95,8 @@ IFilter::ExecuteResult createAndExecuteArrayCalculatorFilter(const std::string& } // ----------------------------------------------------------------------------- -void runTest(const std::string& equation, const DataPath& targetArrayPath, int32 expectedErrorCondition, CalculatorItem::WarningCode expectedWarningCondition, - const int* expectedNumberOfTuples = nullptr, const double* expectedValue = nullptr, CalculatorParameter::AngleUnits units = CalculatorParameter::AngleUnits::Radians) +void runTest(const std::string& equation, const DataPath& targetArrayPath, int32 expectedErrorCondition, CalculatorWarningCode expectedWarningCondition, const int* expectedNumberOfTuples = nullptr, + const double* expectedValue = nullptr, CalculatorParameter::AngleUnits units = CalculatorParameter::AngleUnits::Radians) { std::cout << " Testing equation: ==>" << equation << "<==" << std::endl; @@ -110,7 +111,7 @@ void runTest(const std::string& equation, const DataPath& targetArrayPath, int32 // Execute the filter and check the result auto executeResult = filter.execute(dataStructure, args); - if(expectedErrorCondition == static_cast(CalculatorItem::ErrorCode::Success)) + if(expectedErrorCondition == static_cast(CalculatorErrorCode::Success)) { SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); } @@ -122,12 +123,12 @@ void runTest(const std::string& equation, const DataPath& targetArrayPath, int32 if(executeResult.result.warnings().size() != 0) { - REQUIRE(expectedWarningCondition != CalculatorItem::WarningCode::None); + REQUIRE(expectedWarningCondition != CalculatorWarningCode::None); REQUIRE(executeResult.result.warnings()[0].code == static_cast(expectedWarningCondition)); } else { - REQUIRE(expectedWarningCondition == CalculatorItem::WarningCode::None); + REQUIRE(expectedWarningCondition == CalculatorWarningCode::None); } Float64Array* arrayPtr = dataStructure.getDataAs(targetArrayPath); @@ -267,7 +268,7 @@ void MultiComponentArrayCalculatorTest() UInt32Array* sArray = dataStructure.getDataAs(k_SignArrayPath); SIMPLNX_RESULT_REQUIRE_INVALID(results.result); - REQUIRE(results.result.errors()[0].code == static_cast(CalculatorItem::ErrorCode::InconsistentCompDims)); + REQUIRE(results.result.errors()[0].code == static_cast(CalculatorErrorCode::InconsistentCompDims)); } SECTION("Multi-Component Out of bounds error") @@ -278,7 +279,7 @@ void MultiComponentArrayCalculatorTest() UInt32Array* sArray = dataStructure.getDataAs(k_SignArrayPath); SIMPLNX_RESULT_REQUIRE_INVALID(results.result); - REQUIRE(results.result.errors()[0].code == static_cast(CalculatorItem::ErrorCode::ComponentOutOfRange)); + REQUIRE(results.result.errors()[0].code == static_cast(CalculatorErrorCode::ComponentOutOfRange)); } UnitTest::CheckArraysInheritTupleDims(dataStructure); @@ -289,39 +290,39 @@ void SingleComponentArrayCalculatorTest1() { SECTION("Empty Tests") { - runTest("", k_NumericArrayPath, FilterParameter::Constants::k_Validate_Empty_Value, CalculatorItem::WarningCode::None); - runTest(" ", k_NumericArrayPath, FilterParameter::Constants::k_Validate_Empty_Value, CalculatorItem::WarningCode::None); + runTest("", k_NumericArrayPath, FilterParameter::Constants::k_Validate_Empty_Value, CalculatorWarningCode::None); + runTest(" ", k_NumericArrayPath, FilterParameter::Constants::k_Validate_Empty_Value, CalculatorWarningCode::None); } SECTION("Single Value Tests") { int numTuple = 1; double value = -3; - runTest("-3", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("-3", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); numTuple = 1; value = 14; - runTest("14", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("14", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); numTuple = 1; value = 0.345; - runTest(".345", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest(".345", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Mismatched Parentheses Tests") { int numTuple = 1; double value = 12; - runTest("(3*4)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); - runTest("(3*4", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::MismatchedParentheses), CalculatorItem::WarningCode::None); - runTest("3*4)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::MismatchedParentheses), CalculatorItem::WarningCode::None); + runTest("(3*4)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("(3*4", k_NumericArrayPath, static_cast(CalculatorErrorCode::MismatchedParentheses), CalculatorWarningCode::None); + runTest("3*4)", k_NumericArrayPath, static_cast(CalculatorErrorCode::MismatchedParentheses), CalculatorWarningCode::None); } SECTION("Nested Unary Operator Test") { int numTuple = 1; float64 value = sin(pow(fabs(cos(fabs(static_cast(3)) / 4) + 7), 2)); - runTest("sin( abs( cos( abs(3)/4) + 7)^2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("sin( abs( cos( abs(3)/4) + 7)^2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); // term1 = (12.5 * (3.14 + 2.718)) / (7 - (8 * (9 + 4))) float64 term1 = (12.5 * (3.14 + 2.718)) / (7.0 - (8.0 * (9.0 + 4.0))); @@ -344,451 +345,451 @@ void SingleComponentArrayCalculatorTest1() value = std::min(term1, term2) + term3; runTest("min(((12.5*(3.14+2.718))/(7-(8*(9+4)))),max(sin(3.141592653589793/(4*(2+6))),root((5^(1+2)),(10/((3^2)+1)))))+abs(-((15+3)-(20/(5+5))))", k_NumericArrayPath, - static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); + static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); } SECTION("Single Array Tests (Force Incorrect Tuple Counts)") { - runTest("-InputArray1", k_NumericArrayPath, -268, CalculatorItem::WarningCode::None); - runTest(k_InputArray2, k_NumericArrayPath, -268, CalculatorItem::WarningCode::None); + runTest("-InputArray1", k_NumericArrayPath, -268, CalculatorWarningCode::None); + runTest(k_InputArray2, k_NumericArrayPath, -268, CalculatorWarningCode::None); int numTuple = 10; double value = 18; - runTest("12 + 6", k_AttributeArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("12 + 6", k_AttributeArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Unrecognized Item Tests") { - runTest("-foo", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::UnrecognizedItem), CalculatorItem::WarningCode::None); - runTest("InputArray3", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::UnrecognizedItem), CalculatorItem::WarningCode::None); - runTest("sin(InputArray 2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::UnrecognizedItem), CalculatorItem::WarningCode::None); + runTest("-foo", k_NumericArrayPath, static_cast(CalculatorErrorCode::UnrecognizedItem), CalculatorWarningCode::None); + runTest("InputArray3", k_NumericArrayPath, static_cast(CalculatorErrorCode::UnrecognizedItem), CalculatorWarningCode::None); + runTest("sin(InputArray 2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::UnrecognizedItem), CalculatorWarningCode::None); } // Operator Tests SECTION("Addition Operator") { - runTest("+", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoLeftValue), CalculatorItem::WarningCode::None); - runTest("3 +", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoRightValue), CalculatorItem::WarningCode::None); - runTest("+ 12.5", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoLeftValue), CalculatorItem::WarningCode::None); + runTest("+", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoLeftValue), CalculatorWarningCode::None); + runTest("3 +", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoRightValue), CalculatorWarningCode::None); + runTest("+ 12.5", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoLeftValue), CalculatorWarningCode::None); int numTuple = 1; double value = 18; - runTest("12 + 6", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("12 + 6", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -6; - runTest("-12 + 6", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); - runTest("6 + -12", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("-12 + 6", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); + runTest("6 + -12", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Subtraction Operator") { - runTest("-89.2 -", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoRightValue), CalculatorItem::WarningCode::None); + runTest("-89.2 -", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoRightValue), CalculatorWarningCode::None); int numTuple = 1; double value = 43; - runTest("97 - 54", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("97 - 54", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -34; - runTest("-32 - 2", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("-32 - 2", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 19; - runTest("7 - -12", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("7 - -12", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Multiplication Operator") { - runTest("*", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoLeftValue), CalculatorItem::WarningCode::None); - runTest("3 *", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoRightValue), CalculatorItem::WarningCode::None); - runTest("* 12.5", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoLeftValue), CalculatorItem::WarningCode::None); + runTest("*", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoLeftValue), CalculatorWarningCode::None); + runTest("3 *", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoRightValue), CalculatorWarningCode::None); + runTest("* 12.5", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoLeftValue), CalculatorWarningCode::None); int numTuple = 1; double value = 72; - runTest("12 * 6", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("12 * 6", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); value = -72; - runTest("-12 * 6", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); - runTest("6 * -12", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("-12 * 6", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("6 * -12", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); } SECTION("Division Operator") { - runTest("/", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoLeftValue), CalculatorItem::WarningCode::None); - runTest("3 /", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoRightValue), CalculatorItem::WarningCode::None); - runTest("/ 12.5", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoLeftValue), CalculatorItem::WarningCode::None); + runTest("/", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoLeftValue), CalculatorWarningCode::None); + runTest("3 /", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoRightValue), CalculatorWarningCode::None); + runTest("/ 12.5", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoLeftValue), CalculatorWarningCode::None); int numTuple = 1; double value = 2; - runTest("12 / 6", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("12 / 6", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -2; - runTest("-12 / 6", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("-12 / 6", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -0.5; - runTest("6 / -12", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("6 / -12", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Pow Operator") { - runTest("^", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoLeftValue), CalculatorItem::WarningCode::None); - runTest("3 ^", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoRightValue), CalculatorItem::WarningCode::None); - runTest("^ 12.5", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoLeftValue), CalculatorItem::WarningCode::None); + runTest("^", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoLeftValue), CalculatorWarningCode::None); + runTest("3 ^", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoRightValue), CalculatorWarningCode::None); + runTest("^ 12.5", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoLeftValue), CalculatorWarningCode::None); int numTuple = 1; double value = 125; - runTest("5 ^ 3", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("5 ^ 3", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -8; - runTest("-2 ^ 3", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("-2 ^ 3", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 0.25; - runTest("2 ^ -2", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("2 ^ -2", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Abs Operator") { - runTest("abs", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("abs(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("abs)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("abs()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("abs", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("abs(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("abs)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("abs()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 2; - runTest("abs(2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("abs(2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 4.3; - runTest("abs(-4.3)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("abs(-4.3)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 6.7; - runTest("abs(abs(6.7))", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("abs(abs(6.7))", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Sin Operator") { - runTest("sin", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("sin(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("sin)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("sin()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("sin", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("sin(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("sin)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("sin()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 1; - runTest("sin(90)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("sin(90)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = 0; - runTest("sin(-180)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("sin(-180)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = 0.5; - runTest("sin(" + k_Pi_Str + "/6)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("sin(" + k_Pi_Str + "/6)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); value = 1; - runTest("sin(" + k_Pi_Str + "/2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("sin(" + k_Pi_Str + "/2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); } SECTION("Cos Operator") { - runTest("cos", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("cos(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("cos)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("cos()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("cos", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("cos(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("cos)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("cos()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 0; - runTest("cos(90)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("cos(90)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = -1; - runTest("cos(-180)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("cos(-180)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = 0.5; - runTest("cos(" + k_Pi_Str + "/3)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("cos(" + k_Pi_Str + "/3)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); value = -0.5; - runTest("cos(2*" + k_Pi_Str + "/3)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value, + runTest("cos(2*" + k_Pi_Str + "/3)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value, CalculatorParameter::Radians); } SECTION("Tan Operator") { - runTest("tan", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("tan(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("tan)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("tan()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("tan", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("tan(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("tan)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("tan()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 1; - runTest("tan(45)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("tan(45)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = sqrt(3); - runTest("tan(60)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("tan(60)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = 1; - runTest("tan(" + k_Pi_Str + "/4)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value, + runTest("tan(" + k_Pi_Str + "/4)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value, CalculatorParameter::Radians); value = -sqrt(static_cast(1) / static_cast(3)); - runTest("tan(5*" + k_Pi_Str + "/6)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value, + runTest("tan(5*" + k_Pi_Str + "/6)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value, CalculatorParameter::Radians); } SECTION("ASin Operator") { - runTest("asin", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("asin(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("asin)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("asin()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("asin", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("asin(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("asin)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("asin()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 30; - runTest("asin(0.5)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("asin(0.5)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = 45; - runTest("asin(sqrt(2)/2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("asin(sqrt(2)/2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = numbers::pi / 3; - runTest("asin(sqrt(3)/2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("asin(sqrt(3)/2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); value = numbers::pi / 2; - runTest("asin(1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("asin(1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); } SECTION("ACos Operator") { - runTest("acos", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("acos(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("acos)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("acos()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("acos", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("acos(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("acos)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("acos()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 60; - runTest("acos(0.5)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("acos(0.5)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = 45; - runTest("acos(sqrt(2)/2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("acos(sqrt(2)/2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = numbers::pi / 6; - runTest("acos(sqrt(3)/2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("acos(sqrt(3)/2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); value = 0; - runTest("acos(1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("acos(1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); } SECTION("ATan Operator") { - runTest("atan", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("atan(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("atan)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("atan()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("atan", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("atan(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("atan)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("atan()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = -45; - runTest("atan(-1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("atan(-1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = -60; - runTest("atan(-sqrt(3))", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); + runTest("atan(-sqrt(3))", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Degrees); value = numbers::pi / 6; - runTest("atan(1/sqrt(3))", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("atan(1/sqrt(3))", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); value = numbers::pi / 3; - runTest("atan(sqrt(3))", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value, CalculatorParameter::Radians); + runTest("atan(sqrt(3))", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value, CalculatorParameter::Radians); } SECTION("Sqrt Operator") { - runTest("sqrt", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("sqrt(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("sqrt)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("sqrt()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); - runTest("sqrt(1, 3)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); + runTest("sqrt", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("sqrt(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("sqrt)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("sqrt()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); + runTest("sqrt(1, 3)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 3; - runTest("sqrt(9)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("sqrt(9)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 4; - runTest("sqrt(4*4)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("sqrt(4*4)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); value = 3; - runTest("sqrt(3^2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("sqrt(3^2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Root Operator") { - runTest("root", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("root(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("root)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("root()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NotEnoughArguments), CalculatorItem::WarningCode::None); - runTest("root(1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NotEnoughArguments), CalculatorItem::WarningCode::None); - runTest("root(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("root", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("root(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("root)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("root()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NotEnoughArguments), CalculatorWarningCode::None); + runTest("root(1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NotEnoughArguments), CalculatorWarningCode::None); + runTest("root(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 3; - runTest("root(9, 2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("root(9, 2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 4; - runTest("root(4*4, 2)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("root(4*4, 2)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); value = 4; - runTest("root(4*4+0, 1*2+0)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("root(4*4+0, 1*2+0)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); value = 4; - runTest("root(64, 3)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("root(64, 3)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Log10 Operator") { - runTest("log10", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("log10(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("log10)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("log10()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); - runTest("log10(1, 3)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); - runTest("log10(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); + runTest("log10", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("log10(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("log10)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("log10()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); + runTest("log10(1, 3)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); + runTest("log10(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); int numTuple = 1; double value = log10(10); - runTest("log10(10)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("log10(10)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = log10(40); - runTest("log10(40)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("log10(40)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Log Operator") { - runTest("log", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("log(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("log)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("log()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NotEnoughArguments), CalculatorItem::WarningCode::None); - runTest("log(1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NotEnoughArguments), CalculatorItem::WarningCode::None); - runTest("log(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("log", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("log(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("log)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("log()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NotEnoughArguments), CalculatorWarningCode::None); + runTest("log(1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NotEnoughArguments), CalculatorWarningCode::None); + runTest("log(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = log(5) / log(2); - runTest("log(2, 5)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("log(2, 5)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 2; - runTest("log(10, 100)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("log(10, 100)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Exp Operator") { - runTest("exp", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("exp(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("exp)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("exp()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); - runTest("exp(1, 5)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); - runTest("exp(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); + runTest("exp", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("exp(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("exp)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("exp()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); + runTest("exp(1, 5)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); + runTest("exp(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 2.7182818284590452354; // M_E - runTest("exp(1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("exp(1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 1; - runTest("exp(0)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("exp(0)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Ln Operator") { - runTest("ln", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("ln(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("ln)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("ln()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); - runTest("ln(1, 5)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); - runTest("ln(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); + runTest("ln", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("ln(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("ln)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("ln()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); + runTest("ln(1, 5)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); + runTest("ln(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); int numTuple = 1; double value = log(1); - runTest("ln(1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("ln(1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = log(7); - runTest("ln(7)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("ln(7)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Floor Operator") { - runTest("floor", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("floor(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("floor)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("floor()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); - runTest("floor(1, 5)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); - runTest("floor(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); + runTest("floor", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("floor(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("floor)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("floor()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); + runTest("floor(1, 5)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); + runTest("floor(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 12; - runTest("floor(12.4564)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("floor(12.4564)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -83; - runTest("floor(-82.789367)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("floor(-82.789367)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Ceil Operator") { - runTest("ceil", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("ceil(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("ceil)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("ceil()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); - runTest("ceil(1, 5)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); - runTest("ceil(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::TooManyArguments), CalculatorItem::WarningCode::None); + runTest("ceil", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("ceil(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("ceil)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("ceil()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); + runTest("ceil(1, 5)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); + runTest("ceil(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::TooManyArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 1; - runTest("ceil(.4564)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("ceil(.4564)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -82; - runTest("ceil(-82.789367)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("ceil(-82.789367)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Negative Operator") { - runTest("-", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoRightValue), CalculatorItem::WarningCode::None); + runTest("-", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoRightValue), CalculatorWarningCode::None); - runTest("-(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::MismatchedParentheses), CalculatorItem::WarningCode::None); - runTest("-)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoRightValue), CalculatorItem::WarningCode::None); - runTest("-()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); - runTest("-(1, 5)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoPrecedingUnaryOperator), CalculatorItem::WarningCode::None); - runTest("-(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoPrecedingUnaryOperator), CalculatorItem::WarningCode::None); + runTest("-(", k_NumericArrayPath, static_cast(CalculatorErrorCode::MismatchedParentheses), CalculatorWarningCode::None); + runTest("-)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoRightValue), CalculatorWarningCode::None); + runTest("-()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); + runTest("-(1, 5)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoPrecedingUnaryOperator), CalculatorWarningCode::None); + runTest("-(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoPrecedingUnaryOperator), CalculatorWarningCode::None); int numTuple = 1; double value = -9; - runTest("- 9", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("- 9", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -0.4564; - runTest("-(.4564)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("-(.4564)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = 1; - runTest("-(3-4)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::AmbiguousNameWarning, &numTuple, &value); + runTest("-(3-4)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::AmbiguousNameWarning, &numTuple, &value); } SECTION("Min Operator") { - runTest("min", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("min(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("min)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("min()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NotEnoughArguments), CalculatorItem::WarningCode::None); - runTest("min(1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NotEnoughArguments), CalculatorItem::WarningCode::None); - runTest("min(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("min", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("min(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("min)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("min()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NotEnoughArguments), CalculatorWarningCode::None); + runTest("min(1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NotEnoughArguments), CalculatorWarningCode::None); + runTest("min(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 2; - runTest("min(2,6)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("min(2,6)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -93; - runTest("min(-82,-93)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("min(-82,-93)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } SECTION("Max Operator") { - runTest("max", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("max(", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoClosingParen), CalculatorItem::WarningCode::None); - runTest("max)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::OperatorNoOpeningParen), CalculatorItem::WarningCode::None); - runTest("max()", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NotEnoughArguments), CalculatorItem::WarningCode::None); - runTest("max(1)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NotEnoughArguments), CalculatorItem::WarningCode::None); - runTest("max(,)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::NoNumericArguments), CalculatorItem::WarningCode::None); + runTest("max", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("max(", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoClosingParen), CalculatorWarningCode::None); + runTest("max)", k_NumericArrayPath, static_cast(CalculatorErrorCode::OperatorNoOpeningParen), CalculatorWarningCode::None); + runTest("max()", k_NumericArrayPath, static_cast(CalculatorErrorCode::NotEnoughArguments), CalculatorWarningCode::None); + runTest("max(1)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NotEnoughArguments), CalculatorWarningCode::None); + runTest("max(,)", k_NumericArrayPath, static_cast(CalculatorErrorCode::NoNumericArguments), CalculatorWarningCode::None); int numTuple = 1; double value = 6; - runTest("max(2,6)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("max(2,6)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); value = -82; - runTest("max(-82,-93)", k_NumericArrayPath, static_cast(CalculatorItem::ErrorCode::Success), CalculatorItem::WarningCode::None, &numTuple, &value); + runTest("max(-82,-93)", k_NumericArrayPath, static_cast(CalculatorErrorCode::Success), CalculatorWarningCode::None, &numTuple, &value); } } @@ -876,3 +877,493 @@ TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Filter Execution") SingleComponentArrayCalculatorTest2(); MultiComponentArrayCalculatorTest(); } + +TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Tokenizer") +{ + using TT = nx::core::TokenType; + + SECTION("Simple arithmetic") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize("3 + 4.5"); + REQUIRE(tokens.size() == 3); + REQUIRE(tokens[0].type == TT::Number); + REQUIRE(tokens[0].text == "3"); + REQUIRE(tokens[0].position == 0); + REQUIRE(tokens[1].type == TT::Plus); + REQUIRE(tokens[1].position == 2); + REQUIRE(tokens[2].type == TT::Number); + REQUIRE(tokens[2].text == "4.5"); + REQUIRE(tokens[2].position == 4); + } + + SECTION("Single-word identifiers") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize("Spaced Array + 1"); + REQUIRE(tokens.size() == 4); + REQUIRE(tokens[0].type == TT::Identifier); + REQUIRE(tokens[0].text == "Spaced"); + REQUIRE(tokens[1].type == TT::Identifier); + REQUIRE(tokens[1].text == "Array"); + REQUIRE(tokens[2].type == TT::Plus); + REQUIRE(tokens[3].type == TT::Number); + REQUIRE(tokens[3].text == "1"); + } + + SECTION("Quoted string") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize("\"CellData/Confidence Index\" + 1"); + REQUIRE(tokens.size() == 3); + REQUIRE(tokens[0].type == TT::QuotedString); + REQUIRE(tokens[0].text == "CellData/Confidence Index"); + REQUIRE(tokens[1].type == TT::Plus); + REQUIRE(tokens[2].type == TT::Number); + } + + SECTION("Brackets and comma") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize("Array[3, 2]"); + REQUIRE(tokens.size() == 6); + REQUIRE(tokens[0].type == TT::Identifier); + REQUIRE(tokens[0].text == "Array"); + REQUIRE(tokens[1].type == TT::LBracket); + REQUIRE(tokens[2].type == TT::Number); + REQUIRE(tokens[2].text == "3"); + REQUIRE(tokens[3].type == TT::Comma); + REQUIRE(tokens[4].type == TT::Number); + REQUIRE(tokens[4].text == "2"); + REQUIRE(tokens[5].type == TT::RBracket); + } + + SECTION("All operator tokens") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize("+ - * / ^ %"); + REQUIRE(tokens.size() == 6); + REQUIRE(tokens[0].type == TT::Plus); + REQUIRE(tokens[1].type == TT::Minus); + REQUIRE(tokens[2].type == TT::Star); + REQUIRE(tokens[3].type == TT::Slash); + REQUIRE(tokens[4].type == TT::Caret); + REQUIRE(tokens[5].type == TT::Percent); + } + + SECTION("Decimal starting with dot") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize(".345"); + REQUIRE(tokens.size() == 1); + REQUIRE(tokens[0].type == TT::Number); + REQUIRE(tokens[0].text == ".345"); + } + + SECTION("Complex expression") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize("sin(pi / 2)"); + REQUIRE(tokens.size() == 6); + REQUIRE(tokens[0].type == TT::Identifier); + REQUIRE(tokens[0].text == "sin"); + REQUIRE(tokens[1].type == TT::LParen); + REQUIRE(tokens[2].type == TT::Identifier); + REQUIRE(tokens[2].text == "pi"); + REQUIRE(tokens[3].type == TT::Slash); + REQUIRE(tokens[4].type == TT::Number); + REQUIRE(tokens[4].text == "2"); + REQUIRE(tokens[5].type == TT::RParen); + } + + SECTION("Negative number tokenizes as minus + number") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize("-3.14"); + REQUIRE(tokens.size() == 2); + REQUIRE(tokens[0].type == TT::Minus); + REQUIRE(tokens[1].type == TT::Number); + REQUIRE(tokens[1].text == "3.14"); + } + + SECTION("Empty string") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize(""); + REQUIRE(tokens.empty()); + } + + SECTION("Parentheses") + { + auto tokens = nx::core::ArrayCalculatorParser::tokenize("(3+4)"); + REQUIRE(tokens.size() == 5); + REQUIRE(tokens[0].type == TT::LParen); + REQUIRE(tokens[4].type == TT::RParen); + } +} + +// ----------------------------------------------------------------------------- +// Test 1: Array Resolution +// ----------------------------------------------------------------------------- +TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Array Resolution") +{ + UnitTest::LoadPlugins(); + + // Create a DataStructure with arrays in multiple groups. + // "Group1/SharedName" (UInt32, 10 tuples, filled with 5) + // "Group2/SharedName" (UInt32, 10 tuples, filled with 7) + // "Group1/UniqueName" (UInt32, 10 tuples, filled with 3) + DataStructure ds; + AttributeMatrix* group1 = AttributeMatrix::Create(ds, "Group1", {10ULL}); + auto group1Id = group1->getId(); + AttributeMatrix* group2 = AttributeMatrix::Create(ds, "Group2", {10ULL}); + auto group2Id = group2->getId(); + + UInt32Array* shared1 = UInt32Array::CreateWithStore(ds, "SharedName", {10}, {1}, group1Id); + shared1->fill(5); + UInt32Array* shared2 = UInt32Array::CreateWithStore(ds, "SharedName", {10}, {1}, group2Id); + shared2->fill(7); + UInt32Array* unique1 = UInt32Array::CreateWithStore(ds, "UniqueName", {10}, {1}, group1Id); + unique1->fill(3); + + ArrayCalculatorFilter filter; + + SECTION("Unique bare name resolves without selected group") + { + DataPath outputPath({"Group1", "NewArray"}); + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{DataPath{}, "UniqueName + 1", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(outputPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(outputPath)); + const auto& outputArray = ds.getDataRefAs(outputPath); + REQUIRE(outputArray.getNumberOfTuples() == 10); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), 4.0, 0.01)); + } + } + + SECTION("Ambiguous bare name with no selected group gives error") + { + DataPath outputPath({"Group1", "AmbiguousResult"}); + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{DataPath{}, "SharedName + 1", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(outputPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors()[0].code == static_cast(CalculatorErrorCode::AmbiguousArrayName)); + } + + SECTION("Selected group resolves ambiguity") + { + DataPath outputPath({"Group1", "ResolvedResult"}); + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{DataPath({"Group1"}), "SharedName + 1", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(outputPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(outputPath)); + const auto& outputArray = ds.getDataRefAs(outputPath); + REQUIRE(outputArray.getNumberOfTuples() == 10); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), 6.0, 0.01)); + } + } + + SECTION("Quoted full path resolves directly") + { + DataPath outputPath({"Group2", "QuotedResult"}); + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{DataPath{}, "\"Group2/SharedName\" + 1", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(outputPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(outputPath)); + const auto& outputArray = ds.getDataRefAs(outputPath); + REQUIRE(outputArray.getNumberOfTuples() == 10); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), 8.0, 0.01)); + } + } + + UnitTest::CheckArraysInheritTupleDims(ds); +} + +// ----------------------------------------------------------------------------- +// Test 2: Built-in Constants +// ----------------------------------------------------------------------------- +TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Built-in Constants") +{ + UnitTest::LoadPlugins(); + DataStructure ds = ::createDataStructure(); + ArrayCalculatorFilter filter; + + SECTION("pi resolves to std::numbers::pi") + { + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{k_AttributeMatrixPath, "pi", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), std::numbers::pi, 0.0001)); + } + } + + SECTION("e resolves to std::numbers::e") + { + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{k_AttributeMatrixPath, "e", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), std::numbers::e, 0.0001)); + } + } + + SECTION("2 * pi expression works") + { + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{k_AttributeMatrixPath, "2 * pi", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), 2.0 * std::numbers::pi, 0.0001)); + } + } + + UnitTest::CheckArraysInheritTupleDims(ds); +} + +// ----------------------------------------------------------------------------- +// Test 3: Modulo Operator +// ----------------------------------------------------------------------------- +TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Modulo Operator") +{ + UnitTest::LoadPlugins(); + DataStructure ds = ::createDataStructure(); + ArrayCalculatorFilter filter; + + SECTION("Scalar modulo: 10 % 3 = 1") + { + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{k_AttributeMatrixPath, "10 % 3", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), 1.0, 0.01)); + } + } + + SECTION("Array modulo: InputArray2 % 3 = 1 element-wise") + { + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{k_AttributeMatrixPath, "InputArray2 % 3", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + REQUIRE(outputArray.getNumberOfTuples() == 10); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), 1.0, 0.01)); + } + } + + UnitTest::CheckArraysInheritTupleDims(ds); +} + +// ----------------------------------------------------------------------------- +// Test 4: Tuple+Component Indexing +// ----------------------------------------------------------------------------- +TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Tuple Component Indexing") +{ + UnitTest::LoadPlugins(); + DataStructure ds = ::createDataStructure(); + ArrayCalculatorFilter filter; + + SECTION("MultiComponent Array1[2, 1] produces scalar value 7") + { + // MultiComponent Array1 has 10 tuples x 3 comps, values 0..29 sequentially. + // tuple 2, comp 1 = index 2*3 + 1 = 7 + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{k_AttributeMatrixPath, "\"MultiComponent Array1\"[2, 1]", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + // Scalar result broadcast to all tuples in the AttributeMatrix (10 tuples) + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), 7.0, 0.01)); + } + } + + SECTION("MultiComponent Array1[100, 0] out of bounds gives TupleOutOfRange error") + { + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{k_AttributeMatrixPath, "\"MultiComponent Array1\"[100, 0]", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors()[0].code == static_cast(CalculatorErrorCode::TupleOutOfRange)); + } + + UnitTest::CheckArraysInheritTupleDims(ds); +} + +// ----------------------------------------------------------------------------- +// Test 5: Sub-expression Component Access +// ----------------------------------------------------------------------------- +TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Sub-expression Component Access") +{ + UnitTest::LoadPlugins(); + DataStructure ds = ::createDataStructure(); + ArrayCalculatorFilter filter; + + SECTION("(MultiComponent Array1 + MultiComponent Array2)[0] extracts component 0 of the sum") + { + // MultiComponent Array1 and Array2 both have 10 tuples x 3 components, values 0..29. + // Sum = 2*values = [0,2,4,6,8,10,...,58] + // Component 0 extraction: for each tuple t, take element at (t*3 + 0) = 2*(t*3) + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, std::make_any(CalculatorParameter::ValueType{ + k_AttributeMatrixPath, "(\"MultiComponent Array1\" + \"MultiComponent Array2\")[0]", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + REQUIRE(outputArray.getNumberOfTuples() == 10); + REQUIRE(outputArray.getNumberOfComponents() == 1); + for(usize t = 0; t < 10; t++) + { + // Component 0 of sum: (t*3 + 0) + (t*3 + 0) = 2 * (t * 3) + double expected = 2.0 * static_cast(t * 3); + REQUIRE(UnitTest::CloseEnough(outputArray.at(t), expected, 0.01)); + } + } + + UnitTest::CheckArraysInheritTupleDims(ds); +} + +// ----------------------------------------------------------------------------- +// Test 6: Multi-word Array Names +// ----------------------------------------------------------------------------- +TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Multi-word Array Names") +{ + UnitTest::LoadPlugins(); + DataStructure ds = ::createDataStructure(); + ArrayCalculatorFilter filter; + + SECTION("Spaced Array + 1 = 3") + { + // Spaced Array is filled with 2, so Spaced Array + 1 = 3 + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, + std::make_any(CalculatorParameter::ValueType{k_AttributeMatrixPath, "Spaced Array + 1", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + REQUIRE(outputArray.getNumberOfTuples() == 10); + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), 3.0, 0.01)); + } + } + + UnitTest::CheckArraysInheritTupleDims(ds); +} + +TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Sub-expression Tuple Component Extraction") +{ + UnitTest::LoadPlugins(); + DataStructure ds = ::createDataStructure(); + ArrayCalculatorFilter filter; + + // MultiComponent Array1 has 10 tuples, 3 components, values 0,1,2,3,...,29 + // (ArrayA + ArrayB) at tuple 2, component 1 = 2*(2*3+1) = 14 + + SECTION("(expr)[T, C] produces scalar") + { + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, std::make_any(CalculatorParameter::ValueType{ + k_AttributeMatrixPath, "(\"MultiComponent Array1\" + \"MultiComponent Array2\")[2, 1]", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(ds.getDataRefAs(k_AttributeArrayPath)); + const auto& outputArray = ds.getDataRefAs(k_AttributeArrayPath); + // Scalar result broadcast to AM shape (10 tuples) + double expected = 2.0 * (2 * 3 + 1); // tuple 2, comp 1, doubled = 14 + for(usize i = 0; i < outputArray.getNumberOfTuples(); i++) + { + REQUIRE(UnitTest::CloseEnough(outputArray.at(i), expected, 0.01)); + } + } + + SECTION("(expr)[T, C] out of bounds tuple") + { + Arguments args; + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, std::make_any(CalculatorParameter::ValueType{ + k_AttributeMatrixPath, "(\"MultiComponent Array1\" + \"MultiComponent Array2\")[100, 0]", CalculatorParameter::Radians})); + args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any(NumericType::float64)); + args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any(k_AttributeArrayPath)); + auto result = filter.execute(ds, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors()[0].code == static_cast(CalculatorErrorCode::TupleOutOfRange)); + } + + UnitTest::CheckArraysInheritTupleDims(ds); +} diff --git a/src/simplnx/Parameters/CalculatorParameter.cpp b/src/simplnx/Parameters/CalculatorParameter.cpp index 8694ddaefc..a94264a778 100644 --- a/src/simplnx/Parameters/CalculatorParameter.cpp +++ b/src/simplnx/Parameters/CalculatorParameter.cpp @@ -21,7 +21,7 @@ constexpr StringLiteral k_SelectedGroup = "selected_group"; } // namespace CalculatorParameter::CalculatorParameter(const std::string& name, const std::string& humanName, const std::string& helpText, const ValueType& defaultValue) -: MutableDataParameter(name, humanName, helpText, Category::Required) +: ValueParameter(name, humanName, helpText) , m_DefaultValue(defaultValue) { } @@ -107,7 +107,7 @@ typename CalculatorParameter::ValueType CalculatorParameter::defaultString() con return m_DefaultValue; } -Result<> CalculatorParameter::validate(const DataStructure& dataStructure, const std::any& value) const +Result<> CalculatorParameter::validate(const std::any& value) const { static constexpr StringLiteral prefix = "FilterParameter 'CalculatorParameter' JSON Error: "; [[maybe_unused]] const auto& structValue = GetAnyRef(value); @@ -115,29 +115,12 @@ Result<> CalculatorParameter::validate(const DataStructure& dataStructure, const { return MakeErrorResult(FilterParameter::Constants::k_Validate_Empty_Value, fmt::format("{}expression cannot be empty", prefix)); } - if(!structValue.m_SelectedGroup.empty()) // if empty then using root group - { - const DataObject* dataObject = dataStructure.getData(structValue.m_SelectedGroup); - if(dataObject == nullptr) - { - return nx::core::MakeErrorResult(nx::core::FilterParameter::Constants::k_Validate_DuplicateValue, - fmt::format("{}Object does not exist at path '{}'", prefix, structValue.m_SelectedGroup.toString())); - } - const auto baseGroupObj = dataStructure.getDataAs(structValue.m_SelectedGroup); - if(baseGroupObj == nullptr) - { - return MakeErrorResult(FilterParameter::Constants::k_Validate_DuplicateValue, fmt::format("{}Object at path '{}' is not a BaseGroup type", prefix, structValue.m_SelectedGroup.toString())); - } - } - return {}; -} + // m_SelectedGroup is only a resolution hint for array name lookups. + // An empty path means "use root", and a non-existent or non-BaseGroup + // path is silently accepted -- the parser will handle any issues. -Result CalculatorParameter::resolve(DataStructure& dataStructure, const std::any& value) const -{ - const auto& structValue = GetAnyRef(value); - DataObject* object = dataStructure.getData(structValue.m_SelectedGroup); - return {{object}}; + return {}; } namespace SIMPLConversion diff --git a/src/simplnx/Parameters/CalculatorParameter.hpp b/src/simplnx/Parameters/CalculatorParameter.hpp index 9300b49406..9d59aa376b 100644 --- a/src/simplnx/Parameters/CalculatorParameter.hpp +++ b/src/simplnx/Parameters/CalculatorParameter.hpp @@ -1,15 +1,15 @@ #pragma once #include "simplnx/DataStructure/DataPath.hpp" -#include "simplnx/Filter/MutableDataParameter.hpp" #include "simplnx/Filter/ParameterTraits.hpp" +#include "simplnx/Filter/ValueParameter.hpp" #include "simplnx/simplnx_export.hpp" #include namespace nx::core { -class SIMPLNX_EXPORT CalculatorParameter : public MutableDataParameter +class SIMPLNX_EXPORT CalculatorParameter : public ValueParameter { public: enum AngleUnits : uint8 @@ -78,16 +78,7 @@ class SIMPLNX_EXPORT CalculatorParameter : public MutableDataParameter * @param value * @return */ - Result<> validate(const DataStructure& dataStructure, const std::any& value) const override; - - /** - * @brief Takes the value and a mutable DataStructure and attempts store the actual derived DataObject in the std::any. - * Returns any warnings/errors. - * @param dataStructure - * @param value - * @return - */ - Result resolve(DataStructure& dataStructure, const std::any& value) const override; + Result<> validate(const std::any& value) const override; protected: /**