1use crate::ported::utils::{adjustcolumns, adjustlines};
19use std::sync::atomic::Ordering;
20
21use crate::ported::builtin::{BREAKS, CONTFLAG, LASTVAL, LOOPS, RETFLAG};
25use crate::ported::exec::{execlist, execsubst, noerrexit, simple_pline, this_noerrexit};
26use crate::ported::lex::untokenize;
27use crate::ported::math::{matheval as wc_matheval, mathevali as wc_mathevali};
28use crate::ported::mem::{freeheap, popheap, pushheap};
29use crate::ported::params::setloopvar;
30use crate::ported::parse::{ecgetlist, ecgetstr, ecrawstr};
31use crate::ported::pattern::{patcompile, pattry};
32use crate::ported::prompt::{cmdpop, cmdpush};
33use crate::ported::signals::{queue_signals, unqueue_signals};
34use crate::ported::subst::singsub;
35use crate::ported::utils::{errflag, printprompt4, zerr, ERRFLAG_ERROR};
36use crate::ported::zsh_h::{
37 estate, isset, wc_code, CS_ALWAYS, CS_CASE, CS_CURSH, CS_ELIF, CS_ELIFTHEN, CS_ELSE, CS_FOR,
38 CS_IF, CS_IFTHEN, CS_REPEAT, CS_UNTIL, CS_WHILE, EC_DUP, EC_DUPTOK, EC_NODUP, ERRFLAG_INT,
39 NOERREXIT_EXIT, NOERREXIT_RETURN, PAT_STATIC, WC_CASE, WC_CASE_AND, WC_CASE_OR, WC_CASE_SKIP,
40 WC_CASE_TESTAND, WC_CASE_TYPE, WC_END, WC_FOR_COND, WC_FOR_LIST, WC_FOR_SKIP, WC_FOR_TYPE,
41 WC_IF, WC_IF_ELSE, WC_IF_SKIP, WC_IF_TYPE, WC_REPEAT_SKIP, WC_TRY_SKIP, WC_WHILE_SKIP,
42 WC_WHILE_TYPE, WC_WHILE_UNTIL,
43};
44use crate::zsh_h::XTRACE;
45use std::io::Write;
46use std::sync::atomic::AtomicI32;
47pub static try_tryflag: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0); pub static try_errflag: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1); pub static try_interrupt: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1); pub fn selectlist(items: &[&str], start: usize) -> usize {
154 let mut stderr = std::io::stderr().lock();
156
157 let ct = items.len(); if ct == 0 {
163 return 0;
165 }
166 let mut longest: usize = 1; for ap in items {
168 let aplen = ap.chars().count(); if aplen > longest {
174 longest = aplen; }
177 }
178 longest += 1; let mut t0 = ct; while t0 != 0 {
181 t0 /= 10; longest += 1; }
185
186 let zterm_columns = adjustcolumns(); let zterm_lines = adjustlines();
188 let mut fct: usize = (zterm_columns.saturating_sub(1)) / (longest + 3); let fw: usize;
190 if fct == 0 {
191 fct = 1; fw = 0; } else {
195 fw = (zterm_columns - 1) / fct; }
197 let colsz = (ct + fct - 1) / fct; let mut t1 = start;
208 let max_lines = zterm_lines.saturating_sub(2);
209 while t1 < colsz && (t1.saturating_sub(start)) < max_lines {
210 let mut idx = t1; if idx >= ct {
212 let _ = stderr.write_all(b"\n");
215 t1 += 1;
216 continue;
217 }
218 loop {
219 let entry = items[idx];
221 let mut t2 = entry.chars().count() + 2;
223 let t3 = idx + 1;
225 let _ = write!(stderr, "{}) {}", t3, entry);
226 let mut digits = t3;
228 while digits != 0 {
229 t2 += 1;
231 digits /= 10;
232 }
233 while t2 < fw {
235 let _ = stderr.write_all(b" ");
237 t2 += 1;
238 }
239 let mut t0 = colsz;
241 while t0 != 0 && idx + 1 < ct {
242 t0 -= 1;
243 idx += 1;
244 if t0 == 0 {
245 break;
246 }
247 }
248 if idx + 1 >= ct {
249 break;
250 } }
252 let _ = stderr.write_all(b"\n"); t1 += 1;
254 }
255
256 let _ = stderr.flush(); if t1 < colsz {
259 t1
260 } else {
261 0
262 } }
264
265static LOOP_DEPTH: AtomicI32 = AtomicI32::new(0);
274
275static CONT_FLAG: AtomicI32 = AtomicI32::new(0);
280
281static BREAK_LEVEL: AtomicI32 = AtomicI32::new(0);
286
287
288pub fn execfor(state: &mut estate, do_exec: i32) -> i32 {
303 use crate::ported::zsh_h::Z_END;
304 let code = state.prog.prog[state.pc.wrapping_sub(1)]; let iscond = WC_FOR_TYPE(code) == WC_FOR_COND; let mut last_iter = false; let mut val: i64 = 0; let mut vars: Vec<String> = Vec::new();
309 let mut args: Vec<String> = Vec::new();
310 let mut cond_expr: String = String::new();
311 let mut advance_expr: String = String::new();
312 let old_simple_pline = simple_pline.swap(1, Ordering::Relaxed); let end_pc = state.pc + WC_FOR_SKIP(code) as usize; let mut ctok = 0i32;
315 let mut atok = 0i32;
316 if iscond {
317 let init = ecgetstr(state, EC_NODUP, None); let init_sub = singsub(&init); if isset(XTRACE) {
321 let init_show = untokenize(&init_sub);
323 printprompt4();
324 eprintln!("{}", init_show);
325 }
326 if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
327 let _ = wc_matheval(&init_sub); }
329 if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
330 state.pc = end_pc;
332 simple_pline.store(old_simple_pline, Ordering::Relaxed);
333 return 1;
334 }
335 cond_expr = ecgetstr(state, EC_NODUP, Some(&mut ctok)); advance_expr = ecgetstr(state, EC_NODUP, Some(&mut atok)); } else {
338 let count = state.prog.prog[state.pc] as usize;
340 state.pc += 1;
341 vars = ecgetlist(state, count, EC_NODUP, None);
342 if WC_FOR_TYPE(code) == WC_FOR_LIST {
343 let mut htok = 0i32;
345 let arg_count = state.prog.prog[state.pc] as usize;
346 state.pc += 1;
347 args = ecgetlist(state, arg_count, EC_DUPTOK, Some(&mut htok));
348 if args.is_empty() {
349 state.pc = end_pc;
350 simple_pline.store(old_simple_pline, Ordering::Relaxed);
351 return 0;
352 }
353 if htok != 0 {
354 execsubst(&mut args); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
356 state.pc = end_pc;
357 simple_pline.store(old_simple_pline, Ordering::Relaxed);
358 return 1;
359 }
360 }
361 } else {
362 args = crate::ported::builtin::PPARAMS
365 .lock()
366 .map(|p| p.clone())
367 .unwrap_or_default();
368 }
369 }
370 if !iscond && args.is_empty() {
372 LASTVAL.store(0, Ordering::Relaxed);
373 }
374 LOOPS.fetch_add(1, Ordering::SeqCst); pushheap(); cmdpush(CS_FOR as u8); let loop_pc = state.pc; let mut args_iter = args.into_iter();
379 while !last_iter {
380 if iscond {
381 let mut cs = cond_expr.clone();
383 if ctok != 0 {
384 cs = singsub(&cs);
385 }
386 if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
387 let trimmed = cs.trim_start();
388 if !trimmed.is_empty() {
389 if isset(XTRACE) {
390 printprompt4();
391 eprintln!("{}", trimmed);
392 }
393 val = wc_mathevali(trimmed).unwrap_or(0);
394 } else {
395 val = 1;
396 }
397 }
398 if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
399 if BREAKS.load(Ordering::SeqCst) > 0 {
400 BREAKS.fetch_sub(1, Ordering::SeqCst);
401 }
402 LASTVAL.store(1, Ordering::Relaxed);
403 break;
404 }
405 if val == 0 {
406 break;
407 }
408 } else {
409 let mut count = 0;
411 for name in &vars {
412 let value = match args_iter.next() {
413 Some(v) => v,
414 None => {
415 if count != 0 {
416 last_iter = true;
417 String::new()
418 } else {
419 break;
420 }
421 }
422 };
423 if isset(XTRACE) {
424 printprompt4();
425 eprintln!("{}={}", name, value);
426 }
427 setloopvar(name, &value);
428 count += 1;
429 }
430 if count == 0 {
431 break;
432 }
433 }
434 state.pc = loop_pc; let _do_exec_now = do_exec != 0 && !args_iter.clone().any(|_| true); let _ = execlist(state, 1, if _do_exec_now { 1 } else { 0 });
437 if BREAKS.load(Ordering::SeqCst) > 0 {
439 let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
440 if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
441 break;
442 }
443 CONTFLAG.store(0, Ordering::SeqCst);
444 }
445 if RETFLAG.load(Ordering::SeqCst) != 0 {
446 break;
447 }
448 if iscond && (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
450 let mut adv = advance_expr.clone();
451 if atok != 0 {
452 adv = singsub(&adv);
453 }
454 if isset(XTRACE) {
455 printprompt4();
456 eprintln!("{}", adv);
457 }
458 if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0 {
459 let _ = wc_matheval(&adv);
460 }
461 }
462 if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
463 if BREAKS.load(Ordering::SeqCst) > 0 {
464 BREAKS.fetch_sub(1, Ordering::SeqCst);
465 }
466 LASTVAL.store(1, Ordering::Relaxed);
467 break;
468 }
469 freeheap(); }
471 popheap(); cmdpop(); LOOPS.fetch_sub(1, Ordering::SeqCst); simple_pline.store(old_simple_pline, Ordering::Relaxed);
475 state.pc = end_pc;
476 this_noerrexit.store(1, Ordering::Relaxed);
477 let _ = Z_END;
478 LASTVAL.load(Ordering::Relaxed)
479}
480
481pub fn execselect(state: &mut estate, _do_exec: i32) -> i32 {
484 let code = state.prog.prog[state.pc.wrapping_sub(1)];
490 let end_pc = state.pc + WC_FOR_SKIP(code) as usize;
491 state.pc = end_pc;
495 this_noerrexit.store(1, Ordering::Relaxed);
496 LASTVAL.load(Ordering::Relaxed)
497}
498
499pub fn execwhile(state: &mut estate, _do_exec: i32) -> i32 {
502 let code = state.prog.prog[state.pc.wrapping_sub(1)]; let isuntil = WC_WHILE_TYPE(code) == WC_WHILE_UNTIL; let end_pc = state.pc + WC_WHILE_SKIP(code) as usize; let olderrexit = noerrexit.load(Ordering::Relaxed); let mut oldval: i32 = 0; pushheap(); cmdpush(if isuntil {
509 CS_UNTIL as u8
510 } else {
511 CS_WHILE as u8
512 }); LOOPS.fetch_add(1, Ordering::SeqCst); let loop_pc = state.pc; let old_simple_pline = simple_pline.load(Ordering::Relaxed); if state.prog.prog.get(loop_pc) == Some(&WC_END)
519 && state.prog.prog.get(loop_pc + 1) == Some(&WC_END)
520 {
521 simple_pline.store(1, Ordering::Relaxed);
522 while BREAKS.load(Ordering::SeqCst) == 0 {
524 std::thread::yield_now();
525 }
526 BREAKS.fetch_sub(1, Ordering::SeqCst);
527 simple_pline.store(old_simple_pline, Ordering::Relaxed);
528 } else {
529 loop {
531 state.pc = loop_pc; noerrexit.fetch_or(NOERREXIT_EXIT | NOERREXIT_RETURN, Ordering::Relaxed); simple_pline.store(1, Ordering::Relaxed); let _ = execlist(state, 1, 0); simple_pline.store(old_simple_pline, Ordering::Relaxed);
536 noerrexit.store(olderrexit, Ordering::Relaxed); let cond_status = LASTVAL.load(Ordering::Relaxed); let cond_passed = (cond_status == 0) ^ isuntil;
540 if !cond_passed {
541 if BREAKS.load(Ordering::SeqCst) > 0 {
542 BREAKS.fetch_sub(1, Ordering::SeqCst);
543 }
544 if RETFLAG.load(Ordering::SeqCst) == 0 {
545 LASTVAL.store(oldval, Ordering::Relaxed);
546 }
547 break;
548 }
549 if RETFLAG.load(Ordering::SeqCst) != 0 {
550 if BREAKS.load(Ordering::SeqCst) > 0 {
552 BREAKS.fetch_sub(1, Ordering::SeqCst);
553 }
554 break;
555 }
556 simple_pline.store(1, Ordering::Relaxed); let _ = execlist(state, 1, 0); simple_pline.store(old_simple_pline, Ordering::Relaxed);
559 if BREAKS.load(Ordering::SeqCst) > 0 {
561 let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
562 if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
563 break;
564 }
565 CONTFLAG.store(0, Ordering::SeqCst);
566 }
567 if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
569 LASTVAL.store(1, Ordering::Relaxed);
570 break;
571 }
572 if RETFLAG.load(Ordering::SeqCst) != 0 {
574 break;
575 }
576 freeheap(); oldval = LASTVAL.load(Ordering::Relaxed); }
579 }
580 cmdpop(); popheap(); LOOPS.fetch_sub(1, Ordering::SeqCst); state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
586}
587
588pub fn execrepeat(state: &mut estate, _do_exec: i32) -> i32 {
591 let code = state.prog.prog[state.pc.wrapping_sub(1)]; let old_simple_pline = simple_pline.swap(1, Ordering::Relaxed); let end_pc = state.pc + WC_REPEAT_SKIP(code) as usize; let mut htok = 0i32;
595 let mut tmp = ecgetstr(state, EC_DUPTOK, Some(&mut htok)); if htok != 0 {
597 tmp = singsub(&tmp); tmp = untokenize(&tmp); }
600 let count = wc_mathevali(&tmp).unwrap_or(0); if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
602 simple_pline.store(old_simple_pline, Ordering::Relaxed);
603 return 1;
604 }
605 LASTVAL.store(0, Ordering::Relaxed); pushheap(); cmdpush(CS_REPEAT as u8); LOOPS.fetch_add(1, Ordering::SeqCst); let loop_pc = state.pc; let mut remaining = count;
611 while remaining > 0 {
612 remaining -= 1;
614 state.pc = loop_pc;
615 let _ = execlist(state, 1, 0); freeheap(); if BREAKS.load(Ordering::SeqCst) > 0 {
619 let prev = BREAKS.fetch_sub(1, Ordering::SeqCst);
620 if prev - 1 > 0 || CONTFLAG.load(Ordering::SeqCst) == 0 {
621 break;
622 }
623 CONTFLAG.store(0, Ordering::SeqCst);
624 }
625 if (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0 {
626 LASTVAL.store(1, Ordering::Relaxed);
628 break;
629 }
630 if RETFLAG.load(Ordering::SeqCst) != 0 {
631 break;
633 }
634 }
635 cmdpop(); popheap(); LOOPS.fetch_sub(1, Ordering::SeqCst); simple_pline.store(old_simple_pline, Ordering::Relaxed);
639 state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
642}
643
644pub fn execif(state: &mut estate, do_exec: i32) -> i32 {
647 let code0 = state.prog.prog[state.pc.wrapping_sub(1)]; let olderrexit = noerrexit.load(Ordering::Relaxed); let end_pc = state.pc + WC_IF_SKIP(code0) as usize; noerrexit.fetch_or(NOERREXIT_EXIT | NOERREXIT_RETURN, Ordering::Relaxed); let mut s = 0i32; let mut run = 0i32; while state.pc < end_pc {
654 let code = state.prog.prog[state.pc];
656 state.pc += 1;
657 if wc_code(code) != WC_IF || WC_IF_TYPE(code) == WC_IF_ELSE {
659 run = if wc_code(code) == WC_IF && WC_IF_TYPE(code) == WC_IF_ELSE {
660 2
661 } else {
662 1
663 };
664 if run == 1 {
665 state.pc -= 1; }
667 break;
668 }
669 let next_pc = state.pc + WC_IF_SKIP(code) as usize; cmdpush(if s != 0 { CS_ELIF as u8 } else { CS_IF as u8 }); let _ = execlist(state, 1, 0); cmdpop(); if LASTVAL.load(Ordering::Relaxed) == 0 {
675 run = 1;
676 break;
677 }
678 if RETFLAG.load(Ordering::SeqCst) != 0 {
679 break;
681 }
682 s = 1;
683 state.pc = next_pc;
684 }
685 noerrexit.store(olderrexit, Ordering::Relaxed); if run != 0 {
688 cmdpush(if run == 2 {
689 CS_ELSE as u8
690 } else if s != 0 {
691 CS_ELIFTHEN as u8
692 } else {
693 CS_IFTHEN as u8
694 });
695 let _ = execlist(state, 1, do_exec);
696 cmdpop();
697 } else if RETFLAG.load(Ordering::SeqCst) == 0
698 && (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) == 0
699 {
700 LASTVAL.store(0, Ordering::Relaxed); }
702 state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
705}
706
707pub fn execcase(state: &mut estate, do_exec: i32) -> i32 {
710 let code0 = state.prog.prog[state.pc.wrapping_sub(1)]; let end_pc = state.pc + WC_CASE_SKIP(code0) as usize; let raw_word = ecgetstr(state, EC_DUP, None);
714 let word_sub = singsub(&raw_word);
715 let word = untokenize(&word_sub);
716 let mut anypatok = false; cmdpush(CS_CASE as u8); let mut code = 0u32;
719 while state.pc < end_pc {
720 code = state.prog.prog[state.pc];
722 state.pc += 1;
723 if wc_code(code) != WC_CASE {
724 break;
725 }
726 let next_pc = state.pc + WC_CASE_SKIP(code) as usize; let nalts = state.prog.prog[state.pc] as i32; state.pc += 1;
729 let mut patok = false;
730 let mut nalts_remaining = nalts;
731 while !patok && nalts_remaining > 0 {
732 queue_signals(); let mut htok = 0i32;
737 let pat_raw = ecrawstr(&state.prog, state.pc, Some(&mut htok));
738 let pat = if htok != 0 {
739 singsub(&pat_raw)
740 } else {
741 pat_raw
742 };
743 if let Some(pprog) = patcompile(
744 &{
745 let mut __pat_tok = (&pat).to_string();
746 crate::ported::glob::tokenize(&mut __pat_tok);
747 __pat_tok
748 },
749 PAT_STATIC,
750 None,
751 ) {
752 if pattry(&pprog, &word) {
754 patok = true;
755 anypatok = true;
756 }
757 } else {
758 zerr(&format!("bad pattern: {}", pat)); }
760 state.pc += 2; nalts_remaining -= 1;
762 unqueue_signals(); }
764 state.pc += (2 * nalts_remaining) as usize; if patok {
766 let _ = execlist(
768 state,
769 1,
770 ((WC_CASE_TYPE(code) == WC_CASE_OR) as i32) & do_exec,
771 );
772 while RETFLAG.load(Ordering::SeqCst) == 0
774 && wc_code(code) == WC_CASE
775 && WC_CASE_TYPE(code) == WC_CASE_AND
776 && state.pc < end_pc
777 {
778 state.pc = next_pc;
779 code = state.prog.prog[state.pc];
780 state.pc += 1;
781 let inner_next = state.pc + WC_CASE_SKIP(code) as usize;
782 let inner_nalts = state.prog.prog[state.pc] as usize;
783 state.pc += 1 + 2 * inner_nalts;
784 let _ = execlist(
785 state,
786 1,
787 ((WC_CASE_TYPE(code) == WC_CASE_OR) as i32) & do_exec,
788 );
789 let _ = inner_next;
790 }
791 if WC_CASE_TYPE(code) != WC_CASE_TESTAND {
792 break;
793 }
794 }
795 state.pc = next_pc; }
797 cmdpop(); state.pc = end_pc; if !anypatok {
800 LASTVAL.store(0, Ordering::Relaxed);
802 }
803 this_noerrexit.store(1, Ordering::Relaxed); LASTVAL.load(Ordering::Relaxed)
805}
806
807pub fn exectry(state: &mut estate, _do_exec: i32) -> i32 {
812 let header = state.prog.prog[state.pc.wrapping_sub(1)]; let end_pc = state.pc + WC_TRY_SKIP(header) as usize; let try_inner = state.prog.prog[state.pc]; let always_pc = state.pc + 1 + WC_TRY_SKIP(try_inner) as usize; state.pc += 1; pushheap(); cmdpush(CS_CURSH as u8); try_tryflag.fetch_add(1, Ordering::SeqCst); let _ = execlist(state, 1, 0); try_tryflag.fetch_sub(1, Ordering::SeqCst); let try_status = LASTVAL.load(Ordering::Relaxed);
823 let endval = if try_status != 0 {
824 try_status
826 } else {
827 (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) as i32
828 };
829 freeheap(); cmdpop(); cmdpush(CS_ALWAYS as u8); let saved_err = errflag.load(Ordering::Relaxed);
834 let save_try_err = (saved_err & ERRFLAG_ERROR) != 0;
835 let save_try_int = (saved_err & ERRFLAG_INT) != 0;
836 let saved_try_errflag = crate::ported::r#loop::try_errflag.load(Ordering::Relaxed);
842 let saved_try_interrupt = crate::ported::r#loop::try_interrupt.load(Ordering::Relaxed);
843 crate::ported::r#loop::try_errflag.store((saved_err & ERRFLAG_ERROR) as i64, Ordering::Relaxed); crate::ported::r#loop::try_interrupt.store(
845 if (saved_err & ERRFLAG_INT) != 0 { 1 } else { 0 },
846 Ordering::Relaxed,
847 ); errflag.fetch_and(!(ERRFLAG_ERROR | ERRFLAG_INT), Ordering::Relaxed);
850 let save_retflag = RETFLAG.swap(0, Ordering::SeqCst);
852 let save_breaks = BREAKS.swap(0, Ordering::SeqCst);
853 let save_contflag = CONTFLAG.swap(0, Ordering::SeqCst);
854 state.pc = always_pc; let _ = execlist(state, 1, 0); if save_try_err {
858 errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
859 } else {
860 errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
861 }
862 if save_try_int {
863 errflag.fetch_or(ERRFLAG_INT, Ordering::Relaxed);
864 } else {
865 errflag.fetch_and(!ERRFLAG_INT, Ordering::Relaxed);
866 }
867 crate::ported::r#loop::try_errflag.store(saved_try_errflag, Ordering::Relaxed);
869 crate::ported::r#loop::try_interrupt.store(saved_try_interrupt, Ordering::Relaxed);
870 if RETFLAG.load(Ordering::SeqCst) == 0 {
872 RETFLAG.store(save_retflag, Ordering::SeqCst);
873 }
874 if BREAKS.load(Ordering::SeqCst) == 0 {
875 BREAKS.store(save_breaks, Ordering::SeqCst);
876 }
877 if CONTFLAG.load(Ordering::SeqCst) == 0 {
878 CONTFLAG.store(save_contflag, Ordering::SeqCst);
879 }
880 cmdpop(); popheap(); state.pc = end_pc; this_noerrexit.store(1, Ordering::Relaxed); endval
885}
886
887#[cfg(test)]
888mod tests {
889 use super::*;
890
891 #[test]
892 fn test_selectlist_returns_zero_when_full_fits() {
893 let _g = crate::test_util::global_state_lock();
894 let items = ["one", "two", "three"];
895 let r = selectlist(&items, 0);
896 assert!(r < items.len() || r == 0);
897 }
898
899 #[test]
909 fn selectlist_empty_returns_zero() {
910 let _g = crate::test_util::global_state_lock();
911 assert_eq!(selectlist(&[], 0), 0);
912 }
913
914 #[test]
921 fn selectlist_start_zero_returns_valid_count() {
922 let _g = crate::test_util::global_state_lock();
923 let items = ["a", "b", "c"];
924 let r = selectlist(&items, 0);
925 assert!(r <= items.len());
928 }
929
930 #[test]
932 fn selectlist_single_item_no_panic() {
933 let _g = crate::test_util::global_state_lock();
934 let r = selectlist(&["only"], 0);
935 let _ = r;
936 }
937
938 #[test]
940 fn selectlist_empty_string_entries_no_panic() {
941 let _g = crate::test_util::global_state_lock();
942 let items = ["", "", ""];
943 let _ = selectlist(&items, 0);
944 }
945
946 #[test]
948 fn selectlist_long_items_no_panic() {
949 let _g = crate::test_util::global_state_lock();
950 let long = "x".repeat(500);
951 let items = [long.as_str(), "short"];
952 let _ = selectlist(&items, 0);
953 }
954
955 #[test]
957 fn selectlist_multibyte_entries_no_panic() {
958 let _g = crate::test_util::global_state_lock();
959 let items = ["日本", "中文", "한국어"];
960 let _ = selectlist(&items, 0);
961 }
962
963 #[test]
965 fn selectlist_many_items_no_panic() {
966 let _g = crate::test_util::global_state_lock();
967 let items: Vec<String> = (0..100).map(|i| format!("item{}", i)).collect();
968 let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
969 let _ = selectlist(&refs, 0);
970 }
971
972 #[test]
978 fn selectlist_start_in_range_no_panic() {
979 let _g = crate::test_util::global_state_lock();
980 let items = ["a", "b", "c", "d", "e"];
981 let _ = selectlist(&items, 2);
982 }
983
984 #[test]
986 fn selectlist_is_deterministic() {
987 let _g = crate::test_util::global_state_lock();
988 let items = ["a", "b", "c"];
989 let first = selectlist(&items, 0);
990 for _ in 0..5 {
991 assert_eq!(selectlist(&items, 0), first);
992 }
993 }
994
995 #[test]
1002 fn selectlist_return_bounded_by_items_len() {
1003 let _g = crate::test_util::global_state_lock();
1004 for sz in [1usize, 3, 5, 20] {
1005 let items: Vec<String> = (0..sz).map(|i| format!("i{}", i)).collect();
1006 let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
1007 let r = selectlist(&refs, 0);
1008 assert!(r <= sz, "selectlist({} items, 0) = {} > {}", sz, r, sz);
1009 }
1010 }
1011
1012 #[test]
1014 fn selectlist_returns_usize_type() {
1015 let _g = crate::test_util::global_state_lock();
1016 let _: usize = selectlist(&["x"], 0);
1017 }
1018
1019 #[test]
1021 fn selectlist_two_items_no_panic() {
1022 let _g = crate::test_util::global_state_lock();
1023 let _ = selectlist(&["a", "b"], 0);
1024 }
1025
1026 #[test]
1028 fn selectlist_entries_with_tabs_no_panic() {
1029 let _g = crate::test_util::global_state_lock();
1030 let _ = selectlist(&["a\tb", "c\td"], 0);
1031 }
1032
1033 #[test]
1035 fn try_tryflag_is_atomic_i64() {
1036 use std::sync::atomic::Ordering;
1037 let v: i64 = try_tryflag.load(Ordering::SeqCst);
1038 let _ = v;
1040 }
1041
1042 #[test]
1044 fn selectlist_ansi_escape_entries_no_panic() {
1045 let _g = crate::test_util::global_state_lock();
1046 let _ = selectlist(&["\x1b[31mred\x1b[0m", "plain"], 0);
1047 }
1048
1049 #[test]
1051 fn selectlist_single_space_entry_no_panic() {
1052 let _g = crate::test_util::global_state_lock();
1053 let _ = selectlist(&[" "], 0);
1054 }
1055
1056 #[test]
1064 fn loop_depth_initial_state_is_zero() {
1065 use std::sync::atomic::Ordering;
1066 let v = LOOP_DEPTH.load(Ordering::SeqCst);
1067 assert!(v >= 0, "loop depth must never be negative; got {}", v);
1068 }
1069
1070 #[test]
1073 fn cont_flag_initial_state_non_negative() {
1074 use std::sync::atomic::Ordering;
1075 let v = CONT_FLAG.load(Ordering::SeqCst);
1076 assert!(v >= 0, "cont flag must be non-negative; got {}", v);
1077 }
1078
1079 #[test]
1082 fn break_level_initial_state_non_negative() {
1083 use std::sync::atomic::Ordering;
1084 let v = BREAK_LEVEL.load(Ordering::SeqCst);
1085 assert!(v >= 0, "break level must be non-negative; got {}", v);
1086 }
1087
1088 #[test]
1090 fn selectlist_no_mutation_across_calls() {
1091 let _g = crate::test_util::global_state_lock();
1092 let items = ["alpha", "beta", "gamma"];
1093 let first = selectlist(&items, 0);
1094 for _ in 0..20 {
1095 assert_eq!(
1096 selectlist(&items, 0),
1097 first,
1098 "selectlist must be pure across repeated calls"
1099 );
1100 }
1101 }
1102
1103 #[test]
1105 fn selectlist_four_items_no_panic() {
1106 let _g = crate::test_util::global_state_lock();
1107 let _ = selectlist(&["a", "b", "c", "d"], 0);
1108 }
1109
1110 #[test]
1112 fn selectlist_newline_entry_no_panic() {
1113 let _g = crate::test_util::global_state_lock();
1114 let _ = selectlist(&["a\nb"], 0);
1115 }
1116
1117 #[test]
1119 fn selectlist_mixed_length_entries_no_panic() {
1120 let _g = crate::test_util::global_state_lock();
1121 let _ = selectlist(&["a", "ab", "abc", "abcd", "abcde"], 0);
1122 }
1123
1124 #[test]
1126 fn selectlist_emoji_entries_no_panic() {
1127 let _g = crate::test_util::global_state_lock();
1128 let _ = selectlist(&["foo", "bar"], 0);
1129 }
1130
1131 #[test]
1133 fn try_tryflag_load_idempotent() {
1134 use std::sync::atomic::Ordering;
1135 let first = try_tryflag.load(Ordering::SeqCst);
1136 for _ in 0..50 {
1137 assert_eq!(
1138 try_tryflag.load(Ordering::SeqCst),
1139 first,
1140 "try_tryflag pure load must be deterministic"
1141 );
1142 }
1143 }
1144
1145 #[test]
1147 fn try_tryflag_address_is_stable() {
1148 let p1 = &try_tryflag as *const _;
1149 let p2 = &try_tryflag as *const _;
1150 assert_eq!(p1, p2, "static must have a single, stable address");
1151 }
1152
1153 #[test]
1155 fn selectlist_whitespace_only_entries_no_panic() {
1156 let _g = crate::test_util::global_state_lock();
1157 let _ = selectlist(&[" ", "\t\t", " \t "], 0);
1158 }
1159
1160 #[test]
1163 fn loop_static_addresses_stable() {
1164 let a1 = &LOOP_DEPTH as *const _;
1165 let a2 = &LOOP_DEPTH as *const _;
1166 assert_eq!(a1, a2, "LOOP_DEPTH must be a true static");
1167 let b1 = &CONT_FLAG as *const _;
1168 let b2 = &CONT_FLAG as *const _;
1169 assert_eq!(b1, b2, "CONT_FLAG must be a true static");
1170 let c1 = &BREAK_LEVEL as *const _;
1171 let c2 = &BREAK_LEVEL as *const _;
1172 assert_eq!(c1, c2, "BREAK_LEVEL must be a true static");
1173 }
1174
1175 #[test]
1182 fn selectlist_empty_returns_zero_alt() {
1183 let _g = crate::test_util::global_state_lock();
1184 assert_eq!(
1185 selectlist(&[], 0),
1186 0,
1187 "empty items must produce zero-line output"
1188 );
1189 }
1190
1191 #[test]
1193 fn selectlist_single_entry_no_panic() {
1194 let _g = crate::test_util::global_state_lock();
1195 let _ = selectlist(&["one"], 0);
1196 }
1197
1198 #[test]
1200 fn selectlist_returns_usize_type_alt() {
1201 let _g = crate::test_util::global_state_lock();
1202 let _: usize = selectlist(&["a"], 0);
1203 }
1204
1205 #[test]
1208 fn selectlist_start_past_end_no_panic() {
1209 let _g = crate::test_util::global_state_lock();
1210 let _ = selectlist(&["a", "b"], 100);
1211 let _ = selectlist(&["a", "b"], usize::MAX / 2);
1212 }
1213
1214 #[test]
1217 fn selectlist_large_list_no_panic() {
1218 let _g = crate::test_util::global_state_lock();
1219 let items: Vec<String> = (0..200).map(|i| format!("item{}", i)).collect();
1220 let refs: Vec<&str> = items.iter().map(|s| s.as_str()).collect();
1221 let _ = selectlist(&refs, 0);
1222 }
1223
1224 #[test]
1226 fn selectlist_start_at_last_index_no_panic() {
1227 let _g = crate::test_util::global_state_lock();
1228 let _ = selectlist(&["a", "b", "c"], 2);
1229 }
1230
1231 #[test]
1233 fn loop_statics_are_atomic_i32() {
1234 let _: &AtomicI32 = &LOOP_DEPTH;
1235 let _: &AtomicI32 = &CONT_FLAG;
1236 let _: &AtomicI32 = &BREAK_LEVEL;
1237 }
1238
1239 #[test]
1241 fn loop_depth_store_load_round_trip() {
1242 use std::sync::atomic::Ordering;
1243 let _g = crate::test_util::global_state_lock();
1244 let saved = LOOP_DEPTH.load(Ordering::SeqCst);
1245 for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
1246 LOOP_DEPTH.store(v, Ordering::SeqCst);
1247 assert_eq!(
1248 LOOP_DEPTH.load(Ordering::SeqCst),
1249 v,
1250 "LOOP_DEPTH must round-trip {}",
1251 v
1252 );
1253 }
1254 LOOP_DEPTH.store(saved, Ordering::SeqCst);
1255 }
1256
1257 #[test]
1259 fn cont_flag_store_load_round_trip() {
1260 use std::sync::atomic::Ordering;
1261 let _g = crate::test_util::global_state_lock();
1262 let saved = CONT_FLAG.load(Ordering::SeqCst);
1263 for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
1264 CONT_FLAG.store(v, Ordering::SeqCst);
1265 assert_eq!(CONT_FLAG.load(Ordering::SeqCst), v);
1266 }
1267 CONT_FLAG.store(saved, Ordering::SeqCst);
1268 }
1269
1270 #[test]
1272 fn break_level_store_load_round_trip() {
1273 use std::sync::atomic::Ordering;
1274 let _g = crate::test_util::global_state_lock();
1275 let saved = BREAK_LEVEL.load(Ordering::SeqCst);
1276 for v in [0, 1, 5, 100, -1, i32::MAX, i32::MIN] {
1277 BREAK_LEVEL.store(v, Ordering::SeqCst);
1278 assert_eq!(BREAK_LEVEL.load(Ordering::SeqCst), v);
1279 }
1280 BREAK_LEVEL.store(saved, Ordering::SeqCst);
1281 }
1282
1283 #[test]
1286 fn selectlist_deterministic_on_identical_input() {
1287 let _g = crate::test_util::global_state_lock();
1288 let first = selectlist(&["one", "two", "three"], 0);
1289 for _ in 0..5 {
1290 assert_eq!(
1291 selectlist(&["one", "two", "three"], 0),
1292 first,
1293 "selectlist must be deterministic"
1294 );
1295 }
1296 }
1297}