parseLineSpec function
'4', '4-9' or '1,4-9,12', as the set of numbers it names.
A set rather than a sorted list of ranges because the only question ever asked of it is "is this line in it", once per line. Anything unparseable is dropped rather than thrown: a marked line is an annotation, and a typo in one should cost the annotation, not the code.
Only the numbers from first to last are kept, and a range is cut to them
before it is walked, so '1-100000000' over a block of twelve lines is
twelve steps. A number too long for an int is past the end of any block.
Implementation
Set<int> parseLineSpec(String? spec, {int first = 0, int last = _lastLine}) {
final marked = <int>{};
if (spec == null) {
return marked;
}
final pattern = RegExp(r'^\s*(\d+)\s*(?:-\s*(\d+)\s*)?$');
for (final String part in spec.split(',')) {
final RegExpMatch? range = pattern.firstMatch(part);
if (range == null) {
continue;
}
final int from = int.tryParse(range.group(1)!) ?? _lastLine;
final int to = range.group(2) == null ? from : int.tryParse(range.group(2)!) ?? _lastLine;
// Written the wrong way round is still a range, and the reader who typed
// `9-4` meant the same four lines.
final int low = from < to ? from : to;
final int high = from > to ? from : to;
for (int line = low < first ? first : low; line <= (high > last ? last : high); line += 1) {
marked.add(line);
}
}
return marked;
}