|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2005 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Parse a CORBA IDL file and retrieve modules, interfaces, methods and |
|
8 attributes. |
|
9 |
|
10 Parse enough of a CORBA IDL file to recognize module, interface and method |
|
11 definitions and to find out the superclasses of an interface as well as its |
|
12 attributes. |
|
13 |
|
14 It is based on the Python class browser found in this package. |
|
15 """ |
|
16 |
|
17 import re |
|
18 |
|
19 import Utilities |
|
20 import Utilities.ClassBrowsers as ClassBrowsers |
|
21 from . import ClbrBaseClasses |
|
22 |
|
23 SUPPORTED_TYPES = [ClassBrowsers.IDL_SOURCE] |
|
24 |
|
25 _getnext = re.compile( |
|
26 r""" |
|
27 (?P<String> |
|
28 " [^"\\\n]* (?: \\. [^"\\\n]*)* " |
|
29 ) |
|
30 |
|
31 | (?P<Comment> |
|
32 ^ [ \t]* // .*? $ |
|
33 | |
|
34 ^ [ \t]* /\* .*? \*/ |
|
35 ) |
|
36 |
|
37 | (?P<Method> |
|
38 ^ |
|
39 (?P<MethodIndent> [ \t]* ) |
|
40 (?: oneway [ \t]+ )? |
|
41 (?: [a-zA-Z0-9_:]+ | void ) [ \t]* |
|
42 (?P<MethodName> [a-zA-Z_] [a-zA-Z0-9_]* ) |
|
43 [ \t]* |
|
44 \( |
|
45 (?P<MethodSignature> [^)]*? ) |
|
46 \); |
|
47 [ \t]* |
|
48 ) |
|
49 |
|
50 | (?P<Interface> |
|
51 ^ |
|
52 (?P<InterfaceIndent> [ \t]* ) |
|
53 (?: abstract [ \t]+ )? |
|
54 interface [ \t]+ |
|
55 (?P<InterfaceName> [a-zA-Z_] [a-zA-Z0-9_]* ) |
|
56 [ \t]* |
|
57 (?P<InterfaceSupers> : [^{]+? )? |
|
58 [ \t]* { |
|
59 ) |
|
60 |
|
61 | (?P<Module> |
|
62 ^ |
|
63 (?P<ModuleIndent> [ \t]* ) |
|
64 module [ \t]+ |
|
65 (?P<ModuleName> [a-zA-Z_] [a-zA-Z0-9_]* ) |
|
66 [ \t]* { |
|
67 ) |
|
68 |
|
69 | (?P<Attribute> |
|
70 ^ |
|
71 (?P<AttributeIndent> [ \t]* ) |
|
72 (?P<AttributeReadonly> readonly [ \t]+ )? |
|
73 attribute [ \t]+ |
|
74 (?P<AttributeType> (?: [a-zA-Z0-9_:]+ [ \t]+ )+ ) |
|
75 (?P<AttributeNames> [^;]* ) |
|
76 ; |
|
77 ) |
|
78 |
|
79 | (?P<Begin> |
|
80 [ \t]* { |
|
81 ) |
|
82 |
|
83 | (?P<End> |
|
84 [ \t]* } [ \t]* ; |
|
85 )""", |
|
86 re.VERBOSE | re.DOTALL | re.MULTILINE).search |
|
87 |
|
88 # function to replace comments |
|
89 _commentsub = re.compile(r"""//[^\n]*\n|//[^\n]*$""").sub |
|
90 # function to normalize whitespace |
|
91 _normalize = re.compile(r"""[ \t]{2,}""").sub |
|
92 |
|
93 _modules = {} # cache of modules we've seen |
|
94 |
|
95 |
|
96 class VisibilityMixin(ClbrBaseClasses.ClbrVisibilityMixinBase): |
|
97 """ |
|
98 Mixin class implementing the notion of visibility. |
|
99 """ |
|
100 def __init__(self): |
|
101 """ |
|
102 Constructor |
|
103 """ |
|
104 self.setPublic() |
|
105 |
|
106 |
|
107 class Module(ClbrBaseClasses.Module, VisibilityMixin): |
|
108 """ |
|
109 Class to represent a CORBA IDL module. |
|
110 """ |
|
111 def __init__(self, module, name, file, lineno): |
|
112 """ |
|
113 Constructor |
|
114 |
|
115 @param module name of the module containing this module |
|
116 @type str |
|
117 @param name name of this module |
|
118 @type str |
|
119 @param file filename containing this module |
|
120 @type str |
|
121 @param lineno line number of the module definition |
|
122 @type int |
|
123 """ |
|
124 ClbrBaseClasses.Module.__init__(self, module, name, file, lineno) |
|
125 VisibilityMixin.__init__(self) |
|
126 |
|
127 |
|
128 class Interface(ClbrBaseClasses.Class, VisibilityMixin): |
|
129 """ |
|
130 Class to represent a CORBA IDL interface. |
|
131 """ |
|
132 def __init__(self, module, name, superClasses, file, lineno): |
|
133 """ |
|
134 Constructor |
|
135 |
|
136 @param module name of the module containing this interface |
|
137 @type str |
|
138 @param name name of this interface |
|
139 @type str |
|
140 @param superClasses list of interface names this interface is |
|
141 inherited from |
|
142 @type list of str |
|
143 @param file filename containing this interface |
|
144 @type str |
|
145 @param lineno line number of the interface definition |
|
146 @type int |
|
147 """ |
|
148 ClbrBaseClasses.Class.__init__(self, module, name, superClasses, file, |
|
149 lineno) |
|
150 VisibilityMixin.__init__(self) |
|
151 |
|
152 |
|
153 class Function(ClbrBaseClasses.Function, VisibilityMixin): |
|
154 """ |
|
155 Class to represent a CORBA IDL function. |
|
156 """ |
|
157 def __init__(self, module, name, file, lineno, signature='', |
|
158 separator=','): |
|
159 """ |
|
160 Constructor |
|
161 |
|
162 @param module name of the module containing this function |
|
163 @type str |
|
164 @param name name of this function |
|
165 @type str |
|
166 @param file filename containing this function |
|
167 @type str |
|
168 @param lineno line number of the function definition |
|
169 @type int |
|
170 @param signature parameter list of the function |
|
171 @type str |
|
172 @param separator string separating the parameters |
|
173 @type str |
|
174 """ |
|
175 ClbrBaseClasses.Function.__init__(self, module, name, file, lineno, |
|
176 signature, separator) |
|
177 VisibilityMixin.__init__(self) |
|
178 |
|
179 |
|
180 class Attribute(ClbrBaseClasses.Attribute, VisibilityMixin): |
|
181 """ |
|
182 Class to represent a CORBA IDL attribute. |
|
183 """ |
|
184 def __init__(self, module, name, file, lineno): |
|
185 """ |
|
186 Constructor |
|
187 |
|
188 @param module name of the module containing this attribute |
|
189 @type str |
|
190 @param name name of this attribute |
|
191 @type str |
|
192 @param file filename containing this attribute |
|
193 @type str |
|
194 @param lineno line number of the attribute definition |
|
195 @type int |
|
196 """ |
|
197 ClbrBaseClasses.Attribute.__init__(self, module, name, file, lineno) |
|
198 VisibilityMixin.__init__(self) |
|
199 |
|
200 |
|
201 def readmodule_ex(module, path=None): |
|
202 """ |
|
203 Read a CORBA IDL file and return a dictionary of classes, functions and |
|
204 modules. |
|
205 |
|
206 @param module name of the CORBA IDL file |
|
207 @type str |
|
208 @param path path the file should be searched in |
|
209 @type list of str |
|
210 @return the resulting dictionary |
|
211 @rtype dict |
|
212 """ |
|
213 global _modules |
|
214 |
|
215 if module in _modules: |
|
216 # we've seen this file before... |
|
217 return _modules[module] |
|
218 |
|
219 # search the path for the file |
|
220 f = None |
|
221 fullpath = [] if path is None else path[:] |
|
222 f, file, (suff, mode, type) = ClassBrowsers.find_module(module, fullpath) |
|
223 if f: |
|
224 f.close() |
|
225 if type not in SUPPORTED_TYPES: |
|
226 # not CORBA IDL source, can't do anything with this module |
|
227 _modules[module] = {} |
|
228 return {} |
|
229 |
|
230 try: |
|
231 src = Utilities.readEncodedFile(file)[0] |
|
232 except (UnicodeError, OSError): |
|
233 # can't do anything with this module |
|
234 _modules[module] = {} |
|
235 return {} |
|
236 |
|
237 _modules[module] = scan(src, file, module) |
|
238 return _modules[module] |
|
239 |
|
240 |
|
241 def scan(src, file, module): |
|
242 """ |
|
243 Public method to scan the given source text. |
|
244 |
|
245 @param src source text to be scanned |
|
246 @type str |
|
247 @param file file name associated with the source text |
|
248 @type str |
|
249 @param module module name associated with the source text |
|
250 @type str |
|
251 @return dictionary containing the extracted data |
|
252 @rtype dict |
|
253 """ |
|
254 def calculateEndline(lineno, lines): |
|
255 """ |
|
256 Function to calculate the end line. |
|
257 |
|
258 @param lineno line number to start at (one based) |
|
259 @type int |
|
260 @param lines list of source lines |
|
261 @type list of str |
|
262 @return end line (one based) |
|
263 @rtype int |
|
264 """ |
|
265 # convert lineno to be zero based |
|
266 lineno -= 1 |
|
267 # 1. search for opening brace '{' |
|
268 while lineno < len(lines) and "{" not in lines[lineno]: |
|
269 lineno += 1 |
|
270 depth = lines[lineno].count("{") - lines[lineno].count("}") |
|
271 # 2. search for ending line, i.e. matching closing brace '}' |
|
272 while depth > 0 and lineno < len(lines) - 1: |
|
273 lineno += 1 |
|
274 depth += lines[lineno].count("{") - lines[lineno].count("}") |
|
275 if depth == 0: |
|
276 # found a matching brace |
|
277 return lineno + 1 |
|
278 else: |
|
279 # nothing found |
|
280 return -1 |
|
281 |
|
282 def calculateMethodEndline(lineno, lines): |
|
283 """ |
|
284 Function to calculate the end line. |
|
285 |
|
286 @param lineno line number to start at (one based) |
|
287 @type int |
|
288 @param lines list of source lines |
|
289 @type list of str |
|
290 @return end line (one based) |
|
291 @rtype int |
|
292 """ |
|
293 # convert lineno to be zero based |
|
294 lineno -= 1 |
|
295 while lineno < len(lines) and ";" not in lines[lineno]: |
|
296 lineno += 1 |
|
297 if ";" in lines[lineno]: |
|
298 # found an end indicator, i.e. ';' |
|
299 return lineno + 1 |
|
300 else: |
|
301 return -1 |
|
302 |
|
303 # convert eol markers the Python style |
|
304 src = src.replace("\r\n", "\n").replace("\r", "\n") |
|
305 srcLines = src.splitlines() |
|
306 |
|
307 dictionary = {} |
|
308 dict_counts = {} |
|
309 |
|
310 classstack = [] # stack of (class, indent) pairs |
|
311 indent = 0 |
|
312 |
|
313 lineno, last_lineno_pos = 1, 0 |
|
314 i = 0 |
|
315 while True: |
|
316 m = _getnext(src, i) |
|
317 if not m: |
|
318 break |
|
319 start, i = m.span() |
|
320 |
|
321 if m.start("Method") >= 0: |
|
322 # found a method definition or function |
|
323 thisindent = indent |
|
324 meth_name = m.group("MethodName") |
|
325 meth_sig = m.group("MethodSignature") |
|
326 meth_sig = meth_sig and meth_sig.replace('\\\n', '') or '' |
|
327 meth_sig = _commentsub('', meth_sig) |
|
328 meth_sig = _normalize(' ', meth_sig) |
|
329 lineno += src.count('\n', last_lineno_pos, start) |
|
330 last_lineno_pos = start |
|
331 # close all interfaces/modules indented at least as much |
|
332 while classstack and classstack[-1][1] >= thisindent: |
|
333 del classstack[-1] |
|
334 if classstack: |
|
335 # it's an interface/module method |
|
336 cur_class = classstack[-1][0] |
|
337 if isinstance(cur_class, (Interface, Module)): |
|
338 # it's a method |
|
339 f = Function(None, meth_name, |
|
340 file, lineno, meth_sig) |
|
341 cur_class._addmethod(meth_name, f) |
|
342 # else it's a nested def |
|
343 else: |
|
344 f = None |
|
345 else: |
|
346 # it's a function |
|
347 f = Function(module, meth_name, |
|
348 file, lineno, meth_sig) |
|
349 if meth_name in dict_counts: |
|
350 dict_counts[meth_name] += 1 |
|
351 meth_name = "{0}_{1:d}".format( |
|
352 meth_name, dict_counts[meth_name]) |
|
353 else: |
|
354 dict_counts[meth_name] = 0 |
|
355 dictionary[meth_name] = f |
|
356 if f: |
|
357 endline = calculateMethodEndline(lineno, srcLines) |
|
358 f.setEndLine(endline) |
|
359 classstack.append((f, thisindent)) # Marker for nested fns |
|
360 |
|
361 elif m.start("String") >= 0 or m.start("Comment") >= 0: |
|
362 pass |
|
363 |
|
364 elif m.start("Interface") >= 0: |
|
365 # we found an interface definition |
|
366 thisindent = indent |
|
367 indent += 1 |
|
368 # close all interfaces/modules indented at least as much |
|
369 while classstack and classstack[-1][1] >= thisindent: |
|
370 del classstack[-1] |
|
371 lineno += src.count('\n', last_lineno_pos, start) |
|
372 last_lineno_pos = start |
|
373 class_name = m.group("InterfaceName") |
|
374 inherit = m.group("InterfaceSupers") |
|
375 if inherit: |
|
376 # the interface inherits from other interfaces |
|
377 inherit = inherit[1:].strip() |
|
378 inherit = [_commentsub('', inherit)] |
|
379 # remember this interface |
|
380 cur_class = Interface(module, class_name, inherit, |
|
381 file, lineno) |
|
382 endline = calculateEndline(lineno, srcLines) |
|
383 cur_class.setEndLine(endline) |
|
384 if not classstack: |
|
385 dictionary[class_name] = cur_class |
|
386 else: |
|
387 cls = classstack[-1][0] |
|
388 cls._addclass(class_name, cur_class) |
|
389 classstack.append((cur_class, thisindent)) |
|
390 |
|
391 elif m.start("Module") >= 0: |
|
392 # we found a module definition |
|
393 thisindent = indent |
|
394 indent += 1 |
|
395 # close all interfaces/modules indented at least as much |
|
396 while classstack and classstack[-1][1] >= thisindent: |
|
397 del classstack[-1] |
|
398 lineno += src.count('\n', last_lineno_pos, start) |
|
399 last_lineno_pos = start |
|
400 module_name = m.group("ModuleName") |
|
401 # remember this module |
|
402 cur_class = Module(module, module_name, file, lineno) |
|
403 endline = calculateEndline(lineno, srcLines) |
|
404 cur_class.setEndLine(endline) |
|
405 if not classstack: |
|
406 dictionary[module_name] = cur_class |
|
407 classstack.append((cur_class, thisindent)) |
|
408 |
|
409 elif m.start("Attribute") >= 0: |
|
410 lineno += src.count('\n', last_lineno_pos, start) |
|
411 last_lineno_pos = start |
|
412 index = -1 |
|
413 while index >= -len(classstack): |
|
414 if ( |
|
415 classstack[index][0] is not None and |
|
416 not isinstance(classstack[index][0], Function) and |
|
417 classstack[index][1] < indent |
|
418 ): |
|
419 attributes = m.group("AttributeNames").split(',') |
|
420 ro = m.group("AttributeReadonly") |
|
421 for attribute in attributes: |
|
422 attr = Attribute(module, attribute, file, lineno) |
|
423 if ro: |
|
424 attr.setPrivate() |
|
425 classstack[index][0]._addattribute(attr) |
|
426 break |
|
427 else: |
|
428 index -= 1 |
|
429 |
|
430 elif m.start("Begin") >= 0: |
|
431 # a begin of a block we are not interested in |
|
432 indent += 1 |
|
433 |
|
434 elif m.start("End") >= 0: |
|
435 # an end of a block |
|
436 indent -= 1 |
|
437 |
|
438 return dictionary |