001package gudusoft.gsqlparser.pp2.token; 002 003/** 004 * A half-open {@code [startOffset, endOffset)} span over the original 005 * input SQL string, tagged with what kind of content lives there. 006 * 007 * <p>Used by {@link SourceSpanLedger} to cover every byte of the input. 008 * Immutable. 009 * 010 * <p>Plan reference: §6 ({@code token/SourceSpan.java}) and §10.3 (the 011 * construction sketch). 012 */ 013public final class SourceSpan { 014 015 /** Classification of what the span represents. */ 016 public enum Kind { 017 /** 018 * A solid lexer token: keyword, identifier, literal, operator, 019 * punctuation. Layout rules may freely re-space around it. 020 */ 021 TOKEN, 022 023 /** 024 * Whitespace (including newlines). The assembler may rewrite 025 * trivia spans according to the active layout rules. 026 */ 027 TRIVIA, 028 029 /** 030 * A region whose bytes must be emitted verbatim: comments, string 031 * literals, quoted identifiers, hints, {@code --BEGIN_NO_FORMAT} 032 * blocks. Slice S9 expands this classification. 033 */ 034 PROTECTED, 035 036 /** 037 * A byte range the tokenizer did not cover — pp2 records the gap 038 * here, never silently drops it. Every {@code RAW_FALLBACK} span 039 * yields a {@code FormatDiagnostic} on the result. 040 */ 041 RAW_FALLBACK 042 } 043 044 private final int startOffset; 045 private final int endOffset; 046 private final Kind kind; 047 private final String text; 048 049 public SourceSpan(int startOffset, int endOffset, Kind kind, String text) { 050 if (kind == null) throw new NullPointerException("kind"); 051 if (text == null) throw new NullPointerException("text"); 052 if (startOffset < 0) { 053 throw new IllegalArgumentException("startOffset < 0: " + startOffset); 054 } 055 if (endOffset < startOffset) { 056 throw new IllegalArgumentException( 057 "endOffset < startOffset: " + endOffset + " < " + startOffset); 058 } 059 if (text.length() != endOffset - startOffset) { 060 throw new IllegalArgumentException( 061 "text length " + text.length() + " does not match span width " 062 + (endOffset - startOffset) 063 + " (start=" + startOffset + " end=" + endOffset + ")"); 064 } 065 this.startOffset = startOffset; 066 this.endOffset = endOffset; 067 this.kind = kind; 068 this.text = text; 069 } 070 071 public int getStartOffset() { return startOffset; } 072 public int getEndOffset() { return endOffset; } 073 public Kind getKind() { return kind; } 074 public String getText() { return text; } 075 076 /** Convenience: the half-open width of the span. Equals {@code text.length()}. */ 077 public int length() { return endOffset - startOffset; } 078 079 @Override 080 public String toString() { 081 return "SourceSpan{" + kind + " [" + startOffset + ".." + endOffset 082 + "] '" + (text.length() > 40 ? text.substring(0, 40) + "…" : text) + "'}"; 083 } 084}