Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>(); // empty set
names := Set<str>(); // empty string set
```

### Set Literals

```bishop
nums := {1, 2, 3}; // inferred as Set<int>
names := {"a", "b", "c"}; // inferred as Set<str>
unique := {1, 2, 2, 3, 3, 3}; // duplicates removed: {1, 2, 3}
```

### Typed Declaration

```bishop
Set<int> nums = {1, 2, 3};
Set<str> names = Set<str>();
```

### 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
Expand Down
5 changes: 5 additions & 0 deletions codegen/codegen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>& 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<std::string>& args);

// Method call (emit_method_call.cpp)
std::string method_call(const std::string& object, const std::string& method, const std::vector<std::string>& args);
std::string emit_method_call(CodeGenState& state, const MethodCall& call);
Expand Down
8 changes: 8 additions & 0 deletions codegen/emit_expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ string emit(CodeGenState& state, const ASTNode& node) {
return emit_tuple_create(state, *tuple);
}

if (auto* set = dynamic_cast<const SetCreate*>(&node)) {
return emit_set_create(*set);
}

if (auto* set = dynamic_cast<const SetLiteral*>(&node)) {
return emit_set_literal(state, *set);
}

if (auto* lambda = dynamic_cast<const LambdaExpr*>(&node)) {
return emit_lambda_expr(state, *lambda);
}
Expand Down
5 changes: 5 additions & 0 deletions codegen/emit_method_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, ", "));
Expand Down
132 changes: 132 additions & 0 deletions codegen/emit_set.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @file emit_set.cpp
* @brief Set code generation for the Bishop code generator.
*
* Generates C++ code for Set<T> creation, literals, and method calls.
* Sets are implemented using std::unordered_set.
*/

#include "codegen.hpp"
#include <fmt/format.h>

using namespace std;

namespace codegen {

/**
* Emits a set creation expression: Set<int>() -> std::unordered_set<int>{}
*/
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<int>{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<void>{}";
}

// Infer element type from first element
string first_elem = emit(state, *set.elements[0]);

vector<string> 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<string>& 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<decltype({})> 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<decltype({})> 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<decltype({})> 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
7 changes: 7 additions & 0 deletions codegen/emit_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ string map_type(const string& t) {
return "std::vector<" + map_type(element_type) + ">";
}

// Handle Set<T> types: Set<int> -> std::unordered_set<int>
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<bool(int, std::string)>
if (t.rfind("fn(", 0) == 0) {
// Find the closing paren and extract param types
Expand Down
1 change: 1 addition & 0 deletions lexer/lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ static unordered_map<string, TokenType> keywords = {
{"List", TokenType::LIST},
{"Pair", TokenType::PAIR},
{"Tuple", TokenType::TUPLE},
{"Set", TokenType::SET},
{"select", TokenType::SELECT},
{"case", TokenType::CASE},
{"extern", TokenType::EXTERN},
Expand Down
1 change: 1 addition & 0 deletions lexer/token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ enum class TokenType {
LIST,
PAIR,
TUPLE,
SET,
SELECT,
CASE,
EXTERN,
Expand Down
10 changes: 10 additions & 0 deletions parser/ast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ struct TupleCreate : ASTNode {
vector<unique_ptr<ASTNode>> elements; ///< Element expressions (2-5 elements)
};

/** @brief Set creation: Set<T>() */
struct SetCreate : ASTNode {
string element_type; ///< Type of elements the set holds
};

/** @brief Set literal: {1, 2, 3} */
struct SetLiteral : ASTNode {
vector<unique_ptr<ASTNode>> 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)
Expand Down
52 changes: 52 additions & 0 deletions parser/parse_primary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,58 @@ unique_ptr<ASTNode> parse_primary(ParserState& state) {
return tuple;
}

// Handle set creation: Set<int>() or Set<str>()
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<List<int>>
string element_type = parse_type(state);

consume(state, TokenType::GT);
consume(state, TokenType::LPAREN);
consume(state, TokenType::RPAREN);

auto set = make_unique<SetCreate>();
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<T>() instead
if (check(state, TokenType::RBRACE)) {
throw runtime_error("empty set literal not allowed, use Set<T>() 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<SetLiteral>();
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;
Expand Down
Loading