diff --git a/fontes/bibliotecas/biblioteca-global.ts b/fontes/bibliotecas/biblioteca-global.ts index 8715d9aa..fac787eb 100644 --- a/fontes/bibliotecas/biblioteca-global.ts +++ b/fontes/bibliotecas/biblioteca-global.ts @@ -1508,8 +1508,20 @@ export async function tamanho(interpretador: InterpretadorInterface, objeto: any const metodos = valorObjeto.metodos; let tamanho = 0; - if (metodos.inicializacao && metodos.inicializacao.eInicializador) { - tamanho = metodos.inicializacao.declaracao.parametros.length; + const metodoInicializacao = metodos.inicializacao; + if (metodoInicializacao && !Array.isArray(metodoInicializacao) && metodoInicializacao.eInicializador) { + tamanho = metodoInicializacao.declaracao.parametros.length; + } else if (Array.isArray(metodoInicializacao)) { + // Em caso de construtores sobrecarregados, `metodos.inicializacao` pode ser um array. + // Usamos o maior número de parâmetros entre as sobrecargas que são inicializadores. + for (const inicializador of metodoInicializacao) { + if (inicializador && inicializador.eInicializador && inicializador.declaracao?.parametros) { + const aridade = inicializador.declaracao.parametros.length; + if (aridade > tamanho) { + tamanho = aridade; + } + } + } } return Promise.resolve(tamanho); diff --git a/fontes/bibliotecas/dialetos/pitugues/biblioteca-global.ts b/fontes/bibliotecas/dialetos/pitugues/biblioteca-global.ts index 37a2f985..e59ea61c 100644 --- a/fontes/bibliotecas/dialetos/pitugues/biblioteca-global.ts +++ b/fontes/bibliotecas/dialetos/pitugues/biblioteca-global.ts @@ -1301,8 +1301,9 @@ export async function tamanho(interpretador: InterpretadorInterface, objeto: any const metodos = valorObjeto.metodos; let tamanho = 0; - if (metodos.inicializacao && metodos.inicializacao.eInicializador) { - tamanho = metodos.inicializacao.declaracao.parametros.length; + const metodoInicializacao = metodos.inicializacao; + if (metodoInicializacao && !Array.isArray(metodoInicializacao) && metodoInicializacao.eInicializador) { + tamanho = metodoInicializacao.declaracao.parametros.length; } return Promise.resolve(tamanho); diff --git a/fontes/interpretador/dialetos/egua-classico/interpretador-egua-classico.ts b/fontes/interpretador/dialetos/egua-classico/interpretador-egua-classico.ts index b5dae9f5..831b385c 100644 --- a/fontes/interpretador/dialetos/egua-classico/interpretador-egua-classico.ts +++ b/fontes/interpretador/dialetos/egua-classico/interpretador-egua-classico.ts @@ -487,8 +487,9 @@ export class InterpretadorEguaClassico implements InterpretadorInterface { if (entidadeChamada instanceof DeleguaFuncao) { parametros = entidadeChamada.declaracao.parametros; } else if (entidadeChamada instanceof DescritorTipoClasse) { - parametros = entidadeChamada.metodos.inicializacao - ? entidadeChamada.metodos.inicializacao.declaracao.parametros + const metodoInit = entidadeChamada.metodos.inicializacao; + parametros = metodoInit && !Array.isArray(metodoInit) + ? metodoInit.declaracao.parametros : []; } else { parametros = []; @@ -964,7 +965,7 @@ export class InterpretadorEguaClassico implements InterpretadorInterface { this.pilhaEscoposExecucao.definirVariavel('super', superClasse); } - const metodos = {}; + const metodos: { [nome: string]: DeleguaFuncao | DeleguaFuncao[] } = {}; const definirMetodos = declaracao.metodos; for (let i = 0; i < declaracao.metodos.length; i++) { const metodoAtual = definirMetodos[i]; @@ -975,7 +976,15 @@ export class InterpretadorEguaClassico implements InterpretadorInterface { undefined, eInicializador ); - metodos[metodoAtual.simbolo.lexema] = funcao; + const nomeMetodo = metodoAtual.simbolo.lexema; + if (metodos[nomeMetodo]) { + if (!Array.isArray(metodos[nomeMetodo])) { + metodos[nomeMetodo] = [metodos[nomeMetodo] as DeleguaFuncao]; + } + (metodos[nomeMetodo] as DeleguaFuncao[]).push(funcao); + } else { + metodos[nomeMetodo] = funcao; + } } const deleguaClasse = new DescritorTipoClasse(declaracao.simbolo, superClasse, metodos); diff --git a/fontes/interpretador/estruturas/descritor-tipo-classe.ts b/fontes/interpretador/estruturas/descritor-tipo-classe.ts index 9013c1a4..485a3c0c 100644 --- a/fontes/interpretador/estruturas/descritor-tipo-classe.ts +++ b/fontes/interpretador/estruturas/descritor-tipo-classe.ts @@ -3,8 +3,35 @@ import { ErroEmTempoDeExecucao } from '../../excecoes'; import { InterpretadorInterface, SimboloInterface } from '../../interfaces'; import { Chamavel } from './chamavel'; import { DeleguaFuncao } from './delegua-funcao'; +import { MetodoPolimorfico } from './metodo-polimorfico'; import { ObjetoDeleguaClasse } from './objeto-delegua-classe'; +const mapaDeNormalizacao: { [chave: string]: string } = { + 'numero': 'número', + 'logico': 'lógico', + 'funcao': 'função', + 'dicionario': 'dicionário', + 'modulo': 'módulo', +}; + +function normalizarTipo(tipo: string | undefined): string { + if (!tipo || tipo === 'qualquer') return 'qualquer'; + const tipoMinusculo = tipo.toLowerCase(); + return mapaDeNormalizacao[tipoMinusculo] || tipoMinusculo; +} + +function assinaturasIguais(a: DeleguaFuncao, b: DeleguaFuncao): boolean { + const paramsA = a.declaracao?.parametros || []; + const paramsB = b.declaracao?.parametros || []; + if (paramsA.length !== paramsB.length) return false; + for (let i = 0; i < paramsA.length; i++) { + if (normalizarTipo(paramsA[i].tipoDado) !== normalizarTipo(paramsB[i].tipoDado)) { + return false; + } + } + return true; +} + /** * Descritor de tipo de classe. Quando uma declaração de classe é visitada, o que * vai para a pilha de escopos de execução é esta estrutura. Quando uma nova instância @@ -13,7 +40,7 @@ import { ObjetoDeleguaClasse } from './objeto-delegua-classe'; export class DescritorTipoClasse extends Chamavel { simboloOriginal: SimboloInterface; superClasse: DescritorTipoClasse; - metodos: { [nome: string]: DeleguaFuncao }; + metodos: { [nome: string]: DeleguaFuncao | DeleguaFuncao[] }; propriedades: PropriedadeClasse[]; dialetoRequerExpansaoPropriedadesEspacoMemoria: boolean; dialetoRequerDeclaracaoPropriedades: boolean; @@ -21,7 +48,7 @@ export class DescritorTipoClasse extends Chamavel { constructor( simboloOriginal?: SimboloInterface, superClasse?: DescritorTipoClasse, - metodos?: { [nome: string]: DeleguaFuncao }, + metodos?: { [nome: string]: DeleguaFuncao | DeleguaFuncao[] }, propriedades?: PropriedadeClasse[] ) { super(); @@ -32,16 +59,77 @@ export class DescritorTipoClasse extends Chamavel { this.dialetoRequerDeclaracaoPropriedades = false; } - encontrarMetodo(nome: string): DeleguaFuncao { + /** + * Mescla sobrecargas da classe atual com as da superclasse. + * Sobrecargas da subclasse com mesma assinatura substituem as da superclasse. + */ + private mesclarComSuperclasse( + metodosAtuais: DeleguaFuncao[], + metodosSuperclasse: DeleguaFuncao[] + ): DeleguaFuncao[] { + const resultado = [...metodosAtuais]; + for (const metodoSuper of metodosSuperclasse) { + const jaSobrescrito = metodosAtuais.some((m) => assinaturasIguais(m, metodoSuper)); + if (!jaSobrescrito) { + resultado.push(metodoSuper); + } + } + return resultado; + } + + private obterSobrecargasDaSuperclasse(nome: string): DeleguaFuncao[] { + if (!this.superClasse) return []; + const metodoSuper = this.superClasse.metodos.hasOwnProperty(nome) + ? this.superClasse.metodos[nome] + : undefined; + + let sobrecargasSuper: DeleguaFuncao[] = []; + if (metodoSuper) { + sobrecargasSuper = Array.isArray(metodoSuper) ? metodoSuper : [metodoSuper]; + } + + // Recursivamente mesclar com a superclasse da superclasse + const sobrecargasAncestral = this.superClasse.obterSobrecargasDaSuperclasse(nome); + if (sobrecargasAncestral.length > 0) { + const resultado = [...sobrecargasSuper]; + for (const metodoAnc of sobrecargasAncestral) { + const jaSobrescrito = resultado.some((m) => assinaturasIguais(m, metodoAnc)); + if (!jaSobrescrito) { + resultado.push(metodoAnc); + } + } + sobrecargasSuper = resultado; + } + + return sobrecargasSuper; + } + + encontrarMetodo(nome: string): DeleguaFuncao | MetodoPolimorfico { + let metodosAtuais: DeleguaFuncao[] = []; + if (this.metodos.hasOwnProperty(nome)) { - return this.metodos[nome]; + const metodo = this.metodos[nome]; + metodosAtuais = Array.isArray(metodo) ? metodo : [metodo]; } - if (this.superClasse !== null && this.superClasse !== undefined) { - return this.superClasse.encontrarMetodo(nome); + const metodosSuperclasse = this.obterSobrecargasDaSuperclasse(nome); + + let todasSobrecargas: DeleguaFuncao[]; + if (metodosAtuais.length > 0 && metodosSuperclasse.length > 0) { + todasSobrecargas = this.mesclarComSuperclasse(metodosAtuais, metodosSuperclasse); + } else if (metodosAtuais.length > 0) { + todasSobrecargas = metodosAtuais; + } else if (metodosSuperclasse.length > 0) { + todasSobrecargas = metodosSuperclasse; + } else { + return undefined; } - return undefined; + if (todasSobrecargas.length === 1) { + return todasSobrecargas[0]; + } + + return new MetodoPolimorfico(nome, todasSobrecargas); } encontrarPropriedade(nome: string): PropriedadeClasse { @@ -92,6 +180,9 @@ export class DescritorTipoClasse extends Chamavel { aridade(): number { const inicializador = this.encontrarMetodo('construtor'); + if (inicializador instanceof MetodoPolimorfico) { + return inicializador.aridade(); + } return inicializador ? inicializador.aridade() : 0; } @@ -103,8 +194,22 @@ export class DescritorTipoClasse extends Chamavel { const inicializador = this.encontrarMetodo('construtor'); if (inicializador) { - const metodoConstrutor = inicializador.funcaoPorMetodoDeClasse(instancia); - await metodoConstrutor.chamar(visitante, argumentos); + if (inicializador instanceof MetodoPolimorfico) { + const construtorVinculado = inicializador.funcaoPorMetodoDeClasse(instancia); + await construtorVinculado.chamar(visitante, argumentos); + } else { + // Para construtor não polimórfico, completar os argumentos + // não preenchidos com valores indefinidos. + const aridadeConstrutor = inicializador.aridade(); + if (argumentos.length < aridadeConstrutor) { + const diferenca = aridadeConstrutor - argumentos.length; + for (let i = 0; i < diferenca; i++) { + argumentos.push({ nome: null, valor: null }); + } + } + const metodoConstrutor = inicializador.funcaoPorMetodoDeClasse(instancia); + await metodoConstrutor.chamar(visitante, argumentos); + } } return instancia; diff --git a/fontes/interpretador/estruturas/index.ts b/fontes/interpretador/estruturas/index.ts index 1f2e6619..eaf8b60d 100644 --- a/fontes/interpretador/estruturas/index.ts +++ b/fontes/interpretador/estruturas/index.ts @@ -5,6 +5,7 @@ export * from './descritor-tipo-classe'; export * from './funcao-padrao'; export * from './delegua-funcao'; export * from './metodo-primitiva'; +export * from './metodo-polimorfico'; export * from './modulo'; export * from './objeto-delegua-classe'; export * from './objeto-padrao'; diff --git a/fontes/interpretador/estruturas/metodo-polimorfico.ts b/fontes/interpretador/estruturas/metodo-polimorfico.ts new file mode 100644 index 00000000..2fe148b7 --- /dev/null +++ b/fontes/interpretador/estruturas/metodo-polimorfico.ts @@ -0,0 +1,204 @@ +import { InterpretadorInterface } from '../../interfaces'; +import { inferirTipoVariavel } from '../../inferenciador'; +import { ErroEmTempoDeExecucao } from '../../excecoes'; +import { ArgumentoInterface } from '../argumento-interface'; +import { Chamavel } from './chamavel'; +import { DeleguaFuncao } from './delegua-funcao'; +import { ObjetoDeleguaClasse } from './objeto-delegua-classe'; + +const tiposNumericos = ['inteiro', 'número', 'real', 'longo']; + +const mapaDeNormalizacao: { [chave: string]: string } = { + 'numero': 'número', + 'logico': 'lógico', + 'funcao': 'função', + 'dicionario': 'dicionário', + 'modulo': 'módulo', +}; + +function normalizarTipo(tipo: string | undefined): string { + if (!tipo || tipo === 'qualquer') return 'qualquer'; + const tipoMinusculo = tipo.toLowerCase(); + return mapaDeNormalizacao[tipoMinusculo] || tipoMinusculo; +} + +function tiposCompativeisParaDespacho(tipoParametro: string, tipoArgumento: string): number { + const paramNorm = normalizarTipo(tipoParametro); + const argNorm = normalizarTipo(tipoArgumento); + + if (paramNorm === 'qualquer') return 1; + if (paramNorm === argNorm) return 3; + if (tiposNumericos.includes(paramNorm) && tiposNumericos.includes(argNorm)) return 2; + + return -1; +} + +/** + * Proxy que encapsula múltiplas sobrecargas de um mesmo método, + * resolvendo qual sobrecarga chamar em tempo de execução com base + * nos tipos dos argumentos fornecidos. + */ +export class MetodoPolimorfico extends Chamavel { + nome: string; + sobrecargas: DeleguaFuncao[]; + instancia: ObjetoDeleguaClasse; + + constructor(nome: string, sobrecargas: DeleguaFuncao[], instancia?: ObjetoDeleguaClasse) { + super(); + this.nome = nome; + this.sobrecargas = sobrecargas; + this.instancia = instancia; + } + + aridade(): number { + let maxAridade = 0; + for (const sobrecarga of this.sobrecargas) { + const a = sobrecarga.aridade(); + if (a > maxAridade) maxAridade = a; + } + return maxAridade; + } + + private contarParametrosObrigatorios(sobrecarga: DeleguaFuncao): number { + const parametros = sobrecarga.declaracao?.parametros || []; + let obrigatorios = 0; + for (const p of parametros) { + if (p.abrangencia === 'multiplo') break; + if (p.valorPadrao === undefined) { + obrigatorios++; + } + } + return obrigatorios; + } + + private temParametroEspalhado(sobrecarga: DeleguaFuncao): boolean { + const parametros = sobrecarga.declaracao?.parametros || []; + return parametros.some((p) => p.abrangencia === 'multiplo'); + } + + resolverSobrecarga(argumentos: ArgumentoInterface[]): DeleguaFuncao { + const numArgs = argumentos.length; + let melhorPontuacao = -1; + let melhorSobrecarga: DeleguaFuncao = null; + + for (const sobrecarga of this.sobrecargas) { + const parametros = sobrecarga.declaracao?.parametros || []; + const aridade = sobrecarga.aridade(); + const minParams = this.contarParametrosObrigatorios(sobrecarga); + const temEspalhado = this.temParametroEspalhado(sobrecarga); + + // Verificar se a quantidade de argumentos é compatível + if (temEspalhado) { + if (numArgs < minParams) continue; + } else { + if (numArgs < minParams || numArgs > aridade) continue; + } + + let pontuacao = 0; + let compativel = true; + + for (let i = 0; i < numArgs && i < parametros.length; i++) { + const parametro = parametros[i]; + if (parametro.abrangencia === 'multiplo') { + // Parâmetros espalhados aceitam qualquer coisa + pontuacao += 1; + break; + } + + let valorArgumento = argumentos[i] && argumentos[i].hasOwnProperty('valor') + ? argumentos[i].valor + : argumentos[i]; + // Se o valor é uma VariavelInterface, extrair o valor real + if (valorArgumento && typeof valorArgumento === 'object' + && valorArgumento.hasOwnProperty('valor') && valorArgumento.hasOwnProperty('tipo')) { + valorArgumento = valorArgumento.valor; + } + const tipoArgumento = inferirTipoVariavel(valorArgumento) as string; + const resultado = tiposCompativeisParaDespacho(parametro.tipoDado, tipoArgumento); + + if (resultado < 0) { + compativel = false; + break; + } + pontuacao += resultado; + } + + if (!compativel) continue; + + if (pontuacao > melhorPontuacao) { + melhorPontuacao = pontuacao; + melhorSobrecarga = sobrecarga; + } + } + + if (!melhorSobrecarga) { + const tiposArgs = argumentos.map((a) => { + let val = a && a.hasOwnProperty('valor') ? a.valor : a; + if (val && typeof val === 'object' + && val.hasOwnProperty('valor') && val.hasOwnProperty('tipo')) { + val = val.valor; + } + return normalizarTipo(inferirTipoVariavel(val) as string); + }); + + const assinaturas = this.sobrecargas.map((s) => { + const params = s.declaracao?.parametros || []; + const tipos = params.map((p) => p.tipoDado || 'qualquer'); + return `${this.nome}(${tipos.join(', ')})`; + }); + + throw new ErroEmTempoDeExecucao( + null, + `Nenhuma sobrecarga do método "${this.nome}" corresponde aos argumentos fornecidos (${tiposArgs.join(', ')}). ` + + `Sobrecargas disponíveis: ${assinaturas.join('; ')}.` + ); + } + + return melhorSobrecarga; + } + + async chamar( + visitante: InterpretadorInterface, + argumentos: ArgumentoInterface[] + ): Promise { + const sobrecarga = this.resolverSobrecarga(argumentos); + + // Completar os argumentos não preenchidos com valores indefinidos + // para a sobrecarga selecionada. + const aridadeSobrecarga = sobrecarga.aridade(); + if (argumentos.length < aridadeSobrecarga) { + const diferenca = aridadeSobrecarga - argumentos.length; + for (let i = 0; i < diferenca; i++) { + argumentos.push({ nome: null, valor: null }); + } + } + + // Vincular a instância se existir + let funcaoParaChamar = sobrecarga; + if (this.instancia !== undefined) { + funcaoParaChamar = sobrecarga.funcaoPorMetodoDeClasse(this.instancia); + } + + return await funcaoParaChamar.chamar(visitante, argumentos); + } + + funcaoPorMetodoDeClasse(instancia: ObjetoDeleguaClasse): MetodoPolimorfico { + const sobrecargasVinculadas = this.sobrecargas.map( + (s) => s.funcaoPorMetodoDeClasse(instancia) + ); + return new MetodoPolimorfico(this.nome, sobrecargasVinculadas, instancia); + } + + paraTexto(): string { + const assinaturas = this.sobrecargas.map((s) => { + const params = s.declaracao?.parametros || []; + const tipos = params.map((p) => `${p.nome.lexema}: ${p.tipoDado || 'qualquer'}`); + return `${this.nome}(${tipos.join(', ')})`; + }); + return ``; + } + + toString(): string { + return this.paraTexto(); + } +} diff --git a/fontes/interpretador/interpretador-base.ts b/fontes/interpretador/interpretador-base.ts index 2c7575d3..ae4ab5e2 100644 --- a/fontes/interpretador/interpretador-base.ts +++ b/fontes/interpretador/interpretador-base.ts @@ -33,6 +33,7 @@ import { Chamavel, DescritorTipoClasse, DeleguaFuncao, + MetodoPolimorfico, ObjetoDeleguaClasse, DeleguaModulo, FuncaoPadrao, @@ -1140,7 +1141,13 @@ export class InterpretadorBase implements InterpretadorInterface { : entidadeChamada.length; // Completar os argumentos não preenchidos com valores indefinidos. - if (argumentos.length < aridade) { + // Para métodos polimórficos e classes com construtores polimórficos, + // a quantidade original de argumentos é necessária para o despacho + // correto da sobrecarga. + const ehPolimorfico = entidadeChamada instanceof MetodoPolimorfico || + (entidadeChamada instanceof DescritorTipoClasse && + entidadeChamada.encontrarMetodo('construtor') instanceof MetodoPolimorfico); + if (!ehPolimorfico && argumentos.length < aridade) { const diferenca = aridade - argumentos.length; for (let i = 0; i < diferenca; i++) { argumentos.push({ @@ -1171,7 +1178,8 @@ export class InterpretadorBase implements InterpretadorInterface { // então precisamos testar o nome do construtor também. if ( entidadeChamada instanceof Chamavel || - entidadeChamada.constructor.name === 'DeleguaFuncao' + entidadeChamada.constructor.name === 'DeleguaFuncao' || + entidadeChamada.constructor.name === 'MetodoPolimorfico' ) { const retornoEntidadeChamada = await entidadeChamada.chamar(this, argumentos); return retornoEntidadeChamada; @@ -2088,7 +2096,7 @@ export class InterpretadorBase implements InterpretadorInterface { this.pilhaEscoposExecucao.definirVariavel('super', superClasse); } - const metodos = {}; + const metodos: { [nome: string]: DeleguaFuncao | DeleguaFuncao[] } = {}; const definirMetodos = declaracao.metodos; for (let i = 0; i < declaracao.metodos.length; i++) { const metodoAtual = definirMetodos[i]; @@ -2099,7 +2107,15 @@ export class InterpretadorBase implements InterpretadorInterface { undefined, eInicializador ); - metodos[metodoAtual.simbolo.lexema] = funcao; + const nomeMetodo = metodoAtual.simbolo.lexema; + if (metodos[nomeMetodo]) { + if (!Array.isArray(metodos[nomeMetodo])) { + metodos[nomeMetodo] = [metodos[nomeMetodo] as DeleguaFuncao]; + } + (metodos[nomeMetodo] as DeleguaFuncao[]).push(funcao); + } else { + metodos[nomeMetodo] = funcao; + } } const descritorTipoClasse: DescritorTipoClasse = new DescritorTipoClasse( diff --git a/fontes/interpretador/interpretador.ts b/fontes/interpretador/interpretador.ts index ef6e7c52..131ca58e 100644 --- a/fontes/interpretador/interpretador.ts +++ b/fontes/interpretador/interpretador.ts @@ -613,7 +613,7 @@ export class Interpretador extends InterpretadorBase implements VisitanteDelegua await this.executar(declaracao.corpo); if (retornoInicializacaoResolvido instanceof ObjetoDeleguaClasse) { - const metodoFinalizar = retornoInicializacaoResolvido.classe.metodos['finalizar']; + const metodoFinalizar = retornoInicializacaoResolvido.classe.encontrarMetodo('finalizar'); if (metodoFinalizar) { const chamavel = metodoFinalizar.funcaoPorMetodoDeClasse( retornoInicializacaoResolvido diff --git a/testes/interpretador/interpretador.test.ts b/testes/interpretador/interpretador.test.ts index 2862b5b0..c386b6ce 100644 --- a/testes/interpretador/interpretador.test.ts +++ b/testes/interpretador/interpretador.test.ts @@ -2711,6 +2711,241 @@ describe('Interpretador', () => { }); }); + describe('Polimorfismo de métodos', () => { + it('Despacho por aridade - métodos com quantidades diferentes de parâmetros', async () => { + const codigo = [ + 'classe Calculadora {', + ' somar(a: número) {', + ' retorna a', + ' }', + ' somar(a: número, b: número) {', + ' retorna a + b', + ' }', + '}', + 'var calc = Calculadora()', + 'escreva(calc.somar(5))', + 'escreva(calc.somar(3, 7))', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas).toHaveLength(2); + expect(_saidas[0]).toBe('5'); + expect(_saidas[1]).toBe('10'); + }); + + it('Despacho por tipo - mesma aridade, tipos diferentes', async () => { + const codigo = [ + 'classe Impressora {', + ' imprimir(valor: texto) {', + ' escreva("Texto: " + valor)', + ' }', + ' imprimir(valor: número) {', + ' escreva("Número: " + texto(valor))', + ' }', + '}', + 'var imp = Impressora()', + 'imp.imprimir("olá")', + 'imp.imprimir(42)', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas).toHaveLength(2); + expect(_saidas[0]).toBe('Texto: olá'); + expect(_saidas[1]).toBe('Número: 42'); + }); + + it('Correspondência com curinga - parâmetro tipado tem prioridade sobre não-tipado', async () => { + const codigo = [ + 'classe Processador {', + ' processar(valor: texto) {', + ' escreva("texto: " + valor)', + ' }', + ' processar(valor) {', + ' escreva("genérico: " + texto(valor))', + ' }', + '}', + 'var proc = Processador()', + 'proc.processar("teste")', + 'proc.processar(123)', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas).toHaveLength(2); + expect(_saidas[0]).toBe('texto: teste'); + expect(_saidas[1]).toBe('genérico: 123'); + }); + + it('Compatibilidade numérica - inteiro compatível com número', async () => { + const codigo = [ + 'classe Conversor {', + ' converter(valor: inteiro) {', + ' escreva("inteiro: " + texto(valor))', + ' }', + ' converter(valor: texto) {', + ' escreva("texto: " + valor)', + ' }', + '}', + 'var conv = Conversor()', + 'conv.converter(42)', + 'conv.converter("olá")', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas).toHaveLength(2); + expect(_saidas[0]).toBe('inteiro: 42'); + expect(_saidas[1]).toBe('texto: olá'); + }); + + it('Sobrecarga de construtores', async () => { + const codigo = [ + 'classe Ponto {', + ' x: número', + ' y: número', + ' construtor(x: número, y: número) {', + ' isto.x = x', + ' isto.y = y', + ' }', + ' construtor(valor: número) {', + ' isto.x = valor', + ' isto.y = valor', + ' }', + '}', + 'var p1 = Ponto(3, 4)', + 'var p2 = Ponto(5)', + 'escreva(p1.x)', + 'escreva(p1.y)', + 'escreva(p2.x)', + 'escreva(p2.y)', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas).toHaveLength(4); + expect(_saidas[0]).toBe('3'); + expect(_saidas[1]).toBe('4'); + expect(_saidas[2]).toBe('5'); + expect(_saidas[3]).toBe('5'); + }); + + it('Herança com polimorfismo - subclasse adiciona sobrecargas a métodos herdados', async () => { + const codigo = [ + 'classe Base {', + ' cumprimentar(nome: texto) {', + ' escreva("Olá, " + nome)', + ' }', + '}', + 'classe Derivada herda Base {', + ' cumprimentar(nome: texto, sobrenome: texto) {', + ' escreva("Olá, " + nome + " " + sobrenome)', + ' }', + '}', + 'var d = Derivada()', + 'd.cumprimentar("João")', + 'd.cumprimentar("João", "Silva")', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas).toHaveLength(2); + expect(_saidas[0]).toBe('Olá, João'); + expect(_saidas[1]).toBe('Olá, João Silva'); + }); + + it('Herança com sobrescrita de assinatura específica', async () => { + const codigo = [ + 'classe Base {', + ' processar(valor: texto) {', + ' escreva("base-texto: " + valor)', + ' }', + ' processar(valor: número) {', + ' escreva("base-número: " + texto(valor))', + ' }', + '}', + 'classe Derivada herda Base {', + ' processar(valor: texto) {', + ' escreva("derivada-texto: " + valor)', + ' }', + '}', + 'var d = Derivada()', + 'd.processar("teste")', + 'd.processar(99)', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas).toHaveLength(2); + expect(_saidas[0]).toBe('derivada-texto: teste'); + expect(_saidas[1]).toBe('base-número: 99'); + }); + + it('Método único - compatibilidade retroativa', async () => { + const codigo = [ + 'classe Simples {', + ' saudar() {', + ' escreva("Olá mundo")', + ' }', + '}', + 'var s = Simples()', + 's.saudar()', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(0); + expect(_saidas).toHaveLength(1); + expect(_saidas[0]).toBe('Olá mundo'); + }); + + it('Erro quando nenhuma sobrecarga corresponde', async () => { + const codigo = [ + 'classe Estrita {', + ' executar(valor: texto) {', + ' escreva(valor)', + ' }', + ' executar(a: número, b: número) {', + ' escreva(a + b)', + ' }', + '}', + 'var est = Estrita()', + 'est.executar(verdadeiro)', + ]; + + const retornoLexador = lexador.mapear(codigo, -1); + const retornoAvaliadorSintatico = await avaliadorSintatico.analisar(retornoLexador, -1); + const retornoInterpretador = await interpretador.interpretar(retornoAvaliadorSintatico.declaracoes); + + expect(retornoInterpretador.erros).toHaveLength(1); + expect(retornoInterpretador.erros[0].erroInterno.mensagem).toContain('sobrecarga'); + }); + }); + describe('Declaração e chamada de funções', () => { it('Aglutinação de argumentos', async () => { const codigo = ['função teste(*argumentos) {', ' escreva(argumentos)', '}', 'teste(1, 2, 3)'];