use alloc::borrow::Cow;
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ProfileId(pub &'static str);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EngineKind {
Tex,
Etex,
Xetex,
}
pub trait EngineProfile {
fn id(&self) -> ProfileId;
fn kind(&self) -> EngineKind;
fn primitives(&self) -> &[PrimitiveSpec];
fn catcode_defaults(&self) -> CatcodeDefaults;
fn mathcode_defaults(&self) -> MathcodeDefaults;
fn register_defaults(&self) -> RegisterDefaults;
fn font_semantics(&self) -> FontSemantics;
fn extension_policy(&self) -> ExtensionPolicy;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EngineSemantics {
pub profile: ProfileId,
pub kind: EngineKind,
pub primitives: Vec<PrimitiveSpec>,
pub catcodes: CatcodeDefaults,
pub mathcodes: MathcodeDefaults,
pub registers: RegisterDefaults,
pub fonts: FontSemantics,
pub extensions: ExtensionPolicy,
pub patches: EnginePatches,
}
impl EngineSemantics {
#[must_use]
pub fn from_profile<P>(profile: &P) -> Self
where
P: EngineProfile,
{
let extensions = profile.extension_policy();
Self {
profile: profile.id(),
kind: profile.kind(),
primitives: profile.primitives().to_vec(),
catcodes: profile.catcode_defaults(),
mathcodes: profile.mathcode_defaults(),
registers: profile.register_defaults(),
fonts: profile.font_semantics(),
extensions,
patches: EnginePatches::from_extension_policy(extensions),
}
}
#[must_use]
pub fn primitives(&self) -> &[PrimitiveSpec] {
&self.primitives
}
#[must_use]
pub fn primitive(&self, name: &str) -> Option<&PrimitiveSpec> {
self.primitives
.iter()
.find(|primitive| primitive.name == name)
}
#[must_use]
pub fn has_primitive(&self, name: &str) -> bool {
self.primitive(name).is_some()
}
#[must_use]
pub const fn has_patch(&self, patch: EnginePatch) -> bool {
self.patches.contains(patch)
}
#[must_use]
pub const fn is_xetex(&self) -> bool {
self.has_patch(EnginePatch::Xetex)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TexCore {
profile: ProfileId,
kind: EngineKind,
primitives: &'static [PrimitiveSpec],
catcodes: CatcodeDefaults,
mathcodes: MathcodeDefaults,
registers: RegisterDefaults,
fonts: FontSemantics,
extensions: ExtensionPolicy,
patches: EnginePatches,
}
impl TexCore {
#[must_use]
pub const fn tex(profile: ProfileId) -> Self {
Self {
profile,
kind: EngineKind::Tex,
primitives: TEX_CORE_PRIMITIVES,
catcodes: CatcodeDefaults {
unicode_scalars: false,
},
mathcodes: MathcodeDefaults {
unicode_math: false,
},
registers: RegisterDefaults::tex(),
fonts: FontSemantics::tex(),
extensions: ExtensionPolicy::tex(),
patches: EnginePatches::empty(),
}
}
#[must_use]
pub const fn with_patch(mut self, patch: EnginePatch) -> Self {
self.patches = self.patches.with(patch);
match patch {
EnginePatch::Etex => {
self.kind = EngineKind::Etex;
self.primitives = ETEX_PROFILE_PRIMITIVES;
self.registers = RegisterDefaults::extended();
self.extensions = ExtensionPolicy {
etex: true,
..self.extensions
};
}
EnginePatch::Xetex => {
self.kind = EngineKind::Xetex;
self.primitives = XETEX_PROFILE_PRIMITIVES;
self.catcodes = CatcodeDefaults {
unicode_scalars: true,
};
self.mathcodes = MathcodeDefaults { unicode_math: true };
self.registers = RegisterDefaults::extended();
self.fonts = FontSemantics {
unicode_fonts: true,
shaped_text: true,
unicode_math_fonts: true,
host_native_fonts: false,
};
self.extensions = ExtensionPolicy {
etex: true,
xetex: true,
};
}
}
self
}
#[must_use]
pub const fn profile(&self) -> ProfileId {
self.profile
}
#[must_use]
pub const fn kind(&self) -> EngineKind {
self.kind
}
#[must_use]
pub const fn primitives(&self) -> &'static [PrimitiveSpec] {
self.primitives
}
#[must_use]
pub const fn catcode_defaults(&self) -> CatcodeDefaults {
self.catcodes
}
#[must_use]
pub const fn mathcode_defaults(&self) -> MathcodeDefaults {
self.mathcodes
}
#[must_use]
pub const fn register_defaults(&self) -> RegisterDefaults {
self.registers
}
#[must_use]
pub const fn font_semantics(&self) -> FontSemantics {
self.fonts
}
#[must_use]
pub const fn extension_policy(&self) -> ExtensionPolicy {
self.extensions
}
#[must_use]
pub const fn patches(&self) -> EnginePatches {
self.patches
}
#[must_use]
pub const fn has_patch(&self, patch: EnginePatch) -> bool {
self.patches.contains(patch)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EnginePatch {
Etex,
Xetex,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct EnginePatches {
etex: bool,
xetex: bool,
}
impl EnginePatches {
#[must_use]
pub const fn empty() -> Self {
Self {
etex: false,
xetex: false,
}
}
#[must_use]
pub const fn with(mut self, patch: EnginePatch) -> Self {
match patch {
EnginePatch::Etex => {
self.etex = true;
}
EnginePatch::Xetex => {
self.etex = true;
self.xetex = true;
}
}
self
}
#[must_use]
pub const fn from_extension_policy(policy: ExtensionPolicy) -> Self {
Self {
etex: policy.etex || policy.xetex,
xetex: policy.xetex,
}
}
#[must_use]
pub const fn contains(&self, patch: EnginePatch) -> bool {
match patch {
EnginePatch::Etex => self.etex,
EnginePatch::Xetex => self.xetex,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PrimitiveSpec {
pub name: Cow<'static, str>,
pub opcode: PrimitiveOpcode,
pub kind: PrimitiveKind,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PrimitiveOpcode(pub u32);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum PrimitiveKind {
Expandable,
Assignment,
Math,
Layout,
Resource,
Extension,
#[default]
Other,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CatcodeDefaults {
pub unicode_scalars: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct MathcodeDefaults {
pub unicode_math: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RegisterDefaults {
pub count_registers: u16,
pub dimension_registers: u16,
pub skip_registers: u16,
pub token_registers: u16,
}
impl Default for RegisterDefaults {
fn default() -> Self {
Self::tex()
}
}
impl RegisterDefaults {
#[must_use]
pub const fn tex() -> Self {
Self {
count_registers: 256,
dimension_registers: 256,
skip_registers: 256,
token_registers: 256,
}
}
#[must_use]
pub const fn extended() -> Self {
Self {
count_registers: 32_768,
dimension_registers: 32_768,
skip_registers: 32_768,
token_registers: 32_768,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FontSemantics {
pub unicode_fonts: bool,
pub shaped_text: bool,
pub unicode_math_fonts: bool,
pub host_native_fonts: bool,
}
impl FontSemantics {
#[must_use]
pub const fn tex() -> Self {
Self {
unicode_fonts: false,
shaped_text: false,
unicode_math_fonts: false,
host_native_fonts: false,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ExtensionPolicy {
pub etex: bool,
pub xetex: bool,
}
impl ExtensionPolicy {
#[must_use]
pub const fn tex() -> Self {
Self {
etex: false,
xetex: false,
}
}
}
const TEX_CORE_PRIMITIVES: &[PrimitiveSpec] = &[
PrimitiveSpec {
name: Cow::Borrowed("relax"),
opcode: PrimitiveOpcode(0),
kind: PrimitiveKind::Expandable,
},
PrimitiveSpec {
name: Cow::Borrowed("input"),
opcode: PrimitiveOpcode(1),
kind: PrimitiveKind::Resource,
},
PrimitiveSpec {
name: Cow::Borrowed("hbox"),
opcode: PrimitiveOpcode(2),
kind: PrimitiveKind::Layout,
},
PrimitiveSpec {
name: Cow::Borrowed("vbox"),
opcode: PrimitiveOpcode(3),
kind: PrimitiveKind::Layout,
},
];
const ETEX_PROFILE_PRIMITIVES: &[PrimitiveSpec] = &[
PrimitiveSpec {
name: Cow::Borrowed("relax"),
opcode: PrimitiveOpcode(0),
kind: PrimitiveKind::Expandable,
},
PrimitiveSpec {
name: Cow::Borrowed("input"),
opcode: PrimitiveOpcode(1),
kind: PrimitiveKind::Resource,
},
PrimitiveSpec {
name: Cow::Borrowed("hbox"),
opcode: PrimitiveOpcode(2),
kind: PrimitiveKind::Layout,
},
PrimitiveSpec {
name: Cow::Borrowed("vbox"),
opcode: PrimitiveOpcode(3),
kind: PrimitiveKind::Layout,
},
PrimitiveSpec {
name: Cow::Borrowed("expanded"),
opcode: PrimitiveOpcode(100),
kind: PrimitiveKind::Expandable,
},
];
const XETEX_PROFILE_PRIMITIVES: &[PrimitiveSpec] = &[
PrimitiveSpec {
name: Cow::Borrowed("relax"),
opcode: PrimitiveOpcode(0),
kind: PrimitiveKind::Expandable,
},
PrimitiveSpec {
name: Cow::Borrowed("input"),
opcode: PrimitiveOpcode(1),
kind: PrimitiveKind::Resource,
},
PrimitiveSpec {
name: Cow::Borrowed("hbox"),
opcode: PrimitiveOpcode(2),
kind: PrimitiveKind::Layout,
},
PrimitiveSpec {
name: Cow::Borrowed("vbox"),
opcode: PrimitiveOpcode(3),
kind: PrimitiveKind::Layout,
},
PrimitiveSpec {
name: Cow::Borrowed("expanded"),
opcode: PrimitiveOpcode(100),
kind: PrimitiveKind::Expandable,
},
PrimitiveSpec {
name: Cow::Borrowed("XeTeXrevision"),
opcode: PrimitiveOpcode(200),
kind: PrimitiveKind::Extension,
},
PrimitiveSpec {
name: Cow::Borrowed("font"),
opcode: PrimitiveOpcode(201),
kind: PrimitiveKind::Assignment,
},
];
#[derive(Clone, Copy, Debug, Default)]
pub struct TexProfile;
impl EngineProfile for TexProfile {
fn id(&self) -> ProfileId {
self.core().profile()
}
fn kind(&self) -> EngineKind {
self.core().kind()
}
fn primitives(&self) -> &[PrimitiveSpec] {
self.core().primitives()
}
fn catcode_defaults(&self) -> CatcodeDefaults {
self.core().catcode_defaults()
}
fn mathcode_defaults(&self) -> MathcodeDefaults {
self.core().mathcode_defaults()
}
fn register_defaults(&self) -> RegisterDefaults {
self.core().register_defaults()
}
fn font_semantics(&self) -> FontSemantics {
self.core().font_semantics()
}
fn extension_policy(&self) -> ExtensionPolicy {
self.core().extension_policy()
}
}
impl TexProfile {
#[must_use]
pub const fn core(&self) -> TexCore {
TexCore::tex(ProfileId("tex"))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct EtexProfile;
impl EngineProfile for EtexProfile {
fn id(&self) -> ProfileId {
self.core().profile()
}
fn kind(&self) -> EngineKind {
self.core().kind()
}
fn primitives(&self) -> &[PrimitiveSpec] {
self.core().primitives()
}
fn catcode_defaults(&self) -> CatcodeDefaults {
self.core().catcode_defaults()
}
fn mathcode_defaults(&self) -> MathcodeDefaults {
self.core().mathcode_defaults()
}
fn register_defaults(&self) -> RegisterDefaults {
self.core().register_defaults()
}
fn font_semantics(&self) -> FontSemantics {
self.core().font_semantics()
}
fn extension_policy(&self) -> ExtensionPolicy {
self.core().extension_policy()
}
}
impl EtexProfile {
#[must_use]
pub const fn core(&self) -> TexCore {
TexCore::tex(ProfileId("etex")).with_patch(EnginePatch::Etex)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct XetexProfile;
impl EngineProfile for XetexProfile {
fn id(&self) -> ProfileId {
self.core().profile()
}
fn kind(&self) -> EngineKind {
self.core().kind()
}
fn primitives(&self) -> &[PrimitiveSpec] {
self.core().primitives()
}
fn catcode_defaults(&self) -> CatcodeDefaults {
self.core().catcode_defaults()
}
fn mathcode_defaults(&self) -> MathcodeDefaults {
self.core().mathcode_defaults()
}
fn register_defaults(&self) -> RegisterDefaults {
self.core().register_defaults()
}
fn font_semantics(&self) -> FontSemantics {
self.core().font_semantics()
}
fn extension_policy(&self) -> ExtensionPolicy {
self.core().extension_policy()
}
}
impl XetexProfile {
#[must_use]
pub const fn core(&self) -> TexCore {
TexCore::tex(ProfileId("xetex"))
.with_patch(EnginePatch::Etex)
.with_patch(EnginePatch::Xetex)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xetex_profile_declares_unicode_font_and_extension_semantics() {
let profile = XetexProfile;
assert_eq!(profile.kind(), EngineKind::Xetex);
assert!(profile.font_semantics().unicode_fonts);
assert!(profile.font_semantics().shaped_text);
assert!(profile.font_semantics().unicode_math_fonts);
assert!(!profile.font_semantics().host_native_fonts);
assert!(profile.extension_policy().etex);
assert!(profile.extension_policy().xetex);
assert!(profile.register_defaults().count_registers > 256);
}
#[test]
fn runtime_semantics_resolve_profile_gated_primitives() {
let tex = EngineSemantics::from_profile(&TexProfile);
let xetex = EngineSemantics::from_profile(&XetexProfile);
assert!(tex.has_primitive("input"));
assert!(xetex.has_primitive("input"));
assert!(!tex.has_primitive("XeTeXrevision"));
assert!(xetex.has_primitive("XeTeXrevision"));
assert_eq!(
xetex
.primitive("XeTeXrevision")
.expect("xetex primitive")
.kind,
PrimitiveKind::Extension
);
}
#[test]
fn tex_and_xetex_profiles_share_the_same_core_before_patches() {
let tex = TexProfile.core();
let xetex = XetexProfile.core();
assert_eq!(tex.profile(), ProfileId("tex"));
assert_eq!(xetex.profile(), ProfileId("xetex"));
assert!(!tex.has_patch(EnginePatch::Etex));
assert!(!tex.has_patch(EnginePatch::Xetex));
assert!(xetex.has_patch(EnginePatch::Etex));
assert!(xetex.has_patch(EnginePatch::Xetex));
assert!(xetex.primitives().len() > tex.primitives().len());
for primitive in tex.primitives() {
assert!(
xetex
.primitives()
.iter()
.any(|candidate| candidate.name == primitive.name
&& candidate.opcode == primitive.opcode
&& candidate.kind == primitive.kind),
"xetex should retain TeX core primitive {primitive:?}"
);
}
}
#[test]
fn xetex_patch_adds_unicode_and_font_semantics_conditionally() {
let core = TexCore::tex(ProfileId("custom-xetex")).with_patch(EnginePatch::Xetex);
assert_eq!(core.kind(), EngineKind::Xetex);
assert!(core.catcode_defaults().unicode_scalars);
assert!(core.mathcode_defaults().unicode_math);
assert!(core.font_semantics().unicode_fonts);
assert!(core.font_semantics().shaped_text);
assert!(!core.font_semantics().host_native_fonts);
assert!(core.extension_policy().etex);
assert!(core.extension_policy().xetex);
assert!(core
.primitives()
.iter()
.any(|primitive| primitive.name == "XeTeXrevision"));
}
}