1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
#!/usr/bin/env python3
# coding=utf8
"""
Convert selected @objc attributes in a source file into access notes, removing
the originals in the process.
"""
import io
import re
import sys
#
# Entry point
#
def main():
if len(sys.argv) != 4:
print('Too few args to ' + sys.argv[0])
print('Usage: access-note-gen.py <input-file> <output-source-file> ' +
'<output-access-notes-file>')
sys.exit(1)
with io.open(sys.argv[1], mode='r', encoding='utf8') as input_file, \
io.open(sys.argv[2], mode='w', encoding='utf8') as output_file, \
io.open(sys.argv[3], mode='w', encoding='utf8') as access_notes_file:
# Add header to access notes file
access_notes_file.write(u"""\
Reason: 'fancy tests'
Notes:""")
# Loop over input lines, transforming them into output lines, writing access
# notes as a side effect.
for input_line in input_file:
# Look for access-note-move comments.
input_line = access_note_move_re.sub(replacer(move_at_objc_to_access_note,
access_notes_file),
input_line, count=1)
# Look for access-note-adjust comments.
input_line = adjust_comment_re.sub(replacer(adjust_comments),
input_line, count=1)
output_file.write(input_line)
#
# Offsets
#
"""Matches an @±N offset."""
offset_re_fragment = r'[ \t]*(?:@([+-]\d+))?[ \t]*'
def offsetify(*offsets):
"""Sum line offsets matched by offset_re_fragment and convert them to strings
like @+3 or @-2."""
offset = sum([int(o) for o in offsets if o is not None])
if offset < 0:
return u"@-" + str(-offset)
elif offset > 0:
return u"@+" + str(offset)
else:
return u""
#
# Adjusting comments
#
"""Matches expected-warning/note/remark and its offset."""
expected_other_diag_re = re.compile(r'expected-(warning|note|remark)' +
offset_re_fragment)
"""Matches expected-error and its offset."""
expected_error_re = re.compile(r'expected-error' + offset_re_fragment +
r'\s*(\d*\s*)\{\{' +
r'([^}\\]*(?:(?:\}?\\.|\}[^}])[^}\\]*)*)' +
r'\}\}')
"""Matches the string 'marked @objc'."""
marked_objc_re = re.compile(r'marked @objc')
"""Matches any non-none fix-it expectation."""
fixit_re = re.compile(r'{{\d+-\d+=[^}]*}}')
def adjust_comments(offset, inserted_attr, comment_str):
"""Replace expected-errors with expected-remarks, and make other adjustments
to diagnostics so that they reflect access notes."""
prefix = u"{{ignored access note: "
suffix = u"; did not implicitly add '" + inserted_attr + "' to this }}"
adjusted = expected_other_diag_re.sub(lambda m: u"expected-" + m.group(1) +
offsetify(offset, m.group(2)),
comment_str)
adjusted = expected_error_re.sub(lambda m: u"expected-remark" +
offsetify(offset, m.group(1)) + " " +
m.group(2) + prefix + m.group(3) +
suffix,
adjusted)
adjusted = marked_objc_re.sub(u"marked @objc by an access note", adjusted)
adjusted = fixit_re.sub(u"{{none}}", adjusted)
return u"// [expectations adjusted] " + adjusted
#
# Writing attrs to access notes
#
def move_at_objc_to_access_note(access_notes_file, arg, maybe_bad, offset,
access_note_name):
"""Write an @objc attribute into an access notes file, then return the
string that will replace the attribute and trailing comment."""
is_bad = (maybe_bad == "bad-")
access_notes_file.write(u"""
- Name: '{}'
ObjC: true""".format(access_note_name))
if arg:
access_notes_file.write(u"""
ObjCName: '{}'""".format(arg))
# Default to shifting expected diagnostics down 1 line.
if offset is None:
offset = 1
inserted_attr = u"@objc"
if arg:
inserted_attr += u"(" + arg + u")"
replacement = u"// access-note-adjust" + offsetify(offset) + \
u"{{" + inserted_attr + "}} [attr moved] "
if not is_bad:
replacement += u"expected-remark{{implicitly added '" + inserted_attr + \
u"' to this }} expected-note{{add '" + inserted_attr + \
u"' explicitly to silence this warning}}"
return replacement
#
# Matching lines
#
"""Matches '@objc(foo) // access-note-move{{access-note-name}}'
or '@objc // bad-access-note-move{{access-note-name}}'"""
access_note_move_re = re.compile(r'@objc(?:\(([\w:]+)\))?[ \t]*' +
r'//[ \t]*(bad-)?access-note-move' +
offset_re_fragment +
r'\{\{([^}]*)\}\}')
"""Matches // access-note-adjust{{@objc}} <comment>"""
adjust_comment_re = re.compile(r'//[ \t]*access-note-adjust' + offset_re_fragment +
r'\{\{([^}]*)\}\}[ \t]*(.*)')
def replacer(fn, *args):
"""Returns a lambda which calls fn with args, followed by the groups from
the match passed to the lambda."""
return lambda m: fn(*(args + m.groups()))
main()
|