dts: dtlib/edtlib: Add a syntax-based type-checking system

Property type-checking has been pretty rudimentary until now, only
checking things like the length being divisible by 4 for 'type: array',
and strings being null-terminated. In particular, no checking was done
for 'type: uint8-array', letting

  jedec-id = < 0xc8 0x28 0x17 >;

slip through when

  jedec-id = [ 0xc8 0x28 0x17 ];

was intended.

Fix it by adding a syntax-based type checker:

  1. Add Property.type, which gives a high-level type for the property,
     derived from the markers added in the previous commit.

     This includes types like TYPE_EMPTY ('foo;'),
     TYPE_NUM ('foo = < 3 >;'), TYPE_BYTES ('foo = [ 01 02 ];'),
     TYPE_STRINGS ('foo = "bar", "baz"'),
     TYPE_PHANDLE ('foo = < &bar >;'), and TYPE_COMPOUND (everything not
     recognized).

     See the Property.type docstring in dtlib for more info.

  2. Use the high-level type in
     Property.to_num()/to_string()/to_node()/etc. to verify that the
     property was assigned in an expected way for the type.

     If the assignment looks bad, give a helpful error:

       expected property 'nums' on /foo/bar in some.dts to be assigned
       with 'nums = < (number) (number) ... >', not 'nums = "oops";'

Some other related changes are included as well:

  - There's a new Property.to_bytes() function that works like accessing
    Property.bytes, except with an added check for the value being
    assigned like 'foo = [ ... ]'.

    This function solves problems like the jedec-id one.

  - There's a new Property.to_path() function for fetching the
    referenced node for assignments like 'foo = &node;', with type
    checking. (Strings are accepted too, as long as they give the path
    to an existing node.)

    This function is used for /chosen and /aliases.

  - A new 'type: phandle' type can now be given in bindings, for
    properties that are assigned like 'foo = < &node >;'.

  - Property.__str__() now displays phandles and path references as they
    were written (e.g. '< &foo >' instead of '< 0x1 >', if the
    allocated phandle happened to be 1).

  - Property.to_num() and Property.to_nums() no longer take a 'length'
    parameter, because it makes no sense with the type checking.

  - The global dtlib.to_string() and dtlib.to_strings() functions were
    removed, because they're not that useful.

  - More tests were added, along with misc. minor cleanup in various
    places.

  - Probably other stuff I forgot.

The more strict type checking in dtlib indirectly makes some parts of
edtlib more strict as well (wherever Property.to_*() is used).

Fixes: #18131

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
diff --git a/scripts/dts/testdtlib.py b/scripts/dts/testdtlib.py
index ba63512..365f810 100755
--- a/scripts/dts/testdtlib.py
+++ b/scripts/dts/testdtlib.py
@@ -422,7 +422,7 @@
 /dts-v1/;
 
 / {
-	a = l01: l02: < l03: 0x1 l04: l05: 0x2 l06: l07: l08: >, [ l09: 03 l10: l11: 04 l12: l13: l14: ], "A";
+	a = l01: l02: < l03: &node l04: l05: 0x2 l06: l07: l08: >, [ l09: 03 l10: l11: 04 l12: l13: l14: ], "A";
 	b = < 0x0 l23: l24: >;
 	node: node {
 		phandle = < 0x1 >;
@@ -504,13 +504,13 @@
 /dts-v1/;
 
 / {
-	a = "/abc";
-	b = [ 01 ], "/abc";
-	c = [ 01 ], "/abc", < 0x2 >;
-	d = "/abc";
+	a = &label;
+	b = [ 01 ], &label;
+	c = [ 01 ], &label, < 0x2 >;
+	d = &{/abc};
 	label: abc {
-		e = "/abc";
-		f = "/abc";
+		e = &label;
+		f = &{/abc};
 	};
 };
 """)
@@ -541,7 +541,6 @@
     # Test phandles
     #
 
-    # Check that existing phandles are used (and not reused)
     verify_parse("""
 /dts-v1/;
 
@@ -571,7 +570,7 @@
 /dts-v1/;
 
 / {
-	x = < 0x2 0x4 0xff >;
+	x = < &a &{/b} &c >;
 	dummy1 {
 		phandle = < 0x1 >;
 	};
@@ -617,10 +616,10 @@
 		phandle = < 0x1 >;
 	};
 	a {
-		foo: phandle = < 0x2 >;
+		foo: phandle = < &{/a} >;
 	};
 	label: b {
-		bar: phandle = < 0x3 >;
+		bar: phandle = < &label >;
 	};
 };
 """)
@@ -765,7 +764,7 @@
 /dts-v1/;
 
 / {
-	x = [ FF FF ], "/abc", < 0xff 0x1 0xff 0x1 >, "/abc", [ FF FF ], "abc";
+	x = [ FF FF ], &abc, < 0xff &abc 0xff &abc >, &abc, [ FF FF ], "abc";
 	abc: abc {
 		phandle = < 0x1 >;
 	};
@@ -1007,7 +1006,7 @@
 /dts-v1/;
 
 / {
-	x = < 0x1 >, "/referenced2";
+	x = < &{/referenced} >, &referenced2;
 	referenced {
 		phandle = < 0x1 >;
 	};
@@ -1299,7 +1298,7 @@
 		alias1 = &l1;
 		alias2 = &l2;
 		alias3 = &{/sub/node3};
-		alias4 = [2F 6E 6F 64 65 34 00]; // "/node4";
+		alias4 = &{/node4};
 	};
 
 	l1: node1 {
@@ -1323,7 +1322,6 @@
     verify_alias_target("alias1", "node1")
     verify_alias_target("alias2", "node2")
     verify_alias_target("alias3", "node3")
-    verify_alias_target("alias4", "node4")
     verify_path_is("alias4/node5", "node5")
 
     verify_path_error("alias4/node5/node6",
@@ -1334,6 +1332,28 @@
 
 / {
 	aliases {
+		a = [ 00 ];
+	};
+};
+""",
+"expected property 'a' on /aliases in .tmp.dts to be assigned with either 'a = &foo' or 'a = \"/path/to/node\"', not 'a = [ 00 ];'")
+
+    verify_error(r"""
+/dts-v1/;
+
+/ {
+	aliases {
+		a = "\xFF";
+	};
+};
+""",
+r"value of property 'a' (b'\xff\x00') on /aliases in .tmp.dts is not valid UTF-8")
+
+    verify_error("""
+/dts-v1/;
+
+/ {
+	aliases {
 		A = "/aliases";
 	};
 };
@@ -1345,56 +1365,130 @@
 
 / {
 	aliases {
-		a = "\xFF";
-	};
-};
-""",
-r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
-
-    verify_error(r"""
-/dts-v1/;
-
-/ {
-	aliases {
-		a = [ 41 ]; // "A"
-	};
-};
-""",
-"b'A' is not null-terminated (for property 'a' on /aliases)")
-
-    verify_error(r"""
-/dts-v1/;
-
-/ {
-	aliases {
 		a = "/missing";
 	};
 };
 """,
-"/aliases: bad path for 'a': component 1 ('missing') in path '/missing' does not exist")
+"property 'a' on /aliases in .tmp.dts points to the non-existent node \"/missing\"")
 
     #
-    # Test to_{num,nums,string,strings,node}()
+    # Test Property.type
     #
 
-    def verify_to_num(prop, size, signed, expected):
+    def verify_type(prop, expected):
+        actual = dt.root.props[prop].type
+        if actual != expected:
+            fail("expected {} to have type {}, had type {}"
+                 .format(prop, expected, actual))
+
+    dt = parse("""
+/dts-v1/;
+
+/ {
+	empty;
+	bytes1 = [ ];
+	bytes2 = [ 01 ];
+	bytes3 = [ 01 02 ];
+	bytes4 = foo: [ 01 bar: 02 ];
+	bytes5 = /bits/ 8 < 1 2 3 >;
+	num = < 1 >;
+	nums1 = < >;
+	nums2 = < >, < >;
+	nums3 = < 1 2 >;
+	nums4 = < 1 2 >, < 3 >, < 4 >;
+	string = "foo";
+	strings = "foo", "bar";
+	phandle1 = < &node >;
+	phandle2 = < &{/node} >;
+	path1 = &node;
+	path2 = &{/node};
+	compound1 = < 1 >, [ 02 ];
+	compound2 = "foo", < >;
+	compound3 = < 1 &{/node} 2>;
+
+	node: node {
+	};
+};
+""")
+
+    verify_type("empty", dtlib.TYPE_EMPTY)
+    verify_type("bytes1", dtlib.TYPE_BYTES)
+    verify_type("bytes2", dtlib.TYPE_BYTES)
+    verify_type("bytes3", dtlib.TYPE_BYTES)
+    verify_type("bytes4", dtlib.TYPE_BYTES)
+    verify_type("bytes5", dtlib.TYPE_BYTES)
+    verify_type("num", dtlib.TYPE_NUM)
+    verify_type("nums1", dtlib.TYPE_NUMS)
+    verify_type("nums2", dtlib.TYPE_NUMS)
+    verify_type("nums3", dtlib.TYPE_NUMS)
+    verify_type("nums4", dtlib.TYPE_NUMS)
+    verify_type("string", dtlib.TYPE_STRING)
+    verify_type("strings", dtlib.TYPE_STRINGS)
+    verify_type("phandle1", dtlib.TYPE_PHANDLE)
+    verify_type("phandle2", dtlib.TYPE_PHANDLE)
+    verify_type("path1", dtlib.TYPE_PATH)
+    verify_type("path2", dtlib.TYPE_PATH)
+    verify_type("compound1", dtlib.TYPE_COMPOUND)
+    verify_type("compound2", dtlib.TYPE_COMPOUND)
+    verify_type("compound3", dtlib.TYPE_COMPOUND)
+
+    #
+    # Test Property.to_{num,nums,string,strings,node}()
+    #
+
+    dt = parse(r"""
+/dts-v1/;
+
+/ {
+	u = < 1 >;
+	s = < 0xFFFFFFFF >;
+	u8 = /bits/ 8 < 1 >;
+	u16 = /bits/ 16 < 1 2 >;
+	u64 = /bits/ 64 < 1 >;
+        bytes = [ 01 02 03 ];
+	empty;
+	zero = < >;
+	two_u = < 1 2 >;
+	two_s = < 0xFFFFFFFF 0xFFFFFFFE >;
+	three_u = < 1 2 3 >;
+	three_u_split = < 1 >, < 2 >, < 3 >;
+	empty_string = "";
+	string = "foo\tbar baz";
+	invalid_string = "\xff";
+	strings = "foo", "bar", "baz";
+	invalid_strings = "foo", "\xff", "bar";
+	ref = <&{/target}>;
+	manualref = < 100 >;
+	missingref = < 123 >;
+	path = &{/target};
+	manualpath = "/target";
+	missingpath = "/missing";
+
+	target {
+		phandle = < 100 >;
+	};
+};
+""")
+
+    # Test Property.to_num()
+
+    def verify_to_num(prop, signed, expected):
         try:
-            actual = dt.root.props[prop].to_num(size, signed)
+            actual = dt.root.props[prop].to_num(signed)
         except dtlib.DTError as e:
-            fail("failed to convert {} to {} number with {} bytes: {}"
-                 .format(prop, "a signed" if signed else "an unsigned", size,
-                         e))
+            fail("failed to convert '{}' to {} number: {}"
+                 .format(prop, "a signed" if signed else "an unsigned", e))
 
         if actual != expected:
             fail("expected {} to have the {} numeric value {:#x}, had the "
                  "value {:#x}".format(prop, "signed" if signed else "unsigned",
                                       expected, actual))
 
-    def verify_to_num_error(prop, size, msg):
-        prefix = "expected {} converted from {} bytes to generate the error " \
-                 "'{}', generated".format(prop, size, msg)
+    def verify_to_num_error(prop, msg):
+        prefix = "expected fetching '{}' as a number to generate the error " \
+                 "'{}', generated".format(prop, msg)
         try:
-            dt.root.props[prop].to_num(size)
+            dt.root.props[prop].to_num()
             fail(prefix + " no error")
         except dtlib.DTError as e:
             if str(e) != msg:
@@ -1402,23 +1496,36 @@
         except Exception as e:
             fail("{} the non-DTError '{}'".format(prefix, e))
 
-    def verify_to_nums(prop, size, signed, expected):
+    verify_to_num("u", False, 1)
+    verify_to_num("u", True, 1)
+    verify_to_num("s", False, 0xFFFFFFFF)
+    verify_to_num("s", True, -1)
+
+    verify_to_num_error("two_u", "expected property 'two_u' on / in .tmp.dts to be assigned with 'two_u = < (number) >;', not 'two_u = < 0x1 0x2 >;'")
+    verify_to_num_error("u8", "expected property 'u8' on / in .tmp.dts to be assigned with 'u8 = < (number) >;', not 'u8 = [ 01 ];'")
+    verify_to_num_error("u16", "expected property 'u16' on / in .tmp.dts to be assigned with 'u16 = < (number) >;', not 'u16 = /bits/ 16 < 0x1 0x2 >;'")
+    verify_to_num_error("u64", "expected property 'u64' on / in .tmp.dts to be assigned with 'u64 = < (number) >;', not 'u64 = /bits/ 64 < 0x1 >;'")
+    verify_to_num_error("string", "expected property 'string' on / in .tmp.dts to be assigned with 'string = < (number) >;', not 'string = \"foo\\tbar baz\";'")
+
+    # Test Property.to_nums()
+
+    def verify_to_nums(prop, signed, expected):
         try:
-            actual = dt.root.props[prop].to_nums(size, signed)
+            actual = dt.root.props[prop].to_nums(signed)
         except dtlib.DTError as e:
-            fail("failed to convert {} to {} numbers with {} bytes each: {}"
-                 .format(prop, "signed" if signed else "unsigned", size, e))
+            fail("failed to convert '{}' to {} numbers: {}"
+                 .format(prop, "signed" if signed else "unsigned", e))
 
         if actual != expected:
-            fail("expected {} to give the {} numbers {} for size {}, gave {}"
+            fail("expected {} to give the {} numbers {}, gave {}"
                  .format(prop, "signed" if signed else "unsigned", expected,
-                         size, actual))
+                         actual))
 
-    def verify_to_nums_error(prop, size, msg):
-        prefix = "expected {} converted to numbers with {} bytes each to " \
-                 "generate the error '{}', generated".format(prop, size, msg)
+    def verify_to_nums_error(prop, msg):
+        prefix = "expected converting '{}' to numbers to generate the error " \
+                 "'{}', generated".format(prop, msg)
         try:
-            dt.root.props[prop].to_nums(size)
+            dt.root.props[prop].to_nums()
             fail(prefix + " no error")
         except dtlib.DTError as e:
             if str(e) != msg:
@@ -1426,11 +1533,35 @@
         except Exception as e:
             fail("{} the non-DTError '{}'".format(prefix, e))
 
-    def verify_raw_to_num_error(fn, data, size, msg):
-        prefix = "expected {}() called with data='{}', size='{}' to generate " \
-                 "the error '{}', generated".format(fn.__name__, data, size, msg)
+    verify_to_nums("zero", False, [])
+    verify_to_nums("u", False, [1])
+    verify_to_nums("two_u", False, [1, 2])
+    verify_to_nums("two_u", True, [1, 2])
+    verify_to_nums("two_s", False, [0xFFFFFFFF, 0xFFFFFFFE])
+    verify_to_nums("two_s", True, [-1, -2])
+    verify_to_nums("three_u", False, [1, 2, 3])
+    verify_to_nums("three_u_split", False, [1, 2, 3])
+
+    verify_to_nums_error("empty", "expected property 'empty' on / in .tmp.dts to be assigned with 'empty = < (number) (number) ... >', not 'empty;'")
+    verify_to_nums_error("string", "expected property 'string' on / in .tmp.dts to be assigned with 'string = < (number) (number) ... >', not 'string = \"foo\\tbar baz\";'")
+
+    # Test Property.to_bytes()
+
+    def verify_to_bytes(prop, expected):
         try:
-            dtlib.to_num(data, size)
+            actual = dt.root.props[prop].to_bytes()
+        except dtlib.DTError as e:
+            fail("failed to convert '{}' to bytes: {}".format(prop, e))
+
+        if actual != expected:
+            fail("expected {} to give the bytes {}, gave {}"
+                 .format(prop, expected, actual))
+
+    def verify_to_bytes_error(prop, msg):
+        prefix = "expected converting '{}' to bytes to generate the error " \
+                 "'{}', generated".format(prop, msg)
+        try:
+            dt.root.props[prop].to_bytes()
             fail(prefix + " no error")
         except dtlib.DTError as e:
             if str(e) != msg:
@@ -1438,18 +1569,26 @@
         except Exception as e:
             fail("{} the non-DTError '{}'".format(prefix, e))
 
+    verify_to_bytes("u8", b"\x01")
+    verify_to_bytes("bytes", b"\x01\x02\x03")
+
+    verify_to_bytes_error("u16", "expected property 'u16' on / in .tmp.dts to be assigned with 'u16 = [ (byte) (byte) ... ]', not 'u16 = /bits/ 16 < 0x1 0x2 >;'")
+    verify_to_bytes_error("empty", "expected property 'empty' on / in .tmp.dts to be assigned with 'empty = [ (byte) (byte) ... ]', not 'empty;'")
+
+    # Test Property.to_string()
+
     def verify_to_string(prop, expected):
         try:
             actual = dt.root.props[prop].to_string()
         except dtlib.DTError as e:
-            fail("failed to convert {} to string: {}".format(prop, e))
+            fail("failed to convert '{}' to string: {}".format(prop, e))
 
         if actual != expected:
             fail("expected {} to have the value '{}', had the value '{}'"
                  .format(prop, expected, actual))
 
     def verify_to_string_error(prop, msg):
-        prefix = "expected converting {} to string to generate the error " \
+        prefix = "expected converting '{}' to string to generate the error " \
                  "'{}', generated".format(prop, msg)
         try:
             dt.root.props[prop].to_string()
@@ -1460,33 +1599,30 @@
         except Exception as e:
             fail("{} the non-DTError '{}'".format(prefix, e))
 
-    def verify_raw_to_string_error(data, msg):
-        prefix = "expected to_string() called with data='{}' to generate " \
-                 "the error '{}', generated".format(data, msg)
-        try:
-            dtlib.to_string(data)
-            fail(prefix + " no error")
-        except dtlib.DTError as e:
-            if str(e) != msg:
-                fail("{} the error '{}'".format(prefix, e))
-        except Exception as e:
-            fail("{} the non-DTError '{}'".format(prefix, e))
+    verify_to_string("empty_string", "")
+    verify_to_string("string", "foo\tbar baz")
+
+    verify_to_string_error("u", "expected property 'u' on / in .tmp.dts to be assigned with 'u = \"string\"', not 'u = < 0x1 >;'")
+    verify_to_string_error("strings", "expected property 'strings' on / in .tmp.dts to be assigned with 'strings = \"string\"', not 'strings = \"foo\", \"bar\", \"baz\";'")
+    verify_to_string_error("invalid_string", r"value of property 'invalid_string' (b'\xff\x00') on / in .tmp.dts is not valid UTF-8")
+
+    # Test Property.to_strings()
 
     def verify_to_strings(prop, expected):
         try:
             actual = dt.root.props[prop].to_strings()
         except dtlib.DTError as e:
-            fail("failed to convert {} to strings: {}".format(prop, e))
+            fail("failed to convert '{}' to strings: {}".format(prop, e))
 
         if actual != expected:
             fail("expected {} to have the value '{}', had the value '{}'"
                  .format(prop, expected, actual))
 
     def verify_to_strings_error(prop, msg):
-        prefix = "expected converting {} to strings to generate the error " \
+        prefix = "expected converting '{}' to strings to generate the error " \
                  "'{}', generated".format(prop, msg)
         try:
-            dt.root.props[prop].to_string()
+            dt.root.props[prop].to_strings()
             fail(prefix + " no error")
         except dtlib.DTError as e:
             if str(e) != msg:
@@ -1494,18 +1630,27 @@
         except Exception as e:
             fail("{} the non-DTError '{}'".format(prefix, e))
 
+    verify_to_strings("empty_string", [""])
+    verify_to_strings("string", ["foo\tbar baz"])
+    verify_to_strings("strings", ["foo", "bar", "baz"])
+
+    verify_to_strings_error("u", "expected property 'u' on / in .tmp.dts to be assigned with 'u = \"string\", \"string\", ...', not u = < 0x1 >;")
+    verify_to_strings_error("invalid_strings", r"value of property 'invalid_strings' (b'foo\x00\xff\x00bar\x00') on / in .tmp.dts is not valid UTF-8")
+
+    # Test Property.to_node()
+
     def verify_to_node(prop, path):
         try:
             actual = dt.root.props[prop].to_node().path
         except dtlib.DTError as e:
-            fail("failed to convert {} to node: {}".format(prop, e))
+            fail("failed to convert '{}' to node: {}".format(prop, e))
 
         if actual != path:
             fail("expected {} to point to {}, pointed to {}"
                  .format(prop, path, actual))
 
     def verify_to_node_error(prop, msg):
-        prefix = "expected converting {} to node to generate the error " \
+        prefix = "expected converting '{}' to a node to generate the error " \
                  "'{}', generated".format(prop, msg)
         try:
             dt.root.props[prop].to_node()
@@ -1516,140 +1661,82 @@
         except Exception as e:
             fail("{} the non-DTError '{}'".format(prefix, e))
 
-    dt = parse(r"""
-/dts-v1/;
+    verify_to_node("ref", "/target")
+    verify_to_node("manualref", "/target")
 
-/ {
-	empty;
-	u1 = /bits/ 8 < 0x01 >;
-	u2 = /bits/ 8 < 0x01 0x02 >;
-	u3 = /bits/ 8 < 0x01 0x02 0x03 >;
-	u4 = /bits/ 8 < 0x01 0x02 0x03 0x04 >;
-	s1 = /bits/ 8 < 0xFF >;
-	s2 = /bits/ 8 < 0xFF 0xFE >;
-	s3 = /bits/ 8 < 0xFF 0xFF 0xFD >;
-	s4 = /bits/ 8 < 0xFF 0xFF 0xFF 0xFC >;
-	empty_string = "";
-	string = "foo\tbar baz";
-	invalid_string = "\xff";
-	non_null_terminated_string = [ 41 ]; // A
-	strings = "foo", "bar", "baz";
-	invalid_strings = "foo", "\xff", "bar";
-	non_null_terminated_strings = "foo", "bar", [ 01 ];
-	ref = <&{/target}>;
-	missingref = < 123 >;
-	badref;
+    verify_to_node_error("string", "expected property 'string' on / in .tmp.dts to be assigned with either 'string = < &foo >' or 'string = < (valid phandle number) >', not string = \"foo\\tbar baz\";")
+    verify_to_node_error("missingref", "the phandle given in property 'missingref' (123) on / in .tmp.dts does not exist")
 
-	target {
-	};
-};
-""")
+    # Test Property.to_path()
 
-    # Test to_num()
+    def verify_to_path(prop, path):
+        try:
+            actual = dt.root.props[prop].to_path().path
+        except dtlib.DTError as e:
+            fail("failed to convert '{}' to path: {}".format(prop, e))
 
-    verify_to_num("u1", 1, False, 0x01)
-    verify_to_num("u2", 2, False, 0x0102)
-    verify_to_num("u3", 3, False, 0x010203)
-    verify_to_num("u4", 4, False, 0x01020304)
-    verify_to_num("s1", 1, False, 0xFF)
-    verify_to_num("s2", 2, False, 0xFFFE)
-    verify_to_num("s3", 3, False, 0xFFFFFD)
-    verify_to_num("s4", 4, False, 0xFFFFFFFC)
+        if actual != path:
+            fail("expected {} to contain the path {}, contained {}"
+                 .format(prop, path, actual))
 
-    verify_to_num("u1", 1, True, 0x01)
-    verify_to_num("u2", 2, True, 0x0102)
-    verify_to_num("u3", 3, True, 0x010203)
-    verify_to_num("u4", 4, True, 0x01020304)
-    verify_to_num("s1", 1, True, -1)
-    verify_to_num("s2", 2, True, -2)
-    verify_to_num("s3", 3, True, -3)
-    verify_to_num("s4", 4, True, -4)
+    def verify_to_path_error(prop, msg):
+        prefix = "expected converting '{}' to a path to generate the error " \
+                 "'{}', generated".format(prop, msg)
+        try:
+            dt.root.props[prop].to_path()
+            fail(prefix + " no error")
+        except dtlib.DTError as e:
+            if str(e) != msg:
+                fail("{} the error '{}'".format(prefix, e))
+        except Exception as e:
+            fail("{} the non-DTError '{}'".format(prefix, e))
 
-    verify_to_num("u1", None, False, 0x01)
-    verify_to_num("u2", None, False, 0x0102)
-    verify_to_num("u3", None, False, 0x010203)
-    verify_to_num("u4", None, False, 0x01020304)
-    verify_to_num("s1", None, False, 0xFF)
-    verify_to_num("s2", None, False, 0xFFFE)
-    verify_to_num("s3", None, False, 0xFFFFFD)
-    verify_to_num("s4", None, False, 0xFFFFFFFC)
+    verify_to_path("path", "/target")
+    verify_to_path("manualpath", "/target")
 
-    verify_to_num("u1", None, True, 0x01)
-    verify_to_num("u2", None, True, 0x0102)
-    verify_to_num("u3", None, True, 0x010203)
-    verify_to_num("u4", None, True, 0x01020304)
-    verify_to_num("s1", None, True, -1)
-    verify_to_num("s2", None, True, -2)
-    verify_to_num("s3", None, True, -3)
-    verify_to_num("s4", None, True, -4)
+    verify_to_path_error("u", "expected property 'u' on / in .tmp.dts to be assigned with either 'u = &foo' or 'u = \"/path/to/node\"', not 'u = < 0x1 >;'")
+    verify_to_path_error("missingpath", "property 'missingpath' on / in .tmp.dts points to the non-existent node \"/missing\"")
 
-    verify_to_num_error("u1", 0, "'size' must be greater than zero, was 0 (for property 'u1' on /)")
-    verify_to_num_error("u1", -1, "'size' must be greater than zero, was -1 (for property 'u1' on /)")
-    verify_to_num_error("u1", 2, r"b'\x01' is 1 bytes long, expected 2 (for property 'u1' on /)")
-    verify_to_num_error("u2", 1, r"b'\x01\x02' is 2 bytes long, expected 1 (for property 'u2' on /)")
+    # Test top-level to_num() and to_nums()
+
+    def verify_raw_to_num(fn, prop, length, signed, expected):
+        try:
+            actual = fn(dt.root.props[prop].value, length, signed)
+        except dtlib.DTError as e:
+            fail("failed to convert '{}' to {} number(s) with {}: {}"
+                 .format(prop, "signed" if signed else "unsigned",
+                         fn.__name__, e))
+
+        if actual != expected:
+            fail("expected {}(<{}>, {}, {}) to be {}, was {}"
+                 .format(fn.__name__, prop, length, signed, expected, actual))
+
+    def verify_raw_to_num_error(fn, data, length, msg):
+        prefix = "expected {}() called with data='{}', length='{}' to " \
+                 "generate the error '{}', generated" \
+                 .format(fn.__name__, data, length, msg)
+        try:
+            fn(data, length)
+            fail(prefix + " no error")
+        except dtlib.DTError as e:
+            if str(e) != msg:
+                fail("{} the error '{}'".format(prefix, e))
+        except Exception as e:
+            fail("{} the non-DTError '{}'".format(prefix, e))
+
+    verify_raw_to_num(dtlib.to_num, "u", None, False, 1)
+    verify_raw_to_num(dtlib.to_num, "u", 4, False, 1)
+    verify_raw_to_num(dtlib.to_num, "s", None, False, 0xFFFFFFFF)
+    verify_raw_to_num(dtlib.to_num, "s", None, True, -1)
+    verify_raw_to_num(dtlib.to_nums, "empty", 4, False, [])
+    verify_raw_to_num(dtlib.to_nums, "u16", 2, False, [1, 2])
+    verify_raw_to_num(dtlib.to_nums, "two_s", 4, False, [0xFFFFFFFF, 0xFFFFFFFE])
+    verify_raw_to_num(dtlib.to_nums, "two_s", 4, True, [-1, -2])
 
     verify_raw_to_num_error(dtlib.to_num, 0, 0, "'0' has type 'int', expected 'bytes'")
-    verify_raw_to_num_error(dtlib.to_num, b"", 0, "'size' must be greater than zero, was 0")
-
-    # Test to_nums()
-
-    verify_to_nums("empty", 1, False, [])
-    verify_to_nums("u1", 1, False, [1])
-    verify_to_nums("u2", 1, False, [1, 2])
-    verify_to_nums("u3", 1, False, [1, 2, 3])
-    verify_to_nums("u4", 1, False, [1, 2, 3, 4])
-    verify_to_nums("s1", 1, False, [0xFF])
-    verify_to_nums("s2", 1, False, [0xFF, 0xFE])
-    verify_to_nums("s3", 1, False, [0xFF, 0xFF, 0xFD])
-    verify_to_nums("s4", 1, False, [0xFF, 0xFF, 0xFF, 0xFC])
-
-    verify_to_nums("u2", 2, False, [0x0102])
-    verify_to_nums("u4", 2, False, [0x0102, 0x0304])
-
-    verify_to_nums("u1", 1, True, [1])
-    verify_to_nums("u2", 1, True, [1, 2])
-    verify_to_nums("u3", 1, True, [1, 2, 3])
-    verify_to_nums("u4", 1, True, [1, 2, 3, 4])
-    verify_to_nums("s1", 1, True, [-1])
-    verify_to_nums("s2", 1, True, [-1, -2])
-    verify_to_nums("s3", 1, True, [-1, -1, -3])
-    verify_to_nums("s4", 1, True, [-1, -1, -1, -4])
-
-    verify_to_nums("s2", 2, True, [-2])
-    verify_to_nums("s4", 2, True, [-1, -4])
-
-    verify_to_nums_error("u1", 0, "'size' must be greater than zero, was 0 (for property 'u1' on /)")
-    verify_to_nums_error("u1", 2, r"b'\x01' is 1 bytes long, expected a length that's a multiple of 2 (for property 'u1' on /)")
-    verify_to_nums_error("u2", 3, r"b'\x01\x02' is 2 bytes long, expected a length that's a multiple of 3 (for property 'u2' on /)")
-
+    verify_raw_to_num_error(dtlib.to_num, b"", 0, "'length' must be greater than zero, was 0")
     verify_raw_to_num_error(dtlib.to_nums, 0, 0, "'0' has type 'int', expected 'bytes'")
-    verify_raw_to_num_error(dtlib.to_nums, b"", 0, "'size' must be greater than zero, was 0")
-
-    # Test to_string()
-
-    verify_to_string("empty_string", "")
-    verify_to_string("string", "foo\tbar baz")
-
-    verify_to_string_error("invalid_string", r"b'\xff\x00' is not valid UTF-8 (for property 'invalid_string' on /)")
-    verify_to_string_error("non_null_terminated_string", "b'A' is not null-terminated (for property 'non_null_terminated_string' on /)")
-
-    verify_raw_to_string_error(0, "'0' has type 'int', expected 'bytes'")
-
-    # Test to_strings()
-
-    verify_to_strings("empty_string", [""])
-    verify_to_strings("string", ["foo\tbar baz"])
-    verify_to_strings("strings", ["foo", "bar", "baz"])
-
-    verify_to_strings_error("invalid_strings", r"b'foo\x00\xff\x00bar\x00' is not valid UTF-8 (for property 'invalid_strings' on /)")
-    verify_to_strings_error("non_null_terminated_strings", r"b'foo\x00bar\x00\x01' is not null-terminated (for property 'non_null_terminated_strings' on /)")
-
-    # Test to_node()
-
-    verify_to_node("ref", "/target")
-
-    verify_to_node_error("missingref", "non-existent phandle 123 (for property 'missingref' on /)")
-    verify_to_node_error("badref", "b'' is 0 bytes long, expected 4 (for property 'badref' on /)")
+    verify_raw_to_num_error(dtlib.to_nums, b"", 0, "'length' must be greater than zero, was 0")
 
     #
     # Test duplicate label error
@@ -1820,7 +1907,7 @@
 
 / {
 	label: foo {
-		x = "/foo", "/foo", < 0x1 >;
+		x = &{/foo}, &label, < &label >;
 		phandle = < 0x1 >;
 	};
 };
@@ -1907,7 +1994,7 @@
 /dts-v1/;
 
 / {
-	aA0,._+*#?- = "/aA0,._+*#?@-", "/aA0,._+*#?@-";
+	aA0,._+*#?- = &_, &{/aA0,._+*#?@-};
 	+ = [ 00 ];
 	* = [ 02 ];
 	- = [ 01 ];