forked from xuos/xiuos
288 lines
10 KiB
Python
288 lines
10 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright JS Foundation and other contributors, http://js.foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import print_function
|
|
|
|
try:
|
|
from configparser import ConfigParser
|
|
except ImportError:
|
|
from ConfigParser import ConfigParser
|
|
|
|
import argparse
|
|
import fileinput
|
|
import json
|
|
import os
|
|
import re
|
|
|
|
from settings import PROJECT_DIR
|
|
|
|
|
|
MAGIC_STRINGS_INI = os.path.join(PROJECT_DIR, 'jerry-core', 'lit', 'lit-magic-strings.ini')
|
|
MAGIC_STRINGS_INC_H = os.path.join(PROJECT_DIR, 'jerry-core', 'lit', 'lit-magic-strings.inc.h')
|
|
|
|
|
|
def debug_dump(obj):
|
|
def deepcopy(obj):
|
|
if isinstance(obj, (list, tuple)):
|
|
return [deepcopy(e) for e in obj]
|
|
if isinstance(obj, set):
|
|
return [repr(e) for e in obj]
|
|
if isinstance(obj, dict):
|
|
return {repr(k): deepcopy(e) for k, e in obj.items()}
|
|
return obj
|
|
return json.dumps(deepcopy(obj), indent=4)
|
|
|
|
|
|
def read_magic_string_defs(debug=False):
|
|
# Read the `jerry-core/lit/lit-magic-strings.ini` file and returns the magic
|
|
# string definitions found therein in the form of
|
|
# [LIT_MAGIC_STRINGS]
|
|
# LIT_MAGIC_STRING_xxx = "vvv"
|
|
# ...
|
|
# as
|
|
# [('LIT_MAGIC_STRING_xxx', 'vvv'), ...]
|
|
# sorted by length and alpha.
|
|
ini_parser = ConfigParser()
|
|
ini_parser.optionxform = str # case sensitive options (magic string IDs)
|
|
ini_parser.read(MAGIC_STRINGS_INI)
|
|
|
|
defs = [(str_ref, json.loads(str_value) if str_value != '' else '')
|
|
for str_ref, str_value in ini_parser.items('LIT_MAGIC_STRINGS')]
|
|
defs = sorted(defs, key=lambda ref_value: (len(ref_value[1]), ref_value[1]))
|
|
|
|
if debug:
|
|
print('debug: magic string definitions: {dump}'
|
|
.format(dump=debug_dump(defs)))
|
|
|
|
return defs
|
|
|
|
|
|
def extract_magic_string_refs(debug=False):
|
|
results = {}
|
|
|
|
def process_line(fname, lnum, line, guard_stack):
|
|
# Build `results` dictionary as
|
|
# results['LIT_MAGIC_STRING_xxx'][('!defined (CONFIG_DISABLE_yyy_BUILTIN)', ...)]
|
|
# = [('zzz.c', 123), ...]
|
|
# meaning that the given literal is referenced under the given guards at
|
|
# the listed (file, line number) locations.
|
|
for str_ref in re.findall('LIT_MAGIC_STRING_[a-zA-Z0-9_]+', line):
|
|
if str_ref in ['LIT_MAGIC_STRING_DEF',
|
|
'LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE',
|
|
'LIT_MAGIC_STRING_LENGTH_LIMIT',
|
|
'LIT_MAGIC_STRING__COUNT']:
|
|
continue
|
|
|
|
guard_set = set()
|
|
for guards in guard_stack:
|
|
guard_set.update(guards)
|
|
guard_tuple = tuple(sorted(guard_set))
|
|
|
|
if str_ref not in results:
|
|
results[str_ref] = {}
|
|
str_guards = results[str_ref]
|
|
|
|
if guard_tuple not in str_guards:
|
|
str_guards[guard_tuple] = []
|
|
file_list = str_guards[guard_tuple]
|
|
|
|
file_list.append((fname, lnum))
|
|
|
|
def process_guard(guard):
|
|
# Transform `#ifndef MACRO` to `#if !defined (MACRO)` and
|
|
# `#ifdef MACRO` to `#if defined (MACRO)` to enable or-ing/and-ing the
|
|
# conditions later on.
|
|
if guard.startswith('ndef '):
|
|
guard = guard.replace('ndef ', '!defined (', 1) + ')'
|
|
elif guard.startswith('def '):
|
|
guard = guard.replace('def ', 'defined (', 1) + ')'
|
|
return guard
|
|
|
|
def process_file(fname):
|
|
# Builds `guard_stack` list for each line of a file as
|
|
# [['!defined (CONFIG_DISABLE_yyy_BUILTIN)', ...], ...]
|
|
# meaning that all the listed guards (conditionals) have to hold for the
|
|
# line to be kept by the preprocessor.
|
|
guard_stack = []
|
|
|
|
for line in fileinput.input(fname):
|
|
if_match = re.match('^ *# *if(.*)', line)
|
|
elif_match = re.match('^ *# *elif(.*)', line)
|
|
else_match = re.match('^ *# *else', line)
|
|
endif_match = re.match('^ *# *endif', line)
|
|
if if_match is not None:
|
|
guard_stack.append([process_guard(if_match.group(1))])
|
|
elif elif_match is not None:
|
|
guards = guard_stack[-1]
|
|
guards[-1] = '!(%s)' % guards[-1].strip()
|
|
guards.append(process_guard(elif_match.group(1)))
|
|
elif else_match is not None:
|
|
guards = guard_stack[-1]
|
|
guards[-1] = '!(%s)' % guards[-1].strip()
|
|
elif endif_match is not None:
|
|
guard_stack.pop()
|
|
|
|
lnum = fileinput.filelineno()
|
|
process_line(fname, lnum, line, guard_stack)
|
|
|
|
if guard_stack:
|
|
print('warning: {fname}: unbalanced preprocessor conditional '
|
|
'directives (analysis finished with no closing `#endif` '
|
|
'for {guard_stack})'
|
|
.format(fname=fname, guard_stack=guard_stack))
|
|
|
|
for root, _, files in os.walk(os.path.join(PROJECT_DIR, 'jerry-core')):
|
|
for fname in files:
|
|
if (fname.endswith('.c') or fname.endswith('.h')) \
|
|
and fname != 'lit-magic-strings.inc.h':
|
|
process_file(os.path.join(root, fname))
|
|
|
|
if debug:
|
|
print('debug: magic string references: {dump}'
|
|
.format(dump=debug_dump(results)))
|
|
|
|
return results
|
|
|
|
|
|
def calculate_magic_string_guards(defs, uses, debug=False):
|
|
extended_defs = []
|
|
|
|
for str_ref, str_value in defs:
|
|
if str_ref not in uses:
|
|
print('warning: unused magic string {str_ref}'
|
|
.format(str_ref=str_ref))
|
|
continue
|
|
|
|
# Calculate the most compact guard, i.e., if a magic string is
|
|
# referenced under various guards, keep the one that is more generic.
|
|
# E.g.,
|
|
# guard1 = A and B and C and D and E and F
|
|
# guard2 = A and B and C
|
|
# then guard1 or guard2 == guard2.
|
|
guards = [set(guard_tuple) for guard_tuple in uses[str_ref].keys()]
|
|
for i, guard_i in enumerate(guards):
|
|
if guard_i is None:
|
|
continue
|
|
for j, guard_j in enumerate(guards):
|
|
if j == i or guard_j is None:
|
|
continue
|
|
if guard_i < guard_j:
|
|
guards[j] = None
|
|
guards = {tuple(sorted(guard)) for guard in guards if guard is not None}
|
|
|
|
extended_defs.append((str_ref, str_value, guards))
|
|
|
|
if debug:
|
|
print('debug: magic string definitions (with guards): {dump}'
|
|
.format(dump=debug_dump(extended_defs)))
|
|
|
|
return extended_defs
|
|
|
|
|
|
def guards_to_str(guards):
|
|
return ' \\\n|| '.join(' && '.join(g.strip() for g in sorted(guard))
|
|
for guard in sorted(guards))
|
|
|
|
|
|
def generate_header(gen_file):
|
|
header = \
|
|
"""/* Copyright JS Foundation and other contributors, http://js.foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/* This file is automatically generated by the %s script
|
|
* from %s. Do not edit! */
|
|
""" % (os.path.basename(__file__), os.path.basename(MAGIC_STRINGS_INI))
|
|
print(header, file=gen_file)
|
|
|
|
|
|
def generate_magic_string_defs(gen_file, defs):
|
|
last_guards = set([()])
|
|
for str_ref, str_value, guards in defs:
|
|
if last_guards != guards:
|
|
if () not in last_guards:
|
|
print('#endif', file=gen_file)
|
|
if () not in guards:
|
|
print('#if {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
|
|
|
|
print('LIT_MAGIC_STRING_DEF ({str_ref}, {str_value})'
|
|
.format(str_ref=str_ref, str_value=json.dumps(str_value)), file=gen_file)
|
|
|
|
last_guards = guards
|
|
|
|
if () not in last_guards:
|
|
print('#endif', file=gen_file)
|
|
|
|
|
|
def generate_first_magic_strings(gen_file, defs):
|
|
print(file=gen_file) # empty line separator
|
|
|
|
max_size = len(defs[-1][1])
|
|
for size in range(max_size + 1):
|
|
last_guards = set([()])
|
|
for str_ref, str_value, guards in defs:
|
|
if len(str_value) >= size:
|
|
if () not in guards and () in last_guards:
|
|
print('#if {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
|
|
elif () not in guards and () not in last_guards:
|
|
if guards == last_guards:
|
|
continue
|
|
print('#elif {guards}'.format(guards=guards_to_str(guards)), file=gen_file)
|
|
elif () in guards and () not in last_guards:
|
|
print('#else', file=gen_file)
|
|
|
|
print('LIT_MAGIC_STRING_FIRST_STRING_WITH_SIZE ({size}, {str_ref})'
|
|
.format(size=size, str_ref=str_ref), file=gen_file)
|
|
|
|
if () in guards:
|
|
break
|
|
|
|
last_guards = guards
|
|
|
|
if () not in last_guards:
|
|
print('#endif', file=gen_file)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='lit-magic-strings.inc.h generator')
|
|
parser.add_argument('--debug', action='store_true', help='enable debug output')
|
|
args = parser.parse_args()
|
|
|
|
defs = read_magic_string_defs(debug=args.debug)
|
|
uses = extract_magic_string_refs(debug=args.debug)
|
|
|
|
extended_defs = calculate_magic_string_guards(defs, uses, debug=args.debug)
|
|
|
|
with open(MAGIC_STRINGS_INC_H, 'w') as gen_file:
|
|
generate_header(gen_file)
|
|
generate_magic_string_defs(gen_file, extended_defs)
|
|
generate_first_magic_strings(gen_file, extended_defs)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|