@designliquido/delegua
Version:
Linguagem de programação simples e moderna usando português estruturado.
652 lines • 23.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TradutorReversoPython = void 0;
const antlr4ts_1 = require("antlr4ts");
const AbstractParseTreeVisitor_1 = require("antlr4ts/tree/AbstractParseTreeVisitor");
const python3_lexer_1 = require("./python/python3-lexer");
const python3_parser_1 = require("./python/python3-parser");
/**
* Tradutor reverso de Python para Delégua.
* Utiliza o visitor do ANTLR para percorrer a árvore sintática em
* Python e traduzir para Delégua.
*/
class TradutorReversoPython extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
constructor() {
super(...arguments);
// Funções globais Python → Delégua
this.mapeamentoFuncoes = {
print: 'escreva',
input: 'leia',
len: 'tamanho',
int: 'inteiro',
float: 'real',
str: 'texto',
bool: 'logico',
type: 'tipoDe',
range: 'intervalo',
abs: 'absoluto',
round: 'arredondar',
min: 'minimo',
max: 'maximo',
sum: 'somar',
};
// Métodos de instância Python → Delégua
this.mapeamentoMetodos = {
// Lista / vetor
append: 'adicionar',
pop: 'removerUltimo',
reverse: 'inverter',
sort: 'ordenar',
clear: 'limpar',
// Texto / string
upper: 'maiusculo',
lower: 'minusculo',
strip: 'aparar',
lstrip: 'aparar',
rstrip: 'aparar',
split: 'dividir',
join: 'juntar',
startswith: 'iniciaCom',
endswith: 'terminaCom',
replace: 'substituir',
find: 'encontrar',
count: 'contar',
// Dicionário
keys: 'chaves',
values: 'valores',
items: 'itens',
};
}
defaultResult() {
return '';
}
aggregateResult(aggregate, nextResult) {
return aggregate + nextResult;
}
visitTerminal(node) {
return node.text;
}
visitFile_input(ctx) {
return ctx
.stmt()
.map((s) => this.visit(s))
.join('\n');
}
visitSimple_stmt(ctx) {
return ctx
.small_stmt()
.map((s) => this.visit(s))
.join('; ');
}
visitExpr_stmt(ctx) {
const lhs = this.visit(ctx.testlist_star_expr());
const simpleAssign = ctx.simple_assign();
if (simpleAssign && simpleAssign.childCount > 0) {
const rhs = this.visitSimple_assign(simpleAssign);
// Atribuições a atributos (a.b = x) ou índices (a[i] = x) não usam 'var'
const prefixo = lhs.includes('.') || lhs.includes('[') ? '' : 'var ';
return `${prefixo}${lhs} = ${rhs}`;
}
if (ctx.augassign()) {
const op = this.visitAugassign(ctx.augassign());
const testlist = ctx.testlist();
const yieldExpr = ctx.yield_expr();
const rhs = testlist ? this.visit(testlist) : yieldExpr ? this.visit(yieldExpr) : '';
return `${lhs} ${op} ${rhs}`;
}
return lhs;
}
visitSimple_assign(ctx) {
const exprs = ctx.testlist_star_expr();
return this.visit(exprs[exprs.length - 1]);
}
visitAugassign(ctx) {
if (ctx.ADD_ASSIGN())
return '+=';
if (ctx.SUB_ASSIGN())
return '-=';
if (ctx.MULT_ASSIGN())
return '*=';
if (ctx.DIV_ASSIGN())
return '/=';
if (ctx.MOD_ASSIGN())
return '%=';
if (ctx.AND_ASSIGN())
return '&=';
if (ctx.OR_ASSIGN())
return '|=';
if (ctx.XOR_ASSIGN())
return '^=';
if (ctx.LEFT_SHIFT_ASSIGN())
return '<<=';
if (ctx.RIGHT_SHIFT_ASSIGN())
return '>>=';
if (ctx.POWER_ASSIGN())
return '**=';
if (ctx.IDIV_ASSIGN())
return '//=';
return ctx.text;
}
visitTestlist_star_expr(ctx) {
return ctx
.test()
.map((t) => this.visit(t))
.join(', ');
}
visitTestlist(ctx) {
return ctx
.test()
.map((t) => this.visit(t))
.join(', ');
}
visitTest(ctx) {
const orTests = ctx.or_test();
if (orTests.length === 1)
return this.visit(orTests[0]);
const lambdef = ctx.lambdef();
if (lambdef)
return this.visit(lambdef);
// Expressão ternária (a if cond else b) — implementada em fase futura
return this.visitChildren(ctx);
}
visitOr_test(ctx) {
return ctx
.and_test()
.map((t) => this.visit(t))
.join(' ou ');
}
visitAnd_test(ctx) {
return ctx
.not_test()
.map((t) => this.visit(t))
.join(' e ');
}
visitNot_test(ctx) {
if (ctx.NOT()) {
return `nao ${this.visit(ctx.not_test())}`;
}
const comp = ctx.comparison();
if (comp)
return this.visit(comp);
return this.visitChildren(ctx);
}
visitComparison(ctx) {
const exprs = ctx.expr();
if (exprs.length === 1)
return this.visit(exprs[0]);
const ops = ctx.comp_op();
let resultado = this.visit(exprs[0]);
for (let i = 0; i < ops.length; i++) {
resultado += ` ${this.visitComp_op(ops[i])} ${this.visit(exprs[i + 1])}`;
}
return resultado;
}
visitComp_op(ctx) {
if (ctx.LESS_THAN())
return '<';
if (ctx.GREATER_THAN())
return '>';
if (ctx.EQUALS())
return '==';
if (ctx.GT_EQ())
return '>=';
if (ctx.LT_EQ())
return '<=';
if (ctx.NOT_EQ_1() || ctx.NOT_EQ_2())
return '!=';
if (ctx.NOT() && ctx.IN())
return 'nao em';
if (ctx.IN())
return 'em';
if (ctx.NOT() && ctx.IS())
return '!=';
if (ctx.IS())
return '==';
return ctx.text;
}
visitArith_expr(ctx) {
const termos = ctx.term();
if (termos.length === 1)
return this.visit(termos[0]);
let resultado = this.visit(termos[0]);
let idx = 1;
for (let i = 1; i < ctx.childCount; i++) {
const texto = ctx.getChild(i).text;
if (texto === '+' || texto === '-') {
resultado += ` ${texto} ${this.visit(termos[idx++])}`;
}
}
return resultado;
}
visitTerm(ctx) {
const fatores = ctx.factor();
if (fatores.length === 1)
return this.visit(fatores[0]);
let resultado = this.visit(fatores[0]);
let idx = 1;
for (let i = 1; i < ctx.childCount; i++) {
const texto = ctx.getChild(i).text;
if (['*', '/', '%', '//', '@'].includes(texto)) {
resultado += ` ${texto} ${this.visit(fatores[idx++])}`;
}
}
return resultado;
}
visitFactor(ctx) {
if (ctx.ADD())
return `+${this.visit(ctx.factor())}`;
if (ctx.MINUS())
return `-${this.visit(ctx.factor())}`;
if (ctx.NOT_OP())
return `~${this.visit(ctx.factor())}`;
return this.visit(ctx.power());
}
visitPower(ctx) {
const base = this.visit(ctx.atom_expr());
if (ctx.POWER()) {
return `${base} ** ${this.visit(ctx.factor())}`;
}
return base;
}
visitAtom_expr(ctx) {
const textoAtomo = this.visit(ctx.atom());
const trailers = ctx.trailer();
if (trailers.length === 0)
return textoAtomo;
// Chamada de função simples: nome(args)
if (trailers.length === 1 && trailers[0].OPEN_PAREN()) {
const nomeFuncao = this.mapeamentoFuncoes[textoAtomo] ?? textoAtomo;
const arglist = trailers[0].arglist();
const args = arglist ? this.visit(arglist) : '';
return `${nomeFuncao}(${args})`;
}
// Chamada de método: obj.metodo(args) → dois trailers: .nome e (args)
if (trailers.length === 2 &&
trailers[0].DOT() &&
trailers[0].NAME() &&
trailers[1].OPEN_PAREN()) {
const nomeMetodoPython = trailers[0].NAME().text;
const arglist = trailers[1].arglist();
const args = arglist ? this.visit(arglist) : '';
// join é invertido: sep.join(iteravel) → iteravel.juntar(sep)
if (nomeMetodoPython === 'join') {
return `${args}.juntar(${textoAtomo})`;
}
const nomeMetodoDelégua = this.mapeamentoMetodos[nomeMetodoPython] ?? nomeMetodoPython;
return `${textoAtomo}.${nomeMetodoDelégua}(${args})`;
}
// Fallback: acesso a atributo, índice ou chamadas encadeadas
let resultado = textoAtomo;
for (const trailer of trailers) {
resultado += this.visitTrailer(trailer);
}
return resultado;
}
visitTrailer(ctx) {
if (ctx.DOT() && ctx.NAME()) {
return `.${ctx.NAME().text}`;
}
if (ctx.OPEN_BRACK()) {
const subscriptlist = ctx.subscriptlist();
const conteudo = subscriptlist ? this.visit(subscriptlist) : '';
return `[${conteudo}]`;
}
if (ctx.OPEN_PAREN()) {
const arglist = ctx.arglist();
const args = arglist ? this.visit(arglist) : '';
return `(${args})`;
}
return ctx.text;
}
visitAtom(ctx) {
const nome = ctx.NAME();
if (nome)
return nome.text === 'self' ? 'isto' : nome.text;
const numero = ctx.NUMBER();
if (numero)
return numero.text;
if (ctx.TRUE())
return 'verdadeiro';
if (ctx.FALSE())
return 'falso';
if (ctx.NONE())
return 'nulo';
const strings = ctx.STRING();
if (strings.length > 0)
return strings.map((s) => s.text).join(' ');
if (ctx.OPEN_PAREN()) {
const testlistComp = ctx.testlist_comp();
if (testlistComp) {
// Tupla com vírgula → vetor em Delégua
if (testlistComp.COMMA().length > 0) {
return `[${this.visitTestlist_comp(testlistComp)}]`;
}
return `(${this.visitTestlist_comp(testlistComp)})`;
}
return '()';
}
if (ctx.OPEN_BRACK()) {
const testlistComp = ctx.testlist_comp();
if (testlistComp) {
// Compreensão de lista: resultado já é um vetor, não envolve em []
if (testlistComp.comp_for()) {
return this.visitTestlist_comp(testlistComp);
}
return `[${this.visitTestlist_comp(testlistComp)}]`;
}
return '[]';
}
if (ctx.OPEN_BRACE()) {
const dictorsetmaker = ctx.dictorsetmaker();
if (dictorsetmaker)
return `{${this.visitDictorsetmaker(dictorsetmaker)}}`;
return '{}';
}
return ctx.text;
}
visitArglist(ctx) {
return ctx
.argument()
.map((a) => this.visit(a))
.join(', ');
}
visitArgument(ctx) {
const testes = ctx.test();
if (testes.length === 1 && !ctx.ASSIGN()) {
return this.visit(testes[0]);
}
// Argumento nomeado: nome=valor
if (testes.length === 2 && ctx.ASSIGN()) {
return `${this.visit(testes[0])} = ${this.visit(testes[1])}`;
}
return this.visitChildren(ctx);
}
visitTestlist_comp(ctx) {
const compFor = ctx.comp_for();
if (compFor) {
return this.traduzirCompreensao(ctx.test(0), compFor);
}
return ctx
.test()
.map((t) => this.visit(t))
.join(', ');
}
traduzirCompreensao(exprCtx, compFor) {
const variavel = this.visit(compFor.exprlist());
const iteravel = this.visit(compFor.or_test());
const expr = this.visit(exprCtx);
const compIter = compFor.comp_iter();
const compIf = compIter?.comp_if();
if (compIf) {
const cond = this.visitChildren(compIf.test_nocond());
return `filtrarPor(${iteravel}, funcao(${variavel}) { retorna ${cond} }).mapear(funcao(${variavel}) { retorna ${expr} })`;
}
return `${iteravel}.mapear(funcao(${variavel}) { retorna ${expr} })`;
}
visitLambdef(ctx) {
const varargslist = ctx.varargslist();
const params = varargslist ? this.visitVarargslist(varargslist) : '';
const corpo = this.visit(ctx.test());
return `funcao(${params}) { retorna ${corpo} }`;
}
visitVarargslist(ctx) {
const params = [];
for (let i = 0; i < ctx.childCount;) {
const texto = ctx.getChild(i).text;
if (texto === ',') {
i++;
continue;
}
if (texto === '*' || texto === '**')
break;
const nomeParam = texto;
if (i + 1 < ctx.childCount && ctx.getChild(i + 1).text === '=') {
const valorPadrao = this.visit(ctx.getChild(i + 2));
params.push(`${nomeParam} = ${valorPadrao}`);
i += 3;
}
else {
params.push(nomeParam);
i++;
}
}
return params.join(', ');
}
visitDictorsetmaker(ctx) {
const tests = ctx.test();
if (ctx.COLON().length > 0) {
// Dicionário: testes alternados como chave/valor
const pares = [];
for (let i = 0; i + 1 < tests.length; i += 2) {
pares.push(`${this.visit(tests[i])}: ${this.visit(tests[i + 1])}`);
}
return pares.join(', ');
}
// Conjunto (set) — representado como lista em Delégua
return tests.map((t) => this.visit(t)).join(', ');
}
visitSubscriptlist(ctx) {
return ctx
.subscript()
.map((s) => this.visitSubscript(s))
.join(', ');
}
visitSubscript(ctx) {
const tests = ctx.test();
if (!ctx.COLON()) {
// Índice simples
return tests.length > 0 ? this.visit(tests[0]) : '';
}
// Fatia: [inicio]:fim → inicio..fim
// Se o primeiro filho é ':', não há início (ex: texto[:3])
const temInicio = ctx.childCount > 0 && ctx.getChild(0).text !== ':';
const inicio = temInicio ? this.visit(tests[0]) : '';
const fimIdx = temInicio ? 1 : 0;
const fim = tests.length > fimIdx ? this.visit(tests[fimIdx]) : '';
return `${inicio}..${fim}`;
}
// Retorna as linhas já indentadas de um bloco, sem os colchetes externos.
visitLinhasCorpo(ctx) {
const linhas = [];
const simpleStmt = ctx.simple_stmt();
if (simpleStmt) {
linhas.push(` ${this.visit(simpleStmt)}`);
}
else {
for (const stmt of ctx.stmt()) {
const traduzido = this.visit(stmt);
for (const linha of traduzido.split('\n')) {
linhas.push(` ${linha}`);
}
}
}
return linhas;
}
// Traduz um bloco indentado (suite) para o corpo entre chaves de Delégua.
visitCorpo(ctx) {
return `{\n${this.visitLinhasCorpo(ctx).join('\n')}\n}`;
}
visitTry_stmt(ctx) {
const corpoTente = this.visitCorpo(ctx.suite(0));
let resultado = `tente ${corpoTente}`;
const excepts = ctx.except_clause();
if (excepts.length > 0) {
// Detecta alias do erro na primeira cláusula except com `as nome`
const alias = excepts.find((e) => e.NAME())?.NAME()?.text;
// Junta todos os corpos except num único bloco pegue
const todasLinhas = [];
for (let i = 0; i < excepts.length; i++) {
todasLinhas.push(...this.visitLinhasCorpo(ctx.suite(i + 1)));
}
const corpoPegue = `{\n${todasLinhas.join('\n')}\n}`;
resultado += alias ? ` pegue (${alias}) ${corpoPegue}` : ` pegue ${corpoPegue}`;
}
// Índice da suite do else/finally começa após as suites de except
let suiteIdx = 1 + excepts.length;
if (ctx.ELSE()) {
suiteIdx++; // else não tem equivalente em Delégua — ignora
}
if (ctx.FINALLY()) {
resultado += ` finalmente ${this.visitCorpo(ctx.suite(suiteIdx))}`;
}
return resultado;
}
visitRaise_stmt(ctx) {
const tests = ctx.test();
if (tests.length === 0)
return 'levante';
return `levante ${this.visit(tests[0])}`;
}
visitWith_stmt(ctx) {
const linhasCorpo = this.visitLinhasCorpo(ctx.suite());
// Apenas itens com `as` podem ser traduzidos para `tendo...como`
const itemsComAs = ctx.with_item().filter((item) => item.expr());
if (itemsComAs.length === 0) {
// Nenhum item com `as`: traduz apenas o corpo, sem tendo
return linhasCorpo.map((l) => l.trimStart()).join('\n');
}
return this.construirTendo(itemsComAs, 0, linhasCorpo);
}
// Constrói blocos `tendo` aninhados para cada item do `with`.
// Items sem `as` são ignorados (Delégua exige um identificador).
construirTendo(items, i, linhas) {
if (i >= items.length) {
return `{\n${linhas.join('\n')}\n}`;
}
const item = items[i];
const varExpr = item.expr();
const expr = this.visit(item.test());
if (!varExpr) {
// Sem `as`: sem variável para ligar — pula este item
return this.construirTendo(items, i + 1, linhas);
}
const nomeVar = this.visit(varExpr);
// Para múltiplos itens, o próximo nível é indentado dentro deste bloco
let linhasInternas;
if (i + 1 < items.length) {
const blocoInterno = this.construirTendo(items, i + 1, linhas);
linhasInternas = blocoInterno.split('\n').map((l) => ` ${l}`);
}
else {
linhasInternas = linhas;
}
return `tendo ${expr} como ${nomeVar} {\n${linhasInternas.join('\n')}\n}`;
}
visitIf_stmt(ctx) {
const testes = ctx.test();
const suites = ctx.suite();
const elifs = ctx.ELIF();
// Primeiro bloco: se (cond) { ... }
let resultado = `se (${this.visit(testes[0])}) ${this.visitCorpo(suites[0])}`;
// Blocos elif: senão se (cond) { ... }
for (let i = 0; i < elifs.length; i++) {
resultado += ` senão se (${this.visit(testes[i + 1])}) ${this.visitCorpo(suites[i + 1])}`;
}
// Bloco else: senão { ... }
if (ctx.ELSE()) {
resultado += ` senão ${this.visitCorpo(suites[suites.length - 1])}`;
}
return resultado;
}
visitWhile_stmt(ctx) {
const cond = this.visit(ctx.test());
const corpo = this.visitCorpo(ctx.suite(0));
return `enquanto (${cond}) ${corpo}`;
}
visitFor_stmt(ctx) {
const variavel = this.visit(ctx.exprlist());
const iteravel = this.visit(ctx.testlist());
const corpo = this.visitCorpo(ctx.suite(0));
return `para cada ${variavel} em ${iteravel} ${corpo}`;
}
visitBreak_stmt(_ctx) {
return 'sustar';
}
visitContinue_stmt(_ctx) {
return 'continua';
}
visitReturn_stmt(ctx) {
const testlist = ctx.testlist();
if (testlist)
return `retorna ${this.visit(testlist)}`;
return 'retorna';
}
visitTfpdef(ctx) {
// Ignora anotação de tipo (: Tipo) — retorna apenas o nome
return ctx.NAME().text;
}
visitTypedargslist(ctx) {
const params = [];
for (let i = 0; i < ctx.childCount;) {
const filho = ctx.getChild(i);
const texto = filho.text;
if (texto === ',') {
i++;
continue;
}
// Para em *args ou **kwargs — suporte básico suficiente para fase 4
if (texto === '*' || texto === '**')
break;
const nomeParam = this.visit(filho); // → visitTfpdef → NAME
if (nomeParam === 'self') {
i++;
continue;
} // Remove self
// Verifica se há valor padrão: tfpdef '=' test
if (i + 1 < ctx.childCount && ctx.getChild(i + 1).text === '=') {
const valorPadrao = this.visit(ctx.getChild(i + 2));
params.push(`${nomeParam} = ${valorPadrao}`);
i += 3;
}
else {
params.push(nomeParam);
i++;
}
}
return params.join(', ');
}
visitParameters(ctx) {
const argslist = ctx.typedargslist();
if (!argslist)
return '';
return this.visitTypedargslist(argslist);
}
visitFuncdef(ctx) {
const nomePython = ctx.NAME().text;
const params = this.visitParameters(ctx.parameters());
const corpo = this.visitCorpo(ctx.suite());
if (nomePython === '__init__') {
return `construtor(${params}) ${corpo}`;
}
return `funcao ${nomePython}(${params}) ${corpo}`;
}
visitClassdef(ctx) {
const nome = ctx.NAME().text;
const arglist = ctx.arglist();
const heranca = arglist ? ` herda ${this.visit(arglist)}` : '';
const corpo = this.visitCorpo(ctx.suite());
return `classe ${nome}${heranca} ${corpo}`;
}
visitDecorated(ctx) {
// Ignora decoradores — traduz apenas o funcdef ou classdef subjacente
const funcdef = ctx.funcdef();
if (funcdef)
return this.visitFuncdef(funcdef);
const classdef = ctx.classdef();
if (classdef)
return this.visitClassdef(classdef);
return this.visitChildren(ctx);
}
traduzir(codigo) {
// O lexer Python3 do ANTLR exige NEWLINE ao final de cada instrução.
const codigoNormalizado = codigo.endsWith('\n') ? codigo : codigo + '\n';
const inputStream = antlr4ts_1.CharStreams.fromString(codigoNormalizado);
const lexer = new python3_lexer_1.Python3Lexer(inputStream);
const tokenStream = new antlr4ts_1.CommonTokenStream(lexer);
const parser = new python3_parser_1.Python3Parser(tokenStream);
const tree = parser.file_input();
return this.visit(tree);
}
}
exports.TradutorReversoPython = TradutorReversoPython;
//# sourceMappingURL=tradutor-reverso-python.js.map