Spaces:
Sleeping
Sleeping
| # ============================================================================== | |
| # 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. | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |