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)