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

@@ -1076,6 +1076,23 @@ impl JsBackend {
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 let Expr::Field { object, field, .. } = func.as_ref() {
if let Expr::Var(module_name) = object.as_ref() {
if module_name.name == "Int" && field.name == "toFloat" {
let arg = self.emit_expr(&args[0])?;
return Ok(arg); // JS numbers are already floats
}
if module_name.name == "Float" && field.name == "toInt" {
let arg = self.emit_expr(&args[0])?;
return Ok(format!("Math.trunc({})", arg));
}
}
}
@@ -1165,6 +1182,18 @@ impl JsBackend {
return self.emit_math_operation(&operation.name, args);
}
// Special case: Int module operations
if effect.name == "Int" && operation.name == "toFloat" {
let arg = self.emit_expr(&args[0])?;
return Ok(arg); // JS numbers are already floats
}
// Special case: Float module operations
if effect.name == "Float" && operation.name == "toInt" {
let arg = self.emit_expr(&args[0])?;
return Ok(format!("Math.trunc({})", arg));
}
// Special case: Result module operations (not an effect)
if effect.name == "Result" {
return self.emit_result_operation(&operation.name, args);
@@ -1175,6 +1204,11 @@ impl JsBackend {
return self.emit_json_operation(&operation.name, args);
}
// Special case: Map module operations (not an effect)
if effect.name == "Map" {
return self.emit_map_operation(&operation.name, args);
}
// Special case: Html module operations (not an effect)
if effect.name == "Html" {
return self.emit_html_operation(&operation.name, args);
@@ -2120,6 +2154,86 @@ impl JsBackend {
}
}
/// Emit Map module operations using JS Map
fn emit_map_operation(
&mut self,
operation: &str,
args: &[Expr],
) -> Result<String, JsGenError> {
match operation {
"new" => Ok("new Map()".to_string()),
"set" => {
let map = self.emit_expr(&args[0])?;
let key = self.emit_expr(&args[1])?;
let val = self.emit_expr(&args[2])?;
Ok(format!(
"(function() {{ var m = new Map({}); m.set({}, {}); return m; }})()",
map, key, val
))
}
"get" => {
let map = self.emit_expr(&args[0])?;
let key = self.emit_expr(&args[1])?;
Ok(format!(
"({0}.has({1}) ? Lux.Some({0}.get({1})) : Lux.None())",
map, key
))
}
"contains" => {
let map = self.emit_expr(&args[0])?;
let key = self.emit_expr(&args[1])?;
Ok(format!("{}.has({})", map, key))
}
"remove" => {
let map = self.emit_expr(&args[0])?;
let key = self.emit_expr(&args[1])?;
Ok(format!(
"(function() {{ var m = new Map({}); m.delete({}); return m; }})()",
map, key
))
}
"keys" => {
let map = self.emit_expr(&args[0])?;
Ok(format!("Array.from({}.keys()).sort()", map))
}
"values" => {
let map = self.emit_expr(&args[0])?;
Ok(format!(
"Array.from({0}.entries()).sort(function(a,b) {{ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0; }}).map(function(e) {{ return e[1]; }})",
map
))
}
"size" => {
let map = self.emit_expr(&args[0])?;
Ok(format!("{}.size", map))
}
"isEmpty" => {
let map = self.emit_expr(&args[0])?;
Ok(format!("({}.size === 0)", map))
}
"fromList" => {
let list = self.emit_expr(&args[0])?;
Ok(format!("new Map({}.map(function(t) {{ return [t[0], t[1]]; }}))", list))
}
"toList" => {
let map = self.emit_expr(&args[0])?;
Ok(format!(
"Array.from({}.entries()).sort(function(a,b) {{ return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0; }})",
map
))
}
"merge" => {
let m1 = self.emit_expr(&args[0])?;
let m2 = self.emit_expr(&args[1])?;
Ok(format!("new Map([...{}, ...{}])", m1, m2))
}
_ => Err(JsGenError {
message: format!("Unknown Map operation: {}", operation),
span: None,
}),
}
}
/// Emit Html module operations for type-safe HTML construction
fn emit_html_operation(
&mut self,