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:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user