Tesseractplayground / _internal /tesseract_errors.py
Fran23135's picture
Upload 409 files
3637f8d verified
Raw
History Blame Contribute Delete
50.4 kB
# ==============================================================================
# tesseract_errors.py β€” Sistema de Errores Completo para Tesseract
# ==============================================================================
# Uso:
# from tesseract_errors import (
# TesseractError, LangError, set_language,
# RangeBoundsError, TypeMismatchError, ...
# )
#
# set_language("es") o set_language("en") β€” por defecto "es"
# El --lang flag se maneja desde el archivo de parΓ‘metros CLI (externo a este)
# ==============================================================================
import unicodedata
# ──────────────────────────────────────────────────────────────────────────────
# I18N β€” Mensajes bilingΓΌe
# ──────────────────────────────────────────────────────────────────────────────
_LANG = "es"
def set_language(lang: str):
global _LANG
if lang not in ("es", "en"):
raise ValueError(f"Language '{lang}' not supported. Use 'es' or 'en'.")
_LANG = lang
def get_language() -> str:
return _LANG
_MESSAGES: dict = {
# ── LΓ©xico / Sintaxis ──────────────────────────────────────────────────────
"lex.invalid_char": {
"es": "carΓ‘cter invΓ‘lido '{char}' en la lΓ­nea {line}",
"en": "invalid character '{char}' at line {line}",
},
"lex.unterminated_string": {
"es": "cadena de texto sin cerrar desde la lΓ­nea {line}",
"en": "unterminated string starting at line {line}",
},
"syntax.unexpected_token": {
"es": "token inesperado '{token}', se esperaba '{expected}'",
"en": "unexpected token '{token}', expected '{expected}'",
},
"syntax.missing_token": {
"es": "se esperaba '{expected}' pero se encontrΓ³ fin de archivo",
"en": "expected '{expected}' but reached end of file",
},
"indent.expected": {
"es": "se esperaba una indentaciΓ³n mayor aquΓ­",
"en": "expected a deeper indentation here",
},
"indent.unexpected": {
"es": "indentaciΓ³n inesperada",
"en": "unexpected indentation",
},
"block.unclosed": {
"es": "bloque abierto con '{open}' nunca fue cerrado",
"en": "block opened with '{open}' was never closed",
},
"block.orphan_close": {
"es": "cierre de bloque '{close}' sin bloque abierto correspondiente",
"en": "closing '{close}' without a matching open block",
},
# ── Nombres y Scope ────────────────────────────────────────────────────────
"name.undefined": {
"es": "'{name}' no estΓ‘ definido",
"en": "'{name}' is not defined",
},
"name.redeclaration": {
"es": "'{name}' ya fue declarado en este scope",
"en": "'{name}' was already declared in this scope",
},
"name.shadow_warning": {
"es": "'{name}' oculta una variable del scope padre",
"en": "'{name}' shadows a variable from the outer scope",
},
"scope.invalid_access": {
"es": "no se puede acceder a '{name}' desde este scope",
"en": "cannot access '{name}' from this scope",
},
"import.not_found": {
"es": "mΓ³dulo '{module}' no fue encontrado",
"en": "module '{module}' was not found",
},
"import.member_missing": {
"es": "'{member}' no existe en el mΓ³dulo '{module}'",
"en": "'{member}' does not exist in module '{module}'",
},
# ── Tipos ─────────────────────────────────────────────────────────────────
"type.mismatch": {
"es": "se esperaba '{expected}', se recibiΓ³ '{got}'",
"en": "expected '{expected}', got '{got}'",
},
"type.inference_failed": {
"es": "no se pudo inferir el tipo de '{name}'",
"en": "could not infer the type of '{name}'",
},
"type.explicit_conflict": {
"es": "el tipo declarado '{declared}' no coincide con el valor '{got}'",
"en": "declared type '{declared}' does not match value type '{got}'",
},
"type.return_mismatch": {
"es": "la funciΓ³n '{func}' debe retornar '{expected}', retornΓ³ '{got}'",
"en": "function '{func}' must return '{expected}', returned '{got}'",
},
"type.null_not_allowed": {
"es": "el tipo '{type}' no permite valores null",
"en": "type '{type}' does not allow null values",
},
"type.unsupported_cast": {
"es": "no se puede convertir '{from_type}' a '{to_type}'",
"en": "cannot cast '{from_type}' to '{to_type}'",
},
# ── Variables ─────────────────────────────────────────────────────────────
"var.const_reassign": {
"es": "'{name}' es una constante y no puede reasignarse",
"en": "'{name}' is a constant and cannot be reassigned",
},
"var.uninitialized": {
"es": "'{name}' fue declarado pero nunca inicializado",
"en": "'{name}' was declared but never initialized",
},
"var.invalid_assignment": {
"es": "no se puede asignar '{value}' a la variable '{name}'",
"en": "cannot assign '{value}' to variable '{name}'",
},
# ── Funciones ─────────────────────────────────────────────────────────────
"func.undefined": {
"es": "la funciΓ³n '{name}' no estΓ‘ definida",
"en": "function '{name}' is not defined",
},
"func.arity": {
"es": "'{name}' espera {expected} argumento(s), se recibieron {got}",
"en": "'{name}' expects {expected} argument(s), got {got}",
},
"func.arg_type": {
"es": "argumento '{param}' en '{func}': se esperaba '{expected}', se recibiΓ³ '{got}'",
"en": "argument '{param}' in '{func}': expected '{expected}', got '{got}'",
},
"func.return_outside": {
"es": "'return' usado fuera de una funciΓ³n",
"en": "'return' used outside of a function",
},
"func.recursion_limit": {
"es": "lΓ­mite de recursiΓ³n alcanzado en '{name}' (mΓ‘ximo: {limit})",
"en": "recursion limit reached in '{name}' (maximum: {limit})",
},
"func.missing_return": {
"es": "la funciΓ³n '{name}' tiene tipo de retorno '{type}' pero no siempre retorna un valor",
"en": "function '{name}' has return type '{type}' but does not always return a value",
},
"func.return_mismatch": {
"es": "la funciΓ³n '{name}' debe retornar '{expected}', pero retornΓ³ '{got}'",
"en": "function '{name}' must return '{expected}', but returned '{got}'",
},
# ── Operaciones ───────────────────────────────────────────────────────────
"op.division_zero": {
"es": "no se puede dividir entre cero",
"en": "cannot divide by zero",
},
"op.invalid_operand": {
"es": "operando '{got}' invΓ‘lido para el operador '{op}'",
"en": "invalid operand '{got}' for operator '{op}'",
},
"op.type_mismatch": {
"es": "operaciΓ³n '{op}' no soportada entre '{left}' y '{right}'",
"en": "operation '{op}' not supported between '{left}' and '{right}'",
},
"op.overflow": {
"es": "el resultado de la operaciΓ³n excede el lΓ­mite del tipo '{type}'",
"en": "operation result exceeds the limit for type '{type}'",
},
"op.bitwise_invalid": {
"es": "operaciΓ³n bit a bit '{op}' no aplicable a '{type}'",
"en": "bitwise operation '{op}' not applicable to '{type}'",
},
"op.modulo_zero": {
"es": "mΓ³dulo por cero no estΓ‘ permitido",
"en": "modulo by zero is not allowed",
},
# ── Condiciones / Control de Flujo ────────────────────────────────────────
"cond.not_bool": {
"es": "la condiciΓ³n debe evaluar a booleano, se recibiΓ³ '{got}'",
"en": "condition must evaluate to boolean, got '{got}'",
},
"flow.break_outside": {
"es": "'break' usado fuera de un bucle o switch",
"en": "'break' used outside of a loop or switch",
},
"flow.continue_outside": {
"es": "'continue' usado fuera de un bucle",
"en": "'continue' used outside of a loop",
},
"flow.unreachable": {
"es": "cΓ³digo inalcanzable detectado despuΓ©s de '{stmt}'",
"en": "unreachable code detected after '{stmt}'",
},
# ── Colecciones / Indexing ─────────────────────────────────────────────────
"index.out_of_range": {
"es": "Γ­ndice {index} fuera de rango, la colecciΓ³n tiene longitud {length}",
"en": "index {index} out of range, collection length is {length}",
},
"index.negative": {
"es": "Γ­ndice negativo {index} no estΓ‘ permitido en este contexto",
"en": "negative index {index} is not allowed in this context",
},
"key.not_found": {
"es": "la clave '{key}' no existe en el diccionario",
"en": "key '{key}' does not exist in the dictionary",
},
"iter.not_iterable": {
"es": "'{type}' no es iterable",
"en": "'{type}' is not iterable",
},
"collection.limit_exceeded": {
"es": "la colecciΓ³n '{name}' tiene un lΓ­mite de {limit} elemento(s)",
"en": "collection '{name}' has a limit of {limit} element(s)",
},
# ── Acceso Profundo arr[0:2:1] ────────────────────────────────────────────
"deep.out_of_range": {
"es": "acceso profundo fallΓ³ en nivel {depth}: Γ­ndice {index} fuera de rango (longitud: {length})",
"en": "deep access failed at depth {depth}: index {index} out of range (length: {length})",
},
"deep.key_not_found": {
"es": "acceso profundo fallΓ³ en nivel {depth}: clave '{key}' no existe",
"en": "deep access failed at depth {depth}: key '{key}' does not exist",
},
"deep.not_collection": {
"es": "acceso profundo fallΓ³ en nivel {depth}: se esperaba una colecciΓ³n, se encontrΓ³ '{got}'",
"en": "deep access failed at depth {depth}: expected a collection, found '{got}'",
},
"deep.null_traversal": {
"es": "acceso profundo fallΓ³ en nivel {depth}: el valor es null, no se puede continuar",
"en": "deep access failed at depth {depth}: value is null, cannot continue",
},
"deep.path_malformed": {
"es": "la ruta de acceso profundo '{path}' estΓ‘ mal formada",
"en": "deep access path '{path}' is malformed",
},
"deep.type_mismatch": {
"es": "acceso profundo en nivel {depth}: se usΓ³ Γ­ndice de tipo '{got}', se esperaba '{expected}'",
"en": "deep access at depth {depth}: used index of type '{got}', expected '{expected}'",
},
# ── Rangos ────────────────────────────────────────────────────────────────
"range.bounds": {
"es": "rango invΓ‘lido: el inicio '{start}' no puede ser mayor que el fin '{end}'",
"en": "invalid range: start '{start}' cannot be greater than end '{end}'",
},
"range.type_incompatible": {
"es": "rango entre '{left}' y '{right}' no estΓ‘ permitido (tipos incompatibles)",
"en": "range between '{left}' and '{right}' is not allowed (incompatible types)",
},
"range.float_precision": {
"es": "valor flotante '{value}' excede 1 decimal permitido en rangos (ej: 0.5..1.5)",
"en": "float value '{value}' exceeds 1 decimal allowed in ranges (e.g. 0.5..1.5)",
},
"range.unicode_invalid": {
"es": "cΓ³digo unicode '{code}' no existe o no estΓ‘ asignado en el estΓ‘ndar Unicode",
"en": "unicode code '{code}' does not exist or is unassigned in the Unicode standard",
},
"range.unicode_order": {
"es": "rango string invΓ‘lido: '{start}' (U+{start_cp}) es mayor que '{end}' (U+{end_cp})",
"en": "invalid string range: '{start}' (U+{start_cp}) is greater than '{end}' (U+{end_cp})",
},
"range.unicode_case_mix": {
"es": "rango string no puede mezclar mayΓΊsculas y minΓΊsculas ('{start}'..'{end}')",
"en": "string range cannot mix uppercase and lowercase ('{start}'..'{end}')",
},
"range.null_mixed": {
"es": "solo se permite null..null, no se puede mezclar null con otro tipo",
"en": "only null..null is allowed, cannot mix null with another type",
},
"range.empty": {
"es": "el rango '{start}'..'{end}' produce una secuencia vacΓ­a",
"en": "range '{start}'..'{end}' produces an empty sequence",
},
# ── OOP / Clases ──────────────────────────────────────────────────────────
"class.undefined": {
"es": "la clase '{name}' no estΓ‘ declarada",
"en": "class '{name}' is not declared",
},
"class.attribute_missing": {
"es": "el atributo '{attr}' no existe en la clase '{cls}'",
"en": "attribute '{attr}' does not exist in class '{cls}'",
},
"class.method_missing": {
"es": "el mΓ©todo '{method}' no existe en '{cls}' ni en su jerarquΓ­a",
"en": "method '{method}' does not exist in '{cls}' or its hierarchy",
},
"class.inheritance_invalid": {
"es": "'{child}' no puede extender '{parent}': herencia invΓ‘lida",
"en": "'{child}' cannot extend '{parent}': invalid inheritance",
},
"class.abstract_instantiation": {
"es": "no se puede instanciar la clase abstracta '{name}'",
"en": "cannot instantiate abstract class '{name}'",
},
"class.final_extension": {
"es": "no se puede extender la clase final '{name}'",
"en": "cannot extend final class '{name}'",
},
"class.interface_not_implemented": {
"es": "'{cls}' no implementa el mΓ©todo '{method}' requerido por '{interface}'",
"en": "'{cls}' does not implement method '{method}' required by '{interface}'",
},
# ── Structs ───────────────────────────────────────────────────────────────
"struct.undefined": {
"es": "el struct '{name}' no estΓ‘ declarado",
"en": "struct '{name}' is not declared",
},
"struct.field_missing": {
"es": "el campo '{field}' no existe en el struct '{struct}'",
"en": "field '{field}' does not exist in struct '{struct}'",
},
"struct.field_const_reassign": {
"es": "el campo const '{field}' en struct '{struct}' no puede reasignarse",
"en": "const field '{field}' in struct '{struct}' cannot be reassigned",
},
"struct.field_type_mismatch": {
"es": "campo '{field}' en struct '{struct}': se esperaba '{expected}', se recibiΓ³ '{got}'",
"en": "field '{field}' in struct '{struct}': expected '{expected}', got '{got}'",
},
# ── Async ─────────────────────────────────────────────────────────────────
"async.await_outside": {
"es": "'await' usado fuera de una funciΓ³n async",
"en": "'await' used outside of an async function",
},
"async.non_async_call": {
"es": "no se puede usar 'await' en la funciΓ³n no-async '{name}'",
"en": "cannot use 'await' on non-async function '{name}'",
},
# ── Runtime General ───────────────────────────────────────────────────────
"runtime.stack_overflow": {
"es": "desbordamiento de pila: demasiadas llamadas anidadas",
"en": "stack overflow: too many nested calls",
},
"runtime.not_implemented": {
"es": "'{feature}' no estΓ‘ implementado aΓΊn en este intΓ©rprete",
"en": "'{feature}' is not yet implemented in this interpreter",
},
"runtime.io_error": {
"es": "error de I/O: {detail}",
"en": "I/O error: {detail}",
},
"runtime.invalid_ast": {
"es": "el AST recibido no tiene el nodo raΓ­z 'Program'",
"en": "received AST does not have the root node 'Program'",
},
"runtime.throw_signal": {
"es": "excepciΓ³n lanzada [{cls}]: {msg}",
"en": "thrown exception [{cls}]: {msg}",
},
}
def msg(key: str, **kwargs) -> str:
"""
Obtiene el mensaje en el idioma activo y aplica los kwargs como formato.
Si la clave no existe devuelve el key mismo para no silenciar errores.
"""
entry = _MESSAGES.get(key)
if entry is None:
return key
template = entry.get(_LANG, entry.get("es", key))
try:
return template.format(**kwargs)
except KeyError:
return template
# ──────────────────────────────────────────────────────────────────────────────
# Clases Base
# ──────────────────────────────────────────────────────────────────────────────
class TesseractError(Exception):
"""
RaΓ­z de todos los errores del lenguaje Tesseract.
Preserva lΓ­nea, columna y funciΓ³n para el traceback.
Compatible con InterpreterError existente: hereda de Exception.
"""
def __init__(self, message: str, file: str = None,
line: int = None, col: int = None,
function: str = None):
self.message = message
self.file = file or "<unknown>"
self.line = line
self.col = col
self.function = function
super().__init__(message)
def location_str(self) -> str:
s = f'File "{self.file}"'
if self.line is not None:
s += f", line {self.line}"
if self.col is not None:
s += f", col {self.col}"
if self.function:
s += f", in {self.function}"
return s
def __str__(self):
return f"{self.location_str()}\n{type(self).__name__}: {self.message}"
# Alias de compatibilidad β€” las partes antiguas del intΓ©rprete que atrapan
# InterpreterError tambiΓ©n atraparΓ‘n TesseractError si se hace:
# InterpreterError = TesseractError (ver secciΓ³n de migraciΓ³n al final)
LangError = TesseractError
# ──────────────────────────────────────────────────────────────────────────────
# LΓ©xico / Sintaxis
# ──────────────────────────────────────────────────────────────────────────────
class LexError(TesseractError):
"""Token invΓ‘lido o carΓ‘cter desconocido."""
class SyntaxError(TesseractError):
"""Estructura sintΓ‘ctica invΓ‘lida."""
class IndentError(TesseractError):
"""IndentaciΓ³n incorrecta (modo bloque)."""
class BlockError(TesseractError):
"""Bloque sin cerrar o llave/palabra huΓ©rfana."""
# ──────────────────────────────────────────────────────────────────────────────
# Nombres y Scope
# ──────────────────────────────────────────────────────────────────────────────
class NameError(TesseractError):
"""Variable o sΓ­mbolo no definido."""
class RedeclarationError(TesseractError):
"""SΓ­mbolo declarado mΓ‘s de una vez en el mismo scope."""
class ScopeError(TesseractError):
"""Acceso a sΓ­mbolo fuera de su scope vΓ‘lido."""
class ImportError(TesseractError):
"""MΓ³dulo o miembro no encontrado al importar."""
# ──────────────────────────────────────────────────────────────────────────────
# Tipos
# ──────────────────────────────────────────────────────────────────────────────
class TypeError(TesseractError):
"""Error de tipo general."""
class TypeMismatchError(TypeError):
"""Tipo recibido no coincide con el esperado."""
class TypeInferenceError(TypeError):
"""El intΓ©rprete no pudo inferir el tipo de una expresiΓ³n."""
class TypeExplicitConflictError(TypeError):
"""El tipo declarado explΓ­citamente contradice el valor asignado."""
class ReturnTypeError(TypeError):
"""El valor retornado no coincide con el tipo de retorno declarado."""
class NullTypeError(TypeError):
"""Se recibiΓ³ null donde el tipo no lo permite."""
class UnsupportedCastError(TypeError):
"""ConversiΓ³n de tipo no soportada."""
# ──────────────────────────────────────────────────────────────────────────────
# Variables
# ──────────────────────────────────────────────────────────────────────────────
class AssignmentError(TesseractError):
"""AsignaciΓ³n invΓ‘lida."""
class ConstError(TesseractError):
"""ReasignaciΓ³n de una constante."""
class UninitializedError(TesseractError):
"""Uso de variable antes de inicializarla."""
# ──────────────────────────────────────────────────────────────────────────────
# Funciones
# ──────────────────────────────────────────────────────────────────────────────
class FunctionError(TesseractError):
"""Error general relacionado con funciones."""
class UndefinedFunctionError(FunctionError):
"""FunciΓ³n llamada que no existe."""
class ArityError(FunctionError):
"""NΓΊmero de argumentos incorrecto."""
class ArgumentTypeError(FunctionError):
"""Tipo de un argumento no coincide con el parΓ‘metro."""
class ReturnError(FunctionError):
"""'return' usado fuera de funciΓ³n o falta de return en funciΓ³n tipada."""
class RecursionLimitError(FunctionError):
"""LΓ­mite de recursiΓ³n alcanzado."""
# ──────────────────────────────────────────────────────────────────────────────
# Operaciones
# ──────────────────────────────────────────────────────────────────────────────
class OperationError(TesseractError):
"""Error aritmΓ©tico o de operador general."""
class InvalidOperandError(OperationError):
"""Operando invΓ‘lido para el operador dado."""
class DivisionByZeroError(OperationError):
"""DivisiΓ³n o mΓ³dulo por cero."""
class OverflowError(OperationError):
"""Resultado fuera del rango del tipo."""
class BitwiseError(OperationError):
"""OperaciΓ³n bit a bit en tipo no compatible."""
# ──────────────────────────────────────────────────────────────────────────────
# Condiciones / Control de Flujo
# ──────────────────────────────────────────────────────────────────────────────
class ConditionError(TesseractError):
"""CondiciΓ³n que no evalΓΊa a booleano."""
class BreakOutsideLoopError(TesseractError):
"""'break' fuera de bucle o switch."""
class ContinueOutsideLoopError(TesseractError):
"""'continue' fuera de bucle."""
class UnreachableCodeError(TesseractError):
"""CΓ³digo inalcanzable detectado."""
# ──────────────────────────────────────────────────────────────────────────────
# Colecciones / Indexing
# ──────────────────────────────────────────────────────────────────────────────
class IndexError(TesseractError):
"""Índice fuera de rango en lista o tupla."""
def __init__(self, message: str, index=None, length=None, **kwargs):
self.index = index
self.length = length
super().__init__(message, **kwargs)
class KeyError(TesseractError):
"""Clave no encontrada en diccionario."""
def __init__(self, message: str, key=None, **kwargs):
self.key = key
super().__init__(message, **kwargs)
class IterationError(TesseractError):
"""Intento de iterar sobre un tipo no iterable."""
class CollectionLimitError(TesseractError):
"""Se excediΓ³ el lΓ­mite de elementos de una colecciΓ³n."""
# ──────────────────────────────────────────────────────────────────────────────
# Acceso Profundo arr[0:2:1]
# ──────────────────────────────────────────────────────────────────────────────
class DeepAccessError(TesseractError):
"""Base para todos los errores de acceso profundo."""
def __init__(self, message: str, depth: int = None,
path: list = None, **kwargs):
self.depth = depth # nivel donde fallΓ³ (0-based)
self.path = path or [] # ruta completa ej. [0, 2, 1]
super().__init__(message, **kwargs)
def path_str(self) -> str:
return " β†’ ".join(str(p) for p in self.path)
class DeepIndexOutOfRangeError(DeepAccessError):
"""Índice fuera de rango en algún nivel de la ruta."""
def __init__(self, message: str, depth: int, index, length: int,
path: list = None, **kwargs):
self.index = index
self.length = length
super().__init__(message, depth=depth, path=path, **kwargs)
class DeepKeyError(DeepAccessError):
"""Clave inexistente en un dict anidado."""
def __init__(self, message: str, depth: int, key,
path: list = None, **kwargs):
self.key = key
super().__init__(message, depth=depth, path=path, **kwargs)
class DeepNotCollectionError(DeepAccessError):
"""En algΓΊn nivel la ruta encontrΓ³ un valor que no es colecciΓ³n."""
def __init__(self, message: str, depth: int, got_type: str,
path: list = None, **kwargs):
self.got_type = got_type
super().__init__(message, depth=depth, path=path, **kwargs)
class DeepNullError(DeepAccessError):
"""La ruta pasΓ³ por un valor null."""
class DeepPathError(DeepAccessError):
"""La ruta de acceso profundo estΓ‘ mal formada."""
class DeepTypeMismatchError(DeepAccessError):
"""Se usΓ³ Γ­ndice entero en dict o clave string en lista."""
def __init__(self, message: str, depth: int, expected: str, got: str,
path: list = None, **kwargs):
self.expected = expected
self.got = got
super().__init__(message, depth=depth, path=path, **kwargs)
# ──────────────────────────────────────────────────────────────────────────────
# Rangos
# ──────────────────────────────────────────────────────────────────────────────
class RangeError(TesseractError):
"""Base para todos los errores de rango."""
class RangeBoundsError(RangeError):
"""Inicio mayor que fin."""
def __init__(self, message: str, start=None, end=None, **kwargs):
self.start = start
self.end = end
super().__init__(message, **kwargs)
class RangeTypeError(RangeError):
"""Tipos incompatibles en el rango (int+str, float+str)."""
def __init__(self, message: str, left_type: str = None,
right_type: str = None, **kwargs):
self.left_type = left_type
self.right_type = right_type
super().__init__(message, **kwargs)
class RangeFloatPrecisionError(RangeError):
"""Float con mΓ‘s de 1 decimal en rango."""
def __init__(self, message: str, value=None, **kwargs):
self.value = value
super().__init__(message, **kwargs)
class RangeUnicodeError(RangeError):
"""CΓ³digo unicode invΓ‘lido o no asignado."""
def __init__(self, message: str, code: str = None, **kwargs):
self.code = code
super().__init__(message, **kwargs)
class RangeUnicodeOrderError(RangeError):
"""El carΓ‘cter inicio tiene mayor codepoint unicode que el fin."""
def __init__(self, message: str, start_char: str = None,
end_char: str = None, **kwargs):
self.start_char = start_char
self.end_char = end_char
super().__init__(message, **kwargs)
class RangeUnicodeCaseMixError(RangeError):
"""Mezcla de mayΓΊsculas y minΓΊsculas en rango string."""
class RangeNullMixedError(RangeError):
"""null mezclado con otro tipo en rango (solo null..null es vΓ‘lido)."""
class RangeEmptyError(RangeError):
"""Rango vΓ‘lido pero que produce secuencia vacΓ­a."""
# ──────────────────────────────────────────────────────────────────────────────
# OOP / Clases
# ──────────────────────────────────────────────────────────────────────────────
class ClassError(TesseractError):
"""Error general de clase."""
class AttributeError(ClassError):
"""Atributo no existe en la instancia o clase."""
class MethodError(ClassError):
"""MΓ©todo no encontrado en la jerarquΓ­a."""
class InheritanceError(ClassError):
"""Herencia invΓ‘lida."""
class InstantiationError(ClassError):
"""Clase no puede ser instanciada (abstracta, u otra razΓ³n)."""
class FinalExtensionError(ClassError):
"""Intento de extender una clase final."""
class InterfaceError(ClassError):
"""Interfaz no implementada correctamente."""
# ──────────────────────────────────────────────────────────────────────────────
# Structs
# ──────────────────────────────────────────────────────────────────────────────
class StructError(TesseractError):
"""Error general de struct."""
class StructFieldError(StructError):
"""Campo inexistente o inaccesible en un struct."""
class StructFieldConstError(StructError):
"""ReasignaciΓ³n de campo const en un struct."""
class StructFieldTypeError(StructError):
"""Tipo incorrecto asignado a un campo de struct."""
# ──────────────────────────────────────────────────────────────────────────────
# Async
# ──────────────────────────────────────────────────────────────────────────────
class AsyncError(TesseractError):
"""Error relacionado con funciones async/await."""
class AwaitOutsideAsyncError(AsyncError):
"""'await' usado fuera de una funciΓ³n async."""
class NonAsyncAwaitError(AsyncError):
"""'await' aplicado a una funciΓ³n no-async."""
# ──────────────────────────────────────────────────────────────────────────────
# Runtime General
# ──────────────────────────────────────────────────────────────────────────────
class RuntimeError(TesseractError):
"""Error de tiempo de ejecuciΓ³n general."""
class StackOverflowError(RuntimeError):
"""Demasiadas llamadas anidadas."""
class IOError(RuntimeError):
"""Error de entrada/salida."""
class NotImplementedError(RuntimeError):
"""Funcionalidad no implementada aΓΊn."""
class InvalidASTError(RuntimeError):
"""El AST recibido tiene estructura invΓ‘lida."""
# ──────────────────────────────────────────────────────────────────────────────
# Helpers de construcciΓ³n β€” factories con linecolumn del AST
# ──────────────────────────────────────────────────────────────────────────────
def _lc(node: dict) -> dict:
"""
Extrae file/line/col desde el nodo AST.
El AST usa el subnodo 'linecolumn': {"line": N, "col": N, "end_line": N}
"""
lc = node.get("linecolumn", {}) if isinstance(node, dict) else {}
return {
"line": lc.get("line"),
"col": lc.get("col"),
}
def make_type_mismatch(expected: str, got: str, node: dict,
file: str = None, function: str = None) -> TypeMismatchError:
return TypeMismatchError(
msg("type.mismatch", expected=expected, got=got),
file=file, function=function, **_lc(node)
)
def make_arity_error(name: str, expected: int, got: int, node: dict,
file: str = None, function: str = None) -> ArityError:
return ArityError(
msg("func.arity", name=name, expected=expected, got=got),
file=file, function=function, **_lc(node)
)
def make_return_mismatch(name: str, expected: str, got: str, node: dict,
file: str = None, function: str = None) -> ReturnTypeError:
return ReturnTypeError(
msg("func.return_mismatch", name=name, expected=expected, got=got),
file=file, function=function, **_lc(node)
)
def make_undefined_name(name: str, node: dict,
file: str = None, function: str = None) -> NameError:
return NameError(
msg("name.undefined", name=name),
file=file, function=function, **_lc(node)
)
def make_const_error(name: str, node: dict,
file: str = None, function: str = None) -> ConstError:
return ConstError(
msg("var.const_reassign", name=name),
file=file, function=function, **_lc(node)
)
def make_division_zero(node: dict,
file: str = None, function: str = None) -> DivisionByZeroError:
return DivisionByZeroError(
msg("op.division_zero"),
file=file, function=function, **_lc(node)
)
def make_deep_out_of_range(depth: int, index, length: int,
path: list, node: dict,
file: str = None, function: str = None) -> DeepIndexOutOfRangeError:
return DeepIndexOutOfRangeError(
msg("deep.out_of_range", depth=depth, index=index, length=length),
depth=depth, index=index, length=length, path=path,
file=file, function=function, **_lc(node)
)
def make_range_bounds(start, end, node: dict,
file: str = None, function: str = None) -> RangeBoundsError:
return RangeBoundsError(
msg("range.bounds", start=start, end=end),
start=start, end=end,
file=file, function=function, **_lc(node)
)
def make_range_type(left_type: str, right_type: str, node: dict,
file: str = None, function: str = None) -> RangeTypeError:
return RangeTypeError(
msg("range.type_incompatible", left=left_type, right=right_type),
left_type=left_type, right_type=right_type,
file=file, function=function, **_lc(node)
)
def make_range_float_precision(value, node: dict,
file: str = None, function: str = None) -> RangeFloatPrecisionError:
return RangeFloatPrecisionError(
msg("range.float_precision", value=value),
value=value,
file=file, function=function, **_lc(node)
)
def make_range_unicode(code: str, node: dict,
file: str = None, function: str = None) -> RangeUnicodeError:
return RangeUnicodeError(
msg("range.unicode_invalid", code=code),
code=code,
file=file, function=function, **_lc(node)
)
# ──────────────────────────────────────────────────────────────────────────────
# resolve_deep β€” acceso profundo arr[0:2:1]
# ──────────────────────────────────────────────────────────────────────────────
def resolve_deep(collection, path: list, node: dict,
file: str = None, function: str = None):
"""
Resuelve un acceso profundo tipo arr[0:2:1] donde path = [0, 2, 1].
Lanza la excepciΓ³n DeepAccessError apropiada si algo falla.
"""
current = collection
for depth, index in enumerate(path):
# null
if current is None:
raise DeepNullError(
msg("deep.null_traversal", depth=depth),
depth=depth, path=path,
file=file, function=function, **_lc(node)
)
# debe ser colecciΓ³n
if not isinstance(current, (list, tuple, dict)):
raise DeepNotCollectionError(
msg("deep.not_collection", depth=depth,
got=type(current).__name__),
depth=depth, got_type=type(current).__name__, path=path,
file=file, function=function, **_lc(node)
)
# tipo de Γ­ndice correcto
if isinstance(current, (list, tuple)) and not isinstance(index, int):
raise DeepTypeMismatchError(
msg("deep.type_mismatch", depth=depth,
got=type(index).__name__, expected="int"),
depth=depth, expected="int", got=type(index).__name__,
path=path, file=file, function=function, **_lc(node)
)
if isinstance(current, dict) and isinstance(index, int):
raise DeepTypeMismatchError(
msg("deep.type_mismatch", depth=depth,
got="int", expected="key"),
depth=depth, expected="key", got="int",
path=path, file=file, function=function, **_lc(node)
)
# fuera de rango (lista/tupla)
if isinstance(current, (list, tuple)):
if index < 0 or index >= len(current):
raise DeepIndexOutOfRangeError(
msg("deep.out_of_range", depth=depth,
index=index, length=len(current)),
depth=depth, index=index, length=len(current),
path=path, file=file, function=function, **_lc(node)
)
# clave en dict
if isinstance(current, dict) and index not in current:
raise DeepKeyError(
msg("deep.key_not_found", depth=depth, key=index),
depth=depth, key=index, path=path,
file=file, function=function, **_lc(node)
)
current = current[index]
return current
# ──────────────────────────────────────────────────────────────────────────────
# resolve_unicode β€” para endpoints de rango string
# ──────────────────────────────────────────────────────────────────────────────
def resolve_unicode(value: str, node: dict,
file: str = None, function: str = None) -> str:
"""
Convierte 'u06FF' β†’ carΓ‘cter real.
Si el codepoint no existe o no estΓ‘ asignado lanza RangeUnicodeError.
"""
import re
if re.fullmatch(r'u[0-9A-Fa-f]{4,6}', value):
try:
cp = int(value[1:], 16)
char = chr(cp)
unicodedata.name(char) # lanza ValueError si no asignado
return char
except (ValueError, OverflowError):
raise make_range_unicode(value, node, file=file, function=function)
return value
# ──────────────────────────────────────────────────────────────────────────────
# validate_range β€” validaciΓ³n completa de un rango
# ──────────────────────────────────────────────────────────────────────────────
def validate_range(start, end, node: dict,
file: str = None, function: str = None):
"""
Valida las reglas de rango del lenguaje Tesseract.
Lanza la excepciΓ³n RangeError apropiada si algo viola las reglas.
"""
st = type(start).__name__
et = type(end).__name__
# null mezclado
if (start is None) != (end is None):
raise RangeNullMixedError(
msg("range.null_mixed"),
file=file, function=function, **_lc(node)
)
if start is None and end is None:
return # null..null es vΓ‘lido
# tipos incompatibles
_incompatible = {
("int", "str"), ("str", "int"),
("float", "str"), ("str", "float"),
}
# normalizar: bool -> int a efectos del rango
st_n = "int" if st == "bool" else st
et_n = "int" if et == "bool" else et
if (st_n, et_n) in _incompatible:
raise make_range_type(st_n, et_n, node, file=file, function=function)
# precisiΓ³n decimal en float
for val in (start, end):
if isinstance(val, float):
s = str(val)
decimals = len(s.split(".")[1]) if "." in s else 0
if decimals > 1:
raise make_range_float_precision(
val, node, file=file, function=function)
# bounds numΓ©ricos
if isinstance(start, (int, float)) and isinstance(end, (int, float)):
if start > end:
raise make_range_bounds(start, end, node,
file=file, function=function)
# rangos string
if isinstance(start, str) and isinstance(end, str):
sc = ord(start)
ec = ord(end)
# orden unicode
if sc > ec:
raise RangeUnicodeOrderError(
msg("range.unicode_order",
start=start, start_cp=f"{sc:04X}",
end=end, end_cp=f"{ec:04X}"),
start_char=start, end_char=end,
file=file, function=function, **_lc(node)
)
# mezcla de case
if (start.islower() and end.isupper()) or \
(start.isupper() and end.islower()):
raise RangeUnicodeCaseMixError(
msg("range.unicode_case_mix", start=start, end=end),
file=file, function=function, **_lc(node)
)
# ──────────────────────────────────────────────────────────────────────────────
# Formatter de Traceback β€” estilo Python
# ──────────────────────────────────────────────────────────────────────────────
def format_traceback(error: TesseractError,
call_stack: list,
source_lines: list = None) -> str:
"""
Genera un traceback completo al estilo Python con lΓ­nea de cΓ³digo y cursor.
call_stack: lista de tuplas (file, function, line)
mantenida por el intΓ©rprete en self._call_stack
source_lines: lista de lΓ­neas del archivo fuente (open(f).readlines())
"""
lines = ["Traceback (most recent call last):"]
for (c_file, c_func, c_line) in call_stack:
lines.append(f' File "{c_file}", line {c_line}, in {c_func}')
if source_lines and c_line and 1 <= c_line <= len(source_lines):
code_line = source_lines[c_line - 1].rstrip()
lines.append(f" {code_line}")
# Bloque final β€” archivo/lΓ­nea/col del error exacto
lines.append(f'\nFile "{error.file}"', )
loc_parts = []
if error.line is not None:
loc_parts.append(f"line {error.line}")
if error.col is not None:
loc_parts.append(f"col {error.col}")
if error.function:
loc_parts.append(f"in {error.function}")
if loc_parts:
lines[-1] += ", " + ", ".join(loc_parts)
# Mostrar la lΓ­nea de cΓ³digo exacta con cursor ^^^
if source_lines and error.line and 1 <= error.line <= len(source_lines):
code_line = source_lines[error.line - 1].rstrip()
lines.append(f" {code_line}")
if error.col is not None:
# cursor bajo la columna exacta
indent = len(code_line) - len(code_line.lstrip())
cursor_pos = max(error.col - 1, indent)
lines.append(" " + " " * cursor_pos + "^^^")
lines.append(f"{type(error).__name__}: {error.message}")
return "\n".join(lines)
# ──────────────────────────────────────────────────────────────────────────────
# NOTA DE MIGRACIΓ“N
# ──────────────────────────────────────────────────────────────────────────────
# Para que el bloque except InterpreterError en interpre.py siga funcionando
# sin tocar nada, aΓ±ade al inicio de interpre.py (despuΓ©s del import):
#
# from tesseract_errors import TesseractError
# InterpreterError = TesseractError
#
# Todas las subclases de TesseractError serΓ‘n atrapadas por ese except.
# El resto del intΓ©rprete no necesita cambios para funcionar.
# ──────────────────────────────────────────────────────────────────────────────