Support absolute paths

Change-Id: Ibe087d2bad0c6c9f1619d8811103415bcb3b4906
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 2953dad..aa37574 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -189,6 +189,15 @@
     }
 };
 
+struct absoluteStart_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.m_nodes.clear();
+    }
+};
+
 struct path_class {
     template <typename Iterator, typename Exception, typename Context>
     x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const&, Context const& context)
diff --git a/src/ast_path.hpp b/src/ast_path.hpp
index 029d33f..8827a38 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -68,8 +68,15 @@
     bool operator==(const node_& b) const;
 };
 
+enum class Scope
+{
+    Absolute,
+    Relative
+};
+
 struct path_ {
     bool operator==(const path_& b) const;
+    Scope m_scope = Scope::Relative;
     std::vector<node_> m_nodes;
 };
 
@@ -83,4 +90,4 @@
 BOOST_FUSION_ADAPT_STRUCT(listElement_, m_name, m_keys)
 BOOST_FUSION_ADAPT_STRUCT(module_, m_name)
 BOOST_FUSION_ADAPT_STRUCT(node_, m_prefix, m_suffix)
-BOOST_FUSION_ADAPT_STRUCT(path_, m_nodes)
+BOOST_FUSION_ADAPT_STRUCT(path_, m_scope, m_nodes)
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 06e1bf8..ffe6e8b 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -23,6 +23,7 @@
 x3::rule<leaf_class, leaf_> const leaf = "leaf";
 x3::rule<module_class, module_> const module = "module";
 x3::rule<node_class, node_> const node = "node";
+x3::rule<absoluteStart_class, Scope> const absoluteStart = "absoluteStart";
 x3::rule<path_class, path_> const path = "path";
 x3::rule<leaf_path_class, path_> const leafPath = "leafPath";
 
@@ -85,8 +86,13 @@
 auto const node_def =
         -(module) >> x3::expect[container | listElement | nodeup | leaf];
 
+auto const absoluteStart_def =
+        x3::omit['/'] >> x3::attr(Scope::Absolute);
+
+// I have to insert an empty vector to the first alternative, otherwise they won't have the same attribute
 auto const path_def =
-        node % '/';
+        absoluteStart >> x3::attr(decltype(path_::m_nodes)()) >> x3::eoi |
+        -(absoluteStart) >> node % '/';
 
 auto const leafPath_def =
         path;
@@ -159,6 +165,7 @@
 BOOST_SPIRIT_DEFINE(leaf)
 BOOST_SPIRIT_DEFINE(leafPath)
 BOOST_SPIRIT_DEFINE(node)
+BOOST_SPIRIT_DEFINE(absoluteStart)
 BOOST_SPIRIT_DEFINE(path)
 BOOST_SPIRIT_DEFINE(module)
 BOOST_SPIRIT_DEFINE(leaf_data)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 24c48c4..e3113dd 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -55,7 +55,10 @@
 template <typename T>
 std::string Interpreter::absolutePathFromCommand(const T& command) const
 {
-    return joinPaths(m_parser.currentNode(), pathToDataString(command.m_path));
+    if (command.m_path.m_scope == Scope::Absolute)
+        return "/" + pathToDataString(command.m_path);
+    else
+        return joinPaths(m_parser.currentNode(), pathToDataString(command.m_path));
 }
 
 Interpreter::Interpreter(Parser& parser, DatastoreAccess& datastore)
diff --git a/src/parser.cpp b/src/parser.cpp
index a3550a6..c56b561 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -41,11 +41,15 @@
 
 void Parser::changeNode(const path_& name)
 {
-    for (const auto& it : name.m_nodes) {
-        if (it.m_suffix.type() == typeid(nodeup_))
-            m_curDir.m_nodes.pop_back();
-        else
-            m_curDir.m_nodes.push_back(it);
+    if (name.m_scope == Scope::Absolute) {
+        m_curDir = name;
+    } else {
+        for (const auto& it : name.m_nodes) {
+            if (it.m_suffix.type() == typeid(nodeup_))
+                m_curDir.m_nodes.pop_back();
+            else
+                m_curDir.m_nodes.push_back(it);
+        }
     }
 }
 
diff --git a/src/utils.cpp b/src/utils.cpp
index 9f9fd25..eb48349 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -28,7 +28,7 @@
 
 path_ pathWithoutLastNode(const path_& path)
 {
-    return path_{decltype(path_::m_nodes)(path.m_nodes.begin(), path.m_nodes.end() - 1)};
+    return path_{path.m_scope, decltype(path_::m_nodes)(path.m_nodes.begin(), path.m_nodes.end() - 1)};
 }
 
 std::string leafDataTypeToString(yang::LeafDataTypes type)
diff --git a/tests/ls.cpp b/tests/ls.cpp
index c2cc5fe..9356c69 100644
--- a/tests/ls.cpp
+++ b/tests/ls.cpp
@@ -41,8 +41,75 @@
 
         SECTION("with path argument")
         {
-            input = "ls example:a";
-            expected.m_path = path_{{node_(module_{"example"}, container_{"a"})}};
+            SECTION("ls example:a")
+            {
+                input = "ls example:a";
+                expected.m_path = path_{Scope::Relative, {node_(module_{"example"}, container_{"a"})}};
+            }
+
+            SECTION("ls /example:a")
+            {
+                SECTION("cwd: /") {}
+                SECTION("cwd: /example:a") {parser.changeNode(path_{Scope::Relative, {node_(module_{"example"}, container_{"a"})}});}
+
+                input = "ls /example:a";
+                expected.m_path = path_{Scope::Absolute, {node_(module_{"example"}, container_{"a"})}};
+            }
+
+            SECTION("ls /")
+            {
+                SECTION("cwd: /") {}
+                SECTION("cwd: /example:a") {parser.changeNode(path_{Scope::Relative, {node_(module_{"example"}, container_{"a"})}});}
+                input = "ls /";
+                expected.m_path = path_{Scope::Absolute, {}};
+            }
+
+            SECTION("ls example:a/a2")
+            {
+                input = "ls example:a/a2";
+                expected.m_path = path_{Scope::Relative, {node_(module_{"example"}, container_{"a"}),
+                                                          node_(container_{"a2"})}};
+            }
+
+            SECTION("ls a2")
+            {
+                parser.changeNode(path_{Scope::Relative, {node_(module_{"example"}, container_{"a"})}});
+                input = "ls a2";
+                expected.m_path = path_{Scope::Relative, {node_(container_{"a2"})}};
+            }
+
+            SECTION("ls /example:a/a2")
+            {
+                SECTION("cwd: /") {}
+                SECTION("cwd: /example:a") {parser.changeNode(path_{Scope::Relative, {node_(module_{"example"}, container_{"a"})}});}
+                input = "ls /example:a/a2";
+                expected.m_path = path_{Scope::Absolute, {node_(module_{"example"}, container_{"a"}),
+                                                          node_(container_{"a2"})}};
+            }
+
+            SECTION("ls example:a/example:a2")
+            {
+                input = "ls example:a/example:a2";
+                expected.m_path = path_{Scope::Relative, {node_(module_{"example"}, container_{"a"}),
+                                                          node_(module_{"example"}, container_{"a2"})}};
+            }
+
+            SECTION("ls example:a2")
+            {
+                parser.changeNode(path_{Scope::Relative, {node_(module_{"example"}, container_{"a"})}});
+                input = "ls example:a2";
+                expected.m_path = path_{Scope::Relative, {node_(module_{"example"}, container_{"a2"})}};
+            }
+
+            SECTION("ls /example:a/example:a2")
+            {
+                SECTION("cwd: /") {}
+                SECTION("cwd: /example:a") {parser.changeNode(path_{Scope::Relative, {node_(module_{"example"}, container_{"a"})}});}
+
+                input = "ls /example:a/example:a2";
+                expected.m_path = path_{Scope::Absolute, {node_(module_{"example"}, container_{"a"}),
+                                                          node_(module_{"example"}, container_{"a2"})}};
+            }
         }
 
         command_ command = parser.parseCommand(input, errorStream);
@@ -53,7 +120,20 @@
     {
         SECTION("invalid path")
         {
-            input = "ls example:nonexistent";
+            SECTION("ls example:nonexistent")
+                input = "ls example:nonexistent";
+
+            SECTION("ls /example:nonexistent")
+                input = "ls /example:nonexistent";
+
+            SECTION("ls /bad:nonexistent")
+                input = "ls /bad:nonexistent";
+
+            SECTION( "ls example:a/nonexistent")
+                input = "ls example:a/nonexistent";
+
+            SECTION( "ls /example:a/nonexistent")
+                input = "ls /example:a/nonexistent";
         }
 
         REQUIRE_THROWS(parser.parseCommand(input, errorStream));