1use super::parse::ParseStack;
13use crate::hist::{hist_context_restore, hist_context_save};
14use crate::lex::{lex_context_restore, lex_context_save};
15use crate::parse::{parse_context_restore, parse_context_save};
16use crate::ported::zsh_h::{hist_stack, lex_stack, ZCONTEXT_HIST, ZCONTEXT_LEX, ZCONTEXT_PARSE};
17use crate::signals_h::{queue_signals, unqueue_signals};
18use crate::DPUTS;
19use std::sync::Mutex;
20
21#[allow(non_camel_case_types)]
23pub struct context_stack {
24 pub next: Option<Box<context_stack>>, pub hist_stack: hist_stack, pub lex_stack: lex_stack, pub parse_stack: ParseStack, }
30
31#[allow(non_snake_case)]
33pub fn zcontext_save_partial(parts: i32) {
34 queue_signals(); let mut cs = Box::new(context_stack {
38 next: None,
40 hist_stack: hist_stack {
41 histactive: 0,
42 histdone: 0,
43 stophist: 0,
44 hlinesz: 0,
45 defev: 0,
46 hline: None,
47 hptr: None,
48 chwords: Vec::new(),
49 chwordlen: 0,
50 chwordpos: 0,
51 csp: 0,
52 hist_keep_comment: 0,
53 },
54 lex_stack: lex_stack::default(),
55 parse_stack: ParseStack::default(),
56 });
57
58 let mut head = cstack.lock().unwrap();
59
60 let toplevel: i32 = if head.is_none() { 1 } else { 0 }; if (parts & ZCONTEXT_HIST) != 0 {
62 hist_context_save(&mut cs.hist_stack, toplevel); }
65 if (parts & ZCONTEXT_LEX) != 0 {
66 lex_context_save(&mut cs.lex_stack);
68 }
69 if (parts & ZCONTEXT_PARSE) != 0 {
70 parse_context_save(&mut cs.parse_stack);
72 }
73
74 cs.next = head.take(); *head = Some(cs); unqueue_signals(); }
79
80pub fn zcontext_save() {
82 zcontext_save_partial(ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE);
84}
85
86pub fn zcontext_restore_partial(parts: i32) {
88 let mut head = cstack.lock().unwrap();
90 DPUTS!(
92 head.is_none(),
93 "BUG: zcontext_restore() without zcontext_save()"
94 ); let mut cs = match head.take() {
96 Some(cs) => cs,
98 None => {
99 return;
100 }
101 };
102
103 queue_signals(); *head = cs.next.take(); let toplevel: i32 = if head.is_none() { 1 } else { 0 };
106
107 if (parts & ZCONTEXT_HIST) != 0 {
108 hist_context_restore(&cs.hist_stack, toplevel); }
111 if (parts & ZCONTEXT_LEX) != 0 {
112 lex_context_restore(&mut cs.lex_stack);
114 }
115 if (parts & ZCONTEXT_PARSE) != 0 {
116 parse_context_restore(&cs.parse_stack);
118 }
119
120 drop(cs); unqueue_signals(); }
124
125pub fn zcontext_restore() {
127 zcontext_restore_partial(ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE);
129}
130
131#[allow(non_camel_case_types)]
142struct PerThreadCstack;
143static cstack: PerThreadCstack = PerThreadCstack; impl PerThreadCstack {
146 fn lock(
147 &self,
148 ) -> std::sync::LockResult<std::sync::MutexGuard<'static, Option<Box<context_stack>>>> {
149 thread_local! {
150 static SLOT: &'static Mutex<Option<Box<context_stack>>> =
152 Box::leak(Box::new(Mutex::new(None)));
153 }
154 SLOT.with(|slot| *slot).lock()
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::lex::{lex_init, set_toklineno, toklineno, LEX_DBPARENS};
162
163 fn reset_cstack() {
164 *cstack.lock().unwrap() = None;
165 }
166
167 #[test]
171 fn context_stack_is_per_thread() {
172 let _g = crate::test_util::global_state_lock();
173 reset_cstack();
174 lex_init("");
175 zcontext_save();
176 assert!(cstack.lock().unwrap().is_some());
177 std::thread::spawn(|| {
178 assert!(cstack.lock().unwrap().is_none(), "new thread starts empty");
179 zcontext_save();
180 zcontext_restore();
181 assert!(cstack.lock().unwrap().is_none());
182 })
183 .join()
184 .unwrap();
185 assert!(
186 cstack.lock().unwrap().is_some(),
187 "the other thread's save/restore left this thread's frame alone"
188 );
189 zcontext_restore();
190 assert!(cstack.lock().unwrap().is_none());
191 }
192
193 #[test]
194 fn save_restore_balances_stack() {
195 let _g = crate::test_util::global_state_lock();
196 reset_cstack();
197 lex_init("");
198 zcontext_save();
199 assert!(cstack.lock().unwrap().is_some());
200 zcontext_restore();
201 assert!(cstack.lock().unwrap().is_none());
202 }
203
204 #[test]
205 fn nested_saves_pop_lifo() {
206 let _g = crate::test_util::global_state_lock();
207 reset_cstack();
208 lex_init("");
209 zcontext_save();
210 zcontext_save();
211 zcontext_restore();
212 assert!(cstack.lock().unwrap().is_some());
213 zcontext_restore();
214 assert!(cstack.lock().unwrap().is_none());
215 }
216
217 #[test]
218 fn restore_without_save_is_noop() {
219 let _g = crate::test_util::global_state_lock();
220 reset_cstack();
221 lex_init("");
222 zcontext_restore();
223 assert!(cstack.lock().unwrap().is_none());
224 }
225
226 #[test]
227 fn lex_save_restore_roundtrips_state() {
228 let _g = crate::test_util::global_state_lock();
229 reset_cstack();
230 lex_init("echo hello");
231 LEX_DBPARENS.set(true);
232 set_toklineno(42);
233 zcontext_save();
234 assert!(LEX_DBPARENS.with(|c| c.get()));
235 assert_eq!(toklineno(), 42);
236 zcontext_restore();
237 assert!(LEX_DBPARENS.with(|c| c.get()));
238 assert_eq!(toklineno(), 42);
239 }
240
241 #[test]
248 fn zcontext_flag_bits_are_distinct_and_nonzero() {
249 let _g = crate::test_util::global_state_lock();
250 assert_eq!(ZCONTEXT_HIST, 1 << 0);
252 assert_eq!(ZCONTEXT_LEX, 1 << 1);
253 assert_eq!(ZCONTEXT_PARSE, 1 << 2);
254 assert_eq!(ZCONTEXT_HIST & ZCONTEXT_LEX, 0);
256 assert_eq!(ZCONTEXT_HIST & ZCONTEXT_PARSE, 0);
257 assert_eq!(ZCONTEXT_LEX & ZCONTEXT_PARSE, 0);
258 }
259
260 #[test]
267 fn zcontext_save_partial_with_zero_mask_still_pushes_frame() {
268 let _g = crate::test_util::global_state_lock();
269 reset_cstack();
270 lex_init("");
271 zcontext_save_partial(0);
272 assert!(
273 cstack.lock().unwrap().is_some(),
274 "c:70-71 push must fire even when no parts requested"
275 );
276 zcontext_restore_partial(0);
277 assert!(
278 cstack.lock().unwrap().is_none(),
279 "c:96 pop must mirror the push regardless of parts mask"
280 );
281 }
282
283 #[test]
291 fn zcontext_restore_partial_on_empty_stack_is_noop() {
292 let _g = crate::test_util::global_state_lock();
293 reset_cstack();
294 zcontext_restore_partial(0);
295 zcontext_restore_partial(ZCONTEXT_HIST);
296 assert!(cstack.lock().unwrap().is_none());
297 }
298
299 #[test]
304 fn deep_save_restore_lifo_drains_to_empty() {
305 let _g = crate::test_util::global_state_lock();
306 reset_cstack();
307 lex_init("");
308 for _ in 0..5 {
309 zcontext_save();
310 }
311 for i in (0..5).rev() {
313 assert!(
314 cstack.lock().unwrap().is_some(),
315 "stack must still have entries before restore #{}",
316 5 - i
317 );
318 zcontext_restore();
319 }
320 assert!(
321 cstack.lock().unwrap().is_none(),
322 "5 restores must drain the 5 saves to empty"
323 );
324 }
325
326 #[test]
331 fn zcontext_save_equals_save_partial_full_mask() {
332 let _g = crate::test_util::global_state_lock();
333 let full = ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE;
334
335 reset_cstack();
336 lex_init("");
337 zcontext_save();
338 zcontext_restore();
339 let after_full = cstack.lock().unwrap().is_none();
340
341 reset_cstack();
342 lex_init("");
343 zcontext_save_partial(full);
344 zcontext_restore_partial(full);
345 let after_partial = cstack.lock().unwrap().is_none();
346
347 assert_eq!(
348 after_full, after_partial,
349 "save/restore must equal save_partial(ALL)/restore_partial(ALL)"
350 );
351 }
352
353 #[test]
358 fn many_saves_without_restore_grow_stack_safely() {
359 let _g = crate::test_util::global_state_lock();
360 reset_cstack();
361 lex_init("");
362 for _ in 0..20 {
363 zcontext_save();
364 }
365 assert!(cstack.lock().unwrap().is_some());
367 for _ in 0..20 {
369 zcontext_restore();
370 }
371 assert!(cstack.lock().unwrap().is_none());
372 }
373
374 #[test]
378 fn context_corpus_save_then_restore_returns_empty() {
379 let _g = crate::test_util::global_state_lock();
380 reset_cstack();
381 lex_init("");
382 zcontext_save();
383 zcontext_restore();
384 assert!(
385 cstack.lock().unwrap().is_none(),
386 "save+restore leaves no stack"
387 );
388 }
389
390 #[test]
392 fn context_corpus_nested_save_restore_lifo() {
393 let _g = crate::test_util::global_state_lock();
394 reset_cstack();
395 lex_init("");
396 zcontext_save();
397 zcontext_save();
398 zcontext_save();
399 zcontext_restore();
400 zcontext_restore();
401 zcontext_restore();
402 assert!(cstack.lock().unwrap().is_none(), "all 3 levels drained");
403 }
404
405 #[test]
408 fn context_corpus_save_partial_round_trip() {
409 let _g = crate::test_util::global_state_lock();
410 reset_cstack();
411 lex_init("");
412 zcontext_save_partial(1);
413 zcontext_restore_partial(1);
414 }
415
416 #[test]
422 fn zcontext_save_pushes_onto_stack() {
423 let _g = crate::test_util::global_state_lock();
424 reset_cstack();
425 lex_init("");
426 zcontext_save();
427 assert!(
428 cstack.lock().unwrap().is_some(),
429 "save should leave cstack with one frame"
430 );
431 zcontext_restore();
432 }
433
434 #[test]
436 fn zcontext_restore_pops_to_none_when_balanced() {
437 let _g = crate::test_util::global_state_lock();
438 reset_cstack();
439 lex_init("");
440 zcontext_save();
441 zcontext_restore();
442 assert!(
443 cstack.lock().unwrap().is_none(),
444 "single save+restore returns cstack to None"
445 );
446 }
447
448 #[test]
450 fn zcontext_save_multiple_builds_stack() {
451 let _g = crate::test_util::global_state_lock();
452 reset_cstack();
453 lex_init("");
454 zcontext_save();
455 zcontext_save();
456 zcontext_save();
457 let head = cstack.lock().unwrap();
459 let mut count = 0;
460 let mut cur = head.as_ref();
461 while let Some(node) = cur {
462 count += 1;
463 cur = node.next.as_ref();
464 }
465 drop(head);
466 assert_eq!(count, 3, "3 saves → 3-deep stack");
467 zcontext_restore();
468 zcontext_restore();
469 zcontext_restore();
470 }
471
472 #[test]
475 fn zcontext_save_partial_zero_still_pushes_frame() {
476 let _g = crate::test_util::global_state_lock();
477 reset_cstack();
478 lex_init("");
479 zcontext_save_partial(0);
480 assert!(
481 cstack.lock().unwrap().is_some(),
482 "even parts=0 pushes a frame for restore symmetry"
483 );
484 zcontext_restore_partial(0);
485 }
486
487 #[test]
490 fn zcontext_restore_on_empty_no_panic() {
491 let _g = crate::test_util::global_state_lock();
492 reset_cstack();
493 zcontext_restore_partial(0);
495 }
496
497 #[test]
499 fn zcontext_save_hist_only_round_trip() {
500 let _g = crate::test_util::global_state_lock();
501 reset_cstack();
502 lex_init("");
503 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
504 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
505 assert!(cstack.lock().unwrap().is_none());
506 }
507
508 #[test]
510 fn zcontext_save_lex_only_round_trip() {
511 let _g = crate::test_util::global_state_lock();
512 reset_cstack();
513 lex_init("");
514 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_LEX);
515 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_LEX);
516 assert!(cstack.lock().unwrap().is_none());
517 }
518
519 #[test]
521 fn zcontext_save_parse_only_round_trip() {
522 let _g = crate::test_util::global_state_lock();
523 reset_cstack();
524 lex_init("");
525 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_PARSE);
526 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_PARSE);
527 assert!(cstack.lock().unwrap().is_none());
528 }
529
530 #[test]
537 fn zcontext_flag_bits_pin_to_one_two_four() {
538 assert_eq!(crate::ported::zsh_h::ZCONTEXT_HIST, 1, "c:491 = 1<<0");
539 assert_eq!(crate::ported::zsh_h::ZCONTEXT_LEX, 2, "c:493 = 1<<1");
540 assert_eq!(crate::ported::zsh_h::ZCONTEXT_PARSE, 4, "c:495 = 1<<2");
541 }
542
543 #[test]
545 fn zcontext_flags_are_single_bits() {
546 for &v in &[
547 crate::ported::zsh_h::ZCONTEXT_HIST,
548 crate::ported::zsh_h::ZCONTEXT_LEX,
549 crate::ported::zsh_h::ZCONTEXT_PARSE,
550 ] {
551 assert!((v as u32).is_power_of_two(), "{} must be a single bit", v);
552 }
553 }
554
555 #[test]
559 fn zcontext_save_partial_zero_round_trip_pops() {
560 let _g = crate::test_util::global_state_lock();
561 reset_cstack();
562 lex_init("");
563 zcontext_save_partial(0);
564 assert!(cstack.lock().unwrap().is_some(), "zero-mask still pushes");
565 zcontext_restore_partial(0);
566 assert!(cstack.lock().unwrap().is_none(), "restore drains to None");
567 }
568
569 #[test]
573 fn zcontext_save_hist_restore_with_parse_mask_pops_frame() {
574 let _g = crate::test_util::global_state_lock();
575 reset_cstack();
576 lex_init("");
577 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
578 assert!(cstack.lock().unwrap().is_some());
579 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_PARSE);
580 assert!(cstack.lock().unwrap().is_none(), "frame popped regardless");
581 }
582
583 #[test]
586 fn zcontext_save_shorthand_pushes_one_frame() {
587 let _g = crate::test_util::global_state_lock();
588 reset_cstack();
589 lex_init("");
590 zcontext_save();
591 {
593 let head = cstack.lock().unwrap();
594 assert!(head.is_some(), "save pushed a frame");
595 assert!(head.as_ref().unwrap().next.is_none(), "stack depth = 1");
596 }
597 zcontext_restore();
598 assert!(cstack.lock().unwrap().is_none());
599
600 zcontext_save_partial(
601 crate::ported::zsh_h::ZCONTEXT_HIST
602 | crate::ported::zsh_h::ZCONTEXT_LEX
603 | crate::ported::zsh_h::ZCONTEXT_PARSE,
604 );
605 {
606 let head = cstack.lock().unwrap();
607 assert!(head.is_some(), "full-mask save_partial pushed a frame");
608 assert!(head.as_ref().unwrap().next.is_none(), "stack depth = 1");
609 }
610 zcontext_restore();
611 }
612
613 #[test]
615 fn zcontext_restore_drains_exactly_one_frame() {
616 let _g = crate::test_util::global_state_lock();
617 reset_cstack();
618 lex_init("");
619 zcontext_save();
620 zcontext_save();
621 zcontext_restore();
623 assert!(cstack.lock().unwrap().is_some());
625 zcontext_restore();
626 assert!(cstack.lock().unwrap().is_none());
627 }
628
629 #[test]
632 fn zcontext_restore_shorthand_on_empty_no_panic() {
633 let _g = crate::test_util::global_state_lock();
634 reset_cstack();
635 zcontext_restore();
636 assert!(cstack.lock().unwrap().is_none());
637 }
638
639 #[test]
641 fn zcontext_save_hist_lex_combo_round_trip() {
642 let _g = crate::test_util::global_state_lock();
643 reset_cstack();
644 lex_init("");
645 let mask = crate::ported::zsh_h::ZCONTEXT_HIST | crate::ported::zsh_h::ZCONTEXT_LEX;
646 zcontext_save_partial(mask);
647 zcontext_restore_partial(mask);
648 assert!(cstack.lock().unwrap().is_none());
649 }
650
651 #[test]
653 fn zcontext_save_lex_parse_combo_round_trip() {
654 let _g = crate::test_util::global_state_lock();
655 reset_cstack();
656 lex_init("");
657 let mask = crate::ported::zsh_h::ZCONTEXT_LEX | crate::ported::zsh_h::ZCONTEXT_PARSE;
658 zcontext_save_partial(mask);
659 zcontext_restore_partial(mask);
660 assert!(cstack.lock().unwrap().is_none());
661 }
662
663 #[test]
665 fn many_restores_on_empty_no_panic() {
666 let _g = crate::test_util::global_state_lock();
667 reset_cstack();
668 for _ in 0..10 {
669 zcontext_restore_partial(0);
670 }
671 assert!(cstack.lock().unwrap().is_none());
672 }
673
674 #[test]
676 fn restore_on_empty_via_full_shorthand_no_panic() {
677 let _g = crate::test_util::global_state_lock();
678 reset_cstack();
679 zcontext_restore();
680 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
681 zcontext_restore_partial(
682 crate::ported::zsh_h::ZCONTEXT_HIST | crate::ported::zsh_h::ZCONTEXT_LEX,
683 );
684 assert!(cstack.lock().unwrap().is_none());
685 }
686
687 #[test]
696 fn zcontext_save_partial_hist_pushes_one_frame() {
697 let _g = crate::test_util::global_state_lock();
698 reset_cstack();
699 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
700 assert!(
701 cstack.lock().unwrap().is_some(),
702 "frame must exist after save"
703 );
704 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
705 assert!(cstack.lock().unwrap().is_none(), "frame must be drained");
706 }
707
708 #[test]
710 fn zcontext_save_full_then_restore_full_clears_stack() {
711 let _g = crate::test_util::global_state_lock();
712 reset_cstack();
713 zcontext_save();
714 assert!(cstack.lock().unwrap().is_some());
715 zcontext_restore();
716 assert!(cstack.lock().unwrap().is_none());
717 }
718
719 #[test]
721 fn zcontext_save_three_then_restore_three_clears_stack() {
722 let _g = crate::test_util::global_state_lock();
723 reset_cstack();
724 zcontext_save();
725 zcontext_save();
726 zcontext_save();
727 zcontext_restore();
728 zcontext_restore();
729 zcontext_restore();
730 assert!(
731 cstack.lock().unwrap().is_none(),
732 "all 3 frames must drain (LIFO)"
733 );
734 }
735
736 #[test]
738 fn zcontext_save_equals_or_of_three_flags_behavior() {
739 let _g = crate::test_util::global_state_lock();
740 reset_cstack();
741 zcontext_save();
742 let depth1 = cstack.lock().unwrap().is_some();
743 reset_cstack();
744 zcontext_save_partial(
745 crate::ported::zsh_h::ZCONTEXT_HIST
746 | crate::ported::zsh_h::ZCONTEXT_LEX
747 | crate::ported::zsh_h::ZCONTEXT_PARSE,
748 );
749 let depth2 = cstack.lock().unwrap().is_some();
750 assert_eq!(
751 depth1, depth2,
752 "shorthand and explicit OR must produce same push behavior"
753 );
754 zcontext_restore();
755 }
756
757 #[test]
759 fn zcontext_all_flags_or_equals_seven() {
760 let all = crate::ported::zsh_h::ZCONTEXT_HIST
761 | crate::ported::zsh_h::ZCONTEXT_LEX
762 | crate::ported::zsh_h::ZCONTEXT_PARSE;
763 assert_eq!(all, 7, "HIST|LEX|PARSE must equal 0b111 = 7");
764 }
765
766 #[test]
768 fn zcontext_flags_pairwise_distinct() {
769 use crate::ported::zsh_h::{ZCONTEXT_HIST, ZCONTEXT_LEX, ZCONTEXT_PARSE};
770 let codes = [ZCONTEXT_HIST, ZCONTEXT_LEX, ZCONTEXT_PARSE];
771 let unique: std::collections::HashSet<_> = codes.iter().copied().collect();
772 assert_eq!(unique.len(), codes.len(), "ZCONTEXT_* must be distinct");
773 }
774
775 #[test]
777 fn zcontext_save_restore_is_lifo() {
778 let _g = crate::test_util::global_state_lock();
779 reset_cstack();
780 zcontext_save();
781 zcontext_save();
782 assert!(cstack.lock().unwrap().is_some());
783 zcontext_restore();
784 assert!(cstack.lock().unwrap().is_some(), "one frame still on stack");
785 zcontext_restore();
786 assert!(cstack.lock().unwrap().is_none(), "all frames drained");
787 }
788
789 #[test]
791 fn zcontext_save_partial_all_bits_no_panic() {
792 let _g = crate::test_util::global_state_lock();
793 reset_cstack();
794 zcontext_save_partial(-1);
795 zcontext_restore_partial(-1);
796 assert!(cstack.lock().unwrap().is_none());
797 }
798
799 #[test]
801 fn zcontext_restore_partial_all_bits_on_empty_no_panic() {
802 let _g = crate::test_util::global_state_lock();
803 reset_cstack();
804 zcontext_restore_partial(-1);
805 assert!(cstack.lock().unwrap().is_none());
806 }
807
808 #[test]
810 fn zcontext_many_saves_then_full_drain_clean() {
811 let _g = crate::test_util::global_state_lock();
812 reset_cstack();
813 for _ in 0..20 {
814 zcontext_save();
815 }
816 for _ in 0..20 {
817 zcontext_restore();
818 }
819 assert!(
820 cstack.lock().unwrap().is_none(),
821 "after 20 paired save/restore, stack must be empty"
822 );
823 }
824
825 #[test]
827 fn zcontext_restore_with_subset_mask_drains_frame() {
828 let _g = crate::test_util::global_state_lock();
829 reset_cstack();
830 zcontext_save_partial(
831 crate::ported::zsh_h::ZCONTEXT_HIST | crate::ported::zsh_h::ZCONTEXT_LEX,
832 );
833 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
834 assert!(
835 cstack.lock().unwrap().is_none(),
836 "restore with subset mask still pops the frame"
837 );
838 }
839}