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