diff --git a/CMakeLists.txt b/CMakeLists.txt index b162c0b..d3dc34d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,8 +92,10 @@ add_library(bishop_lib typechecker/lists.cpp typechecker/pairs.cpp typechecker/tuples.cpp + typechecker/sets.cpp typechecker/check_pair.cpp typechecker/check_tuple.cpp + typechecker/check_set.cpp typechecker/check_lambda.cpp codegen/codegen.cpp codegen/emit_type.cpp @@ -124,6 +126,7 @@ add_library(bishop_lib codegen/emit_string.cpp codegen/emit_pair.cpp codegen/emit_tuple.cpp + codegen/emit_set.cpp codegen/emit_method_call.cpp codegen/emit_function_call.cpp codegen/emit_field.cpp diff --git a/README.md b/README.md index 5c1c19d..ef8bde5 100644 --- a/README.md +++ b/README.md @@ -677,6 +677,89 @@ for i in 0..5 { // sum is 15 ``` +## Sets + +Sets store unique elements with O(1) lookup. Duplicate elements are automatically deduplicated. + +### Set Creation + +```bishop +nums := Set(); // empty set +names := Set(); // empty string set +``` + +### Set Literals + +```bishop +nums := {1, 2, 3}; // inferred as Set +names := {"a", "b", "c"}; // inferred as Set +unique := {1, 2, 2, 3, 3, 3}; // duplicates removed: {1, 2, 3} +``` + +### Typed Declaration + +```bishop +Set nums = {1, 2, 3}; +Set names = Set(); +``` + +### Set Methods + +```bishop +nums := {10, 20, 30}; + +// Query methods +nums.length(); // -> int: 3 +nums.is_empty(); // -> bool: false +nums.contains(20); // -> bool: true + +// Modification methods +nums.add(40); // add element +nums.remove(20); // remove element, returns true if found +nums.clear(); // remove all elements +``` + +### Set Operations + +```bishop +a := {1, 2, 3}; +b := {2, 3, 4}; + +// Union: elements in either set +c := a.union(b); // {1, 2, 3, 4} + +// Intersection: elements in both sets +d := a.intersection(b); // {2, 3} + +// Difference: elements in a but not in b +e := a.difference(b); // {1} + +// Symmetric difference: elements in either but not both +f := a.symmetric_difference(b); // {1, 4} +``` + +### Set Predicates + +```bishop +a := {1, 2}; +b := {1, 2, 3}; + +a.is_subset(b); // -> bool: true (all elements of a are in b) +b.is_superset(a); // -> bool: true (b contains all elements of a) +``` + +### Set Iteration + +```bishop +nums := {1, 2, 3}; + +for n in nums { + print(n); +} +``` + +**Note:** Set iteration order is not guaranteed. + ## Error Handling ### Error Types diff --git a/codegen/codegen.hpp b/codegen/codegen.hpp index 6653c2b..65a28d2 100644 --- a/codegen/codegen.hpp +++ b/codegen/codegen.hpp @@ -113,6 +113,11 @@ std::string emit_pair_field_access(const std::string& obj_str, const std::string std::string emit_tuple_create(CodeGenState& state, const TupleCreate& tuple); std::string emit_tuple_method_call(CodeGenState& state, const MethodCall& call, const std::string& obj_str, const std::vector& args); +// Set (emit_set.cpp) +std::string emit_set_create(const SetCreate& set); +std::string emit_set_literal(CodeGenState& state, const SetLiteral& set); +std::string emit_set_method_call(CodeGenState& state, const MethodCall& call, const std::string& obj_str, const std::vector& args); + // Method call (emit_method_call.cpp) std::string method_call(const std::string& object, const std::string& method, const std::vector& args); std::string emit_method_call(CodeGenState& state, const MethodCall& call); diff --git a/codegen/emit_expression.cpp b/codegen/emit_expression.cpp index f416932..806116d 100644 --- a/codegen/emit_expression.cpp +++ b/codegen/emit_expression.cpp @@ -106,6 +106,14 @@ string emit(CodeGenState& state, const ASTNode& node) { return emit_tuple_create(state, *tuple); } + if (auto* set = dynamic_cast(&node)) { + return emit_set_create(*set); + } + + if (auto* set = dynamic_cast(&node)) { + return emit_set_literal(state, *set); + } + if (auto* lambda = dynamic_cast(&node)) { return emit_lambda_expr(state, *lambda); } diff --git a/codegen/emit_method_call.cpp b/codegen/emit_method_call.cpp index cc2051e..f77e11e 100644 --- a/codegen/emit_method_call.cpp +++ b/codegen/emit_method_call.cpp @@ -113,6 +113,11 @@ string emit_method_call(CodeGenState& state, const MethodCall& call) { return emit_tuple_method_call(state, call, obj_str, args); } + // Handle Set methods + if (call.object_type.rfind("Set<", 0) == 0) { + return emit_set_method_call(state, call, obj_str, args); + } + // Use -> for pointer types (auto-deref like Go) if (!call.object_type.empty() && call.object_type.back() == '*') { return fmt::format("{}->{}({})", obj_str, call.method_name, fmt::join(args, ", ")); diff --git a/codegen/emit_set.cpp b/codegen/emit_set.cpp new file mode 100644 index 0000000..808ee7f --- /dev/null +++ b/codegen/emit_set.cpp @@ -0,0 +1,132 @@ +/** + * @file emit_set.cpp + * @brief Set code generation for the Bishop code generator. + * + * Generates C++ code for Set creation, literals, and method calls. + * Sets are implemented using std::unordered_set. + */ + +#include "codegen.hpp" +#include + +using namespace std; + +namespace codegen { + +/** + * Emits a set creation expression: Set() -> std::unordered_set{} + */ +string emit_set_create(const SetCreate& set) { + return fmt::format("std::unordered_set<{}>{{}}", + map_type(set.element_type)); +} + +/** + * Emits a set literal expression: {1, 2, 3} -> std::unordered_set{1, 2, 3} + */ +string emit_set_literal(CodeGenState& state, const SetLiteral& set) { + if (set.elements.empty()) { + // This shouldn't happen due to parser validation, but handle it + return "std::unordered_set{}"; + } + + // Infer element type from first element + string first_elem = emit(state, *set.elements[0]); + + vector elems; + for (const auto& elem : set.elements) { + elems.push_back(emit(state, *elem)); + } + + string elements_str; + for (size_t i = 0; i < elems.size(); i++) { + if (i > 0) elements_str += ", "; + elements_str += elems[i]; + } + + // The type will be deduced from the elements using initializer_list + return fmt::format("std::unordered_set{{{}}}", elements_str); +} + +/** + * Emits a method call on a set. + */ +string emit_set_method_call(CodeGenState& state, const MethodCall& call, + const string& obj_str, const vector& args) { + (void)state; // Unused + const string& method = call.method_name; + + // Query methods + if (method == "length") { + return fmt::format("{}.size()", obj_str); + } + + if (method == "is_empty") { + return fmt::format("{}.empty()", obj_str); + } + + if (method == "contains") { + return fmt::format("{}.count({}) > 0", obj_str, args[0]); + } + + // Modification methods + if (method == "add") { + return fmt::format("{}.insert({})", obj_str, args[0]); + } + + if (method == "remove") { + return fmt::format("{}.erase({}) > 0", obj_str, args[0]); + } + + if (method == "clear") { + return fmt::format("{}.clear()", obj_str); + } + + // Set operations - these create new sets + if (method == "union") { + // Create a new set with elements from both + // Note: We use a lambda to create and return the union set + return fmt::format( + "[&]() {{ auto result = {}; for (const auto& e : {}) result.insert(e); return result; }}()", + obj_str, args[0]); + } + + if (method == "intersection") { + // Create a new set with common elements + return fmt::format( + "[&]() {{ std::remove_cvref_t result; for (const auto& e : {}) if ({}.count(e)) result.insert(e); return result; }}()", + obj_str, obj_str, args[0]); + } + + if (method == "difference") { + // Create a new set with elements in this but not in other + return fmt::format( + "[&]() {{ std::remove_cvref_t result; for (const auto& e : {}) if (!{}.count(e)) result.insert(e); return result; }}()", + obj_str, obj_str, args[0]); + } + + if (method == "symmetric_difference") { + // Create a new set with elements in either but not both + return fmt::format( + "[&]() {{ std::remove_cvref_t result; for (const auto& e : {}) if (!{}.count(e)) result.insert(e); for (const auto& e : {}) if (!{}.count(e)) result.insert(e); return result; }}()", + obj_str, obj_str, args[0], args[0], obj_str); + } + + // Set predicates + if (method == "is_subset") { + return fmt::format( + "[&]() {{ for (const auto& e : {}) if (!{}.count(e)) return false; return true; }}()", + obj_str, args[0]); + } + + if (method == "is_superset") { + return fmt::format( + "[&]() {{ for (const auto& e : {}) if (!{}.count(e)) return false; return true; }}()", + args[0], obj_str); + } + + // Unknown method - should be caught by typechecker + return fmt::format("{}.{}()", obj_str, method); +} + +} // namespace codegen diff --git a/codegen/emit_type.cpp b/codegen/emit_type.cpp index ff74b76..4d6841f 100644 --- a/codegen/emit_type.cpp +++ b/codegen/emit_type.cpp @@ -78,6 +78,13 @@ string map_type(const string& t) { return "std::vector<" + map_type(element_type) + ">"; } + // Handle Set types: Set -> std::unordered_set + if (t.rfind("Set<", 0) == 0 && t.back() == '>') { + string element_type = extract_element_type(t, "Set<"); + assert(!element_type.empty() && "malformed Set type passed typechecker"); + return "std::unordered_set<" + map_type(element_type) + ">"; + } + // Handle function types: fn(int, str) -> bool -> std::function if (t.rfind("fn(", 0) == 0) { // Find the closing paren and extract param types diff --git a/lexer/lexer.cpp b/lexer/lexer.cpp index f771992..a78dd6a 100644 --- a/lexer/lexer.cpp +++ b/lexer/lexer.cpp @@ -36,6 +36,7 @@ static unordered_map keywords = { {"List", TokenType::LIST}, {"Pair", TokenType::PAIR}, {"Tuple", TokenType::TUPLE}, + {"Set", TokenType::SET}, {"select", TokenType::SELECT}, {"case", TokenType::CASE}, {"extern", TokenType::EXTERN}, diff --git a/lexer/token.hpp b/lexer/token.hpp index e47aef8..af2e08c 100644 --- a/lexer/token.hpp +++ b/lexer/token.hpp @@ -161,6 +161,7 @@ enum class TokenType { LIST, PAIR, TUPLE, + SET, SELECT, CASE, EXTERN, diff --git a/parser/ast.hpp b/parser/ast.hpp index f6dcdff..aa0fd2b 100644 --- a/parser/ast.hpp +++ b/parser/ast.hpp @@ -197,6 +197,16 @@ struct TupleCreate : ASTNode { vector> elements; ///< Element expressions (2-5 elements) }; +/** @brief Set creation: Set() */ +struct SetCreate : ASTNode { + string element_type; ///< Type of elements the set holds +}; + +/** @brief Set literal: {1, 2, 3} */ +struct SetLiteral : ASTNode { + vector> elements; ///< Set element expressions +}; + /** @brief A single case in a select statement */ struct SelectCase : ASTNode { string binding_name; ///< Variable to bind result (empty for send) diff --git a/parser/parse_primary.cpp b/parser/parse_primary.cpp index 18ac2ae..ff84b94 100644 --- a/parser/parse_primary.cpp +++ b/parser/parse_primary.cpp @@ -220,6 +220,58 @@ unique_ptr parse_primary(ParserState& state) { return tuple; } + // Handle set creation: Set() or Set() + if (check(state, TokenType::SET)) { + int start_line = current(state).line; + advance(state); + consume(state, TokenType::LT); + + // Use parse_type to support nested generics like Set> + string element_type = parse_type(state); + + consume(state, TokenType::GT); + consume(state, TokenType::LPAREN); + consume(state, TokenType::RPAREN); + + auto set = make_unique(); + set->element_type = element_type; + set->line = start_line; + return set; + } + + // Handle set literal: {expr, expr, ...} + // Distinguished from struct literal by not being preceded by a type name + if (check(state, TokenType::LBRACE)) { + int start_line = current(state).line; + advance(state); + + // Empty set literal is not allowed - use Set() instead + if (check(state, TokenType::RBRACE)) { + throw runtime_error("empty set literal not allowed, use Set() instead at line " + to_string(start_line)); + } + + // Peek ahead to see if this looks like a struct literal (field: value pattern) + // If we see IDENT followed by COLON, it's likely a struct literal, but that + // would have been caught by the IDENT handler above, so here it must be a set literal + auto set = make_unique(); + set->line = start_line; + + while (!check(state, TokenType::RBRACE) && !check(state, TokenType::EOF_TOKEN)) { + auto elem = parse_expression(state); + + if (elem) { + set->elements.push_back(move(elem)); + } + + if (check(state, TokenType::COMMA)) { + advance(state); + } + } + + consume(state, TokenType::RBRACE); + return set; + } + // Handle list literal: [expr, expr, ...] if (check(state, TokenType::LBRACKET)) { int start_line = current(state).line; diff --git a/parser/parse_statement.cpp b/parser/parse_statement.cpp index 1fd9b1f..6b78249 100644 --- a/parser/parse_statement.cpp +++ b/parser/parse_statement.cpp @@ -231,6 +231,27 @@ unique_ptr parse_statement(ParserState& state) { return decl; } + // Set variable declaration: Set nums = {1, 2, 3}; or Set names = Set(); + if (check(state, TokenType::SET)) { + int start_line = current(state).line; + advance(state); + consume(state, TokenType::LT); + + // Use parse_type to support nested generics like Set> + string element_type = parse_type(state); + + consume(state, TokenType::GT); + + auto decl = make_unique(); + decl->type = "Set<" + element_type + ">"; + decl->line = start_line; + decl->name = consume(state, TokenType::IDENT).value; + consume(state, TokenType::ASSIGN); + decl->value = parse_expression(state); + consume(state, TokenType::SEMICOLON); + return decl; + } + // NOT expression statement: !expr or fail "msg"; !valid or continue; etc. if (check(state, TokenType::NOT)) { auto expr = parse_expression(state); diff --git a/parser/parse_type.cpp b/parser/parse_type.cpp index d86fbb0..525bace 100644 --- a/parser/parse_type.cpp +++ b/parser/parse_type.cpp @@ -156,6 +156,15 @@ string parse_base_type(ParserState& state) { return "Tuple<" + element_type + ">"; } + // Set type + if (check(state, TokenType::SET)) { + advance(state); + consume(state, TokenType::LT); + string element_type = parse_type(state); + consume(state, TokenType::GT); + return "Set<" + element_type + ">"; + } + // Custom type (struct name), qualified type (module.Type), or generic (Type) if (check(state, TokenType::IDENT)) { string type = current(state).value; diff --git a/runtime/std/std.hpp b/runtime/std/std.hpp index 9d5c96b..2e0a62f 100644 --- a/runtime/std/std.hpp +++ b/runtime/std/std.hpp @@ -20,10 +20,12 @@ #include #include #include +#include #include #include #include #include +#include // Error handling primitives #include diff --git a/tests/test_sets.b b/tests/test_sets.b new file mode 100644 index 0000000..5d8f379 --- /dev/null +++ b/tests/test_sets.b @@ -0,0 +1,389 @@ +// ============================================ +// Set Creation +// ============================================ + +fn test_set_create_empty_int() { + nums := Set(); + assert_eq(nums.length(), 0); +} + +fn test_set_create_empty_str() { + names := Set(); + assert_eq(names.is_empty(), true); +} + +// ============================================ +// Set Literals +// ============================================ + +fn test_set_literal_int() { + nums := {1, 2, 3}; + assert_eq(nums.length(), 3); +} + +fn test_set_literal_str() { + names := {"a", "b", "c"}; + assert_eq(names.length(), 3); +} + +fn test_set_literal_single() { + nums := {42}; + assert_eq(nums.length(), 1); +} + +fn test_set_literal_bool() { + flags := {true, false}; + assert_eq(flags.length(), 2); +} + +fn test_set_literal_duplicates() { + nums := {1, 2, 2, 3, 3, 3}; + assert_eq(nums.length(), 3); +} + +// ============================================ +// Set Query Methods +// ============================================ + +fn test_set_length() { + nums := {1, 2, 3, 4, 5}; + assert_eq(nums.length(), 5); +} + +fn test_set_is_empty_false() { + nums := {1}; + assert_eq(nums.is_empty(), false); +} + +fn test_set_is_empty_true() { + nums := Set(); + assert_eq(nums.is_empty(), true); +} + +fn test_set_contains_true() { + nums := {1, 2, 3}; + assert_eq(nums.contains(2), true); +} + +fn test_set_contains_false() { + nums := {1, 2, 3}; + assert_eq(nums.contains(5), false); +} + +fn test_set_contains_str() { + names := {"alice", "bob"}; + assert_eq(names.contains("bob"), true); + assert_eq(names.contains("charlie"), false); +} + +// ============================================ +// Set Modification Methods +// ============================================ + +fn test_set_add() { + nums := {1, 2}; + nums.add(3); + assert_eq(nums.length(), 3); + assert_eq(nums.contains(3), true); +} + +fn test_set_add_duplicate() { + nums := {1, 2, 3}; + nums.add(2); + assert_eq(nums.length(), 3); +} + +fn test_set_remove_exists() { + nums := {1, 2, 3}; + result := nums.remove(2); + assert_eq(result, true); + assert_eq(nums.length(), 2); + assert_eq(nums.contains(2), false); +} + +fn test_set_remove_not_exists() { + nums := {1, 2, 3}; + result := nums.remove(5); + assert_eq(result, false); + assert_eq(nums.length(), 3); +} + +fn test_set_clear() { + nums := {1, 2, 3}; + nums.clear(); + assert_eq(nums.length(), 0); + assert_eq(nums.is_empty(), true); +} + +// ============================================ +// Set Operations - Union +// ============================================ + +fn test_set_union() { + a := {1, 2, 3}; + b := {3, 4, 5}; + c := a.union(b); + assert_eq(c.length(), 5); + assert_eq(c.contains(1), true); + assert_eq(c.contains(5), true); +} + +fn test_set_union_disjoint() { + a := {1, 2}; + b := {3, 4}; + c := a.union(b); + assert_eq(c.length(), 4); +} + +fn test_set_union_identical() { + a := {1, 2, 3}; + b := {1, 2, 3}; + c := a.union(b); + assert_eq(c.length(), 3); +} + +fn test_set_union_empty() { + a := {1, 2, 3}; + b := Set(); + c := a.union(b); + assert_eq(c.length(), 3); +} + +// ============================================ +// Set Operations - Intersection +// ============================================ + +fn test_set_intersection() { + a := {1, 2, 3}; + b := {2, 3, 4}; + c := a.intersection(b); + assert_eq(c.length(), 2); + assert_eq(c.contains(2), true); + assert_eq(c.contains(3), true); +} + +fn test_set_intersection_disjoint() { + a := {1, 2}; + b := {3, 4}; + c := a.intersection(b); + assert_eq(c.length(), 0); +} + +fn test_set_intersection_identical() { + a := {1, 2, 3}; + b := {1, 2, 3}; + c := a.intersection(b); + assert_eq(c.length(), 3); +} + +fn test_set_intersection_empty() { + a := {1, 2, 3}; + b := Set(); + c := a.intersection(b); + assert_eq(c.length(), 0); +} + +// ============================================ +// Set Operations - Difference +// ============================================ + +fn test_set_difference() { + a := {1, 2, 3}; + b := {2, 3, 4}; + c := a.difference(b); + assert_eq(c.length(), 1); + assert_eq(c.contains(1), true); +} + +fn test_set_difference_disjoint() { + a := {1, 2}; + b := {3, 4}; + c := a.difference(b); + assert_eq(c.length(), 2); +} + +fn test_set_difference_identical() { + a := {1, 2, 3}; + b := {1, 2, 3}; + c := a.difference(b); + assert_eq(c.length(), 0); +} + +fn test_set_difference_empty_other() { + a := {1, 2, 3}; + b := Set(); + c := a.difference(b); + assert_eq(c.length(), 3); +} + +// ============================================ +// Set Operations - Symmetric Difference +// ============================================ + +fn test_set_symmetric_difference() { + a := {1, 2, 3}; + b := {2, 3, 4}; + c := a.symmetric_difference(b); + assert_eq(c.length(), 2); + assert_eq(c.contains(1), true); + assert_eq(c.contains(4), true); + assert_eq(c.contains(2), false); +} + +fn test_set_symmetric_difference_disjoint() { + a := {1, 2}; + b := {3, 4}; + c := a.symmetric_difference(b); + assert_eq(c.length(), 4); +} + +fn test_set_symmetric_difference_identical() { + a := {1, 2, 3}; + b := {1, 2, 3}; + c := a.symmetric_difference(b); + assert_eq(c.length(), 0); +} + +// ============================================ +// Set Predicates - Subset/Superset +// ============================================ + +fn test_set_is_subset_true() { + a := {1, 2}; + b := {1, 2, 3}; + assert_eq(a.is_subset(b), true); +} + +fn test_set_is_subset_false() { + a := {1, 2, 4}; + b := {1, 2, 3}; + assert_eq(a.is_subset(b), false); +} + +fn test_set_is_subset_equal() { + a := {1, 2, 3}; + b := {1, 2, 3}; + assert_eq(a.is_subset(b), true); +} + +fn test_set_is_subset_empty() { + a := Set(); + b := {1, 2, 3}; + assert_eq(a.is_subset(b), true); +} + +fn test_set_is_superset_true() { + a := {1, 2, 3}; + b := {1, 2}; + assert_eq(a.is_superset(b), true); +} + +fn test_set_is_superset_false() { + a := {1, 2, 3}; + b := {1, 2, 4}; + assert_eq(a.is_superset(b), false); +} + +fn test_set_is_superset_equal() { + a := {1, 2, 3}; + b := {1, 2, 3}; + assert_eq(a.is_superset(b), true); +} + +fn test_set_is_superset_of_empty() { + a := {1, 2, 3}; + b := Set(); + assert_eq(a.is_superset(b), true); +} + +// ============================================ +// Set Iteration +// ============================================ + +fn test_set_iteration() { + nums := {1, 2, 3}; + count := 0; + + for n in nums { + count = count + 1; + } + + assert_eq(count, 3); +} + +fn test_set_iteration_empty() { + nums := Set(); + count := 0; + + for n in nums { + count = count + 1; + } + + assert_eq(count, 0); +} + +fn test_set_iteration_str() { + names := {"alice", "bob", "charlie"}; + count := 0; + + for name in names { + count = count + 1; + } + + assert_eq(count, 3); +} + +// ============================================ +// Typed Variable Declaration +// ============================================ + +fn test_set_typed_decl() { + Set nums = {1, 2, 3}; + assert_eq(nums.length(), 3); +} + +fn test_set_typed_empty() { + Set names = Set(); + assert_eq(names.is_empty(), true); +} + +// ============================================ +// Set with Structs +// ============================================ + +Point :: struct { + x int, + y int +} + +fn test_set_contains_after_add() { + nums := Set(); + nums.add(10); + nums.add(20); + nums.add(30); + assert_eq(nums.contains(20), true); + assert_eq(nums.contains(40), false); +} + +// ============================================ +// Chained Operations +// ============================================ + +fn test_set_chained_add() { + nums := Set(); + nums.add(1); + nums.add(2); + nums.add(3); + assert_eq(nums.length(), 3); + assert_eq(nums.contains(1), true); + assert_eq(nums.contains(3), true); +} + +fn test_set_chained_operations() { + a := {1, 2, 3, 4, 5}; + b := {4, 5, 6, 7}; + common := a.intersection(b); + assert_eq(common.length(), 2); + assert_eq(common.contains(4), true); + assert_eq(common.contains(5), true); +} diff --git a/typechecker/check_expression.cpp b/typechecker/check_expression.cpp index 46c0bc7..0299330 100644 --- a/typechecker/check_expression.cpp +++ b/typechecker/check_expression.cpp @@ -95,6 +95,14 @@ TypeInfo infer_type(TypeCheckerState& state, const ASTNode& expr) { return check_tuple_create(state, *tuple); } + if (auto* set = dynamic_cast(&expr)) { + return check_set_create(state, *set); + } + + if (auto* set = dynamic_cast(&expr)) { + return check_set_literal(state, *set); + } + if (auto* lambda = dynamic_cast(&expr)) { return check_lambda_expr(state, *lambda); } diff --git a/typechecker/check_for_stmt.cpp b/typechecker/check_for_stmt.cpp index f940136..53c6a7c 100644 --- a/typechecker/check_for_stmt.cpp +++ b/typechecker/check_for_stmt.cpp @@ -37,9 +37,7 @@ void check_for_stmt(TypeCheckerState& state, const ForStmt& for_stmt) { } else { TypeInfo iter_type = infer_type(state, *for_stmt.iterable); - if (iter_type.base_type.rfind("List<", 0) != 0) { - error(state, "for-each requires a List, got '" + format_type(iter_type) + "'", for_stmt.line); - } else { + if (iter_type.base_type.rfind("List<", 0) == 0) { string element_type = extract_element_type(iter_type.base_type, "List<"); if (element_type.empty()) { @@ -47,6 +45,16 @@ void check_for_stmt(TypeCheckerState& state, const ForStmt& for_stmt) { } else { loop_var_type = {element_type, false, false}; } + } else if (iter_type.base_type.rfind("Set<", 0) == 0) { + string element_type = extract_element_type(iter_type.base_type, "Set<"); + + if (element_type.empty()) { + error(state, "malformed Set type '" + iter_type.base_type + "' in for-each loop", for_stmt.line); + } else { + loop_var_type = {element_type, false, false}; + } + } else { + error(state, "for-each requires a List or Set, got '" + format_type(iter_type) + "'", for_stmt.line); } } diff --git a/typechecker/check_method_call.cpp b/typechecker/check_method_call.cpp index 8c66e89..a995cfe 100644 --- a/typechecker/check_method_call.cpp +++ b/typechecker/check_method_call.cpp @@ -258,6 +258,17 @@ TypeInfo check_method_call(TypeCheckerState& state, const MethodCall& mcall) { return check_tuple_method(state, mcall, element_type); } + if (effective_type.base_type.rfind("Set<", 0) == 0) { + string element_type = extract_element_type(effective_type.base_type, "Set<"); + + if (element_type.empty()) { + error(state, "malformed Set type '" + effective_type.base_type + "'", mcall.line); + return {"unknown", false, false}; + } + + return check_set_method(state, mcall, element_type); + } + if (effective_type.base_type == "str") { return check_str_method(state, mcall); } diff --git a/typechecker/check_set.cpp b/typechecker/check_set.cpp new file mode 100644 index 0000000..bd6fb0f --- /dev/null +++ b/typechecker/check_set.cpp @@ -0,0 +1,102 @@ +/** + * @file check_set.cpp + * @brief Set type inference for the Bishop type checker. + */ + +#include "typechecker.hpp" +#include "sets.hpp" + +using namespace std; + +namespace typechecker { + +/** + * Infers the type of a set creation expression. + */ +TypeInfo check_set_create(TypeCheckerState& state, const SetCreate& set) { + (void)state; + return {"Set<" + set.element_type + ">", false, false}; +} + +/** + * Infers the type of a set literal. + */ +TypeInfo check_set_literal(TypeCheckerState& state, const SetLiteral& set) { + if (set.elements.empty()) { + error(state, "cannot infer type of empty set literal, use Set() instead", set.line); + return {"unknown", false, false}; + } + + TypeInfo first_type = infer_type(state, *set.elements[0]); + + for (size_t i = 1; i < set.elements.size(); i++) { + TypeInfo elem_type = infer_type(state, *set.elements[i]); + + if (elem_type.base_type != first_type.base_type) { + error(state, "set literal has mixed types: '" + format_type(first_type) + + "' and '" + format_type(elem_type) + "'", set.line); + } + } + + return {"Set<" + first_type.base_type + ">", false, false}; +} + +/** + * Type checks a method call on a set. + */ +TypeInfo check_set_method(TypeCheckerState& state, const MethodCall& mcall, const string& element_type) { + auto method_info = bishop::get_set_method_info(mcall.method_name); + + if (!method_info) { + error(state, "Set has no method '" + mcall.method_name + "'", mcall.line); + return {"unknown", false, false}; + } + + const auto& [param_types, return_type] = *method_info; + + if (mcall.args.size() != param_types.size()) { + error(state, "method '" + mcall.method_name + "' expects " + + to_string(param_types.size()) + " arguments, got " + + to_string(mcall.args.size()), mcall.line); + } + + // Build the Set type string for parameter checking + string set_type = "Set<" + element_type + ">"; + + for (size_t i = 0; i < mcall.args.size() && i < param_types.size(); i++) { + TypeInfo arg_type = infer_type(state, *mcall.args[i]); + string expected_str = param_types[i]; + + // Substitute T with actual element type + if (expected_str == "T") { + expected_str = element_type; + } else if (expected_str == "Set") { + expected_str = set_type; + } + + TypeInfo expected = {expected_str, false, false}; + + if (!types_compatible(expected, arg_type)) { + error(state, "argument " + to_string(i + 1) + " of method '" + + mcall.method_name + "' expects '" + expected_str + + "', got '" + format_type(arg_type) + "'", mcall.line); + } + } + + string ret = return_type; + + // Substitute T with actual element type in return type + if (ret == "T") { + ret = element_type; + } else if (ret == "Set") { + ret = set_type; + } + + if (ret == "void") { + return {"void", false, true}; + } + + return {ret, false, false}; +} + +} // namespace typechecker diff --git a/typechecker/sets.cpp b/typechecker/sets.cpp new file mode 100644 index 0000000..25eb96d --- /dev/null +++ b/typechecker/sets.cpp @@ -0,0 +1,187 @@ +/** + * @file sets.cpp + * @brief Set method type definitions for the Bishop type checker. + * + * Defines type signatures for all built-in Set methods. + * Uses "T" as a placeholder for the element type, which is + * substituted with the actual type at type check time. + */ + +/** + * @bishop_method add + * @type Set + * @description Adds an element to the set. + * @param elem T - The element to add + * @example + * s := Set(); + * s.add(42); + */ + +/** + * @bishop_method remove + * @type Set + * @description Removes an element from the set. + * @param elem T - The element to remove + * @returns bool - True if element was found and removed + * @example + * s := {1, 2, 3}; + * s.remove(2); // returns true + */ + +/** + * @bishop_method contains + * @type Set + * @description Checks if the set contains the given element. + * @param elem T - The element to search for + * @returns bool - True if found, false otherwise + * @example + * s := {1, 2, 3}; + * if s.contains(2) { + * print("Found it!"); + * } + */ + +/** + * @bishop_method length + * @type Set + * @description Returns the number of elements in the set. + * @returns int - The set size + * @example + * s := {1, 2, 3}; + * len := s.length(); // 3 + */ + +/** + * @bishop_method is_empty + * @type Set + * @description Returns true if the set has no elements. + * @returns bool - True if empty, false otherwise + * @example + * s := Set(); + * if s.is_empty() { + * print("Set is empty"); + * } + */ + +/** + * @bishop_method clear + * @type Set + * @description Removes all elements from the set. + * @example + * s := {1, 2, 3}; + * s.clear(); // set is now empty + */ + +/** + * @bishop_method union + * @type Set + * @description Returns a new set with elements from both sets. + * @param other Set - The other set + * @returns Set - New set containing all elements from both + * @example + * a := {1, 2, 3}; + * b := {3, 4, 5}; + * c := a.union(b); // {1, 2, 3, 4, 5} + */ + +/** + * @bishop_method intersection + * @type Set + * @description Returns a new set with elements common to both sets. + * @param other Set - The other set + * @returns Set - New set containing only common elements + * @example + * a := {1, 2, 3}; + * b := {2, 3, 4}; + * c := a.intersection(b); // {2, 3} + */ + +/** + * @bishop_method difference + * @type Set + * @description Returns a new set with elements in this set but not in the other. + * @param other Set - The other set + * @returns Set - New set containing elements only in this set + * @example + * a := {1, 2, 3}; + * b := {2, 3, 4}; + * c := a.difference(b); // {1} + */ + +/** + * @bishop_method symmetric_difference + * @type Set + * @description Returns a new set with elements in either set but not both. + * @param other Set - The other set + * @returns Set - New set containing elements exclusive to each set + * @example + * a := {1, 2, 3}; + * b := {2, 3, 4}; + * c := a.symmetric_difference(b); // {1, 4} + */ + +/** + * @bishop_method is_subset + * @type Set + * @description Checks if this set is a subset of the other set. + * @param other Set - The other set + * @returns bool - True if all elements are in the other set + * @example + * a := {1, 2}; + * b := {1, 2, 3}; + * a.is_subset(b); // true + */ + +/** + * @bishop_method is_superset + * @type Set + * @description Checks if this set is a superset of the other set. + * @param other Set - The other set + * @returns bool - True if this set contains all elements from the other + * @example + * a := {1, 2, 3}; + * b := {1, 2}; + * a.is_superset(b); // true + */ + +#include "sets.hpp" + +#include + +namespace bishop { + +std::optional get_set_method_info(const std::string& method_name) { + // "T" is placeholder for element type, substituted at type check time + // "Set" is placeholder for set type with same element type + static const std::map set_methods = { + // Query methods + {"length", {{}, "int"}}, + {"is_empty", {{}, "bool"}}, + {"contains", {{"T"}, "bool"}}, + + // Modification methods + {"add", {{"T"}, "void"}}, + {"remove", {{"T"}, "bool"}}, + {"clear", {{}, "void"}}, + + // Set operations - return new Set + {"union", {{"Set"}, "Set"}}, + {"intersection", {{"Set"}, "Set"}}, + {"difference", {{"Set"}, "Set"}}, + {"symmetric_difference", {{"Set"}, "Set"}}, + + // Set predicates + {"is_subset", {{"Set"}, "bool"}}, + {"is_superset", {{"Set"}, "bool"}}, + }; + + auto it = set_methods.find(method_name); + + if (it != set_methods.end()) { + return it->second; + } + + return std::nullopt; +} + +} // namespace bishop diff --git a/typechecker/sets.hpp b/typechecker/sets.hpp new file mode 100644 index 0000000..39526eb --- /dev/null +++ b/typechecker/sets.hpp @@ -0,0 +1,33 @@ +/** + * @file sets.hpp + * @brief Set method type definitions for the Bishop type checker. + * + * Defines type signatures for all built-in Set methods. + * Uses "T" as a placeholder for the element type, which is + * substituted with the actual type at type check time. + */ + +#pragma once + +#include +#include +#include + +namespace bishop { + +/** + * Represents a set method signature with parameter types and return type. + * Uses "T" as a placeholder for the element type. + */ +struct SetMethodInfo { + std::vector param_types; + std::string return_type; +}; + +/** + * Returns type information for built-in Set methods. + * Returns nullopt if the method is not found. + */ +std::optional get_set_method_info(const std::string& method_name); + +} // namespace bishop diff --git a/typechecker/typechecker.cpp b/typechecker/typechecker.cpp index 4a63d0a..a838797 100644 --- a/typechecker/typechecker.cpp +++ b/typechecker/typechecker.cpp @@ -267,6 +267,16 @@ bool is_valid_type(const TypeCheckerState& state, const string& type) { return is_valid_type(state, element_type); } + if (type.rfind("Set<", 0) == 0 && type.back() == '>') { + string element_type = extract_element_type(type, "Set<"); + + if (element_type.empty()) { + return false; + } + + return is_valid_type(state, element_type); + } + // Pointer type: StructName* -> check that base is a valid struct if (!type.empty() && type.back() == '*') { string pointee = type.substr(0, type.length() - 1); diff --git a/typechecker/typechecker.hpp b/typechecker/typechecker.hpp index 02201e2..ad4097f 100644 --- a/typechecker/typechecker.hpp +++ b/typechecker/typechecker.hpp @@ -191,6 +191,11 @@ TypeInfo check_pair_field(TypeCheckerState& state, const FieldAccess& access, co TypeInfo check_tuple_create(TypeCheckerState& state, const TupleCreate& tuple); TypeInfo check_tuple_method(TypeCheckerState& state, const MethodCall& mcall, const std::string& element_type); +// Set type inference (check_set.cpp) +TypeInfo check_set_create(TypeCheckerState& state, const SetCreate& set); +TypeInfo check_set_literal(TypeCheckerState& state, const SetLiteral& set); +TypeInfo check_set_method(TypeCheckerState& state, const MethodCall& mcall, const std::string& element_type); + // Function call type inference (check_function_call.cpp) TypeInfo check_function_call(TypeCheckerState& state, const FunctionCall& call);