latexml_core/common/
local_assignments.rs1use std::{cell::RefCell, rc::Rc};
27
28use once_cell::sync::Lazy;
29
30use crate::{
31 Digested, alignment::template::Template, definition::conditional::IfFrame, token::Token,
32};
33
34#[derive(Debug, Default)]
38pub struct Localized {
39 dual_branch: Vec<&'static str>,
40 if_frames: Vec<Option<Rc<RefCell<IfFrame>>>>,
41 current_token: Vec<Token>,
42 align_group_count: Vec<i32>, reading_alignment: Vec<Digested>,
44 build_template: Vec<Template>,
45 unlocked: Vec<bool>,
46}
47
48macro_rules! locals {
49 () => {
50 (*LOCALIZED_VARS).borrow()
51 };
52}
53macro_rules! locals_mut {
54 () => {
55 (*LOCALIZED_VARS).borrow_mut()
56 };
57}
58
59#[thread_local]
60static LOCALIZED_VARS: Lazy<RefCell<Localized>> = Lazy::new(|| RefCell::new(Localized::default()));
61
62pub fn initialize_localized() { *locals_mut!() = Localized::default(); }
65
66pub fn set_ifframe(if_frame: Option<Rc<RefCell<IfFrame>>>) {
68 locals_mut!().if_frames.push(if_frame);
69}
70
71pub fn get_ifframe() -> Option<Rc<RefCell<IfFrame>>> {
73 match locals!().if_frames.last() {
74 Some(Some(frame)) => Some(Rc::clone(frame)),
75 _ => None,
76 }
77}
78pub fn expire_ifframe() { locals_mut!().if_frames.pop(); }
80pub fn open_conditional_count() -> usize { locals!().if_frames.len() }
84pub fn local_current_token(token: Token) { locals_mut!().current_token.push(token); }
86pub fn expire_current_token() { locals_mut!().current_token.pop(); }
88pub fn get_current_token() -> Option<Token> { locals!().current_token.last().cloned() }
90
91pub fn set_dual_branch(mode: &'static str) { locals_mut!().dual_branch.push(mode); }
93pub fn expire_dual_branch() { locals_mut!().dual_branch.pop(); }
95pub fn get_dual_branch() -> Option<&'static str> { locals!().dual_branch.last().cloned() }
97
98pub fn increment_align_group_count() {
99 let mut locals = locals_mut!();
100 match locals.align_group_count.last_mut() {
101 Some(v) => *v += 1,
102 None => locals.align_group_count.push(1),
103 }
104}
105pub fn decrement_align_group_count() {
106 let mut locals = locals_mut!();
107 match locals.align_group_count.last_mut() {
108 Some(v) => *v -= 1,
109 None => locals.align_group_count.push(-1),
110 }
111}
112
113pub fn state_is_unlocked() -> bool { locals!().unlocked.last().copied().unwrap_or(false) }
114pub fn local_state_unlocked(v: bool) { locals_mut!().unlocked.push(v); }
115pub fn expire_state_unlocked() { locals_mut!().unlocked.pop(); }
116
117pub fn align_group_count() -> i32 {
118 locals!()
119 .align_group_count
120 .last()
121 .copied()
122 .unwrap_or_default()
123}
124pub fn set_align_group_count(v: i32) {
125 match locals_mut!().align_group_count.last_mut() {
126 Some(gc) => {
127 *gc = v;
128 },
129 _ => {
130 locals_mut!().align_group_count.push(v);
131 },
132 }
133}
134pub fn local_align_group_count(v: i32) { locals_mut!().align_group_count.push(v); }
135pub fn expire_align_group_count() -> Option<i32> { locals_mut!().align_group_count.pop() }
136
137pub struct SuppressedTabMarks(Option<i32>);
160
161impl SuppressedTabMarks {
162 pub fn for_argument_scan() -> Self {
163 if has_reading_alignment() {
164 let saved = align_group_count();
165 set_align_group_count(1_000_000);
166 Self(Some(saved))
167 } else {
168 Self(None)
169 }
170 }
171}
172
173impl Drop for SuppressedTabMarks {
174 fn drop(&mut self) {
175 if let Some(saved) = self.0 {
176 set_align_group_count(saved);
177 }
178 }
179}
180
181pub fn get_reading_alignment() -> Option<Digested> { locals!().reading_alignment.last().cloned() }
182pub fn has_reading_alignment() -> bool { !locals!().reading_alignment.is_empty() }
183pub fn local_reading_alignment(alignment: &Digested) {
184 locals_mut!().reading_alignment.push(alignment.clone());
185}
186pub fn expire_reading_alignment() -> Option<Digested> { locals_mut!().reading_alignment.pop() }
187
188pub fn local_build_template(template: Template) { locals_mut!().build_template.push(template); }
189pub fn set_build_template(template: Template) {
190 *locals_mut!()
191 .build_template
192 .last_mut()
193 .expect("set_build_template should not be called before the first local_build_template") =
194 template;
195}
196pub fn take_build_template() -> Option<Template> { locals_mut!().build_template.pop() }
197
198pub fn with_current_build_template<R, FnR>(caller: FnR) -> R
199where FnR: FnOnce(Option<&mut Template>) -> R {
200 caller(locals_mut!().build_template.last_mut())
201}
202
203pub struct LocalGuard {
210 expire: fn(),
211}
212impl Drop for LocalGuard {
213 fn drop(&mut self) { (self.expire)(); }
214}
215
216pub fn local_current_token_guard(token: Token) -> LocalGuard {
218 local_current_token(token);
219 LocalGuard { expire: expire_current_token }
220}
221
222pub fn local_ifframe_guard(frame: Option<Rc<RefCell<IfFrame>>>) -> LocalGuard {
224 set_ifframe(frame);
225 LocalGuard { expire: expire_ifframe }
226}
227
228pub fn local_dual_branch_guard(mode: &'static str) -> LocalGuard {
230 set_dual_branch(mode);
231 LocalGuard { expire: expire_dual_branch }
232}
233
234pub fn local_align_group_count_guard(v: i32) -> LocalGuard {
236 local_align_group_count(v);
237 LocalGuard {
238 expire: || {
239 expire_align_group_count();
240 },
241 }
242}
243
244pub fn local_state_unlocked_guard(v: bool) -> LocalGuard {
246 local_state_unlocked(v);
247 LocalGuard { expire: expire_state_unlocked }
248}
249
250pub fn local_reading_alignment_guard(alignment: &Digested) -> LocalGuard {
252 local_reading_alignment(alignment);
253 LocalGuard {
254 expire: || {
255 expire_reading_alignment();
256 },
257 }
258}
259
260#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_dual_branch_push_pop() {
270 initialize_localized();
271 assert_eq!(get_dual_branch(), None);
272 set_dual_branch("true");
273 assert_eq!(get_dual_branch(), Some("true"));
274 set_dual_branch("false");
275 assert_eq!(get_dual_branch(), Some("false"));
276 expire_dual_branch();
277 assert_eq!(get_dual_branch(), Some("true"));
278 expire_dual_branch();
279 assert_eq!(get_dual_branch(), None);
280 }
281
282 #[test]
283 fn test_dual_branch_guard_auto_restore() {
284 initialize_localized();
285 assert_eq!(get_dual_branch(), None);
286 {
287 let _guard = local_dual_branch_guard("guarded");
288 assert_eq!(get_dual_branch(), Some("guarded"));
289 }
290 assert_eq!(get_dual_branch(), None);
292 }
293
294 #[test]
295 fn test_align_group_count() {
296 initialize_localized();
297 assert_eq!(align_group_count(), 0);
298 local_align_group_count(10);
299 assert_eq!(align_group_count(), 10);
300 increment_align_group_count();
301 assert_eq!(align_group_count(), 11);
302 decrement_align_group_count();
303 assert_eq!(align_group_count(), 10);
304 expire_align_group_count();
305 assert_eq!(align_group_count(), 0);
306 }
307
308 #[test]
309 fn test_align_group_count_guard() {
310 initialize_localized();
311 assert_eq!(align_group_count(), 0);
312 {
313 let _guard = local_align_group_count_guard(42);
314 assert_eq!(align_group_count(), 42);
315 increment_align_group_count();
316 assert_eq!(align_group_count(), 43);
317 }
318 assert_eq!(align_group_count(), 0);
320 }
321
322 #[test]
323 fn test_state_unlocked() {
324 initialize_localized();
325 assert!(!state_is_unlocked());
326 local_state_unlocked(true);
327 assert!(state_is_unlocked());
328 {
329 let _guard = local_state_unlocked_guard(false);
330 assert!(!state_is_unlocked());
331 }
332 assert!(state_is_unlocked());
333 expire_state_unlocked();
334 assert!(!state_is_unlocked());
335 }
336}