One engine reads your C++ and holds the whole picture — syntax, symbols, references, control flow. Documentation, code generation and memory-safety analysis are what we build on top of it.
{ template <typename T> = requires(const T& s) { { } -> std::convertible_to<double>; }; { public: : {} { return * * radius_; } private: }; template <> auto (const Ts&... shapes) -> double { return ; } } // namespace geometry Our own preprocessor walks this, not the compiler's — which is why this file needs no build configuration to be read. The edge lands in the include graph, and that graph is what lets an edit invalidate the files that reach it and nothing else.
Recorded as a second edge out of this file. Both directives survive as nodes in the tree rather than being consumed and discarded the way a compiler front-end consumes them.
Opens the scope every declaration below is qualified into, and holds them as its children. The index is keyed on qualified names, not on the spelling at the use site.
A declaration in its own right — span, kind, qualified name — carrying the requires-expression that defines it: one parameter of type const T&, and one compound requirement over it.
The call is kept, with its shape and its span. What it resolves to is not: T is unknown here, so no callee is recorded. Surfaced as unresolved rather than guessed — an index that quietly invents this edge is worse than one that admits it cannot follow it.
One record, with its members already sorted: constructors, member functions, member variables, nested types — each carrying the access it was declared under.
Classified as a constructor rather than a member function, and further as neither a copy nor a move. explicit and noexcept ride along on the record as specifiers, not as text.
Held as a structured initialiser — the member it names on one side, the argument r on the other — rather than as a span of source between a colon and a brace. This is the pair a rename has to move together or refuse.
Flagged as a trailing return, and read through to double. The const qualification and the nodiscard attribute are both recorded on the declaration rather than discarded as noise.
Kept as a chain, not a string: pi, qualified by numbers, qualified by std. Nothing outside this source tree is indexed and nothing about it is assumed — but the name it was written under survives intact.
The return value stays a tree — two nested multiplications, each operand at its own span — instead of collapsing to the text between return and the semicolon. Structure is what later passes have to work with.
Recorded private, of type double, at this exact span. The initialiser on line 13 names this member and the expression on line 16 uses it — three sites that any rename of this field has to reach.
One parameter, marked variadic. We never expand it, because we never instantiate — the declaration is the thing indexed, and that is precisely why this file can be understood without being built.
The constraint is stored on the template as a fold in its own right, with its operator. What it would evaluate to needs instantiation; its shape does not, so its shape is what we keep.
A function template at namespace scope, sitting beside the class and the concept as a sibling in the same namespace record.
A fold, its operator, and underneath it a method call on shapes — all of it still standing even though the pack it folds over is not known here. Structure survives where resolution cannot.
Serious C++ tooling has had two foundations to choose from, and both charge for it. A compiler front-end understands the language completely — and wants a working build configuration, refuses code that does not compile, and was designed to emit object files rather than answer questions about your program.
Pattern matching is the other extreme. It starts in an afternoon and falls apart by the evening: C++ is far too context-sensitive to match your way through, and every rule you add spawns three exceptions.
CodeXX takes the road between them — a forked and extended tree-sitter grammar, with our own AST, symbol index and analysis layer built on top of its concrete syntax tree. Fast, incremental, tolerant of half-written code, and embeddable as a library.
Owning that foundation is what turns documentation, code generation and memory-safety analysis from three research projects into three products.
| Approach | Setup | Half-written code | Re-analysis | Semantic depth |
|---|---|---|---|---|
| Compiler front-end | A working build configuration | Rejected — it has to compile | The whole translation unit | Complete: overload resolution, template instantiation, the lot |
| Pattern matching | None | Tolerated | Cheap, and wrong about as often | Effectively none — C++ is far too context-sensitive to match |
| CodeXX engine | Point it at the source tree | Tolerated — it parses as you type | Incremental, scoped to what depends on the edit | Syntactic, plus best-effort symbol and type resolution |
That last cell is the trade, and we would rather state it than bury it: no overload resolution, no template instantiation. What we get for it is an index that keeps up with an editor, on code that has never once built.
Six stages, composed as a pipeline — each a real stage object, chained with
the | operator, run per item or per batch.
An edit invalidates what depended on it and nothing else. The workspace layer tracks include and module dependencies, debounces editor traffic, and holds a live parser per open file — so a keystroke never pays for a cold index.
The whole-program analysis phases run across cores under one hard rule: the output must be byte-identical to the serial order. Pre-sized buffers, writes by index, nothing accumulated from a worker. Determinism here is a test, not an aspiration.
Each is a consumer of the same index. That is the architectural bet: build the foundation once, and the tools stop being separate research efforts.
Every product runs against this repository and the full closure of its dependencies — every header, every vendored library. The linter's whole-tree automatic refactors are applied to that same corpus and are required to build afterwards: a rename that breaks a single call site fails the run. If the tools are not reliable on 166,027 lines of our own C++, we find out before you do.
A tight feedback loop and a direct line to the author. Further materials available under NDA.