-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathlint_source
executable file
·171 lines (133 loc) · 4.87 KB
/
lint_source
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
#!/usr/bin/env python3
"""
Performs a 'linting' of the assembly, ensuring that formatting errors do not make it into
the final output.
"""
import getopt
import re
import sys
# The input file to be linted.
INPUT_FILE = None
# Whether to abort the linting process on encountering any error.
ABORT_ON_ERROR = False
# The maxmimum length of any line.
MAX_LENGTH_LINE = 80
# The maximum length of a label.
MAX_LENGTH_IDENTIFIER = 36
# The prefix for local labels.
LOCAL_LABEL_PREFIX = "."
def lint_line(line):
"""
Processes an individual line of source code, checking for formatting errors.
"""
# Check the length of a comment line.
REGEX_LINE_COMMENT = r"^;.*"
if re.match(REGEX_LINE_COMMENT, line):
if len(line.rstrip()) > MAX_LENGTH_LINE:
return (False, 'Comment line too long')
# Check the total length of the line.
if len(line.rstrip()) > MAX_LENGTH_LINE:
return (False, 'Line too long')
# Check the maximum length of each label.
REGEX_LABEL = r"([.\w?]+):"
label_match = re.match(REGEX_LABEL, line)
if label_match:
if len(label_match.group(1)) > MAX_LENGTH_IDENTIFIER:
return (False, f"Label '{label_match.group(1)}' too long")
return (True, None)
def check_for_duplicate_labels(file_text):
"""
Search the source text for duplicate label identifiers.
"""
# A map of duplicate labels -> The number of occurences.
label_count = {}
# The regex to use when searching for all labels.
REGEX_LABEL = r"^({}?[\w?]+:)".format(LOCAL_LABEL_PREFIX)
# Iterate over all labels, adding each to the label count map.
all_labels = re.findall(REGEX_LABEL, file_text, flags=re.MULTILINE)
for label_name in all_labels:
if not label_name in label_count:
label_count[label_name] = 1
else:
label_count[label_name] = label_count[label_name] + 1
return label_count
def check_for_unnecessary_labels(file_text):
"""
Search the source text for labels that are defined, but not used.
"""
unnecessary_labels = []
# The regex to use when searching for all labels.
REGEX_LABEL = r"^({}?[\w?]+):".format(LOCAL_LABEL_PREFIX)
# Iterate over all labels, searching for labels that are only found once.
all_labels = re.findall(REGEX_LABEL, file_text, flags=re.MULTILINE)
for label_name in all_labels:
start = 0
count = 0
while start := file_text.find(label_name, start) + 1:
count += 1
if count == 1:
unnecessary_labels.append(label_name)
return unnecessary_labels
def lint_file():
"""
Lints the input file.
"""
try:
with open(INPUT_FILE, 'r') as input_file:
# The current line number.
line_number = 1
# The total number of errors encountered.
error_count = 0
source_lines = input_file.readlines()
for source_line in source_lines:
(lint_result, lint_error) = lint_line(source_line)
if not lint_result:
print(f"Error: '{lint_error}' on line {line_number}", file=sys.stderr)
error_count = error_count + 1
if ABORT_ON_ERROR:
exit(1)
line_number = line_number + 1
# 'Rewind' the file stream, and check for duplicate identifiers.
input_file.seek(0)
source_text = input_file.read()
label_count = check_for_duplicate_labels(source_text)
for label_name in label_count:
if label_count[label_name] > 1:
error_count = error_count + 1
print(
f"Error: Duplicate label: '{label_name}' - ",
f"Encountered {label_count[label_name]} times."
)
unnecessary_labels = check_for_unnecessary_labels(source_text)
for label_name in unnecessary_labels:
error_count = error_count + 1
print(f"Error: Unnecessary label: '{label_name}'")
print(f"Found {error_count} errors in total.")
except FileNotFoundError:
print("Error: Unable to open assembler files. Exiting.", file=sys.stderr)
exit(1)
def print_usage():
"""Prints script usage to STDOUT."""
print("Usage: lint_source --input_file <string>")
if __name__ == "__main__":
try:
OPTS, ARGS = getopt.getopt(
sys.argv[1:],
"h",
[
"input_file=",
]
)
except getopt.GetoptError:
print_usage()
sys.exit(2)
for opt, arg in OPTS:
if opt == '-h':
print_usage()
sys.exit()
elif opt == "--input_file":
INPUT_FILE = arg
if INPUT_FILE is None:
print("No input file provided! Exiting.")
exit(1)
lint_file()