1use alloc::borrow::Cow;
2use alloc::vec::Vec;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6pub struct ProfileId(pub &'static str);
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum EngineKind {
12 Tex,
14 Etex,
16 Xetex,
18}
19
20pub trait EngineProfile {
22 fn id(&self) -> ProfileId;
24
25 fn kind(&self) -> EngineKind;
27
28 fn primitives(&self) -> &[PrimitiveSpec];
30
31 fn catcode_defaults(&self) -> CatcodeDefaults;
33
34 fn mathcode_defaults(&self) -> MathcodeDefaults;
36
37 fn register_defaults(&self) -> RegisterDefaults;
39
40 fn font_semantics(&self) -> FontSemantics;
42
43 fn extension_policy(&self) -> ExtensionPolicy;
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct EngineSemantics {
50 pub profile: ProfileId,
52 pub kind: EngineKind,
54 pub primitives: Vec<PrimitiveSpec>,
56 pub catcodes: CatcodeDefaults,
58 pub mathcodes: MathcodeDefaults,
60 pub registers: RegisterDefaults,
62 pub fonts: FontSemantics,
64 pub extensions: ExtensionPolicy,
66 pub patches: EnginePatches,
68}
69
70impl EngineSemantics {
71 #[must_use]
73 pub fn from_profile<P>(profile: &P) -> Self
74 where
75 P: EngineProfile,
76 {
77 let extensions = profile.extension_policy();
78 Self {
79 profile: profile.id(),
80 kind: profile.kind(),
81 primitives: profile.primitives().to_vec(),
82 catcodes: profile.catcode_defaults(),
83 mathcodes: profile.mathcode_defaults(),
84 registers: profile.register_defaults(),
85 fonts: profile.font_semantics(),
86 extensions,
87 patches: EnginePatches::from_extension_policy(extensions),
88 }
89 }
90
91 #[must_use]
93 pub fn primitives(&self) -> &[PrimitiveSpec] {
94 &self.primitives
95 }
96
97 #[must_use]
99 pub fn primitive(&self, name: &str) -> Option<&PrimitiveSpec> {
100 self.primitives
101 .iter()
102 .find(|primitive| primitive.name == name)
103 }
104
105 #[must_use]
107 pub fn has_primitive(&self, name: &str) -> bool {
108 self.primitive(name).is_some()
109 }
110
111 #[must_use]
113 pub const fn has_patch(&self, patch: EnginePatch) -> bool {
114 self.patches.contains(patch)
115 }
116
117 #[must_use]
119 pub const fn is_xetex(&self) -> bool {
120 self.has_patch(EnginePatch::Xetex)
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub struct TexCore {
127 profile: ProfileId,
128 kind: EngineKind,
129 primitives: &'static [PrimitiveSpec],
130 catcodes: CatcodeDefaults,
131 mathcodes: MathcodeDefaults,
132 registers: RegisterDefaults,
133 fonts: FontSemantics,
134 extensions: ExtensionPolicy,
135 patches: EnginePatches,
136}
137
138impl TexCore {
139 #[must_use]
141 pub const fn tex(profile: ProfileId) -> Self {
142 Self {
143 profile,
144 kind: EngineKind::Tex,
145 primitives: TEX_CORE_PRIMITIVES,
146 catcodes: CatcodeDefaults {
147 unicode_scalars: false,
148 },
149 mathcodes: MathcodeDefaults {
150 unicode_math: false,
151 },
152 registers: RegisterDefaults::tex(),
153 fonts: FontSemantics::tex(),
154 extensions: ExtensionPolicy::tex(),
155 patches: EnginePatches::empty(),
156 }
157 }
158
159 #[must_use]
161 pub const fn with_patch(mut self, patch: EnginePatch) -> Self {
162 self.patches = self.patches.with(patch);
163 match patch {
164 EnginePatch::Etex => {
165 self.kind = EngineKind::Etex;
166 self.primitives = ETEX_PROFILE_PRIMITIVES;
167 self.registers = RegisterDefaults::extended();
168 self.extensions = ExtensionPolicy {
169 etex: true,
170 ..self.extensions
171 };
172 }
173 EnginePatch::Xetex => {
174 self.kind = EngineKind::Xetex;
175 self.primitives = XETEX_PROFILE_PRIMITIVES;
176 self.catcodes = CatcodeDefaults {
177 unicode_scalars: true,
178 };
179 self.mathcodes = MathcodeDefaults { unicode_math: true };
180 self.registers = RegisterDefaults::extended();
181 self.fonts = FontSemantics {
182 unicode_fonts: true,
183 shaped_text: true,
184 unicode_math_fonts: true,
185 host_native_fonts: false,
186 };
187 self.extensions = ExtensionPolicy {
188 etex: true,
189 xetex: true,
190 };
191 }
192 }
193 self
194 }
195
196 #[must_use]
198 pub const fn profile(&self) -> ProfileId {
199 self.profile
200 }
201
202 #[must_use]
204 pub const fn kind(&self) -> EngineKind {
205 self.kind
206 }
207
208 #[must_use]
210 pub const fn primitives(&self) -> &'static [PrimitiveSpec] {
211 self.primitives
212 }
213
214 #[must_use]
216 pub const fn catcode_defaults(&self) -> CatcodeDefaults {
217 self.catcodes
218 }
219
220 #[must_use]
222 pub const fn mathcode_defaults(&self) -> MathcodeDefaults {
223 self.mathcodes
224 }
225
226 #[must_use]
228 pub const fn register_defaults(&self) -> RegisterDefaults {
229 self.registers
230 }
231
232 #[must_use]
234 pub const fn font_semantics(&self) -> FontSemantics {
235 self.fonts
236 }
237
238 #[must_use]
240 pub const fn extension_policy(&self) -> ExtensionPolicy {
241 self.extensions
242 }
243
244 #[must_use]
246 pub const fn patches(&self) -> EnginePatches {
247 self.patches
248 }
249
250 #[must_use]
252 pub const fn has_patch(&self, patch: EnginePatch) -> bool {
253 self.patches.contains(patch)
254 }
255}
256
257#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259#[non_exhaustive]
260pub enum EnginePatch {
261 Etex,
263 Xetex,
265}
266
267#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
269pub struct EnginePatches {
270 etex: bool,
271 xetex: bool,
272}
273
274impl EnginePatches {
275 #[must_use]
277 pub const fn empty() -> Self {
278 Self {
279 etex: false,
280 xetex: false,
281 }
282 }
283
284 #[must_use]
286 pub const fn with(mut self, patch: EnginePatch) -> Self {
287 match patch {
288 EnginePatch::Etex => {
289 self.etex = true;
290 }
291 EnginePatch::Xetex => {
292 self.etex = true;
293 self.xetex = true;
294 }
295 }
296 self
297 }
298
299 #[must_use]
301 pub const fn from_extension_policy(policy: ExtensionPolicy) -> Self {
302 Self {
303 etex: policy.etex || policy.xetex,
304 xetex: policy.xetex,
305 }
306 }
307
308 #[must_use]
310 pub const fn contains(&self, patch: EnginePatch) -> bool {
311 match patch {
312 EnginePatch::Etex => self.etex,
313 EnginePatch::Xetex => self.xetex,
314 }
315 }
316}
317
318#[derive(Clone, Debug, PartialEq, Eq)]
320pub struct PrimitiveSpec {
321 pub name: Cow<'static, str>,
323 pub opcode: PrimitiveOpcode,
325 pub kind: PrimitiveKind,
327}
328
329#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
331pub struct PrimitiveOpcode(pub u32);
332
333#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
335#[non_exhaustive]
336pub enum PrimitiveKind {
337 Expandable,
339 Assignment,
341 Math,
343 Layout,
345 Resource,
347 Extension,
349 #[default]
350 Other,
352}
353
354#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
356pub struct CatcodeDefaults {
357 pub unicode_scalars: bool,
359}
360
361#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
363pub struct MathcodeDefaults {
364 pub unicode_math: bool,
366}
367
368#[derive(Clone, Copy, Debug, PartialEq, Eq)]
370pub struct RegisterDefaults {
371 pub count_registers: u16,
373 pub dimension_registers: u16,
375 pub skip_registers: u16,
377 pub token_registers: u16,
379}
380
381impl Default for RegisterDefaults {
382 fn default() -> Self {
383 Self::tex()
384 }
385}
386
387impl RegisterDefaults {
388 #[must_use]
390 pub const fn tex() -> Self {
391 Self {
392 count_registers: 256,
393 dimension_registers: 256,
394 skip_registers: 256,
395 token_registers: 256,
396 }
397 }
398
399 #[must_use]
401 pub const fn extended() -> Self {
402 Self {
403 count_registers: 32_768,
404 dimension_registers: 32_768,
405 skip_registers: 32_768,
406 token_registers: 32_768,
407 }
408 }
409}
410
411#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
413pub struct FontSemantics {
414 pub unicode_fonts: bool,
416 pub shaped_text: bool,
418 pub unicode_math_fonts: bool,
420 pub host_native_fonts: bool,
422}
423
424impl FontSemantics {
425 #[must_use]
427 pub const fn tex() -> Self {
428 Self {
429 unicode_fonts: false,
430 shaped_text: false,
431 unicode_math_fonts: false,
432 host_native_fonts: false,
433 }
434 }
435}
436
437#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
439pub struct ExtensionPolicy {
440 pub etex: bool,
442 pub xetex: bool,
444}
445
446impl ExtensionPolicy {
447 #[must_use]
449 pub const fn tex() -> Self {
450 Self {
451 etex: false,
452 xetex: false,
453 }
454 }
455}
456
457const TEX_CORE_PRIMITIVES: &[PrimitiveSpec] = &[
458 PrimitiveSpec {
459 name: Cow::Borrowed("relax"),
460 opcode: PrimitiveOpcode(0),
461 kind: PrimitiveKind::Expandable,
462 },
463 PrimitiveSpec {
464 name: Cow::Borrowed("input"),
465 opcode: PrimitiveOpcode(1),
466 kind: PrimitiveKind::Resource,
467 },
468 PrimitiveSpec {
469 name: Cow::Borrowed("hbox"),
470 opcode: PrimitiveOpcode(2),
471 kind: PrimitiveKind::Layout,
472 },
473 PrimitiveSpec {
474 name: Cow::Borrowed("vbox"),
475 opcode: PrimitiveOpcode(3),
476 kind: PrimitiveKind::Layout,
477 },
478];
479
480const ETEX_PROFILE_PRIMITIVES: &[PrimitiveSpec] = &[
481 PrimitiveSpec {
482 name: Cow::Borrowed("relax"),
483 opcode: PrimitiveOpcode(0),
484 kind: PrimitiveKind::Expandable,
485 },
486 PrimitiveSpec {
487 name: Cow::Borrowed("input"),
488 opcode: PrimitiveOpcode(1),
489 kind: PrimitiveKind::Resource,
490 },
491 PrimitiveSpec {
492 name: Cow::Borrowed("hbox"),
493 opcode: PrimitiveOpcode(2),
494 kind: PrimitiveKind::Layout,
495 },
496 PrimitiveSpec {
497 name: Cow::Borrowed("vbox"),
498 opcode: PrimitiveOpcode(3),
499 kind: PrimitiveKind::Layout,
500 },
501 PrimitiveSpec {
502 name: Cow::Borrowed("expanded"),
503 opcode: PrimitiveOpcode(100),
504 kind: PrimitiveKind::Expandable,
505 },
506];
507
508const XETEX_PROFILE_PRIMITIVES: &[PrimitiveSpec] = &[
509 PrimitiveSpec {
510 name: Cow::Borrowed("relax"),
511 opcode: PrimitiveOpcode(0),
512 kind: PrimitiveKind::Expandable,
513 },
514 PrimitiveSpec {
515 name: Cow::Borrowed("input"),
516 opcode: PrimitiveOpcode(1),
517 kind: PrimitiveKind::Resource,
518 },
519 PrimitiveSpec {
520 name: Cow::Borrowed("hbox"),
521 opcode: PrimitiveOpcode(2),
522 kind: PrimitiveKind::Layout,
523 },
524 PrimitiveSpec {
525 name: Cow::Borrowed("vbox"),
526 opcode: PrimitiveOpcode(3),
527 kind: PrimitiveKind::Layout,
528 },
529 PrimitiveSpec {
530 name: Cow::Borrowed("expanded"),
531 opcode: PrimitiveOpcode(100),
532 kind: PrimitiveKind::Expandable,
533 },
534 PrimitiveSpec {
535 name: Cow::Borrowed("XeTeXrevision"),
536 opcode: PrimitiveOpcode(200),
537 kind: PrimitiveKind::Extension,
538 },
539 PrimitiveSpec {
540 name: Cow::Borrowed("font"),
541 opcode: PrimitiveOpcode(201),
542 kind: PrimitiveKind::Assignment,
543 },
544];
545
546#[derive(Clone, Copy, Debug, Default)]
548pub struct TexProfile;
549
550impl EngineProfile for TexProfile {
551 fn id(&self) -> ProfileId {
552 self.core().profile()
553 }
554
555 fn kind(&self) -> EngineKind {
556 self.core().kind()
557 }
558
559 fn primitives(&self) -> &[PrimitiveSpec] {
560 self.core().primitives()
561 }
562
563 fn catcode_defaults(&self) -> CatcodeDefaults {
564 self.core().catcode_defaults()
565 }
566
567 fn mathcode_defaults(&self) -> MathcodeDefaults {
568 self.core().mathcode_defaults()
569 }
570
571 fn register_defaults(&self) -> RegisterDefaults {
572 self.core().register_defaults()
573 }
574
575 fn font_semantics(&self) -> FontSemantics {
576 self.core().font_semantics()
577 }
578
579 fn extension_policy(&self) -> ExtensionPolicy {
580 self.core().extension_policy()
581 }
582}
583
584impl TexProfile {
585 #[must_use]
587 pub const fn core(&self) -> TexCore {
588 TexCore::tex(ProfileId("tex"))
589 }
590}
591
592#[derive(Clone, Copy, Debug, Default)]
594pub struct EtexProfile;
595
596impl EngineProfile for EtexProfile {
597 fn id(&self) -> ProfileId {
598 self.core().profile()
599 }
600
601 fn kind(&self) -> EngineKind {
602 self.core().kind()
603 }
604
605 fn primitives(&self) -> &[PrimitiveSpec] {
606 self.core().primitives()
607 }
608
609 fn catcode_defaults(&self) -> CatcodeDefaults {
610 self.core().catcode_defaults()
611 }
612
613 fn mathcode_defaults(&self) -> MathcodeDefaults {
614 self.core().mathcode_defaults()
615 }
616
617 fn register_defaults(&self) -> RegisterDefaults {
618 self.core().register_defaults()
619 }
620
621 fn font_semantics(&self) -> FontSemantics {
622 self.core().font_semantics()
623 }
624
625 fn extension_policy(&self) -> ExtensionPolicy {
626 self.core().extension_policy()
627 }
628}
629
630impl EtexProfile {
631 #[must_use]
633 pub const fn core(&self) -> TexCore {
634 TexCore::tex(ProfileId("etex")).with_patch(EnginePatch::Etex)
635 }
636}
637
638#[derive(Clone, Copy, Debug, Default)]
640pub struct XetexProfile;
641
642impl EngineProfile for XetexProfile {
643 fn id(&self) -> ProfileId {
644 self.core().profile()
645 }
646
647 fn kind(&self) -> EngineKind {
648 self.core().kind()
649 }
650
651 fn primitives(&self) -> &[PrimitiveSpec] {
652 self.core().primitives()
653 }
654
655 fn catcode_defaults(&self) -> CatcodeDefaults {
656 self.core().catcode_defaults()
657 }
658
659 fn mathcode_defaults(&self) -> MathcodeDefaults {
660 self.core().mathcode_defaults()
661 }
662
663 fn register_defaults(&self) -> RegisterDefaults {
664 self.core().register_defaults()
665 }
666
667 fn font_semantics(&self) -> FontSemantics {
668 self.core().font_semantics()
669 }
670
671 fn extension_policy(&self) -> ExtensionPolicy {
672 self.core().extension_policy()
673 }
674}
675
676impl XetexProfile {
677 #[must_use]
679 pub const fn core(&self) -> TexCore {
680 TexCore::tex(ProfileId("xetex"))
681 .with_patch(EnginePatch::Etex)
682 .with_patch(EnginePatch::Xetex)
683 }
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689
690 #[test]
691 fn xetex_profile_declares_unicode_font_and_extension_semantics() {
692 let profile = XetexProfile;
693
694 assert_eq!(profile.kind(), EngineKind::Xetex);
695 assert!(profile.font_semantics().unicode_fonts);
696 assert!(profile.font_semantics().shaped_text);
697 assert!(profile.font_semantics().unicode_math_fonts);
698 assert!(!profile.font_semantics().host_native_fonts);
699 assert!(profile.extension_policy().etex);
700 assert!(profile.extension_policy().xetex);
701 assert!(profile.register_defaults().count_registers > 256);
702 }
703
704 #[test]
705 fn runtime_semantics_resolve_profile_gated_primitives() {
706 let tex = EngineSemantics::from_profile(&TexProfile);
707 let xetex = EngineSemantics::from_profile(&XetexProfile);
708
709 assert!(tex.has_primitive("input"));
710 assert!(xetex.has_primitive("input"));
711 assert!(!tex.has_primitive("XeTeXrevision"));
712 assert!(xetex.has_primitive("XeTeXrevision"));
713 assert_eq!(
714 xetex
715 .primitive("XeTeXrevision")
716 .expect("xetex primitive")
717 .kind,
718 PrimitiveKind::Extension
719 );
720 }
721
722 #[test]
723 fn tex_and_xetex_profiles_share_the_same_core_before_patches() {
724 let tex = TexProfile.core();
725 let xetex = XetexProfile.core();
726
727 assert_eq!(tex.profile(), ProfileId("tex"));
728 assert_eq!(xetex.profile(), ProfileId("xetex"));
729 assert!(!tex.has_patch(EnginePatch::Etex));
730 assert!(!tex.has_patch(EnginePatch::Xetex));
731 assert!(xetex.has_patch(EnginePatch::Etex));
732 assert!(xetex.has_patch(EnginePatch::Xetex));
733 assert!(xetex.primitives().len() > tex.primitives().len());
734 for primitive in tex.primitives() {
735 assert!(
736 xetex
737 .primitives()
738 .iter()
739 .any(|candidate| candidate.name == primitive.name
740 && candidate.opcode == primitive.opcode
741 && candidate.kind == primitive.kind),
742 "xetex should retain TeX core primitive {primitive:?}"
743 );
744 }
745 }
746
747 #[test]
748 fn xetex_patch_adds_unicode_and_font_semantics_conditionally() {
749 let core = TexCore::tex(ProfileId("custom-xetex")).with_patch(EnginePatch::Xetex);
750
751 assert_eq!(core.kind(), EngineKind::Xetex);
752 assert!(core.catcode_defaults().unicode_scalars);
753 assert!(core.mathcode_defaults().unicode_math);
754 assert!(core.font_semantics().unicode_fonts);
755 assert!(core.font_semantics().shaped_text);
756 assert!(!core.font_semantics().host_native_fonts);
757 assert!(core.extension_policy().etex);
758 assert!(core.extension_policy().xetex);
759 assert!(core
760 .primitives()
761 .iter()
762 .any(|primitive| primitive.name == "XeTeXrevision"));
763 }
764}