-
Notifications
You must be signed in to change notification settings - Fork 0
/
PML_Preprocessor.py
100 lines (89 loc) · 4.28 KB
/
PML_Preprocessor.py
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
import re, os
# NOTE: All line numbers in this class refer to a first line having the number zero.
# Editors normally start with line numbe 1, so error messages have to correct for this.
class PML_Preprocessor():
def __init__(self, rootDir):
"""
Args:
rootDir (str): Root directory to use for includes <@include>
if the path in <@include> starts with '/'
"""
self.rootDir = rootDir
self.incl_patt = re.compile('@include.\"')
self.quot_patt = re.compile('\"(.*?)\"')
self.cmnt_patt = re.compile('//.*\n?')
self.outLineNr = 0
self.lineNrTrack = {}
self.includeStack = []
self.stream = ''
def process(self, path):
self.includeStack.append(path)
self.process_includes(path)
def isIncludeStatement(self, line):
isInclude = False
syntaxError = False
path = None
match = self.incl_patt.search(line)
if match:
isInclude = True
inQuotations = self.quot_patt.search(line)
if inQuotations:
path = inQuotations[0][1:-1]
else:
syntaxError = True
else:
path = None
return isInclude, syntaxError, path
def process_includes(self, path):
with open(path) as fp:
self.lineNrTrack[self.outLineNr] = (0,path)
for localLine, line in enumerate(fp):
# remove single line comments to avoid commented @include,
# but keep the line to simplify line counting
line = re.sub(self.cmnt_patt, '\n', line)
if line != '\n':
# check for @include and include lines if required
isInclude, syntaxError, inclPath = self.isIncludeStatement(line)
if isInclude:
if syntaxError:
errorText = 'Syntax error in file {file} on line {line}, col {col}: {msg}'.format(
file = path, line = localLine+1, col = 0, msg = line)
raise Exception(errorText)
else:
# if the first character of path is '/', add path to the root directory
if inclPath[0] == '/':
inclPath = os.path.join(
self.rootDir,
os.path.join(*inclPath[1:].split('/'))
)
# check for recursive inclusion
if inclPath in self.includeStack:
errorText = 'Error in file {file} on line {line}, col {col}: {msg}'.format(
file = path, line = localLine+1, col = 0, msg = 'attempt to @include recursively')
raise Exception(errorText)
else:
inclPath = os.path.join(
os.path.dirname(path),
inclPath
)
self.includeStack.append(inclPath)
# get all lines from included file
self.process_includes(inclPath)
# replace @include line by empty line to simplify line counting
line = "\n"
self.lineNrTrack[self.outLineNr] = (localLine,path)
self.includeStack.pop()
# push line in output stream
self.stream += line
self.outLineNr += 1
def getStream(self):
return self.stream
def getLineNrTrack(self):
return self.lineNrTrack
def trackDownLineNr(self, lineNr):
# The antlr4 error message is already corrected for first line numbber equal to one.
# find largest line number in lineNrTrack smaller than lineNr
index = max( line for line, content in self.lineNrTrack.items() if line < lineNr )
locaLineNr = self.lineNrTrack[index][0]+(lineNr-index)
localFile = self.lineNrTrack[index][1]
return localFile, locaLineNr