Skip to content

Commit fd5cd58

Browse files
committed
zcbor.py: Disambiguate list labels from map keys
Treat 'foo: bar' as a label in list context even if 'foo' is a known type, while keeping map behavior unchanged. Adds regression coverage in both the Python test suite and the corner case CDDL fixtures. Signed-off-by: Jon Ringle <jringle@gridpoint.com>
1 parent bc275a4 commit fd5cd58

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed

tests/cases/corner_cases.cddl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,17 @@ OptList = [
372372
optlist_int: int,
373373
?optlist: [uint],
374374
]
375+
376+
collision_foo = 2
377+
378+
CollisionList = [
379+
collision_foo: uint,
380+
]
381+
382+
CollisionMap = {
383+
collision_foo: uint,
384+
}
385+
386+
CollisionTypedKeyMap = {
387+
collision_foo => uint,
388+
}

tests/scripts/test_zcbor.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,37 @@ def test_intmax2(self):
764764
self.assertEqual(decoded.UINT_64, 18446744073709551615)
765765

766766

767+
class TestLabelKeyContext(TestCase):
768+
def test_list_label_does_not_become_key_when_type_exists(self):
769+
cddl_str = """
770+
foo = 2
771+
772+
my_list = [
773+
foo: uint
774+
]
775+
"""
776+
cddl_res = zcbor.DataTranslator.from_cddl(cddl_str, 16)
777+
cddl = cddl_res.my_types["my_list"]
778+
self.assertEqual("LIST", cddl.type)
779+
child = cddl.value[0]
780+
self.assertEqual("foo", child.label)
781+
self.assertIsNone(child.key)
782+
783+
def test_map_member_still_uses_key_when_type_exists(self):
784+
cddl_str = """
785+
foo = 2
786+
787+
my_map = {
788+
foo: uint
789+
}
790+
"""
791+
cddl_res = zcbor.DataTranslator.from_cddl(cddl_str, 16)
792+
cddl = cddl_res.my_types["my_map"]
793+
self.assertEqual("MAP", cddl.type)
794+
child = cddl.value[0]
795+
self.assertIsNotNone(child.key)
796+
797+
767798
class TestInvalidIdentifiers(TestCase):
768799
def test_invalid_identifiers0(self):
769800
cddl_res = zcbor.DataTranslator.from_cddl(

zcbor/zcbor.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ class CddlParser:
215215
- For "GROUP" and "UNION" types, there is no separate data item for the instance.
216216
"""
217217
def __init__(self, default_max_qty, my_types, my_control_groups, base_name=None,
218-
short_names=False, base_stem=''):
218+
short_names=False, base_stem='', in_map=False):
219219
self.id_prefix = "temp_" + str(counter())
220220
self.id_num = None # Unique ID number. Only populated if needed.
221221
# The value of the data item. Has different meaning for different
@@ -263,6 +263,7 @@ def __init__(self, default_max_qty, my_types, my_control_groups, base_name=None,
263263
# Stem which can be used when generating an id.
264264
self.base_stem = base_stem.replace("-", "_")
265265
self.short_names = short_names
266+
self.in_map = in_map
266267

267268
if type(self) not in type(self).cddl_regexes:
268269
self.cddl_regexes_init()
@@ -438,7 +439,7 @@ def init_kwargs(self):
438439
"""Return the kwargs that should be used to initialize a new instance of this class."""
439440
return {
440441
"my_types": self.my_types, "my_control_groups": self.my_control_groups,
441-
"short_names": self.short_names}
442+
"short_names": self.short_names, "in_map": self.in_map}
442443

443444
def set_id_prefix(self, id_prefix=''):
444445
self.id_prefix = id_prefix
@@ -708,7 +709,7 @@ def set_key_or_label(self, key_or_label):
708709
map. This code uses a slightly different method for choosing between label and key.
709710
If the string is recognized as a type, it is treated as a key. For use during CDDL parsing.
710711
"""
711-
if key_or_label in self.my_types:
712+
if self.in_map and key_or_label in self.my_types:
712713
self.set_key(self.parse(key_or_label)[0])
713714
assert self.key.type == "OTHER", "This should only be able to produce an OTHER key."
714715
if self.label is None:
@@ -785,13 +786,13 @@ def cddl_regexes_init(self):
785786
range_types = [
786787
(r'(?P<bracket>\[(?P<item>(?>[^[\]]+|(?&bracket))*)\])',
787788
lambda m_self, list_str: m_self.type_and_value(
788-
"LIST", lambda: m_self.parse(list_str))),
789+
"LIST", lambda: m_self.parse(list_str, in_map=False))),
789790
(r'(?P<paren>\((?P<item>(?>[^\(\)]+|(?&paren))*)\))',
790791
lambda m_self, group_str: m_self.type_and_value(
791-
"GROUP", lambda: m_self.parse(group_str))),
792+
"GROUP", lambda: m_self.parse(group_str, in_map=m_self.in_map))),
792793
(r'(?P<curly>{(?P<item>(?>[^{}]+|(?&curly))*)})',
793794
lambda m_self, map_str: m_self.type_and_value(
794-
"MAP", lambda: m_self.parse(map_str))),
795+
"MAP", lambda: m_self.parse(map_str, in_map=True))),
795796
(r'\'(?P<item>.*?)(?<!\\)\'',
796797
lambda m_self, string: m_self.type_and_value("BSTR", lambda: string)),
797798
(r'\"(?P<item>.*?)(?<!\\)\"',
@@ -1005,12 +1006,16 @@ def post_validate_control_group(self):
10051006
if c.type != "UINT" or c.value is None or c.value < 0:
10061007
raise TypeError("control group members must be literal positive integers.")
10071008

1008-
def parse(self, instr):
1009+
def parse(self, instr, in_map=None):
10091010
"""Parses entire instr and returns a list of instances."""
1011+
if in_map is None:
1012+
in_map = self.in_map
10101013
instr = instr.strip()
10111014
values = []
10121015
while instr != '':
1013-
value = type(self)(*self.init_args(), **self.init_kwargs(), base_stem=self.base_stem)
1016+
kwargs = self.init_kwargs()
1017+
kwargs["in_map"] = in_map
1018+
value = type(self)(*self.init_args(), **kwargs, base_stem=self.base_stem)
10141019
instr = value.get_value(instr)
10151020
values.append(value)
10161021
return values

0 commit comments

Comments
 (0)