From 420a6bae76968413ab75b8b17002a07c18bd762a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Tue, 3 Nov 2020 10:49:33 +0100
Subject: [PATCH] Add tests for EscapeString utilities

Actually fixed a few issues and make functions escapeString() and
unescapeString() reciprocal.

This later change leads to a grammar change, now literal do not
store quotes: before `"foo"` was stored as the string "\"foo\"", now
it is just stored as "foo".
---
 src/language/PEGGrammar.hpp       |  6 ++++--
 src/language/utils/ASTPrinter.cpp |  5 +++--
 src/utils/EscapedString.hpp       |  9 +++++++--
 tests/CMakeLists.txt              |  1 +
 tests/test_EscapedString.cpp      | 27 +++++++++++++++++++++++++++
 5 files changed, 42 insertions(+), 6 deletions(-)
 create mode 100644 tests/test_EscapedString.cpp

diff --git a/src/language/PEGGrammar.hpp b/src/language/PEGGrammar.hpp
index fb3b1d74a..553a2797c 100644
--- a/src/language/PEGGrammar.hpp
+++ b/src/language/PEGGrammar.hpp
@@ -55,11 +55,13 @@ struct character : if_must_else< one< '\\' >, escaped_c, ascii::any> {};
 struct open_parent : seq< one< '(' >, ignored > {};
 struct close_parent : seq< one< ')' >, ignored > {};
 
-struct literal : if_must< one< '"' >, until< one< '"' >, character > > {};
+struct literal : star< minus<character, one < '"' > > >{};
+
+struct quoted_literal : if_must< one< '"' >, seq< literal,  one< '"'  > > >{};
 
 struct import_kw :  TAO_PEGTL_KEYWORD("import") {};
 
-struct LITERAL : seq< literal, ignored >{};
+struct LITERAL : seq< quoted_literal, ignored >{};
 
 struct REAL : seq< real, ignored >{};
 
diff --git a/src/language/utils/ASTPrinter.cpp b/src/language/utils/ASTPrinter.cpp
index 9bf18bdcf..8cc88b530 100644
--- a/src/language/utils/ASTPrinter.cpp
+++ b/src/language/utils/ASTPrinter.cpp
@@ -14,9 +14,10 @@ ASTPrinter::_print(std::ostream& os, const ASTNode& node) const
   }
   os << rang::fg::reset;
 
-  if (node.is_type<language::name>() or node.is_type<language::literal>() or node.is_type<language::integer>() or
-      node.is_type<language::real>()) {
+  if (node.is_type<language::name>() or node.is_type<language::integer>() or node.is_type<language::real>()) {
     os << ':' << rang::fgB::green << node.string() << rang::fg::reset;
+  } else if (node.is_type<language::literal>()) {
+    os << ":\"" << rang::fgB::green << node.string() << rang::fg::reset << '"';
   }
 
   if (m_info & static_cast<InfoBaseType>(Info::data_type)) {
diff --git a/src/utils/EscapedString.hpp b/src/utils/EscapedString.hpp
index ed525abd0..cf0f0c676 100644
--- a/src/utils/EscapedString.hpp
+++ b/src/utils/EscapedString.hpp
@@ -1,6 +1,7 @@
 #ifndef ESCAPED_STRING_HPP
 #define ESCAPED_STRING_HPP
 
+#include <utils/Exceptions.hpp>
 #include <utils/PugsMacros.hpp>
 
 #include <sstream>
@@ -11,7 +12,7 @@ PUGS_INLINE std::string
 unescapeString(std::string_view input_string)
 {
   std::stringstream ss;
-  for (size_t i = 1; i < input_string.size() - 1; ++i) {
+  for (size_t i = 0; i < input_string.size(); ++i) {
     char c = input_string[i];
     if (c == '\\') {
       ++i;
@@ -81,11 +82,15 @@ escapeString(std::string_view input_string)
       ss << R"(\\)";
       break;
     }
+    case '\'': {
+      ss << R"(\')";
+      break;
+    }
     case '\"': {
       ss << R"(\")";
       break;
     }
-    case '?': {
+    case '\?': {
       ss << R"(\?)";
       break;
     }
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 027b1293f..b58ed3cf8 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -60,6 +60,7 @@ add_executable (unit_tests
   test_Demangle.cpp
   test_DoWhileProcessor.cpp
   test_EmbeddedData.cpp
+  test_EscapedString.cpp
   test_ExecutionPolicy.cpp
   test_FakeProcessor.cpp
   test_ForProcessor.cpp
diff --git a/tests/test_EscapedString.cpp b/tests/test_EscapedString.cpp
new file mode 100644
index 000000000..925ebadbd
--- /dev/null
+++ b/tests/test_EscapedString.cpp
@@ -0,0 +1,27 @@
+#ifndef TEST_ESCAPED_STRING_HPP
+#define TEST_ESCAPED_STRING_HPP
+
+#include <catch2/catch.hpp>
+
+#include <utils/EscapedString.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("EscapedString", "[utils]")
+{
+  SECTION("escape string")
+  {
+    const std::string s = "foo\'\\\"\?\a\b\f\n\r\t\vbar";
+
+    REQUIRE(escapeString(s) == R"(foo\'\\\"\?\a\b\f\n\r\t\vbar)");
+  }
+
+  SECTION("unescape string")
+  {
+    const std::string s = R"(foo\'\\\"\?\a\b\f\n\r\t\vbar)";
+
+    REQUIRE(unescapeString(s) == std::string{"foo\'\\\"\?\a\b\f\n\r\t\vbar"});
+  }
+}
+
+#endif   // TEST_ESCAPED_STRING_HPP
-- 
GitLab