parseLineSpec function

Set<int> parseLineSpec(
  1. String? spec, {
  2. int first = 0,
  3. int last = _lastLine,
})

'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;
}