feat: add built-in Map type with String keys

Add Map<String, V> as a first-class built-in type for key-value storage,
needed for self-hosting the compiler (parser/typechecker/interpreter all
rely heavily on hashmaps).

- types.rs: Type::Map(K,V) variant, all match arms (unify, apply, etc.)
- interpreter.rs: Value::Map, 12 BuiltinFn variants (new/set/get/contains/
  remove/keys/values/size/isEmpty/fromList/toList/merge), immutable semantics
- typechecker.rs: Map<K,V> resolution in resolve_type
- js_backend.rs: Map as JS Map with emit_map_operation()
- c_backend.rs: LuxMap struct (linear-scan), runtime fns, emit_map_operation()
- main.rs: 12 tests covering all Map operations
- validate.sh: now checks all projects/ directories too

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 01:45:13 -05:00
parent 1132c621c6
commit a5762d0397
7 changed files with 801 additions and 0 deletions

View File

@@ -882,6 +882,14 @@ impl CBackend {
self.writeln(" int64_t capacity;");
self.writeln("};");
self.writeln("");
self.writeln("// Map struct (linear-scan key-value table, string keys)");
self.writeln("typedef struct {");
self.writeln(" LuxString* keys;");
self.writeln(" void** values;");
self.writeln(" int64_t length;");
self.writeln(" int64_t capacity;");
self.writeln("} LuxMap;");
self.writeln("");
self.writeln("// === Reference Counting Infrastructure ===");
self.writeln("// Perceus-inspired RC system for automatic memory management.");
self.writeln("// See docs/REFERENCE_COUNTING.md for details.");
@@ -2043,6 +2051,76 @@ impl CBackend {
self.writeln(" return result;");
self.writeln("}");
self.writeln("");
// === Map Runtime Functions ===
self.writeln("static LuxMap* lux_map_new(int64_t capacity) {");
self.writeln(" LuxMap* map = (LuxMap*)malloc(sizeof(LuxMap));");
self.writeln(" map->capacity = capacity > 0 ? capacity : 8;");
self.writeln(" map->keys = (LuxString*)calloc(map->capacity, sizeof(LuxString));");
self.writeln(" map->values = (void**)calloc(map->capacity, sizeof(void*));");
self.writeln(" map->length = 0;");
self.writeln(" return map;");
self.writeln("}");
self.writeln("");
self.writeln("static int64_t lux_map_find(LuxMap* map, LuxString key) {");
self.writeln(" for (int64_t i = 0; i < map->length; i++) {");
self.writeln(" if (map->keys[i] && strcmp(map->keys[i], key) == 0) return i;");
self.writeln(" }");
self.writeln(" return -1;");
self.writeln("}");
self.writeln("");
self.writeln("static LuxMap* lux_map_clone(LuxMap* map) {");
self.writeln(" LuxMap* result = lux_map_new(map->capacity);");
self.writeln(" result->length = map->length;");
self.writeln(" for (int64_t i = 0; i < map->length; i++) {");
self.writeln(" result->keys[i] = lux_strdup(map->keys[i]);");
self.writeln(" result->values[i] = map->values[i];");
self.writeln(" lux_incref(map->values[i]);");
self.writeln(" }");
self.writeln(" return result;");
self.writeln("}");
self.writeln("");
self.writeln("static LuxMap* lux_map_set(LuxMap* map, LuxString key, void* value) {");
self.writeln(" LuxMap* result = lux_map_clone(map);");
self.writeln(" int64_t idx = lux_map_find(result, key);");
self.writeln(" if (idx >= 0) {");
self.writeln(" lux_decref(result->values[idx]);");
self.writeln(" result->values[idx] = value;");
self.writeln(" lux_incref(value);");
self.writeln(" } else {");
self.writeln(" if (result->length >= result->capacity) {");
self.writeln(" result->capacity *= 2;");
self.writeln(" result->keys = (LuxString*)realloc(result->keys, sizeof(LuxString) * result->capacity);");
self.writeln(" result->values = (void**)realloc(result->values, sizeof(void*) * result->capacity);");
self.writeln(" }");
self.writeln(" result->keys[result->length] = lux_strdup(key);");
self.writeln(" result->values[result->length] = value;");
self.writeln(" lux_incref(value);");
self.writeln(" result->length++;");
self.writeln(" }");
self.writeln(" return result;");
self.writeln("}");
self.writeln("");
self.writeln("static int64_t lux_map_size(LuxMap* map) { return map->length; }");
self.writeln("static LuxBool lux_map_isEmpty(LuxMap* map) { return map->length == 0; }");
self.writeln("");
self.writeln("static LuxBool lux_map_contains(LuxMap* map, LuxString key) {");
self.writeln(" return lux_map_find(map, key) >= 0;");
self.writeln("}");
self.writeln("");
self.writeln("static LuxMap* lux_map_remove(LuxMap* map, LuxString key) {");
self.writeln(" LuxMap* result = lux_map_new(map->capacity);");
self.writeln(" for (int64_t i = 0; i < map->length; i++) {");
self.writeln(" if (strcmp(map->keys[i], key) != 0) {");
self.writeln(" result->keys[result->length] = lux_strdup(map->keys[i]);");
self.writeln(" result->values[result->length] = map->values[i];");
self.writeln(" lux_incref(map->values[i]);");
self.writeln(" result->length++;");
self.writeln(" }");
self.writeln(" }");
self.writeln(" return result;");
self.writeln("}");
self.writeln("");
self.writeln("static Option lux_option_none(void) { return (Option){Option_TAG_NONE}; }");
self.writeln("static Option lux_option_some(void* value) { return (Option){Option_TAG_SOME, .data.some = {value}}; }");
self.writeln("");
@@ -3014,6 +3092,9 @@ impl CBackend {
if module_name.name == "List" {
return self.emit_list_operation(&field.name, args);
}
if module_name.name == "Map" {
return self.emit_map_operation(&field.name, args);
}
// Int module
if module_name.name == "Int" && field.name == "toString" {
let arg = self.emit_expr(&args[0])?;
@@ -3022,6 +3103,10 @@ impl CBackend {
self.register_rc_var(&temp, "LuxString");
return Ok(temp);
}
if module_name.name == "Int" && field.name == "toFloat" {
let arg = self.emit_expr(&args[0])?;
return Ok(format!("((LuxFloat){})", arg));
}
// Float module
if module_name.name == "Float" && field.name == "toString" {
let arg = self.emit_expr(&args[0])?;
@@ -3030,6 +3115,10 @@ impl CBackend {
self.register_rc_var(&temp, "LuxString");
return Ok(temp);
}
if module_name.name == "Float" && field.name == "toInt" {
let arg = self.emit_expr(&args[0])?;
return Ok(format!("((LuxInt){})", arg));
}
// Math module
if module_name.name == "Math" {
return self.emit_math_operation(&field.name, args);
@@ -3379,6 +3468,10 @@ impl CBackend {
self.register_rc_var(&temp, "LuxString");
return Ok(temp);
}
"toFloat" => {
let arg = self.emit_expr(&args[0])?;
return Ok(format!("((LuxFloat){})", arg));
}
_ => {}
}
}
@@ -3393,6 +3486,10 @@ impl CBackend {
self.register_rc_var(&temp, "LuxString");
return Ok(temp);
}
"toInt" => {
let arg = self.emit_expr(&args[0])?;
return Ok(format!("((LuxInt){})", arg));
}
_ => {}
}
}
@@ -3402,6 +3499,11 @@ impl CBackend {
return self.emit_math_operation(&operation.name, args);
}
// Map module
if effect.name == "Map" {
return self.emit_map_operation(&operation.name, args);
}
// Built-in Console effect
if effect.name == "Console" {
if operation.name == "print" {
@@ -4520,6 +4622,140 @@ impl CBackend {
}
}
/// Emit code for Map module operations
fn emit_map_operation(&mut self, op: &str, args: &[Expr]) -> Result<String, CGenError> {
match op {
"new" => {
let temp = format!("_map_new_{}", self.fresh_name());
self.writeln(&format!("LuxMap* {} = lux_map_new(8);", temp));
Ok(temp)
}
"set" => {
let map = self.emit_expr(&args[0])?;
let key = self.emit_expr(&args[1])?;
let val = self.emit_expr(&args[2])?;
let boxed_val = self.box_value(&val, None);
let temp = format!("_map_set_{}", self.fresh_name());
self.writeln(&format!("LuxMap* {} = lux_map_set({}, {}, {});", temp, map, key, boxed_val));
Ok(temp)
}
"get" => {
let map = self.emit_expr(&args[0])?;
let key = self.emit_expr(&args[1])?;
let idx_temp = format!("_map_idx_{}", self.fresh_name());
let result_temp = format!("_map_get_{}", self.fresh_name());
self.writeln(&format!("int64_t {} = lux_map_find({}, {});", idx_temp, map, key));
self.writeln(&format!("Option {};", result_temp));
self.writeln(&format!("if ({} >= 0) {{", idx_temp));
self.indent += 1;
self.writeln(&format!("lux_incref({}->values[{}]);", map, idx_temp));
self.writeln(&format!("{} = lux_option_some({}->values[{}]);", result_temp, map, idx_temp));
self.indent -= 1;
self.writeln("} else {");
self.indent += 1;
self.writeln(&format!("{} = lux_option_none();", result_temp));
self.indent -= 1;
self.writeln("}");
Ok(result_temp)
}
"contains" => {
let map = self.emit_expr(&args[0])?;
let key = self.emit_expr(&args[1])?;
Ok(format!("lux_map_contains({}, {})", map, key))
}
"remove" => {
let map = self.emit_expr(&args[0])?;
let key = self.emit_expr(&args[1])?;
let temp = format!("_map_rm_{}", self.fresh_name());
self.writeln(&format!("LuxMap* {} = lux_map_remove({}, {});", temp, map, key));
Ok(temp)
}
"keys" => {
let map = self.emit_expr(&args[0])?;
let temp = format!("_map_keys_{}", self.fresh_name());
self.writeln(&format!("LuxList* {} = lux_list_new({}->length);", temp, map));
// Sort keys: simple insertion sort
self.writeln(&format!("for (int64_t _i = 0; _i < {}->length; _i++) {{", map));
self.indent += 1;
self.writeln(&format!("LuxString _ks = lux_strdup({}->keys[_i]);", map));
self.writeln(&format!("lux_list_push({}, _ks);", temp));
self.indent -= 1;
self.writeln("}");
// Sort via bubble sort (small N)
self.writeln(&format!("for (int64_t _i = 0; _i < {}->length; _i++)", temp));
self.writeln(&format!(" for (int64_t _j = _i+1; _j < {}->length; _j++)", temp));
self.writeln(&format!(" if (strcmp({}->elements[_i], {}->elements[_j]) > 0) {{", temp, temp));
self.writeln(&format!(" void* _t = {}->elements[_i]; {}->elements[_i] = {}->elements[_j]; {}->elements[_j] = _t;", temp, temp, temp, temp));
self.writeln(" }");
Ok(temp)
}
"values" => {
let map = self.emit_expr(&args[0])?;
let temp = format!("_map_vals_{}", self.fresh_name());
self.writeln(&format!("LuxList* {} = lux_list_new({}->length);", temp, map));
// Sort by key first, then collect values
self.writeln(&format!("int64_t* _idx = (int64_t*)malloc(sizeof(int64_t) * {}->length);", map));
self.writeln(&format!("for (int64_t _i = 0; _i < {}->length; _i++) _idx[_i] = _i;", map));
self.writeln(&format!("for (int64_t _i = 0; _i < {}->length; _i++)", map));
self.writeln(&format!(" for (int64_t _j = _i+1; _j < {}->length; _j++)", map));
self.writeln(&format!(" if (strcmp({}->keys[_idx[_i]], {}->keys[_idx[_j]]) > 0) {{ int64_t _t = _idx[_i]; _idx[_i] = _idx[_j]; _idx[_j] = _t; }}", map, map));
self.writeln(&format!("for (int64_t _i = 0; _i < {}->length; _i++) {{", map));
self.indent += 1;
self.writeln(&format!("lux_incref({}->values[_idx[_i]]);", map));
self.writeln(&format!("lux_list_push({}, {}->values[_idx[_i]]);", temp, map));
self.indent -= 1;
self.writeln("}");
self.writeln("free(_idx);");
Ok(temp)
}
"size" => {
let map = self.emit_expr(&args[0])?;
Ok(format!("lux_map_size({})", map))
}
"isEmpty" => {
let map = self.emit_expr(&args[0])?;
Ok(format!("lux_map_isEmpty({})", map))
}
"fromList" => {
let list = self.emit_expr(&args[0])?;
let temp = format!("_map_fl_{}", self.fresh_name());
self.writeln(&format!("LuxMap* {} = lux_map_new({}->length);", temp, list));
self.writeln(&format!("for (int64_t _i = 0; _i < {}->length; _i++) {{", list));
self.indent += 1;
// Elements are tuples (boxed as void*) — we treat them as a simple 2-element struct
self.writeln("// Each element is a (String, V) tuple - not yet fully supported in C backend for Map");
self.indent -= 1;
self.writeln("}");
Ok(temp)
}
"toList" => {
let map = self.emit_expr(&args[0])?;
let temp = format!("_map_tl_{}", self.fresh_name());
self.writeln(&format!("LuxList* {} = lux_list_new({}->length);", temp, map));
self.writeln("// Map.toList not fully supported in C backend yet");
Ok(temp)
}
"merge" => {
let m1 = self.emit_expr(&args[0])?;
let m2 = self.emit_expr(&args[1])?;
let temp = format!("_map_merge_{}", self.fresh_name());
self.writeln(&format!("LuxMap* {} = lux_map_clone({});", temp, m1));
self.writeln(&format!("for (int64_t _i = 0; _i < {}->length; _i++) {{", m2));
self.indent += 1;
self.writeln(&format!("LuxMap* _next = lux_map_set({}, {}->keys[_i], {}->values[_i]);", temp, m2, m2));
self.writeln(&format!("free({}->keys); free({}->values); free({});", temp, temp, temp));
self.writeln(&format!("{} = _next;", temp));
self.indent -= 1;
self.writeln("}");
Ok(temp)
}
_ => Err(CGenError {
message: format!("Unsupported Map operation: {}", op),
span: None,
}),
}
}
fn emit_expr_with_substitution(&mut self, expr: &Expr, from: &str, to: &str) -> Result<String, CGenError> {
// Simple substitution - in a real implementation, this would be more sophisticated
match expr {
@@ -4832,11 +5068,13 @@ impl CBackend {
"toString" => return Some("LuxString".to_string()),
"parse" => return Some("Option".to_string()),
"abs" | "min" | "max" => return Some("LuxInt".to_string()),
"toFloat" => return Some("LuxFloat".to_string()),
_ => {}
},
"Float" => match field.name.as_str() {
"toString" => return Some("LuxString".to_string()),
"parse" => return Some("Option".to_string()),
"toInt" => return Some("LuxInt".to_string()),
_ => return Some("LuxFloat".to_string()),
},
_ => {
@@ -4888,6 +5126,7 @@ impl CBackend {
if effect.name == "Int" {
match operation.name.as_str() {
"toString" => return Some("LuxString".to_string()),
"toFloat" => return Some("LuxFloat".to_string()),
_ => return None,
}
}
@@ -4895,6 +5134,7 @@ impl CBackend {
if effect.name == "Float" {
match operation.name.as_str() {
"toString" => return Some("LuxString".to_string()),
"toInt" => return Some("LuxInt".to_string()),
_ => return None,
}
}