diff --git a/.clang-tidy b/.clang-tidy
index 6e3af6a2f..65ded0451 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -12,12 +12,10 @@ Checks: ['bugprone-*',
'readability-*',
'zircon-*',
# Remove the following:
- '-bugprone-easily-swappable-parameters',
- '-bugprone-suspicious-semicolon',
'-cert-dcl37-c',
'-cert-dcl50-cpp',
'-cert-dcl51-cpp',
- '-cppcoreguidelines-avoid-magic-numbers',
+ '-cppcoreguidelines-avoid-do-while',
'-cppcoreguidelines-pro-bounds-array-to-pointer-decay',
'-cppcoreguidelines-pro-type-vararg',
'-modernize-use-trailing-return-type',
diff --git a/.clangd b/.clangd
index 41c6fa14d..ba40c50f4 100644
--- a/.clangd
+++ b/.clangd
@@ -28,7 +28,8 @@ Diagnostics:
'cert-dcl51-cpp',
'cppcoreguidelines-pro-type-vararg',
'readability-braces-around-statements',
- 'readability-identifier-length'
+ 'readability-identifier-length',
+ 'cppcoreguidelines-avoid-do-while'
]
---
diff --git a/.idea/runConfigurations/Run_Tests.xml b/.idea/runConfigurations/Run_Tests.xml
new file mode 100644
index 000000000..00d0cd790
--- /dev/null
+++ b/.idea/runConfigurations/Run_Tests.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d49f8fc60..4c71e3a0e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -111,24 +111,11 @@ SET(COMPONENTS
calc::root
calc::subtract
calc::trig
- matrix::ref)
+ matrix::ref
+)
# NEW_COMPONENT: PATCH Do NOT remove the previous comment.
SET(TARGETS ${COMPONENTS} util)
-SET(TEST_TARGETS_TEMP util fraction number mat2d factors format ${COMPONENTS})
-
-FOREACH(TEST_TARGET IN LISTS TEST_TARGETS_TEMP)
- SET(TARGET_NAME "test")
- STRING(REPLACE "::" ";" COMPONENT_PARTS ${TEST_TARGET})
- LIST(LENGTH COMPONENT_PARTS LEN)
- IF(LEN EQUAL 2)
- LIST(GET COMPONENT_PARTS 1 TEST_TARGET)
- ENDIF()
-
- CAPITALIZE(${TEST_TARGET} FILE_NAME)
- STRING(CONCAT TARGET_NAME ${TARGET_NAME} ${FILE_NAME})
- LIST(APPEND TEST_TARGETS ${TARGET_NAME})
-ENDFOREACH()
ADD_SUBDIRECTORY(src/)
ADD_SUBDIRECTORY(lib/)
diff --git a/README.md b/README.md
index 4596218a5..43c2a4f70 100644
--- a/README.md
+++ b/README.md
@@ -5,4 +5,9 @@
[](https://github.com/ZCG-coder/Steppable/actions/workflows/cmake-multi-platform.yml)
[](https://github.com/ZCG-coder/Steppable/actions/workflows/doxygen-gh-pages.yml)
-This project aims to make a Computer Algebra System (CAS) from scratch, and without any external libraries.
+This project aims to build a Computer Algebra System (CAS) from scratch, without any external libraries.
+
+All documentation, usage instructions, and developer guides are available in the [project Wiki](https://github.com/ZCG-coder/Steppable/wiki).
+
+---
+For contribution guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
diff --git a/include/colors.hpp b/include/colors.hpp
index 53203c8f7..de51068b9 100644
--- a/include/colors.hpp
+++ b/include/colors.hpp
@@ -37,6 +37,7 @@
#pragma once
+#include
#include
/**
@@ -61,6 +62,8 @@
namespace steppable::__internals::utils
{
+ using ColorFunc = std::function;
+
/**
* @brief Check if the output stream is a terminal.
*
@@ -87,6 +90,14 @@ namespace steppable::__internals::utils
*/
namespace colors
{
+ /**
+ * @brief Does nothing.
+ *
+ * @param[in] stream The output stream to modify.
+ * @return The modified output stream.
+ */
+ std::ostream& keepOriginal(std::ostream& stream);
+
/**
* @brief Set the text color to black.
*
diff --git a/include/conPlot/conPlot.hpp b/include/conPlot/conPlot.hpp
new file mode 100644
index 000000000..0ac74f407
--- /dev/null
+++ b/include/conPlot/conPlot.hpp
@@ -0,0 +1,40 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include "conPlot/conPlotTypes.hpp"
+
+/**
+ * @namespace steppable::graphing
+ * @brief Graphing utilities for showing graphs in the console.
+ */
+namespace steppable::graphing
+{
+ void conPlot(const std::vector& f,
+ const GraphOptions& graphOptions,
+ const std::vector& linesOptions);
+
+ void conPlotBar(const std::vector>& numbers,
+ const BarGraphOptions& graphOptions,
+ const std::vector& barsOptions);
+} // namespace steppable::graphing
diff --git a/include/conPlot/conPlotInternals.hpp b/include/conPlot/conPlotInternals.hpp
new file mode 100644
index 000000000..b2e56013f
--- /dev/null
+++ b/include/conPlot/conPlotInternals.hpp
@@ -0,0 +1,51 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include "conPlot/conPlotTypes.hpp"
+#include "steppable/number.hpp"
+
+#include
+
+/**
+ * @namespace steppable::graphing::__internals
+ * @brief Graphing utilities for showing graphs in the console.
+ */
+namespace steppable::graphing::__internals
+{
+ void conPlotLine(const Number& xGridSize,
+ const Number& yGridSize,
+ const Number& yMax,
+ const GraphOptions* graphOptions,
+ const LineOptions* lineOptions,
+ prettyPrint::ConsoleOutput* canvas,
+ std::map& fnValues);
+
+ void drawTicks(prettyPrint::ConsoleOutput* canvas,
+ const Number& xGridSize,
+ const Number& yGridSize,
+ const Number& yMax,
+ const GraphOptionsBase* graphOptions);
+
+ void drawGrid(prettyPrint::ConsoleOutput* canvas, const GraphOptionsBase* graphOptions);
+} // namespace steppable::graphing::__internals
diff --git a/include/conPlot/conPlotInterpolation.hpp b/include/conPlot/conPlotInterpolation.hpp
new file mode 100644
index 000000000..2ca2c0a47
--- /dev/null
+++ b/include/conPlot/conPlotInterpolation.hpp
@@ -0,0 +1,46 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include
+
+namespace steppable::graphing
+{
+ /**
+ * @brief Fill in integral values with linear interpolation.
+ *
+ * @param data An x-y corresponding table of values. Must have at least 2 items.
+ * @param xMin Minimum `x` value.
+ * @param xMax Maximum `x` value.
+ */
+ void linearInterpolateFill(std::map* data, long long xMin, long long xMax);
+
+ /**
+ * @brief Fill in integral values with cubic interpolation.
+ *
+ * @param data An x-y corresponding table of values. Must have at least 4 items.
+ * @param xMin Minimum `x` value.
+ * @param xMax Maximum `x` value.
+ */
+ void cubicInterpolateFill(std::map* data, long long xMin, long long xMax);
+} // namespace steppable::graphing
diff --git a/include/conPlot/conPlotTypes.hpp b/include/conPlot/conPlotTypes.hpp
new file mode 100644
index 000000000..5d94ddb7a
--- /dev/null
+++ b/include/conPlot/conPlotTypes.hpp
@@ -0,0 +1,224 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include "colors.hpp"
+#include "steppable/number.hpp"
+#include "steppable/parameter.hpp"
+
+#include
+#include
+#include
+
+namespace steppable::graphing
+{
+ using namespace steppable::__internals::utils;
+ using namespace steppable::__internals::symbols;
+ using namespace steppable::__internals::parameter;
+
+ using GraphFn = std::function;
+
+ /**
+ * @namespace steppable::graphing::GraphDot
+ * @brief Contains string values of dots to be graphed on the screen.
+ */
+ namespace GraphDot
+ {
+ constexpr std::string_view ROUND_DOT = "\u25CF"; ///< Round dot
+ constexpr std::string_view BLOCK = "\u2588"; ///< Solid-filled block
+ constexpr std::string_view LIGHT_BLOCK_1 = "\u2591"; ///< Lightly-filled block
+ constexpr std::string_view LIGHT_BLOCK_2 = "\u2592"; ///< Medium-lightly-filled block
+ constexpr std::string_view LIGHT_BLOCK_3 = "\u2593"; ///< More densly-filled block
+ } // namespace GraphDot
+
+ struct GraphOptionsBase
+ {
+ std::string title = "Graph"; ///< Title of the graph.
+ std::string xAxisTitle = "X Axis Title"; ///< Title of the `x` axis.
+ std::string yAxisTitle = "Y Axis Title"; ///< Title of the `y` axis.
+
+ long long width = 30; ///< Width of the graph.
+ long long height = 20; ///< Height of the graph.
+
+ long long xTickSpacing = 10; ///< Spacing between each `x` axis tick.
+ long long yTickSpacing = 5; ///< Spacing between each `y` axis tick.
+
+ Number xMin = -1; ///< Minimum `x` value.
+ Number xMax = 1; ///< Maximum `x` value.
+ };
+
+ enum class ShadeOptions : std::uint8_t
+ {
+ NO_SHADE = 0,
+ SHADE_ABOVE_FIRST = 1,
+ SHADE_BELOW_SECOND = 2,
+ SHADE_BETWEEN_BOTH = 3,
+ SHADE_OUTSIDE_BOTH = 4,
+ SHADE_ABOVE_SECOND = 5,
+ SHADE_BELOW_FIRST = 6,
+ };
+
+ using NumberPair = std::pair;
+
+ /**
+ * @struct steppable::graphing::GraphOptions
+ * @brief Stores the opotions for each graph shown on screen.
+ */
+ struct GraphOptions : GraphOptionsBase
+ {
+ /**
+ * @brief Initializes a new `GraphOptions` instance.
+ *
+ * @param params The parameters to be passed to initialize the instance.
+ */
+ template
+ GraphOptions(Params... params)
+ {
+ auto map = processParams(params...);
+
+ PARAM_GET_FALLBACK(map, Number, xMin, -1);
+ PARAM_GET_FALLBACK(map, Number, xMax, 1);
+ PARAM_GET_FALLBACK(map, long long, width, 30LL);
+ PARAM_GET_FALLBACK(map, long long, height, 20LL);
+ PARAM_GET_FALLBACK(map, long long, xTickSpacing, 10LL);
+ PARAM_GET_FALLBACK(map, long long, yTickSpacing, 5LL);
+ PARAM_GET_FALLBACK(map, std::string, title, "Graph");
+ PARAM_GET_FALLBACK(map, std::string, xAxisTitle, "X Axis Title");
+ PARAM_GET_FALLBACK(map, std::string, yAxisTitle, "Y Axis Title");
+
+ this->xMin = xMin;
+ this->xMax = xMax;
+ this->width = width;
+ this->height = height;
+ this->xTickSpacing = xTickSpacing;
+ this->yTickSpacing = yTickSpacing;
+ this->title = title;
+ this->xAxisTitle = xAxisTitle;
+ this->yAxisTitle = yAxisTitle;
+ }
+ };
+
+ struct BarGraphOptions : GraphOptionsBase
+ {
+ long long yTickSpacing = 5; ///< Spacing between each `y` axis tick.
+ long long xTickSpacing = 2; ///< Width of a single bar
+ Number xMin = 0;
+ Number xMax = 100;
+
+ /**
+ * @brief Initializes a new `GraphOptions` instance.
+ *
+ * @param params The parameters to be passed to initialize the instance.
+ */
+ template
+ BarGraphOptions(Params... params)
+ {
+ auto map = processParams(params...);
+
+ PARAM_GET_FALLBACK(map, long long, width, 30LL);
+ PARAM_GET_FALLBACK(map, long long, height, 20LL);
+ PARAM_GET_FALLBACK(map, long long, yTickSpacing, 5LL);
+ PARAM_GET_FALLBACK(map, long long, barWidth, 2LL);
+ PARAM_GET_FALLBACK(map, std::string, title, "Bar Graph");
+ PARAM_GET_FALLBACK(map, std::string, xAxisTitle, "X Axis Title");
+ PARAM_GET_FALLBACK(map, std::string, yAxisTitle, "Y Axis Title");
+
+ this->width = width;
+ this->height = height;
+ this->yTickSpacing = yTickSpacing;
+ this->title = title;
+ this->xAxisTitle = xAxisTitle;
+ this->yAxisTitle = yAxisTitle;
+ this->xTickSpacing = barWidth;
+ }
+ };
+
+ struct LineOptionsBase
+ {
+ std::string_view lineDot = GraphDot::BLOCK; ///< Dot type to be drawn on screen.
+ ColorFunc lineColor = colors::green; ///< Color of the dot to output.
+ std::string title = "Line"; ///< Name of the line to be shown in the legend.
+ };
+
+ template
+ concept LineOptionsDerivation = std::derived_from;
+
+ /**
+ * @struct steppable::graphing::LineOptions
+ * @brief Stores options for each line graphed on screen.
+ */
+ struct LineOptions : LineOptionsBase
+ {
+ long long samplesSpacing = 2; ///< Frequency to take a sample, units: grids.
+ NumberPair shadeValues = { 0, 0 };
+ ShadeOptions shadeOptions = ShadeOptions::NO_SHADE;
+ std::string_view shadeDot = GraphDot::LIGHT_BLOCK_1;
+
+ /**
+ * @brief Initializes a new `LineOptions` instance.
+ *
+ * @param params The parameters to be passed to initialize the instance.
+ */
+ template
+ LineOptions(Params... params)
+ {
+ auto map = processParams(params...);
+ PARAM_GET_FALLBACK(map, std::string_view, lineDot, GraphDot::BLOCK);
+ PARAM_GET_FALLBACK(map, ColorFunc, lineColor, (ColorFunc)colors::green);
+ PARAM_GET_FALLBACK(map, std::string, title, "Line"s);
+ PARAM_GET_FALLBACK(map, long long, samplesSpacing, 2LL);
+
+ PARAM_GET_FALLBACK(map, NumberPair, shadeValues, {});
+ PARAM_GET_FALLBACK(map, ShadeOptions, shadeOptions, ShadeOptions::NO_SHADE);
+ PARAM_GET_FALLBACK(map, std::string_view, shadeBlock, GraphDot::LIGHT_BLOCK_1);
+
+ this->lineDot = lineDot;
+ this->lineColor = lineColor;
+ this->title = title;
+ this->samplesSpacing = samplesSpacing;
+
+ this->shadeValues = shadeValues;
+ this->shadeOptions = shadeOptions;
+ this->shadeDot = shadeBlock;
+ }
+ };
+
+ struct BarOptions : LineOptionsBase
+ {
+ std::string title = "Bar";
+
+ template
+ BarOptions(Params... params)
+ {
+ auto map = processParams(params...);
+ PARAM_GET_FALLBACK(map, std::string_view, block, GraphDot::BLOCK);
+ PARAM_GET_FALLBACK(map, ColorFunc, color, colors::keepOriginal);
+ PARAM_GET_FALLBACK(map, long long, barWidth, 2);
+ PARAM_GET_FALLBACK(map, std::string, title, "Bar"s);
+
+ this->lineDot = block;
+ this->lineColor = color;
+ this->title = title;
+ }
+ };
+} // namespace steppable::graphing
diff --git a/include/debugging.hpp b/include/debugging.hpp
new file mode 100644
index 000000000..c7f7d1611
--- /dev/null
+++ b/include/debugging.hpp
@@ -0,0 +1,48 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#pragma once
+#include
+#include
+
+namespace steppable::inspect
+{
+ template
+ void printVector(const std::vector& vector)
+ {
+#ifndef DEBUG
+ return;
+#endif
+
+ std::cout << "[";
+ for (size_t i = 0; i < vector.size(); i++)
+ {
+ std::cout << vector[i];
+
+ if (i != vector.size() - 1)
+ std::cout << ", ";
+ if (i % 5 == 0)
+ std::cout << "\n";
+ }
+ std::cout << "]\n";
+ }
+} // namespace steppable::inspect
\ No newline at end of file
diff --git a/include/rounding.hpp b/include/rounding.hpp
index 21c7bcfde..0f9864ce7 100644
--- a/include/rounding.hpp
+++ b/include/rounding.hpp
@@ -21,7 +21,9 @@
**************************************************************************************************/
#pragma once
-#include
+
+#include "types/rounding.hpp"
+
#include
namespace steppable::__internals::numUtils
diff --git a/include/steppable/number.hpp b/include/steppable/number.hpp
index f2756e251..4f7f97c59 100644
--- a/include/steppable/number.hpp
+++ b/include/steppable/number.hpp
@@ -30,10 +30,12 @@
#pragma once
+#include "rounding.hpp"
#include "testing.hpp"
+#include "types/rounding.hpp"
#include "util.hpp"
-#include
+#include
#include
#include
@@ -43,35 +45,6 @@
*/
namespace steppable
{
- /**
- * @enum RoundingMode
- * @brief Specifies how Steppable should round the number in operations.
- */
- enum class RoundingMode : std::uint8_t
- {
- /// @brief Use the higher precision whenever possible.
- USE_MAXIMUM_PREC = 0xFF,
-
- /// @brief Use the lower precision whenever possible.
- USE_MINIMUM_PREC = 0x01,
-
- /// @brief Use the current precision.
- USE_CURRENT_PREC = 0x02,
-
- /// @brief Use the other number's precision.
- USE_OTHER_PREC = 0x03,
-
- /// @brief Do not append any decimal places.
- DISCARD_ALL_DECIMALS = 0x00
- };
-
- enum class Rounding : std::uint8_t
- {
- ROUND_DOWN = 0x00, ///< Rounds the number down.
- ROUND_UP = 0x01, ///< Rounds the number up.
- ROUND_OFF = 0x02, ///< Rounds the number off.
- };
-
/**
* @class Number
* @brief Represents a number with arbitrary precision. It basically stores the value as a string.
@@ -111,11 +84,6 @@ namespace steppable
}
public:
- /// @brief The default constructor. Initializes the number with a value of 0.
- Number();
-
- Number(const Number& rhs);
-
/**
* @brief Initializes a number with a specified value.
* @note By default, the value is 0.
@@ -138,6 +106,7 @@ namespace steppable
{
this->mode = mode;
prec = newPrec;
+ value = __internals::numUtils::roundOff(value, prec);
}
/**
@@ -306,6 +275,17 @@ namespace steppable
* @return The number as a string.
*/
[[nodiscard]] std::string present() const;
+
+ /**
+ * @brief Gets the absolute value of the number.
+ * @return The absolute value of the current number.
+ */
+ [[nodiscard]] Number abs() const
+ {
+ if (*this > 0)
+ return *this;
+ return -(*this);
+ }
};
/**
@@ -319,3 +299,11 @@ namespace steppable
inline Number operator""_n(unsigned long long value) { return Number(value); }
} // namespace literals
} // namespace steppable
+
+template<>
+struct std::hash
+{
+ size_t operator()(const steppable::Number& n) const { return hash()(n.present()); }
+};
+
+std::ostream& operator<<(std::ostream& os, const steppable::Number& number);
diff --git a/include/steppable/parameter.hpp b/include/steppable/parameter.hpp
new file mode 100644
index 000000000..b38192c9d
--- /dev/null
+++ b/include/steppable/parameter.hpp
@@ -0,0 +1,195 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include "output.hpp"
+#include "platform.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+using namespace std::literals;
+
+/**
+ * @namespace steppable::__internals::parameter
+ * @brief Contains the parameter utilities to allow named parameters to be passed into functions.
+ */
+namespace steppable::__internals::parameter
+{
+ /**
+ * @struct ValuedParameter
+ * @brief A parameter with a name and value.
+ */
+ struct ValuedParameter
+ {
+ std::string name; ///< The name of the parameter.
+ std::any value; ///< The value of the parameter.
+ };
+
+ /// @brief A type containing all the parameters of a function, in calling order.
+ using ValueMap = std::vector;
+
+ /**
+ * @struct Parameter
+ * @brief Stores the name of a parameter
+ */
+ struct Parameter // NOLINT(cppcoreguidelines-special-member-functions)
+ {
+ std::string name; ///< The name of the parameter.
+
+ /**
+ * @brief Shorthand function to facilitate setting values of parameters.
+ *
+ * @param value The value to set for the parameter.
+ * @return A `ValuedParameter` object representing the current parameter.
+ */
+ template
+ ValuedParameter operator=(T value) // NOLINT(cppcoreguidelines-c-copy-assignment-signature)
+ {
+ return { .name = name, .value = std::move(value) };
+ }
+
+ Parameter& operator=(const Parameter&) = delete;
+
+ /**
+ * @brief Initializes a new `Parameter` object.
+ *
+ * @param name The name of the parameter.
+ */
+ explicit Parameter(std::string name) : name(std::move(name)) {}
+ };
+
+ /**
+ * @struct ParameterMap
+ * @brief An object containing all parameters and their values passed into a function.
+ */
+ struct ParameterMap
+ {
+ ValueMap values; ///< All `ValuedParameter` objects passed to a function.
+
+ /**
+ * @brief Initializes a new `ParameterMap` object.
+ *
+ * @param values All `ValuedParameter` objects passed to a function.
+ */
+ ParameterMap(ValueMap values) : values(std::move(values)) {}
+
+ /**
+ * @brief Checks the presence of all the arguments, in the order which they should be specified. Prints an error
+ * message and exits the program if one of the parameter names is not specified according to the order, or the
+ * size of the name vector is not the same as the number of parameters passed into the function.
+ *
+ * @param names The names to check.
+ */
+ void checkParameterOrder(const std::vector& names);
+
+ /**
+ * @brief Checks the presence of all the arguments, no matter which order they are specified. Prints an error
+ * message and exits the program if one of the parameter names specified is not found, or the size of the name
+ * vector is not the same as the number of parameters passed into the function.
+ *
+ * @param names The names to check.
+ */
+ void checkParameterNameUnordered(const std::vector& names);
+
+ /**
+ * @brief Gets an item from the current parameter map.
+ *
+ * @param name The name of the parameter.
+ * @return A value from the parameter list.
+ */
+ template
+ ValueT getItem(const std::string& name)
+ {
+ ValuedParameter value;
+ for (const auto& obj : values)
+ if (obj.name == name)
+ value = obj;
+ if (value.name == "")
+ {
+ output::error("ParameterMap::getItem"s, "Name {0} not found."s, { name });
+ utils::programSafeExit(1);
+ }
+ return std::any_cast(value.value);
+ }
+
+ /**
+ * @brief Gets an item from the current parameter map. Returns the fallback the matching parameter if not found.
+ *
+ * @param name The name of the parameter.
+ * @param fallback If no matching parameter is specified, returns the fallback.
+ * @return A value from the parameter list or the fallback.
+ */
+ template
+ ValueT getItem(const std::string& name, const ValueT& fallback)
+ {
+ ValuedParameter value;
+ for (const auto& obj : values)
+ if (obj.name == name)
+ value = obj;
+ if (value.name == "")
+ return fallback;
+ return std::any_cast(value.value);
+ }
+ };
+
+ /**
+ * @brief Convert a literal to a parameter, using a shorthand notation.
+ *
+ * @param name The name of the parameter, expressed as a simple string literal.
+ * @return A `Parameter` object to set the value of the parameter.
+ */
+ inline Parameter operator""_p(const char* name, size_t /*unused*/) { return Parameter(name); }
+
+ /**
+ * @brief Process parameters passed into a function.
+ * @details Takes in all arguments from a variadic function, and processes them as parameters.
+ *
+ * @param params The parameters passed into a function.
+ * @return An instance of `ParameterMap` containing a list of the parameters.
+ */
+ template
+ ParameterMap processParams(Params... params)
+ {
+ return { std::vector{ params... } };
+ }
+} // namespace steppable::__internals::parameter
+
+/// @brief Get a parameter from a parameter list.
+#define PARAM_GET(map, type, name) \
+ type name; \
+ do \
+ { \
+ (name) = (map).template getItem(#name); \
+ } while (0)
+
+/// @brief Get a parameter from a parameter list. If not found, return the fallback.
+#define PARAM_GET_FALLBACK(map, type, name, fallback) \
+ type name; \
+ do \
+ { \
+ (name) = (map).template getItem(#name, fallback); \
+ } while (0)
diff --git a/include/symbols.hpp b/include/symbols.hpp
index bff6d8b73..fd3a27a58 100644
--- a/include/symbols.hpp
+++ b/include/symbols.hpp
@@ -32,10 +32,14 @@
#pragma once
+#include "colors.hpp"
+
#include
#include
+#include
#include
#include
+#include
#include
/**
@@ -44,6 +48,8 @@
*/
namespace steppable::prettyPrint
{
+ using namespace __internals::utils;
+
/**
* @brief Represents a position in the console.
*/
@@ -66,6 +72,13 @@ namespace steppable::prettyPrint
// PrintingAlignment alignment;
// };
+ enum class HorizontalAlignment : std::uint8_t
+ {
+ LEFT = 0,
+ CENTER = 1,
+ RIGHT = 2
+ };
+
/**
* @brief Represents a console output buffer.
*/
@@ -76,7 +89,7 @@ namespace steppable::prettyPrint
Position curPos;
/// @brief The buffer object.
- std::vector> buffer;
+ std::vector> buffer;
/// @brief The height of the buffer.
size_t height = 10;
@@ -84,6 +97,20 @@ namespace steppable::prettyPrint
/// @brief The width of the buffer.
size_t width = 10;
+ /**
+ * @brief Writes a string to the buffer.
+ *
+ * @param s The string to write.
+ * @param pos The position to write to.
+ * @param updatePos Whether to update the current position.
+ * @param color Color of the text to write.
+ */
+ void _write(const std::string& s,
+ const Position& pos,
+ bool updatePos = false,
+ const ColorFunc& color = colors::keepOriginal,
+ const HorizontalAlignment& alignment = HorizontalAlignment::LEFT);
+
public:
/**
* @brief Creates a new console output buffer.
@@ -100,8 +127,14 @@ namespace steppable::prettyPrint
* @param dLine The change in line.
* @param dCol The change in column.
* @param updatePos Whether to update the current position.
+ * @param color Color of the text to write.
*/
- void write(char c, long long dLine, long long dCol, bool updatePos = false);
+ void write(char c,
+ long long dLine,
+ long long dCol,
+ bool updatePos = false,
+ const ColorFunc& color = colors::keepOriginal,
+ const HorizontalAlignment& alignment = HorizontalAlignment::LEFT);
/**
* @brief Writes a character to the buffer.
@@ -109,17 +142,30 @@ namespace steppable::prettyPrint
* @param c The character to write.
* @param pos The position to write to.
* @param updatePos Whether to update the current position.
+ * @param color Color of the text to write.
*/
- void write(char c, const Position& pos, bool updatePos = false);
+ void write(char c,
+ const Position& pos,
+ bool updatePos = false,
+ const ColorFunc& color = colors::keepOriginal,
+ const HorizontalAlignment& alignment = HorizontalAlignment::LEFT);
/**
- * @brief Writes a string to the buffer.
+ * @brief Writes a `string_view` object to the buffer.
*
* @param s The string to write.
* @param pos The position to write to.
* @param updatePos Whether to update the current position.
+ * @param color Color of the text to write.
*/
- void write(const std::string& s, const Position& pos, bool updatePos = false);
+ void write(const std::string_view& s,
+ const Position& pos,
+ bool updatePos = false,
+ const ColorFunc& color = colors::keepOriginal,
+ const HorizontalAlignment& alignment = HorizontalAlignment::LEFT)
+ {
+ _write(static_cast(s), pos, updatePos, color, alignment);
+ }
/**
* @brief Gets the buffer as a string.
@@ -145,6 +191,112 @@ namespace steppable::prettyPrint
size_t getStringHeight(const std::string& s);
} // namespace steppable::prettyPrint
+namespace steppable::__internals::stringUtils
+{
+
+ /**
+ * @brief Checks if a Unicode code point represents a zero-width character.
+ *
+ * Zero-width characters do not consume display space (e.g., combining marks, zero-width spaces).
+ *
+ * @param codepoint The Unicode code point to check.
+ * @return true if the code point is a zero-width character, false otherwise.
+ */
+ bool isZeroWidthCharacter(uint32_t codepoint);
+
+ /**
+ * @brief Decodes a UTF-8 encoded string into a Unicode code point.
+ *
+ * This function reads one UTF-8 encoded character from the string, starting at index `i`,
+ * and advances the index to the next character.
+ *
+ * @param str The UTF-8 encoded string.
+ * @param i The current position in the string (updated after decoding).
+ * @return The Unicode code point of the character at the given position.
+ */
+ uint32_t getCodepoint(const std::string& str, size_t& i);
+
+ /**
+ * @brief Calculates the display width of a UTF-8 encoded string.
+ *
+ * This function computes the total display width of a string, correctly handling wide, narrow,
+ * zero-width, and CJK characters. CJK characters are counted as one character.
+ *
+ * @param utf8Str The UTF-8 encoded string.
+ * @return The total display width of the string.
+ */
+ size_t getUnicodeDisplayWidth(const std::string& utf8Str);
+
+ bool utf8Decode(const std::string& s, size_t& i, uint32_t& cp);
+
+ bool isEmojiBase(uint32_t cp);
+
+ bool isClusterExtender(uint32_t cp);
+
+ class GraphemeIterator
+ {
+ public:
+ GraphemeIterator(std::string s) : str(std::move(s)) {}
+
+ // Returns false when there are no more clusters
+ bool next(std::string& cluster)
+ {
+ if (pos >= str.size())
+ return false;
+ size_t start = pos;
+ uint32_t cp = 0;
+ size_t temp = pos;
+ if (!utf8Decode(str, temp, cp))
+ return false;
+
+ // For emoji: group sequences with ZWJ or skin tone
+ if (isEmojiBase(cp) || (cp >= 0x1F1E6 && cp <= 0x1F1FF))
+ {
+ pos = temp;
+ while (true)
+ {
+ size_t save = pos;
+ uint32_t nextcp = 0;
+ if (!utf8Decode(str, save, nextcp))
+ break;
+ if (isClusterExtender(nextcp) ||
+ // For flags: two regional indicators
+ ((cp >= 0x1F1E6 && cp <= 0x1F1FF) && (nextcp >= 0x1F1E6 && nextcp <= 0x1F1FF)))
+ {
+ pos = save;
+ }
+ else
+ {
+ break;
+ }
+ }
+ cluster = str.substr(start, pos - start);
+ return true;
+ }
+
+ // For combining marks: group base + following combining
+ pos = temp;
+ while (true)
+ {
+ size_t save = pos;
+ uint32_t nextcp = 0;
+ if (!utf8Decode(str, save, nextcp))
+ break;
+ if (isZeroWidthCharacter(nextcp))
+ pos = save;
+ else
+ break;
+ }
+ cluster = str.substr(start, pos - start);
+ return true;
+ }
+
+ private:
+ std::string str;
+ size_t pos{};
+ };
+} // namespace steppable::__internals::stringUtils
+
/**
* @namespace steppable::__internals::symbols
* @brief The namespace containing various unicode symbols.
@@ -249,6 +401,33 @@ namespace steppable::__internals::symbols
* @return The surd expression.
*/
std::string makeSurd(const std::string& radicand);
+
+ /**
+ * @namespace steppable::__internals::symbols::BoxDrawing
+ * @brief Defines easy ascess to Unicode box-drawing characters.
+ */
+ namespace BoxDrawing
+ {
+ constexpr std::string_view DOTTED_VERTICAL = "\u2575"; ///< Dashed vertical line
+ constexpr std::string_view DOTTED_HORIZONTAL = "\u2574"; ///< Dashed horizontal line
+
+ constexpr std::string_view HORIZONTAL = "\u2500"; ///< Solid horizontal line
+ constexpr std::string_view VERTICAL = "\u2502"; ///< Solid vertical line
+
+ // region CONNECTORS
+ /// @brief Horizontal line that connects to a vertical one, i.e., in `_|_` shape.
+ constexpr std::string_view HORIZONTAL_UP = "\u2534";
+
+ /// @brief Vertical line that connects to a left one, i.e., in `-|` shape.
+ constexpr std::string_view VERTICAL_LEFT = "\u2524";
+ // endregion
+
+ // region CORNERS
+ constexpr std::string_view BOTTOM_RIGHT_CORNER = "\u2518"; ///< The bottom right corner
+ // endregion
+
+ constexpr std::string_view CROSS = "\u253C"; ///< A combining cross between a horizontal and vertical line.
+ } // namespace BoxDrawing
} // namespace steppable::__internals::symbols
/**
diff --git a/include/types/concepts.hpp b/include/types/concepts.hpp
index 058c183fe..b2458cf29 100644
--- a/include/types/concepts.hpp
+++ b/include/types/concepts.hpp
@@ -26,7 +26,10 @@
* @date 23 Jun 2025
*/
+#pragma once
+
#include
+#include
#include
/**
@@ -43,4 +46,10 @@ namespace steppable::concepts
concept Presentable = requires(ObjectT object) {
{ object.present() } -> std::same_as;
};
+
+ template
+ /// @brief Represents any streams with a `.operator<<()` method.
+ concept Stream = requires(ObjectT object) {
+ { object.operator<<() } -> std::same_as;
+ };
} // namespace steppable::concepts
diff --git a/include/types/rounding.hpp b/include/types/rounding.hpp
new file mode 100644
index 000000000..cf1478872
--- /dev/null
+++ b/include/types/rounding.hpp
@@ -0,0 +1,57 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include
+
+namespace steppable
+{
+ /**
+ * @enum RoundingMode
+ * @brief Specifies how Steppable should round the number in operations.
+ */
+ enum class RoundingMode : std::uint8_t
+ {
+ /// @brief Use the higher precision whenever possible.
+ USE_MAXIMUM_PREC = 0xFF,
+
+ /// @brief Use the lower precision whenever possible.
+ USE_MINIMUM_PREC = 0x01,
+
+ /// @brief Use the current precision.
+ USE_CURRENT_PREC = 0x02,
+
+ /// @brief Use the other number's precision.
+ USE_OTHER_PREC = 0x03,
+
+ /// @brief Do not append any decimal places.
+ DISCARD_ALL_DECIMALS = 0x00
+ };
+
+ enum class Rounding : std::uint8_t
+ {
+ ROUND_DOWN = 0x00, ///< Rounds the number down.
+ ROUND_UP = 0x01, ///< Rounds the number up.
+ ROUND_OFF = 0x02, ///< Rounds the number off.
+ };
+} // namespace steppable
diff --git a/include/util.hpp b/include/util.hpp
index b553f2da7..e071ea9cf 100644
--- a/include/util.hpp
+++ b/include/util.hpp
@@ -41,9 +41,11 @@
#include "colors.hpp"
#include "output.hpp"
#include "platform.hpp"
+#include "types/concepts.hpp"
#include
#include
+#include
#include
#include
#include
@@ -607,4 +609,17 @@ namespace steppable::__internals::stringUtils
}
return out.str();
}
+
+ template
+ T toNumeric(const std::string& s)
+ {
+ auto value = T{};
+
+ if (auto result = std::from_chars(s.data(), s.data() + s.size(), value); result.ec != std::errc{})
+ {
+ output::error("toNumeric"s, "Invalid argument"s);
+ utils::programSafeExit(1);
+ }
+ return value;
+ }
} // namespace steppable::__internals::stringUtils
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 2c731b357..a489817e9 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -20,27 +20,31 @@
# SOFTWARE. #
#####################################################################################################
-if(NOT ${STP_NO_BINDINGS}) # Only create the bindings if needed
- set(Python_EXECUTABLE ${Python3_EXECUTABLE})
+IF(NOT ${STP_NO_BINDINGS}) # Only create the bindings if needed
+ SET(Python_EXECUTABLE ${Python3_EXECUTABLE})
# Detect the installed nanobind package and import it into CMake
- execute_process(
+ EXECUTE_PROCESS(
COMMAND "${Python3_EXECUTABLE}" -m nanobind --cmake_dir
OUTPUT_STRIP_TRAILING_WHITESPACE
- OUTPUT_VARIABLE NB_DIR)
- message(TRACE "Found nanobind: ${NB_DIR}")
- list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
- find_package(nanobind CONFIG REQUIRED)
+ OUTPUT_VARIABLE NB_DIR
+ )
+ MESSAGE(TRACE "Found nanobind: ${NB_DIR}")
+ LIST(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
+ FIND_PACKAGE(nanobind CONFIG REQUIRED)
# Python bindings for Steppable.
- set(Python_INTERPRETER_ID ${Python3_INTERPRETER_ID} CACHE INTERNAL "Compatibility for nanobind")
- nanobind_add_module(steppyble STABLE_ABI NB_STATIC LTO bindings.cpp)
+ SET(Python_INTERPRETER_ID
+ ${Python3_INTERPRETER_ID}
+ CACHE INTERNAL "Compatibility for nanobind"
+ )
+ NANOBIND_ADD_MODULE(steppyble STABLE_ABI NB_STATIC LTO bindings.cpp)
- if(NOT ${WINDOWS})
- target_link_options(steppyble PRIVATE -Bsymbolic)
- endif()
- set_target_properties(steppyble PROPERTIES POSITION_INDEPENDENT_CODE ON)
- target_link_libraries(steppyble PRIVATE func steppable)
- target_compile_definitions(steppyble PRIVATE NO_MAIN STP_BINDINGS)
- target_include_directories(steppyble PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
-endif()
+ IF(NOT ${WINDOWS})
+ TARGET_LINK_OPTIONS(steppyble PRIVATE -Bsymbolic)
+ ENDIF()
+ SET_TARGET_PROPERTIES(steppyble PROPERTIES POSITION_INDEPENDENT_CODE ON)
+ TARGET_LINK_LIBRARIES(steppyble PRIVATE func steppable)
+ TARGET_COMPILE_DEFINITIONS(steppyble PRIVATE NO_MAIN STP_BINDINGS)
+ TARGET_INCLUDE_DIRECTORIES(steppyble PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
+ENDIF()
diff --git a/res/translations/en-US/mat2d.stp_localized b/res/translations/en-US/mat2d.stp_localized
new file mode 100644
index 000000000..24530dba2
--- /dev/null
+++ b/res/translations/en-US/mat2d.stp_localized
@@ -0,0 +1,35 @@
+#####################################################################################################
+# Copyright (c) 2023-2025 NWSOFT #
+# #
+# Permission is hereby granted, free of charge, to any person obtaining a copy #
+# of this software and associated documentation files (the "Software"), to deal #
+# in the Software without restriction, including without limitation the rights #
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #
+# copies of the Software, and to permit persons to whom the Software is #
+# furnished to do so, subject to the following conditions: #
+# #
+# The above copyright notice and this permission notice shall be included in all #
+# copies or substantial portions of the Software. #
+# #
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #
+# SOFTWARE. #
+#####################################################################################################
+
+# NOTE: This file is generated. Do not edit it manually. Any changes will be overwritten.
+# STR_GUID: (key) / STRING TRANSLATED: (string)
+# eg: a491b7b2-1239-4acb-9045-0747d806b96f >> "Hello World!"
+# Recommended syntax highlighting: Bash Script
+75953952-0eea-4716-a006-d0f2e7a8f6c9 >> "Matrix has non-uniform dimensions."
+8d4e4757-415b-4aed-8f5e-26b3503a95dd >> "Incorrect x parameter. {0} exceeds the number of columns in this matrix ({1})."
+e7cb3f0b-11d8-4e12-8c93-4a1021b15e10 >> "Incorrect y parameter. {0} exceeds the number of rows in this matrix ({1})."
+fe78bdc2-b409-4078-8e0e-313c46977f25 >> "Matrix is not a square."
+88331f88-3a4c-4b7e-9b43-b51a1d1020e2 >> "Matrix dimensions mismatch. Expect {0} columns. Got {1} columns."
+34e92306-a4d8-4ff0-8441-bfcd29771e94 >> "Matrix dimensions mismatch. Expect {0} rows. Got {1} rows."
+17b6aadd-bce1-4558-a7cc-7a099f00e57c >> "Incorrect matrix dimensions for multiplication."
+8966ce13-8ae9-4f14-ba4e-837b98a4c9fa >> "For matrix multiplication, the number of columns in the first matrix must be equal to the number of rows in the second matrix."
+f255d307-9482-442b-a523-61a1c7465f9c >> "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}."
\ No newline at end of file
diff --git a/res/translations/mat2d.stp_strings b/res/translations/mat2d.stp_strings
new file mode 100644
index 000000000..5f6120909
--- /dev/null
+++ b/res/translations/mat2d.stp_strings
@@ -0,0 +1,35 @@
+#####################################################################################################
+# Copyright (c) 2023-2025 NWSOFT #
+# #
+# Permission is hereby granted, free of charge, to any person obtaining a copy #
+# of this software and associated documentation files (the "Software"), to deal #
+# in the Software without restriction, including without limitation the rights #
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #
+# copies of the Software, and to permit persons to whom the Software is #
+# furnished to do so, subject to the following conditions: #
+# #
+# The above copyright notice and this permission notice shall be included in all #
+# copies or substantial portions of the Software. #
+# #
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #
+# SOFTWARE. #
+#####################################################################################################
+
+# NOTE: This file is generated. Do not edit it manually. Any changes will be overwritten.
+# STR_GUID: (key) / STRING ORIGINAL: (string)
+# eg: a491b7b2-1239-4acb-9045-0747d806b96f >> "Hello World!"
+# Recommended syntax highlighting: Bash Script
+75953952-0eea-4716-a006-d0f2e7a8f6c9 >> "Matrix has non-uniform dimensions."
+8d4e4757-415b-4aed-8f5e-26b3503a95dd >> "Incorrect x parameter. {0} exceeds the number of columns in this matrix ({1})."
+e7cb3f0b-11d8-4e12-8c93-4a1021b15e10 >> "Incorrect y parameter. {0} exceeds the number of rows in this matrix ({1})."
+fe78bdc2-b409-4078-8e0e-313c46977f25 >> "Matrix is not a square."
+88331f88-3a4c-4b7e-9b43-b51a1d1020e2 >> "Matrix dimensions mismatch. Expect {0} columns. Got {1} columns."
+34e92306-a4d8-4ff0-8441-bfcd29771e94 >> "Matrix dimensions mismatch. Expect {0} rows. Got {1} rows."
+17b6aadd-bce1-4558-a7cc-7a099f00e57c >> "Incorrect matrix dimensions for multiplication."
+8966ce13-8ae9-4f14-ba4e-837b98a4c9fa >> "For matrix multiplication, the number of columns in the first matrix must be equal to the number of rows in the second matrix."
+f255d307-9482-442b-a523-61a1c7465f9c >> "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}."
diff --git a/res/translations/zh-HK/mat2d.stp_localized b/res/translations/zh-HK/mat2d.stp_localized
new file mode 100644
index 000000000..7db1b9c58
--- /dev/null
+++ b/res/translations/zh-HK/mat2d.stp_localized
@@ -0,0 +1,35 @@
+#####################################################################################################
+# Copyright (c) 2023-2025 NWSOFT #
+# #
+# Permission is hereby granted, free of charge, to any person obtaining a copy #
+# of this software and associated documentation files (the "Software"), to deal #
+# in the Software without restriction, including without limitation the rights #
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #
+# copies of the Software, and to permit persons to whom the Software is #
+# furnished to do so, subject to the following conditions: #
+# #
+# The above copyright notice and this permission notice shall be included in all #
+# copies or substantial portions of the Software. #
+# #
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #
+# SOFTWARE. #
+#####################################################################################################
+
+# NOTE: This file is generated. Do not edit it manually. Any changes will be overwritten.
+# STR_GUID: (key) / STRING TRANSLATED: (string)
+# eg: a491b7b2-1239-4acb-9045-0747d806b96f >> "Hello World!"
+# Recommended syntax highlighting: Bash Script
+75953952-0eea-4716-a006-d0f2e7a8f6c9 >> "矩陣大小不均勻。"
+8d4e4757-415b-4aed-8f5e-26b3503a95dd >> "x 參數錯誤。{0} 超過了矩陣中的列數({1})。"
+e7cb3f0b-11d8-4e12-8c93-4a1021b15e10 >> "y 參數錯誤。{0} 超過了矩陣中的行數({1})。"
+fe78bdc2-b409-4078-8e0e-313c46977f25 >> "矩陣不為方形。"
+88331f88-3a4c-4b7e-9b43-b51a1d1020e2 >> "矩陣大小不匹配。需要 {0} 列,輸入為 {1} 列。"
+34e92306-a4d8-4ff0-8441-bfcd29771e94 >> "矩陣大小不匹配。需要 {0} 行,輸入為 {1} 行。"
+17b6aadd-bce1-4558-a7cc-7a099f00e57c >> "矩陣大小不適用乘法。"
+8966ce13-8ae9-4f14-ba4e-837b98a4c9fa >> "兩個矩陣的乘法僅當第一個矩陣的列數和B的行數相等時才能定義。"
+f255d307-9482-442b-a523-61a1c7465f9c >> "矩陣 B 大小錯誤。需要 {0} 行,輸入為 {1} 行。"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 112145fc5..dff57541d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -20,6 +20,8 @@
# SOFTWARE. #
#####################################################################################################
+ADD_SUBDIRECTORY(conPlot)
+
ADD_LIBRARY(
util STATIC
argParse.cpp
@@ -62,12 +64,16 @@ FOREACH(COMPONENT IN LISTS COMPONENTS)
ENDFOREACH()
# FUNC: Library containing all Steppable functions.
-ADD_LIBRARY(func STATIC ${CALCULATOR_FILES}
- steppable/fraction.cpp
- steppable/mat2d.cpp
- steppable/number.cpp
- rounding.cpp
- factors.cpp)
+ADD_LIBRARY(
+ func STATIC
+ ${CALCULATOR_FILES}
+ ${CONPLOT_FILES}
+ steppable/fraction.cpp
+ steppable/mat2d.cpp
+ steppable/number.cpp
+ rounding.cpp
+ factors.cpp
+)
SET_TARGET_PROPERTIES(func PROPERTIES POSITION_INDEPENDENT_CODE ON)
TARGET_INCLUDE_DIRECTORIES(steppable PRIVATE ${STP_BASE_DIRECTORY}/include/)
@@ -75,6 +81,8 @@ TARGET_INCLUDE_DIRECTORIES(func PRIVATE ${STP_BASE_DIRECTORY}/include/)
TARGET_INCLUDE_DIRECTORIES(util PRIVATE ${STP_BASE_DIRECTORY}/include/)
TARGET_LINK_LIBRARIES(steppable PRIVATE util)
-TARGET_LINK_LIBRARIES(func PRIVATE steppable)
+TARGET_LINK_LIBRARIES(steppable PRIVATE conPlot)
+TARGET_LINK_LIBRARIES(func PRIVATE util)
+TARGET_LINK_LIBRARIES(func PRIVATE conPlot)
TARGET_COMPILE_DEFINITIONS(func PRIVATE NO_MAIN)
TARGET_COMPILE_DEFINITIONS(steppable PRIVATE NO_MAIN)
diff --git a/src/calc/add/add.cpp b/src/calc/add/add.cpp
index 1b3a8a3fc..009f51990 100644
--- a/src/calc/add/add.cpp
+++ b/src/calc/add/add.cpp
@@ -92,14 +92,8 @@ namespace steppable::__internals::calc
const bool aIsDecimal = not isZeroString(aDecimal);
const bool bIsDecimal = not isZeroString(bDecimal);
- if (negative)
+ if (negative or (aIsNegative and bIsNegative))
resultIsNegative = true;
- else if (aIsNegative and bIsNegative)
- {
- resultIsNegative = true;
- aIsNegative = false;
- bIsNegative = false;
- }
else if (aIsNegative)
{
if (steps == 2)
@@ -158,6 +152,11 @@ namespace steppable::__internals::calc
if (sumDigits.front() == 0 and properlyFormat)
sumDigits.erase(sumDigits.begin());
+ if (aIsNegative)
+ aInteger = "-" + aInteger;
+ if (bIsNegative)
+ bInteger = "-" + bInteger;
+
return reportAdd(
aInteger, aDecimal, bInteger, bDecimal, sumDigits, carries, resultIsNegative, steps, properlyFormat);
}
diff --git a/src/calc/add/addReport.cpp b/src/calc/add/addReport.cpp
index bfd0f5ce1..37d8a2d98 100644
--- a/src/calc/add/addReport.cpp
+++ b/src/calc/add/addReport.cpp
@@ -74,13 +74,15 @@ std::string reportAdd(const std::string& aInteger,
if (bOut.length() > aOut.length())
ss << std::string(bOut.length() - aOut.length(), ' ');
for (const char aChar : aOut)
- ss << aChar << " ";
+ if (aChar != '-')
+ ss << aChar << " ";
ss << '\n' << "+ "; // Print an add sign before printing b
if (aOut.length() > bOut.length())
ss << std::string(aOut.length() - bOut.length(), ' ');
for (const char bChar : bOut)
- ss << bChar << " ";
+ if (bChar != '-')
+ ss << bChar << " ";
ss << '\n' << " ";
for (const int c : carries)
@@ -106,6 +108,8 @@ std::string reportAdd(const std::string& aInteger,
ss << aOut << " + " << bOut << " = ";
std::string outStr;
+ if (resultIsNegative)
+ outStr = "-";
for (const auto c : sumDigits)
if (c == -1)
outStr += ".";
diff --git a/src/calc/division/division.cpp b/src/calc/division/division.cpp
index 1df54e640..2eba42298 100644
--- a/src/calc/division/division.cpp
+++ b/src/calc/division/division.cpp
@@ -130,6 +130,10 @@ namespace steppable::__internals::calc
error("division", $("division", "977f3c9f-01c3-49e4-bf4a-94d7c58bbe82", { _number }));
return "Infinity";
}
+ if (isZeroString(_number))
+ {
+ return "0";
+ }
if (compare(_number, _divisor, 0) == "2")
{
diff --git a/src/calc/subtract/subtract.cpp b/src/calc/subtract/subtract.cpp
index c073a6707..4bd074e9f 100644
--- a/src/calc/subtract/subtract.cpp
+++ b/src/calc/subtract/subtract.cpp
@@ -76,7 +76,7 @@ namespace steppable::__internals::calc
{
if (steps == 2)
// Adding {0} and {1} since {0} is negative
- std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a.substr(1), b }) << "\n";
+ std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a.substr(1), b, b }) << "\n";
auto addResult = add(a.substr(1), b, steps, true);
auto res = addResult.substr(addResult.find_last_of(' ') + 1);
if (steps == 2)
@@ -84,23 +84,22 @@ namespace steppable::__internals::calc
// Replace last line with a subtraction report
std::stringstream ss;
ss << addResult.substr(0, addResult.find_last_of('\n')) << '\n';
- ss << THEREFORE << ' ' << a << " - " << b << " = -" << res;
+ ss << THEREFORE << ' ' << a << " - " << b << " = " << res;
return ss.str();
}
if (steps == 1)
{
std::stringstream ss;
- ss << THEREFORE << ' ' << a << " - " << b << " = -" << res;
+ ss << THEREFORE << ' ' << a << " - " << b << " = " << res;
return ss.str();
}
- return "-" + addResult;
+ return standardizeNumber(addResult);
}
if (bIsNegative)
{
if (steps == 2)
// Adding {0} and {1} since {2} is negative
std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a, b.substr(1), b }) << "\n";
- resultIsNegative = false;
return add(a, b.substr(1), steps);
}
if (compare(a, b, 0) == "0")
@@ -122,7 +121,7 @@ namespace steppable::__internals::calc
<< subtractResult.substr(subtractResult.find_last_of(' ') + 1);
return ss.str();
}
- return "-" + subtractResult;
+ return standardizeNumber("-" + subtractResult);
}
std::string aStr = aInteger + aDecimal;
diff --git a/src/colors.cpp b/src/colors.cpp
index d7b777cde..84f281dca 100644
--- a/src/colors.cpp
+++ b/src/colors.cpp
@@ -25,14 +25,46 @@
#include
#ifdef WINDOWS
-#include
-#include
-#include
-#include
-// ReSharper disable once CppInconsistentNaming
-#define isatty _isatty
-// ReSharper disable once CppInconsistentNaming
-#define fileno _fileno
+ // clang-format off
+ #include
+ // clang-format on
+
+ #include
+ #include
+ #include
+ // ReSharper disable once CppInconsistentNaming
+ #define isatty _isatty
+ // ReSharper disable once CppInconsistentNaming
+ #define fileno _fileno
+
+double* sysVersion = nullptr;
+
+// https://stackoverflow.com/a/52122386/14868780
+/**
+* @brief Gets the Windows version as a double.
+* @warning This method is only available on Windows.
+* @returns The Windows version as double, for example, 10.0.
+*/
+double getSysOpType()
+{
+ if (sysVersion != nullptr)
+ return *sysVersion;
+
+ double ret = 0;
+ sysVersion = new double;
+ NTSTATUS(WINAPI * RtlGetVersion)(LPOSVERSIONINFOEXW);
+ OSVERSIONINFOEXW osInfo;
+
+ *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion");
+
+ if (NULL != RtlGetVersion)
+ {
+ osInfo.dwOSVersionInfoSize = sizeof(osInfo);
+ RtlGetVersion(&osInfo);
+ ret = (double)osInfo.dwMajorVersion;
+ }
+ *sysVersion = ret;
+}
#else
#include
#endif
@@ -42,7 +74,7 @@ namespace steppable::__internals::utils
bool isTerminal(const std::ostream& stream)
{
#ifdef WINDOWS
- if (IsWindows10OrGreater())
+ if (getSysOpType() >= 10.0)
return isatty(fileno(stdout)) != 0;
return false; // The Windows console in Windows 7 does not support ANSI escapes
#else
@@ -59,6 +91,12 @@ namespace steppable::__internals::utils
namespace colors
{
+ std::ostream& keepOriginal(std::ostream& stream)
+ {
+ stream << "";
+ return stream;
+ }
+
std::ostream& black(std::ostream& stream)
{
if (isTerminal(stream))
diff --git a/src/conPlot/CMakeLists.txt b/src/conPlot/CMakeLists.txt
new file mode 100644
index 000000000..c1aa9e841
--- /dev/null
+++ b/src/conPlot/CMakeLists.txt
@@ -0,0 +1,29 @@
+#####################################################################################################
+# Copyright (c) 2023-2025 NWSOFT #
+# #
+# Permission is hereby granted, free of charge, to any person obtaining a copy #
+# of this software and associated documentation files (the "Software"), to deal #
+# in the Software without restriction, including without limitation the rights #
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #
+# copies of the Software, and to permit persons to whom the Software is #
+# furnished to do so, subject to the following conditions: #
+# #
+# The above copyright notice and this permission notice shall be included in all #
+# copies or substantial portions of the Software. #
+# #
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #
+# SOFTWARE. #
+#####################################################################################################
+
+SET(CONPLOT_FILES ${STP_BASE_DIRECTORY}/src/conPlot/conPlot.cpp ${STP_BASE_DIRECTORY}/src/conPlot/conPlotInternals.cpp
+ ${STP_BASE_DIRECTORY}/src/conPlot/conPlotInterpolation.cpp
+)
+
+ADD_LIBRARY(conPlot STATIC ${CONPLOT_FILES})
+SET_TARGET_PROPERTIES(conPlot PROPERTIES POSITION_INDEPENDENT_CODE ON)
+TARGET_INCLUDE_DIRECTORIES(conPlot PRIVATE ${STP_BASE_DIRECTORY}/include/)
diff --git a/src/conPlot/conPlot.cpp b/src/conPlot/conPlot.cpp
new file mode 100644
index 000000000..5e2795608
--- /dev/null
+++ b/src/conPlot/conPlot.cpp
@@ -0,0 +1,173 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#include "conPlot/conPlot.hpp"
+
+#include "conPlot/conPlotInternals.hpp"
+#include "conPlot/conPlotTypes.hpp"
+#include "debugging.hpp"
+#include "steppable/number.hpp"
+#include "symbols.hpp"
+
+#include
+#include
+
+namespace steppable::graphing
+{
+ namespace
+ {
+ template
+ void conPlotLegend(const GraphOptionsBase* graphOptions,
+ const std::vector* lineOptions,
+ prettyPrint::ConsoleOutput* canvas)
+ {
+ long long yLoc = graphOptions->height + 7;
+ canvas->write("Legend", { .x = 0, .y = yLoc++ }, false, formats::bold);
+ for (size_t fnIdx = 0; fnIdx < lineOptions->size(); fnIdx++)
+ {
+ const auto& lineOption = lineOptions->at(fnIdx);
+ yLoc += static_cast(fnIdx);
+ canvas->write(lineOption.title, { .x = 0, .y = yLoc }, false);
+
+ for (int i = 0; i < graphOptions->xTickSpacing; i++)
+ canvas->write(lineOption.lineDot,
+ { .x = static_cast(lineOption.title.length()) + 1 + i, .y = yLoc },
+ false,
+ lineOption.lineColor);
+ }
+ }
+ } // namespace
+
+ void conPlotBar(const std::vector>& numbers,
+ const BarGraphOptions& graphOptions,
+ const std::vector& barsOptions)
+ {
+ // Create buffer
+ prettyPrint::ConsoleOutput canvas(graphOptions.height + 10,
+ graphOptions.width + 12 +
+ steppable::__internals::stringUtils::getUnicodeDisplayWidth(
+ graphOptions.yAxisTitle)); // Extra space for labels
+
+ Number maxValue = 0;
+ size_t datasetSize = numbers[0].size();
+ for (const auto& dataset : numbers)
+ {
+ if (dataset.size() != datasetSize)
+ {
+ output::error("conPlotBar"s, "Bar plot datasets are not of the same size"s);
+ programSafeExit(1);
+ }
+ for (const auto& number : dataset)
+ maxValue = std::max(maxValue, number);
+ }
+ const auto& gridSize = maxValue / graphOptions.height;
+
+ __internals::drawGrid(&canvas, &graphOptions);
+ __internals::drawTicks(&canvas, 0, gridSize, maxValue, &graphOptions);
+
+ for (long long i = 0; i < numbers.size(); i++)
+ {
+ const auto& dataset = numbers[i];
+
+ std::vector yValues;
+ std::vector xValues;
+ long long xGridLoc = graphOptions.xTickSpacing * (1 + i);
+ for (const auto& number : dataset)
+ {
+ long long yGridLoc = std::stoll(((maxValue - number) / gridSize).present());
+ yValues.emplace_back(yGridLoc);
+ xValues.emplace_back(xGridLoc);
+
+ canvas.write(
+ barsOptions[i].lineDot, { .x = xGridLoc, .y = 3 + yGridLoc }, false, barsOptions[i].lineColor);
+ for (long long x = 0; x < graphOptions.xTickSpacing; x++)
+ for (long long y = yGridLoc; y < graphOptions.height; y++)
+ canvas.write(
+ barsOptions[i].lineDot, { .x = xGridLoc + x, .y = 3 + y }, false, barsOptions[i].lineColor);
+
+ xGridLoc += graphOptions.xTickSpacing * (1 + numbers.size());
+ }
+ }
+
+ conPlotLegend(&graphOptions, &barsOptions, &canvas);
+ std::cout << canvas.asString() << "\n";
+ }
+
+ void conPlot(const std::vector& f,
+ const GraphOptions& graphOptions,
+ const std::vector& linesOptions)
+ {
+ // Create buffer
+ prettyPrint::ConsoleOutput canvas(graphOptions.height + 10,
+ graphOptions.width + 12 +
+ steppable::__internals::stringUtils::getUnicodeDisplayWidth(
+ graphOptions.yAxisTitle)); // Extra space for labels
+ std::map> fnValues;
+
+ // Sample min/max
+ Number yMin;
+ for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx)
+ {
+ yMin = f[fnIdx](graphOptions.xMin);
+ fnValues[fnIdx][graphOptions.xMin] = yMin;
+ }
+ Number yMax = yMin;
+ // 1 grid = 1 character on screen
+ Number xGridSize = (graphOptions.xMax - graphOptions.xMin) / graphOptions.width;
+
+ // Calculate range of values
+ for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx)
+ {
+ for (long long i = 0; i <= graphOptions.width; ++i)
+ {
+ if (i % linesOptions[fnIdx].samplesSpacing == 0)
+ {
+ Number x = graphOptions.xMin + Number(i) * xGridSize;
+ Number y = f[fnIdx](x);
+ fnValues[fnIdx][x] = y;
+ yMin = std::min(y, yMin);
+ yMax = std::max(y, yMax);
+ }
+ }
+
+ if ((yMax - yMin).abs() < 1e-12)
+ {
+ yMin -= 1.0;
+ yMax += 1.0;
+ }
+ }
+
+ // Axis positions
+ Number yGridSize = (yMax - yMin) / graphOptions.height;
+ __internals::drawGrid(&canvas, &graphOptions);
+ __internals::drawTicks(&canvas, xGridSize, yGridSize, yMax, &graphOptions);
+
+ // Plot function
+ for (size_t fnIdx = 0; fnIdx < f.size(); ++fnIdx)
+ __internals::conPlotLine(
+ xGridSize, yGridSize, yMax, &graphOptions, &linesOptions[fnIdx], &canvas, fnValues[fnIdx]);
+ conPlotLegend(&graphOptions, &linesOptions, &canvas);
+ std::cout << canvas.asString() << "\n";
+ }
+
+ void pieChart() {}
+} // namespace steppable::graphing
diff --git a/src/conPlot/conPlotInternals.cpp b/src/conPlot/conPlotInternals.cpp
new file mode 100644
index 000000000..2b33f2fda
--- /dev/null
+++ b/src/conPlot/conPlotInternals.cpp
@@ -0,0 +1,210 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#include "conPlot/conPlotInternals.hpp"
+
+#include "conPlot/conPlot.hpp"
+#include "conPlot/conPlotInterpolation.hpp"
+#include "conPlot/conPlotTypes.hpp"
+#include "steppable/number.hpp"
+#include "symbols.hpp"
+
+#include
+#include
+#include
+
+namespace steppable::graphing::__internals
+{
+ void conPlotLine(const Number& xGridSize,
+ const Number& yGridSize,
+ const Number& yMax,
+ const GraphOptions* graphOptions,
+ const LineOptions* lineOptions,
+ prettyPrint::ConsoleOutput* canvas,
+ std::map& fnValues)
+ {
+ const auto& yMin = yMax - yGridSize * graphOptions->height;
+ long long minShadeGrid = 0;
+ long long maxShadeGrid = 0;
+ if (lineOptions->shadeOptions != ShadeOptions::NO_SHADE)
+ {
+ auto [minShade, maxShade] = lineOptions->shadeValues;
+ if (minShade > maxShade)
+ std::swap(minShade, maxShade);
+
+ minShadeGrid = 3 + std::stoll(((yMax - minShade) / yGridSize).present());
+ maxShadeGrid = 3 + std::stoll(((yMax - maxShade) / yGridSize).present());
+ }
+ // Plot function
+ std::map gridPos;
+ for (const auto& [i, y] : fnValues)
+ {
+ auto gridY = std::stoll(((yMax - y) / yGridSize).present());
+ auto gridX = std::stoll(((graphOptions->xMax - i) / xGridSize).present());
+ gridPos[gridX] = gridY;
+ }
+ if (lineOptions->samplesSpacing > 1)
+ cubicInterpolateFill(&gridPos, 0, graphOptions->width);
+
+ long long lastGridY = gridPos[0];
+ for (const auto& [gridX, gridY] : gridPos)
+ {
+ // Plot point
+ const long long diffY = gridY - lastGridY;
+ const long long absDiffY = std::abs(diffY);
+ long long sgn = 1;
+ if (absDiffY != 0)
+ sgn = diffY / absDiffY;
+ if (absDiffY > graphOptions->height / 2)
+ {
+ // Create more points to connect the dots
+ for (long long j = 1; j < absDiffY + 1; j++)
+ canvas->write(lineOptions->lineDot,
+ { .x = gridX, .y = 3 + gridY + (-sgn * j) },
+ false,
+ lineOptions->lineColor);
+ }
+ canvas->write(lineOptions->lineDot, { .x = gridX, .y = 3 + gridY }, false, lineOptions->lineColor);
+ lastGridY = 3 + gridY;
+
+ switch (lineOptions->shadeOptions)
+ {
+ case ShadeOptions::NO_SHADE:
+ break;
+
+ case ShadeOptions::SHADE_BELOW_FIRST:
+ {
+ for (long long j = lastGridY - 1; j >= minShadeGrid; j--)
+ canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor);
+ break;
+ }
+ case ShadeOptions::SHADE_ABOVE_FIRST:
+ {
+ for (long long j = lastGridY + 1; j <= minShadeGrid; j++)
+ canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor);
+ break;
+ }
+ case ShadeOptions::SHADE_BELOW_SECOND:
+ {
+ for (long long j = lastGridY - 1; j >= maxShadeGrid; j--)
+ canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor);
+ break;
+ }
+ case ShadeOptions::SHADE_ABOVE_SECOND:
+ {
+ for (long long j = lastGridY + 1; j <= maxShadeGrid; j++)
+ canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor);
+ break;
+ }
+ case ShadeOptions::SHADE_BETWEEN_BOTH:
+ {
+ for (long long j = std::max(lastGridY + 1, maxShadeGrid); j <= minShadeGrid; j++)
+ canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor);
+ break;
+ }
+ case ShadeOptions::SHADE_OUTSIDE_BOTH:
+ {
+ // Shade below first
+ for (long long j = lastGridY - 1; j >= minShadeGrid; j--)
+ canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor);
+
+ // Shade above second
+ for (long long j = lastGridY + 1; j <= maxShadeGrid; j++)
+ canvas->write(lineOptions->shadeDot, { .x = gridX, .y = j }, false, lineOptions->lineColor);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ void drawTicks(prettyPrint::ConsoleOutput* canvas,
+ const Number& xGridSize,
+ const Number& yGridSize,
+ const Number& yMax,
+ const GraphOptionsBase* graphOptions)
+ {
+ // Axis positions
+ Number yTickValue = yMax;
+
+ for (long long j = 0; j < graphOptions->height; ++j)
+ {
+ yTickValue -= yGridSize;
+ // Write y ticks
+ if (j % graphOptions->yTickSpacing == 0 and yGridSize != 0)
+ {
+ yTickValue.setPrec(2);
+ canvas->write(yTickValue.present(), { .x = graphOptions->width + 2, .y = 3 + j });
+ for (long long i = 0; i < graphOptions->width; ++i)
+ canvas->write(BoxDrawing::DOTTED_HORIZONTAL, { .x = i, .y = 3 + j });
+ canvas->write(BoxDrawing::VERTICAL_LEFT, { .x = graphOptions->width, .y = 3 + j });
+ }
+ }
+
+ // Draw grid
+ for (long long i = 0; i < graphOptions->width - 2; ++i)
+ {
+ Number x = graphOptions->xMin + Number(i) * xGridSize;
+ // Write grid label
+ if (i % graphOptions->xTickSpacing == 0 and xGridSize != 0)
+ {
+ // Write vertical gridlines
+ for (long long j = 0; j < graphOptions->height; ++j)
+ canvas->write(BoxDrawing::DOTTED_VERTICAL, { .x = i, .y = 3 + j });
+ canvas->write(BoxDrawing::HORIZONTAL_UP, { .x = i, .y = 3 + graphOptions->height });
+ x.setPrec(2);
+ canvas->write(x.present(), { .x = i, .y = 3 + graphOptions->height + 1 });
+ }
+ }
+ }
+
+ void drawGrid(prettyPrint::ConsoleOutput* canvas, const GraphOptionsBase* graphOptions)
+ {
+ canvas->write(
+ graphOptions->title, { .x = 0, .y = 1 }, false, formats::bold, prettyPrint::HorizontalAlignment::CENTER);
+ for (long long i = 0; i <= graphOptions->width; ++i)
+ {
+ // Write base frame
+ canvas->write(BoxDrawing::HORIZONTAL, { .x = i, .y = 3 + graphOptions->height });
+ }
+
+ // Write side frame
+ for (long long j = 0; j < graphOptions->height; ++j)
+ canvas->write(BoxDrawing::VERTICAL, { .x = graphOptions->width, .y = 3 + j });
+
+ // Axis Titles
+ canvas->write(BoxDrawing::BOTTOM_RIGHT_CORNER, { .x = graphOptions->width, .y = 3 + graphOptions->height });
+ canvas->write(std::string((graphOptions->width - ::steppable::__internals::stringUtils::getUnicodeDisplayWidth(
+ graphOptions->xAxisTitle)) /
+ 2,
+ ' ') +
+ graphOptions->xAxisTitle,
+ { .x = 0, .y = 5 + graphOptions->height },
+ false,
+ formats::bold);
+ canvas->write(graphOptions->yAxisTitle,
+ { .x = graphOptions->width + 10, .y = (graphOptions->height / 2) + 1 },
+ false,
+ formats::bold);
+ }
+} // namespace steppable::graphing::__internals
diff --git a/src/conPlot/conPlotInterpolation.cpp b/src/conPlot/conPlotInterpolation.cpp
new file mode 100644
index 000000000..485cd59b4
--- /dev/null
+++ b/src/conPlot/conPlotInterpolation.cpp
@@ -0,0 +1,148 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#include "output.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+using namespace std::literals;
+
+namespace steppable::graphing
+{
+ void linearInterpolateFill(std::map* data, long long xMin, long long xMax)
+ {
+ if (data->size() < 2)
+ output::error("graphing::linearInterpolateFill"s,
+ "At least 2 points are required for linear interpolation."s);
+
+ std::vector xs;
+ std::vector ys;
+ for (const auto& kv : *data)
+ {
+ xs.push_back(kv.first);
+ ys.push_back(kv.second);
+ }
+ auto n = static_cast(xs.size());
+
+ for (long long x = xMin; x <= xMax; ++x)
+ {
+ // Find the interval [x0, x1] for x (or clamp to endpoints)
+ auto it = std::ranges::lower_bound(xs, x);
+ long long idx1 = std::distance(xs.begin(), it);
+ long long i0 = 0;
+ long long i1 = 0;
+ if (x <= xs.front())
+ {
+ i0 = 0;
+ i1 = 1;
+ }
+ else if (x >= xs.back())
+ {
+ i0 = n - 2;
+ i1 = n - 1;
+ }
+ else
+ {
+ i0 = idx1 - 1;
+ i1 = idx1;
+ }
+
+ long long x0 = xs[i0];
+ long long x1 = xs[i1];
+ long long y0 = ys[i0];
+ long long y1 = ys[i1];
+ long double t = 0;
+ if (x1 == x0)
+ t = 0.0;
+ else
+ t = static_cast(x - x0) / static_cast(x1 - x0);
+ long long y = llround(static_cast(y0) + (t * static_cast(y1 - y0)));
+ (*data)[x] = y;
+ }
+ }
+
+ void cubicInterpolateFill(std::map* data, long long xMin, long long xMax)
+ {
+ if (data->size() < 4)
+ linearInterpolateFill(data, xMin, xMax);
+ std::vector xs;
+ std::vector ys;
+ for (const auto& kv : *data)
+ {
+ xs.push_back(kv.first);
+ ys.push_back(kv.second);
+ }
+ auto n = static_cast(xs.size());
+
+ for (long long x = xMin; x <= xMax; ++x)
+ {
+ // Find the correct window of 4 points for interpolation
+ auto it = std::ranges::lower_bound(xs, x);
+ long long idx1 = std::distance(xs.begin(), it);
+ long long i1 = std::clamp(idx1 - 1, 1LL, n - 3);
+ long long i0 = i1 - 1;
+ long long i2 = i1 + 1;
+ long long i3 = i1 + 2;
+
+ if (x <= xs[1])
+ {
+ i0 = 0;
+ i1 = 1;
+ i2 = 2;
+ i3 = 3;
+ }
+ if (x >= xs[n - 2])
+ {
+ i0 = n - 4;
+ i1 = n - 3;
+ i2 = n - 2;
+ i3 = n - 1;
+ }
+
+ long long x0 = xs[i0];
+ long long x1 = xs[i1];
+ long long x2 = xs[i2];
+ long long x3 = xs[i3];
+ long long y0 = ys[i0];
+ long long y1 = ys[i1];
+ long long y2 = ys[i2];
+ long long y3 = ys[i3];
+
+ long double t0 = static_cast((x - x1) * (x - x2) * (x - x3)) /
+ static_cast((x0 - x1) * (x0 - x2) * (x0 - x3));
+ long double t1 = static_cast((x - x0) * (x - x2) * (x - x3)) /
+ static_cast((x1 - x0) * (x1 - x2) * (x1 - x3));
+ long double t2 = static_cast((x - x0) * (x - x1) * (x - x3)) /
+ static_cast((x2 - x0) * (x2 - x1) * (x2 - x3));
+ long double t3 = static_cast((x - x0) * (x - x1) * (x - x2)) /
+ static_cast((x3 - x0) * (x3 - x1) * (x3 - x2));
+
+ long long y = llround((static_cast(y0) * t0) + (static_cast(y1) * t1) +
+ (static_cast(y2) * t2) + (static_cast(y3) * t3));
+ (*data)[x] = y;
+ }
+ }
+} // namespace steppable::graphing
diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp
index 205435ff7..5d7d65ce8 100644
--- a/src/matrix/ref/ref.cpp
+++ b/src/matrix/ref/ref.cpp
@@ -28,12 +28,16 @@
* @date 31st May 2025
*/
+#include "conPlot/conPlot.hpp"
+#include "fn/calc.hpp"
#include "refReport.hpp"
-#include "steppable/mat2d.hpp"
#include "steppable/number.hpp"
+#include "steppable/parameter.hpp"
-#include
#include
+#include
+
+using namespace std::literals;
namespace steppable::__internals::matrix
{
@@ -43,13 +47,59 @@ namespace steppable::__internals::matrix
int main()
{
using namespace steppable;
- std::vector> matrix = { { 2, 1, -1, 3, 2, 8 },
- { 1, -2, 1, 0, 2, -4 },
- { 3, 1, -3, 4, 1, 6 },
- { 1, 1, 1, 1, 1, 5 },
- { 2, -1, 2, -1, 3, 3 } };
- auto mat = steppable::Matrix(matrix);
- mat = mat.rref();
-
- std::cout << mat.present(1) << "\n";
+ using namespace steppable::__internals::parameter;
+
+ Utf8CodePage _;
+ // std::vector> matrix = { { 2, 1, -1, 3, 2, 8 },
+ // { 1, -2, 1, 0, 2, -4 },
+ // { 3, 1, -3, 4, 1, 6 },
+ // { 1, 1, 1, 1, 1, 5 },
+ // { 2, -1, 2, -1, 3, 3 } };
+ // auto mat = steppable::Matrix(matrix);
+ // mat = mat.rref();
+
+ // std::cout << mat.present(1) << "\n";
+ graphing::conPlot({
+ [](const Number& x) { return steppable::__internals::calc::sin(x.present(), 2); },
+ [](const Number& x) { return steppable::__internals::calc::cos(x.present(), 2); },
+ },
+ {
+ "width"_p = 90LL,
+ "xMin"_p = Number(-6.28),
+ "xMax"_p = Number(6.28),
+ "title"_p = "Sine and cosine graphs from -pi to +pi"s,
+ },
+ { { "title"_p = "Sine"s },
+ {
+ "lineColor"_p = (ColorFunc)colors::red,
+ "lineDot"_p = graphing::GraphDot::LIGHT_BLOCK_2,
+ "shadeDot"_p = graphing::GraphDot::LIGHT_BLOCK_1,
+ "title"_p = "Cosine"s,
+ "shadeOptions"_p = graphing::ShadeOptions::SHADE_OUTSIDE_BOTH,
+ "shadeValues"_p = graphing::NumberPair{ 0.1, 0.5 },
+ } });
+
+ graphing::conPlotBar(
+ {
+ { 10, 12, 50, 12, 56, 6 },
+ { 13, 14, 42, 64, 23, 10 },
+ },
+ {
+ "width"_p = 110LL,
+ "height"_p = 20LL,
+ "title"_p = "Bar plot"s,
+ "xAxisTitle"_p = "X axis"s,
+ "barWidth"_p = 5LL,
+ },
+ {
+ {
+ "title"_p = "Bar 1"s,
+ "color"_p = static_cast(colors::blue),
+ "block"_p = graphing::GraphDot::LIGHT_BLOCK_1,
+ },
+ {
+ "title"_p = "Bar 2"s,
+ "color"_p = static_cast(colors::red),
+ },
+ });
}
diff --git a/src/steppable/mat2d.cpp b/src/steppable/mat2d.cpp
index a29dc0d4c..6882decbd 100644
--- a/src/steppable/mat2d.cpp
+++ b/src/steppable/mat2d.cpp
@@ -29,6 +29,7 @@
#include "steppable/mat2d.hpp"
+#include "getString.hpp"
#include "output.hpp"
#include "platform.hpp"
#include "rounding.hpp"
@@ -47,6 +48,7 @@ namespace steppable
{
using namespace __internals;
using namespace __internals::numUtils;
+ using namespace localization;
namespace prettyPrint::printers
{
@@ -106,7 +108,7 @@ namespace steppable
const size_t rowSize = row.size();
if (rowSize != size)
{
- output::error("Matrix::_checkDataSanity"s, "Matrix has non-uniform dimensions."s);
+ output::error("Matrix::_checkDataSanity"s, $("mat2d", "75953952-0eea-4716-a006-d0f2e7a8f6c9"));
utils::programSafeExit(1);
}
size = rowSize;
@@ -137,16 +139,16 @@ namespace steppable
if (x > _cols)
{
- output::error("Matrix::operator[]"s,
- "Incorrect x parameter. {0} exceeds the number of columns in this matrix ({1})."s,
- { std::to_string(x), std::to_string(_cols) });
+ output::error(
+ "Matrix::operator[]"s,
+ $("mat2d", "8d4e4757-415b-4aed-8f5e-26b3503a95dd"s, { std::to_string(x), std::to_string(_cols) }));
utils::programSafeExit(1);
}
if (y > _rows)
{
- output::error("Matrix::operator[]"s,
- "Incorrect y parameter. {0} exceeds the number of rows in this matrix ({1})."s,
- { std::to_string(y), std::to_string(_rows) });
+ output::error(
+ "Matrix::operator[]"s,
+ $("mat2d", "e7cb3f0b-11d8-4e12-8c93-4a1021b15e10"s, { std::to_string(y), std::to_string(_rows) }));
utils::programSafeExit(1);
}
}
@@ -241,11 +243,11 @@ namespace steppable
MatVec2D mat = data;
mat = roundOffValues(mat, static_cast(prec) + 3);
- for (int col = 0, row = 0; col < _cols && row < _rows; ++col)
+ for (long long signed col = 0, row = 0; col < _cols && row < _rows; ++col)
{
// Find first non-zero in column col, at or below row
- int sel = -1;
- for (int i = row; i < _rows; ++i)
+ long long signed sel = -1;
+ for (long long signed i = row; i < _rows; ++i)
if (mat[i][col] != 0.0)
{
sel = i;
@@ -258,10 +260,10 @@ namespace steppable
std::swap(mat[row], mat[sel]); // Swap if needed
// Eliminate below
- for (int i = row + 1; i < _rows; ++i)
+ for (long long signed i = row + 1; i < _rows; ++i)
{
Number factor = mat[i][col] / mat[row][col];
- for (int j = col; j < _cols; ++j)
+ for (long long signed j = col; j < _cols; ++j)
mat[i][j] -= factor * mat[row][j];
}
++row;
@@ -274,7 +276,7 @@ namespace steppable
{
if (_rows != _cols)
{
- output::error("Matrix::det"s, "Matrix is not a square."s);
+ output::error("Matrix::det"s, $("mat2d", "fe78bdc2-b409-4078-8e0e-313c46977f25"));
utils::programSafeExit(1);
}
int sign = 1;
@@ -323,15 +325,17 @@ namespace steppable
if (rhs._cols != _cols)
{
output::error("Matrix::operator+"s,
- "Matrix dimensions mismatch. Expect {0} columns. Got {1} columns."s,
- { std::to_string(_cols), std::to_string(rhs._cols) });
+ $("mat2d",
+ "88331f88-3a4c-4b7e-9b43-b51a1d1020e2",
+ { std::to_string(_cols), std::to_string(rhs._cols) }));
utils::programSafeExit(1);
}
if (rhs._rows != _rows)
{
output::error("Matrix::operator+"s,
- "Matrix dimensions mismatch. Expect {0} rows. Got {1} rows."s,
- { std::to_string(_rows), std::to_string(rhs._rows) });
+ $("mat2d",
+ "34e92306-a4d8-4ff0-8441-bfcd29771e94",
+ { std::to_string(_rows), std::to_string(rhs._rows) }));
utils::programSafeExit(1);
}
@@ -381,11 +385,9 @@ namespace steppable
{
if (_cols != rhs._rows)
{
- output::error("Matrix::operator*"s, "Incorrect matrix dimensions for multiplication."s);
+ output::error("Matrix::operator*"s, $("mat2d", "17b6aadd-bce1-4558-a7cc-7a099f00e57c"));
// https://en.wikipedia.org/wiki/Matrix_multiplication
- output::info("Matrix::operator*"s,
- "For matrix multiplication, the number of columns in the first matrix must be equal to the "
- "number of rows in the second matrix"s);
+ output::info("Matrix::operator*"s, $("mat2d", "8966ce13-8ae9-4f14-ba4e-837b98a4c9fa"));
utils::programSafeExit(1);
}
Matrix matrix = Matrix::zeros(_rows, rhs._cols);
@@ -401,8 +403,9 @@ namespace steppable
if (rhs._rows != _rows)
{
output::error("Matrix::operator<<"s,
- "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}"s,
- { std::to_string(rhs._rows), std::to_string(rhs._rows) });
+ $("mat2d",
+ "f255d307-9482-442b-a523-61a1c7465f9c",
+ { std::to_string(rhs._rows), std::to_string(rhs._rows) }));
utils::programSafeExit(1);
}
@@ -425,9 +428,10 @@ namespace steppable
{
if (rhs._rows != _rows)
{
- output::error("Matrix::operator>>"s,
- "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}"s,
- { std::to_string(rhs._rows), std::to_string(rhs._rows) });
+ output::error("Matrix::operator<<"s,
+ $("mat2d",
+ "f255d307-9482-442b-a523-61a1c7465f9c",
+ { std::to_string(rhs._rows), std::to_string(rhs._rows) }));
utils::programSafeExit(1);
}
diff --git a/src/steppable/number.cpp b/src/steppable/number.cpp
index 86f99dd38..38f2ea552 100644
--- a/src/steppable/number.cpp
+++ b/src/steppable/number.cpp
@@ -47,10 +47,6 @@ namespace steppable
{
using namespace steppable::__internals::calc;
- Number::Number() : value("0"), prec(8) {}
-
- Number::Number(const Number& rhs) : value(rhs.value), prec(rhs.prec) {}
-
Number::Number(std::string value, const size_t prec, const RoundingMode mode) :
value(std::move(value)), prec(prec), mode(mode)
{
@@ -165,3 +161,9 @@ namespace steppable
std::string Number::present() const { return value; }
} // namespace steppable
+
+std::ostream& operator<<(std::ostream& os, const steppable::Number& number)
+{
+ os << number.present();
+ return os;
+}
diff --git a/src/steppable/parameter.cpp b/src/steppable/parameter.cpp
new file mode 100644
index 000000000..0014970d2
--- /dev/null
+++ b/src/steppable/parameter.cpp
@@ -0,0 +1,59 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy *
+ * of this software and associated documentation files (the "Software"), to deal *
+ * in the Software without restriction, including without limitation the rights *
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included in all *
+ * copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
+ * SOFTWARE. *
+ **************************************************************************************************/
+
+#include "steppable/parameter.hpp"
+
+namespace steppable::__internals::parameter
+{
+ void ParameterMap::checkParameterOrder(const std::vector& names)
+ {
+ if (names.size() != values.size())
+ {
+ output::error("ParameterMap::checkParameterOrder"s, "Incorrect name vector size."s);
+ utils::programSafeExit(1);
+ }
+
+ for (size_t i = 0; i < values.size(); i++)
+ if (names[i] != values[i].name)
+ {
+ output::error("ParameterMap::checkParameterOrder"s,
+ "Name {0} mismatch. Expect {1}"s,
+ { values[i].name, names[i] });
+ utils::programSafeExit(1);
+ }
+ }
+
+ void ParameterMap::checkParameterNameUnordered(const std::vector& names)
+ {
+ if (names.size() != values.size())
+ {
+ output::error("ParameterMap::checkParameterNameUnordered"s, "Incorrect name vector size."s);
+ utils::programSafeExit(1);
+ }
+ for (const auto& obj : values)
+ if (std::ranges::find(names, obj.name) == names.end())
+ {
+ output::error("ParameterMap::checkParameterNameUnordered"s, "Name {0} not found."s, { obj.name });
+ utils::programSafeExit(1);
+ }
+ }
+}
diff --git a/src/symbols.cpp b/src/symbols.cpp
index 4a38c84fa..0a409aa6f 100644
--- a/src/symbols.cpp
+++ b/src/symbols.cpp
@@ -22,6 +22,7 @@
#include "symbols.hpp"
+#include "colors.hpp"
#include "util.hpp"
#include
@@ -36,28 +37,84 @@
namespace steppable::prettyPrint
{
using namespace steppable::__internals::stringUtils;
+ using namespace steppable::__internals::utils;
ConsoleOutput::ConsoleOutput(size_t height, size_t width) : height(height), width(width)
{
- buffer = std::vector>(height, std::vector(width, ' '));
+ buffer = std::vector(height, std::vector(width, " "s));
}
- void ConsoleOutput::write(const char c, const Position& pos, bool updatePos)
+ void ConsoleOutput::_write(const std::string& s,
+ const Position& pos,
+ bool updatePos,
+ const ColorFunc& color,
+ const HorizontalAlignment& alignment)
{
- std::vector vector = buffer[pos.y];
- vector[pos.x] = c;
- buffer[pos.y] = vector;
+ auto outputString = s;
+ switch (alignment)
+ {
+ case HorizontalAlignment::LEFT:
+ break;
+ case HorizontalAlignment::CENTER:
+ outputString = std::string((width - getUnicodeDisplayWidth(outputString)) / 2, ' ') + outputString;
+ break;
+ case HorizontalAlignment::RIGHT:
+ outputString = std::string(width - getUnicodeDisplayWidth(outputString), ' ') + outputString;
+ break;
+ }
+
+ Position p = pos;
+ if (s == "\n")
+ {
+ p.y++;
+ p.x = pos.x;
+ return;
+ }
+ size_t stringWidth = getUnicodeDisplayWidth(outputString);
+ if (stringWidth <= 1)
+ {
+ std::stringstream ss;
+ color(ss);
+ ss << outputString << reset;
+ buffer[p.y][p.x] = ss.str();
+ }
+ else
+ {
+ GraphemeIterator graphemeIterator(outputString);
+ std::string cluster;
+ size_t i = 0;
+ while (graphemeIterator.next(cluster))
+ {
+ if (cluster == "\n")
+ {
+ i = 0;
+ p.y++;
+ p.x = pos.x;
+ continue;
+ }
+ std::stringstream ss;
+ color(ss);
+ ss << cluster << reset;
+ buffer[p.y][p.x + i] = ss.str();
+ i++;
+ }
+ }
if (updatePos)
- curPos = pos;
+ curPos = p;
}
- void ConsoleOutput::write(const char c, const long long dLine, const long long dCol, bool updatePos)
+ void ConsoleOutput::write(const char c,
+ const long long dLine,
+ const long long dCol,
+ bool updatePos,
+ const ColorFunc& color,
+ const HorizontalAlignment& alignment)
{
if (dLine < 0)
{
// We need to add extra lines for printing at the very top
- buffer.insert(buffer.begin(), -dLine, std::vector(width, ' '));
+ buffer.insert(buffer.begin(), -dLine, std::vector(width, " "s));
curPos.y = 0;
height -= dLine;
}
@@ -65,8 +122,8 @@ namespace steppable::prettyPrint
{
for (auto& i : buffer)
{
- std::vector vector = i;
- vector.resize(vector.size() - dCol, ' ');
+ std::vector vector = i;
+ vector.resize(vector.size() - dCol, " "s);
i = vector;
}
@@ -75,25 +132,16 @@ namespace steppable::prettyPrint
}
else
curPos.x += dCol;
- write(c, curPos, updatePos);
+ _write(std::string(1, c), curPos, updatePos, color, alignment);
}
- void ConsoleOutput::write(const std::string& s, const Position& pos, bool updatePos)
+ void ConsoleOutput::write(const char c,
+ const Position& pos,
+ bool updatePos,
+ const ColorFunc& color,
+ const HorizontalAlignment& alignment)
{
- Position p = pos;
- for (const auto& c : s)
- {
- if (c == '\n')
- {
- p.y++;
- p.x = pos.x;
- continue;
- }
- write(c, p, false);
- p.x++;
- if (updatePos)
- curPos = p;
- }
+ _write(std::string(1, c), pos, updatePos, color, alignment);
}
std::string ConsoleOutput::asString() const
@@ -113,13 +161,123 @@ namespace steppable::prettyPrint
auto strings = split(s, '\n');
size_t max = 0;
for (const auto& string : strings)
- max = std::max(max, string.length());
+ max = std::max(max, getUnicodeDisplayWidth(string));
return max;
}
size_t getStringHeight(const std::string& s) { return split(s, '\n').size(); }
} // namespace steppable::prettyPrint
+namespace steppable::__internals::stringUtils
+{
+ bool isZeroWidthCharacter(uint32_t codepoint)
+ {
+ return (codepoint >= 0x0300 && codepoint <= 0x036F) || // Combining diacritical marks
+ (codepoint >= 0x200B && codepoint <= 0x200F) || // Zero-width space, non-joiners
+ (codepoint == 0x2028 || codepoint == 0x2029 ||
+ codepoint == 0x202F) || // Line separator, para separator, narrow no-break space
+ (codepoint >= 0xFE00 && codepoint <= 0xFE0F) || // Variation selectors
+ (codepoint >= 0xE0100 && codepoint <= 0xE01EF); // Supplemental variation selectors
+ }
+
+ bool isEmojiBase(uint32_t cp)
+ {
+ return (cp >= 0x1F600 && cp <= 0x1F64F) || // Emoticons
+ (cp >= 0x1F300 && cp <= 0x1F5FF) || // Misc Symbols and Pictographs
+ (cp >= 0x1F680 && cp <= 0x1F6FF) || // Transport & Map
+ (cp >= 0x1F1E6 && cp <= 0x1F1FF) || // Regional indicators
+ (cp >= 0x2600 && cp <= 0x26FF) || // Misc symbols
+ (cp >= 0x2700 && cp <= 0x27BF) || // Dingbats
+ (cp >= 0x1F900 && cp <= 0x1F9FF) || // Supplemental Symbols and Pictographs
+ (cp >= 0x1FA70 && cp <= 0x1FAFF) || (cp == 0x200D); // ZWJ
+ }
+
+ bool utf8Decode(const std::string& s, size_t& i, uint32_t& cp)
+ {
+ if (i >= s.size())
+ return false;
+ unsigned char c = s[i];
+ if (c < 0x80)
+ {
+ cp = c;
+ i += 1;
+ }
+ else if ((c & 0xE0) == 0xC0 && i + 1 < s.size())
+ {
+ cp = ((c & 0x1F) << 6) | (s[i + 1] & 0x3F);
+ i += 2;
+ }
+ else if ((c & 0xF0) == 0xE0 && i + 2 < s.size())
+ {
+ cp = ((c & 0x0F) << 12) | ((s[i + 1] & 0x3F) << 6) | (s[i + 2] & 0x3F);
+ i += 3;
+ }
+ else if ((c & 0xF8) == 0xF0 && i + 3 < s.size())
+ {
+ cp = ((c & 0x07) << 18) | ((s[i + 1] & 0x3F) << 12) | ((s[i + 2] & 0x3F) << 6) | (s[i + 3] & 0x3F);
+ i += 4;
+ }
+ else
+ {
+ i += 1;
+ cp = 0xFFFD;
+ }
+ return true;
+ }
+
+ bool isClusterExtender(uint32_t cp)
+ {
+ return (cp >= 0x1F3FB && cp <= 0x1F3FF) || // Skin tone
+ (cp == 0x200D) || // ZWJ
+ (cp == 0xFE0F) || // VS16
+ isZeroWidthCharacter(cp);
+ }
+
+ uint32_t getCodepoint(const std::string& str, size_t& i)
+ {
+ unsigned char c = str[i];
+ if (c <= 0x7F) // Single-byte (ASCII)
+ return c;
+ if ((c & 0xE0) == 0xC0) // Two-byte sequence
+ return ((c & 0x1F) << 6) | (str[++i] & 0x3F);
+ if ((c & 0xF0) == 0xE0) // Three-byte sequence
+ {
+ unsigned char byte1 = c & 0x0F;
+ unsigned char byte2 = str[++i] & 0x3F;
+ unsigned char byte3 = str[++i] & 0x3F;
+
+ return (byte1 << 12) | (byte2 << 6) | byte3;
+ }
+ if ((c & 0xF8) == 0xF0) // Four-byte sequence
+ {
+ unsigned char byte1 = (c & 0x07) << 18; // First byte
+ unsigned char byte2 = (str[++i] & 0x3F) << 12; // Second byte (increment i)
+ unsigned char byte3 = (str[++i] & 0x3F) << 6; // Third byte (increment i)
+ unsigned char byte4 = str[++i] & 0x3F; // Fourth byte (increment i)
+
+ return byte1 | byte2 | byte3 | byte4;
+ }
+ return 0; // Invalid UTF-8
+ }
+
+ size_t getUnicodeDisplayWidth(const std::string& utf8Str)
+ {
+ size_t width = 0;
+ size_t len = utf8Str.size();
+ for (size_t i = 0; i < len; ++i)
+ {
+ uint32_t codepoint = getCodepoint(utf8Str, i);
+
+ if (isZeroWidthCharacter(codepoint))
+ // Skip zero-width characters
+ continue;
+ width += 1;
+ }
+
+ return width;
+ }
+} // namespace steppable::__internals::stringUtils
+
namespace steppable::__internals::symbols
{
using namespace steppable::__internals::stringUtils;
diff --git a/src/util.cpp b/src/util.cpp
index c874ce77a..df1762482 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -204,7 +204,7 @@ namespace steppable::__internals::numUtils
auto removeLeadingZeros(const std::string& string) -> std::decay_t
{
- if (string == "0")
+ if (string == "0" or string.empty())
return "0";
auto out = string;
bool negative = false;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2e25b6389..a0fc1afca 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -22,12 +22,42 @@
ENABLE_TESTING()
-FOREACH(ITEM IN LISTS TEST_TARGETS)
- ADD_EXECUTABLE(${ITEM} ${ITEM}.cpp)
- TARGET_LINK_LIBRARIES(${ITEM} PRIVATE func)
- ADD_TEST(NAME ${ITEM} COMMAND ${CMAKE_BINARY_DIR}/tests/${ITEM})
- MESSAGE(TRACE "Added test case: ${ITEM}: ${ITEM}.cpp")
+SET(TEST_TARGETS_TEMP
+ steppable::util
+ steppable::fraction
+ steppable::number
+ steppable::mat2d
+ steppable::factors
+ steppable::format
+ ${COMPONENTS}
+)
+
+FOREACH(TEST_TARGET IN LISTS TEST_TARGETS_TEMP)
+ SET(TARGET_NAME "test")
+ STRING(REPLACE "::" ";" TEST_TARGET_PARTS ${TEST_TARGET})
+ LIST(GET TEST_TARGET_PARTS 0 DIR_NAME)
+ LIST(GET TEST_TARGET_PARTS 1 FILE_NAME)
+ STRING(REPLACE "::" "_" TEST_TARGET ${TEST_TARGET})
+
+ CAPITALIZE(${DIR_NAME} DIR_NAME)
+ CAPITALIZE(${FILE_NAME} FILE_NAME)
+ CAPITALIZE(${TEST_TARGET} TEST_TARGET)
+ STRING(CONCAT DIR_NAME "test" ${DIR_NAME})
+ STRING(CONCAT FILE_NAME "test" ${FILE_NAME})
+ STRING(CONCAT TEST_TARGET "test" ${TEST_TARGET})
+
+ ADD_EXECUTABLE(${TEST_TARGET} ${DIR_NAME}/${FILE_NAME}.cpp)
+ TARGET_LINK_LIBRARIES(${TEST_TARGET} PRIVATE func)
+ ADD_TEST(NAME ${TEST_TARGET} COMMAND ${CMAKE_BINARY_DIR}/tests/${TEST_TARGET})
+
+ LIST(APPEND TEST_TARGETS ${TEST_TARGET})
+ MESSAGE(TRACE "Added test case: ${TEST_TARGET}: ${DIR_NAME}/${FILE_NAME}.cpp")
ENDFOREACH()
+SET(TEST_TARGETS
+ ${TEST_TARGETS}
+ PARENT_SCOPE
+)
+
ADD_CUSTOM_TARGET(tests)
ADD_DEPENDENCIES(tests ${TEST_TARGETS})
diff --git a/tests/testBaseConvert.cpp b/tests/testBase/testBaseConvert.cpp
similarity index 100%
rename from tests/testBaseConvert.cpp
rename to tests/testBase/testBaseConvert.cpp
diff --git a/tests/testBaseConvert.vcxproj b/tests/testBase/testBaseConvert.vcxproj
similarity index 98%
rename from tests/testBaseConvert.vcxproj
rename to tests/testBase/testBaseConvert.vcxproj
index 516edd035..b4531fb15 100644
--- a/tests/testBaseConvert.vcxproj
+++ b/tests/testBase/testBaseConvert.vcxproj
@@ -1,176 +1,176 @@
-
-
-
-
- Debug
- Win32
-
-
- Release
- Win32
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- 16.0
- Win32Proj
- {F29B13B1-5B1D-46D6-BC8E-B0A882BC762F}
- testAdd
- 10.0
-
-
-
- Application
- true
- v143
- Unicode
-
-
- Application
- false
- v143
- true
- Unicode
-
-
- Application
- true
- v143
- Unicode
-
-
- Application
- false
- v143
- true
- Unicode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- false
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- true
- $(SolutionDir)$(Configuration)\
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- false
- $(SolutionDir)$(Configuration)\
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
-
- Level3
- true
- WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
-
-
-
-
- Level3
- true
- true
- true
- WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
- true
- true
-
-
-
-
- Level3
- true
- _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
-
-
-
-
- Level3
- true
- true
- true
- NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
- true
- true
-
-
-
-
-
-
-
- {867a7b37-c961-4581-ae5b-067fd464cc7c}
-
-
- {0a900aaa-ac12-48cf-85cf-8bb0c0130512}
-
-
- {f6af63f8-9e0f-4da2-89b3-3096b56ba251}
-
-
-
-
-
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {F29B13B1-5B1D-46D6-BC8E-B0A882BC762F}
+ testAdd
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ false
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ true
+ $(SolutionDir)$(Configuration)\
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ false
+ $(SolutionDir)$(Configuration)\
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+ {867a7b37-c961-4581-ae5b-067fd464cc7c}
+
+
+ {0a900aaa-ac12-48cf-85cf-8bb0c0130512}
+
+
+ {f6af63f8-9e0f-4da2-89b3-3096b56ba251}
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/testDecimalConvert.cpp b/tests/testBase/testDecimalConvert.cpp
similarity index 100%
rename from tests/testDecimalConvert.cpp
rename to tests/testBase/testDecimalConvert.cpp
diff --git a/tests/testDecimalConvert.vcxproj b/tests/testBase/testDecimalConvert.vcxproj
similarity index 98%
rename from tests/testDecimalConvert.vcxproj
rename to tests/testBase/testDecimalConvert.vcxproj
index 56fc63aef..9acaa70fe 100644
--- a/tests/testDecimalConvert.vcxproj
+++ b/tests/testBase/testDecimalConvert.vcxproj
@@ -1,176 +1,176 @@
-
-
-
-
- Debug
- Win32
-
-
- Release
- Win32
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- 16.0
- Win32Proj
- {F74A691E-785E-43F1-9D99-6474918BD7DE}
- testAdd
- 10.0
-
-
-
- Application
- true
- v143
- Unicode
-
-
- Application
- false
- v143
- true
- Unicode
-
-
- Application
- true
- v143
- Unicode
-
-
- Application
- false
- v143
- true
- Unicode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- false
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- true
- $(SolutionDir)$(Configuration)\
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- false
- $(SolutionDir)$(Configuration)\
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
-
- Level3
- true
- WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
-
-
-
-
- Level3
- true
- true
- true
- WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
- true
- true
-
-
-
-
- Level3
- true
- _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
-
-
-
-
- Level3
- true
- true
- true
- NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
- true
- true
-
-
-
-
-
-
-
- {867a7b37-c961-4581-ae5b-067fd464cc7c}
-
-
- {0a900aaa-ac12-48cf-85cf-8bb0c0130512}
-
-
- {f6af63f8-9e0f-4da2-89b3-3096b56ba251}
-
-
-
-
-
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {F74A691E-785E-43F1-9D99-6474918BD7DE}
+ testAdd
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ false
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ true
+ $(SolutionDir)$(Configuration)\
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ false
+ $(SolutionDir)$(Configuration)\
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+ {867a7b37-c961-4581-ae5b-067fd464cc7c}
+
+
+ {0a900aaa-ac12-48cf-85cf-8bb0c0130512}
+
+
+ {f6af63f8-9e0f-4da2-89b3-3096b56ba251}
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/testAbs.cpp b/tests/testCalc/testAbs.cpp
similarity index 100%
rename from tests/testAbs.cpp
rename to tests/testCalc/testAbs.cpp
diff --git a/tests/testAbs.vcxproj b/tests/testCalc/testAbs.vcxproj
similarity index 98%
rename from tests/testAbs.vcxproj
rename to tests/testCalc/testAbs.vcxproj
index eef79804b..3492adb03 100644
--- a/tests/testAbs.vcxproj
+++ b/tests/testCalc/testAbs.vcxproj
@@ -1,166 +1,166 @@
-
-
-
-
- Debug
- Win32
-
-
- Release
- Win32
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- 16.0
- Win32Proj
- {E84B0149-B3A3-41AA-AC7B-FC13A7FB02D8}
- testAdd
- 10.0
-
-
-
- Application
- true
- v143
- Unicode
-
-
- Application
- false
- v143
- true
- Unicode
-
-
- Application
- true
- v143
- Unicode
-
-
- Application
- false
- v143
- true
- Unicode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
- false
-
-
- true
- $(SolutionDir)$(Configuration)\
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- false
-
-
-
- Level3
- true
- WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- $(SolutionDir)\include
- stdcpp20
-
-
- Console
- true
-
-
-
-
- Level3
- true
- true
- true
- WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- $(SolutionDir)\include
- stdcpp20
-
-
- Console
- true
- true
- true
-
-
-
-
- Level3
- true
- _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- $(SolutionDir)\include
- stdcpp20
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
-
-
-
-
- Level3
- true
- true
- true
- NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- $(SolutionDir)\include
- stdcpp20
-
-
- Console
- true
- true
- true
-
-
-
-
- {0a900aaa-ac12-48cf-85cf-8bb0c0130512}
-
-
- {f6af63f8-9e0f-4da2-89b3-3096b56ba251}
-
-
-
-
-
-
-
-
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {E84B0149-B3A3-41AA-AC7B-FC13A7FB02D8}
+ testAdd
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+ true
+ $(SolutionDir)$(Configuration)\
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ false
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)\include
+ stdcpp20
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)\include
+ stdcpp20
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)\include
+ stdcpp20
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)\include
+ stdcpp20
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ {0a900aaa-ac12-48cf-85cf-8bb0c0130512}
+
+
+ {f6af63f8-9e0f-4da2-89b3-3096b56ba251}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/testAdd.cpp b/tests/testCalc/testAdd.cpp
similarity index 100%
rename from tests/testAdd.cpp
rename to tests/testCalc/testAdd.cpp
diff --git a/tests/testAdd.vcxproj b/tests/testCalc/testAdd.vcxproj
similarity index 100%
rename from tests/testAdd.vcxproj
rename to tests/testCalc/testAdd.vcxproj
diff --git a/tests/testAtan2.cpp b/tests/testCalc/testAtan2.cpp
similarity index 100%
rename from tests/testAtan2.cpp
rename to tests/testCalc/testAtan2.cpp
diff --git a/tests/testComparison.cpp b/tests/testCalc/testComparison.cpp
similarity index 100%
rename from tests/testComparison.cpp
rename to tests/testCalc/testComparison.cpp
diff --git a/tests/testComparison.vcxproj b/tests/testCalc/testComparison.vcxproj
similarity index 100%
rename from tests/testComparison.vcxproj
rename to tests/testCalc/testComparison.vcxproj
diff --git a/tests/testDivision.cpp b/tests/testCalc/testDivision.cpp
similarity index 100%
rename from tests/testDivision.cpp
rename to tests/testCalc/testDivision.cpp
diff --git a/tests/testDivision.vcxproj b/tests/testCalc/testDivision.vcxproj
similarity index 100%
rename from tests/testDivision.vcxproj
rename to tests/testCalc/testDivision.vcxproj
diff --git a/tests/testFactorial.cpp b/tests/testCalc/testFactorial.cpp
similarity index 100%
rename from tests/testFactorial.cpp
rename to tests/testCalc/testFactorial.cpp
diff --git a/tests/testHyp.cpp b/tests/testCalc/testHyp.cpp
similarity index 100%
rename from tests/testHyp.cpp
rename to tests/testCalc/testHyp.cpp
diff --git a/tests/testLog.cpp b/tests/testCalc/testLog.cpp
similarity index 100%
rename from tests/testLog.cpp
rename to tests/testCalc/testLog.cpp
diff --git a/tests/testMultiply.cpp b/tests/testCalc/testMultiply.cpp
similarity index 100%
rename from tests/testMultiply.cpp
rename to tests/testCalc/testMultiply.cpp
diff --git a/tests/testMultiply.vcxproj b/tests/testCalc/testMultiply.vcxproj
similarity index 100%
rename from tests/testMultiply.vcxproj
rename to tests/testCalc/testMultiply.vcxproj
diff --git a/tests/testPower.cpp b/tests/testCalc/testPower.cpp
similarity index 100%
rename from tests/testPower.cpp
rename to tests/testCalc/testPower.cpp
diff --git a/tests/testPower.vcxproj b/tests/testCalc/testPower.vcxproj
similarity index 98%
rename from tests/testPower.vcxproj
rename to tests/testCalc/testPower.vcxproj
index 8c8ac6560..77a344e46 100644
--- a/tests/testPower.vcxproj
+++ b/tests/testCalc/testPower.vcxproj
@@ -1,173 +1,173 @@
-
-
-
-
- Debug
- Win32
-
-
- Release
- Win32
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- 16.0
- Win32Proj
- {AD080EEA-19BA-4944-B8ED-2B21F1E5F0D7}
- testAdd
- 10.0
-
-
-
- Application
- true
- v143
- Unicode
-
-
- Application
- false
- v143
- true
- Unicode
-
-
- Application
- true
- v143
- Unicode
-
-
- Application
- false
- v143
- true
- Unicode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- false
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- true
- $(SolutionDir)$(Configuration)\
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
- false
- $(SolutionDir)$(Configuration)\
- $(SolutionDir)$(Configuration)\$(ProjectName).build\
-
-
-
- Level3
- true
- WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
-
-
-
-
- Level3
- true
- true
- true
- WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
- true
- true
-
-
-
-
- Level3
- true
- _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
-
-
-
-
- Level3
- true
- true
- true
- NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
- true
- stdcpp20
- $(SolutionDir)\include
- /utf-8 %(AdditionalOptions)
-
-
- Console
- true
- true
- true
-
-
-
-
- {0a900aaa-ac12-48cf-85cf-8bb0c0130512}
-
-
- {f6af63f8-9e0f-4da2-89b3-3096b56ba251}
-
-
-
-
-
-
-
-
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {AD080EEA-19BA-4944-B8ED-2B21F1E5F0D7}
+ testAdd
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ false
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ true
+ $(SolutionDir)$(Configuration)\
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+ false
+ $(SolutionDir)$(Configuration)\
+ $(SolutionDir)$(Configuration)\$(ProjectName).build\
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ $(SolutionDir)\include
+ /utf-8 %(AdditionalOptions)
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ {0a900aaa-ac12-48cf-85cf-8bb0c0130512}
+
+
+ {f6af63f8-9e0f-4da2-89b3-3096b56ba251}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/testRoot.cpp b/tests/testCalc/testRoot.cpp
similarity index 100%
rename from tests/testRoot.cpp
rename to tests/testCalc/testRoot.cpp
diff --git a/tests/testRoot.vcxproj b/tests/testCalc/testRoot.vcxproj
similarity index 100%
rename from tests/testRoot.vcxproj
rename to tests/testCalc/testRoot.vcxproj
diff --git a/tests/testSubtract.cpp b/tests/testCalc/testSubtract.cpp
similarity index 100%
rename from tests/testSubtract.cpp
rename to tests/testCalc/testSubtract.cpp
diff --git a/tests/testSubtract.vcxproj b/tests/testCalc/testSubtract.vcxproj
similarity index 100%
rename from tests/testSubtract.vcxproj
rename to tests/testCalc/testSubtract.vcxproj
diff --git a/tests/testTrig.cpp b/tests/testCalc/testTrig.cpp
similarity index 100%
rename from tests/testTrig.cpp
rename to tests/testCalc/testTrig.cpp
diff --git a/tests/testTrig.vcxproj b/tests/testCalc/testTrig.vcxproj
similarity index 100%
rename from tests/testTrig.vcxproj
rename to tests/testCalc/testTrig.vcxproj
diff --git a/tests/testNInt.cpp b/tests/testCalculus/testNInt.cpp
similarity index 100%
rename from tests/testNInt.cpp
rename to tests/testCalculus/testNInt.cpp
diff --git a/tests/testRef.cpp b/tests/testMatrix/testRef.cpp
similarity index 100%
rename from tests/testRef.cpp
rename to tests/testMatrix/testRef.cpp
diff --git a/tests/testFactors.cpp b/tests/testSteppable/testFactors.cpp
similarity index 100%
rename from tests/testFactors.cpp
rename to tests/testSteppable/testFactors.cpp
diff --git a/tests/testFactors.vcxproj b/tests/testSteppable/testFactors.vcxproj
similarity index 100%
rename from tests/testFactors.vcxproj
rename to tests/testSteppable/testFactors.vcxproj
diff --git a/tests/testFormat.cpp b/tests/testSteppable/testFormat.cpp
similarity index 100%
rename from tests/testFormat.cpp
rename to tests/testSteppable/testFormat.cpp
diff --git a/tests/testFraction.cpp b/tests/testSteppable/testFraction.cpp
similarity index 100%
rename from tests/testFraction.cpp
rename to tests/testSteppable/testFraction.cpp
diff --git a/tests/testMat2d.cpp b/tests/testSteppable/testMat2d.cpp
similarity index 100%
rename from tests/testMat2d.cpp
rename to tests/testSteppable/testMat2d.cpp
diff --git a/tests/testNumber.cpp b/tests/testSteppable/testNumber.cpp
similarity index 100%
rename from tests/testNumber.cpp
rename to tests/testSteppable/testNumber.cpp
diff --git a/tests/testUtil.cpp b/tests/testSteppable/testUtil.cpp
similarity index 100%
rename from tests/testUtil.cpp
rename to tests/testSteppable/testUtil.cpp
diff --git a/tests/testUtil.vcxproj b/tests/testSteppable/testUtil.vcxproj
similarity index 100%
rename from tests/testUtil.vcxproj
rename to tests/testSteppable/testUtil.vcxproj