From 091ff1e4228888aa1d1a365e9dcd32ebb0815827 Mon Sep 17 00:00:00 2001 From: Brandon Lucas Date: Fri, 20 Feb 2026 10:02:21 -0500 Subject: [PATCH] feat: add List.sort and List.sortBy functions (issue 9) Add sorting support to the List module across all backends: - List.sort for natural ordering (Int, Float, String, Bool, Char) - List.sortBy for custom comparator-based sorting Co-Authored-By: Claude Opus 4.6 --- src/codegen/c_backend.rs | 114 +++++++++++++++++++++++++++++++++++++- src/codegen/js_backend.rs | 12 ++++ src/interpreter.rs | 80 ++++++++++++++++++++++++++ src/types.rs | 20 +++++++ 4 files changed, 225 insertions(+), 1 deletion(-) diff --git a/src/codegen/c_backend.rs b/src/codegen/c_backend.rs index 920b8a8..afe63b8 100644 --- a/src/codegen/c_backend.rs +++ b/src/codegen/c_backend.rs @@ -2087,6 +2087,42 @@ impl CBackend { self.writeln(" return result;"); self.writeln("}"); self.writeln(""); + // Sort helper: compare two void* as boxed ints + self.writeln("static int lux_compare_int(const void* a, const void* b) {"); + self.writeln(" LuxInt va = *(LuxInt*)(*(void**)a);"); + self.writeln(" LuxInt vb = *(LuxInt*)(*(void**)b);"); + self.writeln(" return (va > vb) - (va < vb);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static int lux_compare_string(const void* a, const void* b) {"); + self.writeln(" LuxString sa = (LuxString)(*(void**)a);"); + self.writeln(" LuxString sb = (LuxString)(*(void**)b);"); + self.writeln(" return strcmp(sa, sb);"); + self.writeln("}"); + self.writeln(""); + self.writeln("static LuxList* lux_list_sort(LuxList* list) {"); + self.writeln(" if (list->length <= 1) {"); + self.writeln(" lux_incref(list);"); + self.writeln(" return list;"); + self.writeln(" }"); + self.writeln(" LuxList* result = lux_list_new(list->length);"); + self.writeln(" for (int64_t i = 0; i < list->length; i++) {"); + self.writeln(" lux_incref(list->elements[i]);"); + self.writeln(" result->elements[i] = list->elements[i];"); + self.writeln(" }"); + self.writeln(" result->length = list->length;"); + self.writeln(" // Determine element type from first element and sort"); + self.writeln(" if (result->length > 0 && result->elements[0] != NULL) {"); + self.writeln(" uint32_t tag = LUX_RC_HEADER(result->elements[0])->tag;"); + self.writeln(" if (tag == LUX_TAG_BOXED_INT) {"); + self.writeln(" qsort(result->elements, result->length, sizeof(void*), lux_compare_int);"); + self.writeln(" } else if (tag == LUX_TAG_STRING) {"); + self.writeln(" qsort(result->elements, result->length, sizeof(void*), lux_compare_string);"); + self.writeln(" }"); + self.writeln(" }"); + 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));"); @@ -4679,6 +4715,82 @@ impl CBackend { Ok(result_var) } + "sort" => { + if args.len() != 1 { + return Err(CGenError { + message: "List.sort takes 1 argument".to_string(), + span: None, + }); + } + let list = self.emit_expr(&args[0])?; + let result_var = format!("_sorted_{}", self.fresh_name()); + self.writeln(&format!( + "LuxList* {} = lux_list_sort({});", + result_var, list + )); + self.register_rc_var(&result_var, "LuxList*"); + Ok(result_var) + } + "sortBy" => { + if args.len() != 2 { + return Err(CGenError { + message: "List.sortBy takes 2 arguments".to_string(), + span: None, + }); + } + let list = self.emit_expr(&args[0])?; + let closure = self.emit_expr(&args[1])?; + let result_var = format!("_sorted_{}", self.fresh_name()); + // Copy the list then do insertion sort with custom comparator + self.writeln(&format!( + "LuxList* {} = lux_list_new({}->length);", + result_var, list + )); + self.writeln(&format!( + "for (int64_t _i = 0; _i < {}->length; _i++) {{", + list + )); + self.writeln(&format!( + " lux_incref({}->elements[_i]);", + list + )); + self.writeln(&format!( + " {}->elements[_i] = {}->elements[_i];", + result_var, list + )); + self.writeln("}"); + self.writeln(&format!( + "{}->length = {}->length;", + result_var, list + )); + // Insertion sort using the comparator closure + self.writeln(&format!( + "for (int64_t _i = 1; _i < {}->length; _i++) {{", + result_var + )); + self.writeln(&format!( + " void* _key = {}->elements[_i];", + result_var + )); + self.writeln(" int64_t _j = _i - 1;"); + self.writeln(&format!( + " while (_j >= 0 && *(LuxInt*){}.fn({}.env, {}->elements[_j], _key) > 0) {{", + closure, closure, result_var + )); + self.writeln(&format!( + " {}->elements[_j + 1] = {}->elements[_j];", + result_var, result_var + )); + self.writeln(" _j--;"); + self.writeln(" }"); + self.writeln(&format!( + " {}->elements[_j + 1] = _key;", + result_var + )); + self.writeln("}"); + self.register_rc_var(&result_var, "LuxList*"); + Ok(result_var) + } _ => Err(CGenError { message: format!("Unsupported List operation: {}", op), span: None, @@ -5206,7 +5318,7 @@ impl CBackend { if effect.name == "List" { match operation.name.as_str() { // Operations returning lists - "map" | "filter" | "concat" | "reverse" | "take" | "drop" | "range" => Some("LuxList*".to_string()), + "map" | "filter" | "concat" | "reverse" | "take" | "drop" | "range" | "sort" | "sortBy" => Some("LuxList*".to_string()), // Operations returning Option "head" | "tail" | "get" | "find" => Some("Option".to_string()), // Operations returning Int diff --git a/src/codegen/js_backend.rs b/src/codegen/js_backend.rs index 4df8ca8..6330a6d 100644 --- a/src/codegen/js_backend.rs +++ b/src/codegen/js_backend.rs @@ -1812,6 +1812,18 @@ impl JsBackend { end, start, start )) } + "sort" => { + let list = self.emit_expr(&args[0])?; + Ok(format!( + "[...{}].sort((a, b) => a < b ? -1 : a > b ? 1 : 0)", + list + )) + } + "sortBy" => { + let list = self.emit_expr(&args[0])?; + let func = self.emit_expr(&args[1])?; + Ok(format!("[...{}].sort({})", list, func)) + } _ => Err(JsGenError { message: format!("Unknown List operation: {}", operation), span: None, diff --git a/src/interpreter.rs b/src/interpreter.rs index 0d9bf30..55f9d8b 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -28,6 +28,8 @@ pub enum BuiltinFn { ListGet, ListRange, ListForEach, + ListSort, + ListSortBy, // String operations StringSplit, @@ -980,6 +982,11 @@ impl Interpreter { "forEach".to_string(), Value::Builtin(BuiltinFn::ListForEach), ), + ("sort".to_string(), Value::Builtin(BuiltinFn::ListSort)), + ( + "sortBy".to_string(), + Value::Builtin(BuiltinFn::ListSortBy), + ), ])); env.define("List", list_module); @@ -2742,6 +2749,67 @@ impl Interpreter { Ok(EvalResult::Value(Value::Unit)) } + BuiltinFn::ListSort => { + // List.sort(list) - sort using natural ordering (Int, Float, String, Bool) + let mut list = + Self::expect_arg_1::>(&args, "List.sort", span)?; + list.sort_by(|a, b| Self::compare_values(a, b)); + Ok(EvalResult::Value(Value::List(list))) + } + + BuiltinFn::ListSortBy => { + // List.sortBy(list, fn(a, b) => Int) - sort with custom comparator + // Comparator returns negative (a < b), 0 (a == b), or positive (a > b) + let (list, func) = + Self::expect_args_2::, Value>(&args, "List.sortBy", span)?; + let mut indexed: Vec<(usize, Value)> = + list.into_iter().enumerate().collect(); + let mut err: Option = None; + let func_ref = &func; + let self_ptr = self as *mut Self; + indexed.sort_by(|a, b| { + if err.is_some() { + return std::cmp::Ordering::Equal; + } + // Safety: we're in a single-threaded context and the closure + // needs mutable access to call eval_call_to_value + let interp = unsafe { &mut *self_ptr }; + match interp.eval_call_to_value( + func_ref.clone(), + vec![a.1.clone(), b.1.clone()], + span, + ) { + Ok(Value::Int(n)) => { + if n < 0 { + std::cmp::Ordering::Less + } else if n > 0 { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Equal + } + } + Ok(_) => { + err = Some(RuntimeError { + message: "List.sortBy comparator must return Int" + .to_string(), + span: Some(span), + }); + std::cmp::Ordering::Equal + } + Err(e) => { + err = Some(e); + std::cmp::Ordering::Equal + } + } + }); + if let Some(e) = err { + return Err(e); + } + let result: Vec = + indexed.into_iter().map(|(_, v)| v).collect(); + Ok(EvalResult::Value(Value::List(result))) + } + // Additional String operations BuiltinFn::StringStartsWith => { let (s, prefix) = Self::expect_args_2::(&args, "String.startsWith", span)?; @@ -3357,6 +3425,18 @@ impl Interpreter { }) } + /// Compare two values for natural ordering (used by List.sort) + fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering { + match (a, b) { + (Value::Int(x), Value::Int(y)) => x.cmp(y), + (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal), + (Value::String(x), Value::String(y)) => x.cmp(y), + (Value::Bool(x), Value::Bool(y)) => x.cmp(y), + (Value::Char(x), Value::Char(y)) => x.cmp(y), + _ => std::cmp::Ordering::Equal, + } + } + fn match_pattern(&self, pattern: &Pattern, value: &Value) -> Option> { match pattern { Pattern::Wildcard(_) => Some(Vec::new()), diff --git a/src/types.rs b/src/types.rs index 4059a52..ba1f85e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1551,6 +1551,26 @@ impl TypeEnv { Type::Unit, ), ), + ( + "sort".to_string(), + Type::function( + vec![Type::List(Box::new(Type::var()))], + Type::List(Box::new(Type::var())), + ), + ), + ( + "sortBy".to_string(), + { + let elem = Type::var(); + Type::function( + vec![ + Type::List(Box::new(elem.clone())), + Type::function(vec![elem.clone(), elem], Type::Int), + ], + Type::List(Box::new(Type::var())), + ) + }, + ), ]); env.bind("List", TypeScheme::mono(list_module_type));