001package gudusoft.gsqlparser.ir.builder.common; 002 003import gudusoft.gsqlparser.analyzer.v2.AnalyzerV2Config; 004import gudusoft.gsqlparser.analyzer.v2.EDynamicSqlStrategy; 005import gudusoft.gsqlparser.ir.bound.*; 006import gudusoft.gsqlparser.ir.common.*; 007import gudusoft.gsqlparser.nodes.TParseTreeNode; 008import gudusoft.gsqlparser.nodes.TParseTreeVisitor; 009 010import java.util.*; 011 012/** 013 * Shared base class for dialect-specific symbol collectors. 014 * <p> 015 * Owns mechanics only: scope stack management, routine ID tracking, 016 * ref creation helpers, and source anchor utilities. 017 * <p> 018 * Dialect-specific behavior (which AST nodes to visit, how to classify 019 * externals, how to build routine IDs) belongs in subclasses. 020 */ 021public abstract class AbstractSymbolCollector extends TParseTreeVisitor { 022 023 protected final BoundProgram program; 024 protected final AnalyzerV2Config config; 025 protected final EDynamicSqlStrategy dynamicSqlStrategy; 026 protected final String fileId; 027 028 private final Deque<BoundScope> scopeStack = new ArrayDeque<BoundScope>(); 029 private final Deque<String> routineIdStack = new ArrayDeque<String>(); 030 031 protected String currentPackageName; 032 private String currentRoutineIdField; 033 034 protected AbstractSymbolCollector(BoundProgram program, BoundScope globalScope, 035 AnalyzerV2Config config, String fileId) { 036 this.program = program; 037 this.config = config != null ? config : AnalyzerV2Config.createDefault(); 038 this.dynamicSqlStrategy = this.config.dynamicSqlStrategy; 039 this.fileId = fileId != null ? fileId : ""; 040 this.scopeStack.push(globalScope); 041 } 042 043 // ====== Scope Management ====== 044 045 protected final BoundScope currentScope() { 046 return scopeStack.peek(); 047 } 048 049 protected final BoundScope pushScope(EScopeKind kind, SourceAnchor anchor) { 050 BoundScope scope = new BoundScope(kind, currentScope(), anchor); 051 program.addScope(scope); 052 scopeStack.push(scope); 053 return scope; 054 } 055 056 protected final void popScope() { 057 if (scopeStack.size() > 1) { 058 scopeStack.pop(); 059 } 060 } 061 062 // ====== Routine Context ====== 063 064 protected final String currentRoutineId() { 065 return currentRoutineIdField; 066 } 067 068 protected final void pushRoutineId(String routineId) { 069 routineIdStack.push(routineId); 070 currentRoutineIdField = routineId; 071 } 072 073 protected final void popRoutineId() { 074 if (!routineIdStack.isEmpty()) { 075 routineIdStack.pop(); 076 } 077 currentRoutineIdField = routineIdStack.isEmpty() ? null : routineIdStack.peek(); 078 } 079 080 // ====== Ref Creation ====== 081 082 /** 083 * Creates a BoundRoutineRef and adds it to the program. 084 */ 085 protected final BoundRoutineRef createRoutineRef(String name, List<String> parts, 086 Evidence evidence, Confidence confidence, 087 SourceAnchor anchor) { 088 BoundRoutineRef ref = new BoundRoutineRef( 089 name, parts, 090 EBindingStatus.UNRESOLVED_SOFT, null, 091 null, Collections.<BoundArgument>emptyList(), 092 evidence, confidence); 093 ref.setSourceAnchor(anchor); 094 if (currentRoutineIdField != null) { 095 ref.setProperty("owningRoutineId", currentRoutineIdField); 096 } 097 program.addRoutineRef(ref); 098 return ref; 099 } 100 101 /** 102 * Creates a BoundRoutineRef with arguments and adds it to the program. 103 */ 104 protected final BoundRoutineRef createRoutineRef(String name, List<String> parts, 105 List<BoundArgument> args, 106 Evidence evidence, Confidence confidence, 107 SourceAnchor anchor) { 108 BoundRoutineRef ref = new BoundRoutineRef( 109 name, parts, 110 EBindingStatus.UNRESOLVED_SOFT, null, 111 null, args, 112 evidence, confidence); 113 ref.setSourceAnchor(anchor); 114 if (currentRoutineIdField != null) { 115 ref.setProperty("owningRoutineId", currentRoutineIdField); 116 } 117 program.addRoutineRef(ref); 118 return ref; 119 } 120 121 /** 122 * Creates a BoundObjectRef and adds it to the program. 123 */ 124 protected final BoundObjectRef createObjectRef(String name, List<String> parts, 125 EObjectRefKind kind, SourceAnchor anchor) { 126 BoundObjectRef ref = new BoundObjectRef( 127 name, parts, 128 EBindingStatus.UNRESOLVED_SOFT, null, null, 129 kind, new Evidence(EvidenceKind.STATIC_RESOLVED, "Table reference")); 130 ref.setSourceAnchor(anchor); 131 if (currentRoutineIdField != null) { 132 ref.setProperty("owningRoutineId", currentRoutineIdField); 133 } 134 program.addObjectRef(ref); 135 return ref; 136 } 137 138 protected final void addColumnRef(BoundColumnRef ref) { 139 program.addColumnRef(ref); 140 } 141 142 // ====== Utilities ====== 143 144 /** 145 * Creates a SourceAnchor from an AST node, optionally including the fileId. 146 */ 147 protected final SourceAnchor anchor(TParseTreeNode node) { 148 SourceAnchor base = SourceAnchor.from(node); 149 if (base != null && !fileId.isEmpty()) { 150 return new SourceAnchor(fileId, 151 base.startOffset, base.endOffset, 152 base.startLine, base.startCol, 153 base.endLine, base.endCol, 154 base.statementKey, base.snippet); 155 } 156 return base; 157 } 158 159 /** 160 * Splits a qualified name (e.g., "schema.pkg.proc") into parts. 161 */ 162 public static List<String> splitName(String qualifiedName) { 163 if (qualifiedName == null || qualifiedName.isEmpty()) { 164 return Collections.emptyList(); 165 } 166 String[] parts = qualifiedName.split("\\."); 167 List<String> result = new ArrayList<String>(parts.length); 168 for (String part : parts) { 169 String trimmed = part.trim(); 170 if (!trimmed.isEmpty()) { 171 result.add(trimmed); 172 } 173 } 174 return result; 175 } 176 177 /** 178 * Builds a routine ID from name, kind code, and parameter count. 179 * Uses the current package name as namespace prefix. 180 */ 181 protected String buildRoutineId(String name, String kindCode, int paramCount) { 182 StringBuilder sb = new StringBuilder(); 183 if (currentPackageName != null && !currentPackageName.isEmpty()) { 184 sb.append(currentPackageName).append('.'); 185 } 186 sb.append(name).append('/').append(kindCode).append('(').append(paramCount).append(')'); 187 return sb.toString(); 188 } 189 190 // ====== Abstract Dialect Hooks ====== 191 192 /** 193 * Returns true if the given name represents an external dependency 194 * (built-in function, system package, etc.) for this dialect. 195 */ 196 protected abstract boolean isExternalDependency(String name, List<String> parts); 197 198 /** 199 * Returns the routine ID string for the given routine AST node. 200 */ 201 protected abstract String routineIdFor(Object routineNode); 202}