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