blob: 1e63a801c89df41d301039d2994869c96804ac22 [file] [edit]
// Copyright (c) 2025, 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 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import '../base.dart';
import 'code.dart';
import 'expression.dart';
import 'reference.dart';
part 'control.g.dart';
part '../mixins/control.dart';
part './control/loops.dart';
part './control/branches.dart';
part './control/handling.dart';
part './control/switch.dart';
/// Knowledge of different types of control blocks.
///
@internal
abstract class ControlBlockVisitor<T>
implements ExpressionVisitor<T>, CodeVisitor<T> {
T visitControlBlock(ControlBlock block, [T? context]);
T visitLabeledBlock(LabeledControlBlock block, [T? context]);
T visitWhileLoop(WhileLoop loop, [T? context]);
T visitControlTree(ControlTree tree, [T? context]);
T visitControlExpression(ControlExpression expression, [T? context]);
T visitSwitch(Switch statement, [T? context]);
// [context] is actually used, but the analyzer doesn't detect it.
// ignore: unused_element_parameter
T _visitCaseStatement(CaseStatement statement, [T? context]);
// [context] is actually used, but the analyzer doesn't detect it.
// ignore: unused_element_parameter
T _visitCaseExpression(CaseExpression expression, [T? context]);
}
/// Knowledge of how to write valid Dart code from [ControlBlockVisitor].
///
@internal
abstract mixin class ControlBlockEmitter
implements ControlBlockVisitor<StringSink> {
@override
StringSink visitControlBlock(ControlBlock block, [StringSink? output]) {
output ??= StringBuffer();
block._expression.accept(this, output);
output.writeln(' {');
block.body.accept(this, output);
output.write(' }');
return output;
}
@override
StringSink visitLabeledBlock(
LabeledControlBlock block, [
StringSink? output,
]) {
output ??= StringBuffer();
if (block.label != null) {
output.writeln('${block.label!}:');
}
return visitControlBlock(block, output);
}
@override
StringSink visitWhileLoop(WhileLoop loop, [StringSink? output]) {
output ??= StringBuffer();
visitLabeledBlock(loop, output);
if (loop.doWhile != true) return output;
output.write(' ');
loop._statement.statement.accept(this, output);
output.writeln();
return output;
}
@override
StringSink visitControlTree(ControlTree tree, [StringSink? output]) {
output ??= StringBuffer();
for (final item in tree._blocks.nonNulls) {
item.accept(this, output);
output.write(' ');
}
return output;
}
@override
StringSink visitControlExpression(
ControlExpression expression, [
StringSink? output,
]) {
output ??= StringBuffer();
output.write(expression.control);
if (expression.body == null || expression.body!.isEmpty) {
return output;
}
final body = expression.body!; // convenience
output.write(' ');
if (expression.parenthesised) {
output.write('(');
}
if (body.length == 1) {
body.first?.accept(this, output);
if (expression.parenthesised) {
output.write(')');
}
return output;
}
if (expression.separator == null) {
throw ArgumentError(
'A separator must be provided when body contains '
'multiple expressions.',
'separator',
);
}
final separator = expression.separator!; // convenience
for (var i = 0; i < body.length; i++) {
final expression = body[i];
if (i != 0 && expression != null) {
output.write(' ');
}
expression?.accept(this, output);
if (i == body.length - 1) continue; // no separator after last item
output.write(separator);
}
if (expression.parenthesised) {
output.write(')');
}
return output;
}
@override
StringSink visitSwitch(Switch statement, [StringSink? output]) {
output ??= StringBuffer();
final buildable = BuildableSwitch(
value: statement.value,
cases: statement._cases,
);
return visitControlBlock(buildable, output);
}
@override
StringSink _visitCaseStatement(
CaseStatement statement, [
StringSink? output,
]) {
output ??= StringBuffer();
if (statement.label case final String label) {
output.writeln('$label:');
}
if (statement._default) {
output.writeln('default:');
} else {
output.write('case ');
statement.pattern.accept(this, output);
if (statement.guard case final Expression guard) {
output.write(' when ');
guard.accept(this, output);
}
output.writeln(':');
}
if (statement.body case final Code body) {
body.accept(this, output);
}
return output;
}
@override
StringSink _visitCaseExpression(
CaseExpression expression, [
StringSink? output,
]) {
output ??= StringBuffer();
expression.pattern.accept(this, output);
if (expression.guard case final Expression guard) {
output.write(' when ');
guard.accept(this, output);
}
output.write(' => ');
// body will never be null; CaseExpression ensures a value is
// provided when it is constructed
expression.body!.accept(this, output);
output.writeln(',');
return output;
}
}