idl-parser 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {idl_parser-0.2.2/idl_parser.egg-info → idl_parser-0.3.0}/PKG-INFO +2 -1
  2. {idl_parser-0.2.2 → idl_parser-0.3.0}/README.md +1 -0
  3. {idl_parser-0.2.2 → idl_parser-0.3.0}/README.rst +1 -0
  4. idl_parser-0.3.0/examples/scoped_name_example.py +121 -0
  5. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/const.py +1 -1
  6. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/enum.py +2 -0
  7. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/module.py +59 -20
  8. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/node.py +41 -1
  9. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/struct.py +3 -10
  10. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/type.py +19 -2
  11. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/typedef.py +8 -1
  12. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/union.py +3 -4
  13. {idl_parser-0.2.2 → idl_parser-0.3.0/idl_parser.egg-info}/PKG-INFO +2 -1
  14. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser.egg-info/SOURCES.txt +5 -0
  15. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser.egg-info/scm_file_list.json +5 -0
  16. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser.egg-info/scm_version.json +2 -2
  17. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/examples_test.py +41 -1
  18. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue30_test.py +1 -1
  19. idl_parser-0.3.0/tests/issue60_test.py +64 -0
  20. idl_parser-0.3.0/tests/issue67_test.py +117 -0
  21. idl_parser-0.3.0/tests/issue69_test.py +78 -0
  22. idl_parser-0.3.0/tests/issue72_test.py +143 -0
  23. {idl_parser-0.2.2 → idl_parser-0.3.0}/.github/workflows/release.yml +0 -0
  24. {idl_parser-0.2.2 → idl_parser-0.3.0}/.github/workflows/test.yml +0 -0
  25. {idl_parser-0.2.2 → idl_parser-0.3.0}/.gitignore +0 -0
  26. {idl_parser-0.2.2 → idl_parser-0.3.0}/.readthedocs.yaml +0 -0
  27. {idl_parser-0.2.2 → idl_parser-0.3.0}/LICENSE +0 -0
  28. {idl_parser-0.2.2 → idl_parser-0.3.0}/docs/api/index.rst +0 -0
  29. {idl_parser-0.2.2 → idl_parser-0.3.0}/docs/conf.py +0 -0
  30. {idl_parser-0.2.2 → idl_parser-0.3.0}/docs/index.rst +0 -0
  31. {idl_parser-0.2.2 → idl_parser-0.3.0}/docs/requirements.txt +0 -0
  32. {idl_parser-0.2.2 → idl_parser-0.3.0}/examples/_path.py +0 -0
  33. {idl_parser-0.2.2 → idl_parser-0.3.0}/examples/annotation_example.py +0 -0
  34. {idl_parser-0.2.2 → idl_parser-0.3.0}/examples/example.py +0 -0
  35. {idl_parser-0.2.2 → idl_parser-0.3.0}/examples/union_example.py +0 -0
  36. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/__init__.py +0 -0
  37. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/bitmask.py +0 -0
  38. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/bitset.py +0 -0
  39. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/exception.py +0 -0
  40. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/interface.py +0 -0
  41. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/parser.py +0 -0
  42. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/pragma.py +0 -0
  43. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser/token_buffer.py +0 -0
  44. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser.egg-info/dependency_links.txt +0 -0
  45. {idl_parser-0.2.2 → idl_parser-0.3.0}/idl_parser.egg-info/top_level.txt +0 -0
  46. {idl_parser-0.2.2 → idl_parser-0.3.0}/pyproject.toml +0 -0
  47. {idl_parser-0.2.2 → idl_parser-0.3.0}/setup.cfg +0 -0
  48. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/__init__.py +0 -0
  49. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/annotation_test.py +0 -0
  50. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/bitmask_bitset_test.py +0 -0
  51. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/forward_declaration_test.py +0 -0
  52. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/generalization_test.py +0 -0
  53. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/basic_module_test.idl +0 -0
  54. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/circular/circular_a.idl +0 -0
  55. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/circular/circular_b.idl +0 -0
  56. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/circular/diamond.idl +0 -0
  57. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/forward_declaration.idl +0 -0
  58. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/generalization.idl +0 -0
  59. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/generalization_extended.idl +0 -0
  60. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/included_idl.idl +0 -0
  61. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/including_idl.idl +0 -0
  62. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/invalid_idl.idl +0 -0
  63. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/issue12_subpath/relative_include.idl +0 -0
  64. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/issue12_subpath/sensor_msgs/msg/Imu.idl +0 -0
  65. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/issue12_subpath/std/msg/Header.idl +0 -0
  66. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/issue18_idl42_int_types.idl +0 -0
  67. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/issue28_pragma_keylist.idl +0 -0
  68. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/issue9_const_pragma.idl +0 -0
  69. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/issue9_tabs_bounded_string.idl +0 -0
  70. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/issue9_whitespace_variants.idl +0 -0
  71. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/idls/multi_module_test.idl +0 -0
  72. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/include_test.py +0 -0
  73. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/invalid_idl_test.py +0 -0
  74. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue12_test.py +0 -0
  75. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue18_test.py +0 -0
  76. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue31_test.py +0 -0
  77. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue39_test.py +0 -0
  78. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue43_test.py +0 -0
  79. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue45_test.py +0 -0
  80. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue48_test.py +0 -0
  81. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue49_test.py +0 -0
  82. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue50_test.py +0 -0
  83. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue52_test.py +0 -0
  84. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue53_test.py +0 -0
  85. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue57_test.py +0 -0
  86. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/issue9_test.py +0 -0
  87. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/key_annotation_test.py +0 -0
  88. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/module_test.py +0 -0
  89. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/multimodule_test.py +0 -0
  90. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/pragma_test.py +0 -0
  91. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/simple_dic_test.py +0 -0
  92. {idl_parser-0.2.2 → idl_parser-0.3.0}/tests/syntax_error_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: idl_parser
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Very simple OMG IDL (Interface Definition Language) parser. This parses IDL files and outputs intermediate class objects.
5
5
  Author-email: ysuga <ysuga@ysuga.net>
6
6
  License-Expression: GPL-3.0-or-later
@@ -102,6 +102,7 @@ More examples are in the ``examples`` folder of the repository:
102
102
  - ``example.py``: interfaces, typedefs, unions and structs
103
103
  - ``union_example.py``: unions (discriminator kinds, case and ``default`` labels, member types)
104
104
  - ``annotation_example.py``: IDL 4 annotations (``@key``, ``@range``, ...) and struct keys
105
+ - ``scoped_name_example.py``: same-named types in several modules (relative ``X``, ``C::X`` and absolute ``::B::X`` names)
105
106
 
106
107
  https://github.com/sugarsweetrobotics/idl_parser/tree/main/examples
107
108
 
@@ -77,6 +77,7 @@ More examples are in the [examples](examples/) folder:
77
77
  | [`example.py`](examples/example.py) | interfaces, typedefs, unions and structs |
78
78
  | [`union_example.py`](examples/union_example.py) | unions: discriminator kinds, case and `default` labels, member types |
79
79
  | [`annotation_example.py`](examples/annotation_example.py) | IDL 4 annotations (`@key`, `@range`, ...) and struct keys |
80
+ | [`scoped_name_example.py`](examples/scoped_name_example.py) | same-named types in several modules: relative (`X`, `C::X`) and absolute (`::B::X`) names |
80
81
 
81
82
  Run them with e.g. `python examples/union_example.py` (in a source checkout no install is needed).
82
83
 
@@ -79,6 +79,7 @@ More examples are in the ``examples`` folder of the repository:
79
79
  - ``example.py``: interfaces, typedefs, unions and structs
80
80
  - ``union_example.py``: unions (discriminator kinds, case and ``default`` labels, member types)
81
81
  - ``annotation_example.py``: IDL 4 annotations (``@key``, ``@range``, ...) and struct keys
82
+ - ``scoped_name_example.py``: same-named types in several modules (relative ``X``, ``C::X`` and absolute ``::B::X`` names)
82
83
 
83
84
  https://github.com/sugarsweetrobotics/idl_parser/tree/main/examples
84
85
 
@@ -0,0 +1,121 @@
1
+ """Example: resolve type names across modules (relative and absolute names).
2
+
3
+ When several modules define a type with the same name, a type name is
4
+ resolved with the IDL scoping rules (issues #69, #72):
5
+
6
+ - a relative name (``X``, ``C::X``) is looked up from the scope where it is
7
+ written, innermost first, then in the outer scopes;
8
+ - an absolute name (``::B::X``) is looked up from the global scope only.
9
+
10
+ Shows how to get
11
+ - the definition a member, argument or return type refers to (``obj`` / ``type``),
12
+ - the name as written (``ref_name``) and the name of the definition (``name``),
13
+ - a typedef resolved in the scope where it is defined,
14
+ - ``find_types()`` with and without a scope.
15
+
16
+ Run::
17
+
18
+ python examples/scoped_name_example.py
19
+ """
20
+ import _path # noqa: F401 (use the idl_parser of this checkout)
21
+
22
+ from idl_parser import parser
23
+
24
+ IDL = '''
25
+ // A global Point, and a Point in each module
26
+ struct Point { long id; };
27
+
28
+ module geometry {
29
+ struct Point { double x; double y; };
30
+ typedef Point Origin; // geometry::Point (the scope of the typedef)
31
+
32
+ module d3 {
33
+ struct Point { double x; double y; double z; };
34
+ };
35
+ };
36
+
37
+ module robot {
38
+ struct Point { long joint; };
39
+
40
+ module arm {
41
+ struct Point { double angle; };
42
+
43
+ interface Controller {
44
+ // relative names: looked up from robot::arm, then robot, then global
45
+ Point current(); // robot::arm::Point
46
+ void move_joint(in robot::Point p); // robot::Point
47
+ void move_to(in geometry::Point p); // geometry::Point
48
+ void move_to_3d(in geometry::d3::Point p);
49
+ // absolute names: from the global scope only
50
+ void mark(in ::Point p); // the global Point
51
+ void move_arm(in ::robot::arm::Point p);
52
+ geometry::Origin origin(); // typedef of geometry::Point
53
+ };
54
+ };
55
+
56
+ struct Pose {
57
+ Point joint; // robot::Point
58
+ arm::Point arm; // relative name with a scope: robot::arm::Point
59
+ sequence<geometry::Point> path; // element type is resolved too
60
+ ::Point marker; // the global Point
61
+ };
62
+ };
63
+ '''
64
+
65
+
66
+ def path(node):
67
+ """Full path of a definition ('' for the global scope is shown as '::')."""
68
+ p = node.full_path.lstrip(':')
69
+ return p if '::' in p else '::' + p
70
+
71
+
72
+ def show(label, written, definition):
73
+ print(' %-22s %-24s -> %s' % (label, written, path(definition)))
74
+
75
+
76
+ def main():
77
+ global_module = parser.IDLParser().load(IDL)
78
+ robot = global_module.module_by_name('robot')
79
+ arm = robot.module_by_name('arm')
80
+
81
+ print('robot::arm::Controller (name as written -> definition)')
82
+ controller = arm.interface_by_name('Controller')
83
+ for method in controller.methods:
84
+ if method.returns.name != 'void':
85
+ show(method.name + '() returns', method.returns.ref_name, method.returns.obj)
86
+ for a in method.arguments:
87
+ show(method.name + '(' + a.name + ')', a.type.ref_name, a.type.obj)
88
+ print()
89
+
90
+ # For a struct member, .type is the definition itself
91
+ print('robot::Pose members (member -> definition)')
92
+ pose = robot.struct_by_name('Pose')
93
+ for m in pose.members:
94
+ t = m.type
95
+ if t.is_sequence:
96
+ print(' %-22s -> sequence of %s' % (m.name, path(t.inner_type.obj)))
97
+ else:
98
+ print(' %-22s -> %s' % (m.name, path(t)))
99
+ print()
100
+
101
+ # The typedef is resolved where it is defined (module geometry),
102
+ # not where it is used (module robot::arm).
103
+ typedef = controller.method_by_name('origin').returns.obj
104
+ print('typedef %s -> %s' % (path(typedef), path(typedef.type)))
105
+ print()
106
+
107
+ # type.name is the name of the definition, without its scope
108
+ t = controller.method_by_name('move_to').arguments[0].type
109
+ print('name: %s, ref_name: %s, full path: %s' % (t.name, t.ref_name, path(t.obj)))
110
+ print()
111
+
112
+ # find_types(): with a scope the IDL scoping rules are used;
113
+ # without one, every definition with that name is returned.
114
+ print('find_types("Point", scope=robot::arm):', [path(n) for n in global_module.find_types('Point', scope=arm)])
115
+ print('find_types("Point", scope=robot): ', [path(n) for n in global_module.find_types('Point', scope=robot)])
116
+ print('find_types("::Point"): ', [path(n) for n in global_module.find_types('::Point')])
117
+ print('find_types("Point") (no scope): ', sorted(path(n) for n in global_module.find_types('Point')))
118
+
119
+
120
+ if __name__ == '__main__':
121
+ main()
@@ -35,7 +35,7 @@ class IDLConst(node.IDLNode):
35
35
 
36
36
  @property
37
37
  def type(self):
38
- return self.root_node.find_types(self.typename)[0]
38
+ return self.root_node.find_types(self.typename, scope=self)[0]
39
39
  @property
40
40
  def value(self):
41
41
  return self._value
@@ -54,6 +54,8 @@ class IDLEnum(node.IDLNode):
54
54
  if quiet:
55
55
  return 'enum %s' % name
56
56
  dic = { 'enum %s' % name : [v.to_simple_dic() for v in self.values] }
57
+ if member_only:
58
+ return list(dic.values())[0]
57
59
  return dic
58
60
 
59
61
 
@@ -461,29 +461,68 @@ class IDLModule(node.IDLNode):
461
461
  for m in self.typedefs:
462
462
  retval.append(func(m))
463
463
 
464
- def find_types(self, full_typename, parent=None):
464
+ def find_types(self, full_typename, parent=None, scope=None):
465
+ """Return the type definitions named ``full_typename`` (a list).
466
+
467
+ ``scope`` is the node where the name is written (issue #72). When it
468
+ is given, the name is resolved with the IDL scoping rules: it is
469
+ looked up in the innermost enclosing scope first, then in the outer
470
+ ones (``X`` in module ``B`` finds ``B::X`` before ``X`` elsewhere).
471
+ A name starting with ``::`` is looked up from the global scope only.
472
+ If the scoping rules find nothing, every definition whose name or
473
+ full path is ``full_typename`` is returned, as before.
474
+ """
465
475
  if type.is_primitive(full_typename):
466
476
  return [type.IDLType(full_typename, self)]
467
- typenode = []
468
-
469
- def parse_node(s, name=str(full_typename)):
470
- if parent:
471
- if parent.full_path + '::' + name.strip() == s.full_path or name.strip() == s.full_path:
472
- typenode.append(s)
473
- else:
474
- if s.name == name.strip() or s.full_path == name.strip():
475
- typenode.append(s)
476
-
477
+ # A fully qualified name may start with '::' ("::M::A", issue #69).
478
+ # full_path has no leading '::' ("M::A"), so drop it before comparing.
479
+ name = str(full_typename).strip()
480
+ absolute = name.startswith('::')
481
+ if absolute:
482
+ name = name.lstrip(':').strip()
483
+
484
+ nodes = []
477
485
  def parse_module(m):
478
486
  m.for_each_module(parse_module)
479
- m.for_each_struct(parse_node)
480
- m.for_each_typedef(parse_node)
481
- m.for_each_enum(parse_node)
482
- m.for_each_bitmask(parse_node)
483
- m.for_each_bitset(parse_node)
484
- m.for_each_union(parse_node)
485
- m.for_each_interface(parse_node)
486
-
487
+ for each in (m.for_each_struct, m.for_each_typedef, m.for_each_enum,
488
+ m.for_each_bitmask, m.for_each_bitset, m.for_each_union,
489
+ m.for_each_interface):
490
+ each(nodes.append)
487
491
  parse_module(self)
488
492
 
489
- return typenode
493
+ def with_full_path(full):
494
+ return [s for s in nodes if s.full_path.lstrip(':') == full]
495
+
496
+ if absolute:
497
+ # '::X' always names X from the global scope
498
+ return with_full_path(name)
499
+
500
+ if scope is not None:
501
+ for prefix in _enclosing_scopes(scope):
502
+ found = with_full_path(prefix + sep + name if prefix else name)
503
+ if len(found) > 0:
504
+ return found
505
+
506
+ if parent:
507
+ prefix = parent.full_path.lstrip(':')
508
+ return [s for s in nodes
509
+ if s.full_path.lstrip(':') in ((prefix + sep + name if prefix else name), name)]
510
+ return [s for s in nodes if s.name == name or s.full_path.lstrip(':') == name]
511
+
512
+
513
+ _scope_classnames = ('IDLModule', 'IDLStruct', 'IDLUnion', 'IDLInterface')
514
+
515
+ def _enclosing_scopes(node):
516
+ """Full paths (without a leading '::') of the scopes enclosing ``node``,
517
+ innermost first and ending with the global scope ('')."""
518
+ scopes = []
519
+ n = node
520
+ while n is not None:
521
+ if n.classname in _scope_classnames:
522
+ path = n.full_path.lstrip(':')
523
+ if path not in scopes:
524
+ scopes.append(path)
525
+ n = n.parent
526
+ if '' not in scopes:
527
+ scopes.append('')
528
+ return scopes
@@ -308,9 +308,49 @@ class IDLNode(object):
308
308
  walk(self.root_node)
309
309
  return len(found) > 0
310
310
 
311
+ def check_element_types(self, typ):
312
+ """Raise InvalidDataTypeException if an element type of the sequence
313
+ or array ``typ`` (nested ones included) is not declared (issue #67).
314
+
315
+ A type that has been forward-declared ("interface A;", "struct A;",
316
+ "union A;") may be used before its definition, as with a direct member.
317
+ """
318
+ from . import exception
319
+ while getattr(typ, 'is_sequence', False) or getattr(typ, 'is_array', False):
320
+ typ = typ.inner_type
321
+ if typ.classname != 'IDLBasicType':
322
+ continue
323
+ name = typ.name.strip()
324
+ if name.startswith(self.sep):
325
+ name = name[len(self.sep):]
326
+ if len(typ.resolve()) > 0:
327
+ continue
328
+ if self.is_pending_forward_declaration(name):
329
+ continue
330
+ if self._is_enclosing_definition(name):
331
+ continue # recursive type: "struct Node { sequence<Node> children; };"
332
+ print('Can not find Data Type (%s)\n' % typ.name)
333
+ raise exception.InvalidDataTypeException(
334
+ message='Can not find Data Type (%s)' % typ.name)
335
+
336
+ def _is_enclosing_definition(self, name):
337
+ """True if name is a struct or union that encloses this node and is
338
+ still being parsed (so it is not registered in its module yet)."""
339
+ n = self.parent
340
+ while n is not None and not n.is_root:
341
+ if n.classname in ('IDLStruct', 'IDLUnion'):
342
+ if name == n.name or name == n.full_path.lstrip(':'):
343
+ return True
344
+ n = n.parent
345
+ return False
346
+
311
347
  def refine_typename(self, typ):
312
348
  return self._refine_typename(typ.name)
313
349
 
350
+ def _lookup_scope(self):
351
+ """The node whose scope type names written here are resolved from."""
352
+ return self
353
+
314
354
  def _refine_typename(self, name):
315
355
  from . import type as idl_type
316
356
  name = name.strip()
@@ -323,7 +363,7 @@ class IDLNode(object):
323
363
  bounded = idl_type.parse_bounded_string(name)
324
364
  if bounded is not None:
325
365
  return bounded[0]
326
- typs = self.root_node.find_types(name)
366
+ typs = self.root_node.find_types(name, scope=self._lookup_scope())
327
367
  if len(typs) == 0:
328
368
  # Not resolvable yet (e.g. a forward-declared interface): keep the name
329
369
  return name
@@ -41,8 +41,6 @@ class IDLMember(node.IDLNode):
41
41
  if recursive:
42
42
  if self.type.is_primitive:
43
43
  return str(self.type) + ' ' + self.name
44
- elif self.type.obj.is_enum:
45
- return str('enum') + ' ' + self.name
46
44
  dic = {str(self.type) +' ' + self.name :
47
45
  self.type.obj.to_simple_dic(recursive=recursive, member_only=True)}
48
46
  return dic
@@ -59,16 +57,10 @@ class IDLMember(node.IDLNode):
59
57
  @property
60
58
  def type(self):
61
59
  if self._type.classname == 'IDLBasicType': # Struct
62
- typs = self.root_node.find_types(self._type.name)
60
+ typs = self._type.resolve() # from this member's scope (issue #72)
63
61
  if len(typs) == 0:
64
- print('Can not find Data Type (%s)\n' % self._type.name)
62
+ print('Can not find Data Type (%s)\n' % self._type.ref_name)
65
63
  raise exception.InvalidDataTypeException()
66
- elif len(typs) > 1:
67
- typs = self.root_node.find_types(self._type.name, parent=self.parent.parent)
68
- if len(typs) == 0:
69
- print('Can not find Data Type (%s)\n' % self._type.name)
70
- raise exception.InvalidDataTypeException()
71
-
72
64
  return typs[0]
73
65
  return self._type
74
66
 
@@ -83,6 +75,7 @@ class IDLMember(node.IDLNode):
83
75
  def post_process(self):
84
76
  if self._type.classname == 'IDLBasicType' and self.is_pending_forward_declaration(self._type.name):
85
77
  return # forward-declared type; resolved via .type once it is defined
78
+ self.check_element_types(self._type) # sequence / array elements (issue #67)
86
79
  self._type._name = self.refine_typename(self.type)
87
80
 
88
81
 
@@ -383,16 +383,33 @@ class IDLPrimitive(IDLTypeBase):
383
383
  return (self.parent.full_path + self.sep + self.name).strip()
384
384
  class IDLBasicType(IDLTypeBase):
385
385
  def __init__(self, name, parent):
386
+ # The node where the name is written: the name is resolved from its
387
+ # scope (issue #72). The parent of a type node is the root node.
388
+ self._scope = parent
389
+ self._ref_name = name.strip()
386
390
  super(IDLBasicType, self).__init__('IDLBasicType', name, parent.root_node)
387
391
  self._verbose = True
388
392
  #if self.name.find('['):
389
393
  # self._name = self.name[self.name.find('[')+1:]
390
394
  self._name = self.refine_typename(self)
391
395
 
396
+ @property
397
+ def ref_name(self):
398
+ """The type name as written (``'A::X'``, ``'::B::X'``). ``name`` is
399
+ the name of the definition without its scope (``'X'``)."""
400
+ return self._ref_name
401
+
402
+ def _lookup_scope(self):
403
+ return self._scope
404
+
405
+ def resolve(self):
406
+ """The definitions this name refers to, found from the scope where
407
+ it is written (a list; empty if it is not defined)."""
408
+ return self.root_node.find_types(self._ref_name, scope=self._scope)
409
+
392
410
  @property
393
411
  def obj(self):
394
- global_module = self.root_node
395
- typs = global_module.find_types(self.name)
412
+ typs = self.resolve()
396
413
  if len(typs) == 0:
397
414
  return None
398
415
  else:
@@ -40,7 +40,13 @@ class IDLTypedef(node.IDLNode):
40
40
  @property
41
41
  def type(self):
42
42
  if self._type.classname == 'IDLBasicType': # Struct
43
- return self.root_node.find_types(self._type.name)[0]
43
+ typs = self._type.resolve() # from this typedef's scope (issue #72)
44
+ if len(typs) == 0:
45
+ # issue #69: report an unknown type instead of an IndexError
46
+ from . import exception
47
+ raise exception.InvalidDataTypeException(
48
+ message='Can not find Data Type (%s)' % self._type.ref_name)
49
+ return typs[0]
44
50
  return self._type
45
51
 
46
52
  def get_type(self, extract_typedef=False):
@@ -83,4 +89,5 @@ class IDLTypedef(node.IDLNode):
83
89
  def _post_process(self):
84
90
  if self._type.classname == 'IDLBasicType' and self.is_pending_forward_declaration(self._type.name):
85
91
  return # forward-declared type; resolved via .type once it is defined
92
+ self.check_element_types(self._type) # sequence / array elements (issue #67)
86
93
  self._type._name = self.refine_typename(self.type)
@@ -52,8 +52,6 @@ class IDLUnionMember(node.IDLNode):
52
52
  if recursive:
53
53
  if self.type.is_primitive:
54
54
  return str(self.type) + ' ' + self.name
55
- elif self.type.obj.is_enum:
56
- return str('enum') + ' ' + self.name
57
55
  dic = {str(self.type) +' ' + self.name :
58
56
  self.type.obj.to_simple_dic(recursive=recursive, member_only=True)}
59
57
  return dic
@@ -72,9 +70,9 @@ class IDLUnionMember(node.IDLNode):
72
70
  @property
73
71
  def type(self):
74
72
  if self._type.classname == 'IDLBasicType': # Union
75
- typs = self.root_node.find_types(self._type.name)
73
+ typs = self._type.resolve() # from this member's scope (issue #72)
76
74
  if len(typs) == 0:
77
- print('Can not find Data Type (%s)\n' % self._type.name)
75
+ print('Can not find Data Type (%s)\n' % self._type.ref_name)
78
76
  raise exception.InvalidDataTypeException()
79
77
  return typs[0]
80
78
  return self._type
@@ -98,6 +96,7 @@ class IDLUnionMember(node.IDLNode):
98
96
  def post_process(self):
99
97
  if self._type.classname == 'IDLBasicType' and self.is_pending_forward_declaration(self._type.name):
100
98
  return # forward-declared type; resolved via .type once it is defined
99
+ self.check_element_types(self._type) # sequence / array elements (issue #67)
101
100
  self._type._name = self.refine_typename(self.type)
102
101
 
103
102
  class IDLUnion(node.IDLNode):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: idl_parser
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Very simple OMG IDL (Interface Definition Language) parser. This parses IDL files and outputs intermediate class objects.
5
5
  Author-email: ysuga <ysuga@ysuga.net>
6
6
  License-Expression: GPL-3.0-or-later
@@ -102,6 +102,7 @@ More examples are in the ``examples`` folder of the repository:
102
102
  - ``example.py``: interfaces, typedefs, unions and structs
103
103
  - ``union_example.py``: unions (discriminator kinds, case and ``default`` labels, member types)
104
104
  - ``annotation_example.py``: IDL 4 annotations (``@key``, ``@range``, ...) and struct keys
105
+ - ``scoped_name_example.py``: same-named types in several modules (relative ``X``, ``C::X`` and absolute ``::B::X`` names)
105
106
 
106
107
  https://github.com/sugarsweetrobotics/idl_parser/tree/main/examples
107
108
 
@@ -13,6 +13,7 @@ docs/api/index.rst
13
13
  examples/_path.py
14
14
  examples/annotation_example.py
15
15
  examples/example.py
16
+ examples/scoped_name_example.py
16
17
  examples/union_example.py
17
18
  idl_parser/__init__.py
18
19
  idl_parser/bitmask.py
@@ -57,6 +58,10 @@ tests/issue50_test.py
57
58
  tests/issue52_test.py
58
59
  tests/issue53_test.py
59
60
  tests/issue57_test.py
61
+ tests/issue60_test.py
62
+ tests/issue67_test.py
63
+ tests/issue69_test.py
64
+ tests/issue72_test.py
60
65
  tests/issue9_test.py
61
66
  tests/key_annotation_test.py
62
67
  tests/module_test.py
@@ -14,6 +14,7 @@
14
14
  "examples/_path.py",
15
15
  "examples/annotation_example.py",
16
16
  "examples/example.py",
17
+ "examples/scoped_name_example.py",
17
18
  "examples/union_example.py",
18
19
  "idl_parser/__init__.py",
19
20
  "idl_parser/bitmask.py",
@@ -72,6 +73,10 @@
72
73
  "tests/issue52_test.py",
73
74
  "tests/issue53_test.py",
74
75
  "tests/issue57_test.py",
76
+ "tests/issue60_test.py",
77
+ "tests/issue67_test.py",
78
+ "tests/issue69_test.py",
79
+ "tests/issue72_test.py",
75
80
  "tests/issue9_test.py",
76
81
  "tests/key_annotation_test.py",
77
82
  "tests/module_test.py",
@@ -1,7 +1,7 @@
1
1
  {
2
- "tag": "0.2.2",
2
+ "tag": "0.3.0",
3
3
  "distance": 0,
4
- "node": "g58eea3fbf48f5d5a8a6c1178dde50925ecfded03",
4
+ "node": "g0f3a257197ff60ce02868622378b3843fa21c028",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-10-07"
@@ -50,7 +50,8 @@ class ExamplesRunAsScriptTest(unittest.TestCase):
50
50
 
51
51
  def test_examples_exist(self):
52
52
  names = [os.path.basename(p) for p in example_scripts()]
53
- for name in ('example.py', 'union_example.py', 'annotation_example.py'):
53
+ for name in ('example.py', 'union_example.py', 'annotation_example.py',
54
+ 'scoped_name_example.py'):
54
55
  self.assertIn(name, names)
55
56
 
56
57
  def test_each_example_runs_as_script(self):
@@ -174,5 +175,44 @@ class AnnotationExampleTest(unittest.TestCase):
174
175
  self.assertIn('"APPENDABLE"', self.text)
175
176
 
176
177
 
178
+ class ScopedNameExampleTest(unittest.TestCase):
179
+
180
+ @classmethod
181
+ def setUpClass(cls):
182
+ cls.text = run_main('scoped_name_example')
183
+
184
+ def assertResolved(self, label, written, definition):
185
+ for line in self.text.splitlines():
186
+ if line.split() == [*label.split(), written, '->', definition]:
187
+ return
188
+ self.fail('no line for %s %s -> %s in output:\n%s' % (label, written, definition, self.text))
189
+
190
+ def test_relative_names(self):
191
+ self.assertResolved('current() returns', 'Point', 'robot::arm::Point')
192
+ self.assertResolved('move_joint(p)', 'robot::Point', 'robot::Point')
193
+ self.assertResolved('move_to(p)', 'geometry::Point', 'geometry::Point')
194
+ self.assertResolved('move_to_3d(p)', 'geometry::d3::Point', 'geometry::d3::Point')
195
+
196
+ def test_absolute_names(self):
197
+ self.assertResolved('mark(p)', '::Point', '::Point')
198
+ self.assertResolved('move_arm(p)', '::robot::arm::Point', 'robot::arm::Point')
199
+
200
+ def test_struct_members(self):
201
+ self.assertIn(' joint -> robot::Point', self.text)
202
+ self.assertIn(' arm -> robot::arm::Point', self.text)
203
+ self.assertIn(' path -> sequence of geometry::Point', self.text)
204
+ self.assertIn(' marker -> ::Point', self.text)
205
+
206
+ def test_typedef_and_names(self):
207
+ self.assertIn('typedef geometry::Origin -> geometry::Point', self.text)
208
+ self.assertIn('name: Point, ref_name: geometry::Point, full path: geometry::Point', self.text)
209
+
210
+ def test_find_types(self):
211
+ self.assertIn("scope=robot::arm): ['robot::arm::Point']", self.text)
212
+ self.assertIn("scope=robot): ['robot::Point']", self.text)
213
+ self.assertIn("(no scope): ['::Point', 'geometry::Point', 'geometry::d3::Point', "
214
+ "'robot::Point', 'robot::arm::Point']", self.text)
215
+
216
+
177
217
  if __name__ == '__main__':
178
218
  unittest.main()
@@ -68,7 +68,7 @@ class MemberTypeSimpleDicTest(unittest.TestCase):
68
68
  'WithSeqInline': [{'sequence<long> q': {'sequence<long>': 'long'}}],
69
69
  'WithArray': [{'long[3] a': 'long[3]'}],
70
70
  'WithStruct': [{'T t': ['long s']}],
71
- 'WithEnum': ['enum e'],
71
+ 'WithEnum': [{'E e': [{'A': 0}, {'B': 1}]}],
72
72
  'WithBitmask': [{'F f': [{'F1': 0}, {'F2': 1}]}],
73
73
  'WithBitset': [{'BS bs': [{'b': 'bitfield<3, octet>'}]}],
74
74
  'WithSeqTypedef': [{'DSeq d': {'typedef sequence<double> DSeq':
@@ -0,0 +1,64 @@
1
+ """to_simple_dic(recursive=True) expands enum members like other types (issue #60).
2
+
3
+ A direct enum member used to become ``'enum <member name>'`` (no type name,
4
+ no values), while ``sequence<E>`` / typedef of an enum expanded the values.
5
+ Enum members now expand the same way as struct and bitmask members.
6
+ """
7
+ import unittest
8
+
9
+ from idl_parser import parser
10
+
11
+ IDL = '''
12
+ module M {
13
+ enum E { A, B };
14
+ bitmask F { F1, F2 };
15
+ typedef E EAlias;
16
+ typedef sequence<E> ESeq;
17
+ struct S { E e; sequence<E> se; EAlias ea; ESeq es; F f; };
18
+ union U switch (E) { case A: E ue; case B: long l; };
19
+ };
20
+ '''
21
+
22
+ VALUES = [{'A': 0}, {'B': 1}]
23
+
24
+
25
+ class EnumMemberSimpleDicTest(unittest.TestCase):
26
+
27
+ def setUp(self):
28
+ self.m = parser.IDLParser().load(IDL).module_by_name('M')
29
+
30
+ def _members(self):
31
+ return self.m.struct_by_name('S').to_simple_dic(recursive=True)['struct S']
32
+
33
+ def test_direct_member(self):
34
+ self.assertEqual(self._members()[0], {'E e': VALUES})
35
+
36
+ def test_sequence_of_enum(self):
37
+ self.assertEqual(self._members()[1], {'sequence<E> se': {'sequence<E>': VALUES}})
38
+
39
+ def test_typedef_of_enum(self):
40
+ self.assertEqual(self._members()[2], {'EAlias ea': {'typedef E EAlias': VALUES}})
41
+
42
+ def test_typedef_of_sequence_of_enum(self):
43
+ self.assertEqual(self._members()[3],
44
+ {'ESeq es': {'typedef sequence<E> ESeq': {'sequence<E>': VALUES}}})
45
+
46
+ def test_same_shape_as_bitmask(self):
47
+ self.assertEqual(self._members()[4], {'F f': [{'F1': 0}, {'F2': 1}]})
48
+
49
+ def test_union_member(self):
50
+ self.assertEqual(self.m.union_by_name('U').to_simple_dic(recursive=True),
51
+ {'union U': [{'E ue': VALUES}, 'long l']})
52
+
53
+ def test_enum_itself_unchanged(self):
54
+ e = self.m.enum_by_name('E')
55
+ self.assertEqual(e.to_simple_dic(), {'enum E': VALUES})
56
+ self.assertEqual(e.to_simple_dic(member_only=True), VALUES)
57
+ self.assertEqual(e.to_simple_dic(quiet=True), 'enum E')
58
+
59
+ def test_non_recursive_unchanged(self):
60
+ self.assertEqual(self.m.struct_by_name('S').to_simple_dic()['struct S'][0], {'e': 'E'})
61
+
62
+
63
+ if __name__ == '__main__':
64
+ unittest.main()