blob: 4345e266e34e3cefa4649eb26d5e6bb09770ee5c [file] [log] [blame]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import '../back_end/code_writer.dart';
import 'piece.dart';
/// A piece for a series of binary expressions at the same precedence, like:
///
/// a + b + c
abstract base class InfixPiece extends Piece {
/// The series of operands.
///
/// Since we don't split on both sides of the operator, the operators will be
/// embedded in the operand pieces. If the operator is a hanging one, it will
/// be in the preceding operand, so `1 + 2` becomes "Infix(`1 +`, `2`)".
/// A leading operator like `foo as int` becomes "Infix(`foo`, `as int`)".
final List<Piece> _operands;
/// What kind of indentation should be applied to the subsequent operands.
final Indent _indent;
/// Creates an [InfixPiece] for the given series of [operands].
factory InfixPiece(
List<Piece> operands, {
required bool is3Dot7,
bool conditional = false,
Indent indent = Indent.expression,
}) {
if (is3Dot7) {
return _InfixPiece3Dot7(operands, indent);
} else {
return _InfixPiece(operands, indent, conditional);
}
}
/// Creates an [InfixPiece] for a conditional (`?:`) expression.
factory InfixPiece.conditional(
List<Piece> operands, {
required bool is3Dot7,
}) {
if (is3Dot7) {
return _InfixPiece3Dot7(operands, Indent.expression);
} else {
return _InfixPiece(operands, Indent.infix, true);
}
}
InfixPiece._(this._operands, this._indent);
@override
List<State> get additionalStates => const [State.split];
@override
Set<Shape> allowedChildShapes(State state, Piece child) =>
Shape.anyIf(state == State.split);
@override
void forEachChild(void Function(Piece piece) callback) {
_operands.forEach(callback);
}
@override
State? fixedStateForPageWidth(int pageWidth) {
var totalLength = 0;
for (var operand in _operands) {
// If any operand contains a newline, then we have to split.
if (operand.containsHardNewline) return State.split;
totalLength += operand.totalCharacters;
if (totalLength > pageWidth) break;
}
// If the total length doesn't fit in the page, then we have to split.
if (totalLength > pageWidth) return State.split;
return null;
}
}
/// InfixPiece subclass for 3.8 and newer style.
final class _InfixPiece extends InfixPiece {
/// Whether this piece is for a conditional expression.
final bool _isConditional;
_InfixPiece(super.operands, super.indent, this._isConditional) : super._();
@override
void format(CodeWriter writer, State state) {
writer.pushIndent(_indent);
// If this is a conditional expression (or chain of them), then allow the
// leading condition to be headline formatted in an assignment, like:
//
// variable = condition
// ? thenBranch
// : elseBranch;
//
// We only do this for conditional expressions and not other infix operators
// because with other operators, the operands are homogeneous and it makes
// more sense to split before the first one so that they are aligned in
// parallel:
//
// variable =
// operand +
// another;
if (_isConditional) writer.setShapeMode(ShapeMode.beforeHeadline);
writer.format(_operands[0]);
if (_isConditional) writer.setShapeMode(ShapeMode.afterHeadline);
for (var i = 1; i < _operands.length; i++) {
writer.splitIf(state == State.split);
// If this is a branch of a conditional expression, then indent the
// branch's contents past the `?` or `:`.
if (_isConditional) writer.pushIndent(Indent.block);
// We can format each operand separately if the operand is on its own
// line. This happens when the operator is split and we aren't the first
// or last operand.
var separate = state == State.split && i < _operands.length - 1;
writer.format(_operands[i], separate: separate);
if (_isConditional) writer.popIndent();
}
writer.popIndent();
}
}
/// [InfixPiece] subclass for 3.7 style.
final class _InfixPiece3Dot7 extends InfixPiece {
_InfixPiece3Dot7(super.operands, super.indent) : super._();
@override
void format(CodeWriter writer, State state) {
writer.pushIndent(_indent);
for (var i = 0; i < _operands.length; i++) {
// We can format each operand separately if the operand is on its own
// line. This happens when the operator is split and we aren't the first
// or last operand.
var separate = state == State.split && i > 0 && i < _operands.length - 1;
writer.format(_operands[i], separate: separate);
if (i < _operands.length - 1) writer.splitIf(state == State.split);
}
writer.popIndent();
}
}