| // Copyright (c) 2026, 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:meta/meta.dart'; | |
| import '../allocator.dart'; | |
| import '../base.dart'; | |
| import 'code.dart'; | |
| import 'expression.dart'; | |
| import 'pattern.dart'; | |
| import 'reference.dart'; | |
| part 'control.g.dart'; | |
| part 'control/branches.dart'; | |
| part 'control/handling.dart'; | |
| part 'control/loops.dart'; | |
| part 'control/switch.dart'; | |
| /// Knowledge of different types of control flow constructs in Dart. | |
| abstract class ControlVisitor<T> | |
| implements CodeVisitor<T>, ExpressionVisitor<T>, PatternVisitor<T> { | |
| T visitForLoop(ForLoop spec, [T? context]); | |
| T visitForInLoop(ForInLoop spec, [T? context]); | |
| T visitWhileLoop(WhileLoop spec, [T? context]); | |
| T visitConditional(Conditional spec, [T? context]); | |
| T visitBooleanCondition(BooleanCondition condition, [T? context]); | |
| T visitCaseCondition(CaseCondition condition, [T? context]); | |
| T visitTry(Try spec, [T? context]); | |
| T visitSwitchStatement(SwitchStatement spec, [T? context]); | |
| T visitSwitchExpression(SwitchExpression spec, [T? context]); | |
| } | |
| /// Knowledge of how to write valid Dart code from [ControlVisitor]. | |
| abstract mixin class ControlEmitter implements ControlVisitor<StringSink> { | |
| @protected | |
| Allocator get allocator; | |
| @override | |
| StringSink visitBooleanCondition( | |
| BooleanCondition condition, [ | |
| StringSink? output, | |
| ]) { | |
| output ??= StringBuffer(); | |
| condition.expression.accept(this, output); | |
| return output; | |
| } | |
| @override | |
| StringSink visitCaseCondition(CaseCondition condition, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| condition.object.accept(this, output); | |
| output.write(' case '); | |
| condition.pattern.accept(this, output); | |
| if (condition.guard != null) { | |
| output.write(' when '); | |
| condition.guard!.accept(this, output); | |
| } | |
| return output; | |
| } | |
| @override | |
| StringSink visitForLoop(ForLoop loop, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| if (loop.label != null) { | |
| output.writeln('${loop.label}:'); | |
| } | |
| output.write('for ('); | |
| if (loop.initialize != null) { | |
| loop.initialize!.accept(this, output); | |
| } | |
| output.write('; '); | |
| if (loop.condition != null) { | |
| loop.condition!.accept(this, output); | |
| } | |
| output.write('; '); | |
| if (loop.advance != null) { | |
| loop.advance!.accept(this, output); | |
| } | |
| output.writeln(') {'); | |
| if (loop.body != null) { | |
| loop.body!.accept(this, output); | |
| } | |
| output.write(' }'); | |
| return output; | |
| } | |
| @override | |
| StringSink visitForInLoop(ForInLoop loop, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| if (loop.label != null) { | |
| output.writeln('${loop.label}:'); | |
| } | |
| if (loop.async) { | |
| output.write('await '); | |
| } | |
| output.write('for ('); | |
| loop.variable.accept(this, output); | |
| output.write(' in '); | |
| loop.object.accept(this, output); | |
| output.writeln(') {'); | |
| if (loop.body != null) { | |
| loop.body!.accept(this, output); | |
| } | |
| output.write(' }'); | |
| return output; | |
| } | |
| @override | |
| StringSink visitWhileLoop(WhileLoop loop, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| if (loop.label != null) { | |
| output.writeln('${loop.label}:'); | |
| } | |
| if (loop.doWhile) { | |
| output.writeln('do {'); | |
| if (loop.body != null) { | |
| loop.body!.accept(this, output); | |
| } | |
| output.write(' } while ('); | |
| loop.condition.accept(this, output); | |
| output.write(');'); | |
| } else { | |
| output.write('while ('); | |
| loop.condition.accept(this, output); | |
| output.writeln(') {'); | |
| if (loop.body != null) { | |
| loop.body!.accept(this, output); | |
| } | |
| output.write(' }'); | |
| } | |
| return output; | |
| } | |
| @override | |
| StringSink visitConditional(Conditional conditional, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| for (var i = 0; i < conditional.branches.length; i++) { | |
| final branch = conditional.branches[i]; | |
| if (i == 0) { | |
| output.write('if ('); | |
| } else { | |
| output.write(' else if ('); | |
| } | |
| branch.condition.accept(this, output); | |
| output.writeln(') {'); | |
| if (branch.body != null) { | |
| branch.body!.accept(this, output); | |
| } | |
| output.write(' }'); | |
| } | |
| if (conditional.orElse != null) { | |
| output.writeln(' else {'); | |
| conditional.orElse!.accept(this, output); | |
| output.write(' }'); | |
| } | |
| return output; | |
| } | |
| @override | |
| StringSink visitTry(Try spec, [StringSink? output]) { | |
| output ??= StringBuffer(); | |
| output.writeln('try {'); | |
| if (spec.body != null) { | |
| spec.body!.accept(this, output); | |
| } | |
| output.write(' }'); | |
| for (final catchClause in spec.catches) { | |
| if (catchClause.on != null) { | |
| output.write(' on '); | |
| catchClause.on!.accept(this, output); | |
| } | |
| if (catchClause.exception != null || catchClause.stackTrace != null) { | |
| final ex = catchClause.exception ?? '_'; | |
| final st = catchClause.stackTrace != null | |
| ? ', ${catchClause.stackTrace}' | |
| : ''; | |
| output.write(' catch ($ex$st)'); | |
| } else if (catchClause.on == null) { | |
| output.write(' catch (_)'); | |
| } | |
| output.writeln(' {'); | |
| if (catchClause.body != null) { | |
| catchClause.body!.accept(this, output); | |
| } | |
| output.write(' }'); | |
| } | |
| if (spec.finallyBlock != null) { | |
| output.writeln(' finally {'); | |
| spec.finallyBlock!.accept(this, output); | |
| output.write(' }'); | |
| } | |
| return output; | |
| } | |
| @override | |
| StringSink visitSwitchStatement( | |
| SwitchStatement statement, [ | |
| StringSink? output, | |
| ]) { | |
| output ??= StringBuffer(); | |
| if (statement.label != null) { | |
| output.writeln('${statement.label}:'); | |
| } | |
| output.write('switch ('); | |
| statement.value.accept(this, output); | |
| output.writeln(') {'); | |
| for (final c in statement.cases) { | |
| if (c.label != null) { | |
| output.writeln('${c.label}:'); | |
| } | |
| output.write('case '); | |
| c.pattern.accept(this, output); | |
| if (c.guard != null) { | |
| output.write(' when '); | |
| c.guard!.accept(this, output); | |
| } | |
| output.writeln(':'); | |
| if (c.body != null) { | |
| c.body!.accept(this, output); | |
| } | |
| } | |
| if (statement.defaultCase != null) { | |
| output.writeln('default:'); | |
| statement.defaultCase!.accept(this, output); | |
| } | |
| output.write(' }'); | |
| return output; | |
| } | |
| @override | |
| StringSink visitSwitchExpression( | |
| SwitchExpression expression, [ | |
| StringSink? output, | |
| ]) { | |
| output ??= StringBuffer(); | |
| output.write('switch ('); | |
| expression.value.accept(this, output); | |
| output.writeln(') {'); | |
| for (final c in expression.cases) { | |
| c.pattern.accept(this, output); | |
| if (c.guard != null) { | |
| output.write(' when '); | |
| c.guard!.accept(this, output); | |
| } | |
| output.write(' => '); | |
| c.body.accept(this, output); | |
| output.writeln(','); | |
| } | |
| output.write(' }'); | |
| return output; | |
| } | |
| } |