1use 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::{CellArray, StructValue, Tensor, Value};
11use runmat_macros::runtime_builtin;
12use std::cmp::Ordering;
13use std::collections::{HashMap, HashSet};
14
15use crate::{build_runtime_error, BuiltinResult, RuntimeError};
16
17#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::structs::core::orderfields")]
18pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
19 name: "orderfields",
20 op_kind: GpuOpKind::Custom("orderfields"),
21 supported_precisions: &[],
22 broadcast: BroadcastSemantics::None,
23 provider_hooks: &[],
24 constant_strategy: ConstantStrategy::InlineLiteral,
25 residency: ResidencyPolicy::InheritInputs,
26 nan_mode: ReductionNaN::Include,
27 two_pass_threshold: None,
28 workgroup_size: None,
29 accepts_nan_mode: false,
30 notes: "Host-only metadata manipulation; struct values that live on the GPU remain resident.",
31};
32
33#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::structs::core::orderfields")]
34pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
35 name: "orderfields",
36 shape: ShapeRequirements::Any,
37 constant_strategy: ConstantStrategy::InlineLiteral,
38 elementwise: None,
39 reduction: None,
40 emits_nan: false,
41 notes: "Reordering fields is a metadata operation and does not participate in fusion planning.",
42};
43
44const MESSAGE_ID_TOO_MANY_INPUTS: &str = "orderfields:TooManyInputs";
45const MESSAGE_ID_INVALID_INPUT: &str = "orderfields:InvalidInput";
46const MESSAGE_ID_EMPTY_STRUCT_ARRAY: &str = "orderfields:EmptyStructArray";
47const MESSAGE_ID_NO_FIELDS: &str = "orderfields:NoFields";
48const MESSAGE_ID_INVALID_STRUCT_ARRAY: &str = "orderfields:InvalidStructArray";
49const MESSAGE_ID_INVALID_STRUCT_CONTENTS: &str = "orderfields:InvalidStructContents";
50const MESSAGE_ID_REBUILD_FAILED: &str = "orderfields:RebuildFailed";
51const MESSAGE_ID_INVALID_REFERENCE: &str = "orderfields:InvalidReference";
52const MESSAGE_ID_INVALID_NAME_LIST: &str = "orderfields:InvalidFieldNameList";
53const MESSAGE_ID_EMPTY_FIELD_NAME: &str = "orderfields:EmptyFieldName";
54const MESSAGE_ID_INVALID_PERMUTATION: &str = "orderfields:InvalidPermutation";
55const MESSAGE_ID_INDEX_NOT_INTEGER: &str = "orderfields:IndexNotInteger";
56const MESSAGE_ID_INDEX_OUT_OF_RANGE: &str = "orderfields:IndexOutOfRange";
57const MESSAGE_ID_INDEX_DUPLICATE: &str = "orderfields:DuplicateIndex";
58const MESSAGE_ID_FIELD_MISMATCH: &str = "orderfields:FieldMismatch";
59const MESSAGE_ID_UNKNOWN_FIELD: &str = "orderfields:UnknownField";
60const MESSAGE_ID_DUPLICATE_FIELD: &str = "orderfields:DuplicateField";
61const MESSAGE_ID_MISSING_FIELD: &str = "orderfields:MissingField";
62const MESSAGE_ID_INVALID_ORDER_ARGUMENT: &str = "orderfields:InvalidOrderArgument";
63const MESSAGE_ID_INTERNAL: &str = "orderfields:InternalError";
64
65fn orderfields_flow(message_id: &str, message: impl Into<String>) -> RuntimeError {
66 build_runtime_error(message)
67 .with_builtin("orderfields")
68 .with_identifier(message_id)
69 .build()
70}
71
72#[runtime_builtin(
73 name = "orderfields",
74 category = "structs/core",
75 summary = "Reorder structure field definitions alphabetically or using a supplied order.",
76 keywords = "orderfields,struct,reorder fields,alphabetical,struct array",
77 type_resolver(orderfields_type),
78 builtin_path = "crate::builtins::structs::core::orderfields"
79)]
80async fn orderfields_builtin(value: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
81 let eval = evaluate(value, &rest)?;
82 if let Some(out_count) = crate::output_count::current_output_count() {
83 if out_count == 0 {
84 return Ok(Value::OutputList(Vec::new()));
85 }
86 let (ordered, permutation) = eval.into_values();
87 let mut outputs = vec![ordered];
88 if out_count >= 2 {
89 outputs.push(permutation);
90 }
91 return Ok(crate::output_count::output_list_with_padding(
92 out_count, outputs,
93 ));
94 }
95 Ok(eval.into_ordered_value())
96}
97
98pub fn evaluate(value: Value, rest: &[Value]) -> BuiltinResult<OrderFieldsEvaluation> {
100 if rest.len() > 1 {
101 return Err(orderfields_flow(
102 MESSAGE_ID_TOO_MANY_INPUTS,
103 "orderfields: expected at most two input arguments",
104 ));
105 }
106 let order_arg = rest.first();
107
108 match value {
109 Value::Struct(struct_value) => {
110 let original: Vec<String> = struct_value.field_names().cloned().collect();
111 let order = resolve_order(&struct_value, order_arg)?;
112 let permutation = permutation_from(&original, &order)?;
113 let permutation = permutation_tensor(permutation)?;
114 let reordered = reorder_struct(&struct_value, &order)?;
115 Ok(OrderFieldsEvaluation::new(
116 Value::Struct(reordered),
117 permutation,
118 ))
119 }
120 Value::Cell(cell) => {
121 if cell.data.is_empty() {
122 let permutation = permutation_tensor(Vec::new())?;
123 if let Some(arg) = order_arg {
124 if let Some(reference) = extract_reference_struct(arg)? {
125 if reference.fields.is_empty() {
126 return Ok(OrderFieldsEvaluation::new(Value::Cell(cell), permutation));
127 } else {
128 return Err(orderfields_flow(
129 MESSAGE_ID_EMPTY_STRUCT_ARRAY,
130 "orderfields: empty struct arrays cannot adopt a non-empty reference order",
131 ));
132 }
133 }
134 if let Some(names) = extract_name_list(arg)? {
135 if names.is_empty() {
136 return Ok(OrderFieldsEvaluation::new(Value::Cell(cell), permutation));
137 }
138 return Err(orderfields_flow(
139 MESSAGE_ID_NO_FIELDS,
140 "orderfields: struct array has no fields to reorder",
141 ));
142 }
143 if let Value::Tensor(tensor) = arg {
144 if tensor.data.is_empty() {
145 return Ok(OrderFieldsEvaluation::new(Value::Cell(cell), permutation));
146 }
147 return Err(orderfields_flow(
148 MESSAGE_ID_NO_FIELDS,
149 "orderfields: struct array has no fields to reorder",
150 ));
151 }
152 return Err(orderfields_flow(
153 MESSAGE_ID_NO_FIELDS,
154 "orderfields: struct array has no fields to reorder",
155 ));
156 }
157 return Ok(OrderFieldsEvaluation::new(Value::Cell(cell), permutation));
158 }
159 let first = extract_struct_from_cell(&cell, 0)?;
160 let original: Vec<String> = first.field_names().cloned().collect();
161 let order = resolve_order(&first, order_arg)?;
162 let permutation = permutation_from(&original, &order)?;
163 let permutation = permutation_tensor(permutation)?;
164 let reordered = reorder_struct_array(&cell, &order)?;
165 Ok(OrderFieldsEvaluation::new(
166 Value::Cell(reordered),
167 permutation,
168 ))
169 }
170 other => Err(orderfields_flow(
171 MESSAGE_ID_INVALID_INPUT,
172 format!("orderfields: first argument must be a struct or struct array (got {other:?})"),
173 )),
174 }
175}
176
177pub struct OrderFieldsEvaluation {
178 ordered: Value,
179 permutation: Tensor,
180}
181
182impl OrderFieldsEvaluation {
183 fn new(ordered: Value, permutation: Tensor) -> Self {
184 Self {
185 ordered,
186 permutation,
187 }
188 }
189
190 pub fn into_ordered_value(self) -> Value {
191 self.ordered
192 }
193
194 pub fn permutation_value(&self) -> Value {
195 tensor::tensor_into_value(self.permutation.clone())
196 }
197
198 pub fn into_values(self) -> (Value, Value) {
199 let perm = tensor::tensor_into_value(self.permutation);
200 (self.ordered, perm)
201 }
202}
203
204fn reorder_struct_array(array: &CellArray, order: &[String]) -> BuiltinResult<CellArray> {
205 let mut reordered_elems = Vec::with_capacity(array.data.len());
206 for (index, handle) in array.data.iter().enumerate() {
207 let value = unsafe { &*handle.as_raw() };
208 let Value::Struct(st) = value else {
209 return Err(orderfields_flow(
210 MESSAGE_ID_INVALID_STRUCT_ARRAY,
211 format!(
212 "orderfields: struct array element {} is not a struct",
213 index + 1
214 ),
215 ));
216 };
217 ensure_same_field_set(order, st)?;
218 let reordered = reorder_struct(st, order)?;
219 reordered_elems.push(Value::Struct(reordered));
220 }
221 CellArray::new_with_shape(reordered_elems, array.shape.clone()).map_err(|e| {
222 orderfields_flow(
223 MESSAGE_ID_REBUILD_FAILED,
224 format!("orderfields: failed to rebuild struct array: {e}"),
225 )
226 })
227}
228
229fn reorder_struct(struct_value: &StructValue, order: &[String]) -> BuiltinResult<StructValue> {
230 let mut reordered = StructValue::new();
231 for name in order {
232 let value = struct_value
233 .fields
234 .get(name)
235 .ok_or_else(|| missing_field(name))?
236 .clone();
237 reordered.fields.insert(name.clone(), value);
238 }
239 Ok(reordered)
240}
241
242fn resolve_order(
243 struct_value: &StructValue,
244 order_arg: Option<&Value>,
245) -> BuiltinResult<Vec<String>> {
246 let mut current: Vec<String> = struct_value.field_names().cloned().collect();
247 if let Some(arg) = order_arg {
248 if let Some(reference) = extract_reference_struct(arg)? {
249 let reference_names: Vec<String> = reference.field_names().cloned().collect();
250 ensure_same_field_set(&reference_names, struct_value)?;
251 return Ok(reference_names);
252 }
253
254 if let Some(names) = extract_name_list(arg)? {
255 ensure_same_field_set(&names, struct_value)?;
256 return Ok(names);
257 }
258
259 if let Some(permutation) = extract_indices(¤t, arg)? {
260 return Ok(permutation);
261 }
262
263 return Err(orderfields_flow(
264 MESSAGE_ID_INVALID_ORDER_ARGUMENT,
265 "orderfields: unrecognised ordering argument",
266 ));
267 }
268
269 sort_field_names(&mut current);
270 Ok(current)
271}
272
273fn permutation_from(original: &[String], order: &[String]) -> BuiltinResult<Vec<f64>> {
274 let mut index_map = HashMap::with_capacity(original.len());
275 for (idx, name) in original.iter().enumerate() {
276 index_map.insert(name.as_str(), idx);
277 }
278 let mut indices = Vec::with_capacity(order.len());
279 for name in order {
280 let Some(position) = index_map.get(name.as_str()) else {
281 return Err(missing_field(name));
282 };
283 indices.push((*position as f64) + 1.0);
284 }
285 Ok(indices)
286}
287
288fn permutation_tensor(indices: Vec<f64>) -> BuiltinResult<Tensor> {
289 let rows = indices.len();
290 let shape = vec![rows, 1];
291 Tensor::new(indices, shape)
292 .map_err(|e| orderfields_flow(MESSAGE_ID_INTERNAL, format!("orderfields: {e}")))
293}
294
295fn sort_field_names(names: &mut [String]) {
296 names.sort_by(|a, b| {
297 let lower_a = a.to_ascii_lowercase();
298 let lower_b = b.to_ascii_lowercase();
299 match lower_a.cmp(&lower_b) {
300 Ordering::Equal => a.cmp(b),
301 other => other,
302 }
303 });
304}
305
306fn extract_reference_struct(value: &Value) -> BuiltinResult<Option<StructValue>> {
307 match value {
308 Value::Struct(st) => Ok(Some(st.clone())),
309 Value::Cell(cell) => {
310 let mut first: Option<StructValue> = None;
311 for (index, handle) in cell.data.iter().enumerate() {
312 let value = unsafe { &*handle.as_raw() };
313 if let Value::Struct(st) = value {
314 if first.is_none() {
315 first = Some(st.clone());
316 }
317 } else if first.is_some() {
318 return Err(orderfields_flow(
319 MESSAGE_ID_INVALID_REFERENCE,
320 format!(
321 "orderfields: reference struct array element {} is not a struct",
322 index + 1
323 ),
324 ));
325 } else {
326 return Ok(None);
327 }
328 }
329 Ok(first)
330 }
331 _ => Ok(None),
332 }
333}
334
335fn extract_name_list(arg: &Value) -> BuiltinResult<Option<Vec<String>>> {
336 match arg {
337 Value::Cell(cell) => {
338 let mut names = Vec::with_capacity(cell.data.len());
339 for (index, handle) in cell.data.iter().enumerate() {
340 let value = unsafe { &*handle.as_raw() };
341 let text = scalar_string(value).ok_or_else(|| {
342 orderfields_flow(
343 MESSAGE_ID_INVALID_NAME_LIST,
344 format!(
345 "orderfields: cell array element {} must be a string or character vector",
346 index + 1
347 ),
348 )
349 })?;
350 if text.is_empty() {
351 return Err(orderfields_flow(
352 MESSAGE_ID_EMPTY_FIELD_NAME,
353 "orderfields: field names must be nonempty",
354 ));
355 }
356 names.push(text);
357 }
358 Ok(Some(names))
359 }
360 Value::StringArray(sa) => Ok(Some(sa.data.clone())),
361 Value::CharArray(ca) => {
362 if ca.rows == 0 {
363 return Ok(Some(Vec::new()));
364 }
365 let mut names = Vec::with_capacity(ca.rows);
366 for row in 0..ca.rows {
367 let start = row * ca.cols;
368 let end = start + ca.cols;
369 let mut text: String = ca.data[start..end].iter().collect();
370 while text.ends_with(' ') {
371 text.pop();
372 }
373 if text.is_empty() {
374 return Err(orderfields_flow(
375 MESSAGE_ID_EMPTY_FIELD_NAME,
376 "orderfields: field names must be nonempty",
377 ));
378 }
379 names.push(text);
380 }
381 Ok(Some(names))
382 }
383 _ => Ok(None),
384 }
385}
386
387fn extract_indices(current: &[String], arg: &Value) -> BuiltinResult<Option<Vec<String>>> {
388 let Value::Tensor(tensor) = arg else {
389 return Ok(None);
390 };
391 if tensor.data.is_empty() && current.is_empty() {
392 return Ok(Some(Vec::new()));
393 }
394 if tensor.data.len() != current.len() {
395 return Err(orderfields_flow(
396 MESSAGE_ID_INVALID_PERMUTATION,
397 "orderfields: index vector must permute every field exactly once",
398 ));
399 }
400 let mut seen = HashSet::with_capacity(current.len());
401 let mut order = Vec::with_capacity(current.len());
402 for value in &tensor.data {
403 if !value.is_finite() || value.fract() != 0.0 {
404 return Err(orderfields_flow(
405 MESSAGE_ID_INDEX_NOT_INTEGER,
406 "orderfields: index vector must contain integers",
407 ));
408 }
409 let idx = *value as isize;
410 if idx < 1 || idx as usize > current.len() {
411 return Err(orderfields_flow(
412 MESSAGE_ID_INDEX_OUT_OF_RANGE,
413 "orderfields: index vector element out of range",
414 ));
415 }
416 let zero_based = (idx as usize) - 1;
417 if !seen.insert(zero_based) {
418 return Err(orderfields_flow(
419 MESSAGE_ID_INDEX_DUPLICATE,
420 "orderfields: index vector contains duplicate positions",
421 ));
422 }
423 order.push(current[zero_based].clone());
424 }
425 Ok(Some(order))
426}
427
428fn ensure_same_field_set(order: &[String], original: &StructValue) -> BuiltinResult<()> {
429 if order.len() != original.fields.len() {
430 return Err(orderfields_flow(
431 MESSAGE_ID_FIELD_MISMATCH,
432 "orderfields: field names must match the struct exactly",
433 ));
434 }
435 let mut seen = HashSet::with_capacity(order.len());
436 let original_set: HashSet<&str> = original.field_names().map(|s| s.as_str()).collect();
437 for name in order {
438 if !original_set.contains(name.as_str()) {
439 return Err(orderfields_flow(
440 MESSAGE_ID_UNKNOWN_FIELD,
441 format!("orderfields: unknown field '{name}' in requested order"),
442 ));
443 }
444 if !seen.insert(name.as_str()) {
445 return Err(orderfields_flow(
446 MESSAGE_ID_DUPLICATE_FIELD,
447 format!("orderfields: duplicate field '{name}' in requested order"),
448 ));
449 }
450 }
451 Ok(())
452}
453
454fn extract_struct_from_cell(cell: &CellArray, index: usize) -> BuiltinResult<StructValue> {
455 let value = unsafe { &*cell.data[index].as_raw() };
456 match value {
457 Value::Struct(st) => Ok(st.clone()),
458 other => Err(orderfields_flow(
459 MESSAGE_ID_INVALID_STRUCT_CONTENTS,
460 format!("orderfields: expected struct array contents to be structs (found {other:?})"),
461 )),
462 }
463}
464
465fn scalar_string(value: &Value) -> Option<String> {
466 match value {
467 Value::String(s) => Some(s.clone()),
468 Value::StringArray(sa) if sa.data.len() == 1 => Some(sa.data[0].clone()),
469 Value::CharArray(ca) if ca.rows == 1 => {
470 let mut text: String = ca.data.iter().collect();
471 while text.ends_with(' ') {
472 text.pop();
473 }
474 Some(text)
475 }
476 _ => None,
477 }
478}
479
480fn missing_field(name: &str) -> RuntimeError {
481 orderfields_flow(
482 MESSAGE_ID_MISSING_FIELD,
483 format!("orderfields: field '{name}' does not exist on the struct"),
484 )
485}
486
487#[cfg(test)]
488pub(crate) mod tests {
489 use super::*;
490 use futures::executor::block_on;
491 use runmat_builtins::{CellArray, CharArray, StringArray, Tensor};
492
493 fn run_orderfields(value: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
494 block_on(super::orderfields_builtin(value, rest))
495 }
496
497 fn assert_error_identifier(error: RuntimeError, expected: &str) {
498 assert_eq!(error.identifier(), Some(expected));
499 }
500
501 fn field_order(struct_value: &StructValue) -> Vec<String> {
502 struct_value.field_names().cloned().collect()
503 }
504
505 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
506 #[test]
507 fn default_sorts_alphabetically() {
508 let mut st = StructValue::new();
509 st.fields.insert("beta".to_string(), Value::Num(2.0));
510 st.fields.insert("alpha".to_string(), Value::Num(1.0));
511 st.fields.insert("gamma".to_string(), Value::Num(3.0));
512
513 let result = run_orderfields(Value::Struct(st), Vec::new()).expect("orderfields");
514 let Value::Struct(sorted) = result else {
515 panic!("expected struct result");
516 };
517 assert_eq!(
518 field_order(&sorted),
519 vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()]
520 );
521 }
522
523 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
524 #[test]
525 fn reorder_with_cell_name_list() {
526 let mut st = StructValue::new();
527 st.fields.insert("a".to_string(), Value::Num(1.0));
528 st.fields.insert("b".to_string(), Value::Num(2.0));
529 st.fields.insert("c".to_string(), Value::Num(3.0));
530 let names = CellArray::new(
531 vec![Value::from("c"), Value::from("a"), Value::from("b")],
532 1,
533 3,
534 )
535 .expect("cell");
536
537 let reordered =
538 run_orderfields(Value::Struct(st), vec![Value::Cell(names)]).expect("orderfields");
539 let Value::Struct(result) = reordered else {
540 panic!("expected struct result");
541 };
542 assert_eq!(
543 field_order(&result),
544 vec!["c".to_string(), "a".to_string(), "b".to_string()]
545 );
546 }
547
548 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
549 #[test]
550 fn reorder_with_string_array_names() {
551 let mut st = StructValue::new();
552 st.fields.insert("alpha".to_string(), Value::Num(1.0));
553 st.fields.insert("beta".to_string(), Value::Num(2.0));
554 st.fields.insert("gamma".to_string(), Value::Num(3.0));
555
556 let strings = StringArray::new(
557 vec!["gamma".into(), "alpha".into(), "beta".into()],
558 vec![1, 3],
559 )
560 .expect("string array");
561
562 let result = run_orderfields(Value::Struct(st), vec![Value::StringArray(strings)])
563 .expect("orderfields");
564 let Value::Struct(sorted) = result else {
565 panic!("expected struct result");
566 };
567 assert_eq!(
568 field_order(&sorted),
569 vec!["gamma".to_string(), "alpha".to_string(), "beta".to_string()]
570 );
571 }
572
573 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
574 #[test]
575 fn reorder_with_char_array_names() {
576 let mut st = StructValue::new();
577 st.fields.insert("cat".to_string(), Value::Num(1.0));
578 st.fields.insert("ant".to_string(), Value::Num(2.0));
579 st.fields.insert("bat".to_string(), Value::Num(3.0));
580
581 let data = vec!['b', 'a', 't', 'c', 'a', 't', 'a', 'n', 't'];
582 let char_array = CharArray::new(data, 3, 3).expect("char array");
583
584 let result =
585 run_orderfields(Value::Struct(st), vec![Value::CharArray(char_array)]).expect("order");
586 let Value::Struct(sorted) = result else {
587 panic!("expected struct result");
588 };
589 assert_eq!(
590 field_order(&sorted),
591 vec!["bat".to_string(), "cat".to_string(), "ant".to_string()]
592 );
593 }
594
595 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
596 #[test]
597 fn reorder_with_reference_struct() {
598 let mut source = StructValue::new();
599 source.fields.insert("y".to_string(), Value::Num(2.0));
600 source.fields.insert("x".to_string(), Value::Num(1.0));
601
602 let mut reference = StructValue::new();
603 reference.fields.insert("x".to_string(), Value::Num(0.0));
604 reference.fields.insert("y".to_string(), Value::Num(0.0));
605
606 let result = run_orderfields(
607 Value::Struct(source),
608 vec![Value::Struct(reference.clone())],
609 )
610 .expect("orderfields");
611 let Value::Struct(reordered) = result else {
612 panic!("expected struct result");
613 };
614 assert_eq!(
615 field_order(&reordered),
616 vec!["x".to_string(), "y".to_string()]
617 );
618 }
619
620 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
621 #[test]
622 fn reorder_with_index_vector() {
623 let mut st = StructValue::new();
624 st.fields.insert("first".to_string(), Value::Num(1.0));
625 st.fields.insert("second".to_string(), Value::Num(2.0));
626 st.fields.insert("third".to_string(), Value::Num(3.0));
627
628 let permutation = Tensor::new(vec![3.0, 1.0, 2.0], vec![1, 3]).expect("tensor permutation");
629 let result = run_orderfields(Value::Struct(st), vec![Value::Tensor(permutation)])
630 .expect("orderfields");
631 let Value::Struct(reordered) = result else {
632 panic!("expected struct result");
633 };
634 assert_eq!(
635 field_order(&reordered),
636 vec![
637 "third".to_string(),
638 "first".to_string(),
639 "second".to_string()
640 ]
641 );
642 }
643
644 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
645 #[test]
646 fn index_vector_must_be_integers() {
647 let mut st = StructValue::new();
648 st.fields.insert("one".to_string(), Value::Num(1.0));
649 st.fields.insert("two".to_string(), Value::Num(2.0));
650
651 let permutation = Tensor::new(vec![1.0, 1.5], vec![1, 2]).expect("tensor");
652 let err = run_orderfields(Value::Struct(st), vec![Value::Tensor(permutation)]).unwrap_err();
653 assert_error_identifier(err, MESSAGE_ID_INDEX_NOT_INTEGER);
654 }
655
656 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
657 #[test]
658 fn permutation_vector_matches_original_positions() {
659 let mut st = StructValue::new();
660 st.fields.insert("beta".to_string(), Value::Num(2.0));
661 st.fields.insert("alpha".to_string(), Value::Num(1.0));
662 st.fields.insert("gamma".to_string(), Value::Num(3.0));
663
664 let eval = evaluate(Value::Struct(st), &[]).expect("evaluate");
665 let perm = eval.permutation_value();
666 match perm {
667 Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 1.0, 3.0]),
668 other => panic!("expected tensor permutation, got {other:?}"),
669 }
670 let Value::Struct(ordered) = eval.into_ordered_value() else {
671 panic!("expected struct result");
672 };
673 assert_eq!(
674 field_order(&ordered),
675 vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()]
676 );
677 }
678
679 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
680 #[test]
681 fn reorder_struct_array() {
682 let mut first = StructValue::new();
683 first.fields.insert("b".to_string(), Value::Num(1.0));
684 first.fields.insert("a".to_string(), Value::Num(2.0));
685 let mut second = StructValue::new();
686 second.fields.insert("b".to_string(), Value::Num(3.0));
687 second.fields.insert("a".to_string(), Value::Num(4.0));
688 let array = CellArray::new_with_shape(
689 vec![Value::Struct(first), Value::Struct(second)],
690 vec![1, 2],
691 )
692 .expect("struct array");
693 let names =
694 CellArray::new(vec![Value::from("a"), Value::from("b")], 1, 2).expect("cell names");
695
696 let result =
697 run_orderfields(Value::Cell(array), vec![Value::Cell(names)]).expect("orderfields");
698 let Value::Cell(reordered) = result else {
699 panic!("expected cell array");
700 };
701 for handle in &reordered.data {
702 let Value::Struct(st) = (unsafe { &*handle.as_raw() }) else {
703 panic!("expected struct element");
704 };
705 assert_eq!(field_order(st), vec!["a".to_string(), "b".to_string()]);
706 }
707 }
708
709 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
710 #[test]
711 fn struct_array_permutation_reuses_order() {
712 let mut first = StructValue::new();
713 first.fields.insert("z".to_string(), Value::Num(1.0));
714 first.fields.insert("x".to_string(), Value::Num(2.0));
715 first.fields.insert("y".to_string(), Value::Num(3.0));
716
717 let mut second = StructValue::new();
718 second.fields.insert("z".to_string(), Value::Num(4.0));
719 second.fields.insert("x".to_string(), Value::Num(5.0));
720 second.fields.insert("y".to_string(), Value::Num(6.0));
721
722 let array = CellArray::new_with_shape(
723 vec![Value::Struct(first), Value::Struct(second)],
724 vec![1, 2],
725 )
726 .expect("struct array");
727
728 let eval = evaluate(Value::Cell(array), &[]).expect("evaluate");
729 let perm = eval.permutation_value();
730 match perm {
731 Value::Tensor(t) => assert_eq!(t.data, vec![2.0, 3.0, 1.0]),
732 other => panic!("expected tensor permutation, got {other:?}"),
733 }
734 }
735
736 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
737 #[test]
738 fn rejects_unknown_field() {
739 let mut st = StructValue::new();
740 st.fields.insert("alpha".to_string(), Value::Num(1.0));
741 st.fields.insert("beta".to_string(), Value::Num(2.0));
742 let err = run_orderfields(
743 Value::Struct(st),
744 vec![Value::Cell(
745 CellArray::new(vec![Value::from("beta"), Value::from("gamma")], 1, 2)
746 .expect("cell"),
747 )],
748 )
749 .unwrap_err();
750 assert_error_identifier(err, MESSAGE_ID_UNKNOWN_FIELD);
751 }
752
753 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
754 #[test]
755 fn duplicate_field_names_rejected() {
756 let mut st = StructValue::new();
757 st.fields.insert("alpha".to_string(), Value::Num(1.0));
758 st.fields.insert("beta".to_string(), Value::Num(2.0));
759
760 let names =
761 CellArray::new(vec![Value::from("alpha"), Value::from("alpha")], 1, 2).expect("cell");
762 let err = run_orderfields(Value::Struct(st), vec![Value::Cell(names)]).unwrap_err();
763 assert_error_identifier(err, MESSAGE_ID_DUPLICATE_FIELD);
764 }
765
766 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
767 #[test]
768 fn reference_struct_mismatch_errors() {
769 let mut source = StructValue::new();
770 source.fields.insert("x".to_string(), Value::Num(1.0));
771 source.fields.insert("y".to_string(), Value::Num(2.0));
772
773 let mut reference = StructValue::new();
774 reference.fields.insert("x".to_string(), Value::Num(0.0));
775
776 let err =
777 run_orderfields(Value::Struct(source), vec![Value::Struct(reference)]).unwrap_err();
778 assert_error_identifier(err, MESSAGE_ID_FIELD_MISMATCH);
779 }
780
781 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
782 #[test]
783 fn invalid_order_argument_type_errors() {
784 let mut st = StructValue::new();
785 st.fields.insert("x".to_string(), Value::Num(1.0));
786
787 let err = run_orderfields(Value::Struct(st), vec![Value::Num(1.0)]).unwrap_err();
788 assert_error_identifier(err, MESSAGE_ID_INVALID_ORDER_ARGUMENT);
789 }
790
791 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
792 #[test]
793 fn empty_struct_array_nonempty_reference_errors() {
794 let empty = CellArray::new(Vec::new(), 0, 0).expect("empty struct array");
795 let mut reference = StructValue::new();
796 reference
797 .fields
798 .insert("field".to_string(), Value::Num(1.0));
799
800 let err = run_orderfields(Value::Cell(empty), vec![Value::Struct(reference)]).unwrap_err();
801 assert_error_identifier(err, MESSAGE_ID_EMPTY_STRUCT_ARRAY);
802 }
803}