Merge changes I0d08d82a,Ie72ef296,I55447e43

* changes:
  Python: use the same order of definitions as in the abstract base class
  tests: run the Python test suite with the example-module enabled
  tests: register kill_daemons as a resource cleanup complementing start_daemons
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index 3af3ff0..c349e8c 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -12,6 +12,11 @@
     return this->m_path == b.m_path && this->m_data == b.m_data;
 }
 
+bool get_::operator==(const get_& b) const
+{
+    return this->m_path == b.m_path;
+}
+
 bool cd_::operator==(const cd_& b) const
 {
     return this->m_path == b.m_path;
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 33715a4..b9cf883 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -210,6 +210,18 @@
     }
 };
 
+struct leafListElementPath_class {
+    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);
+        if (ast.m_nodes.empty() || ast.m_nodes.back().m_suffix.type() != typeid(leafListElement_)) {
+            parserContext.m_errorMsg = "This is not a leaf list element.";
+            _pass(context) = false;
+        }
+    }
+};
+
 struct space_separator_class {
     template <typename T, typename Iterator, typename Context>
     void on_success(Iterator const&, Iterator const&, T&, Context const& context)
diff --git a/src/ast_path.cpp b/src/ast_path.cpp
index 0aa839c..b9b9b5a 100644
--- a/src/ast_path.cpp
+++ b/src/ast_path.cpp
@@ -26,6 +26,23 @@
 {
 }
 
+bool leafListElement_::operator==(const leafListElement_& b) const
+{
+    return this->m_name == b.m_name && this->m_value == b.m_value;
+}
+
+leafList_::leafList_() = default;
+
+leafList_::leafList_(const std::string& name)
+    : m_name(name)
+{
+}
+
+bool leafList_::operator==(const leafList_& b) const
+{
+    return this->m_name == b.m_name;
+}
+
 bool module_::operator==(const module_& b) const
 {
     return this->m_name == b.m_name;
@@ -142,6 +159,10 @@
         res << "]";
         return res.str();
     }
+    std::string operator()(const leafListElement_& node) const
+    {
+        return node.m_name + "[.=" + escapeListKeyString(leafDataToString(node.m_value)) + "]";
+    }
     std::string operator()(const nodeup_&) const
     {
         return "..";
@@ -202,6 +223,11 @@
         return list_{listElement.m_name};
     }
 
+    auto operator()(const leafListElement_& leafListElement) const
+    {
+        return leafList_{leafListElement.m_name};
+    }
+
     template <typename T>
     auto operator()(const T& suffix) const
     {
diff --git a/src/ast_path.hpp b/src/ast_path.hpp
index 966f771..f768461 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -46,6 +46,22 @@
     std::string m_name;
 };
 
+struct leafList_ {
+    leafList_();
+    leafList_(const std::string& name);
+
+    bool operator==(const leafList_& b) const;
+
+    std::string m_name;
+};
+
+struct leafListElement_ {
+    bool operator==(const leafListElement_& b) const;
+
+    std::string m_name;
+    leaf_data_ m_value;
+};
+
 struct listElement_ {
     listElement_() {}
     listElement_(const std::string& listName, const std::map<std::string, leaf_data_>& keys);
@@ -67,7 +83,7 @@
 
 struct schemaNode_ {
     boost::optional<module_> m_prefix;
-    boost::variant<container_, list_, nodeup_, leaf_> m_suffix;
+    boost::variant<container_, list_, nodeup_, leaf_, leafList_> m_suffix;
 
     schemaNode_();
     schemaNode_(decltype(m_suffix) node);
@@ -77,7 +93,7 @@
 
 struct dataNode_ {
     boost::optional<module_> m_prefix;
-    boost::variant<container_, listElement_, nodeup_, leaf_, list_> m_suffix;
+    boost::variant<container_, listElement_, nodeup_, leaf_, leafListElement_, leafList_, list_> m_suffix;
 
     dataNode_();
     dataNode_(decltype(m_suffix) node);
@@ -120,6 +136,7 @@
 
 BOOST_FUSION_ADAPT_STRUCT(container_, m_name)
 BOOST_FUSION_ADAPT_STRUCT(listElement_, m_name, m_keys)
+BOOST_FUSION_ADAPT_STRUCT(leafListElement_, m_name, m_value)
 BOOST_FUSION_ADAPT_STRUCT(module_, m_name)
 BOOST_FUSION_ADAPT_STRUCT(dataNode_, m_prefix, m_suffix)
 BOOST_FUSION_ADAPT_STRUCT(schemaNode_, m_prefix, m_suffix)
diff --git a/src/ast_values.cpp b/src/ast_values.cpp
index a381c1a..c3f2ce7 100644
--- a/src/ast_values.cpp
+++ b/src/ast_values.cpp
@@ -99,6 +99,8 @@
         return "(presence container)";
     case SpecialValue::List:
         return "(list)";
+    case SpecialValue::LeafList:
+        return "(leaflist)";
     }
 
     __builtin_unreachable();
diff --git a/src/ast_values.hpp b/src/ast_values.hpp
index 9ec8389..c1d8f63 100644
--- a/src/ast_values.hpp
+++ b/src/ast_values.hpp
@@ -50,6 +50,7 @@
 
 enum class SpecialValue {
     List,
+    LeafList,
     Container,
     PresenceContainer
 };
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index e70d9c9..6412037 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -48,6 +48,8 @@
     virtual void deletePresenceContainer(const std::string& path) = 0;
     virtual void createListInstance(const std::string& path) = 0;
     virtual void deleteListInstance(const std::string& path) = 0;
+    virtual void createLeafListInstance(const std::string& path) = 0;
+    virtual void deleteLeafListInstance(const std::string& path) = 0;
     virtual Tree executeRpc(const std::string& path, const Tree& input) = 0;
 
     virtual std::shared_ptr<Schema> schema() = 0;
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 1b4b49d..02d6666 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -53,10 +53,10 @@
     cd_::name >> space_separator > dataPath;
 
 auto const create_def =
-    create_::name >> space_separator > (presenceContainerPath | listInstancePath);
+    create_::name >> space_separator > (presenceContainerPath | listInstancePath | leafListElementPath);
 
 auto const delete_rule_def =
-    delete_::name >> space_separator > (presenceContainerPath | listInstancePath);
+    delete_::name >> space_separator > (presenceContainerPath | listInstancePath | leafListElementPath);
 
 auto const get_def =
     get_::name >> -(space_separator >> ((dataPathListEnd | dataPath) | (module >> "*")));
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index eb0fd66..909d863 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -6,6 +6,7 @@
  *
 */
 
+#include <boost/algorithm/string/predicate.hpp>
 #include <boost/mpl/for_each.hpp>
 #include <iostream>
 #include <sstream>
@@ -41,8 +42,19 @@
 void Interpreter::operator()(const get_& get) const
 {
     auto items = m_datastore.getItems(absolutePathFromCommand(get));
-    for (auto it : items) {
-        std::cout << it.first << " = " << leafDataToString(it.second) << std::endl;
+    for (auto it = items.begin(); it != items.end(); it++) {
+        auto [path, value] = *it;
+        if (value.type() == typeid(special_) && boost::get<special_>(value).m_value == SpecialValue::LeafList) {
+            auto leafListPrefix = path;
+            std::cout << path << " = " << leafDataToString(value) << std::endl;
+            ++it;
+            while (boost::starts_with(it->first, leafListPrefix)) {
+                std::cout << stripLeafListValueFromPath(it->first) << " = " << leafDataToString(it->second) << std::endl;
+                ++it;
+            }
+        } else {
+            std::cout << path << " = " << leafDataToString(value) << std::endl;
+        }
     }
 }
 
@@ -55,6 +67,8 @@
 {
     if (create.m_path.m_nodes.back().m_suffix.type() == typeid(listElement_))
         m_datastore.createListInstance(absolutePathFromCommand(create));
+    else if (create.m_path.m_nodes.back().m_suffix.type() == typeid(leafListElement_))
+        m_datastore.createLeafListInstance(absolutePathFromCommand(create));
     else
         m_datastore.createPresenceContainer(absolutePathFromCommand(create));
 }
@@ -63,6 +77,8 @@
 {
     if (delet.m_path.m_nodes.back().m_suffix.type() == typeid(container_))
         m_datastore.deletePresenceContainer(absolutePathFromCommand(delet));
+    else if (delet.m_path.m_nodes.back().m_suffix.type() == typeid(leafListElement_))
+        m_datastore.deleteLeafListInstance(absolutePathFromCommand(delet));
     else
         m_datastore.deleteListInstance(absolutePathFromCommand(delet));
 }
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 1340332..6fd3f69 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -38,9 +38,15 @@
         if (it->schema()->nodetype() == LYS_LIST) {
             res.emplace(stripXPathPrefix(it->path()), special_{SpecialValue::List});
         }
-        if (it->schema()->nodetype() == LYS_LEAF) {
+        if (it->schema()->nodetype() == LYS_LEAF || it->schema()->nodetype() == LYS_LEAFLIST) {
+            using namespace std::string_literals;
             libyang::Data_Node_Leaf_List leaf(it);
-            res.emplace(stripXPathPrefix(it->path()), leafValueFromValue(leaf.value(), leaf.leaf_type()->base()));
+            auto value = leafValueFromValue(leaf.value(), leaf.leaf_type()->base());
+            if (it->schema()->nodetype() == LYS_LEAFLIST) {
+                std::string strippedLeafListValue = stripLeafListValueFromPath(it->path());
+                res.emplace(stripXPathPrefix(strippedLeafListValue), special_{SpecialValue::LeafList});
+            }
+            res.emplace(stripXPathPrefix(it->path()), value);
         }
     }
 }
@@ -115,6 +121,19 @@
     doEditFromDataNode(node);
 }
 
+void NetconfAccess::createLeafListInstance(const std::string& path)
+{
+    auto node = m_schema->dataNodeFromPath(path);
+    doEditFromDataNode(node);
+}
+void NetconfAccess::deleteLeafListInstance(const std::string& path)
+{
+    auto node = m_schema->dataNodeFromPath(path);
+    auto list = *(node->find_path(path.c_str())->data().begin());
+    list->insert_attr(m_schema->getYangModule("ietf-netconf"), "operation", "delete");
+    doEditFromDataNode(node);
+}
+
 void NetconfAccess::doEditFromDataNode(std::shared_ptr<libyang::Data_Node> dataNode)
 {
     auto data = dataNode->print_mem(LYD_XML, 0);
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index 107fcd3..964fa54 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -39,6 +39,8 @@
     void deletePresenceContainer(const std::string& path) override;
     void createListInstance(const std::string& path) override;
     void deleteListInstance(const std::string& path) override;
+    void createLeafListInstance(const std::string& path) override;
+    void deleteLeafListInstance(const std::string& path) override;
     void commitChanges() override;
     void discardChanges() override;
     Tree executeRpc(const std::string& path, const Tree& input) override;
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 15655d7..ecd3bd7 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -17,6 +17,7 @@
 x3::rule<writable_leaf_path_class, dataPath_> const writableLeafPath = "writableLeafPath";
 x3::rule<presenceContainerPath_class, dataPath_> const presenceContainerPath = "presenceContainerPath";
 x3::rule<listInstancePath_class, dataPath_> const listInstancePath = "listInstancePath";
+x3::rule<leafListElementPath_class, dataPath_> const leafListElementPath = "leafListElementPath";
 x3::rule<initializePath_class, x3::unused_type> const initializePath = "initializePath";
 x3::rule<trailingSlash_class, TrailingSlash> const trailingSlash = "trailingSlash";
 x3::rule<absoluteStart_class, Scope> const absoluteStart = "absoluteStart";
@@ -26,6 +27,7 @@
 x3::rule<createKeySuggestions_class, x3::unused_type> const createKeySuggestions = "createKeySuggestions";
 x3::rule<createValueSuggestions_class, x3::unused_type> const createValueSuggestions = "createValueSuggestions";
 x3::rule<suggestKeysEnd_class, x3::unused_type> const suggestKeysEnd = "suggestKeysEnd";
+x3::rule<class leafListValue_class, leaf_data_> const leafListValue = "leafListValue";
 
 enum class NodeParserMode {
     CompleteDataNode,
@@ -56,6 +58,7 @@
     using type = dataNode_;
 };
 
+
 template <NodeParserMode PARSER_MODE>
 struct NodeParser : x3::parser<NodeParser<PARSER_MODE>> {
     using attribute_type = typename ModeToAttribute<PARSER_MODE>::type;
@@ -113,9 +116,16 @@
                     }
                     parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
                     break;
+                case yang::NodeTypes::LeafList:
+                    if constexpr (std::is_same<attribute_type, schemaNode_>()) {
+                        out.m_suffix = leafList_{child.second};
+                    } else {
+                        out.m_suffix = leafListElement_{child.second, {}};
+                    }
+                    parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
+                    break;
                 case yang::NodeTypes::Action:
                 case yang::NodeTypes::AnyXml:
-                case yang::NodeTypes::LeafList:
                 case yang::NodeTypes::Notification:
                 case yang::NodeTypes::Rpc:
                     continue;
@@ -172,6 +182,25 @@
                         }
                     }
                 }
+
+                if (attr.m_suffix.type() == typeid(leafListElement_)) {
+                    parserContext.m_tmpListKeyLeafPath.m_location = parserContext.currentSchemaPath();
+                    ModuleNodePair node{attr.m_prefix.flat_map([](const auto& it) {
+                                            return boost::optional<std::string>{it.m_name};
+                                        }),
+                                        boost::get<leafListElement_>(attr.m_suffix).m_name};
+                    parserContext.m_tmpListKeyLeafPath.m_node = node;
+                    res = leafListValue.parse(begin, end, ctx, rctx, boost::get<leafListElement_>(attr.m_suffix).m_value);
+
+                    if (!res) {
+                        if constexpr (PARSER_MODE == NodeParserMode::IncompleteDataNode) {
+                            res = true;
+                            attr.m_suffix = leafList_{boost::get<leafListElement_>(attr.m_suffix).m_name};
+                        } else {
+                            begin = saveIter;
+                        }
+                    }
+                }
             }
 
             if (res) {
@@ -189,7 +218,7 @@
 
 using schemaNode = NodeParser<NodeParserMode::SchemaNode>;
 using dataNode = NodeParser<NodeParserMode::CompleteDataNode>;
-using dataNodeAllowList = NodeParser<NodeParserMode::IncompleteDataNode>;
+using incompleteDataNode = NodeParser<NodeParserMode::IncompleteDataNode>;
 using pathCompletions = NodeParser<NodeParserMode::CompletionsOnly>;
 
 using AnyPath = boost::variant<schemaPath_, dataPath_>;
@@ -245,7 +274,7 @@
         if constexpr (PARSER_MODE == PathParserMode::DataPathListEnd || PARSER_MODE == PathParserMode::AnyPath) {
             if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
                 dataNode_ attrNodeList;
-                res = dataNodeAllowList{m_filterFunction}.parse(begin, end, ctx, rctx, attrNodeList);
+                res = incompleteDataNode{m_filterFunction}.parse(begin, end, ctx, rctx, attrNodeList);
                 if (res) {
                     attrData.m_nodes.push_back(attrNodeList);
                     // If the trailing slash matches, no more nodes are parsed.
@@ -295,6 +324,22 @@
 #pragma GCC diagnostic ignored "-Woverloaded-shift-op-parentheses"
 #endif
 
+struct SuggestLeafListEnd : x3::parser<SuggestLeafListEnd> {
+    using attribute_type = x3::unused_type;
+    template <typename It, typename Ctx, typename RCtx, typename Attr>
+    bool parse(It& begin, It, Ctx const& ctx, RCtx&, Attr&) const
+    {
+        auto& parserContext = x3::get<parser_context_tag>(ctx);
+        parserContext.m_completionIterator = begin;
+        parserContext.m_suggestions = {Completion{"]"}};
+
+        return true;
+    }
+} const suggestLeafListEnd;
+
+auto const leafListValue_def =
+    '[' >> leaf_data >> suggestLeafListEnd >> ']';
+
 auto const rest =
     x3::omit[x3::no_skip[+(x3::char_ - '/' - space_separator)]];
 
@@ -344,6 +389,9 @@
 auto const listInstancePath_def =
     dataPath;
 
+auto const leafListElementPath_def =
+    dataPath;
+
 // A "nothing" parser, which is used to indicate we tried to parse a path
 auto const initializePath_def =
     x3::eps;
@@ -360,9 +408,11 @@
 BOOST_SPIRIT_DEFINE(writableLeafPath)
 BOOST_SPIRIT_DEFINE(presenceContainerPath)
 BOOST_SPIRIT_DEFINE(listInstancePath)
+BOOST_SPIRIT_DEFINE(leafListElementPath)
 BOOST_SPIRIT_DEFINE(initializePath)
 BOOST_SPIRIT_DEFINE(createKeySuggestions)
 BOOST_SPIRIT_DEFINE(createValueSuggestions)
 BOOST_SPIRIT_DEFINE(suggestKeysEnd)
+BOOST_SPIRIT_DEFINE(leafListValue)
 BOOST_SPIRIT_DEFINE(absoluteStart)
 BOOST_SPIRIT_DEFINE(trailingSlash)
diff --git a/src/schema.cpp b/src/schema.cpp
index 2cfcbf2..e092569 100644
--- a/src/schema.cpp
+++ b/src/schema.cpp
@@ -46,3 +46,12 @@
         return false;
     }
 }
+
+bool Schema::isLeafList(const std::string& path) const
+{
+    try {
+        return nodeType(path) == yang::NodeTypes::LeafList;
+    } catch (InvalidNodeException&) {
+        return false;
+    }
+}
diff --git a/src/schema.hpp b/src/schema.hpp
index 0a9b2ff..995d921 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -58,6 +58,7 @@
     bool isLeaf(const schemaPath_& location, const ModuleNodePair& node) const;
     bool isList(const schemaPath_& location, const ModuleNodePair& node) const;
     bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const;
+    bool isLeafList(const std::string& path) const;
     virtual yang::NodeTypes nodeType(const std::string& path) const = 0;
     virtual yang::NodeTypes nodeType(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual bool isModule(const std::string& name) const = 0;
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index 738ce49..a1a5602 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -49,7 +49,7 @@
     assert(isList(location, node));
 
     const auto& child = children(locationString).at(fullNodeName(location, node));
-    const auto& list = boost::get<yang::list>(child.m_nodeType);
+    const auto& list = std::get<yang::list>(child.m_nodeType);
     return list.m_keys.find(key) != list.m_keys.end();
 }
 
@@ -59,7 +59,7 @@
     assert(isList(location, node));
 
     const auto& child = children(locationString).at(fullNodeName(location, node));
-    const auto& list = boost::get<yang::list>(child.m_nodeType);
+    const auto& list = std::get<yang::list>(child.m_nodeType);
     return list.m_keys;
 }
 
@@ -86,6 +86,13 @@
     m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
 }
 
+void StaticSchema::addLeafList(const std::string& location, const std::string& name, const yang::LeafDataType& type)
+{
+    m_nodes.at(location).emplace(name, NodeInfo{yang::leaflist{yang::TypeInfo{type, std::nullopt}}, yang::AccessType::Writable});
+    std::string key = joinPaths(location, name);
+    m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
+}
+
 void StaticSchema::addModule(const std::string& name)
 {
     m_modules.emplace(name);
@@ -120,14 +127,23 @@
 yang::TypeInfo StaticSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
 {
     std::string locationString = pathToSchemaString(location, Prefixes::Always);
-    return boost::get<yang::leaf>(children(locationString).at(fullNodeName(location, node)).m_nodeType).m_type;
+    auto nodeType = children(locationString).at(fullNodeName(location, node)).m_nodeType;
+    if (std::holds_alternative<yang::leaf>(nodeType)) {
+        return std::get<yang::leaf>(nodeType).m_type;
+    }
+
+    if (std::holds_alternative<yang::leaflist>(nodeType)) {
+        return std::get<yang::leaflist>(nodeType).m_type;
+    }
+
+    throw std::logic_error("StaticSchema::leafType: Path is not a leaf or a leaflist");
 }
 
 yang::TypeInfo StaticSchema::leafType(const std::string& path) const
 {
     auto locationString = stripLastNodeFromPath(path);
     auto node = lastNodeOfSchemaPath(path);
-    return boost::get<yang::leaf>(children(locationString).at(node).m_nodeType).m_type;
+    return std::get<yang::leaf>(children(locationString).at(node).m_nodeType).m_type;
 }
 
 std::set<ModuleNodePair> StaticSchema::availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const
@@ -178,6 +194,30 @@
     return res;
 }
 
+struct impl_nodeType {
+
+    yang::NodeTypes operator()(const yang::container& cont)
+    {
+        if (cont.m_presence == yang::ContainerTraits::Presence) {
+            return yang::NodeTypes::PresenceContainer;
+        }
+        return yang::NodeTypes::Container;
+    }
+    yang::NodeTypes operator()(const yang::list&)
+    {
+        return yang::NodeTypes::List;
+    }
+    yang::NodeTypes operator()(const yang::leaf&)
+    {
+        return yang::NodeTypes::Leaf;
+
+    }
+    yang::NodeTypes operator()(const yang::leaflist&)
+    {
+        return yang::NodeTypes::LeafList;
+    }
+};
+
 yang::NodeTypes StaticSchema::nodeType(const schemaPath_& location, const ModuleNodePair& node) const
 {
     std::string locationString = pathToSchemaString(location, Prefixes::Always);
@@ -185,23 +225,7 @@
     try {
         auto targetNode = children(locationString).at(fullName);
 
-        if (targetNode.m_nodeType.type() == typeid(yang::container)) {
-            if (boost::get<yang::container>(targetNode.m_nodeType).m_presence == yang::ContainerTraits::Presence) {
-                return yang::NodeTypes::PresenceContainer;
-            }
-            return yang::NodeTypes::Container;
-        }
-
-        if (targetNode.m_nodeType.type() == typeid(yang::list)) {
-            return yang::NodeTypes::List;
-        }
-
-        if (targetNode.m_nodeType.type() == typeid(yang::leaf)) {
-            return yang::NodeTypes::Leaf;
-        }
-
-        throw std::runtime_error{"YangSchema::nodeType: unsupported type"};
-
+        return std::visit(impl_nodeType{}, targetNode.m_nodeType);
     } catch (std::out_of_range&) {
         throw InvalidNodeException();
     }
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index b94dfa3..aab4383 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -30,6 +30,10 @@
     yang::TypeInfo m_type;
 };
 
+struct leaflist {
+    yang::TypeInfo m_type;
+};
+
 struct module {
 };
 
@@ -39,7 +43,7 @@
 };
 }
 
-using NodeType = boost::variant<yang::container, yang::list, yang::leaf, yang::module>;
+using NodeType = std::variant<yang::container, yang::list, yang::leaf, yang::leaflist>;
 
 struct NodeInfo {
     NodeType m_nodeType;
@@ -77,6 +81,7 @@
     std::set<identityRef_> validIdentities(std::string_view module, std::string_view value);
     void addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence = yang::ContainerTraits::None);
     void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type, const yang::AccessType accessType = yang::AccessType::Writable);
+    void addLeafList(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<identityRef_>& base, const identityRef_& name);
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 5b01d3e..4aaa156 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -191,11 +191,17 @@
     using namespace std::string_literals;
     Tree res;
 
-    auto fillMap = [&res](auto items) {
+    auto fillMap = [this, &res](auto items) {
         if (!items)
             return;
         for (unsigned int i = 0; i < items->val_cnt(); i++) {
-            res.emplace(items->val(i)->xpath(), leafValueFromVal(items->val(i)));
+            auto value = leafValueFromVal(items->val(i));
+            if (m_schema->isLeafList(items->val(i)->xpath())) {
+                res.emplace(items->val(i)->xpath(), special_{SpecialValue::LeafList});
+                res.emplace(items->val(i)->xpath() + "[.="s + escapeListKeyString(leafDataToString(value)) + "]", value);
+            } else {
+                res.emplace(items->val(i)->xpath(), value);
+            }
         }
     };
 
@@ -242,6 +248,24 @@
     }
 }
 
+void SysrepoAccess::createLeafListInstance(const std::string& path)
+{
+    try {
+        m_session->set_item(path.c_str());
+    } catch (sysrepo::sysrepo_exception& ex) {
+        reportErrors();
+    }
+}
+
+void SysrepoAccess::deleteLeafListInstance(const std::string& path)
+{
+    try {
+        m_session->delete_item(path.c_str());
+    } catch (sysrepo::sysrepo_exception& ex) {
+        reportErrors();
+    }
+}
+
 void SysrepoAccess::createListInstance(const std::string& path)
 {
     try {
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index 1d72525..26d6c80 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -34,6 +34,8 @@
     void deletePresenceContainer(const std::string& path) override;
     void createListInstance(const std::string& path) override;
     void deleteListInstance(const std::string& path) override;
+    void createLeafListInstance(const std::string& path) override;
+    void deleteLeafListInstance(const std::string& path) override;
     Tree executeRpc(const std::string& path, const Tree& input) override;
 
     std::shared_ptr<Schema> schema() override;
diff --git a/src/utils.cpp b/src/utils.cpp
index 3543787..941fa4e 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -228,3 +228,10 @@
 {
     return boost::apply_visitor(getSchemaPathVisitor(), path);
 }
+
+std::string stripLeafListValueFromPath(const std::string& path)
+{
+    auto res = path;
+    res.erase(res.find_last_of('['));
+    return res;
+}
diff --git a/src/utils.hpp b/src/utils.hpp
index 781170d..ecbf919 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -25,3 +25,4 @@
 std::string fullNodeName(const dataPath_& location, const ModuleNodePair& pair);
 std::string leafDataToString(const leaf_data_ value);
 schemaPath_ anyPathToSchemaPath(const boost::variant<dataPath_, schemaPath_, module_>& path);
+std::string stripLeafListValueFromPath(const std::string& path);
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 132c25f..48b3724 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -201,10 +201,11 @@
 }
 }
 
+template <typename NodeType>
 yang::TypeInfo YangSchema::impl_leafType(const libyang::S_Schema_Node& node) const
 {
     using namespace std::string_literals;
-    auto leaf = std::make_shared<libyang::Schema_Node_Leaf>(node);
+    auto leaf = std::make_shared<NodeType>(node);
     auto leafUnits = leaf->units();
     std::function<yang::TypeInfo(std::shared_ptr<libyang::Type>)> resolveType;
     resolveType = [this, &resolveType, leaf, leafUnits] (std::shared_ptr<libyang::Type> type) -> yang::TypeInfo {
@@ -293,12 +294,28 @@
 
 yang::TypeInfo YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
 {
-    return impl_leafType(getSchemaNode(location, node));
+    auto lyNode = getSchemaNode(location, node);
+    switch (lyNode->nodetype()) {
+    case LYS_LEAF:
+        return impl_leafType<libyang::Schema_Node_Leaf>(lyNode);
+    case LYS_LEAFLIST:
+        return impl_leafType<libyang::Schema_Node_Leaflist>(lyNode);
+    default:
+        throw std::logic_error("YangSchema::leafType: type must be leaf or leaflist");
+    }
 }
 
 yang::TypeInfo YangSchema::leafType(const std::string& path) const
 {
-    return impl_leafType(getSchemaNode(path));
+    auto lyNode = getSchemaNode(path);
+    switch (lyNode->nodetype()) {
+    case LYS_LEAF:
+        return impl_leafType<libyang::Schema_Node_Leaf>(lyNode);
+    case LYS_LEAFLIST:
+        return impl_leafType<libyang::Schema_Node_Leaflist>(lyNode);
+    default:
+        throw std::logic_error("YangSchema::leafType: type must be leaf or leaflist");
+    }
 }
 
 std::optional<std::string> YangSchema::leafTypeName(const std::string& path) const
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index a4c0b90..5b2db04 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -70,6 +70,7 @@
     std::shared_ptr<libyang::Module> getYangModule(const std::string& name);
 
 private:
+    template <typename NodeType>
     yang::TypeInfo impl_leafType(const std::shared_ptr<libyang::Schema_Node>& node) const;
     std::set<std::string> modules() const;
 
diff --git a/submodules/dependencies b/submodules/dependencies
index 676f63b..ab68d53 160000
--- a/submodules/dependencies
+++ b/submodules/dependencies
@@ -1 +1 @@
-Subproject commit 676f63bcbf2a14140bf303a51b1fa73d9b8f27d2
+Subproject commit ab68d53de22d1ce6ead243a612538e6244522ff6
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 088fc4b..e11eec5 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -386,6 +386,36 @@
         REQUIRE(datastore.getItems(xpath) == expected);
     }
 
+    SECTION("leaf list")
+    {
+        DatastoreAccess::Tree expected;
+        REQUIRE_CALL(mock, write("/example-schema:addresses", std::nullopt, "0.0.0.0"s));
+        REQUIRE_CALL(mock, write("/example-schema:addresses", std::nullopt, "127.0.0.1"s));
+        datastore.createLeafListInstance("/example-schema:addresses[.='0.0.0.0']");
+        datastore.createLeafListInstance("/example-schema:addresses[.='127.0.0.1']");
+        datastore.commitChanges();
+        expected = {
+            {"/example-schema:addresses", special_{SpecialValue::LeafList}},
+            {"/example-schema:addresses[.='0.0.0.0']", "0.0.0.0"s},
+            {"/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s},
+        };
+        REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
+
+        REQUIRE_CALL(mock, write("/example-schema:addresses", "0.0.0.0"s, std::nullopt));
+        datastore.deleteLeafListInstance("/example-schema:addresses[.='0.0.0.0']");
+        datastore.commitChanges();
+        expected = {
+            {"/example-schema:addresses", special_{SpecialValue::LeafList}},
+            {"/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s},
+        };
+        REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
+
+        REQUIRE_CALL(mock, write("/example-schema:addresses", "127.0.0.1"s, std::nullopt));
+        datastore.deleteLeafListInstance("/example-schema:addresses[.='127.0.0.1']");
+        datastore.commitChanges();
+        expected = {};
+        REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
+    }
 
     SECTION("copying data from startup refreshes the data")
     {
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
index 8798973..ac75875 100644
--- a/tests/datastoreaccess_mock.hpp
+++ b/tests/datastoreaccess_mock.hpp
@@ -23,6 +23,8 @@
     IMPLEMENT_MOCK2(setLeaf);
     IMPLEMENT_MOCK1(createPresenceContainer);
     IMPLEMENT_MOCK1(deletePresenceContainer);
+    IMPLEMENT_MOCK1(createLeafListInstance);
+    IMPLEMENT_MOCK1(deleteLeafListInstance);
     IMPLEMENT_MOCK1(createListInstance);
     IMPLEMENT_MOCK1(deleteListInstance);
     IMPLEMENT_MOCK2(executeRpc);
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index ef9d02d..3f32a87 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -247,4 +247,8 @@
     leaf dummy {
         type empty;
     }
+
+    leaf-list addresses {
+        type string;
+    }
 }
diff --git a/tests/list_manipulation.cpp b/tests/list_manipulation.cpp
index 15f2f04..974d806 100644
--- a/tests/list_manipulation.cpp
+++ b/tests/list_manipulation.cpp
@@ -11,11 +11,13 @@
 
 TEST_CASE("list manipulation")
 {
+    using namespace std::string_literals;
     auto schema = std::make_shared<StaticSchema>();
     schema->addModule("mod");
     schema->addList("/", "mod:list", {"number"});
     schema->addLeaf("/mod:list", "mod:number", yang::Int32{});
     schema->addLeaf("/mod:list", "mod:leafInList", yang::String{});
+    schema->addLeafList("/", "mod:addresses", yang::String{});
     Parser parser(schema);
     std::string input;
     std::ostringstream errorStream;
@@ -31,6 +33,13 @@
             expectedPath.m_nodes.push_back(dataNode_{module_{"mod"}, listElement_("list", keys)});
         }
 
+        SECTION("create mod:addresses['0.0.0.0']")
+        {
+            input = "mod:addresses['0.0.0.0']";
+            expectedPath.m_nodes.push_back(dataNode_{module_{"mod"}, leafListElement_{"addresses", "0.0.0.0"s}});
+        }
+
+
         command_ parsedCreate = parser.parseCommand("create " + input, errorStream);
         command_ parsedDelete = parser.parseCommand("delete " + input, errorStream);
         create_ expectedCreate;
@@ -42,4 +51,17 @@
         REQUIRE(boost::get<create_>(parsedCreate) == expectedCreate);
         REQUIRE(boost::get<delete_>(parsedDelete) == expectedDelete);
     }
+
+    SECTION("retrieving all leaflist instances")
+    {
+        dataPath_ expected;
+        input = "get mod:addresses";
+        expected.m_nodes.push_back(dataNode_{module_{"mod"}, leafList_{"addresses"}});
+
+        get_ expectedGet;
+        expectedGet.m_path = expected;
+        command_ commandGet = parser.parseCommand(input, errorStream);
+        REQUIRE(commandGet.type() == typeid(get_));
+        REQUIRE(boost::get<get_>(commandGet) == expectedGet);
+    }
 }
diff --git a/tests/path_utils.cpp b/tests/path_utils.cpp
index 3a3fb72..6765001 100644
--- a/tests/path_utils.cpp
+++ b/tests/path_utils.cpp
@@ -28,6 +28,21 @@
             path.m_nodes.push_back(dataNode_{module_{"example-schema"}, listElement_{"twoKeyList", {{"first", std::string{"a"}}, {"second", std::string{"b"}}}}});
             expected += "example-schema:twoKeyList[first='a'][second='b']";
         }
+
+        SECTION("example-schema:addresses[.='0.0.0.0']")
+        {
+            SECTION("absolute")
+            {
+                path.m_scope = Scope::Absolute;
+                expected += "/";
+            }
+            SECTION("relative")
+            {
+                path.m_scope = Scope::Relative;
+            }
+            path.m_nodes.push_back(dataNode_{module_{"example-schema"}, leafListElement_{"addresses", std::string{"0.0.0.0"}}});
+            expected += "example-schema:addresses[.='0.0.0.0']";
+        }
         REQUIRE(pathToDataString(path, Prefixes::WhenNeeded) == expected);
     }
 }
diff --git a/tests/yang.cpp b/tests/yang.cpp
index 3d42c5e..71cb69d 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -419,6 +419,9 @@
         status obsolete;
     }
 
+    leaf-list addresses {
+        type string;
+    }
 })";
 
 namespace std {
@@ -809,6 +812,14 @@
                 }};
             }
 
+            SECTION("addresses")
+            {
+                node.first = "example-schema";
+                node.second = "addresses";
+                type.emplace<yang::String>();
+            }
+
+
             REQUIRE(ys.leafType(path, node) == type);
         }
         SECTION("availableNodes")
@@ -851,6 +862,7 @@
                         {"example-schema"s, "myRpc"},
                         {"example-schema"s, "systemStats"},
                         {"example-schema"s, "dummyLeaf"},
+                        {"example-schema"s, "addresses"},
                         {"example-schema"s, "subLeaf"}};
                 }
 
@@ -894,6 +906,7 @@
                         {"example-schema"s, "activeMappedPort"},
                         {"example-schema"s, "activeNumber"},
                         {"example-schema"s, "activePort"},
+                        {"example-schema"s, "addresses"},
                         {"example-schema"s, "another-duration"},
                         {"example-schema"s, "b"},
                         {"example-schema"s, "carry"},
@@ -943,6 +956,7 @@
                         {boost::none, "/example-schema:_list/contInList"},
                         {boost::none, "/example-schema:_list/number"},
                         {boost::none, "/example-schema:a"},
+                        {boost::none, "/example-schema:addresses"},
                         {boost::none, "/example-schema:a/a2"},
                         {boost::none, "/example-schema:a/a2/a3"},
                         {boost::none, "/example-schema:a/leafa"},
@@ -1129,6 +1143,8 @@
                 expectedType.emplace<yang::Int32>();
                 expectedUnits = "vt";
             }
+            auto nodeType = ys.nodeType(pathToSchemaString(path, Prefixes::WhenNeeded));
+            REQUIRE((nodeType == yang::NodeTypes::Leaf || nodeType == yang::NodeTypes::LeafList));
             REQUIRE(ys.leafType(pathToSchemaString(path, Prefixes::WhenNeeded)) == yang::TypeInfo{expectedType, expectedUnits});
         }