1 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """Utility functions (file reading, simple IDL parsing by regexes) for IDL build. 6 7 Design doc: http://www.chromium.org/developers/design-documents/idl-build 8 """ 9 10 import os 11 import cPickle as pickle 12 import re 13 import string 14 15 16 class IdlBadFilenameError(Exception): 17 """Raised if an IDL filename disagrees with the interface name in the file.""" 18 pass 19 20 21 def idl_filename_to_interface_name(idl_filename): 22 # interface name is the root of the basename: InterfaceName.idl 23 return os.path.splitext(os.path.basename(idl_filename))[0] 24 25 26 ################################################################################ 27 # Basic file reading/writing 28 ################################################################################ 29 30 def get_file_contents(filename): 31 with open(filename) as f: 32 return f.read() 33 34 35 def read_file_to_list(filename): 36 """Returns a list of (stripped) lines for a given filename.""" 37 with open(filename) as f: 38 return [line.rstrip('\n') for line in f] 39 40 41 def read_pickle_files(pickle_filenames): 42 for pickle_filename in pickle_filenames: 43 with open(pickle_filename) as pickle_file: 44 yield pickle.load(pickle_file) 45 46 47 def write_file(new_text, destination_filename, only_if_changed): 48 if only_if_changed and os.path.isfile(destination_filename): 49 with open(destination_filename) as destination_file: 50 if destination_file.read() == new_text: 51 return 52 with open(destination_filename, 'w') as destination_file: 53 destination_file.write(new_text) 54 55 56 def write_pickle_file(pickle_filename, data, only_if_changed): 57 if only_if_changed and os.path.isfile(pickle_filename): 58 with open(pickle_filename) as pickle_file: 59 try: 60 if pickle.load(pickle_file) == data: 61 return 62 except (EOFError, pickle.UnpicklingError): 63 # If trouble unpickling, overwrite 64 pass 65 with open(pickle_filename, 'w') as pickle_file: 66 pickle.dump(data, pickle_file) 67 68 69 ################################################################################ 70 # IDL parsing 71 # 72 # We use regular expressions for parsing; this is incorrect (Web IDL is not a 73 # regular language), but simple and sufficient in practice. 74 # Leading and trailing context (e.g. following '{') used to avoid false matches. 75 ################################################################################ 76 77 def get_partial_interface_name_from_idl(file_contents): 78 match = re.search(r'partial\s+interface\s+(\w+)\s*{', file_contents) 79 return match and match.group(1) 80 81 82 def get_implements_from_idl(file_contents, interface_name): 83 """Returns lists of implementing and implemented interfaces. 84 85 Rule is: identifier-A implements identifier-B; 86 i.e., implement*ing* implements implement*ed*; 87 http://www.w3.org/TR/WebIDL/#idl-implements-statements 88 89 Returns two lists of interfaces: identifier-As and identifier-Bs. 90 An 'implements' statements can be present in the IDL file for either the 91 implementing or the implemented interface, but not other files. 92 """ 93 implements_re = (r'^\s*' 94 r'(\w+)\s+' 95 r'implements\s+' 96 r'(\w+)\s*' 97 r';') 98 implements_matches = re.finditer(implements_re, file_contents, re.MULTILINE) 99 implements_pairs = [match.groups() for match in implements_matches] 100 101 foreign_implements = [pair for pair in implements_pairs 102 if interface_name not in pair] 103 if foreign_implements: 104 left, right = foreign_implements.pop() 105 raise IdlBadFilenameError( 106 'implements statement found in unrelated IDL file.\n' 107 'Statement is:\n' 108 ' %s implements %s;\n' 109 'but filename is unrelated "%s.idl"' % 110 (left, right, interface_name)) 111 112 return ( 113 [left for left, right in implements_pairs if right == interface_name], 114 [right for left, right in implements_pairs if left == interface_name]) 115 116 117 def is_callback_interface_from_idl(file_contents): 118 match = re.search(r'callback\s+interface\s+\w+\s*{', file_contents) 119 return bool(match) 120 121 122 def get_parent_interface(file_contents): 123 match = re.search(r'interface\s+' 124 r'\w+\s*' 125 r':\s*(\w+)\s*' 126 r'{', 127 file_contents) 128 return match and match.group(1) 129 130 131 def get_interface_extended_attributes_from_idl(file_contents): 132 # Strip comments 133 # re.compile needed b/c Python 2.6 doesn't support flags in re.sub 134 single_line_comment_re = re.compile(r'//.*$', flags=re.MULTILINE) 135 block_comment_re = re.compile(r'/\*.*?\*/', flags=re.MULTILINE | re.DOTALL) 136 file_contents = re.sub(single_line_comment_re, '', file_contents) 137 file_contents = re.sub(block_comment_re, '', file_contents) 138 139 match = re.search(r'\[(.*)\]\s*' 140 r'((callback|partial)\s+)?' 141 r'(interface|exception)\s+' 142 r'\w+\s*' 143 r'(:\s*\w+\s*)?' 144 r'{', 145 file_contents, flags=re.DOTALL) 146 if not match: 147 return {} 148 149 extended_attributes_string = match.group(1) 150 extended_attributes = {} 151 # FIXME: this splitting is WRONG: it fails on ExtendedAttributeArgList like 152 # 'NamedConstructor=Foo(a, b)' 153 parts = [extended_attribute.strip() 154 for extended_attribute in extended_attributes_string.split(',') 155 # Discard empty parts, which may exist due to trailing comma 156 if extended_attribute.strip()] 157 for part in parts: 158 name, _, value = map(string.strip, part.partition('=')) 159 extended_attributes[name] = value 160 return extended_attributes 161 162 163 def get_put_forward_interfaces_from_idl(file_contents): 164 put_forwards_pattern = (r'\[[^\]]*PutForwards=[^\]]*\]\s+' 165 r'readonly\s+' 166 r'attribute\s+' 167 r'(\w+)') 168 return sorted(set(match.group(1) 169 for match in re.finditer(put_forwards_pattern, 170 file_contents, 171 flags=re.DOTALL))) 172