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}