Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format binary expressions with the new back-end. #1267

Merged
merged 4 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions lib/src/front_end/ast_node_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {

@override
void visitAsExpression(AsExpression node) {
throw UnimplementedError();
createInfix(node.expression, node.asOperator, node.type);
}

@override
Expand Down Expand Up @@ -96,7 +96,14 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {

@override
void visitBinaryExpression(BinaryExpression node) {
throw UnimplementedError();
createInfixChain<BinaryExpression>(
node,
precedence: node.operator.type.precedence,
(expression) => (
expression.leftOperand,
expression.operator,
expression.rightOperand
));
}

@override
Expand Down Expand Up @@ -290,7 +297,8 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {

@override
void visitExpressionStatement(ExpressionStatement node) {
throw UnimplementedError();
visit(node.expression);
token(node.semicolon);
}

@override
Expand Down Expand Up @@ -443,7 +451,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {

@override
void visitIntegerLiteral(IntegerLiteral node) {
throw UnimplementedError();
token(node.literal);
}

@override
Expand All @@ -458,7 +466,11 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {

@override
void visitIsExpression(IsExpression node) {
throw UnimplementedError();
createInfix(
node.expression,
node.isOperator,
operator2: node.notOperator,
node.type);
}

@override
Expand Down Expand Up @@ -541,7 +553,16 @@ class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {

@override
void visitNamedType(NamedType node) {
throw UnimplementedError();
// TODO(tall): Handle import prefix.
if (node.importPrefix != null) throw UnimplementedError();

token(node.name2);

// TODO(tall): Handle type arguments.
if (node.typeArguments != null) throw UnimplementedError();

// TODO(tall): Handle nullable types.
if (node.question != null) throw UnimplementedError();
}

@override
Expand Down
57 changes: 56 additions & 1 deletion lib/src/front_end/piece_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import '../piece/postfix.dart';
import '../piece/sequence.dart';
import 'piece_writer.dart';

/// Record type for a destructured binary operator-like syntactic construct.
typedef BinaryOperation = (AstNode left, Token operator, AstNode right);

/// Utility methods for creating pieces that share formatting logic across
/// multiple parts of the language.
///
Expand Down Expand Up @@ -134,19 +137,24 @@ mixin PieceFactory {
/// If [hanging] is `true` then the operator goes at the end of the first
/// line, like `+`. Otherwise, it goes at the beginning of the second, like
/// `as`.
///
/// The [operator2] parameter may be passed if the "operator" is actually two
/// separate tokens, as in `foo is! Bar`.
void createInfix(AstNode left, Token operator, AstNode right,
{bool hanging = false}) {
{bool hanging = false, Token? operator2}) {
var operands = <Piece>[];
visit(left);
operands.add(writer.pop());

if (hanging) {
writer.space();
token(operator);
token(operator2);
writer.split();
} else {
writer.split();
token(operator);
token(operator2);
writer.space();
}

Expand All @@ -155,6 +163,53 @@ mixin PieceFactory {
writer.push(InfixPiece(operands));
}

/// Creates a chained infix operation: a binary operator expression, or
/// binary pattern.
///
/// In a tree of binary AST nodes, all operators at the same precedence are
/// treated as a single chain of operators that either all split or none do.
/// Operands within those (which may themselves be chains of higher
/// precedence binary operators) are then formatted independently.
///
/// [T] is the type of node being visited and [destructure] is a callback
/// that takes one of those and yields the operands and operator. We need
/// this since there's no interface shared by the various binary operator
/// AST nodes.
///
/// If [precedence] is given, then this only flattens binary nodes with that
/// same precedence.
void createInfixChain<T extends AstNode>(
T node, BinaryOperation Function(T node) destructure,
{int? precedence}) {
var operands = <Piece>[];

void traverse(AstNode e) {
if (e is! T) {
visit(e);
operands.add(writer.pop());
} else {
var (left, operator, right) = destructure(e);
if (precedence != null && operator.type.precedence != precedence) {
// Binary node, but a different precedence, so don't flatten.
visit(e);
operands.add(writer.pop());
} else {
traverse(left);

writer.space();
token(operator);

writer.split();
traverse(right);
}
}
}

traverse(node);

writer.push(InfixPiece(operands));
}

/// Emit [token], along with any comments and formatted whitespace that comes
/// before it.
///
Expand Down
118 changes: 118 additions & 0 deletions test/expression/binary.stmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
40 columns |
>>> Multiplicative operators.
1*2/3~/4%5;
<<<
1 * 2 / 3 ~/ 4 % 5;
>>> Additive operators.
1+2-3;
<<<
1 + 2 - 3;
>>> Shift operators.
1<<2>>3>>>4;
<<<
1 << 2 >> 3 >>> 4;
>>> Bitwise operators.
1&2^3|4;
<<<
1 & 2 ^ 3 | 4;
>>> Relation operators (which are not associative in Dart).
1<2;
<<<
1 < 2;
>>>
1>2;
<<<
1 > 2;
>>>
1<=2;
<<<
1 <= 2;
>>>
1>=2;
<<<
1 >= 2;
>>> Equality operators (which are not associative in Dart).
1==2;
<<<
1 == 2;
>>> Equality operators.
1!=2;
<<<
1 != 2;
>>> Logical operators.
1&&2||3;
<<<
1 && 2 || 3;
>>> If-null operator.
foo??bar;
<<<
foo ?? bar;
>>> Unsplit operators with mixed precedence.
1+2/3-4*5%6<<7;
<<<
1 + 2 / 3 - 4 * 5 % 6 << 7;
>>> If any operator splits, they all do.
operand1 + operand2 + operand3 + operand4;
<<<
operand1 +
operand2 +
operand3 +
operand4;
>>> Mixed multiplicative operators split together.
longName * longName / longName % longName ~/ longName;
<<<
longName *
longName /
longName %
longName ~/
longName;
>>> Mixed additive operators split together.
longName + longName - longName + longName - longName;
<<<
longName +
longName -
longName +
longName -
longName;
>>> Mixed shift operators split together.
longName >> longName << longName >> longName >>> longName;
<<<
longName >>
longName <<
longName >>
longName >>>
longName;
>>> Mixed ascending precedence.
b___________________ || a______________ && a______________ == a______________ >
a______________ + a______________;
<<<
b___________________ ||
a______________ &&
a______________ ==
a______________ >
a______________ +
a______________;
>>> Mixed descending precedence.
b___________________ + a_______________ > a______________ == a______________ &&
a______________ || a______________;
<<<
b___________________ +
a_______________ >
a______________ ==
a______________ &&
a______________ ||
a______________;
>>> Mixture of same and different precedence.
veryLongIdentifier + veryLongIdentifier / veryLongIdentifier *
veryLongIdentifier - veryLongIdentifier * veryLongIdentifier +
veryLongIdentifier / veryLongIdentifier - veryLongIdentifier;
<<<
veryLongIdentifier +
veryLongIdentifier /
veryLongIdentifier *
veryLongIdentifier -
veryLongIdentifier *
veryLongIdentifier +
veryLongIdentifier /
veryLongIdentifier -
veryLongIdentifier;
28 changes: 28 additions & 0 deletions test/expression/type_test.stmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
40 columns |
>>>
foo as Bar;
<<<
foo as Bar;
>>>
foo is Bar;
<<<
foo is Bar;
>>>
foo is ! Bar;
<<<
foo is! Bar;
>>> Split `as` before operator.
extremelyLongIdentifier as VeryLongTypeName;
<<<
extremelyLongIdentifier
as VeryLongTypeName;
>>> Split `is` before operator.
extremelyLongIdentifier is VeryLongTypeName;
<<<
extremelyLongIdentifier
is VeryLongTypeName;
>>> Split `is!` before operator.
extremelyLongIdentifier is ! VeryLongTypeName;
<<<
extremelyLongIdentifier
is! VeryLongTypeName;
5 changes: 5 additions & 0 deletions test/statement/expression.stmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
40 columns |
>>> No space before ";".
expression ;
<<<
expression;
2 changes: 2 additions & 0 deletions test/tall_format_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:test/test.dart';
import 'utils.dart';

void main() async {
await testDirectory('expression', tall: true);
await testDirectory('statement', tall: true);
await testDirectory('top_level', tall: true);

// TODO(tall): The old formatter_test.dart has tests here for things like
Expand Down