blob: 45f10210a70a1b220599c67382dd46550900157a [file] [log] [blame]
[email protected]2ec654a2012-01-10 17:47:001#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]d3864f5c12011-04-08 15:19:043# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
[email protected]e4bda4e2011-04-20 17:41:586""" Parser for PPAPI IDL """
[email protected]d3864f5c12011-04-08 15:19:047
8#
9# IDL Parser
10#
11# The parser is uses the PLY yacc library to build a set of parsing rules based
12# on WebIDL.
13#
14# WebIDL, and WebIDL regular expressions can be found at:
15# http://dev.w3.org/2006/webapi/WebIDL/
16# PLY can be found at:
17# http://www.dabeaz.com/ply/
18#
19# The parser generates a tree by recursively matching sets of items against
20# defined patterns. When a match is made, that set of items is reduced
21# to a new item. The new item can provide a match for parent patterns.
22# In this way an AST is built (reduced) depth first.
23
24
25import getopt
[email protected]7a9f43d2011-05-04 18:01:4526import glob
[email protected]d3864f5c12011-04-08 15:19:0427import os.path
28import re
29import sys
[email protected]5eef2882011-07-19 00:08:5430import time
[email protected]d3864f5c12011-04-08 15:19:0431
[email protected]16796362011-07-02 19:43:1932from idl_ast import IDLAst
[email protected]4ad36962011-04-26 18:27:5733from idl_log import ErrOut, InfoOut, WarnOut
[email protected]d3864f5c12011-04-08 15:19:0434from idl_lexer import IDLLexer
[email protected]16796362011-07-02 19:43:1935from idl_node import IDLAttribute, IDLFile, IDLNode
[email protected]5b497ed2011-05-15 22:08:5636from idl_option import GetOption, Option, ParseOptions
[email protected]caff0c32011-07-22 16:34:1337from idl_lint import Lint
[email protected]4ad36962011-04-26 18:27:5738
[email protected]d3864f5c12011-04-08 15:19:0439from ply import lex
40from ply import yacc
41
[email protected]5b497ed2011-05-15 22:08:5642Option('build_debug', 'Debug tree building.')
43Option('parse_debug', 'Debug parse reduction steps.')
44Option('token_debug', 'Debug token generation.')
45Option('dump_tree', 'Dump the tree.')
[email protected]f93616a2011-10-04 22:54:2246Option('srcroot', 'Working directory.', default=os.path.join('..', 'api'))
[email protected]2069a3c2011-12-01 01:58:1747Option('include_private', 'Include private IDL directory in default API paths.')
[email protected]d3864f5c12011-04-08 15:19:0448
49#
50# ERROR_REMAP
51#
52# Maps the standard error formula into a more friendly error message.
53#
54ERROR_REMAP = {
55 'Unexpected ")" after "(".' : 'Empty argument list.',
56 'Unexpected ")" after ",".' : 'Missing argument.',
57 'Unexpected "}" after ",".' : 'Trailing comma in block.',
58 'Unexpected "}" after "{".' : 'Unexpected empty block.',
[email protected]16796362011-07-02 19:43:1959 'Unexpected comment after "}".' : 'Unexpected trailing comment.',
[email protected]d3864f5c12011-04-08 15:19:0460 'Unexpected "{" after keyword "enum".' : 'Enum missing name.',
61 'Unexpected "{" after keyword "struct".' : 'Struct missing name.',
62 'Unexpected "{" after keyword "interface".' : 'Interface missing name.',
63}
64
65# DumpReduction
66#
67# Prints out the set of items which matched a particular pattern and the
68# new item or set it was reduced to.
69def DumpReduction(cls, p):
70 if p[0] is None:
[email protected]4ad36962011-04-26 18:27:5771 InfoOut.Log("OBJ: %s(%d) - None\n" % (cls, len(p)))
[email protected]16796362011-07-02 19:43:1972 InfoOut.Log(" [%s]\n" % [str(x) for x in p[1:]])
[email protected]d3864f5c12011-04-08 15:19:0473 else:
74 out = ""
75 for index in range(len(p) - 1):
76 out += " >%s< " % str(p[index + 1])
[email protected]4ad36962011-04-26 18:27:5777 InfoOut.Log("OBJ: %s(%d) - %s : %s\n" % (cls, len(p), str(p[0]), out))
[email protected]d3864f5c12011-04-08 15:19:0478
79
80# CopyToList
81#
82# Takes an input item, list, or None, and returns a new list of that set.
83def CopyToList(item):
84 # If the item is 'Empty' make it an empty list
85 if not item: item = []
86
87 # If the item is not a list
88 if type(item) is not type([]): item = [item]
89
90 # Make a copy we can modify
91 return list(item)
92
93
94
95# ListFromConcat
96#
97# Generate a new List by joining of two sets of inputs which can be an
98# individual item, a list of items, or None.
99def ListFromConcat(*items):
100 itemsout = []
101 for item in items:
102 itemlist = CopyToList(item)
103 itemsout.extend(itemlist)
104
105 return itemsout
106
107
108# TokenTypeName
109#
110# Generate a string which has the type and value of the token.
111def TokenTypeName(t):
112 if t.type == 'SYMBOL': return 'symbol %s' % t.value
113 if t.type in ['HEX', 'INT', 'OCT', 'FLOAT']:
114 return 'value %s' % t.value
115 if t.type == 'STRING' : return 'string "%s"' % t.value
[email protected]16796362011-07-02 19:43:19116 if t.type == 'COMMENT' : return 'comment'
[email protected]d3864f5c12011-04-08 15:19:04117 if t.type == t.value: return '"%s"' % t.value
118 return 'keyword "%s"' % t.value
119
120
[email protected]e4bda4e2011-04-20 17:41:58121#
[email protected]d3864f5c12011-04-08 15:19:04122# IDL Parser
123#
124# The Parser inherits the from the Lexer to provide PLY with the tokenizing
125# definitions. Parsing patterns are encoded as function where p_<name> is
126# is called any time a patern matching the function documentation is found.
127# Paterns are expressed in the form of:
128# """ <new item> : <item> ....
129# | <item> ...."""
130#
131# Where new item is the result of a match against one or more sets of items
132# separated by the "|".
133#
134# The function is called with an object 'p' where p[0] is the output object
135# and p[n] is the set of inputs for positive values of 'n'. Len(p) can be
136# used to distinguish between multiple item sets in the pattern.
137#
138# For more details on parsing refer to the PLY documentation at
139# http://www.dabeaz.com/ply/
140#
141#
142# The parser uses the following conventions:
143# a <type>_block defines a block of <type> definitions in the form of:
144# [comment] [ext_attr_block] <type> <name> '{' <type>_list '}' ';'
145# A block is reduced by returning an object of <type> with a name of <name>
146# which in turn has <type>_list as children.
147#
148# A [comment] is a optional C style comment block enclosed in /* ... */ which
149# is appended to the adjacent node as a child.
150#
151# A [ext_attr_block] is an optional list of Extended Attributes which is
152# appended to the adjacent node as a child.
153#
154# a <type>_list defines a list of <type> items which will be passed as a
155# list of children to the parent pattern. A list is in the form of:
156# [comment] [ext_attr_block] <...DEF...> ';' <type>_list | (empty)
157# or
158# [comment] [ext_attr_block] <...DEF...> <type>_cont
159#
160# In the first form, the list is reduced recursively, where the right side
161# <type>_list is first reduced then joined with pattern currently being
162# matched. The list is terminated with the (empty) pattern is matched.
163#
164# In the second form the list is reduced recursively, where the right side
165# <type>_cont is first reduced then joined with the pattern currently being
166# matched. The type_<cont> is in the form of:
167# ',' <type>_list | (empty)
168# The <type>_cont form is used to consume the ',' which only occurs when
169# there is more than one object in the list. The <type>_cont also provides
170# the terminating (empty) definition.
171#
172
173
174class IDLParser(IDLLexer):
175# TOP
176#
177# This pattern defines the top of the parse tree. The parse tree is in the
178# the form of:
179#
180# top
181# *modifiers
182# *comments
183# *ext_attr_block
184# ext_attr_list
185# attr_arg_list
186# *integer, value
187# *param_list
188# *typeref
189#
190# top_list
191# describe_block
192# describe_list
193# enum_block
[email protected]16796362011-07-02 19:43:19194# enum_item
[email protected]d3864f5c12011-04-08 15:19:04195# interface_block
[email protected]16796362011-07-02 19:43:19196# member
197# label_block
198# label_item
[email protected]d3864f5c12011-04-08 15:19:04199# struct_block
200# member
201# typedef_decl
202# typedef_data
203# typedef_func
204#
205# (* sub matches found at multiple levels and are not truly children of top)
206#
207# We force all input files to start with two comments. The first comment is a
208# Copyright notice followed by a set of file wide Extended Attributes, followed
209# by the file comment and finally by file level patterns.
210#
211 # Find the Copyright, File comment, and optional file wide attributes. We
212 # use a match with COMMENT instead of comments to force the token to be
213 # present. The extended attributes and the top_list become siblings which
214 # in turn are children of the file object created from the results of top.
215 def p_top(self, p):
216 """top : COMMENT COMMENT ext_attr_block top_list"""
217
[email protected]16796362011-07-02 19:43:19218 Copyright = self.BuildComment('Copyright', p, 1)
219 Filedoc = self.BuildComment('Comment', p, 2)
[email protected]d3864f5c12011-04-08 15:19:04220
[email protected]f0dd202f2011-06-07 01:36:01221 p[0] = ListFromConcat(Copyright, Filedoc, p[3], p[4])
[email protected]d3864f5c12011-04-08 15:19:04222 if self.parse_debug: DumpReduction('top', p)
223
224 # Build a list of top level items.
225 def p_top_list(self, p):
226 """top_list : describe_block top_list
227 | enum_block top_list
[email protected]16796362011-07-02 19:43:19228 | inline top_list
[email protected]d3864f5c12011-04-08 15:19:04229 | interface_block top_list
[email protected]16796362011-07-02 19:43:19230 | label_block top_list
[email protected]d3864f5c12011-04-08 15:19:04231 | struct_block top_list
[email protected]16796362011-07-02 19:43:19232 | typedef_decl top_list
[email protected]d3864f5c12011-04-08 15:19:04233 | """
234 if len(p) > 2:
235 p[0] = ListFromConcat(p[1], p[2])
236 if self.parse_debug: DumpReduction('top_list', p)
237
238 # Recover from error and continue parsing at the next top match.
239 def p_top_error(self, p):
240 """top_list : error top_list"""
241 p[0] = p[2]
242
243#
244# Modifier List
245#
246#
247 def p_modifiers(self, p):
248 """modifiers : comments ext_attr_block"""
249 p[0] = ListFromConcat(p[1], p[2])
250 if self.parse_debug: DumpReduction('modifiers', p)
251
252#
253# Comments
254#
255# Comments are optional list of C style comment objects. Comments are returned
256# as a list or None.
257#
258 def p_comments(self, p):
259 """comments : COMMENT comments
260 | """
261 if len(p) > 1:
[email protected]16796362011-07-02 19:43:19262 child = self.BuildComment('Comment', p, 1)
[email protected]d3864f5c12011-04-08 15:19:04263 p[0] = ListFromConcat(child, p[2])
264 if self.parse_debug: DumpReduction('comments', p)
265 else:
266 if self.parse_debug: DumpReduction('no comments', p)
267
[email protected]16796362011-07-02 19:43:19268
[email protected]d3864f5c12011-04-08 15:19:04269#
[email protected]16796362011-07-02 19:43:19270# Inline
271#
272# Inline blocks define option code to be emitted based on language tag,
273# in the form of:
274# #inline <LANGUAGE>
275# <CODE>
276# #endinl
277#
278 def p_inline(self, p):
279 """inline : modifiers INLINE"""
280 words = p[2].split()
281 name = self.BuildAttribute('NAME', words[1])
282 lines = p[2].split('\n')
283 value = self.BuildAttribute('VALUE', '\n'.join(lines[1:-1]) + '\n')
284 children = ListFromConcat(name, value, p[1])
285 p[0] = self.BuildProduction('Inline', p, 2, children)
286 if self.parse_debug: DumpReduction('inline', p)
287
[email protected]d3864f5c12011-04-08 15:19:04288# Extended Attributes
289#
290# Extended Attributes denote properties which will be applied to a node in the
291# AST. A list of extended attributes are denoted by a brackets '[' ... ']'
292# enclosing a comma separated list of extended attributes in the form of:
293#
294# Name
295# Name=HEX | INT | OCT | FLOAT
296# Name="STRING"
297# Name=Function(arg ...)
298# TODO(noelallen) -Not currently supported:
299# ** Name(arg ...) ...
300# ** Name=Scope::Value
301#
302# Extended Attributes are returned as a list or None.
303
304 def p_ext_attr_block(self, p):
305 """ext_attr_block : '[' ext_attr_list ']'
306 | """
307 if len(p) > 1:
308 p[0] = p[2]
309 if self.parse_debug: DumpReduction('ext_attr_block', p)
310 else:
311 if self.parse_debug: DumpReduction('no ext_attr_block', p)
312
313 def p_ext_attr_list(self, p):
[email protected]16796362011-07-02 19:43:19314 """ext_attr_list : SYMBOL '=' SYMBOL ext_attr_cont
315 | SYMBOL '=' value ext_attr_cont
316 | SYMBOL '=' SYMBOL param_list ext_attr_cont
317 | SYMBOL ext_attr_cont"""
318 # If there are 4 tokens plus a return slot, this must be in the form
319 # SYMBOL = SYMBOL|value ext_attr_cont
[email protected]d3864f5c12011-04-08 15:19:04320 if len(p) == 5:
[email protected]16796362011-07-02 19:43:19321 p[0] = ListFromConcat(self.BuildAttribute(p[1], p[3]), p[4])
322 # If there are 5 tokens plus a return slot, this must be in the form
323 # SYMBOL = SYMBOL (param_list) ext_attr_cont
324 elif len(p) == 6:
325 member = self.BuildNamed('Member', p, 3, [p[4]])
326 p[0] = ListFromConcat(self.BuildAttribute(p[1], member), p[5])
327 # Otherwise, this must be: SYMBOL ext_attr_cont
328 else:
329 p[0] = ListFromConcat(self.BuildAttribute(p[1], 'True'), p[2])
[email protected]d3864f5c12011-04-08 15:19:04330 if self.parse_debug: DumpReduction('ext_attribute_list', p)
331
332 def p_ext_attr_cont(self, p):
333 """ext_attr_cont : ',' ext_attr_list
334 |"""
[email protected]e4bda4e2011-04-20 17:41:58335 if len(p) > 1: p[0] = p[2]
[email protected]d3864f5c12011-04-08 15:19:04336 if self.parse_debug: DumpReduction('ext_attribute_cont', p)
337
[email protected]16796362011-07-02 19:43:19338 def p_ext_attr_func(self, p):
339 """ext_attr_list : SYMBOL '(' attr_arg_list ')' ext_attr_cont"""
340 p[0] = ListFromConcat(self.BuildAttribute(p[1] + '()', p[3]), p[5])
341 if self.parse_debug: DumpReduction('attr_arg_func', p)
342
343 def p_ext_attr_arg_list(self, p):
[email protected]d3864f5c12011-04-08 15:19:04344 """attr_arg_list : SYMBOL attr_arg_cont
[email protected]16796362011-07-02 19:43:19345 | value attr_arg_cont"""
346 p[0] = ListFromConcat(p[1], p[2])
[email protected]d3864f5c12011-04-08 15:19:04347
348 def p_attr_arg_cont(self, p):
349 """attr_arg_cont : ',' attr_arg_list
350 | """
[email protected]d3864f5c12011-04-08 15:19:04351 if self.parse_debug: DumpReduction('attr_arg_cont', p)
[email protected]16796362011-07-02 19:43:19352 if len(p) > 1: p[0] = p[2]
[email protected]d3864f5c12011-04-08 15:19:04353
354 def p_attr_arg_error(self, p):
355 """attr_arg_cont : error attr_arg_cont"""
356 p[0] = p[2]
357 if self.parse_debug: DumpReduction('attr_arg_error', p)
358
359
360#
361# Describe
362#
363# A describe block is defined at the top level. It provides a mechanism for
364# attributing a group of ext_attr to a describe_list. Members of the
365# describe list are language specific 'Type' declarations
366#
367 def p_describe_block(self, p):
368 """describe_block : modifiers DESCRIBE '{' describe_list '}' ';'"""
[email protected]ec5af272011-07-19 01:21:53369 children = ListFromConcat(p[1], p[4])
[email protected]d3864f5c12011-04-08 15:19:04370 p[0] = self.BuildProduction('Describe', p, 2, children)
371 if self.parse_debug: DumpReduction('describe_block', p)
372
373 def p_describe_list(self, p):
374 """describe_list : modifiers SYMBOL ';' describe_list
375 | modifiers ENUM ';' describe_list
376 | modifiers STRUCT ';' describe_list
377 | modifiers TYPEDEF ';' describe_list
378 | """
379 if len(p) > 1:
[email protected]ec5af272011-07-19 01:21:53380 Type = self.BuildNamed('Type', p, 2, p[1])
[email protected]d3864f5c12011-04-08 15:19:04381 p[0] = ListFromConcat(Type, p[4])
382
383 def p_describe_error(self, p):
384 """describe_list : error describe_list"""
385 p[0] = p[2]
386
387#
388# Constant Values (integer, value)
389#
390# Constant values can be found at various levels. A Constant value is returns
391# as the string value after validated against a FLOAT, HEX, INT, OCT or
392# STRING pattern as appropriate.
393#
394 def p_value(self, p):
395 """value : FLOAT
396 | HEX
397 | INT
398 | OCT
399 | STRING"""
400 p[0] = p[1]
401 if self.parse_debug: DumpReduction('value', p)
402
403 def p_value_lshift(self, p):
404 """value : integer LSHIFT INT"""
[email protected]16796362011-07-02 19:43:19405 p[0] = "%s << %s" % (p[1], p[3])
[email protected]d3864f5c12011-04-08 15:19:04406 if self.parse_debug: DumpReduction('value', p)
407
408# Integers are numbers which may not be floats used in cases like array sizes.
409 def p_integer(self, p):
410 """integer : HEX
411 | INT
412 | OCT"""
413 p[0] = p[1]
[email protected]16796362011-07-02 19:43:19414 if self.parse_debug: DumpReduction('integer', p)
415
[email protected]a2365a22011-10-07 22:43:04416#
417# Expression
418#
419# A simple arithmetic expression.
420#
421 precedence = (
422 ('left','|','&','^'),
423 ('left','LSHIFT','RSHIFT'),
424 ('left','+','-'),
425 ('left','*','/'),
426 ('right','UMINUS','~'),
427 )
[email protected]16796362011-07-02 19:43:19428
[email protected]a2365a22011-10-07 22:43:04429 def p_expression_binop(self, p):
430 """expression : expression LSHIFT expression
431 | expression RSHIFT expression
432 | expression '|' expression
433 | expression '&' expression
434 | expression '^' expression
435 | expression '+' expression
436 | expression '-' expression
437 | expression '*' expression
438 | expression '/' expression"""
439 p[0] = "%s %s %s" % (str(p[1]), str(p[2]), str(p[3]))
440 if self.parse_debug: DumpReduction('expression_binop', p)
441
442 def p_expression_unop(self, p):
443 """expression : '-' expression %prec UMINUS
444 | '~' expression %prec '~'"""
445 p[0] = "%s%s" % (str(p[1]), str(p[2]))
446 if self.parse_debug: DumpReduction('expression_unop', p)
447
448 def p_expression_term(self, p):
449 "expression : '(' expression ')'"
450 p[0] = "%s%s%s" % (str(p[1]), str(p[2]), str(p[3]))
451 if self.parse_debug: DumpReduction('expression_term', p)
452
453 def p_expression_symbol(self, p):
454 "expression : SYMBOL"
455 p[0] = p[1]
456 if self.parse_debug: DumpReduction('expression_symbol', p)
457
458 def p_expression_integer(self, p):
459 "expression : integer"
460 p[0] = p[1]
461 if self.parse_debug: DumpReduction('expression_integer', p)
[email protected]16796362011-07-02 19:43:19462
463#
464# Array List
465#
466# Defined a list of array sizes (if any).
467#
468 def p_arrays(self, p):
469 """arrays : '[' ']' arrays
470 | '[' integer ']' arrays
471 | """
472 # If there are 3 tokens plus a return slot it is an unsized array
473 if len(p) == 4:
474 array = self.BuildProduction('Array', p, 1)
475 p[0] = ListFromConcat(array, p[3])
476 # If there are 4 tokens plus a return slot it is a fixed array
477 elif len(p) == 5:
478 count = self.BuildAttribute('FIXED', p[2])
479 array = self.BuildProduction('Array', p, 2, [count])
480 p[0] = ListFromConcat(array, p[4])
481 # If there is only a return slot, do not fill it for this terminator.
482 elif len(p) == 1: return
483 if self.parse_debug: DumpReduction('arrays', p)
[email protected]d3864f5c12011-04-08 15:19:04484
485#
486# Parameter List
487#
488# A parameter list is a collection of arguments which are passed to a
[email protected]7a9f43d2011-05-04 18:01:45489# function.
[email protected]d3864f5c12011-04-08 15:19:04490#
[email protected]d3864f5c12011-04-08 15:19:04491 def p_param_list(self, p):
[email protected]16796362011-07-02 19:43:19492 """param_list : '(' param_item param_cont ')'
493 | '(' ')' """
494 if len(p) > 3:
495 args = ListFromConcat(p[2], p[3])
[email protected]7a9f43d2011-05-04 18:01:45496 else:
[email protected]38c0f7e2011-06-02 01:16:30497 args = []
[email protected]16796362011-07-02 19:43:19498 p[0] = self.BuildProduction('Callspec', p, 1, args)
[email protected]7a9f43d2011-05-04 18:01:45499 if self.parse_debug: DumpReduction('param_list', p)
500
501 def p_param_item(self, p):
[email protected]16796362011-07-02 19:43:19502 """param_item : modifiers SYMBOL arrays SYMBOL"""
503 typeref = self.BuildAttribute('TYPEREF', p[2])
504 children = ListFromConcat(p[1],typeref, p[3])
505 p[0] = self.BuildNamed('Param', p, 4, children)
[email protected]7a9f43d2011-05-04 18:01:45506 if self.parse_debug: DumpReduction('param_item', p)
[email protected]d3864f5c12011-04-08 15:19:04507
508 def p_param_cont(self, p):
[email protected]7a9f43d2011-05-04 18:01:45509 """param_cont : ',' param_item param_cont
[email protected]d3864f5c12011-04-08 15:19:04510 | """
511 if len(p) > 1:
[email protected]16796362011-07-02 19:43:19512 p[0] = ListFromConcat(p[2], p[3])
[email protected]d3864f5c12011-04-08 15:19:04513 if self.parse_debug: DumpReduction('param_cont', p)
514
515 def p_param_error(self, p):
516 """param_cont : error param_cont"""
517 p[0] = p[2]
518
[email protected]d3864f5c12011-04-08 15:19:04519
520#
[email protected]38c0f7e2011-06-02 01:16:30521# Typedef
522#
523# A typedef creates a new referencable type. The tyepdef can specify an array
524# definition as well as a function declaration.
525#
526 def p_typedef_data(self, p):
[email protected]16796362011-07-02 19:43:19527 """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL ';' """
528 typeref = self.BuildAttribute('TYPEREF', p[3])
529 children = ListFromConcat(p[1], typeref)
530 p[0] = self.BuildNamed('Typedef', p, 4, children)
[email protected]38c0f7e2011-06-02 01:16:30531 if self.parse_debug: DumpReduction('typedef_data', p)
532
[email protected]16796362011-07-02 19:43:19533 def p_typedef_array(self, p):
534 """typedef_decl : modifiers TYPEDEF SYMBOL arrays SYMBOL ';' """
535 typeref = self.BuildAttribute('TYPEREF', p[3])
536 children = ListFromConcat(p[1], typeref, p[4])
537 p[0] = self.BuildNamed('Typedef', p, 5, children)
538 if self.parse_debug: DumpReduction('typedef_array', p)
539
[email protected]38c0f7e2011-06-02 01:16:30540 def p_typedef_func(self, p):
[email protected]16796362011-07-02 19:43:19541 """typedef_decl : modifiers TYPEDEF SYMBOL SYMBOL param_list ';' """
542 typeref = self.BuildAttribute('TYPEREF', p[3])
543 children = ListFromConcat(p[1], typeref, p[5])
544 p[0] = self.BuildNamed('Typedef', p, 4, children)
[email protected]38c0f7e2011-06-02 01:16:30545 if self.parse_debug: DumpReduction('typedef_func', p)
546
547#
[email protected]d3864f5c12011-04-08 15:19:04548# Enumeration
549#
550# An enumeration is a set of named integer constants. An enumeration
551# is valid type which can be referenced in other definitions.
552#
553 def p_enum_block(self, p):
554 """enum_block : modifiers ENUM SYMBOL '{' enum_list '}' ';'"""
[email protected]16796362011-07-02 19:43:19555 p[0] = self.BuildNamed('Enum', p, 3, ListFromConcat(p[1], p[5]))
[email protected]d3864f5c12011-04-08 15:19:04556 if self.parse_debug: DumpReduction('enum_block', p)
557
558 def p_enum_list(self, p):
[email protected]a2365a22011-10-07 22:43:04559 """enum_list : modifiers SYMBOL '=' expression enum_cont
[email protected]16796362011-07-02 19:43:19560 | modifiers SYMBOL enum_cont"""
561 if len(p) > 4:
562 val = self.BuildAttribute('VALUE', p[4])
563 enum = self.BuildNamed('EnumItem', p, 2, ListFromConcat(val, p[1]))
564 p[0] = ListFromConcat(enum, p[5])
565 else:
566 enum = self.BuildNamed('EnumItem', p, 2, p[1])
567 p[0] = ListFromConcat(enum, p[3])
[email protected]d3864f5c12011-04-08 15:19:04568 if self.parse_debug: DumpReduction('enum_list', p)
569
570 def p_enum_cont(self, p):
571 """enum_cont : ',' enum_list
572 |"""
573 if len(p) > 1: p[0] = p[2]
574 if self.parse_debug: DumpReduction('enum_cont', p)
575
576 def p_enum_cont_error(self, p):
577 """enum_cont : error enum_cont"""
578 p[0] = p[2]
579 if self.parse_debug: DumpReduction('enum_error', p)
580
581
582#
[email protected]16796362011-07-02 19:43:19583# Label
584#
585# A label is a special kind of enumeration which allows us to go from a
586# set of labels
587#
588 def p_label_block(self, p):
589 """label_block : modifiers LABEL SYMBOL '{' label_list '}' ';'"""
590 p[0] = self.BuildNamed('Label', p, 3, ListFromConcat(p[1], p[5]))
591 if self.parse_debug: DumpReduction('label_block', p)
592
593 def p_label_list(self, p):
594 """label_list : modifiers SYMBOL '=' FLOAT label_cont"""
595 val = self.BuildAttribute('VALUE', p[4])
596 label = self.BuildNamed('LabelItem', p, 2, ListFromConcat(val, p[1]))
597 p[0] = ListFromConcat(label, p[5])
598 if self.parse_debug: DumpReduction('label_list', p)
599
600 def p_label_cont(self, p):
601 """label_cont : ',' label_list
602 |"""
603 if len(p) > 1: p[0] = p[2]
604 if self.parse_debug: DumpReduction('label_cont', p)
605
606 def p_label_cont_error(self, p):
607 """label_cont : error label_cont"""
608 p[0] = p[2]
609 if self.parse_debug: DumpReduction('label_error', p)
610
611
612#
613# Members
614#
615# A member attribute or function of a struct or interface.
616#
617 def p_member_attribute(self, p):
618 """member_attribute : modifiers SYMBOL SYMBOL """
619 typeref = self.BuildAttribute('TYPEREF', p[2])
620 children = ListFromConcat(p[1], typeref)
621 p[0] = self.BuildNamed('Member', p, 3, children)
622 if self.parse_debug: DumpReduction('attribute', p)
623
624 def p_member_attribute_array(self, p):
625 """member_attribute : modifiers SYMBOL arrays SYMBOL """
626 typeref = self.BuildAttribute('TYPEREF', p[2])
627 children = ListFromConcat(p[1], typeref, p[3])
628 p[0] = self.BuildNamed('Member', p, 4, children)
629 if self.parse_debug: DumpReduction('attribute', p)
630
631 def p_member_function(self, p):
632 """member_function : modifiers SYMBOL SYMBOL param_list"""
633 typeref = self.BuildAttribute('TYPEREF', p[2])
634 children = ListFromConcat(p[1], typeref, p[4])
635 p[0] = self.BuildNamed('Member', p, 3, children)
636 if self.parse_debug: DumpReduction('function', p)
637
638#
[email protected]d3864f5c12011-04-08 15:19:04639# Interface
640#
641# An interface is a named collection of functions.
642#
643 def p_interface_block(self, p):
[email protected]16796362011-07-02 19:43:19644 """interface_block : modifiers INTERFACE SYMBOL '{' interface_list '}' ';'"""
645 p[0] = self.BuildNamed('Interface', p, 3, ListFromConcat(p[1], p[5]))
[email protected]d3864f5c12011-04-08 15:19:04646 if self.parse_debug: DumpReduction('interface_block', p)
647
[email protected]16796362011-07-02 19:43:19648 def p_interface_list(self, p):
649 """interface_list : member_function ';' interface_list
650 | """
[email protected]d3864f5c12011-04-08 15:19:04651 if len(p) > 1 :
[email protected]16796362011-07-02 19:43:19652 p[0] = ListFromConcat(p[1], p[3])
653 if self.parse_debug: DumpReduction('interface_list', p)
[email protected]d3864f5c12011-04-08 15:19:04654
[email protected]16796362011-07-02 19:43:19655 def p_interface_error(self, p):
656 """interface_list : error interface_list"""
[email protected]d3864f5c12011-04-08 15:19:04657 p[0] = p[2]
658
659#
660# Struct
661#
662# A struct is a named collection of members which in turn reference other
663# types. The struct is a referencable type.
664#
665 def p_struct_block(self, p):
666 """struct_block : modifiers STRUCT SYMBOL '{' struct_list '}' ';'"""
[email protected]16796362011-07-02 19:43:19667 children = ListFromConcat(p[1], p[5])
668 p[0] = self.BuildNamed('Struct', p, 3, children)
[email protected]d3864f5c12011-04-08 15:19:04669 if self.parse_debug: DumpReduction('struct_block', p)
670
671 def p_struct_list(self, p):
[email protected]16796362011-07-02 19:43:19672 """struct_list : member_attribute ';' struct_list
673 | member_function ';' struct_list
674 |"""
675 if len(p) > 1: p[0] = ListFromConcat(p[1], p[3])
[email protected]d3864f5c12011-04-08 15:19:04676
[email protected]16796362011-07-02 19:43:19677 def p_struct_error(self, p):
678 """struct_list : error struct_list"""
679 p[0] = p[2]
[email protected]d3864f5c12011-04-08 15:19:04680
681#
682# Parser Errors
683#
684# p_error is called whenever the parser can not find a pattern match for
685# a set of items from the current state. The p_error function defined here
686# is triggered logging an error, and parsing recover happens as the
687# p_<type>_error functions defined above are called. This allows the parser
688# to continue so as to capture more than one error per file.
689#
690 def p_error(self, t):
691 filename = self.lexobj.filename
692 self.parse_errors += 1
693 if t:
694 lineno = t.lineno
695 pos = t.lexpos
696 prev = self.yaccobj.symstack[-1]
697 if type(prev) == lex.LexToken:
698 msg = "Unexpected %s after %s." % (
699 TokenTypeName(t), TokenTypeName(prev))
700 else:
701 msg = "Unexpected %s." % (t.value)
702 else:
703 lineno = self.last.lineno
704 pos = self.last.lexpos
705 msg = "Unexpected end of file after %s." % TokenTypeName(self.last)
706 self.yaccobj.restart()
707
708 # Attempt to remap the error to a friendlier form
709 if msg in ERROR_REMAP:
710 msg = ERROR_REMAP[msg]
711
712 # Log the error
[email protected]4ad36962011-04-26 18:27:57713 ErrOut.LogLine(filename, lineno, pos, msg)
[email protected]d3864f5c12011-04-08 15:19:04714
[email protected]38c0f7e2011-06-02 01:16:30715 def Warn(self, node, msg):
716 WarnOut.LogLine(node.filename, node.lineno, node.pos, msg)
717 self.parse_warnings += 1
[email protected]d3864f5c12011-04-08 15:19:04718
[email protected]5b497ed2011-05-15 22:08:56719 def __init__(self):
720 IDLLexer.__init__(self)
[email protected]d3864f5c12011-04-08 15:19:04721 self.yaccobj = yacc.yacc(module=self, tabmodule=None, debug=False,
722 optimize=0, write_tables=0)
723
[email protected]5b497ed2011-05-15 22:08:56724 self.build_debug = GetOption('build_debug')
725 self.parse_debug = GetOption('parse_debug')
726 self.token_debug = GetOption('token_debug')
727 self.verbose = GetOption('verbose')
[email protected]d3864f5c12011-04-08 15:19:04728 self.parse_errors = 0
729
730#
731# Tokenizer
732#
733# The token function returns the next token provided by IDLLexer for matching
734# against the leaf paterns.
735#
736 def token(self):
737 tok = self.lexobj.token()
738 if tok:
739 self.last = tok
740 if self.token_debug:
[email protected]4ad36962011-04-26 18:27:57741 InfoOut.Log("TOKEN %s(%s)" % (tok.type, tok.value))
[email protected]d3864f5c12011-04-08 15:19:04742 return tok
743
744#
745# BuildProduction
746#
747# Production is the set of items sent to a grammar rule resulting in a new
748# item being returned.
749#
750# p - Is the Yacc production object containing the stack of items
751# index - Index into the production of the name for the item being produced.
752# cls - The type of item being producted
753# childlist - The children of the new item
[email protected]16796362011-07-02 19:43:19754 def BuildProduction(self, cls, p, index, childlist=None):
755 if not childlist: childlist = []
[email protected]d3864f5c12011-04-08 15:19:04756 filename = self.lexobj.filename
757 lineno = p.lineno(index)
758 pos = p.lexpos(index)
[email protected]16796362011-07-02 19:43:19759 out = IDLNode(cls, filename, lineno, pos, childlist)
[email protected]d3864f5c12011-04-08 15:19:04760 if self.build_debug:
[email protected]16796362011-07-02 19:43:19761 InfoOut.Log("Building %s" % out)
[email protected]e4bda4e2011-04-20 17:41:58762 return out
[email protected]d3864f5c12011-04-08 15:19:04763
[email protected]16796362011-07-02 19:43:19764 def BuildNamed(self, cls, p, index, childlist=None):
765 if not childlist: childlist = []
766 childlist.append(self.BuildAttribute('NAME', p[index]))
767 return self.BuildProduction(cls, p, index, childlist)
768
769 def BuildComment(self, cls, p, index):
770 name = p[index]
771
772 # Remove comment markers
773 if name[:2] == '//':
774 # For C++ style, remove the preceding '//'
775 form = 'cc'
776 name = name[2:].rstrip()
777 else:
778 # For C style, remove ending '*/''
779 form = 'c'
780 lines = []
781 for line in name[:-2].split('\n'):
782 # Remove characters until start marker for this line '*' if found
783 # otherwise it should be blank.
784 offs = line.find('*')
785 if offs >= 0:
786 line = line[offs + 1:].rstrip()
787 else:
788 line = ''
789 lines.append(line)
790 name = '\n'.join(lines)
791
792 childlist = [self.BuildAttribute('NAME', name),
793 self.BuildAttribute('FORM', form)]
794 return self.BuildProduction(cls, p, index, childlist)
795
[email protected]d3864f5c12011-04-08 15:19:04796#
[email protected]16796362011-07-02 19:43:19797# BuildAttribute
[email protected]d3864f5c12011-04-08 15:19:04798#
799# An ExtendedAttribute is a special production that results in a property
800# which is applied to the adjacent item. Attributes have no children and
801# instead represent key/value pairs.
802#
[email protected]16796362011-07-02 19:43:19803 def BuildAttribute(self, key, val):
804 return IDLAttribute(key, val)
805
[email protected]d3864f5c12011-04-08 15:19:04806
807#
808# ParseData
809#
810# Attempts to parse the current data loaded in the lexer.
811#
[email protected]38c0f7e2011-06-02 01:16:30812 def ParseData(self, data, filename='<Internal>'):
813 self.SetData(filename, data)
[email protected]d3864f5c12011-04-08 15:19:04814 try:
[email protected]7a9f43d2011-05-04 18:01:45815 self.parse_errors = 0
[email protected]38c0f7e2011-06-02 01:16:30816 self.parse_warnings = 0
[email protected]d3864f5c12011-04-08 15:19:04817 return self.yaccobj.parse(lexer=self)
818
819 except lex.LexError as le:
[email protected]4ad36962011-04-26 18:27:57820 ErrOut.Log(str(le))
[email protected]d3864f5c12011-04-08 15:19:04821 return []
822
823#
824# ParseFile
825#
826# Loads a new file into the lexer and attemps to parse it.
827#
828 def ParseFile(self, filename):
[email protected]5eef2882011-07-19 00:08:54829 date = time.ctime(os.path.getmtime(filename))
[email protected]16796362011-07-02 19:43:19830 data = open(filename).read()
[email protected]d3864f5c12011-04-08 15:19:04831 if self.verbose:
[email protected]4ad36962011-04-26 18:27:57832 InfoOut.Log("Parsing %s" % filename)
[email protected]d3864f5c12011-04-08 15:19:04833 try:
[email protected]38c0f7e2011-06-02 01:16:30834 out = self.ParseData(data, filename)
[email protected]16796362011-07-02 19:43:19835
836 # If we have a src root specified, remove it from the path
837 srcroot = GetOption('srcroot')
838 if srcroot and filename.find(srcroot) == 0:
839 filename = filename[len(srcroot) + 1:]
[email protected]5eef2882011-07-19 00:08:54840 filenode = IDLFile(filename, out, self.parse_errors + self.lex_errors)
841 filenode.SetProperty('DATETIME', date)
842 return filenode
[email protected]d3864f5c12011-04-08 15:19:04843
844 except Exception as e:
[email protected]4ad36962011-04-26 18:27:57845 ErrOut.LogLine(filename, self.last.lineno, self.last.lexpos,
846 'Internal parsing error - %s.' % str(e))
[email protected]d3864f5c12011-04-08 15:19:04847 raise
[email protected]d3864f5c12011-04-08 15:19:04848
849
850
[email protected]e4bda4e2011-04-20 17:41:58851#
852# Flatten Tree
853#
854# Flattens the tree of IDLNodes for use in testing.
855#
[email protected]d3864f5c12011-04-08 15:19:04856def FlattenTree(node):
857 add_self = False
858 out = []
[email protected]16796362011-07-02 19:43:19859 for child in node.children:
860 if child.IsA('Comment'):
[email protected]d3864f5c12011-04-08 15:19:04861 add_self = True
862 else:
[email protected]16796362011-07-02 19:43:19863 out.extend(FlattenTree(child))
[email protected]d3864f5c12011-04-08 15:19:04864
865 if add_self:
866 out = [str(node)] + out
867 return out
868
869
[email protected]16796362011-07-02 19:43:19870def TestErrors(filename, filenode):
871 nodelist = filenode.GetChildren()
872
[email protected]d3864f5c12011-04-08 15:19:04873 lexer = IDLLexer()
874 data = open(filename).read()
875 lexer.SetData(filename, data)
876
877 pass_comments = []
878 fail_comments = []
879 while True:
880 tok = lexer.lexobj.token()
881 if tok == None: break
882 if tok.type == 'COMMENT':
[email protected]16796362011-07-02 19:43:19883 args = tok.value[3:-3].split()
884 if args[0] == 'OK':
885 pass_comments.append((tok.lineno, ' '.join(args[1:])))
[email protected]d3864f5c12011-04-08 15:19:04886 else:
[email protected]16796362011-07-02 19:43:19887 if args[0] == 'FAIL':
888 fail_comments.append((tok.lineno, ' '.join(args[1:])))
[email protected]d3864f5c12011-04-08 15:19:04889 obj_list = []
[email protected]7a9f43d2011-05-04 18:01:45890 for node in nodelist:
[email protected]d3864f5c12011-04-08 15:19:04891 obj_list.extend(FlattenTree(node))
892
893 errors = 0
894
895 #
896 # Check for expected successes
897 #
898 obj_cnt = len(obj_list)
899 pass_cnt = len(pass_comments)
900 if obj_cnt != pass_cnt:
[email protected]4ad36962011-04-26 18:27:57901 InfoOut.Log("Mismatched pass (%d) vs. nodes built (%d)."
[email protected]d3864f5c12011-04-08 15:19:04902 % (pass_cnt, obj_cnt))
[email protected]4ad36962011-04-26 18:27:57903 InfoOut.Log("PASS: %s" % [x[1] for x in pass_comments])
904 InfoOut.Log("OBJS: %s" % obj_list)
[email protected]d3864f5c12011-04-08 15:19:04905 errors += 1
906 if pass_cnt > obj_cnt: pass_cnt = obj_cnt
907
908 for i in range(pass_cnt):
909 line, comment = pass_comments[i]
910 if obj_list[i] != comment:
[email protected]4ad36962011-04-26 18:27:57911 ErrOut.LogLine(filename, line, None, "OBJ %s : EXPECTED %s\n" %
912 (obj_list[i], comment))
[email protected]d3864f5c12011-04-08 15:19:04913 errors += 1
914
915 #
916 # Check for expected errors
917 #
[email protected]4ad36962011-04-26 18:27:57918 err_list = ErrOut.DrainLog()
[email protected]d3864f5c12011-04-08 15:19:04919 err_cnt = len(err_list)
920 fail_cnt = len(fail_comments)
921 if err_cnt != fail_cnt:
[email protected]4ad36962011-04-26 18:27:57922 InfoOut.Log("Mismatched fail (%d) vs. errors seen (%d)."
[email protected]d3864f5c12011-04-08 15:19:04923 % (fail_cnt, err_cnt))
[email protected]4ad36962011-04-26 18:27:57924 InfoOut.Log("FAIL: %s" % [x[1] for x in fail_comments])
925 InfoOut.Log("ERRS: %s" % err_list)
[email protected]d3864f5c12011-04-08 15:19:04926 errors += 1
927 if fail_cnt > err_cnt: fail_cnt = err_cnt
928
929 for i in range(fail_cnt):
930 line, comment = fail_comments[i]
[email protected]4ad36962011-04-26 18:27:57931 err = err_list[i].strip()
932
[email protected]d3864f5c12011-04-08 15:19:04933 if err_list[i] != comment:
[email protected]4ad36962011-04-26 18:27:57934 ErrOut.Log("%s(%d) Error\n\tERROR : %s\n\tEXPECT: %s" % (
[email protected]d3864f5c12011-04-08 15:19:04935 filename, line, err_list[i], comment))
936 errors += 1
937
938 # Clear the error list for the next run
939 err_list = []
940 return errors
941
942
[email protected]7a9f43d2011-05-04 18:01:45943def TestFile(parser, filename):
944 # Capture errors instead of reporting them so we can compare them
945 # with the expected errors.
946 ErrOut.SetConsole(False)
947 ErrOut.SetCapture(True)
[email protected]4ad36962011-04-26 18:27:57948
[email protected]16796362011-07-02 19:43:19949 filenode = parser.ParseFile(filename)
[email protected]7a9f43d2011-05-04 18:01:45950
951 # Renable output
952 ErrOut.SetConsole(True)
953 ErrOut.SetCapture(False)
954
955 # Compare captured errors
[email protected]16796362011-07-02 19:43:19956 return TestErrors(filename, filenode)
[email protected]7a9f43d2011-05-04 18:01:45957
958
[email protected]16796362011-07-02 19:43:19959def TestErrorFiles(filter):
[email protected]7a9f43d2011-05-04 18:01:45960 idldir = os.path.split(sys.argv[0])[0]
961 idldir = os.path.join(idldir, 'test_parser', '*.idl')
962 filenames = glob.glob(idldir)
[email protected]5b497ed2011-05-15 22:08:56963 parser = IDLParser()
[email protected]7a9f43d2011-05-04 18:01:45964 total_errs = 0
965 for filename in filenames:
[email protected]16796362011-07-02 19:43:19966 if filter and filename not in filter: continue
[email protected]7a9f43d2011-05-04 18:01:45967 errs = TestFile(parser, filename)
968 if errs:
969 ErrOut.Log("%s test failed with %d error(s)." % (filename, errs))
970 total_errs += errs
971
972 if total_errs:
973 ErrOut.Log("Failed parsing test.")
974 else:
975 InfoOut.Log("Passed parsing test.")
976 return total_errs
977
[email protected]2ec654a2012-01-10 17:47:00978
[email protected]16796362011-07-02 19:43:19979def TestNamespaceFiles(filter):
[email protected]7a9f43d2011-05-04 18:01:45980 idldir = os.path.split(sys.argv[0])[0]
981 idldir = os.path.join(idldir, 'test_namespace', '*.idl')
982 filenames = glob.glob(idldir)
[email protected]16796362011-07-02 19:43:19983 testnames = []
984
985 for filename in filenames:
986 if filter and filename not in filter: continue
987 testnames.append(filename)
988
989 # If we have no files to test, then skip this test
990 if not testnames:
991 InfoOut.Log('No files to test for namespace.')
992 return 0
[email protected]7a9f43d2011-05-04 18:01:45993
994 InfoOut.SetConsole(False)
[email protected]16796362011-07-02 19:43:19995 ast = ParseFiles(testnames)
[email protected]7a9f43d2011-05-04 18:01:45996 InfoOut.SetConsole(True)
997
[email protected]16796362011-07-02 19:43:19998 errs = ast.GetProperty('ERRORS')
999 if errs:
[email protected]7a9f43d2011-05-04 18:01:451000 ErrOut.Log("Failed namespace test.")
1001 else:
1002 InfoOut.Log("Passed namespace test.")
[email protected]16796362011-07-02 19:43:191003 return errs
[email protected]7a9f43d2011-05-04 18:01:451004
[email protected]2e4361ae2011-12-15 00:03:351005default_dirs = ['.', 'trusted', 'dev', 'private']
[email protected]5b497ed2011-05-15 22:08:561006def ParseFiles(filenames):
1007 parser = IDLParser()
[email protected]7a9f43d2011-05-04 18:01:451008 filenodes = []
[email protected]7a9f43d2011-05-04 18:01:451009
[email protected]5eef2882011-07-19 00:08:541010 if not filenames:
1011 filenames = []
1012 srcroot = GetOption('srcroot')
[email protected]2069a3c2011-12-01 01:58:171013 dirs = default_dirs
1014 if GetOption('include_private'):
1015 dirs += ['private']
1016 for dirname in dirs:
[email protected]406793d2011-11-14 22:26:541017 srcdir = os.path.join(srcroot, dirname, '*.idl')
[email protected]ec5af272011-07-19 01:21:531018 srcdir = os.path.normpath(srcdir)
1019 filenames += sorted(glob.glob(srcdir))
[email protected]5eef2882011-07-19 00:08:541020
[email protected]4a41db52012-01-13 00:02:561021 if not filenames:
1022 ErrOut.Log('No sources provided.')
1023
[email protected]7a9f43d2011-05-04 18:01:451024 for filename in filenames:
[email protected]16796362011-07-02 19:43:191025 filenode = parser.ParseFile(filename)
[email protected]7a9f43d2011-05-04 18:01:451026 filenodes.append(filenode)
1027
1028 ast = IDLAst(filenodes)
[email protected]16796362011-07-02 19:43:191029 if GetOption('dump_tree'): ast.Dump(0)
[email protected]caff0c32011-07-22 16:34:131030
1031 Lint(ast)
[email protected]16796362011-07-02 19:43:191032 return ast
[email protected]7a9f43d2011-05-04 18:01:451033
[email protected]4ad36962011-04-26 18:27:571034
[email protected]d3864f5c12011-04-08 15:19:041035def Main(args):
[email protected]5b497ed2011-05-15 22:08:561036 filenames = ParseOptions(args)
[email protected]d3864f5c12011-04-08 15:19:041037
[email protected]5b497ed2011-05-15 22:08:561038 # If testing...
1039 if GetOption('test'):
[email protected]16796362011-07-02 19:43:191040 errs = TestErrorFiles(filenames)
1041 errs = TestNamespaceFiles(filenames)
[email protected]7a9f43d2011-05-04 18:01:451042 if errs:
1043 ErrOut.Log("Parser failed with %d errors." % errs)
1044 return -1
[email protected]5b497ed2011-05-15 22:08:561045 return 0
[email protected]4ad36962011-04-26 18:27:571046
[email protected]5b497ed2011-05-15 22:08:561047 # Otherwise, build the AST
[email protected]16796362011-07-02 19:43:191048 ast = ParseFiles(filenames)
1049 errs = ast.GetProperty('ERRORS')
1050 if errs:
1051 ErrOut.Log('Found %d error(s).' % errs);
[email protected]7a9f43d2011-05-04 18:01:451052 InfoOut.Log("%d files processed." % len(filenames))
[email protected]16796362011-07-02 19:43:191053 return errs
[email protected]d3864f5c12011-04-08 15:19:041054
[email protected]2ec654a2012-01-10 17:47:001055
[email protected]d3864f5c12011-04-08 15:19:041056if __name__ == '__main__':
1057 sys.exit(Main(sys.argv[1:]))