use crate::ast::AtomFamily;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpacingSpec {
pub thick: String,
pub medium: String,
pub thin: String,
pub operator: String,
}
pub fn atom_family_name(family: AtomFamily) -> String {
match family {
AtomFamily::Mord => "mord",
AtomFamily::Mop => "mop",
AtomFamily::Mbin => "mbin",
AtomFamily::Mrel => "mrel",
AtomFamily::Mopen => "mopen",
AtomFamily::Mclose => "mclose",
AtomFamily::Mpunct => "mpunct",
AtomFamily::Minner => "minner",
}
.to_string()
}
fn normal_spacing<'a>(left: &str, right: &str, spec: &'a SpacingSpec) -> Option<&'a str> {
match (left, right) {
("mord", "mop") | ("mord", "mbig") => Some(&spec.thin),
("mord", "mbin") => Some(&spec.medium),
("mord", "mrel") => Some(&spec.thick),
("mord", "minner") => Some(&spec.thin),
("mop", "mord") => Some(&spec.operator),
("mop", "mop") | ("mop", "mbig") => Some(&spec.operator),
("mop", "mrel") => Some(&spec.thick),
("mop", "minner") => Some(&spec.thin),
("mbig", "mord") | ("mbig", "mop") | ("mbig", "mopen") | ("mbig", "minner") => {
Some(&spec.medium)
}
("mbig", "mbin") => Some(&spec.medium),
("mbig", "mrel") => Some(&spec.thick),
("mbig", "mclose") | ("mbig", "mpunct") => Some(&spec.thin),
("mbin", "mord") => Some(&spec.medium),
("mbin", "mop") | ("mbin", "mbig") => Some(&spec.medium),
("mbin", "mopen") => Some(&spec.medium),
("mbin", "minner") => Some(&spec.medium),
("mrel", "mord") => Some(&spec.thick),
("mrel", "mop") | ("mrel", "mbig") => Some(&spec.thick),
("mrel", "mopen") => Some(&spec.thick),
("mrel", "minner") => Some(&spec.thick),
("mclose", "mop") | ("mclose", "mbig") => Some(&spec.thin),
("mclose", "mbin") => Some(&spec.medium),
("mclose", "mrel") => Some(&spec.thick),
("mclose", "minner") => Some(&spec.thin),
("mpunct", "mord") => Some(&spec.thin),
("mpunct", "mop") | ("mpunct", "mbig") => Some(&spec.thin),
("mpunct", "mrel") => Some(&spec.thick),
("mpunct", "mopen") => Some(&spec.thin),
("mpunct", "mclose") => Some(&spec.thin),
("mpunct", "mpunct") => Some(&spec.thin),
("mpunct", "minner") => Some(&spec.thin),
("minner", "mord") => Some(&spec.thin),
("minner", "mop") | ("minner", "mbig") => Some(&spec.thin),
("minner", "mbin") => Some(&spec.medium),
("minner", "mrel") => Some(&spec.thick),
("minner", "mopen") => Some(&spec.thin),
("minner", "mpunct") => Some(&spec.thin),
("minner", "minner") => Some(&spec.thin),
_ => None,
}
}
fn tight_spacing<'a>(left: &str, right: &str, spec: &'a SpacingSpec) -> Option<&'a str> {
match (left, right) {
("mord", "mop") => Some(&spec.thin),
("mop", "mord") => Some(&spec.thin),
("mop", "mop") => Some(&spec.thin),
("mbig", "mord") | ("mbig", "mop") | ("mbig", "minner") => Some(&spec.thin),
("mclose", "mop") => Some(&spec.thin),
("minner", "mop") => Some(&spec.thin),
_ => None,
}
}
pub fn math_spacing(
left_type: &str,
right_type: &str,
tight: bool,
spec: &SpacingSpec,
) -> Option<String> {
if tight {
tight_spacing(left_type, right_type, spec)
} else {
normal_spacing(left_type, right_type, spec)
}
.map(|s| s.to_string())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpacableItem {
pub atom_type: Option<String>,
pub text: String,
pub baseline: usize,
}
fn cancels_left_of_bin(ty: &str) -> bool {
matches!(ty, "leftmost" | "mbin" | "mopen" | "mrel" | "mop" | "mpunct" | "mbig")
}
fn cancels_right_of_bin(ty: &str) -> bool {
matches!(ty, "rightmost" | "mrel" | "mclose" | "mpunct")
}
pub fn cancel_bin_atoms(items: Vec<SpacableItem>) -> Vec<SpacableItem> {
let n = items.len();
if n == 0 {
return items;
}
let mut items = items;
if items[0].atom_type.as_deref() == Some("mbin") {
items[0].atom_type = Some("mord".to_string());
}
for i in 1..n {
let prev_ty = items[i - 1].atom_type.clone();
let curr_ty = items[i].atom_type.clone();
match (prev_ty.as_deref(), curr_ty.as_deref()) {
(Some("mbin"), Some(right)) => {
if cancels_right_of_bin(right) {
items[i - 1].atom_type = Some("mord".to_string());
}
}
(Some(left), Some("mbin"))
if cancels_left_of_bin(left) => {
items[i].atom_type = Some("mord".to_string());
}
_ => (),
}
}
if n >= 1 && items[n - 1].atom_type.as_deref() == Some("mbin") {
items[n - 1].atom_type = Some("mord".to_string());
}
items
}
pub fn join_with_spacing(items: &[SpacableItem], tight: bool, spec: &SpacingSpec) -> String {
let n = items.len();
if n == 0 {
return String::new();
}
if n == 1 {
return items[0].text.clone();
}
let mut result = items[0].text.clone();
for i in 1..n {
let prev = &items[i - 1];
let curr = &items[i];
match (&prev.atom_type, &curr.atom_type) {
(Some(left), Some(right)) => {
if let Some(space) = math_spacing(left, right, tight, spec) {
result.push_str(&space);
}
result.push_str(&curr.text);
}
_ => result.push_str(&curr.text),
}
}
result
}