520cc8
commit e6e6184bed490403811771fa527eb95b4ae53c7c
520cc8
Author: Florian Weimer <fweimer@redhat.com>
520cc8
Date:   Thu Sep 22 12:10:41 2022 +0200
520cc8
520cc8
    scripts: Enhance glibcpp to do basic macro processing
520cc8
520cc8
    Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
520cc8
520cc8
Conflicts:
520cc8
	support/Makefile
520cc8
	  (spurious tests sorting change upstream)
520cc8
520cc8
diff --git a/scripts/glibcpp.py b/scripts/glibcpp.py
520cc8
index b44c6a4392dde8ce..455459a609eab120 100644
520cc8
--- a/scripts/glibcpp.py
520cc8
+++ b/scripts/glibcpp.py
520cc8
@@ -33,7 +33,9 @@ Accepts non-ASCII characters only within comments and strings.
520cc8
 """
520cc8
 
520cc8
 import collections
520cc8
+import operator
520cc8
 import re
520cc8
+import sys
520cc8
 
520cc8
 # Caution: The order of the outermost alternation matters.
520cc8
 # STRING must be before BAD_STRING, CHARCONST before BAD_CHARCONST,
520cc8
@@ -210,3 +212,318 @@ def tokenize_c(file_contents, reporter):
520cc8
 
520cc8
         yield tok
520cc8
         pos = mo.end()
520cc8
+
520cc8
+class MacroDefinition(collections.namedtuple('MacroDefinition',
520cc8
+                                             'name_token args body error')):
520cc8
+    """A preprocessor macro definition.
520cc8
+
520cc8
+    name_token is the Token_ for the name.
520cc8
+
520cc8
+    args is None for a macro that is not function-like.  Otherwise, it
520cc8
+    is a tuple that contains the macro argument name tokens.
520cc8
+
520cc8
+    body is a tuple that contains the tokens that constitue the body
520cc8
+    of the macro definition (excluding whitespace).
520cc8
+
520cc8
+    error is None if no error was detected, or otherwise a problem
520cc8
+    description associated with this macro definition.
520cc8
+
520cc8
+    """
520cc8
+
520cc8
+    @property
520cc8
+    def function(self):
520cc8
+        """Return true if the macro is function-like."""
520cc8
+        return self.args is not None
520cc8
+
520cc8
+    @property
520cc8
+    def name(self):
520cc8
+        """Return the name of the macro being defined."""
520cc8
+        return self.name_token.text
520cc8
+
520cc8
+    @property
520cc8
+    def line(self):
520cc8
+        """Return the line number of the macro defintion."""
520cc8
+        return self.name_token.line
520cc8
+
520cc8
+    @property
520cc8
+    def args_lowered(self):
520cc8
+        """Return the macro argument list as a list of strings"""
520cc8
+        if self.function:
520cc8
+            return [token.text for token in self.args]
520cc8
+        else:
520cc8
+            return None
520cc8
+
520cc8
+    @property
520cc8
+    def body_lowered(self):
520cc8
+        """Return the macro body as a list of strings."""
520cc8
+        return [token.text for token in self.body]
520cc8
+
520cc8
+def macro_definitions(tokens):
520cc8
+    """A generator for C macro definitions among tokens.
520cc8
+
520cc8
+    The generator yields MacroDefinition objects.
520cc8
+
520cc8
+    tokens must be iterable, yielding Token_ objects.
520cc8
+
520cc8
+    """
520cc8
+
520cc8
+    macro_name = None
520cc8
+    macro_start = False # Set to false after macro name and one otken.
520cc8
+    macro_args = None # Set to a list during the macro argument sequence.
520cc8
+    in_macro_args = False # True while processing macro identifier-list.
520cc8
+    error = None
520cc8
+    body = []
520cc8
+
520cc8
+    for token in tokens:
520cc8
+        if token.context == 'define' and macro_name is None \
520cc8
+           and token.kind == 'IDENT':
520cc8
+            # Starting up macro processing.
520cc8
+            if macro_start:
520cc8
+                # First identifier is the macro name.
520cc8
+                macro_name = token
520cc8
+            else:
520cc8
+                # Next token is the name.
520cc8
+                macro_start = True
520cc8
+            continue
520cc8
+
520cc8
+        if macro_name is None:
520cc8
+            # Drop tokens not in macro definitions.
520cc8
+            continue
520cc8
+
520cc8
+        if token.context != 'define':
520cc8
+            # End of the macro definition.
520cc8
+            if in_macro_args and error is None:
520cc8
+                error = 'macro definition ends in macro argument list'
520cc8
+            yield MacroDefinition(macro_name, macro_args, tuple(body), error)
520cc8
+            # No longer in a macro definition.
520cc8
+            macro_name = None
520cc8
+            macro_start = False
520cc8
+            macro_args = None
520cc8
+            in_macro_args = False
520cc8
+            error = None
520cc8
+            body.clear()
520cc8
+            continue
520cc8
+
520cc8
+        if macro_start:
520cc8
+            # First token after the macro name.
520cc8
+            macro_start = False
520cc8
+            if token.kind == 'PUNCTUATOR' and token.text == '(':
520cc8
+                macro_args = []
520cc8
+                in_macro_args = True
520cc8
+            continue
520cc8
+
520cc8
+        if in_macro_args:
520cc8
+            if token.kind == 'IDENT' \
520cc8
+               or (token.kind == 'PUNCTUATOR' and token.text == '...'):
520cc8
+                # Macro argument or ... placeholder.
520cc8
+                macro_args.append(token)
520cc8
+            if token.kind == 'PUNCTUATOR':
520cc8
+                if token.text == ')':
520cc8
+                    macro_args = tuple(macro_args)
520cc8
+                    in_macro_args = False
520cc8
+                elif token.text == ',':
520cc8
+                    pass # Skip.  Not a full syntax check.
520cc8
+                elif error is None:
520cc8
+                    error = 'invalid punctuator in macro argument list: ' \
520cc8
+                        + repr(token.text)
520cc8
+            elif error is None:
520cc8
+                error = 'invalid {} token in macro argument list'.format(
520cc8
+                    token.kind)
520cc8
+            continue
520cc8
+
520cc8
+        if token.kind not in ('WHITESPACE', 'BLOCK_COMMENT'):
520cc8
+            body.append(token)
520cc8
+
520cc8
+    # Emit the macro in case the last line does not end with a newline.
520cc8
+    if macro_name is not None:
520cc8
+        if in_macro_args and error is None:
520cc8
+            error = 'macro definition ends in macro argument list'
520cc8
+        yield MacroDefinition(macro_name, macro_args, tuple(body), error)
520cc8
+
520cc8
+# Used to split UL etc. suffixes from numbers such as 123UL.
520cc8
+RE_SPLIT_INTEGER_SUFFIX = re.compile(r'([^ullULL]+)([ullULL]*)')
520cc8
+
520cc8
+BINARY_OPERATORS = {
520cc8
+    '+': operator.add,
520cc8
+    '<<': operator.lshift,
520cc8
+}
520cc8
+
520cc8
+# Use the general-purpose dict type if it is order-preserving.
520cc8
+if (sys.version_info[0], sys.version_info[1]) <= (3, 6):
520cc8
+    OrderedDict = collections.OrderedDict
520cc8
+else:
520cc8
+    OrderedDict = dict
520cc8
+
520cc8
+def macro_eval(macro_defs, reporter):
520cc8
+    """Compute macro values
520cc8
+
520cc8
+    macro_defs is the output from macro_definitions.  reporter is an
520cc8
+    object that accepts reporter.error(line_number, message) and
520cc8
+    reporter.note(line_number, message) calls to report errors
520cc8
+    and error context invocations.
520cc8
+
520cc8
+    The returned dict contains the values of macros which are not
520cc8
+    function-like, pairing their names with their computed values.
520cc8
+
520cc8
+    The current implementation is incomplete.  It is deliberately not
520cc8
+    entirely faithful to C, even in the implemented parts.  It checks
520cc8
+    that macro replacements follow certain syntactic rules even if
520cc8
+    they are never evaluated.
520cc8
+
520cc8
+    """
520cc8
+
520cc8
+    # Unevaluated macro definitions by name.
520cc8
+    definitions = OrderedDict()
520cc8
+    for md in macro_defs:
520cc8
+        if md.name in definitions:
520cc8
+            reporter.error(md.line, 'macro {} redefined'.format(md.name))
520cc8
+            reporter.note(definitions[md.name].line,
520cc8
+                          'location of previous definition')
520cc8
+        else:
520cc8
+            definitions[md.name] = md
520cc8
+
520cc8
+    # String to value mappings for fully evaluated macros.
520cc8
+    evaluated = OrderedDict()
520cc8
+
520cc8
+    # String to macro definitions during evaluation.  Nice error
520cc8
+    # reporting relies on determinstic iteration order.
520cc8
+    stack = OrderedDict()
520cc8
+
520cc8
+    def eval_token(current, token):
520cc8
+        """Evaluate one macro token.
520cc8
+
520cc8
+        Integers and strings are returned as such (the latter still
520cc8
+        quoted).  Identifiers are expanded.
520cc8
+
520cc8
+        None indicates an empty expansion or an error.
520cc8
+
520cc8
+        """
520cc8
+
520cc8
+        if token.kind == 'PP_NUMBER':
520cc8
+            value = None
520cc8
+            m = RE_SPLIT_INTEGER_SUFFIX.match(token.text)
520cc8
+            if m:
520cc8
+                try:
520cc8
+                    value = int(m.group(1), 0)
520cc8
+                except ValueError:
520cc8
+                    pass
520cc8
+            if value is None:
520cc8
+                reporter.error(token.line,
520cc8
+                    'invalid number {!r} in definition of {}'.format(
520cc8
+                        token.text, current.name))
520cc8
+            return value
520cc8
+
520cc8
+        if token.kind == 'STRING':
520cc8
+            return token.text
520cc8
+
520cc8
+        if token.kind == 'CHARCONST' and len(token.text) == 3:
520cc8
+            return ord(token.text[1])
520cc8
+
520cc8
+        if token.kind == 'IDENT':
520cc8
+            name = token.text
520cc8
+            result = eval1(current, name)
520cc8
+            if name not in evaluated:
520cc8
+                evaluated[name] = result
520cc8
+            return result
520cc8
+
520cc8
+        reporter.error(token.line,
520cc8
+            'unrecognized {!r} in definition of {}'.format(
520cc8
+                token.text, current.name))
520cc8
+        return None
520cc8
+
520cc8
+
520cc8
+    def eval1(current, name):
520cc8
+        """Evaluate one name.
520cc8
+
520cc8
+        The name is looked up and the macro definition evaluated
520cc8
+        recursively if necessary.  The current argument is the macro
520cc8
+        definition being evaluated.
520cc8
+
520cc8
+        None as a return value indicates an error.
520cc8
+
520cc8
+        """
520cc8
+
520cc8
+        # Fast path if the value has already been evaluated.
520cc8
+        if name in evaluated:
520cc8
+            return evaluated[name]
520cc8
+
520cc8
+        try:
520cc8
+            md = definitions[name]
520cc8
+        except KeyError:
520cc8
+            reporter.error(current.line,
520cc8
+                'reference to undefined identifier {} in definition of {}'
520cc8
+                           .format(name, current.name))
520cc8
+            return None
520cc8
+
520cc8
+        if md.name in stack:
520cc8
+            # Recursive macro definition.
520cc8
+            md = stack[name]
520cc8
+            reporter.error(md.line,
520cc8
+                'macro definition {} refers to itself'.format(md.name))
520cc8
+            for md1 in reversed(list(stack.values())):
520cc8
+                if md1 is md:
520cc8
+                    break
520cc8
+                reporter.note(md1.line,
520cc8
+                              'evaluated from {}'.format(md1.name))
520cc8
+            return None
520cc8
+
520cc8
+        stack[md.name] = md
520cc8
+        if md.function:
520cc8
+            reporter.error(current.line,
520cc8
+                'attempt to evaluate function-like macro {}'.format(name))
520cc8
+            reporter.note(md.line, 'definition of {}'.format(md.name))
520cc8
+            return None
520cc8
+
520cc8
+        try:
520cc8
+            body = md.body
520cc8
+            if len(body) == 0:
520cc8
+                # Empty expansion.
520cc8
+                return None
520cc8
+
520cc8
+            # Remove surrounding ().
520cc8
+            if body[0].text == '(' and body[-1].text == ')':
520cc8
+                body = body[1:-1]
520cc8
+                had_parens = True
520cc8
+            else:
520cc8
+                had_parens = False
520cc8
+
520cc8
+            if len(body) == 1:
520cc8
+                return eval_token(md, body[0])
520cc8
+
520cc8
+            # Minimal expression evaluator for binary operators.
520cc8
+            op = body[1].text
520cc8
+            if len(body) == 3 and op in BINARY_OPERATORS:
520cc8
+                if not had_parens:
520cc8
+                    reporter.error(body[1].line,
520cc8
+                        'missing parentheses around {} expression'.format(op))
520cc8
+                    reporter.note(md.line,
520cc8
+                                  'in definition of macro {}'.format(md.name))
520cc8
+
520cc8
+                left = eval_token(md, body[0])
520cc8
+                right = eval_token(md, body[2])
520cc8
+
520cc8
+                if type(left) != type(1):
520cc8
+                    reporter.error(left.line,
520cc8
+                        'left operand of {} is not an integer'.format(op))
520cc8
+                    reporter.note(md.line,
520cc8
+                                  'in definition of macro {}'.format(md.name))
520cc8
+                if type(right) != type(1):
520cc8
+                    reporter.error(left.line,
520cc8
+                        'right operand of {} is not an integer'.format(op))
520cc8
+                    reporter.note(md.line,
520cc8
+                                  'in definition of macro {}'.format(md.name))
520cc8
+                return BINARY_OPERATORS[op](left, right)
520cc8
+
520cc8
+            reporter.error(md.line,
520cc8
+                'uninterpretable macro token sequence: {}'.format(
520cc8
+                    ' '.join(md.body_lowered)))
520cc8
+            return None
520cc8
+        finally:
520cc8
+            del stack[md.name]
520cc8
+
520cc8
+    # Start of main body of macro_eval.
520cc8
+    for md in definitions.values():
520cc8
+        name = md.name
520cc8
+        if name not in evaluated and not md.function:
520cc8
+            evaluated[name] = eval1(md, name)
520cc8
+    return evaluated
520cc8
diff --git a/support/Makefile b/support/Makefile
520cc8
index 09b41b0d57e9239a..7749ac24f1ac3622 100644
520cc8
--- a/support/Makefile
520cc8
+++ b/support/Makefile
520cc8
@@ -223,11 +223,11 @@ $(objpfx)true-container : $(libsupport)
520cc8
 tests = \
520cc8
   README-testing \
520cc8
   tst-support-namespace \
520cc8
+  tst-support-process_state \
520cc8
   tst-support_blob_repeat \
520cc8
   tst-support_capture_subprocess \
520cc8
   tst-support_descriptors \
520cc8
   tst-support_format_dns_packet \
520cc8
-  tst-support-process_state \
520cc8
   tst-support_quote_blob \
520cc8
   tst-support_quote_string \
520cc8
   tst-support_record_failure \
520cc8
@@ -248,6 +248,12 @@ $(objpfx)tst-support_record_failure-2.out: tst-support_record_failure-2.sh \
520cc8
 	$(evaluate-test)
520cc8
 endif
520cc8
 
520cc8
+tests-special += $(objpfx)tst-glibcpp.out
520cc8
+
520cc8
+$(objpfx)tst-glibcpp.out: tst-glibcpp.py $(..)scripts/glibcpp.py
520cc8
+	PYTHONPATH=$(..)scripts $(PYTHON) tst-glibcpp.py > $@ 2>&1; \
520cc8
+	$(evaluate-test)
520cc8
+
520cc8
 $(objpfx)tst-support_format_dns_packet: $(common-objpfx)resolv/libresolv.so
520cc8
 
520cc8
 tst-support_capture_subprocess-ARGS = -- $(host-test-program-cmd)
520cc8
diff --git a/support/tst-glibcpp.py b/support/tst-glibcpp.py
520cc8
new file mode 100644
520cc8
index 0000000000000000..a2db1916ccfce3c3
520cc8
--- /dev/null
520cc8
+++ b/support/tst-glibcpp.py
520cc8
@@ -0,0 +1,217 @@
520cc8
+#! /usr/bin/python3
520cc8
+# Tests for scripts/glibcpp.py
520cc8
+# Copyright (C) 2022 Free Software Foundation, Inc.
520cc8
+# This file is part of the GNU C Library.
520cc8
+#
520cc8
+# The GNU C Library is free software; you can redistribute it and/or
520cc8
+# modify it under the terms of the GNU Lesser General Public
520cc8
+# License as published by the Free Software Foundation; either
520cc8
+# version 2.1 of the License, or (at your option) any later version.
520cc8
+#
520cc8
+# The GNU C Library is distributed in the hope that it will be useful,
520cc8
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
520cc8
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
520cc8
+# Lesser General Public License for more details.
520cc8
+#
520cc8
+# You should have received a copy of the GNU Lesser General Public
520cc8
+# License along with the GNU C Library; if not, see
520cc8
+# <https://www.gnu.org/licenses/>.
520cc8
+
520cc8
+import inspect
520cc8
+import sys
520cc8
+
520cc8
+import glibcpp
520cc8
+
520cc8
+# Error counter.
520cc8
+errors = 0
520cc8
+
520cc8
+class TokenizerErrors:
520cc8
+    """Used as the error reporter during tokenization."""
520cc8
+
520cc8
+    def __init__(self):
520cc8
+        self.errors = []
520cc8
+
520cc8
+    def error(self, token, message):
520cc8
+        self.errors.append((token, message))
520cc8
+
520cc8
+def check_macro_definitions(source, expected):
520cc8
+    reporter = TokenizerErrors()
520cc8
+    tokens = glibcpp.tokenize_c(source, reporter)
520cc8
+
520cc8
+    actual = []
520cc8
+    for md in glibcpp.macro_definitions(tokens):
520cc8
+        if md.function:
520cc8
+            md_name = '{}({})'.format(md.name, ','.join(md.args_lowered))
520cc8
+        else:
520cc8
+            md_name = md.name
520cc8
+        actual.append((md_name, md.body_lowered))
520cc8
+
520cc8
+    if actual != expected or reporter.errors:
520cc8
+        global errors
520cc8
+        errors += 1
520cc8
+        # Obtain python source line information.
520cc8
+        frame = inspect.stack(2)[1]
520cc8
+        print('{}:{}: error: macro definition mismatch, actual definitions:'
520cc8
+              .format(frame[1], frame[2]))
520cc8
+        for md in actual:
520cc8
+            print('note: {} {!r}'.format(md[0], md[1]))
520cc8
+
520cc8
+        if reporter.errors:
520cc8
+            for err in reporter.errors:
520cc8
+                print('note: tokenizer error: {}: {}'.format(
520cc8
+                    err[0].line, err[1]))
520cc8
+
520cc8
+def check_macro_eval(source, expected, expected_errors=''):
520cc8
+    reporter = TokenizerErrors()
520cc8
+    tokens = list(glibcpp.tokenize_c(source, reporter))
520cc8
+
520cc8
+    if reporter.errors:
520cc8
+        # Obtain python source line information.
520cc8
+        frame = inspect.stack(2)[1]
520cc8
+        for err in reporter.errors:
520cc8
+            print('{}:{}: tokenizer error: {}: {}'.format(
520cc8
+                frame[1], frame[2], err[0].line, err[1]))
520cc8
+        return
520cc8
+
520cc8
+    class EvalReporter:
520cc8
+        """Used as the error reporter during evaluation."""
520cc8
+
520cc8
+        def __init__(self):
520cc8
+            self.lines = []
520cc8
+
520cc8
+        def error(self, line, message):
520cc8
+            self.lines.append('{}: error: {}\n'.format(line, message))
520cc8
+
520cc8
+        def note(self, line, message):
520cc8
+            self.lines.append('{}: note: {}\n'.format(line, message))
520cc8
+
520cc8
+    reporter = EvalReporter()
520cc8
+    actual = glibcpp.macro_eval(glibcpp.macro_definitions(tokens), reporter)
520cc8
+    actual_errors = ''.join(reporter.lines)
520cc8
+    if actual != expected or actual_errors != expected_errors:
520cc8
+        global errors
520cc8
+        errors += 1
520cc8
+        # Obtain python source line information.
520cc8
+        frame = inspect.stack(2)[1]
520cc8
+        print('{}:{}: error: macro evaluation mismatch, actual results:'
520cc8
+              .format(frame[1], frame[2]))
520cc8
+        for k, v in actual.items():
520cc8
+            print('  {}: {!r}'.format(k, v))
520cc8
+        for msg in reporter.lines:
520cc8
+            sys.stdout.write('  | ' + msg)
520cc8
+
520cc8
+# Individual test cases follow.
520cc8
+
520cc8
+check_macro_definitions('', [])
520cc8
+check_macro_definitions('int main()\n{\n{\n', [])
520cc8
+check_macro_definitions("""
520cc8
+#define A 1
520cc8
+#define B 2 /* ignored */
520cc8
+#define C 3 // also ignored
520cc8
+#define D \
520cc8
+ 4
520cc8
+#define STRING "string"
520cc8
+#define FUNCLIKE(a, b) (a + b)
520cc8
+#define FUNCLIKE2(a, b) (a + \
520cc8
+ b)
520cc8
+""", [('A', ['1']),
520cc8
+      ('B', ['2']),
520cc8
+      ('C', ['3']),
520cc8
+      ('D', ['4']),
520cc8
+      ('STRING', ['"string"']),
520cc8
+      ('FUNCLIKE(a,b)', list('(a+b)')),
520cc8
+      ('FUNCLIKE2(a,b)', list('(a+b)')),
520cc8
+      ])
520cc8
+check_macro_definitions('#define MACRO', [('MACRO', [])])
520cc8
+check_macro_definitions('#define MACRO\n', [('MACRO', [])])
520cc8
+check_macro_definitions('#define MACRO()', [('MACRO()', [])])
520cc8
+check_macro_definitions('#define MACRO()\n', [('MACRO()', [])])
520cc8
+
520cc8
+check_macro_eval('#define A 1', {'A': 1})
520cc8
+check_macro_eval('#define A (1)', {'A': 1})
520cc8
+check_macro_eval('#define A (1 + 1)', {'A': 2})
520cc8
+check_macro_eval('#define A (1U << 31)', {'A': 1 << 31})
520cc8
+check_macro_eval('''\
520cc8
+#define A (B + 1)
520cc8
+#define B 10
520cc8
+#define F(x) ignored
520cc8
+#define C "not ignored"
520cc8
+''', {
520cc8
+    'A': 11,
520cc8
+    'B': 10,
520cc8
+    'C': '"not ignored"',
520cc8
+})
520cc8
+
520cc8
+# Checking for evaluation errors.
520cc8
+check_macro_eval('''\
520cc8
+#define A 1
520cc8
+#define A 2
520cc8
+''', {
520cc8
+    'A': 1,
520cc8
+}, '''\
520cc8
+2: error: macro A redefined
520cc8
+1: note: location of previous definition
520cc8
+''')
520cc8
+
520cc8
+check_macro_eval('''\
520cc8
+#define A A
520cc8
+#define B 1
520cc8
+''', {
520cc8
+    'A': None,
520cc8
+    'B': 1,
520cc8
+}, '''\
520cc8
+1: error: macro definition A refers to itself
520cc8
+''')
520cc8
+
520cc8
+check_macro_eval('''\
520cc8
+#define A B
520cc8
+#define B A
520cc8
+''', {
520cc8
+    'A': None,
520cc8
+    'B': None,
520cc8
+}, '''\
520cc8
+1: error: macro definition A refers to itself
520cc8
+2: note: evaluated from B
520cc8
+''')
520cc8
+
520cc8
+check_macro_eval('''\
520cc8
+#define A B
520cc8
+#define B C
520cc8
+#define C A
520cc8
+''', {
520cc8
+    'A': None,
520cc8
+    'B': None,
520cc8
+    'C': None,
520cc8
+}, '''\
520cc8
+1: error: macro definition A refers to itself
520cc8
+3: note: evaluated from C
520cc8
+2: note: evaluated from B
520cc8
+''')
520cc8
+
520cc8
+check_macro_eval('''\
520cc8
+#define A 1 +
520cc8
+''', {
520cc8
+    'A': None,
520cc8
+}, '''\
520cc8
+1: error: uninterpretable macro token sequence: 1 +
520cc8
+''')
520cc8
+
520cc8
+check_macro_eval('''\
520cc8
+#define A 3*5
520cc8
+''', {
520cc8
+    'A': None,
520cc8
+}, '''\
520cc8
+1: error: uninterpretable macro token sequence: 3 * 5
520cc8
+''')
520cc8
+
520cc8
+check_macro_eval('''\
520cc8
+#define A 3 + 5
520cc8
+''', {
520cc8
+    'A': 8,
520cc8
+}, '''\
520cc8
+1: error: missing parentheses around + expression
520cc8
+1: note: in definition of macro A
520cc8
+''')
520cc8
+
520cc8
+if errors:
520cc8
+    sys.exit(1)