1use crate::SheetId;
9use crate::engine::addr::GridAddr;
10use crate::engine::named_range::{NameScope, NamedDefinition};
11use crate::engine::row_visibility::RowVisibilitySource;
12use crate::engine::vertex::VertexId;
13use crate::reference::CellRef;
14use formualizer_common::LiteralValue;
15use formualizer_parse::parser::ASTNode;
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct SpillSnapshot {
19 pub target_cells: Vec<CellRef>,
21 pub values: Vec<Vec<LiteralValue>>,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Default)]
31pub struct ChangeEventMeta {
32 pub actor_id: Option<String>,
33 pub correlation_id: Option<String>,
34 pub reason: Option<String>,
35}
36
37#[derive(Debug, Clone, PartialEq)]
39pub enum ChangeEvent {
40 SetValue {
42 addr: CellRef,
43 old_value: Option<LiteralValue>,
44 old_formula: Option<ASTNode>,
45 new: LiteralValue,
46 },
47 SetFormula {
48 addr: CellRef,
49 old_value: Option<LiteralValue>,
50 old_formula: Option<ASTNode>,
51 new: ASTNode,
52 },
53 TableResized {
54 name: String,
55 old_range: crate::reference::RangeRef,
56 new_range: crate::reference::RangeRef,
57 old_empty_body: bool,
58 new_empty_body: bool,
59 },
60 SetRowVisibility {
61 sheet_id: SheetId,
62 row0: u32,
63 source: RowVisibilitySource,
64 old_hidden: bool,
65 new_hidden: bool,
66 },
67 AddVertex {
69 id: VertexId,
70 coord: GridAddr,
71 sheet_id: SheetId,
72 value: Option<LiteralValue>,
73 formula: Option<ASTNode>,
74 kind: Option<crate::engine::vertex::VertexKind>,
75 flags: Option<u8>,
76 },
77 RemoveVertex {
78 id: VertexId,
79 old_value: Option<LiteralValue>,
81 old_formula: Option<ASTNode>,
82 old_dependencies: Vec<VertexId>, old_dependents: Vec<VertexId>, coord: Option<GridAddr>,
85 sheet_id: Option<SheetId>,
86 kind: Option<crate::engine::vertex::VertexKind>,
87 flags: Option<u8>,
88 },
89
90 CompoundStart {
92 description: String, depth: usize,
94 },
95 CompoundEnd {
96 depth: usize,
97 },
98
99 VertexMoved {
101 id: VertexId,
102 sheet_id: SheetId,
103 old_coord: GridAddr,
104 new_coord: GridAddr,
105 },
106 FormulaAdjusted {
107 id: VertexId,
108 addr: Option<CellRef>,
110 old_ast: ASTNode,
111 new_ast: ASTNode,
112 },
113 NamedRangeAdjusted {
114 name: String,
115 scope: NameScope,
116 old_definition: NamedDefinition,
117 new_definition: NamedDefinition,
118 },
119 EdgeAdded {
120 from: VertexId,
121 to: VertexId,
122 },
123 EdgeRemoved {
124 from: VertexId,
125 to: VertexId,
126 },
127
128 DefineName {
130 name: String,
131 scope: NameScope,
132 definition: NamedDefinition,
133 },
134 UpdateName {
135 name: String,
136 scope: NameScope,
137 old_definition: NamedDefinition,
138 new_definition: NamedDefinition,
139 },
140 DeleteName {
141 name: String,
142 scope: NameScope,
143 old_definition: Option<NamedDefinition>,
144 },
145
146 SpillCommitted {
148 anchor: VertexId,
149 old: Option<SpillSnapshot>,
150 new: SpillSnapshot,
151 },
152 SpillCleared {
153 anchor: VertexId,
154 old: SpillSnapshot,
155 },
156 StagedFormulaCellChanged {
170 sheet: String,
171 row: u32,
172 col: u32,
173 old: Option<String>,
174 new: Option<String>,
175 },
176}
177
178#[derive(Debug, Clone, PartialEq)]
185pub(crate) struct FormulaRunAdjusted {
186 pub(crate) first: VertexId,
187 pub(crate) len: u32,
188 pub(crate) sheet_id: SheetId,
189 pub(crate) col: u32,
190 pub(crate) row0: u32,
191 pub(crate) old_first: ASTNode,
192 pub(crate) new_first: ASTNode,
193}
194
195impl FormulaRunAdjusted {
196 #[inline]
197 pub(crate) fn len(&self) -> usize {
198 self.len as usize
199 }
200
201 pub(crate) fn event(&self, i: u32) -> ChangeEvent {
203 let relocate = |ast: &ASTNode| {
204 if i == 0 {
205 return ast.clone();
206 }
207 crate::engine::template::relocate::instantiate_member_ast(ast, i64::from(i), 0)
210 .expect("a run member's formula relocates")
211 };
212 ChangeEvent::FormulaAdjusted {
213 id: VertexId(self.first.0 + i),
214 addr: Some(CellRef::new(
215 self.sheet_id,
216 crate::reference::Coord::new(self.row0 + i, self.col, true, true),
217 )),
218 old_ast: relocate(&self.old_first),
219 new_ast: relocate(&self.new_first),
220 }
221 }
222
223 pub(crate) fn events(&self) -> impl Iterator<Item = ChangeEvent> + '_ {
225 (0..self.len).map(|i| self.event(i))
226 }
227}
228
229#[derive(Debug)]
233struct LazyEntry {
234 pos: usize,
235 seq0: u64,
236 group: Option<u64>,
237 meta: ChangeEventMeta,
238 run: std::sync::Arc<FormulaRunAdjusted>,
239}
240
241#[derive(Debug, Default)]
247struct Retained {
248 events: Vec<ChangeEvent>,
249 metas: Vec<ChangeEventMeta>,
250 seqs: Vec<u64>,
251 groups: Vec<Option<u64>>,
252 lazy: Vec<LazyEntry>,
253}
254
255impl Retained {
256 fn expand(&mut self) -> usize {
261 let Some(first) = self.lazy.first() else {
262 return 0;
263 };
264 let start = first.pos;
265 let tail_events = self.events.split_off(start);
266 let tail_metas = self.metas.split_off(start);
267 let tail_seqs = self.seqs.split_off(start);
268 let tail_groups = self.groups.split_off(start);
269 let tail_len = tail_events.len();
270 let added: usize = self.lazy.iter().map(|e| e.run.len()).sum();
271 self.events.reserve(tail_len + added);
272 self.metas.reserve(tail_len + added);
273 self.seqs.reserve(tail_len + added);
274 self.groups.reserve(tail_len + added);
275 let mut tail = tail_events
276 .into_iter()
277 .zip(tail_metas)
278 .zip(tail_seqs.into_iter().zip(tail_groups));
279 let mut p = start;
280 for entry in std::mem::take(&mut self.lazy) {
281 while p < entry.pos {
282 let ((event, meta), (seq, group)) = tail.next().expect("record position in range");
283 self.events.push(event);
284 self.metas.push(meta);
285 self.seqs.push(seq);
286 self.groups.push(group);
287 p += 1;
288 }
289 for (i, event) in entry.run.events().enumerate() {
290 self.events.push(event);
291 self.metas.push(entry.meta.clone());
292 self.seqs.push(entry.seq0 + i as u64);
293 self.groups.push(entry.group);
294 }
295 }
296 for ((event, meta), (seq, group)) in tail {
297 self.events.push(event);
298 self.metas.push(meta);
299 self.seqs.push(seq);
300 self.groups.push(group);
301 }
302 tail_len + added
303 }
304}
305
306struct RetainedCell {
317 inner: std::cell::UnsafeCell<Retained>,
318 expanded: std::sync::OnceLock<()>,
319 len: usize,
321 pending_records: usize,
323 #[cfg(test)]
326 work: std::sync::atomic::AtomicUsize,
327}
328
329unsafe impl Sync for RetainedCell {}
338
339const _: () = {
340 const fn assert_send_sync<T: Send + Sync>() {}
341 assert_send_sync::<Retained>();
342};
343
344impl Default for RetainedCell {
345 fn default() -> Self {
346 Self {
347 inner: std::cell::UnsafeCell::new(Retained::default()),
348 expanded: std::sync::OnceLock::from(()),
349 len: 0,
350 pending_records: 0,
351 #[cfg(test)]
352 work: std::sync::atomic::AtomicUsize::new(0),
353 }
354 }
355}
356
357impl std::fmt::Debug for RetainedCell {
358 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359 f.debug_struct("RetainedCell")
360 .field("len", &self.len)
361 .field("expanded", &self.expanded.get().is_some())
362 .finish()
363 }
364}
365
366impl RetainedCell {
367 #[inline]
368 fn note_work(&self, _work: usize) {
369 #[cfg(test)]
370 self.work
371 .fetch_add(_work, std::sync::atomic::Ordering::Relaxed);
372 }
373
374 fn get(&self) -> &Retained {
376 self.expanded.get_or_init(|| {
377 let inner = unsafe { &mut *self.inner.get() };
380 self.note_work(inner.expand());
381 });
382 unsafe { &*self.inner.get() }
384 }
385
386 fn settle(&mut self) -> &mut Retained {
388 if self.expanded.get().is_none() {
389 let work = self.inner.get_mut().expand();
390 self.note_work(work);
391 self.expanded = std::sync::OnceLock::from(());
392 self.pending_records = 0;
393 }
394 self.inner.get_mut()
395 }
396
397 fn plain_len(&mut self) -> usize {
399 self.inner.get_mut().events.len()
400 }
401
402 fn push(&mut self, event: ChangeEvent, meta: ChangeEventMeta, seq: u64, group: Option<u64>) {
403 let r = self.inner.get_mut();
404 r.events.push(event);
405 r.metas.push(meta);
406 r.seqs.push(seq);
407 r.groups.push(group);
408 self.len += 1;
409 }
410
411 fn push_run(&mut self, entry: LazyEntry) {
412 self.len += entry.run.len();
413 self.inner.get_mut().lazy.push(entry);
414 self.pending_records += 1;
415 self.expanded = std::sync::OnceLock::new();
416 }
417
418 fn drain_front(&mut self, n: usize) {
419 let r = self.settle();
420 r.events.drain(0..n);
421 r.metas.drain(0..n);
422 r.seqs.drain(0..n);
423 r.groups.drain(0..n);
424 self.len = r.events.len();
425 }
426
427 fn truncate(&mut self, len: usize) {
428 let r = self.settle();
429 r.events.truncate(len);
430 r.metas.truncate(len);
431 r.seqs.truncate(len);
432 r.groups.truncate(len);
433 self.len = r.events.len();
434 }
435
436 fn split_off(&mut self, index: usize) -> Vec<ChangeEvent> {
437 let r = self.settle();
438 let events = r.events.split_off(index);
439 let _ = r.metas.split_off(index);
440 let _ = r.seqs.split_off(index);
441 let _ = r.groups.split_off(index);
442 self.len = r.events.len();
443 events
444 }
445
446 fn clear(&mut self) {
447 *self = Self {
448 #[cfg(test)]
449 work: std::sync::atomic::AtomicUsize::new(
450 self.work.load(std::sync::atomic::Ordering::Relaxed),
451 ),
452 ..Self::default()
453 };
454 }
455}
456
457#[derive(Debug, Default)]
459pub struct ChangeLog {
460 enabled: bool,
461 max_changelog_events: Option<usize>,
463 compound_depth: usize,
465 next_seq: u64,
466 spill_clear_revision: u64,
467 group_stack: Vec<u64>,
469 next_group_id: u64,
470
471 current_meta: ChangeEventMeta,
472
473 retained: RetainedCell,
478}
479
480#[derive(Debug)]
485pub(crate) struct MutationCapture {
486 events: Vec<ChangeEvent>,
487 lazy: Vec<(usize, std::sync::Arc<FormulaRunAdjusted>)>,
490 compound_depth: usize,
491 current_meta: ChangeEventMeta,
492}
493
494impl MutationCapture {
495 pub(crate) fn new(current_meta: ChangeEventMeta) -> Self {
496 Self {
497 events: Vec::new(),
498 lazy: Vec::new(),
499 compound_depth: 0,
500 current_meta,
501 }
502 }
503
504 pub(crate) fn len(&self) -> usize {
507 self.events.len()
508 }
509
510 pub(crate) fn events(&self) -> &[ChangeEvent] {
515 &self.events
516 }
517
518 pub(crate) fn lazy_len(&self) -> usize {
520 self.lazy.len()
521 }
522
523 pub(crate) fn record_lazy(&mut self, run: FormulaRunAdjusted) {
525 if run.len == 0 {
526 return;
527 }
528 self.lazy
529 .push((self.events.len(), std::sync::Arc::new(run)));
530 }
531
532 pub(crate) fn expanded_events_from(&self, start: usize, lazy_start: usize) -> Vec<ChangeEvent> {
535 let lazy = &self.lazy[lazy_start.min(self.lazy.len())..];
536 let mut out = Vec::with_capacity(
537 self.events.len().saturating_sub(start)
538 + lazy.iter().map(|(_, r)| r.len()).sum::<usize>(),
539 );
540 let mut k = 0;
541 for p in start..=self.events.len() {
542 while k < lazy.len() && lazy[k].0 <= p {
543 out.extend(lazy[k].1.events());
544 k += 1;
545 }
546 if let Some(e) = self.events.get(p) {
547 out.push(e.clone());
548 }
549 }
550 out
551 }
552
553 pub(crate) fn close_compounds(&mut self) {
554 while self.compound_depth > 0 {
555 self.end_compound();
556 }
557 }
558
559 fn push(&mut self, event: ChangeEvent) {
560 self.events.push(event);
561 }
562}
563
564pub(crate) fn compound_start_description<'a>(
569 end: usize,
570 event: impl Fn(usize) -> &'a ChangeEvent,
571) -> Option<&'a str> {
572 if !matches!(event(end), ChangeEvent::CompoundEnd { .. }) {
573 return None;
574 }
575 let mut open = 1usize;
576 for i in (0..end).rev() {
577 match event(i) {
578 ChangeEvent::CompoundEnd { .. } => open += 1,
579 ChangeEvent::CompoundStart { description, .. } => {
580 open -= 1;
581 if open == 0 {
582 return Some(description.as_str());
583 }
584 }
585 _ => {}
586 }
587 }
588 None
589}
590
591impl ChangeLogger for MutationCapture {
592 fn record(&mut self, event: ChangeEvent) {
593 self.push(event);
594 }
595
596 fn set_enabled(&mut self, _: bool) {}
597
598 fn begin_compound(&mut self, description: String) {
599 self.compound_depth += 1;
600 self.push(ChangeEvent::CompoundStart {
601 description,
602 depth: self.compound_depth,
603 });
604 }
605
606 fn end_compound(&mut self) {
607 if self.compound_depth == 0 {
608 return;
609 }
610 self.push(ChangeEvent::CompoundEnd {
611 depth: self.compound_depth,
612 });
613 self.compound_depth -= 1;
614 }
615}
616
617impl ChangeLog {
618 pub fn new() -> Self {
619 Self {
620 enabled: true,
621 max_changelog_events: None,
622 compound_depth: 0,
623 next_seq: 0,
624 spill_clear_revision: 0,
625 group_stack: Vec::new(),
626 next_group_id: 1,
627 current_meta: ChangeEventMeta::default(),
628 retained: RetainedCell::default(),
629 }
630 }
631
632 pub fn with_max_changelog_events(max: usize) -> Self {
633 let mut out = Self::new();
634 out.max_changelog_events = Some(max);
635 out
636 }
637
638 pub fn set_max_changelog_events(&mut self, max: Option<usize>) {
639 self.max_changelog_events = max;
640 self.enforce_cap();
641 }
642
643 fn enforce_cap(&mut self) {
644 let Some(max) = self.max_changelog_events else {
645 return;
646 };
647 if max == 0 {
648 self.retained.clear();
649 return;
650 }
651 if self.len() <= max {
652 return;
653 }
654 let drop_n = self.len() - max;
655 self.retained.drain_front(drop_n);
656 }
657
658 fn replay_lazy(
659 &mut self,
660 run: std::sync::Arc<FormulaRunAdjusted>,
661 meta: &ChangeEventMeta,
662 retain: bool,
663 ) {
664 if !self.enabled {
665 return;
666 }
667 let seq0 = self.next_seq;
668 self.next_seq += run.len as u64;
669 if retain {
670 let entry = LazyEntry {
671 pos: self.retained.plain_len(),
672 seq0,
673 group: self.group_stack.last().copied(),
674 meta: meta.clone(),
675 run,
676 };
677 self.retained.push_run(entry);
678 }
679 }
680
681 fn replay_record(&mut self, event: ChangeEvent, meta: &ChangeEventMeta, retain: bool) {
682 if !self.enabled {
683 return;
684 }
685 let seq = self.next_seq;
686 self.next_seq += 1;
687 if retain {
688 self.note_spill_clear(&event);
689 let group = self.group_stack.last().copied();
690 self.retained.push(event, meta.clone(), seq, group);
691 }
692 }
693
694 fn replay_begin_compound(&mut self, description: String, meta: &ChangeEventMeta, retain: bool) {
695 self.compound_depth += 1;
696 if self.compound_depth == 1 {
697 let gid = self.next_group_id;
698 self.next_group_id += 1;
699 self.group_stack.push(gid);
700 } else if let Some(&gid) = self.group_stack.last() {
701 self.group_stack.push(gid);
702 }
703 self.replay_record(
704 ChangeEvent::CompoundStart {
705 description,
706 depth: self.compound_depth,
707 },
708 meta,
709 retain,
710 );
711 }
712
713 fn replay_end_compound(&mut self, meta: &ChangeEventMeta, retain: bool) {
714 if self.compound_depth == 0 {
715 return;
716 }
717 self.replay_record(
718 ChangeEvent::CompoundEnd {
719 depth: self.compound_depth,
720 },
721 meta,
722 retain,
723 );
724 self.compound_depth -= 1;
725 self.group_stack.pop();
726 }
727
728 fn replay_capture(&mut self, capture: MutationCapture, retain: bool) {
729 let mut lazy = capture.lazy.into_iter().peekable();
730 for (p, event) in capture.events.into_iter().enumerate() {
731 while let Some((_, run)) = lazy.next_if(|(pos, _)| *pos <= p) {
732 self.replay_lazy(run, &capture.current_meta, retain);
733 }
734 match event {
735 ChangeEvent::CompoundStart { description, .. } => {
736 self.replay_begin_compound(description, &capture.current_meta, retain);
737 }
738 ChangeEvent::CompoundEnd { .. } => {
739 self.replay_end_compound(&capture.current_meta, retain);
740 }
741 event => self.replay_record(event, &capture.current_meta, retain),
742 }
743 }
744 for (_, run) in lazy {
745 self.replay_lazy(run, &capture.current_meta, retain);
746 }
747 if retain {
748 self.enforce_cap();
749 }
750 }
751
752 pub(crate) fn current_meta(&self) -> ChangeEventMeta {
753 self.current_meta.clone()
754 }
755
756 pub(crate) fn publish_capture(&mut self, capture: MutationCapture) {
757 self.replay_capture(capture, true);
758 }
759
760 pub(crate) fn discard_capture(&mut self, capture: MutationCapture) {
761 self.replay_capture(capture, false);
762 }
763
764 pub fn spill_clear_revision(&self) -> u64 {
767 self.spill_clear_revision
768 }
769
770 fn note_spill_clear(&mut self, event: &ChangeEvent) {
771 if matches!(event, ChangeEvent::SpillCleared { .. }) {
772 self.spill_clear_revision = self.spill_clear_revision.wrapping_add(1);
773 }
774 }
775
776 pub fn record(&mut self, event: ChangeEvent) {
777 if self.enabled {
778 self.note_spill_clear(&event);
779 let seq = self.next_seq;
780 self.next_seq += 1;
781 let current_group = self.group_stack.last().copied();
782 self.retained
783 .push(event, self.current_meta.clone(), seq, current_group);
784 self.enforce_cap();
785 }
786 }
787
788 pub fn record_with_meta(&mut self, event: ChangeEvent, meta: ChangeEventMeta) {
790 if self.enabled {
791 self.note_spill_clear(&event);
792 let seq = self.next_seq;
793 self.next_seq += 1;
794 let current_group = self.group_stack.last().copied();
795 self.retained.push(event, meta, seq, current_group);
796 self.enforce_cap();
797 }
798 }
799
800 pub fn begin_compound(&mut self, description: String) {
802 self.compound_depth += 1;
803 if self.compound_depth == 1 {
804 let gid = self.next_group_id;
806 self.next_group_id += 1;
807 self.group_stack.push(gid);
808 } else {
809 if let Some(&gid) = self.group_stack.last() {
811 self.group_stack.push(gid);
812 }
813 }
814 if self.enabled {
815 self.record(ChangeEvent::CompoundStart {
816 description,
817 depth: self.compound_depth,
818 });
819 }
820 }
821
822 pub fn end_compound(&mut self) {
824 if self.compound_depth > 0 {
825 if self.enabled {
826 self.record(ChangeEvent::CompoundEnd {
827 depth: self.compound_depth,
828 });
829 }
830 self.compound_depth -= 1;
831 self.group_stack.pop();
832 }
833 }
834
835 #[cfg(test)]
837 pub(crate) fn unexpanded_run_records(&self) -> usize {
838 if self.retained.expanded.get().is_some() {
839 0
840 } else {
841 self.retained.pending_records
842 }
843 }
844
845 #[cfg(test)]
849 pub(crate) fn expansion_work(&self) -> usize {
850 self.retained
851 .work
852 .load(std::sync::atomic::Ordering::Relaxed)
853 }
854
855 pub fn events(&self) -> &[ChangeEvent] {
856 &self.retained.get().events
857 }
858
859 pub fn event_meta(&self, index: usize) -> Option<&ChangeEventMeta> {
860 self.retained.get().metas.get(index)
861 }
862
863 pub fn set_actor_id(&mut self, actor_id: Option<String>) {
864 self.current_meta.actor_id = actor_id;
865 }
866
867 pub fn set_correlation_id(&mut self, correlation_id: Option<String>) {
868 self.current_meta.correlation_id = correlation_id;
869 }
870
871 pub fn set_reason(&mut self, reason: Option<String>) {
872 self.current_meta.reason = reason;
873 }
874
875 pub fn truncate(&mut self, len: usize) {
877 self.retained.truncate(len);
878 }
879
880 pub fn clear(&mut self) {
881 self.retained.clear();
882 self.compound_depth = 0;
883 self.group_stack.clear();
884 }
885
886 pub fn len(&self) -> usize {
887 self.retained.len
888 }
889
890 pub fn is_empty(&self) -> bool {
891 self.len() == 0
892 }
893
894 pub fn take_from(&mut self, index: usize) -> Vec<ChangeEvent> {
896 self.retained.split_off(index)
897 }
898
899 pub fn set_enabled(&mut self, enabled: bool) {
901 self.enabled = enabled;
902 }
903
904 pub fn compound_depth(&self) -> usize {
906 self.compound_depth
907 }
908
909 pub fn meta(&self, index: usize) -> Option<(u64, Option<u64>)> {
911 let r = self.retained.get();
912 r.seqs.get(index).copied().zip(r.groups.get(index).copied())
913 }
914
915 pub fn last_group_indices(&self) -> Vec<usize> {
917 let groups = &self.retained.get().groups;
918 if let Some(&last_gid) = groups.iter().rev().flatten().next() {
919 let idxs: Vec<usize> = groups
920 .iter()
921 .enumerate()
922 .filter_map(|(i, g)| if *g == Some(last_gid) { Some(i) } else { None })
923 .collect();
924 if !idxs.is_empty() {
925 return idxs;
926 }
927 }
928 self.len().checked_sub(1).into_iter().collect()
929 }
930}
931
932pub trait ChangeLogger {
934 fn record(&mut self, event: ChangeEvent);
935 fn set_enabled(&mut self, enabled: bool);
936 fn begin_compound(&mut self, description: String);
937 fn end_compound(&mut self);
938}
939
940impl ChangeLogger for ChangeLog {
941 fn record(&mut self, event: ChangeEvent) {
942 ChangeLog::record(self, event);
943 }
944
945 fn set_enabled(&mut self, enabled: bool) {
946 self.enabled = enabled;
947 }
948
949 fn begin_compound(&mut self, description: String) {
950 ChangeLog::begin_compound(self, description);
951 }
952
953 fn end_compound(&mut self) {
954 ChangeLog::end_compound(self);
955 }
956}
957
958pub struct NullChangeLogger;
960
961impl ChangeLogger for NullChangeLogger {
962 fn record(&mut self, _: ChangeEvent) {}
963 fn set_enabled(&mut self, _: bool) {}
964 fn begin_compound(&mut self, _: String) {}
965 fn end_compound(&mut self) {}
966}