Skip to main content

runmat_runtime/builtins/structs/core/
orderfields.rs

1//! MATLAB-compatible `orderfields` builtin.
2
3use crate::builtins::common::spec::{
4    BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
5    ReductionNaN, ResidencyPolicy, ShapeRequirements,
6};
7use crate::builtins::common::tensor;
8use crate::builtins::structs::type_resolvers::orderfields_type;
9
10use runmat_builtins::{
11    BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
12    BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
13    CellArray, StructValue, Tensor, Value,
14};
15use runmat_macros::runtime_builtin;
16use std::cmp::Ordering;
17use std::collections::{HashMap, HashSet};
18
19use crate::{build_runtime_error, BuiltinResult, RuntimeError};
20
21#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::structs::core::orderfields")]
22pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
23    name: "orderfields",
24    op_kind: GpuOpKind::Custom("orderfields"),
25    supported_precisions: &[],
26    broadcast: BroadcastSemantics::None,
27    provider_hooks: &[],
28    constant_strategy: ConstantStrategy::InlineLiteral,
29    residency: ResidencyPolicy::InheritInputs,
30    nan_mode: ReductionNaN::Include,
31    two_pass_threshold: None,
32    workgroup_size: None,
33    accepts_nan_mode: false,
34    notes: "Host-only metadata manipulation; struct values that live on the GPU remain resident.",
35};
36
37#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::structs::core::orderfields")]
38pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
39    name: "orderfields",
40    shape: ShapeRequirements::Any,
41    constant_strategy: ConstantStrategy::InlineLiteral,
42    elementwise: None,
43    reduction: None,
44    emits_nan: false,
45    notes: "Reordering fields is a metadata operation and does not participate in fusion planning.",
46};
47
48const BUILTIN_NAME: &str = "orderfields";
49
50const ORDERFIELDS_OUTPUT_ORDERED: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
51    name: "S",
52    ty: BuiltinParamType::Any,
53    arity: BuiltinParamArity::Required,
54    default: None,
55    description: "Struct or struct array with reordered fields.",
56}];
57
58const ORDERFIELDS_OUTPUT_PERM: [BuiltinParamDescriptor; 2] = [
59    BuiltinParamDescriptor {
60        name: "S",
61        ty: BuiltinParamType::Any,
62        arity: BuiltinParamArity::Required,
63        default: None,
64        description: "Struct or struct array with reordered fields.",
65    },
66    BuiltinParamDescriptor {
67        name: "P",
68        ty: BuiltinParamType::NumericArray,
69        arity: BuiltinParamArity::Required,
70        default: None,
71        description: "Permutation vector mapping ordered fields to original positions.",
72    },
73];
74
75const ORDERFIELDS_INPUTS_ONE: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
76    name: "S",
77    ty: BuiltinParamType::Any,
78    arity: BuiltinParamArity::Required,
79    default: None,
80    description: "Input struct or struct array.",
81}];
82
83const ORDERFIELDS_INPUTS_TWO: [BuiltinParamDescriptor; 2] = [
84    BuiltinParamDescriptor {
85        name: "S",
86        ty: BuiltinParamType::Any,
87        arity: BuiltinParamArity::Required,
88        default: None,
89        description: "Input struct or struct array.",
90    },
91    BuiltinParamDescriptor {
92        name: "order",
93        ty: BuiltinParamType::Any,
94        arity: BuiltinParamArity::Optional,
95        default: None,
96        description: "Reference struct, field-name list, or permutation vector.",
97    },
98];
99
100const ORDERFIELDS_SIGNATURES: [BuiltinSignatureDescriptor; 4] = [
101    BuiltinSignatureDescriptor {
102        label: "S = orderfields(S)",
103        inputs: &ORDERFIELDS_INPUTS_ONE,
104        outputs: &ORDERFIELDS_OUTPUT_ORDERED,
105    },
106    BuiltinSignatureDescriptor {
107        label: "S = orderfields(S, order)",
108        inputs: &ORDERFIELDS_INPUTS_TWO,
109        outputs: &ORDERFIELDS_OUTPUT_ORDERED,
110    },
111    BuiltinSignatureDescriptor {
112        label: "[S,P] = orderfields(S)",
113        inputs: &ORDERFIELDS_INPUTS_ONE,
114        outputs: &ORDERFIELDS_OUTPUT_PERM,
115    },
116    BuiltinSignatureDescriptor {
117        label: "[S,P] = orderfields(S, order)",
118        inputs: &ORDERFIELDS_INPUTS_TWO,
119        outputs: &ORDERFIELDS_OUTPUT_PERM,
120    },
121];
122
123const ORDERFIELDS_ERROR_TOO_MANY_INPUTS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
124    code: "RM.ORDERFIELDS.TOO_MANY_INPUTS",
125    identifier: Some("orderfields:TooManyInputs"),
126    when: "More than two input arguments are supplied.",
127    message: "orderfields: expected at most two input arguments",
128};
129
130const ORDERFIELDS_ERROR_INVALID_INPUT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
131    code: "RM.ORDERFIELDS.INVALID_INPUT",
132    identifier: Some("orderfields:InvalidInput"),
133    when: "First argument is not a struct or struct array.",
134    message: "orderfields: first argument must be a struct or struct array",
135};
136
137const ORDERFIELDS_ERROR_EMPTY_STRUCT_ARRAY: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
138    code: "RM.ORDERFIELDS.EMPTY_STRUCT_ARRAY",
139    identifier: Some("orderfields:EmptyStructArray"),
140    when: "Empty struct array is asked to adopt a non-empty reference order.",
141    message: "orderfields: empty struct arrays cannot adopt a non-empty reference order",
142};
143
144const ORDERFIELDS_ERROR_NO_FIELDS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
145    code: "RM.ORDERFIELDS.NO_FIELDS",
146    identifier: Some("orderfields:NoFields"),
147    when: "Ordering is requested for a struct array with no fields.",
148    message: "orderfields: struct array has no fields to reorder",
149};
150
151const ORDERFIELDS_ERROR_INVALID_STRUCT_ARRAY: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
152    code: "RM.ORDERFIELDS.INVALID_STRUCT_ARRAY",
153    identifier: Some("orderfields:InvalidStructArray"),
154    when: "A struct-array element is not a struct.",
155    message: "orderfields: struct array element is not a struct",
156};
157
158const ORDERFIELDS_ERROR_INVALID_STRUCT_CONTENTS: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
159    code: "RM.ORDERFIELDS.INVALID_STRUCT_CONTENTS",
160    identifier: Some("orderfields:InvalidStructContents"),
161    when: "Struct-array contents are not all structs.",
162    message: "orderfields: expected struct array contents to be structs",
163};
164
165const ORDERFIELDS_ERROR_REBUILD_FAILED: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
166    code: "RM.ORDERFIELDS.REBUILD_FAILED",
167    identifier: Some("orderfields:RebuildFailed"),
168    when: "Rebuilding reordered struct array failed.",
169    message: "orderfields: failed to rebuild struct array",
170};
171
172const ORDERFIELDS_ERROR_INVALID_REFERENCE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
173    code: "RM.ORDERFIELDS.INVALID_REFERENCE",
174    identifier: Some("orderfields:InvalidReference"),
175    when: "Reference struct-array contains non-struct values.",
176    message: "orderfields: reference struct array element is not a struct",
177};
178
179const ORDERFIELDS_ERROR_INVALID_NAME_LIST: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
180    code: "RM.ORDERFIELDS.INVALID_NAME_LIST",
181    identifier: Some("orderfields:InvalidFieldNameList"),
182    when: "Name-list entries are not scalar strings or character vectors.",
183    message: "orderfields: cell array elements must be a string or character vector",
184};
185
186const ORDERFIELDS_ERROR_EMPTY_FIELD_NAME: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
187    code: "RM.ORDERFIELDS.EMPTY_FIELD_NAME",
188    identifier: Some("orderfields:EmptyFieldName"),
189    when: "Requested field-name list contains an empty name.",
190    message: "orderfields: field names must be nonempty",
191};
192
193const ORDERFIELDS_ERROR_INVALID_PERMUTATION: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
194    code: "RM.ORDERFIELDS.INVALID_PERMUTATION",
195    identifier: Some("orderfields:InvalidPermutation"),
196    when: "Permutation vector does not include each field exactly once.",
197    message: "orderfields: index vector must permute every field exactly once",
198};
199
200const ORDERFIELDS_ERROR_INDEX_NOT_INTEGER: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
201    code: "RM.ORDERFIELDS.INDEX_NOT_INTEGER",
202    identifier: Some("orderfields:IndexNotInteger"),
203    when: "Permutation vector contains non-integer entries.",
204    message: "orderfields: index vector must contain integers",
205};
206
207const ORDERFIELDS_ERROR_INDEX_OUT_OF_RANGE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
208    code: "RM.ORDERFIELDS.INDEX_OUT_OF_RANGE",
209    identifier: Some("orderfields:IndexOutOfRange"),
210    when: "Permutation vector index is outside valid field range.",
211    message: "orderfields: index vector element out of range",
212};
213
214const ORDERFIELDS_ERROR_INDEX_DUPLICATE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
215    code: "RM.ORDERFIELDS.INDEX_DUPLICATE",
216    identifier: Some("orderfields:DuplicateIndex"),
217    when: "Permutation vector contains duplicate positions.",
218    message: "orderfields: index vector contains duplicate positions",
219};
220
221const ORDERFIELDS_ERROR_FIELD_MISMATCH: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
222    code: "RM.ORDERFIELDS.FIELD_MISMATCH",
223    identifier: Some("orderfields:FieldMismatch"),
224    when: "Requested field set does not exactly match struct fields.",
225    message: "orderfields: field names must match the struct exactly",
226};
227
228const ORDERFIELDS_ERROR_UNKNOWN_FIELD: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
229    code: "RM.ORDERFIELDS.UNKNOWN_FIELD",
230    identifier: Some("orderfields:UnknownField"),
231    when: "Requested order references an unknown field.",
232    message: "orderfields: unknown field in requested order",
233};
234
235const ORDERFIELDS_ERROR_DUPLICATE_FIELD: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
236    code: "RM.ORDERFIELDS.DUPLICATE_FIELD",
237    identifier: Some("orderfields:DuplicateField"),
238    when: "Requested field order includes duplicate names.",
239    message: "orderfields: duplicate field in requested order",
240};
241
242const ORDERFIELDS_ERROR_MISSING_FIELD: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
243    code: "RM.ORDERFIELDS.MISSING_FIELD",
244    identifier: Some("orderfields:MissingField"),
245    when: "A field from the requested order is missing on the struct.",
246    message: "orderfields: field does not exist on the struct",
247};
248
249const ORDERFIELDS_ERROR_INVALID_ORDER_ARGUMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
250    code: "RM.ORDERFIELDS.INVALID_ORDER_ARGUMENT",
251    identifier: Some("orderfields:InvalidOrderArgument"),
252    when: "Second argument is not a supported order descriptor.",
253    message: "orderfields: unrecognised ordering argument",
254};
255
256const ORDERFIELDS_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
257    code: "RM.ORDERFIELDS.INTERNAL",
258    identifier: Some("orderfields:InternalError"),
259    when: "Internal permutation tensor construction failed.",
260    message: "orderfields: internal error",
261};
262
263const ORDERFIELDS_ERRORS: [BuiltinErrorDescriptor; 20] = [
264    ORDERFIELDS_ERROR_TOO_MANY_INPUTS,
265    ORDERFIELDS_ERROR_INVALID_INPUT,
266    ORDERFIELDS_ERROR_EMPTY_STRUCT_ARRAY,
267    ORDERFIELDS_ERROR_NO_FIELDS,
268    ORDERFIELDS_ERROR_INVALID_STRUCT_ARRAY,
269    ORDERFIELDS_ERROR_INVALID_STRUCT_CONTENTS,
270    ORDERFIELDS_ERROR_REBUILD_FAILED,
271    ORDERFIELDS_ERROR_INVALID_REFERENCE,
272    ORDERFIELDS_ERROR_INVALID_NAME_LIST,
273    ORDERFIELDS_ERROR_EMPTY_FIELD_NAME,
274    ORDERFIELDS_ERROR_INVALID_PERMUTATION,
275    ORDERFIELDS_ERROR_INDEX_NOT_INTEGER,
276    ORDERFIELDS_ERROR_INDEX_OUT_OF_RANGE,
277    ORDERFIELDS_ERROR_INDEX_DUPLICATE,
278    ORDERFIELDS_ERROR_FIELD_MISMATCH,
279    ORDERFIELDS_ERROR_UNKNOWN_FIELD,
280    ORDERFIELDS_ERROR_DUPLICATE_FIELD,
281    ORDERFIELDS_ERROR_MISSING_FIELD,
282    ORDERFIELDS_ERROR_INVALID_ORDER_ARGUMENT,
283    ORDERFIELDS_ERROR_INTERNAL,
284];
285
286pub const ORDERFIELDS_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
287    signatures: &ORDERFIELDS_SIGNATURES,
288    output_mode: BuiltinOutputMode::ByRequestedOutputCount,
289    completion_policy: BuiltinCompletionPolicy::Public,
290    errors: &ORDERFIELDS_ERRORS,
291};
292
293fn orderfields_error(error: &'static BuiltinErrorDescriptor) -> RuntimeError {
294    orderfields_error_with_message(error.message, error)
295}
296
297fn orderfields_error_with_message(
298    message: impl Into<String>,
299    error: &'static BuiltinErrorDescriptor,
300) -> RuntimeError {
301    let mut builder = build_runtime_error(message).with_builtin(BUILTIN_NAME);
302    if let Some(identifier) = error.identifier {
303        builder = builder.with_identifier(identifier);
304    }
305    builder.build()
306}
307
308#[runtime_builtin(
309    name = "orderfields",
310    category = "structs/core",
311    summary = "Reorder struct field definitions alphabetically or by supplied order.",
312    keywords = "orderfields,struct,reorder fields,alphabetical,struct array",
313    type_resolver(orderfields_type),
314    descriptor(crate::builtins::structs::core::orderfields::ORDERFIELDS_DESCRIPTOR),
315    builtin_path = "crate::builtins::structs::core::orderfields"
316)]
317async fn orderfields_builtin(value: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
318    let eval = evaluate(value, &rest)?;
319    if let Some(out_count) = crate::output_count::current_output_count() {
320        if out_count == 0 {
321            return Ok(Value::OutputList(Vec::new()));
322        }
323        let (ordered, permutation) = eval.into_values();
324        let mut outputs = vec![ordered];
325        if out_count >= 2 {
326            outputs.push(permutation);
327        }
328        return Ok(crate::output_count::output_list_with_padding(
329            out_count, outputs,
330        ));
331    }
332    Ok(eval.into_ordered_value())
333}
334
335/// Evaluate the `orderfields` builtin once and expose both outputs.
336pub fn evaluate(value: Value, rest: &[Value]) -> BuiltinResult<OrderFieldsEvaluation> {
337    if rest.len() > 1 {
338        return Err(orderfields_error(&ORDERFIELDS_ERROR_TOO_MANY_INPUTS));
339    }
340    let order_arg = rest.first();
341
342    match value {
343        Value::Struct(struct_value) => {
344            let original: Vec<String> = struct_value.field_names().cloned().collect();
345            let order = resolve_order(&struct_value, order_arg)?;
346            let permutation = permutation_from(&original, &order)?;
347            let permutation = permutation_tensor(permutation)?;
348            let reordered = reorder_struct(&struct_value, &order)?;
349            Ok(OrderFieldsEvaluation::new(
350                Value::Struct(reordered),
351                permutation,
352            ))
353        }
354        Value::Cell(cell) => {
355            if cell.data.is_empty() {
356                let permutation = permutation_tensor(Vec::new())?;
357                if let Some(arg) = order_arg {
358                    if let Some(reference) = extract_reference_struct(arg)? {
359                        if reference.fields.is_empty() {
360                            return Ok(OrderFieldsEvaluation::new(Value::Cell(cell), permutation));
361                        } else {
362                            return Err(orderfields_error(&ORDERFIELDS_ERROR_EMPTY_STRUCT_ARRAY));
363                        }
364                    }
365                    if let Some(names) = extract_name_list(arg)? {
366                        if names.is_empty() {
367                            return Ok(OrderFieldsEvaluation::new(Value::Cell(cell), permutation));
368                        }
369                        return Err(orderfields_error(&ORDERFIELDS_ERROR_NO_FIELDS));
370                    }
371                    if let Value::Tensor(tensor) = arg {
372                        if tensor.data.is_empty() {
373                            return Ok(OrderFieldsEvaluation::new(Value::Cell(cell), permutation));
374                        }
375                        return Err(orderfields_error(&ORDERFIELDS_ERROR_NO_FIELDS));
376                    }
377                    return Err(orderfields_error(&ORDERFIELDS_ERROR_NO_FIELDS));
378                }
379                return Ok(OrderFieldsEvaluation::new(Value::Cell(cell), permutation));
380            }
381            let first = extract_struct_from_cell(&cell, 0)?;
382            let original: Vec<String> = first.field_names().cloned().collect();
383            let order = resolve_order(&first, order_arg)?;
384            let permutation = permutation_from(&original, &order)?;
385            let permutation = permutation_tensor(permutation)?;
386            let reordered = reorder_struct_array(&cell, &order)?;
387            Ok(OrderFieldsEvaluation::new(
388                Value::Cell(reordered),
389                permutation,
390            ))
391        }
392        other => Err(orderfields_error_with_message(
393            format!(
394                "{} (got {other:?})",
395                ORDERFIELDS_ERROR_INVALID_INPUT.message
396            ),
397            &ORDERFIELDS_ERROR_INVALID_INPUT,
398        )),
399    }
400}
401
402pub struct OrderFieldsEvaluation {
403    ordered: Value,
404    permutation: Tensor,
405}
406
407impl OrderFieldsEvaluation {
408    fn new(ordered: Value, permutation: Tensor) -> Self {
409        Self {
410            ordered,
411            permutation,
412        }
413    }
414
415    pub fn into_ordered_value(self) -> Value {
416        self.ordered
417    }
418
419    pub fn permutation_value(&self) -> Value {
420        tensor::tensor_into_value(self.permutation.clone())
421    }
422
423    pub fn into_values(self) -> (Value, Value) {
424        let perm = tensor::tensor_into_value(self.permutation);
425        (self.ordered, perm)
426    }
427}
428
429fn reorder_struct_array(array: &CellArray, order: &[String]) -> BuiltinResult<CellArray> {
430    let mut reordered_elems = Vec::with_capacity(array.data.len());
431    for (index, handle) in array.data.iter().enumerate() {
432        let value = unsafe { &*handle.as_raw() };
433        let Value::Struct(st) = value else {
434            return Err(orderfields_error_with_message(
435                format!(
436                    "{} (element {} is not a struct)",
437                    ORDERFIELDS_ERROR_INVALID_STRUCT_ARRAY.message,
438                    index + 1
439                ),
440                &ORDERFIELDS_ERROR_INVALID_STRUCT_ARRAY,
441            ));
442        };
443        ensure_same_field_set(order, st)?;
444        let reordered = reorder_struct(st, order)?;
445        reordered_elems.push(Value::Struct(reordered));
446    }
447    CellArray::new_with_shape(reordered_elems, array.shape.clone()).map_err(|e| {
448        orderfields_error_with_message(
449            format!("{}: {e}", ORDERFIELDS_ERROR_REBUILD_FAILED.message),
450            &ORDERFIELDS_ERROR_REBUILD_FAILED,
451        )
452    })
453}
454
455fn reorder_struct(struct_value: &StructValue, order: &[String]) -> BuiltinResult<StructValue> {
456    let mut reordered = StructValue::new();
457    for name in order {
458        let value = struct_value
459            .fields
460            .get(name)
461            .ok_or_else(|| missing_field(name))?
462            .clone();
463        reordered.fields.insert(name.clone(), value);
464    }
465    Ok(reordered)
466}
467
468fn resolve_order(
469    struct_value: &StructValue,
470    order_arg: Option<&Value>,
471) -> BuiltinResult<Vec<String>> {
472    let mut current: Vec<String> = struct_value.field_names().cloned().collect();
473    if let Some(arg) = order_arg {
474        if let Some(reference) = extract_reference_struct(arg)? {
475            let reference_names: Vec<String> = reference.field_names().cloned().collect();
476            ensure_same_field_set(&reference_names, struct_value)?;
477            return Ok(reference_names);
478        }
479
480        if let Some(names) = extract_name_list(arg)? {
481            ensure_same_field_set(&names, struct_value)?;
482            return Ok(names);
483        }
484
485        if let Some(permutation) = extract_indices(&current, arg)? {
486            return Ok(permutation);
487        }
488
489        return Err(orderfields_error(&ORDERFIELDS_ERROR_INVALID_ORDER_ARGUMENT));
490    }
491
492    sort_field_names(&mut current);
493    Ok(current)
494}
495
496fn permutation_from(original: &[String], order: &[String]) -> BuiltinResult<Vec<f64>> {
497    let mut index_map = HashMap::with_capacity(original.len());
498    for (idx, name) in original.iter().enumerate() {
499        index_map.insert(name.as_str(), idx);
500    }
501    let mut indices = Vec::with_capacity(order.len());
502    for name in order {
503        let Some(position) = index_map.get(name.as_str()) else {
504            return Err(missing_field(name));
505        };
506        indices.push((*position as f64) + 1.0);
507    }
508    Ok(indices)
509}
510
511fn permutation_tensor(indices: Vec<f64>) -> BuiltinResult<Tensor> {
512    let rows = indices.len();
513    let shape = vec![rows, 1];
514    Tensor::new(indices, shape).map_err(|e| {
515        orderfields_error_with_message(
516            format!("{}: {e}", ORDERFIELDS_ERROR_INTERNAL.message),
517            &ORDERFIELDS_ERROR_INTERNAL,
518        )
519    })
520}
521
522fn sort_field_names(names: &mut [String]) {
523    names.sort_by(|a, b| {
524        let lower_a = a.to_ascii_lowercase();
525        let lower_b = b.to_ascii_lowercase();
526        match lower_a.cmp(&lower_b) {
527            Ordering::Equal => a.cmp(b),
528            other => other,
529        }
530    });
531}
532
533fn extract_reference_struct(value: &Value) -> BuiltinResult<Option<StructValue>> {
534    match value {
535        Value::Struct(st) => Ok(Some(st.clone())),
536        Value::Cell(cell) => {
537            let mut first: Option<StructValue> = None;
538            for (index, handle) in cell.data.iter().enumerate() {
539                let value = unsafe { &*handle.as_raw() };
540                if let Value::Struct(st) = value {
541                    if first.is_none() {
542                        first = Some(st.clone());
543                    }
544                } else if first.is_some() {
545                    return Err(orderfields_error_with_message(
546                        format!(
547                            "{} (element {} is not a struct)",
548                            ORDERFIELDS_ERROR_INVALID_REFERENCE.message,
549                            index + 1
550                        ),
551                        &ORDERFIELDS_ERROR_INVALID_REFERENCE,
552                    ));
553                } else {
554                    return Ok(None);
555                }
556            }
557            Ok(first)
558        }
559        _ => Ok(None),
560    }
561}
562
563fn extract_name_list(arg: &Value) -> BuiltinResult<Option<Vec<String>>> {
564    match arg {
565        Value::Cell(cell) => {
566            let mut names = Vec::with_capacity(cell.data.len());
567            for (index, handle) in cell.data.iter().enumerate() {
568                let value = unsafe { &*handle.as_raw() };
569                let text = scalar_string(value).ok_or_else(|| {
570                    orderfields_error_with_message(
571                        format!(
572                            "{} (cell array element {})",
573                            ORDERFIELDS_ERROR_INVALID_NAME_LIST.message,
574                            index + 1
575                        ),
576                        &ORDERFIELDS_ERROR_INVALID_NAME_LIST,
577                    )
578                })?;
579                if text.is_empty() {
580                    return Err(orderfields_error(&ORDERFIELDS_ERROR_EMPTY_FIELD_NAME));
581                }
582                names.push(text);
583            }
584            Ok(Some(names))
585        }
586        Value::StringArray(sa) => Ok(Some(sa.data.clone())),
587        Value::CharArray(ca) => {
588            if ca.rows == 0 {
589                return Ok(Some(Vec::new()));
590            }
591            let mut names = Vec::with_capacity(ca.rows);
592            for row in 0..ca.rows {
593                let start = row * ca.cols;
594                let end = start + ca.cols;
595                let mut text: String = ca.data[start..end].iter().collect();
596                while text.ends_with(' ') {
597                    text.pop();
598                }
599                if text.is_empty() {
600                    return Err(orderfields_error(&ORDERFIELDS_ERROR_EMPTY_FIELD_NAME));
601                }
602                names.push(text);
603            }
604            Ok(Some(names))
605        }
606        _ => Ok(None),
607    }
608}
609
610fn extract_indices(current: &[String], arg: &Value) -> BuiltinResult<Option<Vec<String>>> {
611    let Value::Tensor(tensor) = arg else {
612        return Ok(None);
613    };
614    if tensor.data.is_empty() && current.is_empty() {
615        return Ok(Some(Vec::new()));
616    }
617    if tensor.data.len() != current.len() {
618        return Err(orderfields_error(&ORDERFIELDS_ERROR_INVALID_PERMUTATION));
619    }
620    let mut seen = HashSet::with_capacity(current.len());
621    let mut order = Vec::with_capacity(current.len());
622    for value in &tensor.data {
623        if !value.is_finite() || value.fract() != 0.0 {
624            return Err(orderfields_error(&ORDERFIELDS_ERROR_INDEX_NOT_INTEGER));
625        }
626        let idx = *value as isize;
627        if idx < 1 || idx as usize > current.len() {
628            return Err(orderfields_error(&ORDERFIELDS_ERROR_INDEX_OUT_OF_RANGE));
629        }
630        let zero_based = (idx as usize) - 1;
631        if !seen.insert(zero_based) {
632            return Err(orderfields_error(&ORDERFIELDS_ERROR_INDEX_DUPLICATE));
633        }
634        order.push(current[zero_based].clone());
635    }
636    Ok(Some(order))
637}
638
639fn ensure_same_field_set(order: &[String], original: &StructValue) -> BuiltinResult<()> {
640    if order.len() != original.fields.len() {
641        return Err(orderfields_error(&ORDERFIELDS_ERROR_FIELD_MISMATCH));
642    }
643    let mut seen = HashSet::with_capacity(order.len());
644    let original_set: HashSet<&str> = original.field_names().map(|s| s.as_str()).collect();
645    for name in order {
646        if !original_set.contains(name.as_str()) {
647            return Err(orderfields_error_with_message(
648                format!(
649                    "{} ('{name}' in requested order)",
650                    ORDERFIELDS_ERROR_UNKNOWN_FIELD.message
651                ),
652                &ORDERFIELDS_ERROR_UNKNOWN_FIELD,
653            ));
654        }
655        if !seen.insert(name.as_str()) {
656            return Err(orderfields_error_with_message(
657                format!(
658                    "{} ('{name}' in requested order)",
659                    ORDERFIELDS_ERROR_DUPLICATE_FIELD.message
660                ),
661                &ORDERFIELDS_ERROR_DUPLICATE_FIELD,
662            ));
663        }
664    }
665    Ok(())
666}
667
668fn extract_struct_from_cell(cell: &CellArray, index: usize) -> BuiltinResult<StructValue> {
669    let value = unsafe { &*cell.data[index].as_raw() };
670    match value {
671        Value::Struct(st) => Ok(st.clone()),
672        other => Err(orderfields_error_with_message(
673            format!(
674                "{} (found {other:?})",
675                ORDERFIELDS_ERROR_INVALID_STRUCT_CONTENTS.message
676            ),
677            &ORDERFIELDS_ERROR_INVALID_STRUCT_CONTENTS,
678        )),
679    }
680}
681
682fn scalar_string(value: &Value) -> Option<String> {
683    match value {
684        Value::String(s) => Some(s.clone()),
685        Value::StringArray(sa) if sa.data.len() == 1 => Some(sa.data[0].clone()),
686        Value::CharArray(ca) if ca.rows == 1 => {
687            let mut text: String = ca.data.iter().collect();
688            while text.ends_with(' ') {
689                text.pop();
690            }
691            Some(text)
692        }
693        _ => None,
694    }
695}
696
697fn missing_field(name: &str) -> RuntimeError {
698    orderfields_error_with_message(
699        format!("{} ('{name}')", ORDERFIELDS_ERROR_MISSING_FIELD.message),
700        &ORDERFIELDS_ERROR_MISSING_FIELD,
701    )
702}
703
704#[cfg(test)]
705pub(crate) mod tests {
706    use super::*;
707    use futures::executor::block_on;
708    use runmat_builtins::{CellArray, CharArray, StringArray, Tensor};
709
710    fn run_orderfields(value: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
711        block_on(super::orderfields_builtin(value, rest))
712    }
713
714    fn assert_error_identifier(error: RuntimeError, expected: &str) {
715        assert_eq!(error.identifier(), Some(expected));
716    }
717
718    fn field_order(struct_value: &StructValue) -> Vec<String> {
719        struct_value.field_names().cloned().collect()
720    }
721
722    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
723    #[test]
724    fn default_sorts_alphabetically() {
725        let mut st = StructValue::new();
726        st.fields.insert("beta".to_string(), Value::Num(2.0));
727        st.fields.insert("alpha".to_string(), Value::Num(1.0));
728        st.fields.insert("gamma".to_string(), Value::Num(3.0));
729
730        let result = run_orderfields(Value::Struct(st), Vec::new()).expect("orderfields");
731        let Value::Struct(sorted) = result else {
732            panic!("expected struct result");
733        };
734        assert_eq!(
735            field_order(&sorted),
736            vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()]
737        );
738    }
739
740    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
741    #[test]
742    fn reorder_with_cell_name_list() {
743        let mut st = StructValue::new();
744        st.fields.insert("a".to_string(), Value::Num(1.0));
745        st.fields.insert("b".to_string(), Value::Num(2.0));
746        st.fields.insert("c".to_string(), Value::Num(3.0));
747        let names = CellArray::new(
748            vec![Value::from("c"), Value::from("a"), Value::from("b")],
749            1,
750            3,
751        )
752        .expect("cell");
753
754        let reordered =
755            run_orderfields(Value::Struct(st), vec![Value::Cell(names)]).expect("orderfields");
756        let Value::Struct(result) = reordered else {
757            panic!("expected struct result");
758        };
759        assert_eq!(
760            field_order(&result),
761            vec!["c".to_string(), "a".to_string(), "b".to_string()]
762        );
763    }
764
765    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
766    #[test]
767    fn reorder_with_string_array_names() {
768        let mut st = StructValue::new();
769        st.fields.insert("alpha".to_string(), Value::Num(1.0));
770        st.fields.insert("beta".to_string(), Value::Num(2.0));
771        st.fields.insert("gamma".to_string(), Value::Num(3.0));
772
773        let strings = StringArray::new(
774            vec!["gamma".into(), "alpha".into(), "beta".into()],
775            vec![1, 3],
776        )
777        .expect("string array");
778
779        let result = run_orderfields(Value::Struct(st), vec![Value::StringArray(strings)])
780            .expect("orderfields");
781        let Value::Struct(sorted) = result else {
782            panic!("expected struct result");
783        };
784        assert_eq!(
785            field_order(&sorted),
786            vec!["gamma".to_string(), "alpha".to_string(), "beta".to_string()]
787        );
788    }
789
790    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
791    #[test]
792    fn reorder_with_char_array_names() {
793        let mut st = StructValue::new();
794        st.fields.insert("cat".to_string(), Value::Num(1.0));
795        st.fields.insert("ant".to_string(), Value::Num(2.0));
796        st.fields.insert("bat".to_string(), Value::Num(3.0));
797
798        let data = vec!['b', 'a', 't', 'c', 'a', 't', 'a', 'n', 't'];
799        let char_array = CharArray::new(data, 3, 3).expect("char array");
800
801        let result =
802            run_orderfields(Value::Struct(st), vec![Value::CharArray(char_array)]).expect("order");
803        let Value::Struct(sorted) = result else {
804            panic!("expected struct result");
805        };
806        assert_eq!(
807            field_order(&sorted),
808            vec!["bat".to_string(), "cat".to_string(), "ant".to_string()]
809        );
810    }
811
812    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
813    #[test]
814    fn reorder_with_reference_struct() {
815        let mut source = StructValue::new();
816        source.fields.insert("y".to_string(), Value::Num(2.0));
817        source.fields.insert("x".to_string(), Value::Num(1.0));
818
819        let mut reference = StructValue::new();
820        reference.fields.insert("x".to_string(), Value::Num(0.0));
821        reference.fields.insert("y".to_string(), Value::Num(0.0));
822
823        let result = run_orderfields(
824            Value::Struct(source),
825            vec![Value::Struct(reference.clone())],
826        )
827        .expect("orderfields");
828        let Value::Struct(reordered) = result else {
829            panic!("expected struct result");
830        };
831        assert_eq!(
832            field_order(&reordered),
833            vec!["x".to_string(), "y".to_string()]
834        );
835    }
836
837    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
838    #[test]
839    fn reorder_with_index_vector() {
840        let mut st = StructValue::new();
841        st.fields.insert("first".to_string(), Value::Num(1.0));
842        st.fields.insert("second".to_string(), Value::Num(2.0));
843        st.fields.insert("third".to_string(), Value::Num(3.0));
844
845        let permutation = Tensor::new(vec![3.0, 1.0, 2.0], vec![1, 3]).expect("tensor permutation");
846        let result = run_orderfields(Value::Struct(st), vec![Value::Tensor(permutation)])
847            .expect("orderfields");
848        let Value::Struct(reordered) = result else {
849            panic!("expected struct result");
850        };
851        assert_eq!(
852            field_order(&reordered),
853            vec![
854                "third".to_string(),
855                "first".to_string(),
856                "second".to_string()
857            ]
858        );
859    }
860
861    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
862    #[test]
863    fn index_vector_must_be_integers() {
864        let mut st = StructValue::new();
865        st.fields.insert("one".to_string(), Value::Num(1.0));
866        st.fields.insert("two".to_string(), Value::Num(2.0));
867
868        let permutation = Tensor::new(vec![1.0, 1.5], vec![1, 2]).expect("tensor");
869        let err = run_orderfields(Value::Struct(st), vec![Value::Tensor(permutation)]).unwrap_err();
870        assert_error_identifier(err, ORDERFIELDS_ERROR_INDEX_NOT_INTEGER.identifier.unwrap());
871    }
872
873    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
874    #[test]
875    fn permutation_vector_matches_original_positions() {
876        let mut st = StructValue::new();
877        st.fields.insert("beta".to_string(), Value::Num(2.0));
878        st.fields.insert("alpha".to_string(), Value::Num(1.0));
879        st.fields.insert("gamma".to_string(), Value::Num(3.0));
880
881        let eval = evaluate(Value::Struct(st), &[]).expect("evaluate");
882        let perm = eval.permutation_value();
883        match perm {
884            Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 1.0, 3.0]),
885            other => panic!("expected tensor permutation, got {other:?}"),
886        }
887        let Value::Struct(ordered) = eval.into_ordered_value() else {
888            panic!("expected struct result");
889        };
890        assert_eq!(
891            field_order(&ordered),
892            vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()]
893        );
894    }
895
896    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
897    #[test]
898    fn reorder_struct_array() {
899        let mut first = StructValue::new();
900        first.fields.insert("b".to_string(), Value::Num(1.0));
901        first.fields.insert("a".to_string(), Value::Num(2.0));
902        let mut second = StructValue::new();
903        second.fields.insert("b".to_string(), Value::Num(3.0));
904        second.fields.insert("a".to_string(), Value::Num(4.0));
905        let array = CellArray::new_with_shape(
906            vec![Value::Struct(first), Value::Struct(second)],
907            vec![1, 2],
908        )
909        .expect("struct array");
910        let names =
911            CellArray::new(vec![Value::from("a"), Value::from("b")], 1, 2).expect("cell names");
912
913        let result =
914            run_orderfields(Value::Cell(array), vec![Value::Cell(names)]).expect("orderfields");
915        let Value::Cell(reordered) = result else {
916            panic!("expected cell array");
917        };
918        for handle in &reordered.data {
919            let Value::Struct(st) = (unsafe { &*handle.as_raw() }) else {
920                panic!("expected struct element");
921            };
922            assert_eq!(field_order(st), vec!["a".to_string(), "b".to_string()]);
923        }
924    }
925
926    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
927    #[test]
928    fn struct_array_permutation_reuses_order() {
929        let mut first = StructValue::new();
930        first.fields.insert("z".to_string(), Value::Num(1.0));
931        first.fields.insert("x".to_string(), Value::Num(2.0));
932        first.fields.insert("y".to_string(), Value::Num(3.0));
933
934        let mut second = StructValue::new();
935        second.fields.insert("z".to_string(), Value::Num(4.0));
936        second.fields.insert("x".to_string(), Value::Num(5.0));
937        second.fields.insert("y".to_string(), Value::Num(6.0));
938
939        let array = CellArray::new_with_shape(
940            vec![Value::Struct(first), Value::Struct(second)],
941            vec![1, 2],
942        )
943        .expect("struct array");
944
945        let eval = evaluate(Value::Cell(array), &[]).expect("evaluate");
946        let perm = eval.permutation_value();
947        match perm {
948            Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 3.0, 1.0]),
949            other => panic!("expected tensor permutation, got {other:?}"),
950        }
951    }
952
953    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
954    #[test]
955    fn rejects_unknown_field() {
956        let mut st = StructValue::new();
957        st.fields.insert("alpha".to_string(), Value::Num(1.0));
958        st.fields.insert("beta".to_string(), Value::Num(2.0));
959        let err = run_orderfields(
960            Value::Struct(st),
961            vec![Value::Cell(
962                CellArray::new(vec![Value::from("beta"), Value::from("gamma")], 1, 2)
963                    .expect("cell"),
964            )],
965        )
966        .unwrap_err();
967        assert_error_identifier(err, ORDERFIELDS_ERROR_UNKNOWN_FIELD.identifier.unwrap());
968    }
969
970    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
971    #[test]
972    fn duplicate_field_names_rejected() {
973        let mut st = StructValue::new();
974        st.fields.insert("alpha".to_string(), Value::Num(1.0));
975        st.fields.insert("beta".to_string(), Value::Num(2.0));
976
977        let names =
978            CellArray::new(vec![Value::from("alpha"), Value::from("alpha")], 1, 2).expect("cell");
979        let err = run_orderfields(Value::Struct(st), vec![Value::Cell(names)]).unwrap_err();
980        assert_error_identifier(err, ORDERFIELDS_ERROR_DUPLICATE_FIELD.identifier.unwrap());
981    }
982
983    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
984    #[test]
985    fn reference_struct_mismatch_errors() {
986        let mut source = StructValue::new();
987        source.fields.insert("x".to_string(), Value::Num(1.0));
988        source.fields.insert("y".to_string(), Value::Num(2.0));
989
990        let mut reference = StructValue::new();
991        reference.fields.insert("x".to_string(), Value::Num(0.0));
992
993        let err =
994            run_orderfields(Value::Struct(source), vec![Value::Struct(reference)]).unwrap_err();
995        assert_error_identifier(err, ORDERFIELDS_ERROR_FIELD_MISMATCH.identifier.unwrap());
996    }
997
998    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
999    #[test]
1000    fn invalid_order_argument_type_errors() {
1001        let mut st = StructValue::new();
1002        st.fields.insert("x".to_string(), Value::Num(1.0));
1003
1004        let err = run_orderfields(Value::Struct(st), vec![Value::Num(1.0)]).unwrap_err();
1005        assert_error_identifier(
1006            err,
1007            ORDERFIELDS_ERROR_INVALID_ORDER_ARGUMENT.identifier.unwrap(),
1008        );
1009    }
1010
1011    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1012    #[test]
1013    fn empty_struct_array_nonempty_reference_errors() {
1014        let empty = CellArray::new(Vec::new(), 0, 0).expect("empty struct array");
1015        let mut reference = StructValue::new();
1016        reference
1017            .fields
1018            .insert("field".to_string(), Value::Num(1.0));
1019
1020        let err = run_orderfields(Value::Cell(empty), vec![Value::Struct(reference)]).unwrap_err();
1021        assert_error_identifier(
1022            err,
1023            ORDERFIELDS_ERROR_EMPTY_STRUCT_ARRAY.identifier.unwrap(),
1024        );
1025    }
1026}