001package gudusoft.gsqlparser.pp2.token; 002 003import gudusoft.gsqlparser.TSourceToken; 004 005import java.util.Collections; 006import java.util.EnumSet; 007import java.util.Set; 008 009/** 010 * Lightweight wrapper around a {@link TSourceToken}, owned by pp2. 011 * 012 * <p>Carries: 013 * <ul> 014 * <li>A reference to the underlying {@link TSourceToken} — pp2 <b>never 015 * mutates</b> it. This is the cache-safety invariant from plan §5.3. 016 * Probe A7 in slice S1 confirmed {@code TGSqlParser} reuses the same 017 * {@code TSourceTokenList} reference across tokenizations, which makes 018 * this non-mutation contract mandatory. <b>Callers must build the pp2 019 * token stream and copy any per-token data they need before reusing 020 * the parser instance</b>; holding {@code Pp2Token} references across 021 * a re-tokenization of the same parser will silently observe 022 * post-reuse data.</li> 023 * <li>A mutable {@link EnumSet} of {@link TokenRole}s — pp2's side-channel 024 * annotations. Set during token-stream construction (S7), by 025 * designated detector/annotator stages (S9 protected zones, S21 026 * clause scopes, S33 AST overlay), and by layout/island stages 027 * (S22 onward).</li> 028 * <li>{@code precedingBlanks} — number of whitespace columns immediately 029 * preceding this token in the source (e.g., 4 for {@code " SELECT"} 030 * at line start after the linebreak).</li> 031 * <li>{@code precedingLinebreaks} — number of newline characters between 032 * the previous solid token and this one.</li> 033 * </ul> 034 * 035 * <p>The {@code precedingBlanks} / {@code precedingLinebreaks} model 036 * mirrors the Delphi {@code initTokenArray} reconstruction property 037 * (plan §7.4 S7): a stream of {@code Pp2Token}s reproduces the original 038 * input byte-for-byte by concatenating 039 * {@code Σ(linebreaks + blanks + token.text)}. 040 * 041 * <p>Construction does not snapshot the wrapped token's text — it stores 042 * the reference and exposes {@link #getText()} which delegates to 043 * {@code TSourceToken.toString()}. Pp2 callers must not rely on 044 * {@code TSourceToken} field stability across tokenization reuse. 045 */ 046public final class Pp2Token { 047 048 private final TSourceToken sourceToken; 049 private final EnumSet<TokenRole> roles; 050 private final int precedingBlanks; 051 private final int precedingLinebreaks; 052 053 /** 054 * Construct with no roles, zero preceding whitespace. 055 * 056 * @param sourceToken non-null reference to the underlying source token 057 */ 058 public Pp2Token(TSourceToken sourceToken) { 059 this(sourceToken, 0, 0, EnumSet.noneOf(TokenRole.class)); 060 } 061 062 /** 063 * Construct with the given roles and preceding whitespace counts. 064 * 065 * @param sourceToken non-null reference; never mutated 066 * @param precedingBlanks count of whitespace columns immediately before 067 * this token; must be {@code >= 0} 068 * @param precedingLinebreaks count of linebreaks immediately before this 069 * token; must be {@code >= 0} 070 * @param roles initial role set; copied into an {@link EnumSet} owned by 071 * this token; may be {@code null} or empty 072 */ 073 public Pp2Token(TSourceToken sourceToken, 074 int precedingBlanks, 075 int precedingLinebreaks, 076 Set<TokenRole> roles) { 077 if (sourceToken == null) throw new NullPointerException("sourceToken"); 078 if (precedingBlanks < 0) { 079 throw new IllegalArgumentException( 080 "precedingBlanks < 0: " + precedingBlanks); 081 } 082 if (precedingLinebreaks < 0) { 083 throw new IllegalArgumentException( 084 "precedingLinebreaks < 0: " + precedingLinebreaks); 085 } 086 this.sourceToken = sourceToken; 087 this.precedingBlanks = precedingBlanks; 088 this.precedingLinebreaks = precedingLinebreaks; 089 this.roles = (roles == null || roles.isEmpty()) 090 ? EnumSet.noneOf(TokenRole.class) 091 : EnumSet.copyOf(roles); 092 } 093 094 /** Reference to the wrapped {@link TSourceToken}. Never mutated by pp2. */ 095 public TSourceToken getSourceToken() { 096 return sourceToken; 097 } 098 099 /** 100 * Text of the wrapped token. Delegates to 101 * {@link TSourceToken#toString()} — pp2 never caches or modifies it. 102 */ 103 public String getText() { 104 return sourceToken.toString(); 105 } 106 107 /** 108 * Read-only view of the role set. Use {@link #addRole(TokenRole)} and 109 * {@link #removeRole(TokenRole)} to mutate; bulk replacement is not 110 * supported by design (the invariants live in the mutators). 111 */ 112 public Set<TokenRole> getRoles() { 113 return Collections.unmodifiableSet(roles); 114 } 115 116 public boolean hasRole(TokenRole role) { 117 return roles.contains(role); 118 } 119 120 public void addRole(TokenRole role) { 121 if (role == null) throw new NullPointerException("role"); 122 roles.add(role); 123 } 124 125 public void removeRole(TokenRole role) { 126 if (role == null) throw new NullPointerException("role"); 127 roles.remove(role); 128 } 129 130 public int getPrecedingBlanks() { 131 return precedingBlanks; 132 } 133 134 public int getPrecedingLinebreaks() { 135 return precedingLinebreaks; 136 } 137 138 @Override 139 public String toString() { 140 return "Pp2Token{'" + getText() + "' roles=" + roles 141 + " blanks=" + precedingBlanks 142 + " breaks=" + precedingLinebreaks + "}"; 143 } 144}