dts: dtlib: Refactor to get rid of _is_parsing flag

The public DT.get_node() function was used during parsing to look up
paths in references like &{/foo/bar}, along with an ugly
'DT._is_parsing' flag to adapt its behavior (to not mention aliases in
error messages).

Split out common node lookup code needed during parsing and by
get_node() instead, and stop using get_node() during parsing. This
allows '_is_parsing' to be removed and untangles things a bit.

Piggyback some other small reference-related cleanups, and fix an issue
with the filename/linenr being given twice in some error messages.

This commit also removes the index of path components from error
messages, but just the string is probably good enough.

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
diff --git a/scripts/dts/dtlib.py b/scripts/dts/dtlib.py
index 69abdf0..d13c16b 100644
--- a/scripts/dts/dtlib.py
+++ b/scripts/dts/dtlib.py
@@ -103,8 +103,6 @@
 
         self._lineno = 1
 
-        self._is_parsing = True
-
         self._parse_dt()
 
         self._register_phandles()
@@ -113,8 +111,6 @@
         self._remove_unreferenced()
         self._register_labels()
 
-        self._is_parsing = False
-
     def get_node(self, path):
         """
         Returns the Node instance for the node with path or alias 'path' (a
@@ -142,37 +138,15 @@
         dt.get_node("bar-alias/baz") returns the 'baz' node.
         """
         if path.startswith("/"):
-            cur = self.root
-            component_i = 0
-            rest = path
-        else:
-            # Strip the first component from 'path' and store it in 'alias'.
-            # Use a separate 'rest' variable rather than directly modifying
-            # 'path' so that all of 'path' still shows up in error messages.
-            alias, _, rest = path.partition("/")
-            if alias not in self.alias2node:
-                raise DTError("node path does not start with '/'"
-                              if self._is_parsing else
-                              "no alias '{}' found -- did you forget the "
-                              "leading '/' in the node path?".format(alias))
-            cur = self.alias2node[alias]
-            component_i = 1
+            return _root_and_path_to_node(self.root, path, path)
 
-        for component in rest.split("/"):
-            # Collapse multiple / in a row, and allow a / at the end
-            if not component:
-                continue
+        # Path does not start with '/'. First component must be an alias.
+        alias, _, rest = path.partition("/")
+        if alias not in self.alias2node:
+            raise DTError("no alias '{}' found -- did you forget the "
+                          "leading '/' in the node path?".format(alias))
 
-            component_i += 1
-
-            if component not in cur.nodes:
-                raise DTError("component {} ({}) in path {} does not exist"
-                              .format(component_i, repr(component),
-                                      repr(path)))
-
-            cur = cur.nodes[component]
-
-        return cur
+        return _root_and_path_to_node(self.alias2node[alias], rest, path)
 
     def has_node(self, path):
         """
@@ -266,17 +240,11 @@
                     _append_no_dup(node.labels, label)
 
             elif tok.id is _T_DEL_NODE:
-                try:
-                    self._del_node(self._next_ref2node())
-                except DTError as e:
-                    self._parse_error(e)
+                self._del_node(self._next_ref2node())
                 self._expect_token(";")
 
             elif tok.id is _T_OMIT_IF_NO_REF:
-                try:
-                    self._next_ref2node()._omit_if_no_ref = True
-                except DTError as e:
-                    self._parse_error(e)
+                self._next_ref2node()._omit_if_no_ref = True
                 self._expect_token(";")
 
             elif tok.id is _T_EOF:
@@ -924,21 +892,33 @@
 
     def _next_ref2node(self):
         # Checks that the next token is a label/path reference and returns the
-        # Node it points to
+        # Node it points to. Only used during parsing, so uses _parse_error()
+        # on errors to save some code in callers.
 
         label = self._next_token()
         if label.id is not _T_REF:
-            self._parse_error("expected label reference (&foo) or path")
-        return self._ref2node(label.val)
+            self._parse_error(
+                "expected label (&foo) or path (&{/foo/bar}) reference")
+        try:
+            return self._ref2node(label.val)
+        except DTError as e:
+            self._parse_error(e)
 
     def _ref2node(self, s):
         # Returns the Node the label/path reference 's' points to
 
         if s[0] == "{":
+            # Path reference (&{/foo/bar})
+            path = s[1:-1]
+            if not path.startswith("/"):
+                raise DTError("node path '{}' does not start with '/'"
+                              .format(path))
             # Will raise DTError if the path doesn't exist
-            return self.get_node(s[1:-1])
+            return _root_and_path_to_node(self.root, path, path)
 
-        # node2label hasn't been filled in yet, and using it would get messy
+        # Label reference (&foo).
+
+        # label2node hasn't been filled in yet, and using it would get messy
         # when nodes are deleted
         for node in self.node_iter():
             if s in node.labels:
@@ -1838,6 +1818,26 @@
             .decode("utf-8", "backslashreplace")
 
 
+def _root_and_path_to_node(cur, path, fullpath):
+    # Returns the node pointed at by 'path', relative to the Node 'cur'. For
+    # example, if 'cur' has path /foo/bar, and 'path' is "baz/qaz", then the
+    # node with path /foo/bar/baz/qaz is returned. 'fullpath' is the path as
+    # given in the .dts file, for error messages.
+
+    for component in path.split("/"):
+        # Collapse multiple / in a row, and allow a / at the end
+        if not component:
+            continue
+
+        if component not in cur.nodes:
+            raise DTError("component '{}' in path '{}' does not exist"
+                          .format(component, fullpath))
+
+        cur = cur.nodes[component]
+
+    return cur
+
+
 _escape_table = str.maketrans({
     "\\": "\\\\",
     '"': '\\"',
diff --git a/scripts/dts/testdtlib.py b/scripts/dts/testdtlib.py
index 029b865..777e1fd 100755
--- a/scripts/dts/testdtlib.py
+++ b/scripts/dts/testdtlib.py
@@ -386,7 +386,7 @@
 &{foo} {
 };
 """,
-".tmp.dts:6 (column 1): parse error: node path does not start with '/'")
+".tmp.dts:6 (column 1): parse error: node path 'foo' does not start with '/'")
 
     verify_error("""
 /dts-v1/;
@@ -397,7 +397,7 @@
 &{/foo} {
 };
 """,
-".tmp.dts:6 (column 1): parse error: component 1 ('foo') in path '/foo' does not exist")
+".tmp.dts:6 (column 1): parse error: component 'foo' in path '/foo' does not exist")
 
     #
     # Test property labels
@@ -586,7 +586,7 @@
 	};
 };
 """,
-"/sub: component 2 ('missing') in path '/sub/missing' does not exist")
+"/sub: component 'missing' in path '/sub/missing' does not exist")
 
     #
     # Test phandles
@@ -901,6 +901,13 @@
 """,
 ".tmp.dts:6 (column 15): parse error: undefined node label 'missing'")
 
+    verify_error("""
+/dts-v1/;
+
+/delete-node/ {
+""",
+".tmp.dts:3 (column 15): parse error: expected label (&foo) or path (&{/foo/bar}) reference")
+
     #
     # Test /include/ (which is handled in the lexer)
     #
@@ -1113,6 +1120,13 @@
 """,
 ".tmp.dts:6 (column 18): parse error: undefined node label 'missing'")
 
+    verify_error("""
+/dts-v1/;
+
+/omit-if-no-ref/ {
+""",
+".tmp.dts:3 (column 18): parse error: expected label (&foo) or path (&{/foo/bar}) reference")
+
     #
     # Test expressions
     #
@@ -1321,8 +1335,8 @@
 
     verify_path_error("", "no alias '' found -- did you forget the leading '/' in the node path?")
     verify_path_error("missing", "no alias 'missing' found -- did you forget the leading '/' in the node path?")
-    verify_path_error("/missing", "component 1 ('missing') in path '/missing' does not exist")
-    verify_path_error("/foo/missing", "component 2 ('missing') in path '/foo/missing' does not exist")
+    verify_path_error("/missing", "component 'missing' in path '/missing' does not exist")
+    verify_path_error("/foo/missing", "component 'missing' in path '/foo/missing' does not exist")
 
     verify_path_exists("/")
     verify_path_exists("/foo")
@@ -1378,7 +1392,7 @@
     verify_path_is("alias4/node5", "node5")
 
     verify_path_error("alias4/node5/node6",
-                      "component 3 ('node6') in path 'alias4/node5/node6' does not exist")
+                      "component 'node6' in path 'alias4/node5/node6' does not exist")
 
     verify_error("""
 /dts-v1/;