Complete step468 operator overloading and friend support with tests
This commit is contained in:
@@ -3091,4 +3091,13 @@ target_link_libraries(step467_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step468_test tests/step468_test.cpp)
|
||||
target_include_directories(step468_test PRIVATE src)
|
||||
target_link_libraries(step468_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)
|
||||
|
||||
129
editor/src/ast/CppOperators.h
Normal file
129
editor/src/ast/CppOperators.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
// Step 468: Operator Overloading + Friend
|
||||
// Focused node + parser helpers for C++ operator overload declarations.
|
||||
|
||||
#include "ASTNode.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
class OperatorOverload : public ASTNode {
|
||||
public:
|
||||
std::string operatorSymbol; // <, <<, ==, +, ...
|
||||
std::vector<std::string> parameters;
|
||||
std::string returnType;
|
||||
bool isFriend = false;
|
||||
bool isConst = false;
|
||||
|
||||
OperatorOverload() { conceptType = "OperatorOverload"; }
|
||||
};
|
||||
|
||||
class CppOperators {
|
||||
public:
|
||||
static bool parseOperatorOverload(const std::string& decl, OperatorOverload& out) {
|
||||
// e.g. bool operator<(const Foo&) const;
|
||||
// e.g. friend ostream& operator<<(ostream&, const Foo&);
|
||||
std::regex rx(R"(^\s*(friend\s+)?(.+?)\s+operator\s*([^\s(]+)\s*\(([^)]*)\)\s*(const)?\s*;?\s*$)");
|
||||
std::smatch m;
|
||||
if (!std::regex_match(decl, m, rx)) return false;
|
||||
|
||||
out.isFriend = m[1].matched;
|
||||
out.returnType = trim(m[2].str());
|
||||
out.operatorSymbol = trim(m[3].str());
|
||||
out.parameters = parseParamList(m[4].str());
|
||||
out.isConst = m[5].matched;
|
||||
return !out.operatorSymbol.empty();
|
||||
}
|
||||
|
||||
static json toJson(const OperatorOverload& o) {
|
||||
return {
|
||||
{"concept", o.conceptType},
|
||||
{"operatorSymbol", o.operatorSymbol},
|
||||
{"parameters", o.parameters},
|
||||
{"returnType", o.returnType},
|
||||
{"isFriend", o.isFriend},
|
||||
{"isConst", o.isConst}
|
||||
};
|
||||
}
|
||||
|
||||
static OperatorOverload fromJson(const json& j) {
|
||||
OperatorOverload o;
|
||||
o.operatorSymbol = j.value("operatorSymbol", "");
|
||||
o.parameters = j.value("parameters", std::vector<std::string>{});
|
||||
o.returnType = j.value("returnType", "");
|
||||
o.isFriend = j.value("isFriend", false);
|
||||
o.isConst = j.value("isConst", false);
|
||||
return o;
|
||||
}
|
||||
|
||||
static std::string generateCppDeclaration(const OperatorOverload& o) {
|
||||
std::ostringstream out;
|
||||
if (o.isFriend) out << "friend ";
|
||||
out << o.returnType << " operator" << o.operatorSymbol << "(";
|
||||
for (size_t i = 0; i < o.parameters.size(); ++i) {
|
||||
out << o.parameters[i];
|
||||
if (i + 1 < o.parameters.size()) out << ", ";
|
||||
}
|
||||
out << ")";
|
||||
if (o.isConst) out << " const";
|
||||
out << ";";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static std::string projectToJavaMethod(const OperatorOverload& o) {
|
||||
if (o.operatorSymbol == "<") return "int compareTo(Foo other)";
|
||||
if (o.operatorSymbol == "==") return "boolean equals(Object other)";
|
||||
if (o.operatorSymbol == "<<") return "String toDisplayString()";
|
||||
return "Object operator" + sanitize(o.operatorSymbol) + "(Object other)";
|
||||
}
|
||||
|
||||
static std::string projectToPythonMethod(const OperatorOverload& o) {
|
||||
if (o.operatorSymbol == "<") return "def __lt__(self, other):";
|
||||
if (o.operatorSymbol == "==") return "def __eq__(self, other):";
|
||||
if (o.operatorSymbol == "<<") return "def __str__(self):";
|
||||
return "def __op_" + sanitize(o.operatorSymbol) + "(self, other):";
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string trim(const std::string& s) {
|
||||
size_t start = s.find_first_not_of(" \t\r\n");
|
||||
if (start == std::string::npos) return "";
|
||||
size_t end = s.find_last_not_of(" \t\r\n");
|
||||
return s.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
static std::vector<std::string> parseParamList(const std::string& params) {
|
||||
std::vector<std::string> out;
|
||||
std::string cur;
|
||||
int depth = 0;
|
||||
for (char c : params) {
|
||||
if (c == '<') ++depth;
|
||||
if (c == '>') --depth;
|
||||
if (c == ',' && depth == 0) {
|
||||
auto t = trim(cur);
|
||||
if (!t.empty()) out.push_back(t);
|
||||
cur.clear();
|
||||
} else {
|
||||
cur.push_back(c);
|
||||
}
|
||||
}
|
||||
auto t = trim(cur);
|
||||
if (!t.empty()) out.push_back(t);
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string sanitize(const std::string& op) {
|
||||
std::string out;
|
||||
for (char c : op) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c))) out.push_back(c);
|
||||
else out.push_back('_');
|
||||
}
|
||||
return out.empty() ? "unknown" : out;
|
||||
}
|
||||
};
|
||||
152
editor/tests/step468_test.cpp
Normal file
152
editor/tests/step468_test.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
// Step 468: Operator Overloading + Friend Tests (12 tests)
|
||||
|
||||
#include "ast/CppOperators.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
void test_parse_member_operator_less_const() {
|
||||
TEST(parse_member_operator_less_const);
|
||||
OperatorOverload o;
|
||||
bool ok = CppOperators::parseOperatorOverload("bool operator<(const Foo&) const;", o);
|
||||
CHECK(ok, "parse should succeed");
|
||||
CHECK(o.operatorSymbol == "<", "wrong operator symbol");
|
||||
CHECK(o.returnType == "bool", "wrong return type");
|
||||
CHECK(o.isConst, "expected const qualifier");
|
||||
CHECK(!o.isFriend, "member overload should not be friend");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_parse_friend_operator_stream_insertion() {
|
||||
TEST(parse_friend_operator_stream_insertion);
|
||||
OperatorOverload o;
|
||||
bool ok = CppOperators::parseOperatorOverload(
|
||||
"friend ostream& operator<<(ostream&, const Foo&);", o);
|
||||
CHECK(ok, "parse should succeed");
|
||||
CHECK(o.operatorSymbol == "<<", "wrong operator symbol");
|
||||
CHECK(o.isFriend, "expected friend");
|
||||
CHECK(o.parameters.size() == 2, "expected two parameters");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_parse_operator_equals() {
|
||||
TEST(parse_operator_equals);
|
||||
OperatorOverload o;
|
||||
bool ok = CppOperators::parseOperatorOverload(
|
||||
"bool operator==(const Foo& rhs) const;", o);
|
||||
CHECK(ok, "parse should succeed");
|
||||
CHECK(o.operatorSymbol == "==", "wrong operator symbol");
|
||||
CHECK(o.isConst, "expected const");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_parse_operator_plus_non_const() {
|
||||
TEST(parse_operator_plus_non_const);
|
||||
OperatorOverload o;
|
||||
bool ok = CppOperators::parseOperatorOverload(
|
||||
"Foo operator+(const Foo& rhs);", o);
|
||||
CHECK(ok, "parse should succeed");
|
||||
CHECK(o.operatorSymbol == "+", "wrong operator symbol");
|
||||
CHECK(!o.isConst, "unexpected const");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_parse_invalid_declaration_fails() {
|
||||
TEST(parse_invalid_declaration_fails);
|
||||
OperatorOverload o;
|
||||
bool ok = CppOperators::parseOperatorOverload("int notAnOperator(int x);", o);
|
||||
CHECK(!ok, "non-operator declaration should fail");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_operator_json_roundtrip() {
|
||||
TEST(operator_json_roundtrip);
|
||||
OperatorOverload o;
|
||||
CppOperators::parseOperatorOverload("bool operator<(const Foo&) const;", o);
|
||||
auto j = CppOperators::toJson(o);
|
||||
auto out = CppOperators::fromJson(j);
|
||||
CHECK(out.operatorSymbol == o.operatorSymbol, "operator symbol mismatch");
|
||||
CHECK(out.returnType == o.returnType, "return type mismatch");
|
||||
CHECK(out.isConst == o.isConst, "const flag mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_cpp_declaration_member() {
|
||||
TEST(generate_cpp_declaration_member);
|
||||
OperatorOverload o;
|
||||
CppOperators::parseOperatorOverload("bool operator<(const Foo&) const;", o);
|
||||
auto s = CppOperators::generateCppDeclaration(o);
|
||||
CHECK(s == "bool operator<(const Foo&) const;", "wrong generated declaration");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_generate_cpp_declaration_friend() {
|
||||
TEST(generate_cpp_declaration_friend);
|
||||
OperatorOverload o;
|
||||
CppOperators::parseOperatorOverload("friend ostream& operator<<(ostream&, const Foo&);", o);
|
||||
auto s = CppOperators::generateCppDeclaration(o);
|
||||
CHECK(s.find("friend ostream& operator<<") == 0, "missing friend declaration start");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_project_less_to_java_compare_to() {
|
||||
TEST(project_less_to_java_compare_to);
|
||||
OperatorOverload o;
|
||||
CppOperators::parseOperatorOverload("bool operator<(const Foo&) const;", o);
|
||||
auto java = CppOperators::projectToJavaMethod(o);
|
||||
CHECK(java.find("compareTo") != std::string::npos, "expected compareTo projection");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_project_less_to_python_dunder_lt() {
|
||||
TEST(project_less_to_python_dunder_lt);
|
||||
OperatorOverload o;
|
||||
CppOperators::parseOperatorOverload("bool operator<(const Foo&) const;", o);
|
||||
auto py = CppOperators::projectToPythonMethod(o);
|
||||
CHECK(py.find("__lt__") != std::string::npos, "expected __lt__ projection");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_project_equals_to_python_dunder_eq() {
|
||||
TEST(project_equals_to_python_dunder_eq);
|
||||
OperatorOverload o;
|
||||
CppOperators::parseOperatorOverload("bool operator==(const Foo&) const;", o);
|
||||
auto py = CppOperators::projectToPythonMethod(o);
|
||||
CHECK(py.find("__eq__") != std::string::npos, "expected __eq__ projection");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_project_stream_operator_to_java_display_method() {
|
||||
TEST(project_stream_operator_to_java_display_method);
|
||||
OperatorOverload o;
|
||||
CppOperators::parseOperatorOverload("friend ostream& operator<<(ostream&, const Foo&);", o);
|
||||
auto java = CppOperators::projectToJavaMethod(o);
|
||||
CHECK(java.find("toDisplayString") != std::string::npos, "expected display projection");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 468: Operator Overloading + Friend Tests\n";
|
||||
|
||||
test_parse_member_operator_less_const(); // 1
|
||||
test_parse_friend_operator_stream_insertion(); // 2
|
||||
test_parse_operator_equals(); // 3
|
||||
test_parse_operator_plus_non_const(); // 4
|
||||
test_parse_invalid_declaration_fails(); // 5
|
||||
test_operator_json_roundtrip(); // 6
|
||||
test_generate_cpp_declaration_member(); // 7
|
||||
test_generate_cpp_declaration_friend(); // 8
|
||||
test_project_less_to_java_compare_to(); // 9
|
||||
test_project_less_to_python_dunder_lt(); // 10
|
||||
test_project_equals_to_python_dunder_eq(); // 11
|
||||
test_project_stream_operator_to_java_display_method(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||
<< " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
35
progress.md
35
progress.md
@@ -6325,3 +6325,38 @@ model projections.
|
||||
**Architecture gate check:**
|
||||
- `editor/src/ast/CppExceptions.h` within header-size limit (`178` <= `600`)
|
||||
- `editor/tests/step467_test.cpp` within test-file size guidance (`148` lines)
|
||||
|
||||
### Step 468: Operator Overloading + Friend
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Adds focused support for C++ operator-overload declarations, including friend
|
||||
operator forms, JSON round-trip, declaration generation, and projections to
|
||||
named methods in Java/Python.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/ast/CppOperators.h` — operator-overload helpers:
|
||||
- `OperatorOverload` node model (`operatorSymbol`, params, return type,
|
||||
`isFriend`, `isConst`)
|
||||
- parser for member and friend operator declarations
|
||||
- JSON round-trip helpers
|
||||
- C++ declaration generation
|
||||
- cross-language projection:
|
||||
- `<` → Java `compareTo`, Python `__lt__`
|
||||
- `==` → Java `equals`, Python `__eq__`
|
||||
- `<<` → display/string projection methods
|
||||
- `editor/tests/step468_test.cpp` — 12 tests covering:
|
||||
- parsing member/friend overload forms
|
||||
- const/friend flag behavior
|
||||
- invalid declaration rejection
|
||||
- JSON round-trip and declaration generation
|
||||
- Java/Python projection behavior for key operators
|
||||
- `editor/CMakeLists.txt` — `step468_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake --build editor/build-native --target step468_test` — PASS
|
||||
- `./editor/build-native/step468_test` — PASS (12/12)
|
||||
- `./editor/build-native/step467_test` — PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/ast/CppOperators.h` within header-size limit (`129` <= `600`)
|
||||
- `editor/tests/step468_test.cpp` within test-file size guidance (`152` lines)
|
||||
|
||||
Reference in New Issue
Block a user