001package gudusoft.gsqlparser.ir.semantic; 002 003import gudusoft.gsqlparser.TSourceToken; 004import gudusoft.gsqlparser.nodes.TParseTreeNode; 005 006import java.util.Objects; 007 008/** 009 * Half-open source-text range, {@code [startLine:startColumn, 010 * endLine:endColumn)}. Used by {@link Diagnostic} to point at the 011 * offending AST node when a reject site has one in scope. 012 * 013 * <p>{@code startLine} / {@code startColumn} mark the first character 014 * of the anchor (1-based as populated by GSP's lexer via 015 * {@link TSourceToken#lineNo} / {@link TSourceToken#columnNo}). 016 * {@code endLine} / {@code endColumn} mark the position 017 * <strong>one past</strong> the last character. For a single-line 018 * single-token anchor of length {@code L}, {@code endColumn == 019 * startColumn + L}. Multi-line tokens (e.g. triple-quoted strings) 020 * are handled by counting newlines in the end token's text. 021 * 022 * <p>The class is intentionally null-tolerant at the factory level: 023 * {@link #of(TParseTreeNode)} returns {@code null} when the node or 024 * either of its boundary tokens is {@code null}. Diagnostic factories 025 * pass this {@code null} through to {@link Diagnostic#getSpan()} 026 * unchanged. 027 */ 028public final class SourceSpan { 029 030 private final long startLine; 031 private final long startColumn; 032 private final long endLine; 033 private final long endColumn; 034 035 private SourceSpan(long startLine, long startColumn, 036 long endLine, long endColumn) { 037 this.startLine = startLine; 038 this.startColumn = startColumn; 039 this.endLine = endLine; 040 this.endColumn = endColumn; 041 } 042 043 /** 044 * Derive a span from the AST node's start and end tokens. 045 * Null-safe: returns {@code null} when {@code node} is {@code null} 046 * or either boundary token is {@code null}. 047 */ 048 public static SourceSpan of(TParseTreeNode node) { 049 if (node == null) return null; 050 return of(node.getStartToken(), node.getEndToken()); 051 } 052 053 /** 054 * Derive a span from explicit start / end tokens. End position is 055 * computed as one past the last character of {@code end} so the 056 * resulting interval is half-open {@code [start, end)}. 057 * 058 * <p>Null-safe: returns {@code null} when either token is 059 * {@code null}. 060 */ 061 public static SourceSpan of(TSourceToken start, TSourceToken end) { 062 if (start == null || end == null) return null; 063 String endText = end.getAstext(); 064 if (endText == null) endText = ""; 065 long endLine = end.lineNo; 066 long endColumn = end.columnNo + endText.length(); 067 // Multi-line token: advance the line count and recompute the 068 // trailing column from the last newline. 069 int lastNl = endText.lastIndexOf('\n'); 070 if (lastNl >= 0) { 071 long extraLines = 0; 072 for (int i = 0; i < endText.length(); i++) { 073 if (endText.charAt(i) == '\n') extraLines++; 074 } 075 endLine = end.lineNo + extraLines; 076 endColumn = endText.length() - lastNl - 1 + 1; 077 // +1 keeps the half-open convention (column index of the 078 // position one past the last char on the final line; the 079 // final line starts at column 1 by GSP convention). 080 } 081 return new SourceSpan(start.lineNo, start.columnNo, endLine, endColumn); 082 } 083 084 public long getStartLine() { 085 return startLine; 086 } 087 088 public long getStartColumn() { 089 return startColumn; 090 } 091 092 public long getEndLine() { 093 return endLine; 094 } 095 096 public long getEndColumn() { 097 return endColumn; 098 } 099 100 @Override 101 public boolean equals(Object o) { 102 if (this == o) return true; 103 if (!(o instanceof SourceSpan)) return false; 104 SourceSpan that = (SourceSpan) o; 105 return startLine == that.startLine 106 && startColumn == that.startColumn 107 && endLine == that.endLine 108 && endColumn == that.endColumn; 109 } 110 111 @Override 112 public int hashCode() { 113 return Objects.hash(startLine, startColumn, endLine, endColumn); 114 } 115 116 @Override 117 public String toString() { 118 return startLine + ":" + startColumn + "-" + endLine + ":" + endColumn; 119 } 120}