diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index e6e3d37..6a710d2 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -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) diff --git a/editor/src/ast/CppOperators.h b/editor/src/ast/CppOperators.h new file mode 100644 index 0000000..84b5f49 --- /dev/null +++ b/editor/src/ast/CppOperators.h @@ -0,0 +1,129 @@ +#pragma once + +// Step 468: Operator Overloading + Friend +// Focused node + parser helpers for C++ operator overload declarations. + +#include "ASTNode.h" + +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +class OperatorOverload : public ASTNode { +public: + std::string operatorSymbol; // <, <<, ==, +, ... + std::vector 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{}); + 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 parseParamList(const std::string& params) { + std::vector 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(c))) out.push_back(c); + else out.push_back('_'); + } + return out.empty() ? "unknown" : out; + } +}; diff --git a/editor/tests/step468_test.cpp b/editor/tests/step468_test.cpp new file mode 100644 index 0000000..faaab31 --- /dev/null +++ b/editor/tests/step468_test.cpp @@ -0,0 +1,152 @@ +// Step 468: Operator Overloading + Friend Tests (12 tests) + +#include "ast/CppOperators.h" + +#include + +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; +} diff --git a/progress.md b/progress.md index 763cfce..92b58c9 100644 --- a/progress.md +++ b/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)