#! /usr/bin/python3 # Tests for scripts/glibcpp.py # Copyright (C) 2022-2024 Free Software Foundation, Inc. # This file is part of the GNU C Library. # # The GNU C Library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # The GNU C Library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with the GNU C Library; if not, see # <https://www.gnu.org/licenses/>. import inspect import sys import glibcpp # Error counter. errors = 0 class TokenizerErrors: """Used as the error reporter during tokenization.""" def __init__(self): self.errors = [] def error(self, token, message): self.errors.append((token, message)) def check_macro_definitions(source, expected): reporter = TokenizerErrors() tokens = glibcpp.tokenize_c(source, reporter) actual = [] for md in glibcpp.macro_definitions(tokens): if md.function: md_name = '{}({})'.format(md.name, ','.join(md.args_lowered)) else: md_name = md.name actual.append((md_name, md.body_lowered)) if actual != expected or reporter.errors: global errors errors += 1 # Obtain python source line information. frame = inspect.stack(2)[1] print('{}:{}: error: macro definition mismatch, actual definitions:' .format(frame[1], frame[2])) for md in actual: print('note: {} {!r}'.format(md[0], md[1])) if reporter.errors: for err in reporter.errors: print('note: tokenizer error: {}: {}'.format( err[0].line, err[1])) def check_macro_eval(source, expected, expected_errors=''): reporter = TokenizerErrors() tokens = list(glibcpp.tokenize_c(source, reporter)) if reporter.errors: # Obtain python source line information. frame = inspect.stack(2)[1] for err in reporter.errors: print('{}:{}: tokenizer error: {}: {}'.format( frame[1], frame[2], err[0].line, err[1])) return class EvalReporter: """Used as the error reporter during evaluation.""" def __init__(self): self.lines = [] def error(self, line, message): self.lines.append('{}: error: {}\n'.format(line, message)) def note(self, line, message): self.lines.append('{}: note: {}\n'.format(line, message)) reporter = EvalReporter() actual = glibcpp.macro_eval(glibcpp.macro_definitions(tokens), reporter) actual_errors = ''.join(reporter.lines) if actual != expected or actual_errors != expected_errors: global errors errors += 1 # Obtain python source line information. frame = inspect.stack(2)[1] print('{}:{}: error: macro evaluation mismatch, actual results:' .format(frame[1], frame[2])) for k, v in actual.items(): print(' {}: {!r}'.format(k, v)) for msg in reporter.lines: sys.stdout.write(' | ' + msg) # Individual test cases follow. check_macro_definitions('', []) check_macro_definitions('int main()\n{\n{\n', []) check_macro_definitions(""" #define A 1 #define B 2 /* ignored */ #define C 3 // also ignored #define D \ 4 #define STRING "string" #define FUNCLIKE(a, b) (a + b) #define FUNCLIKE2(a, b) (a + \ b) """, [('A', ['1']), ('B', ['2']), ('C', ['3']), ('D', ['4']), ('STRING', ['"string"']), ('FUNCLIKE(a,b)', list('(a+b)')), ('FUNCLIKE2(a,b)', list('(a+b)')), ]) check_macro_definitions('#define MACRO', [('MACRO', [])]) check_macro_definitions('#define MACRO\n', [('MACRO', [])]) check_macro_definitions('#define MACRO()', [('MACRO()', [])]) check_macro_definitions('#define MACRO()\n', [('MACRO()', [])]) check_macro_eval('#define A 1', {'A': 1}) check_macro_eval('#define A (1)', {'A': 1}) check_macro_eval('#define A (1 + 1)', {'A': 2}) check_macro_eval('#define A (1U << 31)', {'A': 1 << 31}) check_macro_eval('#define A (1 | 2)', {'A': 1 | 2}) check_macro_eval('''\ #define A (B + 1) #define B 10 #define F(x) ignored #define C "not ignored" ''', { 'A': 11, 'B': 10, 'C': '"not ignored"', }) # Checking for evaluation errors. check_macro_eval('''\ #define A 1 #define A 2 ''', { 'A': 1, }, '''\ 2: error: macro A redefined 1: note: location of previous definition ''') check_macro_eval('''\ #define A A #define B 1 ''', { 'A': None, 'B': 1, }, '''\ 1: error: macro definition A refers to itself ''') check_macro_eval('''\ #define A B #define B A ''', { 'A': None, 'B': None, }, '''\ 1: error: macro definition A refers to itself 2: note: evaluated from B ''') check_macro_eval('''\ #define A B #define B C #define C A ''', { 'A': None, 'B': None, 'C': None, }, '''\ 1: error: macro definition A refers to itself 3: note: evaluated from C 2: note: evaluated from B ''') check_macro_eval('''\ #define A 1 + ''', { 'A': None, }, '''\ 1: error: uninterpretable macro token sequence: 1 + ''') check_macro_eval('''\ #define A 3*5 ''', { 'A': None, }, '''\ 1: error: uninterpretable macro token sequence: 3 * 5 ''') check_macro_eval('''\ #define A 3 + 5 ''', { 'A': 8, }, '''\ 1: error: missing parentheses around + expression 1: note: in definition of macro A ''') if errors: sys.exit(1)