Allow data path to end with a list for get and ls

Change-Id: I3facc8315fa6192da4318012a85121de37e7314b
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index 91380e7..787fb94 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -40,7 +40,7 @@
 struct ls_ : x3::position_tagged {
     bool operator==(const ls_& b) const;
     std::vector<LsOption> m_options;
-    boost::optional<dataPath_> m_path;
+    boost::optional<boost::variant<dataPath_, schemaPath_>> m_path;
 };
 
 struct cd_ : x3::position_tagged {
@@ -70,7 +70,7 @@
 
 struct get_ : x3::position_tagged {
     bool operator==(const get_& b) const;
-    boost::optional<dataPath_> m_path;
+    boost::optional<boost::variant<dataPath_, schemaPath_>> m_path;
 };
 
 using command_ = boost::variant<discard_, ls_, cd_, create_, delete_, set_, commit_, get_>;
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index c670168..0cb64f1 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -201,6 +201,8 @@
     }
 };
 
+struct dataNodeList_class;
+
 struct dataNode_class {
     template <typename T, typename Iterator, typename Context>
     void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
@@ -226,6 +228,10 @@
     }
 };
 
+struct dataNodesListEnd_class;
+
+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)
@@ -332,7 +338,7 @@
             leaf_ leaf = boost::get<leaf_>(parserContext.m_curPath.m_nodes.back().m_suffix);
             schemaPath_ location = pathWithoutLastNode(parserContext.m_curPath);
             if (location.m_nodes.empty()) {
-               parserContext.m_curModule = parserContext.m_curPath.m_nodes.back().m_prefix->m_name;
+                parserContext.m_curModule = parserContext.m_curPath.m_nodes.back().m_prefix->m_name;
             }
             parserContext.m_errorMsg = "Expected " + leafDataTypeToString(schema.leafType(location, {parserContext.m_curModule, leaf.m_name})) + " here:";
             return x3::error_handler_result::fail;
@@ -455,3 +461,18 @@
         return x3::error_handler_result::fail;
     }
 };
+
+struct initializeContext_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const&, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        parserContext.m_curPath = parserContext.m_curPathOrig;
+        parserContext.m_tmpListKeys.clear();
+        parserContext.m_tmpListName.clear();
+        if (!parserContext.m_curPath.m_nodes.empty() && parserContext.m_curPath.m_nodes.at(0).m_prefix)
+            parserContext.m_topLevelModulePresent = true;
+        else
+            parserContext.m_topLevelModulePresent = false;
+    }
+};
diff --git a/src/ast_path.cpp b/src/ast_path.cpp
index 56d33c0..1f01a72 100644
--- a/src/ast_path.cpp
+++ b/src/ast_path.cpp
@@ -43,6 +43,11 @@
 {
 }
 
+schemaNode_::schemaNode_(decltype(m_suffix) node)
+    : m_suffix(node)
+{
+}
+
 schemaNode_::schemaNode_(module_ module, decltype(m_suffix) node)
     : m_prefix(module)
     , m_suffix(node)
@@ -186,7 +191,7 @@
     return res;
 }
 
-std::string pathToSchemaString(const dataPath_& path)
+std::string pathToSchemaString(const schemaPath_& path)
 {
     std::string res;
     for (const auto it : path.m_nodes) {
@@ -198,6 +203,11 @@
     return res;
 }
 
+std::string pathToSchemaString(const dataPath_& path)
+{
+    return pathToSchemaString(dataPathToSchemaPath(path));
+}
+
 struct dataSuffixToSchemaSuffix : boost::static_visitor<decltype(schemaNode_::m_suffix)> {
     auto operator()(const listElement_& listElement) const
     {
diff --git a/src/ast_path.hpp b/src/ast_path.hpp
index 4fbeaea..2711a30 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -79,7 +79,7 @@
 
 struct dataNode_ {
     boost::optional<module_> m_prefix;
-    boost::variant<container_, listElement_, nodeup_, leaf_> m_suffix;
+    boost::variant<container_, listElement_, nodeup_, leaf_, list_> m_suffix;
 
     dataNode_();
     dataNode_(decltype(m_suffix) node);
@@ -109,7 +109,7 @@
 std::string pathToAbsoluteSchemaString(const dataPath_& path);
 std::string pathToAbsoluteSchemaString(const schemaPath_& path);
 std::string pathToDataString(const dataPath_& path);
-std::string pathToSchemaString(const dataPath_& path);
+std::string pathToSchemaString(const schemaPath_& path);
 schemaNode_ dataNodeToSchemaNode(const dataNode_& node);
 schemaPath_ dataPathToSchemaPath(const dataPath_& path);
 
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 7f76ac7..7caa45c 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -28,6 +28,9 @@
 x3::rule<schemaNode_class, schemaNode_> const schemaNode = "schemaNode";
 x3::rule<absoluteStart_class, Scope> const absoluteStart = "absoluteStart";
 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";
 x3::rule<dataPath_class, dataPath_> const dataPath = "dataPath";
 x3::rule<leaf_path_class, dataPath_> const leafPath = "leafPath";
 
@@ -49,6 +52,8 @@
 x3::rule<commit_class, commit_> const commit = "commit";
 x3::rule<command_class, command_> const command = "command";
 
+x3::rule<initializeContext_class, x3::unused_type> const initializeContext = "initializeContext";
+
 #if __clang__
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Woverloaded-shift-op-parentheses"
@@ -90,7 +95,7 @@
         listPrefix > listSuffix;
 
 auto const list_def =
-        node_identifier;
+        node_identifier >> !char_('[');
 
 auto const nodeup_def =
         lit("..") > x3::attr(nodeup_());
@@ -109,7 +114,7 @@
         -(module) >> x3::expect[container | list | nodeup | leaf];
 
 auto const dataNode_def =
-        -(module) >> x3::expect[container | listElement | nodeup | leaf];
+        -(module) >> (container | listElement | nodeup | leaf);
 
 auto const absoluteStart_def =
         x3::omit['/'] >> x3::attr(Scope::Absolute);
@@ -119,6 +124,21 @@
         absoluteStart >> x3::attr(decltype(dataPath_::m_nodes)()) >> x3::eoi |
         -(absoluteStart) >> dataNode % '/';
 
+auto const dataNodeList_def =
+        -(module) >> list;
+
+// This intermediate rule is mandatory, because we need the first alternative
+// to be collapsed to a vector. If we didn't use the intermediate rule,
+// Spirit wouldn't know we want it to collapse.
+// https://github.com/boostorg/spirit/issues/408
+auto const dataNodesListEnd_def =
+        dataNode % '/' >> '/' >> dataNodeList |
+        initializeContext >> x3::attr(decltype(dataPath_::m_nodes)()) >> dataNodeList;
+
+auto const dataPathListEnd_def =
+        absoluteStart >> x3::attr(decltype(dataPath_::m_nodes)()) >> x3::eoi |
+        -(absoluteStart) >> dataNodesListEnd;
+
 auto const schemaPath_def =
         absoluteStart >> x3::attr(decltype(schemaPath_::m_nodes)()) >> x3::eoi |
         -(absoluteStart) >> schemaNode % '/';
@@ -169,8 +189,12 @@
     }
 } const ls_options;
 
+// A "nothing" parser, which is used to reset the context (when trying to parse different types of paths)
+auto const initializeContext_def =
+        x3::eps;
+
 auto const ls_def =
-        lit("ls") >> *(space_separator >> ls_options) >> -(space_separator >> dataPath);
+        lit("ls") >> *(space_separator >> ls_options) >> -(space_separator >> (dataPathListEnd | initializeContext >> dataPath));
 
 auto const cd_def =
         lit("cd") >> space_separator > dataPath;
@@ -182,7 +206,7 @@
         lit("delete") >> space_separator > dataPath;
 
 auto const get_def =
-        lit("get") >> -dataPath;
+        lit("get") >> -(space_separator >> (dataPathListEnd | initializeContext >> dataPath));
 
 auto const set_def =
         lit("set") >> space_separator > leafPath > leaf_data;
@@ -216,6 +240,9 @@
 BOOST_SPIRIT_DEFINE(leafPath)
 BOOST_SPIRIT_DEFINE(schemaPath)
 BOOST_SPIRIT_DEFINE(dataPath)
+BOOST_SPIRIT_DEFINE(dataNodeList)
+BOOST_SPIRIT_DEFINE(dataNodesListEnd)
+BOOST_SPIRIT_DEFINE(dataPathListEnd)
 BOOST_SPIRIT_DEFINE(absoluteStart)
 BOOST_SPIRIT_DEFINE(module)
 BOOST_SPIRIT_DEFINE(leaf_data)
@@ -225,6 +252,7 @@
 BOOST_SPIRIT_DEFINE(leaf_data_int)
 BOOST_SPIRIT_DEFINE(leaf_data_uint)
 BOOST_SPIRIT_DEFINE(leaf_data_string)
+BOOST_SPIRIT_DEFINE(initializeContext)
 BOOST_SPIRIT_DEFINE(set)
 BOOST_SPIRIT_DEFINE(commit)
 BOOST_SPIRIT_DEFINE(get)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 452bc74..80e6195 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -84,14 +84,39 @@
         return joinPaths(m_parser.currentNode(), pathToDataString(command.m_path));
 }
 
+struct pathToStringVisitor : boost::static_visitor<std::string> {
+    std::string operator()(const schemaPath_& path) const
+    {
+        return pathToSchemaString(path);
+    }
+    std::string operator()(const dataPath_& path) const
+    {
+        return pathToDataString(path);
+    }
+};
+
+struct getPathScopeVisitor : boost::static_visitor<Scope> {
+    template <typename T>
+    Scope operator()(const T& path) const
+    {
+        return path.m_scope;
+    }
+};
+
 std::string Interpreter::absolutePathFromCommand(const get_& get) const
 {
     if (!get.m_path) {
         return m_parser.currentNode();
-    } else if (get.m_path->m_scope == Scope::Absolute) {
-        return "/" + pathToDataString(*get.m_path);
+    }
+
+    const auto path = *get.m_path;
+    std::string pathString = boost::apply_visitor(pathToStringVisitor(), path);
+    auto pathScope{boost::apply_visitor(getPathScopeVisitor(), path)};
+
+    if (pathScope == Scope::Absolute) {
+        return "/" + pathString;
     } else {
-        return joinPaths(m_parser.currentNode(), pathToDataString(*get.m_path));
+        return joinPaths(m_parser.currentNode(), pathString);
     }
 }
 
diff --git a/src/parser.cpp b/src/parser.cpp
index bb17495..ee0233e 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -70,11 +70,12 @@
     }
 };
 
-std::set<std::string> Parser::availableNodes(const boost::optional<dataPath_>& path, const Recursion& option) const
+
+std::set<std::string> Parser::availableNodes(const boost::optional<boost::variant<dataPath_, schemaPath_>>& path, const Recursion& option) const
 {
     auto pathArg = dataPathToSchemaPath(m_curDir);
     if (path) {
-        auto schemaPath = dataPathToSchemaPath(*path);
+        auto schemaPath = boost::apply_visitor(getSchemaPathVisitor(), *path);
         pathArg.m_nodes.insert(pathArg.m_nodes.end(), schemaPath.m_nodes.begin(), schemaPath.m_nodes.end());
     }
     return m_schema->childNodes(pathArg, option);
diff --git a/src/parser.hpp b/src/parser.hpp
index 013a2f4..343a1e5 100644
--- a/src/parser.hpp
+++ b/src/parser.hpp
@@ -29,7 +29,7 @@
     command_ parseCommand(const std::string& line, std::ostream& errorStream);
     void changeNode(const dataPath_& name);
     std::string currentNode() const;
-    std::set<std::string> availableNodes(const boost::optional<dataPath_>& path, const Recursion& option) const;
+    std::set<std::string> availableNodes(const boost::optional<boost::variant<dataPath_, schemaPath_>>& path, const Recursion& option) const;
 
 private:
     const std::shared_ptr<const Schema> m_schema;
diff --git a/src/parser_context.cpp b/src/parser_context.cpp
index f5e2d32..c240984 100644
--- a/src/parser_context.cpp
+++ b/src/parser_context.cpp
@@ -9,9 +9,9 @@
 #include "parser_context.hpp"
 ParserContext::ParserContext(const Schema& schema, const schemaPath_& curDir)
     : m_schema(schema)
+    , m_curPath(curDir)
+    , m_curPathOrig(curDir)
 {
-    m_curPath = curDir;
-
     if (!m_curPath.m_nodes.empty() && m_curPath.m_nodes.at(0).m_prefix)
         m_topLevelModulePresent = true;
 }
diff --git a/src/parser_context.hpp b/src/parser_context.hpp
index 01ab7e8..a0b8e30 100644
--- a/src/parser_context.hpp
+++ b/src/parser_context.hpp
@@ -11,6 +11,7 @@
     ParserContext(const Schema& schema, const schemaPath_& curDir);
     const Schema& m_schema;
     schemaPath_ m_curPath;
+    const schemaPath_ m_curPathOrig;
     boost::optional<std::string> m_curModule;
     std::string m_errorMsg;
     std::string m_tmpListName;
diff --git a/tests/ls.cpp b/tests/ls.cpp
index d202a1c..e42152d 100644
--- a/tests/ls.cpp
+++ b/tests/ls.cpp
@@ -17,6 +17,7 @@
     schema->addModule("example");
     schema->addModule("second");
     schema->addContainer("", "example:a");
+    schema->addList("example:a", "example:listInCont", {"number"});
     schema->addContainer("", "second:a");
     schema->addContainer("", "example:b");
     schema->addContainer("example:a", "example:a2");
@@ -127,6 +128,28 @@
                 expected.m_options.push_back(LsOption::Recursive);
                 expected.m_path = dataPath_{Scope::Absolute, {dataNode_(module_{"example"}, container_{"a"})}};
             }
+
+            SECTION("ls example:list")
+            {
+                input = "ls example:list";
+                expected.m_path = dataPath_{Scope::Relative, {dataNode_(module_{"example"}, list_{"list"})}};
+            }
+
+            SECTION("ls example:a/example:listInCont")
+            {
+                input = "ls example:a/example:listInCont";
+                expected.m_path = dataPath_{Scope::Relative, {dataNode_(module_{"example"}, container_{"a"}),
+                                                                dataNode_(module_{"example"}, list_{"listInCont"})}};
+            }
+
+            SECTION("ls example:list[number=342]/contInList")
+            {
+                input = "ls example:list[number=342]/contInList";
+                auto keys = std::map<std::string, std::string>{
+                    {"number", "342"}};
+                expected.m_path = dataPath_{Scope::Relative, {dataNode_(module_{"example"}, listElement_{"list", keys}),
+                                                                dataNode_(container_{"contInList"})}};
+            }
         }
 
         command_ command = parser.parseCommand(input, errorStream);