2016-05-24 03:01:15 +02:00
|
|
|
// Copyright 2016 Google Inc. Use of this source code is governed by an
|
|
|
|
// MIT-style license that can be found in the LICENSE file or at
|
|
|
|
// https://opensource.org/licenses/MIT.
|
|
|
|
|
2016-05-24 23:01:41 +02:00
|
|
|
import 'dart:collection';
|
2016-08-05 02:25:57 +02:00
|
|
|
import 'dart:math' as math;
|
2016-05-24 23:01:41 +02:00
|
|
|
|
2016-06-06 08:51:50 +02:00
|
|
|
import 'package:charcode/charcode.dart';
|
2016-08-13 00:24:27 +02:00
|
|
|
import 'package:collection/collection.dart';
|
2016-05-24 03:01:15 +02:00
|
|
|
import 'package:source_span/source_span.dart';
|
|
|
|
|
|
|
|
import 'ast/node.dart';
|
2016-06-03 22:36:20 +02:00
|
|
|
import 'value/number.dart';
|
|
|
|
|
2016-06-04 01:31:29 +02:00
|
|
|
const _epsilon = 1 / (10 * SassNumber.precision);
|
2016-05-24 03:01:15 +02:00
|
|
|
|
2016-06-10 02:55:04 +02:00
|
|
|
class LinkedListValue<T> extends LinkedListEntry<LinkedListValue<T>> {
|
|
|
|
final T value;
|
|
|
|
|
|
|
|
LinkedListValue(this.value);
|
|
|
|
}
|
|
|
|
|
2016-08-15 07:32:00 +02:00
|
|
|
/// A pair of values.
|
|
|
|
class Pair<E, F> {
|
|
|
|
final E first;
|
|
|
|
final F last;
|
|
|
|
|
|
|
|
Pair(this.first, this.last);
|
|
|
|
|
|
|
|
String toString() => '($first, $last)';
|
|
|
|
|
|
|
|
bool operator==(other) {
|
|
|
|
if (other is! Pair) return false;
|
|
|
|
return other.first == first && other.last == last;
|
|
|
|
}
|
|
|
|
|
|
|
|
int get hashCode => first.hashCode ^ last.hashCode;
|
|
|
|
}
|
|
|
|
|
2016-08-13 00:24:27 +02:00
|
|
|
bool listEquals/*<T>*/(List/*<T>*/ list1, List/*<T>*/ list2) =>
|
|
|
|
const ListEquality().equals(list1, list2);
|
|
|
|
|
2016-08-13 01:53:19 +02:00
|
|
|
int listHash(List list) => const ListEquality().hash(list);
|
|
|
|
|
2016-06-06 09:05:04 +02:00
|
|
|
FileSpan spanForList(List<AstNode> nodes) {
|
2016-05-24 03:01:15 +02:00
|
|
|
if (nodes.isEmpty) return null;
|
2016-06-06 09:05:04 +02:00
|
|
|
return nodes.first.span.expand(nodes.last.span);
|
2016-05-24 03:01:15 +02:00
|
|
|
}
|
2016-05-24 23:01:41 +02:00
|
|
|
|
2016-06-06 08:51:50 +02:00
|
|
|
String unvendor(String name) {
|
2016-08-19 23:25:44 +02:00
|
|
|
if (name.length < 2) return name;
|
|
|
|
if (name.codeUnitAt(0) != $dash) return name;
|
|
|
|
if (name.codeUnitAt(1) == $dash) return name;
|
2016-06-06 08:51:50 +02:00
|
|
|
|
2016-08-19 23:25:44 +02:00
|
|
|
for (var i = 2; i < name.length; i++) {
|
|
|
|
if (name.codeUnitAt(i) == $dash) return name.substring(i + 1);
|
2016-06-06 08:51:50 +02:00
|
|
|
}
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2016-08-20 00:54:57 +02:00
|
|
|
bool equalsIgnoreSeparator(String string1, String string2) {
|
|
|
|
if (identical(string1, string2)) return true;
|
|
|
|
if (string1 == null || string2 == null) return false;
|
|
|
|
if (string1.length != string2.length) return false;
|
|
|
|
for (var i = 0; i < string1.length; i++) {
|
|
|
|
var codeUnit1 = string1.codeUnitAt(i);
|
|
|
|
var codeUnit2 = string2.codeUnitAt(i);
|
|
|
|
if (codeUnit1 == codeUnit2) continue;
|
|
|
|
if (codeUnit1 == $dash) {
|
|
|
|
if (codeUnit2 != $underscore) return false;
|
|
|
|
} else if (codeUnit1 == $underscore) {
|
|
|
|
if (codeUnit2 != $dash) return false;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int hashCodeIgnoreSeparator(String string) {
|
|
|
|
var hash = 4603;
|
|
|
|
for (var i = 0; i < string.length; i++) {
|
|
|
|
var codeUnit = string.codeUnitAt(i);
|
|
|
|
if (codeUnit == $underscore) codeUnit = $dash;
|
|
|
|
hash &= 0x3FFFFFF;
|
|
|
|
hash *= 33;
|
|
|
|
hash ^= codeUnit;
|
|
|
|
}
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
2016-06-10 02:55:04 +02:00
|
|
|
bool equalsIgnoreCase(String string1, String string2) {
|
2016-08-20 00:54:57 +02:00
|
|
|
if (identical(string1, string2)) return true;
|
|
|
|
if (string1 == null || string2 == null) return false;
|
2016-06-10 02:55:04 +02:00
|
|
|
if (string1.length != string2.length) return false;
|
|
|
|
return string1.toUpperCase() == string2.toUpperCase();
|
2016-05-24 23:01:41 +02:00
|
|
|
}
|
2016-06-03 22:36:20 +02:00
|
|
|
|
2016-08-27 06:59:28 +02:00
|
|
|
Map/*<String, V>*/ normalizedMap/*<V>*/() =>
|
2016-08-20 00:54:57 +02:00
|
|
|
new LinkedHashMap(
|
|
|
|
equals: equalsIgnoreSeparator, hashCode: hashCodeIgnoreSeparator);
|
|
|
|
|
2016-08-27 06:59:28 +02:00
|
|
|
Map/*<String, V2>*/ normalizedMapMap/*<K, V1, V2>*/(
|
2016-08-20 01:40:44 +02:00
|
|
|
Map/*<K, V1>*/ map,
|
|
|
|
{String key(/*=K*/ key, /*=V1*/ value),
|
|
|
|
/*=V2*/ value(/*=K*/ key, /*=V1*/ value)}) {
|
|
|
|
key ??= (mapKey, _) => mapKey as String;
|
|
|
|
value ??= (_, mapValue) => mapValue as dynamic/*=V2*/;
|
|
|
|
|
2016-08-27 06:59:28 +02:00
|
|
|
var result = normalizedMap/*<V2>*/();
|
2016-08-20 01:40:44 +02:00
|
|
|
map.forEach((mapKey, mapValue) {
|
|
|
|
result[key(mapKey, mapValue)] = value(mapKey, mapValue);
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-06-03 22:36:20 +02:00
|
|
|
bool almostEquals(num number1, num number2) =>
|
|
|
|
(number1 - number2).abs() < _epsilon;
|
2016-06-10 02:55:04 +02:00
|
|
|
|
2016-08-02 20:36:34 +02:00
|
|
|
List/*<T>*/ longestCommonSubsequence/*<T>*/(List/*<T>*/ list1,
|
|
|
|
List/*<T>*/ list2, {/*=T*/ select(/*=T*/ element1, /*=T*/ element2)}) {
|
|
|
|
select ??= (element1, element2) => element1 == element2 ? element1 : null;
|
|
|
|
|
|
|
|
var lengths = new List.generate(
|
|
|
|
list1.length + 1, (_) => new List.filled(list2.length + 1, 0),
|
|
|
|
growable: false);
|
|
|
|
|
2016-08-15 05:19:27 +02:00
|
|
|
var selections = new List<List/*<T>*/>.generate(
|
2016-08-02 20:36:34 +02:00
|
|
|
list1.length, (_) => new List/*<T>*/(list2.length),
|
|
|
|
growable: false);
|
|
|
|
|
|
|
|
// TODO(nweiz): Calling [select] here may be expensive. Can we use a memoizing
|
|
|
|
// approach to avoid calling it O(n*m) times in most cases?
|
|
|
|
for (var i = 0; i < list1.length; i++) {
|
|
|
|
for (var j = 0; j < list2.length; j++) {
|
|
|
|
var selection = select(list1[i], list2[j]);
|
|
|
|
selections[i][j] = selection;
|
|
|
|
lengths[i + 1][j + 1] = selection == null
|
|
|
|
? math.max(lengths[i + 1][j], lengths[i][j + 1])
|
|
|
|
: lengths[i][j] + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
List/*<T>*/ backtrack(int i, int j) {
|
|
|
|
if (i == -1 || j == -1) return [];
|
|
|
|
var selection = selections[i][j];
|
|
|
|
if (selection != null) return backtrack(i - 1, j - 1)..add(selection);
|
|
|
|
|
|
|
|
return lengths[i + 1][j] > lengths[i][j + 1]
|
|
|
|
? backtrack(i, j - 1)
|
|
|
|
: backtrack(i - 1, j);
|
|
|
|
}
|
|
|
|
|
|
|
|
return backtrack(list1.length - 1, list2.length - 1);
|
|
|
|
}
|