Merge "Fix identityref completion not giving module names"
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/common_parsers.hpp b/src/common_parsers.hpp
index eac8e61..8da90a6 100644
--- a/src/common_parsers.hpp
+++ b/src/common_parsers.hpp
@@ -34,6 +34,22 @@
 auto const space_separator_def =
     x3::omit[x3::no_skip[x3::space]];
 
+template <typename CoerceTo>
+struct as_type {
+    template <typename...> struct Tag{};
+
+    template <typename ParserType>
+    auto operator[](ParserType p) const {
+        return x3::rule<Tag<CoerceTo, ParserType>, CoerceTo> {"as"} = x3::as_parser(p);
+    }
+};
+
+// The `as` parser creates an ad-hoc x3::rule with the attribute specified with `CoerceTo`.
+// Example usage: as<std::string>[someParser]
+// someParser will have its attribute coerced to std::string
+// https://github.com/boostorg/spirit/issues/530#issuecomment-584836532
+template <typename CoerceTo> const as_type<CoerceTo> as{};
+
 BOOST_SPIRIT_DEFINE(node_identifier)
 BOOST_SPIRIT_DEFINE(module)
 BOOST_SPIRIT_DEFINE(module_identifier)
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/leaf_data.hpp b/src/leaf_data.hpp
index b33d4c1..a94770a 100644
--- a/src/leaf_data.hpp
+++ b/src/leaf_data.hpp
@@ -22,8 +22,6 @@
 x3::rule<struct leaf_data_class<yang::Binary>, binary_> const leaf_data_binary = "leaf_data_binary";
 x3::rule<struct leaf_data_class<yang::Decimal>, double> const leaf_data_decimal = "leaf_data_decimal";
 x3::rule<struct leaf_data_class<yang::String>, std::string> const leaf_data_string = "leaf_data_string";
-x3::rule<struct leaf_data_class_binary, std::string> const leaf_data_binary_data = "leaf_data_binary_data";
-x3::rule<struct leaf_data_identityRef_data_class, identityRef_> const leaf_data_identityRef_data = "leaf_data_identityRef_data";
 
 using x3::char_;
 
@@ -43,22 +41,11 @@
     '\'' >> *(char_-'\'') >> '\'' |
     '\"' >> *(char_-'\"') >> '\"';
 
-// This intermediate rule is neccessary for coercing to std::string.
-// TODO: check if I can do the coercing right in the grammar with `as{}` from
-// https://github.com/boostorg/spirit/issues/530#issuecomment-584836532
-// This would shave off some more lines.
-auto const leaf_data_binary_data_def =
-    +(x3::alnum | char_('+') | char_('/')) >> -char_('=') >> -char_('=');
-
 auto const leaf_data_binary_def =
-    leaf_data_binary_data;
+    as<std::string>[+(x3::alnum | char_('+') | char_('/')) >> -char_('=') >> -char_('=')];
 
-auto const leaf_data_identityRef_data_def =
-    -module >> node_identifier;
-
-// TODO: get rid of this and use leaf_data_identityRef_data directly
 auto const leaf_data_identityRef_def =
-    leaf_data_identityRef_data;
+    -module >> node_identifier;
 
 template <typename It, typename Ctx, typename RCtx, typename Attr>
 struct impl_LeafData {
@@ -137,44 +124,29 @@
     bool operator()(const yang::Enum& type) const
     {
         createSetSuggestions(type);
-        // leaf_data_enum will advance the iterator if it succeeds, so I have
-        // to save the iterator here, to roll it back in case the enum is
-        // invalid.
-        auto saveIter = first;
-        auto pass = leaf_data_enum.parse(first, last, ctx, rctx, attr);
-        if (!pass) {
-            return false;
-        }
-        auto isValidEnum = type.m_allowedValues.count(boost::get<enum_>(attr)) != 0;
-        if (!isValidEnum) {
-            first = saveIter;
-            parserContext.m_errorMsg = "leaf data type mismatch: Expected an enum here. Allowed values:";
-            for (const auto& it : type.m_allowedValues) {
-                parserContext.m_errorMsg += " " + it.m_value;
+        auto checkValidEnum = [this, type] (auto& ctx) {
+            if (type.m_allowedValues.count(boost::get<enum_>(attr)) == 0) {
+                _pass(ctx) = false;
+                parserContext.m_errorMsg = "leaf data type mismatch: Expected an enum here. Allowed values:";
+                for (const auto& it : type.m_allowedValues) {
+                    parserContext.m_errorMsg += " " + it.m_value;
+                }
             }
-        }
-        return isValidEnum;
+        };
+        return leaf_data_enum[checkValidEnum].parse(first, last, ctx, rctx, attr);
     }
     bool operator()(const yang::IdentityRef& type) const
     {
         createSetSuggestions(type);
-        // leaf_data_identityRef will advance the iterator if it succeeds, so I have
-        // to save the iterator here, to roll it back in case the enum is
-        // invalid.
-        auto saveIter = first;
-        auto pass = leaf_data_identityRef.parse(first, last, ctx, rctx, attr);
-        if (!pass) {
-            return false;
-        }
-        identityRef_ pair{boost::get<identityRef_>(attr)};
-        if (!pair.m_prefix) {
-            pair.m_prefix = module_{parserContext.currentSchemaPath().m_nodes.front().m_prefix.get().m_name};
-        }
-        auto isValidIdentity  = type.m_allowedValues.count(pair) != 0;
-        if (!isValidIdentity) {
-            first = saveIter;
-        }
-        return isValidIdentity;
+        auto checkValidIdentity = [this, type] (auto& ctx) {
+            identityRef_ pair{boost::get<identityRef_>(_attr(ctx))};
+            if (!pair.m_prefix) {
+                pair.m_prefix = module_{parserContext.currentSchemaPath().m_nodes.front().m_prefix.get().m_name};
+            }
+            _pass(ctx) = type.m_allowedValues.count(pair) != 0;
+        };
+
+        return leaf_data_identityRef[checkValidIdentity].parse(first, last, ctx, rctx, attr);
     }
     bool operator()(const yang::LeafRef& leafRef) const
     {
@@ -214,7 +186,5 @@
 
 BOOST_SPIRIT_DEFINE(leaf_data_enum)
 BOOST_SPIRIT_DEFINE(leaf_data_string)
-BOOST_SPIRIT_DEFINE(leaf_data_binary_data)
 BOOST_SPIRIT_DEFINE(leaf_data_binary)
-BOOST_SPIRIT_DEFINE(leaf_data_identityRef_data)
 BOOST_SPIRIT_DEFINE(leaf_data_identityRef)
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)
diff --git a/src/schema.hpp b/src/schema.hpp
index f11265f..0a9b2ff 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -15,8 +15,6 @@
 #include "ast_path.hpp"
 #include "leaf_data_type.hpp"
 
-using ModuleValuePair = std::pair<boost::optional<std::string>, std::string>;
-
 namespace yang {
 enum class NodeTypes {
     Container,
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index 2395aa0..aecad5a 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -73,15 +73,10 @@
 
 std::set<identityRef_> StaticSchema::validIdentities(std::string_view module, std::string_view value)
 {
-    std::set<ModuleValuePair> identities;
-    getIdentSet(ModuleNodePair{boost::optional<std::string>{module}, value}, identities);
-    std::set<identityRef_> res;
+    std::set<identityRef_> identities;
+    getIdentSet(identityRef_{std::string{module}, std::string{value}}, identities);
 
-    std::transform(identities.begin(), identities.end(), std::inserter(res, res.end()), [](const auto& identity) {
-        return identityRef_{*identity.first, identity.second};
-    });
-
-    return res;
+    return identities;
 }
 
 void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type)
@@ -96,15 +91,15 @@
     m_modules.emplace(name);
 }
 
-void StaticSchema::addIdentity(const std::optional<ModuleValuePair>& base, const ModuleValuePair& name)
+void StaticSchema::addIdentity(const std::optional<identityRef_>& base, const identityRef_& name)
 {
     if (base)
         m_identities.at(base.value()).emplace(name);
 
-    m_identities.emplace(name, std::set<ModuleValuePair>());
+    m_identities.emplace(name, std::set<identityRef_>());
 }
 
-void StaticSchema::getIdentSet(const ModuleValuePair& ident, std::set<ModuleValuePair>& res) const
+void StaticSchema::getIdentSet(const identityRef_& ident, std::set<identityRef_>& res) const
 {
     res.insert(ident);
     auto derivedIdentities = m_identities.at(ident);
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 67bd7b9..5184ae6 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -69,16 +69,15 @@
     void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type);
     void addList(const std::string& location, const std::string& name, const std::set<std::string>& keys);
     void addModule(const std::string& name);
-    void addIdentity(const std::optional<ModuleValuePair>& base, const ModuleValuePair& name);
+    void addIdentity(const std::optional<identityRef_>& base, const identityRef_& name);
 
 private:
     const std::unordered_map<std::string, NodeType>& children(const std::string& name) const;
-    void getIdentSet(const ModuleValuePair& ident, std::set<ModuleValuePair>& res) const;
+    void getIdentSet(const identityRef_& ident, std::set<identityRef_>& res) const;
     bool nodeExists(const std::string& location, const std::string& node) const;
 
     std::unordered_map<std::string, std::unordered_map<std::string, NodeType>> m_nodes;
     std::set<std::string> m_modules;
 
-    // FIXME: Change the template arguments to identityRef_
-    std::map<ModuleValuePair, std::set<ModuleValuePair>> m_identities;
+    std::map<identityRef_, std::set<identityRef_>> m_identities;
 };
diff --git a/tests/leaf_editing.cpp b/tests/leaf_editing.cpp
index 64abbf2..986139d 100644
--- a/tests/leaf_editing.cpp
+++ b/tests/leaf_editing.cpp
@@ -37,11 +37,11 @@
     schema->addLeaf("/", "mod:leafUint32", yang::Uint32{});
     schema->addLeaf("/", "mod:leafUint64", yang::Uint64{});
     schema->addLeaf("/", "mod:leafBinary", yang::Binary{});
-    schema->addIdentity(std::nullopt, ModuleValuePair{"mod", "food"});
-    schema->addIdentity(std::nullopt, ModuleValuePair{"mod", "vehicle"});
-    schema->addIdentity(ModuleValuePair{"mod", "food"}, ModuleValuePair{"mod", "pizza"});
-    schema->addIdentity(ModuleValuePair{"mod", "food"}, ModuleValuePair{"mod", "spaghetti"});
-    schema->addIdentity(ModuleValuePair{"mod", "pizza"}, ModuleValuePair{"pizza-module", "hawaii"});
+    schema->addIdentity(std::nullopt, identityRef_{"mod", "food"});
+    schema->addIdentity(std::nullopt, identityRef_{"mod", "vehicle"});
+    schema->addIdentity(identityRef_{"mod", "food"}, identityRef_{"mod", "pizza"});
+    schema->addIdentity(identityRef_{"mod", "food"}, identityRef_{"mod", "spaghetti"});
+    schema->addIdentity(identityRef_{"mod", "pizza"}, identityRef_{"pizza-module", "hawaii"});
     schema->addLeaf("/", "mod:foodIdentRef", yang::IdentityRef{schema->validIdentities("mod", "food")});
     schema->addLeaf("/", "mod:pizzaIdentRef", yang::IdentityRef{schema->validIdentities("mod", "pizza")});
     schema->addLeaf("/mod:contA", "mod:identInCont", yang::IdentityRef{schema->validIdentities("mod", "pizza")});