blob: f2d1bf416be4378352fb040b2570a6711f8780b9 [file] [log] [blame]
// Copyright (c) 2015, 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.
library dart_style.src.rule.rule;
import '../chunk.dart';
import '../fast_hash.dart';
/// A constraint that determines the different ways a related set of chunks may
/// be split.
abstract class Rule extends FastHash {
/// The number of different states this rule can be in.
///
/// Each state determines which set of chunks using this rule are split and
/// which aren't. Values range from zero to one minus this. Value zero
/// always means "no chunks are split" and increasing values by convention
/// mean increasingly undesirable splits.
int get numValues;
/// The rule value that forces this rule into its maximally split state.
///
/// By convention, this is the highest of the range of allowed values.
int get fullySplitValue => numValues - 1;
int get cost => Cost.normal;
/// The span of [Chunk]s that were written while this rule was still in
/// effect.
///
/// This is used to tell which rules should be pre-emptively split if their
/// contents are too long. This may be a wider range than the set of chunks
/// enclosed by chunks whose rule is this one. A rule may still be on the
/// list of open rules for a while after its last chunk is written.
// TODO(rnystrom): These are only used by preemption which is kind of hacky.
// Remove this if preemption is redone.
int start;
int end;
/// The other [Rule]s that "surround" this one (and care about that fact).
///
/// In many cases, if a split occurs inside an expression, surrounding rules
/// also want to split too. For example, a split in the middle of an argument
/// forces the entire argument list to also split.
///
/// This tracks those relationships. If this rule splits, (sets its value to
/// [fullySplitValue]) then all of the outer rules will also be set to their
/// fully split value.
///
/// This contains all direct as well as transitive relationships. If A
/// contains B which contains C, C's outerRules contains both B and A.
Iterable<Rule> get outerRules => _outerRules;
final Set<Rule> _outerRules = new Set<Rule>();
/// Adds [inner] as an inner rule of this rule if it cares about inner rules.
///
/// When an inner rule splits, it forces any surrounding outer rules to also
/// split.
void contain(Rule inner) {
if (!splitsOnInnerRules) return;
inner._outerRules.add(this);
}
/// Whether this rule cares about rules that it contains.
///
/// If `true` then inner rules will constrain this one and force it to split
/// when they split. Otherwise, it can split independently of any contained
/// rules.
bool get splitsOnInnerRules => true;
bool isSplit(int value, Chunk chunk);
/// Given that this rule has [value], determine if [other]'s value should be
/// constrained.
///
/// Allows relationships between rules like "if I split, then this should
/// split too". Returns a non-negative value to force [other] to take that
/// value. Returns -1 to allow [other] to take any non-zero value. Returns
/// null to not constrain other.
int constrain(int value, Rule other) {
// By default, any implied rule will be fully split if this one is fully
// split.
if (value == 0) return null;
if (_outerRules.contains(other)) return other.fullySplitValue;
return null;
}
/// Like [constrain], but in the other direction.
///
/// If [other] has [otherValue], returns the constrained value this rule may
/// have, or `null` if any value is allowed.
int reverseConstrain(int otherValue, Rule other) {
// If [other] did not fully split, then we can't split either if us
// splitting implies that it should have.
if (otherValue == other.fullySplitValue) return null;
if (_outerRules.contains(other)) return 0;
return null;
}
String toString() => "$id";
}
/// A rule that always splits a chunk.
class HardSplitRule extends Rule {
int get numValues => 1;
/// It's always going to be applied, so there's no point in penalizing it.
///
/// Also, this avoids doubled counting in literal blocks where there is both
/// a split in the outer chunk containing the block and the inner hard split
/// between the elements or statements.
int get cost => 0;
/// It's always split anyway.
bool get splitsOnInnerRules => false;
bool isSplit(int value, Chunk chunk) => true;
String toString() => "Hard";
}
/// A basic rule that has two states: unsplit or split.
class SimpleRule extends Rule {
/// Two values: 0 is unsplit, 1 is split.
int get numValues => 2;
final int cost;
final bool splitsOnInnerRules;
SimpleRule({int cost, bool splitsOnInnerRules})
: cost = cost != null ? cost : Cost.normal,
splitsOnInnerRules = splitsOnInnerRules != null
? splitsOnInnerRules
: true;
bool isSplit(int value, Chunk chunk) => value == 1;
String toString() => "Simple${super.toString()}";
}