diff --git a/fontes/analisador-semantico/analisador-semantico-base.ts b/fontes/analisador-semantico/analisador-semantico-base.ts index ebfd8d3e..1975ac82 100644 --- a/fontes/analisador-semantico/analisador-semantico-base.ts +++ b/fontes/analisador-semantico/analisador-semantico-base.ts @@ -66,6 +66,7 @@ import { TextoDocumentacao, } from '../declaracoes'; import { + CorrecaoSugeridaInterface, DiagnosticoAnalisadorSemantico, DiagnosticoSeveridade, SimboloInterface, @@ -122,7 +123,7 @@ export abstract class AnalisadorSemanticoBase implements AnalisadorSemanticoInte sugestao( simbolo: SimboloInterface, mensagem: string, - correcoes: import('../interfaces/erros').CorrecaoSugeridaInterface[] + correcoes: CorrecaoSugeridaInterface[] ): void { if (this.diagnosticoJaExiste(simbolo, mensagem)) { return; diff --git a/fontes/analisador-semantico/analisador-semantico.ts b/fontes/analisador-semantico/analisador-semantico.ts index ef9b3f85..cb3fb1be 100644 --- a/fontes/analisador-semantico/analisador-semantico.ts +++ b/fontes/analisador-semantico/analisador-semantico.ts @@ -1120,8 +1120,8 @@ export class AnalisadorSemantico extends AnalisadorSemanticoBase { textoOriginal: 'qualquer', textoSubstituto: tipoMelhor, linha: declaracao.simbolo.linha, - colunaInicio: 0, - colunaFim: 0, + colunaInicio: declaracao.simbolo.colunaInicio, + colunaFim: declaracao.simbolo.colunaFim, }] ); } diff --git a/fontes/interfaces/simbolo-interface.ts b/fontes/interfaces/simbolo-interface.ts index 4a433876..da222a8b 100644 --- a/fontes/interfaces/simbolo-interface.ts +++ b/fontes/interfaces/simbolo-interface.ts @@ -4,4 +4,6 @@ export interface SimboloInterface { literal: string; linha: number; hashArquivo: number; + colunaInicio?: number; + colunaFim?: number; } diff --git a/fontes/lexador/dialetos/lexador-calango.ts b/fontes/lexador/dialetos/lexador-calango.ts index 11fd8ec3..596a8912 100644 --- a/fontes/lexador/dialetos/lexador-calango.ts +++ b/fontes/lexador/dialetos/lexador-calango.ts @@ -85,7 +85,10 @@ export class LexadorCalango implements LexadorInterface { } adicionarSimbolo(tipo: any, literal?: any): void { const texto = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); - this.simbolos.push(new Simbolo(tipo, texto, literal, this.linha, -1)); + const comprimento = Math.max(texto.length, 1); + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; + this.simbolos.push(new Simbolo(tipo, texto, literal, this.linha, -1, colunaInicio, colunaFim)); } simboloAtual(): string { diff --git a/fontes/lexador/dialetos/lexador-egua-classico.ts b/fontes/lexador/dialetos/lexador-egua-classico.ts index 202af936..10015900 100644 --- a/fontes/lexador/dialetos/lexador-egua-classico.ts +++ b/fontes/lexador/dialetos/lexador-egua-classico.ts @@ -89,7 +89,10 @@ export class LexadorEguaClassico implements LexadorInterface { adicionarSimbolo(tipo: any, literal: any = null) { const texto = this.codigo.substring(this.inicioSimbolo, this.atual); - this.simbolos.push(new Simbolo(tipo, texto, literal, this.linha, -1)); + const comprimento = Math.max(texto.length, 1); + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; + this.simbolos.push(new Simbolo(tipo, texto, literal, this.linha, -1, colunaInicio, colunaFim)); } proximoIgualA(esperado: any) { diff --git a/fontes/lexador/dialetos/lexador-pitugues.ts b/fontes/lexador/dialetos/lexador-pitugues.ts index a984ffbb..32c2c8b5 100644 --- a/fontes/lexador/dialetos/lexador-pitugues.ts +++ b/fontes/lexador/dialetos/lexador-pitugues.ts @@ -120,7 +120,10 @@ export class LexadorPitugues implements LexadorInterface { adicionarSimbolo(tipo: any, literal: any = null, linha: number = null): void { const texto: string = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); - this.simbolos.push(new Simbolo(tipo, texto, literal, linha || this.linha + 1, this.hashArquivo)); + const comprimento = Math.max(texto.length, 1); + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; + this.simbolos.push(new Simbolo(tipo, texto, literal, linha || this.linha + 1, this.hashArquivo, colunaInicio, colunaFim)); } simboloAtual(): string { diff --git a/fontes/lexador/dialetos/lexador-portugol-ipt.ts b/fontes/lexador/dialetos/lexador-portugol-ipt.ts index 6e951b5e..46a65086 100644 --- a/fontes/lexador/dialetos/lexador-portugol-ipt.ts +++ b/fontes/lexador/dialetos/lexador-portugol-ipt.ts @@ -88,8 +88,13 @@ export class LexadorPortugolIpt implements LexadorInterface { adicionarSimbolo(tipo: any, literal?: any): void { const texto: string = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); + const lexema = literal || texto; + const comprimentoLexema = typeof lexema === 'string' ? lexema.length : 0; + const comprimento = Math.max(comprimentoLexema, texto.length) || 1; + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; this.simbolos.push( - new Simbolo(tipo, literal || texto, literal, this.linha + 1, this.hashArquivo) + new Simbolo(tipo, lexema, literal, this.linha + 1, this.hashArquivo, colunaInicio, colunaFim) ); } diff --git a/fontes/lexador/dialetos/lexador-prisma.ts b/fontes/lexador/dialetos/lexador-prisma.ts index 2d713d04..0a7cc04d 100644 --- a/fontes/lexador/dialetos/lexador-prisma.ts +++ b/fontes/lexador/dialetos/lexador-prisma.ts @@ -110,7 +110,10 @@ export class LexadorPrisma implements LexadorInterface { adicionarSimbolo(tipo: string, literal: any = null): void { const texto: string = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); - this.simbolos.push(new Simbolo(tipo, texto, literal, this.linha + 1, this.hashArquivo)); + const comprimento = Math.max(texto.length, 1); + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; + this.simbolos.push(new Simbolo(tipo, texto, literal, this.linha + 1, this.hashArquivo, colunaInicio, colunaFim)); } analisarTexto(delimitador: string = '"'): void { diff --git a/fontes/lexador/dialetos/lexador-tenda.ts b/fontes/lexador/dialetos/lexador-tenda.ts index 9c9d889a..511041ce 100644 --- a/fontes/lexador/dialetos/lexador-tenda.ts +++ b/fontes/lexador/dialetos/lexador-tenda.ts @@ -121,7 +121,10 @@ export class LexadorTenda implements LexadorInterface { adicionarSimbolo(tipo: any, literal: any = null): void { const texto: string = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); - this.simbolos.push(new Simbolo(tipo, texto, literal, this.linha + 1, this.hashArquivo)); + const comprimento = Math.max(texto.length, 1); + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; + this.simbolos.push(new Simbolo(tipo, texto, literal, this.linha + 1, this.hashArquivo, colunaInicio, colunaFim)); } simboloAtual(): string { diff --git a/fontes/lexador/lexador-base-linha-unica.ts b/fontes/lexador/lexador-base-linha-unica.ts index 0b3f10e8..7d5c6eab 100644 --- a/fontes/lexador/lexador-base-linha-unica.ts +++ b/fontes/lexador/lexador-base-linha-unica.ts @@ -83,8 +83,13 @@ export abstract class LexadorBaseLinhaUnica implements LexadorInterface adicionarSimbolo(tipo: any, literal?: any): void { const texto: string = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); + const lexema = literal || texto; + const comprimentoLexema = typeof lexema === 'string' ? lexema.length : 0; + const comprimento = Math.max(comprimentoLexema, texto.length) || 1; + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; this.simbolos.push( - new Simbolo(tipo, literal || texto, literal, this.linha + 1, this.hashArquivo) + new Simbolo(tipo, lexema, literal, this.linha + 1, this.hashArquivo, colunaInicio, colunaFim) ); } diff --git a/fontes/lexador/lexador.ts b/fontes/lexador/lexador.ts index 24a21090..bb372179 100644 --- a/fontes/lexador/lexador.ts +++ b/fontes/lexador/lexador.ts @@ -126,8 +126,13 @@ export class Lexador implements LexadorInterface { adicionarSimbolo(tipo: string, literal: any = null): void { const texto: string = this.codigo[this.linha].substring(this.inicioSimbolo, this.atual); + const lexema = literal || texto; + const comprimentoLexema = typeof lexema === 'string' ? lexema.length : 0; + const comprimento = Math.max(comprimentoLexema, texto.length) || 1; + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; this.simbolos.push( - new Simbolo(tipo, literal || texto, literal, this.linha + 1, this.hashArquivo) + new Simbolo(tipo, lexema, literal, this.linha + 1, this.hashArquivo, colunaInicio, colunaFim) ); } diff --git a/fontes/lexador/micro-lexador-pitugues.ts b/fontes/lexador/micro-lexador-pitugues.ts index 229f6020..6de61b92 100644 --- a/fontes/lexador/micro-lexador-pitugues.ts +++ b/fontes/lexador/micro-lexador-pitugues.ts @@ -71,7 +71,12 @@ export class MicroLexadorPitugues { adicionarSimbolo(tipo: string, literal: any = null): void { const texto: string = this.codigo.substring(this.inicioSimbolo, this.atual); - this.simbolos.push(new Simbolo(tipo, literal || texto, literal, 1, -1)); + const lexema = literal || texto; + const comprimentoLexema = typeof lexema === 'string' ? lexema.length : 0; + const comprimento = Math.max(comprimentoLexema, texto.length) || 1; + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; + this.simbolos.push(new Simbolo(tipo, lexema, literal, 1, -1, colunaInicio, colunaFim)); } analisarTexto(delimitador = '"'): void { diff --git a/fontes/lexador/micro-lexador.ts b/fontes/lexador/micro-lexador.ts index b9efd915..67fb039f 100644 --- a/fontes/lexador/micro-lexador.ts +++ b/fontes/lexador/micro-lexador.ts @@ -69,7 +69,12 @@ export class MicroLexador { adicionarSimbolo(tipo: string, literal: any = null): void { const texto: string = this.codigo.substring(this.inicioSimbolo, this.atual); - this.simbolos.push(new Simbolo(tipo, literal || texto, literal, 1, -1)); + const lexema = literal || texto; + const comprimentoLexema = typeof lexema === 'string' ? lexema.length : 0; + const comprimento = Math.max(comprimentoLexema, texto.length) || 1; + const colunaInicio = this.inicioSimbolo + 1; + const colunaFim = this.inicioSimbolo + comprimento; + this.simbolos.push(new Simbolo(tipo, lexema, literal, 1, -1, colunaInicio, colunaFim)); } analisarTexto(delimitador = '"'): void { diff --git a/fontes/lexador/simbolo.ts b/fontes/lexador/simbolo.ts index c7b88e95..ab961656 100644 --- a/fontes/lexador/simbolo.ts +++ b/fontes/lexador/simbolo.ts @@ -6,13 +6,17 @@ export class Simbolo implements SimboloInterface { literal: any; linha: number; hashArquivo: number; + colunaInicio: number; + colunaFim: number; - constructor(tipo: string, lexema: string, literal: any, linha: number, hashArquivo: number) { + constructor(tipo: string, lexema: string, literal: any, linha: number, hashArquivo: number, colunaInicio: number = 0, colunaFim: number = 0) { this.tipo = tipo; this.lexema = lexema; this.literal = literal; this.linha = linha; this.hashArquivo = hashArquivo; + this.colunaInicio = colunaInicio; + this.colunaFim = colunaFim; } paraTexto(): string { diff --git a/testes/analisador-semantico/analisador-semantico.test.ts b/testes/analisador-semantico/analisador-semantico.test.ts index 298b20da..2d99869f 100644 --- a/testes/analisador-semantico/analisador-semantico.test.ts +++ b/testes/analisador-semantico/analisador-semantico.test.ts @@ -1965,9 +1965,15 @@ describe('Analisador semântico', () => { expect(sugestao.mensagem).toBe('Um tipo melhor pode ser inferido.'); expect(sugestao.correcoes).toBeDefined(); expect(sugestao.correcoes).toHaveLength(1); - expect(sugestao.correcoes[0].titulo).toBe("Alterar tipo para 'número'"); - expect(sugestao.correcoes[0].textoOriginal).toBe('qualquer'); - expect(sugestao.correcoes[0].textoSubstituto).toBe('número'); + const primeiraCorrecao = sugestao.correcoes![0]; + expect(primeiraCorrecao.titulo).toBe("Alterar tipo para 'número'"); + expect(primeiraCorrecao.textoOriginal).toBe('qualquer'); + expect(primeiraCorrecao.textoSubstituto).toBe('número'); + // 'a' está na coluna 5 + expect(primeiraCorrecao.colunaInicio).toBe(5); + expect(primeiraCorrecao.colunaFim).toBe(5); + expect(sugestao.colunaInicio).toBe(5); + expect(sugestao.colunaFim).toBe(5); }); it('Sugestão - tipo qualquer pode ser inferido para texto', async () => { @@ -1985,8 +1991,12 @@ describe('Analisador semântico', () => { const sugestao = retornoAnalisadorSemantico.diagnosticos[0]; expect(sugestao.severidade).toBe(DiagnosticoSeveridade.SUGESTAO); - expect(sugestao.correcoes[0].titulo).toBe("Alterar tipo para 'texto'"); - expect(sugestao.correcoes[0].textoSubstituto).toBe('texto'); + const primeiraCorrecao = sugestao.correcoes![0]; + expect(primeiraCorrecao.titulo).toBe("Alterar tipo para 'texto'"); + expect(primeiraCorrecao.textoSubstituto).toBe('texto'); + // 'b' está na coluna 5 + expect(primeiraCorrecao.colunaInicio).toBe(5); + expect(primeiraCorrecao.colunaFim).toBe(5); }); it('Sem sugestão - tipo já é específico', async () => { diff --git a/testes/lexador/lexador.test.ts b/testes/lexador/lexador.test.ts index 32bfc896..ba48b4ca 100644 --- a/testes/lexador/lexador.test.ts +++ b/testes/lexador/lexador.test.ts @@ -267,6 +267,128 @@ describe('Lexador', () => { }); }); + describe('Informações de coluna (colunaInicio e colunaFim)', () => { + it('Identificador simples - colunaInicio e colunaFim corretas', () => { + const resultado = lexador.mapear(['var x = 10'], -1); + + expect(resultado).toBeTruthy(); + // 'var' na coluna 1-3 + expect(resultado.simbolos[0]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.VARIAVEL, + colunaInicio: 1, + colunaFim: 3, + }) + ); + // 'x' na coluna 5 + expect(resultado.simbolos[1]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.IDENTIFICADOR, + colunaInicio: 5, + colunaFim: 5, + }) + ); + // '=' na coluna 7 + expect(resultado.simbolos[2]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.IGUAL, + colunaInicio: 7, + colunaFim: 7, + }) + ); + // '10' na coluna 9-10 + expect(resultado.simbolos[3]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.NUMERO, + colunaInicio: 9, + colunaFim: 10, + }) + ); + }); + + it('Operadores compostos - colunaInicio e colunaFim corretas', () => { + const resultado = lexador.mapear(['a >= b'], -1); + + expect(resultado).toBeTruthy(); + // 'a' na coluna 1 + expect(resultado.simbolos[0]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.IDENTIFICADOR, + colunaInicio: 1, + colunaFim: 1, + }) + ); + // '>=' na coluna 3-4 + expect(resultado.simbolos[1]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.MAIOR_IGUAL, + colunaInicio: 3, + colunaFim: 4, + }) + ); + // 'b' na coluna 6 + expect(resultado.simbolos[2]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.IDENTIFICADOR, + colunaInicio: 6, + colunaFim: 6, + }) + ); + }); + + it('Texto (string) - colunaInicio e colunaFim corretas', () => { + const resultado = lexador.mapear(['"ola"'], -1); + + expect(resultado).toBeTruthy(); + // '"ola"' abrange colunas 1-5 + expect(resultado.simbolos[0]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.TEXTO, + colunaInicio: 1, + colunaFim: 5, + }) + ); + }); + + it('Múltiplas linhas - cada linha tem colunas independentes', () => { + const resultado = lexador.mapear(['var a = 1', 'var b = 2'], -1); + + expect(resultado).toBeTruthy(); + // Primeira linha: 'var' col 1-3, 'a' col 5, '=' col 7, '1' col 9 + expect(resultado.simbolos[0]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.VARIAVEL, + linha: 1, + colunaInicio: 1, + colunaFim: 3, + }) + ); + // Segunda linha: 'var' col 1-3 (coluna reinicia) + expect(resultado.simbolos[4]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.VARIAVEL, + linha: 2, + colunaInicio: 1, + colunaFim: 3, + }) + ); + }); + + it('Número decimal - colunaInicio e colunaFim corretas', () => { + const resultado = lexador.mapear(['3.14'], -1); + + expect(resultado).toBeTruthy(); + // '3.14' na coluna 1-4 + expect(resultado.simbolos[0]).toEqual( + expect.objectContaining({ + tipo: tiposDeSimbolos.NUMERO, + colunaInicio: 1, + colunaFim: 4, + }) + ); + }); + }); + describe('Cenários de falha', () => { it('Falha léxica - texto sem fim', () => { const resultado = lexador.mapear(['"texto sem fim'], -1);