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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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::<Vec<Value>>(&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::<Vec<Value>, Value>(&args, "List.sortBy", span)?;
|
||||
let mut indexed: Vec<(usize, Value)> =
|
||||
list.into_iter().enumerate().collect();
|
||||
let mut err: Option<RuntimeError> = 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<Value> =
|
||||
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::<String, String>(&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<Vec<(String, Value)>> {
|
||||
match pattern {
|
||||
Pattern::Wildcard(_) => Some(Vec::new()),
|
||||
|
||||
20
src/types.rs
20
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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user