Rework path parsing

Right now, there are two types of path parsing I want to do: either I
want parse a data path (which could possibly be one ending with a list
with no keys; this patch doesn't handle that), or, I want to parse any
path. All of the commands that can take a schema path can also take any
other type of path because a data path is just a "subset" of a schema
path. So, I changed the schema path parser to an "any path" parser.
This any path parser can then work more efficiently than a "dataPath |
schemaPath" parser: it will try to parse a data path and on the first
non-data node it will switch and continue parsing schema nodes. This has
the advantage that I don't have to do workarounds for completion. Before
this, if the parser tried to backtrack to the schema path it would
always have to clear ParserContext path and completions and do
everything again, and that would mean trouble, because I didn't really
have much control about where exactly the completions get created.
Example: the data path parser would create the completions I wanted, but
then fail. The parser would then backtrack to schema path, but it
wouldn't parse as much of the input as the other one and that would
create different completions.

There is a small caveat: I do have create my own local variables for
dataPath and schemaPath. Before this I never did have to create a
`dataPath_` or a `schemaPath_` instance. However, I think that the
control that I get over how nodes are parsed (and over the resulting
attribute of the parser) outweighs that.

Also, there was another attempt on this, which just used Spirit
backtracking. As was said before, more control over this backtracking is
better. Also, not having Spirit backtracking will hopefully allow me to
transition to new Boost version more easily.

Change-Id: I3c8a1ac2ddad83a3da6c654557b36634596a5e8d
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 14d3451..ab9958a 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -172,34 +172,6 @@
 
 struct dataPathListEnd_class;
 
-struct dataPath_class {
-    template <typename Iterator, typename Exception, typename Context>
-    x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const&, Context const& context)
-    {
-        auto& parserContext = x3::get<parser_context_tag>(context);
-        if (parserContext.m_errorMsg.empty()) {
-            parserContext.m_errorMsg = "Expected path.";
-            return x3::error_handler_result::fail;
-        } else {
-            return x3::error_handler_result::rethrow;
-        }
-    }
-};
-
-struct schemaPath_class {
-    template <typename Iterator, typename Exception, typename Context>
-    x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const&, Context const& context)
-    {
-        auto& parserContext = x3::get<parser_context_tag>(context);
-        if (parserContext.m_errorMsg.empty()) {
-            parserContext.m_errorMsg = "Expected path.";
-            return x3::error_handler_result::fail;
-        } else {
-            return x3::error_handler_result::rethrow;
-        }
-    }
-};
-
 struct discard_class;
 
 struct ls_class;
diff --git a/src/grammars.hpp b/src/grammars.hpp
index ae7a980..74a6a17 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -47,7 +47,7 @@
 } const ls_options;
 
 auto const ls_def =
-    ls_::name >> *(space_separator >> ls_options) >> -(space_separator >> (dataPathListEnd | dataPath | schemaPath | (module >> "*")));
+    ls_::name >> *(space_separator >> ls_options) >> -(space_separator >> (dataPathListEnd | anyPath | (module >> "*")));
 
 auto const cd_def =
     cd_::name >> space_separator > dataPath;
@@ -132,7 +132,7 @@
     copy_::name > space_separator > copy_args;
 
 auto const describe_def =
-    describe_::name >> space_separator > (dataPathListEnd | dataPath | schemaPath);
+    describe_::name >> space_separator > (dataPathListEnd | anyPath);
 
 auto const createCommandSuggestions_def =
     x3::eps;
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 19ab2a2..0334d12 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -14,8 +14,6 @@
 
 namespace x3 = boost::spirit::x3;
 
-x3::rule<dataPath_class, dataPath_> const dataPath = "dataPath";
-x3::rule<schemaPath_class, schemaPath_> const schemaPath = "schemaPath";
 x3::rule<dataNodeList_class, decltype(dataPath_::m_nodes)::value_type> const dataNodeList = "dataNodeList";
 x3::rule<dataNodesListEnd_class, decltype(dataPath_::m_nodes)> const dataNodesListEnd = "dataNodesListEnd";
 x3::rule<dataPathListEnd_class, dataPath_> const dataPathListEnd = "dataPathListEnd";
@@ -128,6 +126,53 @@
 NodeParser<schemaNode_> schemaNode;
 NodeParser<dataNode_> dataNode;
 
+using AnyPath = boost::variant<schemaPath_, dataPath_>;
+
+struct PathParser : x3::parser<PathParser> {
+    template <typename It, typename Ctx, typename RCtx, typename Attr>
+    bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, Attr& attr) const
+    {
+        initializePath.parse(begin, end, ctx, rctx, attr);
+        dataPath_ attrData;
+
+        // absoluteStart has to be separate from the dataPath parser,
+        // otherwise, if the "dataNode % '/'" parser fails, the begin iterator
+        // gets reverted to before the starting slash.
+        auto res = -absoluteStart.parse(begin, end, ctx, rctx, attrData.m_scope);
+        auto dataPath = x3::attr(attrData.m_scope) >> dataNode % '/' >> -trailingSlash;
+        res = dataPath.parse(begin, end, ctx, rctx, attrData);
+        attr = attrData;
+
+        if constexpr (std::is_same<Attr, AnyPath>()) {
+            auto pathEnd = x3::rule<class PathEnd>{"pathEnd"} = &space_separator | x3::eoi;
+            // If parsing failed, or if there's more input we try parsing schema nodes
+            if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
+                // If dataPath parsed some nodes, they will be saved in `attrData`. We have to keep these.
+                schemaPath_ attrSchema = dataPathToSchemaPath(attrData);
+                auto schemaPath = schemaNode % '/';
+                // The schemaPath parser continues where the dataPath parser ended.
+                res = schemaPath.parse(begin, end, ctx, rctx, attrSchema.m_nodes);
+                auto trailing = -trailingSlash >> pathEnd;
+                res = trailing.parse(begin, end, ctx, rctx, attrSchema.m_trailingSlash);
+                attr = attrSchema;
+            }
+        }
+        return res;
+    }
+} const pathParser;
+
+// Need to use these wrappers so that my PathParser class gets the proper
+// attribute. Otherwise, Spirit injects the attribute of the outer parser that
+// uses my PathParser.
+// Example grammar: anyPath | module.
+// The PathParser class would get a boost::variant as the attribute, but I
+// don't want to deal with that, so I use these wrappers to ensure the
+// attribute I want (and let Spirit deal with boost::variant). Also, the
+// attribute gets passed to PathParser::parse via a template argument, so the
+// class doesn't even to need to be a template. Convenient!
+auto const anyPath = x3::rule<class anyPath_class, AnyPath>{"anyPath"} = pathParser;
+auto const dataPath = x3::rule<class dataPath_class, dataPath_>{"dataPath"} = pathParser;
+
 #if __clang__
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Woverloaded-shift-op-parentheses"
@@ -172,9 +217,6 @@
 auto const createPathSuggestions_def =
     x3::eps;
 
-// I have to insert an empty vector to the first alternative, otherwise they won't have the same attribute
-auto const dataPath_def = initializePath >> absoluteStart >> createPathSuggestions >> x3::attr(decltype(dataPath_::m_nodes)()) >> x3::attr(TrailingSlash::NonPresent) >> x3::eoi | initializePath >> -(absoluteStart >> createPathSuggestions) >> dataNode % '/' >> (-(trailingSlash >> createPathSuggestions) >> -(completing >> rest) >> (&space_separator | x3::eoi));
-
 auto const dataNodeList_def =
     createPathSuggestions >> -(module) >> list;
 
@@ -188,8 +230,6 @@
 
 auto const dataPathListEnd_def = initializePath >> absoluteStart >> createPathSuggestions >> x3::attr(decltype(dataPath_::m_nodes)()) >> x3::attr(TrailingSlash::NonPresent) >> x3::eoi | initializePath >> -(absoluteStart >> createPathSuggestions) >> dataNodesListEnd >> (-(trailingSlash >> createPathSuggestions) >> -(completing >> rest) >> (&space_separator | x3::eoi));
 
-auto const schemaPath_def = initializePath >> absoluteStart >> createPathSuggestions >> x3::attr(decltype(schemaPath_::m_nodes)()) >> x3::attr(TrailingSlash::NonPresent) >> x3::eoi | initializePath >> -(absoluteStart >> createPathSuggestions) >> schemaNode % '/' >> (-(trailingSlash >> createPathSuggestions) >> -(completing >> rest) >> (&space_separator | x3::eoi));
-
 auto const leafPath_def =
     dataPath;
 
@@ -218,8 +258,6 @@
 BOOST_SPIRIT_DEFINE(leafPath)
 BOOST_SPIRIT_DEFINE(presenceContainerPath)
 BOOST_SPIRIT_DEFINE(listInstancePath)
-BOOST_SPIRIT_DEFINE(schemaPath)
-BOOST_SPIRIT_DEFINE(dataPath)
 BOOST_SPIRIT_DEFINE(dataPathListEnd)
 BOOST_SPIRIT_DEFINE(initializePath)
 BOOST_SPIRIT_DEFINE(createKeySuggestions)