Add parser support for bits

Change-Id: I8f36a2639d3f4911c2fb825dd3bf28956886a9a6
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index b863640..d0c0985 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -299,6 +299,7 @@
 BOOST_FUSION_ADAPT_STRUCT(delete_, m_path)
 BOOST_FUSION_ADAPT_STRUCT(set_, m_path, m_data)
 BOOST_FUSION_ADAPT_STRUCT(enum_, m_value)
+BOOST_FUSION_ADAPT_STRUCT(bits_, m_bits)
 BOOST_FUSION_ADAPT_STRUCT(binary_, m_value)
 BOOST_FUSION_ADAPT_STRUCT(identityRef_, m_prefix, m_value)
 BOOST_FUSION_ADAPT_STRUCT(commit_)
diff --git a/src/leaf_data.hpp b/src/leaf_data.hpp
index fb490c7..964c154 100644
--- a/src/leaf_data.hpp
+++ b/src/leaf_data.hpp
@@ -154,6 +154,31 @@
     {
         return std::visit(*this, leafRef.m_targetType->m_type);
     }
+    bool operator()(const yang::Bits& bits) const
+    {
+        parserContext.m_suggestions.clear();
+        x3::symbols<std::string> parser;
+        for (const auto& bit : bits.m_allowedValues) {
+            parser.add(bit, bit);
+            parserContext.m_suggestions.insert(Completion{bit});
+        }
+
+        std::vector<std::string> bitsRes;
+
+        do {
+            std::string bit;
+            auto pass = parser.parse(first, last, ctx, rctx, bit);
+            if (pass) {
+                bitsRes.push_back(bit);
+                parser.remove(bit);
+                parserContext.m_suggestions.erase(Completion{bit});
+            }
+        } while (space_separator.parse(first, last, ctx, rctx, x3::unused));
+
+        attr = bits_{bitsRes};
+
+        return true;
+    }
     bool operator()(const yang::Union& unionInfo) const
     {
         return std::any_of(unionInfo.m_unionTypes.begin(), unionInfo.m_unionTypes.end(), [this](const auto& type) {
diff --git a/src/leaf_data_type.cpp b/src/leaf_data_type.cpp
index b7a3876..fe6fe04 100644
--- a/src/leaf_data_type.cpp
+++ b/src/leaf_data_type.cpp
@@ -104,4 +104,8 @@
 {
     return true;
 }
+bool Bits::operator==(const Bits& other) const
+{
+    return this->m_allowedValues == other.m_allowedValues;
+}
 }
diff --git a/src/leaf_data_type.hpp b/src/leaf_data_type.hpp
index 288f765..98cb12e 100644
--- a/src/leaf_data_type.hpp
+++ b/src/leaf_data_type.hpp
@@ -65,6 +65,10 @@
     bool operator==(const IdentityRef& other) const;
     std::set<identityRef_> m_allowedValues;
 };
+struct Bits {
+    bool operator==(const Bits& other) const;
+    std::set<std::string> m_allowedValues;
+};
 struct LeafRef;
 struct Union;
 using LeafDataType = std::variant<
@@ -83,6 +87,7 @@
     yang::Binary,
     yang::Empty,
     yang::IdentityRef,
+    yang::Bits,
     yang::LeafRef,
     yang::Union
 >;
diff --git a/src/utils.cpp b/src/utils.cpp
index 11a97b4..afb291e 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -135,6 +135,14 @@
         });
         return ss.str();
     }
+    std::string operator()(const yang::Bits& type)
+    {
+        std::ostringstream ss;
+        ss << "bits {";
+        std::copy(type.m_allowedValues.begin(), type.m_allowedValues.end(), std::experimental::make_ostream_joiner(ss, ", "));
+        ss << "}";
+        return ss.str();
+    }
 };
 
 std::string leafDataTypeToString(const yang::LeafDataType& type)
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 681861e..8793dd4 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -249,6 +249,15 @@
         case LY_TYPE_LEAFREF:
             resType.emplace<yang::LeafRef>(::leafrefPath(type), std::make_unique<yang::TypeInfo>(leafType(::leafrefPath(type))));
             break;
+        case LY_TYPE_BITS:
+            {
+                auto resBits = yang::Bits{};
+                for (const auto& bit : type->info()->bits()->bit()) {
+                    resBits.m_allowedValues.emplace(bit->name());
+                }
+                resType.emplace<yang::Bits>(std::move(resBits));
+                break;
+            }
         case LY_TYPE_UNION:
             {
                 auto resUnion = yang::Union{};
diff --git a/tests/leaf_editing.cpp b/tests/leaf_editing.cpp
index 53194c0..7b203f4 100644
--- a/tests/leaf_editing.cpp
+++ b/tests/leaf_editing.cpp
@@ -71,6 +71,8 @@
     schema->addLeaf("/", "mod:dummy", yang::Empty{});
     schema->addLeaf("/", "mod:readonly", yang::Int32{}, yang::AccessType::ReadOnly);
 
+    schema->addLeaf("/", "mod:flags", yang::Bits{{"carry", "sign"}});
+
     Parser parser(schema);
     std::string input;
     std::ostringstream errorStream;
@@ -457,6 +459,33 @@
                 expected.m_path.m_nodes.emplace_back(module_{"mod"}, leaf_("dummy"));
                 expected.m_data = empty_{};
             }
+
+            SECTION("bits")
+            {
+                input = "set mod:flags ";
+                decltype(bits_::m_bits) bits;
+                SECTION("<nothing>") {
+                    bits = {};
+                }
+                SECTION("carry") {
+                    input += "carry";
+                    bits = {"carry"};
+                }
+                SECTION("sign") {
+                    input += "sign";
+                    bits = {"sign"};
+                }
+                SECTION("carry sign") {
+                    input += "carry sign";
+                    bits = {"carry", "sign"};
+                }
+                SECTION("sign carry") {
+                    input += "sign carry";
+                    bits = {"sign", "carry"};
+                }
+                expected.m_path.m_nodes.emplace_back(module_{"mod"}, leaf_("flags"));
+                expected.m_data = bits_{bits};
+            }
         }
 
         command_ command = parser.parseCommand(input, errorStream);
@@ -614,6 +643,16 @@
             input = "set mod:readonly 123";
         }
 
+        SECTION("nonexistent bits")
+        {
+            input = "set mod:flags daw";
+        }
+
+        SECTION("same bit more than once")
+        {
+            input = "set mod:flags carry carry";
+        }
+
         REQUIRE_THROWS_AS(parser.parseCommand(input, errorStream), InvalidCommandException);
         REQUIRE(errorStream.str().find(expectedError) != std::string::npos);
     }
diff --git a/tests/yang.cpp b/tests/yang.cpp
index f226778..93e5925 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -429,6 +429,14 @@
     leaf-list addresses {
         type string;
     }
+
+    leaf flagBits {
+        type bits {
+            bit carry;
+            bit sign;
+            bit overflow;
+        }
+    }
 })";
 
 TEST_CASE("yangschema")
@@ -720,6 +728,13 @@
                 type.emplace<yang::String>();
             }
 
+            SECTION("flagBits")
+            {
+                node.first = "example-schema";
+                node.second = "flagBits";
+                type = yang::Bits{{"carry", "sign", "overflow"}};
+            }
+
 
             REQUIRE(ys.leafType(path, node) == type);
         }
@@ -764,7 +779,8 @@
                         {"example-schema"s, "systemStats"},
                         {"example-schema"s, "dummyLeaf"},
                         {"example-schema"s, "addresses"},
-                        {"example-schema"s, "subLeaf"}};
+                        {"example-schema"s, "subLeaf"},
+                        {"example-schema"s, "flagBits"}};
                 }
 
                 SECTION("example-schema:a")
@@ -819,6 +835,7 @@
                         {"example-schema"s, "ethernet"},
                         {"example-schema"s, "foodDrinkIdentLeaf"},
                         {"example-schema"s, "foodIdentLeaf"},
+                        {"example-schema"s, "flagBits"},
                         {"example-schema"s, "interrupt"},
                         {"example-schema"s, "leafBool"},
                         {"example-schema"s, "leafDecimal"},
@@ -875,6 +892,8 @@
                         {boost::none, "/example-schema:direction"},
                         {boost::none, "/example-schema:duration"},
                         {boost::none, "/example-schema:dummyLeaf"},
+                        {boost::none, "/example-schema:flagBits"},
+                        {boost::none, "/example-schema:foodDrinkIdentLeaf"},
                         {boost::none, "/example-schema:foodDrinkIdentLeaf"},
                         {boost::none, "/example-schema:foodIdentLeaf"},
                         {boost::none, "/example-schema:interface/caseEthernet/ethernet"},