API: Add support for type aliases
[vpp.git] / src / tools / vppapigen / vppapigen.py
1 #!/usr/bin/env python
2
3 from __future__ import print_function
4 import ply.lex as lex
5 import ply.yacc as yacc
6 import sys
7 import argparse
8 import logging
9 import binascii
10 import os
11
12 # Ensure we don't leave temporary files around
13 sys.dont_write_bytecode = True
14
15 #
16 # VPP API language
17 #
18
19 # Global dictionary of new types (including enums)
20 global_types = {}
21 global_crc = 0
22
23
24 def global_type_add(name):
25     '''Add new type to the dictionary of types '''
26     type_name = 'vl_api_' + name + '_t'
27     if type_name in global_types:
28         raise KeyError('Type is already defined: {}'.format(name))
29     global_types[type_name] = True
30
31
32 # All your trace are belong to us!
33 def exception_handler(exception_type, exception, traceback):
34     print("%s: %s" % (exception_type.__name__, exception))
35
36
37 #
38 # Lexer
39 #
40 class VPPAPILexer(object):
41     def __init__(self, filename):
42         self.filename = filename
43
44     reserved = {
45         'service': 'SERVICE',
46         'rpc': 'RPC',
47         'returns': 'RETURNS',
48         'null': 'NULL',
49         'stream': 'STREAM',
50         'events': 'EVENTS',
51         'define': 'DEFINE',
52         'typedef': 'TYPEDEF',
53         'enum': 'ENUM',
54         'typeonly': 'TYPEONLY',
55         'manual_print': 'MANUAL_PRINT',
56         'manual_endian': 'MANUAL_ENDIAN',
57         'dont_trace': 'DONT_TRACE',
58         'autoreply': 'AUTOREPLY',
59         'option': 'OPTION',
60         'u8': 'U8',
61         'u16': 'U16',
62         'u32': 'U32',
63         'u64': 'U64',
64         'i8': 'I8',
65         'i16': 'I16',
66         'i32': 'I32',
67         'i64': 'I64',
68         'f64': 'F64',
69         'bool': 'BOOL',
70         'string': 'STRING',
71         'import': 'IMPORT',
72         'true': 'TRUE',
73         'false': 'FALSE',
74         'union': 'UNION',
75     }
76
77     tokens = ['STRING_LITERAL',
78               'ID', 'NUM'] + list(reserved.values())
79
80     t_ignore_LINE_COMMENT = '//.*'
81
82     def t_NUM(self, t):
83         r'0[xX][0-9a-fA-F]+|\d+'
84         base = 16 if t.value.startswith('0x') else 10
85         t.value = int(t.value, base)
86         return t
87
88     def t_ID(self, t):
89         r'[a-zA-Z_][a-zA-Z_0-9]*'
90         # Check for reserved words
91         t.type = VPPAPILexer.reserved.get(t.value, 'ID')
92         return t
93
94     # C string
95     def t_STRING_LITERAL(self, t):
96         r'\"([^\\\n]|(\\.))*?\"'
97         t.value = str(t.value).replace("\"", "")
98         return t
99
100     # C or C++ comment (ignore)
101     def t_comment(self, t):
102         r'(/\*(.|\n)*?\*/)|(//.*)'
103         t.lexer.lineno += t.value.count('\n')
104
105     # Error handling rule
106     def t_error(self, t):
107         raise ParseError("Illegal character '{}' ({})"
108                          "in {}: line {}".format(t.value[0],
109                                                  hex(ord(t.value[0])),
110                                                  self.filename,
111                                                  t.lexer.lineno))
112         t.lexer.skip(1)
113
114     # Define a rule so we can track line numbers
115     def t_newline(self, t):
116         r'\n+'
117         t.lexer.lineno += len(t.value)
118
119     literals = ":{}[];=.,"
120
121     # A string containing ignored characters (spaces and tabs)
122     t_ignore = ' \t'
123
124
125 class Service():
126     def __init__(self, caller, reply, events=[], stream=False):
127         self.caller = caller
128         self.reply = reply
129         self.stream = stream
130         self.events = events
131
132
133 class Typedef():
134     def __init__(self, name, flags, block):
135         global global_crc
136         self.name = name
137         self.flags = flags
138         self.block = block
139         self.crc = binascii.crc32(str(block)) & 0xffffffff
140         global_crc = binascii.crc32(str(block), global_crc)
141         self.manual_print = False
142         self.manual_endian = False
143         for f in flags:
144             if f == 'manual_print':
145                 self.manual_print = True
146             elif f == 'manual_endian':
147                 self.manual_endian = True
148         global_type_add(name)
149
150     def __repr__(self):
151         return self.name + str(self.flags) + str(self.block)
152
153
154 class Using():
155     def __init__(self, name, alias):
156         global global_crc
157         self.name = name
158
159         if isinstance(alias, Array):
160             a = { 'type': alias.fieldtype,
161                   'length': alias.length }
162         else:
163             a = { 'type': alias.fieldtype }
164         self.alias = a
165         self.crc = binascii.crc32(str(alias)) & 0xffffffff
166         global_crc = binascii.crc32(str(alias), global_crc)
167         global_type_add(name)
168
169     def __repr__(self):
170         return self.name + str(self.alias)
171
172
173 class Union():
174     def __init__(self, name, block):
175         self.type = 'Union'
176         self.manual_print = False
177         self.manual_endian = False
178         global global_crc
179         self.name = name
180         self.block = block
181         self.crc = binascii.crc32(str(block)) & 0xffffffff
182         global_crc = binascii.crc32(str(block), global_crc)
183         global_type_add(name)
184
185     def __repr__(self):
186         return str(self.block)
187
188
189 class Define():
190     def __init__(self, name, flags, block):
191         global global_crc
192         self.name = name
193         self.flags = flags
194         self.block = block
195         self.crc = binascii.crc32(str(block)) & 0xffffffff
196         global_crc = binascii.crc32(str(block), global_crc)
197         self.dont_trace = False
198         self.manual_print = False
199         self.manual_endian = False
200         self.autoreply = False
201         self.singular = False
202         for f in flags:
203             if f == 'dont_trace':
204                 self.dont_trace = True
205             elif f == 'manual_print':
206                 self.manual_print = True
207             elif f == 'manual_endian':
208                 self.manual_endian = True
209             elif f == 'autoreply':
210                 self.autoreply = True
211
212         for b in block:
213             if isinstance(b, Option):
214                 if b[1] == 'singular' and b[2] == 'true':
215                     self.singular = True
216                 block.remove(b)
217
218     def __repr__(self):
219         return self.name + str(self.flags) + str(self.block)
220
221
222 class Enum():
223     def __init__(self, name, block, enumtype='u32'):
224         global global_crc
225         self.name = name
226         self.enumtype = enumtype
227
228         count = 0
229         for i, b in enumerate(block):
230             if type(b) is list:
231                 count = b[1]
232             else:
233                 count += 1
234                 block[i] = [b, count]
235
236         self.block = block
237         self.crc = binascii.crc32(str(block)) & 0xffffffff
238         global_crc = binascii.crc32(str(block), global_crc)
239         global_type_add(name)
240
241     def __repr__(self):
242         return self.name + str(self.block)
243
244
245 class Import():
246     def __init__(self, filename):
247         self.filename = filename
248
249         # Deal with imports
250         parser = VPPAPI(filename=filename)
251         dirlist = dirlist_get()
252         f = filename
253         for dir in dirlist:
254             f = os.path.join(dir, filename)
255             if os.path.exists(f):
256                 break
257         with open(f) as fd:
258             self.result = parser.parse_file(fd, None)
259
260     def __repr__(self):
261         return self.filename
262
263
264 class Option():
265     def __init__(self, option):
266         global global_crc
267         self.option = option
268         self.crc = binascii.crc32(str(option)) & 0xffffffff
269         global_crc = binascii.crc32(str(option), global_crc)
270
271     def __repr__(self):
272         return str(self.option)
273
274     def __getitem__(self, index):
275         return self.option[index]
276
277
278 class Array():
279     def __init__(self, fieldtype, name, length):
280         self.type = 'Array'
281         self.fieldtype = fieldtype
282         self.fieldname = name
283         if type(length) is str:
284             self.lengthfield = length
285             self.length = 0
286         else:
287             self.length = length
288             self.lengthfield = None
289
290     def __repr__(self):
291         return str([self.fieldtype, self.fieldname, self.length,
292                     self.lengthfield])
293
294
295 class Field():
296     def __init__(self, fieldtype, name):
297         self.type = 'Field'
298         self.fieldtype = fieldtype
299         self.fieldname = name
300
301     def __repr__(self):
302         return str([self.fieldtype, self.fieldname])
303
304
305 class Coord(object):
306     """ Coordinates of a syntactic element. Consists of:
307             - File name
308             - Line number
309             - (optional) column number, for the Lexer
310     """
311     __slots__ = ('file', 'line', 'column', '__weakref__')
312
313     def __init__(self, file, line, column=None):
314         self.file = file
315         self.line = line
316         self.column = column
317
318     def __str__(self):
319         str = "%s:%s" % (self.file, self.line)
320         if self.column:
321             str += ":%s" % self.column
322         return str
323
324
325 class ParseError(Exception):
326     pass
327
328
329 #
330 # Grammar rules
331 #
332 class VPPAPIParser(object):
333     tokens = VPPAPILexer.tokens
334
335     def __init__(self, filename, logger):
336         self.filename = filename
337         self.logger = logger
338         self.fields = []
339
340     def _parse_error(self, msg, coord):
341         raise ParseError("%s: %s" % (coord, msg))
342
343     def _parse_warning(self, msg, coord):
344         if self.logger:
345             self.logger.warning("%s: %s" % (coord, msg))
346
347     def _coord(self, lineno, column=None):
348         return Coord(
349                 file=self.filename,
350                 line=lineno, column=column)
351
352     def _token_coord(self, p, token_idx):
353         """ Returns the coordinates for the YaccProduction object 'p' indexed
354             with 'token_idx'. The coordinate includes the 'lineno' and
355             'column'. Both follow the lex semantic, starting from 1.
356         """
357         last_cr = p.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx))
358         if last_cr < 0:
359             last_cr = -1
360         column = (p.lexpos(token_idx) - (last_cr))
361         return self._coord(p.lineno(token_idx), column)
362
363     def p_slist(self, p):
364         '''slist : stmt
365                  | slist stmt'''
366         if len(p) == 2:
367             p[0] = [p[1]]
368         else:
369             p[0] = p[1] + [p[2]]
370
371     def p_stmt(self, p):
372         '''stmt : define
373                 | typedef
374                 | option
375                 | import
376                 | enum
377                 | union
378                 | service'''
379         p[0] = p[1]
380
381     def p_import(self, p):
382         '''import : IMPORT STRING_LITERAL ';' '''
383         p[0] = Import(p[2])
384
385     def p_service(self, p):
386         '''service : SERVICE '{' service_statements '}' ';' '''
387         p[0] = p[3]
388
389     def p_service_statements(self, p):
390         '''service_statements : service_statement
391                         | service_statements service_statement'''
392         if len(p) == 2:
393             p[0] = [p[1]]
394         else:
395             p[0] = p[1] + [p[2]]
396
397     def p_service_statement(self, p):
398         '''service_statement : RPC ID RETURNS NULL ';'
399                              | RPC ID RETURNS ID ';'
400                              | RPC ID RETURNS STREAM ID ';'
401                              | RPC ID RETURNS ID EVENTS event_list ';' '''
402         if p[2] == p[4]:
403             # Verify that caller and reply differ
404             self._parse_error(
405                 'Reply ID ({}) should not be equal to Caller ID'.format(p[2]),
406                 self._token_coord(p, 1))
407         if len(p) == 8:
408             p[0] = Service(p[2], p[4], p[6])
409         elif len(p) == 7:
410             p[0] = Service(p[2], p[5], stream=True)
411         else:
412             p[0] = Service(p[2], p[4])
413
414     def p_event_list(self, p):
415         '''event_list : events
416                       | event_list events '''
417         if len(p) == 2:
418             p[0] = [p[1]]
419         else:
420             p[0] = p[1] + [p[2]]
421
422     def p_event(self, p):
423         '''events : ID
424                   | ID ',' '''
425         p[0] = p[1]
426
427     def p_enum(self, p):
428         '''enum : ENUM ID '{' enum_statements '}' ';' '''
429         p[0] = Enum(p[2], p[4])
430
431     def p_enum_type(self, p):
432         ''' enum : ENUM ID ':' enum_size '{' enum_statements '}' ';' '''
433         if len(p) == 9:
434             p[0] = Enum(p[2], p[6], enumtype=p[4])
435         else:
436             p[0] = Enum(p[2], p[4])
437
438     def p_enum_size(self, p):
439         ''' enum_size : U8
440                       | U16
441                       | U32 '''
442         p[0] = p[1]
443
444     def p_define(self, p):
445         '''define : DEFINE ID '{' block_statements_opt '}' ';' '''
446         self.fields = []
447         p[0] = Define(p[2], [], p[4])
448
449     def p_define_flist(self, p):
450         '''define : flist DEFINE ID '{' block_statements_opt '}' ';' '''
451         # Legacy typedef
452         if 'typeonly' in p[1]:
453             p[0] = Typedef(p[3], p[1], p[5])
454         else:
455             p[0] = Define(p[3], p[1], p[5])
456
457     def p_flist(self, p):
458         '''flist : flag
459                  | flist flag'''
460         if len(p) == 2:
461             p[0] = [p[1]]
462         else:
463             p[0] = p[1] + [p[2]]
464
465     def p_flag(self, p):
466         '''flag : MANUAL_PRINT
467                 | MANUAL_ENDIAN
468                 | DONT_TRACE
469                 | TYPEONLY
470                 | AUTOREPLY'''
471         if len(p) == 1:
472             return
473         p[0] = p[1]
474
475     def p_typedef(self, p):
476         '''typedef : TYPEDEF ID '{' block_statements_opt '}' ';' '''
477         p[0] = Typedef(p[2], [], p[4])
478
479     def p_typedef_alias(self, p):
480         '''typedef : TYPEDEF declaration '''
481         p[0] = Using(p[2].fieldname, p[2])
482
483     def p_block_statements_opt(self, p):
484         '''block_statements_opt : block_statements '''
485         p[0] = p[1]
486
487     def p_block_statements(self, p):
488         '''block_statements : block_statement
489                             | block_statements block_statement'''
490         if len(p) == 2:
491             p[0] = [p[1]]
492         else:
493             p[0] = p[1] + [p[2]]
494
495     def p_block_statement(self, p):
496         '''block_statement : declaration
497                            | option '''
498         p[0] = p[1]
499
500     def p_enum_statements(self, p):
501         '''enum_statements : enum_statement
502                             | enum_statements enum_statement'''
503         if len(p) == 2:
504             p[0] = [p[1]]
505         else:
506             p[0] = p[1] + [p[2]]
507
508     def p_enum_statement(self, p):
509         '''enum_statement : ID '=' NUM ','
510                           | ID ',' '''
511         if len(p) == 5:
512             p[0] = [p[1], p[3]]
513         else:
514             p[0] = p[1]
515
516     def p_declaration(self, p):
517         '''declaration : type_specifier ID ';' '''
518         if len(p) != 4:
519             self._parse_error('ERROR')
520         self.fields.append(p[2])
521         p[0] = Field(p[1], p[2])
522
523     def p_declaration_array(self, p):
524         '''declaration : type_specifier ID '[' NUM ']' ';'
525                        | type_specifier ID '[' ID ']' ';' '''
526         if len(p) != 7:
527             return self._parse_error(
528                 'array: %s' % p.value,
529                 self._coord(lineno=p.lineno))
530
531         # Make this error later
532         if type(p[4]) is int and p[4] == 0:
533             # XXX: Line number is wrong
534             self._parse_warning('Old Style VLA: {} {}[{}];'
535                                 .format(p[1], p[2], p[4]),
536                                 self._token_coord(p, 1))
537
538         if type(p[4]) is str and p[4] not in self.fields:
539             # Verify that length field exists
540             self._parse_error('Missing length field: {} {}[{}];'
541                               .format(p[1], p[2], p[4]),
542                               self._token_coord(p, 1))
543         p[0] = Array(p[1], p[2], p[4])
544
545     def p_option(self, p):
546         '''option : OPTION ID '=' assignee ';' '''
547         p[0] = Option([p[1], p[2], p[4]])
548
549     def p_assignee(self, p):
550         '''assignee : NUM
551                     | TRUE
552                     | FALSE
553                     | STRING_LITERAL '''
554         p[0] = p[1]
555
556     def p_type_specifier(self, p):
557         '''type_specifier : U8
558                           | U16
559                           | U32
560                           | U64
561                           | I8
562                           | I16
563                           | I32
564                           | I64
565                           | F64
566                           | BOOL
567                           | STRING'''
568         p[0] = p[1]
569
570     # Do a second pass later to verify that user defined types are defined
571     def p_typedef_specifier(self, p):
572         '''type_specifier : ID '''
573         if p[1] not in global_types:
574             self._parse_error('Undefined type: {}'.format(p[1]),
575                               self._token_coord(p, 1))
576         p[0] = p[1]
577
578     def p_union(self, p):
579         '''union : UNION ID '{' block_statements_opt '}' ';' '''
580         p[0] = Union(p[2], p[4])
581
582     # Error rule for syntax errors
583     def p_error(self, p):
584         if p:
585             self._parse_error(
586                 'before: %s' % p.value,
587                 self._coord(lineno=p.lineno))
588         else:
589             self._parse_error('At end of input', self.filename)
590
591
592 class VPPAPI(object):
593
594     def __init__(self, debug=False, filename='', logger=None):
595         self.lexer = lex.lex(module=VPPAPILexer(filename), debug=debug)
596         self.parser = yacc.yacc(module=VPPAPIParser(filename, logger),
597                                 write_tables=False, debug=debug)
598         self.logger = logger
599
600     def parse_string(self, code, debug=0, lineno=1):
601         self.lexer.lineno = lineno
602         return self.parser.parse(code, lexer=self.lexer, debug=debug)
603
604     def parse_file(self, fd, debug=0):
605         data = fd.read()
606         return self.parse_string(data, debug=debug)
607
608     def autoreply_block(self, name):
609         block = [Field('u32', 'context'),
610                  Field('i32', 'retval')]
611         return Define(name + '_reply', [], block)
612
613     def process(self, objs):
614         s = {}
615         s['Option'] = {}
616         s['Define'] = []
617         s['Service'] = []
618         s['types'] = []
619         s['Import'] = []
620         s['Alias'] = {}
621         for o in objs:
622             tname = o.__class__.__name__
623             if isinstance(o, Define):
624                 s[tname].append(o)
625                 if o.autoreply:
626                     s[tname].append(self.autoreply_block(o.name))
627             elif isinstance(o, Option):
628                 s[tname][o[1]] = o[2]
629             elif type(o) is list:
630                 for o2 in o:
631                     if isinstance(o2, Service):
632                         s['Service'].append(o2)
633             elif isinstance(o, Enum) or isinstance(o, Typedef) or isinstance(o, Union):
634                 s['types'].append(o)
635             elif isinstance(o, Using):
636                 s['Alias'][o.name] = o.alias
637             else:
638                 if tname not in s:
639                     raise ValueError('Unknown class type: {} {}'.format(tname, o))
640                 s[tname].append(o)
641
642         msgs = {d.name: d for d in s['Define']}
643         svcs = {s.caller: s for s in s['Service']}
644         replies = {s.reply: s for s in s['Service']}
645         seen_services = {}
646
647         for service in svcs:
648             if service not in msgs:
649                 raise ValueError(
650                     'Service definition refers to unknown message'
651                     ' definition: {}'.format(service))
652             if svcs[service].reply != 'null' and \
653                svcs[service].reply not in msgs:
654                 raise ValueError('Service definition refers to unknown message'
655                                  ' definition in reply: {}'
656                                  .format(svcs[service].reply))
657             if service in replies:
658                 raise ValueError('Service definition refers to message'
659                                  ' marked as reply: {}'.format(service))
660             for event in svcs[service].events:
661                 if event not in msgs:
662                     raise ValueError('Service definition refers to unknown '
663                                      'event: {} in message: {}'
664                                      .format(event, service))
665                 seen_services[event] = True
666
667         # Create services implicitly
668         for d in msgs:
669             if d in seen_services:
670                 continue
671             if msgs[d].singular is True:
672                 continue
673             if d.endswith('_reply'):
674                 if d[:-6] in svcs:
675                     continue
676                 if d[:-6] not in msgs:
677                     raise ValueError('{} missing calling message'
678                                      .format(d))
679                 continue
680             if d.endswith('_dump'):
681                 if d in svcs:
682                     continue
683                 if d[:-5]+'_details' in msgs:
684                     s['Service'].append(Service(d, d[:-5]+'_details',
685                                                  stream=True))
686                 else:
687                     raise ValueError('{} missing details message'
688                                      .format(d))
689                 continue
690
691             if d.endswith('_details'):
692                 if d[:-8]+'_dump' not in msgs:
693                     raise ValueError('{} missing dump message'
694                                      .format(d))
695                 continue
696
697             if d in svcs:
698                 continue
699             if d+'_reply' in msgs:
700                 s['Service'].append(Service(d, d+'_reply'))
701             else:
702                 raise ValueError(
703                     '{} missing reply message ({}) or service definition'
704                     .format(d, d+'_reply'))
705
706         return s
707
708     def process_imports(self, objs, in_import, result):
709         imported_objs = []
710         for o in objs:
711             # Only allow the following object types from imported file
712             if in_import and not (isinstance(o, Enum) or
713                                   isinstance(o, Union) or
714                                   isinstance(o, Typedef) or
715                                   isinstance(o, Import) or
716                                   isinstance(o, Using)):
717                 continue
718             if isinstance(o, Import):
719                 self.process_imports(o.result, True, result)
720             else:
721                 result.append(o)
722
723 # Add message ids to each message.
724 def add_msg_id(s):
725     for o in s:
726         o.block.insert(0, Field('u16', '_vl_msg_id'))
727     return s
728
729
730 dirlist = []
731
732
733 def dirlist_add(dirs):
734     global dirlist
735     if dirs:
736         dirlist = dirlist + dirs
737
738
739 def dirlist_get():
740     return dirlist
741
742
743 #
744 # Main
745 #
746 def main():
747     cliparser = argparse.ArgumentParser(description='VPP API generator')
748     cliparser.add_argument('--pluginpath', default=""),
749     cliparser.add_argument('--includedir', action='append'),
750     cliparser.add_argument('--input', type=argparse.FileType('r'),
751                            default=sys.stdin)
752     cliparser.add_argument('--output', nargs='?', type=argparse.FileType('w'),
753                            default=sys.stdout)
754
755     cliparser.add_argument('output_module', nargs='?', default='C')
756     cliparser.add_argument('--debug', action='store_true')
757     cliparser.add_argument('--show-name', nargs=1)
758     args = cliparser.parse_args()
759
760     dirlist_add(args.includedir)
761     if not args.debug:
762         sys.excepthook = exception_handler
763
764     # Filename
765     if args.show_name:
766         filename = args.show_name[0]
767     elif args.input != sys.stdin:
768         filename = args.input.name
769     else:
770         filename = ''
771
772     if args.debug:
773         logging.basicConfig(stream=sys.stdout, level=logging.WARNING)
774     else:
775         logging.basicConfig()
776     log = logging.getLogger('vppapigen')
777
778     parser = VPPAPI(debug=args.debug, filename=filename, logger=log)
779     parsed_objects = parser.parse_file(args.input, log)
780
781     # Build a list of objects. Hash of lists.
782     result = []
783     parser.process_imports(parsed_objects, False, result)
784     s = parser.process(result)
785
786     # Add msg_id field
787     s['Define'] = add_msg_id(s['Define'])
788
789     file_crc = global_crc & 0xffffffff
790
791     #
792     # Debug
793     if args.debug:
794         import pprint
795         pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr)
796         for t in s['Define']:
797             pp.pprint([t.name, t.flags, t.block])
798         for t in s['types']:
799             pp.pprint([t.name, t.block])
800
801     #
802     # Generate representation
803     #
804     import imp
805
806     # Default path
807     pluginpath = ''
808     if not args.pluginpath:
809         cand = []
810         cand.append(os.path.dirname(os.path.realpath(__file__)))
811         cand.append(os.path.dirname(os.path.realpath(__file__)) +
812                     '/../share/vpp/')
813         for c in cand:
814             c += '/'
815             if os.path.isfile('{}vppapigen_{}.py'.format(c, args.output_module.lower())):
816                 pluginpath = c
817                 break
818     else:
819         pluginpath = args.pluginpath + '/'
820     if pluginpath == '':
821         raise Exception('Output plugin not found')
822     module_path = '{}vppapigen_{}.py'.format(pluginpath, args.output_module.lower())
823
824     try:
825         plugin = imp.load_source(args.output_module, module_path)
826     except Exception, err:
827         raise Exception('Error importing output plugin: {}, {}'
828                         .format(module_path, err))
829
830     result = plugin.run(filename, s, file_crc)
831     if result:
832         print(result, file=args.output)
833     else:
834         raise Exception('Running plugin failed: {} {}'
835                         .format(filename, result))
836
837
838 if __name__ == '__main__':
839     main()