blob: 57aeba2dcea0c89ee98dac2fc6cfb0e0c451826d [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.
/// Set of features used in annotations.
class Features {
Map<String, Object> _features = {};
void add(String key, {var value: ''}) {
_features[key] = value.toString();
}
void addElement(String key, [var value]) {
List<String> list = _features.putIfAbsent(key, () => <String>[]);
if (value != null) {
list.add(value.toString());
}
}
bool containsKey(String key) {
return _features.containsKey(key);
}
void operator []=(String key, String value) {
_features[key] = value;
}
String operator [](String key) => _features[key];
String remove(String key) => _features.remove(key);
void forEach(void Function(String, Object) f) {
_features.forEach(f);
}
/// Returns a string containing all features in a comma-separated list sorted
/// by feature names.
String getText() {
StringBuffer sb = new StringBuffer();
bool needsComma = false;
for (String name in _features.keys.toList()..sort()) {
dynamic value = _features[name];
if (value != null) {
if (needsComma) {
sb.write(',');
}
sb.write(name);
if (value is List<String>) {
value = '[${(value..sort()).join(',')}]';
}
if (value != '') {
sb.write('=');
sb.write(value);
}
needsComma = true;
}
}
return sb.toString();
}
/// Creates a [Features] object by parse the [text] encoding.
///
/// Single features will be parsed as strings and list features (features
/// encoded in `[...]` will be parsed as lists of strings.
static Features fromText(String text) {
Features features = new Features();
int index = 0;
while (index < text.length) {
int eqPos = text.indexOf('=', index);
int commaPos = text.indexOf(',', index);
String name;
bool hasValue = false;
if (eqPos != -1 && commaPos != -1) {
if (eqPos < commaPos) {
name = text.substring(index, eqPos);
hasValue = true;
index = eqPos + 1;
} else {
name = text.substring(index, commaPos);
index = commaPos + 1;
}
} else if (eqPos != -1) {
name = text.substring(index, eqPos);
hasValue = true;
index = eqPos + 1;
} else if (commaPos != -1) {
name = text.substring(index, commaPos);
index = commaPos + 1;
} else {
name = text.substring(index);
index = text.length;
}
if (hasValue) {
const Map<String, String> delimiters = const {
'[': ']',
'{': '}',
'(': ')',
'<': '>'
};
List<String> endDelimiters = <String>[];
bool isList = index < text.length && text.startsWith('[', index);
if (isList) {
features.addElement(name);
endDelimiters.add(']');
index++;
}
int valueStart = index;
while (index < text.length) {
String char = text.substring(index, index + 1);
if (endDelimiters.isNotEmpty && endDelimiters.last == char) {
endDelimiters.removeLast();
index++;
} else {
String endDelimiter = delimiters[char];
if (endDelimiter != null) {
endDelimiters.add(endDelimiter);
index++;
} else if (char == ',') {
if (endDelimiters.isEmpty) {
break;
} else if (endDelimiters.length == 1 && isList) {
String value = text.substring(valueStart, index);
features.addElement(name, value);
index++;
valueStart = index;
} else {
index++;
}
} else {
index++;
}
}
}
if (isList) {
String value = text.substring(valueStart, index - 1);
if (value.isNotEmpty) {
features.addElement(name, value);
}
} else {
String value = text.substring(valueStart, index);
features.add(name, value: value);
}
index++;
} else {
features.add(name);
}
}
return features;
}
}