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 7import re 8import os 9import sys 10 11from io import BytesIO 12 13GECKO_DIR = os.path.dirname(__file__.replace("\\", "/")) 14sys.path.insert(0, os.path.join(os.path.dirname(GECKO_DIR), "properties")) 15 16import build 17 18 19# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`. 20PATTERN = re.compile( 21 '^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)', 22 re.MULTILINE, 23) 24FILE = "include/nsGkAtomList.h" 25 26 27def 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 44class 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 94def collect_atoms(objdir): 95 atoms = [] 96 path = os.path.abspath(os.path.join(objdir, FILE)) 97 print("cargo:rerun-if-changed={}".format(path)) 98 with open(path) as f: 99 content = f.read() 100 for result in PATTERN.finditer(content): 101 atoms.append( 102 Atom( 103 result.group(1), 104 result.group(2), 105 result.group(3), 106 result.group(4), 107 result.group(5), 108 ) 109 ) 110 return atoms 111 112 113class FileAvoidWrite(BytesIO): 114 """File-like object that buffers output and only writes if content changed.""" 115 116 def __init__(self, filename): 117 BytesIO.__init__(self) 118 self.name = filename 119 120 def write(self, buf): 121 if isinstance(buf, str): 122 buf = buf.encode("utf-8") 123 BytesIO.write(self, buf) 124 125 def close(self): 126 buf = self.getvalue() 127 BytesIO.close(self) 128 try: 129 with open(self.name, "rb") as f: 130 old_content = f.read() 131 if old_content == buf: 132 print("{} is not changed, skip".format(self.name)) 133 return 134 except IOError: 135 pass 136 with open(self.name, "wb") as f: 137 f.write(buf) 138 139 def __enter__(self): 140 return self 141 142 def __exit__(self, type, value, traceback): 143 if not self.closed: 144 self.close() 145 146 147PRELUDE = """ 148/* This Source Code Form is subject to the terms of the Mozilla Public 149 * License, v. 2.0. If a copy of the MPL was not distributed with this 150 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 151 152// Autogenerated file created by components/style/gecko/regen_atoms.py. 153// DO NOT EDIT DIRECTLY 154"""[ 155 1: 156] 157 158RULE_TEMPLATE = """ 159 ("{atom}") => {{{{ 160 #[allow(unsafe_code)] #[allow(unused_unsafe)] 161 unsafe {{ $crate::string_cache::Atom::from_index_unchecked({index}) }} 162 }}}}; 163"""[ 164 1: 165] 166 167MACRO_TEMPLATE = """ 168/// Returns a static atom by passing the literal string it represents. 169#[macro_export] 170macro_rules! atom {{ 171{body}\ 172}} 173""" 174 175 176def write_atom_macro(atoms, file_name): 177 with FileAvoidWrite(file_name) as f: 178 f.write(PRELUDE) 179 macro_rules = [ 180 RULE_TEMPLATE.format(atom=atom.value, name=atom.ident, index=i) 181 for (i, atom) in enumerate(atoms) 182 ] 183 f.write(MACRO_TEMPLATE.format(body="".join(macro_rules))) 184 185 186def write_pseudo_elements(atoms, target_filename): 187 pseudos = [] 188 for atom in atoms: 189 if ( 190 atom.type() == "nsCSSPseudoElementStaticAtom" 191 or atom.type() == "nsCSSAnonBoxPseudoStaticAtom" 192 ): 193 pseudos.append(atom) 194 195 pseudo_definition_template = os.path.join( 196 GECKO_DIR, "pseudo_element_definition.mako.rs" 197 ) 198 print("cargo:rerun-if-changed={}".format(pseudo_definition_template)) 199 contents = build.render(pseudo_definition_template, PSEUDOS=pseudos) 200 201 with FileAvoidWrite(target_filename) as f: 202 f.write(contents) 203 204 205def generate_atoms(dist, out): 206 atoms = collect_atoms(dist) 207 write_atom_macro(atoms, os.path.join(out, "atom_macro.rs")) 208 write_pseudo_elements(atoms, os.path.join(out, "pseudo_element_definition.rs")) 209 210 211if __name__ == "__main__": 212 if len(sys.argv) != 3: 213 print("Usage: {} dist out".format(sys.argv[0])) 214 exit(2) 215 generate_atoms(sys.argv[1], sys.argv[2]) 216