Rust · 22991 bytes Raw Blame History
1 //! Fortran character string operations.
2 //!
3 //! This is where gfortran corrupts memory on ARM64. Our implementation
4 //! follows one critical invariant: **always allocate new before freeing old**
5 //! for deferred-length assignment. This prevents use-after-free when the
6 //! source overlaps the destination (e.g., s = s(2:) // s(1:1)).
7 //!
8 //! Descriptors are always in memory (stack slots), never in registers.
9 //! The register allocator only puts the descriptor *pointer* in a register.
10
11 use crate::descriptor::*;
12 use std::ptr;
13
14 extern "C" {
15 fn malloc(size: usize) -> *mut u8;
16 fn free(ptr: *mut u8);
17 }
18
19 // ---- Fixed-length character assignment ----
20
21 /// Assign a source string to a fixed-length destination with space padding.
22 /// If src is shorter, remainder is padded with spaces.
23 /// If src is longer, it is truncated.
24 #[no_mangle]
25 pub extern "C" fn afs_assign_char_fixed(
26 dest: *mut u8,
27 dest_len: i64,
28 src: *const u8,
29 src_len: i64,
30 ) {
31 if dest.is_null() || dest_len <= 0 {
32 return;
33 }
34 let copy_len = src_len.min(dest_len) as usize;
35 unsafe {
36 if !src.is_null() && copy_len > 0 {
37 // Use ptr::copy (not copy_nonoverlapping) to handle overlapping
38 // src/dest (e.g., s = s(2:) for fixed-length self-assignment).
39 ptr::copy(src, dest, copy_len);
40 }
41 // Pad remainder with spaces.
42 let pad_len = (dest_len as usize).saturating_sub(copy_len);
43 if pad_len > 0 {
44 ptr::write_bytes(dest.add(copy_len), b' ', pad_len);
45 }
46 }
47 }
48
49 // ---- Deferred-length character assignment (THE CRITICAL PATH) ----
50
51 /// Assign to a deferred-length character variable.
52 /// **Key safety property**: allocates new memory BEFORE freeing old.
53 /// This prevents use-after-free when src points into dest's buffer
54 /// (e.g., s = s(2:5) or s = s // 'more').
55 #[no_mangle]
56 pub extern "C" fn afs_assign_char_deferred(
57 desc: *mut StringDescriptor,
58 src: *const u8,
59 src_len: i64,
60 ) {
61 if desc.is_null() {
62 return;
63 }
64 let desc = unsafe { &mut *desc };
65
66 if src_len <= 0 {
67 desc.len = 0;
68 desc.flags = STR_ALLOCATED | STR_DEFERRED;
69 if !desc.is_allocated() || desc.data.is_null() {
70 desc.data = ptr::null_mut();
71 desc.capacity = 0;
72 }
73 return;
74 }
75
76 // CRITICAL: Allocate new buffer BEFORE freeing old.
77 // This handles self-referential assignment (s = s(2:) // 'x').
78 let needs_realloc = src_len > desc.capacity || desc.data.is_null();
79
80 if needs_realloc {
81 let new_data = unsafe { malloc(src_len as usize) };
82 if new_data.is_null() {
83 eprintln!("character assignment: out of memory ({} bytes)", src_len);
84 std::process::exit(1);
85 }
86
87 // Copy source to new buffer. Use `ptr::copy` rather than
88 // `copy_nonoverlapping`: the runtime intentionally accepts
89 // self-referential/alias-heavy deferred-length assignment
90 // shapes, and some compiler paths can still route an
91 // overlapping slice through the reallocating branch.
92 if !src.is_null() {
93 unsafe {
94 ptr::copy(src, new_data, src_len as usize);
95 }
96 }
97
98 // NOW free old buffer (after copy is complete).
99 if desc.is_allocated() && !desc.data.is_null() {
100 unsafe {
101 free(desc.data);
102 }
103 }
104
105 desc.data = new_data;
106 desc.capacity = src_len;
107 } else {
108 // Fits in existing buffer. Use ptr::copy (not copy_nonoverlapping)
109 // in case src overlaps with dest (e.g., s = s(2:)).
110 if !src.is_null() {
111 unsafe {
112 ptr::copy(src, desc.data, src_len as usize);
113 }
114 }
115 }
116
117 desc.len = src_len;
118 desc.flags |= STR_ALLOCATED | STR_DEFERRED;
119 }
120
121 /// Deallocate a deferred-length string descriptor.
122 #[no_mangle]
123 pub extern "C" fn afs_dealloc_string(desc: *mut StringDescriptor) {
124 if desc.is_null() {
125 return;
126 }
127 let desc = unsafe { &mut *desc };
128 if desc.is_allocated() && !desc.data.is_null() {
129 unsafe {
130 free(desc.data);
131 }
132 }
133 desc.data = ptr::null_mut();
134 desc.len = 0;
135 desc.capacity = 0;
136 // Preserve STR_DEFERRED — the variable is still character(:), allocatable
137 // after DEALLOCATE. Clear only STR_ALLOCATED.
138 desc.flags &= STR_DEFERRED; // keep deferred, clear everything else
139 }
140
141 /// ALLOCATED(s) for deferred-length character descriptors.
142 #[no_mangle]
143 pub extern "C" fn afs_string_allocated(desc: *const StringDescriptor) -> i32 {
144 if desc.is_null() {
145 return 0;
146 }
147 unsafe { (*desc).is_allocated() as i32 }
148 }
149
150 /// Transfer allocation from `from` to `to` (F2003 MOVE_ALLOC).
151 ///
152 /// `to` is deallocated if allocated, then receives `from`'s descriptor.
153 /// `from` is cleared back to an unallocated deferred-length descriptor.
154 #[no_mangle]
155 pub extern "C" fn afs_move_alloc_string(from: *mut StringDescriptor, to: *mut StringDescriptor) {
156 if from.is_null() || to.is_null() {
157 return;
158 }
159
160 let from_desc = unsafe { &mut *from };
161 let to_desc = unsafe { &mut *to };
162
163 if to_desc.is_allocated() && !to_desc.data.is_null() {
164 unsafe {
165 free(to_desc.data);
166 }
167 }
168
169 *to_desc = from_desc.clone();
170
171 from_desc.data = ptr::null_mut();
172 from_desc.len = 0;
173 from_desc.capacity = 0;
174 from_desc.flags &= STR_DEFERRED;
175 }
176
177 // ---- Concatenation ----
178
179 /// Concatenate two strings into a pre-allocated result buffer.
180 /// The caller must ensure result has at least a_len + b_len bytes.
181 #[no_mangle]
182 pub extern "C" fn afs_concat(result: *mut u8, a: *const u8, a_len: i64, b: *const u8, b_len: i64) {
183 if result.is_null() {
184 return;
185 }
186 unsafe {
187 if !a.is_null() && a_len > 0 {
188 // Some compiler paths still materialize concatenation into a
189 // buffer that aliases one of the source slices. `ptr::copy`
190 // tolerates overlap and keeps the runtime from tripping Rust's
191 // UB guards on otherwise valid self-referential Fortran shapes.
192 ptr::copy(a, result, a_len as usize);
193 }
194 if !b.is_null() && b_len > 0 {
195 ptr::copy(b, result.add(a_len as usize), b_len as usize);
196 }
197 }
198 }
199
200 // ---- Comparison ----
201
202 /// Compare two Fortran character strings.
203 /// Shorter string is padded with spaces for comparison (Fortran standard).
204 /// Returns: -1 if a < b, 0 if a == b, 1 if a > b.
205 #[no_mangle]
206 pub extern "C" fn afs_compare_char(a: *const u8, a_len: i64, b: *const u8, b_len: i64) -> i32 {
207 let max_len = a_len.max(b_len) as usize;
208 for i in 0..max_len {
209 let ac = if i < a_len as usize && !a.is_null() {
210 unsafe { *a.add(i) }
211 } else {
212 b' '
213 };
214 let bc = if i < b_len as usize && !b.is_null() {
215 unsafe { *b.add(i) }
216 } else {
217 b' '
218 };
219 if ac < bc {
220 return -1;
221 }
222 if ac > bc {
223 return 1;
224 }
225 }
226 0
227 }
228
229 // ---- String intrinsics ----
230
231 /// TRIM: return length of string without trailing spaces.
232 /// The data pointer is unchanged (TRIM returns a view, not a copy).
233 #[no_mangle]
234 pub extern "C" fn afs_len_trim(src: *const u8, src_len: i64) -> i64 {
235 if src.is_null() || src_len <= 0 {
236 return 0;
237 }
238 let slice = unsafe { std::slice::from_raw_parts(src, src_len as usize) };
239 let trimmed = slice
240 .iter()
241 .rposition(|&b| b != b' ')
242 .map(|pos| pos + 1)
243 .unwrap_or(0);
244 trimmed as i64
245 }
246
247 /// ADJUSTL: left-justify by removing leading spaces, padding trailing.
248 #[no_mangle]
249 pub extern "C" fn afs_adjustl(dest: *mut u8, src: *const u8, len: i64) {
250 if dest.is_null() || src.is_null() || len <= 0 {
251 return;
252 }
253 let slice = unsafe { std::slice::from_raw_parts(src, len as usize) };
254 let leading = slice
255 .iter()
256 .position(|&b| b != b' ')
257 .unwrap_or(len as usize);
258 let content_len = (len as usize) - leading;
259 unsafe {
260 if content_len > 0 {
261 ptr::copy(src.add(leading), dest, content_len);
262 }
263 if leading > 0 {
264 ptr::write_bytes(dest.add(content_len), b' ', leading);
265 }
266 }
267 }
268
269 /// ADJUSTR: right-justify by removing trailing spaces, padding leading.
270 #[no_mangle]
271 pub extern "C" fn afs_adjustr(dest: *mut u8, src: *const u8, len: i64) {
272 if dest.is_null() || src.is_null() || len <= 0 {
273 return;
274 }
275 let slice = unsafe { std::slice::from_raw_parts(src, len as usize) };
276 let trailing = slice
277 .iter()
278 .rposition(|&b| b != b' ')
279 .map(|pos| (len as usize) - pos - 1)
280 .unwrap_or(len as usize);
281 let content_len = (len as usize) - trailing;
282 unsafe {
283 if trailing > 0 {
284 ptr::write_bytes(dest, b' ', trailing);
285 }
286 if content_len > 0 {
287 ptr::copy(src, dest.add(trailing), content_len);
288 }
289 }
290 }
291
292 /// INDEX: find first (or last if back=1) occurrence of substring.
293 /// Returns 1-based position, or 0 if not found.
294 #[no_mangle]
295 pub extern "C" fn afs_c_strlen(src: *const u8) -> i64 {
296 if src.is_null() {
297 return 0;
298 }
299 let mut len: i64 = 0;
300 unsafe {
301 while *src.add(len as usize) != 0 {
302 len += 1;
303 }
304 }
305 len
306 }
307
308 /// INDEX: find first (or last if back=1) occurrence of substring.
309 /// Returns 1-based position, or 0 if not found.
310 #[no_mangle]
311 pub extern "C" fn afs_index(
312 str_ptr: *const u8,
313 str_len: i64,
314 sub_ptr: *const u8,
315 sub_len: i64,
316 back: i32,
317 ) -> i64 {
318 if str_ptr.is_null() || sub_ptr.is_null() || str_len <= 0 {
319 return 0;
320 }
321 if sub_len <= 0 {
322 return if back != 0 { str_len + 1 } else { 1 };
323 }
324 if sub_len > str_len {
325 return 0;
326 }
327
328 let haystack = unsafe { std::slice::from_raw_parts(str_ptr, str_len as usize) };
329 let needle = unsafe { std::slice::from_raw_parts(sub_ptr, sub_len as usize) };
330
331 if back != 0 {
332 // Search from right.
333 for i in (0..=(str_len - sub_len) as usize).rev() {
334 if &haystack[i..i + needle.len()] == needle {
335 return (i + 1) as i64; // 1-based
336 }
337 }
338 } else {
339 for i in 0..=(str_len - sub_len) as usize {
340 if &haystack[i..i + needle.len()] == needle {
341 return (i + 1) as i64;
342 }
343 }
344 }
345 0
346 }
347
348 /// SCAN: find first (or last) character from set in string.
349 /// Returns 1-based position, or 0 if not found.
350 #[no_mangle]
351 pub extern "C" fn afs_scan(
352 str_ptr: *const u8,
353 str_len: i64,
354 set_ptr: *const u8,
355 set_len: i64,
356 back: i32,
357 ) -> i64 {
358 if str_ptr.is_null() || set_ptr.is_null() || str_len <= 0 || set_len <= 0 {
359 return 0;
360 }
361 let s = unsafe { std::slice::from_raw_parts(str_ptr, str_len as usize) };
362 let set = unsafe { std::slice::from_raw_parts(set_ptr, set_len as usize) };
363
364 if back != 0 {
365 for (i, &c) in s.iter().enumerate().rev() {
366 if set.contains(&c) {
367 return (i + 1) as i64;
368 }
369 }
370 } else {
371 for (i, &c) in s.iter().enumerate() {
372 if set.contains(&c) {
373 return (i + 1) as i64;
374 }
375 }
376 }
377 0
378 }
379
380 /// VERIFY: find first (or last) character NOT in set.
381 /// Returns 1-based position, or 0 if all characters are in set.
382 #[no_mangle]
383 pub extern "C" fn afs_verify(
384 str_ptr: *const u8,
385 str_len: i64,
386 set_ptr: *const u8,
387 set_len: i64,
388 back: i32,
389 ) -> i64 {
390 if str_ptr.is_null() || str_len <= 0 {
391 return 0;
392 }
393 let s = unsafe { std::slice::from_raw_parts(str_ptr, str_len as usize) };
394 let set = if !set_ptr.is_null() && set_len > 0 {
395 unsafe { std::slice::from_raw_parts(set_ptr, set_len as usize) }
396 } else {
397 &[]
398 };
399
400 if back != 0 {
401 for (i, &c) in s.iter().enumerate().rev() {
402 if !set.contains(&c) {
403 return (i + 1) as i64;
404 }
405 }
406 } else {
407 for (i, &c) in s.iter().enumerate() {
408 if !set.contains(&c) {
409 return (i + 1) as i64;
410 }
411 }
412 }
413 0
414 }
415
416 /// REPEAT: repeat string ncopies times into dest.
417 /// Dest must have at least src_len * ncopies bytes.
418 #[no_mangle]
419 pub extern "C" fn afs_repeat(src: *const u8, src_len: i64, ncopies: i64, dest: *mut u8) {
420 if dest.is_null() || src.is_null() || src_len <= 0 || ncopies <= 0 {
421 return;
422 }
423 unsafe {
424 for i in 0..ncopies as usize {
425 ptr::copy_nonoverlapping(src, dest.add(i * src_len as usize), src_len as usize);
426 }
427 }
428 }
429
430 /// CHAR: integer to character (single byte).
431 #[no_mangle]
432 pub extern "C" fn afs_char(i: i32) -> u8 {
433 (i & 0xFF) as u8
434 }
435
436 /// ICHAR: character to integer.
437 #[no_mangle]
438 pub extern "C" fn afs_ichar(c: u8) -> i32 {
439 c as i32
440 }
441
442 /// ICHAR from an addressable character byte.
443 #[no_mangle]
444 pub extern "C" fn afs_ichar_ptr(c: *const u8) -> i32 {
445 if c.is_null() {
446 return 0;
447 }
448 unsafe { *c as i32 }
449 }
450
451 /// LGE: lexicographic greater-than-or-equal (ASCII collating sequence).
452 #[no_mangle]
453 pub extern "C" fn afs_lge(a: *const u8, a_len: i64, b: *const u8, b_len: i64) -> i32 {
454 (afs_compare_char(a, a_len, b, b_len) >= 0) as i32
455 }
456
457 /// LGT: lexicographic greater-than.
458 #[no_mangle]
459 pub extern "C" fn afs_lgt(a: *const u8, a_len: i64, b: *const u8, b_len: i64) -> i32 {
460 (afs_compare_char(a, a_len, b, b_len) > 0) as i32
461 }
462
463 /// LLE: lexicographic less-than-or-equal.
464 #[no_mangle]
465 pub extern "C" fn afs_lle(a: *const u8, a_len: i64, b: *const u8, b_len: i64) -> i32 {
466 (afs_compare_char(a, a_len, b, b_len) <= 0) as i32
467 }
468
469 /// LLT: lexicographic less-than.
470 #[no_mangle]
471 pub extern "C" fn afs_llt(a: *const u8, a_len: i64, b: *const u8, b_len: i64) -> i32 {
472 (afs_compare_char(a, a_len, b, b_len) < 0) as i32
473 }
474
475 #[cfg(test)]
476 mod tests {
477 use super::*;
478
479 // ---- Fixed-length assignment ----
480
481 #[test]
482 fn fixed_assign_shorter_src() {
483 let mut dest = [0u8; 10];
484 let src = b"hello";
485 afs_assign_char_fixed(dest.as_mut_ptr(), 10, src.as_ptr(), 5);
486 assert_eq!(&dest, b"hello ");
487 }
488
489 #[test]
490 fn fixed_assign_longer_src() {
491 let mut dest = [0u8; 5];
492 let src = b"hello world";
493 afs_assign_char_fixed(dest.as_mut_ptr(), 5, src.as_ptr(), 11);
494 assert_eq!(&dest, b"hello");
495 }
496
497 #[test]
498 fn fixed_assign_exact_length() {
499 let mut dest = [0u8; 5];
500 let src = b"hello";
501 afs_assign_char_fixed(dest.as_mut_ptr(), 5, src.as_ptr(), 5);
502 assert_eq!(&dest, b"hello");
503 }
504
505 // ---- Deferred-length assignment (THE CRITICAL TESTS) ----
506
507 #[test]
508 fn deferred_assign_initial() {
509 let mut desc = StringDescriptor::zeroed();
510 let src = b"hello";
511 afs_assign_char_deferred(&mut desc, src.as_ptr(), 5);
512 assert!(desc.is_allocated());
513 assert_eq!(desc.len, 5);
514 let data = unsafe { std::slice::from_raw_parts(desc.data, 5) };
515 assert_eq!(data, b"hello");
516 afs_dealloc_string(&mut desc);
517 }
518
519 #[test]
520 fn deferred_assign_realloc_larger() {
521 let mut desc = StringDescriptor::zeroed();
522 afs_assign_char_deferred(&mut desc, b"hi".as_ptr(), 2);
523 assert_eq!(desc.len, 2);
524 afs_assign_char_deferred(&mut desc, b"hello world".as_ptr(), 11);
525 assert_eq!(desc.len, 11);
526 assert!(desc.capacity >= 11);
527 let data = unsafe { std::slice::from_raw_parts(desc.data, 11) };
528 assert_eq!(data, b"hello world");
529 afs_dealloc_string(&mut desc);
530 }
531
532 #[test]
533 fn deferred_assign_fits_in_existing() {
534 let mut desc = StringDescriptor::zeroed();
535 afs_assign_char_deferred(&mut desc, b"hello world".as_ptr(), 11);
536 let old_ptr = desc.data;
537 afs_assign_char_deferred(&mut desc, b"hi".as_ptr(), 2);
538 // Should reuse buffer (capacity >= 2).
539 assert_eq!(desc.data, old_ptr); // same buffer
540 assert_eq!(desc.len, 2);
541 let data = unsafe { std::slice::from_raw_parts(desc.data, 2) };
542 assert_eq!(data, b"hi");
543 afs_dealloc_string(&mut desc);
544 }
545
546 #[test]
547 fn deferred_assign_empty() {
548 let mut desc = StringDescriptor::zeroed();
549 afs_assign_char_deferred(&mut desc, b"hello".as_ptr(), 5);
550 let old_data = desc.data;
551 let old_capacity = desc.capacity;
552 afs_assign_char_deferred(&mut desc, ptr::null(), 0);
553 assert!(desc.is_allocated());
554 assert_eq!(afs_string_allocated(&desc), 1);
555 assert_eq!(desc.len, 0);
556 assert_eq!(desc.data, old_data);
557 assert_eq!(desc.capacity, old_capacity);
558 afs_dealloc_string(&mut desc);
559 assert_eq!(afs_string_allocated(&desc), 0);
560 }
561
562 #[test]
563 fn deferred_self_referential_safe() {
564 // s = s(2:5) — source points into dest's buffer.
565 let mut desc = StringDescriptor::zeroed();
566 afs_assign_char_deferred(&mut desc, b"hello world".as_ptr(), 11);
567
568 // Simulate s = s(2:5): source is desc.data + 1, len = 4.
569 let src_ptr = unsafe { desc.data.add(1) };
570 afs_assign_char_deferred(&mut desc, src_ptr, 4);
571
572 assert_eq!(desc.len, 4);
573 let data = unsafe { std::slice::from_raw_parts(desc.data, 4) };
574 assert_eq!(data, b"ello");
575 afs_dealloc_string(&mut desc);
576 }
577
578 #[test]
579 fn deferred_self_referential_realloc_safe() {
580 // Force the aliasing path through the "needs_realloc" branch by
581 // shrinking capacity metadata while keeping the backing buffer valid.
582 let mut desc = StringDescriptor::zeroed();
583 afs_assign_char_deferred(&mut desc, b"abcdef".as_ptr(), 6);
584 desc.capacity = 1;
585
586 let src_ptr = unsafe { desc.data.add(1) };
587 afs_assign_char_deferred(&mut desc, src_ptr, 4);
588
589 assert_eq!(desc.len, 4);
590 let data = unsafe { std::slice::from_raw_parts(desc.data, 4) };
591 assert_eq!(data, b"bcde");
592 afs_dealloc_string(&mut desc);
593 }
594
595 #[test]
596 fn move_alloc_transfers_deferred_string_storage() {
597 let mut from = StringDescriptor::zeroed();
598 let mut to = StringDescriptor::zeroed();
599
600 afs_assign_char_deferred(&mut from, b"hello".as_ptr(), 5);
601 afs_assign_char_deferred(&mut to, b"bye".as_ptr(), 3);
602
603 afs_move_alloc_string(&mut from, &mut to);
604
605 assert!(!from.is_allocated());
606 assert!(from.data.is_null());
607 assert_eq!(from.len, 0);
608 assert_eq!(from.capacity, 0);
609
610 assert!(to.is_allocated());
611 let data = unsafe { std::slice::from_raw_parts(to.data, 5) };
612 assert_eq!(data, b"hello");
613
614 afs_dealloc_string(&mut to);
615 }
616
617 #[test]
618 fn string_allocated_reflects_descriptor_state() {
619 let mut desc = StringDescriptor::zeroed();
620 assert_eq!(afs_string_allocated(&desc), 0);
621 afs_assign_char_deferred(&mut desc, b"abc".as_ptr(), 3);
622 assert_eq!(afs_string_allocated(&desc), 1);
623 afs_dealloc_string(&mut desc);
624 assert_eq!(afs_string_allocated(&desc), 0);
625 }
626
627 // ---- Concatenation ----
628
629 #[test]
630 fn concat_basic() {
631 let mut result = [0u8; 11];
632 afs_concat(
633 result.as_mut_ptr(),
634 b"hello".as_ptr(),
635 5,
636 b" world".as_ptr(),
637 6,
638 );
639 assert_eq!(&result, b"hello world");
640 }
641
642 #[test]
643 fn concat_tolerates_result_aliasing_lhs() {
644 let mut result = [0u8; 6];
645 result[..3].copy_from_slice(b"abc");
646 afs_concat(result.as_mut_ptr(), result.as_ptr(), 3, b"def".as_ptr(), 3);
647 assert_eq!(&result, b"abcdef");
648 }
649
650 // ---- Comparison ----
651
652 #[test]
653 fn compare_equal() {
654 assert_eq!(
655 afs_compare_char(b"hello".as_ptr(), 5, b"hello".as_ptr(), 5),
656 0
657 );
658 }
659
660 #[test]
661 fn compare_less() {
662 assert_eq!(afs_compare_char(b"abc".as_ptr(), 3, b"abd".as_ptr(), 3), -1);
663 }
664
665 #[test]
666 fn compare_greater() {
667 assert_eq!(afs_compare_char(b"abd".as_ptr(), 3, b"abc".as_ptr(), 3), 1);
668 }
669
670 #[test]
671 fn compare_with_padding() {
672 // "abc" vs "abc " — should be equal (space padding).
673 assert_eq!(
674 afs_compare_char(b"abc".as_ptr(), 3, b"abc ".as_ptr(), 6),
675 0
676 );
677 }
678
679 // ---- Intrinsics ----
680
681 #[test]
682 fn len_trim_basic() {
683 assert_eq!(afs_len_trim(b"hello ".as_ptr(), 8), 5);
684 assert_eq!(afs_len_trim(b" ".as_ptr(), 3), 0);
685 assert_eq!(afs_len_trim(b"hello".as_ptr(), 5), 5);
686 }
687
688 #[test]
689 fn c_strlen_basic() {
690 let s = b"scan.f90\0";
691 assert_eq!(afs_c_strlen(s.as_ptr()), 8);
692 }
693
694 #[test]
695 fn adjustl_basic() {
696 let mut dest = [0u8; 8];
697 afs_adjustl(dest.as_mut_ptr(), b" hello".as_ptr(), 8);
698 assert_eq!(&dest, b"hello ");
699 }
700
701 #[test]
702 fn adjustr_basic() {
703 let mut dest = [0u8; 8];
704 afs_adjustr(dest.as_mut_ptr(), b"hello ".as_ptr(), 8);
705 assert_eq!(&dest, b" hello");
706 }
707
708 #[test]
709 fn index_basic() {
710 assert_eq!(
711 afs_index(b"hello world".as_ptr(), 11, b"world".as_ptr(), 5, 0),
712 7
713 );
714 assert_eq!(
715 afs_index(b"hello world".as_ptr(), 11, b"xyz".as_ptr(), 3, 0),
716 0
717 );
718 }
719
720 #[test]
721 fn index_back() {
722 assert_eq!(afs_index(b"abcabc".as_ptr(), 6, b"abc".as_ptr(), 3, 1), 4);
723 }
724
725 #[test]
726 fn scan_basic() {
727 assert_eq!(afs_scan(b"hello".as_ptr(), 5, b"lo".as_ptr(), 2, 0), 3); // 'l' at pos 3
728 assert_eq!(afs_scan(b"hello".as_ptr(), 5, b"xyz".as_ptr(), 3, 0), 0);
729 }
730
731 #[test]
732 fn verify_basic() {
733 assert_eq!(afs_verify(b"aabba".as_ptr(), 5, b"ab".as_ptr(), 2, 0), 0); // all in set
734 assert_eq!(afs_verify(b"aabxba".as_ptr(), 6, b"ab".as_ptr(), 2, 0), 4); // 'x' at pos 4
735 }
736
737 #[test]
738 fn repeat_basic() {
739 let mut dest = [0u8; 15];
740 afs_repeat(b"abc".as_ptr(), 3, 5, dest.as_mut_ptr());
741 assert_eq!(&dest, b"abcabcabcabcabc");
742 }
743
744 #[test]
745 fn char_ichar() {
746 assert_eq!(afs_char(65), b'A');
747 assert_eq!(afs_ichar(b'A'), 65);
748 let byte = 0xFFu8;
749 assert_eq!(afs_ichar_ptr(&byte), 255);
750 }
751
752 #[test]
753 fn lge_lgt_lle_llt() {
754 assert_eq!(afs_lge(b"b".as_ptr(), 1, b"a".as_ptr(), 1), 1);
755 assert_eq!(afs_lgt(b"b".as_ptr(), 1, b"a".as_ptr(), 1), 1);
756 assert_eq!(afs_lle(b"a".as_ptr(), 1, b"b".as_ptr(), 1), 1);
757 assert_eq!(afs_llt(b"a".as_ptr(), 1, b"b".as_ptr(), 1), 1);
758 assert_eq!(afs_lge(b"a".as_ptr(), 1, b"a".as_ptr(), 1), 1); // equal → GE true
759 assert_eq!(afs_lgt(b"a".as_ptr(), 1, b"a".as_ptr(), 1), 0); // equal → GT false
760 }
761 }
762