Step 334: C++ Parser — Inheritance + Templates (12/12 tests)

Multiple inheritance with access specifiers and virtual flags from tree-sitter
CST. Template class type parameters, CRTP detection, struct support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bill
2026-02-16 00:24:56 -07:00
parent 6b2b5bf13b
commit b75fba72d8
3 changed files with 311 additions and 10 deletions

View File

@@ -2005,4 +2005,13 @@ add_executable(step333_test tests/step333_test.cpp)
target_include_directories(step333_test PRIVATE src)
target_link_libraries(step333_test PRIVATE nlohmann_json::nlohmann_json)
add_executable(step334_test tests/step334_test.cpp)
target_include_directories(step334_test PRIVATE src)
target_link_libraries(step334_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -89,16 +89,31 @@ private:
}
}
if (!ts_node_is_null(baseClause)) {
uint32_t bc = ts_node_named_child_count(baseClause);
for (uint32_t i = 0; i < bc; ++i) {
TSNode base = ts_node_named_child(baseClause, i);
std::string baseText = nodeText(base, source);
// Strip access specifier prefix
if (baseText.find("public ") == 0) baseText = baseText.substr(7);
else if (baseText.find("private ") == 0) baseText = baseText.substr(8);
else if (baseText.find("protected ") == 0) baseText = baseText.substr(10);
if (!baseText.empty() && cls->superClass.empty()) {
cls->superClass = baseText;
// tree-sitter-cpp base_class_clause children (ALL, not just named):
// ":" [access_specifier] ("virtual")? [type_identifier|template_type] ","
// ... repeating for each base
// "virtual" is an unnamed node, so we must iterate ALL children.
uint32_t totalChildren = ts_node_child_count(baseClause);
std::string pendingAccess = isStruct ? "public" : "private";
bool pendingVirtual = false;
for (uint32_t i = 0; i < totalChildren; ++i) {
TSNode child = ts_node_child(baseClause, i);
std::string ctype = ts_node_type(child);
if (ctype == "access_specifier") {
std::string accessText = nodeText(child, source);
if (accessText.find("public") != std::string::npos) pendingAccess = "public";
else if (accessText.find("protected") != std::string::npos) pendingAccess = "protected";
else if (accessText.find("private") != std::string::npos) pendingAccess = "private";
} else if (ctype == "virtual") {
pendingVirtual = true;
} else if (ctype == "type_identifier" || ctype == "template_type" ||
ctype == "qualified_identifier" || ctype == "dependent_type") {
std::string baseName = nodeText(child, source);
if (!baseName.empty()) {
cls->addBase(baseName, pendingAccess, pendingVirtual);
}
pendingAccess = isStruct ? "public" : "private";
pendingVirtual = false;
}
}
}

View File

@@ -0,0 +1,277 @@
// Step 334: C++ Parser — Inheritance + Templates (12 tests)
// Tests multiple inheritance, virtual, access specifiers, template classes, CRTP
#include <cassert>
#include <iostream>
#include <string>
#include <memory>
#include "ast/Parser.h"
#include "ast/ClassDeclaration.h"
#include "ast/GenericType.h"
#include "ast/Serialization.h"
int main() {
int passed = 0;
// Test 1: Single inheritance backward compat
{
std::string src = R"(
class Foo : public Bar {
public:
void doStuff() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(classes.size() >= 1);
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
assert(cls != nullptr);
assert(cls->name == "Foo");
// Should have at least superClass set
assert(!cls->superClass.empty() || !cls->baseClasses.empty());
std::cout << "Test 1 PASSED: Single inheritance backward compat\n";
passed++;
}
// Test 2: Multiple inheritance parsed
{
std::string src = R"(
class Widget : public Base, public Mixin {
void draw() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
assert(cls->name == "Widget");
auto bases = cls->getBases();
assert(bases.size() >= 2);
std::cout << "Test 2 PASSED: Multiple inheritance parsed (" << bases.size() << " bases)\n";
passed++;
}
// Test 3: Access specifiers detected
{
std::string src = R"(
class D : public A, protected B, private C {
void f() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
auto bases = cls->getBases();
assert(bases.size() >= 3);
// Check that at least different specifiers exist
bool hasPublic = false, hasProtected = false, hasPrivate = false;
for (const auto& b : bases) {
if (b.accessSpecifier == "public") hasPublic = true;
if (b.accessSpecifier == "protected") hasProtected = true;
if (b.accessSpecifier == "private") hasPrivate = true;
}
assert(hasPublic);
assert(hasProtected);
assert(hasPrivate);
std::cout << "Test 3 PASSED: Access specifiers detected\n";
passed++;
}
// Test 4: Virtual inheritance flag
{
std::string src = R"(
class D : public virtual Base {
void f() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
auto bases = cls->getBases();
assert(!bases.empty());
assert(bases[0].isVirtual);
std::cout << "Test 4 PASSED: Virtual inheritance flag detected\n";
passed++;
}
// Test 5: Template class parsed
{
std::string src = R"(
template<typename T>
class Container {
public:
void add(T item) {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
assert(cls->name == "Container");
auto tps = cls->getChildren("typeParameters");
assert(tps.size() >= 1);
auto* tp = dynamic_cast<TypeParameter*>(tps[0]);
assert(tp != nullptr);
assert(tp->name == "T");
std::cout << "Test 5 PASSED: Template class parsed with TypeParameter\n";
passed++;
}
// Test 6: CRTP pattern detected via base class name
{
std::string src = R"(
template<typename T>
class Base {
};
class Derived : public Base<Derived> {
void f() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
// Find Derived class
ClassDeclaration* derived = nullptr;
for (auto* c : classes) {
auto* cls = dynamic_cast<ClassDeclaration*>(c);
if (cls && cls->name == "Derived") {
derived = cls;
break;
}
}
assert(derived != nullptr);
auto bases = derived->getBases();
assert(!bases.empty());
// Check CRTP: Base<Derived> contains "Derived" as template arg
std::vector<std::string> baseNames;
for (const auto& b : bases) baseNames.push_back(b.name);
assert(isCRTPClass("Derived", baseNames));
std::cout << "Test 6 PASSED: CRTP pattern detected\n";
passed++;
}
// Test 7: struct vs class default access
{
std::string src = R"(
struct Point {
int x;
int y;
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
assert(cls->name == "Point");
std::cout << "Test 7 PASSED: struct parsed as ClassDeclaration\n";
passed++;
}
// Test 8: Template with variadic (typename... Args)
{
std::string src = R"(
template<typename T, typename U>
class Pair {
void f() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
assert(cls->name == "Pair");
auto tps = cls->getChildren("typeParameters");
assert(tps.size() == 2);
std::cout << "Test 8 PASSED: Template with 2 type params\n";
passed++;
}
// Test 9: Combined template + multiple inheritance
{
std::string src = R"(
template<typename T>
class MyClass : public Base, public Interface {
void f() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
assert(cls->name == "MyClass");
auto bases = cls->getBases();
assert(bases.size() >= 2);
auto tps = cls->getChildren("typeParameters");
assert(tps.size() >= 1);
std::cout << "Test 9 PASSED: Template + multiple inheritance combined\n";
passed++;
}
// Test 10: Methods inside class body still parsed
{
std::string src = R"(
class Foo : public Bar {
public:
virtual void draw() {}
static void create() {}
private:
void helper() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
auto methods = cls->getChildren("methods");
assert(methods.size() >= 2); // at least some methods parsed
std::cout << "Test 10 PASSED: Methods inside class body parsed (" << methods.size() << " methods)\n";
passed++;
}
// Test 11: Whetstone-style class signature
{
std::string src = R"(
class KotlinGenerator : public ProjectionGenerator, public AnnotationVisitorExtended {
public:
void generate() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
auto classes = mod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
assert(cls->name == "KotlinGenerator");
auto bases = cls->getBases();
assert(bases.size() >= 2);
std::cout << "Test 11 PASSED: Whetstone-style class signature\n";
passed++;
}
// Test 12: JSON roundtrip preserves parsed class with bases
{
std::string src = R"(
class D : public A, protected B {
void f() {}
};
)";
auto mod = TreeSitterParser::parseCpp(src);
json j = toJson(mod.get());
ASTNode* restored = fromJson(j);
auto* rmod = dynamic_cast<Module*>(restored);
assert(rmod != nullptr);
auto classes = rmod->getChildren("classes");
assert(!classes.empty());
auto* cls = dynamic_cast<ClassDeclaration*>(classes[0]);
assert(cls->name == "D");
auto bases = cls->getBases();
assert(bases.size() >= 2);
deleteTree(restored);
std::cout << "Test 12 PASSED: JSON roundtrip preserves parsed class\n";
passed++;
}
std::cout << "\nResults: " << passed << "/12 tests passed\n";
return (passed == 12) ? 0 : 1;
}