#[path = "specs_yaml.rs"]
mod specs_yaml;
use specs_yaml::{
AllowedModeYaml, CharacterAttributesYaml, CharacterSpecYaml, CommandKindYaml, CommandSpecYaml,
ContentModeYaml, DelimiterSpecYaml, EnvironmentSpecYaml, PackageSpecsYaml,
};
pub use texform_argspec::ContentMode;
pub use texform_argspec::{
ArgForm, ArgSpec, ArgSpecParseError, DelimiterToken, OwnedArgSpec, ParsedArgSpec, ValueKind,
parse_arg_specs,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CommandKind {
Prefix,
Infix,
Declarative,
}
impl CommandKind {
pub const fn label(&self) -> &'static str {
match self {
CommandKind::Prefix => "prefix",
CommandKind::Infix => "infix",
CommandKind::Declarative => "declarative",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllowedMode {
Math,
Text,
Both,
}
impl AllowedMode {
pub const fn as_str(self) -> &'static str {
match self {
AllowedMode::Math => "math",
AllowedMode::Text => "text",
AllowedMode::Both => "both",
}
}
pub const fn union(self, other: Self) -> Self {
match (self, other) {
(AllowedMode::Both, _) | (_, AllowedMode::Both) => AllowedMode::Both,
(AllowedMode::Math, AllowedMode::Math) => AllowedMode::Math,
(AllowedMode::Text, AllowedMode::Text) => AllowedMode::Text,
(AllowedMode::Math, AllowedMode::Text) | (AllowedMode::Text, AllowedMode::Math) => {
AllowedMode::Both
}
}
}
pub const fn allows(self, mode: ContentMode) -> bool {
match self {
AllowedMode::Both => true,
AllowedMode::Math => matches!(mode, ContentMode::Math),
AllowedMode::Text => matches!(mode, ContentMode::Text),
}
}
}
impl std::fmt::Display for AllowedMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str((*self).as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinCommandRecord {
pub name: &'static str,
pub kind: CommandKind,
pub allowed_mode: AllowedMode,
pub argspec: ParsedArgSpec,
pub tags: &'static [&'static str],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinEnvironmentRecord {
pub name: &'static str,
pub allowed_mode: AllowedMode,
pub argspec: ParsedArgSpec,
pub body_mode: ContentMode,
pub tags: &'static [&'static str],
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinCharacterAttributes {
pub mathvariant: Option<&'static str>,
pub tex_class: Option<&'static str>,
pub stretchy: Option<bool>,
pub move_sup_sub: Option<bool>,
pub large_op: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinCharacterRecord {
pub name: &'static str,
pub allowed_mode: AllowedMode,
pub unicode_value: &'static str,
pub attributes: BuiltinCharacterAttributes,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinDelimiterRecord {
pub name: &'static str,
pub is_control_sequence: bool,
pub allowed_mode: AllowedMode,
pub unicode_value: &'static str,
pub attributes: BuiltinCharacterAttributes,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActiveCommandRecord {
pub name: &'static str,
pub kind: CommandKind,
pub allowed_mode: AllowedMode,
pub argspec: ParsedArgSpec,
pub tags: &'static [&'static str],
pub from_packages: &'static [&'static str],
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActiveEnvironmentRecord {
pub name: &'static str,
pub allowed_mode: AllowedMode,
pub argspec: ParsedArgSpec,
pub body_mode: ContentMode,
pub tags: &'static [&'static str],
pub from_packages: &'static [&'static str],
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct CharacterAttributes {
pub mathvariant: Option<String>,
pub tex_class: Option<String>,
pub stretchy: Option<bool>,
pub move_sup_sub: Option<bool>,
pub large_op: Option<bool>,
}
impl From<BuiltinCharacterAttributes> for CharacterAttributes {
fn from(value: BuiltinCharacterAttributes) -> Self {
CharacterAttributes {
mathvariant: value.mathvariant.map(ToString::to_string),
tex_class: value.tex_class.map(ToString::to_string),
stretchy: value.stretchy,
move_sup_sub: value.move_sup_sub,
large_op: value.large_op,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActiveCharacterRecord {
pub name: String,
pub allowed_mode: AllowedMode,
pub unicode_value: String,
pub attributes: CharacterAttributes,
pub package: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActiveDelimiterRecord {
pub name: &'static str,
pub is_control_sequence: bool,
pub allowed_mode: AllowedMode,
pub unicode_value: String,
pub attributes: CharacterAttributes,
pub package: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandSpec {
pub name: String,
pub kind: CommandKind,
pub allowed_mode: AllowedMode,
pub argspec: OwnedArgSpec,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnvironmentSpec {
pub name: String,
pub allowed_mode: AllowedMode,
pub argspec: OwnedArgSpec,
pub body_mode: ContentMode,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CharacterSpec {
pub name: String,
pub allowed_mode: AllowedMode,
pub unicode_value: String,
pub attributes: CharacterAttributes,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DelimiterSpec {
pub name: String,
pub is_control_sequence: bool,
pub allowed_mode: AllowedMode,
pub unicode_value: String,
pub attributes: CharacterAttributes,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct PackageSpecs {
pub characters: Vec<CharacterSpec>,
pub delimiters: Vec<DelimiterSpec>,
pub commands: Vec<CommandSpec>,
pub environments: Vec<EnvironmentSpec>,
}
pub fn load_package_specs_from_str(yaml: &str, context: &str) -> PackageSpecs {
let parsed: PackageSpecsYaml = serde_yaml::from_str(yaml)
.unwrap_or_else(|e| panic!("failed to parse package specs ({context}): {e}"));
parsed.into_specs()
}
impl PackageSpecsYaml {
fn into_specs(self) -> PackageSpecs {
PackageSpecs {
characters: self.characters.into_iter().map(Into::into).collect(),
delimiters: self.delimiters.into_iter().map(Into::into).collect(),
commands: self.commands.into_iter().map(Into::into).collect(),
environments: self.environments.into_iter().map(Into::into).collect(),
}
}
}
impl From<CharacterSpecYaml> for CharacterSpec {
fn from(value: CharacterSpecYaml) -> Self {
CharacterSpec {
name: value.name,
allowed_mode: value.allowed_mode.into(),
unicode_value: value.unicode_value,
attributes: value.attributes.into(),
}
}
}
impl From<CharacterAttributesYaml> for CharacterAttributes {
fn from(value: CharacterAttributesYaml) -> Self {
CharacterAttributes {
mathvariant: value.mathvariant,
tex_class: value.tex_class,
stretchy: value.stretchy,
move_sup_sub: value.move_sup_sub,
large_op: value.large_op,
}
}
}
impl From<DelimiterSpecYaml> for DelimiterSpec {
fn from(value: DelimiterSpecYaml) -> Self {
DelimiterSpec {
name: value.name,
is_control_sequence: value.is_control_sequence,
allowed_mode: value.allowed_mode.into(),
unicode_value: value.unicode_value,
attributes: value.attributes.into(),
}
}
}
impl From<CommandSpecYaml> for CommandSpec {
fn from(value: CommandSpecYaml) -> Self {
let context = format!("command {}", value.name);
let args =
parse_arg_specs(value.argspec.as_str(), context.as_str()).unwrap_or_else(|error| {
panic!("{error}");
});
CommandSpec {
name: value.name,
kind: value.kind.into(),
allowed_mode: value.allowed_mode.into(),
argspec: OwnedArgSpec {
args,
source: value.argspec,
},
tags: value.tags,
}
}
}
impl From<CommandKindYaml> for CommandKind {
fn from(value: CommandKindYaml) -> Self {
match value {
CommandKindYaml::Prefix => CommandKind::Prefix,
CommandKindYaml::Infix => CommandKind::Infix,
CommandKindYaml::Declarative => CommandKind::Declarative,
}
}
}
impl From<AllowedModeYaml> for AllowedMode {
fn from(value: AllowedModeYaml) -> Self {
match value {
AllowedModeYaml::Math => AllowedMode::Math,
AllowedModeYaml::Text => AllowedMode::Text,
AllowedModeYaml::Both => AllowedMode::Both,
}
}
}
impl From<EnvironmentSpecYaml> for EnvironmentSpec {
fn from(value: EnvironmentSpecYaml) -> Self {
let context = format!("environment {}", value.name);
let args =
parse_arg_specs(value.argspec.as_str(), context.as_str()).unwrap_or_else(|error| {
panic!("{error}");
});
EnvironmentSpec {
name: value.name,
allowed_mode: value.allowed_mode.into(),
argspec: OwnedArgSpec {
args,
source: value.argspec,
},
body_mode: value.body_mode.into(),
tags: value.tags,
}
}
}
impl From<ContentModeYaml> for ContentMode {
fn from(value: ContentModeYaml) -> Self {
match value {
ContentModeYaml::Math => ContentMode::Math,
ContentModeYaml::Text => ContentMode::Text,
}
}
}