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. The terminator token (if any) is included in the
010 * range. The matching source-offset interval
011 * {@code [startOffset, endOffset)} on the original input lets the
012 * region assembler (S15) reconstruct verbatim bytes from the
013 * {@code SourceSpanLedger}.
014 *
015 * <p>Plan reference: §6 layout, §7.3/S11, §7.4/S11.
016 */
017public final class StatementRange {
018
019    /** What ends a statement range. */
020    public enum Terminator {
021        /** Standard SQL semicolon. */
022        SEMICOLON,
023        /** SQL Server / Sybase {@code GO} batch separator (case-insensitive keyword). */
024        GO,
025        /** The range is the final segment of the script with no trailing terminator. */
026        NONE
027    }
028
029    private final int startTokenIndex;
030    private final int endTokenIndex;
031    private final int startOffset;
032    private final int endOffset;
033    private final Terminator terminator;
034
035    public StatementRange(int startTokenIndex, int endTokenIndex,
036                          int startOffset, int endOffset,
037                          Terminator terminator) {
038        if (terminator == null) throw new NullPointerException("terminator");
039        if (startTokenIndex < 0) {
040            throw new IllegalArgumentException(
041                "startTokenIndex < 0: " + startTokenIndex);
042        }
043        if (endTokenIndex < startTokenIndex) {
044            throw new IllegalArgumentException(
045                "endTokenIndex < startTokenIndex: "
046                + endTokenIndex + " < " + startTokenIndex);
047        }
048        if (startOffset < 0) {
049            throw new IllegalArgumentException(
050                "startOffset < 0: " + startOffset);
051        }
052        if (endOffset < startOffset) {
053            throw new IllegalArgumentException(
054                "endOffset < startOffset: "
055                + endOffset + " < " + startOffset);
056        }
057        this.startTokenIndex = startTokenIndex;
058        this.endTokenIndex = endTokenIndex;
059        this.startOffset = startOffset;
060        this.endOffset = endOffset;
061        this.terminator = terminator;
062    }
063
064    public int getStartTokenIndex() { return startTokenIndex; }
065    public int getEndTokenIndex() { return endTokenIndex; }
066    public int getStartOffset() { return startOffset; }
067    public int getEndOffset() { return endOffset; }
068    public Terminator getTerminator() { return terminator; }
069
070    /** Number of token slots in this range (including the terminator). */
071    public int tokenCount() { return endTokenIndex - startTokenIndex; }
072
073    /** True if the range carries no token slots. */
074    public boolean isEmpty() { return endTokenIndex == startTokenIndex; }
075
076    @Override
077    public String toString() {
078        return "StatementRange{tokens=[" + startTokenIndex + ".." + endTokenIndex
079            + ") chars=[" + startOffset + ".." + endOffset + ") " + terminator + "}";
080    }
081}