>From 2bffcdfec69a8d28e9cb2c535724fbba8e12b820 Mon Sep 17 00:00:00 2001 From: Alexey Dobriyan <adobri...@gmail.com> Date: Tue, 9 Feb 2021 14:47:34 +0300 Subject: [PATCH 11/11] pragma once: conversion script (in Python 2)
Script accepts list of files to be converted from the command line, strips include guard if any and inserts "#pragma once" directive in the beginning. The following patterns are recognised: #ifndef FOO_H #ifndef FOO_H #define FOO_H #ifndef FOO_H 1 #endif #endif // comment #endif /* one line comment */ This is how almost all include guards look like. Scripts doesn't pretend to be a compiler. For example, comments inside preprocessor directive aren't recognised because people don't write code like this: # /* * legal C */ def\ ine FOO /* * no, we don't care */ Trailing multiline comments aren't recognised as well. Script can cut through SPDX comments: /* SPDX-License-Identifier: xxx * <=== pragma once will be inserted here * Copyright ... */ In other words, the script is simple but not too simple. It cowardly exits and doesn't do anything as a safety measure in case of an existing pragma once directive, missing/broken include guard or a bug. Running it second time shouldn't do anything. Signed-off-by: Alexey Dobriyan <adobri...@gmail.com> --- scripts/pragma-once.py | 159 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100755 scripts/pragma-once.py diff --git a/scripts/pragma-once.py b/scripts/pragma-once.py new file mode 100755 index 000000000000..7c8a274aad28 --- /dev/null +++ b/scripts/pragma-once.py @@ -0,0 +1,159 @@ +#!/usr/bin/python2 +# Copyright (c) 2021 Alexey Dobriyan <adobri...@gmail.com> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# Change include guard to "#pragma once" directive in place. +import os +import re +import sys + +def read_file(filename): + with open(filename) as f: + return f.read() + +def write_file(filename, buf): + tmp = '%s.pragma-once' % filename + with open(tmp, 'w') as f: + f.write(buf) + os.rename(tmp, filename) + +def ws(c): + return c == ' ' or c == '\t' or c == '\n' + +re_ifndef = re.compile('#[ \t]*ifndef[ \t]+([A-Za-z_][A-Za-z0-9_]*)\n') +re_define = re.compile('#[ \t]*define[ \t]+([A-Za-z_][A-Za-z0-9_]*)([ \t]+1)?\n') +re_endif1 = re.compile('#[ \t]*endif[ \t]*//') +re_endif2 = re.compile('#[ \t]*endif[ \t]*/\*') + +def pragma_once(c): + i = 0 + + # skip leading whitespace and comments + while i < len(c): + if ws(c[i]): + i += 1 + elif c[i] == '/' and c[i + 1] == '*': + i = c.index('*/', i + 2) + 2 + elif c[i] == '/' and c[i + 1] == '/': + i = c.index('\n', i + 2) + 1 + else: + break; + + # find #ifndef + ifndef_start = i + match = re_ifndef.match(c, i) + guard = match.group(1) + i = match.end() + + # find #define + match = re_define.match(c, i) + if match.group(1) != guard: + raise + i = match.end() + + while ws(c[i]): + i += 1 + + define_end = i + + # trim whitespace before #ifndef + i = ifndef_start + while i > 0 and ws(c[i - 1]): + i -= 1 + if c[i] == '\n': + i += 1 + ifndef_start = i + + #print repr(c[ifndef_start:define_end]) + + # find #endif + i = c.rindex('\n#endif', i) + 1 + endif_start = i + + match = None + if match is None: + match = re_endif1.match(c, i) + if match: + try: + i = c.index('\n', match.end()) + 1 + except ValueError: + i = len(c) + + if match is None: + match = re_endif2.match(c, i) + if match: + try: + i = c.index('*/', match.end()) + 2 + except ValueError: + i = len(c) + + if match is None: + i = endif_start + len('#endif') + + while i < len(c) and ws(c[i]): + i += 1 + if i != len(c): + raise + + endif_end = i + + # trim whitespace before #endif + i = endif_start + while i > 0 and ws(c[i - 1]): + i -= 1 + if c[i] == '\n': + i += 1 + endif_start = i + + #print repr(c[endif_start:endif_end]) + + if define_end > endif_start: + raise + + spdx_end = None + pragma_once = '#pragma once\n' + cut_comment = False + if c.startswith('/* SPDX'): + spdx_end = c.index('\n') + 1 + if not (c[spdx_end - 3] == '*' and c[spdx_end - 2] == '/'): + cut_comment = True + elif c.startswith('// SPDX') or c.startswith('//SPDX'): + spdx_end = c.index('\n') + 1 + + if spdx_end is None: + l = [pragma_once, c[0:ifndef_start]] + elif cut_comment: + l = [c[0:spdx_end - 1], ' */\n', pragma_once, '/*\n', c[spdx_end:ifndef_start]] + else: + l = [c[0:spdx_end], pragma_once, c[spdx_end:ifndef_start]] + + l.append(c[define_end:endif_start]) + l.append(c[endif_end:]) + return ''.join(l) + +def main(): + for filename in sys.argv[1:]: + s = '' + try: + buf = read_file(filename) + if buf.find('#pragma once') == -1: + write_file(filename, pragma_once(buf)) + else: + s = 'skip ' + except: + pass + print '#pragma once: %s%s' % (s, filename) + +if __name__ == '__main__': + main() -- 2.29.2