1use std::rc::Rc;
5
6use crate::Engine;
7use crate::cmd::Cmd;
8use crate::hash::{CsId, NO_CS};
9use crate::input::{Level, ScannerStatus, TokKind};
10use crate::primitives_gen::assign_int as ip;
11use crate::token::{Token, TokenList};
12
13pub const EXPAND_DEPTH: u32 = 10000;
15
16pub mod cs_name_codes {
18 pub const CSNAME: u32 = 0;
20 pub const LAST_NAMED_CS: u32 = 1;
22 pub const BEGIN_CSNAME: u32 = 2;
24}
25
26#[inline]
28fn is_match_or_end(t: Token) -> bool {
29 !t.is_cs() && (t.cmd_code() == Cmd::ParEnd as u32 || t.cmd_code() == Cmd::Stop as u32)
30}
31
32#[inline]
35fn is_brace(t: Token) -> bool {
36 !t.is_cs() && (t.cmd_code() == Cmd::LeftBrace as u32 || t.cmd_code() == Cmd::RightBrace as u32)
37}
38
39#[inline]
40fn is_left_brace(t: Token) -> bool {
41 !t.is_cs() && t.cmd_code() == Cmd::LeftBrace as u32
42}
43
44impl Engine {
45 pub fn get_x_token(&mut self) {
48 self.get_next();
49 if self.cur_cmd > Cmd::MAX_COMMAND {
50 self.get_x_token_expanding();
51 } else {
52 self.cur_tok = self.make_cur_tok();
53 }
54 }
55
56 #[inline(never)]
59 fn get_x_token_expanding(&mut self) {
60 loop {
61 if self.cur_cmd >= Cmd::Call {
62 if self.cur_cmd < Cmd::EndTemplate {
63 self.macro_call();
64 } else {
65 self.cur_cs = self.cs.frozen_endv;
66 self.cur_cmd = Cmd::Endv;
67 break;
68 }
69 } else {
70 let exec = std::mem::replace(&mut self.exec_context, false);
73 self.expand();
74 self.exec_context = exec;
75 }
76 self.get_next();
77 if self.cur_cmd <= Cmd::MAX_COMMAND {
78 break;
79 }
80 }
81 self.cur_tok = self.make_cur_tok();
82 }
83
84 pub fn x_token(&mut self) {
86 while self.cur_cmd > Cmd::MAX_COMMAND {
87 if self.cur_cmd == Cmd::EndTemplate {
88 self.cur_cs = self.cs.frozen_endv;
91 self.cur_cmd = Cmd::Endv;
92 break;
93 }
94 self.expand();
95 self.get_next();
96 }
97 self.cur_tok = self.make_cur_tok();
98 }
99
100 pub fn expand(&mut self) {
102 let cv = self.cur_val;
103 let cvl = self.cur_val_level;
104 let cg = self.cur_glue;
105 let one_step = std::mem::take(&mut self.one_step);
108 self.expand_depth += 1;
109 if self.expand_depth >= EXPAND_DEPTH {
110 self.overflow("expansion depth", i64::from(EXPAND_DEPTH));
111 }
112 loop {
113 if self.cur_cmd < Cmd::Call {
114 if self.eqtb.int(ip::TRACINGCOMMANDS) > 1 {
115 self.show_cur_cmd_chr();
116 }
117 match self.cur_cmd {
118 Cmd::TopBotMark => self.insert_mark_text(),
119 Cmd::ExpandAfter => {
120 if self.cur_chr == 0 {
121 self.get_token();
123 let t = self.cur_tok;
124 self.get_token();
125 if self.cur_cmd > Cmd::MAX_COMMAND {
126 self.one_step = true;
127 self.expand();
128 } else {
129 self.back_input();
130 }
131 self.cur_tok = t;
132 self.back_input();
133 } else {
134 self.get_token();
136 if self.cur_cmd == Cmd::IfTest
137 && self.cur_chr != crate::cond::if_codes::IF_CASE
138 {
139 self.cur_chr += crate::cond::UNLESS_CODE;
140 continue; }
142 self.print_err("You can't use `");
143 self.print_esc_str("unless");
144 self.printer.print_str("' before `");
145 self.print_cmd_chr(self.cur_cmd, self.cur_chr);
146 self.printer.print_char('\'' as u32);
147 self.help(&["Continue, and I'll forget that it ever happened."]);
148 self.back_error();
149 }
150 }
151 Cmd::NoExpand => {
152 if self.cur_chr == 0 {
153 self.no_expand();
154 } else {
155 self.primitive_cmd();
156 if self.cur_cmd > Cmd::MAX_COMMAND && self.cur_cmd != Cmd::NoExpand {
157 continue;
158 }
159 if self.cur_cmd <= Cmd::MAX_COMMAND {
160 self.back_input();
161 }
162 }
163 }
164 Cmd::CsName => self.manufacture_cs_name(),
165 Cmd::Convert => self.conv_toks(),
166 Cmd::The => self.ins_the_toks(),
167 Cmd::IfTest => self.conditional(),
168 Cmd::FiOrElse => self.fi_or_else(),
169 Cmd::Input => self.input_command(),
170 Cmd::LuaExpandableCall | Cmd::LuaLocalCall => self.lua_expandable_call(),
171 Cmd::Variable | Cmd::Feedback => self.backend_expandable(),
172 _ => {
173 self.print_err("Undefined control sequence");
174 self.help(&[
175 "The control sequence at the end of the top line",
176 "of your error message was never \\def'ed. If you have",
177 "misspelled it (e.g., `\\hobx'), type `I' and the correct",
178 "spelling (e.g., `I\\hbox'). Otherwise just continue,",
179 "and I'll forget about whatever was undefined.",
180 ]);
181 self.error();
182 }
183 }
184 } else if self.cur_cmd < Cmd::EndTemplate {
185 self.one_step = one_step;
186 self.macro_call();
187 } else {
188 self.cur_tok = Token::cs(self.cs.frozen_endv);
189 self.back_input();
190 }
191 break;
192 }
193 self.expand_depth -= 1;
194 self.cur_val = cv;
195 self.cur_val_level = cvl;
196 self.cur_glue = cg;
197 }
198
199 fn no_expand(&mut self) {
201 let save = self.input.scanner_status;
202 self.input.scanner_status = ScannerStatus::Normal;
203 self.get_token();
204 self.input.scanner_status = save;
205 let t = self.cur_tok;
206 if t.is_cs() {
207 while let Some(Level::Tokens(l)) = self.input.stack.last() {
209 if l.loc < l.list.len() || l.kind == TokKind::VTemplate {
210 break;
211 }
212 self.end_token_list();
213 }
214 let list: TokenList = Rc::from(vec![Token::cs(self.cs.frozen_dont_expand), t]);
215 self.begin_token_list(list, TokKind::BackedUp);
216 } else {
217 self.back_input();
218 }
219 }
220
221 fn primitive_cmd(&mut self) {
224 let save = self.input.scanner_status;
225 self.input.scanner_status = ScannerStatus::Normal;
226 self.get_token();
227 self.input.scanner_status = save;
228 if self.cur_cs != NO_CS
229 && let Some(p) = crate::primitives::lookup(self.hash.name(self.cur_cs))
230 {
231 self.cur_cmd = p.cmd;
232 self.cur_chr = p.chr;
233 let frozen = self.frozen_primitive(self.cur_cs);
235 self.cur_cs = frozen;
236 self.cur_tok = Token::cs(frozen);
237 if self.cur_cmd > Cmd::MAX_COMMAND {
238 return;
239 }
240 return;
241 }
242 self.cur_cmd = Cmd::Relax;
243 self.cur_chr = 0;
244 self.back_input();
245 self.cur_cmd = Cmd::NoExpand;
246 }
247
248 pub(crate) fn lookup_named_cs(&mut self, name: &[u8]) -> CsId {
252 let cs = if name.is_empty() {
253 NO_CS
254 } else {
255 match self.hash.lookup(name) {
256 Some(cs) if self.eqtb.meaning(cs).cmd != Cmd::UndefinedCs => cs,
257 _ => crate::hash::UNDEFINED_CS,
258 }
259 };
260 self.last_cs_name = cs;
261 cs
262 }
263
264 fn manufacture_cs_name(&mut self) {
266 if self.cur_chr == cs_name_codes::LAST_NAMED_CS {
267 if self.last_cs_name != NO_CS {
268 self.cur_tok = Token::cs(self.last_cs_name);
269 self.back_input();
270 }
271 return;
272 }
273 let begin = self.cur_chr == cs_name_codes::BEGIN_CSNAME;
274 let e = self.cond.is_in_csname;
275 self.cond.is_in_csname = true;
276 let mut name: Vec<u8> = Vec::new();
277 loop {
278 self.get_x_token();
279 if self.cur_cs != NO_CS {
280 break;
281 }
282 crate::utf8x::push(&mut name, self.cur_chr);
283 }
284 if self.cur_cmd != Cmd::EndCsName {
285 self.complain_missing_endcsname();
286 }
287 self.cond.is_in_csname = e;
288 if begin {
289 let cs = self.lookup_named_cs(&name);
290 if cs != NO_CS && cs != crate::hash::UNDEFINED_CS {
291 self.cur_tok = Token::cs(cs);
292 self.back_input();
293 }
294 return;
295 }
296 let cs = self.hash.lookup_or_insert(&name);
297 self.last_cs_name = cs;
298 if self.eqtb.meaning(cs).cmd == Cmd::UndefinedCs {
299 self.eq_define(
300 crate::eqtb::Loc::Cs(cs),
301 crate::eqtb::Val::Meaning(crate::eqtb::Meaning::prim(Cmd::Relax, RELAX_CHR)),
302 );
303 }
304 self.cur_tok = Token::cs(cs);
305 self.back_input();
306 }
307
308 pub fn complain_missing_endcsname(&mut self) {
310 self.print_err("Missing ");
311 self.print_esc_str("endcsname");
312 self.printer.print_str(" inserted");
313 self.help(&[
314 "The control sequence marked <to be read again> should",
315 "not appear between \\csname and \\endcsname.",
316 ]);
317 self.back_error();
318 }
319
320 pub fn insert_relax(&mut self) {
322 self.cur_tok = Token::cs(self.cur_cs);
323 self.back_input();
324 self.cur_tok = Token::cs(self.cs.frozen_relax);
325 self.back_input();
326 if let Some(Level::Tokens(t)) = self.input.stack.last_mut() {
327 t.kind = TokKind::Inserted;
328 }
329 }
330
331 fn input_command(&mut self) {
333 match self.cur_chr {
334 1 => self.input.force_eof = true,
335 2 | 3 => self.pseudo_start(self.cur_chr == 3),
336 _ => {
337 if self.files.name_in_progress {
338 self.insert_relax();
339 } else {
340 self.start_input();
341 }
342 }
343 }
344 }
345
346 fn insert_mark_text(&mut self) {
348 let mut code = self.cur_chr;
349 let class = if code >= 5 {
350 code -= 5;
351 self.scan_register_num();
352 self.cur_val as u32
353 } else {
354 0
355 };
356 if let Some(list) = self.marks.get(class, code)
357 && !list.is_empty()
358 {
359 self.begin_token_list(list, TokKind::MarkText);
360 }
361 }
362
363 pub fn macro_call(&mut self) {
365 let one_step = std::mem::take(&mut self.one_step);
366 self.check_snapshot();
367 if self.macro_profile.is_some() {
368 self.profile_macro_call(self.cur_cs);
369 }
370 if let Some(mac) = &self.eqtb.meaning(self.cur_cs).mac
372 && mac.tokens[0] == Token::END_MATCH
373 && self.eqtb.int(ip::TRACINGMACROS) <= 0
374 {
375 let tokens = mac.tokens.clone();
376 let native = (mac.native.get() != 1).then(|| Rc::clone(mac));
379 while let Some(Level::Tokens(t)) = self.input.stack.last() {
380 if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
381 break;
382 }
383 self.end_token_list();
384 }
385 if let Some(mac) = native
386 && let Some(i) = self.native_of(&mac, self.cur_cs)
387 && self.run_native(i, &[], one_step)
388 {
389 return;
390 }
391 let param_start = self.input.param_stack.len();
392 self.begin_macro(tokens, 1, self.cur_cs, param_start);
393 return;
394 }
395 let saved_runaway = std::mem::take(&mut self.runaway_list);
399 self.macro_call_with_args(one_step);
400 self.runaway_list = saved_runaway;
401 }
402
403 fn macro_call_with_args(&mut self, one_step: bool) {
407 let save_scanner_status = self.input.scanner_status;
408 let save_warning_index = self.input.warning_index;
409 let save_cs = self.cur_cs;
410 self.input.warning_index = self.cur_cs;
411 let Some(mac) = self.eqtb.meaning(self.cur_cs).mac.clone() else {
412 self.input.scanner_status = save_scanner_status;
413 self.input.warning_index = save_warning_index;
414 return;
415 };
416 let tokens = mac.tokens.clone();
417 if self.eqtb.int(ip::TRACINGMACROS) > 0 {
418 self.begin_diagnostic();
419 self.printer.print_ln();
420 self.print_input_level();
421 self.print_cs(self.input.warning_index);
422 self.token_show_list(&tokens, 0, tokens.len(), 10_000_000);
423 self.end_diagnostic(false);
424 }
425 let mut r = 0usize;
426 let mut pstack: Vec<TokenList> = std::mem::take(&mut self.param_scratch);
428 pstack.clear();
429 if tokens[0] != Token::END_MATCH {
430 self.input.scanner_status = ScannerStatus::Matching;
431 let mut unbalance: i32 = 0;
432 self.long_state = self.cur_cmd;
433 if self.long_state >= Cmd::OuterCall {
434 self.long_state = if self.long_state == Cmd::OuterCall {
435 Cmd::Call
436 } else {
437 Cmd::LongCall
438 };
439 }
440 let par_token = Token::cs(self.cs.par);
441 loop {
442 self.runaway_list = std::mem::take(&mut self.arg_scratch);
447 self.runaway_list.clear();
448 let s: Option<usize>;
449 let mut match_chr = 0u32;
450 let mut m = 0i32;
451 let mut rbrace_ptr = 0usize;
452 if !is_match_or_end(tokens[r]) || tokens[r] == Token::END_MATCH {
453 s = None;
454 } else {
455 match_chr = tokens[r].chr();
456 s = Some(r + 1);
457 r += 1;
458 }
459 'cont: loop {
460 self.get_token();
461 if self.cur_tok == tokens[r] {
462 r += 1;
463 if is_match_or_end(tokens[r]) {
464 if is_left_brace(self.cur_tok) {
465 self.input.align_state -= 1;
466 }
467 break 'cont; }
469 continue 'cont;
470 }
471 if s != Some(r) {
473 match s {
474 None => {
475 self.print_err("Use of ");
476 self.sprint_cs(self.input.warning_index);
477 self.printer.print_str(" doesn't match its definition");
478 self.help(&[
479 "If you say, e.g., `\\def\\a1{...}', then you must always",
480 "put `1' after `\\a', since control sequence names are",
481 "made up of letters only. The macro here has not been",
482 "followed by the required stuff, so I'm ignoring it.",
483 ]);
484 self.error();
485 self.runaway_list.clear();
486 self.input.scanner_status = save_scanner_status;
487 self.input.warning_index = save_warning_index;
488 return;
489 }
490 Some(s) => {
491 let mut t = s;
492 loop {
493 self.runaway_list.push(tokens[t]);
494 m += 1;
495 let mut u = t + 1;
496 let mut v = s;
497 let mut resumed = false;
498 loop {
499 if u == r {
500 if self.cur_tok != tokens[v] {
501 break;
502 }
503 r = v + 1;
504 resumed = true;
505 break;
506 }
507 if tokens[u] != tokens[v] {
508 break;
509 }
510 u += 1;
511 v += 1;
512 }
513 if resumed {
514 continue 'cont;
515 }
516 t += 1;
517 if t == r {
518 break;
519 }
520 }
521 r = s;
522 }
523 }
524 }
525 if self.cur_tok == par_token && self.long_state != Cmd::LongCall {
526 self.report_runaway_argument();
527 self.input.align_state -= unbalance;
528 self.input.scanner_status = save_scanner_status;
529 self.input.warning_index = save_warning_index;
530 return;
531 }
532 if is_brace(self.cur_tok) {
533 if is_left_brace(self.cur_tok) {
534 unbalance = 1;
536 loop {
537 self.runaway_list.push(self.cur_tok);
538 self.get_token();
539 if self.cur_tok == par_token && self.long_state != Cmd::LongCall {
540 self.report_runaway_argument();
541 self.input.align_state -= unbalance;
542 self.input.scanner_status = save_scanner_status;
543 self.input.warning_index = save_warning_index;
544 return;
545 }
546 if is_brace(self.cur_tok) {
547 if is_left_brace(self.cur_tok) {
548 unbalance += 1;
549 } else {
550 unbalance -= 1;
551 if unbalance == 0 {
552 break;
553 }
554 }
555 }
556 }
557 rbrace_ptr = self.runaway_list.len();
558 self.runaway_list.push(self.cur_tok);
559 } else {
560 self.back_input();
562 self.print_err("Argument of ");
563 self.sprint_cs(self.input.warning_index);
564 self.printer.print_str(" has an extra }");
565 self.help(&[
566 "I've run across a `}' that doesn't seem to match anything.",
567 "For example, `\\def\\a#1{...}' and `\\a}' would produce",
568 "this error. If you simply proceed now, the `\\par' that",
569 "I've just inserted will cause me to report a runaway",
570 "argument that might be the root of the problem. But if",
571 "your `}' was spurious, just type `2' and it will go away.",
572 ]);
573 self.input.align_state += 1;
574 self.long_state = Cmd::Call;
575 self.cur_tok = par_token;
576 self.ins_error();
577 continue 'cont;
578 }
579 } else {
580 if self.cur_tok == Token::SPACE && is_match_or_end(tokens[r]) {
581 continue 'cont;
582 }
583 self.runaway_list.push(self.cur_tok);
584 }
585 m += 1;
586 if !is_match_or_end(tokens[r]) {
587 continue 'cont;
588 }
589 break 'cont;
590 }
591 if s.is_some() {
593 let param: TokenList = if m == 1
595 && self.runaway_list.last().is_some_and(|&t| is_brace(t))
596 && rbrace_ptr > 0
597 {
598 Rc::from(&self.runaway_list[1..rbrace_ptr])
599 } else {
600 Rc::from(&self.runaway_list[..])
601 };
602 pstack.push(param);
603 let param = &pstack[pstack.len() - 1];
604 if self.eqtb.int(ip::TRACINGMACROS) > 0 {
605 self.begin_diagnostic();
606 if !self.print_input_level() {
607 self.printer.begin_line();
608 }
609 self.printer.print_char(match_chr);
610 self.printer.print_int(pstack.len() as i64);
611 self.printer.print_str("<-");
612 self.show_token_list(param, 0, param.len(), 1000);
613 self.end_diagnostic(false);
614 }
615 }
616 self.arg_scratch = std::mem::take(&mut self.runaway_list);
617 if tokens[r] == Token::END_MATCH {
618 break;
619 }
620 }
621 }
622 while let Some(Level::Tokens(t)) = self.input.stack.last() {
624 if t.loc < t.list.len() || t.kind == TokKind::VTemplate {
625 break;
626 }
627 self.end_token_list();
628 }
629 if let Some(i) = self.native_of(&mac, save_cs)
630 && self.run_native(i, &pstack, one_step)
631 {
632 pstack.clear();
633 self.param_scratch = pstack;
634 self.input.scanner_status = save_scanner_status;
635 self.input.warning_index = save_warning_index;
636 return;
637 }
638 let param_start = self.input.param_stack.len();
639 self.input.param_stack.append(&mut pstack);
640 self.param_scratch = pstack;
641 self.begin_macro(tokens, r + 1, self.input.warning_index, param_start);
642 self.input.scanner_status = save_scanner_status;
643 self.input.warning_index = save_warning_index;
644 }
645
646 fn report_runaway_argument(&mut self) {
648 if self.long_state == Cmd::Call {
649 self.runaway();
650 self.print_err("Paragraph ended before ");
651 self.sprint_cs(self.input.warning_index);
652 self.printer.print_str(" was complete");
653 self.help(&[
654 "I suspect you've forgotten a `}', causing me to apply this",
655 "control sequence to too much text. How can we recover?",
656 "My plan is to forget the whole thing and hope for the best.",
657 ]);
658 self.back_error();
659 }
660 }
661
662 pub fn runaway(&mut self) {
664 let what = match self.input.scanner_status {
665 ScannerStatus::Defining => "definition",
666 ScannerStatus::Matching => "argument",
667 ScannerStatus::Aligning => "preamble",
668 ScannerStatus::Absorbing => "text",
669 _ => return,
670 };
671 self.printer.print_nl("Runaway ");
672 self.printer.print_str(what);
673 self.printer.print_char('?' as u32);
674 self.printer.print_ln();
675 let list = std::mem::take(&mut self.runaway_list);
676 let limit = self.printer.error_line.saturating_sub(10);
677 self.show_token_list(&list, 0, list.len(), limit as i64);
678 self.runaway_list = list;
679 }
680
681 pub fn check_outer_validity(&mut self) {
684 if self.input.scanner_status == ScannerStatus::Normal {
685 return;
686 }
687 if self.eqtb.int(ip::SUPPRESSOUTERERROR) > 0 && self.cur_cs != NO_CS {
688 return;
689 }
690 self.err.deletions_allowed = false;
691 if self.cur_cs != NO_CS {
693 let reread = match self.input.stack.last() {
694 Some(Level::Tokens(_)) => true,
695 Some(Level::Text(t)) => !matches!(
696 t.source,
697 crate::input::LineSource::Terminal | crate::input::LineSource::Read(_)
698 ),
699 None => true,
700 };
701 if reread {
702 let list: TokenList = Rc::from(vec![Token::cs(self.cur_cs)]);
703 self.back_list(list);
704 }
705 self.cur_cmd = Cmd::Spacer;
706 self.cur_chr = ' ' as u32;
707 }
708 if self.input.scanner_status > ScannerStatus::Skipping {
709 self.runaway();
710 if self.cur_cs == NO_CS {
711 self.print_err("File ended");
712 } else {
713 self.cur_cs = NO_CS;
714 self.print_err("Forbidden control sequence found");
715 }
716 self.printer.print_str(" while scanning ");
717 let mut ins: Vec<Token> = Vec::new();
718 match self.input.scanner_status {
719 ScannerStatus::Defining => {
720 self.printer.print_str("definition");
721 ins.push(Token::RIGHT_BRACE);
722 }
723 ScannerStatus::Matching => {
724 self.printer.print_str("use");
725 ins.push(Token::cs(self.cs.par));
726 self.long_state = Cmd::OuterCall;
727 }
728 ScannerStatus::Aligning => {
729 self.printer.print_str("preamble");
730 ins.push(Token::cs(self.cs.frozen_cr));
731 ins.push(Token::RIGHT_BRACE);
732 self.input.align_state = -1_000_000;
733 }
734 _ => {
735 self.printer.print_str("text");
736 ins.push(Token::RIGHT_BRACE);
737 }
738 }
739 self.ins_list(Rc::from(ins));
740 self.printer.print_str(" of ");
741 self.sprint_cs(self.input.warning_index);
742 self.help(&[
743 "I suspect you have forgotten a `}', causing me",
744 "to read past where you wanted me to stop.",
745 "I'll try to recover; but if the error is serious,",
746 "you'd better type `E' or `X' now and fix your file.",
747 ]);
748 self.error();
749 } else {
750 self.print_err("Incomplete ");
751 self.print_cmd_chr(Cmd::IfTest, self.cond.cur_if);
752 self.printer.print_str("; all text was ignored after line ");
753 self.printer.print_int(self.cond.skip_line.into());
754 let third = if self.cur_cs != NO_CS {
755 self.cur_cs = NO_CS;
756 "the matching `\\fi'. I've inserted a `\\fi'; this might work."
757 } else {
758 "The file ended while I was skipping conditional text."
759 };
760 self.help(&[
761 "A forbidden control sequence occurred in skipped text.",
762 "This kind of error happens when you say `\\if...' and forget",
763 third,
764 ]);
765 self.cur_tok = Token::cs(self.cs.frozen_fi);
766 self.ins_error();
767 }
768 self.err.deletions_allowed = true;
769 }
770
771 fn frozen_primitive(&mut self, cs: CsId) -> CsId {
774 let name = self.hash.name(cs).to_vec();
775 if let Some(&f) = self.frozen_prims.get(&name) {
776 return f;
777 }
778 let f = self.hash.new_frozen(&name);
779 if let Some(p) = crate::primitives::lookup(&name) {
780 self.eqtb.ensure_cs(f);
781 self.eqtb.cs[f.0 as usize].val = crate::eqtb::Meaning::prim(p.cmd, p.chr);
782 }
783 self.frozen_prims.insert(name, f);
784 f
785 }
786}
787
788pub const RELAX_CHR: u32 = 0x11_0000;