Skip to content

Commit

Permalink
Melhorias no verificador de erros com escopo e tipos
Browse files Browse the repository at this point in the history
Closes #88
Closes #85
  • Loading branch information
dgadelha committed Dec 26, 2023
1 parent 866c529 commit ef89403
Show file tree
Hide file tree
Showing 11 changed files with 1,065 additions and 158 deletions.
23 changes: 10 additions & 13 deletions packages/antlr/src/PortugolErrorListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,14 @@ export class PortugolCodeError extends Error {
typeof (possibleContext as unknown as { _stop: any })._stop === "object"
) {
const { _start, _stop } = possibleContext as unknown as { _start: any; _stop: any };
const { line: startLine, _charPositionInLine: startCol } = _start;
let { line: endLine, _charPositionInLine: endCol } = _stop;

return new PortugolCodeError(
message,
ctx,
_start.line,
_start._charPositionInLine + 1,
_stop.line,
_stop._charPositionInLine + 2,
);
if (startLine === endLine && startCol === endCol) {
endCol += ctx.text.length - 1;
}

return new PortugolCodeError(message, ctx, startLine, startCol, endLine, endCol);
}

const possibleSymbol: Token | RuleContext | undefined = (ctx as any).symbol || (ctx as any).payload;
Expand All @@ -51,9 +50,9 @@ export class PortugolCodeError extends Error {
message,
ctx,
_line,
_charPositionInLine + 1,
_charPositionInLine,
_line,
_charPositionInLine + 2 + ctx.text.length,
_charPositionInLine + ctx.text.length,
);
}

Expand All @@ -73,9 +72,7 @@ export class PortugolErrorListener implements ANTLRErrorListener<Token> {
exception: RecognitionException | undefined,
) {
const endColumn =
offendingSymbol && offendingSymbol.text
? charPositionInLine + offendingSymbol.text.length
: charPositionInLine + 1;
offendingSymbol && offendingSymbol.text ? charPositionInLine + offendingSymbol.text.length : charPositionInLine;

this.errors.push(
new PortugolCodeError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node">
<li mat-ripple (click)="loadItem(node)" [class.active]="current?.id === node.id">
<button mat-icon-button disabled>
<button mat-icon-button disabled title="Expandir/retrair">
<mat-icon></mat-icon>
</button>
<span>{{ node.name }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class DialogOpenExampleComponent implements OnInit, OnDestroy, AfterViewI

treeControl: NestedTreeControl<ExampleItem>;
dataSource: MatTreeNestedDataSource<ExampleItem>;
current?: ExampleItem;
current: ExampleItem | null = null;
loading = true;

private _loadSubscription$?: Subscription;
Expand Down
4 changes: 2 additions & 2 deletions packages/ide/src/app/tab-editor/tab-editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,9 @@ export class TabEditorComponent implements OnInit, OnDestroy {
errors.map(error => {
return {
startLineNumber: error.startLine,
startColumn: error.startCol,
startColumn: error.startCol + 1,
endLineNumber: error.endLine,
endColumn: error.endCol,
endColumn: error.endCol + 2,
message: error.message,
severity: monaco.MarkerSeverity.Error,
};
Expand Down
2 changes: 1 addition & 1 deletion packages/ide/src/app/tab-start/tab-start.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@
<hr />

<h4>📰&nbsp;&nbsp;Novidades</h4>
<p><strong>26/12/2023:</strong> Melhorias nas verificações de erros (escopos e tipos)</p>
<p><strong>15/10/2023:</strong> Correção de verificação simples de retorno de função</p>
<p><strong>11/10/2023:</strong> Correção de quebra de linha após execução da função <code>leia()</code></p>
<p><strong>28/09/2023:</strong> Correção de uso excessivo de recursos ao usar atribuições.</p>
</section>

<footer>
Expand Down
5 changes: 3 additions & 2 deletions packages/parser/src/errors/01-estrutura-básica.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PortugolCodeError } from "@portugol-webstudio/antlr";

import { getAllChildrenFromNode } from "../helpers/nodes.js";
import { TipoPrimitivo } from "../helpers/Tipo.js";
import { Arquivo } from "../nodes/Arquivo.js";
import { RetorneCmd } from "../nodes/RetorneCmd.js";

Expand All @@ -12,7 +13,7 @@ export function* checarFunçãoInício(arquivo: Arquivo) {
yield PortugolCodeError.fromContext(funcInicio.ctx, "A função 'inicio' não deve receber parâmetros");
}

if (funcInicio.retorno.primitivo !== "vazio") {
if (funcInicio.retorno.primitivo !== TipoPrimitivo.VAZIO) {
yield PortugolCodeError.fromContext(funcInicio.ctx, "A função 'inicio' não deve retornar valores");
}
} else {
Expand All @@ -22,7 +23,7 @@ export function* checarFunçãoInício(arquivo: Arquivo) {

export function* checarFunçõesComRetorno(arquivo: Arquivo) {
for (const func of arquivo.funções) {
if (func.retorno.primitivo !== "vazio") {
if (func.retorno.primitivo !== TipoPrimitivo.VAZIO) {
if (!getAllChildrenFromNode(func).some(instrução => instrução instanceof RetorneCmd)) {
yield PortugolCodeError.fromContext(func.ctx, `A função '${func.nome}' deve retornar um valor`);
}
Expand Down
222 changes: 87 additions & 135 deletions packages/parser/src/errors/02-variáveis.ts
Original file line number Diff line number Diff line change
@@ -1,164 +1,116 @@
import { PortugolCodeError } from "@portugol-webstudio/antlr";
import { PortugolCodeError } from "packages/antlr/lib/PortugolErrorListener.js";

import { getAllChildrenFromNode } from "../helpers/nodes.js";
import { Tipo } from "../helpers/Tipo.js";
import { ResultadoCompatibilidade, TabelaCompatibilidadeAtribuição } from "../helpers/compatibilidade.js";
import { Escopo } from "../helpers/Escopo.js";
import { resolverResultadoExpressão } from "../helpers/expressões.js";
import { Arquivo } from "../nodes/Arquivo.js";
import { AtribuiçãoCmd } from "../nodes/AtribuiçãoCmd.js";
import { CadeiaExpr } from "../nodes/CadeiaExpr.js";
import { CaractereExpr } from "../nodes/CaractereExpr.js";
import { ChamadaFunçãoExpr } from "../nodes/ChamadaFunçãoExpr.js";
import { Comando } from "../nodes/Comando.js";
import { DeclaraçãoCmd } from "../nodes/DeclaraçãoCmd.js";
import { Expressão } from "../nodes/Expressão.js";
import { InteiroExpr } from "../nodes/InteiroExpr.js";
import { LógicoExpr } from "../nodes/LógicoExpr.js";
import { RealExpr } from "../nodes/RealExpr.js";
import { EnquantoCmd } from "../nodes/EnquantoCmd.js";
import { EscolhaCmd } from "../nodes/EscolhaCmd.js";
import { FaçaEnquantoCmd } from "../nodes/FaçaEnquantoCmd.js";
import { Função } from "../nodes/Função.js";
import { Node } from "../nodes/Node.js";
import { ParaCmd } from "../nodes/ParaCmd.js";
import { Parâmetro } from "../nodes/Parâmetro.js";
import { ReferênciaVarExpr } from "../nodes/ReferênciaVarExpr.js";
import { SeCmd } from "../nodes/SeCmd.js";

interface Escopo {
variáveis: Map<string, Tipo>;
funções: Map<string, Tipo>;
}
export function* checarUsoEscopo(arquivo: Arquivo): Generator<PortugolCodeError> {
const escopo = new Escopo();

export function* checarUsoEscopo(arquivo: Arquivo) {
const escopo: Escopo = {
variáveis: new Map<string, Tipo>(),
funções: new Map<string, Tipo>([
["escreva", { primitivo: "vazio" }],
["leia", { primitivo: "cadeia" }],
["limpa", { primitivo: "vazio" }],
]),
};

for (const declr of arquivo.declarações) {
if (escopo.variáveis.has(declr.nome)) {
yield PortugolCodeError.fromContext(declr.ctx, `A variável '${declr.nome}' foi declarada múltiplas vezes`);
function* varrerNós(nós: Node[]) {
for (const of nós) {
yield* varrerNó();
}

escopo.variáveis.set(declr.nome, declr.tipo);
}

for (const func of arquivo.funções) {
if (escopo.funções.has(func.nome)) {
yield PortugolCodeError.fromContext(func.ctx, `A função '${func.nome}' foi declarada múltiplas vezes`);
}
function* varrerNó(: Node): Generator<PortugolCodeError> {
switch (.constructor) {
case DeclaraçãoCmd:
case Parâmetro:
const declr = as DeclaraçãoCmd | Parâmetro;

escopo.funções.set(func.nome, func.retorno);
}
escopo.variáveis.set(declr.nome, declr.tipo);
break;

for (const func of arquivo.funções) {
const escopoFunção: Escopo = {
variáveis: new Map(escopo.variáveis),
funções: new Map(escopo.funções),
};
case ReferênciaVarExpr:
const ref = as ReferênciaVarExpr;

for (const param of func.parâmetros) {
if (escopoFunção.variáveis.has(param.nome)) {
yield PortugolCodeError.fromContext(param.ctx, `O parâmetro '${param.nome}' foi declarado múltiplas vezes`);
}
if (!escopo.hasVariável(ref.nome)) {
yield PortugolCodeError.fromContext(ref.ctx, `Variável não declarada: ${ref.nome}`);
}

escopoFunção.variáveis.set(param.nome, param.tipo);
}
break;

const instruções: Array<Comando | Expressão> = func.instruções.concat(
func.instruções.flatMap(getAllChildrenFromNode) as Array<Comando | Expressão>,
);
case AtribuiçãoCmd:
const attr = as AtribuiçãoCmd;

for (const expr of instruções) {
if (expr instanceof DeclaraçãoCmd) {
if (escopoFunção.variáveis.has(expr.nome)) {
yield PortugolCodeError.fromContext(expr.ctx, `A variável '${expr.nome}' foi declarada múltiplas vezes`);
}
yield* varrerNós(attr.children);

escopoFunção.variáveis.set(expr.nome, expr.tipo);
} else if (expr instanceof AtribuiçãoCmd) {
const nome = expr.variável instanceof ReferênciaVarExpr ? expr.variável.nome : expr.variável.variável.nome;
// TODO: bibliotecas
if (attr.variável instanceof ReferênciaVarExpr && !attr.variável.escopoBiblioteca) {
const svar = escopo.variáveis.get(attr.variável.nome);

if (!escopoFunção.variáveis.has(nome)) {
yield PortugolCodeError.fromContext(expr.ctx, `A variável '${nome}' não foi declarada`);
}

const variável = escopoFunção.variáveis.get(nome);

if (expr.expressão instanceof ChamadaFunçãoExpr) {
if (
!expr.expressão.escopoBiblioteca &&
expr.expressão.nome !== "leia" &&
escopoFunção.funções.get(expr.expressão.nome)?.primitivo !== variável?.primitivo
) {
yield PortugolCodeError.fromContext(
expr.ctx,
`A função '${expr.expressão.nome}' não retorna um valor do tipo '${variável?.primitivo}'`,
);
}
} else if (expr.expressão instanceof ReferênciaVarExpr) {
if (escopoFunção.variáveis.get(expr.expressão.nome)?.primitivo !== variável?.primitivo) {
yield PortugolCodeError.fromContext(
expr.ctx,
`A variável '${expr.expressão.nome}' não é do tipo '${variável?.primitivo}'`,
);
}
} else if (expr.expressão instanceof InteiroExpr) {
if (variável?.primitivo !== "inteiro") {
yield PortugolCodeError.fromContext(
expr.ctx,
`A variável '${nome}' esperava ser atribuída com um valor do tipo '${variável?.primitivo}', mas recebeu um valor do tipo 'inteiro'`,
);
}
} else if (expr.expressão instanceof RealExpr) {
if (variável?.primitivo !== "real") {
yield PortugolCodeError.fromContext(
expr.ctx,
`A variável '${nome}' esperava ser atribuída com um valor do tipo '${variável?.primitivo}', mas recebeu um valor do tipo 'real'`,
);
}
} else if (expr.expressão instanceof CadeiaExpr) {
if (variável?.primitivo !== "cadeia") {
yield PortugolCodeError.fromContext(
expr.ctx,
`A variável '${nome}' esperava ser atribuída com um valor do tipo '${variável?.primitivo}', mas recebeu um valor do tipo 'cadeia'`,
);
}
} else if (expr.expressão instanceof CaractereExpr) {
if (variável?.primitivo !== "caracter") {
yield PortugolCodeError.fromContext(
expr.ctx,
`A variável '${nome}' esperava ser atribuída com um valor do tipo '${variável?.primitivo}', mas recebeu um valor do tipo 'caracter'`,
);
}
} else if (expr.expressão instanceof LógicoExpr) {
if (variável?.primitivo !== "logico") {
yield PortugolCodeError.fromContext(
expr.ctx,
`A variável '${nome}' esperava ser atribuída com um valor do tipo '${variável?.primitivo}', mas recebeu um valor do tipo 'logico'`,
);
if (!svar) {
break;
}
}
} else if (expr instanceof ReferênciaVarExpr) {
if (!escopoFunção.variáveis.has(expr.nome)) {
yield PortugolCodeError.fromContext(expr.ctx, `A variável '${expr.nome}' não foi declarada`);
}
} else if (expr instanceof ChamadaFunçãoExpr) {
if (!expr.escopoBiblioteca && !escopoFunção.funções.has(expr.nome)) {
yield PortugolCodeError.fromContext(expr.ctx, `A função '${expr.nome}' não foi declarada`);
}

const args = expr.argumentos;
try {
const tret = resolverResultadoExpressão(attr.expressão, escopo);

for (const arg of args) {
if (arg instanceof ChamadaFunçãoExpr) {
if (!arg.escopoBiblioteca && !escopoFunção.funções.has(arg.nome)) {
yield PortugolCodeError.fromContext(arg.ctx, `A função '${arg.nome}' não foi declarada`);
if (TabelaCompatibilidadeAtribuição[svar.primitivo][tret] === ResultadoCompatibilidade.INCOMPATÍVEL) {
yield PortugolCodeError.fromContext(
attr.ctx,
`Não é possível atribuir um valor do tipo '${tret}' a uma variável do tipo '${svar.primitivo}'`,
);
}
} catch (e) {
const message = e instanceof Error ? e.message : "Não foi possível resolver o tipo da expressão";

const tipo = escopoFunção.funções.get(arg.nome);

if (tipo?.primitivo === "vazio") {
yield PortugolCodeError.fromContext(arg.ctx, `A função '${arg.nome}' não retorna um valor`);
if (message === "TODO") {
break;
}

yield PortugolCodeError.fromContext(attr.ctx, message);
}
}
}

// TODO: array e matriz

break;

case EnquantoCmd:
case EscolhaCmd:
case FaçaEnquantoCmd:
case Função:
case ParaCmd:
escopo.push();
yield* varrerNós(.children);
escopo.pop();
break;

case SeCmd:
const se = as SeCmd;

escopo.push();
yield* varrerNó(se.condição);
yield* varrerNós(se.instruções);
escopo.pop();

if (se.senão) {
escopo.push();
yield* varrerNós(se.senão.instruções);
escopo.pop();
}

break;

default:
yield* varrerNós(.children);
break;
}
}

yield* varrerNó(arquivo);
}
Loading

0 comments on commit ef89403

Please sign in to comment.