summaryrefslogtreecommitdiff
path: root/src/eval
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval')
-rw-r--r--src/eval/array.rs2
-rw-r--r--src/eval/dict.rs40
-rw-r--r--src/eval/function.rs11
-rw-r--r--src/eval/mod.rs19
-rw-r--r--src/eval/ops.rs16
-rw-r--r--src/eval/str.rs164
-rw-r--r--src/eval/template.rs13
-rw-r--r--src/eval/value.rs26
-rw-r--r--src/eval/walk.rs18
9 files changed, 77 insertions, 232 deletions
diff --git a/src/eval/array.rs b/src/eval/array.rs
index f6adee6d..912aa3c0 100644
--- a/src/eval/array.rs
+++ b/src/eval/array.rs
@@ -70,7 +70,7 @@ impl Array {
/// Clear the array.
pub fn clear(&mut self) {
- if Rc::strong_count(&mut self.0) == 1 {
+ if Rc::strong_count(&self.0) == 1 {
Rc::make_mut(&mut self.0).clear();
} else {
*self = Self::new();
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index e7a46b40..0d7198e1 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -4,9 +4,9 @@ use std::iter::FromIterator;
use std::ops::{Add, AddAssign};
use std::rc::Rc;
-use super::{Str, Value};
+use super::Value;
use crate::diag::StrResult;
-use crate::util::RcExt;
+use crate::util::{EcoString, RcExt};
/// Create a new [`Dict`] from key-value pairs.
#[allow(unused_macros)]
@@ -21,7 +21,7 @@ macro_rules! dict {
/// A dictionary from strings to values with clone-on-write value semantics.
#[derive(Default, Clone, PartialEq)]
-pub struct Dict(Rc<BTreeMap<Str, Value>>);
+pub struct Dict(Rc<BTreeMap<EcoString, Value>>);
impl Dict {
/// Create a new, empty dictionary.
@@ -30,7 +30,7 @@ impl Dict {
}
/// Create a new dictionary from a mapping of strings to values.
- pub fn from_map(map: BTreeMap<Str, Value>) -> Self {
+ pub fn from_map(map: BTreeMap<EcoString, Value>) -> Self {
Self(Rc::new(map))
}
@@ -45,7 +45,7 @@ impl Dict {
}
/// Borrow the value the given `key` maps to.
- pub fn get(&self, key: Str) -> StrResult<&Value> {
+ pub fn get(&self, key: EcoString) -> StrResult<&Value> {
self.0.get(&key).ok_or_else(|| missing_key(&key))
}
@@ -53,18 +53,18 @@ impl Dict {
///
/// This inserts the key with [`None`](Value::None) as the value if not
/// present so far.
- pub fn get_mut(&mut self, key: Str) -> &mut Value {
- Rc::make_mut(&mut self.0).entry(key.into()).or_default()
+ pub fn get_mut(&mut self, key: EcoString) -> &mut Value {
+ Rc::make_mut(&mut self.0).entry(key).or_default()
}
/// Insert a mapping from the given `key` to the given `value`.
- pub fn insert(&mut self, key: Str, value: Value) {
- Rc::make_mut(&mut self.0).insert(key.into(), value);
+ pub fn insert(&mut self, key: EcoString, value: Value) {
+ Rc::make_mut(&mut self.0).insert(key, value);
}
/// Clear the dictionary.
pub fn clear(&mut self) {
- if Rc::strong_count(&mut self.0) == 1 {
+ if Rc::strong_count(&self.0) == 1 {
Rc::make_mut(&mut self.0).clear();
} else {
*self = Self::new();
@@ -72,14 +72,14 @@ impl Dict {
}
/// Iterate over pairs of references to the contained keys and values.
- pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> {
+ pub fn iter(&self) -> std::collections::btree_map::Iter<EcoString, Value> {
self.0.iter()
}
}
/// The missing key access error message.
#[cold]
-fn missing_key(key: &Str) -> String {
+fn missing_key(key: &EcoString) -> String {
format!("dictionary does not contain key: {:?}", key)
}
@@ -119,21 +119,21 @@ impl AddAssign for Dict {
}
}
-impl Extend<(Str, Value)> for Dict {
- fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) {
+impl Extend<(EcoString, Value)> for Dict {
+ fn extend<T: IntoIterator<Item = (EcoString, Value)>>(&mut self, iter: T) {
Rc::make_mut(&mut self.0).extend(iter);
}
}
-impl FromIterator<(Str, Value)> for Dict {
- fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self {
+impl FromIterator<(EcoString, Value)> for Dict {
+ fn from_iter<T: IntoIterator<Item = (EcoString, Value)>>(iter: T) -> Self {
Self(Rc::new(iter.into_iter().collect()))
}
}
impl IntoIterator for Dict {
- type Item = (Str, Value);
- type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
+ type Item = (EcoString, Value);
+ type IntoIter = std::collections::btree_map::IntoIter<EcoString, Value>;
fn into_iter(self) -> Self::IntoIter {
Rc::take(self.0).into_iter()
@@ -141,8 +141,8 @@ impl IntoIterator for Dict {
}
impl<'a> IntoIterator for &'a Dict {
- type Item = (&'a Str, &'a Value);
- type IntoIter = std::collections::btree_map::Iter<'a, Str, Value>;
+ type Item = (&'a EcoString, &'a Value);
+ type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
diff --git a/src/eval/function.rs b/src/eval/function.rs
index cbbc0b36..c83d8b2b 100644
--- a/src/eval/function.rs
+++ b/src/eval/function.rs
@@ -1,7 +1,7 @@
use std::fmt::{self, Debug, Formatter, Write};
use std::rc::Rc;
-use super::{Cast, EvalContext, Str, Value};
+use super::{Cast, EvalContext, Value};
use crate::diag::{At, TypResult};
use crate::syntax::{Span, Spanned};
use crate::util::EcoString;
@@ -52,7 +52,10 @@ impl Debug for Function {
impl PartialEq for Function {
fn eq(&self, other: &Self) -> bool {
// We cast to thin pointers for comparison.
- Rc::as_ptr(&self.0) as *const () == Rc::as_ptr(&other.0) as *const ()
+ std::ptr::eq(
+ Rc::as_ptr(&self.0) as *const (),
+ Rc::as_ptr(&other.0) as *const (),
+ )
}
}
@@ -71,7 +74,7 @@ pub struct Arg {
/// The span of the whole argument.
pub span: Span,
/// The name of the argument (`None` for positional arguments).
- pub name: Option<Str>,
+ pub name: Option<EcoString>,
/// The value of the argument.
pub value: Spanned<Value>,
}
@@ -173,7 +176,7 @@ impl Args {
}
/// Reinterpret these arguments as actually being a dictionary key.
- pub fn into_key(self) -> TypResult<Str> {
+ pub fn into_key(self) -> TypResult<EcoString> {
self.into_castable("key")
}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index fda2184e..1ff497e8 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -5,8 +5,6 @@ mod array;
#[macro_use]
mod dict;
#[macro_use]
-mod str;
-#[macro_use]
mod value;
mod capture;
mod function;
@@ -15,7 +13,6 @@ mod scope;
mod template;
mod walk;
-pub use self::str::*;
pub use array::*;
pub use capture::*;
pub use dict::*;
@@ -31,6 +28,8 @@ use std::io;
use std::mem;
use std::path::PathBuf;
+use unicode_segmentation::UnicodeSegmentation;
+
use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Fractional, Length, Relative};
use crate::image::ImageStore;
@@ -38,7 +37,7 @@ use crate::loading::Loader;
use crate::source::{SourceId, SourceStore};
use crate::syntax::ast::*;
use crate::syntax::{Span, Spanned};
-use crate::util::RefMutExt;
+use crate::util::{EcoString, RefMutExt};
use crate::Context;
/// Evaluate a parsed source file into a module.
@@ -210,7 +209,7 @@ impl Eval for Lit {
LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
LitKind::Fractional(v) => Value::Fractional(Fractional::new(v)),
- LitKind::Str(ref v) => Value::Str(v.into()),
+ LitKind::Str(ref v) => Value::Str(v.clone()),
})
}
}
@@ -239,7 +238,7 @@ impl Eval for DictExpr {
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
self.items()
- .map(|x| Ok((x.name().take().into(), x.expr().eval(ctx)?)))
+ .map(|x| Ok((x.name().take(), x.expr().eval(ctx)?)))
.collect()
}
}
@@ -401,7 +400,7 @@ impl Eval for CallArgs {
CallArg::Named(named) => {
items.push(Arg {
span,
- name: Some(named.name().take().into()),
+ name: Some(named.name().take()),
value: Spanned::new(named.expr().eval(ctx)?, named.expr().span()),
});
}
@@ -600,7 +599,7 @@ impl Eval for ForExpr {
match (key, value, iter) {
(None, v, Value::Str(string)) => {
- iter!(for (v => value) in string.iter());
+ iter!(for (v => value) in string.graphemes(true));
}
(None, v, Value::Array(array)) => {
iter!(for (v => value) in array.into_iter());
@@ -629,7 +628,7 @@ impl Eval for ImportExpr {
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
let path = self.path();
- let resolved = path.eval(ctx)?.cast::<Str>().at(path.span())?;
+ let resolved = path.eval(ctx)?.cast::<EcoString>().at(path.span())?;
let file = ctx.import(&resolved, path.span())?;
let module = &ctx.modules[&file];
@@ -659,7 +658,7 @@ impl Eval for IncludeExpr {
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
let path = self.path();
- let resolved = path.eval(ctx)?.cast::<Str>().at(path.span())?;
+ let resolved = path.eval(ctx)?.cast::<EcoString>().at(path.span())?;
let file = ctx.import(&resolved, path.span())?;
let module = &ctx.modules[&file];
Ok(Value::Template(module.template.clone()))
diff --git a/src/eval/ops.rs b/src/eval/ops.rs
index 732bfb14..e40fa78d 100644
--- a/src/eval/ops.rs
+++ b/src/eval/ops.rs
@@ -1,7 +1,9 @@
use std::cmp::Ordering;
+use std::convert::TryFrom;
use super::Value;
use crate::diag::StrResult;
+use crate::util::EcoString;
use Value::*;
/// Bail with a type mismatch error.
@@ -150,8 +152,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
(Fractional(a), Float(b)) => Fractional(a * b),
(Int(a), Fractional(b)) => Fractional(a as f64 * b),
- (Str(a), Int(b)) => Str(a.repeat(b)?),
- (Int(a), Str(b)) => Str(b.repeat(a)?),
+ (Str(a), Int(b)) => Str(repeat_str(a, b)?),
+ (Int(a), Str(b)) => Str(repeat_str(b, a)?),
(Array(a), Int(b)) => Array(a.repeat(b)?),
(Int(a), Array(b)) => Array(b.repeat(a)?),
(Template(a), Int(b)) => Template(a.repeat(b)?),
@@ -161,6 +163,16 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
})
}
+/// Repeat a string a number of times.
+fn repeat_str(string: EcoString, n: i64) -> StrResult<EcoString> {
+ let n = usize::try_from(n)
+ .ok()
+ .and_then(|n| string.len().checked_mul(n).map(|_| n))
+ .ok_or_else(|| format!("cannot repeat this string {} times", n))?;
+
+ Ok(string.repeat(n))
+}
+
/// Compute the quotient of two values.
pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
Ok(match (lhs, rhs) {
diff --git a/src/eval/str.rs b/src/eval/str.rs
deleted file mode 100644
index 800d1709..00000000
--- a/src/eval/str.rs
+++ /dev/null
@@ -1,164 +0,0 @@
-use std::borrow::Borrow;
-use std::convert::TryFrom;
-use std::fmt::{self, Debug, Formatter, Write};
-use std::ops::{Add, AddAssign, Deref};
-
-use unicode_segmentation::UnicodeSegmentation;
-
-use crate::diag::StrResult;
-use crate::util::EcoString;
-
-/// Create a new [`Str`] from a format string.
-macro_rules! format_str {
- ($($tts:tt)*) => {{
- use std::fmt::Write;
- let mut s = $crate::eval::Str::new();
- write!(s, $($tts)*).unwrap();
- s
- }};
-}
-
-/// A string value with inline storage and clone-on-write semantics.
-#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd)]
-pub struct Str(EcoString);
-
-impl Str {
- /// Create a new, empty string.
- pub fn new() -> Self {
- Self::default()
- }
-
- /// Whether the string is empty.
- pub fn is_empty(&self) -> bool {
- self.0.is_empty()
- }
-
- /// The length of the string in bytes.
- pub fn len(&self) -> i64 {
- self.0.len() as i64
- }
-
- /// Borrow this as a string slice.
- pub fn as_str(&self) -> &str {
- self.0.as_str()
- }
-
- /// Return an iterator over the grapheme clusters as strings.
- pub fn iter(&self) -> impl Iterator<Item = Str> + '_ {
- self.graphemes(true).map(Into::into)
- }
-
- /// Repeat this string `n` times.
- pub fn repeat(&self, n: i64) -> StrResult<Self> {
- let n = usize::try_from(n)
- .ok()
- .and_then(|n| self.0.len().checked_mul(n).map(|_| n))
- .ok_or_else(|| format!("cannot repeat this string {} times", n))?;
-
- Ok(self.0.repeat(n).into())
- }
-}
-
-impl Deref for Str {
- type Target = str;
-
- fn deref(&self) -> &str {
- self.0.deref()
- }
-}
-
-impl Debug for Str {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_char('"')?;
- for c in self.chars() {
- match c {
- '\\' => f.write_str(r"\\")?,
- '"' => f.write_str(r#"\""#)?,
- '\n' => f.write_str(r"\n")?,
- '\r' => f.write_str(r"\r")?,
- '\t' => f.write_str(r"\t")?,
- _ => f.write_char(c)?,
- }
- }
- f.write_char('"')
- }
-}
-
-impl Add for Str {
- type Output = Self;
-
- fn add(mut self, rhs: Self) -> Self::Output {
- self += rhs;
- self
- }
-}
-
-impl AddAssign for Str {
- fn add_assign(&mut self, rhs: Self) {
- self.0.push_str(rhs.as_str());
- }
-}
-
-impl Write for Str {
- fn write_str(&mut self, s: &str) -> fmt::Result {
- self.0.write_str(s)
- }
-
- fn write_char(&mut self, c: char) -> fmt::Result {
- self.0.write_char(c)
- }
-}
-
-impl AsRef<str> for Str {
- fn as_ref(&self) -> &str {
- self
- }
-}
-
-impl Borrow<str> for Str {
- fn borrow(&self) -> &str {
- self
- }
-}
-
-impl From<char> for Str {
- fn from(c: char) -> Self {
- Self(c.into())
- }
-}
-
-impl From<&str> for Str {
- fn from(s: &str) -> Self {
- Self(s.into())
- }
-}
-
-impl From<String> for Str {
- fn from(s: String) -> Self {
- Self(s.into())
- }
-}
-
-impl From<EcoString> for Str {
- fn from(s: EcoString) -> Self {
- Self(s)
- }
-}
-
-impl From<&EcoString> for Str {
- fn from(s: &EcoString) -> Self {
- Self(s.clone())
- }
-}
-
-impl From<Str> for EcoString {
- fn from(s: Str) -> Self {
- s.0
- }
-}
-
-impl From<&Str> for EcoString {
- fn from(s: &Str) -> Self {
- s.0.clone()
- }
-}
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 2622a1f0..18104638 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -5,7 +5,6 @@ use std::mem;
use std::ops::{Add, AddAssign};
use std::rc::Rc;
-use super::Str;
use crate::diag::StrResult;
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size};
use crate::layout::{BlockLevel, BlockNode, InlineLevel, InlineNode, PageNode};
@@ -214,20 +213,20 @@ impl AddAssign for Template {
}
}
-impl Add<Str> for Template {
+impl Add<EcoString> for Template {
type Output = Self;
- fn add(mut self, rhs: Str) -> Self::Output {
- Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into()));
+ fn add(mut self, rhs: EcoString) -> Self::Output {
+ Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs));
self
}
}
-impl Add<Template> for Str {
+impl Add<Template> for EcoString {
type Output = Template;
fn add(self, mut rhs: Template) -> Self::Output {
- Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into()));
+ Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self));
rhs
}
}
@@ -491,7 +490,7 @@ impl ParBuilder {
self.children.last_mut()
{
if prev_align == curr_align && Rc::ptr_eq(prev_props, curr_props) {
- prev_text.push_str(&curr_text);
+ prev_text.push_str(curr_text);
return;
}
}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 804f8d55..0fcc4bfc 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -3,7 +3,7 @@ use std::cmp::Ordering;
use std::fmt::{self, Debug, Formatter};
use std::rc::Rc;
-use super::{ops, Array, Dict, Function, Str, Template};
+use super::{ops, Array, Dict, Function, Template};
use crate::diag::StrResult;
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
use crate::syntax::Spanned;
@@ -35,7 +35,7 @@ pub enum Value {
/// A color value: `#f79143ff`.
Color(Color),
/// A string: `"string"`.
- Str(Str),
+ Str(EcoString),
/// An array of values: `(1, "hi", 12cm)`.
Array(Array),
/// A dictionary value: `(color: #f79143, pattern: dashed)`.
@@ -63,7 +63,7 @@ impl Value {
Self::Linear(_) => Linear::TYPE_NAME,
Self::Fractional(_) => Fractional::TYPE_NAME,
Self::Color(_) => Color::TYPE_NAME,
- Self::Str(_) => Str::TYPE_NAME,
+ Self::Str(_) => EcoString::TYPE_NAME,
Self::Array(_) => Array::TYPE_NAME,
Self::Dict(_) => Dict::TYPE_NAME,
Self::Template(_) => Template::TYPE_NAME,
@@ -81,8 +81,8 @@ impl Value {
}
/// Return the debug representation of the value.
- pub fn repr(&self) -> Str {
- format_str!("{:?}", self)
+ pub fn repr(&self) -> EcoString {
+ format_eco!("{:?}", self)
}
/// Join the value with another value.
@@ -163,17 +163,12 @@ impl From<String> for Value {
}
}
-impl From<EcoString> for Value {
- fn from(v: EcoString) -> Self {
- Self::Str(v.into())
- }
-}
-
impl From<Dynamic> for Value {
fn from(v: Dynamic) -> Self {
Self::Dyn(v)
}
}
+
/// A dynamic value.
#[derive(Clone)]
pub struct Dynamic(Rc<dyn Bounds>);
@@ -338,7 +333,8 @@ macro_rules! dynamic {
}
castable! {
- $type: <Self as $crate::eval::Type>::TYPE_NAME,
+ $type,
+ Expected: <Self as $crate::eval::Type>::TYPE_NAME,
$($tts)*
@this: Self => this.clone(),
}
@@ -354,8 +350,8 @@ macro_rules! dynamic {
/// Make a type castable from a value.
macro_rules! castable {
(
- $type:ty:
- $expected:expr,
+ $type:ty,
+ Expected: $expected:expr,
$($pattern:pat => $out:expr,)*
$(@$dyn_in:ident: $dyn_type:ty => $dyn_out:expr,)*
) => {
@@ -398,7 +394,7 @@ primitive! { Relative: "relative", Relative }
primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() }
primitive! { Fractional: "fractional", Fractional }
primitive! { Color: "color", Color }
-primitive! { Str: "string", Str }
+primitive! { EcoString: "string", Str }
primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
primitive! { Template: "template", Template }
diff --git a/src/eval/walk.rs b/src/eval/walk.rs
index aab32f40..6d5df5ba 100644
--- a/src/eval/walk.rs
+++ b/src/eval/walk.rs
@@ -1,12 +1,12 @@
use std::rc::Rc;
-use super::{Eval, EvalContext, Str, Template, Value};
+use super::{Eval, EvalContext, Template, Value};
use crate::diag::TypResult;
use crate::geom::Spec;
use crate::layout::BlockLevel;
use crate::library::{GridNode, ParChild, ParNode, TrackSizing};
use crate::syntax::ast::*;
-use crate::util::BoolExt;
+use crate::util::{BoolExt, EcoString};
/// Walk markup, filling the currently built template.
pub trait Walk {
@@ -39,8 +39,8 @@ impl Walk for MarkupNode {
Self::Enum(enum_) => enum_.walk(ctx)?,
Self::Expr(expr) => match expr.eval(ctx)? {
Value::None => {}
- Value::Int(v) => ctx.template.text(format_str!("{}", v)),
- Value::Float(v) => ctx.template.text(format_str!("{}", v)),
+ Value::Int(v) => ctx.template.text(format_eco!("{}", v)),
+ Value::Float(v) => ctx.template.text(format_eco!("{}", v)),
Value::Str(v) => ctx.template.text(v),
Value::Template(v) => ctx.template += v,
// For values which can't be shown "naturally", we print the
@@ -108,7 +108,7 @@ impl Walk for HeadingNode {
impl Walk for ListNode {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
let body = self.body().eval(ctx)?;
- walk_item(ctx, Str::from('•'), body);
+ walk_item(ctx, EcoString::from('•'), body);
Ok(())
}
}
@@ -116,19 +116,19 @@ impl Walk for ListNode {
impl Walk for EnumNode {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
let body = self.body().eval(ctx)?;
- let label = format_str!("{}.", self.number().unwrap_or(1));
+ let label = format_eco!("{}.", self.number().unwrap_or(1));
walk_item(ctx, label, body);
Ok(())
}
}
-fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
+fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) {
ctx.template += Template::from_block(move |style| {
let label = ParNode {
dir: style.dir,
leading: style.leading(),
children: vec![ParChild::Text(
- (&label).into(),
+ label.clone(),
style.aligns.inline,
Rc::clone(&style.text),
)],
@@ -138,7 +138,7 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
GridNode {
tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]),
gutter: Spec::new(vec![TrackSizing::Linear(spacing.into())], vec![]),
- children: vec![label.pack(), body.to_stack(&style).pack()],
+ children: vec![label.pack(), body.to_stack(style).pack()],
}
});
}