code: private AstNumList parseNumList() {
      ArrayList<Double> bases = new ArrayList<>();
      ArrayList<Double> strides = new ArrayList<>();
      ArrayList<Long> counts = new ArrayList<>();
  
      while (skipWS() != ']') {
        double base = number();
        double count = 1;
        double stride = 1;
        if (skipWS() == ':') {
          eatChar(':');
          skipWS();
          count = number();
          if (count < 1 || ((long) count) != count)
            throw new IllegalASTException("Count must be a positive integer, got " + count);
        }
        if (skipWS() == ':') {
          eatChar(':');
          skipWS();
          stride = number();
          if (stride < 0 || Double.isNaN(stride))
            throw new IllegalASTException("Stride must be positive, got " + stride);
        }
        if (count == 1 && stride != 1)
          throw new IllegalASTException("If count is 1, then stride must be one (and ignored)");
        bases.add(base);
        counts.add((long) count);
        strides.add(stride);
        // Optional comma separating span
        if (skipWS() == ',') eatChar(',');
      }
  
      return new AstNumList(bases, strides, counts);
    }
docs: Parse a "num list". This could be either a plain list of numbers, or a range, or a list of ranges. For example
  [2 3 4 5 6 7] can also be written as [2:6] or [2:2 4:4:1]. The format of each "range" is `start:count[:stride]`,
  and it denotes the sequence {start, start + stride, ..., start + (count-1)*stride}. Here start and stride may
  be real numbers, however count must be a non-negative integer. Negative strides are also not allowed.