Skip to content

Commit

Permalink
Fix up Display implementations for AST nodes.
Browse files Browse the repository at this point in the history
Get details right like parens depending on precedence,
semicolons after statements only when warranted, etc.
I haven't extensively tested this yet, so there are likely
places where it does the wrong thing, but this will be
a slightly more principled implementation we can fix up
as needed.
  • Loading branch information
mikebenfield committed Jan 29, 2025
1 parent 4faaaae commit 51eb4ff
Show file tree
Hide file tree
Showing 23 changed files with 291 additions and 72 deletions.
2 changes: 1 addition & 1 deletion compiler/ast/src/access/array_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub struct ArrayAccess {

impl fmt::Display for ArrayAccess {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}", self.array, self.index)
write!(f, "{}[{}]", self.array, self.index)
}
}

Expand Down
3 changes: 2 additions & 1 deletion compiler/ast/src/access/associated_function_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use crate::{Expression, Identifier, Node, NodeID};
use leo_span::Span;

use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
use std::fmt;

Expand All @@ -37,7 +38,7 @@ pub struct AssociatedFunction {

impl fmt::Display for AssociatedFunction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}::{}", self.variant, self.name)
write!(f, "{}::{}({})", self.variant, self.name, self.arguments.iter().format(", "))
}
}

Expand Down
4 changes: 3 additions & 1 deletion compiler/ast/src/expressions/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

use super::*;

use itertools::Itertools as _;

/// An array expression, e.g., `[true, false, true, false]`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArrayExpression {
Expand All @@ -29,7 +31,7 @@ pub struct ArrayExpression {

impl fmt::Display for ArrayExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.elements.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(","))
write!(f, "[{}]", self.elements.iter().format(", "))
}
}

Expand Down
75 changes: 74 additions & 1 deletion compiler/ast/src/expressions/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use super::*;
use leo_span::{Symbol, sym};

use std::cmp::Ordering;

/// A binary operator.
///
/// Precedence is defined in the parser.
Expand Down Expand Up @@ -177,7 +179,78 @@ pub struct BinaryExpression {

impl fmt::Display for BinaryExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {} {}", self.left, self.op, self.right)
use Associativity::*;
use BinaryOperation::*;

if matches!(
self.op,
AddWrapped
| DivWrapped
| Mod
| MulWrapped
| Nand
| Nor
| PowWrapped
| RemWrapped
| ShlWrapped
| ShrWrapped
| SubWrapped
) {
if self.left.precedence() < 20 {
write!(f, "({})", self.left)?;
} else {
write!(f, "{}", self.left)?;
}
write!(f, ".{}({})", self.op, self.right)
} else {
let my_precedence = self.precedence();
let my_associativity = self.associativity();
match (self.left.precedence().cmp(&my_precedence), my_associativity, self.left.associativity()) {
(Ordering::Greater, _, _) | (Ordering::Equal, Left, Left) => write!(f, "{}", self.left)?,
_ => write!(f, "({})", self.left)?,
}
write!(f, " {} ", self.op)?;
match (self.right.precedence().cmp(&my_precedence), my_associativity, self.right.associativity()) {
(Ordering::Greater, _, _) | (Ordering::Equal, Right, Right) => write!(f, "{}", self.right)?,
_ => write!(f, "({})", self.right)?,
}
Ok(())
}
}
}

impl BinaryExpression {
pub(crate) fn precedence(&self) -> u32 {
use BinaryOperation::*;

match self.op {
BitwiseOr => 1,
BitwiseAnd => 2,
Eq | Neq | Lt | Gt | Lte | Gte => 3,
Or => 4,
Xor => 5,
And => 6,
Shl => 7,
Shr => 8,
Add | Sub => 9,
Mul | Div | Rem => 10,
Pow => 11,
AddWrapped | DivWrapped | Mod | MulWrapped | Nand | Nor | PowWrapped | RemWrapped | ShlWrapped
| ShrWrapped | SubWrapped => 20,
}
}

pub(crate) fn associativity(&self) -> Associativity {
use Associativity::*;
use BinaryOperation::*;

match self.op {
Pow => Right,
BitwiseOr | BitwiseAnd | Eq | Neq | Lt | Gt | Lte | Gte | Or | Xor | And | Shl | Shr | Add | Sub | Mul
| Div | Rem => Left,
AddWrapped | DivWrapped | Mod | MulWrapped | Nand | Nor | PowWrapped | RemWrapped | ShlWrapped
| ShrWrapped | SubWrapped => None,
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion compiler/ast/src/expressions/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ pub struct CastExpression {

impl fmt::Display for CastExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({} as {})", self.expression, self.type_)
if self.expression.precedence() < 12 {
write!(f, "({})", self.expression)?;
} else {
write!(f, "{}", self.expression)?;
}
write!(f, " as {}", self.type_)
}
}

Expand Down
24 changes: 24 additions & 0 deletions compiler/ast/src/expressions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,27 @@ impl fmt::Display for Expression {
}
}
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum Associativity {
Left,
Right,
None,
}

impl Expression {
pub(crate) fn precedence(&self) -> u32 {
use Expression::*;
match self {
Binary(e) => e.precedence(),
Cast(_) => 12,
Ternary(_) => 14,
Access(_) | Array(_) | Call(_) | Err(_) | Identifier(_) | Literal(_) | Locator(_) | Struct(_)
| Tuple(_) | Unary(_) | Unit(_) => 20,
}
}

pub(crate) fn associativity(&self) -> Associativity {
if let Expression::Binary(bin) = self { bin.associativity() } else { Associativity::None }
}
}
12 changes: 11 additions & 1 deletion compiler/ast/src/expressions/struct_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use super::*;
use leo_span::sym;

use itertools::Itertools as _;

/// An initializer for a single field / variable of a struct initializer expression.
/// That is, in `Foo { bar: 42, baz }`, this is either `bar: 42`, or `baz`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -90,7 +92,15 @@ impl StructExpression {

impl fmt::Display for StructExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{{{}}}", self.members.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "))
write!(f, "{} {{", self.name)?;
if !self.members.is_empty() {
write!(f, " ")?;
}
write!(f, "{}", self.members.iter().format(", "))?;
if !self.members.is_empty() {
write!(f, " ")?;
}
write!(f, "}}")
}
}

Expand Down
16 changes: 15 additions & 1 deletion compiler/ast/src/expressions/ternary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,21 @@ pub struct TernaryExpression {

impl fmt::Display for TernaryExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({} ? {} : {})", self.condition, self.if_true, self.if_false)
if self.condition.precedence() > 14 {
write!(f, "{}", self.condition)?;
} else {
write!(f, "({})", self.condition)?;
}

write!(f, " ? {} : ", self.if_true)?;

if self.if_false.precedence() > 14 {
write!(f, "{}", self.if_false)?;
} else {
write!(f, "({})", self.if_false)?;
}

Ok(())
}
}

Expand Down
7 changes: 6 additions & 1 deletion compiler/ast/src/expressions/unary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ pub struct UnaryExpression {

impl fmt::Display for UnaryExpression {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}).{}()", self.receiver, self.op)
if self.receiver.precedence() < 20 {
write!(f, "({})", self.receiver)?;
} else {
write!(f, "{}", self.receiver)?;
}
write!(f, ".{}()", self.op)
}
}

Expand Down
6 changes: 5 additions & 1 deletion compiler/ast/src/functions/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ pub struct Input {

impl Input {
fn format(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}: {}", self.mode, self.identifier, self.type_)
if self.mode == Mode::None {
write!(f, "{}: {}", self.identifier, self.type_)
} else {
write!(f, "{} {}: {}", self.mode, self.identifier, self.type_)
}
}

pub fn identifier(&self) -> &Identifier {
Expand Down
54 changes: 29 additions & 25 deletions compiler/ast/src/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ pub use output::*;
pub mod mode;
pub use mode::*;

use crate::{Block, FunctionStub, Identifier, Node, NodeID, TupleType, Type};
use crate::{Block, FunctionStub, Identifier, Indent, Node, NodeID, TupleType, Type};
use leo_span::{Span, Symbol};

use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
use std::fmt;

Expand Down Expand Up @@ -95,28 +96,6 @@ impl Function {
pub fn name(&self) -> Symbol {
self.identifier.name
}

///
/// Private formatting method used for optimizing [fmt::Debug] and [fmt::Display] implementations.
///
fn format(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.variant {
Variant::Inline => write!(f, "inline ")?,
Variant::Function | Variant::AsyncFunction => write!(f, "function ")?,
Variant::Transition | Variant::AsyncTransition => write!(f, "transition ")?,
}
write!(f, "{}", self.identifier)?;

let parameters = self.input.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(",");
let returns = match self.output.len() {
0 => "()".to_string(),
1 => self.output[0].to_string(),
_ => self.output.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(","),
};
write!(f, "({parameters}) -> {returns} {}", self.block)?;

Ok(())
}
}

impl From<FunctionStub> for Function {
Expand All @@ -137,13 +116,38 @@ impl From<FunctionStub> for Function {

impl fmt::Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
write!(f, "{}", self)
}
}

impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
match self.variant {
Variant::Inline => write!(f, "inline ")?,
Variant::Function => write!(f, "function ")?,
Variant::AsyncFunction => write!(f, "async function ")?,
Variant::Transition => write!(f, "transition ")?,
Variant::AsyncTransition => write!(f, "asyc transition ")?,
}
write!(f, "{}({})", self.identifier, self.input.iter().format(", "))?;

match self.output.len() {
0 => {}
1 => {
if !matches!(self.output[0].type_, Type::Unit) {
write!(f, " -> {}", self.output[0])?;
}
}
_ => {
write!(f, " -> {}", self.output.iter().format(", "))?;
}
}

writeln!(f, " {{")?;
for stmt in self.block.statements.iter() {
writeln!(f, "{}{}", Indent(stmt), stmt.semicolon())?;
}
write!(f, "}}")
}
}

Expand Down
55 changes: 55 additions & 0 deletions compiler/ast/src/indent_display.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (C) 2019-2025 Aleo Systems Inc.
// This file is part of the Leo library.

// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.

use std::{fmt, fmt::Write};

/// Implements `Display` by putting 4 spaces in front of each line
/// of `T`'s output.
pub struct Indent<T>(pub T);

impl<T: fmt::Display> fmt::Display for Indent<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(IndentWriter { f, new_line: true }, "{}", self.0)
}
}

const SPACES: &str = " ";

struct IndentWriter<'a, 'b> {
new_line: bool,
f: &'b mut fmt::Formatter<'a>,
}

impl Write for IndentWriter<'_, '_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut iter = s.lines().peekable();

while let Some(line) = iter.next() {
if self.new_line {
self.f.write_str(SPACES)?;
}
self.f.write_str(line)?;
if iter.peek().is_some() || s.ends_with('\n') {
self.f.write_str("\n")?;
self.new_line = true;
} else {
self.new_line = false;
}
}

Ok(())
}
}
3 changes: 3 additions & 0 deletions compiler/ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub use self::functions::*;
pub mod groups;
pub use self::groups::*;

mod indent_display;
use indent_display::*;

pub mod mapping;
pub use self::mapping::*;

Expand Down
Loading

0 comments on commit 51eb4ff

Please sign in to comment.