From e99431b80ed9e3e576210aead579ce6e51ca9859 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 00:01:47 -0500 Subject: [PATCH 01/30] Add PALMSerializer and related constants for expression serialization --- io/CMakeLists.txt | 3 +- io/include/Oasis/PALMSerializer.hpp | 152 ++++++++++++++++++++++++++++ io/src/FromPALM.cpp | 20 +--- io/src/PALMConst.hpp | 129 +++++++++++++++++++++++ io/src/PALMSerializer.cpp | 105 +++++++++++++++++++ io/tests/PALMTests.cpp | 80 +++++++++++++++ 6 files changed, 469 insertions(+), 20 deletions(-) create mode 100644 io/include/Oasis/PALMSerializer.hpp create mode 100644 io/src/PALMConst.hpp create mode 100644 io/src/PALMSerializer.cpp diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 9ce85dea..5843d7f7 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -1,7 +1,7 @@ set(OASIS_IO_SOURCES # cmake-format: sortable src/FromPALM.cpp src/FromString.cpp src/InFixSerializer.cpp - src/MathMLSerializer.cpp src/TeXSerializer.cpp) + src/MathMLSerializer.cpp src/PALMSerializer.cpp src/TeXSerializer.cpp) set(OASIS_IO_HEADERS # cmake-format: sortable @@ -9,6 +9,7 @@ set(OASIS_IO_HEADERS include/Oasis/FromString.hpp include/Oasis/InFixSerializer.hpp include/Oasis/MathMLSerializer.hpp + include/Oasis/PALMSerializer.hpp include/Oasis/TeXSerializer.hpp) add_library(OasisIO ${OASIS_IO_SOURCES} ${OASIS_IO_HEADERS}) diff --git a/io/include/Oasis/PALMSerializer.hpp b/io/include/Oasis/PALMSerializer.hpp new file mode 100644 index 00000000..140c3e33 --- /dev/null +++ b/io/include/Oasis/PALMSerializer.hpp @@ -0,0 +1,152 @@ +// +// Created by codin on 10/31/25. +// + +#ifndef OASIS_PALMSERIALIZER_HPP +#define OASIS_PALMSERIALIZER_HPP + +#include "../../src/PALMConst.hpp" +#include "Oasis/Add.hpp" + +#include +#include +#include +#include + +#include "Oasis/Visit.hpp" + +namespace Oasis { + +inline std::string_view PALM_UNEXPECTED_NO_MOST_SIG_OP = "Expression missing most significant operand"; +inline std::string_view PALM_UNEXPECTED_NO_LEAST_SIG_OP = "Expression missing least significant operand"; + +class PALMSerializer final : public TypedVisitor> { +public: + PALMSerializer() + : PALMSerializer(PALMOpts {}) + { + } + explicit PALMSerializer(PALMOpts options) + : palmOptions(std::move(options)) + { + } + + auto TypedVisit(const Real& real) -> RetT override; + auto TypedVisit(const Imaginary& imaginary) -> RetT override; + auto TypedVisit(const Variable& variable) -> RetT override; + auto TypedVisit(const Undefined& undefined) -> RetT override; + auto TypedVisit(const Add& add) -> RetT override; + auto TypedVisit(const Subtract& subtract) -> RetT override; + auto TypedVisit(const Multiply& multiply) -> RetT override; + auto TypedVisit(const Divide& divide) -> RetT override; + auto TypedVisit(const Exponent& exponent) -> RetT override; + auto TypedVisit(const Log& log) -> RetT override; + auto TypedVisit(const Negate& negate) -> RetT override; + auto TypedVisit(const Derivative& derivative) -> RetT override; + auto TypedVisit(const Integral& integral) -> RetT override; + auto TypedVisit(const Matrix& matrix) -> RetT override; + auto TypedVisit(const EulerNumber&) -> RetT override; + auto TypedVisit(const Pi&) -> RetT override; + auto TypedVisit(const Magnitude& magnitude) -> RetT override; + ~PALMSerializer() override = default; + +private: + PALMOpts palmOptions {}; + + /** Serialize a generic expression. + * + * @tparam RetT The return type of the serialization (usually std::expected) + * @param expressionType The type of the expression. + * @param operands The serialized operands of the expression. + * @return The serialized expression as a string, or an error if serialization fails. + */ + auto SerializeExpression(ExpressionType expressionType, const std::vector& operands = {}) const -> RetT + { + // Build the serialized string + std::ostringstream serialized; + + // Start Expression + serialized << PALMDelimiterToToken(START_EXPRESSION, palmOptions) << PALMDelimiterToToken(SEPARATOR, palmOptions); + + // Add Operator + serialized << PALMExpressionToToken(expressionType, palmOptions); + + // Add Operands + for (const auto& operand : operands) { + // Check for errors in operand serialization + auto value = operand; + + if (!value) { + return std::unexpected { value.error() }; + } + + // Add Separator and Operand Value + serialized << PALMDelimiterToToken(SEPARATOR, palmOptions) << value.value(); + } + + // End Expression + serialized << PALMDelimiterToToken(SEPARATOR, palmOptions) << PALMDelimiterToToken(END_EXPRESSION, palmOptions); + + // Return the serialized string + return serialized.str(); + } + + /** Serialize a unary expression. + * + * @tparam RetT The return type of the serialization (usually std::expected) + * @param expr The unary expression to serialize. + * @return The serialized expression as a string, or an error if serialization fails. + */ + auto SerializeUnaryExpression(const DerivedFromUnaryExpression auto& expr) -> RetT + { + // Ensure the operand exists + if (!expr.HasOperand()) { + return std::unexpected { std::string(PALM_UNEXPECTED_NO_MOST_SIG_OP) }; + } + + // Serialize the operand + auto operandResult = expr.GetOperand().Accept(*this); + if (!operandResult) { + return std::unexpected { operandResult.error() }; + } + + // Serialize the expression + return SerializeExpression(expr.GetType(), { operandResult }); + } + + /** Serialize a binary expression. + * + * @tparam RetT The return type of the serialization (usually std::expected) + * @param expr The binary expression to serialize. + * @return The serialized expression as a string, or an error if serialization fails. + */ + auto SerializeBinaryExpression(const DerivedFromBinaryExpression auto& expr) -> RetT + { + // Ensure both operands exist + if (!expr.HasMostSigOp()) { + return std::unexpected { std::string(PALM_UNEXPECTED_NO_MOST_SIG_OP) }; + } + if (!expr.HasLeastSigOp()) { + return std::unexpected { std::string(PALM_UNEXPECTED_NO_LEAST_SIG_OP) }; + } + + // Serialize both operands + auto mostSigOpResult = expr.GetMostSigOp().Accept(*this); + if (!mostSigOpResult) { + return std::unexpected { mostSigOpResult.error() }; + } + + auto leastSigOpResult = expr.GetLeastSigOp().Accept(*this); + if (!leastSigOpResult) { + return std::unexpected { leastSigOpResult.error() }; + } + + // Serialize the expression + + return SerializeExpression(expr.GetType(), { mostSigOpResult, leastSigOpResult }); + } +}; + +} + +#endif // OASIS_PALMSERIALIZER_HPP diff --git a/io/src/FromPALM.cpp b/io/src/FromPALM.cpp index 4768ff33..9a4bd8fa 100644 --- a/io/src/FromPALM.cpp +++ b/io/src/FromPALM.cpp @@ -24,25 +24,7 @@ #include "Oasis/Variable.hpp" -// Define PALM tokens -constexpr std::string_view PALM_OPEN_PARENS = "("; -constexpr std::string_view PALM_CLOSE_PARENS = ")"; - -constexpr std::string_view PALM_REAL = "real"; -constexpr std::array PALM_IMAGINARY = { "i", "j" }; -constexpr std::string_view PALM_VARIABLE = "var"; -constexpr std::string_view PALM_ADD = "+"; -constexpr std::string_view PALM_SUBTRACT = "-"; -constexpr std::string_view PALM_MULTIPLY = "*"; -constexpr std::string_view PALM_DIVIDE = "/"; -constexpr std::string_view PALM_EXPONENT = "^"; -constexpr std::string_view PALM_LOG = "log"; -constexpr std::string_view PALM_INTEGRAL = "int"; -constexpr std::string_view PALM_NEGATE = "neg"; -constexpr std::string_view PALM_DERIVATIVE = "d"; -// constexpr std::string_view PALM_MATRIX = "matrix"; -constexpr std::array PALM_PI = { "pi" }; -constexpr std::string_view PALM_EULER = "e"; +#include "PALMConst.hpp" namespace Oasis { /** A simple token stream for parsing PALM strings. diff --git a/io/src/PALMConst.hpp b/io/src/PALMConst.hpp new file mode 100644 index 00000000..befda0d4 --- /dev/null +++ b/io/src/PALMConst.hpp @@ -0,0 +1,129 @@ +// +// Created by codin on 10/31/25. +// + +#ifndef OASIS_PALMCONST_HPP +#define OASIS_PALMCONST_HPP + +#include +#include +#include + +#include "Oasis/Expression.hpp" + +namespace Oasis { +struct PALMOpts { + enum class ImaginarySymbol { + I, + J + } imaginarySymbol + = ImaginarySymbol::I; + + uint8_t numPlaces = 5; + + enum class Separator { + SPACE + } separator + = Separator::SPACE; +}; + +static auto PALMExpressionToToken(const ExpressionType type, const PALMOpts& options) -> std::string_view +{ + switch (type) { + case Oasis::ExpressionType::Real: + return "real"; + case Oasis::ExpressionType::Imaginary: + switch (options.imaginarySymbol) { + case PALMOpts::ImaginarySymbol::J: + return "j"; + case PALMOpts::ImaginarySymbol::I: + default: + return "i"; + } + case Oasis::ExpressionType::Variable: + return "var"; + case Oasis::ExpressionType::Add: + return "+"; + case Oasis::ExpressionType::Subtract: + return "-"; + case Oasis::ExpressionType::Multiply: + return "*"; + case Oasis::ExpressionType::Divide: + return "/"; + case Oasis::ExpressionType::Exponent: + return "^"; + case Oasis::ExpressionType::Log: + return "log"; + case Oasis::ExpressionType::Integral: + return "int"; + case Oasis::ExpressionType::Limit: + return "limit"; + case Oasis::ExpressionType::Derivative: + return "d"; + case Oasis::ExpressionType::Negate: + return "neg"; + case Oasis::ExpressionType::Sqrt: + return "sqrt"; + case Oasis::ExpressionType::Matrix: + return "matrix"; + case Oasis::ExpressionType::Pi: + return "pi"; + case Oasis::ExpressionType::EulerNumber: + return "e"; + case Oasis::ExpressionType::Magnitude: + return "magnitude"; + default: + return ""; + } +} + +enum PALMDelimiterType { + START_EXPRESSION, + END_EXPRESSION, + SEPARATOR +}; + +static auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& options) -> std::string_view +{ + switch (type) { + case START_EXPRESSION: + return "("; + case END_EXPRESSION: + return ")"; + case SEPARATOR: + switch (options.separator) { + case PALMOpts::Separator::SPACE: + default: + return " "; + } + default: + return ""; + } +} + +struct PALMDelimiters { + constexpr static inline std::string_view START_EXPRESSION = "("; + constexpr static inline std::string_view END_EXPRESSION = ")"; +}; + +// Define PALM tokens +constexpr inline std::string_view PALM_OPEN_PARENS = "("; +constexpr inline std::string_view PALM_CLOSE_PARENS = ")"; + +constexpr inline std::string_view PALM_REAL = "real"; +constexpr inline std::array PALM_IMAGINARY = { "i", "j" }; +constexpr inline std::string_view PALM_VARIABLE = "var"; +constexpr inline std::string_view PALM_ADD = "+"; +constexpr inline std::string_view PALM_SUBTRACT = "-"; +constexpr inline std::string_view PALM_MULTIPLY = "*"; +constexpr inline std::string_view PALM_DIVIDE = "/"; +constexpr inline std::string_view PALM_EXPONENT = "^"; +constexpr inline std::string_view PALM_LOG = "log"; +constexpr inline std::string_view PALM_INTEGRAL = "int"; +constexpr inline std::string_view PALM_NEGATE = "neg"; +constexpr inline std::string_view PALM_DERIVATIVE = "d"; +constexpr std::string_view PALM_MATRIX = "matrix"; +constexpr inline std::array PALM_PI = { "pi" }; +constexpr inline std::string_view PALM_EULER = "e"; +} +#endif // OASIS_PALMCONST_HPP \ No newline at end of file diff --git a/io/src/PALMSerializer.cpp b/io/src/PALMSerializer.cpp new file mode 100644 index 00000000..bf9e5517 --- /dev/null +++ b/io/src/PALMSerializer.cpp @@ -0,0 +1,105 @@ +// +// Created by codin on 10/31/25. +// + +#include + +#include "Oasis/PALMSerializer.hpp" +#include "PALMConst.hpp" + +#include "Oasis/Add.hpp" +#include "Oasis/Multiply.hpp" +#include "Oasis/Real.hpp" +#include "Oasis/Subtract.hpp" +#include "Oasis/Variable.hpp" + +namespace Oasis { + +auto PALMSerializer::TypedVisit(const Real& real) -> RetT +{ + auto realString = std::format("{:.{}}", real.GetValue(), palmOptions.numPlaces + 1); + return SerializeExpression(ExpressionType::Real, { realString }); +} + +auto PALMSerializer::TypedVisit(const Imaginary&) -> RetT +{ + return SerializeExpression(ExpressionType::Imaginary); +} + +auto PALMSerializer::TypedVisit(const Variable& variable) -> RetT +{ + return SerializeExpression(ExpressionType::Variable, { variable.GetName() }); +} + +auto PALMSerializer::TypedVisit(const Undefined&) -> RetT +{ + return SerializeExpression(ExpressionType::None); +} + +auto PALMSerializer::TypedVisit(const Add& add) -> RetT +{ + return SerializeBinaryExpression(add); +} + + +auto PALMSerializer::TypedVisit(const Subtract& subtract) -> RetT +{ + return SerializeBinaryExpression(subtract); +} + +auto PALMSerializer::TypedVisit(const Multiply& multiply) -> RetT +{ + return SerializeBinaryExpression(multiply); +} + +auto PALMSerializer::TypedVisit(const Divide& divide) -> RetT +{ + return std::unexpected { "Not implemented yet" }; +} + +auto PALMSerializer::TypedVisit(const Exponent& exponent) -> RetT +{ + return std::unexpected { "Not implemented yet" }; +} + +auto PALMSerializer::TypedVisit(const Log& log) -> RetT +{ + return std::unexpected { "Not implemented yet" }; +} + +auto PALMSerializer::TypedVisit(const Negate& negate) -> RetT +{ + return std::unexpected { "Not implemented yet" }; +} + +auto PALMSerializer::TypedVisit(const Derivative& derivative) -> RetT +{ + return std::unexpected { "Not implemented yet" }; +} + +auto PALMSerializer::TypedVisit(const Integral& integral) -> RetT +{ + return std::unexpected { "Not implemented yet" }; +} + +auto PALMSerializer::TypedVisit(const Matrix& matrix) -> RetT +{ + return std::unexpected { "Not implemented yet" }; +} + +auto PALMSerializer::TypedVisit(const EulerNumber&) -> RetT +{ + return SerializeExpression(ExpressionType::EulerNumber); +} + +auto PALMSerializer::TypedVisit(const Pi&) -> RetT +{ + return SerializeExpression(ExpressionType::Pi); +} + +auto PALMSerializer::TypedVisit(const Magnitude& magnitude) -> RetT +{ + return std::unexpected { "Not implemented yet" }; +} + +} \ No newline at end of file diff --git a/io/tests/PALMTests.cpp b/io/tests/PALMTests.cpp index 6cc3b19c..66f9f2f2 100644 --- a/io/tests/PALMTests.cpp +++ b/io/tests/PALMTests.cpp @@ -4,6 +4,7 @@ #include "catch2/catch_test_macros.hpp" #include "Oasis/FromPALM.hpp" +#include "Oasis/PALMSerializer.hpp" #include "Oasis/Add.hpp" #include "Oasis/Derivative.hpp" @@ -858,4 +859,83 @@ TEST_CASE("Parse Empty Expression", "[FromPALM][Parsing]") const auto expr = Oasis::FromPALM(""); REQUIRE(!expr); REQUIRE(expr.error() == Oasis::ParseError::UnexpectedEndOfInput); +} + + +/* + * Start of Serializer Tests + */ +TEST_CASE("Serialize Real", "[FromPALM][Serialization]") +{ + Oasis::Real real { 5.0 }; + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = real.Accept(serializer); + + REQUIRE(result.has_value()); + REQUIRE(result.value() == "( real 5 )"); +} + +TEST_CASE("Serialize Negative Real", "[FromPALM][Serialization]") +{ + Oasis::Real real { -5.0 }; + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = real.Accept(serializer); + + REQUIRE(result.has_value()); + REQUIRE(result.value() == "( real -5 )"); +} + +TEST_CASE("Serialize Imaginary", "[FromPALM][Serialization]") +{ + Oasis::Imaginary imaginary; + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = imaginary.Accept(serializer); + + REQUIRE(result.has_value()); + REQUIRE(result.value() == "( i )"); +} + +TEST_CASE("Serialize Variable", "[FromPALM][Serialization]") +{ + Oasis::Variable variable { "x" }; + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = variable.Accept(serializer); + + REQUIRE(result.has_value()); + REQUIRE(result.value() == "( var x )"); +} + +TEST_CASE("Serialize Addition", "[FromPALM][Serialization]") +{ + Oasis::Add<> addition { + Oasis::Real { 5.0 }, + Oasis::Real { 3.0 } + }; + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = addition.Accept(serializer); + + REQUIRE(result.has_value()); + REQUIRE(result.value() == "( + ( real 5 ) ( real 3 ) )"); +} + +TEST_CASE("Serialize Malformed Addition", "[FromPALM][Serialization]") +{ + Oasis::Add addition; + addition.SetMostSigOp(Oasis::Real { 5.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = addition.Accept(serializer); + + REQUIRE(!result.has_value()); } \ No newline at end of file From dd995e089cf613dd99e070567cf6cd725377d062 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 00:55:19 -0500 Subject: [PATCH 02/30] Refactor PALMOpts and enhance PALMSerializer with comprehensive serialization tests --- io/src/PALMConst.hpp | 8 +- io/src/PALMSerializer.cpp | 4 +- io/tests/PALMTests.cpp | 452 +++++++++++++++++++++++++++++++++++++- 3 files changed, 457 insertions(+), 7 deletions(-) diff --git a/io/src/PALMConst.hpp b/io/src/PALMConst.hpp index befda0d4..e1a2e2ed 100644 --- a/io/src/PALMConst.hpp +++ b/io/src/PALMConst.hpp @@ -13,11 +13,11 @@ namespace Oasis { struct PALMOpts { - enum class ImaginarySymbol { + enum class ImgSym { I, J } imaginarySymbol - = ImaginarySymbol::I; + = ImgSym::I; uint8_t numPlaces = 5; @@ -34,9 +34,9 @@ static auto PALMExpressionToToken(const ExpressionType type, const PALMOpts& opt return "real"; case Oasis::ExpressionType::Imaginary: switch (options.imaginarySymbol) { - case PALMOpts::ImaginarySymbol::J: + case PALMOpts::ImgSym::J: return "j"; - case PALMOpts::ImaginarySymbol::I: + case PALMOpts::ImgSym::I: default: return "i"; } diff --git a/io/src/PALMSerializer.cpp b/io/src/PALMSerializer.cpp index bf9e5517..62bd8c2e 100644 --- a/io/src/PALMSerializer.cpp +++ b/io/src/PALMSerializer.cpp @@ -54,12 +54,12 @@ auto PALMSerializer::TypedVisit(const Multiply& multiply auto PALMSerializer::TypedVisit(const Divide& divide) -> RetT { - return std::unexpected { "Not implemented yet" }; + return SerializeBinaryExpression(divide); } auto PALMSerializer::TypedVisit(const Exponent& exponent) -> RetT { - return std::unexpected { "Not implemented yet" }; + return SerializeBinaryExpression(exponent); } auto PALMSerializer::TypedVisit(const Log& log) -> RetT diff --git a/io/tests/PALMTests.cpp b/io/tests/PALMTests.cpp index 66f9f2f2..4b35051d 100644 --- a/io/tests/PALMTests.cpp +++ b/io/tests/PALMTests.cpp @@ -22,6 +22,457 @@ #include "Oasis/Subtract.hpp" #include "Oasis/Variable.hpp" +// Serializer +TEST_CASE("PALM Serialization for Real with various precisions", "[PALM][Serializer][Real]") +{ + const Oasis::Real real { 3.1415926535 }; + + { // Test with 5 decimal places + Oasis::PALMSerializer serializer { { .numPlaces = 5 } }; + + auto result = real.Accept(serializer).value(); + std::string expected = "( real 3.14159 )"; + + REQUIRE(expected == result); + } + + { // Test with 2 decimal places + Oasis::PALMSerializer serializer { { .numPlaces = 2 } }; + + auto result = real.Accept(serializer).value(); + std::string expected = "( real 3.14 )"; + + REQUIRE(expected == result); + } + + { // Test with 0 decimal places + Oasis::PALMSerializer serializer { { .numPlaces = 0 } }; + + auto result = real.Accept(serializer).value(); + std::string expected = "( real 3 )"; + + REQUIRE(expected == result); + } + + { // Test Default (5 decimal places)} + Oasis::PALMSerializer serializer {}; + + auto result = real.Accept(serializer).value(); + std::string expected = "( real 3.14159 )"; + + REQUIRE(expected == result); + } +} + +TEST_CASE("PALM Serialization for Real with negative value", "[PALM][Serializer][Real]") +{ + const Oasis::Real real { -2.71828 }; + + Oasis::PALMSerializer serializer {}; + + auto result = real.Accept(serializer).value(); + std::string expected = "( real -2.71828 )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Imaginary with different characters", "[PALM][Serializer][Imaginary]") +{ + const Oasis::Imaginary imaginary {}; + + { // Test with default character 'i' + Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMOpts::ImgSym::I } }; + + auto result = imaginary.Accept(serializer).value(); + std::string expected = "( i )"; + + REQUIRE(expected == result); + } + + { // Test with character 'j' + Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMOpts::ImgSym::J } }; + + auto result = imaginary.Accept(serializer).value(); + std::string expected = "( j )"; + + REQUIRE(expected == result); + } + + { // Test Default + Oasis::PALMSerializer serializer {}; + + auto result = imaginary.Accept(serializer).value(); + std::string expected = "( i )"; + + REQUIRE(expected == result); + } +} + +TEST_CASE("PALM Serialization for Variable", "[PALM][Serializer][Variable]") +{ + const Oasis::Variable variable { "x_variable" }; + + Oasis::PALMSerializer serializer {}; + + auto result = variable.Accept(serializer).value(); + std::string expected = "( var x_variable )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Addition", "[PALM][Serializer][Addition]") +{ + const Oasis::Add<> addition { + Oasis::Real { 5.0 }, + Oasis::Real { 3.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = addition.Accept(serializer).value(); + std::string expected = "( + ( real 5 ) ( real 3 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Addition", "[PALM][Serializer][Addition]") +{ + const Oasis::Add<> addition { + Oasis::Real { 5.0 }, + Oasis::Add { + Oasis::Real { 3.0 }, + Oasis::Real { 2.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = addition.Accept(serializer).value(); + std::string expected = "( + ( real 5 ) ( + ( real 3 ) ( real 2 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Subtraction", "[PALM][Serializer][Subtraction]") +{ + const Oasis::Subtract<> subtraction { + Oasis::Real { 5.0 }, + Oasis::Real { 3.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = subtraction.Accept(serializer).value(); + std::string expected = "( - ( real 5 ) ( real 3 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Subtraction", "[PALM][Serializer][Subtraction]") +{ + const Oasis::Subtract<> subtraction { + Oasis::Real { 5.0 }, + Oasis::Subtract { + Oasis::Real { 3.0 }, + Oasis::Real { 2.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = subtraction.Accept(serializer).value(); + std::string expected = "( - ( real 5 ) ( - ( real 3 ) ( real 2 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Multiplication", "[PALM][Serializer][Multiplication]") +{ + const Oasis::Multiply<> multiplication { + Oasis::Real { 5.0 }, + Oasis::Real { 3.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = multiplication.Accept(serializer).value(); + std::string expected = "( * ( real 5 ) ( real 3 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Multiplication", "[PALM][Serializer][Multiplication]") +{ + const Oasis::Multiply<> multiplication { + Oasis::Real { 5.0 }, + Oasis::Multiply { + Oasis::Real { 3.0 }, + Oasis::Real { 2.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = multiplication.Accept(serializer).value(); + std::string expected = "( * ( real 5 ) ( * ( real 3 ) ( real 2 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Division", "[PALM][Serializer][Division]") +{ + const Oasis::Divide<> division { + Oasis::Real { 6.0 }, + Oasis::Real { 3.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = division.Accept(serializer); + std::string expected = "( / ( real 6 ) ( real 3 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Division", "[PALM][Serializer][Division]") +{ + const Oasis::Divide<> division { + Oasis::Real { 6.0 }, + Oasis::Divide { + Oasis::Real { 3.0 }, + Oasis::Real { 2.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = division.Accept(serializer); + std::string expected = "( / ( real 6 ) ( / ( real 3 ) ( real 2 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Exponentiation", "[PALM][Serializer][Exponentiation]") +{ + const Oasis::Exponent<> exponent { + Oasis::Real { 2.0 }, + Oasis::Real { 3.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = exponent.Accept(serializer); + std::string expected = "( ^ ( real 2 ) ( real 3 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Exponentiation", "[PALM][Serializer][Exponentiation]") +{ + const Oasis::Exponent<> exponent { + Oasis::Real { 2.0 }, + Oasis::Exponent { + Oasis::Real { 3.0 }, + Oasis::Real { 2.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = exponent.Accept(serializer); + std::string expected = "( ^ ( real 2 ) ( ^ ( real 3 ) ( real 2 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Logarithm", "[PALM][Serializer][Logarithm]") +{ + const Oasis::Log<> log { + Oasis::Real { 10.0 }, + Oasis::Real { 1000.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = log.Accept(serializer); + std::string expected = "( log ( real 10 ) ( real 1000 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Logarithm", "[PALM][Serializer][Logarithm]") +{ + const Oasis::Log<> log { + Oasis::Real { 10.0 }, + Oasis::Log { + Oasis::Real { 10.0 }, + Oasis::Real { 1000.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = log.Accept(serializer); + std::string expected = "( log ( real 10 ) ( log ( real 10 ) ( real 1000 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Negation", "[PALM][Serializer][Negation]") +{ + const Oasis::Negate<> negate { + Oasis::Real { 5.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = negate.Accept(serializer).value(); + std::string expected = "( neg ( real 5 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Negation", "[PALM][Serializer][Negation]") +{ + const Oasis::Negate<> negate { + Oasis::Negate { + Oasis::Real { 5.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = negate.Accept(serializer).value(); + std::string expected = "( neg ( neg ( real 5 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Derivative", "[PALM][Serializer][Derivative]") +{ + const Oasis::Derivative<> derivative { + Oasis::Variable { "x" }, + Oasis::Real { 5.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = derivative.Accept(serializer); + std::string expected = "( d ( var x ) ( real 5 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Derivative", "[PALM][Serializer][Derivative]") +{ + const Oasis::Derivative<> derivative { + Oasis::Variable { "x" }, + Oasis::Derivative { + Oasis::Variable { "y" }, + Oasis::Real { 5.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = derivative.Accept(serializer); + std::string expected = "( d ( var x ) ( d ( var y ) ( real 5 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Integral", "[PALM][Serializer][Integral]") +{ + const Oasis::Integral<> integral { + Oasis::Variable { "x" }, + Oasis::Real { 5.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = integral.Accept(serializer); + std::string expected = "( int ( var x ) ( real 5 ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Nested Integral", "[PALM][Serializer][Integral]") +{ + const Oasis::Integral<> integral { + Oasis::Variable { "x" }, + Oasis::Integral { + Oasis::Variable { "y" }, + Oasis::Real { 5.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = integral.Accept(serializer); + std::string expected = "( int ( var x ) ( int ( var y ) ( real 5 ) ) )"; + + REQUIRE(expected == result); +} + +// TODO: Add tests for Matrix + +TEST_CASE("PALM Serialization for Euler Number", "[PALM][Serializer][EulerNumber]") +{ + const Oasis::EulerNumber euler {}; + + Oasis::PALMSerializer serializer {}; + + auto result = euler.Accept(serializer).value(); + std::string expected = "( e )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Pi", "[PALM][Serializer][Pi]") +{ + const Oasis::Pi pi {}; + + Oasis::PALMSerializer serializer {}; + + auto result = pi.Accept(serializer).value(); + std::string expected = "( pi )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Complex Expressions", "[PALM][Serializer][ComplexExpression]") +{ + Oasis::PALMSerializer serializer {}; + + { // Test 1 + const Oasis::Add<> expression { + Oasis::Real { 5.0 }, + Oasis::Multiply { + Oasis::Variable { "x" }, + Oasis::Exponent { + Oasis::Real { 2.0 }, + Oasis::Real { 3.0 } + } + } + }; + + auto result = expression.Accept(serializer).value(); + std::string expected = "( + ( real 5 ) ( * ( var x ) ( ^ ( real 2 ) ( real 3 ) ) ) )"; + + REQUIRE(expected == result); + } + + { // Test 2 + const Oasis::Subtract<> expression { + Oasis::Divide { + Oasis::Real { 10.0 }, + Oasis::Real { 2.0 } + }, + Oasis::Log { + Oasis::Real { 10.0 }, + Oasis::Real { 100.0 } + } + }; + + auto result = expression.Accept(serializer).value(); + std::string expected = "( - ( / ( real 10 ) ( real 2 ) ) ( log ( real 10 ) ( real 100 ) ) )"; + + REQUIRE(expected == result); + } +} + + /* * Real Operation */ @@ -861,7 +1312,6 @@ TEST_CASE("Parse Empty Expression", "[FromPALM][Parsing]") REQUIRE(expr.error() == Oasis::ParseError::UnexpectedEndOfInput); } - /* * Start of Serializer Tests */ From 86dc7a7b394aed0a4e88427b6a183a76a42d18a2 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 01:15:35 -0500 Subject: [PATCH 03/30] Enhance PALMSerializer with support for additional expressions and implement serialization for Log, Negate, Derivative, Integral, and Magnitude --- io/include/Oasis/PALMSerializer.hpp | 1 - io/src/PALMSerializer.cpp | 35 +++++++++++++++++------------ io/tests/PALMTests.cpp | 15 +++++++++++++ 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/io/include/Oasis/PALMSerializer.hpp b/io/include/Oasis/PALMSerializer.hpp index 140c3e33..57baa2a7 100644 --- a/io/include/Oasis/PALMSerializer.hpp +++ b/io/include/Oasis/PALMSerializer.hpp @@ -142,7 +142,6 @@ class PALMSerializer final : public TypedVisitor RetT return SerializeExpression(ExpressionType::None); } -auto PALMSerializer::TypedVisit(const Add& add) -> RetT +auto PALMSerializer::TypedVisit(const Add<>& add) -> RetT { return SerializeBinaryExpression(add); } -auto PALMSerializer::TypedVisit(const Subtract& subtract) -> RetT +auto PALMSerializer::TypedVisit(const Subtract<>& subtract) -> RetT { return SerializeBinaryExpression(subtract); } -auto PALMSerializer::TypedVisit(const Multiply& multiply) -> RetT +auto PALMSerializer::TypedVisit(const Multiply<>& multiply) -> RetT { return SerializeBinaryExpression(multiply); } -auto PALMSerializer::TypedVisit(const Divide& divide) -> RetT +auto PALMSerializer::TypedVisit(const Divide<>& divide) -> RetT { return SerializeBinaryExpression(divide); } -auto PALMSerializer::TypedVisit(const Exponent& exponent) -> RetT +auto PALMSerializer::TypedVisit(const Exponent<>& exponent) -> RetT { return SerializeBinaryExpression(exponent); } -auto PALMSerializer::TypedVisit(const Log& log) -> RetT +auto PALMSerializer::TypedVisit(const Log<>& log) -> RetT { - return std::unexpected { "Not implemented yet" }; + return SerializeBinaryExpression(log); } -auto PALMSerializer::TypedVisit(const Negate& negate) -> RetT +auto PALMSerializer::TypedVisit(const Negate<>& negate) -> RetT { - return std::unexpected { "Not implemented yet" }; + return SerializeUnaryExpression(negate); } -auto PALMSerializer::TypedVisit(const Derivative& derivative) -> RetT +auto PALMSerializer::TypedVisit(const Derivative<>& derivative) -> RetT { - return std::unexpected { "Not implemented yet" }; + return SerializeBinaryExpression(derivative); } -auto PALMSerializer::TypedVisit(const Integral& integral) -> RetT +auto PALMSerializer::TypedVisit(const Integral<>& integral) -> RetT { - return std::unexpected { "Not implemented yet" }; + return SerializeBinaryExpression(integral); } auto PALMSerializer::TypedVisit(const Matrix& matrix) -> RetT @@ -99,7 +106,7 @@ auto PALMSerializer::TypedVisit(const Pi&) -> RetT auto PALMSerializer::TypedVisit(const Magnitude& magnitude) -> RetT { - return std::unexpected { "Not implemented yet" }; + return SerializeUnaryExpression(magnitude); } } \ No newline at end of file diff --git a/io/tests/PALMTests.cpp b/io/tests/PALMTests.cpp index 4b35051d..ff5faafe 100644 --- a/io/tests/PALMTests.cpp +++ b/io/tests/PALMTests.cpp @@ -14,6 +14,7 @@ #include "Oasis/Imaginary.hpp" #include "Oasis/Integral.hpp" #include "Oasis/Log.hpp" +#include "Oasis/Magnitude.hpp" #include "Oasis/Matrix.hpp" #include "Oasis/Multiply.hpp" #include "Oasis/Negate.hpp" @@ -431,6 +432,20 @@ TEST_CASE("PALM Serialization for Pi", "[PALM][Serializer][Pi]") REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Magnitude", "[PALM][Serializer][Magnitude]") +{ + const Oasis::Magnitude magnitude { + Oasis::Real { -5.0 } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = magnitude.Accept(serializer).value(); + std::string expected = "( magnitude ( real -5 ) )"; + + REQUIRE(expected == result); +} + TEST_CASE("PALM Serialization for Complex Expressions", "[PALM][Serializer][ComplexExpression]") { Oasis::PALMSerializer serializer {}; From a081c5c009e0431d6cc6b3e06610234f551ca8f8 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 01:17:11 -0500 Subject: [PATCH 04/30] Refactor PALMDelimiters to use static string_view for expression delimiters --- io/src/PALMConst.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io/src/PALMConst.hpp b/io/src/PALMConst.hpp index e1a2e2ed..598e84bb 100644 --- a/io/src/PALMConst.hpp +++ b/io/src/PALMConst.hpp @@ -102,8 +102,8 @@ static auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& o } struct PALMDelimiters { - constexpr static inline std::string_view START_EXPRESSION = "("; - constexpr static inline std::string_view END_EXPRESSION = ")"; + constexpr static std::string_view START_EXPRESSION = "("; + constexpr static std::string_view END_EXPRESSION = ")"; }; // Define PALM tokens From dde3d6872983599450379fac02b8ab4e28feaf60 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 01:30:20 -0500 Subject: [PATCH 05/30] Add tests for malformed expressions in PALMSerializer --- io/tests/PALMTests.cpp | 337 +++++++++++++++++++++++++++++++---------- 1 file changed, 259 insertions(+), 78 deletions(-) diff --git a/io/tests/PALMTests.cpp b/io/tests/PALMTests.cpp index ff5faafe..36f00826 100644 --- a/io/tests/PALMTests.cpp +++ b/io/tests/PALMTests.cpp @@ -153,6 +153,33 @@ TEST_CASE("PALM Serialization for Nested Addition", "[PALM][Serializer][Addition REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Addition", "[PALM][Serializer][Addition]") +{ + { // Missing least significant operand + Oasis::Add addition; + addition.SetMostSigOp(Oasis::Real { 5.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = addition.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing least significant operand"); + } + + { // Missing most significant operand + Oasis::Add addition; + addition.SetLeastSigOp(Oasis::Real { 3.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = addition.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Subtraction", "[PALM][Serializer][Subtraction]") { const Oasis::Subtract<> subtraction { @@ -185,6 +212,33 @@ TEST_CASE("PALM Serialization for Nested Subtraction", "[PALM][Serializer][Subtr REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Subtraction", "[PALM][Serializer][Subtraction]") +{ + { // Missing least significant operand + Oasis::Subtract subtraction; + subtraction.SetMostSigOp(Oasis::Real { 5.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = subtraction.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing least significant operand"); + } + + { // Missing most significant operand + Oasis::Subtract subtraction; + subtraction.SetLeastSigOp(Oasis::Real { 3.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = subtraction.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Multiplication", "[PALM][Serializer][Multiplication]") { const Oasis::Multiply<> multiplication { @@ -217,6 +271,33 @@ TEST_CASE("PALM Serialization for Nested Multiplication", "[PALM][Serializer][Mu REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Multiplication", "[PALM][Serializer][Multiplication]") +{ + { // Missing least significant operand + Oasis::Multiply multiplication; + multiplication.SetMostSigOp(Oasis::Real { 5.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = multiplication.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing least significant operand"); + } + + { // Missing most significant operand + Oasis::Multiply multiplication; + multiplication.SetLeastSigOp(Oasis::Real { 3.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = multiplication.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Division", "[PALM][Serializer][Division]") { const Oasis::Divide<> division { @@ -249,6 +330,33 @@ TEST_CASE("PALM Serialization for Nested Division", "[PALM][Serializer][Division REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Division", "[PALM][Serializer][Division]") +{ + { // Missing least significant operand + Oasis::Divide division; + division.SetMostSigOp(Oasis::Real { 6.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = division.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing least significant operand"); + } + + { // Missing most significant operand + Oasis::Divide division; + division.SetLeastSigOp(Oasis::Real { 3.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = division.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Exponentiation", "[PALM][Serializer][Exponentiation]") { const Oasis::Exponent<> exponent { @@ -281,6 +389,33 @@ TEST_CASE("PALM Serialization for Nested Exponentiation", "[PALM][Serializer][Ex REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Exponentiation", "[PALM][Serializer][Exponentiation]") +{ + { // Missing least significant operand + Oasis::Exponent exponent; + exponent.SetMostSigOp(Oasis::Real { 2.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = exponent.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing least significant operand"); + } + + { // Missing most significant operand + Oasis::Exponent exponent; + exponent.SetLeastSigOp(Oasis::Real { 3.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = exponent.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Logarithm", "[PALM][Serializer][Logarithm]") { const Oasis::Log<> log { @@ -313,6 +448,33 @@ TEST_CASE("PALM Serialization for Nested Logarithm", "[PALM][Serializer][Logarit REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Logarithm", "[PALM][Serializer][Logarithm]") +{ + { // Missing least significant operand + Oasis::Log log; + log.SetMostSigOp(Oasis::Real { 10.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = log.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing least significant operand"); + } + + { // Missing most significant operand + Oasis::Log log; + log.SetLeastSigOp(Oasis::Real { 1000.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = log.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Negation", "[PALM][Serializer][Negation]") { const Oasis::Negate<> negate { @@ -342,6 +504,20 @@ TEST_CASE("PALM Serialization for Nested Negation", "[PALM][Serializer][Negation REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Negation", "[PALM][Serializer][Negation]") +{ + { // Missing operand + Oasis::Negate negate; + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = negate.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Derivative", "[PALM][Serializer][Derivative]") { const Oasis::Derivative<> derivative { @@ -374,6 +550,33 @@ TEST_CASE("PALM Serialization for Nested Derivative", "[PALM][Serializer][Deriva REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Derivative", "[PALM][Serializer][Derivative]") +{ + { // Missing least significant operand + Oasis::Derivative derivative; + derivative.SetMostSigOp(Oasis::Variable { "x" }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = derivative.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing least significant operand"); + } + + { // Missing most significant operand + Oasis::Derivative derivative; + derivative.SetLeastSigOp(Oasis::Real { 5.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = derivative.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Integral", "[PALM][Serializer][Integral]") { const Oasis::Integral<> integral { @@ -406,6 +609,33 @@ TEST_CASE("PALM Serialization for Nested Integral", "[PALM][Serializer][Integral REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Malformed Integral", "[PALM][Serializer][Integral]") +{ + { // Missing least significant operand + Oasis::Integral integral; + integral.SetMostSigOp(Oasis::Variable { "x" }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = integral.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing least significant operand"); + } + + { // Missing most significant operand + Oasis::Integral integral; + integral.SetLeastSigOp(Oasis::Real { 5.0 }); + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = integral.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + // TODO: Add tests for Matrix TEST_CASE("PALM Serialization for Euler Number", "[PALM][Serializer][EulerNumber]") @@ -446,6 +676,35 @@ TEST_CASE("PALM Serialization for Magnitude", "[PALM][Serializer][Magnitude]") REQUIRE(expected == result); } +TEST_CASE("PALM Serialization for Nested Magnitude", "[PALM][Serializer][Magnitude]") +{ + const Oasis::Magnitude> magnitude { + Oasis::Magnitude { + Oasis::Real { -5.0 } } + }; + + Oasis::PALMSerializer serializer {}; + + auto result = magnitude.Accept(serializer).value(); + std::string expected = "( magnitude ( magnitude ( real -5 ) ) )"; + + REQUIRE(expected == result); +} + +TEST_CASE("PALM Serialization for Malformed Magnitude", "[PALM][Serializer][Magnitude]") +{ + { // Missing operand + Oasis::Magnitude magnitude; + + Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); + + auto result = magnitude.Accept(serializer); + + REQUIRE(!result.has_value()); + REQUIRE(result.error() == "Expression missing most significant operand"); + } +} + TEST_CASE("PALM Serialization for Complex Expressions", "[PALM][Serializer][ComplexExpression]") { Oasis::PALMSerializer serializer {}; @@ -1325,82 +1584,4 @@ TEST_CASE("Parse Empty Expression", "[FromPALM][Parsing]") const auto expr = Oasis::FromPALM(""); REQUIRE(!expr); REQUIRE(expr.error() == Oasis::ParseError::UnexpectedEndOfInput); -} - -/* - * Start of Serializer Tests - */ -TEST_CASE("Serialize Real", "[FromPALM][Serialization]") -{ - Oasis::Real real { 5.0 }; - - Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); - - auto result = real.Accept(serializer); - - REQUIRE(result.has_value()); - REQUIRE(result.value() == "( real 5 )"); -} - -TEST_CASE("Serialize Negative Real", "[FromPALM][Serialization]") -{ - Oasis::Real real { -5.0 }; - - Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); - - auto result = real.Accept(serializer); - - REQUIRE(result.has_value()); - REQUIRE(result.value() == "( real -5 )"); -} - -TEST_CASE("Serialize Imaginary", "[FromPALM][Serialization]") -{ - Oasis::Imaginary imaginary; - - Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); - - auto result = imaginary.Accept(serializer); - - REQUIRE(result.has_value()); - REQUIRE(result.value() == "( i )"); -} - -TEST_CASE("Serialize Variable", "[FromPALM][Serialization]") -{ - Oasis::Variable variable { "x" }; - - Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); - - auto result = variable.Accept(serializer); - - REQUIRE(result.has_value()); - REQUIRE(result.value() == "( var x )"); -} - -TEST_CASE("Serialize Addition", "[FromPALM][Serialization]") -{ - Oasis::Add<> addition { - Oasis::Real { 5.0 }, - Oasis::Real { 3.0 } - }; - - Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); - - auto result = addition.Accept(serializer); - - REQUIRE(result.has_value()); - REQUIRE(result.value() == "( + ( real 5 ) ( real 3 ) )"); -} - -TEST_CASE("Serialize Malformed Addition", "[FromPALM][Serialization]") -{ - Oasis::Add addition; - addition.SetMostSigOp(Oasis::Real { 5.0 }); - - Oasis::PALMSerializer serializer = Oasis::PALMSerializer(); - - auto result = addition.Accept(serializer); - - REQUIRE(!result.has_value()); } \ No newline at end of file From f17f1eab29609d2a9394ab1a4473da83b0c36e2a Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 09:45:58 -0500 Subject: [PATCH 06/30] Add PALMHelper for expression token conversion and update includes in PALMSerializer --- io/CMakeLists.txt | 5 +- io/include/Oasis/PALMSerializer.hpp | 2 +- io/src/FromPALM.cpp | 2 +- io/src/PALMConst.hpp | 129 ---------------------------- io/src/PALMHelper.cpp | 77 +++++++++++++++++ io/src/PALMHelper.hpp | 73 ++++++++++++++++ io/src/PALMSerializer.cpp | 15 ++-- 7 files changed, 162 insertions(+), 141 deletions(-) delete mode 100644 io/src/PALMConst.hpp create mode 100644 io/src/PALMHelper.cpp create mode 100644 io/src/PALMHelper.hpp diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 5843d7f7..742112ed 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -1,7 +1,7 @@ set(OASIS_IO_SOURCES # cmake-format: sortable src/FromPALM.cpp src/FromString.cpp src/InFixSerializer.cpp - src/MathMLSerializer.cpp src/PALMSerializer.cpp src/TeXSerializer.cpp) + src/MathMLSerializer.cpp src/PALMSerializer.cpp src/PALMHelper.cpp src/TeXSerializer.cpp) set(OASIS_IO_HEADERS # cmake-format: sortable @@ -10,7 +10,8 @@ set(OASIS_IO_HEADERS include/Oasis/InFixSerializer.hpp include/Oasis/MathMLSerializer.hpp include/Oasis/PALMSerializer.hpp - include/Oasis/TeXSerializer.hpp) + include/Oasis/TeXSerializer.hpp + src/PALMHelper.hpp) add_library(OasisIO ${OASIS_IO_SOURCES} ${OASIS_IO_HEADERS}) add_library(Oasis::IO ALIAS OasisIO) diff --git a/io/include/Oasis/PALMSerializer.hpp b/io/include/Oasis/PALMSerializer.hpp index 57baa2a7..ab47ef63 100644 --- a/io/include/Oasis/PALMSerializer.hpp +++ b/io/include/Oasis/PALMSerializer.hpp @@ -5,7 +5,7 @@ #ifndef OASIS_PALMSERIALIZER_HPP #define OASIS_PALMSERIALIZER_HPP -#include "../../src/PALMConst.hpp" +#include "../../src/PALMHelper.hpp" #include "Oasis/Add.hpp" #include diff --git a/io/src/FromPALM.cpp b/io/src/FromPALM.cpp index 9a4bd8fa..6e489dab 100644 --- a/io/src/FromPALM.cpp +++ b/io/src/FromPALM.cpp @@ -24,7 +24,7 @@ #include "Oasis/Variable.hpp" -#include "PALMConst.hpp" +#include "PALMHelper.hpp" namespace Oasis { /** A simple token stream for parsing PALM strings. diff --git a/io/src/PALMConst.hpp b/io/src/PALMConst.hpp deleted file mode 100644 index 598e84bb..00000000 --- a/io/src/PALMConst.hpp +++ /dev/null @@ -1,129 +0,0 @@ -// -// Created by codin on 10/31/25. -// - -#ifndef OASIS_PALMCONST_HPP -#define OASIS_PALMCONST_HPP - -#include -#include -#include - -#include "Oasis/Expression.hpp" - -namespace Oasis { -struct PALMOpts { - enum class ImgSym { - I, - J - } imaginarySymbol - = ImgSym::I; - - uint8_t numPlaces = 5; - - enum class Separator { - SPACE - } separator - = Separator::SPACE; -}; - -static auto PALMExpressionToToken(const ExpressionType type, const PALMOpts& options) -> std::string_view -{ - switch (type) { - case Oasis::ExpressionType::Real: - return "real"; - case Oasis::ExpressionType::Imaginary: - switch (options.imaginarySymbol) { - case PALMOpts::ImgSym::J: - return "j"; - case PALMOpts::ImgSym::I: - default: - return "i"; - } - case Oasis::ExpressionType::Variable: - return "var"; - case Oasis::ExpressionType::Add: - return "+"; - case Oasis::ExpressionType::Subtract: - return "-"; - case Oasis::ExpressionType::Multiply: - return "*"; - case Oasis::ExpressionType::Divide: - return "/"; - case Oasis::ExpressionType::Exponent: - return "^"; - case Oasis::ExpressionType::Log: - return "log"; - case Oasis::ExpressionType::Integral: - return "int"; - case Oasis::ExpressionType::Limit: - return "limit"; - case Oasis::ExpressionType::Derivative: - return "d"; - case Oasis::ExpressionType::Negate: - return "neg"; - case Oasis::ExpressionType::Sqrt: - return "sqrt"; - case Oasis::ExpressionType::Matrix: - return "matrix"; - case Oasis::ExpressionType::Pi: - return "pi"; - case Oasis::ExpressionType::EulerNumber: - return "e"; - case Oasis::ExpressionType::Magnitude: - return "magnitude"; - default: - return ""; - } -} - -enum PALMDelimiterType { - START_EXPRESSION, - END_EXPRESSION, - SEPARATOR -}; - -static auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& options) -> std::string_view -{ - switch (type) { - case START_EXPRESSION: - return "("; - case END_EXPRESSION: - return ")"; - case SEPARATOR: - switch (options.separator) { - case PALMOpts::Separator::SPACE: - default: - return " "; - } - default: - return ""; - } -} - -struct PALMDelimiters { - constexpr static std::string_view START_EXPRESSION = "("; - constexpr static std::string_view END_EXPRESSION = ")"; -}; - -// Define PALM tokens -constexpr inline std::string_view PALM_OPEN_PARENS = "("; -constexpr inline std::string_view PALM_CLOSE_PARENS = ")"; - -constexpr inline std::string_view PALM_REAL = "real"; -constexpr inline std::array PALM_IMAGINARY = { "i", "j" }; -constexpr inline std::string_view PALM_VARIABLE = "var"; -constexpr inline std::string_view PALM_ADD = "+"; -constexpr inline std::string_view PALM_SUBTRACT = "-"; -constexpr inline std::string_view PALM_MULTIPLY = "*"; -constexpr inline std::string_view PALM_DIVIDE = "/"; -constexpr inline std::string_view PALM_EXPONENT = "^"; -constexpr inline std::string_view PALM_LOG = "log"; -constexpr inline std::string_view PALM_INTEGRAL = "int"; -constexpr inline std::string_view PALM_NEGATE = "neg"; -constexpr inline std::string_view PALM_DERIVATIVE = "d"; -constexpr std::string_view PALM_MATRIX = "matrix"; -constexpr inline std::array PALM_PI = { "pi" }; -constexpr inline std::string_view PALM_EULER = "e"; -} -#endif // OASIS_PALMCONST_HPP \ No newline at end of file diff --git a/io/src/PALMHelper.cpp b/io/src/PALMHelper.cpp new file mode 100644 index 00000000..fdc9fa12 --- /dev/null +++ b/io/src/PALMHelper.cpp @@ -0,0 +1,77 @@ +// +// Created by codin on 11/8/25. +// + +#include "PALMHelper.hpp" + +namespace Oasis { + +auto PALMExpressionToToken(const ExpressionType type, const PALMOpts& options) -> std::string_view +{ + switch (type) { + case Oasis::ExpressionType::Real: + return "real"; + case Oasis::ExpressionType::Imaginary: + switch (options.imaginarySymbol) { + case PALMOpts::ImgSym::J: + return "j"; + case PALMOpts::ImgSym::I: + default: + return "i"; + } + case Oasis::ExpressionType::Variable: + return "var"; + case Oasis::ExpressionType::Add: + return "+"; + case Oasis::ExpressionType::Subtract: + return "-"; + case Oasis::ExpressionType::Multiply: + return "*"; + case Oasis::ExpressionType::Divide: + return "/"; + case Oasis::ExpressionType::Exponent: + return "^"; + case Oasis::ExpressionType::Log: + return "log"; + case Oasis::ExpressionType::Integral: + return "int"; + case Oasis::ExpressionType::Limit: + return "limit"; + case Oasis::ExpressionType::Derivative: + return "d"; + case Oasis::ExpressionType::Negate: + return "neg"; + case Oasis::ExpressionType::Sqrt: + return "sqrt"; + case Oasis::ExpressionType::Matrix: + return "matrix"; + case Oasis::ExpressionType::Pi: + return "pi"; + case Oasis::ExpressionType::EulerNumber: + return "e"; + case Oasis::ExpressionType::Magnitude: + return "magnitude"; + default: + return ""; + } +} + +auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& options) -> std::string_view +{ + switch (type) { + case START_EXPRESSION: + return "("; + case END_EXPRESSION: + return ")"; + case SEPARATOR: + switch (options.separator) { + case PALMOpts::Separator::SPACE: + default: + return " "; + } + default: + return ""; + } +} + +} diff --git a/io/src/PALMHelper.hpp b/io/src/PALMHelper.hpp new file mode 100644 index 00000000..f820ff1f --- /dev/null +++ b/io/src/PALMHelper.hpp @@ -0,0 +1,73 @@ +// +// Created by codin on 10/31/25. +// + +#ifndef OASIS_PALMCONST_HPP +#define OASIS_PALMCONST_HPP + +#include +#include + +#include "Oasis/Expression.hpp" + +namespace Oasis { + +/** Options for PALM serialization. */ +struct PALMOpts { + enum class ImgSym { + I, + J + } imaginarySymbol + = ImgSym::I; + + uint8_t numPlaces = 5; + + enum class Separator { + SPACE + } separator + = Separator::SPACE; +}; + +/** Types of delimiters used in PALM serialization. */ +enum PALMDelimiterType { + START_EXPRESSION, + END_EXPRESSION, + SEPARATOR +}; + +/** Convert an ExpressionType to its corresponding PALM token. + * + * @param type The ExpressionType to convert. + * @param options The PALM options to consider during conversion. + * @return The corresponding PALM token as a string view. + */ +auto PALMExpressionToToken(ExpressionType type, const PALMOpts& options) -> std::string_view; + +auto PALMDelimiterToToken(PALMDelimiterType type, const PALMOpts& options) -> std::string_view; + +struct PALMDelimiters { + constexpr static std::string_view START_EXPRESSION = "("; + constexpr static std::string_view END_EXPRESSION = ")"; +}; + +// Define PALM tokens +constexpr inline std::string_view PALM_OPEN_PARENS = "("; +constexpr inline std::string_view PALM_CLOSE_PARENS = ")"; + +constexpr inline std::string_view PALM_REAL = "real"; +constexpr inline std::array PALM_IMAGINARY = { "i", "j" }; +constexpr inline std::string_view PALM_VARIABLE = "var"; +constexpr inline std::string_view PALM_ADD = "+"; +constexpr inline std::string_view PALM_SUBTRACT = "-"; +constexpr inline std::string_view PALM_MULTIPLY = "*"; +constexpr inline std::string_view PALM_DIVIDE = "/"; +constexpr inline std::string_view PALM_EXPONENT = "^"; +constexpr inline std::string_view PALM_LOG = "log"; +constexpr inline std::string_view PALM_INTEGRAL = "int"; +constexpr inline std::string_view PALM_NEGATE = "neg"; +constexpr inline std::string_view PALM_DERIVATIVE = "d"; +constexpr std::string_view PALM_MATRIX = "matrix"; +constexpr inline std::array PALM_PI = { "pi" }; +constexpr inline std::string_view PALM_EULER = "e"; +} +#endif // OASIS_PALMCONST_HPP \ No newline at end of file diff --git a/io/src/PALMSerializer.cpp b/io/src/PALMSerializer.cpp index a9f85b0b..c2a8f12d 100644 --- a/io/src/PALMSerializer.cpp +++ b/io/src/PALMSerializer.cpp @@ -5,20 +5,19 @@ #include #include "Oasis/PALMSerializer.hpp" -#include "PALMConst.hpp" #include "Oasis/Add.hpp" -#include "Oasis/Multiply.hpp" -#include "Oasis/Real.hpp" -#include "Oasis/Subtract.hpp" -#include "Oasis/Variable.hpp" +#include "Oasis/Derivative.hpp" #include "Oasis/Divide.hpp" #include "Oasis/Exponent.hpp" -#include "Oasis/Log.hpp" -#include "Oasis/Negate.hpp" -#include "Oasis/Derivative.hpp" #include "Oasis/Integral.hpp" +#include "Oasis/Log.hpp" #include "Oasis/Magnitude.hpp" +#include "Oasis/Multiply.hpp" +#include "Oasis/Negate.hpp" +#include "Oasis/Real.hpp" +#include "Oasis/Subtract.hpp" +#include "Oasis/Variable.hpp" namespace Oasis { From 19865eee045f14fc6ed2ec181c505062364cdad7 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 11:16:05 -0500 Subject: [PATCH 07/30] Refactor PALMHelper to use maps for expression and delimiter token conversion --- io/src/PALMHelper.cpp | 87 +++++++++++++++--------------------------- io/src/PALMHelper.hpp | 43 +++++++++++++++++++++ io/tests/PALMTests.cpp | 33 ++++++++++++++++ 3 files changed, 107 insertions(+), 56 deletions(-) diff --git a/io/src/PALMHelper.cpp b/io/src/PALMHelper.cpp index fdc9fa12..103d2c6f 100644 --- a/io/src/PALMHelper.cpp +++ b/io/src/PALMHelper.cpp @@ -8,70 +8,45 @@ namespace Oasis { auto PALMExpressionToToken(const ExpressionType type, const PALMOpts& options) -> std::string_view { - switch (type) { - case Oasis::ExpressionType::Real: - return "real"; - case Oasis::ExpressionType::Imaginary: - switch (options.imaginarySymbol) { - case PALMOpts::ImgSym::J: - return "j"; - case PALMOpts::ImgSym::I: - default: - return "i"; - } - case Oasis::ExpressionType::Variable: - return "var"; - case Oasis::ExpressionType::Add: - return "+"; - case Oasis::ExpressionType::Subtract: - return "-"; - case Oasis::ExpressionType::Multiply: - return "*"; - case Oasis::ExpressionType::Divide: - return "/"; - case Oasis::ExpressionType::Exponent: - return "^"; - case Oasis::ExpressionType::Log: - return "log"; - case Oasis::ExpressionType::Integral: - return "int"; - case Oasis::ExpressionType::Limit: - return "limit"; - case Oasis::ExpressionType::Derivative: - return "d"; - case Oasis::ExpressionType::Negate: - return "neg"; - case Oasis::ExpressionType::Sqrt: - return "sqrt"; - case Oasis::ExpressionType::Matrix: - return "matrix"; - case Oasis::ExpressionType::Pi: - return "pi"; - case Oasis::ExpressionType::EulerNumber: - return "e"; - case Oasis::ExpressionType::Magnitude: - return "magnitude"; - default: + // Find the type in the map + auto it = expressionTypeToPALMTokenMap.find(type); + if (it == expressionTypeToPALMTokenMap.end()) { // Not found return ""; } + + // If the value is a string_view (one option), return it directly + if (std::holds_alternative(it->second)) { + return std::get(it->second); + } + + // Check for variants + if (std::holds_alternative>(it->second)) { // Imaginary symbol + const auto& symMap = std::get>(it->second); + auto symIt = symMap.find(options.imaginarySymbol); + + // Return the corresponding symbol if found + if (symIt != symMap.end()) { + return symIt->second; + } + + // Default to 'i' if not found + return symMap.at(PALMOpts::ImgSym::I); + } + + // Fallback + return ""; } auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& options) -> std::string_view { - switch (type) { - case START_EXPRESSION: - return "("; - case END_EXPRESSION: - return ")"; - case SEPARATOR: - switch (options.separator) { - case PALMOpts::Separator::SPACE: - default: - return " "; - } - default: + // Find the type in the map + auto it = palmDelimiterToTokenMap.find(type); + if (it == palmDelimiterToTokenMap.end()) { // Not found return ""; } + + // Return the corresponding token + return std::get(it->second); } } diff --git a/io/src/PALMHelper.hpp b/io/src/PALMHelper.hpp index f820ff1f..755f8c13 100644 --- a/io/src/PALMHelper.hpp +++ b/io/src/PALMHelper.hpp @@ -10,6 +10,8 @@ #include "Oasis/Expression.hpp" +#include + namespace Oasis { /** Options for PALM serialization. */ @@ -35,6 +37,47 @@ enum PALMDelimiterType { SEPARATOR }; +/** Mapping from PalmDelimiterType to PALM token strings. */ +static const std::unordered_map> palmDelimiterToTokenMap = { + { START_EXPRESSION, "(" }, + { END_EXPRESSION, ")" }, + { SEPARATOR, " " } +}; + +/** Operators are taken from ExpressionType enum in Expression.hpp + * @see ExpressionType + */ + +/** Mapping from ExpressionType to PALM token strings. */ +static const std::unordered_map +>> expressionTypeToPALMTokenMap = { + { ExpressionType::Real, "real" }, + { ExpressionType::Imaginary, std::unordered_map{ + { + { PALMOpts::ImgSym::I, "i" }, + { PALMOpts::ImgSym::J, "j" } + } + } }, + { ExpressionType::Variable, "var" }, + { ExpressionType::Add, "+" }, + { ExpressionType::Subtract, "-" }, + { ExpressionType::Multiply, "*" }, + { ExpressionType::Divide, "/" }, + { ExpressionType::Exponent, "^" }, + { ExpressionType::Log, "log" }, + { ExpressionType::Integral, "int" }, + { ExpressionType::Derivative, "d" }, + { ExpressionType::Negate, "neg" }, + { ExpressionType::Matrix, "matrix" }, + { ExpressionType::Pi, "pi" }, + { ExpressionType::EulerNumber, "e" }, + { ExpressionType::Magnitude, "magnitude" } +}; + /** Convert an ExpressionType to its corresponding PALM token. * * @param type The ExpressionType to convert. diff --git a/io/tests/PALMTests.cpp b/io/tests/PALMTests.cpp index 36f00826..4c7ac3af 100644 --- a/io/tests/PALMTests.cpp +++ b/io/tests/PALMTests.cpp @@ -746,6 +746,39 @@ TEST_CASE("PALM Serialization for Complex Expressions", "[PALM][Serializer][Comp } } +// Parser +TEST_CASE("PALM Parsing for Real with various formats", "[PALM][Parser][Real]") +{ + { // Test with integer + const auto expr = Oasis::FromPALM("( real 42 )"); + + REQUIRE(expr.has_value()); + REQUIRE((*expr)->Is()); + + const auto real = Oasis::RecursiveCast(**expr); + REQUIRE(real->GetValue() == 42.0); + } + + { // Test with float + const auto expr = Oasis::FromPALM("( real 3.14 )"); + + REQUIRE(expr.has_value()); + REQUIRE((*expr)->Is()); + + const auto real = Oasis::RecursiveCast(**expr); + REQUIRE(real->GetValue() == 3.14); + } + + { // Test with negative float + const auto expr = Oasis::FromPALM("( real -0.001 )"); + + REQUIRE(expr.has_value()); + REQUIRE((*expr)->Is()); + + const auto real = Oasis::RecursiveCast(**expr); + REQUIRE(real->GetValue() == -0.001); + } +} /* * Real Operation From d89b9f6ece0f74111f811df3805978b9e509b4e7 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 11:23:01 -0500 Subject: [PATCH 08/30] Add TODO comment for Matrix serialization in PALMSerializer --- io/src/PALMSerializer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/io/src/PALMSerializer.cpp b/io/src/PALMSerializer.cpp index c2a8f12d..fd458793 100644 --- a/io/src/PALMSerializer.cpp +++ b/io/src/PALMSerializer.cpp @@ -88,9 +88,10 @@ auto PALMSerializer::TypedVisit(const Integral<>& integral) -> RetT return SerializeBinaryExpression(integral); } -auto PALMSerializer::TypedVisit(const Matrix& matrix) -> RetT +auto PALMSerializer::TypedVisit(const Matrix& /*matrix*/) -> RetT { - return std::unexpected { "Not implemented yet" }; + // TODO: Implement Matrix serialization + return std::unexpected { "Matrix is not implemented yet" }; } auto PALMSerializer::TypedVisit(const EulerNumber&) -> RetT From d3b7e369d1d706bca4caccd7644cd58f9875f241 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 8 Nov 2025 16:25:35 +0000 Subject: [PATCH 09/30] [Actions] Format CMakeLists.txt --- io/CMakeLists.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 742112ed..6c3a4356 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -1,7 +1,12 @@ set(OASIS_IO_SOURCES # cmake-format: sortable - src/FromPALM.cpp src/FromString.cpp src/InFixSerializer.cpp - src/MathMLSerializer.cpp src/PALMSerializer.cpp src/PALMHelper.cpp src/TeXSerializer.cpp) + src/FromPALM.cpp + src/FromString.cpp + src/InFixSerializer.cpp + src/MathMLSerializer.cpp + src/PALMHelper.cpp + src/PALMSerializer.cpp + src/TeXSerializer.cpp) set(OASIS_IO_HEADERS # cmake-format: sortable @@ -11,7 +16,7 @@ set(OASIS_IO_HEADERS include/Oasis/MathMLSerializer.hpp include/Oasis/PALMSerializer.hpp include/Oasis/TeXSerializer.hpp - src/PALMHelper.hpp) + src/PALMHelper.hpp) add_library(OasisIO ${OASIS_IO_SOURCES} ${OASIS_IO_HEADERS}) add_library(Oasis::IO ALIAS OasisIO) From 03ed67d41e8abcf2af48db3213ce9d2adfd3cf18 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 13:53:20 -0500 Subject: [PATCH 10/30] Add Boost Bimap includes and define TokenBimap for expression type mapping --- io/CMakeLists.txt | 1 + io/src/PALMHelper.hpp | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 6c3a4356..621dff37 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(OasisIO ${OASIS_IO_SOURCES} ${OASIS_IO_HEADERS}) add_library(Oasis::IO ALIAS OasisIO) target_include_directories(OasisIO PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(OasisIO PRIVATE ${Boost_SOURCE_DIR}) target_link_libraries(OasisIO PUBLIC Oasis::Oasis tinyxml2::tinyxml2) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") diff --git a/io/src/PALMHelper.hpp b/io/src/PALMHelper.hpp index 755f8c13..a134b0a4 100644 --- a/io/src/PALMHelper.hpp +++ b/io/src/PALMHelper.hpp @@ -7,13 +7,22 @@ #include #include +#include + +#include +#include +#include #include "Oasis/Expression.hpp" -#include namespace Oasis { +using TokenBimap = boost::bimaps::bimap< + boost::bimaps::unordered_multiset_of, + boost::bimaps::unordered_set_of +>; + /** Options for PALM serialization. */ struct PALMOpts { enum class ImgSym { From 6acfe67a1b2584a64d68f3e63272d0b190a7110a Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 13:57:48 -0500 Subject: [PATCH 11/30] Update PALMDelimiterToToken to remove unused options parameter --- io/src/PALMHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/src/PALMHelper.cpp b/io/src/PALMHelper.cpp index 103d2c6f..6bc7f06e 100644 --- a/io/src/PALMHelper.cpp +++ b/io/src/PALMHelper.cpp @@ -37,7 +37,7 @@ auto PALMExpressionToToken(const ExpressionType type, const PALMOpts& options) - return ""; } -auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& options) -> std::string_view +auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& /*options*/) -> std::string_view { // Find the type in the map auto it = palmDelimiterToTokenMap.find(type); From 550c5956e691d935385132f2d2ceba623394d011 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 14:01:04 -0500 Subject: [PATCH 12/30] Update FetchBoost.cmake to include 'bimap' in BOOST_INCLUDE_LIBRARIES --- cmake/FetchBoost.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FetchBoost.cmake b/cmake/FetchBoost.cmake index 73a7a2c6..d2dc4d71 100644 --- a/cmake/FetchBoost.cmake +++ b/cmake/FetchBoost.cmake @@ -6,5 +6,5 @@ FetchContent_Declare( EXCLUDE_FROM_ALL ) -set(BOOST_INCLUDE_LIBRARIES any callable_traits mpl) +set(BOOST_INCLUDE_LIBRARIES any bimap callable_traits mpl) FetchContent_MakeAvailable(Boost) \ No newline at end of file From d10cde9293e45c2168fe82a27d14bfe46dce2b21 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 14:06:07 -0500 Subject: [PATCH 13/30] Refactor PALMSerializer to improve code formatting and readability --- io/src/PALMSerializer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/io/src/PALMSerializer.cpp b/io/src/PALMSerializer.cpp index fd458793..584faa74 100644 --- a/io/src/PALMSerializer.cpp +++ b/io/src/PALMSerializer.cpp @@ -34,12 +34,12 @@ auto PALMSerializer::TypedVisit(const Imaginary&) -> RetT auto PALMSerializer::TypedVisit(const Variable& variable) -> RetT { - return SerializeExpression(ExpressionType::Variable, { variable.GetName() }); + return SerializeExpression(ExpressionType::Variable, { variable.GetName() }); } auto PALMSerializer::TypedVisit(const Undefined&) -> RetT { - return SerializeExpression(ExpressionType::None); + return SerializeExpression(ExpressionType::None); } auto PALMSerializer::TypedVisit(const Add<>& add) -> RetT @@ -47,7 +47,6 @@ auto PALMSerializer::TypedVisit(const Add<>& add) -> RetT return SerializeBinaryExpression(add); } - auto PALMSerializer::TypedVisit(const Subtract<>& subtract) -> RetT { return SerializeBinaryExpression(subtract); From f7eae8bc35fa5760e3391d06e26ccc7d37e2e6ac Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 14:16:47 -0500 Subject: [PATCH 14/30] Update CMakeLists.txt to link Boost Bimap and adjust include directories --- io/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 621dff37..0622aa0c 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -22,7 +22,8 @@ add_library(OasisIO ${OASIS_IO_SOURCES} ${OASIS_IO_HEADERS}) add_library(Oasis::IO ALIAS OasisIO) target_include_directories(OasisIO PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -target_include_directories(OasisIO PRIVATE ${Boost_SOURCE_DIR}) +target_link_libraries(OasisIO PUBLIC Boost::bimap) +target_include_directories(OasisIO PUBLIC ${Boost_INCLUDE_DIRS}) target_link_libraries(OasisIO PUBLIC Oasis::Oasis tinyxml2::tinyxml2) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") From 21942deb300e6490ec8bc683cefcd5eaa3403823 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 15:36:54 -0500 Subject: [PATCH 15/30] Update CMakeLists.txt and FetchBoost.cmake to include Boost Assign; modify PALMHelper.hpp to define TokenBimap for expression type mapping --- cmake/FetchBoost.cmake | 2 +- io/CMakeLists.txt | 2 ++ io/src/PALMHelper.hpp | 30 ++++++++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/cmake/FetchBoost.cmake b/cmake/FetchBoost.cmake index d2dc4d71..eaddcacc 100644 --- a/cmake/FetchBoost.cmake +++ b/cmake/FetchBoost.cmake @@ -6,5 +6,5 @@ FetchContent_Declare( EXCLUDE_FROM_ALL ) -set(BOOST_INCLUDE_LIBRARIES any bimap callable_traits mpl) +set(BOOST_INCLUDE_LIBRARIES any assign bimap callable_traits mpl) FetchContent_MakeAvailable(Boost) \ No newline at end of file diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 0622aa0c..3a1f14a6 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -23,7 +23,9 @@ add_library(Oasis::IO ALIAS OasisIO) target_include_directories(OasisIO PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_link_libraries(OasisIO PUBLIC Boost::bimap) +target_link_libraries(OasisIO PUBLIC Boost::assign) target_include_directories(OasisIO PUBLIC ${Boost_INCLUDE_DIRS}) +target_include_directories(OasisIO PRIVATE ${Boost_SOURCE_DIR}/libs/assign/include) target_link_libraries(OasisIO PUBLIC Oasis::Oasis tinyxml2::tinyxml2) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") diff --git a/io/src/PALMHelper.hpp b/io/src/PALMHelper.hpp index a134b0a4..e3eb60cb 100644 --- a/io/src/PALMHelper.hpp +++ b/io/src/PALMHelper.hpp @@ -5,13 +5,15 @@ #ifndef OASIS_PALMCONST_HPP #define OASIS_PALMCONST_HPP -#include #include +#include #include +#include #include -#include #include +#include +#include #include "Oasis/Expression.hpp" @@ -23,6 +25,30 @@ using TokenBimap = boost::bimaps::bimap< boost::bimaps::unordered_set_of >; +#include + +#include + +const TokenBimap kTokenBimap = ::boost::assign::list_of + ("real", ExpressionType::Real) + ("i", ExpressionType::Imaginary) + ("j", ExpressionType::Imaginary) + ("var", ExpressionType::Variable) + ("+", ExpressionType::Add) + ("-", ExpressionType::Subtract) + ("*", ExpressionType::Multiply) + ("/", ExpressionType::Divide) + ("^", ExpressionType::Exponent) + ("log", ExpressionType::Log) + ("int", ExpressionType::Integral) + ("d", ExpressionType::Derivative) + ("neg", ExpressionType::Negate) + ("matrix", ExpressionType::Matrix) + ("pi", ExpressionType::Pi) + ("e", ExpressionType::EulerNumber) + ("magnitude", ExpressionType::Magnitude) + ; + /** Options for PALM serialization. */ struct PALMOpts { enum class ImgSym { From 0c7c82a8b6530a9e9a52bac7f12da1f46977e8f4 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 20:16:24 -0500 Subject: [PATCH 16/30] Update CMakeLists.txt to conditionally link Boost libraries based on OASIS_BUILD_JS --- io/CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 3a1f14a6..cfd0b962 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -22,10 +22,12 @@ add_library(OasisIO ${OASIS_IO_SOURCES} ${OASIS_IO_HEADERS}) add_library(Oasis::IO ALIAS OasisIO) target_include_directories(OasisIO PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) -target_link_libraries(OasisIO PUBLIC Boost::bimap) -target_link_libraries(OasisIO PUBLIC Boost::assign) -target_include_directories(OasisIO PUBLIC ${Boost_INCLUDE_DIRS}) -target_include_directories(OasisIO PRIVATE ${Boost_SOURCE_DIR}/libs/assign/include) +if(NOT OASIS_BUILD_JS) + target_link_libraries(OasisIO PUBLIC Boost::bimap) + target_link_libraries(OasisIO PUBLIC Boost::assign) + target_include_directories(OasisIO PUBLIC ${Boost_INCLUDE_DIRS}) + target_include_directories(OasisIO PRIVATE ${Boost_SOURCE_DIR}/libs/assign/include) +endif() target_link_libraries(OasisIO PUBLIC Oasis::Oasis tinyxml2::tinyxml2) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") From b2607a26b7b17ba0a50409b53a3205543a767a8d Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 9 Nov 2025 01:20:19 +0000 Subject: [PATCH 17/30] [Actions] Format CMakeLists.txt --- io/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index cfd0b962..b424cdf2 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -26,7 +26,8 @@ if(NOT OASIS_BUILD_JS) target_link_libraries(OasisIO PUBLIC Boost::bimap) target_link_libraries(OasisIO PUBLIC Boost::assign) target_include_directories(OasisIO PUBLIC ${Boost_INCLUDE_DIRS}) - target_include_directories(OasisIO PRIVATE ${Boost_SOURCE_DIR}/libs/assign/include) + target_include_directories(OasisIO + PRIVATE ${Boost_SOURCE_DIR}/libs/assign/include) endif() target_link_libraries(OasisIO PUBLIC Oasis::Oasis tinyxml2::tinyxml2) From 0cd5c6ddd55c666fbd4bc2c07bd4574ccdf68526 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 21:38:14 -0500 Subject: [PATCH 18/30] Refactored PALMTokens to take in all possible tokens instead of split by type (operators and delimiters). Also refactored the helper functions to use the new feature. Also split the separators between the start/end parenthesis and between operands into two separate delimiters (so in the future we could do something like (+ 5 2) instead of needing to be ( + 5 2 ) --- io/include/Oasis/PALMSerializer.hpp | 6 +- io/src/PALMHelper.cpp | 41 ++------- io/src/PALMHelper.hpp | 127 +++++++++++----------------- io/tests/PALMTests.cpp | 4 +- 4 files changed, 62 insertions(+), 116 deletions(-) diff --git a/io/include/Oasis/PALMSerializer.hpp b/io/include/Oasis/PALMSerializer.hpp index ab47ef63..da091d2c 100644 --- a/io/include/Oasis/PALMSerializer.hpp +++ b/io/include/Oasis/PALMSerializer.hpp @@ -66,7 +66,7 @@ class PALMSerializer final : public TypedVisitor std::string_view { - // Find the type in the map - auto it = expressionTypeToPALMTokenMap.find(type); - if (it == expressionTypeToPALMTokenMap.end()) { // Not found - return ""; + // Determine if there are multiple tokens for the type + switch (type) { + case ExpressionType::Imaginary: + return tokenBimap.right.at(options.imaginarySymbol); + default: + return tokenBimap.right.at(type); } - - // If the value is a string_view (one option), return it directly - if (std::holds_alternative(it->second)) { - return std::get(it->second); - } - - // Check for variants - if (std::holds_alternative>(it->second)) { // Imaginary symbol - const auto& symMap = std::get>(it->second); - auto symIt = symMap.find(options.imaginarySymbol); - - // Return the corresponding symbol if found - if (symIt != symMap.end()) { - return symIt->second; - } - - // Default to 'i' if not found - return symMap.at(PALMOpts::ImgSym::I); - } - - // Fallback - return ""; } auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& /*options*/) -> std::string_view { - // Find the type in the map - auto it = palmDelimiterToTokenMap.find(type); - if (it == palmDelimiterToTokenMap.end()) { // Not found - return ""; - } - - // Return the corresponding token - return std::get(it->second); + return tokenBimap.right.at(type); } } diff --git a/io/src/PALMHelper.hpp b/io/src/PALMHelper.hpp index e3eb60cb..44ad34da 100644 --- a/io/src/PALMHelper.hpp +++ b/io/src/PALMHelper.hpp @@ -13,49 +13,19 @@ #include #include #include -#include #include "Oasis/Expression.hpp" namespace Oasis { -using TokenBimap = boost::bimaps::bimap< - boost::bimaps::unordered_multiset_of, - boost::bimaps::unordered_set_of ->; - -#include - -#include - -const TokenBimap kTokenBimap = ::boost::assign::list_of - ("real", ExpressionType::Real) - ("i", ExpressionType::Imaginary) - ("j", ExpressionType::Imaginary) - ("var", ExpressionType::Variable) - ("+", ExpressionType::Add) - ("-", ExpressionType::Subtract) - ("*", ExpressionType::Multiply) - ("/", ExpressionType::Divide) - ("^", ExpressionType::Exponent) - ("log", ExpressionType::Log) - ("int", ExpressionType::Integral) - ("d", ExpressionType::Derivative) - ("neg", ExpressionType::Negate) - ("matrix", ExpressionType::Matrix) - ("pi", ExpressionType::Pi) - ("e", ExpressionType::EulerNumber) - ("magnitude", ExpressionType::Magnitude) - ; - /** Options for PALM serialization. */ struct PALMOpts { - enum class ImgSym { + enum class ImgSymType { I, J } imaginarySymbol - = ImgSym::I; + = ImgSymType::I; uint8_t numPlaces = 5; @@ -65,53 +35,55 @@ struct PALMOpts { = Separator::SPACE; }; +/** Operators are taken from ExpressionType enum in Expression.hpp + * @see ExpressionType + */ + /** Types of delimiters used in PALM serialization. */ enum PALMDelimiterType { START_EXPRESSION, END_EXPRESSION, - SEPARATOR -}; - -/** Mapping from PalmDelimiterType to PALM token strings. */ -static const std::unordered_map> palmDelimiterToTokenMap = { - { START_EXPRESSION, "(" }, - { END_EXPRESSION, ")" }, - { SEPARATOR, " " } + TOKEN_SEPARATOR, + EXPRESSION_PADDING }; -/** Operators are taken from ExpressionType enum in Expression.hpp - * @see ExpressionType - */ +/** Bi-directional mapping between PALM operator tokens and their corresponding ExpressionType, PALMDelimiterType, or variant type. */ +using PALMTokenBimap = boost::bimaps::bimap< + boost::bimaps::unordered_multiset_of, + boost::bimaps::unordered_set_of>>; + +/** A mapping from PALM tokens to con */ +const PALMTokenBimap tokenBimap = ::boost::assign::list_of + // Operators + ("real", ExpressionType::Real) + ("i", ExpressionType::Imaginary) + ("i", PALMOpts::ImgSymType::I) + ("j", ExpressionType::Imaginary) + ("j", PALMOpts::ImgSymType::J) + ("var", ExpressionType::Variable) + ("+", ExpressionType::Add) + ("-", ExpressionType::Subtract) + ("*", ExpressionType::Multiply) + ("/", ExpressionType::Divide) + ("^", ExpressionType::Exponent) + ("log", ExpressionType::Log) + ("int", ExpressionType::Integral) + ("d", ExpressionType::Derivative) + ("neg", ExpressionType::Negate) + ("matrix", ExpressionType::Matrix) + ("pi", ExpressionType::Pi) + ("e", ExpressionType::EulerNumber) + ("magnitude", ExpressionType::Magnitude) + // Delimiters + ("(", START_EXPRESSION) + (")", END_EXPRESSION) + (" ", TOKEN_SEPARATOR) + (" ", EXPRESSION_PADDING); -/** Mapping from ExpressionType to PALM token strings. */ -static const std::unordered_map ->> expressionTypeToPALMTokenMap = { - { ExpressionType::Real, "real" }, - { ExpressionType::Imaginary, std::unordered_map{ - { - { PALMOpts::ImgSym::I, "i" }, - { PALMOpts::ImgSym::J, "j" } - } - } }, - { ExpressionType::Variable, "var" }, - { ExpressionType::Add, "+" }, - { ExpressionType::Subtract, "-" }, - { ExpressionType::Multiply, "*" }, - { ExpressionType::Divide, "/" }, - { ExpressionType::Exponent, "^" }, - { ExpressionType::Log, "log" }, - { ExpressionType::Integral, "int" }, - { ExpressionType::Derivative, "d" }, - { ExpressionType::Negate, "neg" }, - { ExpressionType::Matrix, "matrix" }, - { ExpressionType::Pi, "pi" }, - { ExpressionType::EulerNumber, "e" }, - { ExpressionType::Magnitude, "magnitude" } -}; /** Convert an ExpressionType to its corresponding PALM token. * @@ -121,13 +93,14 @@ static const std::unordered_map std::string_view; +/** Convert a PALMDelimiterType to its corresponding PALM token. + * + * @param type The PALMDelimiterType to convert. + * @param options The PALM options to consider during conversion. + * @return The corresponding PALM token as a string view. + */ auto PALMDelimiterToToken(PALMDelimiterType type, const PALMOpts& options) -> std::string_view; -struct PALMDelimiters { - constexpr static std::string_view START_EXPRESSION = "("; - constexpr static std::string_view END_EXPRESSION = ")"; -}; - // Define PALM tokens constexpr inline std::string_view PALM_OPEN_PARENS = "("; constexpr inline std::string_view PALM_CLOSE_PARENS = ")"; diff --git a/io/tests/PALMTests.cpp b/io/tests/PALMTests.cpp index 4c7ac3af..e5e34baf 100644 --- a/io/tests/PALMTests.cpp +++ b/io/tests/PALMTests.cpp @@ -82,7 +82,7 @@ TEST_CASE("PALM Serialization for Imaginary with different characters", "[PALM][ const Oasis::Imaginary imaginary {}; { // Test with default character 'i' - Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMOpts::ImgSym::I } }; + Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMOpts::ImgSymType::I } }; auto result = imaginary.Accept(serializer).value(); std::string expected = "( i )"; @@ -91,7 +91,7 @@ TEST_CASE("PALM Serialization for Imaginary with different characters", "[PALM][ } { // Test with character 'j' - Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMOpts::ImgSym::J } }; + Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMOpts::ImgSymType::J } }; auto result = imaginary.Accept(serializer).value(); std::string expected = "( j )"; From 5987d3e7de62347d2ac34ceff0758097531dfcb5 Mon Sep 17 00:00:00 2001 From: codin Date: Sat, 8 Nov 2025 21:55:43 -0500 Subject: [PATCH 19/30] Clang Format --- io/src/PALMHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io/src/PALMHelper.cpp b/io/src/PALMHelper.cpp index 1c0066f7..7ef718ec 100644 --- a/io/src/PALMHelper.cpp +++ b/io/src/PALMHelper.cpp @@ -19,7 +19,7 @@ auto PALMExpressionToToken(const ExpressionType type, const PALMOpts& options) - auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& /*options*/) -> std::string_view { - return tokenBimap.right.at(type); + return tokenBimap.right.at(type); } } From 981649846f5aa920f662859c0860d90a0a649f96 Mon Sep 17 00:00:00 2001 From: codin Date: Sun, 9 Nov 2025 21:23:55 -0500 Subject: [PATCH 20/30] Split PALMHelper.hpp into PALMTypes.hpp, PALMConsts.hpp, and PALMSpec.hpp Created PALMTokenizer to tokenize the input string. (By whitespace or punctuators) --- io/CMakeLists.txt | 1 + io/include/Oasis/FromPALM.hpp | 31 ++++-- io/src/FromPALM.cpp | 32 ++++++ io/src/PALMConsts.hpp | 47 ++++++++ io/src/PALMHelper.hpp | 1 + io/src/PALMSpec.hpp | 60 +++++++++++ io/src/PALMTokenizer.cpp | 195 ++++++++++++++++++++++++++++++++++ io/src/PALMTokenizer.hpp | 45 ++++++++ io/src/PALMTypes.hpp | 70 ++++++++++++ io/tests/PALMTests.cpp | 62 +++++++++-- 10 files changed, 529 insertions(+), 15 deletions(-) create mode 100644 io/src/PALMConsts.hpp create mode 100644 io/src/PALMSpec.hpp create mode 100644 io/src/PALMTokenizer.cpp create mode 100644 io/src/PALMTokenizer.hpp create mode 100644 io/src/PALMTypes.hpp diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index b424cdf2..36787b8f 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -6,6 +6,7 @@ set(OASIS_IO_SOURCES src/MathMLSerializer.cpp src/PALMHelper.cpp src/PALMSerializer.cpp + src/PALMTokenizer.cpp src/TeXSerializer.cpp) set(OASIS_IO_HEADERS diff --git a/io/include/Oasis/FromPALM.hpp b/io/include/Oasis/FromPALM.hpp index c1ab5a6f..886156d5 100644 --- a/io/include/Oasis/FromPALM.hpp +++ b/io/include/Oasis/FromPALM.hpp @@ -11,20 +11,39 @@ namespace Oasis { enum class ParseError { None, - UnexpectedToken, - MissingClosingParen, - InvalidNumberFormat, - IncompleteExpression, - UnknownOperator, - UnexpectedEndOfInput + IncompleteExpression, // expression opens but required operands/args missing + MissingClosingParen, // '(' without matching ')' + UnexpectedEndOfInput, // input ended while more tokens were required + UnexpectedToken, // token present but not valid in this position + InvalidNumberFormat, // number failed to parse + UnknownOperator, // operator not recognized + TooManyOperands, // more expressions than operator's arity allows + TooFewOperands, // fewer expressions than operator's arity requires + AmbiguousParse, // grammar ambiguity or overload not resolvable + LexicalError, // tokenization error (bad character, unterminated string) + Other // fallback }; +// struct ParseError { +// ParseErrorType type; +// size_t token_index; // index in token stream where error detected +// size_t char_offset; // approximate character offset in source +// std::string got; // token text that caused the error (if any) +// std::string expected; // brief description of what was expected +// std::string message; // human-friendly diagnostic +// }; + + + /** Parses an expression from a PALM string. * * @param palmString The PALM string to parse. * @return The parsed expression, or nullptr if the string could not be parsed. */ auto FromPALM(const std::string& palmString) -> std::expected, ParseError>; + + +auto FromPALMNew(const std::string& palmString) -> std::expected, Oasis::ParseError>; } #endif // OASIS_FROMPALM_HPP diff --git a/io/src/FromPALM.cpp b/io/src/FromPALM.cpp index 6e489dab..6fa914a9 100644 --- a/io/src/FromPALM.cpp +++ b/io/src/FromPALM.cpp @@ -25,6 +25,7 @@ #include "Oasis/Variable.hpp" #include "PALMHelper.hpp" +#include "PALMTokenizer.hpp" namespace Oasis { /** A simple token stream for parsing PALM strings. @@ -257,6 +258,20 @@ auto ParseUnaryOperationElements(TokenStream& tokens) -> std::expected std::expected>, ParseError>; + + + + + + + + + + + + + + // PALM Grammar Start -> Expression $$ auto FromPALM(const std::string& palmString) -> std::expected, ParseError> { @@ -276,6 +291,22 @@ auto FromPALM(const std::string& palmString) -> std::expected std::expected, Oasis::ParseError> +{ + // String to input stream + auto inputStream = std::istringstream(palmString); + auto tokenizer = PALMTokenizer(inputStream); + + auto token = tokenizer.lookahead(); + + while (!tokenizer.eof()) { + // Process token + tokenizer.match(token); + token = tokenizer.lookahead(); + } + + return std::unexpected { Oasis::ParseError::Other }; +} auto TokenizePALM(const std::string& palmString) -> TokenStream { @@ -633,4 +664,5 @@ auto ParseNullaryOperationElements(TokenStream& tokens) -> std::expected + +namespace Oasis { +/** A mapping from PALM operator tokens to their corresponding ExpressionType. */ +inline const PALMOperatorBimap kPALMOperatorBimap = ::boost::assign::list_of + ("real", ExpressionType::Real) + ("i", ExpressionType::Imaginary) + ("j", ExpressionType::Imaginary) + ("var", ExpressionType::Variable) + ("+", ExpressionType::Add) + ("-", ExpressionType::Subtract) + ("*", ExpressionType::Multiply) + ("/", ExpressionType::Divide) + ("^", ExpressionType::Exponent) + ("log", ExpressionType::Log) + ("int", ExpressionType::Integral) + ("d", ExpressionType::Derivative) + ("neg", ExpressionType::Negate) + ("matrix", ExpressionType::Matrix) + ("pi", ExpressionType::Pi) + ("e", ExpressionType::EulerNumber) + ("magnitude", ExpressionType::Magnitude); + +/** Regular expression for valid PALM identifiers. */ +static constexpr std::string_view kPALMIdentifierRegex = R"(^[a-zA-Z_][a-zA-Z0-9_]*$)"; + +/** Regular expression for valid PALM numbers. */ +static constexpr std::string_view kPALMNumberRegex = R"(^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$)"; + +/** A mapping from PALM punctuator tokens to their corresponding PALMPunctuatorType. */ +inline const PALMPunctuatorBimap kPALMPunctuatorBimap = ::boost::assign::list_of + ("(", PALMPunctuatorType::StartExpression) + (")", PALMPunctuatorType::EndExpression); + +} + +#endif // OASIS_PALMCONSTS_HPP diff --git a/io/src/PALMHelper.hpp b/io/src/PALMHelper.hpp index 44ad34da..eb67e867 100644 --- a/io/src/PALMHelper.hpp +++ b/io/src/PALMHelper.hpp @@ -101,6 +101,7 @@ auto PALMExpressionToToken(ExpressionType type, const PALMOpts& options) -> std: */ auto PALMDelimiterToToken(PALMDelimiterType type, const PALMOpts& options) -> std::string_view; + // Define PALM tokens constexpr inline std::string_view PALM_OPEN_PARENS = "("; constexpr inline std::string_view PALM_CLOSE_PARENS = ")"; diff --git a/io/src/PALMSpec.hpp b/io/src/PALMSpec.hpp new file mode 100644 index 00000000..f434c96f --- /dev/null +++ b/io/src/PALMSpec.hpp @@ -0,0 +1,60 @@ +// +// Created by codin on 11/9/25. +// + +#ifndef OASIS_PALMSPEC_HPP +#define OASIS_PALMSPEC_HPP + +#include "PALMTypes.hpp" +#include "PALMConsts.hpp" + +#include + +namespace Oasis { + +/** Checks if a token is a PALM operator. + * + * @param token The token to check. + * @return True if the token is a PALM operator, false otherwise. + */ +static auto isTokenPALMOperator(const std::string& token) -> bool +{ + return kPALMOperatorBimap.left.count(token) != 0; +} + +/** Checks if a token is a PALM identifier. + * + * @param token The token to check. + * @return True if the token is a PALM identifier, false otherwise. + */ +static auto isTokenPALMIdentifier(const std::string& token) -> bool +{ + static const std::regex identifierRegex { std::string(kPALMIdentifierRegex)}; + return std::regex_match(token, identifierRegex); +} + +/** Checks if a token is a PALM number. + * + * @param token The token to check. + * @return True if the token is a PALM number, false otherwise. + */ +static auto isTokenPALMNumber(const std::string& token) -> bool +{ + static const std::regex numberRegex { std::string(kPALMNumberRegex)}; + return std::regex_match(token, numberRegex); +} + +/** Checks if a token is a PALM punctuator. + * + * @param token The token to check. + * @return True if the token is a PALM punctuator, false otherwise. + */ +static auto isTokenPALMPunctuator(const std::string& token) -> bool +{ + return kPALMPunctuatorBimap.left.count(token) != 0; +} + + +} + +#endif // OASIS_PALMSPEC_HPP \ No newline at end of file diff --git a/io/src/PALMTokenizer.cpp b/io/src/PALMTokenizer.cpp new file mode 100644 index 00000000..d473b01c --- /dev/null +++ b/io/src/PALMTokenizer.cpp @@ -0,0 +1,195 @@ +// +// Created by codin on 11/9/25. +// + +#include "PALMTokenizer.hpp" + +/** Builds a set of all prefixes of PALM punctuators. + * + * If a punctuator is "===", its prefixes are "=", "==", and "===". + * @return A set of string views representing all prefixes of PALM punctuators. + */ +std::unordered_set Oasis::PALMTokenizer::buildPunctuatorPrefixes() +{ + std::unordered_set prefixes; + for (const auto& [text, _] : PALMPunctuatorBimap().left) { + for (size_t i = 1; i <= text.size(); ++i) { + prefixes.insert(text.substr(0, i)); + } + } + return prefixes; +} + +/** Returns the set of all prefixes of PALM punctuators. + * + * @return A reference to the set of string views representing all prefixes of PALM punctuators. + */ +const std::unordered_set& Oasis::PALMTokenizer::punctuatorPrefixes() +{ + static const auto prefixes = buildPunctuatorPrefixes(); + return prefixes; +} + +/** Reads the next token from the input stream. + * + * @return The next PALM token. + */ +auto Oasis::PALMTokenizer::nextToken() const -> PALMToken +{ + // Create new token + PALMToken token = {}; + + if (currentToken) { // Update position based on previous token + token.tokenIndex = currentToken->tokenIndex + 1; + token.offset = currentToken->offset + currentToken->length; + token.line = currentToken->line; + token.column = currentToken->column + currentToken->length; + } + + // Skip whitespace + char ch; + while (inputStream.get(ch)) { + if (!std::isspace(static_cast(ch))) { + inputStream.unget(); // Put back the non-whitespace character + break; + } + if (ch == '\n') { + token.line++; + token.column = 1; + } else { + token.column++; + } + token.offset++; + } + + // End of Input Token + if (!inputStream.get(ch)) { + token.type = PALMTokenType::EndOfInput; + token.text = ""; + token.length = 0; + return token; + } + + // Put back the character for further processing + inputStream.unget(); + + // Punctuator Token + if (const auto punctuator = tryMatchPunctuator(inputStream); !punctuator.empty()) { + token.type = PALMTokenType::Punctuator; + token.text = punctuator; + token.length = punctuator.length(); + return token; + } + + // Build lexeme until whitespace or punctuator + std::stringbuf lexemeBuffer; + std::streampos lexemeTail = inputStream.tellg(); + + while (inputStream.get(ch)) { + if (std::isspace(static_cast(ch))) { + inputStream.unget(); // Put back the whitespace character + break; + } + + // Check for punctuator match + inputStream.unget(); + if (!tryMatchPunctuator(inputStream).empty()) { // Found a punctuator + inputStream.seekg(lexemeTail); // Reset to start of punctuator + break; + } + inputStream.get(ch); // Consume character + lexemeBuffer.sputc(ch); + lexemeTail = inputStream.tellg(); + } + + // Save lexeme info + token.text = lexemeBuffer.str(); + token.length = token.text.length(); + + // Operator Token + if (isTokenPALMOperator(lexemeBuffer.str())) { + token.type = PALMTokenType::Operator; + return token; + } + + // Identifier Token + if (isTokenPALMIdentifier(lexemeBuffer.str())) { + token.type = PALMTokenType::Identifier; + return token; + } + + // Number Token + if (isTokenPALMNumber(lexemeBuffer.str())) { + token.type = PALMTokenType::Number; + return token; + } + + // Unknown Token + token.type = PALMTokenType::Unknown; + return token; +} + +/** Tries to match a PALM punctuator from the input stream. + * + * @param in The input stream to read from. + * @return The matched punctuator string, or an empty string if no match. + */ +auto Oasis::PALMTokenizer::tryMatchPunctuator(std::istream& in) -> std::string +{ + std::string buffer; + const std::streampos startPos = in.tellg(); + + // Find Matching Punctuator + while (true) { + char ch; + if (!in.get(ch)) { + break; // End of stream + } + buffer.push_back(ch); + + if (isTokenPALMPunctuator(buffer)) { + // Found a matching punctuator + return buffer; + } + + if (!punctuatorPrefixes().contains(buffer)) { + // No longer a valid prefix + break; + } + } + + in.clear(); + in.seekg(startPos); // Reset stream position + return ""; // No match found +} + + + +auto Oasis::PALMTokenizer::lookahead() -> PALMToken +{ + if (!currentToken) { + currentToken = std::make_unique(nextToken()); + } + return *currentToken; +} + +auto Oasis::PALMTokenizer::match(const PALMToken& token) -> bool +{ + return match(token.text); +} + +auto Oasis::PALMTokenizer::match(const std::string& lexeme) -> bool +{ + if (lookahead().text == lexeme) { + currentToken = std::make_unique(nextToken()); + return true; + } + return false; +} + +auto Oasis::PALMTokenizer::eof() const -> bool +{ + return inputStream.eof(); +} + + diff --git a/io/src/PALMTokenizer.hpp b/io/src/PALMTokenizer.hpp new file mode 100644 index 00000000..ebb82247 --- /dev/null +++ b/io/src/PALMTokenizer.hpp @@ -0,0 +1,45 @@ +// +// Created by codin on 11/9/25. +// + +#ifndef OASIS_PALMTOKENIZER_HPP +#define OASIS_PALMTOKENIZER_HPP + +#include "PALMSpec.hpp" + +#include + +namespace Oasis { + +/** A tokenizer for the PALM language. + * + * This class reads from an input stream and produces PALM tokens. + */ +class PALMTokenizer { +public: + explicit PALMTokenizer(std::istream& inputStream) + : inputStream(inputStream) + , currentToken(nullptr) + { + } + + auto lookahead() -> PALMToken; + auto match(const PALMToken& token) -> bool; + auto match(const std::string& lexeme) -> bool; + + auto eof() const -> bool; + +private: + std::istream& inputStream; + std::unique_ptr currentToken; + + static std::unordered_set buildPunctuatorPrefixes(); + static const std::unordered_set& punctuatorPrefixes(); + + auto nextToken() const -> PALMToken; + + static auto tryMatchPunctuator(std::istream& in) -> std::string; +}; + +} +#endif // OASIS_PALMTOKENIZER_HPP diff --git a/io/src/PALMTypes.hpp b/io/src/PALMTypes.hpp new file mode 100644 index 00000000..d8daaf9a --- /dev/null +++ b/io/src/PALMTypes.hpp @@ -0,0 +1,70 @@ +// +// Created by codin on 11/9/25. +// + +#ifndef OASIS_PALMTYPES_HPP +#define OASIS_PALMTYPES_HPP + +#include "Oasis/Expression.hpp" + +#include +#include +#include +#include +#include + +namespace Oasis { +/** Types of PALM tokens. */ +enum class PALMTokenType { + Operator, + Identifier, + Number, + Punctuator, + EndOfInput, + Unknown +}; + +/** Represents a token in the PALM language. */ +struct PALMToken { + PALMTokenType type; // type of token + std::string text; // raw lexeme + size_t offset = 0; // byte offset in source + size_t length = 0; // length in bytes + size_t line = 1; // optional + size_t column = 1; // optional + size_t tokenIndex = 0; // index in token stream +}; + +/** Operators are taken from ExpressionType enum in Expression.hpp + * @see ExpressionType + */ + +/** Types of PALM punctuators. */ +enum class PALMPunctuatorType { + StartExpression, + EndExpression +}; + +/** Bi-directional mapping between PALM operator tokens and their corresponding ExpressionType. */ +using PALMOperatorBimap = boost::bimaps::bimap< + boost::bimaps::unordered_multiset_of, + boost::bimaps::unordered_set_of>; + +/** Bi-directional mapping between PALM punctuator tokens and their corresponding PALMPunctuatorType. */ +using PALMPunctuatorBimap = boost::bimaps::bimap; + + + + + + + +/** Checks if a character is a PALM operator. + * + * @param ch The character to check. + * @return True if the character is a PALM operator, false otherwise. + */ +auto isPALMOperator(char ch) -> bool; +} + +#endif // OASIS_PALMTYPES_HPP diff --git a/io/tests/PALMTests.cpp b/io/tests/PALMTests.cpp index e5e34baf..6b809dc8 100644 --- a/io/tests/PALMTests.cpp +++ b/io/tests/PALMTests.cpp @@ -778,21 +778,65 @@ TEST_CASE("PALM Parsing for Real with various formats", "[PALM][Parser][Real]") const auto real = Oasis::RecursiveCast(**expr); REQUIRE(real->GetValue() == -0.001); } + + { // Test with scientific notation + const auto expr = Oasis::FromPALM("( real 1.5e3 )"); + + REQUIRE(expr.has_value()); + REQUIRE((*expr)->Is()); + + const auto real = Oasis::RecursiveCast(**expr); + REQUIRE(real->GetValue() == 1500.0); + } } +TEST_CASE("PALM Parsing for Malformed Real", "[PALM][Parser][Real]") +{ + { // Missing value + // const auto expr = Oasis::FromPALM("( real )"); + // const auto expectedError = Oasis::ParseError { + // .type = Oasis::ParseErrorType::TooFewOperands, + // .token_index = 4, + // .char_offset = 7, + // .got = "", + // .expected = "real number", + // .message = "Expected a real number after 'real' token." + // }; + // + // REQUIRE(!expr); + // REQUIRE(expr.error() == expectedError); + } + + { // Invalid format + const auto expr = Oasis::FromPALM("( real three.point.onefour )"); + + REQUIRE(!expr); + REQUIRE(expr.error() == Oasis::ParseError::InvalidNumberFormat); + } + + { // Extra tokens + const auto expr = Oasis::FromPALM("( real 3.14 extra )"); + + REQUIRE(!expr); + REQUIRE(expr.error() == Oasis::ParseError::UnexpectedToken); + } + + +} + +//TODO: Remove +TEST_CASE("TEMP RUN PALM") +{ + const std::string palmString = "(+(rea%l 5) (* (var x ) \n ( ^ ( real 2 ) ( real 3 ) ) ) )"; + + const auto expr = Oasis::FromPALMNew(palmString); +} + + /* * Real Operation */ -TEST_CASE("Parse Real", "[FromPALM][Parsing]") -{ - const auto expr = Oasis::FromPALM("( real 5 )"); - REQUIRE(expr.has_value()); - REQUIRE((*expr)->Is()); - - const auto real = Oasis::RecursiveCast(**expr); - REQUIRE(real->GetValue() == 5.0); -} TEST_CASE("Parse Multiple Real", "[FromPALM][Parsing]") { From bfa060e6f4685c1fe5644fa6cc4316c4c5ce5710 Mon Sep 17 00:00:00 2001 From: codin Date: Sun, 9 Nov 2025 22:12:00 -0500 Subject: [PATCH 21/30] Refactored PalmSerializer to use PALMSpec --- io/CMakeLists.txt | 1 - io/include/Oasis/PALMSerializer.hpp | 118 +++++------------------- io/src/PALMHelper.cpp | 25 ------ io/src/PALMHelper.hpp | 91 ------------------- io/src/PALMSerializer.cpp | 135 ++++++++++++++++++++++++++++ io/src/PALMTokenizer.cpp | 1 + io/src/PALMTokenizer.hpp | 2 +- io/src/PALMTypes.hpp | 10 --- io/tests/PALMTests.cpp | 4 +- 9 files changed, 163 insertions(+), 224 deletions(-) delete mode 100644 io/src/PALMHelper.cpp diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 36787b8f..ffa71a98 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -4,7 +4,6 @@ set(OASIS_IO_SOURCES src/FromString.cpp src/InFixSerializer.cpp src/MathMLSerializer.cpp - src/PALMHelper.cpp src/PALMSerializer.cpp src/PALMTokenizer.cpp src/TeXSerializer.cpp) diff --git a/io/include/Oasis/PALMSerializer.hpp b/io/include/Oasis/PALMSerializer.hpp index da091d2c..71833dbb 100644 --- a/io/include/Oasis/PALMSerializer.hpp +++ b/io/include/Oasis/PALMSerializer.hpp @@ -5,28 +5,41 @@ #ifndef OASIS_PALMSERIALIZER_HPP #define OASIS_PALMSERIALIZER_HPP -#include "../../src/PALMHelper.hpp" -#include "Oasis/Add.hpp" #include -#include #include #include #include "Oasis/Visit.hpp" +#include "../../src/PALMTypes.hpp" namespace Oasis { +/** Options for PALM serialization. */ +struct PALMSerializationOpts { + enum class ImgSymType { + I, + J + } imaginarySymbol + = ImgSymType::I; + + uint8_t numPlaces = 5; + + std::string tokenSeparator = " "; + + std::string expressionPadding = " "; +}; + inline std::string_view PALM_UNEXPECTED_NO_MOST_SIG_OP = "Expression missing most significant operand"; inline std::string_view PALM_UNEXPECTED_NO_LEAST_SIG_OP = "Expression missing least significant operand"; class PALMSerializer final : public TypedVisitor> { public: PALMSerializer() - : PALMSerializer(PALMOpts {}) + : PALMSerializer(PALMSerializationOpts {}) { } - explicit PALMSerializer(PALMOpts options) + explicit PALMSerializer(PALMSerializationOpts options) : palmOptions(std::move(options)) { } @@ -51,99 +64,16 @@ class PALMSerializer final : public TypedVisitor) - * @param expressionType The type of the expression. - * @param operands The serialized operands of the expression. - * @return The serialized expression as a string, or an error if serialization fails. - */ - auto SerializeExpression(ExpressionType expressionType, const std::vector& operands = {}) const -> RetT - { - // Build the serialized string - std::ostringstream serialized; - - // Start Expression - serialized << PALMDelimiterToToken(START_EXPRESSION, palmOptions) << PALMDelimiterToToken(EXPRESSION_PADDING, palmOptions); - - // Add Operator - serialized << PALMExpressionToToken(expressionType, palmOptions); + PALMSerializationOpts palmOptions {}; - // Add Operands - for (const auto& operand : operands) { - // Check for errors in operand serialization - auto value = operand; - if (!value) { - return std::unexpected { value.error() }; - } + static inline auto OperatorToToken(ExpressionType op, const PALMSerializationOpts& options) -> std::string_view; + static inline auto PunctuatorToToken(PALMPunctuatorType punctuator, const PALMSerializationOpts& options) -> std::string_view; - // Add Separator and Operand Value - serialized << PALMDelimiterToToken(TOKEN_SEPARATOR, palmOptions) << value.value(); - } - // End Expression - serialized << PALMDelimiterToToken(EXPRESSION_PADDING, palmOptions) << PALMDelimiterToToken(END_EXPRESSION, palmOptions); - - // Return the serialized string - return serialized.str(); - } - - /** Serialize a unary expression. - * - * @tparam RetT The return type of the serialization (usually std::expected) - * @param expr The unary expression to serialize. - * @return The serialized expression as a string, or an error if serialization fails. - */ - auto SerializeUnaryExpression(const DerivedFromUnaryExpression auto& expr) -> RetT - { - // Ensure the operand exists - if (!expr.HasOperand()) { - return std::unexpected { std::string(PALM_UNEXPECTED_NO_MOST_SIG_OP) }; - } - - // Serialize the operand - auto operandResult = expr.GetOperand().Accept(*this); - if (!operandResult) { - return std::unexpected { operandResult.error() }; - } - - // Serialize the expression - return SerializeExpression(expr.GetType(), { operandResult }); - } - - /** Serialize a binary expression. - * - * @tparam RetT The return type of the serialization (usually std::expected) - * @param expr The binary expression to serialize. - * @return The serialized expression as a string, or an error if serialization fails. - */ - auto SerializeBinaryExpression(const DerivedFromBinaryExpression auto& expr) -> RetT - { - // Ensure both operands exist - if (!expr.HasMostSigOp()) { - return std::unexpected { std::string(PALM_UNEXPECTED_NO_MOST_SIG_OP) }; - } - if (!expr.HasLeastSigOp()) { - return std::unexpected { std::string(PALM_UNEXPECTED_NO_LEAST_SIG_OP) }; - } - - // Serialize both operands - auto mostSigOpResult = expr.GetMostSigOp().Accept(*this); - if (!mostSigOpResult) { - return std::unexpected { mostSigOpResult.error() }; - } - - auto leastSigOpResult = expr.GetLeastSigOp().Accept(*this); - if (!leastSigOpResult) { - return std::unexpected { leastSigOpResult.error() }; - } - - // Serialize the expression - return SerializeExpression(expr.GetType(), { mostSigOpResult, leastSigOpResult }); - } + auto SerializeExpression(ExpressionType expressionType, const std::vector& operands = {}) const -> RetT; + auto SerializeUnaryExpression(const DerivedFromUnaryExpression auto& expr) -> RetT; + auto SerializeBinaryExpression(const DerivedFromBinaryExpression auto& expr) -> RetT; }; } diff --git a/io/src/PALMHelper.cpp b/io/src/PALMHelper.cpp deleted file mode 100644 index 7ef718ec..00000000 --- a/io/src/PALMHelper.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// -// Created by codin on 11/8/25. -// - -#include "PALMHelper.hpp" - -namespace Oasis { - -auto PALMExpressionToToken(const ExpressionType type, const PALMOpts& options) -> std::string_view -{ - // Determine if there are multiple tokens for the type - switch (type) { - case ExpressionType::Imaginary: - return tokenBimap.right.at(options.imaginarySymbol); - default: - return tokenBimap.right.at(type); - } -} - -auto PALMDelimiterToToken(const PALMDelimiterType type, const PALMOpts& /*options*/) -> std::string_view -{ - return tokenBimap.right.at(type); -} - -} diff --git a/io/src/PALMHelper.hpp b/io/src/PALMHelper.hpp index eb67e867..35dbc3dd 100644 --- a/io/src/PALMHelper.hpp +++ b/io/src/PALMHelper.hpp @@ -7,101 +7,10 @@ #include #include -#include - -#include -#include -#include -#include - -#include "Oasis/Expression.hpp" namespace Oasis { -/** Options for PALM serialization. */ -struct PALMOpts { - enum class ImgSymType { - I, - J - } imaginarySymbol - = ImgSymType::I; - - uint8_t numPlaces = 5; - - enum class Separator { - SPACE - } separator - = Separator::SPACE; -}; - -/** Operators are taken from ExpressionType enum in Expression.hpp - * @see ExpressionType - */ - -/** Types of delimiters used in PALM serialization. */ -enum PALMDelimiterType { - START_EXPRESSION, - END_EXPRESSION, - TOKEN_SEPARATOR, - EXPRESSION_PADDING -}; - -/** Bi-directional mapping between PALM operator tokens and their corresponding ExpressionType, PALMDelimiterType, or variant type. */ -using PALMTokenBimap = boost::bimaps::bimap< - boost::bimaps::unordered_multiset_of, - boost::bimaps::unordered_set_of>>; - -/** A mapping from PALM tokens to con */ -const PALMTokenBimap tokenBimap = ::boost::assign::list_of - // Operators - ("real", ExpressionType::Real) - ("i", ExpressionType::Imaginary) - ("i", PALMOpts::ImgSymType::I) - ("j", ExpressionType::Imaginary) - ("j", PALMOpts::ImgSymType::J) - ("var", ExpressionType::Variable) - ("+", ExpressionType::Add) - ("-", ExpressionType::Subtract) - ("*", ExpressionType::Multiply) - ("/", ExpressionType::Divide) - ("^", ExpressionType::Exponent) - ("log", ExpressionType::Log) - ("int", ExpressionType::Integral) - ("d", ExpressionType::Derivative) - ("neg", ExpressionType::Negate) - ("matrix", ExpressionType::Matrix) - ("pi", ExpressionType::Pi) - ("e", ExpressionType::EulerNumber) - ("magnitude", ExpressionType::Magnitude) - // Delimiters - ("(", START_EXPRESSION) - (")", END_EXPRESSION) - (" ", TOKEN_SEPARATOR) - (" ", EXPRESSION_PADDING); - - -/** Convert an ExpressionType to its corresponding PALM token. - * - * @param type The ExpressionType to convert. - * @param options The PALM options to consider during conversion. - * @return The corresponding PALM token as a string view. - */ -auto PALMExpressionToToken(ExpressionType type, const PALMOpts& options) -> std::string_view; - -/** Convert a PALMDelimiterType to its corresponding PALM token. - * - * @param type The PALMDelimiterType to convert. - * @param options The PALM options to consider during conversion. - * @return The corresponding PALM token as a string view. - */ -auto PALMDelimiterToToken(PALMDelimiterType type, const PALMOpts& options) -> std::string_view; - - // Define PALM tokens constexpr inline std::string_view PALM_OPEN_PARENS = "("; constexpr inline std::string_view PALM_CLOSE_PARENS = ")"; diff --git a/io/src/PALMSerializer.cpp b/io/src/PALMSerializer.cpp index 584faa74..a60a8b67 100644 --- a/io/src/PALMSerializer.cpp +++ b/io/src/PALMSerializer.cpp @@ -4,6 +4,7 @@ #include +#include "PALMSpec.hpp" #include "Oasis/PALMSerializer.hpp" #include "Oasis/Add.hpp" @@ -108,4 +109,138 @@ auto PALMSerializer::TypedVisit(const Magnitude& magnitude) -> RetT return SerializeUnaryExpression(magnitude); } +/** Convert an ExpressionType (Operator) to its corresponding PALM token. + * + * @param op The ExpressionType (Operator) to convert. + * @param options The PALM options to consider during conversion. + * @return The corresponding PALM token as a string view. + */ +auto PALMSerializer::OperatorToToken(const ExpressionType op, const PALMSerializationOpts& options) -> std::string_view +{ + switch (op) { + case ExpressionType::Imaginary: + switch (options.imaginarySymbol) { + case PALMSerializationOpts::ImgSymType::J: + return "j"; + case PALMSerializationOpts::ImgSymType::I: + default: + return "i"; + } + + default: + return kPALMOperatorBimap.right.at(op); + } +} + +/** Convert a PALMPunctuatorType to its corresponding PALM token. + * + * @param punctuator The PALMDelimiterType to convert. + + * @return The corresponding PALM token as a string view. + */ +auto PALMSerializer::PunctuatorToToken(const PALMPunctuatorType punctuator, const PALMSerializationOpts& /*options*/) -> std::string_view +{ + return kPALMPunctuatorBimap.right.at(punctuator); +} + +/** Serialize a generic expression. + * + * @tparam RetT The return type of the serialization (usually std::expected) + * @param expressionType The type of the expression. + * @param operands The serialized operands of the expression. + * @return The serialized expression as a string, or an error if serialization fails. + */ +auto PALMSerializer::SerializeExpression(ExpressionType expressionType, const std::vector& operands) const -> RetT +{ + // Build the serialized string + std::ostringstream serialized; + + // Start Expression + serialized + << PunctuatorToToken(PALMPunctuatorType::StartExpression, palmOptions) + << palmOptions.expressionPadding + + + // Add Operator + << OperatorToToken(expressionType, palmOptions); + + // Add Operands + for (const auto& operand : operands) { + // Check for errors in operand serialization + auto value = operand; + + if (!value) { + return std::unexpected { value.error() }; + } + + // Add Separator and Operand Value + serialized + << palmOptions.tokenSeparator + << value.value(); + } + + // End Expression + serialized + << palmOptions.expressionPadding + << PunctuatorToToken(PALMPunctuatorType::EndExpression, palmOptions); + + // Return the serialized string + return serialized.str(); +} + + +/** Serialize a unary expression. + * + * @tparam RetT The return type of the serialization (usually std::expected) + * @param expr The unary expression to serialize. + * @return The serialized expression as a string, or an error if serialization fails. + */ +auto PALMSerializer::SerializeUnaryExpression(const DerivedFromUnaryExpression auto& expr) -> RetT +{ + // Ensure the operand exists + if (!expr.HasOperand()) { + return std::unexpected { std::string(PALM_UNEXPECTED_NO_MOST_SIG_OP) }; + } + + // Serialize the operand + auto operandResult = expr.GetOperand().Accept(*this); + if (!operandResult) { + return std::unexpected { operandResult.error() }; + } + + // Serialize the expression + return SerializeExpression(expr.GetType(), { operandResult }); +} + +/** Serialize a binary expression. + * + * @tparam RetT The return type of the serialization (usually std::expected) + * @param expr The binary expression to serialize. + * @return The serialized expression as a string, or an error if serialization fails. + */ +auto PALMSerializer::SerializeBinaryExpression(const DerivedFromBinaryExpression auto& expr) -> RetT +{ + // Ensure both operands exist + if (!expr.HasMostSigOp()) { + return std::unexpected { std::string(PALM_UNEXPECTED_NO_MOST_SIG_OP) }; + } + if (!expr.HasLeastSigOp()) { + return std::unexpected { std::string(PALM_UNEXPECTED_NO_LEAST_SIG_OP) }; + } + + // Serialize both operands + auto mostSigOpResult = expr.GetMostSigOp().Accept(*this); + if (!mostSigOpResult) { + return std::unexpected { mostSigOpResult.error() }; + } + + auto leastSigOpResult = expr.GetLeastSigOp().Accept(*this); + if (!leastSigOpResult) { + return std::unexpected { leastSigOpResult.error() }; + } + + // Serialize the expression + return SerializeExpression(expr.GetType(), { mostSigOpResult, leastSigOpResult }); +} + } \ No newline at end of file diff --git a/io/src/PALMTokenizer.cpp b/io/src/PALMTokenizer.cpp index d473b01c..fbe4885b 100644 --- a/io/src/PALMTokenizer.cpp +++ b/io/src/PALMTokenizer.cpp @@ -3,6 +3,7 @@ // #include "PALMTokenizer.hpp" +#include "PALMSpec.hpp" /** Builds a set of all prefixes of PALM punctuators. * diff --git a/io/src/PALMTokenizer.hpp b/io/src/PALMTokenizer.hpp index ebb82247..4540f825 100644 --- a/io/src/PALMTokenizer.hpp +++ b/io/src/PALMTokenizer.hpp @@ -5,7 +5,7 @@ #ifndef OASIS_PALMTOKENIZER_HPP #define OASIS_PALMTOKENIZER_HPP -#include "PALMSpec.hpp" +#include "PALMTypes.hpp" #include diff --git a/io/src/PALMTypes.hpp b/io/src/PALMTypes.hpp index d8daaf9a..717784f9 100644 --- a/io/src/PALMTypes.hpp +++ b/io/src/PALMTypes.hpp @@ -55,16 +55,6 @@ using PALMPunctuatorBimap = boost::bimaps::bimap bool; } #endif // OASIS_PALMTYPES_HPP diff --git a/io/tests/PALMTests.cpp b/io/tests/PALMTests.cpp index 6b809dc8..a3222ac6 100644 --- a/io/tests/PALMTests.cpp +++ b/io/tests/PALMTests.cpp @@ -82,7 +82,7 @@ TEST_CASE("PALM Serialization for Imaginary with different characters", "[PALM][ const Oasis::Imaginary imaginary {}; { // Test with default character 'i' - Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMOpts::ImgSymType::I } }; + Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMSerializationOpts::ImgSymType::I } }; auto result = imaginary.Accept(serializer).value(); std::string expected = "( i )"; @@ -91,7 +91,7 @@ TEST_CASE("PALM Serialization for Imaginary with different characters", "[PALM][ } { // Test with character 'j' - Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMOpts::ImgSymType::J } }; + Oasis::PALMSerializer serializer { { .imaginarySymbol = Oasis::PALMSerializationOpts::ImgSymType::J } }; auto result = imaginary.Accept(serializer).value(); std::string expected = "( j )"; From e5e0e5e7cbc209d152d72729a766ebb91e38eac5 Mon Sep 17 00:00:00 2001 From: codin Date: Sun, 9 Nov 2025 23:26:01 -0500 Subject: [PATCH 22/30] Refactored PalmSerializer to SerializeExpression by the parameter type (LeafExpression, BinaryExpression, UnaryExpression). --- include/Oasis/Concepts.hpp | 6 + io/include/Oasis/PALMSerializer.hpp | 8 +- io/src/PALMSerializer.cpp | 64 ++++--- io/tests/PALMTests.cpp | 248 ++++++++++++++++++++-------- 4 files changed, 227 insertions(+), 99 deletions(-) diff --git a/include/Oasis/Concepts.hpp b/include/Oasis/Concepts.hpp index 94500df4..a5be810c 100644 --- a/include/Oasis/Concepts.hpp +++ b/include/Oasis/Concepts.hpp @@ -39,6 +39,9 @@ class BinaryExpression; template