17db96d56Sopenharmony_ciimport re
27db96d56Sopenharmony_ciimport sys
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_cidef negate(condition):
57db96d56Sopenharmony_ci    """
67db96d56Sopenharmony_ci    Returns a CPP conditional that is the opposite of the conditional passed in.
77db96d56Sopenharmony_ci    """
87db96d56Sopenharmony_ci    if condition.startswith('!'):
97db96d56Sopenharmony_ci        return condition[1:]
107db96d56Sopenharmony_ci    return "!" + condition
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_ciclass Monitor:
137db96d56Sopenharmony_ci    """
147db96d56Sopenharmony_ci    A simple C preprocessor that scans C source and computes, line by line,
157db96d56Sopenharmony_ci    what the current C preprocessor #if state is.
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ci    Doesn't handle everything--for example, if you have /* inside a C string,
187db96d56Sopenharmony_ci    without a matching */ (also inside a C string), or with a */ inside a C
197db96d56Sopenharmony_ci    string but on another line and with preprocessor macros in between...
207db96d56Sopenharmony_ci    the parser will get lost.
217db96d56Sopenharmony_ci
227db96d56Sopenharmony_ci    Anyway this implementation seems to work well enough for the CPython sources.
237db96d56Sopenharmony_ci    """
247db96d56Sopenharmony_ci
257db96d56Sopenharmony_ci    is_a_simple_defined = re.compile(r'^defined\s*\(\s*[A-Za-z0-9_]+\s*\)$').match
267db96d56Sopenharmony_ci
277db96d56Sopenharmony_ci    def __init__(self, filename=None, *, verbose=False):
287db96d56Sopenharmony_ci        self.stack = []
297db96d56Sopenharmony_ci        self.in_comment = False
307db96d56Sopenharmony_ci        self.continuation = None
317db96d56Sopenharmony_ci        self.line_number = 0
327db96d56Sopenharmony_ci        self.filename = filename
337db96d56Sopenharmony_ci        self.verbose = verbose
347db96d56Sopenharmony_ci
357db96d56Sopenharmony_ci    def __repr__(self):
367db96d56Sopenharmony_ci        return ''.join((
377db96d56Sopenharmony_ci            '<Monitor ',
387db96d56Sopenharmony_ci            str(id(self)),
397db96d56Sopenharmony_ci            " line=", str(self.line_number),
407db96d56Sopenharmony_ci            " condition=", repr(self.condition()),
417db96d56Sopenharmony_ci            ">"))
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_ci    def status(self):
447db96d56Sopenharmony_ci        return str(self.line_number).rjust(4) + ": " + self.condition()
457db96d56Sopenharmony_ci
467db96d56Sopenharmony_ci    def condition(self):
477db96d56Sopenharmony_ci        """
487db96d56Sopenharmony_ci        Returns the current preprocessor state, as a single #if condition.
497db96d56Sopenharmony_ci        """
507db96d56Sopenharmony_ci        return " && ".join(condition for token, condition in self.stack)
517db96d56Sopenharmony_ci
527db96d56Sopenharmony_ci    def fail(self, *a):
537db96d56Sopenharmony_ci        if self.filename:
547db96d56Sopenharmony_ci            filename = " " + self.filename
557db96d56Sopenharmony_ci        else:
567db96d56Sopenharmony_ci            filename = ''
577db96d56Sopenharmony_ci        print("Error at" + filename, "line", self.line_number, ":")
587db96d56Sopenharmony_ci        print("   ", ' '.join(str(x) for x in a))
597db96d56Sopenharmony_ci        sys.exit(-1)
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ci    def close(self):
627db96d56Sopenharmony_ci        if self.stack:
637db96d56Sopenharmony_ci            self.fail("Ended file while still in a preprocessor conditional block!")
647db96d56Sopenharmony_ci
657db96d56Sopenharmony_ci    def write(self, s):
667db96d56Sopenharmony_ci        for line in s.split("\n"):
677db96d56Sopenharmony_ci            self.writeline(line)
687db96d56Sopenharmony_ci
697db96d56Sopenharmony_ci    def writeline(self, line):
707db96d56Sopenharmony_ci        self.line_number += 1
717db96d56Sopenharmony_ci        line = line.strip()
727db96d56Sopenharmony_ci
737db96d56Sopenharmony_ci        def pop_stack():
747db96d56Sopenharmony_ci            if not self.stack:
757db96d56Sopenharmony_ci                self.fail("#" + token + " without matching #if / #ifdef / #ifndef!")
767db96d56Sopenharmony_ci            return self.stack.pop()
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci        if self.continuation:
797db96d56Sopenharmony_ci            line = self.continuation + line
807db96d56Sopenharmony_ci            self.continuation = None
817db96d56Sopenharmony_ci
827db96d56Sopenharmony_ci        if not line:
837db96d56Sopenharmony_ci            return
847db96d56Sopenharmony_ci
857db96d56Sopenharmony_ci        if line.endswith('\\'):
867db96d56Sopenharmony_ci            self.continuation = line[:-1].rstrip() + " "
877db96d56Sopenharmony_ci            return
887db96d56Sopenharmony_ci
897db96d56Sopenharmony_ci        # we have to ignore preprocessor commands inside comments
907db96d56Sopenharmony_ci        #
917db96d56Sopenharmony_ci        # we also have to handle this:
927db96d56Sopenharmony_ci        #     /* start
937db96d56Sopenharmony_ci        #     ...
947db96d56Sopenharmony_ci        #     */   /*    <-- tricky!
957db96d56Sopenharmony_ci        #     ...
967db96d56Sopenharmony_ci        #     */
977db96d56Sopenharmony_ci        # and this:
987db96d56Sopenharmony_ci        #     /* start
997db96d56Sopenharmony_ci        #     ...
1007db96d56Sopenharmony_ci        #     */   /* also tricky! */
1017db96d56Sopenharmony_ci        if self.in_comment:
1027db96d56Sopenharmony_ci            if '*/' in line:
1037db96d56Sopenharmony_ci                # snip out the comment and continue
1047db96d56Sopenharmony_ci                #
1057db96d56Sopenharmony_ci                # GCC allows
1067db96d56Sopenharmony_ci                #    /* comment
1077db96d56Sopenharmony_ci                #    */ #include <stdio.h>
1087db96d56Sopenharmony_ci                # maybe other compilers too?
1097db96d56Sopenharmony_ci                _, _, line = line.partition('*/')
1107db96d56Sopenharmony_ci                self.in_comment = False
1117db96d56Sopenharmony_ci
1127db96d56Sopenharmony_ci        while True:
1137db96d56Sopenharmony_ci            if '/*' in line:
1147db96d56Sopenharmony_ci                if self.in_comment:
1157db96d56Sopenharmony_ci                    self.fail("Nested block comment!")
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_ci                before, _, remainder = line.partition('/*')
1187db96d56Sopenharmony_ci                comment, comment_ends, after = remainder.partition('*/')
1197db96d56Sopenharmony_ci                if comment_ends:
1207db96d56Sopenharmony_ci                    # snip out the comment
1217db96d56Sopenharmony_ci                    line = before.rstrip() + ' ' + after.lstrip()
1227db96d56Sopenharmony_ci                    continue
1237db96d56Sopenharmony_ci                # comment continues to eol
1247db96d56Sopenharmony_ci                self.in_comment = True
1257db96d56Sopenharmony_ci                line = before.rstrip()
1267db96d56Sopenharmony_ci            break
1277db96d56Sopenharmony_ci
1287db96d56Sopenharmony_ci        # we actually have some // comments
1297db96d56Sopenharmony_ci        # (but block comments take precedence)
1307db96d56Sopenharmony_ci        before, line_comment, comment = line.partition('//')
1317db96d56Sopenharmony_ci        if line_comment:
1327db96d56Sopenharmony_ci            line = before.rstrip()
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_ci        if not line.startswith('#'):
1357db96d56Sopenharmony_ci            return
1367db96d56Sopenharmony_ci
1377db96d56Sopenharmony_ci        line = line[1:].lstrip()
1387db96d56Sopenharmony_ci        assert line
1397db96d56Sopenharmony_ci
1407db96d56Sopenharmony_ci        fields = line.split()
1417db96d56Sopenharmony_ci        token = fields[0].lower()
1427db96d56Sopenharmony_ci        condition = ' '.join(fields[1:]).strip()
1437db96d56Sopenharmony_ci
1447db96d56Sopenharmony_ci        if token in {'if', 'ifdef', 'ifndef', 'elif'}:
1457db96d56Sopenharmony_ci            if not condition:
1467db96d56Sopenharmony_ci                self.fail("Invalid format for #" + token + " line: no argument!")
1477db96d56Sopenharmony_ci            if token in {'if', 'elif'}:
1487db96d56Sopenharmony_ci                if not self.is_a_simple_defined(condition):
1497db96d56Sopenharmony_ci                    condition = "(" + condition + ")"
1507db96d56Sopenharmony_ci                if token == 'elif':
1517db96d56Sopenharmony_ci                    previous_token, previous_condition = pop_stack()
1527db96d56Sopenharmony_ci                    self.stack.append((previous_token, negate(previous_condition)))
1537db96d56Sopenharmony_ci            else:
1547db96d56Sopenharmony_ci                fields = condition.split()
1557db96d56Sopenharmony_ci                if len(fields) != 1:
1567db96d56Sopenharmony_ci                    self.fail("Invalid format for #" + token + " line: should be exactly one argument!")
1577db96d56Sopenharmony_ci                symbol = fields[0]
1587db96d56Sopenharmony_ci                condition = 'defined(' + symbol + ')'
1597db96d56Sopenharmony_ci                if token == 'ifndef':
1607db96d56Sopenharmony_ci                    condition = '!' + condition
1617db96d56Sopenharmony_ci                token = 'if'
1627db96d56Sopenharmony_ci
1637db96d56Sopenharmony_ci            self.stack.append((token, condition))
1647db96d56Sopenharmony_ci
1657db96d56Sopenharmony_ci        elif token == 'else':
1667db96d56Sopenharmony_ci            previous_token, previous_condition = pop_stack()
1677db96d56Sopenharmony_ci            self.stack.append((previous_token, negate(previous_condition)))
1687db96d56Sopenharmony_ci
1697db96d56Sopenharmony_ci        elif token == 'endif':
1707db96d56Sopenharmony_ci            while pop_stack()[0] != 'if':
1717db96d56Sopenharmony_ci                pass
1727db96d56Sopenharmony_ci
1737db96d56Sopenharmony_ci        else:
1747db96d56Sopenharmony_ci            return
1757db96d56Sopenharmony_ci
1767db96d56Sopenharmony_ci        if self.verbose:
1777db96d56Sopenharmony_ci            print(self.status())
1787db96d56Sopenharmony_ci
1797db96d56Sopenharmony_ciif __name__ == '__main__':
1807db96d56Sopenharmony_ci    for filename in sys.argv[1:]:
1817db96d56Sopenharmony_ci        with open(filename, "rt") as f:
1827db96d56Sopenharmony_ci            cpp = Monitor(filename, verbose=True)
1837db96d56Sopenharmony_ci            print()
1847db96d56Sopenharmony_ci            print(filename)
1857db96d56Sopenharmony_ci            for line_number, line in enumerate(f.read().split('\n'), 1):
1867db96d56Sopenharmony_ci                cpp.writeline(line)
187