How to Use C++ Semantic Mode for Complex Parsing in Code‑Graph‑RAG
Enable C++ semantic mode by installing the [cpp] extra, setting CG_RAG_CPP_FRONTEND=libclang, and running the indexer—this activates LibClang‑based symbol resolution for fully‑qualified names and template‑aware parsing.
The code‑graph‑rag open‑source project provides two C++ parsing strategies. For complex codebases—those with heavy template use, nested namespaces, or cross‑module dependencies—the semantic mode delivers accurate qualified names and type resolution that Tree‑Sitter alone cannot provide. This guide shows how to configure and verify C++ semantic parsing.
Understanding the Two C++ Parsing Modes
| Mode | Backend | Capabilities | Best For |
|---|---|---|---|
| Tree‑Sitter | Syntactic only | Fast concrete syntax tree, no type resolution | Quick indexing of simple codebases |
| Semantic (LibClang/Hybrid) | LibClang + Clang AST | Fully‑qualified names, template instantiation, overload resolution, namespace tracking | Complex C++ with templates, cross‑language edges, precise call graphs |
The Hybrid frontend (default) attempts Tree‑Sitter first and falls back to LibClang when available. For guaranteed semantic parsing, force LibClang explicitly.
Installing the C++ Semantic Frontend
The semantic parser requires the optional cpp extra, which pulls in libclang bindings.
pip install "code-graph-rag[cpp]"
On Linux systems, you may additionally need the development headers:
sudo apt-get install libclang-dev
Verify the installation by checking that clang.cindex imports successfully:
python -c "import clang.cindex; print(clang.cindex.Config().lib)"
Selecting the C++ Frontend via Environment Variable
The CppFrontend enum in codebase_rag/constants/languages.py defines three valid values1:
treesitter— Pure syntactic parsinglibclang— Full semantic resolutionhybrid— Tree‑Sitter with LibClang fallback (default)
Set the environment variable before any indexing operation:
export CG_RAG_CPP_FRONTEND=libclang
Or within Python:
import os
os.environ["CG_RAG_CPP_FRONTEND"] = "libclang"
The parser_loader.py module reads this variable and instantiates CppHandler with the corresponding backend2.
Configuring and Running the Semantic Parser
Step‑by‑Step Indexing Workflow
1. Import and instantiate the handler directly (programmatic use):
from codebase_rag.parsers.handlers.cpp import CppHandler
handler = CppHandler() # Respects CG_RAG_CPP_FRONTEND automatically
2. Run the main indexer:
python -m codebase_rag.main /path/to/cpp/repo --language cpp
The indexer emits a log message confirming the active frontend (see logs.py for exact formatting). Expect output like:
Using LibClang C++ frontend
3. Verify semantic resolution in output:
After indexing, load the generated graph and inspect qualified_name attributes:
from codebase_rag.graph_loader import load_graph
from pathlib import Path
graph = load_graph(Path("/tmp/cgr_graph"))
# Semantic mode produces fully-qualified names
func = graph.get_node_by_qn("engine::render::Pipeline::bind")
print(func["qualified_name"]) # → engine::render::Pipeline::bind
Without semantic mode, the same function would appear as merely bind.
How Semantic Resolution Constructs Qualified Names
The semantic resolution pipeline in codebase_rag/parsers/handlers/cpp.py operates as follows3:
-
AST node identification —
CppHandler.extract_function_nameattempts basic extraction viacpp_utilshelpers. For lambdas, it synthesizes names likelambda_42_15. -
Qualified‑name construction —
CppHandler.build_function_qualified_namedelegates toresolve_fqn_from_astwhen a semantic frontend is active. This utility traverses the Clang AST to locate enclosing namespaces, classes, and template parameters. -
Name concatenation — The resolver concatenates scope components:
namespace::Class<T>::function. -
Export detection —
is_function_exportedchecksextern "C"and visibility attributes usingcpp_utils.is_exported, respecting semantic parser information. -
Base‑class handling —
extract_base_class_nameprocesses both simple identifiers andTS_TEMPLATE_TYPEnodes for complex inheritance chains.
The core resolution logic resides in codebase_rag/utils/fqn_resolver.py, which queries LibClang's cursor.semantic_parent chain to build complete identifiers.
Complete Example: End‑to‑End Semantic Indexing
import os
from pathlib import Path
from codebase_rag.main import run_indexer
from codebase_rag.graph_loader import load_graph
# Force semantic mode
os.environ["CG_RAG_CPP_FRONTEND"] = "libclang"
# Run indexing
repo = Path("/home/dev/graphics-engine")
run_indexer([str(repo)], languages=["cpp"])
# Inspect results
graph = load_graph(Path("/tmp/cgr_graph"))
# Find all methods in a specific namespace
methods = [
n for n in graph.nodes()
if n.get("qualified_name", "").startswith("gfx::vulkan::")
]
print(f"Found {len(methods)} Vulkan-gfx methods with qualified names")
for m in methods[:5]:
print(f" - {m['qualified_name']}")
Using Semantic Search with C++ Results
The semantic search tool leverages embeddings generated by the semantic frontend. Once indexed, query the codebase:
from codebase_rag.tools.semantic_search import semantic_code_search
results = semantic_code_search(
ingestor=your_ingestor_instance,
query="memory pool allocator initialization",
top_k=10,
project="graphics-engine"
)
for r in results:
print(f"{r['function_qn']}: score {r['score']:.3f}")
Accurate function_qn values from semantic parsing ensure search results map to precise code locations.
Summary
- Install:
pip install "code-graph-rag[cpp]"to obtain LibClang bindings - Configure: Set
CG_RAG_CPP_FRONTEND=libclangfor guaranteed semantic parsing (or usehybrid/treesitteralternatives) - Verify: Check
qualified_nameattributes contain full namespace and class scoping - Key files:
languages.py(enum definition),cpp.py(handler),fqn_resolver.py(resolution logic)
Frequently Asked Questions
What is the difference between Hybrid and LibClang modes?
Hybrid mode attempts Tree‑Sitter parsing first and falls back to LibClang only when the syntactic parser fails or when semantic information is explicitly required. LibClang mode forces the semantic frontend for all C++ files, ensuring consistent fully‑qualified naming at the cost of slightly slower indexing. Use LibClang when you need guaranteed accuracy for template‑heavy code.
Why does my qualified name still lack namespace information?
This occurs when: (1) the cpp extra is not installed, (2) CG_RAG_CPP_FRONTEND is unset or set to treesitter, or (3) LibClang cannot locate system headers. Verify with python -c "import clang.cindex" and check that libclang-dev is installed on Linux systems.
Can I mix semantic and non‑semantic parsing in the same project?
No— the frontend selection applies globally per indexing run. However, you can run separate indexing passes with different frontends and merge graphs if needed. The CppHandler initializes its strategy once at import time based on the environment variable.
Does semantic mode support C++20 modules?
Yes. The LibClang frontend recognizes export declarations and module boundaries. The extract_function_name and is_function_exported methods in cpp.py handle visibility attributes and module linkage as reported by the Clang AST, though module‑specific naming conventions may require additional verification in your codebase.
Footnotes
-
CppFrontendenum definition —codebase_rag/constants/languages.pylines 101–106 ↩ -
Frontend loading logic —
codebase_rag/parsers/parser_loader.py↩ -
Qualified name construction —
codebase_rag/parsers/handlers/cpp.pylines 41–49 ↩
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →