Skip to main content

tex_engine/engine/state/
tex_state.rs

1/*! Implementation of a plain TeX [`State`]. */
2use crate::commands::primitives::{PrimitiveCommands, PrimitiveIdentifier, PRIMITIVES};
3use crate::commands::{PrimitiveCommand, TeXCommand};
4use crate::engine::fontsystem::Font;
5use crate::engine::mouth::Mouth;
6use crate::engine::state::{GroupType, State, StateChange, StateChangeTracker, StateStack};
7use crate::engine::utils::outputs::Outputs;
8use crate::engine::{EngineAux, EngineTypes};
9use crate::tex::catcodes::{CategoryCode, CategoryCodeScheme};
10use crate::tex::characters::Character;
11use crate::tex::characters::CharacterMap;
12use crate::tex::nodes::boxes::TeXBox;
13use crate::tex::numerics::{MuSkip, Skip};
14use crate::tex::tokens::control_sequences::CSNameMap;
15use crate::tex::tokens::control_sequences::{CSHandler, CSName};
16use crate::tex::tokens::token_lists::TokenList;
17use crate::utils::HMap;
18
19/// Default implementation of a plain TeX [`State`].
20#[derive(Clone)]
21pub struct DefaultState<ET: EngineTypes> {
22    stack: StateStack<ET>,
23    primitives: PrimitiveCommands<ET>,
24    catcodes: CategoryCodeScheme<ET::Char>,
25    sfcodes: <ET::Char as Character>::CharMap<u16>,
26    lccodes: <ET::Char as Character>::CharMap<ET::Char>,
27    uccodes: <ET::Char as Character>::CharMap<ET::Char>,
28    mathcodes: <ET::Char as Character>::CharMap<u32>,
29    delcodes: <ET::Char as Character>::CharMap<ET::Int>,
30    primitive_ints: HMap<PrimitiveIdentifier, ET::Int>,
31    primitive_dims: HMap<PrimitiveIdentifier, ET::Dim>,
32    primitive_skips: HMap<PrimitiveIdentifier, Skip<ET::Dim>>,
33    primitive_muskips: HMap<PrimitiveIdentifier, MuSkip<ET::MuDim>>,
34    primitive_toks: HMap<PrimitiveIdentifier, TokenList<ET::Token>>,
35    int_register: Vec<ET::Int>,
36    dim_register: Vec<ET::Dim>,
37    skip_register: Vec<Skip<ET::Dim>>,
38    muskip_register: Vec<MuSkip<ET::MuDim>>,
39    toks_register: Vec<TokenList<ET::Token>>,
40    box_register: Vec<Option<TeXBox<ET>>>,
41    pub commands: <ET::CSName as CSName<ET::Char>>::Map<TeXCommand<ET>>, //Vec<Option<Command<ET>>>,//HMap<<ET::Token as Token>::CS,Command<ET>>,
42    ac_commands: <ET::Char as Character>::CharMap<Option<TeXCommand<ET>>>,
43    endline_char: Option<ET::Char>,
44    escape_char: Option<ET::Char>,
45    newline_char: Option<ET::Char>,
46    current_font: ET::Font,
47    textfonts: [ET::Font; 16],
48    scriptfonts: [ET::Font; 16],
49    scriptscriptfonts: [ET::Font; 16],
50    empty_list: TokenList<ET::Token>,
51    parshape: Vec<(ET::Dim, ET::Dim)>,
52}
53impl<ET: EngineTypes> DefaultState<ET> {
54    fn tracing_assigns(&self) -> bool {
55        matches!(self.primitive_ints.get(&PRIMITIVES.tracingassigns), Some(v) if *v > ET::Int::default())
56    }
57    fn tracing_restores(&self) -> bool {
58        matches!(self.primitive_ints.get(&PRIMITIVES.tracingrestores), Some(v) if *v > ET::Int::default())
59    }
60    pub fn set_command_direct(&mut self, name: ET::CSName, cmd: Option<TeXCommand<ET>>) {
61        match cmd {
62            None => self.commands.remove(&name),
63            Some(c) => self.commands.insert(name, c),
64        };
65    }
66}
67
68impl<ET: EngineTypes> StateChangeTracker<ET> for DefaultState<ET> {
69    fn stack(&mut self) -> &mut StateStack<ET> {
70        &mut self.stack
71    }
72}
73
74impl<ET: EngineTypes> State<ET> for DefaultState<ET> {
75    fn new(nullfont: ET::Font, aux: &mut EngineAux<ET>) -> Self {
76        let mem = &aux.memory;
77        let mut lccodes: <ET::Char as Character>::CharMap<ET::Char> = CharacterMap::default();
78        let mut uccodes: <ET::Char as Character>::CharMap<ET::Char> = CharacterMap::default();
79        let mut mathcodes: <ET::Char as Character>::CharMap<u32> = CharacterMap::default();
80        for i in 97..123 {
81            *uccodes.get_mut(i.into()) = (i - 32).into();
82            *lccodes.get_mut((i - 32).into()) = i.into();
83            *mathcodes.get_mut(ET::Char::from(i - 32)) =
84                (i as u32 - 32) + (16 * 16) + (7 * 16 * 16 * 16);
85            *mathcodes.get_mut(ET::Char::from(i)) = (i as u32) + (16 * 16) + (7 * 16 * 16 * 16);
86        }
87        for i in 48..58 {
88            *mathcodes.get_mut(ET::Char::from(i)) = (i as u32) + (7 * 16 * 16 * 16);
89        }
90        let mathfonts = array_init::array_init(|_| nullfont.clone());
91        Self {
92            stack: StateStack::default(),
93            primitives: PrimitiveCommands::default(),
94            catcodes: ET::Char::starting_catcode_scheme(),
95            sfcodes: CharacterMap::default(),
96            delcodes: CharacterMap::default(),
97            lccodes,
98            uccodes,
99            mathcodes,
100            current_font: nullfont,
101            primitive_ints: HMap::default(),
102            primitive_dims: HMap::default(),
103            primitive_skips: HMap::default(),
104            primitive_muskips: HMap::default(),
105            primitive_toks: HMap::default(),
106            int_register: Vec::new(),
107            dim_register: Vec::new(),
108            skip_register: Vec::new(),
109            muskip_register: Vec::new(),
110            toks_register: Vec::new(),
111            box_register: Vec::new(),
112            commands: <ET::CSName as CSName<ET::Char>>::Map::default(),
113            ac_commands: <ET::Char as Character>::CharMap::default(),
114            endline_char: Some(ET::Char::from(b'\r')),
115            escape_char: Some(ET::Char::from(b'\\')),
116            newline_char: None,
117            empty_list: mem.empty_list(),
118            parshape: Vec::new(),
119            textfonts: mathfonts.clone(),
120            scriptfonts: mathfonts.clone(),
121            scriptscriptfonts: mathfonts,
122        }
123    }
124
125    fn register_primitive(
126        &mut self,
127        aux: &mut EngineAux<ET>,
128        name: &'static str,
129        cmd: PrimitiveCommand<ET>,
130    ) {
131        let id = self.primitives.register(name, cmd.clone());
132        let name = aux.memory.cs_interner_mut().cs_from_str(name);
133        self.commands
134            .insert(name, TeXCommand::Primitive { name: id, cmd });
135    }
136    fn primitives(&self) -> &PrimitiveCommands<ET> {
137        &self.primitives
138    }
139
140    fn get_group_type(&self) -> Option<GroupType> {
141        self.stack.stack.last().map(|lvl| lvl.group_type)
142    }
143
144    fn get_group_level(&self) -> usize {
145        self.stack.stack.len()
146    }
147
148    fn aftergroup(&mut self, token: ET::Token) {
149        match self.stack.stack.last_mut() {
150            None => (),
151            Some(lvl) => lvl.aftergroup.push(token),
152        }
153    }
154
155    fn push(&mut self, aux: &mut EngineAux<ET>, group_type: GroupType, line_number: usize) {
156        self.stack.push(group_type);
157        let tracing = matches!(self.primitive_ints.get(&PRIMITIVES.tracinggroups), Some(v) if *v > ET::Int::default());
158        if tracing {
159            aux.outputs.write_neg1(format_args!(
160                "{{entering {} group (level {}) at line {}}}",
161                group_type,
162                self.stack.stack.len(),
163                line_number
164            ))
165        }
166    }
167
168    fn pop(&mut self, aux: &mut EngineAux<ET>, mouth: &mut ET::Mouth) {
169        let len = self.stack.stack.len();
170        assert!(len > 0);
171        let traceg = matches!(self.primitive_ints.get(&PRIMITIVES.tracinggroups), Some(v) if *v > ET::Int::default());
172        let trace = self.tracing_restores();
173
174        let (gt, ag, ch) = self.stack.pop();
175
176        if traceg {
177            aux.outputs.write_neg1(format_args!(
178                "{{leaving {} group (level {}) at line {}}}",
179                gt,
180                len,
181                mouth.line_number()
182            ))
183        }
184        ch.close(|c| {
185            match c {
186                //StateChange::Custom { change } => change.restore(aux,self,trace),
187                StateChange::Catcode { char, old } => {
188                    if trace {
189                        aux.outputs.write_neg1(format_args!(
190                            "{{restoring {}catcode{}={}}}",
191                            <ET::Char as Character>::display_opt(self.escape_char),
192                            char.into(),
193                            old
194                        ));
195                    }
196                    *self.catcodes.get_mut(char) = old;
197                }
198                StateChange::CurrentFont(font) => {
199                    if trace {
200                        aux.outputs.write_neg1(format_args!(
201                            "{{restoring current font ={}{}}}",
202                            <ET::Char as Character>::display_opt(self.escape_char),
203                            aux.memory.cs_interner().resolve(font.name())
204                        ));
205                    }
206                    self.current_font = font;
207                }
208                StateChange::TextFont { idx, old: font } => {
209                    if trace {
210                        aux.outputs.write_neg1(format_args!(
211                            "{{restoring textfont{} ={}{}}}",
212                            idx,
213                            <ET::Char as Character>::display_opt(self.escape_char),
214                            aux.memory.cs_interner().resolve(font.name())
215                        ));
216                    }
217                    self.textfonts[idx as usize] = font;
218                }
219                StateChange::ScriptFont { idx, old: font } => {
220                    if trace {
221                        aux.outputs.write_neg1(format_args!(
222                            "{{restoring scriptfont{} ={}{}}}",
223                            idx,
224                            <ET::Char as Character>::display_opt(self.escape_char),
225                            aux.memory.cs_interner().resolve(font.name())
226                        ));
227                    }
228                    self.scriptfonts[idx as usize] = font;
229                }
230                StateChange::ScriptScriptFont { idx, old: font } => {
231                    if trace {
232                        aux.outputs.write_neg1(format_args!(
233                            "{{restoring scriptscriptfont{} ={}{}}}",
234                            idx,
235                            <ET::Char as Character>::display_opt(self.escape_char),
236                            aux.memory.cs_interner().resolve(font.name())
237                        ));
238                    }
239                    self.scriptscriptfonts[idx as usize] = font;
240                }
241                StateChange::ParShape { old } => {
242                    if trace {
243                        aux.outputs.write_neg1(format_args!("{{TODO parshape}}"));
244                    }
245                    self.parshape = old;
246                }
247                StateChange::SfCode { char, old } => {
248                    if trace {
249                        aux.outputs.write_neg1(format_args!(
250                            "{{restoring {}sfcode{}={}}}",
251                            <ET::Char as Character>::display_opt(self.escape_char),
252                            char.into(),
253                            old
254                        ));
255                    }
256                    *self.sfcodes.get_mut(char) = old;
257                }
258                StateChange::DelCode { char, old } => {
259                    if trace {
260                        aux.outputs.write_neg1(format_args!(
261                            "{{restoring {}delcode{}={}}}",
262                            <ET::Char as Character>::display_opt(self.escape_char),
263                            char.into(),
264                            old
265                        ));
266                    }
267                    *self.delcodes.get_mut(char) = old;
268                }
269                StateChange::LcCode { char, old } => {
270                    if trace {
271                        aux.outputs.write_neg1(format_args!(
272                            "{{restoring {}lccode{}={}}}",
273                            <ET::Char as Character>::display_opt(self.escape_char),
274                            char.into(),
275                            old
276                        ));
277                    }
278                    *self.lccodes.get_mut(char) = old;
279                }
280                StateChange::UcCode { char, old } => {
281                    if trace {
282                        aux.outputs.write_neg1(format_args!(
283                            "{{restoring {}uccode{}={}}}",
284                            <ET::Char as Character>::display_opt(self.escape_char),
285                            char.into(),
286                            old
287                        ));
288                    }
289                    *self.uccodes.get_mut(char) = old;
290                }
291                StateChange::MathCode { char, old } => {
292                    if trace {
293                        aux.outputs.write_neg1(format_args!(
294                            "{{restoring {}mathcode{}=\"{:X}}}",
295                            <ET::Char as Character>::display_opt(self.escape_char),
296                            char.into(),
297                            old
298                        ));
299                    }
300                    *self.mathcodes.get_mut(char) = old;
301                }
302                StateChange::EndlineChar { old } => {
303                    if trace {
304                        aux.outputs.write_neg1(format_args!(
305                            "{{restoring {}endlinechar={}}}",
306                            <ET::Char as Character>::display_opt(self.escape_char),
307                            match old {
308                                None => -1,
309                                Some(c) => c.into() as i64,
310                            }
311                        ));
312                    }
313                    self.endline_char = old;
314                }
315                StateChange::EscapeChar { old } => {
316                    if trace {
317                        aux.outputs.write_neg1(format_args!(
318                            "{{restoring {}escapechar={}}}",
319                            <ET::Char as Character>::display_opt(self.escape_char),
320                            match old {
321                                None => -1,
322                                Some(c) => c.into() as i64,
323                            }
324                        ));
325                    }
326                    self.escape_char = old;
327                }
328                StateChange::NewlineChar { old } => {
329                    if trace {
330                        aux.outputs.write_neg1(format_args!(
331                            "{{restoring {}newlinechar={}}}",
332                            <ET::Char as Character>::display_opt(self.escape_char),
333                            match old {
334                                None => -1,
335                                Some(c) => c.into() as i64,
336                            }
337                        ));
338                    }
339                    self.newline_char = old;
340                }
341                StateChange::IntRegister { idx, old } => {
342                    if trace {
343                        aux.outputs.write_neg1(format_args!(
344                            "{{restoring {}count{}={}}}",
345                            <ET::Char as Character>::display_opt(self.escape_char),
346                            idx,
347                            old
348                        ));
349                    }
350                    self.int_register[idx] = old;
351                }
352                StateChange::DimRegister { idx, old } => {
353                    if trace {
354                        aux.outputs.write_neg1(format_args!(
355                            "{{restoring {}dimen{}={}}}",
356                            <ET::Char as Character>::display_opt(self.escape_char),
357                            idx,
358                            old
359                        ));
360                    }
361                    self.dim_register[idx] = old;
362                }
363                StateChange::SkipRegister { idx, old } => {
364                    if trace {
365                        aux.outputs.write_neg1(format_args!(
366                            "{{restoring {}skip{}={}}}",
367                            <ET::Char as Character>::display_opt(self.escape_char),
368                            idx,
369                            old
370                        ));
371                    }
372                    self.skip_register[idx] = old;
373                }
374                StateChange::MuSkipRegister { idx, old } => {
375                    if trace {
376                        aux.outputs.write_neg1(format_args!(
377                            "{{restoring {}muskip{}={}}}",
378                            <ET::Char as Character>::display_opt(self.escape_char),
379                            idx,
380                            old
381                        ));
382                    }
383                    self.muskip_register[idx] = old;
384                }
385                StateChange::ToksRegister { idx, old } => {
386                    if trace {
387                        aux.outputs.write_neg1(format_args!(
388                            "{{restoring {}toks{}={}}}",
389                            <ET::Char as Character>::display_opt(self.escape_char),
390                            idx,
391                            old.display(
392                                aux.memory.cs_interner(),
393                                &self.catcodes,
394                                self.escape_char,
395                                false
396                            )
397                        ));
398                    }
399                    self.toks_register[idx] = old;
400                }
401                StateChange::BoxRegister { idx, old } => {
402                    if trace {
403                        aux.outputs.write_neg1("TODO trace box restore")
404                    }
405                    self.box_register[idx] = old;
406                }
407                StateChange::PrimitiveInt { name, old } => {
408                    if trace {
409                        aux.outputs.write_neg1(format_args!(
410                            "{{restoring {}{}={}}}",
411                            <ET::Char as Character>::display_opt(self.escape_char),
412                            name.display(self.escape_char),
413                            old
414                        ));
415                    }
416                    self.primitive_ints.insert(name, old);
417                }
418                StateChange::PrimitiveDim { name, old } => {
419                    if trace {
420                        aux.outputs.write_neg1(format_args!(
421                            "{{restoring {}{}={}}}",
422                            <ET::Char as Character>::display_opt(self.escape_char),
423                            name.display(self.escape_char),
424                            old
425                        ));
426                    }
427                    self.primitive_dims.insert(name, old);
428                }
429                StateChange::PrimitiveSkip { name, old } => {
430                    if trace {
431                        aux.outputs.write_neg1(format_args!(
432                            "{{restoring {}{}={}}}",
433                            <ET::Char as Character>::display_opt(self.escape_char),
434                            name.display(self.escape_char),
435                            old
436                        ));
437                    }
438                    self.primitive_skips.insert(name, old);
439                }
440                StateChange::PrimitiveMuSkip { name, old } => {
441                    if trace {
442                        aux.outputs.write_neg1(format_args!(
443                            "{{restoring {}{}={}}}",
444                            <ET::Char as Character>::display_opt(self.escape_char),
445                            name.display(self.escape_char),
446                            old
447                        ));
448                    }
449                    self.primitive_muskips.insert(name, old);
450                }
451                StateChange::PrimitiveToks { name, old } => {
452                    if trace {
453                        aux.outputs.write_neg1(format_args!(
454                            "{{restoring {}{}={}}}",
455                            <ET::Char as Character>::display_opt(self.escape_char),
456                            name.display(self.escape_char),
457                            old.display(
458                                aux.memory.cs_interner(),
459                                &self.catcodes,
460                                self.escape_char,
461                                false
462                            )
463                        ));
464                    }
465                    self.primitive_toks.insert(name, old);
466                }
467                StateChange::Command { name, old } => {
468                    /*
469                                        {
470                                            let dpname = aux.memory.cs_interner().resolve(&name);
471                                            let dpname = dpname.to_string();
472                                            if dpname.starts_with("c_stex_module_") && dpname.ends_with("_notations_prop") {
473                                                println!("\n\n Resetting {dpname} to {}",old.as_ref().map(|c| c.meaning(aux.memory.cs_interner(),&self.catcodes,self.escape_char).to_string()).unwrap_or_else(|| "(NONE)".to_string()));
474                                            }
475                                        }
476                    */
477                    if trace {
478                        match old {
479                            None => aux.outputs.write_neg1(format_args!(
480                                "{{restoring {}{}={}undefined}}",
481                                <ET::Char as Character>::display_opt(self.escape_char),
482                                aux.memory.cs_interner().resolve(&name),
483                                <ET::Char as Character>::display_opt(self.escape_char)
484                            )),
485                            Some(ref c) => aux.outputs.write_neg1(format_args!(
486                                "{{restoring {}{}={}}}",
487                                <ET::Char as Character>::display_opt(self.escape_char),
488                                aux.memory.cs_interner().resolve(&name),
489                                c.meaning(
490                                    aux.memory.cs_interner(),
491                                    &self.catcodes,
492                                    self.escape_char
493                                )
494                            )),
495                        }
496                    }
497                    match old {
498                        Some(o) => self.commands.insert(name, o),
499                        None => self.commands.remove(&name),
500                    };
501                }
502                StateChange::AcCommand { char, old } => {
503                    if trace {
504                        match old {
505                            None => aux.outputs.write_neg1(format_args!(
506                                "{{restoring {}{}={}undefined}}",
507                                <ET::Char as Character>::display_opt(self.escape_char),
508                                char.display(),
509                                <ET::Char as Character>::display_opt(self.escape_char)
510                            )),
511                            Some(ref c) => aux.outputs.write_neg1(format_args!(
512                                "{{restoring {}{}={}}}",
513                                <ET::Char as Character>::display_opt(self.escape_char),
514                                char.display(),
515                                c.meaning(
516                                    aux.memory.cs_interner(),
517                                    &self.catcodes,
518                                    self.escape_char
519                                )
520                            )),
521                        }
522                    }
523                    *self.ac_commands.get_mut(char) = old;
524                } /*
525                  StateChange::Custom {change:c} => {
526                      let mut m = c.lock().unwrap().take();
527                      if let Some(ref mut c) = m {
528                          c.restore(aux,self,trace);
529                      }
530                  }*/
531            }
532        });
533        if !ag.is_empty() {
534            mouth.push_vec(ag)
535        }
536    }
537
538    fn get_parshape(&self) -> &Vec<(ET::Dim, ET::Dim)> {
539        &self.parshape
540    }
541    fn take_parshape(&mut self) -> Vec<(ET::Dim, ET::Dim)> {
542        let sh = std::mem::take(&mut self.parshape);
543        self.stack
544            .add_change_locally(StateChange::ParShape { old: sh.clone() });
545        sh
546    }
547    fn set_parshape(
548        &mut self,
549        aux: &EngineAux<ET>,
550        parshape: Vec<(ET::Dim, ET::Dim)>,
551        globally: bool,
552    ) {
553        self.change_field(globally, |s, g| {
554            if s.tracing_assigns() {
555                aux.outputs.write_neg1(format_args!(
556                    "{{{}changing parshape}}",
557                    if g { "globally " } else { "" }
558                ));
559            }
560            let old = std::mem::replace(&mut s.parshape, parshape);
561            StateChange::ParShape { old }
562        })
563    }
564
565    fn get_current_font(&self) -> &ET::Font {
566        &self.current_font
567    }
568    fn set_current_font(&mut self, aux: &mut EngineAux<ET>, fnt: ET::Font, globally: bool) {
569        self.change_field(globally, |s, g| {
570            if s.tracing_assigns() {
571                aux.outputs.write_neg1(format_args!(
572                    "{{{}changing current font={}{}}}",
573                    if g { "globally " } else { "" },
574                    <ET::Char as Character>::display_opt(s.escape_char),
575                    aux.memory.cs_interner().resolve(s.current_font.name())
576                ));
577                aux.outputs.write_neg1(format_args!(
578                    "{{into current font={}{}}}",
579                    <ET::Char as Character>::display_opt(s.escape_char),
580                    aux.memory.cs_interner().resolve(fnt.name())
581                ));
582            }
583            let old = std::mem::replace(&mut s.current_font, fnt);
584            StateChange::CurrentFont(old)
585        })
586    }
587
588    fn get_textfont(&self, i: u8) -> &<ET as EngineTypes>::Font {
589        &self.textfonts[i as usize]
590    }
591    fn set_textfont(
592        &mut self,
593        aux: &mut EngineAux<ET>,
594        idx: u8,
595        fnt: <ET as EngineTypes>::Font,
596        globally: bool,
597    ) {
598        self.change_field(globally, |s, g| {
599            if s.tracing_assigns() {
600                aux.outputs.write_neg1(format_args!(
601                    "{{{}changing textfont{}={}{}}}",
602                    if g { "globally " } else { "" },
603                    idx,
604                    <ET::Char as Character>::display_opt(s.escape_char),
605                    aux.memory
606                        .cs_interner()
607                        .resolve(s.textfonts[idx as usize].name())
608                ));
609                aux.outputs.write_neg1(format_args!(
610                    "{{into textfont{}={}{}}}",
611                    idx,
612                    <ET::Char as Character>::display_opt(s.escape_char),
613                    aux.memory.cs_interner().resolve(fnt.name())
614                ));
615            }
616            let old = std::mem::replace(&mut s.textfonts[idx as usize], fnt);
617            StateChange::TextFont { idx, old }
618        })
619    }
620
621    fn get_scriptfont(&self, i: u8) -> &<ET as EngineTypes>::Font {
622        &self.scriptfonts[i as usize]
623    }
624    fn set_scriptfont(
625        &mut self,
626        aux: &mut EngineAux<ET>,
627        idx: u8,
628        fnt: <ET as EngineTypes>::Font,
629        globally: bool,
630    ) {
631        self.change_field(globally, |s, g| {
632            if s.tracing_assigns() {
633                aux.outputs.write_neg1(format_args!(
634                    "{{{}changing scriptfont{}={}{}}}",
635                    if g { "globally " } else { "" },
636                    idx,
637                    <ET::Char as Character>::display_opt(s.escape_char),
638                    aux.memory
639                        .cs_interner()
640                        .resolve(s.scriptfonts[idx as usize].name())
641                ));
642                aux.outputs.write_neg1(format_args!(
643                    "{{into scriptfont{}={}{}}}",
644                    idx,
645                    <ET::Char as Character>::display_opt(s.escape_char),
646                    aux.memory.cs_interner().resolve(fnt.name())
647                ));
648            }
649            let old = std::mem::replace(&mut s.scriptfonts[idx as usize], fnt);
650            StateChange::ScriptFont { idx, old }
651        })
652    }
653
654    fn get_scriptscriptfont(&self, i: u8) -> &<ET as EngineTypes>::Font {
655        &self.scriptscriptfonts[i as usize]
656    }
657    fn set_scriptscriptfont(
658        &mut self,
659        aux: &mut EngineAux<ET>,
660        idx: u8,
661        fnt: <ET as EngineTypes>::Font,
662        globally: bool,
663    ) {
664        self.change_field(globally, |s, g| {
665            if s.tracing_assigns() {
666                aux.outputs.write_neg1(format_args!(
667                    "{{{}changing scriptscriptfont{}={}{}}}",
668                    if g { "globally " } else { "" },
669                    idx,
670                    <ET::Char as Character>::display_opt(s.escape_char),
671                    aux.memory
672                        .cs_interner()
673                        .resolve(s.scriptscriptfonts[idx as usize].name())
674                ));
675                aux.outputs.write_neg1(format_args!(
676                    "{{into scriptscriptfont{}={}{}}}",
677                    idx,
678                    <ET::Char as Character>::display_opt(s.escape_char),
679                    aux.memory.cs_interner().resolve(fnt.name())
680                ));
681            }
682            let old = std::mem::replace(&mut s.scriptscriptfonts[idx as usize], fnt);
683            StateChange::ScriptScriptFont { idx, old }
684        })
685    }
686
687    fn get_catcode_scheme(&self) -> &CategoryCodeScheme<ET::Char> {
688        &self.catcodes
689    }
690    fn set_catcode(&mut self, aux: &EngineAux<ET>, c: ET::Char, cc: CategoryCode, globally: bool) {
691        self.change_field(globally, |s, g| {
692            if s.tracing_assigns() {
693                let num = c.into();
694                let cc: u8 = cc.into();
695                aux.outputs.write_neg1(format_args!(
696                    "{{{} {}catcode{}={}}}",
697                    if g {
698                        "globally changing"
699                    } else {
700                        "reassigning"
701                    },
702                    <ET::Char as Character>::display_opt(s.escape_char),
703                    num,
704                    cc
705                ));
706            }
707            let old = std::mem::replace(s.catcodes.get_mut(c), cc);
708            StateChange::Catcode { char: c, old }
709        })
710    }
711
712    fn get_sfcode(&self, c: ET::Char) -> u16 {
713        *self.sfcodes.get(c)
714    }
715    fn set_sfcode(&mut self, aux: &EngineAux<ET>, c: ET::Char, val: u16, globally: bool) {
716        self.change_field(globally, |s, g| {
717            let old = std::mem::replace(s.sfcodes.get_mut(c), val);
718            if s.tracing_assigns() {
719                let num = c.into();
720                aux.outputs.write_neg1(format_args!(
721                    "{{{}changing {}sfcode{}={}}}",
722                    if g { "globally " } else { "" },
723                    <ET::Char as Character>::display_opt(s.escape_char),
724                    num,
725                    old
726                ));
727                aux.outputs.write_neg1(format_args!(
728                    "{{into {}sfcode{}={}}}",
729                    <ET::Char as Character>::display_opt(s.escape_char),
730                    num,
731                    val
732                ));
733            }
734            StateChange::SfCode { char: c, old }
735        })
736    }
737
738    fn get_delcode(&self, c: ET::Char) -> ET::Int {
739        *self.delcodes.get(c)
740    }
741    fn set_delcode(&mut self, aux: &EngineAux<ET>, c: ET::Char, val: ET::Int, globally: bool) {
742        self.change_field(globally, |s, g| {
743            let old = std::mem::replace(s.delcodes.get_mut(c), val);
744            if s.tracing_assigns() {
745                let num = c.into();
746                aux.outputs.write_neg1(format_args!(
747                    "{{{}changing {}delcode{}={}}}",
748                    if g { "globally " } else { "" },
749                    <ET::Char as Character>::display_opt(s.escape_char),
750                    num,
751                    old
752                ));
753                aux.outputs.write_neg1(format_args!(
754                    "{{into {}delcode{}={}}}",
755                    <ET::Char as Character>::display_opt(s.escape_char),
756                    num,
757                    val
758                ));
759            }
760            StateChange::DelCode { char: c, old }
761        })
762    }
763
764    fn get_lccode(&self, c: ET::Char) -> ET::Char {
765        *self.lccodes.get(c)
766    }
767    fn set_lccode(&mut self, aux: &EngineAux<ET>, c: ET::Char, val: ET::Char, globally: bool) {
768        self.change_field(globally, |s, g| {
769            let old = std::mem::replace(s.lccodes.get_mut(c), val);
770            if s.tracing_assigns() {
771                let num = c.into();
772                aux.outputs.write_neg1(format_args!(
773                    "{{{}changing {}lccode{}={}}}",
774                    if g { "globally " } else { "" },
775                    <ET::Char as Character>::display_opt(s.escape_char),
776                    num,
777                    old.into()
778                ));
779                aux.outputs.write_neg1(format_args!(
780                    "{{into {}lccode{}={}}}",
781                    <ET::Char as Character>::display_opt(s.escape_char),
782                    num,
783                    val.into()
784                ));
785            }
786            StateChange::LcCode { char: c, old }
787        })
788    }
789
790    fn get_uccode(&self, c: ET::Char) -> ET::Char {
791        *self.uccodes.get(c)
792    }
793    fn set_uccode(&mut self, aux: &EngineAux<ET>, c: ET::Char, val: ET::Char, globally: bool) {
794        self.change_field(globally, |s, g| {
795            let old = std::mem::replace(s.uccodes.get_mut(c), val);
796            if s.tracing_assigns() {
797                let num = c.into();
798                aux.outputs.write_neg1(format_args!(
799                    "{{{}changing {}uccode{}={}}}",
800                    if g { "globally " } else { "" },
801                    <ET::Char as Character>::display_opt(s.escape_char),
802                    num,
803                    old.into()
804                ));
805                aux.outputs.write_neg1(format_args!(
806                    "{{into {}uccode{}={}}}",
807                    <ET::Char as Character>::display_opt(s.escape_char),
808                    num,
809                    val.into()
810                ));
811            }
812            StateChange::UcCode { char: c, old }
813        })
814    }
815
816    fn get_mathcode(&self, c: ET::Char) -> u32 {
817        *self.mathcodes.get(c)
818    }
819    fn set_mathcode(&mut self, aux: &EngineAux<ET>, c: ET::Char, val: u32, globally: bool) {
820        self.change_field(globally, |s, g| {
821            let old = std::mem::replace(s.mathcodes.get_mut(c), val);
822            if s.tracing_assigns() {
823                let num = c.into();
824                aux.outputs.write_neg1(format_args!(
825                    "{{{}changing {}mathcode{}=\"{:X}}}",
826                    if g { "globally " } else { "" },
827                    <ET::Char as Character>::display_opt(s.escape_char),
828                    num,
829                    old
830                ));
831                aux.outputs.write_neg1(format_args!(
832                    "{{into {}mathcode{}=\"{:X}}}",
833                    <ET::Char as Character>::display_opt(s.escape_char),
834                    num,
835                    val
836                ));
837            }
838            StateChange::MathCode { char: c, old }
839        })
840    }
841
842    fn get_endline_char(&self) -> Option<ET::Char> {
843        self.endline_char
844    }
845    fn set_endline_char(&mut self, aux: &EngineAux<ET>, c: Option<ET::Char>, globally: bool) {
846        self.change_field(globally, |s, g| {
847            if s.tracing_assigns() {
848                aux.outputs.write_neg1(format_args!(
849                    "{{{}changing {}endlinechar={}}}",
850                    if g { "globally " } else { "" },
851                    <ET::Char as Character>::display_opt(s.escape_char),
852                    match s.endline_char {
853                        None => -1,
854                        Some(c) => c.into() as i64,
855                    }
856                ));
857                aux.outputs.write_neg1(format_args!(
858                    "{{into {}endlinechar={}}}",
859                    <ET::Char as Character>::display_opt(s.escape_char),
860                    match c {
861                        None => -1,
862                        Some(c) => c.into() as i64,
863                    }
864                ));
865            }
866            let old = std::mem::replace(&mut s.endline_char, c);
867            StateChange::EndlineChar { old }
868        })
869    }
870
871    fn get_escape_char(&self) -> Option<ET::Char> {
872        self.escape_char
873    }
874    fn set_escape_char(&mut self, aux: &EngineAux<ET>, c: Option<ET::Char>, globally: bool) {
875        self.change_field(globally, |s, g| {
876            if s.tracing_assigns() {
877                aux.outputs.write_neg1(format_args!(
878                    "{{{}changing {}escapechar={}}}",
879                    if g { "globally " } else { "" },
880                    <ET::Char as Character>::display_opt(s.escape_char),
881                    match s.escape_char {
882                        None => -1,
883                        Some(c) => c.into() as i64,
884                    }
885                ));
886                aux.outputs.write_neg1(format_args!(
887                    "{{into {}escapechar={}}}",
888                    <ET::Char as Character>::display_opt(s.escape_char),
889                    match c {
890                        None => -1,
891                        Some(c) => c.into() as i64,
892                    }
893                ));
894            }
895            let old = std::mem::replace(&mut s.escape_char, c);
896            StateChange::EscapeChar { old }
897        })
898    }
899
900    fn get_newline_char(&self) -> Option<ET::Char> {
901        self.newline_char
902    }
903    fn set_newline_char(&mut self, aux: &EngineAux<ET>, c: Option<ET::Char>, globally: bool) {
904        self.change_field(globally, |s, g| {
905            if s.tracing_assigns() {
906                aux.outputs.write_neg1(format_args!(
907                    "{{{}changing {}newlinechar={}}}",
908                    if g { "globally " } else { "" },
909                    <ET::Char as Character>::display_opt(s.escape_char),
910                    match s.newline_char {
911                        None => -1,
912                        Some(c) => c.into() as i64,
913                    }
914                ));
915                aux.outputs.write_neg1(format_args!(
916                    "{{into {}newlinechar={}}}",
917                    <ET::Char as Character>::display_opt(s.escape_char),
918                    match c {
919                        None => -1,
920                        Some(c) => c.into() as i64,
921                    }
922                ));
923            }
924            let old = std::mem::replace(&mut s.newline_char, c);
925            StateChange::NewlineChar { old }
926        })
927    }
928
929    fn get_primitive_int(&self, name: PrimitiveIdentifier) -> ET::Int {
930        match self.primitive_ints.get(&name) {
931            Some(i) => *i,
932            _ => ET::Int::default(),
933        }
934    }
935    fn set_primitive_int(
936        &mut self,
937        aux: &EngineAux<ET>,
938        name: PrimitiveIdentifier,
939        v: ET::Int,
940        globally: bool,
941    ) {
942        self.change_field(globally, |s, g| {
943            let old = s.primitive_ints.insert(name, v).unwrap_or_default();
944            if s.tracing_assigns() {
945                aux.outputs.write_neg1(format_args!(
946                    "{{{}changing {}={}}}",
947                    if g { "globally " } else { "" },
948                    name.display(s.escape_char),
949                    old
950                ));
951                aux.outputs.write_neg1(format_args!(
952                    "{{into {}={}}}",
953                    name.display(s.escape_char),
954                    v
955                ))
956            }
957            StateChange::PrimitiveInt { name, old }
958        });
959    }
960
961    fn get_int_register(&self, idx: usize) -> ET::Int {
962        match self.int_register.get(idx) {
963            Some(i) => *i,
964            _ => ET::Int::default(),
965        }
966    }
967    fn set_int_register(&mut self, aux: &EngineAux<ET>, idx: usize, v: ET::Int, globally: bool) {
968        self.change_field(globally, |s, g| {
969            if s.int_register.len() <= idx {
970                s.int_register.resize(idx + 1, ET::Int::default());
971            }
972            let old = std::mem::replace(&mut s.int_register[idx], v);
973            if s.tracing_assigns() {
974                aux.outputs.write_neg1(format_args!(
975                    "{{{}changing {}count{}={}}}",
976                    if g { "globally " } else { "" },
977                    ET::Char::display_opt(s.escape_char),
978                    idx,
979                    old
980                ));
981                aux.outputs.write_neg1(format_args!(
982                    "{{into {}count{}={}}}",
983                    ET::Char::display_opt(s.escape_char),
984                    idx,
985                    v
986                ))
987            }
988            StateChange::IntRegister { idx, old }
989        });
990    }
991
992    fn get_dim_register(&self, idx: usize) -> ET::Dim {
993        match self.dim_register.get(idx) {
994            Some(i) => *i,
995            _ => ET::Dim::default(),
996        }
997    }
998    fn set_dim_register(&mut self, aux: &EngineAux<ET>, idx: usize, v: ET::Dim, globally: bool) {
999        self.change_field(globally, |s, g| {
1000            if s.dim_register.len() <= idx {
1001                s.dim_register.resize(idx + 1, ET::Dim::default());
1002            }
1003            let old = std::mem::replace(&mut s.dim_register[idx], v);
1004            if s.tracing_assigns() {
1005                aux.outputs.write_neg1(format_args!(
1006                    "{{{}changing {}dimen{}={}}}",
1007                    if g { "globally " } else { "" },
1008                    ET::Char::display_opt(s.escape_char),
1009                    idx,
1010                    old
1011                ));
1012                aux.outputs.write_neg1(format_args!(
1013                    "{{into {}dimen{}={}}}",
1014                    ET::Char::display_opt(s.escape_char),
1015                    idx,
1016                    v
1017                ));
1018            }
1019            StateChange::DimRegister { idx, old }
1020        });
1021    }
1022
1023    fn get_skip_register(&self, idx: usize) -> Skip<ET::Dim> {
1024        match self.skip_register.get(idx) {
1025            Some(i) => *i,
1026            _ => Skip::default(),
1027        }
1028    }
1029    fn set_skip_register(
1030        &mut self,
1031        aux: &EngineAux<ET>,
1032        idx: usize,
1033        v: Skip<ET::Dim>,
1034        globally: bool,
1035    ) {
1036        self.change_field(globally, |s, g| {
1037            if s.skip_register.len() <= idx {
1038                s.skip_register.resize(idx + 1, Skip::default());
1039            }
1040            let old = std::mem::replace(&mut s.skip_register[idx], v);
1041            if s.tracing_assigns() {
1042                aux.outputs.write_neg1(format_args!(
1043                    "{{{}changing {}skip{}={}}}",
1044                    if g { "globally " } else { "" },
1045                    ET::Char::display_opt(s.escape_char),
1046                    idx,
1047                    old
1048                ));
1049                aux.outputs.write_neg1(format_args!(
1050                    "{{into {}skip{}={}}}",
1051                    ET::Char::display_opt(s.escape_char),
1052                    idx,
1053                    v
1054                ))
1055            }
1056            StateChange::SkipRegister { idx, old }
1057        });
1058    }
1059
1060    fn get_muskip_register(&self, idx: usize) -> MuSkip<ET::MuDim> {
1061        match self.muskip_register.get(idx) {
1062            Some(i) => *i,
1063            _ => MuSkip::default(),
1064        }
1065    }
1066    fn set_muskip_register(
1067        &mut self,
1068        aux: &EngineAux<ET>,
1069        idx: usize,
1070        v: MuSkip<ET::MuDim>,
1071        globally: bool,
1072    ) {
1073        self.change_field(globally, |s, g| {
1074            if s.muskip_register.len() <= idx {
1075                s.muskip_register.resize(idx + 1, MuSkip::default());
1076            }
1077            let old = std::mem::replace(&mut s.muskip_register[idx], v);
1078            if s.tracing_assigns() {
1079                aux.outputs.write_neg1(format_args!(
1080                    "{{{}changing {}muskip{}={}}}",
1081                    if g { "globally " } else { "" },
1082                    ET::Char::display_opt(s.escape_char),
1083                    idx,
1084                    old
1085                ));
1086                aux.outputs.write_neg1(format_args!(
1087                    "{{into {}muskip{}={}}}",
1088                    ET::Char::display_opt(s.escape_char),
1089                    idx,
1090                    v
1091                ))
1092            }
1093            StateChange::MuSkipRegister { idx, old }
1094        });
1095    }
1096
1097    fn get_box_register(&self, idx: usize) -> Option<&TeXBox<ET>> {
1098        match self.box_register.get(idx) {
1099            None => None,
1100            Some(i) => i.as_ref(),
1101        }
1102    }
1103    fn get_box_register_mut(&mut self, idx: usize) -> Option<&mut TeXBox<ET>> {
1104        match self.box_register.get_mut(idx) {
1105            None => None,
1106            Some(i) => i.as_mut(),
1107        }
1108    }
1109    fn take_box_register(&mut self, idx: usize) -> Option<TeXBox<ET>> {
1110        match self.box_register.get_mut(idx) {
1111            None => None,
1112            Some(i) => std::mem::take(i),
1113        }
1114    }
1115    fn set_box_register(
1116        &mut self,
1117        aux: &EngineAux<ET>,
1118        idx: usize,
1119        v: Option<TeXBox<ET>>,
1120        globally: bool,
1121    ) {
1122        self.change_field(globally, |s, _| {
1123            if s.box_register.len() <= idx {
1124                s.box_register.resize(idx + 1, None);
1125            }
1126            let old = std::mem::replace(&mut s.box_register[idx], v);
1127            if s.tracing_assigns() {
1128                aux.outputs
1129                    .write_neg1(format_args!("{{TODO: trace box register change {}}}", idx));
1130            }
1131            StateChange::BoxRegister { idx, old }
1132        });
1133    }
1134
1135    fn get_toks_register(&self, idx: usize) -> &TokenList<ET::Token> {
1136        match self.toks_register.get(idx) {
1137            Some(i) => i,
1138            _ => &self.empty_list,
1139        }
1140    }
1141    fn set_toks_register(
1142        &mut self,
1143        aux: &EngineAux<ET>,
1144        idx: usize,
1145        v: TokenList<ET::Token>,
1146        globally: bool,
1147    ) {
1148        self.change_field(globally, |s, g| {
1149            if s.toks_register.len() <= idx {
1150                s.toks_register.resize(idx + 1, s.empty_list.clone());
1151            }
1152            let trace = s.tracing_assigns();
1153            if trace {
1154                aux.outputs.write_neg1(format_args!(
1155                    "{{{}changing {}toks{}={}}}",
1156                    if g { "globally " } else { "" },
1157                    ET::Char::display_opt(s.escape_char),
1158                    idx,
1159                    s.toks_register[idx].display(
1160                        aux.memory.cs_interner(),
1161                        &s.catcodes,
1162                        s.escape_char,
1163                        false
1164                    )
1165                ));
1166            }
1167            let old = std::mem::replace(&mut s.toks_register[idx], v);
1168            if s.tracing_assigns() {
1169                aux.outputs.write_neg1(format_args!(
1170                    "{{into {}toks{}={}}}",
1171                    ET::Char::display_opt(s.escape_char),
1172                    idx,
1173                    s.toks_register[idx].display(
1174                        aux.memory.cs_interner(),
1175                        &s.catcodes,
1176                        s.escape_char,
1177                        false
1178                    )
1179                ))
1180            }
1181            StateChange::ToksRegister { idx, old }
1182        });
1183    }
1184
1185    fn get_primitive_tokens(&self, name: PrimitiveIdentifier) -> &TokenList<ET::Token> {
1186        match self.primitive_toks.get(&name) {
1187            Some(i) => i,
1188            _ => &self.empty_list,
1189        }
1190    }
1191    fn set_primitive_tokens(
1192        &mut self,
1193        aux: &EngineAux<ET>,
1194        name: PrimitiveIdentifier,
1195        v: TokenList<ET::Token>,
1196        globally: bool,
1197    ) {
1198        self.change_field(globally, |s, g| {
1199            let old = s
1200                .primitive_toks
1201                .insert(name, v)
1202                .unwrap_or(s.empty_list.clone());
1203            if s.tracing_assigns() {
1204                aux.outputs.write_neg1(format_args!(
1205                    "{{{}changing {}={}}}",
1206                    if g { "globally " } else { "" },
1207                    name.display(s.escape_char),
1208                    old.display(aux.memory.cs_interner(), &s.catcodes, s.escape_char, false)
1209                ));
1210                aux.outputs.write_neg1(format_args!(
1211                    "{{into {}={}}}",
1212                    name.display(s.escape_char),
1213                    s.primitive_toks.get(&name).unwrap().display(
1214                        aux.memory.cs_interner(),
1215                        &s.catcodes,
1216                        s.escape_char,
1217                        false
1218                    )
1219                ))
1220            }
1221            StateChange::PrimitiveToks { name, old }
1222        });
1223    }
1224
1225    fn get_primitive_dim(&self, name: PrimitiveIdentifier) -> ET::Dim {
1226        match self.primitive_dims.get(&name) {
1227            Some(i) => *i,
1228            _ => ET::Dim::default(),
1229        }
1230    }
1231    fn set_primitive_dim(
1232        &mut self,
1233        aux: &EngineAux<ET>,
1234        name: PrimitiveIdentifier,
1235        v: ET::Dim,
1236        globally: bool,
1237    ) {
1238        self.change_field(globally, |s, g| {
1239            let old = s.primitive_dims.insert(name, v).unwrap_or_default();
1240            if s.tracing_assigns() {
1241                aux.outputs.write_neg1(format_args!(
1242                    "{{{}changing {}={}}}",
1243                    if g { "globally " } else { "" },
1244                    name.display(s.escape_char),
1245                    old
1246                ));
1247                aux.outputs.write_neg1(format_args!(
1248                    "{{into {}={}}}",
1249                    name.display(s.escape_char),
1250                    v
1251                ))
1252            }
1253            StateChange::PrimitiveDim { name, old }
1254        });
1255    }
1256
1257    fn get_primitive_skip(&self, name: PrimitiveIdentifier) -> Skip<ET::Dim> {
1258        match self.primitive_skips.get(&name) {
1259            Some(i) => *i,
1260            _ => Skip::default(),
1261        }
1262    }
1263    fn set_primitive_skip(
1264        &mut self,
1265        aux: &EngineAux<ET>,
1266        name: PrimitiveIdentifier,
1267        v: Skip<ET::Dim>,
1268        globally: bool,
1269    ) {
1270        self.change_field(globally, |s, g| {
1271            let old = s.primitive_skips.insert(name, v).unwrap_or_default();
1272            if s.tracing_assigns() {
1273                aux.outputs.write_neg1(format_args!(
1274                    "{{{}changing {}={}}}",
1275                    if g { "globally " } else { "" },
1276                    name.display(s.escape_char),
1277                    old
1278                ));
1279                aux.outputs.write_neg1(format_args!(
1280                    "{{into {}={}}}",
1281                    name.display(s.escape_char),
1282                    v
1283                ))
1284            }
1285            StateChange::PrimitiveSkip { name, old }
1286        });
1287    }
1288
1289    fn get_primitive_muskip(&self, name: PrimitiveIdentifier) -> MuSkip<ET::MuDim> {
1290        match self.primitive_muskips.get(&name) {
1291            Some(i) => *i,
1292            _ => MuSkip::default(),
1293        }
1294    }
1295    fn set_primitive_muskip(
1296        &mut self,
1297        aux: &EngineAux<ET>,
1298        name: PrimitiveIdentifier,
1299        v: MuSkip<ET::MuDim>,
1300        globally: bool,
1301    ) {
1302        self.change_field(globally, |s, g| {
1303            let old = s.primitive_muskips.insert(name, v).unwrap_or_default();
1304            if s.tracing_assigns() {
1305                aux.outputs.write_neg1(format_args!(
1306                    "{{{}changing {}={}}}",
1307                    if g { "globally " } else { "" },
1308                    name.display(s.escape_char),
1309                    old
1310                ));
1311                aux.outputs.write_neg1(format_args!(
1312                    "{{into {}={}}}",
1313                    name.display(s.escape_char),
1314                    v
1315                ))
1316            }
1317            StateChange::PrimitiveMuSkip { name, old }
1318        });
1319    }
1320
1321    fn get_command(&self, name: &ET::CSName) -> Option<&TeXCommand<ET>> {
1322        self.commands.get(name)
1323    }
1324    fn set_command(
1325        &mut self,
1326        aux: &EngineAux<ET>,
1327        name: ET::CSName,
1328        cmd: Option<TeXCommand<ET>>,
1329        globally: bool,
1330    ) {
1331        self.change_field(globally, |s, g| {
1332            /*
1333                       {
1334                           let dpname = aux.memory.cs_interner().resolve(&name);
1335                           let dpname = dpname.to_string();
1336                           if dpname.starts_with("c_stex_module_") && dpname.ends_with("_notations_prop") {
1337                               println!("\n\n Setting {dpname} to {} \n(globally: {g})\n",cmd.as_ref().map(|c| c.meaning(aux.memory.cs_interner(),&s.catcodes,s.escape_char).to_string()).unwrap_or_else(|| "(NONE)".to_string()));
1338                           }
1339                       }
1340            */
1341            let old = match cmd {
1342                None => {
1343                    let o = s.commands.remove(&name);
1344                    if s.tracing_assigns() {
1345                        match o {
1346                            None => aux.outputs.write_neg1(format_args!(
1347                                "{{{}changing {}{}={}undefined}}",
1348                                if g { "globally " } else { "" },
1349                                ET::Char::display_opt(s.escape_char),
1350                                aux.memory.cs_interner().resolve(&name),
1351                                ET::Char::display_opt(s.escape_char)
1352                            )),
1353                            Some(ref c) => aux.outputs.write_neg1(format_args!(
1354                                "{{{}changing {}{}={}}}",
1355                                if g { "globally " } else { "" },
1356                                ET::Char::display_opt(s.escape_char),
1357                                aux.memory.cs_interner().resolve(&name),
1358                                c.meaning(aux.memory.cs_interner(), &s.catcodes, s.escape_char)
1359                            )),
1360                        }
1361                        aux.outputs.write_neg1(format_args!(
1362                            "{{into {}{}={}undefined}}",
1363                            ET::Char::display_opt(s.escape_char),
1364                            aux.memory.cs_interner().resolve(&name),
1365                            ET::Char::display_opt(s.escape_char)
1366                        ));
1367                    }
1368                    o
1369                }
1370                Some(cmd) => {
1371                    if s.tracing_assigns() {
1372                        match s.commands.get(&name) {
1373                            None => aux.outputs.write_neg1(format_args!(
1374                                "{{{}changing {}{}={}undefined}}",
1375                                if g { "globally " } else { "" },
1376                                ET::Char::display_opt(s.escape_char),
1377                                aux.memory.cs_interner().resolve(&name),
1378                                ET::Char::display_opt(s.escape_char)
1379                            )),
1380                            Some(c) => aux.outputs.write_neg1(format_args!(
1381                                "{{{}changing {}{}={}}}",
1382                                if g { "globally " } else { "" },
1383                                ET::Char::display_opt(s.escape_char),
1384                                aux.memory.cs_interner().resolve(&name),
1385                                c.meaning(aux.memory.cs_interner(), &s.catcodes, s.escape_char)
1386                            )),
1387                        }
1388                        aux.outputs.write_neg1(format_args!(
1389                            "{{into {}{}={}}}",
1390                            ET::Char::display_opt(s.escape_char),
1391                            aux.memory.cs_interner().resolve(&name),
1392                            cmd.meaning(aux.memory.cs_interner(), &s.catcodes, s.escape_char)
1393                        ));
1394                    }
1395                    s.commands.insert(name.clone(), cmd)
1396                }
1397            };
1398            StateChange::Command { name, old }
1399        });
1400    }
1401
1402    fn get_ac_command(&self, c: ET::Char) -> Option<&TeXCommand<ET>> {
1403        self.ac_commands.get(c).as_ref()
1404    }
1405    fn set_ac_command(
1406        &mut self,
1407        _aux: &EngineAux<ET>,
1408        c: ET::Char,
1409        cmd: Option<TeXCommand<ET>>,
1410        globally: bool,
1411    ) {
1412        self.change_field(globally, |s, _| {
1413            let old = std::mem::replace(s.ac_commands.get_mut(c), cmd);
1414            StateChange::AcCommand { char: c, old }
1415        });
1416    }
1417}