| // 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. |
| |
| part of '../expression.dart'; |
| |
| /// **INTERNAL** |
| /// |
| /// Represents a control expression. |
| /// |
| /// {@category controlFlow} |
| @internal |
| class ControlExpression extends Expression { |
| /// The control statement (e.g. `if`, `for`). |
| final String control; |
| |
| /// Zero or more expressions that make up this control |
| /// expression's body. |
| /// |
| /// If multiple expressions are provided, they will be |
| /// separated with [separator]. |
| /// |
| /// If [parenthesised] is `true`, the whole body will be |
| /// wrapped in parenthesis. |
| /// |
| /// If [body] is `null` or empty, the body will be omitted. |
| /// If individual items are `null`, they will be omitted, |
| /// but separators will still be inserted. |
| final List<Expression?>? body; |
| |
| /// Inserted between expressions in [body]. |
| /// |
| /// If body contains multiple items, a non-`null` separator is required. |
| /// An [ArgumentError] will be thrown if one is not provided. |
| /// |
| /// A space (" ") will be appended to the separator if it is followed |
| /// by an expression. If an item in [body] is `null` (resulting in a |
| /// blank string), no space will be inserted before it. |
| final String? separator; |
| |
| /// Whether or not the body should be wrapped in parenthesis (default: `true`) |
| final bool parenthesised; |
| |
| @visibleForTesting |
| const ControlExpression( |
| this.control, { |
| this.body, |
| this.separator, |
| this.parenthesised = true, |
| }); |
| |
| @override |
| R accept<R>(covariant ControlBlockVisitor<R> visitor, [R? context]) => |
| visitor.visitControlExpression(this, context); |
| |
| factory ControlExpression.ifStatement(Expression condition) => |
| ControlExpression('if', body: [condition]); |
| |
| factory ControlExpression.elseStatement(Expression? condition) => |
| ControlExpression( |
| 'else', |
| body: condition != null ? [condition] : null, |
| parenthesised: false, |
| ); |
| |
| factory ControlExpression.forLoop( |
| Expression? initialize, |
| Expression? condition, |
| Expression? advance, |
| ) => ControlExpression( |
| 'for', |
| body: [initialize, condition, advance], |
| separator: ';', |
| ); |
| |
| factory ControlExpression.forInLoop( |
| Expression identifier, |
| Expression expression, |
| ) => ControlExpression( |
| 'for', |
| body: [identifier, expression], |
| separator: ' in', |
| ); |
| |
| factory ControlExpression.awaitForLoop( |
| Expression identifier, |
| Expression expression, |
| ) => ControlExpression( |
| 'await for', |
| body: [identifier, expression], |
| separator: ' in', |
| ); |
| |
| factory ControlExpression.whileLoop(Expression condition) => |
| ControlExpression('while', body: [condition]); |
| |
| static const doStatement = ControlExpression('do'); |
| |
| static const tryStatement = ControlExpression('try'); |
| |
| factory ControlExpression.catchStatement( |
| String error, [ |
| String? stacktrace, |
| ]) => ControlExpression( |
| 'catch', |
| body: [refer(error), if (stacktrace != null) refer(stacktrace)], |
| separator: ',', |
| ); |
| |
| factory ControlExpression.onStatement( |
| Reference type, [ |
| ControlExpression? statement, |
| ]) => ControlExpression( |
| 'on', |
| body: [type, if (statement != null) statement], |
| parenthesised: false, |
| separator: '', |
| ); |
| |
| static const finallyStatement = ControlExpression('finally'); |
| |
| factory ControlExpression.switchStatement(Expression value) => |
| ControlExpression('switch', body: [value]); |
| } |
| |
| /// **INTERNAL** |
| /// |
| /// A collection control-flow expression |
| /// |
| /// Supports chaining when used in collections via [chainTarget] and [chain]. |
| /// These fields have no effect when used outside of collections. |
| /// |
| @internal |
| class CollectionExpression extends BinaryExpression { |
| /// Whether the [CollectionExpression] that follows this in a collection |
| /// may chain with it. Chained expressions will not have a comma between |
| /// them. |
| final bool chainTarget; |
| |
| /// Whether this [CollectionExpression] should try to chain with its |
| /// antecedent in collections. |
| final bool chain; |
| |
| const CollectionExpression._({ |
| required Expression control, |
| required Expression value, |
| this.chain = false, |
| this.chainTarget = false, |
| }) : super._(control, value, ''); |
| } |
| |
| /// Provides control-flow utilities for [Expression]. |
| /// |
| /// {@category controlFlow} |
| extension ControlFlow on Expression { |
| /// Returns `yield {this}` |
| Expression get yielded => |
| BinaryExpression._(Expression._empty, this, 'yield'); |
| |
| /// Returns `yield* {this}` |
| Expression get yieldStarred => |
| BinaryExpression._(Expression._empty, this, 'yield*'); |
| |
| /// Build a `while` loop from this expression. |
| /// |
| /// ```dart |
| /// while (this) { |
| /// body |
| /// } |
| /// ``` |
| WhileLoop loopWhile(void Function(BlockBuilder block) builder) => WhileLoop( |
| (loop) => |
| loop |
| ..condition = this |
| ..body.update(builder), |
| ); |
| |
| /// Build a `do-while` loop from this expression. |
| /// |
| /// ```dart |
| /// do { |
| /// body |
| /// } while (this); |
| /// ``` |
| WhileLoop loopDoWhile(void Function(BlockBuilder block) builder) => WhileLoop( |
| (loop) => |
| loop |
| ..doWhile = true |
| ..condition = this |
| ..body.update(builder), |
| ); |
| |
| /// Build a `for-in` loop from this expression in [object]. |
| /// |
| /// ```dart |
| /// for (this in object) { |
| /// body |
| /// } |
| /// ``` |
| ForInLoop loopForIn( |
| Expression object, |
| void Function(BlockBuilder block) builder, |
| ) => ForInLoop( |
| (loop) => |
| loop |
| ..object = object |
| ..variable = this |
| ..body.update(builder), |
| ); |
| |
| /// Build this expression into a [Conditional] |
| /// |
| /// ```dart |
| /// if (this) { |
| /// body |
| /// } |
| /// ``` |
| /// |
| /// Chain [Conditional.elseIf] and [Conditional.orElse] to |
| /// easily create a full `if-elseif-else` tree: |
| /// |
| /// ```dart |
| /// literal(1) |
| /// .equalTo(literal(2)) |
| /// .ifThen( |
| /// (body) => body.addExpression( |
| /// refer('print').call([literal('Bad')])) |
| /// ).elseIf( |
| /// (block) => block |
| /// ..condition = literal(2).equalTo(literal(2)) |
| /// ..body.addExpression(refer('print').call([literal('Good')])), |
| /// ).orElse( |
| /// (body) => body.addExpression( |
| /// refer('print').call([literal('What?')])), |
| /// ); |
| /// ``` |
| /// |
| /// Outputs: |
| /// ```dart |
| /// if (1 == 2) { |
| /// print('Bad'); |
| /// } else if (2 == 2) { |
| /// print('Good'); |
| /// } else { |
| /// print('What?'); |
| /// } |
| /// ``` |
| Conditional ifThen(void Function(BlockBuilder body) builder) => Conditional( |
| (tree) => |
| tree..add( |
| (branch) => |
| branch |
| ..condition = this |
| ..body.update(builder), |
| ), |
| ); |
| |
| /// Returns `if (this) return value` |
| Expression ifThenReturn([Expression? value]) => BinaryExpression._( |
| ControlExpression.ifStatement(this), |
| value == null ? ControlFlow.returnVoid : value.returned, |
| '', |
| ); |
| |
| /// `return` |
| static const returnVoid = LiteralExpression._('return'); |
| |
| /// `break` |
| /// |
| /// For usage with a label, use [breakLabel]. |
| static const breakVoid = LiteralExpression._('break'); |
| |
| /// `continue` |
| /// |
| /// For usage with a label, use [continueLabel]. |
| static const continueVoid = LiteralExpression._('continue'); |
| |
| /// `rethrow` |
| static const rethrowVoid = LiteralExpression._('rethrow'); |
| |
| /// Returns a labeled `break` statement. |
| /// |
| /// ```dart |
| /// break label |
| /// ``` |
| /// |
| /// For usage without a label, use [breakVoid]. |
| static Expression breakLabel(String label) => |
| BinaryExpression._(breakVoid, refer(label), ''); |
| |
| /// Returns a labeled `continue` statement. |
| /// |
| /// ```dart |
| /// continue label |
| /// ``` |
| /// |
| /// For usage without a label, use [continueVoid]. |
| static Expression continueLabel(String label) => |
| BinaryExpression._(continueVoid, refer(label), ''); |
| |
| /// Returns a case match expression for an `if-case` statement, |
| /// matching [object] against [pattern] with optional guard clause [guard]. |
| /// |
| /// ```dart |
| /// object case pattern |
| /// object case pattern when guard |
| /// ``` |
| /// |
| /// See https://dart.dev/language/branches#if-case |
| static Expression ifCase({ |
| required Expression object, |
| required Expression pattern, |
| Expression? guard, |
| }) { |
| final first = BinaryExpression._(object, pattern, 'case'); |
| if (guard == null) return first; |
| return BinaryExpression._(first, guard, 'when'); |
| } |
| |
| /// Returns a collection-`if` expression. |
| /// |
| /// ```dart |
| /// if (condition) value |
| /// ``` |
| /// |
| /// {@template controlflow.collection.chaining.if} |
| /// [collectionIf] expressions followed by [collectionElse] expressions |
| /// in a [literal] list, set, or map will be chained together, e.g: |
| /// |
| /// ```dart |
| /// literalMap({ |
| /// ControlFlow.collectionIf( |
| /// condition: literalTrue, value: refer('key') |
| /// ): refer('value'), |
| /// ControlFlow.collectionElse( |
| /// condition: literalFalse, |
| /// value: refer('key2') |
| /// ): refer('value2'), |
| /// ControlFlow.collectionElse(value: refer('key3') |
| /// ): refer('value3'), |
| /// }); |
| /// ``` |
| /// Outputs: |
| /// ```dart |
| /// { |
| /// if (true) key: value |
| /// else if (false) key2: value2 |
| /// else key3: value3 |
| /// } |
| /// ``` |
| /// {@endtemplate} |
| static Expression collectionIf({ |
| required Expression condition, |
| required Expression value, |
| }) => CollectionExpression._( |
| chainTarget: true, |
| control: ControlExpression.ifStatement(condition), |
| value: value, |
| ); |
| |
| /// Returns a collection-`else` expression. |
| /// |
| /// If [condition] is specified, returns a collection-`else if` expression. |
| /// |
| /// ```dart |
| /// else value |
| /// else if (condition) value |
| /// ``` |
| /// |
| /// {@macro controlflow.collection.chaining.if} |
| static Expression collectionElse({ |
| Expression? condition, |
| required Expression value, |
| }) => CollectionExpression._( |
| chain: true, |
| chainTarget: condition != null, |
| // only chainable if this is an else-if statement |
| control: ControlExpression.elseStatement( |
| condition == null ? null : ControlExpression.ifStatement(condition), |
| ), |
| value: value, |
| ); |
| |
| /// Returns a collection-`for` expression. |
| /// |
| /// ```dart |
| /// for (initialize; condition; advance) value |
| /// ``` |
| /// |
| /// {@template controlflow.collection.chaining.for} |
| /// If [value] is chainable (a [collectionIf], [collectionElse] with a |
| /// condition, or a collection-`for`/`for-in` containing one of those), |
| /// this will also be chainable. If this is followed by a collection-`else` |
| /// in a [literal] collection, they will be chained together. |
| /// |
| /// ```dart |
| /// literalMap({ |
| /// ControlFlow.collectionForIn( |
| /// identifier: declareFinal('x'), |
| /// expression: refer('items'), |
| /// value: ControlFlow.collectionIf( |
| /// condition: refer('x').property('valid'), |
| /// value: refer('key') |
| /// )): refer('x'), |
| /// ControlFlow.collectionElse(value: refer('key2') |
| /// ): refer('fix').call([refer('x')]) |
| /// }); |
| /// ``` |
| /// Outputs: |
| /// ```dart |
| /// { |
| /// for (final x in items) if (x.valid) key: x |
| /// else key2: fix |
| /// } |
| /// ``` |
| /// {@endtemplate} |
| static Expression collectionFor({ |
| Expression? initialize, |
| Expression? condition, |
| Expression? advance, |
| required Expression value, |
| }) => CollectionExpression._( |
| chainTarget: true, |
| control: ControlExpression.forLoop(initialize, condition, advance), |
| value: value, |
| ); |
| |
| /// Returns a collection-`for-in` expression |
| /// |
| /// ```dart |
| /// for (identifier in expression) value |
| /// ``` |
| /// |
| /// {@macro controlflow.collection.chaining.for} |
| static Expression collectionForIn({ |
| required Expression identifier, |
| required Expression expression, |
| required Expression value, |
| }) => CollectionExpression._( |
| chainTarget: value is CollectionExpression && value.chainTarget, |
| control: ControlExpression.forInLoop(identifier, expression), |
| value: value, |
| ); |
| } |