diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-01-28 12:01:05 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-01-28 12:14:03 +0100 |
| commit | 28c554ec2185a15e22f0408ce485ed4afe035e03 (patch) | |
| tree | 622d2d281133c4e6b92633e44bfc1e1301250fb4 /src/model | |
| parent | 23238d4d44881a5b466ab23a32e2a7447f460127 (diff) | |
Rework math attachments and accents
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/cast.rs | 11 | ||||
| -rw-r--r-- | src/model/eval.rs | 41 | ||||
| -rw-r--r-- | src/model/library.rs | 19 | ||||
| -rw-r--r-- | src/model/module.rs | 6 | ||||
| -rw-r--r-- | src/model/ops.rs | 19 | ||||
| -rw-r--r-- | src/model/str.rs | 8 | ||||
| -rw-r--r-- | src/model/symbol.rs | 35 | ||||
| -rw-r--r-- | src/model/value.rs | 6 |
8 files changed, 120 insertions, 25 deletions
diff --git a/src/model/cast.rs b/src/model/cast.rs index 17ed2d30..0da9906f 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -233,6 +233,17 @@ castable! { } castable! { + char, + string: Str => { + let mut chars = string.chars(); + match (chars.next(), chars.next()) { + (Some(c), None) => c, + _ => Err("expected exactly one character")?, + } + }, +} + +castable! { EcoString, string: Str => string.into(), } diff --git a/src/model/eval.rs b/src/model/eval.rs index 538fa687..44f08a76 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -8,8 +8,9 @@ use comemo::{Track, Tracked, TrackedMut}; use unicode_segmentation::UnicodeSegmentation; use super::{ - methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func, Label, - LangItems, Module, Recipe, Scopes, Selector, StyleMap, Symbol, Transform, Value, + combining_accent, methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, + Dict, Func, Label, LangItems, Module, Recipe, Scopes, Selector, StyleMap, Symbol, + Transform, Value, }; use crate::diag::{ bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint, @@ -347,7 +348,7 @@ impl Eval for ast::Expr { Self::MathIdent(v) => v.eval(vm), Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content), Self::MathDelimited(v) => v.eval(vm).map(Value::Content), - Self::MathScript(v) => v.eval(vm).map(Value::Content), + Self::MathAttach(v) => v.eval(vm).map(Value::Content), Self::MathFrac(v) => v.eval(vm).map(Value::Content), Self::Ident(v) => v.eval(vm), Self::None(v) => v.eval(vm), @@ -593,20 +594,20 @@ impl Eval for ast::MathDelimited { } } -impl Eval for ast::MathScript { +impl Eval for ast::MathAttach { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { let base = self.base().eval(vm)?.display_in_math(); let sub = self - .sub() + .bottom() .map(|expr| expr.eval(vm).map(Value::display_in_math)) .transpose()?; let sup = self - .sup() + .top() .map(|expr| expr.eval(vm).map(Value::display_in_math)) .transpose()?; - Ok((vm.items.math_script)(base, sub, sup)) + Ok((vm.items.math_attach)(base, sub, sup)) } } @@ -929,13 +930,21 @@ impl Eval for ast::FuncCall { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let callee = self.callee(); - let callee_span = callee.span(); - let in_math = matches!(callee, ast::Expr::MathIdent(_)); - let callee = callee.eval(vm)?; + let callee_expr = self.callee(); + let callee_span = callee_expr.span(); + let callee = callee_expr.eval(vm)?; let mut args = self.args().eval(vm)?; - if in_math && !matches!(callee, Value::Func(_)) { + if in_math(&callee_expr) && !matches!(callee, Value::Func(_)) { + if let Value::Symbol(sym) = &callee { + let c = sym.get(); + if let Some(accent) = combining_accent(c) { + let base = args.expect("base")?; + args.finish()?; + return Ok(Value::Content((vm.items.math_accent)(base, accent))); + } + } + let mut body = (vm.items.math_atom)('('.into()); for (i, arg) in args.all::<Content>()?.into_iter().enumerate() { if i > 0 { @@ -952,6 +961,14 @@ impl Eval for ast::FuncCall { } } +fn in_math(expr: &ast::Expr) -> bool { + match expr { + ast::Expr::MathIdent(_) => true, + ast::Expr::FieldAccess(access) => in_math(&access.target()), + _ => false, + } +} + fn complete_call( vm: &mut Vm, callee: &Func, diff --git a/src/model/library.rs b/src/model/library.rs index 773342b3..4208a4c7 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -71,12 +71,13 @@ pub struct LangItems { pub math_atom: fn(atom: EcoString) -> Content, /// An alignment point in a formula: `&`. pub math_align_point: fn() -> Content, - /// A subsection in a math formula that is surrounded by matched delimiters: - /// `[x + y]`. + /// Matched delimiters surrounding math in a formula: `[x + y]`. pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content, - /// A base with optional sub- and superscripts in a formula: `a_1^2`. - pub math_script: - fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content, + /// A base with optional attachments in a formula: `a_1^2`. + pub math_attach: + fn(base: Content, bottom: Option<Content>, top: Option<Content>) -> Content, + /// A base with an accent: `arrow(x)`. + pub math_accent: fn(base: Content, accent: char) -> Content, /// A fraction in a formula: `x/2`. pub math_frac: fn(num: Content, denom: Content) -> Content, } @@ -95,6 +96,8 @@ impl Hash for LangItems { self.space.hash(state); self.linebreak.hash(state); self.text.hash(state); + self.text_id.hash(state); + (self.text_str as usize).hash(state); self.smart_quote.hash(state); self.parbreak.hash(state); self.strong.hash(state); @@ -108,9 +111,11 @@ impl Hash for LangItems { self.term_item.hash(state); self.formula.hash(state); self.math_atom.hash(state); - self.math_script.hash(state); - self.math_frac.hash(state); self.math_align_point.hash(state); + self.math_delimited.hash(state); + self.math_attach.hash(state); + self.math_accent.hash(state); + self.math_frac.hash(state); } } diff --git a/src/model/module.rs b/src/model/module.rs index 6a1c60a5..954a84f0 100644 --- a/src/model/module.rs +++ b/src/model/module.rs @@ -78,3 +78,9 @@ impl Debug for Module { write!(f, "<module {}>", self.name()) } } + +impl PartialEq for Module { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} diff --git a/src/model/ops.rs b/src/model/ops.rs index 7acf917d..83137f38 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; -use super::{Regex, Value}; +use super::{format_str, Regex, Value}; use crate::diag::StrResult; use crate::geom::{Axes, Axis, GenAlign, Length, Numeric, PartialStroke, Rel, Smart}; use crate::util::format_eco; @@ -20,10 +20,15 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> { Ok(match (lhs, rhs) { (a, None) => a, (None, b) => b, + (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")), (Str(a), Str(b)) => Str(a + b), - (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), - (Content(a), Str(b)) => Content(a + item!(text)(b.into())), + (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")), + (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), (Content(a), Content(b)) => Content(a + b), + (Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())), + (Content(a), Str(b)) => Content(a + item!(text)(b.into())), + (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), + (Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), (a, b) => mismatch!("cannot join {} with {}", a, b), @@ -85,10 +90,15 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> { (Fraction(a), Fraction(b)) => Fraction(a + b), + (Symbol(a), Symbol(b)) => Str(format_str!("{a}{b}")), (Str(a), Str(b)) => Str(a + b), + (Str(a), Symbol(b)) => Str(format_str!("{a}{b}")), + (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), (Content(a), Content(b)) => Content(a + b), + (Content(a), Symbol(b)) => Content(a + item!(text)(b.get().into())), (Content(a), Str(b)) => Content(a + item!(text)(b.into())), (Str(a), Content(b)) => Content(item!(text)(a.into()) + b), + (Symbol(a), Content(b)) => Content(item!(text)(a.get().into()) + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), @@ -326,11 +336,14 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool { (Relative(a), Relative(b)) => a == b, (Fraction(a), Fraction(b)) => a == b, (Color(a), Color(b)) => a == b, + (Symbol(a), Symbol(b)) => a == b, (Str(a), Str(b)) => a == b, (Label(a), Label(b)) => a == b, (Array(a), Array(b)) => a == b, (Dict(a), Dict(b)) => a == b, (Func(a), Func(b)) => a == b, + (Args(a), Args(b)) => a == b, + (Module(a), Module(b)) => a == b, (Dyn(a), Dyn(b)) => a == b, // Some technically different things should compare equal. diff --git a/src/model/str.rs b/src/model/str.rs index 9196a35a..6bfbcebd 100644 --- a/src/model/str.rs +++ b/src/model/str.rs @@ -1,5 +1,5 @@ use std::borrow::{Borrow, Cow}; -use std::fmt::{self, Debug, Formatter, Write}; +use std::fmt::{self, Debug, Display, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::ops::{Add, AddAssign, Deref}; @@ -334,6 +334,12 @@ impl Deref for Str { } } +impl Display for Str { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(self) + } +} + impl Debug for Str { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_char('"')?; diff --git a/src/model/symbol.rs b/src/model/symbol.rs index 686f1b81..214fea3e 100644 --- a/src/model/symbol.rs +++ b/src/model/symbol.rs @@ -1,6 +1,6 @@ use std::cmp::Reverse; use std::collections::BTreeSet; -use std::fmt::{self, Debug, Formatter, Write}; +use std::fmt::{self, Debug, Display, Formatter, Write}; use crate::diag::StrResult; use crate::util::EcoString; @@ -109,6 +109,12 @@ impl Debug for Symbol { } } +impl Display for Symbol { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_char(self.get()) + } +} + /// Find the best symbol from the list. fn find(list: &[(&str, char)], modifiers: &str) -> Option<char> { let mut best = None; @@ -150,3 +156,30 @@ fn parts(modifiers: &str) -> impl Iterator<Item = &str> { fn contained(modifiers: &str, m: &str) -> bool { parts(modifiers).any(|part| part == m) } + +/// Normalize an accent to a combining one. +/// +/// https://www.w3.org/TR/mathml-core/#combining-character-equivalences +pub fn combining_accent(c: char) -> Option<char> { + Some(match c { + '\u{0300}' | '`' => '\u{0300}', + '\u{0301}' | '´' => '\u{0301}', + '\u{0302}' | '^' | 'ˆ' => '\u{0302}', + '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', + '\u{0304}' | '¯' => '\u{0304}', + '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', + '\u{0306}' | '˘' => '\u{0306}', + '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', + '\u{0308}' | '¨' => '\u{0308}', + '\u{030a}' | '∘' | '○' => '\u{030a}', + '\u{030b}' | '˝' => '\u{030b}', + '\u{030c}' | 'ˇ' => '\u{030c}', + '\u{0327}' | '¸' => '\u{0327}', + '\u{0328}' | '˛' => '\u{0328}', + '\u{0332}' | '_' => '\u{0332}', + '\u{20d6}' | '←' => '\u{20d6}', + '\u{20d7}' | '→' | '⟶' => '\u{20d7}', + '⏞' | '⏟' | '⎴' | '⎵' | '⏜' | '⏝' | '⏠' | '⏡' => c, + _ => return None, + }) +} diff --git a/src/model/value.rs b/src/model/value.rs index d03911c6..4b9fa5f7 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -438,7 +438,11 @@ primitive! { Rel<Length>: "relative length", primitive! { Fr: "fraction", Fraction } primitive! { Color: "color", Color } primitive! { Symbol: "symbol", Symbol } -primitive! { Str: "string", Str } +primitive! { + Str: "string", + Str, + Symbol(symbol) => symbol.get().into() +} primitive! { Label: "label", Label } primitive! { Content: "content", Content, |
