blob: 1add7120b17b92dcb2acf2038d4fec9cef062943 [file] [log] [blame]
// 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);
}