1#![allow(clippy::module_name_repetitions)]
2use crate::commands::primitives::PRIMITIVES;
6use crate::commands::{ResolvedToken, TeXCommand};
7use crate::engine::filesystem::{File, FileSystem, VirtualFile};
8use crate::engine::fontsystem::{Font, FontSystem, TfmFont, TfmFontSystem};
9use crate::engine::gullet::{DefaultGullet, Gullet};
10use crate::engine::mouth::{DefaultMouth, Mouth};
11use crate::engine::state::State;
12use crate::engine::stomach::{DefaultStomach, Stomach};
13use crate::engine::utils::memory::MemoryManager;
14use crate::engine::utils::outputs::{LogOutputs, Outputs};
15use crate::tex;
16use crate::tex::catcodes::CommandCode;
17use crate::tex::characters::Character;
18use crate::tex::nodes::vertical::VNode;
19use crate::tex::nodes::CustomNodeTrait;
20use crate::tex::numerics::{Dim32, Mu, MuDim, NumSet, Numeric, TeXDimen, TeXInt};
21use crate::tex::tokens::control_sequences::{CSName, InternedCSName};
22use crate::tex::tokens::Token;
23use crate::utils::errors::{ErrorHandler, ErrorThrower, TeXError, TeXResult};
24use chrono::{Datelike, Timelike};
25use std::convert::Infallible;
26use std::fmt::Debug;
27
28pub mod filesystem;
29pub mod fontsystem;
30pub mod gullet;
31pub mod mouth;
32pub mod state;
33pub mod stomach;
34pub mod utils;
35
36pub trait EngineTypes: Sized + Copy + Clone + Debug + 'static {
41 type Char: Character;
42 type CSName: CSName<Self::Char>;
43 #[cfg(feature = "multithreaded")]
44 type Token: Token<Char = Self::Char, CS = Self::CSName> + Send + Sync;
45 #[cfg(not(feature = "multithreaded"))]
46 type Token: Token<Char = Self::Char, CS = Self::CSName>;
47 type Extension: EngineExtension<Self>;
48 #[cfg(feature = "multithreaded")]
49 type File: File<Char = Self::Char> + Send + Sync;
50 #[cfg(not(feature = "multithreaded"))]
51 type File: File<Char = Self::Char>;
52 type FileSystem: FileSystem<File = Self::File>;
53 type Int: TeXInt;
54 type Dim: TeXDimen + Numeric<Self::Int>;
55 type MuDim: MuDim + Numeric<Self::Int>;
56 type Num: crate::tex::numerics::NumSet<Int = Self::Int, Dim = Self::Dim, MuDim = Self::MuDim>;
57 type State: State<Self>;
58 type Outputs: Outputs;
59 type Mouth: Mouth<Self>;
60 type Gullet: Gullet<Self>;
61 type Stomach: Stomach<Self>;
62 type ErrorHandler: ErrorHandler<Self>;
63 type CustomNode: CustomNodeTrait<Self>;
64 type Font: Font<Char = Self::Char, Int = Self::Int, Dim = Self::Dim, CS = Self::CSName>;
65 type FontSystem: FontSystem<
66 Font = Self::Font,
67 Char = Self::Char,
68 Int = Self::Int,
69 Dim = Self::Dim,
70 CS = Self::CSName,
71 >;
72}
73pub struct EngineAux<ET: EngineTypes> {
75 pub memory: MemoryManager<ET::Token>,
77 pub error_handler: ET::ErrorHandler,
79 pub outputs: ET::Outputs,
81 pub start_time: chrono::DateTime<chrono::Local>,
83 pub jobname: String,
85 pub extension: ET::Extension,
87}
88
89struct Colon<'c, ET: EngineTypes> {
90 out: Box<dyn FnMut(&mut EngineReferences<ET>, VNode<ET>) -> TeXResult<(), ET> + 'c>,
91}
92impl<'c, ET: EngineTypes> Colon<'c, ET> {
93 fn new<F: FnMut(&mut EngineReferences<ET>, VNode<ET>) -> TeXResult<(), ET> + 'c>(f: F) -> Self {
94 Colon { out: Box::new(f) }
95 }
96 fn out(&mut self, engine: &mut EngineReferences<ET>, n: VNode<ET>) -> TeXResult<(), ET> {
97 (self.out)(engine, n)
98 }
99}
100impl<ET: EngineTypes> Default for Colon<'_, ET> {
101 fn default() -> Self {
102 Colon {
103 out: Box::new(|_, _| Ok(())),
104 }
105 }
106}
107
108impl<ET: EngineTypes> EngineReferences<'_, ET> {
109 pub fn shipout(&mut self, n: VNode<ET>) -> TeXResult<(), ET> {
113 let mut colon = std::mem::take(&mut self.colon);
114 let r = colon.out(self, n);
115 self.colon = colon;
116 r
117 }
118}
119
120pub struct EngineReferences<'et, ET: EngineTypes> {
127 pub state: &'et mut ET::State,
128 pub mouth: &'et mut ET::Mouth,
129 pub gullet: &'et mut ET::Gullet,
130 pub stomach: &'et mut ET::Stomach,
131 pub filesystem: &'et mut ET::FileSystem,
132 pub fontsystem: &'et mut ET::FontSystem,
133 colon: Colon<'et, ET>,
134 pub aux: &'et mut EngineAux<ET>,
135}
136
137#[derive(Copy, Clone, Debug)]
139pub struct DefaultPlainTeXEngineTypes;
140impl EngineTypes for DefaultPlainTeXEngineTypes {
141 type Char = u8;
142 type CSName = InternedCSName<u8>;
143 type Token = super::tex::tokens::CompactToken;
144 type Extension = ();
145 type Int = i32;
146 type Dim = Dim32;
147 type MuDim = Mu;
148 type Num = tex::numerics::DefaultNumSet;
149 type State = state::tex_state::DefaultState<Self>;
150 type File = VirtualFile<u8>;
151 type FileSystem = filesystem::NoOutputFileSystem<u8>;
152 type Outputs = LogOutputs;
153 type Mouth = DefaultMouth<Self>;
154 type Gullet = DefaultGullet<Self>;
155 type CustomNode = Infallible;
156 type ErrorHandler = ErrorThrower<Self>;
157 type Stomach = DefaultStomach<Self>;
158 type Font = TfmFont<i32, Dim32, InternedCSName<u8>>;
159 type FontSystem = TfmFontSystem<i32, Dim32, InternedCSName<u8>>;
160}
161
162pub trait TeXEngine: Sized {
164 type Types: EngineTypes;
165 fn get_engine_refs(&mut self) -> EngineReferences<Self::Types>;
167 #[allow(clippy::cast_possible_wrap)]
171 fn init_file(&mut self, s: &str) -> TeXResult<(), Self::Types> {
172 log::debug!("Initializing with file {}", s);
173 let mut comps = self.get_engine_refs();
174 comps.aux.start_time = chrono::Local::now();
175 comps.state.set_primitive_int(
176 comps.aux,
177 PRIMITIVES.year,
178 comps.aux.start_time.year().into(),
179 true,
180 );
181 comps.state.set_primitive_int(
182 comps.aux,
183 PRIMITIVES.month,
184 (comps.aux.start_time.month() as i32).into(),
185 true,
186 );
187 comps.state.set_primitive_int(
188 comps.aux,
189 PRIMITIVES.day,
190 (comps.aux.start_time.day() as i32).into(),
191 true,
192 );
193 comps.state.set_primitive_int(
194 comps.aux,
195 PRIMITIVES.time,
196 (((comps.aux.start_time.hour() * 60) + comps.aux.start_time.minute()) as i32).into(),
197 true,
198 );
199 let file = comps.filesystem.get(s);
200 let Some(filename) = file
201 .path()
202 .file_stem()
203 .and_then(|s| s.to_str().map(ToString::to_string))
204 else {
205 return Err(TeXError::General(format!("Invalid init file {s}")));
206 };
207 comps.aux.jobname = filename;
208 comps.push_file(file);
209 comps.top_loop()
210 }
211
212 #[allow(clippy::cast_possible_wrap)]
215 fn run<
216 F: FnMut(&mut EngineReferences<Self::Types>, VNode<Self::Types>) -> TeXResult<(), Self::Types>,
217 >(
218 &mut self,
219 f: F,
220 ) -> TeXResult<(), Self::Types> {
221 let mut comps = self.get_engine_refs();
222 comps.aux.start_time = chrono::Local::now();
223 comps.state.set_primitive_int(
224 comps.aux,
225 PRIMITIVES.year,
226 comps.aux.start_time.year().into(),
227 true,
228 );
229 comps.state.set_primitive_int(
230 comps.aux,
231 PRIMITIVES.month,
232 (comps.aux.start_time.month() as i32).into(),
233 true,
234 );
235 comps.state.set_primitive_int(
236 comps.aux,
237 PRIMITIVES.day,
238 (comps.aux.start_time.day() as i32).into(),
239 true,
240 );
241 comps.state.set_primitive_int(
242 comps.aux,
243 PRIMITIVES.time,
244 (((comps.aux.start_time.hour() * 60) + comps.aux.start_time.minute()) as i32).into(),
245 true,
246 );
247 comps.push_every(PRIMITIVES.everyjob);
248 comps.colon = Colon::new(f);
249 comps.top_loop()
250 }
251
252 fn do_file_default<
257 F: FnMut(&mut EngineReferences<Self::Types>, VNode<Self::Types>) -> TeXResult<(), Self::Types>,
258 >(
259 &mut self,
260 s: &str,
261 f: F,
262 ) -> TeXResult<(), Self::Types> {
263 log::debug!("Running file {}", s);
264 {
265 let mut comps = self.get_engine_refs();
266 let file = comps.filesystem.get(s);
267 let Some(parent) = file.path().parent() else {
268 return Err(TeXError::General(format!("Invalid file {s}")));
269 };
270 comps.filesystem.set_pwd(parent.to_path_buf());
271 let Some(filename) = file
272 .path()
273 .file_stem()
274 .and_then(|s| s.to_str().map(ToString::to_string))
275 else {
276 return Err(TeXError::General(format!("Invalid init file {s}")));
277 };
278 comps.aux.jobname = filename;
279 comps.push_file(file);
280 }
281 self.run(f)
282 }
283 fn initialize_tex_primitives(&mut self) {
285 super::commands::tex::register_tex_primitives(self);
286 let mag = PRIMITIVES.mag;
287 let fam = PRIMITIVES.fam;
288 let refs = self.get_engine_refs();
289 refs.state
290 .set_primitive_int(refs.aux, mag, (1000).into(), true);
291 refs.state
292 .set_primitive_int(refs.aux, fam, (-1).into(), true);
293 }
294
295 fn initialize_plain_tex(&mut self) -> TeXResult<(), Self::Types> {
299 self.initialize_tex_primitives();
300 self.init_file("plain.tex")
301 }
302
303 fn initialize_etex_primitives(&mut self) {
305 self.initialize_tex_primitives();
306 super::commands::etex::register_etex_primitives(self);
307 }
308
309 fn initialize_eplain_tex(&mut self) -> TeXResult<(), Self::Types> {
313 self.initialize_etex_primitives();
314 self.init_file("eplain.tex")
315 }
316
317 fn load_latex(&mut self) -> TeXResult<(), Self::Types> {
322 self.init_file("latex.ltx")
323 }
324}
325
326pub struct DefaultEngine<ET: EngineTypes> {
328 pub aux: EngineAux<ET>,
329 pub state: ET::State,
330 pub filesystem: ET::FileSystem,
331 pub fontsystem: ET::FontSystem,
332 pub mouth: ET::Mouth,
333 pub gullet: ET::Gullet,
334 pub stomach: ET::Stomach,
335}
336impl<ET: EngineTypes> Default for DefaultEngine<ET> {
337 fn default() -> Self {
338 let mut memory = MemoryManager::default();
339 let mut aux = EngineAux {
340 outputs: ET::Outputs::new(),
341 error_handler: ET::ErrorHandler::new(),
342 start_time: chrono::Local::now(),
343 extension: ET::Extension::new(&mut memory),
344 memory,
345 jobname: String::new(),
346 };
347 let fontsystem = ET::FontSystem::new(&mut aux);
348 let mut state = ET::State::new(fontsystem.null(), &mut aux);
349 let mut mouth = ET::Mouth::new(&mut aux, &mut state);
350 let gullet = ET::Gullet::new(&mut aux, &mut state, &mut mouth);
351 let stomach = ET::Stomach::new(&mut aux, &mut state);
352 Self {
353 state,
354 aux,
355 fontsystem,
356 filesystem: ET::FileSystem::new(crate::utils::PWD.to_path_buf()),
357 mouth,
358 gullet,
359 stomach,
360 }
361 }
362}
363impl<ET: EngineTypes> TeXEngine for DefaultEngine<ET> {
364 type Types = ET;
365 fn get_engine_refs(&mut self) -> EngineReferences<ET> {
366 EngineReferences {
367 aux: &mut self.aux,
368 state: &mut self.state,
369 filesystem: &mut self.filesystem,
370 fontsystem: &mut self.fontsystem,
371 mouth: &mut self.mouth,
372 gullet: &mut self.gullet,
373 stomach: &mut self.stomach,
374 colon: Colon::default(),
375 }
376 }
377}
378pub type PlainTeXEngine = DefaultEngine<DefaultPlainTeXEngineTypes>;
380
381pub trait EngineExtension<ET: EngineTypes> {
385 fn new(memory: &mut MemoryManager<ET::Token>) -> Self;
386}
387impl<ET: EngineTypes<Extension = ()>> EngineExtension<ET> for () {
388 fn new(_memory: &mut MemoryManager<ET::Token>) -> Self {}
389}
390
391impl<ET: EngineTypes> EngineReferences<'_, ET> {
392 pub fn trace_command<D: std::fmt::Display, F: FnOnce(&mut Self) -> D>(&mut self, f: F) {
394 let trace = self.state.get_primitive_int(PRIMITIVES.tracingcommands)
395 > <ET::Num as NumSet>::Int::default();
396 if trace {
397 let d = f(self);
398 self.aux.outputs.write_neg1(format_args!("{{{d}}}"));
399 }
400 }
401 pub fn top_loop(&mut self) -> TeXResult<(), ET> {
405 crate::expand_loop!(ET::Stomach::every_top(self) => token => {
406 if token.is_primitive() == Some(PRIMITIVES.noexpand) {
407 let _ = self.get_next(false);
408 continue
409 }
410 }; self,
411 ResolvedToken::Tk { char, code } => ET::Stomach::do_char(self, token, char, code)?,
412 ResolvedToken::Cmd(Some(TeXCommand::Char {char, code})) => ET::Stomach::do_char(self, token, *char, *code)?,
413 ResolvedToken::Cmd(None) => TeXError::undefined(self.aux,self.state,self.mouth,&token)?,
414 ResolvedToken::Cmd(Some(cmd)) => crate::do_cmd!(self,token,cmd)
415 );
416 Ok(())
417 }
418}
419
420#[macro_export]
423macro_rules! expand_loop {
424 ($engine:ident,$tk:ident,$($case:tt)*) => {{
425 $crate::expand_loop!(ET;$engine,$tk,$($case)*)
426 }};
427 ($then:expr => $engine:ident,$tk:ident,$($case:tt)*) => {{
428 $then;
429 while let Some($tk) = $engine.get_next(false)? {
430 $crate::expand!($engine,$tk;$($case)*);
431 $then;
432 }
433 }};
434 ($then:expr => $tk:ident => $first:expr; $engine:ident,$($case:tt)*) => {{
435 $then;
436 while let Some($tk) = $engine.get_next(false)? {
437 $first;
438 $crate::expand!($engine,$tk;$($case)*);
439 $then;
440 }
441 }};
442 ($tk:ident => $first:expr; $engine:ident,$($case:tt)*) => {{
443 while let Some($tk) = $engine.get_next(false)? {
444 $first;
445 $crate::expand!($engine,$tk;$($case)*);
446 }
447 }};
448 ($ET:ty; $engine:ident,$tk:ident,$($case:tt)*) => {{
449 while let Some($tk) = $engine.get_next(false)? {
450 $crate::expand!($ET;$engine,$tk;$($case)*);
451 }
452 }};
453 ($ET:ty; $tk:ident => $first:expr; $engine:ident,$($case:tt)*) => {{
454 while let Some($tk) = $engine.get_next(false)? {
455 $first;
456 $crate::expand!($ET;$engine,$tk;$($case)*);
457 }
458 }}
459}
460
461#[macro_export]
463macro_rules! expand {
464 ($engine:ident,$tk:expr;$($case:tt)*) => {
465 $crate::expand!(ET;$engine,$tk;$($case)*)
466 };
467 ($ET:ty; $engine:ident,$token:expr;$($case:tt)*) => {
468 let cmd = <<$ET as EngineTypes>::Gullet as $crate::engine::gullet::Gullet<$ET>>::resolve($engine.state,&$token);
469 match cmd {
470 $crate::commands::ResolvedToken::Cmd(Some($crate::commands::TeXCommand::Macro(m))) =>
471 <<$ET as EngineTypes>::Gullet as $crate::engine::gullet::Gullet<$ET>>::do_macro($engine,m.clone(),$token)?,
472 $crate::commands::ResolvedToken::Cmd(Some($crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Conditional(cond)})) =>
473 <<$ET as EngineTypes>::Gullet as $crate::engine::gullet::Gullet<$ET>>::do_conditional($engine,*name,$token,*cond,false)?,
474 $crate::commands::ResolvedToken::Cmd(Some($crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Expandable(e)})) =>
475 <<$ET as EngineTypes>::Gullet as $crate::engine::gullet::Gullet<$ET>>::do_expandable($engine,*name,$token,*e)?,
476 $crate::commands::ResolvedToken::Cmd(Some($crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::SimpleExpandable(e)})) =>
477 <<$ET as EngineTypes>::Gullet as $crate::engine::gullet::Gullet<$ET>>::do_simple_expandable($engine,*name,$token,*e)?,
478 $($case)*
479 }
480 }
481}
482
483#[macro_export]
486macro_rules! do_cmd {
487 ($engine:ident,$token:expr,$cmd:ident) => {
488 $crate::do_cmd!(ET;$engine,$token,$cmd)
489 };
490 ($ET:ty;$engine:ident,$token:expr,$cmd:ident) => {
491 match $cmd {
492 $crate::commands::TeXCommand::CharDef(char) => <$ET as EngineTypes>::Stomach::do_char($engine, $token, *char, CommandCode::Other)?,
495 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Unexpandable { scope, apply }} =>
496 <$ET as EngineTypes>::Stomach::do_unexpandable($engine, *name, *scope,$token, *apply)?,
497 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Assignment(assign)} =>
498 <$ET as EngineTypes>::Stomach::do_assignment($engine, *name, $token, *assign,false)?,
499 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Int { assign: Some(assign), .. }} =>
500 <$ET as EngineTypes>::Stomach::do_assignment($engine, *name, $token, *assign,false)?,
501 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Dim { assign: Some(assign), .. }} =>
502 <$ET as EngineTypes>::Stomach::do_assignment($engine, *name, $token, *assign,false)?,
503 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Skip { assign: Some(assign), .. }} =>
504 <$ET as EngineTypes>::Stomach::do_assignment($engine, *name, $token, *assign,false)?,
505 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::MuSkip { assign: Some(assign), .. }} =>
506 <$ET as EngineTypes>::Stomach::do_assignment($engine, *name, $token, *assign,false)?,
507 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::FontCmd { assign: Some(assign), .. }} =>
508 <$ET as EngineTypes>::Stomach::do_assignment($engine, *name, $token, *assign,false)?,
509 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Box(read)} =>
510 <$ET as EngineTypes>::Stomach::do_box($engine, *name, $token, *read)?,
511 $crate::commands::TeXCommand::Font(f) =>
512 <$ET as EngineTypes>::Stomach::assign_font($engine, $token, f.clone(),false)?,
513 $crate::commands::TeXCommand::IntRegister(u) =>
514 <$ET as EngineTypes>::Stomach::assign_int_register($engine, *u,false,$token)?,
515 $crate::commands::TeXCommand::DimRegister(u) =>
516 <$ET as EngineTypes>::Stomach::assign_dim_register($engine, *u,false,$token)?,
517 $crate::commands::TeXCommand::SkipRegister(u) =>
518 <$ET as EngineTypes>::Stomach::assign_skip_register($engine, *u,false,$token)?,
519 $crate::commands::TeXCommand::MuSkipRegister(u) =>
520 <$ET as EngineTypes>::Stomach::assign_muskip_register($engine, *u,false,$token)?,
521 $crate::commands::TeXCommand::ToksRegister(u) =>
522 <$ET as EngineTypes>::Stomach::assign_toks_register($engine,$token, *u,false)?,
523 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::Whatsit { get, .. }} =>
524 <$ET as EngineTypes>::Stomach::do_whatsit($engine, *name,$token, *get)?,
525 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::PrimitiveInt} =>
526 <$ET as EngineTypes>::Stomach::assign_primitive_int($engine,*name,false,$token)?,
527 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::PrimitiveDim} =>
528 <$ET as EngineTypes>::Stomach::assign_primitive_dim($engine,*name,false,$token)?,
529 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::PrimitiveSkip} =>
530 <$ET as EngineTypes>::Stomach::assign_primitive_skip($engine,*name,false,$token)?,
531 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::PrimitiveMuSkip} =>
532 <$ET as EngineTypes>::Stomach::assign_primitive_muskip($engine,*name,false,$token)?,
533 $crate::commands::TeXCommand::Primitive{name,cmd:$crate::commands::PrimitiveCommand::PrimitiveToks} =>
534 <$ET as EngineTypes>::Stomach::assign_primitive_toks($engine,$token,*name,false)?,
535 $crate::commands::TeXCommand::MathChar(u) if <$ET as EngineTypes>::Stomach::data_mut($engine.stomach).mode().is_math() =>
536 <$ET as EngineTypes>::Stomach::do_mathchar($engine,*u,Some($token)),
537 $crate::commands::TeXCommand::Primitive{cmd:$crate::commands::PrimitiveCommand::Relax,..} => (),
538 $crate::commands::TeXCommand::Primitive{
539 cmd:$crate::commands::PrimitiveCommand::Int { .. } |
540 $crate::commands::PrimitiveCommand::Dim { .. } |
541 $crate::commands::PrimitiveCommand::Skip { .. } |
542 $crate::commands::PrimitiveCommand::MuSkip { .. } |
543 $crate::commands::PrimitiveCommand::FontCmd { .. }
544 ,name
545 } =>
546 TeXError::not_allowed_in_mode($engine.aux,$engine.state,$engine.mouth,*name,
547 <$ET as EngineTypes>::Stomach::data_mut($engine.stomach).mode()
548 )?,
549 $crate::commands::TeXCommand::MathChar(_) =>
550 TeXError::not_allowed_in_mode($engine.aux,$engine.state,$engine.mouth,
551 $crate::commands::primitives::PRIMITIVES.mathchar,
552 <$ET as EngineTypes>::Stomach::data_mut($engine.stomach).mode()
553 )?,
554 $crate::commands::TeXCommand::Macro(_) |
555 $crate::commands::TeXCommand::Primitive{ cmd:$crate::commands::PrimitiveCommand::Conditional { .. } |
556 $crate::commands::PrimitiveCommand::Expandable { .. } |
557 $crate::commands::PrimitiveCommand::SimpleExpandable { .. },..
558 } | TeXCommand::Char {..} => unreachable!(),
559 }
560 }
561}