153
|
1 from collections import namedtuple
|
312
|
2 import logging
|
153
|
3
|
312
|
4 """
|
|
5 Error handling routines
|
|
6 Diagnostic utils
|
|
7 Source location structures
|
|
8 """
|
287
|
9
|
194
|
10 class Token:
|
396
|
11 """
|
|
12 Token is used in the lexical analyzer. The lexical analyzer takes
|
|
13 a text and splits it into tokens.
|
|
14 """
|
|
15 def __init__(self, typ, val, loc):
|
194
|
16 self.typ = typ
|
|
17 self.val = val
|
|
18 assert type(loc) is SourceLocation
|
|
19 self.loc = loc
|
292
|
20
|
194
|
21 def __repr__(self):
|
|
22 return 'Token({0}, {1})'.format(self.typ, self.val)
|
191
|
23
|
287
|
24
|
163
|
25 class SourceLocation:
|
287
|
26 def __init__(self, filename, row, col, ln):
|
|
27 self.filename = filename
|
249
|
28 self.row = row
|
|
29 self.col = col
|
|
30 self.length = ln
|
|
31
|
|
32 def __repr__(self):
|
293
|
33 return '{}, {}, {}'.format(self.filename, self.row, self.col)
|
163
|
34
|
287
|
35
|
153
|
36 SourceRange = namedtuple('SourceRange', ['p1', 'p2'])
|
312
|
37
|
|
38
|
|
39 class CompilerError(Exception):
|
|
40 def __init__(self, msg, loc=None):
|
|
41 self.msg = msg
|
|
42 self.loc = loc
|
|
43 if loc:
|
|
44 assert type(loc) is SourceLocation, \
|
|
45 '{0} must be SourceLocation'.format(type(loc))
|
|
46 self.row = loc.row
|
|
47 self.col = loc.col
|
|
48 else:
|
|
49 self.row = self.col = 0
|
|
50
|
|
51 def __repr__(self):
|
|
52 return '"{}"'.format(self.msg)
|
|
53
|
|
54
|
|
55 class DiagnosticsManager:
|
|
56 def __init__(self):
|
|
57 self.diags = []
|
|
58 self.sources = {}
|
|
59 self.logger = logging.getLogger('diagnostics')
|
|
60
|
|
61 def addSource(self, name, src):
|
396
|
62 self.logger.debug('Adding source, filename="{}"'.format(name))
|
312
|
63 self.sources[name] = src
|
|
64
|
|
65 def addDiag(self, d):
|
353
|
66 self.logger.error(str(d.msg))
|
312
|
67 self.diags.append(d)
|
|
68
|
|
69 def error(self, msg, loc):
|
|
70 self.addDiag(CompilerError(msg, loc))
|
|
71
|
|
72 def clear(self):
|
|
73 del self.diags[:]
|
|
74 self.sources.clear()
|
|
75
|
|
76 def printErrors(self):
|
|
77 if len(self.diags) > 0:
|
|
78 print('{0} Errors'.format(len(self.diags)))
|
|
79 for d in self.diags:
|
|
80 self.printError(d)
|
|
81
|
|
82 def printError(self, e):
|
|
83 def printLine(row, txt):
|
396
|
84 print(str(row) + ':' + txt)
|
312
|
85 print('==============')
|
|
86 if not e.loc:
|
|
87 print('Error: {0}'.format(e))
|
|
88 else:
|
|
89 if e.loc.filename not in self.sources:
|
|
90 print('Error: {0}'.format(e))
|
|
91 return
|
|
92 print("File: {}".format(e.loc.filename))
|
|
93 source = self.sources[e.loc.filename]
|
|
94 lines = source.split('\n')
|
|
95 ro, co = e.row, e.col
|
|
96 prerow = ro - 2
|
|
97 if prerow < 1:
|
|
98 prerow = 1
|
|
99 afterrow = ro + 3
|
|
100 if afterrow > len(lines):
|
|
101 afterrow = len(lines)
|
|
102
|
|
103 # print preceding source lines:
|
|
104 for r in range(prerow, ro):
|
|
105 printLine(r, lines[r-1])
|
|
106 # print source line containing error:
|
|
107 printLine(ro, lines[ro-1])
|
|
108 print(' '*(len(str(ro)+':')+co-1) + '^ Error: {0}'.format(e.msg))
|
|
109 # print trailing source line:
|
|
110 for r in range(ro+1, afterrow+1):
|
|
111 printLine(r, lines[r-1])
|
|
112 print('==============')
|