1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import
6from __future__ import unicode_literals
7import re
8
9from .base import (
10    CAN_COPY,
11    Entry, OffsetComment, Junk, Whitespace,
12    Parser
13)
14
15
16class DefinesInstruction(Entry):
17    '''Entity-like object representing processing instructions in inc files
18    '''
19    def __init__(self, ctx, span, val_span):
20        self.ctx = ctx
21        self.span = span
22        self.key_span = self.val_span = val_span
23
24    def __repr__(self):
25        return self.raw_val
26
27
28class DefinesParser(Parser):
29    # can't merge, #unfilter needs to be the last item, which we don't support
30    capabilities = CAN_COPY
31    reWhitespace = re.compile('\n+', re.M)
32
33    EMPTY_LINES = 1 << 0
34
35    class Comment(OffsetComment):
36        comment_offset = 2
37
38    class Context(Parser.Context):
39        def __init__(self, contents):
40            super(DefinesParser.Context, self).__init__(contents)
41            self.filter_empty_lines = False
42
43    def __init__(self):
44        self.reComment = re.compile('(?:^# .*?\n)*(?:^# [^\n]*)', re.M)
45        # corresponds to
46        # https://hg.mozilla.org/mozilla-central/file/72ee4800d4156931c89b58bd807af4a3083702bb/python/mozbuild/mozbuild/preprocessor.py#l561  # noqa
47        self.reKey = re.compile(
48            r'#define[ \t]+(?P<key>\w+)(?:[ \t](?P<val>[^\n]*))?', re.M)
49        self.rePI = re.compile(r'#(?P<val>\w+[ \t]+[^\n]+)', re.M)
50        Parser.__init__(self)
51
52    def getNext(self, ctx, offset):
53        junk_offset = offset
54        contents = ctx.contents
55
56        m = self.reComment.match(ctx.contents, offset)
57        if m:
58            current_comment = self.Comment(ctx, m.span())
59            offset = m.end()
60        else:
61            current_comment = None
62
63        m = self.reWhitespace.match(contents, offset)
64        if m:
65            # blank lines outside of filter_empty_lines or
66            # leading whitespace are bad
67            if (
68                offset == 0 or
69                not (len(m.group()) == 1 or ctx.filter_empty_lines)
70            ):
71                if current_comment:
72                    return current_comment
73                return Junk(ctx, m.span())
74            white_space = Whitespace(ctx, m.span())
75            offset = m.end()
76            if (
77                current_comment is not None
78                and white_space.raw_val.count('\n') > 1
79            ):
80                # standalone comment
81                # return the comment, and reparse the whitespace next time
82                return current_comment
83            if current_comment is None:
84                return white_space
85        else:
86            white_space = None
87
88        m = self.reKey.match(contents, offset)
89        if m:
90            return self.createEntity(ctx, m, current_comment, white_space)
91        # defines instructions don't have comments
92        # Any pending commment is standalone
93        if current_comment:
94            return current_comment
95        if white_space:
96            return white_space
97        m = self.rePI.match(contents, offset)
98        if m:
99            instr = DefinesInstruction(ctx, m.span(), m.span('val'))
100            if instr.val == 'filter emptyLines':
101                ctx.filter_empty_lines = True
102            if instr.val == 'unfilter emptyLines':
103                ctx.filter_empty_lines = False
104            return instr
105        return self.getJunk(
106            ctx, junk_offset, self.reComment, self.reKey, self.rePI)
107