Rework leaf_data

The previous implementation of leaf_data used a grammar, where all of
the leaf_data_* parsers were tried one after another and then in each of
their on_success handlers, the type of leaf was checked against the
attribute of the parser. This leads to two problems:
1) Ambiguity. Example: 1 can be parsed as any of the integral parsers.
This can lead to a problems inside the parser (ambiguity is the reason
the parser doesn't work in newer Boost versions).
2) Order of the alternatives. Because the alternative parser can only be
defined at compile-time, there is no way to change the order of the
alternatives. Right now, this isn't a problem, since a leaf can only
have one type, but I'm going to need a way to reorder the parsers to
implement unions.

The new leaf_data parser is implemented by creating an entirely new type
of parser. A parser (as far as Spirit is concerned) is a class that
inherits from x3::parser and implements parse(), which returns whether
the parser passed or not. In this method I have access to ParserContext.
That means I can dynamically choose which parser is going to be used
according to the return value of Schema::leafType.

Another advantage is that now I don't need on_success handlers for basic
types and I can use the elemental parsers directly. Types like enum need
extra validation, so they keep their handlers. If I ever need extra
validation on more types, I can easily add some of the handlers back.

I also moved the createSetSuggestions handler to leaf_data.hpp since
it's directly connected to leaf_data (even the rule is defined in this
file).

Side note: the parser could have been implemented by using an
implementation of the lazy parser from here
https://github.com/boostorg/spirit/issues/530.
However, a Spirit developer suggested I just inherit from x3::parser and
use that.

Side note 2: all of the completion creators can be implemented in a
similar way, and that brings some advantages too.

Side note 3: the parser for paths can be rewritten in a similar way to
this, but right now it's not needed. When Boost gets updated, the
rewrite will happen then.

Change-Id: Ia29c1895833b811a45873f8490da9c64fa4742eb
diff --git a/src/leaf_data.hpp b/src/leaf_data.hpp
index cdff9f5..bbacbea 100644
--- a/src/leaf_data.hpp
+++ b/src/leaf_data.hpp
@@ -14,47 +14,90 @@
 #include "schema.hpp"
 namespace x3 = boost::spirit::x3;
 
-using x3::char_;
-using x3::double_;
-using x3::int8;
-using x3::int16;
-using x3::int32;
-using x3::int64;
-using x3::uint8;
-using x3::uint16;
-using x3::uint32;
-using x3::uint64;
+template <yang::LeafDataTypes TYPE>
+struct leaf_data_class;
 
-x3::rule<leaf_data_class, leaf_data_> const leaf_data = "leaf_data";
-x3::rule<leaf_data_enum_class, enum_> const leaf_data_enum = "leaf_data_enum";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Decimal>, double> const leaf_data_decimal = "leaf_data_decimal";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Bool>, bool> const leaf_data_bool = "leaf_data_bool";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Int8>, int8_t> const leaf_data_int8 = "leaf_data_int8";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Uint8>, uint8_t> const leaf_data_uint8 = "leaf_data_uint8";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Int16>, int16_t> const leaf_data_int16 = "leaf_data_int16";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Uint16>, uint16_t> const leaf_data_uint16 = "leaf_data_uint16";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Int32>, int32_t> const leaf_data_int32 = "leaf_data_int32";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Uint32>, uint32_t> const leaf_data_uint32 = "leaf_data_uint32";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Int64>, int64_t> const leaf_data_int64 = "leaf_data_int64";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Uint64>, uint64_t> const leaf_data_uint64 = "leaf_data_uint64";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::String>, std::string> const leaf_data_string = "leaf_data_string";
-x3::rule<leaf_data_binary_data_class, std::string> const leaf_data_binary_data = "leaf_data_binary_data";
-x3::rule<leaf_data_base_class<yang::LeafDataTypes::Binary>, binary_> const leaf_data_binary = "leaf_data_binary";
-x3::rule<leaf_data_identityRef_data_class, identityRef_> const leaf_data_identityRef_data = "leaf_data_identityRef_data";
-x3::rule<leaf_data_identityRef_class, identityRef_> const leaf_data_identityRef = "leaf_data_identityRef";
+template <yang::LeafDataTypes TYPE>
+struct createSetSuggestions_class {
+    std::set<std::string> getSuggestions(const ParserContext& ctx, const Schema& schema) const;
+
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        const Schema& schema = parserContext.m_schema;
+
+        // Only generate completions if the type is correct so that we don't
+        // overwrite some other completions.
+        if (schema.leafType(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node) == TYPE) {
+            parserContext.m_completionIterator = begin;
+            auto suggestions = getSuggestions(parserContext, schema);
+            std::set<Completion> res;
+            std::transform(suggestions.begin(), suggestions.end(), std::inserter(res, res.end()), [](auto it) { return Completion{it}; });
+            parserContext.m_suggestions = res;
+        }
+    }
+};
+
+template <>
+struct leaf_data_class<yang::LeafDataTypes::Enum> {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
+    {
+        if (_pass(context) == false)
+            return;
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        auto& schema = parserContext.m_schema;
+
+        if (!schema.leafEnumHasValue(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node, ast.m_value)) {
+            _pass(context) = false;
+            parserContext.m_errorMsg = "leaf data type mismatch: Expected an enum here. Allowed values:";
+            for (const auto& it : schema.enumValues(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node)) {
+                parserContext.m_errorMsg += " " + it;
+            }
+        }
+    }
+};
+
+template <>
+struct leaf_data_class<yang::LeafDataTypes::IdentityRef> {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        auto& schema = parserContext.m_schema;
+
+        ModuleValuePair pair;
+        if (ast.m_prefix) {
+            pair.first = ast.m_prefix.get().m_name;
+        }
+        pair.second = ast.m_value;
+
+        if (!schema.leafIdentityIsValid(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node, pair)) {
+            _pass(context) = false;
+        }
+    }
+};
+
+x3::rule<leaf_data_class<yang::LeafDataTypes::Enum>, enum_> const leaf_data_enum = "leaf_data_enum";
+x3::rule<leaf_data_class<yang::LeafDataTypes::IdentityRef>, identityRef_> const leaf_data_identityRef = "leaf_data_identityRef";
+x3::rule<struct leaf_data_class<yang::LeafDataTypes::Binary>, binary_> const leaf_data_binary = "leaf_data_binary";
+x3::rule<struct leaf_data_class<yang::LeafDataTypes::Decimal>, double> const leaf_data_decimal = "leaf_data_decimal";
+x3::rule<struct leaf_data_class<yang::LeafDataTypes::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";
 
 x3::rule<createSetSuggestions_class<yang::LeafDataTypes::Enum>, x3::unused_type> const createEnumSuggestions = "createEnumSuggestions";
 x3::rule<createSetSuggestions_class<yang::LeafDataTypes::IdentityRef>, x3::unused_type> const createIdentitySuggestions = "createIdentitySuggestions";
 
+using x3::char_;
+
 auto const createEnumSuggestions_def =
     x3::eps;
 
 auto const leaf_data_enum_def =
     createEnumSuggestions >> +char_;
 
-auto const leaf_data_decimal_def =
-    double_;
-
 struct bool_symbol_table : x3::symbols<bool> {
     bool_symbol_table()
     {
@@ -62,31 +105,16 @@
         ("true", true)
         ("false", false);
     }
-} const bool_rule;
+} const bool_symbols;
 
-auto const leaf_data_bool_def =
-    bool_rule;
-auto const leaf_data_int8_def =
-    int8;
-auto const leaf_data_int16_def =
-    int16;
-auto const leaf_data_int32_def =
-    int32;
-auto const leaf_data_int64_def =
-    int64;
-auto const leaf_data_uint8_def =
-    uint8;
-auto const leaf_data_uint16_def =
-    uint16;
-auto const leaf_data_uint32_def =
-    uint32;
-auto const leaf_data_uint64_def =
-    uint64;
 auto const leaf_data_string_def =
     '\'' >> *(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_('=');
 
@@ -102,35 +130,68 @@
 auto const leaf_data_identityRef_def =
     createIdentitySuggestions >> leaf_data_identityRef_data;
 
-auto const leaf_data_def =
-x3::no_skip[x3::expect[
-    leaf_data_enum |
-    leaf_data_decimal |
-    leaf_data_bool |
-    leaf_data_int8 |
-    leaf_data_int16 |
-    leaf_data_int32 |
-    leaf_data_int64 |
-    leaf_data_uint8 |
-    leaf_data_uint16 |
-    leaf_data_uint32 |
-    leaf_data_uint64 |
-    leaf_data_binary |
-    leaf_data_identityRef |
-    leaf_data_string]];
+struct LeafData : x3::parser<LeafData> {
+    using attribute_type = leaf_data_;
 
-BOOST_SPIRIT_DEFINE(leaf_data)
+    // TODO: Can this be placed in a .cpp file?
+    template <typename It, typename Ctx, typename RCtx, typename Attr>
+    bool parse(It& first, It last, Ctx const& ctx, RCtx& rctx, Attr& attr) const
+    {
+        ParserContext& parserContext = x3::get<parser_context_tag>(ctx);
+        const Schema& schema = parserContext.m_schema;
+        auto type = schema.leafType(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node);
+
+        std::function<bool(yang::LeafDataTypes)> parse_impl = [&](auto type) {
+            switch (type) {
+            case yang::LeafDataTypes::Binary:
+                return leaf_data_binary.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Bool:
+                return bool_symbols.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Decimal:
+                return x3::double_.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Uint8:
+                return x3::uint8.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Uint16:
+                return x3::uint16.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Uint32:
+                return x3::uint32.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Uint64:
+                return x3::uint64.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Int8:
+                return x3::int8.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Int16:
+                return x3::int16.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Int32:
+                return x3::int32.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Int64:
+                return x3::int64.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::String:
+                return leaf_data_string.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::Enum:
+                return leaf_data_enum.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::IdentityRef:
+                return leaf_data_identityRef.parse(first, last, ctx, rctx, attr);
+            case yang::LeafDataTypes::LeafRef:
+                auto actualType = schema.leafrefBaseType(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node);
+                return parse_impl(actualType);
+            }
+            __builtin_unreachable();
+        };
+        auto pass = parse_impl(type);
+
+        if (!pass) {
+            if (parserContext.m_errorMsg.empty()) {
+                parserContext.m_errorMsg = "leaf data type mismatch: Expected " +
+                    leafDataTypeToString(schema.leafType(parserContext.m_tmpListKeyLeafPath.m_location, parserContext.m_tmpListKeyLeafPath.m_node)) + " here:";
+            }
+        }
+        return pass;
+    }
+};
+
+auto const leaf_data = x3::no_skip[std::move(LeafData())];
+
 BOOST_SPIRIT_DEFINE(leaf_data_enum)
-BOOST_SPIRIT_DEFINE(leaf_data_decimal)
-BOOST_SPIRIT_DEFINE(leaf_data_bool)
-BOOST_SPIRIT_DEFINE(leaf_data_int8)
-BOOST_SPIRIT_DEFINE(leaf_data_int16)
-BOOST_SPIRIT_DEFINE(leaf_data_int32)
-BOOST_SPIRIT_DEFINE(leaf_data_int64)
-BOOST_SPIRIT_DEFINE(leaf_data_uint8)
-BOOST_SPIRIT_DEFINE(leaf_data_uint16)
-BOOST_SPIRIT_DEFINE(leaf_data_uint32)
-BOOST_SPIRIT_DEFINE(leaf_data_uint64)
 BOOST_SPIRIT_DEFINE(leaf_data_string)
 BOOST_SPIRIT_DEFINE(leaf_data_binary_data)
 BOOST_SPIRIT_DEFINE(leaf_data_binary)