1425bb815Sopenharmony_ci#!/usr/bin/env python
2425bb815Sopenharmony_ci
3425bb815Sopenharmony_ci# Copyright JS Foundation and other contributors, http://js.foundation
4425bb815Sopenharmony_ci#
5425bb815Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
6425bb815Sopenharmony_ci# you may not use this file except in compliance with the License.
7425bb815Sopenharmony_ci# You may obtain a copy of the License at
8425bb815Sopenharmony_ci#
9425bb815Sopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
10425bb815Sopenharmony_ci#
11425bb815Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
12425bb815Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS
13425bb815Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14425bb815Sopenharmony_ci# See the License for the specific language governing permissions and
15425bb815Sopenharmony_ci# limitations under the License.
16425bb815Sopenharmony_ci
17425bb815Sopenharmony_cifrom __future__ import print_function
18425bb815Sopenharmony_ci
19425bb815Sopenharmony_citry:
20425bb815Sopenharmony_ci    from configparser import ConfigParser
21425bb815Sopenharmony_ciexcept ImportError:
22425bb815Sopenharmony_ci    from ConfigParser import ConfigParser
23425bb815Sopenharmony_ci
24425bb815Sopenharmony_ciimport argparse
25425bb815Sopenharmony_ciimport fileinput
26425bb815Sopenharmony_ciimport json
27425bb815Sopenharmony_ciimport os
28425bb815Sopenharmony_ciimport re
29425bb815Sopenharmony_ci
30425bb815Sopenharmony_cifrom settings import PROJECT_DIR
31425bb815Sopenharmony_ci
32425bb815Sopenharmony_ci
33425bb815Sopenharmony_ciMAGIC_STRINGS_INI = os.path.join(PROJECT_DIR, 'jerry-core', 'lit', 'lit-magic-strings.ini')
34425bb815Sopenharmony_ciMAGIC_STRINGS_INC_H = os.path.join(PROJECT_DIR, 'jerry-core', 'lit', 'lit-magic-strings.inc.h')
35425bb815Sopenharmony_ci
36425bb815Sopenharmony_ci
37425bb815Sopenharmony_cidef debug_dump(obj):
38425bb815Sopenharmony_ci    def deepcopy(obj):
39425bb815Sopenharmony_ci        if isinstance(obj, (list, tuple)):
40425bb815Sopenharmony_ci            return [deepcopy(e) for e in obj]
41425bb815Sopenharmony_ci        if isinstance(obj, set):
42425bb815Sopenharmony_ci            return [repr(e) for e in obj]
43425bb815Sopenharmony_ci        if isinstance(obj, dict):
44425bb815Sopenharmony_ci            return {repr(k): deepcopy(e) for k, e in obj.items()}
45425bb815Sopenharmony_ci        return obj
46425bb815Sopenharmony_ci    return json.dumps(deepcopy(obj), indent=4)
47425bb815Sopenharmony_ci
48425bb815Sopenharmony_ci
49425bb815Sopenharmony_cidef read_magic_string_defs(debug=False):
50425bb815Sopenharmony_ci    # Read the `jerry-core/lit/lit-magic-strings.ini` file and returns the magic
51425bb815Sopenharmony_ci    # string definitions found therein in the form of
52425bb815Sopenharmony_ci    #   [LIT_MAGIC_STRINGS]
53425bb815Sopenharmony_ci    #   LIT_MAGIC_STRING_xxx = "vvv"
54425bb815Sopenharmony_ci    #   ...
55425bb815Sopenharmony_ci    # as
56425bb815Sopenharmony_ci    #   [('LIT_MAGIC_STRING_xxx', 'vvv'), ...]
57425bb815Sopenharmony_ci    # sorted by length and alpha.
58425bb815Sopenharmony_ci    ini_parser = ConfigParser()
59425bb815Sopenharmony_ci    ini_parser.optionxform = str # case sensitive options (magic string IDs)
60425bb815Sopenharmony_ci    ini_parser.read(MAGIC_STRINGS_INI)
61425bb815Sopenharmony_ci
62425bb815Sopenharmony_ci    defs = [(str_ref, json.loads(str_value) if str_value != '' else '')
63425bb815Sopenharmony_ci            for str_ref, str_value in ini_parser.items('LIT_MAGIC_STRINGS')]
64425bb815Sopenharmony_ci    defs = sorted(defs, key=lambda ref_value: (len(ref_value[1]), ref_value[1]))
65425bb815Sopenharmony_ci
66425bb815Sopenharmony_ci    if debug:
67425bb815Sopenharmony_ci        print('debug: magic string definitions: {dump}'
68425bb815Sopenharmony_ci              .format(dump=debug_dump(defs)))
69425bb815Sopenharmony_ci
70425bb815Sopenharmony_ci    return defs
71425bb815Sopenharmony_ci
72425bb815Sopenharmony_ci
73425bb815Sopenharmony_cidef extract_magic_string_refs(debug=False):
74425bb815Sopenharmony_ci    results = {}
75425bb815Sopenharmony_ci
76425bb815Sopenharmony_ci    def process_line(fname, lnum, line, guard_stack):
77425bb815Sopenharmony_ci        # Build `results` dictionary as
78425bb815Sopenharmony_ci        #   results['LIT_MAGIC_STRING_xxx'][('!defined (CONFIG_DISABLE_yyy_BUILTIN)', ...)]
79425bb815Sopenharmony_ci        #       = [('zzz.c', 123), ...]
80425bb815Sopenharmony_ci        # meaning that the given literal is referenced under the given guards at
81425bb815Sopenharmony_ci        # the listed (file, line number) locations.
82425bb815Sopenharmony_ci        for str_ref in re.findall('LIT_MAGIC_STRING_[a-zA-Z0-9_]+', line):
83425bb815Sopenharmony_ci            if str_ref in ['LIT_MAGIC_STRING_DEF',
84425bb815Sopenharmony_ci                           'LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE',
85425bb815Sopenharmony_ci                           'LIT_MAGIC_STRING_LENGTH_LIMIT',
86425bb815Sopenharmony_ci                           'LIT_MAGIC_STRING__COUNT']:
87425bb815Sopenharmony_ci                continue
88425bb815Sopenharmony_ci
89425bb815Sopenharmony_ci            guard_set = set()
90425bb815Sopenharmony_ci            for guards in guard_stack:
91425bb815Sopenharmony_ci                guard_set.update(guards)
92425bb815Sopenharmony_ci            guard_tuple = tuple(sorted(guard_set))
93425bb815Sopenharmony_ci
94425bb815Sopenharmony_ci            if str_ref not in results:
95425bb815Sopenharmony_ci                results[str_ref] = {}
96425bb815Sopenharmony_ci            str_guards = results[str_ref]
97425bb815Sopenharmony_ci
98425bb815Sopenharmony_ci            if guard_tuple not in str_guards:
99425bb815Sopenharmony_ci                str_guards[guard_tuple] = []
100425bb815Sopenharmony_ci            file_list = str_guards[guard_tuple]
101425bb815Sopenharmony_ci
102425bb815Sopenharmony_ci            file_list.append((fname, lnum))
103425bb815Sopenharmony_ci
104425bb815Sopenharmony_ci    def process_guard(guard):
105425bb815Sopenharmony_ci        # Transform `#ifndef MACRO` to `#if !defined (MACRO)` and
106425bb815Sopenharmony_ci        # `#ifdef MACRO` to `#if defined (MACRO)` to enable or-ing/and-ing the
107425bb815Sopenharmony_ci        # conditions later on.
108425bb815Sopenharmony_ci        if guard.startswith('ndef '):
109425bb815Sopenharmony_ci            guard = guard.replace('ndef ', '!defined (', 1) + ')'
110425bb815Sopenharmony_ci        elif guard.startswith('def '):
111425bb815Sopenharmony_ci            guard = guard.replace('def ', 'defined (', 1) + ')'
112425bb815Sopenharmony_ci        return guard
113425bb815Sopenharmony_ci
114425bb815Sopenharmony_ci    def process_file(fname):
115425bb815Sopenharmony_ci        # Builds `guard_stack` list for each line of a file as
116425bb815Sopenharmony_ci        #   [['!defined (CONFIG_DISABLE_yyy_BUILTIN)', ...], ...]
117425bb815Sopenharmony_ci        # meaning that all the listed guards (conditionals) have to hold for the
118425bb815Sopenharmony_ci        # line to be kept by the preprocessor.
119425bb815Sopenharmony_ci        guard_stack = []
120425bb815Sopenharmony_ci
121425bb815Sopenharmony_ci        for line in fileinput.input(fname):
122425bb815Sopenharmony_ci            if_match = re.match('^ *# *if(.*)', line)
123425bb815Sopenharmony_ci            elif_match = re.match('^ *# *elif(.*)', line)
124425bb815Sopenharmony_ci            else_match = re.match('^ *# *else', line)
125425bb815Sopenharmony_ci            endif_match = re.match('^ *# *endif', line)
126425bb815Sopenharmony_ci            if if_match is not None:
127425bb815Sopenharmony_ci                guard_stack.append([process_guard(if_match.group(1))])
128425bb815Sopenharmony_ci            elif elif_match is not None:
129425bb815Sopenharmony_ci                guards = guard_stack[-1]
130425bb815Sopenharmony_ci                guards[-1] = '!(%s)' % guards[-1]
131425bb815Sopenharmony_ci                guards.append(process_guard(elif_match.group(1)))
132425bb815Sopenharmony_ci            elif else_match is not None:
133425bb815Sopenharmony_ci                guards = guard_stack[-1]
134425bb815Sopenharmony_ci                guards[-1] = '!(%s)' % guards[-1]
135425bb815Sopenharmony_ci            elif endif_match is not None:
136425bb815Sopenharmony_ci                guard_stack.pop()
137425bb815Sopenharmony_ci
138425bb815Sopenharmony_ci            lnum = fileinput.filelineno()
139425bb815Sopenharmony_ci            process_line(fname, lnum, line, guard_stack)
140425bb815Sopenharmony_ci
141425bb815Sopenharmony_ci        if guard_stack:
142425bb815Sopenharmony_ci            print('warning: {fname}: unbalanced preprocessor conditional '
143425bb815Sopenharmony_ci                  'directives (analysis finished with no closing `#endif` '
144425bb815Sopenharmony_ci                  'for {guard_stack})'
145425bb815Sopenharmony_ci                  .format(fname=fname, guard_stack=guard_stack))
146425bb815Sopenharmony_ci
147425bb815Sopenharmony_ci    for root, _, files in os.walk(os.path.join(PROJECT_DIR, 'jerry-core')):
148425bb815Sopenharmony_ci        for fname in files:
149425bb815Sopenharmony_ci            if (fname.endswith('.c') or fname.endswith('.h')) \
150425bb815Sopenharmony_ci               and fname != 'lit-magic-strings.inc.h':
151425bb815Sopenharmony_ci                process_file(os.path.join(root, fname))
152425bb815Sopenharmony_ci
153425bb815Sopenharmony_ci    if debug:
154425bb815Sopenharmony_ci        print('debug: magic string references: {dump}'
155425bb815Sopenharmony_ci              .format(dump=debug_dump(results)))
156425bb815Sopenharmony_ci
157425bb815Sopenharmony_ci    return results
158425bb815Sopenharmony_ci
159425bb815Sopenharmony_ci
160425bb815Sopenharmony_cidef calculate_magic_string_guards(defs, uses, debug=False):
161425bb815Sopenharmony_ci    extended_defs = []
162425bb815Sopenharmony_ci
163425bb815Sopenharmony_ci    for str_ref, str_value in defs:
164425bb815Sopenharmony_ci        if str_ref not in uses:
165425bb815Sopenharmony_ci            print('warning: unused magic string {str_ref}'
166425bb815Sopenharmony_ci                  .format(str_ref=str_ref))
167425bb815Sopenharmony_ci            continue
168425bb815Sopenharmony_ci
169425bb815Sopenharmony_ci        # Calculate the most compact guard, i.e., if a magic string is
170425bb815Sopenharmony_ci        # referenced under various guards, keep the one that is more generic.
171425bb815Sopenharmony_ci        # E.g.,
172425bb815Sopenharmony_ci        # guard1 = A and B and C and D and E and F
173425bb815Sopenharmony_ci        # guard2 = A and B and C
174425bb815Sopenharmony_ci        # then guard1 or guard2 == guard2.
175425bb815Sopenharmony_ci        guards = [set(guard_tuple) for guard_tuple in uses[str_ref].keys()]
176425bb815Sopenharmony_ci        for i, guard_i in enumerate(guards):
177425bb815Sopenharmony_ci            if guard_i is None:
178425bb815Sopenharmony_ci                continue
179425bb815Sopenharmony_ci            for j, guard_j in enumerate(guards):
180425bb815Sopenharmony_ci                if j == i or guard_j is None:
181425bb815Sopenharmony_ci                    continue
182425bb815Sopenharmony_ci                if guard_i < guard_j:
183425bb815Sopenharmony_ci                    guards[j] = None
184425bb815Sopenharmony_ci        guards = {tuple(sorted(guard)) for guard in guards if guard is not None}
185425bb815Sopenharmony_ci
186425bb815Sopenharmony_ci        extended_defs.append((str_ref, str_value, guards))
187425bb815Sopenharmony_ci
188425bb815Sopenharmony_ci    if debug:
189425bb815Sopenharmony_ci        print('debug: magic string definitions (with guards): {dump}'
190425bb815Sopenharmony_ci              .format(dump=debug_dump(extended_defs)))
191425bb815Sopenharmony_ci
192425bb815Sopenharmony_ci    return extended_defs
193425bb815Sopenharmony_ci
194425bb815Sopenharmony_ci
195425bb815Sopenharmony_cidef guards_to_str(guards):
196425bb815Sopenharmony_ci    return ' \\\n|| '.join(' && '.join(g.strip() for g in sorted(guard))
197425bb815Sopenharmony_ci                           for guard in sorted(guards))
198425bb815Sopenharmony_ci
199425bb815Sopenharmony_ci
200425bb815Sopenharmony_cidef generate_header(gen_file):
201425bb815Sopenharmony_ci    header = \
202425bb815Sopenharmony_ci"""/* Copyright JS Foundation and other contributors, http://js.foundation
203425bb815Sopenharmony_ci *
204425bb815Sopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License");
205425bb815Sopenharmony_ci * you may not use this file except in compliance with the License.
206425bb815Sopenharmony_ci * You may obtain a copy of the License at
207425bb815Sopenharmony_ci *
208425bb815Sopenharmony_ci *     http://www.apache.org/licenses/LICENSE-2.0
209425bb815Sopenharmony_ci *
210425bb815Sopenharmony_ci * Unless required by applicable law or agreed to in writing, software
211425bb815Sopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS
212425bb815Sopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
213425bb815Sopenharmony_ci * See the License for the specific language governing permissions and
214425bb815Sopenharmony_ci * limitations under the License.
215425bb815Sopenharmony_ci */
216425bb815Sopenharmony_ci
217425bb815Sopenharmony_ci/* This file is automatically generated by the %s script
218425bb815Sopenharmony_ci * from %s. Do not edit! */
219425bb815Sopenharmony_ci""" % (os.path.basename(__file__), os.path.basename(MAGIC_STRINGS_INI))
220425bb815Sopenharmony_ci    print(header, file=gen_file)
221425bb815Sopenharmony_ci
222425bb815Sopenharmony_ci
223425bb815Sopenharmony_cidef generate_magic_string_defs(gen_file, defs):
224425bb815Sopenharmony_ci    last_guards = set([()])
225425bb815Sopenharmony_ci    for str_ref, str_value, guards in defs:
226425bb815Sopenharmony_ci        if last_guards != guards:
227425bb815Sopenharmony_ci            if () not in last_guards:
228425bb815Sopenharmony_ci                print('#endif', file=gen_file)
229425bb815Sopenharmony_ci            if () not in guards:
230425bb815Sopenharmony_ci                print('#if {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
231425bb815Sopenharmony_ci
232425bb815Sopenharmony_ci        print('LIT_MAGIC_STRING_DEF ({str_ref}, {str_value})'
233425bb815Sopenharmony_ci              .format(str_ref=str_ref, str_value=json.dumps(str_value)), file=gen_file)
234425bb815Sopenharmony_ci
235425bb815Sopenharmony_ci        last_guards = guards
236425bb815Sopenharmony_ci
237425bb815Sopenharmony_ci    if () not in last_guards:
238425bb815Sopenharmony_ci        print('#endif', file=gen_file)
239425bb815Sopenharmony_ci
240425bb815Sopenharmony_ci
241425bb815Sopenharmony_cidef generate_first_magic_strings(gen_file, defs):
242425bb815Sopenharmony_ci    print(file=gen_file) # empty line separator
243425bb815Sopenharmony_ci
244425bb815Sopenharmony_ci    max_size = len(defs[-1][1])
245425bb815Sopenharmony_ci    for size in range(max_size + 1):
246425bb815Sopenharmony_ci        last_guards = set([()])
247425bb815Sopenharmony_ci        for str_ref, str_value, guards in defs:
248425bb815Sopenharmony_ci            if len(str_value) >= size:
249425bb815Sopenharmony_ci                if () not in guards and () in last_guards:
250425bb815Sopenharmony_ci                    print('#if {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
251425bb815Sopenharmony_ci                elif () not in guards and () not in last_guards:
252425bb815Sopenharmony_ci                    if guards == last_guards:
253425bb815Sopenharmony_ci                        continue
254425bb815Sopenharmony_ci                    print('#elif {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
255425bb815Sopenharmony_ci                elif () in guards and () not in last_guards:
256425bb815Sopenharmony_ci                    print('#else', file=gen_file)
257425bb815Sopenharmony_ci
258425bb815Sopenharmony_ci                print('LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE ({size}, {str_ref})'
259425bb815Sopenharmony_ci                      .format(size=size, str_ref=str_ref), file=gen_file)
260425bb815Sopenharmony_ci
261425bb815Sopenharmony_ci                if () in guards:
262425bb815Sopenharmony_ci                    break
263425bb815Sopenharmony_ci
264425bb815Sopenharmony_ci                last_guards = guards
265425bb815Sopenharmony_ci
266425bb815Sopenharmony_ci        if () not in last_guards:
267425bb815Sopenharmony_ci            print('#endif', file=gen_file)
268425bb815Sopenharmony_ci
269425bb815Sopenharmony_ci
270425bb815Sopenharmony_cidef main():
271425bb815Sopenharmony_ci    parser = argparse.ArgumentParser(description='lit-magic-strings.inc.h generator')
272425bb815Sopenharmony_ci    parser.add_argument('--debug', action='store_true', help='enable debug output')
273425bb815Sopenharmony_ci    args = parser.parse_args()
274425bb815Sopenharmony_ci
275425bb815Sopenharmony_ci    defs = read_magic_string_defs(debug=args.debug)
276425bb815Sopenharmony_ci    uses = extract_magic_string_refs(debug=args.debug)
277425bb815Sopenharmony_ci
278425bb815Sopenharmony_ci    extended_defs = calculate_magic_string_guards(defs, uses, debug=args.debug)
279425bb815Sopenharmony_ci
280425bb815Sopenharmony_ci    with open(MAGIC_STRINGS_INC_H, 'w') as gen_file:
281425bb815Sopenharmony_ci        generate_header(gen_file)
282425bb815Sopenharmony_ci        generate_magic_string_defs(gen_file, extended_defs)
283425bb815Sopenharmony_ci        generate_first_magic_strings(gen_file, extended_defs)
284425bb815Sopenharmony_ci
285425bb815Sopenharmony_ci
286425bb815Sopenharmony_ciif __name__ == '__main__':
287425bb815Sopenharmony_ci    main()
288