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