1use std::collections::HashMap;
2use std::sync::LazyLock;
3
4use bumpalo::Bump;
5use comemo::Tracked;
6
7use crate::engine::Engine;
8use crate::foundations::{
9 Args, CastInfo, Content, Context, Func, IntoValue, NativeElement, NativeFunc,
10 NativeFuncData, NativeFuncPtr, NativeParamInfo, Reflect, Scope, SymbolElem, Type,
11 elem, func,
12};
13use crate::layout::{Em, Length, Rel};
14use crate::math::Mathy;
15
16pub const DELIM_SHORT_FALL: Em = Em::new(0.1);
18
19#[elem(title = "Left/Right", Mathy)]
24pub struct LrElem {
25 #[default(Rel::one())]
27 pub size: Rel<Length>,
28
29 #[required]
31 #[parse(
32 let mut arguments = args.all::<Content>()?.into_iter();
33 let mut body = arguments.next().unwrap_or_default();
34 arguments.for_each(|arg| body += SymbolElem::packed(',') + arg);
35 body
36 )]
37 pub body: Content,
38}
39
40#[elem(Mathy)]
46pub struct MidElem {
47 #[required]
49 pub body: Content,
50}
51
52#[func]
58pub fn floor(
59 #[named]
63 size: Option<Rel<Length>>,
64 body: Content,
66) -> Content {
67 delimited(body, '⌊', '⌋', size)
68}
69
70#[func]
76pub fn ceil(
77 #[named]
81 size: Option<Rel<Length>>,
82 body: Content,
84) -> Content {
85 delimited(body, '⌈', '⌉', size)
86}
87
88#[func]
94pub fn round(
95 #[named]
99 size: Option<Rel<Length>>,
100 body: Content,
102) -> Content {
103 delimited(body, '⌊', '⌉', size)
104}
105
106#[func]
112pub fn abs(
113 #[named]
117 size: Option<Rel<Length>>,
118 body: Content,
120) -> Content {
121 delimited(body, '|', '|', size)
122}
123
124#[func]
130pub fn norm(
131 #[named]
135 size: Option<Rel<Length>>,
136 body: Content,
138) -> Content {
139 delimited(body, '‖', '‖', size)
140}
141
142pub fn get_lr_wrapper_func(value: &str) -> Option<Func> {
145 let left = value.parse::<char>().ok()?;
146 match left {
147 '⌈' => Some(ceil::func()),
154 '⌊' => Some(floor::func()),
155 l => FUNCS.get(&l).map(Func::from),
156 }
157}
158
159const DELIMS: &[(char, char)] = &[
161 ('(', ')'),
164 ('⟮', '⟯'),
165 ('⦇', '⦈'),
166 ('⦅', '⦆'),
167 ('⦓', '⦔'),
168 ('⦕', '⦖'),
169 ('{', '}'),
170 ('⦃', '⦄'),
171 ('[', ']'),
172 ('⦍', '⦐'),
173 ('⦏', '⦎'),
174 ('⟦', '⟧'),
175 ('⦋', '⦌'),
176 ('❲', '❳'),
177 ('⟬', '⟭'),
178 ('⦗', '⦘'),
179 ('⟅', '⟆'),
180 ('⎰', '⎱'),
181 ('⎱', '⎰'),
182 ('⧘', '⧙'),
183 ('⧚', '⧛'),
184 ('⟨', '⟩'),
185 ('⧼', '⧽'),
186 ('⦑', '⦒'),
187 ('⦉', '⦊'),
188 ('⟪', '⟫'),
189 ('⌜', '⌝'),
190 ('⌞', '⌟'),
191 ('|', '|'),
193 ('‖', '‖'),
194 ('⦀', '⦀'),
195 ('⦙', '⦙'),
196 ('⦚', '⦚'),
197];
198
199static FUNCS: LazyLock<HashMap<char, NativeFuncData>> = LazyLock::new(|| {
201 let bump = Box::leak(Box::new(Bump::new()));
202 DELIMS
203 .iter()
204 .copied()
205 .map(|(l, r)| (l, create_lr_func_data(l, r, bump)))
206 .collect()
207});
208
209fn create_lr_func_data(left: char, right: char, bump: &'static Bump) -> NativeFuncData {
211 let title = bumpalo::format!(in bump, "{}{} Left/Right", left, right).into_bump_str();
212 let docs = bumpalo::format!(in bump, "Wraps an expression in {}{}.", left, right)
213 .into_bump_str();
214 NativeFuncData {
215 function: NativeFuncPtr(bump.alloc(
216 move |_: &mut Engine, _: Tracked<Context>, args: &mut Args| {
217 let size = args.named("size")?;
218 let body = args.expect("body")?;
219 Ok(delimited(body, left, right, size).into_value())
220 },
221 )),
222 name: "(..) => ..",
223 title,
224 docs,
225 def_site: None,
226 keywords: &[],
227 contextual: false,
228 scope: LazyLock::new(&|| Scope::new()),
229 params: LazyLock::new(&|| create_lr_param_info()),
230 returns: LazyLock::new(&|| CastInfo::Type(Type::of::<Content>())),
231 }
232}
233
234fn create_lr_param_info() -> Vec<NativeParamInfo> {
236 vec.",
243 def_site: None,
244 input: Rel::<Length>::input(),
245 default: None,
246 positional: false,
247 named: true,
248 variadic: false,
249 required: false,
250 settable: false,
251 },
252 NativeParamInfo {
253 name: "body",
254 docs: "The expression to wrap.",
255 def_site: None,
256 input: Content::input(),
257 default: None,
258 positional: true,
259 named: false,
260 variadic: false,
261 required: true,
262 settable: false,
263 },
264 ]
265}
266
267fn delimited(
269 body: Content,
270 left: char,
271 right: char,
272 size: Option<Rel<Length>>,
273) -> Content {
274 let span = body.span();
275 let mut elem = LrElem::new(Content::sequence([
276 SymbolElem::packed(left),
277 body,
278 SymbolElem::packed(right),
279 ]));
280 if let Some(size) = size {
282 elem.size.set(size);
283 }
284 elem.pack().spanned(span)
285}