regen_atoms.py (6539B)
1 #!/usr/bin/env python 2 3 # This Source Code Form is subject to the terms of the Mozilla Public 4 # License, v. 2.0. If a copy of the MPL was not distributed with this 5 # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 7 import re 8 import os 9 import sys 10 11 from io import BytesIO 12 13 GECKO_DIR = os.path.dirname(__file__.replace("\\", "/")) 14 sys.path.insert(0, os.path.join(os.path.dirname(GECKO_DIR), "properties")) 15 16 import build 17 18 19 # Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`. 20 PATTERN = re.compile( 21 r'^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)', 22 re.MULTILINE, 23 ) 24 FILE = "include/nsGkAtomList.h" 25 26 27 def map_atom(ident): 28 if ident in { 29 "box", 30 "loop", 31 "match", 32 "mod", 33 "ref", 34 "self", 35 "type", 36 "use", 37 "where", 38 "in", 39 }: 40 return ident + "_" 41 return ident 42 43 44 class Atom: 45 def __init__(self, ident, value, hash, ty, atom_type): 46 self.ident = "nsGkAtoms_{}".format(ident) 47 self.original_ident = ident 48 self.value = value 49 self.hash = hash 50 # The Gecko type: "nsStaticAtom", "nsCSSPseudoElementStaticAtom", or 51 # "nsAnonBoxPseudoStaticAtom". 52 self.ty = ty 53 # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox", 54 # or "InheritingAnonBox". 55 self.atom_type = atom_type 56 57 if ( 58 self.is_pseudo_element() 59 or self.is_anon_box() 60 or self.is_tree_pseudo_element() 61 ): 62 self.pseudo_ident = (ident.split("_", 1))[1] 63 64 if self.is_anon_box(): 65 assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() 66 67 def type(self): 68 return self.ty 69 70 def capitalized_pseudo(self): 71 return self.pseudo_ident[0].upper() + self.pseudo_ident[1:] 72 73 def is_pseudo_element(self): 74 return self.atom_type == "PseudoElementAtom" 75 76 def is_anon_box(self): 77 if self.is_tree_pseudo_element(): 78 return False 79 return self.is_non_inheriting_anon_box() or self.is_inheriting_anon_box() 80 81 def is_non_inheriting_anon_box(self): 82 assert not self.is_tree_pseudo_element() 83 return self.atom_type == "NonInheritingAnonBoxAtom" 84 85 def is_inheriting_anon_box(self): 86 if self.is_tree_pseudo_element(): 87 return False 88 return self.atom_type == "InheritingAnonBoxAtom" 89 90 def is_tree_pseudo_element(self): 91 return self.value.startswith(":-moz-tree-") 92 93 def is_named_view_transition_pseudo(self) -> bool: 94 return ( 95 self.pseudo_ident == "viewTransitionGroup" 96 or self.pseudo_ident == "viewTransitionImagePair" 97 or self.pseudo_ident == "viewTransitionOld" 98 or self.pseudo_ident == "viewTransitionNew" 99 ) 100 101 def is_simple_pseudo_element(self) -> bool: 102 return not ( 103 self.is_tree_pseudo_element() 104 or self.pseudo_ident == "highlight" 105 or self.is_named_view_transition_pseudo() 106 ) 107 108 109 def collect_atoms(objdir): 110 atoms = [] 111 path = os.path.abspath(os.path.join(objdir, FILE)) 112 print("cargo:rerun-if-changed={}".format(path)) 113 with open(path) as f: 114 content = f.read() 115 for result in PATTERN.finditer(content): 116 atoms.append( 117 Atom( 118 result.group(1), 119 result.group(2), 120 result.group(3), 121 result.group(4), 122 result.group(5), 123 ) 124 ) 125 return atoms 126 127 128 class FileAvoidWrite(BytesIO): 129 """File-like object that buffers output and only writes if content changed.""" 130 131 def __init__(self, filename): 132 BytesIO.__init__(self) 133 self.name = filename 134 135 def write(self, buf): 136 if isinstance(buf, str): 137 buf = buf.encode("utf-8") 138 BytesIO.write(self, buf) 139 140 def close(self): 141 buf = self.getvalue() 142 BytesIO.close(self) 143 try: 144 with open(self.name, "rb") as f: 145 old_content = f.read() 146 if old_content == buf: 147 print("{} is not changed, skip".format(self.name)) 148 return 149 except IOError: 150 pass 151 with open(self.name, "wb") as f: 152 f.write(buf) 153 154 def __enter__(self): 155 return self 156 157 def __exit__(self, type, value, traceback): 158 if not self.closed: 159 self.close() 160 161 162 PRELUDE = """ 163 /* This Source Code Form is subject to the terms of the Mozilla Public 164 * License, v. 2.0. If a copy of the MPL was not distributed with this 165 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 166 167 // Autogenerated file created by components/style/gecko/regen_atoms.py. 168 // DO NOT EDIT DIRECTLY 169 """[ 170 1: 171 ] 172 173 RULE_TEMPLATE = """ 174 ("{atom}") => {{{{ 175 #[allow(unsafe_code)] #[allow(unused_unsafe)] 176 unsafe {{ $crate::string_cache::Atom::from_index_unchecked({index}) }} 177 }}}}; 178 """[ 179 1: 180 ] 181 182 MACRO_TEMPLATE = """ 183 /// Returns a static atom by passing the literal string it represents. 184 #[macro_export] 185 macro_rules! atom {{ 186 {body}\ 187 }} 188 """ 189 190 191 def write_atom_macro(atoms, file_name): 192 with FileAvoidWrite(file_name) as f: 193 f.write(PRELUDE) 194 macro_rules = [ 195 RULE_TEMPLATE.format(atom=atom.value, name=atom.ident, index=i) 196 for (i, atom) in enumerate(atoms) 197 ] 198 f.write(MACRO_TEMPLATE.format(body="".join(macro_rules))) 199 200 201 def write_pseudo_elements(atoms, target_filename): 202 pseudos = [] 203 for atom in atoms: 204 if ( 205 atom.type() == "nsCSSPseudoElementStaticAtom" 206 or atom.type() == "nsCSSAnonBoxPseudoStaticAtom" 207 ): 208 pseudos.append(atom) 209 210 pseudo_definition_template = os.path.join( 211 GECKO_DIR, "pseudo_element_definition.mako.rs" 212 ) 213 print("cargo:rerun-if-changed={}".format(pseudo_definition_template)) 214 contents = build.render(pseudo_definition_template, PSEUDOS=pseudos) 215 216 with FileAvoidWrite(target_filename) as f: 217 f.write(contents) 218 219 220 def generate_atoms(dist, out): 221 atoms = collect_atoms(dist) 222 write_atom_macro(atoms, os.path.join(out, "atom_macro.rs")) 223 write_pseudo_elements(atoms, os.path.join(out, "pseudo_element_definition.rs")) 224 225 226 if __name__ == "__main__": 227 if len(sys.argv) != 3: 228 print("Usage: {} dist out".format(sys.argv[0])) 229 exit(2) 230 generate_atoms(sys.argv[1], sys.argv[2])