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 @@ -90,11 +90,13 @@ add_library(bishop_lib
typechecker/check_select_stmt.cpp
typechecker/strings.cpp
typechecker/lists.cpp
typechecker/maps.cpp
typechecker/pairs.cpp
typechecker/tuples.cpp
typechecker/sets.cpp
typechecker/check_pair.cpp
typechecker/check_tuple.cpp
typechecker/check_map.cpp
typechecker/check_set.cpp
typechecker/check_lambda.cpp
codegen/codegen.cpp
Expand Down Expand Up @@ -123,6 +125,7 @@ add_library(bishop_lib
codegen/emit_lambda.cpp
codegen/emit_channel.cpp
codegen/emit_list.cpp
codegen/emit_map.cpp
codegen/emit_string.cpp
codegen/emit_pair.cpp
codegen/emit_tuple.cpp
Expand Down
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,78 @@ quotient := result.first; // 3
remainder := result.second; // 2
```

## Maps

Maps are key-value collections that provide fast lookup by key.

### Map Creation

```bishop
ages := Map<str, int>(); // empty map
config := Map<str, str>(); // string to string map
codes := Map<int, str>(); // int to string map
```

### Map Literals

```bishop
ages := {"alice": 30, "bob": 25}; // inferred as Map<str, int>
config := {"host": "localhost", "port": "8080"}; // inferred as Map<str, str>
```

### Typed Declaration

```bishop
Map<str, int> ages = {"alice": 30};
Map<str, str> config = Map<str, str>();
```

### Map Methods

```bishop
ages := {"alice": 30, "bob": 25};

// Query methods
ages.length(); // -> int: 2
ages.is_empty(); // -> bool: false
ages.contains("alice"); // -> bool: true

// Access methods (get returns optional)
age := ages.get("alice") default 0; // -> int: 30
age := ages.get("unknown") default 0; // -> int: 0 (key not found)

// Modification methods
ages.set("charlie", 35); // add or update key
ages.remove("bob"); // remove key
ages.clear(); // remove all entries

// Iteration methods
keys := ages.keys(); // -> List<str>: all keys
vals := ages.values(); // -> List<int>: all values
items := ages.items(); // -> List<MapItem<str, int>>: all entries
```

### Iterating Maps

```bishop
ages := {"alice": 30, "bob": 25};

// Iterate over keys
for key in ages.keys() {
print(key);
}

// Iterate over values
for val in ages.values() {
print(val);
}

// Iterate over key-value pairs
for item in ages.items() {
print(item.key + ": " + str(item.value));
}
```

## Tuples

Tuples hold 2-5 values of the same type.
Expand Down
5 changes: 5 additions & 0 deletions codegen/codegen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ std::string emit_list_create(const ListCreate& list);
std::string emit_list_literal(CodeGenState& state, const ListLiteral& list);
std::string emit_list_method_call(CodeGenState& state, const MethodCall& call, const std::string& obj_str, const std::vector<std::string>& args);

// Map (emit_map.cpp)
std::string emit_map_create(const MapCreate& map);
std::string emit_map_literal(CodeGenState& state, const MapLiteral& map);
std::string emit_map_method_call(CodeGenState& state, const MethodCall& call, const std::string& obj_str, const std::vector<std::string>& args);

// String methods (emit_string.cpp)
std::string emit_str_method_call(CodeGenState& state, const MethodCall& call, const std::string& obj_str, const std::vector<std::string>& args);

Expand Down
8 changes: 8 additions & 0 deletions codegen/emit_expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ string emit(CodeGenState& state, const ASTNode& node) {
return emit_list_literal(state, *list);
}

if (auto* map = dynamic_cast<const MapCreate*>(&node)) {
return emit_map_create(*map);
}

if (auto* map = dynamic_cast<const MapLiteral*>(&node)) {
return emit_map_literal(state, *map);
}

if (auto* pair = dynamic_cast<const PairCreate*>(&node)) {
return emit_pair_create(state, *pair);
}
Expand Down
131 changes: 131 additions & 0 deletions codegen/emit_map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* @file emit_map.cpp
* @brief Map emission for the Bishop code generator.
*/

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

using namespace std;

namespace codegen {

/**
* Emits a map creation: Map<K, V>() -> std::unordered_map<K, V>{}.
*/
string emit_map_create(const MapCreate& map) {
string cpp_key_type = map_type(map.key_type);
string cpp_value_type = map_type(map.value_type);
return "std::unordered_map<" + cpp_key_type + ", " + cpp_value_type + ">{}";
}

/**
* Emits a map literal: {"key": value, ...} -> std::unordered_map{std::make_pair(key, value), ...}.
*
* Uses std::make_pair for each entry to help class template argument deduction (CTAD)
* correctly infer the map types.
*/
string emit_map_literal(CodeGenState& state, const MapLiteral& map) {
string entries;

for (size_t i = 0; i < map.entries.size(); i++) {
if (i > 0) {
entries += ", ";
}

string key = emit(state, *map.entries[i].first);
string value = emit(state, *map.entries[i].second);
entries += "std::make_pair(" + key + ", " + value + ")";
}

return "std::unordered_map{" + entries + "}";
}

/**
* Emits a map method call, mapping Bishop methods to std::unordered_map equivalents.
*/
string emit_map_method_call(CodeGenState& state, const MethodCall& call, const string& obj_str, const vector<string>& args) {
if (call.method_name == "length") {
return obj_str + ".size()";
}

if (call.method_name == "is_empty") {
return obj_str + ".empty()";
}

if (call.method_name == "contains") {
return "(" + obj_str + ".find(" + args[0] + ") != " + obj_str + ".end())";
}

if (call.method_name == "get") {
// Returns optional - uses find and returns std::optional
return fmt::format(
"[](const auto& m, const auto& key) -> std::optional<std::decay_t<decltype(m.begin()->second)>> {{ "
"auto it = m.find(key); "
"if (it != m.end()) return it->second; "
"return std::nullopt; "
"}}({}, {})",
obj_str, args[0]
);
}

if (call.method_name == "set") {
return obj_str + "[" + args[0] + "] = " + args[1];
}

if (call.method_name == "remove") {
return obj_str + ".erase(" + args[0] + ")";
}

if (call.method_name == "clear") {
return obj_str + ".clear()";
}

if (call.method_name == "keys") {
// Returns a vector of keys
return fmt::format(
"[](const auto& m) {{ "
"std::vector<std::decay_t<decltype(m.begin()->first)>> keys; "
"keys.reserve(m.size()); "
"for (const auto& [k, v] : m) keys.push_back(k); "
"return keys; "
"}}({})",
obj_str
);
}

if (call.method_name == "values") {
// Returns a vector of values
return fmt::format(
"[](const auto& m) {{ "
"std::vector<std::decay_t<decltype(m.begin()->second)>> values; "
"values.reserve(m.size()); "
"for (const auto& [k, v] : m) values.push_back(v); "
"return values; "
"}}({})",
obj_str
);
}

if (call.method_name == "items") {
// Returns a vector of MapItem structs with key and value fields
return fmt::format(
"[](const auto& m) {{ "
"struct MapItem {{ "
"std::decay_t<decltype(m.begin()->first)> key; "
"std::decay_t<decltype(m.begin()->second)> value; "
"}}; "
"std::vector<MapItem> items; "
"items.reserve(m.size()); "
"for (const auto& [k, v] : m) items.push_back({{k, v}}); "
"return items; "
"}}({})",
obj_str
);
}

// Unknown map method - fall back to generic method call
return method_call(obj_str, call.method_name, args);
}

} // namespace codegen
5 changes: 5 additions & 0 deletions codegen/emit_method_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ string emit_method_call(CodeGenState& state, const MethodCall& call) {
return emit_list_method_call(state, call, obj_str, args);
}

// Handle Map methods - map to std::unordered_map equivalents
if (call.object_type.rfind("Map<", 0) == 0) {
return emit_map_method_call(state, call, obj_str, args);
}

// Handle extended string methods
if (call.object_type == "str") {
string result = emit_str_method_call(state, call, obj_str, args);
Expand Down
45 changes: 45 additions & 0 deletions codegen/emit_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,51 @@ string map_type(const string& t) {
return "std::vector<" + map_type(element_type) + ">";
}

// Handle Map<K, V> types: Map<str, int> -> std::unordered_map<std::string, int>
if (t.rfind("Map<", 0) == 0 && t.back() == '>') {
// Extract key and value types by finding the comma at depth 1
size_t start = 4; // After "Map<"
int depth = 1;
size_t comma_pos = string::npos;

for (size_t i = start; i < t.size(); i++) {
if (t[i] == '<') depth++;
else if (t[i] == '>') depth--;
else if (t[i] == ',' && depth == 1 && comma_pos == string::npos) {
comma_pos = i;
break;
}
}

assert(comma_pos != string::npos && "malformed Map type passed typechecker");
string key_type = t.substr(start, comma_pos - start);
string value_type = t.substr(comma_pos + 2, t.size() - comma_pos - 3); // Skip ", " and ">"
return "std::unordered_map<" + map_type(key_type) + ", " + map_type(value_type) + ">";
}

// Handle MapItem<K, V> types for iteration
if (t.rfind("MapItem<", 0) == 0 && t.back() == '>') {
// MapItem is a struct with key and value fields - we use an anonymous struct
size_t start = 8; // After "MapItem<"
int depth = 1;
size_t comma_pos = string::npos;

for (size_t i = start; i < t.size(); i++) {
if (t[i] == '<') depth++;
else if (t[i] == '>') depth--;
else if (t[i] == ',' && depth == 1 && comma_pos == string::npos) {
comma_pos = i;
break;
}
}

assert(comma_pos != string::npos && "malformed MapItem type passed typechecker");
string key_type = t.substr(start, comma_pos - start);
string value_type = t.substr(comma_pos + 2, t.size() - comma_pos - 3);
// Use auto for the struct since it's generated inline
return "auto";
}

// Handle Pair<T> types: Pair<int> -> std::pair<int, int>
if (t.rfind("Pair<", 0) == 0 && t.back() == '>') {
string element_type = extract_element_type(t, "Pair<");
Expand Down
46 changes: 46 additions & 0 deletions common/type_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,50 @@ inline std::string extract_element_type(const std::string& generic_type, const s
return "";
}

/**
* Extracts both key and value types from a Map<K, V> type string.
*
* For example:
* - extract_map_types("Map<str, int>") returns {"str", "int"}
* - extract_map_types("Map<str, List<int>>") returns {"str", "List<int>"}
*
* @param map_type The full Map type string (e.g., "Map<str, int>")
* @return A pair of {key_type, value_type}, or {"", ""} if invalid
*/
inline std::pair<std::string, std::string> extract_map_types(const std::string& map_type) {
const std::string prefix = "Map<";

if (map_type.empty() || map_type.rfind(prefix, 0) != 0 || map_type.back() != '>') {
return {"", ""};
}

size_t start = prefix.length();
int depth = 1;
size_t comma_pos = std::string::npos;

// Find the comma at depth 1 that separates K and V
for (size_t i = start; i < map_type.size(); i++) {
if (map_type[i] == '<') {
depth++;
} else if (map_type[i] == '>') {
depth--;

if (depth == 0) {
// Found the end, extract both types
if (comma_pos == std::string::npos) {
return {"", ""}; // No comma found
}

std::string key_type = map_type.substr(start, comma_pos - start);
std::string value_type = map_type.substr(comma_pos + 2, i - comma_pos - 2); // +2 to skip ", "
return {key_type, value_type};
}
} else if (map_type[i] == ',' && depth == 1 && comma_pos == std::string::npos) {
comma_pos = i;
}
}

return {"", ""};
}

} // namespace bishop
1 change: 1 addition & 0 deletions lexer/lexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ static unordered_map<string, TokenType> keywords = {
{"private", TokenType::PRIVATE},
{"Channel", TokenType::CHANNEL},
{"List", TokenType::LIST},
{"Map", TokenType::MAP},
{"Pair", TokenType::PAIR},
{"Tuple", TokenType::TUPLE},
{"Set", TokenType::SET},
Expand Down
1 change: 1 addition & 0 deletions lexer/token.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ enum class TokenType {
PRIVATE,
CHANNEL,
LIST,
MAP,
PAIR,
TUPLE,
SET,
Expand Down
Loading