| // Copyright (c) 2019, 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. |
| |
| /// Repository of constraints corresponding to the code being migrated. |
| /// |
| /// This data structure carries information from the second pass of migration |
| /// ([ConstraintGatherer]) to the third (which creates individual code |
| /// modifications from each constraint). |
| abstract class Constraints { |
| /// Records a new constraint equation. |
| void record( |
| Iterable<ConstraintVariable> conditions, ConstraintVariable consequence); |
| } |
| |
| /// Representation of a single boolean variable in the constraint solver. |
| class ConstraintVariable { |
| /// A special boolean variable whose value is known to be `true`. |
| static final ConstraintVariable always = _Always(); |
| |
| /// A list of all constraints containing this variable on their left hand side |
| /// that may not have been satisfied yet. |
| final _dependencies = <_Clause>[]; |
| |
| /// The value assigned to this constraint variable by the solution currently |
| /// being computed. |
| bool _value = false; |
| |
| /// If this variable represents a disjunction ("or") of several other |
| /// variables, the variables in the disjunction. Otherwise a singleton list |
| /// containing `this`. |
| final List<ConstraintVariable> _disjunctionParts; |
| |
| ConstraintVariable() : _disjunctionParts = List.filled(1, null) { |
| _disjunctionParts[0] = this; |
| } |
| |
| /// Creates a [ConstraintVariable] representing a disjunction ("or") of |
| /// several other variables. |
| /// |
| /// Additional constraints will be recorded in [constraints] to ensure that |
| /// the solution will be consistent. |
| factory ConstraintVariable.or( |
| Constraints constraints, ConstraintVariable a, ConstraintVariable b) { |
| if (a == null) return b; |
| if (b == null) return a; |
| var parts = a.disjunctionParts.toList(); |
| parts.addAll(b.disjunctionParts); |
| assert(parts.length > 1); |
| var result = ConstraintVariable._(parts); |
| constraints.record([a], result); |
| constraints.record([b], result); |
| return result; |
| } |
| |
| ConstraintVariable._(this._disjunctionParts); |
| |
| /// If this variable represents a disjunction ("or") of several other |
| /// variables, the variables in the disjunction. Otherwise a singleton list |
| /// containing `this`. |
| Iterable<ConstraintVariable> get disjunctionParts => _disjunctionParts; |
| |
| /// Indicates whether this variable represents a disjunction ("or") of several |
| /// other variables. |
| bool get isDisjunction => _disjunctionParts.length > 1; |
| |
| /// The value assigned to this constraint variable by the solution currently |
| /// being computed. |
| get value => _value; |
| |
| @override |
| String toString() => |
| isDisjunction ? '(${_disjunctionParts.join(' | ')})' : super.toString(); |
| } |
| |
| /// The core of the migration tool's constraint solver. This class implements |
| /// unit propagation (see https://en.wikipedia.org/wiki/Unit_propagation), |
| /// extended to support disjunctions. |
| /// |
| /// The extension works approximately as follows: first we perform ordinary unit |
| /// propagation, accumulating a list of any disjunction variables that need to |
| /// be assigned a value of `true`. Once this finishes, we heuristically choose |
| /// one of these disjunction variables and ensure that it is assigned a value of |
| /// `true` by setting one of its constituent variables to `true` and propagating |
| /// again. Once all disjunctions have been resolved, we have a final solution. |
| class Solver extends Constraints { |
| /// Clauses that should be evaluated as part of ordinary unit propagation. |
| final _pending = <_Clause>[]; |
| |
| /// Disjunction variables that have been determined by unit propagation to be |
| /// `true`, but for which we have not yet propagated the `true` value to one |
| /// of the constituent variables. |
| final _pendingDisjunctions = <ConstraintVariable>[]; |
| |
| /// Heuristically resolves any pending disjunctions. |
| void applyHeuristics() { |
| while (_pendingDisjunctions.isNotEmpty) { |
| var disjunction = _pendingDisjunctions.removeLast(); |
| if (disjunction.disjunctionParts.any((v) => v.value)) continue; |
| // TODO(paulberry): smarter heuristics |
| var choice = disjunction.disjunctionParts.first; |
| record([], choice); |
| } |
| } |
| |
| @override |
| void record(Iterable<ConstraintVariable> conditions, |
| covariant ConstraintVariable consequence) { |
| var _conditions = List<ConstraintVariable>.from(conditions); |
| var clause = _Clause(_conditions, consequence); |
| int i = 0; |
| while (i < _conditions.length) { |
| ConstraintVariable variable = _conditions[i]; |
| if (variable._value) { |
| int j = _conditions.length - 1; |
| _conditions[i] = _conditions[j]; |
| _conditions.removeLast(); |
| continue; |
| } |
| variable._dependencies.add(clause); |
| i++; |
| } |
| if (i == 0) { |
| if (!consequence._value) { |
| consequence._value = true; |
| if (consequence.isDisjunction) { |
| _pendingDisjunctions.add(consequence); |
| } |
| _pending.addAll(consequence._dependencies); |
| _propagate(); |
| } |
| } else { |
| consequence._dependencies.add(clause); |
| } |
| } |
| |
| /// Performs ordinary unit propagation, recording any disjunctions encountered |
| /// in [_pendingDisjunctions]. |
| void _propagate() { |
| while (_pending.isNotEmpty) { |
| var clause = _pending.removeLast(); |
| var conditions = clause.conditions; |
| int i = 0; |
| while (i < conditions.length) { |
| ConstraintVariable variable = conditions[i]; |
| if (variable._value) { |
| int j = conditions.length - 1; |
| conditions[i] = conditions[j]; |
| conditions.removeLast(); |
| continue; |
| } |
| i++; |
| } |
| if (i == 0) { |
| var consequence = clause.consequence; |
| if (!consequence._value) { |
| consequence._value = true; |
| if (consequence.isDisjunction) { |
| _pendingDisjunctions.add(consequence); |
| } |
| _pending.addAll(consequence._dependencies); |
| } |
| } |
| } |
| } |
| } |
| |
| /// The special singleton [ConstraintVariable] whose value is always `true`. |
| class _Always extends ConstraintVariable { |
| _Always() { |
| _value = true; |
| } |
| |
| @override |
| String toString() => 'always'; |
| } |
| |
| /// A single equation in a system of constraints. |
| class _Clause { |
| /// The conditions on the left hand side of the equation. |
| final List<ConstraintVariable> conditions; |
| |
| /// The single variable on the right hand side of the equation. |
| final ConstraintVariable consequence; |
| |
| _Clause(this.conditions, this.consequence); |
| } |