# error handling
import os
import atexit
import tempfile
import inspect
import re
import weakref
from collections import *
from pyrser import meta
Severity = meta.enum('INFO', 'WARNING', 'ERROR')
[docs]class LocationInfo:
[docs] def __init__(self, filepath: str, line: int, col: int, size: int=1):
self.filepath = filepath
self.line = line
self.col = col
self.size = size
@staticmethod
[docs] def from_stream(stream: 'Stream', is_error=False) -> 'LocationInfo':
if stream._name is None and is_error is True:
(fh, stream._name) = tempfile.mkstemp()
tmpf = os.fdopen(fh, 'w')
tmpf.write(stream._content)
tmpf.close()
atexit.register(os.remove, stream._name)
loc = LocationInfo(
stream._name,
stream._cursor.lineno,
stream._cursor.col_offset
)
return loc
@staticmethod
[docs] def from_maxstream(stream: 'Stream', is_error=False) -> 'LocationInfo':
if stream._name is None:
(fh, stream._name) = tempfile.mkstemp()
tmpf = os.fdopen(fh, 'w')
tmpf.write(stream._content)
tmpf.close()
atexit.register(os.remove, stream._name)
loc = LocationInfo(
stream._name,
stream._cursor._maxline,
stream._cursor._maxcol
)
return loc
@staticmethod
[docs] def from_here(pos=1):
f = inspect.currentframe()
fcaller = inspect.getouterframes(f)[pos]
rstr = r'(\s+).'
cl = re.compile(rstr)
call = fcaller[4][0]
m = cl.match(call)
current_file = os.path.abspath(fcaller[1])
li = LocationInfo(current_file, fcaller[2], len(m.group(1)) + 1)
return li
[docs] def get_content(self) -> str:
f = open(self.filepath, 'r')
lines = list(f)
f.close()
txtline = lines[self.line - 1]
if txtline[-1] != '\n':
txtline += '\n'
indent = ' ' * (self.col - 1)
if self.size != 1:
indent += '~' * (self.size)
else:
indent += '^'
txt = "from {f} at line:{l} col:{c} :\n{content}{i}".format(
f=self.filepath,
content=txtline,
l=self.line,
c=self.col,
i=indent
)
return txt
[docs]class Notification:
"""
Just One notification
"""
[docs] def __init__(self, severity: Severity, msg: str,
location: LocationInfo=None, details: str=None):
self.severity = severity
self.location = location
self.msg = msg
self.details = details
[docs] def get_content(self, with_locinfos=False, with_details=False) -> str:
sevtxt = ""
txt = "{s} : {msg}\n".format(
s=Severity.rmap[self.severity].lower(),
msg=self.msg
)
if with_locinfos and self.location is not None:
txt += self.location.get_content()
if with_details and self.details is not None:
txt += self.details
return txt
[docs]class Diagnostic(Exception):
"""
The diagnostic object is use to handle easily
all errors/warnings/infos in a compiler that you could
encounter. Error while parsing, Error while type checking etc...
You could use different severity for your notification.
"""
[docs] def __init__(self):
self.logs = []
[docs] def __bool__(self):
return self.have_errors
[docs] def __str__(self) -> str:
return self.get_content(with_details=True)
[docs] def notify(self, severity: Severity, msg: str,
location: LocationInfo=None, details: str=None) -> int:
nfy = Notification(severity, msg, location, details)
self.logs.append(nfy)
return len(self.logs) - 1
[docs] def add(self, n: Notification) -> int:
if not isinstance(n, Notification):
raise TypeError("Must be a notification")
self.logs.append(n)
return len(self.logs) - 1
[docs] def get_content(self, with_locinfos=True, with_details=False) -> str:
ls = []
for v in self.logs:
ls.append(v.get_content(with_locinfos, with_details))
txt = ('=' * 79) + '\n'
txt += ('\n' + ('-' * 79) + '\n').join(ls)
txt += '\n' + ('-' * 79)
return txt
[docs] def get_infos(self) -> {Severity, int}:
infos = dict()
for s in Severity.map.values():
infos[s] = 0
for v in self.logs:
s = v.severity
infos[s] += 1
return infos
@property
def have_errors(self) -> bool:
for v in self.logs:
if v.severity == Severity.ERROR:
return True