Place parentheses in expressions when necessary
This commit is contained in:
parent
4156006ada
commit
7833ef533d
2 changed files with 131 additions and 10 deletions
|
|
@ -4,18 +4,30 @@ use crate::span::{HasSpan, Span};
|
||||||
|
|
||||||
use super::{Call, Field, FuncDef, Lit, Space, TableConstr, TableDestr, Var};
|
use super::{Call, Field, FuncDef, Lit, Space, TableConstr, TableDestr, Var};
|
||||||
|
|
||||||
|
// Warning: If you change these precedences and associativities, you need to
|
||||||
|
// update the parser and pretty-printer as well.
|
||||||
|
|
||||||
|
// Warning: Operators at the same precedence must also have the same
|
||||||
|
// associativity.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Assoc {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum BinOp {
|
pub enum BinOp {
|
||||||
/// `+`
|
|
||||||
Add,
|
|
||||||
/// `-`
|
|
||||||
Sub,
|
|
||||||
/// `*`
|
/// `*`
|
||||||
Mul,
|
Mul,
|
||||||
/// `/`
|
/// `/`
|
||||||
Div,
|
Div,
|
||||||
/// `%`
|
/// `%`
|
||||||
Mod,
|
Mod,
|
||||||
|
/// `+`
|
||||||
|
Add,
|
||||||
|
/// `-`
|
||||||
|
Sub,
|
||||||
/// `==`
|
/// `==`
|
||||||
Eq,
|
Eq,
|
||||||
/// `!=`
|
/// `!=`
|
||||||
|
|
@ -34,6 +46,36 @@ pub enum BinOp {
|
||||||
Or,
|
Or,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BinOp {
|
||||||
|
/// The higher the precedence, the more strongly the operator binds.
|
||||||
|
pub fn precedence(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::Mul | Self::Div | Self::Mod => 4,
|
||||||
|
Self::Add | Self::Sub => 3,
|
||||||
|
Self::Eq | Self::Neq | Self::Gt | Self::Ge | Self::Lt | Self::Le => 2,
|
||||||
|
Self::And => 1,
|
||||||
|
Self::Or => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assoc(self) -> Assoc {
|
||||||
|
match self {
|
||||||
|
Self::Mul
|
||||||
|
| Self::Div
|
||||||
|
| Self::Mod
|
||||||
|
| Self::Add
|
||||||
|
| Self::Sub
|
||||||
|
| Self::Eq
|
||||||
|
| Self::Neq
|
||||||
|
| Self::Gt
|
||||||
|
| Self::Ge
|
||||||
|
| Self::Lt
|
||||||
|
| Self::Le => Assoc::Left,
|
||||||
|
Self::And | Self::Or => Assoc::Right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
Lit(Lit),
|
Lit(Lit),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,26 @@
|
||||||
use pretty::{DocAllocator, DocBuilder, Pretty};
|
use pretty::{DocAllocator, DocBuilder, Pretty};
|
||||||
|
|
||||||
use crate::ast::Expr;
|
use crate::ast::{Assoc, BinOp, Expr, Field, Var};
|
||||||
|
|
||||||
|
impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for BinOp {
|
||||||
|
fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> {
|
||||||
|
allocator.text(match self {
|
||||||
|
Self::Mul => "*",
|
||||||
|
Self::Div => "/",
|
||||||
|
Self::Mod => "%",
|
||||||
|
Self::Add => "+",
|
||||||
|
Self::Sub => "-",
|
||||||
|
Self::Eq => "==",
|
||||||
|
Self::Neq => "!=",
|
||||||
|
Self::Gt => ">",
|
||||||
|
Self::Ge => ">=",
|
||||||
|
Self::Lt => "<",
|
||||||
|
Self::Le => "<=",
|
||||||
|
Self::And => "and",
|
||||||
|
Self::Or => "or",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for Expr {
|
impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for Expr {
|
||||||
fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> {
|
fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D> {
|
||||||
|
|
@ -19,22 +39,33 @@ impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for Expr {
|
||||||
span: _,
|
span: _,
|
||||||
} => inner.pretty(allocator).parens(),
|
} => inner.pretty(allocator).parens(),
|
||||||
|
|
||||||
// TODO Check whether parentheses are necessary
|
|
||||||
Self::Neg {
|
Self::Neg {
|
||||||
minus: _,
|
minus: _,
|
||||||
s0,
|
s0,
|
||||||
expr,
|
expr,
|
||||||
span: _,
|
span: _,
|
||||||
} => allocator.text("-").append(expr.pretty(allocator)),
|
} => {
|
||||||
|
let parenthesize = matches!(*expr, Self::BinOp { .. });
|
||||||
|
let inner = expr.pretty(allocator);
|
||||||
|
allocator
|
||||||
|
.text("-")
|
||||||
|
.append(if parenthesize { inner.parens() } else { inner })
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Check whether parentheses are necessary
|
|
||||||
Self::Not {
|
Self::Not {
|
||||||
not: _,
|
not: _,
|
||||||
s0,
|
s0,
|
||||||
expr,
|
expr,
|
||||||
span: _,
|
span: _,
|
||||||
} => allocator.text("not ").append(expr.pretty(allocator)),
|
} => {
|
||||||
|
let parenthesize = matches!(*expr, Self::BinOp { .. });
|
||||||
|
let inner = expr.pretty(allocator);
|
||||||
|
allocator
|
||||||
|
.text("not ")
|
||||||
|
.append(if parenthesize { inner.parens() } else { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Add newlines and group properly
|
||||||
Self::BinOp {
|
Self::BinOp {
|
||||||
left,
|
left,
|
||||||
s0,
|
s0,
|
||||||
|
|
@ -42,7 +73,55 @@ impl<'a, D: DocAllocator<'a>> Pretty<'a, D> for Expr {
|
||||||
s1,
|
s1,
|
||||||
right,
|
right,
|
||||||
span: _,
|
span: _,
|
||||||
} => allocator.text("<binop>"),
|
} => {
|
||||||
|
// If we're left-associative, then the left subexpression can be
|
||||||
|
// at the same precedence and the right subexpression must be at
|
||||||
|
// a higher precedence.
|
||||||
|
|
||||||
|
// If we're right-associative, then the left subexpression must
|
||||||
|
// be at a higher precedence and the right subexpression can be
|
||||||
|
// at the same precedence.
|
||||||
|
|
||||||
|
// Minimum precedence that the left subexpression can be at
|
||||||
|
// without requiring parentheses.
|
||||||
|
let min_left_prec = match op.assoc() {
|
||||||
|
Assoc::Left => op.precedence(),
|
||||||
|
Assoc::Right => op.precedence() + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Minimum precedence that the right subexpression can be at
|
||||||
|
// without requiring parentheses.
|
||||||
|
let min_right_prec = match op.assoc() {
|
||||||
|
Assoc::Left => op.precedence() + 1,
|
||||||
|
Assoc::Right => op.precedence(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_paren = match *left {
|
||||||
|
// These end with an arbitrary expression on the right. If
|
||||||
|
// we don't add parentheses, we'll be assimilated into that
|
||||||
|
// expression.
|
||||||
|
Self::Field(Field::Assign { .. } | Field::AssignIdent { .. }) => true,
|
||||||
|
Self::Var(Var::Assign { .. } | Var::AssignIdent { .. }) => true,
|
||||||
|
|
||||||
|
Self::BinOp { op, .. } if op.precedence() < min_left_prec => true,
|
||||||
|
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let right_paren =
|
||||||
|
matches!(*right, Self::BinOp { op, .. } if op.precedence() < min_right_prec);
|
||||||
|
|
||||||
|
let left = left.pretty(allocator);
|
||||||
|
let left = if left_paren { left.parens() } else { left };
|
||||||
|
|
||||||
|
let right = right.pretty(allocator);
|
||||||
|
let right = if right_paren { right.parens() } else { right };
|
||||||
|
|
||||||
|
left.append(allocator.space())
|
||||||
|
.append(op.pretty(allocator))
|
||||||
|
.append(allocator.space())
|
||||||
|
.append(right)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue