You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
91 lines
3.3 KiB
Python
91 lines
3.3 KiB
Python
2 years ago
|
from .defs import Span, LexingContext, Token, TokenType
|
||
|
from math import exp, log10, ceil
|
||
|
from typing import Iterable
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
def create_span_context_str(span: Span, message: str, color: str = '\033[31m'):
|
||
|
lines, offset_into_file, line_no = span.context.get_lines_containing(span)
|
||
|
relative_offset = span.start - offset_into_file
|
||
|
annotation_len = span.end - span.start
|
||
|
|
||
|
digit_len = ceil(log10(line_no + len(lines)))
|
||
|
if digit_len == 0:
|
||
|
digit_len = 1
|
||
|
|
||
|
output_str = ">>> In file {}:{}\n".format(span.source_name, line_no)
|
||
|
|
||
|
for i, source_line in enumerate(lines):
|
||
|
source_line = source_line[:relative_offset] + color + source_line[relative_offset:relative_offset+annotation_len] + '\033[0m' + source_line[relative_offset+annotation_len:]
|
||
|
output_str += '{:>{}d}: {}\n'.format(line_no + i, digit_len, source_line)
|
||
|
|
||
|
if relative_offset > len(source_line):
|
||
|
continue
|
||
|
# TODO: handle multi-line underlines
|
||
|
output_str += "{}{}{}{}\n".format(
|
||
|
color,
|
||
|
' ' * (relative_offset + digit_len + 2),
|
||
|
'^' * min(annotation_len, len(source_line) - relative_offset),
|
||
|
'\033[0m'
|
||
|
)
|
||
|
if annotation_len > len(source_line) - relative_offset:
|
||
|
relative_offset = 0
|
||
|
annotation_len -= len(source_line) - relative_offset
|
||
|
|
||
|
if message:
|
||
|
output_str += color
|
||
|
output_str += ' ' * (relative_offset + digit_len + 2) + '|\n'
|
||
|
for message_line in message.split("\n"):
|
||
|
output_str += ' ' * (relative_offset + digit_len + 2) + message_line + '\n'
|
||
|
|
||
|
return output_str + '\033[0m'
|
||
|
|
||
|
def print_warning(span: Span, message: str, color="\033[33m"):
|
||
|
print(create_span_context_str(span, "Warning: " + message, color))
|
||
|
|
||
|
|
||
|
class CompilerError(Exception):
|
||
|
span: Span
|
||
|
message: str
|
||
|
|
||
|
def __init__(self, msg: str, span: Span=None) -> None:
|
||
|
super().__init__((msg, span))
|
||
|
self.span = span
|
||
|
self.message = msg
|
||
|
|
||
|
|
||
|
def print_context_message(self):
|
||
|
if not self.span:
|
||
|
print("\n".join(">>> {}".format(line) for line in self.message.split('\n')))
|
||
|
else:
|
||
|
print(create_span_context_str(self.span, self.message))
|
||
|
|
||
|
|
||
|
class EndOfInputError(CompilerError):
|
||
|
def __init__(self,span: Span, search_str:str = None) -> None:
|
||
|
|
||
|
if search_str:
|
||
|
super().__init__(f"Unexpected end-of-input in {span.source_name} while scanning for {search_str}!", span)
|
||
|
else:
|
||
|
super().__init__(f"Unexpected end-of-input in {span.source_name}!", span)
|
||
|
|
||
|
class ParseError(CompilerError):
|
||
|
def __init__(self, msg: str, span: Span = None) -> None:
|
||
|
super().__init__(msg, span)
|
||
|
|
||
|
class InvalidTokenError(CompilerError):
|
||
|
def __init__(self, token: Token, expected_type: Iterable[str | TokenType] = None, message: str = None) -> None:
|
||
|
|
||
|
expected = ", expected {}".format(", ".join(f"{x}" for x in expected_type)) if expected_type else ""
|
||
|
|
||
|
super().__init__("Unexpected token {}{} {}".format(
|
||
|
token, expected, '\n' + message if message else ""
|
||
|
), token.span if token is not None else None)
|
||
|
|
||
|
|
||
|
class UnsupportedSyntaxError(CompilerError):
|
||
|
def __init__(self, token: Token, feature: str) -> None:
|
||
|
super().__init__("Unsupported syntax: {}".format(feature), token.span)
|
||
|
|