Merge "Rework path parsing"
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)