Bläddra i källkod

:sparkles: added implementation

Felix Bytow 2 år sedan
förälder
incheckning
e5025dc628

+ 2 - 0
projects/forth-kata/CMakeLists.txt

@@ -1,7 +1,9 @@
 add_library_project(forth-kata STATIC
     SOURCES
     src/Token.cpp include/Token.h
+    src/ForthEvaluator.cpp include/ForthEvaluator.h
     TEST_SOURCES
     src/TokenTests.cpp
+    src/ForthEvaluatorTests.cpp
     )
 target_link_libraries(forth-kata PUBLIC Microsoft.GSL::GSL)

+ 16 - 0
projects/forth-kata/include/ForthEvaluator.h

@@ -0,0 +1,16 @@
+#ifndef FORTH_KATA_FORTHEVALUATOR_H
+#define FORTH_KATA_FORTHEVALUATOR_H
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace forth {
+
+std::vector<std::string> splitAtWhiteSpace(std::string const &input) noexcept;
+
+std::vector<std::int16_t> evaluate(std::string const &source) noexcept(false);
+
+}
+
+#endif // !FORTH_KATA_FORTHEVALUATOR_H

+ 3 - 0
projects/forth-kata/include/Token.h

@@ -36,6 +36,9 @@ std::ostream &operator<<(std::ostream &os, Number const &number);
  */
 struct Keyword final
 {
+    /*
+    * This value holds the value of the key word.
+    */
     std::string value;
 
     bool operator==(Keyword const &rhs) const noexcept;

+ 119 - 0
projects/forth-kata/src/ForthEvaluator.cpp

@@ -0,0 +1,119 @@
+#include "ForthEvaluator.h"
+#include "Token.h"
+
+#include <algorithm>
+#include <cctype>
+#include <string>
+#include <unordered_set>
+#include <gsl/gsl>
+
+using namespace std::literals::string_literals;
+
+namespace forth {
+
+template<typename Source>
+class NarrowProxy final
+{
+    Source source;
+
+public:
+    explicit NarrowProxy(Source const &src): source{src}
+    {}
+
+    template<typename Target>
+    operator Target() const
+    {
+        return gsl::narrow<Target>(source);
+    }
+};
+
+template<typename Source>
+NarrowProxy<Source> narrow(Source const &source)
+{
+    return NarrowProxy<Source>{source};
+}
+
+std::vector<std::string> splitAtWhiteSpace(std::string const &input) noexcept
+{
+    std::vector<std::string> result{};
+
+    std::string temp{};
+    for (auto const c : input)
+    {
+        if (!std::isspace(c))
+        {
+            temp += c;
+        }
+        else if (!temp.empty())
+        {
+            result.push_back(temp);
+            temp.clear();
+        }
+    }
+    if (!temp.empty())
+    {
+        result.push_back(temp);
+    }
+
+    return result;
+}
+
+static std::unordered_set<std::string> const keywords{":"s, ";"s};
+
+std::vector<std::int16_t> evaluate(std::string const &source) noexcept(false)
+{
+    auto const words = splitAtWhiteSpace(source);
+    std::vector<Token> tokens;
+    tokens.reserve(words.size());
+    std::transform(std::cbegin(words),
+                   std::cend(words),
+                   std::back_inserter(tokens),
+                   [](std::string const &word) -> Token
+                   {
+                       try
+                       {
+                           return token::Number{gsl::narrow<std::int16_t>(std::stoi(word))};
+                       }
+                       catch (std::invalid_argument const &)
+                       {
+                           if (keywords.find(word) == keywords.end())
+                           {
+                               return token::Word{word};
+                           }
+                           else
+                           {
+                               return token::Keyword{word};
+                           }
+                       }
+                   });
+
+    std::vector<std::int16_t> stack;
+
+    for (auto const &token : tokens)
+    {
+        if (token.IsNumber())
+        {
+            stack.push_back(token.GetNumber().value);
+        }
+        else if (token.IsWord())
+        {
+            auto const instruction = token.GetWord().value;
+            if (instruction == "DROP")
+            {
+                stack.pop_back();
+            }
+            else if (instruction == "+")
+            {
+                auto const a = stack.back();
+                stack.pop_back();
+                auto const b = stack.back();
+                stack.pop_back();
+                stack.push_back(narrow(a + b));
+            }
+        }
+    }
+
+    return stack;
+}
+
+} // namespace forth

+ 69 - 0
projects/forth-kata/src/ForthEvaluatorTests.cpp

@@ -0,0 +1,69 @@
+#include "ForthEvaluator.h"
+
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+using namespace std::literals::string_literals;
+
+namespace forth {
+
+TEST(ForthEvaluatorTests, SplittingEmptyStringYieldsEmptyVector)
+{
+    std::vector<std::string> const vec = splitAtWhiteSpace(""s);
+    EXPECT_TRUE(vec.empty());
+}
+
+TEST(ForthEvaluatorTests, SplittingStringWithoutWhiteSpacesYieldsVectorWithOneEntry)
+{
+    std::vector<std::string> const vec = splitAtWhiteSpace("test"s);
+    ASSERT_EQ(vec.size(), 1U);
+    EXPECT_EQ(vec[0], "test"s);
+}
+
+TEST(ForthEvaluatorTests, SplittingStringWithWhiteSpacesYieldsVectorWithTwoEntries)
+{
+    std::vector<std::string> const vec = splitAtWhiteSpace("ab cd"s);
+    EXPECT_EQ(vec, (std::vector<std::string>{"ab"s, "cd"s}));
+}
+
+TEST(ForthEvaluatorTests, SplittingStringWithTwoWhiteSpacesYieldsVectorWithThreeEntries)
+{
+    std::vector<std::string> const vec = splitAtWhiteSpace("ab cd ef"s);
+    EXPECT_EQ(vec, (std::vector<std::string>{"ab"s, "cd"s, "ef"s}));
+}
+
+TEST(ForthEvaluatorTests, EvaluatingEmptyStringYieldsEmptyStack)
+{
+    std::vector<std::int16_t> const stack = evaluate(""s);
+    EXPECT_TRUE(stack.empty());
+}
+
+using ForthEvaluatorTests_ParseSingleNumber = testing::TestWithParam<std::int16_t>;
+
+TEST_P(ForthEvaluatorTests_ParseSingleNumber, EvaluatingStringWithNumberYieldsStackWithNumber)
+{
+    auto const value = GetParam();
+    std::vector<std::int16_t> const stack = evaluate(std::to_string(value));
+    ASSERT_EQ(stack.size(), 1U);
+    EXPECT_EQ(stack[0], value);
+}
+
+INSTANTIATE_TEST_SUITE_P(ForthEvaluatorTests_ParseSingleNumber,
+                         ForthEvaluatorTests_ParseSingleNumber, testing::Values(23, 42, 1337, 4711));
+
+TEST(ForthEvaluatorTests, EvaluatingStringWithNumbersYieldsStackWithNumbers)
+{
+    std::vector<std::int16_t> const stack = evaluate("23 42 1337"s);
+    EXPECT_EQ(stack, (std::vector<std::int16_t>{23, 42, 1337}));
+}
+
+TEST(ForthEvaluatorTests, EvaluatingStringsWithValidKeyword)
+{
+    std::vector<std::int16_t> const stack = evaluate("23 12 DROP"s);
+    EXPECT_EQ(stack, (std::vector<std::int16_t>{23}));
+}
+
+} // namespace forth