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
131static cstack: Mutex<Option<Box<context_stack>>> = Mutex::new(None); #[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::lex::{lex_init, set_toklineno, toklineno, LEX_DBPARENS};
138
139 fn reset_cstack() {
140 *cstack.lock().unwrap() = None;
141 }
142
143 #[test]
144 fn save_restore_balances_stack() {
145 let _g = crate::test_util::global_state_lock();
146 reset_cstack();
147 lex_init("");
148 zcontext_save();
149 assert!(cstack.lock().unwrap().is_some());
150 zcontext_restore();
151 assert!(cstack.lock().unwrap().is_none());
152 }
153
154 #[test]
155 fn nested_saves_pop_lifo() {
156 let _g = crate::test_util::global_state_lock();
157 reset_cstack();
158 lex_init("");
159 zcontext_save();
160 zcontext_save();
161 zcontext_restore();
162 assert!(cstack.lock().unwrap().is_some());
163 zcontext_restore();
164 assert!(cstack.lock().unwrap().is_none());
165 }
166
167 #[test]
168 fn restore_without_save_is_noop() {
169 let _g = crate::test_util::global_state_lock();
170 reset_cstack();
171 lex_init("");
172 zcontext_restore();
173 assert!(cstack.lock().unwrap().is_none());
174 }
175
176 #[test]
177 fn lex_save_restore_roundtrips_state() {
178 let _g = crate::test_util::global_state_lock();
179 reset_cstack();
180 lex_init("echo hello");
181 LEX_DBPARENS.set(true);
182 set_toklineno(42);
183 zcontext_save();
184 assert!(LEX_DBPARENS.with(|c| c.get()));
185 assert_eq!(toklineno(), 42);
186 zcontext_restore();
187 assert!(LEX_DBPARENS.with(|c| c.get()));
188 assert_eq!(toklineno(), 42);
189 }
190
191 #[test]
198 fn zcontext_flag_bits_are_distinct_and_nonzero() {
199 let _g = crate::test_util::global_state_lock();
200 assert_eq!(ZCONTEXT_HIST, 1 << 0);
202 assert_eq!(ZCONTEXT_LEX, 1 << 1);
203 assert_eq!(ZCONTEXT_PARSE, 1 << 2);
204 assert_eq!(ZCONTEXT_HIST & ZCONTEXT_LEX, 0);
206 assert_eq!(ZCONTEXT_HIST & ZCONTEXT_PARSE, 0);
207 assert_eq!(ZCONTEXT_LEX & ZCONTEXT_PARSE, 0);
208 }
209
210 #[test]
217 fn zcontext_save_partial_with_zero_mask_still_pushes_frame() {
218 let _g = crate::test_util::global_state_lock();
219 reset_cstack();
220 lex_init("");
221 zcontext_save_partial(0);
222 assert!(
223 cstack.lock().unwrap().is_some(),
224 "c:70-71 push must fire even when no parts requested"
225 );
226 zcontext_restore_partial(0);
227 assert!(
228 cstack.lock().unwrap().is_none(),
229 "c:96 pop must mirror the push regardless of parts mask"
230 );
231 }
232
233 #[test]
241 fn zcontext_restore_partial_on_empty_stack_is_noop() {
242 let _g = crate::test_util::global_state_lock();
243 reset_cstack();
244 zcontext_restore_partial(0);
245 zcontext_restore_partial(ZCONTEXT_HIST);
246 assert!(cstack.lock().unwrap().is_none());
247 }
248
249 #[test]
254 fn deep_save_restore_lifo_drains_to_empty() {
255 let _g = crate::test_util::global_state_lock();
256 reset_cstack();
257 lex_init("");
258 for _ in 0..5 {
259 zcontext_save();
260 }
261 for i in (0..5).rev() {
263 assert!(
264 cstack.lock().unwrap().is_some(),
265 "stack must still have entries before restore #{}",
266 5 - i
267 );
268 zcontext_restore();
269 }
270 assert!(
271 cstack.lock().unwrap().is_none(),
272 "5 restores must drain the 5 saves to empty"
273 );
274 }
275
276 #[test]
281 fn zcontext_save_equals_save_partial_full_mask() {
282 let _g = crate::test_util::global_state_lock();
283 let full = ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE;
284
285 reset_cstack();
286 lex_init("");
287 zcontext_save();
288 zcontext_restore();
289 let after_full = cstack.lock().unwrap().is_none();
290
291 reset_cstack();
292 lex_init("");
293 zcontext_save_partial(full);
294 zcontext_restore_partial(full);
295 let after_partial = cstack.lock().unwrap().is_none();
296
297 assert_eq!(
298 after_full, after_partial,
299 "save/restore must equal save_partial(ALL)/restore_partial(ALL)"
300 );
301 }
302
303 #[test]
308 fn many_saves_without_restore_grow_stack_safely() {
309 let _g = crate::test_util::global_state_lock();
310 reset_cstack();
311 lex_init("");
312 for _ in 0..20 {
313 zcontext_save();
314 }
315 assert!(cstack.lock().unwrap().is_some());
317 for _ in 0..20 {
319 zcontext_restore();
320 }
321 assert!(cstack.lock().unwrap().is_none());
322 }
323
324 #[test]
328 fn context_corpus_save_then_restore_returns_empty() {
329 let _g = crate::test_util::global_state_lock();
330 reset_cstack();
331 lex_init("");
332 zcontext_save();
333 zcontext_restore();
334 assert!(
335 cstack.lock().unwrap().is_none(),
336 "save+restore leaves no stack"
337 );
338 }
339
340 #[test]
342 fn context_corpus_nested_save_restore_lifo() {
343 let _g = crate::test_util::global_state_lock();
344 reset_cstack();
345 lex_init("");
346 zcontext_save();
347 zcontext_save();
348 zcontext_save();
349 zcontext_restore();
350 zcontext_restore();
351 zcontext_restore();
352 assert!(cstack.lock().unwrap().is_none(), "all 3 levels drained");
353 }
354
355 #[test]
358 fn context_corpus_save_partial_round_trip() {
359 let _g = crate::test_util::global_state_lock();
360 reset_cstack();
361 lex_init("");
362 zcontext_save_partial(1);
363 zcontext_restore_partial(1);
364 }
365
366 #[test]
372 fn zcontext_save_pushes_onto_stack() {
373 let _g = crate::test_util::global_state_lock();
374 reset_cstack();
375 lex_init("");
376 zcontext_save();
377 assert!(
378 cstack.lock().unwrap().is_some(),
379 "save should leave cstack with one frame"
380 );
381 zcontext_restore();
382 }
383
384 #[test]
386 fn zcontext_restore_pops_to_none_when_balanced() {
387 let _g = crate::test_util::global_state_lock();
388 reset_cstack();
389 lex_init("");
390 zcontext_save();
391 zcontext_restore();
392 assert!(
393 cstack.lock().unwrap().is_none(),
394 "single save+restore returns cstack to None"
395 );
396 }
397
398 #[test]
400 fn zcontext_save_multiple_builds_stack() {
401 let _g = crate::test_util::global_state_lock();
402 reset_cstack();
403 lex_init("");
404 zcontext_save();
405 zcontext_save();
406 zcontext_save();
407 let head = cstack.lock().unwrap();
409 let mut count = 0;
410 let mut cur = head.as_ref();
411 while let Some(node) = cur {
412 count += 1;
413 cur = node.next.as_ref();
414 }
415 drop(head);
416 assert_eq!(count, 3, "3 saves → 3-deep stack");
417 zcontext_restore();
418 zcontext_restore();
419 zcontext_restore();
420 }
421
422 #[test]
425 fn zcontext_save_partial_zero_still_pushes_frame() {
426 let _g = crate::test_util::global_state_lock();
427 reset_cstack();
428 lex_init("");
429 zcontext_save_partial(0);
430 assert!(
431 cstack.lock().unwrap().is_some(),
432 "even parts=0 pushes a frame for restore symmetry"
433 );
434 zcontext_restore_partial(0);
435 }
436
437 #[test]
440 fn zcontext_restore_on_empty_no_panic() {
441 let _g = crate::test_util::global_state_lock();
442 reset_cstack();
443 zcontext_restore_partial(0);
445 }
446
447 #[test]
449 fn zcontext_save_hist_only_round_trip() {
450 let _g = crate::test_util::global_state_lock();
451 reset_cstack();
452 lex_init("");
453 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
454 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
455 assert!(cstack.lock().unwrap().is_none());
456 }
457
458 #[test]
460 fn zcontext_save_lex_only_round_trip() {
461 let _g = crate::test_util::global_state_lock();
462 reset_cstack();
463 lex_init("");
464 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_LEX);
465 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_LEX);
466 assert!(cstack.lock().unwrap().is_none());
467 }
468
469 #[test]
471 fn zcontext_save_parse_only_round_trip() {
472 let _g = crate::test_util::global_state_lock();
473 reset_cstack();
474 lex_init("");
475 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_PARSE);
476 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_PARSE);
477 assert!(cstack.lock().unwrap().is_none());
478 }
479
480 #[test]
487 fn zcontext_flag_bits_pin_to_one_two_four() {
488 assert_eq!(crate::ported::zsh_h::ZCONTEXT_HIST, 1, "c:491 = 1<<0");
489 assert_eq!(crate::ported::zsh_h::ZCONTEXT_LEX, 2, "c:493 = 1<<1");
490 assert_eq!(crate::ported::zsh_h::ZCONTEXT_PARSE, 4, "c:495 = 1<<2");
491 }
492
493 #[test]
495 fn zcontext_flags_are_single_bits() {
496 for &v in &[
497 crate::ported::zsh_h::ZCONTEXT_HIST,
498 crate::ported::zsh_h::ZCONTEXT_LEX,
499 crate::ported::zsh_h::ZCONTEXT_PARSE,
500 ] {
501 assert!((v as u32).is_power_of_two(), "{} must be a single bit", v);
502 }
503 }
504
505 #[test]
509 fn zcontext_save_partial_zero_round_trip_pops() {
510 let _g = crate::test_util::global_state_lock();
511 reset_cstack();
512 lex_init("");
513 zcontext_save_partial(0);
514 assert!(cstack.lock().unwrap().is_some(), "zero-mask still pushes");
515 zcontext_restore_partial(0);
516 assert!(cstack.lock().unwrap().is_none(), "restore drains to None");
517 }
518
519 #[test]
523 fn zcontext_save_hist_restore_with_parse_mask_pops_frame() {
524 let _g = crate::test_util::global_state_lock();
525 reset_cstack();
526 lex_init("");
527 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
528 assert!(cstack.lock().unwrap().is_some());
529 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_PARSE);
530 assert!(cstack.lock().unwrap().is_none(), "frame popped regardless");
531 }
532
533 #[test]
536 fn zcontext_save_shorthand_pushes_one_frame() {
537 let _g = crate::test_util::global_state_lock();
538 reset_cstack();
539 lex_init("");
540 zcontext_save();
541 {
543 let head = cstack.lock().unwrap();
544 assert!(head.is_some(), "save pushed a frame");
545 assert!(head.as_ref().unwrap().next.is_none(), "stack depth = 1");
546 }
547 zcontext_restore();
548 assert!(cstack.lock().unwrap().is_none());
549
550 zcontext_save_partial(
551 crate::ported::zsh_h::ZCONTEXT_HIST
552 | crate::ported::zsh_h::ZCONTEXT_LEX
553 | crate::ported::zsh_h::ZCONTEXT_PARSE,
554 );
555 {
556 let head = cstack.lock().unwrap();
557 assert!(head.is_some(), "full-mask save_partial pushed a frame");
558 assert!(head.as_ref().unwrap().next.is_none(), "stack depth = 1");
559 }
560 zcontext_restore();
561 }
562
563 #[test]
565 fn zcontext_restore_drains_exactly_one_frame() {
566 let _g = crate::test_util::global_state_lock();
567 reset_cstack();
568 lex_init("");
569 zcontext_save();
570 zcontext_save();
571 zcontext_restore();
573 assert!(cstack.lock().unwrap().is_some());
575 zcontext_restore();
576 assert!(cstack.lock().unwrap().is_none());
577 }
578
579 #[test]
582 fn zcontext_restore_shorthand_on_empty_no_panic() {
583 let _g = crate::test_util::global_state_lock();
584 reset_cstack();
585 zcontext_restore();
586 assert!(cstack.lock().unwrap().is_none());
587 }
588
589 #[test]
591 fn zcontext_save_hist_lex_combo_round_trip() {
592 let _g = crate::test_util::global_state_lock();
593 reset_cstack();
594 lex_init("");
595 let mask = crate::ported::zsh_h::ZCONTEXT_HIST | crate::ported::zsh_h::ZCONTEXT_LEX;
596 zcontext_save_partial(mask);
597 zcontext_restore_partial(mask);
598 assert!(cstack.lock().unwrap().is_none());
599 }
600
601 #[test]
603 fn zcontext_save_lex_parse_combo_round_trip() {
604 let _g = crate::test_util::global_state_lock();
605 reset_cstack();
606 lex_init("");
607 let mask = crate::ported::zsh_h::ZCONTEXT_LEX | crate::ported::zsh_h::ZCONTEXT_PARSE;
608 zcontext_save_partial(mask);
609 zcontext_restore_partial(mask);
610 assert!(cstack.lock().unwrap().is_none());
611 }
612
613 #[test]
615 fn many_restores_on_empty_no_panic() {
616 let _g = crate::test_util::global_state_lock();
617 reset_cstack();
618 for _ in 0..10 {
619 zcontext_restore_partial(0);
620 }
621 assert!(cstack.lock().unwrap().is_none());
622 }
623
624 #[test]
626 fn restore_on_empty_via_full_shorthand_no_panic() {
627 let _g = crate::test_util::global_state_lock();
628 reset_cstack();
629 zcontext_restore();
630 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
631 zcontext_restore_partial(
632 crate::ported::zsh_h::ZCONTEXT_HIST | crate::ported::zsh_h::ZCONTEXT_LEX,
633 );
634 assert!(cstack.lock().unwrap().is_none());
635 }
636
637 #[test]
646 fn zcontext_save_partial_hist_pushes_one_frame() {
647 let _g = crate::test_util::global_state_lock();
648 reset_cstack();
649 zcontext_save_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
650 assert!(
651 cstack.lock().unwrap().is_some(),
652 "frame must exist after save"
653 );
654 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
655 assert!(cstack.lock().unwrap().is_none(), "frame must be drained");
656 }
657
658 #[test]
660 fn zcontext_save_full_then_restore_full_clears_stack() {
661 let _g = crate::test_util::global_state_lock();
662 reset_cstack();
663 zcontext_save();
664 assert!(cstack.lock().unwrap().is_some());
665 zcontext_restore();
666 assert!(cstack.lock().unwrap().is_none());
667 }
668
669 #[test]
671 fn zcontext_save_three_then_restore_three_clears_stack() {
672 let _g = crate::test_util::global_state_lock();
673 reset_cstack();
674 zcontext_save();
675 zcontext_save();
676 zcontext_save();
677 zcontext_restore();
678 zcontext_restore();
679 zcontext_restore();
680 assert!(
681 cstack.lock().unwrap().is_none(),
682 "all 3 frames must drain (LIFO)"
683 );
684 }
685
686 #[test]
688 fn zcontext_save_equals_or_of_three_flags_behavior() {
689 let _g = crate::test_util::global_state_lock();
690 reset_cstack();
691 zcontext_save();
692 let depth1 = cstack.lock().unwrap().is_some();
693 reset_cstack();
694 zcontext_save_partial(
695 crate::ported::zsh_h::ZCONTEXT_HIST
696 | crate::ported::zsh_h::ZCONTEXT_LEX
697 | crate::ported::zsh_h::ZCONTEXT_PARSE,
698 );
699 let depth2 = cstack.lock().unwrap().is_some();
700 assert_eq!(
701 depth1, depth2,
702 "shorthand and explicit OR must produce same push behavior"
703 );
704 zcontext_restore();
705 }
706
707 #[test]
709 fn zcontext_all_flags_or_equals_seven() {
710 let all = crate::ported::zsh_h::ZCONTEXT_HIST
711 | crate::ported::zsh_h::ZCONTEXT_LEX
712 | crate::ported::zsh_h::ZCONTEXT_PARSE;
713 assert_eq!(all, 7, "HIST|LEX|PARSE must equal 0b111 = 7");
714 }
715
716 #[test]
718 fn zcontext_flags_pairwise_distinct() {
719 use crate::ported::zsh_h::{ZCONTEXT_HIST, ZCONTEXT_LEX, ZCONTEXT_PARSE};
720 let codes = [ZCONTEXT_HIST, ZCONTEXT_LEX, ZCONTEXT_PARSE];
721 let unique: std::collections::HashSet<_> = codes.iter().copied().collect();
722 assert_eq!(unique.len(), codes.len(), "ZCONTEXT_* must be distinct");
723 }
724
725 #[test]
727 fn zcontext_save_restore_is_lifo() {
728 let _g = crate::test_util::global_state_lock();
729 reset_cstack();
730 zcontext_save();
731 zcontext_save();
732 assert!(cstack.lock().unwrap().is_some());
733 zcontext_restore();
734 assert!(cstack.lock().unwrap().is_some(), "one frame still on stack");
735 zcontext_restore();
736 assert!(cstack.lock().unwrap().is_none(), "all frames drained");
737 }
738
739 #[test]
741 fn zcontext_save_partial_all_bits_no_panic() {
742 let _g = crate::test_util::global_state_lock();
743 reset_cstack();
744 zcontext_save_partial(-1);
745 zcontext_restore_partial(-1);
746 assert!(cstack.lock().unwrap().is_none());
747 }
748
749 #[test]
751 fn zcontext_restore_partial_all_bits_on_empty_no_panic() {
752 let _g = crate::test_util::global_state_lock();
753 reset_cstack();
754 zcontext_restore_partial(-1);
755 assert!(cstack.lock().unwrap().is_none());
756 }
757
758 #[test]
760 fn zcontext_many_saves_then_full_drain_clean() {
761 let _g = crate::test_util::global_state_lock();
762 reset_cstack();
763 for _ in 0..20 {
764 zcontext_save();
765 }
766 for _ in 0..20 {
767 zcontext_restore();
768 }
769 assert!(
770 cstack.lock().unwrap().is_none(),
771 "after 20 paired save/restore, stack must be empty"
772 );
773 }
774
775 #[test]
777 fn zcontext_restore_with_subset_mask_drains_frame() {
778 let _g = crate::test_util::global_state_lock();
779 reset_cstack();
780 zcontext_save_partial(
781 crate::ported::zsh_h::ZCONTEXT_HIST | crate::ported::zsh_h::ZCONTEXT_LEX,
782 );
783 zcontext_restore_partial(crate::ported::zsh_h::ZCONTEXT_HIST);
784 assert!(
785 cstack.lock().unwrap().is_none(),
786 "restore with subset mask still pops the frame"
787 );
788 }
789}