001package gudusoft.gsqlparser.pp2.region;
002
003/**
004 * Immutable record of a single statement region produced by
005 * {@link StatementBoundaryDetector}.
006 *
007 * <p>A range covers a half-open token index interval
008 * {@code [startTokenIndex, endTokenIndex)} on the {@code Pp2TokenStream}
009 * that produced it. A {@code ;} terminator is included in the range; a
010 * {@code GO} batch separator (and the count of {@code GO n}) is not: it
011 * lies in the gap before the next range, where the assembler emits it
012 * verbatim. The matching source-offset interval
013 * {@code [startOffset, endOffset)} on the original input lets the
014 * region assembler (S15) reconstruct verbatim bytes from the
015 * {@code SourceSpanLedger}.
016 *
017 * <p>Plan reference: §6 layout, §7.3/S11, §7.4/S11.
018 */
019public final class StatementRange {
020
021    /** What ends a statement range. */
022    public enum Terminator {
023        /** Standard SQL semicolon. */
024        SEMICOLON,
025        /**
026         * SQL Server / Sybase {@code GO} batch separator (case-insensitive keyword), which
027         * follows the range rather than ending it.
028         */
029        GO,
030        /** The range is the final segment of the script with no trailing terminator. */
031        NONE
032    }
033
034    private final int startTokenIndex;
035    private final int endTokenIndex;
036    private final int startOffset;
037    private final int endOffset;
038    private final Terminator terminator;
039
040    public StatementRange(int startTokenIndex, int endTokenIndex,
041                          int startOffset, int endOffset,
042                          Terminator terminator) {
043        if (terminator == null) throw new NullPointerException("terminator");
044        if (startTokenIndex < 0) {
045            throw new IllegalArgumentException(
046                "startTokenIndex < 0: " + startTokenIndex);
047        }
048        if (endTokenIndex < startTokenIndex) {
049            throw new IllegalArgumentException(
050                "endTokenIndex < startTokenIndex: "
051                + endTokenIndex + " < " + startTokenIndex);
052        }
053        if (startOffset < 0) {
054            throw new IllegalArgumentException(
055                "startOffset < 0: " + startOffset);
056        }
057        if (endOffset < startOffset) {
058            throw new IllegalArgumentException(
059                "endOffset < startOffset: "
060                + endOffset + " < " + startOffset);
061        }
062        this.startTokenIndex = startTokenIndex;
063        this.endTokenIndex = endTokenIndex;
064        this.startOffset = startOffset;
065        this.endOffset = endOffset;
066        this.terminator = terminator;
067    }
068
069    public int getStartTokenIndex() { return startTokenIndex; }
070    public int getEndTokenIndex() { return endTokenIndex; }
071    public int getStartOffset() { return startOffset; }
072    public int getEndOffset() { return endOffset; }
073    public Terminator getTerminator() { return terminator; }
074
075    /** Number of token slots in this range (a {@code ;} included, a following {@code GO} not). */
076    public int tokenCount() { return endTokenIndex - startTokenIndex; }
077
078    /** True if the range carries no token slots. */
079    public boolean isEmpty() { return endTokenIndex == startTokenIndex; }
080
081    @Override
082    public String toString() {
083        return "StatementRange{tokens=[" + startTokenIndex + ".." + endTokenIndex
084            + ") chars=[" + startOffset + ".." + endOffset + ") " + terminator + "}";
085    }
086}