|
1
|
|
|
|
|
2
|
1 |
|
import os |
|
3
|
1 |
|
from collections import OrderedDict |
|
4
|
1 |
|
from pyfranca import franca_parser, ast |
|
5
|
|
|
|
|
6
|
|
|
|
|
7
|
1 |
|
class ProcessorException(Exception): |
|
8
|
|
|
|
|
9
|
1 |
|
def __init__(self, message): |
|
10
|
1 |
|
super(ProcessorException, self).__init__() |
|
11
|
1 |
|
self.message = message |
|
12
|
|
|
|
|
13
|
1 |
|
def __str__(self): |
|
14
|
1 |
|
return self.message |
|
15
|
|
|
|
|
16
|
|
|
|
|
17
|
1 |
|
class Processor(object): |
|
18
|
|
|
""" |
|
19
|
|
|
Franca IDL processor. |
|
20
|
|
|
""" |
|
21
|
|
|
|
|
22
|
1 |
|
def __init__(self): |
|
23
|
|
|
""" |
|
24
|
|
|
Constructor. |
|
25
|
|
|
""" |
|
26
|
|
|
# Default package paths. |
|
27
|
1 |
|
self.package_paths = [] |
|
28
|
1 |
|
self.files = {} |
|
29
|
1 |
|
self.packages = {} |
|
30
|
|
|
|
|
31
|
1 |
|
@staticmethod |
|
32
|
|
|
def basename(namespace): |
|
33
|
|
|
""" |
|
34
|
|
|
Extract the type or namespace name from a Franca FQN. |
|
35
|
|
|
""" |
|
36
|
1 |
|
dot = namespace.rfind(".") |
|
37
|
1 |
|
if dot == -1: |
|
38
|
1 |
|
return namespace |
|
39
|
|
|
else: |
|
40
|
1 |
|
return namespace[dot + 1:] |
|
41
|
|
|
|
|
42
|
1 |
|
@staticmethod |
|
43
|
|
|
def packagename(namespace): |
|
44
|
|
|
""" |
|
45
|
|
|
Extract the package name from a Franca FQN. |
|
46
|
|
|
""" |
|
47
|
1 |
|
dot = namespace.rfind(".") |
|
48
|
1 |
|
if dot == -1: |
|
49
|
1 |
|
return None |
|
50
|
|
|
else: |
|
51
|
1 |
|
return namespace[0:dot] |
|
52
|
|
|
|
|
53
|
1 |
|
@staticmethod |
|
54
|
|
|
def is_fqn(string): |
|
55
|
|
|
""" |
|
56
|
|
|
Defines whether a Franca name is an ID or an FQN. |
|
57
|
|
|
""" |
|
58
|
1 |
|
return string.count(".") >= 2 |
|
59
|
|
|
|
|
60
|
1 |
|
@staticmethod |
|
61
|
|
|
def split_fqn(fqn): |
|
62
|
|
|
""" |
|
63
|
|
|
Split a Franca FQN into a tuple - package, namespace, and name. |
|
64
|
|
|
""" |
|
65
|
1 |
|
parts = fqn.rsplit(".", 2) |
|
66
|
1 |
|
while len(parts) < 3: |
|
67
|
1 |
|
parts.insert(0, None) |
|
68
|
1 |
|
return tuple(parts) |
|
69
|
|
|
|
|
70
|
1 |
|
@staticmethod |
|
71
|
|
|
def resolve(namespace, fqn): |
|
72
|
|
|
""" |
|
73
|
|
|
Resolve type references. |
|
74
|
|
|
|
|
75
|
|
|
:param namespace: context ast.Namespace object. |
|
76
|
|
|
:param fqn: FQN or ID string. |
|
77
|
|
|
:return: Dereferenced ast.Type object. |
|
78
|
|
|
""" |
|
79
|
1 |
|
if not isinstance(namespace, ast.Namespace) or \ |
|
80
|
|
|
not isinstance(fqn, str): |
|
81
|
|
|
raise ValueError("Unexpected input.") |
|
82
|
1 |
|
pkg, ns, name = Processor.split_fqn(fqn) |
|
83
|
1 |
|
if pkg is None: |
|
84
|
|
|
# This is an ID |
|
85
|
|
|
# Look in the type's namespace |
|
86
|
1 |
|
if name in namespace: |
|
87
|
1 |
|
return namespace[name] |
|
88
|
|
|
# Look in other type collections in the type's package |
|
89
|
1 |
|
for typecollection in namespace.package.typecollections.values(): |
|
90
|
1 |
|
if name in typecollection: |
|
91
|
1 |
|
return typecollection[name] |
|
92
|
|
|
# Look in imports |
|
93
|
1 |
|
for package_import in namespace.package.imports: |
|
94
|
1 |
|
if package_import.namespace_reference: |
|
95
|
|
|
# Look in namespaces imported in the type's package |
|
96
|
1 |
|
if name in package_import.namespace_reference: |
|
97
|
1 |
|
return package_import.namespace_reference[name] |
|
98
|
|
|
else: |
|
99
|
|
|
# This is an FQN |
|
100
|
1 |
|
if pkg == namespace.package.name: |
|
101
|
|
|
# Check in the current package |
|
102
|
1 |
|
if ns in namespace.package.typecollections: |
|
103
|
1 |
|
if name in namespace.package.typecollections[ns]: |
|
104
|
1 |
|
return namespace.package.typecollections[ns][name] |
|
105
|
|
|
else: |
|
106
|
|
|
# Look in typecollections of packages imported in the |
|
107
|
|
|
# type's package using FQNs. |
|
108
|
1 |
|
for package_import in namespace.package.imports: |
|
109
|
1 |
|
if package_import.namespace == "{}.{}.*".format(pkg, ns): |
|
110
|
1 |
|
for typecollection in package_import.\ |
|
111
|
|
|
package_reference.typecollections.values(): |
|
112
|
1 |
|
if typecollection.name == ns and \ |
|
113
|
|
|
name in typecollection: |
|
114
|
1 |
|
return typecollection[name] |
|
115
|
|
|
for interface in package_import. \ |
|
116
|
|
|
package_reference.interfaces.values(): |
|
117
|
|
|
if name in interface: |
|
118
|
|
|
return interface[name] |
|
119
|
|
|
# Give up |
|
120
|
1 |
|
raise ProcessorException( |
|
121
|
|
|
"Unresolved reference '{}'.".format(fqn)) |
|
122
|
|
|
|
|
123
|
1 |
|
@staticmethod |
|
124
|
|
|
def resolve_namespace(package, fqn): |
|
125
|
|
|
""" |
|
126
|
|
|
Resolve namespace references. |
|
127
|
|
|
|
|
128
|
|
|
:param package: context ast.Package object. |
|
129
|
|
|
:param fqn: FQN or ID string. |
|
130
|
|
|
:return: Dereferenced ast.Namespace object. |
|
131
|
|
|
""" |
|
132
|
1 |
|
if not isinstance(package, ast.Package) or not isinstance(fqn, str): |
|
133
|
|
|
raise ValueError("Unexpected input.") |
|
134
|
1 |
|
if fqn.count(".") > 0: |
|
135
|
1 |
|
pkg, name = fqn.rsplit(".", 2) |
|
136
|
|
|
else: |
|
137
|
1 |
|
pkg, name = (None, fqn) |
|
138
|
1 |
|
if pkg is None: |
|
139
|
|
|
# This is an ID |
|
140
|
|
|
# Look for other namespaces in the package |
|
141
|
1 |
|
if name in package: |
|
142
|
1 |
|
return package[name] |
|
143
|
|
|
# Look in model imports |
|
144
|
1 |
|
for package_import in package.imports: |
|
145
|
1 |
|
if not package_import.namespace: |
|
146
|
1 |
|
if name in package_import.package_reference: |
|
147
|
1 |
|
return package_import.package_reference[name] |
|
148
|
|
|
else: |
|
149
|
|
|
# This is an FQN |
|
150
|
1 |
|
if pkg == package.name: |
|
151
|
|
|
# Check in the current package |
|
152
|
1 |
|
if name in package: |
|
153
|
1 |
|
return package[name] |
|
154
|
|
|
else: |
|
155
|
|
|
# Look in model imports |
|
156
|
1 |
|
for package_import in package.imports: |
|
157
|
1 |
|
if not package_import.namespace: |
|
158
|
1 |
|
if name in package_import.package_reference: |
|
159
|
1 |
|
return package_import.package_reference[name] |
|
160
|
|
|
# Give up |
|
161
|
1 |
|
raise ProcessorException( |
|
162
|
|
|
"Unresolved namespace reference '{}'.".format(fqn)) |
|
163
|
|
|
|
|
164
|
1 |
|
def _update_complextype_references(self, name): |
|
165
|
|
|
""" |
|
166
|
|
|
Update type references in a complex type. |
|
167
|
|
|
|
|
168
|
|
|
:param name: ast.ComplexType object. |
|
169
|
|
|
""" |
|
170
|
1 |
|
if isinstance(name, ast.Enumeration): |
|
171
|
1 |
|
if name.extends: |
|
172
|
1 |
|
name.reference = self.resolve(name.namespace, name.extends) |
|
173
|
1 |
|
if not isinstance(name.reference, ast.Enumeration): |
|
174
|
1 |
|
raise ProcessorException( |
|
175
|
|
|
"Invalid enumeration reference '{}'.".format( |
|
176
|
|
|
name.extends)) |
|
177
|
1 |
|
elif isinstance(name, ast.Struct): |
|
178
|
1 |
|
for field in name.fields.values(): |
|
179
|
1 |
|
self._update_type_references(name.namespace, field.type) |
|
180
|
1 |
|
if name.extends: |
|
181
|
1 |
|
name.reference = self.resolve(name.namespace, name.extends) |
|
182
|
1 |
|
if not isinstance(name.reference, ast.Struct): |
|
183
|
1 |
|
raise ProcessorException( |
|
184
|
|
|
"Invalid struct reference '{}'.".format( |
|
185
|
|
|
name.extends)) |
|
186
|
1 |
|
elif isinstance(name, ast.Array): |
|
187
|
1 |
|
self._update_type_references(name.namespace, name.type) |
|
188
|
1 |
|
elif isinstance(name, ast.Map): |
|
189
|
1 |
|
self._update_type_references(name.namespace, name.key_type) |
|
190
|
1 |
|
self._update_type_references(name.namespace, name.value_type) |
|
191
|
|
|
elif isinstance(name, ast.Constant): |
|
192
|
|
|
self._update_type_references(name.namespace, name.type) |
|
193
|
|
|
else: |
|
194
|
|
|
assert False |
|
195
|
|
|
|
|
196
|
1 |
|
def _update_type_references(self, namespace, name): |
|
197
|
|
|
""" |
|
198
|
|
|
Update type references in a type. |
|
199
|
|
|
|
|
200
|
|
|
:param namespace: ast.Namespace context. |
|
201
|
|
|
:param name: ast.Type object. |
|
202
|
|
|
""" |
|
203
|
1 |
|
if isinstance(name, ast.Typedef): |
|
204
|
1 |
|
self._update_type_references(name.namespace, name.type) |
|
205
|
1 |
|
elif isinstance(name, ast.PrimitiveType): |
|
206
|
1 |
|
pass |
|
207
|
1 |
|
elif isinstance(name, ast.ComplexType): |
|
208
|
1 |
|
self._update_complextype_references(name) |
|
209
|
1 |
|
elif isinstance(name, ast.Reference): |
|
210
|
1 |
|
if not name.namespace: |
|
211
|
1 |
|
name.namespace = namespace |
|
212
|
1 |
|
if not name.reference: |
|
213
|
1 |
|
resolved_name = self.resolve(namespace, name.name) |
|
214
|
1 |
|
name.reference = resolved_name |
|
215
|
1 |
|
elif isinstance(name, ast.Attribute): |
|
216
|
1 |
|
self._update_type_references(name.namespace, name.type) |
|
217
|
1 |
|
elif isinstance(name, ast.Method): |
|
218
|
1 |
|
for arg in name.in_args.values(): |
|
219
|
1 |
|
self._update_type_references(name.namespace, arg.type) |
|
220
|
1 |
|
for arg in name.out_args.values(): |
|
221
|
1 |
|
self._update_type_references(name.namespace, arg.type) |
|
222
|
1 |
|
if isinstance(name.errors, OrderedDict): |
|
223
|
1 |
|
pass |
|
224
|
1 |
|
elif isinstance(name.errors, ast.Reference): |
|
225
|
|
|
# Errors can be a reference to an enumeration |
|
226
|
1 |
|
self._update_type_references(name.namespace, name.errors) |
|
227
|
1 |
|
if not isinstance(name.errors.reference, ast.Enumeration): |
|
228
|
1 |
|
raise ProcessorException( |
|
229
|
|
|
"Invalid error reference '{}'.".format( |
|
230
|
|
|
name.errors.name)) |
|
231
|
|
|
else: |
|
232
|
|
|
assert False |
|
233
|
1 |
|
elif isinstance(name, ast.Broadcast): |
|
234
|
1 |
|
for arg in name.out_args.values(): |
|
235
|
1 |
|
self._update_type_references(name.namespace, arg.type) |
|
236
|
|
|
else: |
|
237
|
|
|
assert False |
|
238
|
|
|
|
|
239
|
1 |
|
def _update_namespace_references(self, namespace): |
|
240
|
|
|
""" |
|
241
|
|
|
Update type references in a namespace. |
|
242
|
|
|
|
|
243
|
|
|
:param namespace: ast.Namespace object. |
|
244
|
|
|
""" |
|
245
|
1 |
|
for name in namespace.typedefs.values(): |
|
246
|
1 |
|
self._update_type_references(namespace, name) |
|
247
|
1 |
|
for name in namespace.enumerations.values(): |
|
248
|
1 |
|
self._update_type_references(namespace, name) |
|
249
|
1 |
|
for name in namespace.structs.values(): |
|
250
|
1 |
|
self._update_type_references(namespace, name) |
|
251
|
1 |
|
for name in namespace.arrays.values(): |
|
252
|
1 |
|
self._update_type_references(namespace, name) |
|
253
|
1 |
|
for name in namespace.maps.values(): |
|
254
|
1 |
|
self._update_type_references(namespace, name) |
|
255
|
1 |
|
for name in namespace.constants.values(): |
|
256
|
|
|
self._update_type_references(namespace, name) |
|
257
|
|
|
|
|
258
|
1 |
|
def _update_interface_references(self, namespace): |
|
259
|
|
|
""" |
|
260
|
|
|
Update type references in an interface. |
|
261
|
|
|
|
|
262
|
|
|
:param namespace: ast.Interface object. |
|
263
|
|
|
""" |
|
264
|
1 |
|
self._update_namespace_references(namespace) |
|
265
|
1 |
|
for name in namespace.attributes.values(): |
|
266
|
1 |
|
self._update_type_references(namespace, name) |
|
267
|
1 |
|
for name in namespace.methods.values(): |
|
268
|
1 |
|
self._update_type_references(namespace, name) |
|
269
|
1 |
|
for name in namespace.broadcasts.values(): |
|
270
|
1 |
|
self._update_type_references(namespace, name) |
|
271
|
1 |
|
if namespace.extends: |
|
272
|
1 |
|
namespace.reference = self.resolve_namespace( |
|
273
|
|
|
namespace.package, namespace.extends) |
|
274
|
1 |
|
if not isinstance(namespace.reference, ast.Interface): |
|
275
|
|
|
raise ProcessorException( |
|
276
|
|
|
"Invalid interface reference '{}'.".format( |
|
277
|
|
|
namespace.extends)) |
|
278
|
|
|
|
|
279
|
1 |
|
def _update_package_references(self, package): |
|
280
|
|
|
""" |
|
281
|
|
|
Update type references in a package. |
|
282
|
|
|
|
|
283
|
|
|
:param package: ast.Package object. |
|
284
|
|
|
""" |
|
285
|
1 |
|
for package_import in package.imports: |
|
286
|
1 |
|
assert package_import.package_reference is not None |
|
287
|
1 |
|
if package_import.namespace: |
|
288
|
|
|
# Namespace import |
|
289
|
1 |
|
package_reference = package_import.package_reference |
|
290
|
1 |
|
if not package_import.namespace.endswith(".*"): |
|
291
|
|
|
raise ProcessorException( |
|
292
|
|
|
"Invalid namespace import {}.".format( |
|
293
|
|
|
package_import.namespace)) |
|
294
|
1 |
|
namespace_name = \ |
|
295
|
|
|
package_import.namespace[len(package_reference.name) + 1:-2] |
|
296
|
|
|
# Update namespace reference |
|
297
|
1 |
|
if namespace_name in package_reference: |
|
298
|
1 |
|
namespace = package_reference[namespace_name] |
|
299
|
1 |
|
package_import.namespace_reference = namespace |
|
300
|
|
|
else: |
|
301
|
1 |
|
raise ProcessorException( |
|
302
|
|
|
"Namespace '{}' not found.".format( |
|
303
|
|
|
package_import.namespace)) |
|
304
|
|
|
else: |
|
305
|
|
|
# Model import |
|
306
|
1 |
|
assert package_import.namespace_reference is None |
|
307
|
1 |
|
for namespace in package.typecollections: |
|
308
|
1 |
|
self._update_namespace_references( |
|
309
|
|
|
package.typecollections[namespace]) |
|
310
|
1 |
|
for namespace in package.interfaces: |
|
311
|
1 |
|
self._update_interface_references( |
|
312
|
|
|
package.interfaces[namespace]) |
|
313
|
|
|
|
|
314
|
1 |
|
def import_package(self, fspec, package, references=None): |
|
315
|
|
|
""" |
|
316
|
|
|
Import an ast.Package into the processor. |
|
317
|
|
|
|
|
318
|
|
|
:param fspec: File specification of the package. |
|
319
|
|
|
:param package: ast.Package object. |
|
320
|
|
|
:param references: A list of package references. |
|
321
|
|
|
""" |
|
322
|
1 |
|
if not isinstance(package, ast.Package): |
|
323
|
|
|
ValueError("Expected ast.Package as input.") |
|
324
|
1 |
|
if not references: |
|
325
|
1 |
|
references = [] |
|
326
|
|
|
# Check whether package is already imported |
|
327
|
1 |
|
if package.name in self.packages: |
|
328
|
1 |
|
if fspec not in self.packages[package.name].files: |
|
329
|
|
|
# Merge the new package into the already existing one. |
|
330
|
1 |
|
self.packages[package.name] += package |
|
331
|
|
|
# Register the package file in the processor. |
|
332
|
1 |
|
self.files[fspec] = self.packages[package.name] |
|
333
|
1 |
|
package = self.packages[package.name] |
|
334
|
|
|
else: |
|
335
|
1 |
|
return |
|
336
|
|
|
else: |
|
337
|
|
|
# Register the package in the processor. |
|
338
|
1 |
|
self.packages[package.name] = package |
|
339
|
|
|
# Register the package file in the processor. |
|
340
|
1 |
|
self.files[fspec] = package |
|
341
|
|
|
# Process package imports |
|
342
|
1 |
|
fspec_path = os.path.abspath(fspec) |
|
343
|
1 |
|
fspec_dir = os.path.dirname(fspec_path) |
|
344
|
1 |
|
for package_import in package.imports: |
|
345
|
1 |
|
imported_package = self.import_file( |
|
346
|
|
|
package_import.file, references + [package.name], fspec_dir) |
|
347
|
|
|
# Update import reference |
|
348
|
1 |
|
package_import.package_reference = imported_package |
|
349
|
|
|
# Update type references |
|
350
|
1 |
|
self._update_package_references(package) |
|
351
|
|
|
|
|
352
|
1 |
|
def import_string(self, fspec, fidl, references=None): |
|
353
|
|
|
""" |
|
354
|
|
|
Parse an FIDL string and import it into the processor as package. |
|
355
|
|
|
|
|
356
|
|
|
:param fspec: File specification of the package. |
|
357
|
|
|
:param fidl: FIDL string. |
|
358
|
|
|
:param references: A list of package references. |
|
359
|
|
|
:return: The parsed ast.Package. |
|
360
|
|
|
""" |
|
361
|
|
|
# Parse the string. |
|
362
|
1 |
|
parser = franca_parser.Parser() |
|
363
|
1 |
|
package = parser.parse(fidl) |
|
364
|
1 |
|
package.files = [fspec] |
|
365
|
|
|
# Import the package in the processor. |
|
366
|
1 |
|
self.import_package(fspec, package, references) |
|
367
|
1 |
|
return package |
|
368
|
|
|
|
|
369
|
1 |
|
def import_file(self, fspec, references=None, package_path=None): |
|
370
|
|
|
""" |
|
371
|
|
|
Parse an FIDL file and import it into the processor as package. |
|
372
|
|
|
|
|
373
|
|
|
:param fspec: File specification. |
|
374
|
|
|
:param references: A list of package references. |
|
375
|
|
|
:param package_path: Additional model path to search for imports. |
|
376
|
|
|
:return: The parsed ast.Package. |
|
377
|
|
|
""" |
|
378
|
1 |
|
if fspec in self.files: |
|
379
|
|
|
# File already loaded. |
|
380
|
1 |
|
return self.files[fspec] |
|
381
|
1 |
|
if not os.path.exists(fspec): |
|
382
|
1 |
|
if os.path.isabs(fspec): |
|
383
|
|
|
# Absolute specification |
|
384
|
|
|
raise ProcessorException( |
|
385
|
|
|
"Model '{}' not found.".format(fspec)) |
|
386
|
|
|
else: |
|
387
|
|
|
# Relative specification. |
|
388
|
1 |
|
package_paths = self.package_paths[:] |
|
389
|
1 |
|
if package_path: |
|
390
|
1 |
|
package_paths.insert(0, package_path) |
|
391
|
|
|
# Check in the package path list. |
|
392
|
1 |
|
for path in package_paths: |
|
393
|
1 |
|
temp_fspec = os.path.join(path, fspec) |
|
394
|
1 |
|
if os.path.exists(temp_fspec): |
|
395
|
1 |
|
fspec = temp_fspec |
|
396
|
1 |
|
break |
|
397
|
|
|
else: |
|
398
|
1 |
|
raise ProcessorException( |
|
399
|
|
|
"Model '{}' not found.".format(fspec)) |
|
400
|
|
|
# Parse the file. |
|
401
|
1 |
|
parser = franca_parser.Parser() |
|
402
|
1 |
|
package = parser.parse_file(fspec) |
|
403
|
|
|
# Import the package in the processor. |
|
404
|
1 |
|
self.import_package(fspec, package, references) |
|
405
|
|
|
return package |
|
406
|
|
|
|