private JmespathExpression parseIndex() {
        int line = iterator.line();
        int column = iterator.column();
        Integer[] parts = new Integer[] {null, null, 1}; // start, stop, step (defaults to 1)
        int pos = 0;

        loop: while (true) {
            Token next = iterator.expectPeek(TokenType.NUMBER, TokenType.RBRACKET, TokenType.COLON);
            switch (next.type) {
                case NUMBER:
                    iterator.expect(TokenType.NUMBER);
                    parts[pos] = next.value.expectNumberValue().intValue();
                    iterator.expectPeek(TokenType.COLON, TokenType.RBRACKET);
                    break;
                case RBRACKET:
                    break loop;
                default: // COLON
                    iterator.expect(TokenType.COLON);
                    if (++pos == 3) {
                        throw iterator.syntax("Too many colons in slice expression");
                    }
                    break;
            }
        }

        iterator.expect(TokenType.RBRACKET);

        if (pos == 0) {
            // No colons were found, so this is a simple index extraction.
            return new IndexExpression(parts[0], line, column);
        }

        // Sliced array from start (e.g., [2:]). A projection is created here
        // because a projection has very similar semantics to what's actually
        // happening here (i.e., turn the LHS into an array, take specific
        // items from it, then pass the result to RHS). The only difference
        // between foo[*] and foo[1:] is the size of the array. Anything that
        // selects more than one element is a generally a projection.
        JmespathExpression slice = new SliceExpression(parts[0], parts[1], parts[2], line, column);
        JmespathExpression rhs = parseProjectionRhs(TokenType.STAR.lbp);
        return new ProjectionExpression(slice, rhs, line, column);
    }
Tokens that can follow led tokens.
smithy-jmespath/src/main/java/software/amazon/smithy/jmespath/Parser.java