UNPKG

@gobstones/gobstones-parser

Version:
841 lines (731 loc) 27.7 kB
/* eslint-disable camelcase */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable quote-props */ /* istanbul ignore file */ const keyword = (palabra: string): string => 'la palabra clave "' + palabra + '"'; function pluralize(n: number, singular: string, plural: string) { if (n === 0) { return 'ningún ' + singular; } else if (n === 1) { return 'un ' + singular; } else { return n.toString() + ' ' + plural; } } // Only for typing purposes // eslint-disable-next-line @typescript-eslint/ban-types const toFunc = (x: string | Function): Function => x as Function; function ordinalNumber(n: number): string { const units = [ '', 'primer', 'segundo', 'tercer', 'cuarto', 'quinto', 'sexto', 'séptimo', 'octavo', 'noveno' ]; if (n >= 1 && n <= 9) { return units[n]; } else { return '#' + n.toString(); } } function describeType(type: any): string[] { if (type.isInteger()) { return ['m', 'número', 'números']; } else if (type.isBoolean()) { return ['m', 'booleano', 'booleanos']; } else if (type.isColor()) { return ['m', 'color', 'colores']; } else if (type.isDirection()) { return ['f', 'dirección', 'direcciones']; } else if (type.isList() && type.contentType.isAny()) { return ['f', 'lista', 'listas']; } else if (type.isList()) { const description = describeType(type.contentType); if (description === undefined) { return undefined; } else { const plural = description[2]; return ['f', 'lista de ' + plural, 'listas de ' + plural]; } } else { return undefined; } } function describeTypeSingular(type: { toString: () => string }): string { const description = describeType(type); if (description === undefined) { return type.toString(); } else { const singular = description[1]; return singular; } } function typeAsNoun(type: { toString: () => string }): string { const description = describeType(type); if (description === undefined) { return 'un valor de tipo ' + type.toString(); } else { const gender = description[0]; const singular = description[1]; if (gender === 'm') { return 'un ' + singular; } else { return 'una ' + singular; } } } function typeAsQualifierSingular(type: { toString: () => string }): string { const description = describeType(type); if (description === undefined) { return 'de tipo ' + type.toString(); } else { const gender = description[0]; const singular = description[1]; if (gender === 'm') { return 'un ' + singular; } else { return 'una ' + singular; } } } function typeAsQualifierPlural(type: { toString: () => string }): string { const description = describeType(type); if (description === undefined) { return 'de tipo ' + type.toString(); } else { const gender = description[0]; const plural = description[2]; if (gender === 'm') { return plural; } else { return plural; } } } function listOfTypes(types: any) { const typeStrings = []; for (const type of types) { typeStrings.push(describeTypeSingular(type)); } return typeStrings.join(', '); } function openingDelimiterName(delimiter: string) { if (delimiter === '(' || delimiter === ')') { return 'un paréntesis abierto "("'; } else if (delimiter === '[' || delimiter === ']') { return 'un corchete abierto "["'; } else if (delimiter === '{' || delimiter === '}') { return 'una llave abierta "{"'; } else { return delimiter; } } function formatTypes(string: string | any[], type1: any, type2: any) { let result = ''; for (let i = 0; i < string.length; i++) { if (string[i] === '%' && i + 1 < string.length) { if (string[i + 1] === '%') { result += '%'; i++; } else if (string[i + 1] === '1') { result += typeAsNoun(type1); i++; } else if (string[i + 1] === '2') { result += typeAsNoun(type2); i++; } else { result += '%'; } } else { result += string[i]; } } return result; } // eslint-disable-next-line @typescript-eslint/ban-types export const LOCALE_ES: Record<string, string | Function> = { /* Descriptions of syntactic constructions and tokens */ definition: 'una definición (de programa, función, procedimiento, o tipo)', pattern: 'un patrón (comodín "_", constructor aplicado a variables, o tupla)', statement: 'un comando', expression: 'una expresión', 'procedure call': 'una invocación a un procedimiento', 'field name': 'el nombre de un campo', T_EOF: 'el final del archivo', T_NUM: 'un número', T_STRING: 'una cadena (string)', T_UPPERID: 'un identificador con mayúsculas', T_LOWERID: 'un identificador con minúsculas', T_PROGRAM: keyword('program'), T_INTERACTIVE: keyword('interactive'), T_PROCEDURE: keyword('procedure'), T_FUNCTION: keyword('function'), T_RETURN: keyword('return'), T_IF: keyword('if'), T_THEN: keyword('then'), T_ELSE: keyword('else'), T_REPEAT: keyword('repeat'), T_FOREACH: keyword('foreach'), T_IN: keyword('in'), T_WHILE: keyword('while'), T_SWITCH: keyword('switch'), T_TO: keyword('to'), T_LET: keyword('let'), T_NOT: keyword('not'), T_DIV: keyword('div'), T_MOD: keyword('mod'), T_TYPE: keyword('type'), T_IS: keyword('is'), T_CHOOSE: keyword('choose'), T_WHEN: keyword('when'), T_OTHERWISE: keyword('otherwise'), T_MATCHING: keyword('matching'), T_SELECT: keyword('select'), T_ON: keyword('on'), T_RECORD: keyword('record'), T_VARIANT: keyword('variant'), T_CASE: keyword('case'), T_FIELD: keyword('field'), T_UNDERSCORE: 'un guión bajo ("_")', T_LPAREN: 'un paréntesis izquierdo ("(")', T_RPAREN: 'un paréntesis derecho (")")', T_LBRACE: 'una llave izquierda ("{")', T_RBRACE: 'una llave derecha ("}")', T_LBRACK: 'un corchete izquierdo ("[")', T_RBRACK: 'un corchete derecho ("]")', T_COMMA: 'una coma (",")', T_SEMICOLON: 'un punto y coma (";")', T_RANGE: 'un separador de rango ("..")', T_GETS: 'una flecha hacia la izquierda ("<-")', T_PIPE: 'una barra vertical ("|")', T_ARROW: 'una flecha ("->")', T_ASSIGN: 'un operador de asignación (":=")', T_EQ: 'una comparación por igualdad ("==")', T_NE: 'una comparación por desigualdad ("/=")', T_LE: 'un menor o igual ("<=")', T_GE: 'un mayor o igual (">=")', T_LT: 'un menor estricto ("<")', T_GT: 'un mayor estricto (">")', T_AND: 'el "y" lógico ("&&")', T_OR: 'el "o" lógico ("||")', T_CONCAT: 'el operador de concatenación de listas ("++")', T_PLUS: 'el operador de suma ("+")', T_MINUS: 'el operador de resta ("-")', T_TIMES: 'el operador de producto ("*")', T_POW: 'el operador de potencia ("^")', /* Local name categories */ LocalVariable: 'variable', LocalIndex: 'índice', LocalParameter: 'parámetro', /* Descriptions of value types */ V_Integer: 'un número', V_String: 'una cadena', V_Tuple: 'una tupla', V_List: 'una lista', V_Structure: 'una estructura', /* Lexer */ 'errmsg:unclosed-multiline-comment': 'El comentario se abre pero nunca se cierra.', 'errmsg:unclosed-string-constant': 'La comilla que abre no tiene una comilla que cierra correspondiente.', // eslint-disable-next-line max-len 'errmsg:numeric-constant-should-not-have-leading-zeroes': `Las constantes numéricas no se pueden escribir con ceros a la izquierda.`, // eslint-disable-next-line max-len 'errmsg:identifier-must-start-with-alphabetic-character': `Los identificadores deben empezar con un caracter alfabético (a...z,A...Z).`, 'errmsg:unknown-token': (symbol: string) => 'Símbolo desconocido en la entrada: "' + symbol + '".', 'warning:empty-pragma': 'Directiva pragma vacía.', 'warning:unknown-pragma': (pragmaName: string) => 'Directiva pragma desconocida: "' + pragmaName + '".', 'errmsg:unmatched-opening-delimiter': (delimiter: any) => 'Se encontró ' + openingDelimiterName(delimiter) + ' pero nunca se cierra.', 'errmsg:unmatched-closing-delimiter': (delimiter: string) => 'Se encontró un "' + delimiter + '" ' + 'pero no había ' + openingDelimiterName(delimiter) + '.', 'errmsg:unknown-language-option': (option: string) => 'Opción desconocida. "' + option + '".', /* Parser */ 'errmsg:empty-source': 'El programa está vacío.', 'errmsg:expected-but-found': (expected: string, found: string) => `Se esperaba ${expected}. Se encontró: ${found}.`, 'errmsg:pattern-number-cannot-be-negative-zero': 'El patrón numérico no puede ser "-0".', 'errmsg:return-tuple-cannot-be-empty': 'El return tiene que devolver algo.', 'errmsg:pattern-tuple-cannot-be-singleton': 'El patrón para una tupla no puede tener una sola componente. ' + 'Las tuplas tienen 0, 2, 3, o más componentes, pero no 1.', 'errmsg:assignment-tuple-cannot-be-singleton': 'La asignación a una tupla no puede constar de una sola componente. ' + 'Las tuplas tienen 0, 2, 3, o más componentes, pero no 1.', 'errmsg:operators-are-not-associative': (op1: string, op2: string) => 'La expresión usa ' + op1 + ' y ' + op2 + ', pero estos operadores no se pueden asociar. ' + 'Quizás faltan paréntesis.', 'errmsg:obsolete-tuple-assignment': 'Se esperaba un comando pero se encontró un paréntesis izquierdo. ' + 'Nota: la sintaxis de asignación de tuplas "(x1, ..., xN) := y" ' + 'está obsoleta. Usar "let (x1, ..., xN) := y".', /* Linter */ 'errmsg:program-already-defined': (pos1: string, pos2: string) => 'Ya había un programa definido en ' + pos1 + '.\n' + 'No se puede definir un programa en ' + pos2 + '.', 'errmsg:procedure-already-defined': (name: string, pos1: string, pos2: string) => 'El procedimiento "' + name + '" está definido dos veces: ' + 'en ' + pos1 + ' y en ' + pos2 + '.', 'errmsg:function-already-defined': (name: string, pos1: string, pos2: string) => 'La función "' + name + '" está definida dos veces: ' + 'en ' + pos1 + ' y en ' + pos2 + '.', 'errmsg:type-already-defined': (name: string, pos1: string, pos2: string) => `El tipo "${name}" está definido dos veces: en ${pos1} y en ${pos2}.`, 'errmsg:constructor-already-defined': (name: string, pos1: string, pos2: string) => 'El constructor "' + name + '" está definido dos veces: ' + 'en ' + pos1 + ' y en ' + pos2 + '.', 'errmsg:repeated-field-name': (constructorName: string, fieldName: string) => 'El campo "' + fieldName + '" no puede estar repetido ' + 'para el constructor "' + constructorName + '".', 'errmsg:function-and-field-cannot-have-the-same-name': ( name: string, posFunction: string, posField: string ) => 'El nombre "' + name + '" se usa ' + 'para una función en ' + posFunction + ' y ' + 'para un campo en ' + posField + '.', 'errmsg:source-should-have-a-program-definition': /* Note: the code may actually be completely empty, but * we avoid this technicality since the message could be * confusing. */ 'El código debe tener una definición de "program { ... }".', 'errmsg:procedure-should-not-have-return': (name: string) => `El procedimiento "${name}" no debería tener un comando "return".`, 'errmsg:function-should-have-return': (name: string) => 'La función "' + name + '" debería tener un comando "return".', 'errmsg:return-statement-not-allowed-here': 'El comando "return" solo puede aparecer como el último comando ' + 'de una función o como el último comando del programa.', 'errmsg:local-name-conflict': ( name: string, oldCat: string, oldPos: string, newCat: string, newPos: string ) => 'Conflicto de nombres: "' + name + '" se usa dos veces: ' + 'como ' + oldCat + ' en ' + oldPos + ', y ' + 'como ' + newCat + ' en ' + newPos + '.', 'errmsg:repeated-variable-in-tuple-assignment': (name: string) => `La variable "${name}" está repetida en la asignación de tuplas.`, 'errmsg:constructor-used-as-procedure': (name: string, type: string) => 'El procedimiento "' + name + '" no está definido. ' + 'El nombre "' + name + '" es el nombre de un constructor ' + 'del tipo "' + type + '".', 'errmsg:undefined-procedure': (name: string) => 'El procedimiento "' + name + '" no está definido.', 'errmsg:undefined-function': (name: string) => 'La función "' + name + '" no está definida.', 'errmsg:procedure-arity-mismatch': (name: string, expected: any, received: any) => '"El procedimiento "' + name + '" espera recibir ' + toFunc(LOCALE_ES['<n>-parameters'])(expected) + ' pero se lo invoca con ' + toFunc(LOCALE_ES['<n>-arguments'])(received) + '.', 'errmsg:function-arity-mismatch': (name: string, expected: any, received: any) => 'La función "' + name + '" espera recibir ' + toFunc(LOCALE_ES['<n>-parameters'])(expected) + ' pero se la invoca con ' + toFunc(LOCALE_ES['<n>-arguments'])(received) + '.', 'errmsg:structure-pattern-arity-mismatch': (name: string, expected: any, received: any) => 'El constructor "' + name + '" tiene ' + toFunc(LOCALE_ES['<n>-fields'])(expected) + ' pero el patrón tiene ' + toFunc(LOCALE_ES['<n>-parameters'])(received) + '.', 'errmsg:type-used-as-constructor'(name: string, constructorNames: any[]) { let msg: string; if (constructorNames.length === 0) { msg = '(no tiene constructores).'; } else if (constructorNames.length === 1) { msg = '(tiene un constructor: ' + constructorNames[0] + ').'; } else { msg = '(sus constructores son: ' + constructorNames.join(', ') + ').'; } return ( 'El constructor "' + name + '" no está definido. ' + 'El nombre "' + name + '" es el nombre de un tipo ' + msg ); }, 'errmsg:procedure-used-as-constructor': (name: string) => 'El constructor "' + name + '" no está definido. ' + 'El nombre "' + name + '" es el nombre de un procedimiento.', 'errmsg:undeclared-constructor': (name: string) => 'El constructor "' + name + '" no está definido.', 'errmsg:wildcard-pattern-should-be-last': 'El comodín "_" debe estar en la última rama.', 'errmsg:variable-pattern-should-be-last': (name: string) => 'El patrón variable "' + name + '" tiene debe estar en la última rama.', 'errmsg:numeric-pattern-repeats-number': (number: string) => 'Hay dos ramas distintas para el número "' + number + '".', 'errmsg:structure-pattern-repeats-constructor': (name: string) => 'Hay dos ramas distintas para el constructor "' + name + '".', 'errmsg:structure-pattern-repeats-tuple-arity': (arity: { toString: () => string }) => 'Hay dos ramas distintas para las tuplas de ' + arity.toString() + ' componentes.', 'errmsg:structure-pattern-repeats-timeout': 'Hay dos ramas distintas para el TIMEOUT.', 'errmsg:pattern-does-not-match-type': (expectedType: string, patternType: string) => 'Los patrones tienen que ser todos del mismo tipo. ' + 'El patrón debería ser de tipo ' + expectedType + 'pero es de tipo ' + patternType + '.', 'errmsg:patterns-in-interactive-program-must-be-events': 'Los patrones de un "interactive program" deben ser eventos.', 'errmsg:patterns-in-interactive-program-cannot-be-variables': 'El patrón no puede ser una variable.', 'errmsg:patterns-in-switch-must-not-be-events': 'El patrón no puede ser un evento.', 'errmsg:structure-construction-repeated-field': (constructorName: string, fieldName: string) => 'El campo "' + fieldName + '" está repetido en ' + 'la instanciación del constructor "' + constructorName + '".', 'errmsg:structure-construction-invalid-field': (constructorName: string, fieldName: string) => 'El campo "' + fieldName + '" no es un campo válido ' + 'para el constructor "' + constructorName + '".', 'errmsg:structure-construction-missing-field': (constructorName: string, fieldName: string) => 'Falta darle valor al campo "' + fieldName + '" ' + 'del constructor "' + constructorName + '".', 'errmsg:structure-construction-cannot-be-an-event': (constructorName: string) => 'El constructor "' + constructorName + '" corresponde a un ' + 'evento, y solamente se puede manejar implícitamente ' + 'en un programa interactivo (el usuario no puede construir ' + 'instancias).', 'errmsg:forbidden-extension-destructuring-foreach': 'El índice de la repetición indexada debe ser un identificador.', ['errmsg:forbidden-extension-allow-recursion']: (cycle: any) => { const msg = []; for (const call of cycle) { msg.push( ' ' + call.caller + ' llama a ' + call.callee + ' (' + call.location.startPos.filename.toString() + ':' + call.location.startPos.line.toString() + ':' + call.location.startPos.column.toString() + ')' ); } return ( 'La recursión está deshabilitada. ' + 'Hay un ciclo en las invocaciones:\n' + msg.join('\n') ); }, 'errmsg:patterns-in-foreach-must-not-be-events': 'El patrón de un foreach no puede ser un evento.', /* Runtime errors (virtual machine) */ 'errmsg:ellipsis': 'El programa todavía no está completo.', 'errmsg:undefined-variable': (variableName: string) => 'La variable "' + variableName + '" no está definida.', 'errmsg:too-few-arguments': (routineName: string) => 'Faltan argumentos para "' + routineName + '".', 'errmsg:expected-structure-but-got': (constructorName: string, valueTag: string) => 'Se esperaba una estructura construida ' + 'con el constructor "' + constructorName + '", ' + 'pero se recibió ' + valueTag + '.', 'errmsg:expected-constructor-but-got': ( constructorNameExpected: string, constructorNameReceived: string ) => 'Se esperaba una estructura construida ' + 'con el constructor "' + constructorNameExpected + '", ' + 'pero el constructor recibido es ' + constructorNameReceived + '".', 'errmsg:incompatible-types-on-assignment': (variableName: string, oldType: any, newType: any) => 'La variable "' + variableName + '" ' + 'contenía ' + typeAsNoun(oldType) + ', ' + 'no se le puede asignar ' + typeAsNoun(newType) + '".', 'errmsg:incompatible-types-on-list-creation': ( index: { toString: () => string }, oldType: any, newType: any ) => 'Todos los elementos de una lista deben ser del mismo tipo. ' + 'Los elementos son ' + typeAsQualifierPlural(oldType) + ', ' + 'pero el elemento en la posición ' + index.toString() + ' ' + 'es ' + typeAsQualifierSingular(newType) + '.', 'errmsg:incompatible-types-on-structure-update': ( fieldName: string, oldType: any, newType: any ) => 'El campo "' + fieldName + '" es ' + typeAsQualifierSingular(oldType) + '. ' + 'No se lo puede actualizar con ' + typeAsNoun(newType) + '.', 'errmsg:expected-tuple-value-but-got': (receivedType: any) => 'Se esperaba una tupla pero se recibió ' + typeAsNoun(receivedType) + '.', 'errmsg:tuple-component-out-of-bounds': ( size: { toString: () => string }, index: { toString: () => string } ) => 'Índice fuera de rango. ' + 'La tupla es de tamaño ' + size.toString() + ' y ' + 'el índice es ' + index.toString() + '.', 'errmsg:expected-structure-value-but-got': (receivedType: any) => 'Se esperaba una estructura pero se recibió ' + typeAsNoun(receivedType) + '.', 'errmsg:structure-field-not-present': (fieldNames: any[], missingFieldName: string) => 'La estructura no tiene un campo "' + missingFieldName + '". ' + 'Los campos son: [' + fieldNames.join(', ') + '].', 'errmsg:primitive-does-not-exist': (primitiveName: string) => `La operación primitiva "${primitiveName}" no existe o no está disponible.`, 'errmsg:primitive-arity-mismatch': (name: string, expected: any, received: any) => 'La operación "' + name + '" espera recibir ' + toFunc(LOCALE_ES['<n>-parameters'])(expected) + ' pero se la invoca con ' + toFunc(LOCALE_ES['<n>-arguments'])(received) + '.', 'errmsg:primitive-argument-type-mismatch'( name: string, parameterIndex: any, numArgs: number, expectedType: any, receivedType: any ) { let msg = 'El '; if (numArgs > 1) { msg += ordinalNumber(parameterIndex) + ' '; } msg += 'parámetro '; msg += 'de "' + name + '" '; msg += 'debería ser ' + typeAsQualifierSingular(expectedType) + ' '; msg += 'pero es ' + typeAsQualifierSingular(receivedType) + '.'; return msg; }, 'errmsg:expected-value-of-type-but-got': (expectedType: any, receivedType: any) => 'Se esperaba ' + typeAsNoun(expectedType) + ' ' + 'pero se recibió ' + typeAsNoun(receivedType) + '.', 'errmsg:expected-value-of-some-type-but-got': (expectedTypes: any, receivedType: any) => 'Se esperaba un valor de alguno de los siguientes tipos: ' + listOfTypes(expectedTypes) + '. ' + 'Pero se recibió ' + typeAsNoun(receivedType) + '.', 'errmsg:expected-values-to-have-compatible-types': (type1: any, type2: any) => 'Los tipos de las expresiones no coinciden: ' + 'la primera es ' + typeAsQualifierSingular(type1) + ' ' + 'y la segunda es ' + typeAsQualifierSingular(type2) + '.', 'errmsg:switch-does-not-match': 'El valor analizado no coincide con ninguna de las ramas del switch.', 'errmsg:foreach-pattern-does-not-match': 'El elemento no coincide con el patrón esperado por el foreach.', 'errmsg:cannot-divide-by-zero': 'No se puede dividir por cero.', 'errmsg:negative-exponent': 'El exponente de la potencia no puede ser negativo.', 'errmsg:list-cannot-be-empty': 'La lista no puede ser vacía.', 'errmsg:timeout': (millisecs: { toString: () => string }) => 'La ejecución del programa demoró más de ' + millisecs.toString() + 'ms.', /* Typecheck */ 'errmsg:typecheck-failed': (errorMessage: any, type1: any, type2: any) => formatTypes(errorMessage, type1, type2), /* Board operations */ 'errmsg:cannot-move-to': (dirName: string) => 'No se puede mover hacia la dirección ' + dirName + ': cae afuera del tablero.', 'errmsg:cannot-remove-stone': (dirName: string) => 'No se puede sacar una bolita de color ' + dirName + ': no hay bolitas de ese color.', /* Runtime */ 'TYPE:Integer': 'Number', 'TYPE:String': 'String', 'TYPE:Tuple': '', 'TYPE:List': 'List', 'TYPE:Event': 'Event', 'CONS:INIT': 'INIT', 'CONS:TIMEOUT': 'TIMEOUT', 'TYPE:Bool': 'Bool', 'CONS:False': 'False', 'CONS:True': 'True', 'TYPE:Color': 'Color', 'CONS:Color0': 'Azul', 'CONS:Color1': 'Negro', 'CONS:Color2': 'Rojo', 'CONS:Color3': 'Verde', 'TYPE:Dir': 'Dir', 'CONS:Dir0': 'Norte', 'CONS:Dir1': 'Este', 'CONS:Dir2': 'Sur', 'CONS:Dir3': 'Oeste', 'PRIM:TypeCheck': 'TypeCheck', 'PRIM:BOOM': 'BOOM', 'PRIM:boom': 'boom', 'PRIM:PutStone': 'Poner', 'PRIM:RemoveStone': 'Sacar', 'PRIM:Move': 'Mover', 'PRIM:GoToEdge': 'IrAlBorde', 'PRIM:EmptyBoardContents': 'VaciarTablero', 'PRIM:numStones': 'nroBolitas', 'PRIM:anyStones': 'hayBolitas', 'PRIM:canMove': 'puedeMover', 'PRIM:next': 'siguiente', 'PRIM:prev': 'previo', 'PRIM:opposite': 'opuesto', 'PRIM:minBool': 'minBool', 'PRIM:maxBool': 'maxBool', 'PRIM:minColor': 'minColor', 'PRIM:maxColor': 'maxColor', 'PRIM:minDir': 'minDir', 'PRIM:maxDir': 'maxDir', 'PRIM:isEmpty': 'esVacía', 'PRIM:head': 'primero', 'PRIM:tail': 'sinElPrimero', 'PRIM:oldTail': 'resto', 'PRIM:init': 'comienzo', 'PRIM:last': 'último', /* Helpers */ '<alternative>': (strings: any[]) => 'alguna de las siguientes alternativas:\n' + strings.map((s: string) => ' ' + s).join('\n'), '<position>': ( filename: string, line: { toString: () => string }, column: { toString: () => string } ) => filename + ':' + line.toString() + ':' + column.toString(), '<n>-parameters': (n: any) => pluralize(n, 'parámetro', 'parámetros'), '<n>-arguments': (n: any) => pluralize(n, 'argumento', 'argumentos'), '<n>-fields': (n: any) => pluralize(n, 'campo', 'campos'), '<pattern-type>'(patternType: string) { if (patternType === 'Event') { return 'evento del programa interactivo'; } else if (patternType.substring(0, 7) === '_TUPLE_') { return 'tupla de ' + patternType.substring(7) + ' componentes'; } else { return patternType; } } };