Skip to content
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod ir;
pub mod parser;
pub mod pretty_print;
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod interpreter;
pub mod ir;
pub mod parser;
pub mod type_checker;
pub mod pretty_print;

fn main() {
println!("Hello, world!");
Expand Down
147 changes: 147 additions & 0 deletions src/pretty_print/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Biblioteca de Pretty-Printing para RPython 🚀

[](https://www.rust-lang.org/)
[](https://github.com/UnBCIC-TP2/r-python/issues)
[](https://github.com/UnBCIC-TP2/r-python/actions)

Este fork do projeto **RPython** introduz uma implementação robusta de uma biblioteca de **pretty-printing**. O objetivo principal é converter a Árvore de Sintaxe Abstrata (AST) da linguagem de volta para código-fonte legível, com formatação consistente, indentação correta e quebras de linha inteligentes que se adaptam ao espaço disponível.

## Integrantes do grupoe e suas contribuições
Célio Júnio de Freitas Eduardo - 211010350
- Escolha pela nova pasta e definição específica dos módulos necessários
- Escolha dos artigos de base
- Definição e esboço inicial do módulo pretty_print.rs (Tipos bases utilizados)
- Tentativa de fazer um teste de integração (não foi possível)
- Coordenação dos trabalhos e verificação conjunta com todos os membros
Tiago Nunes Silva Nascimento - 200060422
- Esboço da parte de implementação específica do pretty_print.rs
- Desevolvimento das funcões principais
- Desenvolvimento dos testes unitários do módulo
Ana Carolina Dias do Nascimento - 232035692
- Esboço da implementação do toDoc para Types
- Desenvolvimento completo do módulo
- Desenvolvimento dos testes unitários do módulo
Gabriel Pessoa Faustino - 231006121
- Esboço da implementação do toDoc para Statements
- Desenvolvimento completo do módulo
- Desenvolvimento dos testes unitários do módulo
Wagner de Sousa da Silva (Cyber) - 242039882
- Esboço da implementação do toDoc para Expressions
- Desenvolvimento completo do módulo
- Desenvolvimento dos testes unitários do módulo

## 📋 Sobre a Biblioteca de Pretty-Printer

Um pretty-printer é uma ferramenta essencial no ciclo de vida de um compilador ou interpretador. Ele permite que a representação interna do código (a AST) seja visualizada de forma clara e esteticamente agradável, facilitando a depuração, a análise de código e a interação com o programador.

Esta implementação foi adicionada ao projeto RPython criando um novo módulo, `pretty_print`, e integrando-o à estrutura principal do projeto.

## 🏛️ Conceitos Fundamentais: O Algoritmo de Wadler/Oppen

[cite\_start]A implementação é baseada nos algoritmos formalizados nos artigos de **Derek C. Oppen** e, principalmente, na abordagem funcional e elegante de **Philip Wadler**. A lógica opera em duas fases principais:

1. **Construção do Documento (`AST` -\> `Doc`)**: A AST é convertida para uma representação de layout intermediária e abstrata, chamada `Doc`. Esta estrutura descreve o documento em termos de `text`, `line` (possíveis quebras de linha), e `nest` (indentação), sem se comprometer com uma formatação final.
2. **Renderização do Documento (`Doc` -\> `String`)**: Um motor de renderização processa a estrutura `Doc` e a transforma na `String` final. É aqui que a "mágica" acontece: o motor decide qual o melhor layout para uma dada largura de linha. A primitiva `group` é a chave, pois permite definir layouts alternativos (por exemplo, "tente manter em uma linha, mas se não couber, quebre a linha e indente aqui").

Essa arquitetura torna o pretty-printer extremamente flexível e poderoso.

## 🏗️ Estrutura e Integração

Para integrar a biblioteca, a seguinte estrutura de pastas e arquivos foi adicionada ao projeto RPython:

```
src/
└── pretty_print/
├── mod.rs # Ponto de entrada do módulo
├── pretty_print.rs # O motor principal do pretty-printer (Doc, pretty, best, etc.)
├── pretty_expressions.rs # Implementação de ToDoc para Expressões
├── pretty_statements.rs # Implementação de ToDoc para Comandos
├── pretty_type.rs # Implementação de ToDoc para Tipos
└── README.md # Documentação específica do módulo
r-python-pp/
└──tests/
└──pretty_print_tests.rs
```

- **`main.rs` e `lib.rs`**: Não foram atualizados para declarar e expor o novo módulo `pretty_print`, isto será feito caso o projeto seja aceito na sua implementação definida aqui.
- **`Cargo.toml`**: Permaneceu o mesmo, pois não foram necessárias novas dependências externas.

## ✨ Como Usar

O uso do pretty-printer é centralizado e simples. O fluxo de trabalho é sempre:

1. Ter uma instância de um nó da AST (uma expressão, um statement, etc.).
2. Importar o trait `ToDoc` e a função `pretty`.
3. Chamar o método `.to_doc()` no nó da AST para obter a representação `Doc`.
4. Passar o `Doc` e a largura de linha desejada para a função `pretty()`.

#### Exemplo de Uso

```rust
use crate::ir::ast::{Expression, Statement};
use crate::pretty_print::pretty_print::{pretty, ToDoc}; // Importações principais

// 1. Crie um nó da AST.
let stmt = Statement::VarDeclaration(
"resultado".to_string(),
Box::new(Expression::Add(
Box::new(Expression::CInt(10)),
Box::new(Expression::CInt(20)),
)),
);

// 2. Converta a AST para um Doc.
let document = stmt.to_doc();

// 3. Renderize o Doc para uma String com a largura desejada.
let formatted_code = pretty(80, &document); // Largura de 80 colunas

// 4. Imprima o resultado.
println!("{}", formatted_code);
// Saída esperada: var resultado = 10 + 20;
```

## 🚀 Layout Flexível: A Magia do `group`

A principal vantagem desta implementação é sua capacidade de adaptar o layout. Veja o mesmo nó da AST (`FuncCall`) renderizado com larguras diferentes:

#### Exemplo 1: Com Espaço Suficiente (width = 120)

```rust
// AST para: minha_funcao(arg1_longo, arg2_longo, arg3_longo)
let doc = ...;
println!("{}", pretty(120, &doc));
```

**Saída:**

```
minha_funcao( arg1_longo, arg2_longo, arg3_longo )
```

#### Exemplo 2: Com Espaço Limitado (width = 40)

```rust
let doc = ...; // O mesmo doc de antes
println!("{}", pretty(40, &doc));
```

**Saída:**

```
minha_funcao(
arg1_longo,
arg2_longo,
arg3_longo
)
```

## ✅ Executando os Testes

Os testes unitários estão localizados dentro de cada submódulo e validam tanto a conversão para `Doc` quanto o resultado final da renderização com diferentes larguras.

Para rodar todos os testes do projeto, incluindo os da biblioteca de pretty-print, execute no terminal:

```bash
cargo test
```
29 changes: 29 additions & 0 deletions src/pretty_print/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// src/pretty_print/mod.rs

//! # Módulo de Pretty-Printing
//!
//! Este módulo define a API pública para a biblioteca de pretty-printing do RPython.
//! Ele atua como uma "fachada" (facade), declarando os submódulos internos e
//! reexportando seus componentes públicos para que possam ser facilmente utilizados
//! por outras partes do compilador.
//!
//! A estrutura é a seguinte:
//! - `pretty_print`: O motor de renderização principal, contendo a definição de `Doc`.
//! - `pretty_expressions`: Lógica para formatar `Expression`s.
//! - `pretty_statements`: Lógica para formatar `Statement`s.
//! - `pretty_type`: Lógica para formatar `Type`s.

// Declara os submódulos para que o compilador os reconheça como parte do módulo `pretty_print`.
pub mod pretty_expressions;
pub mod pretty_print;
pub mod pretty_statements;
pub mod pretty_type;

// Utiliza `pub use` para reexportar todo o conteúdo público dos submódulos.
// Isso cria uma API conveniente e unificada. Em vez de um usuário do módulo precisar
// importar de `crate::pretty_print::pretty_print::ToDoc`, ele pode simplesmente
// importar de `crate::pretty_print::ToDoc`.
pub use pretty_expressions::*;
pub use pretty_print::*;
pub use pretty_statements::*;
pub use pretty_type::*;
195 changes: 195 additions & 0 deletions src/pretty_print/pretty_expressions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// src/pretty_print/pretty_expressions.rs

use std::rc::Rc;
use crate::ir::ast::Expression;
use super::pretty_print::{
group, nest, nil, text, ToDoc, Doc, concat, line
};

// --- Níveis de Precedência dos Operadores ---
// Define a ordem de operações para evitar o uso desnecessário de parênteses.
// Um operador com maior precedência é avaliado antes de um com menor precedência.
// Ex: `*` (PREC_MUL_DIV) tem maior precedência que `+` (PREC_ADD_SUB).
const PREC_NONE: usize = 0; // Contexto sem operador, como o topo de uma expressão.
const PREC_OR: usize = 1; // Precedência para o operador `or`.
const PREC_AND: usize = 2; // Precedência para o operador `and`.
const PREC_RELATIONAL: usize = 3; // Precedência para operadores como `==`, `>`, `<`.
const PREC_ADD_SUB: usize = 4; // Precedência para `+`, `-`.
const PREC_MUL_DIV: usize = 5; // Precedência para `*`, `/`.
const PREC_UNARY: usize = 6; // Precedência para operadores unários como `not`.
const PREC_CALL: usize = 7; // Precedência para chamadas de função e construtores.

// --- Implementação do Trait ---

/// Ponto de entrada para a conversão de uma `Expression` em `Doc`.
impl ToDoc for Expression {
fn to_doc(&self) -> Rc<Doc> {
// Inicia a conversão recursiva com a menor precedência possível.
self.to_doc_inner(PREC_NONE)
}
}

impl Expression {
/// Função recursiva auxiliar que formata uma `Expression` em `Doc`,
/// levando em conta a precedência do operador pai para decidir se
/// parênteses são necessários.
///
/// # Argumentos
/// * `parent_precedence` - O nível de precedência da expressão externa (pai).
fn to_doc_inner(&self, parent_precedence: usize) -> Rc<Doc> {
/// Closure que adiciona parênteses a um `Doc` se a precedência atual
/// for menor que a do operador pai. Ex: em `(a + b) * c`, `+` tem
/// menor precedência que `*`, então `a + b` precisa de parênteses.
let maybe_paren = |current_precedence, doc| {
if current_precedence < parent_precedence {
concat(text("("), concat(doc, text(")")))
} else {
doc
}
};

/// Closure auxiliar para formatar operadores binários de forma consistente,
/// aplicando a lógica de parênteses.
let binary_op = |op, current_prec, lhs: &Expression, rhs: &Expression| {
let doc = concat(
// Formata o lado esquerdo, passando a precedência atual.
lhs.to_doc_inner(current_prec),
// Adiciona o operador.
concat(text(op), rhs.to_doc_inner(current_prec)),
);
// Adiciona parênteses se necessário.
maybe_paren(current_prec, doc)
};

match self {
// Literais e variáveis são simples: apenas converte para texto.
Expression::CTrue => text("True"),
Expression::CFalse => text("False"),
Expression::CInt(i) => text(i.to_string()),
Expression::CReal(f) => text(f.to_string()),
Expression::CString(s) => text(format!("\"{}\"", s)),
Expression::CVoid => text("()"),
Expression::Var(name) => text(name.clone()),

// Formatação para todos os operadores binários usando a closure `binary_op`.
Expression::Or(l, r) => binary_op(" or ", PREC_OR, l, r),
Expression::And(l, r) => binary_op(" and ", PREC_AND, l, r),
Expression::EQ(l, r) => binary_op(" == ", PREC_RELATIONAL, l, r),
Expression::NEQ(l, r) => binary_op(" != ", PREC_RELATIONAL, l, r),
Expression::GT(l, r) => binary_op(" > ", PREC_RELATIONAL, l, r),
Expression::LT(l, r) => binary_op(" < ", PREC_RELATIONAL, l, r),
Expression::GTE(l, r) => binary_op(" >= ", PREC_RELATIONAL, l, r),
Expression::LTE(l, r) => binary_op(" <= ", PREC_RELATIONAL, l, r),
Expression::Add(l, r) => binary_op(" + ", PREC_ADD_SUB, l, r),
Expression::Sub(l, r) => binary_op(" - ", PREC_ADD_SUB, l, r),
Expression::Mul(l, r) => binary_op(" * ", PREC_MUL_DIV, l, r),
Expression::Div(l, r) => binary_op(" / ", PREC_MUL_DIV, l, r),

// Formatação para operadores unários.
Expression::Not(expr) => {
let doc = concat(text("not "), expr.to_doc_inner(PREC_UNARY));
maybe_paren(PREC_UNARY, doc)
}
Expression::Unwrap(expr) => {
concat(expr.to_doc_inner(PREC_CALL), text("!"))
}

// Estruturas complexas que podem ter quebra de linha.
Expression::FuncCall(name, args) => {
let separator = concat(text(","), line()); // Separador: "," e uma possível quebra de linha.
let args_docs = args.iter().map(|arg| arg.to_doc_inner(PREC_NONE)).collect();

concat(
text(name.clone()),
// `group` permite que a lista de argumentos fique em uma linha ou
// seja quebrada e indentada se não couber.
group(concat(
text("("),
concat(nest(4, join(separator, args_docs)), text(")")),
)),
)
}

Expression::ListValue(elements) => {
let separator = concat(text(","), line());
let elems_docs = elements.iter().map(|el| el.to_doc_inner(PREC_NONE)).collect();

group(concat(
text("["),
concat(nest(4, concat(line(), join(separator, elems_docs))), concat(line(), text("]")))
))
}

Expression::Constructor(name, args) => {
let args_docs = args.iter().map(|arg| arg.to_doc_inner(PREC_NONE)).collect();
let doc = concat(text(name.clone()), concat(text(" "), join(text(" "), args_docs)));
maybe_paren(PREC_CALL, doc)
}

// Tipos de tratamento de erro (Maybe/Result).
Expression::COk(expr) => concat(text("Ok("), concat(expr.to_doc(), text(")"))),
Expression::CErr(expr) => concat(text("Err("), concat(expr.to_doc(), text(")"))),
Expression::CJust(expr) => concat(text("Just("), concat(expr.to_doc(), text(")"))),
Expression::CNothing => text("Nothing"),

// Fallback para expressões que ainda não têm uma regra de formatação.
_ => text("/* expr not implemented */"),
}
}
}

/// Função auxiliar para juntar uma lista de documentos com um separador.
fn join(sep: Rc<Doc>, docs: Vec<Rc<Doc>>) -> Rc<Doc> {
docs.into_iter().reduce(|acc, doc| concat(acc, concat(sep.clone(), doc))).unwrap_or_else(nil)
}

#[cfg(test)]
mod tests {
use crate::ir::ast::Expression;
use super::*;
use crate::pretty_print::pretty;

#[test]
fn test_precedence_add_mul() {
let expr = Expression::Mul(
Box::new(Expression::Add(
Box::new(Expression::Var("a".to_string())),
Box::new(Expression::Var("b".to_string())),
)),
Box::new(Expression::Var("c".to_string())),
);
assert_eq!(pretty(80, &expr.to_doc()), "(a + b) * c");
}

#[test]
fn test_precedence_relational_and() {
let expr = Expression::And(
Box::new(Expression::GT(
Box::new(Expression::Var("a".to_string())),
Box::new(Expression::Var("b".to_string())),
)),
Box::new(Expression::LT(
Box::new(Expression::Var("c".to_string())),
Box::new(Expression::Var("d".to_string())),
)),
);
assert_eq!(pretty(80, &expr.to_doc()), "a > b and c < d");
}

#[test]
fn test_list_layout() {
let list = Expression::ListValue(vec![
Expression::CString("item longo".to_string()),
Expression::CString("outro item longo".to_string()),
]);

let doc = list.to_doc();

// Com espaço suficiente, a lista é formatada em uma única linha.
assert_eq!(pretty(100, &doc), "[ \"item longo\", \"outro item longo\" ]");

// Com espaço limitado, a lista quebra a linha e indenta os elementos.
let narrow_expected = "[\n \"item longo\",\n \"outro item longo\"\n]";
assert_eq!(pretty(20, &doc), narrow_expected);
}
}
Loading