use std::collections::HashMap;
use std::sync::LazyLock;
use bumpalo::Bump;
use comemo::Tracked;
use crate::engine::Engine;
use crate::foundations::{
Args, CastInfo, Content, Context, Func, IntoValue, NativeElement, NativeFunc,
NativeFuncData, NativeFuncPtr, NativeParamInfo, Reflect, Scope, SymbolElem, Type,
elem, func,
};
use crate::layout::{Em, Length, Rel};
use crate::math::Mathy;
pub const DELIM_SHORT_FALL: Em = Em::new(0.1);
#[elem(title = "Left/Right", Mathy)]
pub struct LrElem {
#[default(Rel::one())]
pub size: Rel<Length>,
#[required]
#[parse(
let mut arguments = args.all::<Content>()?.into_iter();
let mut body = arguments.next().unwrap_or_default();
arguments.for_each(|arg| body += SymbolElem::packed(',') + arg);
body
)]
pub body: Content,
}
#[elem(Mathy)]
pub struct MidElem {
#[required]
pub body: Content,
}
#[func]
pub fn floor(
#[named]
size: Option<Rel<Length>>,
body: Content,
) -> Content {
delimited(body, '⌊', '⌋', size)
}
#[func]
pub fn ceil(
#[named]
size: Option<Rel<Length>>,
body: Content,
) -> Content {
delimited(body, '⌈', '⌉', size)
}
#[func]
pub fn round(
#[named]
size: Option<Rel<Length>>,
body: Content,
) -> Content {
delimited(body, '⌊', '⌉', size)
}
#[func]
pub fn abs(
#[named]
size: Option<Rel<Length>>,
body: Content,
) -> Content {
delimited(body, '|', '|', size)
}
#[func]
pub fn norm(
#[named]
size: Option<Rel<Length>>,
body: Content,
) -> Content {
delimited(body, '‖', '‖', size)
}
pub fn get_lr_wrapper_func(value: &str) -> Option<Func> {
let left = value.parse::<char>().ok()?;
match left {
'⌈' => Some(ceil::func()),
'⌊' => Some(floor::func()),
l => FUNCS.get(&l).map(Func::from),
}
}
const DELIMS: &[(char, char)] = &[
('(', ')'),
('⟮', '⟯'),
('⦇', '⦈'),
('⦅', '⦆'),
('⦓', '⦔'),
('⦕', '⦖'),
('{', '}'),
('⦃', '⦄'),
('[', ']'),
('⦍', '⦐'),
('⦏', '⦎'),
('⟦', '⟧'),
('⦋', '⦌'),
('❲', '❳'),
('⟬', '⟭'),
('⦗', '⦘'),
('⟅', '⟆'),
('⎰', '⎱'),
('⎱', '⎰'),
('⧘', '⧙'),
('⧚', '⧛'),
('⟨', '⟩'),
('⧼', '⧽'),
('⦑', '⦒'),
('⦉', '⦊'),
('⟪', '⟫'),
('⌜', '⌝'),
('⌞', '⌟'),
('|', '|'),
('‖', '‖'),
('⦀', '⦀'),
('⦙', '⦙'),
('⦚', '⦚'),
];
static FUNCS: LazyLock<HashMap<char, NativeFuncData>> = LazyLock::new(|| {
let bump = Box::leak(Box::new(Bump::new()));
DELIMS
.iter()
.copied()
.map(|(l, r)| (l, create_lr_func_data(l, r, bump)))
.collect()
});
fn create_lr_func_data(left: char, right: char, bump: &'static Bump) -> NativeFuncData {
let title = bumpalo::format!(in bump, "{}{} Left/Right", left, right).into_bump_str();
let docs = bumpalo::format!(in bump, "Wraps an expression in {}{}.", left, right)
.into_bump_str();
NativeFuncData {
function: NativeFuncPtr(bump.alloc(
move |_: &mut Engine, _: Tracked<Context>, args: &mut Args| {
let size = args.named("size")?;
let body = args.expect("body")?;
Ok(delimited(body, left, right, size).into_value())
},
)),
name: "(..) => ..",
title,
docs,
def_site: None,
keywords: &[],
contextual: false,
scope: LazyLock::new(&|| Scope::new()),
params: LazyLock::new(&|| create_lr_param_info()),
returns: LazyLock::new(&|| CastInfo::Type(Type::of::<Content>())),
}
}
fn create_lr_param_info() -> Vec<NativeParamInfo> {
vec.",
def_site: None,
input: Rel::<Length>::input(),
default: None,
positional: false,
named: true,
variadic: false,
required: false,
settable: false,
},
NativeParamInfo {
name: "body",
docs: "The expression to wrap.",
def_site: None,
input: Content::input(),
default: None,
positional: true,
named: false,
variadic: false,
required: true,
settable: false,
},
]
}
fn delimited(
body: Content,
left: char,
right: char,
size: Option<Rel<Length>>,
) -> Content {
let span = body.span();
let mut elem = LrElem::new(Content::sequence([
SymbolElem::packed(left),
body,
SymbolElem::packed(right),
]));
if let Some(size) = size {
elem.size.set(size);
}
elem.pack().spanned(span)
}