From baebb32bacf20bea0eac71c0ea28d5cc4112cb52 Mon Sep 17 00:00:00 2001 From: Ljudevit Bracko Date: Fri, 16 Nov 2018 13:29:10 +0100 Subject: [PATCH 1/5] Initial commit - blocks with unit tests --- MathLab/MathLab.cpp | Bin 0 -> 154 bytes MathLab/MathLab.sln | 41 +++++ MathLab/MathLab.vcxproj | 171 +++++++++++++++++++++ MathLab/MathLab.vcxproj.filters | 30 ++++ MathLab/blocks.cpp | 1 + MathLab/blocks.h | 45 ++++++ MathLabTests/MathLabTests.vcxproj | 173 ++++++++++++++++++++++ MathLabTests/MathLabTests.vcxproj.filters | 33 +++++ MathLabTests/blockTests.cpp | 72 +++++++++ MathLabTests/targetver.h | 8 + 10 files changed, 574 insertions(+) create mode 100644 MathLab/MathLab.cpp create mode 100644 MathLab/MathLab.sln create mode 100644 MathLab/MathLab.vcxproj create mode 100644 MathLab/MathLab.vcxproj.filters create mode 100644 MathLab/blocks.cpp create mode 100644 MathLab/blocks.h create mode 100644 MathLabTests/MathLabTests.vcxproj create mode 100644 MathLabTests/MathLabTests.vcxproj.filters create mode 100644 MathLabTests/blockTests.cpp create mode 100644 MathLabTests/targetver.h diff --git a/MathLab/MathLab.cpp b/MathLab/MathLab.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c798b1e47ad0a2e81cf1a1a6112cbde690628d7c GIT binary patch literal 154 zcmW-au@1s83`F0I#6N_vz%R5BGk?I+rAio(l12*cg!uKsIXc<0&v)nFe&)qOB(srt z;s|0YZ&sT2nZk`DhhC;#r77ZVzHsgQr+24|!Muq + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {3314C41D-F478-4CB2-A408-F0B113C7AF2C} + Win32Proj + MathLab + 10.0.17134.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + NotUsing + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + stdcpp17 + + + Console + true + + + + + NotUsing + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + stdcpp17 + + + Console + true + + + + + NotUsing + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + stdcpp17 + + + Console + true + true + true + + + + + NotUsing + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + stdcpp17 + + + Console + true + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/MathLab/MathLab.vcxproj.filters b/MathLab/MathLab.vcxproj.filters new file mode 100644 index 0000000..4e65b9a --- /dev/null +++ b/MathLab/MathLab.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/MathLab/blocks.cpp b/MathLab/blocks.cpp new file mode 100644 index 0000000..4879e50 --- /dev/null +++ b/MathLab/blocks.cpp @@ -0,0 +1 @@ +#include "blocks.h" \ No newline at end of file diff --git a/MathLab/blocks.h b/MathLab/blocks.h new file mode 100644 index 0000000..cfe9090 --- /dev/null +++ b/MathLab/blocks.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include + +namespace mathlab +{ + +class block { + std::function fn_; +public: + block(std::function fn) : fn_(fn) { } + double eval(double input) { return fn_(input); } +}; + +class identity : public block { +public: + identity() : block([](double input) {return input; }) {} +}; + +class addition : public block { +public: + addition(double constant) : block([constant](double input) {return input + constant; }) {} +}; + +class multiplication : public block { +public: + multiplication(double constant) : block([constant](double input) {return input * constant; }) {} +}; + +class power : public block { +public: + power(double constant) : block([constant](double input) {return std::pow(input, constant); }) {} +}; + +class condition : public block { +public: + condition(double constant) : block([constant](double input) {return input < constant ? -1 : (input == constant ? 0 : 1); }) {} +}; + +class limit : public block { +public: + limit(double lower, double upper) : block([lower, upper](double input) {return std::clamp(input, lower, upper); }) {} +}; + +} \ No newline at end of file diff --git a/MathLabTests/MathLabTests.vcxproj b/MathLabTests/MathLabTests.vcxproj new file mode 100644 index 0000000..63e6b90 --- /dev/null +++ b/MathLabTests/MathLabTests.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {5DBEC0A4-DD81-4B5D-81E7-22940933E8F3} + Win32Proj + MathLabTests + 10.0.17134.0 + NativeUnitTestProject + + + + DynamicLibrary + true + v141 + Unicode + false + + + DynamicLibrary + false + v141 + true + Unicode + false + + + DynamicLibrary + true + v141 + Unicode + false + + + DynamicLibrary + false + v141 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + true + + + true + + + + NotUsing + Level3 + Disabled + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;%(PreprocessorDefinitions) + true + + stdcpp17 + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + Disabled + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + _DEBUG;%(PreprocessorDefinitions) + true + + stdcpp17 + + + Windows + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + NotUsing + MaxSpeed + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;%(PreprocessorDefinitions) + true + + stdcpp17 + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + NotUsing + MaxSpeed + true + true + $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + true + + stdcpp17 + + + Windows + true + true + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MathLabTests/MathLabTests.vcxproj.filters b/MathLabTests/MathLabTests.vcxproj.filters new file mode 100644 index 0000000..b70831b --- /dev/null +++ b/MathLabTests/MathLabTests.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/MathLabTests/blockTests.cpp b/MathLabTests/blockTests.cpp new file mode 100644 index 0000000..f5153bf --- /dev/null +++ b/MathLabTests/blockTests.cpp @@ -0,0 +1,72 @@ +#include "CppUnitTest.h" +#include "../MathLab/blocks.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace MathLabTests +{ + TEST_CLASS(block_types) + { + public: + + TEST_METHOD(identity_returns_input) + { + mathlab::block identity = mathlab::identity(); + Assert::AreEqual(3.14, identity.eval(3.14)); + } + + TEST_METHOD(addition_works) + { + mathlab::block block = mathlab::addition(100.0); + Assert::AreEqual(103.14, block.eval(3.14)); + } + + TEST_METHOD(multiplication_works) + { + mathlab::block block = mathlab::multiplication(100.0); + Assert::AreEqual(314., block.eval(3.14)); + } + + TEST_METHOD(power_works) + { + mathlab::block block = mathlab::power(3.); + Assert::AreEqual(15.625, block.eval(2.5)); + } + + TEST_METHOD(condition_returns_0_if_same_as_input) + { + mathlab::block block = mathlab::condition(3.14); + Assert::AreEqual(0., block.eval(3.14)); + } + + TEST_METHOD(condition_returns_minus_1_if_less_than_input) + { + mathlab::block block = mathlab::condition(3.14); + Assert::AreEqual(-1., block.eval(0.5)); + } + + TEST_METHOD(condition_returns_1_if_greater_than_input) + { + mathlab::block block = mathlab::condition(3.14); + Assert::AreEqual(1., block.eval(5.)); + } + + TEST_METHOD(limit_returns_value_if_within_limits) + { + mathlab::block block = mathlab::limit(1., 100.); + Assert::AreEqual(50., block.eval(50.)); + } + + TEST_METHOD(limit_returns_lower_if_value_is_lower) + { + mathlab::block block = mathlab::limit(1., 100.); + Assert::AreEqual(1., block.eval(-50.)); + } + + TEST_METHOD(limit_returns_upper_if_value_is_above) + { + mathlab::block block = mathlab::limit(1., 100.); + Assert::AreEqual(100., block.eval(200.)); + } + }; +} \ No newline at end of file diff --git a/MathLabTests/targetver.h b/MathLabTests/targetver.h new file mode 100644 index 0000000..90e767b --- /dev/null +++ b/MathLabTests/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include From 28e225f1ba66a881c0b8dd9e8f78b81e89f7aeab Mon Sep 17 00:00:00 2001 From: Ljudevit Bracko Date: Thu, 22 Nov 2018 13:04:32 +0100 Subject: [PATCH 2/5] Refactored & completed Missing: Automatic lock registration --- MathLab/MathLab.cpp | Bin 154 -> 12180 bytes MathLab/MathLab.vcxproj | 4 + MathLab/MathLab.vcxproj.filters | 12 ++ MathLab/block_sequence.cpp | 76 ++++++++++++ MathLab/block_sequence.h | 23 ++++ MathLab/blocks.cpp | 10 +- MathLab/blocks.h | 126 ++++++++++++------- MathLab/factory.cpp | 34 ++++++ MathLab/factory.h | 21 ++++ MathLabTests/MathLabTests.vcxproj | 6 + MathLabTests/MathLabTests.vcxproj.filters | 18 +++ MathLabTests/blockTests.cpp | 140 ++++++++++++++++++---- MathLabTests/block_sequenceTests.cpp | 102 ++++++++++++++++ MathLabTests/factoryTests.cpp | 48 ++++++++ 14 files changed, 558 insertions(+), 62 deletions(-) create mode 100644 MathLab/block_sequence.cpp create mode 100644 MathLab/block_sequence.h create mode 100644 MathLab/factory.cpp create mode 100644 MathLab/factory.h create mode 100644 MathLabTests/block_sequenceTests.cpp create mode 100644 MathLabTests/factoryTests.cpp diff --git a/MathLab/MathLab.cpp b/MathLab/MathLab.cpp index c798b1e47ad0a2e81cf1a1a6112cbde690628d7c..d6552daeb93e63cfd3ccaabcd398fe2e2f7c8e67 100644 GIT binary patch literal 12180 zcmeHN$!;4*5bbk-{KJ?51PX~6`)Vt?#(M5QM*E#On;y-%t z1iwe&dwg<(*9>|taOE?6rr+9qL~#Lh&wI(F&N=LD!sGbvIwEMGV~$r7<8=l6l;6hx zXo*?B9t=Yl0P4aQf z120GBiCJ1hc?_PDXM`~$Ry5fZtBoF%*;&StnAvmY_Nwb8}zQgtJ9~c)|)eK&hDSwU5zJPs`>q!PzVPX&+Nn4%>~? zY?FRMY|@F(;{@_~%T6Uzm#mw)w1Nl7yrm;Evg{emHrg(|JdJ2S+pUwA@+M$>ub_K# z-jU5iovE8$#PX|!R#y$8> z_T0)y5vlBrGy_c^FVQ^G&A|_0YbKBHbXDPF>21}OaaV+HBX=^Hic!`lQ}|36Bs(}6 z;0Zc7dBH07>y)<$9=Y=fJ$p9;tuxtvdUFnc21K%#$(&@St?qIA+^YG-cs(bYPG8IH z+C3%j%(=7qGp~XXSSNYs55Q?G&-OC78zFlc{pK=J`xTPK+3JMX_>44WpRV<%=mo;P zl-KKCuGWsf$P`E~^RO#>i-=szu}&d)C}fe}@Q?VQ}yF z4l_blEo7AQ$YTwYMTjx?nVVLU|0c~P#qTZE{Nbv_@Ol{&e8Uyu622zOl6A}B`NMS! z*O0VYcL`M{_u93KdK`+_ys$5O_`O9>_VZ<4)S@6+GXf&X>}kx*HR3u)HLek(P2B4k zRdXYiu2@CpzU)Zv)oitW!H>z0Wsx7LHXq$_oUWVunQHambA;E+2?@J5ci`60z}|X; zY)JXFUb(+3`?*dg?c?qdvU3ilqgz&bR#uxL<^@4VyjO9ibbB|~_aYp@cj+4BC2aS; z@YO79V!4N zp59;$&iTHiRMi6P&L})Q%g%u;H%6}Xhv~`p4hrBSeMD($2I>5U2}w7 z>Xt~%vOc%ozE37khf|qd=es6TcL9tkrCu<^Jd@4kr=FdlPW3#Q{>e{iJ-4J5?nW{i zyEB^;>kyPV#UFwpro7zPrr14Mf?!C*@~Tf6tZC&CxNYwq{TBr0lDo z0^`kI5B|OQ+P(O6)|6}3&k^Gmy)dtjJj#YpPfa~D96sv42kx4v^;K4i$5^sSDtA72 z^=?~ZnFN_@GUGT^WktnPD(+h4aWik_C!oZtyAsC1yH`na5~GY!Iq=7>*t+-ywe4f& zqrzHS0UXZA(>T;|O`y^CL~Ipmak5j1G7=IY^<2t$O6FiEMP*dUBXaq|Bs705qM9MH z($W9=<%>;b%RlCgf9}e@(cIP~~QgPN0lq)8cKu<9~ z!uq)1e@J!sNtJuo*u)rJOLzLbHclAhNh+&fW|X~ch;ypXl@Vlu-V?UlG+aECZPtDN zvAZ6-a`;E&JI~v-9pNsRE9AahH7ffB* zs`*Kce5rrAci63W_bP;P%(}c!&?oh&sZVdvF6y@YVTX`YVq3GOr{KGv@l>C8TclSQ zZtY?g6{%sXWJ=?cOYGTvpHEinz3GZXI&j(P)2nQ%(4Xbi>~4N7!xMb&NAko) zI#~C5#w2kc=#ZV&Q&fQ6&FESrv<4(cWP` td;oGjy&+SOJS}JKDU!pG5qF*uI$Q1O^YmOLzh2pm_TzFrsP;%z{{`M= + + + + diff --git a/MathLab/MathLab.vcxproj.filters b/MathLab/MathLab.vcxproj.filters index 4e65b9a..14dde3d 100644 --- a/MathLab/MathLab.vcxproj.filters +++ b/MathLab/MathLab.vcxproj.filters @@ -18,6 +18,12 @@ Header Files + + Header Files + + + Header Files + @@ -26,5 +32,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/MathLab/block_sequence.cpp b/MathLab/block_sequence.cpp new file mode 100644 index 0000000..e03f9d9 --- /dev/null +++ b/MathLab/block_sequence.cpp @@ -0,0 +1,76 @@ +#include "block_sequence.h" +#include + +namespace { + std::pair extract_type(const std::string& line) + { + std::string block_type; + std::istringstream line_stream(line); + line_stream >> block_type; + return std::make_pair(std::move(block_type), std::move(line_stream)); + } +} + +namespace mathlab { + + block_sequence::block_sequence(factory& factory) : factory_(factory) {} + + std::string block_sequence::append_from(std::istream& input_stream) { + std::string block_type; + std::istringstream constants; + std::string invalid_lines; + std::string line; + while (!input_stream.eof()) { + std::getline(input_stream, line); + std::tie(block_type, constants) = extract_type(line); + if (!block_type.empty()) { + std::unique_ptr block; + try { + block = factory_.create(block_type, constants); + } + catch (std::invalid_argument&) { + // Deliberately empty - handle same as unknown command + } + if (block != nullptr) + blocks_.emplace_back(std::move(block)); + else + invalid_lines += line + "\n"; + } + } + return invalid_lines; + } + + std::string block_sequence::load_from(std::istream& input_stream) { + blocks_.clear(); + return append_from(input_stream); + } + + std::ostream& block_sequence::dump(std::ostream& to_stream, bool with_line_numbers) const { + int position = 1; + for (auto& block : blocks_) { + if (with_line_numbers) + to_stream << position++ << ": "; + to_stream << *block; + } + return to_stream; + } + + double block_sequence::eval(double input) const { + return std::accumulate( + blocks_.cbegin(), + blocks_.cend(), + input, + [](double val, const std::unique_ptr &block) { return block->eval(val); }); + } + + void block_sequence::remove_at(unsigned index) { + if (index < blocks_.size()) + blocks_.erase(blocks_.begin() + index); + } + + void block_sequence::move_to_beginning(unsigned index) { + if (index < blocks_.size() && index > 0) { + std::swap(blocks_[0], blocks_[index]); + } + } +} diff --git a/MathLab/block_sequence.h b/MathLab/block_sequence.h new file mode 100644 index 0000000..d7b6dd8 --- /dev/null +++ b/MathLab/block_sequence.h @@ -0,0 +1,23 @@ +#pragma once +#include "factory.h" +#include +#include + +namespace mathlab { + class block_sequence { + std::vector> blocks_; + factory& factory_; + public: + block_sequence(factory& factory); + // Appends one or more block from supplied stream + // Returns invalid lines + std::string append_from(std::istream& input_stream); + // Load sequence from supplied stream + // Returns invalid lines + std::string load_from(std::istream& input_stream); + std::ostream& dump(std::ostream& to_stream, bool with_line_numbers) const; + double eval(double input) const; + void remove_at(unsigned index); + void move_to_beginning(unsigned index); + }; +} diff --git a/MathLab/blocks.cpp b/MathLab/blocks.cpp index 4879e50..46892ad 100644 --- a/MathLab/blocks.cpp +++ b/MathLab/blocks.cpp @@ -1 +1,9 @@ -#include "blocks.h" \ No newline at end of file +#include "blocks.h" + +namespace mathlab +{ + std::ostream& operator<<(std::ostream& stream, const block& block) { + block.dump(stream); + return stream; + } +} \ No newline at end of file diff --git a/MathLab/blocks.h b/MathLab/blocks.h index cfe9090..24c27b0 100644 --- a/MathLab/blocks.h +++ b/MathLab/blocks.h @@ -1,45 +1,91 @@ #pragma once -#include -#include +#include +#include +#include namespace mathlab { + // block interface + class block { + public: + virtual double eval(double input) const = 0; + virtual void dump(std::ostream& to_stream) const = 0; + virtual ~block() = default; + }; -class block { - std::function fn_; -public: - block(std::function fn) : fn_(fn) { } - double eval(double input) { return fn_(input); } -}; - -class identity : public block { -public: - identity() : block([](double input) {return input; }) {} -}; - -class addition : public block { -public: - addition(double constant) : block([constant](double input) {return input + constant; }) {} -}; - -class multiplication : public block { -public: - multiplication(double constant) : block([constant](double input) {return input * constant; }) {} -}; - -class power : public block { -public: - power(double constant) : block([constant](double input) {return std::pow(input, constant); }) {} -}; - -class condition : public block { -public: - condition(double constant) : block([constant](double input) {return input < constant ? -1 : (input == constant ? 0 : 1); }) {} -}; - -class limit : public block { -public: - limit(double lower, double upper) : block([lower, upper](double input) {return std::clamp(input, lower, upper); }) {} -}; - -} \ No newline at end of file + // represents block with N constants (inputs) that can be constructed from stream and be written to stream + template + class block_with_constants : public block { + protected: + std::array constants_; + std::string type_; + + // "It" represents iterator that contains (at least) N constants + // throws invalid_argument if iterator does not contain exactly N values of type double + template block_with_constants(const std::string& type, It begin, It end) : type_(type) { + // In case of istream_iterator, std::difference advances iterator => not able to do the check + auto index = 0U; + for (; begin != end; ++begin) { + if (index++ < N) + constants_[index - 1] = *begin; + else break; + } + if (index != N) + throw std::invalid_argument("Expected number of constants: " + std::to_string(N) + " but was: " + std::to_string(index)); + } + + // throws invalid_argument if initializer list does not contain exactly N values of type double + block_with_constants(const std::string& type, std::initializer_list list) : block_with_constants(type, list.begin(), list.end()) { } + + public: + // Each block outputs: "block_type[ constant1][ constant2][...constantN]\n" + void dump(std::ostream& stream) const override { + stream << type_; + std::for_each(constants_.begin(), constants_.end(), [&stream](double constant) { stream << ' ' << constant; }); + stream << std::endl; + } + }; + + std::ostream& operator<<(std::ostream& stream, const block& block); + + struct identity final : block_with_constants<0> { + inline static const char* my_type = "identity"; + identity() : block_with_constants(my_type, {}) {} + double eval(double input) const override { return input; } + }; + + struct addition final : block_with_constants<1> { + inline static const char* my_type = "addition"; + addition(double constant) : block_with_constants(my_type, { constant }) {} + addition(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} + double eval(double input) const override { return input + constants_[0]; } + }; + + struct multiplication final : block_with_constants<1> { + inline static const char* my_type = "multiplication"; + multiplication(double constant) : block_with_constants(my_type, { constant }) {} + multiplication(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} + double eval(double input) const override { return input * constants_[0]; } + }; + + struct power final : block_with_constants<1> { + inline static const char* my_type = "power"; + power(double constant) : block_with_constants(my_type, { constant }) {} + power(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} + double eval(double input) const override { return std::pow(input, constants_[0]); } + }; + + struct condition final : block_with_constants<1> { + inline static const char* my_type = "condition"; + condition(double constant) : block_with_constants(my_type, { constant }) {} + condition(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} + double eval(double input) const override { return input < constants_[0] ? -1 : (input == constants_[0] ? 0 : 1); } + }; + + struct limit final : block_with_constants<2> { + inline static const char* my_type = "limit"; + limit(double lower, double upper) : block_with_constants(my_type, { lower, upper }) {} + limit(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} + double eval(double input) const override { return std::clamp(input, constants_[0], constants_[1]); } + }; +} diff --git a/MathLab/factory.cpp b/MathLab/factory.cpp new file mode 100644 index 0000000..aa43648 --- /dev/null +++ b/MathLab/factory.cpp @@ -0,0 +1,34 @@ +#include "factory.h" + +namespace mathlab +{ + void factory::register_block(const std::string& type, fn_type from_stream_creator) { + block_types_[type] = from_stream_creator; + } + + std::unique_ptr factory::create(const std::string& type, std::istream& input) + { + block* pblock = nullptr; + const auto found = block_types_.find(type); + if (found != block_types_.end()) + pblock = found->second(input); + return std::unique_ptr(pblock); + } + + std::ostream& factory::dump_registered(std::ostream& to_stream) + { + for (auto pair : block_types_) + to_stream << pair.first << ' '; + return to_stream; + } + + void register_all_blocks(factory& factory) + { + factory.register_block(mathlab::identity::my_type, [](std::istream&) {return new mathlab::identity(); }); + factory.register_block(mathlab::addition::my_type, [](std::istream& input) {return new mathlab::addition(input); }); + factory.register_block(mathlab::multiplication::my_type, [](std::istream& input) {return new mathlab::multiplication(input); }); + factory.register_block(mathlab::power::my_type, [](std::istream& input) {return new mathlab::power(input); }); + factory.register_block(mathlab::condition::my_type, [](std::istream& input) {return new mathlab::condition(input); }); + factory.register_block(mathlab::limit::my_type, [](std::istream& input) {return new mathlab::limit(input); }); + } +} diff --git a/MathLab/factory.h b/MathLab/factory.h new file mode 100644 index 0000000..4598a1a --- /dev/null +++ b/MathLab/factory.h @@ -0,0 +1,21 @@ +#pragma once +#include "blocks.h" +#include +#include +#include + +namespace mathlab +{ + struct factory final { + using fn_type = std::function; + void register_block(const std::string& type, fn_type from_stream_creator); + // Returns nullptr if block with given type is not registered + // Throws invalid_argument if block is known but input stream does not contain expected number of constants + std::unique_ptr create(const std::string& type, std::istream& input); + std::ostream& dump_registered(std::ostream &to_stream); + private: + std::map block_types_; + }; + + void register_all_blocks(factory& factory); +} \ No newline at end of file diff --git a/MathLabTests/MathLabTests.vcxproj b/MathLabTests/MathLabTests.vcxproj index 63e6b90..96b6ee8 100644 --- a/MathLabTests/MathLabTests.vcxproj +++ b/MathLabTests/MathLabTests.vcxproj @@ -161,11 +161,17 @@ + + + + + + diff --git a/MathLabTests/MathLabTests.vcxproj.filters b/MathLabTests/MathLabTests.vcxproj.filters index b70831b..8ef9a66 100644 --- a/MathLabTests/MathLabTests.vcxproj.filters +++ b/MathLabTests/MathLabTests.vcxproj.filters @@ -21,6 +21,12 @@ Header Files + + Header Files + + + Header Files + @@ -29,5 +35,17 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + \ No newline at end of file diff --git a/MathLabTests/blockTests.cpp b/MathLabTests/blockTests.cpp index f5153bf..f599b9a 100644 --- a/MathLabTests/blockTests.cpp +++ b/MathLabTests/blockTests.cpp @@ -1,72 +1,170 @@ #include "CppUnitTest.h" #include "../MathLab/blocks.h" +#include using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace MathLabTests { - TEST_CLASS(block_types) + TEST_CLASS(block_types_directly_constructed) { public: - TEST_METHOD(identity_returns_input) { - mathlab::block identity = mathlab::identity(); + auto identity = mathlab::identity(); Assert::AreEqual(3.14, identity.eval(3.14)); } TEST_METHOD(addition_works) { - mathlab::block block = mathlab::addition(100.0); - Assert::AreEqual(103.14, block.eval(3.14)); + auto addition = mathlab::addition(100.0); + Assert::AreEqual(103.14, addition.eval(3.14)); } TEST_METHOD(multiplication_works) { - mathlab::block block = mathlab::multiplication(100.0); - Assert::AreEqual(314., block.eval(3.14)); + auto multiplication = mathlab::multiplication(100.0); + Assert::AreEqual(314., multiplication.eval(3.14)); } TEST_METHOD(power_works) { - mathlab::block block = mathlab::power(3.); - Assert::AreEqual(15.625, block.eval(2.5)); + auto power = mathlab::power(3.); + Assert::AreEqual(15.625, power.eval(2.5)); } TEST_METHOD(condition_returns_0_if_same_as_input) { - mathlab::block block = mathlab::condition(3.14); - Assert::AreEqual(0., block.eval(3.14)); + auto condition = mathlab::condition(3.14); + Assert::AreEqual(0., condition.eval(3.14)); } TEST_METHOD(condition_returns_minus_1_if_less_than_input) { - mathlab::block block = mathlab::condition(3.14); - Assert::AreEqual(-1., block.eval(0.5)); + auto condition = mathlab::condition(3.14); + Assert::AreEqual(-1., condition.eval(0.5)); } TEST_METHOD(condition_returns_1_if_greater_than_input) { - mathlab::block block = mathlab::condition(3.14); - Assert::AreEqual(1., block.eval(5.)); + auto condition = mathlab::condition(3.14); + Assert::AreEqual(1., condition.eval(5.)); } TEST_METHOD(limit_returns_value_if_within_limits) { - mathlab::block block = mathlab::limit(1., 100.); - Assert::AreEqual(50., block.eval(50.)); + auto limit = mathlab::limit(1., 100.); + Assert::AreEqual(50., limit.eval(50.)); } TEST_METHOD(limit_returns_lower_if_value_is_lower) { - mathlab::block block = mathlab::limit(1., 100.); - Assert::AreEqual(1., block.eval(-50.)); + auto limit = mathlab::limit(1., 100.); + Assert::AreEqual(1., limit.eval(-50.)); } TEST_METHOD(limit_returns_upper_if_value_is_above) { - mathlab::block block = mathlab::limit(1., 100.); - Assert::AreEqual(100., block.eval(200.)); + auto limit = mathlab::limit(1., 100.); + Assert::AreEqual(100., limit.eval(200.)); } }; + + TEST_CLASS(blocks_can_be_created_from_stream_and_dumped_to_stream) + { + public: + TEST_METHOD(block_without_constants) + { + auto identity = mathlab::identity(); + std::ostringstream output_stream; + output_stream << identity; + Assert::AreEqual(std::string("identity\n"), output_stream.str()); + } + + TEST_METHOD(block_with_one_constant) + { + std::istringstream input("123.45"); + auto multiplication = mathlab::multiplication(input); + std::ostringstream output_stream; + output_stream << multiplication; + Assert::AreEqual(std::string("multiplication 123.45\n"), output_stream.str()); + } + + TEST_METHOD(block_with_one_constant_throws_if_no_input) + { + Assert::ExpectException([]() + { + std::istringstream input(""); + auto dummy = mathlab::multiplication(input); + }); + } + + TEST_METHOD(block_with_one_constant_throws_if_too_much_input) + { + Assert::ExpectException([]() + { + std::istringstream input("123. 456."); + auto dummy = mathlab::multiplication(input); + }); + } + + TEST_METHOD(block_with_one_constant_throws_in_case_of_wrong_input_type) + { + Assert::ExpectException([]() + { + std::istringstream input("Hello"); + auto dummy = mathlab::multiplication(input); + }); + } + + TEST_METHOD(block_with_two_constants) + { + std::istringstream input("-123.45 666.66"); + auto limit = mathlab::limit(input); + std::ostringstream output_stream; + output_stream << limit; + Assert::AreEqual(std::string("limit -123.45 666.66\n"), output_stream.str()); + } + + TEST_METHOD(block_with_two_constants_throws_if_no_input) + { + Assert::ExpectException([]() + { + std::istringstream input(""); + auto dummy = mathlab::limit(input); + }); + } + + TEST_METHOD(block_with_two_constants_throws_if_too_much_input) + { + Assert::ExpectException([]() + { + std::istringstream input("123. 456. 789."); + auto dummy = mathlab::limit(input); + }); + } + + TEST_METHOD(block_with_two_constants_throws_in_case_of_wrong_input_type) + { + Assert::ExpectException([]() + { + std::istringstream input("Hello World"); + auto dummy = mathlab::multiplication(input); + }); + } + }; + + TEST_CLASS(block_serialization) + { + public: + TEST_METHOD(identity_serialization) + { + /*auto identity = mathlab::identity {}; + std::ostringstream output; + identity.serialize(output); + auto str = output.str(); + + Assert::AreEqual(3.14, identity.eval(3.14));*/ + } + }; } \ No newline at end of file diff --git a/MathLabTests/block_sequenceTests.cpp b/MathLabTests/block_sequenceTests.cpp new file mode 100644 index 0000000..6c8eb81 --- /dev/null +++ b/MathLabTests/block_sequenceTests.cpp @@ -0,0 +1,102 @@ +#include "CppUnitTest.h" +#include "../MathLab/block_sequence.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace MathLabTests +{ + TEST_CLASS(block_sequence_tests) + { + public: + TEST_METHOD(dump_empty_sequence) + { + mathlab::factory factory; + mathlab::block_sequence sequence(factory); + std::ostringstream output; + sequence.dump(output, false); + Assert::AreEqual(std::string(""), output.str()); + } + + TEST_METHOD(dump_sequence_with_line_numbers) + { + mathlab::factory factory; + factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); + mathlab::block_sequence sequence(factory); + std::istringstream input("addition 100\n"); + sequence.append_from(input); + + std::ostringstream output; + sequence.dump(output, true); + Assert::AreEqual(std::string("1: addition 100\n"), output.str()); + } + + TEST_METHOD(append_multiple_and_eval) + { + mathlab::factory factory; + factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); + factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + mathlab::block_sequence sequence(factory); + std::istringstream input("addition 100.\nmultiplication 2.\n"); + auto invalid_lines = sequence.append_from(input); + Assert::IsTrue(invalid_lines.empty()); + Assert::AreEqual(400., sequence.eval(100.)); + } + + TEST_METHOD(append_skips_invalid_input_and_returns_invalid_lines) + { + mathlab::factory factory; + factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); + factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + mathlab::block_sequence sequence(factory); + std::istringstream input("dummy\naddition 100.\ndummy\nmultiplication 2.\ndummy\n"); + const auto invalid_lines = sequence.append_from(input); + Assert::AreEqual(std::string("dummy\ndummy\ndummy\n"), invalid_lines); + Assert::AreEqual(400., sequence.eval(100.)); + } + + TEST_METHOD(load_clears_sequence_and_appends) + { + mathlab::factory factory; + factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); + factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + mathlab::block_sequence sequence(factory); + std::istringstream input1("addition 100.\n"); + sequence.append_from(input1); + Assert::AreEqual(103., sequence.eval(3.)); + std::istringstream input2("multiplication 2.\n"); + sequence.load_from(input2); + Assert::AreEqual(6., sequence.eval(3.)); + } + + TEST_METHOD(remove_from_sequence) + { + mathlab::factory factory; + factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); + factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + mathlab::block_sequence sequence(factory); + std::istringstream input("addition 100.\nmultiplication 2.\n"); + sequence.append_from(input); + sequence.remove_at(0); + + std::ostringstream output; + sequence.dump(output, false); + Assert::AreEqual(std::string("multiplication 2\n"), output.str()); + } + + TEST_METHOD(move_to_beginning_works) + { + mathlab::factory factory; + factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); + factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + mathlab::block_sequence sequence(factory); + std::istringstream input("addition 100.\nmultiplication 2.\n"); + sequence.append_from(input); + sequence.move_to_beginning(1); + + std::ostringstream output; + sequence.dump(output, false); + // ReSharper restore CppExpressionWithoutSideEffects + Assert::AreEqual(std::string("multiplication 2\naddition 100\n"), output.str()); + } + }; +} \ No newline at end of file diff --git a/MathLabTests/factoryTests.cpp b/MathLabTests/factoryTests.cpp new file mode 100644 index 0000000..03d13ba --- /dev/null +++ b/MathLabTests/factoryTests.cpp @@ -0,0 +1,48 @@ +#include "CppUnitTest.h" +#include "../MathLab/factory.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace MathLabTests +{ + TEST_CLASS(factory_tests) + { + public: + TEST_METHOD(dump_after_register) + { + std::ostringstream output; + auto factory = mathlab::factory(); + factory.dump_registered(output); + Assert::AreEqual(std::string(""), output.str()); + factory.register_block("dummy1", mathlab::factory::fn_type()); + factory.register_block("dummy2", mathlab::factory::fn_type()); + factory.dump_registered(output); + Assert::AreEqual(std::string("dummy1 dummy2 "), output.str()); + } + + TEST_METHOD(create_for_non_registered_fails) + { + std::istringstream input; + auto factory = mathlab::factory(); + const auto ptr = factory.create("dummy", input); + Assert::IsFalse(static_cast(ptr)); + } + + TEST_METHOD(create_after_registration) + { + std::istringstream input; + auto factory = mathlab::factory(); + factory.register_block("dummy", [](std::istream&) { return new mathlab::identity(); }); + const auto ptr = factory.create("dummy", input); + Assert::AreEqual(123.45, ptr->eval(123.45)); + } + + TEST_METHOD(create_after_registration_with_wrong_number_of_constants_throws) + { + std::istringstream input; + auto factory = mathlab::factory(); + factory.register_block(mathlab::power::my_type, [](std::istream& stream) { return new mathlab::power(stream); }); + Assert::ExpectException([&factory, &input]() { factory.create(mathlab::power::my_type, input); }); + } + }; +} \ No newline at end of file From 3e7be22feb5ad03d8a120e195f5e3aa9599eca9e Mon Sep 17 00:00:00 2001 From: Ljudevit Bracko Date: Thu, 22 Nov 2018 15:25:04 +0100 Subject: [PATCH 3/5] Changed process_eval_from_file_command as suggested --- MathLab/MathLab.cpp | Bin 12180 -> 12136 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/MathLab/MathLab.cpp b/MathLab/MathLab.cpp index d6552daeb93e63cfd3ccaabcd398fe2e2f7c8e67..180734cdc70e77d39855bf2f6fef8e346a540d31 100644 GIT binary patch delta 56 zcmbOd{~~Tfn)GBTX@SW~Qc~PH3 Date: Mon, 26 Nov 2018 18:00:16 +0100 Subject: [PATCH 4/5] Final : minimalistic block definition, generic serialization/de-serialization --- MathLab/MathLab.cpp | Bin 12136 -> 12222 bytes MathLab/MathLab.vcxproj | 1 + MathLab/MathLab.vcxproj.filters | 3 + MathLab/block_sequence.cpp | 9 ++- MathLab/block_sequence.h | 3 +- MathLab/blocks.h | 107 +++++++++++---------------- MathLab/factory.cpp | 38 +++++----- MathLab/factory.h | 9 +-- MathLab/tuple_serialization.h | 50 +++++++++++++ MathLabTests/blockTests.cpp | 54 +++----------- MathLabTests/block_sequenceTests.cpp | 30 ++++---- MathLabTests/factoryTests.cpp | 16 ++-- 12 files changed, 159 insertions(+), 161 deletions(-) create mode 100644 MathLab/tuple_serialization.h diff --git a/MathLab/MathLab.cpp b/MathLab/MathLab.cpp index 180734cdc70e77d39855bf2f6fef8e346a540d31..eac9488a978094dec0ae693d53b84e4979d76a07 100644 GIT binary patch delta 52 zcmaD6w=aIe4rUvM;)#-~6L*A6TqY!L!;r~P!jQ^P#E=N2^MSM-11|&DWJNCF%~Fhh FdH~7o4nP0^ delta 18 acmdlN{~~U}j)@OKCZ`GUZ7yK+(*poo + diff --git a/MathLab/MathLab.vcxproj.filters b/MathLab/MathLab.vcxproj.filters index 14dde3d..8495eb4 100644 --- a/MathLab/MathLab.vcxproj.filters +++ b/MathLab/MathLab.vcxproj.filters @@ -24,6 +24,9 @@ Header Files + + Header Files + diff --git a/MathLab/block_sequence.cpp b/MathLab/block_sequence.cpp index e03f9d9..6f90347 100644 --- a/MathLab/block_sequence.cpp +++ b/MathLab/block_sequence.cpp @@ -1,5 +1,6 @@ #include "block_sequence.h" #include +#include namespace { std::pair extract_type(const std::string& line) @@ -32,7 +33,7 @@ namespace mathlab { // Deliberately empty - handle same as unknown command } if (block != nullptr) - blocks_.emplace_back(std::move(block)); + blocks_.push_back(std::make_pair(std::move(block_type), std::move(block))); else invalid_lines += line + "\n"; } @@ -47,10 +48,10 @@ namespace mathlab { std::ostream& block_sequence::dump(std::ostream& to_stream, bool with_line_numbers) const { int position = 1; - for (auto& block : blocks_) { + for (auto& name_with_block : blocks_) { if (with_line_numbers) to_stream << position++ << ": "; - to_stream << *block; + to_stream << name_with_block.first << ' ' << *name_with_block.second << std::endl; } return to_stream; } @@ -60,7 +61,7 @@ namespace mathlab { blocks_.cbegin(), blocks_.cend(), input, - [](double val, const std::unique_ptr &block) { return block->eval(val); }); + [](double val, const std::pair> &name_with_block) { return name_with_block.second->eval(val); }); } void block_sequence::remove_at(unsigned index) { diff --git a/MathLab/block_sequence.h b/MathLab/block_sequence.h index d7b6dd8..ce810a2 100644 --- a/MathLab/block_sequence.h +++ b/MathLab/block_sequence.h @@ -2,10 +2,11 @@ #include "factory.h" #include #include +#include namespace mathlab { class block_sequence { - std::vector> blocks_; + std::vector>> blocks_; factory& factory_; public: block_sequence(factory& factory); diff --git a/MathLab/blocks.h b/MathLab/blocks.h index 24c27b0..1370e4b 100644 --- a/MathLab/blocks.h +++ b/MathLab/blocks.h @@ -1,91 +1,68 @@ #pragma once -#include -#include -#include +#include +#include +#include "tuple_serialization.h" namespace mathlab { // block interface - class block { - public: + struct block { virtual double eval(double input) const = 0; virtual void dump(std::ostream& to_stream) const = 0; virtual ~block() = default; }; - // represents block with N constants (inputs) that can be constructed from stream and be written to stream - template - class block_with_constants : public block { - protected: - std::array constants_; - std::string type_; - - // "It" represents iterator that contains (at least) N constants - // throws invalid_argument if iterator does not contain exactly N values of type double - template block_with_constants(const std::string& type, It begin, It end) : type_(type) { - // In case of istream_iterator, std::difference advances iterator => not able to do the check - auto index = 0U; - for (; begin != end; ++begin) { - if (index++ < N) - constants_[index - 1] = *begin; - else break; - } - if (index != N) - throw std::invalid_argument("Expected number of constants: " + std::to_string(N) + " but was: " + std::to_string(index)); + template + struct block_with_constants : block { + double eval(double input) const override { + auto input_with_constants = std::tuple_cat(std::tuple(input), constants_); + return std::apply(callable_, input_with_constants); } - - // throws invalid_argument if initializer list does not contain exactly N values of type double - block_with_constants(const std::string& type, std::initializer_list list) : block_with_constants(type, list.begin(), list.end()) { } - - public: - // Each block outputs: "block_type[ constant1][ constant2][...constantN]\n" - void dump(std::ostream& stream) const override { - stream << type_; - std::for_each(constants_.begin(), constants_.end(), [&stream](double constant) { stream << ' ' << constant; }); - stream << std::endl; + // Serializes all constants to stream + void dump(std::ostream& to) const override { + tuple_serialization::serialize(to, constants_); + } + // Creates new block from stream + // throws invalid_argument if stream cannot be converted + static std::unique_ptr create_from_stream(std::istream& input_stream) { + std::tuple to; + tuple_serialization::deserialize(input_stream, to); + TBlock* block = std::apply(create, to); + return std::unique_ptr(block); } + protected: + block_with_constants(TCallable callable, TArgs... args) : callable_(std::move(callable)), constants_(std::tie(args...)) {} + private: + std::tuple constants_; + TCallable callable_; }; std::ostream& operator<<(std::ostream& stream, const block& block); - struct identity final : block_with_constants<0> { - inline static const char* my_type = "identity"; - identity() : block_with_constants(my_type, {}) {} - double eval(double input) const override { return input; } - }; + // Supported blocks: - struct addition final : block_with_constants<1> { - inline static const char* my_type = "addition"; - addition(double constant) : block_with_constants(my_type, { constant }) {} - addition(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} - double eval(double input) const override { return input + constants_[0]; } + struct identity final : block_with_constants> { + identity() : block_with_constants([](auto input) { return input; }) {} }; - struct multiplication final : block_with_constants<1> { - inline static const char* my_type = "multiplication"; - multiplication(double constant) : block_with_constants(my_type, { constant }) {} - multiplication(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} - double eval(double input) const override { return input * constants_[0]; } + struct addition final : block_with_constants, double> { + addition(double constant) : block_with_constants(std::plus(), constant) {} }; - - struct power final : block_with_constants<1> { - inline static const char* my_type = "power"; - power(double constant) : block_with_constants(my_type, { constant }) {} - power(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} - double eval(double input) const override { return std::pow(input, constants_[0]); } + + struct multiplication final : block_with_constants, double> { + multiplication(double constant) : block_with_constants(std::multiplies(), constant) {} }; - struct condition final : block_with_constants<1> { - inline static const char* my_type = "condition"; - condition(double constant) : block_with_constants(my_type, { constant }) {} - condition(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} - double eval(double input) const override { return input < constants_[0] ? -1 : (input == constants_[0] ? 0 : 1); } + struct power final : block_with_constants, double> { + power(double constant) : block_with_constants([](double input, double other) { return std::pow(input, other); }, constant) {} + }; + + struct condition final : block_with_constants, double> { + condition(double constant) : + block_with_constants([](double input, double other) { return input < other ? -1 : (input == other ? 0 : 1); }, constant) {} }; - struct limit final : block_with_constants<2> { - inline static const char* my_type = "limit"; - limit(double lower, double upper) : block_with_constants(my_type, { lower, upper }) {} - limit(std::istream& stream) : block_with_constants(my_type, std::istream_iterator(stream), std::istream_iterator()) {} - double eval(double input) const override { return std::clamp(input, constants_[0], constants_[1]); } + struct limit final : block_with_constants, double, double> { + limit(double lower, double upper) : block_with_constants(std::clamp, lower, upper) {} }; } diff --git a/MathLab/factory.cpp b/MathLab/factory.cpp index aa43648..b2b2b58 100644 --- a/MathLab/factory.cpp +++ b/MathLab/factory.cpp @@ -2,33 +2,31 @@ namespace mathlab { - void factory::register_block(const std::string& type, fn_type from_stream_creator) { - block_types_[type] = from_stream_creator; + template + void factory::register_block(const std::string& type_name) { + types_[type_name] = TBlock::create_from_stream; } - std::unique_ptr factory::create(const std::string& type, std::istream& input) - { - block* pblock = nullptr; - const auto found = block_types_.find(type); - if (found != block_types_.end()) - pblock = found->second(input); - return std::unique_ptr(pblock); + std::unique_ptr factory::create(const std::string& type_name, std::istream& stream) const { + const auto found = types_.find(type_name); + if (found != types_.end()) + return found->second(stream); + return std::unique_ptr(); } - std::ostream& factory::dump_registered(std::ostream& to_stream) - { - for (auto pair : block_types_) + std::ostream& factory::dump_registered(std::ostream& to_stream) { + for (auto pair : types_) to_stream << pair.first << ' '; return to_stream; } - void register_all_blocks(factory& factory) - { - factory.register_block(mathlab::identity::my_type, [](std::istream&) {return new mathlab::identity(); }); - factory.register_block(mathlab::addition::my_type, [](std::istream& input) {return new mathlab::addition(input); }); - factory.register_block(mathlab::multiplication::my_type, [](std::istream& input) {return new mathlab::multiplication(input); }); - factory.register_block(mathlab::power::my_type, [](std::istream& input) {return new mathlab::power(input); }); - factory.register_block(mathlab::condition::my_type, [](std::istream& input) {return new mathlab::condition(input); }); - factory.register_block(mathlab::limit::my_type, [](std::istream& input) {return new mathlab::limit(input); }); + void register_all_blocks(factory& factory) { + factory.register_block("identity"); + factory.register_block("addition"); + factory.register_block("multiplication"); + factory.register_block("power"); + factory.register_block("condition"); + factory.register_block("addition"); + factory.register_block("limit"); } } diff --git a/MathLab/factory.h b/MathLab/factory.h index 4598a1a..5ced743 100644 --- a/MathLab/factory.h +++ b/MathLab/factory.h @@ -6,15 +6,14 @@ namespace mathlab { - struct factory final { - using fn_type = std::function; - void register_block(const std::string& type, fn_type from_stream_creator); + struct factory { + template void register_block(const std::string& type_name); // Returns nullptr if block with given type is not registered // Throws invalid_argument if block is known but input stream does not contain expected number of constants - std::unique_ptr create(const std::string& type, std::istream& input); + std::unique_ptr create(const std::string& type_name, std::istream& stream) const; std::ostream& dump_registered(std::ostream &to_stream); private: - std::map block_types_; + std::map(std::istream&)>> types_; }; void register_all_blocks(factory& factory); diff --git a/MathLab/tuple_serialization.h b/MathLab/tuple_serialization.h new file mode 100644 index 0000000..1acff18 --- /dev/null +++ b/MathLab/tuple_serialization.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include + +namespace mathlab { + + // Tuple serialization / de-serialization + // Type arguments: + // N - number of tuple members that are considered + // TArgs - tuple types (e.g. double, int => tuple + template + struct tuple_serialization { + + // Serializes first N tuple members to output stream + // A space is added after each member + static void serialize(std::ostream& to, const std::tuple& from) { + tuple_serialization::serialize(to, from); + const double& x = std::get(from); + + to << x << ' '; + } + + // De-serializes first N tuple members from input stream + // Throws std::invalid_argument when reading from stream fails + static void deserialize(std::istream& from, std::tuple& to) { + tuple_serialization::deserialize(from, to); + using type_n = typename std::tuple_element>::type; + type_n object; + from >> object; + if (! from) // bad-bit or eof + throw std::invalid_argument(std::string("Unable to read object from stream. Object type: ") + typeid(type_n).name()); + std::get(to) = object; + } + }; + + // Specialization for empty tuple, doing nothing + template + struct tuple_serialization<0, TArgs...> { + static void serialize(std::ostream& to, const std::tuple& from) {} + static void deserialize(std::istream& from, std::tuple& to) {} + }; + + // Helper function for constructing an object of type TBlock, using variable constructor arguments + template + T* create(TArgs ...args) { + return new T(args...); + } + +} \ No newline at end of file diff --git a/MathLabTests/blockTests.cpp b/MathLabTests/blockTests.cpp index f599b9a..b467875 100644 --- a/MathLabTests/blockTests.cpp +++ b/MathLabTests/blockTests.cpp @@ -78,16 +78,16 @@ namespace MathLabTests auto identity = mathlab::identity(); std::ostringstream output_stream; output_stream << identity; - Assert::AreEqual(std::string("identity\n"), output_stream.str()); + Assert::AreEqual(std::string(""), output_stream.str()); } TEST_METHOD(block_with_one_constant) { std::istringstream input("123.45"); - auto multiplication = mathlab::multiplication(input); + auto block = mathlab::addition::create_from_stream(input); std::ostringstream output_stream; - output_stream << multiplication; - Assert::AreEqual(std::string("multiplication 123.45\n"), output_stream.str()); + output_stream << *block; + Assert::AreEqual(std::string("123.45 "), output_stream.str()); } TEST_METHOD(block_with_one_constant_throws_if_no_input) @@ -95,16 +95,7 @@ namespace MathLabTests Assert::ExpectException([]() { std::istringstream input(""); - auto dummy = mathlab::multiplication(input); - }); - } - - TEST_METHOD(block_with_one_constant_throws_if_too_much_input) - { - Assert::ExpectException([]() - { - std::istringstream input("123. 456."); - auto dummy = mathlab::multiplication(input); + mathlab::multiplication::create_from_stream(input); }); } @@ -113,17 +104,17 @@ namespace MathLabTests Assert::ExpectException([]() { std::istringstream input("Hello"); - auto dummy = mathlab::multiplication(input); + mathlab::multiplication::create_from_stream(input); }); } TEST_METHOD(block_with_two_constants) { std::istringstream input("-123.45 666.66"); - auto limit = mathlab::limit(input); + auto limit = mathlab::limit::create_from_stream(input); std::ostringstream output_stream; - output_stream << limit; - Assert::AreEqual(std::string("limit -123.45 666.66\n"), output_stream.str()); + output_stream << *limit; + Assert::AreEqual(std::string("-123.45 666.66 "), output_stream.str()); } TEST_METHOD(block_with_two_constants_throws_if_no_input) @@ -131,16 +122,7 @@ namespace MathLabTests Assert::ExpectException([]() { std::istringstream input(""); - auto dummy = mathlab::limit(input); - }); - } - - TEST_METHOD(block_with_two_constants_throws_if_too_much_input) - { - Assert::ExpectException([]() - { - std::istringstream input("123. 456. 789."); - auto dummy = mathlab::limit(input); + mathlab::limit::create_from_stream(input); }); } @@ -149,22 +131,8 @@ namespace MathLabTests Assert::ExpectException([]() { std::istringstream input("Hello World"); - auto dummy = mathlab::multiplication(input); + mathlab::multiplication::create_from_stream(input); }); } }; - - TEST_CLASS(block_serialization) - { - public: - TEST_METHOD(identity_serialization) - { - /*auto identity = mathlab::identity {}; - std::ostringstream output; - identity.serialize(output); - auto str = output.str(); - - Assert::AreEqual(3.14, identity.eval(3.14));*/ - } - }; } \ No newline at end of file diff --git a/MathLabTests/block_sequenceTests.cpp b/MathLabTests/block_sequenceTests.cpp index 6c8eb81..88145e1 100644 --- a/MathLabTests/block_sequenceTests.cpp +++ b/MathLabTests/block_sequenceTests.cpp @@ -20,23 +20,23 @@ namespace MathLabTests TEST_METHOD(dump_sequence_with_line_numbers) { mathlab::factory factory; - factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); + factory.register_block("addition"); mathlab::block_sequence sequence(factory); std::istringstream input("addition 100\n"); sequence.append_from(input); std::ostringstream output; sequence.dump(output, true); - Assert::AreEqual(std::string("1: addition 100\n"), output.str()); + Assert::AreEqual(std::string("1: addition 100 \n"), output.str()); } TEST_METHOD(append_multiple_and_eval) { mathlab::factory factory; - factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); - factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + factory.register_block("add"); + factory.register_block("multiply"); mathlab::block_sequence sequence(factory); - std::istringstream input("addition 100.\nmultiplication 2.\n"); + std::istringstream input("add 100.\nmultiply 2.\n"); auto invalid_lines = sequence.append_from(input); Assert::IsTrue(invalid_lines.empty()); Assert::AreEqual(400., sequence.eval(100.)); @@ -45,8 +45,8 @@ namespace MathLabTests TEST_METHOD(append_skips_invalid_input_and_returns_invalid_lines) { mathlab::factory factory; - factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); - factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + factory.register_block("addition"); + factory.register_block("multiplication"); mathlab::block_sequence sequence(factory); std::istringstream input("dummy\naddition 100.\ndummy\nmultiplication 2.\ndummy\n"); const auto invalid_lines = sequence.append_from(input); @@ -57,8 +57,8 @@ namespace MathLabTests TEST_METHOD(load_clears_sequence_and_appends) { mathlab::factory factory; - factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); - factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + factory.register_block("addition"); + factory.register_block("multiplication"); mathlab::block_sequence sequence(factory); std::istringstream input1("addition 100.\n"); sequence.append_from(input1); @@ -71,8 +71,8 @@ namespace MathLabTests TEST_METHOD(remove_from_sequence) { mathlab::factory factory; - factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); - factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + factory.register_block("addition"); + factory.register_block("multiplication"); mathlab::block_sequence sequence(factory); std::istringstream input("addition 100.\nmultiplication 2.\n"); sequence.append_from(input); @@ -80,14 +80,14 @@ namespace MathLabTests std::ostringstream output; sequence.dump(output, false); - Assert::AreEqual(std::string("multiplication 2\n"), output.str()); + Assert::AreEqual(std::string("multiplication 2 \n"), output.str()); } TEST_METHOD(move_to_beginning_works) { mathlab::factory factory; - factory.register_block("addition", [](std::istream& stream) {return new mathlab::addition(stream); }); - factory.register_block("multiplication", [](std::istream& stream) {return new mathlab::multiplication(stream); }); + factory.register_block("addition"); + factory.register_block("multiplication"); mathlab::block_sequence sequence(factory); std::istringstream input("addition 100.\nmultiplication 2.\n"); sequence.append_from(input); @@ -96,7 +96,7 @@ namespace MathLabTests std::ostringstream output; sequence.dump(output, false); // ReSharper restore CppExpressionWithoutSideEffects - Assert::AreEqual(std::string("multiplication 2\naddition 100\n"), output.str()); + Assert::AreEqual(std::string("multiplication 2 \naddition 100 \n"), output.str()); } }; } \ No newline at end of file diff --git a/MathLabTests/factoryTests.cpp b/MathLabTests/factoryTests.cpp index 03d13ba..4a710cf 100644 --- a/MathLabTests/factoryTests.cpp +++ b/MathLabTests/factoryTests.cpp @@ -14,8 +14,8 @@ namespace MathLabTests auto factory = mathlab::factory(); factory.dump_registered(output); Assert::AreEqual(std::string(""), output.str()); - factory.register_block("dummy1", mathlab::factory::fn_type()); - factory.register_block("dummy2", mathlab::factory::fn_type()); + factory.register_block("dummy1"); + factory.register_block("dummy2"); factory.dump_registered(output); Assert::AreEqual(std::string("dummy1 dummy2 "), output.str()); } @@ -30,19 +30,19 @@ namespace MathLabTests TEST_METHOD(create_after_registration) { - std::istringstream input; + std::istringstream input("100"); auto factory = mathlab::factory(); - factory.register_block("dummy", [](std::istream&) { return new mathlab::identity(); }); - const auto ptr = factory.create("dummy", input); - Assert::AreEqual(123.45, ptr->eval(123.45)); + factory.register_block("dummy"); + auto ptr = factory.create("dummy", input); + Assert::AreEqual(223.45, ptr->eval(123.45)); } TEST_METHOD(create_after_registration_with_wrong_number_of_constants_throws) { std::istringstream input; auto factory = mathlab::factory(); - factory.register_block(mathlab::power::my_type, [](std::istream& stream) { return new mathlab::power(stream); }); - Assert::ExpectException([&factory, &input]() { factory.create(mathlab::power::my_type, input); }); + factory.register_block("dummy"); + Assert::ExpectException([&factory, &input]() { factory.create("dummy", input); }); } }; } \ No newline at end of file From d12efeaa0c1c7d696a8236b7005f8dd4089c4f8a Mon Sep 17 00:00:00 2001 From: Ljudevit Bracko Date: Tue, 27 Nov 2018 09:29:05 +0100 Subject: [PATCH 5/5] minor improvements --- MathLab/blocks.h | 15 ++++++++------- MathLab/factory.cpp | 2 +- MathLab/tuple_serialization.h | 4 +--- MathLabTests/blockTests.cpp | 12 ++++++------ 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/MathLab/blocks.h b/MathLab/blocks.h index 1370e4b..a200c30 100644 --- a/MathLab/blocks.h +++ b/MathLab/blocks.h @@ -12,7 +12,7 @@ namespace mathlab virtual ~block() = default; }; - template + template struct block_with_constants : block { double eval(double input) const override { auto input_with_constants = std::tuple_cat(std::tuple(input), constants_); @@ -24,6 +24,7 @@ namespace mathlab } // Creates new block from stream // throws invalid_argument if stream cannot be converted + template static std::unique_ptr create_from_stream(std::istream& input_stream) { std::tuple to; tuple_serialization::deserialize(input_stream, to); @@ -41,28 +42,28 @@ namespace mathlab // Supported blocks: - struct identity final : block_with_constants> { + struct identity final : block_with_constants> { identity() : block_with_constants([](auto input) { return input; }) {} }; - struct addition final : block_with_constants, double> { + struct addition final : block_with_constants, double> { addition(double constant) : block_with_constants(std::plus(), constant) {} }; - struct multiplication final : block_with_constants, double> { + struct multiplication final : block_with_constants, double> { multiplication(double constant) : block_with_constants(std::multiplies(), constant) {} }; - struct power final : block_with_constants, double> { + struct power final : block_with_constants, double> { power(double constant) : block_with_constants([](double input, double other) { return std::pow(input, other); }, constant) {} }; - struct condition final : block_with_constants, double> { + struct condition final : block_with_constants, double> { condition(double constant) : block_with_constants([](double input, double other) { return input < other ? -1 : (input == other ? 0 : 1); }, constant) {} }; - struct limit final : block_with_constants, double, double> { + struct limit final : block_with_constants, double, double> { limit(double lower, double upper) : block_with_constants(std::clamp, lower, upper) {} }; } diff --git a/MathLab/factory.cpp b/MathLab/factory.cpp index b2b2b58..10c9810 100644 --- a/MathLab/factory.cpp +++ b/MathLab/factory.cpp @@ -4,7 +4,7 @@ namespace mathlab { template void factory::register_block(const std::string& type_name) { - types_[type_name] = TBlock::create_from_stream; + types_[type_name] = TBlock::template create_from_stream; } std::unique_ptr factory::create(const std::string& type_name, std::istream& stream) const { diff --git a/MathLab/tuple_serialization.h b/MathLab/tuple_serialization.h index 1acff18..cbe7c8f 100644 --- a/MathLab/tuple_serialization.h +++ b/MathLab/tuple_serialization.h @@ -16,9 +16,7 @@ namespace mathlab { // A space is added after each member static void serialize(std::ostream& to, const std::tuple& from) { tuple_serialization::serialize(to, from); - const double& x = std::get(from); - - to << x << ' '; + to << std::get(from) << ' '; } // De-serializes first N tuple members from input stream diff --git a/MathLabTests/blockTests.cpp b/MathLabTests/blockTests.cpp index b467875..0331543 100644 --- a/MathLabTests/blockTests.cpp +++ b/MathLabTests/blockTests.cpp @@ -84,7 +84,7 @@ namespace MathLabTests TEST_METHOD(block_with_one_constant) { std::istringstream input("123.45"); - auto block = mathlab::addition::create_from_stream(input); + auto block = mathlab::addition::create_from_stream(input); std::ostringstream output_stream; output_stream << *block; Assert::AreEqual(std::string("123.45 "), output_stream.str()); @@ -95,7 +95,7 @@ namespace MathLabTests Assert::ExpectException([]() { std::istringstream input(""); - mathlab::multiplication::create_from_stream(input); + mathlab::multiplication::create_from_stream(input); }); } @@ -104,14 +104,14 @@ namespace MathLabTests Assert::ExpectException([]() { std::istringstream input("Hello"); - mathlab::multiplication::create_from_stream(input); + mathlab::multiplication::create_from_stream(input); }); } TEST_METHOD(block_with_two_constants) { std::istringstream input("-123.45 666.66"); - auto limit = mathlab::limit::create_from_stream(input); + auto limit = mathlab::limit::create_from_stream(input); std::ostringstream output_stream; output_stream << *limit; Assert::AreEqual(std::string("-123.45 666.66 "), output_stream.str()); @@ -122,7 +122,7 @@ namespace MathLabTests Assert::ExpectException([]() { std::istringstream input(""); - mathlab::limit::create_from_stream(input); + mathlab::limit::create_from_stream(input); }); } @@ -131,7 +131,7 @@ namespace MathLabTests Assert::ExpectException([]() { std::istringstream input("Hello World"); - mathlab::multiplication::create_from_stream(input); + mathlab::multiplication::create_from_stream(input); }); } };