mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Expose content representation more
This commit is contained in:
parent
62f35602a8
commit
a9fdff244a
@ -53,7 +53,7 @@ pub fn repr(
|
|||||||
/// Fail with an error.
|
/// Fail with an error.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// The code below produces the error `panicked at: "this is wrong"`.
|
/// The code below produces the error `panicked with: "this is wrong"`.
|
||||||
/// ```typ
|
/// ```typ
|
||||||
/// #panic("this is wrong")
|
/// #panic("this is wrong")
|
||||||
/// ```
|
/// ```
|
||||||
@ -63,14 +63,21 @@ pub fn repr(
|
|||||||
/// Returns:
|
/// Returns:
|
||||||
#[func]
|
#[func]
|
||||||
pub fn panic(
|
pub fn panic(
|
||||||
/// The value (or message) to panic with.
|
/// The values to panic with.
|
||||||
#[default]
|
#[variadic]
|
||||||
payload: Option<Value>,
|
values: Vec<Value>,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
match payload {
|
let mut msg = EcoString::from("panicked");
|
||||||
Some(v) => bail!(args.span, "panicked with: {}", v.repr()),
|
if !values.is_empty() {
|
||||||
None => bail!(args.span, "panicked"),
|
msg.push_str(" with: ");
|
||||||
|
for (i, value) in values.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
msg.push_str(", ");
|
||||||
}
|
}
|
||||||
|
msg.push_str(&value.repr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bail!(args.span, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that a condition is fulfilled.
|
/// Ensure that a condition is fulfilled.
|
||||||
|
@ -58,7 +58,7 @@ pub struct AlignNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for AlignNode {
|
impl Show for AlignNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.body()
|
.body()
|
||||||
.styled(Self::set_alignment(self.alignment(styles).map(Some))))
|
.styled(Self::set_alignment(self.alignment(styles).map(Some))))
|
||||||
|
@ -32,7 +32,7 @@ impl Layout for FlowNode {
|
|||||||
let outer = styles;
|
let outer = styles;
|
||||||
let mut styles = outer;
|
let mut styles = outer;
|
||||||
if let Some(node) = child.to::<StyledNode>() {
|
if let Some(node) = child.to::<StyledNode>() {
|
||||||
map = node.map();
|
map = node.styles();
|
||||||
styles = outer.chain(&map);
|
styles = outer.chain(&map);
|
||||||
child = node.body();
|
child = node.body();
|
||||||
}
|
}
|
||||||
@ -48,15 +48,15 @@ impl Layout for FlowNode {
|
|||||||
|| child.is::<ImageNode>()
|
|| child.is::<ImageNode>()
|
||||||
{
|
{
|
||||||
layouter.layout_single(vt, &child, styles)?;
|
layouter.layout_single(vt, &child, styles)?;
|
||||||
} else if child.has::<dyn Layout>() {
|
} else if child.can::<dyn Layout>() {
|
||||||
layouter.layout_multiple(vt, &child, styles)?;
|
layouter.layout_multiple(vt, &child, styles)?;
|
||||||
} else if child.is::<ColbreakNode>() {
|
} else if child.is::<ColbreakNode>() {
|
||||||
if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some()
|
if !layouter.regions.backlog.is_empty() || layouter.regions.last.is_some()
|
||||||
{
|
{
|
||||||
layouter.finish_region();
|
layouter.finish_region();
|
||||||
}
|
}
|
||||||
} else if let Some(span) = child.span() {
|
} else {
|
||||||
bail!(span, "unexpected flow child");
|
bail!(child.span(), "unexpected flow child");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +207,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
let aligns = if let Some(align) = block.to::<AlignNode>() {
|
let aligns = if let Some(align) = block.to::<AlignNode>() {
|
||||||
align.alignment(styles)
|
align.alignment(styles)
|
||||||
} else if let Some(styled) = block.to::<StyledNode>() {
|
} else if let Some(styled) = block.to::<StyledNode>() {
|
||||||
AlignNode::alignment_in(styles.chain(&styled.map()))
|
AlignNode::alignment_in(styles.chain(&styled.styles()))
|
||||||
} else {
|
} else {
|
||||||
AlignNode::alignment_in(styles)
|
AlignNode::alignment_in(styles)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ pub struct HideNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for HideNode {
|
impl Show for HideNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(MetaNode::set_data(vec![Meta::Hidden])))
|
Ok(self.body().styled(MetaNode::set_data(vec![Meta::Hidden])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ fn realize_root<'a>(
|
|||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<(Content, StyleChain<'a>)> {
|
) -> SourceResult<(Content, StyleChain<'a>)> {
|
||||||
if content.has::<dyn LayoutRoot>() && !applicable(content, styles) {
|
if content.can::<dyn LayoutRoot>() && !applicable(content, styles) {
|
||||||
return Ok((content.clone(), styles));
|
return Ok((content.clone(), styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ fn realize_block<'a>(
|
|||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<(Content, StyleChain<'a>)> {
|
) -> SourceResult<(Content, StyleChain<'a>)> {
|
||||||
if content.has::<dyn Layout>()
|
if content.can::<dyn Layout>()
|
||||||
&& !content.is::<RectNode>()
|
&& !content.is::<RectNode>()
|
||||||
&& !content.is::<SquareNode>()
|
&& !content.is::<SquareNode>()
|
||||||
&& !content.is::<EllipseNode>()
|
&& !content.is::<EllipseNode>()
|
||||||
@ -227,16 +227,20 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
|||||||
mut content: &'a Content,
|
mut content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
if content.has::<dyn LayoutMath>() && !content.is::<FormulaNode>() {
|
if content.can::<dyn LayoutMath>() && !content.is::<FormulaNode>() {
|
||||||
content =
|
content =
|
||||||
self.scratch.content.alloc(FormulaNode::new(content.clone()).pack());
|
self.scratch.content.alloc(FormulaNode::new(content.clone()).pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare only if this is the first application for this node.
|
// Prepare only if this is the first application for this node.
|
||||||
if let Some(node) = content.with::<dyn Prepare>() {
|
if content.can::<dyn Prepare>() {
|
||||||
if !content.is_prepared() {
|
if !content.is_prepared() {
|
||||||
let prepared =
|
let prepared = content
|
||||||
node.prepare(self.vt, content.clone().prepared(), styles)?;
|
.clone()
|
||||||
|
.prepared()
|
||||||
|
.with::<dyn Prepare>()
|
||||||
|
.unwrap()
|
||||||
|
.prepare(self.vt, styles)?;
|
||||||
let stored = self.scratch.content.alloc(prepared);
|
let stored = self.scratch.content.alloc(prepared);
|
||||||
return self.accept(stored, styles);
|
return self.accept(stored, styles);
|
||||||
}
|
}
|
||||||
@ -291,11 +295,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(span) = content.span() {
|
bail!(content.span(), "not allowed here");
|
||||||
bail!(span, "not allowed here");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled(
|
fn styled(
|
||||||
@ -303,7 +303,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
|||||||
styled: &'a StyledNode,
|
styled: &'a StyledNode,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let map = self.scratch.maps.alloc(styled.map());
|
let map = self.scratch.maps.alloc(styled.styles());
|
||||||
let stored = self.scratch.styles.alloc(styles);
|
let stored = self.scratch.styles.alloc(styles);
|
||||||
let content = self.scratch.content.alloc(styled.body());
|
let content = self.scratch.content.alloc(styled.body());
|
||||||
let styles = stored.chain(map);
|
let styles = stored.chain(map);
|
||||||
@ -436,7 +436,7 @@ impl<'a> FlowBuilder<'a> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.has::<dyn Layout>() || content.is::<ParNode>() {
|
if content.can::<dyn Layout>() || content.is::<ParNode>() {
|
||||||
let is_tight_list = if let Some(node) = content.to::<ListNode>() {
|
let is_tight_list = if let Some(node) = content.to::<ListNode>() {
|
||||||
node.tight(styles)
|
node.tight(styles)
|
||||||
} else if let Some(node) = content.to::<EnumNode>() {
|
} else if let Some(node) = content.to::<EnumNode>() {
|
||||||
|
@ -519,7 +519,7 @@ fn collect<'a>(
|
|||||||
let mut styles = *styles;
|
let mut styles = *styles;
|
||||||
if let Some(node) = child.to::<StyledNode>() {
|
if let Some(node) = child.to::<StyledNode>() {
|
||||||
child = Box::leak(Box::new(node.body()));
|
child = Box::leak(Box::new(node.body()));
|
||||||
styles = outer.chain(Box::leak(Box::new(node.map())));
|
styles = outer.chain(Box::leak(Box::new(node.styles())));
|
||||||
}
|
}
|
||||||
|
|
||||||
let segment = if child.is::<SpaceNode>() {
|
let segment = if child.is::<SpaceNode>() {
|
||||||
@ -570,10 +570,8 @@ fn collect<'a>(
|
|||||||
let frac = node.width(styles).is_fractional();
|
let frac = node.width(styles).is_fractional();
|
||||||
full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE });
|
full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE });
|
||||||
Segment::Box(node, frac)
|
Segment::Box(node, frac)
|
||||||
} else if let Some(span) = child.span() {
|
|
||||||
bail!(span, "unexpected document child");
|
|
||||||
} else {
|
} else {
|
||||||
continue;
|
bail!(child.span(), "unexpected paragraph child");
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(last) = full.chars().last() {
|
if let Some(last) = full.chars().last() {
|
||||||
@ -730,7 +728,7 @@ fn shared_get<'a, T: PartialEq>(
|
|||||||
children
|
children
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|child| child.to::<StyledNode>())
|
.filter_map(|child| child.to::<StyledNode>())
|
||||||
.all(|node| getter(styles.chain(&node.map())) == value)
|
.all(|node| getter(styles.chain(&node.styles())) == value)
|
||||||
.then(|| value)
|
.then(|| value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
let aligns = if let Some(align) = block.to::<AlignNode>() {
|
let aligns = if let Some(align) = block.to::<AlignNode>() {
|
||||||
align.alignment(styles)
|
align.alignment(styles)
|
||||||
} else if let Some(styled) = block.to::<StyledNode>() {
|
} else if let Some(styled) = block.to::<StyledNode>() {
|
||||||
AlignNode::alignment_in(styles.chain(&styled.map()))
|
AlignNode::alignment_in(styles.chain(&styled.styles()))
|
||||||
} else {
|
} else {
|
||||||
AlignNode::alignment_in(styles)
|
AlignNode::alignment_in(styles)
|
||||||
}
|
}
|
||||||
|
@ -29,65 +29,65 @@ fn global(math: Module, calc: Module) -> Module {
|
|||||||
let mut global = Scope::deduplicating();
|
let mut global = Scope::deduplicating();
|
||||||
|
|
||||||
// Text.
|
// Text.
|
||||||
global.define("text", text::TextNode::func());
|
global.define("text", text::TextNode::id());
|
||||||
global.define("linebreak", text::LinebreakNode::func());
|
global.define("linebreak", text::LinebreakNode::id());
|
||||||
global.define("smartquote", text::SmartQuoteNode::func());
|
global.define("smartquote", text::SmartQuoteNode::id());
|
||||||
global.define("strong", text::StrongNode::func());
|
global.define("strong", text::StrongNode::id());
|
||||||
global.define("emph", text::EmphNode::func());
|
global.define("emph", text::EmphNode::id());
|
||||||
global.define("lower", text::lower);
|
global.define("lower", text::lower);
|
||||||
global.define("upper", text::upper);
|
global.define("upper", text::upper);
|
||||||
global.define("smallcaps", text::smallcaps);
|
global.define("smallcaps", text::smallcaps);
|
||||||
global.define("sub", text::SubNode::func());
|
global.define("sub", text::SubNode::id());
|
||||||
global.define("super", text::SuperNode::func());
|
global.define("super", text::SuperNode::id());
|
||||||
global.define("underline", text::UnderlineNode::func());
|
global.define("underline", text::UnderlineNode::id());
|
||||||
global.define("strike", text::StrikeNode::func());
|
global.define("strike", text::StrikeNode::id());
|
||||||
global.define("overline", text::OverlineNode::func());
|
global.define("overline", text::OverlineNode::id());
|
||||||
global.define("raw", text::RawNode::func());
|
global.define("raw", text::RawNode::id());
|
||||||
global.define("lorem", text::lorem);
|
global.define("lorem", text::lorem);
|
||||||
|
|
||||||
// Math.
|
// Math.
|
||||||
global.define("math", math);
|
global.define("math", math);
|
||||||
|
|
||||||
// Layout.
|
// Layout.
|
||||||
global.define("page", layout::PageNode::func());
|
global.define("page", layout::PageNode::id());
|
||||||
global.define("pagebreak", layout::PagebreakNode::func());
|
global.define("pagebreak", layout::PagebreakNode::id());
|
||||||
global.define("v", layout::VNode::func());
|
global.define("v", layout::VNode::id());
|
||||||
global.define("par", layout::ParNode::func());
|
global.define("par", layout::ParNode::id());
|
||||||
global.define("parbreak", layout::ParbreakNode::func());
|
global.define("parbreak", layout::ParbreakNode::id());
|
||||||
global.define("h", layout::HNode::func());
|
global.define("h", layout::HNode::id());
|
||||||
global.define("box", layout::BoxNode::func());
|
global.define("box", layout::BoxNode::id());
|
||||||
global.define("block", layout::BlockNode::func());
|
global.define("block", layout::BlockNode::id());
|
||||||
global.define("list", layout::ListNode::func());
|
global.define("list", layout::ListNode::id());
|
||||||
global.define("enum", layout::EnumNode::func());
|
global.define("enum", layout::EnumNode::id());
|
||||||
global.define("terms", layout::TermsNode::func());
|
global.define("terms", layout::TermsNode::id());
|
||||||
global.define("table", layout::TableNode::func());
|
global.define("table", layout::TableNode::id());
|
||||||
global.define("stack", layout::StackNode::func());
|
global.define("stack", layout::StackNode::id());
|
||||||
global.define("grid", layout::GridNode::func());
|
global.define("grid", layout::GridNode::id());
|
||||||
global.define("columns", layout::ColumnsNode::func());
|
global.define("columns", layout::ColumnsNode::id());
|
||||||
global.define("colbreak", layout::ColbreakNode::func());
|
global.define("colbreak", layout::ColbreakNode::id());
|
||||||
global.define("place", layout::PlaceNode::func());
|
global.define("place", layout::PlaceNode::id());
|
||||||
global.define("align", layout::AlignNode::func());
|
global.define("align", layout::AlignNode::id());
|
||||||
global.define("pad", layout::PadNode::func());
|
global.define("pad", layout::PadNode::id());
|
||||||
global.define("repeat", layout::RepeatNode::func());
|
global.define("repeat", layout::RepeatNode::id());
|
||||||
global.define("move", layout::MoveNode::func());
|
global.define("move", layout::MoveNode::id());
|
||||||
global.define("scale", layout::ScaleNode::func());
|
global.define("scale", layout::ScaleNode::id());
|
||||||
global.define("rotate", layout::RotateNode::func());
|
global.define("rotate", layout::RotateNode::id());
|
||||||
global.define("hide", layout::HideNode::func());
|
global.define("hide", layout::HideNode::id());
|
||||||
|
|
||||||
// Visualize.
|
// Visualize.
|
||||||
global.define("image", visualize::ImageNode::func());
|
global.define("image", visualize::ImageNode::id());
|
||||||
global.define("line", visualize::LineNode::func());
|
global.define("line", visualize::LineNode::id());
|
||||||
global.define("rect", visualize::RectNode::func());
|
global.define("rect", visualize::RectNode::id());
|
||||||
global.define("square", visualize::SquareNode::func());
|
global.define("square", visualize::SquareNode::id());
|
||||||
global.define("ellipse", visualize::EllipseNode::func());
|
global.define("ellipse", visualize::EllipseNode::id());
|
||||||
global.define("circle", visualize::CircleNode::func());
|
global.define("circle", visualize::CircleNode::id());
|
||||||
|
|
||||||
// Meta.
|
// Meta.
|
||||||
global.define("document", meta::DocumentNode::func());
|
global.define("document", meta::DocumentNode::id());
|
||||||
global.define("ref", meta::RefNode::func());
|
global.define("ref", meta::RefNode::id());
|
||||||
global.define("link", meta::LinkNode::func());
|
global.define("link", meta::LinkNode::id());
|
||||||
global.define("outline", meta::OutlineNode::func());
|
global.define("outline", meta::OutlineNode::id());
|
||||||
global.define("heading", meta::HeadingNode::func());
|
global.define("heading", meta::HeadingNode::id());
|
||||||
global.define("numbering", meta::numbering);
|
global.define("numbering", meta::numbering);
|
||||||
|
|
||||||
// Symbols.
|
// Symbols.
|
||||||
|
@ -47,52 +47,52 @@ use crate::text::{
|
|||||||
/// Create a module with all math definitions.
|
/// Create a module with all math definitions.
|
||||||
pub fn module() -> Module {
|
pub fn module() -> Module {
|
||||||
let mut math = Scope::deduplicating();
|
let mut math = Scope::deduplicating();
|
||||||
math.define("formula", FormulaNode::func());
|
math.define("formula", FormulaNode::id());
|
||||||
math.define("text", TextNode::func());
|
math.define("text", TextNode::id());
|
||||||
|
|
||||||
// Grouping.
|
// Grouping.
|
||||||
math.define("lr", LrNode::func());
|
math.define("lr", LrNode::id());
|
||||||
math.define("abs", abs);
|
math.define("abs", abs);
|
||||||
math.define("norm", norm);
|
math.define("norm", norm);
|
||||||
math.define("floor", floor);
|
math.define("floor", floor);
|
||||||
math.define("ceil", ceil);
|
math.define("ceil", ceil);
|
||||||
|
|
||||||
// Attachments and accents.
|
// Attachments and accents.
|
||||||
math.define("attach", AttachNode::func());
|
math.define("attach", AttachNode::id());
|
||||||
math.define("scripts", ScriptsNode::func());
|
math.define("scripts", ScriptsNode::id());
|
||||||
math.define("limits", LimitsNode::func());
|
math.define("limits", LimitsNode::id());
|
||||||
math.define("accent", AccentNode::func());
|
math.define("accent", AccentNode::id());
|
||||||
math.define("underline", UnderlineNode::func());
|
math.define("underline", UnderlineNode::id());
|
||||||
math.define("overline", OverlineNode::func());
|
math.define("overline", OverlineNode::id());
|
||||||
math.define("underbrace", UnderbraceNode::func());
|
math.define("underbrace", UnderbraceNode::id());
|
||||||
math.define("overbrace", OverbraceNode::func());
|
math.define("overbrace", OverbraceNode::id());
|
||||||
math.define("underbracket", UnderbracketNode::func());
|
math.define("underbracket", UnderbracketNode::id());
|
||||||
math.define("overbracket", OverbracketNode::func());
|
math.define("overbracket", OverbracketNode::id());
|
||||||
|
|
||||||
// Fractions and matrix-likes.
|
// Fractions and matrix-likes.
|
||||||
math.define("frac", FracNode::func());
|
math.define("frac", FracNode::id());
|
||||||
math.define("binom", BinomNode::func());
|
math.define("binom", BinomNode::id());
|
||||||
math.define("vec", VecNode::func());
|
math.define("vec", VecNode::id());
|
||||||
math.define("mat", MatNode::func());
|
math.define("mat", MatNode::id());
|
||||||
math.define("cases", CasesNode::func());
|
math.define("cases", CasesNode::id());
|
||||||
|
|
||||||
// Roots.
|
// Roots.
|
||||||
math.define("sqrt", SqrtNode::func());
|
math.define("sqrt", SqrtNode::id());
|
||||||
math.define("root", RootNode::func());
|
math.define("root", RootNode::id());
|
||||||
|
|
||||||
// Styles.
|
// Styles.
|
||||||
math.define("upright", UprightNode::func());
|
math.define("upright", UprightNode::id());
|
||||||
math.define("bold", BoldNode::func());
|
math.define("bold", BoldNode::id());
|
||||||
math.define("italic", ItalicNode::func());
|
math.define("italic", ItalicNode::id());
|
||||||
math.define("serif", SerifNode::func());
|
math.define("serif", SerifNode::id());
|
||||||
math.define("sans", SansNode::func());
|
math.define("sans", SansNode::id());
|
||||||
math.define("cal", CalNode::func());
|
math.define("cal", CalNode::id());
|
||||||
math.define("frak", FrakNode::func());
|
math.define("frak", FrakNode::id());
|
||||||
math.define("mono", MonoNode::func());
|
math.define("mono", MonoNode::id());
|
||||||
math.define("bb", BbNode::func());
|
math.define("bb", BbNode::id());
|
||||||
|
|
||||||
// Text operators.
|
// Text operators.
|
||||||
math.define("op", OpNode::func());
|
math.define("op", OpNode::id());
|
||||||
op::define(&mut math);
|
op::define(&mut math);
|
||||||
|
|
||||||
// Spacings.
|
// Spacings.
|
||||||
@ -144,7 +144,7 @@ pub struct FormulaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for FormulaNode {
|
impl Show for FormulaNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
|
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
|
||||||
if self.block(styles) {
|
if self.block(styles) {
|
||||||
realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
|
realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
|
||||||
@ -183,10 +183,7 @@ impl Layout for FormulaNode {
|
|||||||
Some(font)
|
Some(font)
|
||||||
})
|
})
|
||||||
else {
|
else {
|
||||||
if let Some(span) = self.span() {
|
bail!(self.span(), "current font does not support math");
|
||||||
bail!(span, "current font does not support math");
|
|
||||||
}
|
|
||||||
return Ok(Fragment::frame(Frame::new(Size::zero())))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ctx = MathContext::new(vt, styles, regions, &font, block);
|
let mut ctx = MathContext::new(vt, styles, regions, &font, block);
|
||||||
@ -228,7 +225,7 @@ impl LayoutMath for Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(styled) = self.to::<StyledNode>() {
|
if let Some(styled) = self.to::<StyledNode>() {
|
||||||
let map = styled.map();
|
let map = styled.styles();
|
||||||
if TextNode::font_in(ctx.styles().chain(&map))
|
if TextNode::font_in(ctx.styles().chain(&map))
|
||||||
!= TextNode::font_in(ctx.styles())
|
!= TextNode::font_in(ctx.styles())
|
||||||
{
|
{
|
||||||
|
@ -39,7 +39,7 @@ impl LayoutRoot for DocumentNode {
|
|||||||
let outer = styles;
|
let outer = styles;
|
||||||
let mut styles = outer;
|
let mut styles = outer;
|
||||||
if let Some(node) = child.to::<StyledNode>() {
|
if let Some(node) = child.to::<StyledNode>() {
|
||||||
map = node.map();
|
map = node.styles();
|
||||||
styles = outer.chain(&map);
|
styles = outer.chain(&map);
|
||||||
child = node.body();
|
child = node.body();
|
||||||
}
|
}
|
||||||
@ -48,8 +48,8 @@ impl LayoutRoot for DocumentNode {
|
|||||||
let number = 1 + pages.len();
|
let number = 1 + pages.len();
|
||||||
let fragment = page.layout(vt, number, styles)?;
|
let fragment = page.layout(vt, number, styles)?;
|
||||||
pages.extend(fragment);
|
pages.extend(fragment);
|
||||||
} else if let Some(span) = child.span() {
|
} else {
|
||||||
bail!(span, "unexpected document child");
|
bail!(child.span(), "unexpected document child");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,13 +79,8 @@ pub struct HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Prepare for HeadingNode {
|
impl Prepare for HeadingNode {
|
||||||
fn prepare(
|
fn prepare(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
&self,
|
let my_id = vt.identify(self);
|
||||||
vt: &mut Vt,
|
|
||||||
mut this: Content,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
let my_id = vt.identify(&this);
|
|
||||||
|
|
||||||
let mut counter = HeadingCounter::new();
|
let mut counter = HeadingCounter::new();
|
||||||
for (node_id, node) in vt.locate(Selector::node::<HeadingNode>()) {
|
for (node_id, node) in vt.locate(Selector::node::<HeadingNode>()) {
|
||||||
@ -105,18 +100,18 @@ impl Prepare for HeadingNode {
|
|||||||
numbers = numbering.apply(vt.world(), counter.advance(self))?;
|
numbers = numbering.apply(vt.world(), counter.advance(self))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.push_field("outlined", Value::Bool(self.outlined(styles)));
|
let mut node = self.clone().pack();
|
||||||
this.push_field("numbers", numbers);
|
node.push_field("outlined", Value::Bool(self.outlined(styles)));
|
||||||
|
node.push_field("numbers", numbers);
|
||||||
let meta = Meta::Node(my_id, this.clone());
|
let meta = Meta::Node(my_id, node.clone());
|
||||||
Ok(this.styled(MetaNode::set_data(vec![meta])))
|
Ok(node.styled(MetaNode::set_data(vec![meta])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for HeadingNode {
|
impl Show for HeadingNode {
|
||||||
fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
let mut realized = self.body();
|
let mut realized = self.body();
|
||||||
let numbers = this.field("numbers").unwrap();
|
let numbers = self.0.field("numbers").unwrap();
|
||||||
if *numbers != Value::None {
|
if *numbers != Value::None {
|
||||||
realized = numbers.clone().display()
|
realized = numbers.clone().display()
|
||||||
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
|
+ HNode::new(Em::new(0.3).into()).with_weak(true).pack()
|
||||||
|
@ -78,7 +78,7 @@ impl LinkNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for LinkNode {
|
impl Show for LinkNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body())
|
Ok(self.body())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,12 +70,7 @@ pub struct OutlineNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Prepare for OutlineNode {
|
impl Prepare for OutlineNode {
|
||||||
fn prepare(
|
fn prepare(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
mut this: Content,
|
|
||||||
_: StyleChain,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
let headings = vt
|
let headings = vt
|
||||||
.locate(Selector::node::<HeadingNode>())
|
.locate(Selector::node::<HeadingNode>())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -84,18 +79,14 @@ impl Prepare for OutlineNode {
|
|||||||
.map(|node| Value::Content(node.clone()))
|
.map(|node| Value::Content(node.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
this.push_field("headings", Value::Array(Array::from_vec(headings)));
|
let mut node = self.clone().pack();
|
||||||
Ok(this)
|
node.push_field("headings", Value::Array(Array::from_vec(headings)));
|
||||||
|
Ok(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for OutlineNode {
|
impl Show for OutlineNode {
|
||||||
fn show(
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
_: &Content,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
let mut seq = vec![ParbreakNode::new().pack()];
|
let mut seq = vec![ParbreakNode::new().pack()];
|
||||||
if let Some(title) = self.title(styles) {
|
if let Some(title) = self.title(styles) {
|
||||||
let title = title.clone().unwrap_or_else(|| {
|
let title = title.clone().unwrap_or_else(|| {
|
||||||
|
@ -25,7 +25,7 @@ pub struct RefNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for RefNode {
|
impl Show for RefNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(TextNode::packed(eco_format!("@{}", self.target())))
|
Ok(TextNode::packed(eco_format!("@{}", self.target())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ pub struct UnderlineNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for UnderlineNode {
|
impl Show for UnderlineNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||||
line: DecoLine::Underline,
|
line: DecoLine::Underline,
|
||||||
stroke: self.stroke(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
@ -145,7 +145,7 @@ pub struct OverlineNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for OverlineNode {
|
impl Show for OverlineNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||||
line: DecoLine::Overline,
|
line: DecoLine::Overline,
|
||||||
stroke: self.stroke(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
@ -209,7 +209,7 @@ pub struct StrikeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for StrikeNode {
|
impl Show for StrikeNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||||
line: DecoLine::Strikethrough,
|
line: DecoLine::Strikethrough,
|
||||||
stroke: self.stroke(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
|
@ -99,7 +99,7 @@ pub struct StrongNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for StrongNode {
|
impl Show for StrongNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_delta(Delta(self.delta(styles)))))
|
Ok(self.body().styled(TextNode::set_delta(Delta(self.delta(styles)))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ pub struct EmphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for EmphNode {
|
impl Show for EmphNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_emph(Toggle)))
|
Ok(self.body().styled(TextNode::set_emph(Toggle)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,19 +104,15 @@ pub struct RawNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Prepare for RawNode {
|
impl Prepare for RawNode {
|
||||||
fn prepare(
|
fn prepare(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
&self,
|
let mut node = self.clone().pack();
|
||||||
_: &mut Vt,
|
node.push_field("lang", self.lang(styles).clone());
|
||||||
mut this: Content,
|
Ok(node)
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
this.push_field("lang", self.lang(styles).clone());
|
|
||||||
Ok(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for RawNode {
|
impl Show for RawNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let text = self.text();
|
let text = self.text();
|
||||||
let lang = self.lang(styles).as_ref().map(|s| s.to_lowercase());
|
let lang = self.lang(styles).as_ref().map(|s| s.to_lowercase());
|
||||||
let foreground = THEME
|
let foreground = THEME
|
||||||
@ -279,7 +275,6 @@ pub static THEME: Lazy<synt::Theme> = Lazy::new(|| synt::Theme {
|
|||||||
item("support.macro", Some("#16718d"), None),
|
item("support.macro", Some("#16718d"), None),
|
||||||
item("meta.annotation", Some("#301414"), None),
|
item("meta.annotation", Some("#301414"), None),
|
||||||
item("entity.other, meta.interpolation", Some("#8b41b1"), None),
|
item("entity.other, meta.interpolation", Some("#8b41b1"), None),
|
||||||
item("invalid", Some("#ff0000"), None),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -47,12 +47,7 @@ pub struct SubNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for SubNode {
|
impl Show for SubNode {
|
||||||
fn show(
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
_: &Content,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
let body = self.body();
|
let body = self.body();
|
||||||
let mut transformed = None;
|
let mut transformed = None;
|
||||||
if self.typographic(styles) {
|
if self.typographic(styles) {
|
||||||
@ -114,12 +109,7 @@ pub struct SuperNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for SuperNode {
|
impl Show for SuperNode {
|
||||||
fn show(
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
_: &Content,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
let body = self.body();
|
let body = self.body();
|
||||||
let mut transformed = None;
|
let mut transformed = None;
|
||||||
if self.typographic(styles) {
|
if self.typographic(styles) {
|
||||||
|
@ -186,7 +186,7 @@ fn create(node: &Node) -> TokenStream {
|
|||||||
#(#field_style_methods)*
|
#(#field_style_methods)*
|
||||||
|
|
||||||
/// The node's span.
|
/// The node's span.
|
||||||
pub fn span(&self) -> Option<::typst::syntax::Span> {
|
pub fn span(&self) -> ::typst::syntax::Span {
|
||||||
self.0.span()
|
self.0.span()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,7 +228,7 @@ fn create_field_method(field: &Field) -> TokenStream {
|
|||||||
quote! {
|
quote! {
|
||||||
#[doc = #docs]
|
#[doc = #docs]
|
||||||
#vis fn #ident(&self) -> #output {
|
#vis fn #ident(&self) -> #output {
|
||||||
self.0.cast_required_field(#name)
|
self.0.field(#name).unwrap().clone().cast().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -319,19 +319,8 @@ fn create_node_impl(node: &Node) -> TokenStream {
|
|||||||
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
|
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
|
||||||
name: #name,
|
name: #name,
|
||||||
vtable: #vtable_func,
|
vtable: #vtable_func,
|
||||||
};
|
construct: <#ident as ::typst::model::Construct>::construct,
|
||||||
::typst::model::NodeId::from_meta(&META)
|
set: <#ident as ::typst::model::Set>::set,
|
||||||
}
|
|
||||||
|
|
||||||
fn pack(self) -> ::typst::model::Content {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn func() -> ::typst::eval::NodeFunc {
|
|
||||||
::typst::eval::NodeFunc {
|
|
||||||
id: Self::id(),
|
|
||||||
construct: <Self as ::typst::model::Construct>::construct,
|
|
||||||
set: <Self as ::typst::model::Set>::set,
|
|
||||||
info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo {
|
info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo {
|
||||||
name: #name,
|
name: #name,
|
||||||
display: #display,
|
display: #display,
|
||||||
@ -340,7 +329,12 @@ fn create_node_impl(node: &Node) -> TokenStream {
|
|||||||
returns: ::std::vec!["content"],
|
returns: ::std::vec!["content"],
|
||||||
category: #category,
|
category: #category,
|
||||||
}),
|
}),
|
||||||
|
};
|
||||||
|
::typst::model::NodeId(&META)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pack(self) -> ::typst::model::Content {
|
||||||
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use ecow::EcoVec;
|
use ecow::{eco_format, EcoVec};
|
||||||
|
|
||||||
use super::{Array, Cast, Dict, Str, Value};
|
use super::{Array, Cast, Dict, Str, Value};
|
||||||
use crate::diag::{bail, At, SourceResult};
|
use crate::diag::{bail, At, SourceResult};
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
|
use crate::util::pretty_array;
|
||||||
|
|
||||||
/// Evaluated arguments to a function.
|
/// Evaluated arguments to a function.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
@ -171,14 +172,9 @@ impl Args {
|
|||||||
|
|
||||||
impl Debug for Args {
|
impl Debug for Args {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_char('(')?;
|
let pieces: Vec<_> =
|
||||||
for (i, arg) in self.items.iter().enumerate() {
|
self.items.iter().map(|arg| eco_format!("{arg:?}")).collect();
|
||||||
arg.fmt(f)?;
|
f.write_str(&pretty_array(&pieces, false))
|
||||||
if i + 1 < self.items.len() {
|
|
||||||
f.write_str(", ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.write_char(')')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString, EcoVec};
|
use ecow::{eco_format, EcoString, EcoVec};
|
||||||
|
|
||||||
use super::{ops, Args, Func, Value, Vm};
|
use super::{ops, Args, Func, Value, Vm};
|
||||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
|
use crate::util::pretty_array;
|
||||||
|
|
||||||
/// Create a new [`Array`] from values.
|
/// Create a new [`Array`] from values.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -147,7 +148,6 @@ impl Array {
|
|||||||
return Ok(Some(item.clone()));
|
return Ok(Some(item.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,6 +262,14 @@ impl Array {
|
|||||||
self.0.iter().cloned().rev().collect()
|
self.0.iter().cloned().rev().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Split all values in the array.
|
||||||
|
pub fn split(&self, at: Value) -> Array {
|
||||||
|
self.as_slice()
|
||||||
|
.split(|value| *value == at)
|
||||||
|
.map(|subslice| Value::Array(subslice.iter().cloned().collect()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Join all values in the array, optionally with separator and last
|
/// Join all values in the array, optionally with separator and last
|
||||||
/// separator (between the final two items).
|
/// separator (between the final two items).
|
||||||
pub fn join(&self, sep: Option<Value>, mut last: Option<Value>) -> StrResult<Value> {
|
pub fn join(&self, sep: Option<Value>, mut last: Option<Value>) -> StrResult<Value> {
|
||||||
@ -332,31 +340,10 @@ impl Array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The out of bounds access error message.
|
|
||||||
#[cold]
|
|
||||||
fn out_of_bounds(index: i64, len: i64) -> EcoString {
|
|
||||||
eco_format!("array index out of bounds (index: {}, len: {})", index, len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error message when the array is empty.
|
|
||||||
#[cold]
|
|
||||||
fn array_is_empty() -> EcoString {
|
|
||||||
"array is empty".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Array {
|
impl Debug for Array {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_char('(')?;
|
let pieces: Vec<_> = self.iter().map(|value| eco_format!("{value:?}")).collect();
|
||||||
for (i, value) in self.iter().enumerate() {
|
f.write_str(&pretty_array(&pieces, self.len() == 1))
|
||||||
value.fmt(f)?;
|
|
||||||
if i + 1 < self.0.len() {
|
|
||||||
f.write_str(", ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.len() == 1 {
|
|
||||||
f.write_char(',')?;
|
|
||||||
}
|
|
||||||
f.write_char(')')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,3 +391,15 @@ impl<'a> IntoIterator for &'a Array {
|
|||||||
self.iter()
|
self.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The error message when the array is empty.
|
||||||
|
#[cold]
|
||||||
|
fn array_is_empty() -> EcoString {
|
||||||
|
"array is empty".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The out of bounds access error message.
|
||||||
|
#[cold]
|
||||||
|
fn out_of_bounds(index: i64, len: i64) -> EcoString {
|
||||||
|
eco_format!("array index out of bounds (index: {}, len: {})", index, len)
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ use ecow::{eco_format, EcoString};
|
|||||||
use super::{array, Array, Str, Value};
|
use super::{array, Array, Str, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::syntax::is_ident;
|
use crate::syntax::is_ident;
|
||||||
use crate::util::ArcExt;
|
use crate::util::{pretty_array, ArcExt};
|
||||||
|
|
||||||
/// Create a new [`Dict`] from key-value pairs.
|
/// Create a new [`Dict`] from key-value pairs.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -132,31 +132,24 @@ impl Dict {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The missing key access error message.
|
|
||||||
#[cold]
|
|
||||||
fn missing_key(key: &str) -> EcoString {
|
|
||||||
eco_format!("dictionary does not contain key {:?}", Str::from(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Dict {
|
impl Debug for Dict {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_char('(')?;
|
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
f.write_char(':')?;
|
return f.write_str("(:)");
|
||||||
}
|
}
|
||||||
for (i, (key, value)) in self.iter().enumerate() {
|
|
||||||
|
let pieces: Vec<_> = self
|
||||||
|
.iter()
|
||||||
|
.map(|(key, value)| {
|
||||||
if is_ident(key) {
|
if is_ident(key) {
|
||||||
f.write_str(key)?;
|
eco_format!("{key}: {value:?}")
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{key:?}")?;
|
eco_format!("{key:?}: {value:?}")
|
||||||
}
|
}
|
||||||
f.write_str(": ")?;
|
})
|
||||||
value.fmt(f)?;
|
.collect();
|
||||||
if i + 1 < self.0.len() {
|
|
||||||
f.write_str(", ")?;
|
f.write_str(&pretty_array(&pieces, false))
|
||||||
}
|
|
||||||
}
|
|
||||||
f.write_char(')')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,3 +200,9 @@ impl<'a> IntoIterator for &'a Dict {
|
|||||||
self.iter()
|
self.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The missing key access error message.
|
||||||
|
#[cold]
|
||||||
|
fn missing_key(key: &str) -> EcoString {
|
||||||
|
eco_format!("dictionary does not contain key {:?}", Str::from(key))
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@ use super::{
|
|||||||
Vm,
|
Vm,
|
||||||
};
|
};
|
||||||
use crate::diag::{bail, SourceResult, StrResult};
|
use crate::diag::{bail, SourceResult, StrResult};
|
||||||
use crate::model::{Content, NodeId, Selector, StyleMap};
|
use crate::model::{NodeId, Selector, StyleMap};
|
||||||
use crate::syntax::ast::{self, AstNode, Expr};
|
use crate::syntax::ast::{self, AstNode, Expr};
|
||||||
use crate::syntax::{SourceId, Span, SyntaxNode};
|
use crate::syntax::{SourceId, Span, SyntaxNode};
|
||||||
use crate::util::hash128;
|
use crate::util::hash128;
|
||||||
@ -29,7 +29,7 @@ enum Repr {
|
|||||||
/// A native Rust function.
|
/// A native Rust function.
|
||||||
Native(NativeFunc),
|
Native(NativeFunc),
|
||||||
/// A function for a node.
|
/// A function for a node.
|
||||||
Node(NodeFunc),
|
Node(NodeId),
|
||||||
/// A user-defined closure.
|
/// A user-defined closure.
|
||||||
Closure(Closure),
|
Closure(Closure),
|
||||||
/// A nested function with pre-applied arguments.
|
/// A nested function with pre-applied arguments.
|
||||||
@ -156,13 +156,13 @@ impl Func {
|
|||||||
|
|
||||||
/// Create a selector for this function's node type.
|
/// Create a selector for this function's node type.
|
||||||
pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> {
|
pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> {
|
||||||
match &**self.0 {
|
match **self.0 {
|
||||||
Repr::Node(node) => {
|
Repr::Node(id) => {
|
||||||
if node.id == item!(text_id) {
|
if id == item!(text_id) {
|
||||||
Err("to select text, please use a string or regex instead")?;
|
Err("to select text, please use a string or regex instead")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Selector::Node(node.id, fields))
|
Ok(Selector::Node(id, fields))
|
||||||
}
|
}
|
||||||
_ => Err("this function is not selectable")?,
|
_ => Err("this function is not selectable")?,
|
||||||
}
|
}
|
||||||
@ -172,8 +172,8 @@ impl Func {
|
|||||||
impl Debug for Func {
|
impl Debug for Func {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self.name() {
|
match self.name() {
|
||||||
Some(name) => write!(f, "<function {name}>"),
|
Some(name) => write!(f, "{name}"),
|
||||||
None => f.write_str("<function>"),
|
None => f.write_str("(..) => .."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,6 +190,16 @@ impl From<Repr> for Func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NodeId> for Func {
|
||||||
|
fn from(id: NodeId) -> Self {
|
||||||
|
Repr::Node(id).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_to_value! {
|
||||||
|
v: NodeId => Value::Func(v.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// A native Rust function.
|
/// A native Rust function.
|
||||||
pub struct NativeFunc {
|
pub struct NativeFunc {
|
||||||
/// The function's implementation.
|
/// The function's implementation.
|
||||||
@ -223,36 +233,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A function defined by a native Rust node.
|
|
||||||
pub struct NodeFunc {
|
|
||||||
/// The node's id.
|
|
||||||
pub id: NodeId,
|
|
||||||
/// The node's constructor.
|
|
||||||
pub construct: fn(&Vm, &mut Args) -> SourceResult<Content>,
|
|
||||||
/// The node's set rule.
|
|
||||||
pub set: fn(&mut Args) -> SourceResult<StyleMap>,
|
|
||||||
/// Details about the function.
|
|
||||||
pub info: Lazy<FuncInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for NodeFunc {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.id.hash(state);
|
|
||||||
(self.construct as usize).hash(state);
|
|
||||||
(self.set as usize).hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<NodeFunc> for Func {
|
|
||||||
fn from(node: NodeFunc) -> Self {
|
|
||||||
Repr::Node(node).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cast_to_value! {
|
|
||||||
v: NodeFunc => Value::Func(v.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Details about a function.
|
/// Details about a function.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FuncInfo {
|
pub struct FuncInfo {
|
||||||
|
@ -69,6 +69,13 @@ pub fn call(
|
|||||||
_ => return missing(),
|
_ => return missing(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Value::Content(content) => match method {
|
||||||
|
"func" => Value::Func(content.id().into()),
|
||||||
|
"has" => Value::Bool(content.has(&args.expect::<EcoString>("field")?)),
|
||||||
|
"at" => content.at(&args.expect::<EcoString>("field")?).at(span)?.clone(),
|
||||||
|
_ => return missing(),
|
||||||
|
},
|
||||||
|
|
||||||
Value::Array(array) => match method {
|
Value::Array(array) => match method {
|
||||||
"len" => Value::Int(array.len()),
|
"len" => Value::Int(array.len()),
|
||||||
"first" => array.first().at(span)?.clone(),
|
"first" => array.first().at(span)?.clone(),
|
||||||
@ -96,6 +103,7 @@ pub fn call(
|
|||||||
"all" => Value::Bool(array.all(vm, args.expect("function")?)?),
|
"all" => Value::Bool(array.all(vm, args.expect("function")?)?),
|
||||||
"flatten" => Value::Array(array.flatten()),
|
"flatten" => Value::Array(array.flatten()),
|
||||||
"rev" => Value::Array(array.rev()),
|
"rev" => Value::Array(array.rev()),
|
||||||
|
"split" => Value::Array(array.split(args.expect("separator")?)),
|
||||||
"join" => {
|
"join" => {
|
||||||
let sep = args.eat()?;
|
let sep = args.eat()?;
|
||||||
let last = args.named("last")?;
|
let last = args.named("last")?;
|
||||||
@ -107,7 +115,7 @@ pub fn call(
|
|||||||
|
|
||||||
Value::Dict(dict) => match method {
|
Value::Dict(dict) => match method {
|
||||||
"len" => Value::Int(dict.len()),
|
"len" => Value::Int(dict.len()),
|
||||||
"at" => dict.at(&args.expect::<Str>("key")?).cloned().at(span)?,
|
"at" => dict.at(&args.expect::<Str>("key")?).at(span)?.clone(),
|
||||||
"keys" => Value::Array(dict.keys()),
|
"keys" => Value::Array(dict.keys()),
|
||||||
"values" => Value::Array(dict.values()),
|
"values" => Value::Array(dict.values()),
|
||||||
"pairs" => Value::Array(dict.pairs()),
|
"pairs" => Value::Array(dict.pairs()),
|
||||||
@ -237,6 +245,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
|||||||
("starts-with", true),
|
("starts-with", true),
|
||||||
("trim", true),
|
("trim", true),
|
||||||
],
|
],
|
||||||
|
"content" => &[("func", false), ("has", true), ("at", true)],
|
||||||
"array" => &[
|
"array" => &[
|
||||||
("all", true),
|
("all", true),
|
||||||
("any", true),
|
("any", true),
|
||||||
@ -248,6 +257,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
|||||||
("flatten", false),
|
("flatten", false),
|
||||||
("fold", true),
|
("fold", true),
|
||||||
("insert", true),
|
("insert", true),
|
||||||
|
("split", true),
|
||||||
("join", true),
|
("join", true),
|
||||||
("last", false),
|
("last", false),
|
||||||
("len", false),
|
("len", false),
|
||||||
|
@ -47,6 +47,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
use crate::diag::{
|
use crate::diag::{
|
||||||
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
|
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
|
||||||
};
|
};
|
||||||
|
use crate::model::Unlabellable;
|
||||||
use crate::model::{Content, Label, Recipe, Selector, StyleMap, Transform};
|
use crate::model::{Content, Label, Recipe, Selector, StyleMap, Transform};
|
||||||
use crate::syntax::ast::AstNode;
|
use crate::syntax::ast::AstNode;
|
||||||
use crate::syntax::{
|
use crate::syntax::{
|
||||||
@ -357,12 +358,12 @@ fn eval_markup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tail = eval_markup(vm, exprs)?;
|
let tail = eval_markup(vm, exprs)?;
|
||||||
seq.push(tail.apply_recipe(vm.world, recipe)?)
|
seq.push(tail.styled_with_recipe(vm.world, recipe)?)
|
||||||
}
|
}
|
||||||
expr => match expr.eval(vm)? {
|
expr => match expr.eval(vm)? {
|
||||||
Value::Label(label) => {
|
Value::Label(label) => {
|
||||||
if let Some(node) =
|
if let Some(node) =
|
||||||
seq.iter_mut().rev().find(|node| node.labellable())
|
seq.iter_mut().rev().find(|node| !node.can::<dyn Unlabellable>())
|
||||||
{
|
{
|
||||||
*node = mem::take(node).labelled(label);
|
*node = mem::take(node).labelled(label);
|
||||||
}
|
}
|
||||||
@ -786,7 +787,7 @@ fn eval_code(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tail = eval_code(vm, exprs)?.display();
|
let tail = eval_code(vm, exprs)?.display();
|
||||||
Value::Content(tail.apply_recipe(vm.world, recipe)?)
|
Value::Content(tail.styled_with_recipe(vm.world, recipe)?)
|
||||||
}
|
}
|
||||||
_ => expr.eval(vm)?,
|
_ => expr.eval(vm)?,
|
||||||
};
|
};
|
||||||
@ -979,6 +980,10 @@ impl Eval for ast::FuncCall {
|
|||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let span = self.span();
|
let span = self.span();
|
||||||
|
if vm.depth >= MAX_CALL_DEPTH {
|
||||||
|
bail!(span, "maximum function call depth exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
let callee = self.callee();
|
let callee = self.callee();
|
||||||
let in_math = in_math(&callee);
|
let in_math = in_math(&callee);
|
||||||
let callee_span = callee.span();
|
let callee_span = callee.span();
|
||||||
@ -1042,11 +1047,6 @@ impl Eval for ast::FuncCall {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, just a normal function call!
|
|
||||||
if vm.depth >= MAX_CALL_DEPTH {
|
|
||||||
bail!(span, "maximum function call depth exceeded");
|
|
||||||
}
|
|
||||||
|
|
||||||
let callee = callee.cast::<Func>().at(callee_span)?;
|
let callee = callee.cast::<Func>().at(callee_span)?;
|
||||||
let point = || Tracepoint::Call(callee.name().map(Into::into));
|
let point = || Tracepoint::Call(callee.name().map(Into::into));
|
||||||
callee.call(vm, args).trace(vm.world, point, span)
|
callee.call(vm, args).trace(vm.world, point, span)
|
||||||
|
@ -340,6 +340,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
|||||||
(Symbol(a), Symbol(b)) => a == b,
|
(Symbol(a), Symbol(b)) => a == b,
|
||||||
(Str(a), Str(b)) => a == b,
|
(Str(a), Str(b)) => a == b,
|
||||||
(Label(a), Label(b)) => a == b,
|
(Label(a), Label(b)) => a == b,
|
||||||
|
(Content(a), Content(b)) => a == b,
|
||||||
(Array(a), Array(b)) => a == b,
|
(Array(a), Array(b)) => a == b,
|
||||||
(Dict(a), Dict(b)) => a == b,
|
(Dict(a), Dict(b)) => a == b,
|
||||||
(Func(a), Func(b)) => a == b,
|
(Func(a), Func(b)) => a == b,
|
||||||
|
@ -120,10 +120,7 @@ impl Value {
|
|||||||
match self {
|
match self {
|
||||||
Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol),
|
Self::Symbol(symbol) => symbol.clone().modified(&field).map(Self::Symbol),
|
||||||
Self::Dict(dict) => dict.at(&field).cloned(),
|
Self::Dict(dict) => dict.at(&field).cloned(),
|
||||||
Self::Content(content) => content
|
Self::Content(content) => content.at(&field).cloned(),
|
||||||
.field(&field)
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| eco_format!("unknown field `{field}`")),
|
|
||||||
Self::Module(module) => module.get(&field).cloned(),
|
Self::Module(module) => module.get(&field).cloned(),
|
||||||
v => Err(eco_format!("cannot access fields on type {}", v.type_name())),
|
v => Err(eco_format!("cannot access fields on type {}", v.type_name())),
|
||||||
}
|
}
|
||||||
@ -190,7 +187,7 @@ impl Debug for Value {
|
|||||||
Self::Symbol(v) => Debug::fmt(v, f),
|
Self::Symbol(v) => Debug::fmt(v, f),
|
||||||
Self::Str(v) => Debug::fmt(v, f),
|
Self::Str(v) => Debug::fmt(v, f),
|
||||||
Self::Label(v) => Debug::fmt(v, f),
|
Self::Label(v) => Debug::fmt(v, f),
|
||||||
Self::Content(_) => f.pad("[...]"),
|
Self::Content(v) => Debug::fmt(v, f),
|
||||||
Self::Array(v) => Debug::fmt(v, f),
|
Self::Array(v) => Debug::fmt(v, f),
|
||||||
Self::Dict(v) => Debug::fmt(v, f),
|
Self::Dict(v) => Debug::fmt(v, f),
|
||||||
Self::Func(v) => Debug::fmt(v, f),
|
Self::Func(v) => Debug::fmt(v, f),
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use ecow::EcoString;
|
||||||
|
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
|
||||||
|
|
||||||
use super::{analyze_expr, plain_docs_sentence, summarize_font_family};
|
use super::{analyze_expr, plain_docs_sentence, summarize_font_family};
|
||||||
use crate::eval::{CastInfo, Tracer, Value};
|
use crate::eval::{CastInfo, Tracer, Value};
|
||||||
use crate::geom::{round_2, Length, Numeric};
|
use crate::geom::{round_2, Length, Numeric};
|
||||||
use crate::syntax::ast;
|
use crate::syntax::ast;
|
||||||
use crate::syntax::{LinkedNode, Source, SyntaxKind};
|
use crate::syntax::{LinkedNode, Source, SyntaxKind};
|
||||||
|
use crate::util::pretty_comma_list;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Describe the item under the cursor.
|
/// Describe the item under the cursor.
|
||||||
@ -60,31 +64,27 @@ fn expr_tooltip(world: &(dyn World + 'static), leaf: &LinkedNode) -> Option<Tool
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tooltip = String::new();
|
let mut last = None;
|
||||||
let mut iter = values.into_iter().enumerate();
|
let mut pieces: Vec<EcoString> = vec![];
|
||||||
for (i, value) in (&mut iter).take(Tracer::MAX - 1) {
|
let mut iter = values.iter();
|
||||||
if i > 0 && !tooltip.is_empty() {
|
for value in (&mut iter).take(Tracer::MAX - 1) {
|
||||||
tooltip.push_str(", ");
|
if let Some((prev, count)) = &mut last {
|
||||||
|
if *prev == value {
|
||||||
|
*count += 1;
|
||||||
|
continue;
|
||||||
|
} else if *count > 1 {
|
||||||
|
write!(pieces.last_mut().unwrap(), " (x{count})").unwrap();
|
||||||
}
|
}
|
||||||
let repr = value.repr();
|
|
||||||
let repr = repr.as_str();
|
|
||||||
let len = repr.len();
|
|
||||||
if len <= 40 {
|
|
||||||
tooltip.push_str(repr);
|
|
||||||
} else {
|
|
||||||
let mut graphemes = repr.graphemes(true);
|
|
||||||
let r = graphemes.next_back().map_or(0, str::len);
|
|
||||||
let l = graphemes.take(40).map(str::len).sum();
|
|
||||||
tooltip.push_str(&repr[..l]);
|
|
||||||
tooltip.push_str("...");
|
|
||||||
tooltip.push_str(&repr[len - r..]);
|
|
||||||
}
|
}
|
||||||
|
pieces.push(value.repr().into());
|
||||||
|
last = Some((value, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if iter.next().is_some() {
|
if iter.next().is_some() {
|
||||||
tooltip.push_str(", ...");
|
pieces.push("...".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tooltip = pretty_comma_list(&pieces, false);
|
||||||
(!tooltip.is_empty()).then(|| Tooltip::Code(tooltip))
|
(!tooltip.is_empty()).then(|| Tooltip::Code(tooltip))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,22 +2,24 @@ use std::any::TypeId;
|
|||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::{self, Sum};
|
use std::iter::{self, Sum};
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use ecow::{EcoString, EcoVec};
|
use ecow::{eco_format, EcoString, EcoVec};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use super::{node, Guard, Recipe, Style, StyleMap};
|
use super::{node, Guard, Recipe, Style, StyleMap};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::eval::{cast_from_value, Args, Cast, NodeFunc, Value, Vm};
|
use crate::eval::{cast_from_value, Args, FuncInfo, Str, Value, Vm};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
|
use crate::util::pretty_array;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Composable representation of styled content.
|
/// Composable representation of styled content.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct Content {
|
pub struct Content {
|
||||||
id: NodeId,
|
id: NodeId,
|
||||||
span: Option<Span>,
|
span: Span,
|
||||||
fields: EcoVec<(EcoString, Value)>,
|
fields: EcoVec<(EcoString, Value)>,
|
||||||
modifiers: EcoVec<Modifier>,
|
modifiers: EcoVec<Modifier>,
|
||||||
}
|
}
|
||||||
@ -33,7 +35,7 @@ impl Content {
|
|||||||
pub fn new<T: Node>() -> Self {
|
pub fn new<T: Node>() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: T::id(),
|
id: T::id(),
|
||||||
span: None,
|
span: Span::detached(),
|
||||||
fields: EcoVec::new(),
|
fields: EcoVec::new(),
|
||||||
modifiers: EcoVec::new(),
|
modifiers: EcoVec::new(),
|
||||||
}
|
}
|
||||||
@ -52,82 +54,67 @@ impl Content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach a span to the content.
|
|
||||||
pub fn spanned(mut self, span: Span) -> Self {
|
|
||||||
if let Some(styled) = self.to::<StyledNode>() {
|
|
||||||
self = StyledNode::new(styled.map(), styled.body().spanned(span)).pack();
|
|
||||||
}
|
|
||||||
self.span = Some(span);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attach a label to the content.
|
|
||||||
pub fn labelled(self, label: Label) -> Self {
|
|
||||||
self.with_field("label", label)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Style this content with a style entry.
|
|
||||||
pub fn styled(self, style: impl Into<Style>) -> Self {
|
|
||||||
self.styled_with_map(style.into().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Style this content with a full style map.
|
|
||||||
pub fn styled_with_map(self, styles: StyleMap) -> Self {
|
|
||||||
if styles.is_empty() {
|
|
||||||
self
|
|
||||||
} else if let Some(styled) = self.to::<StyledNode>() {
|
|
||||||
let mut map = styled.map();
|
|
||||||
map.apply(styles);
|
|
||||||
StyledNode::new(map, styled.body()).pack()
|
|
||||||
} else {
|
|
||||||
StyledNode::new(styles, self).pack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Style this content with a recipe, eagerly applying it if possible.
|
|
||||||
pub fn apply_recipe(
|
|
||||||
self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
recipe: Recipe,
|
|
||||||
) -> SourceResult<Self> {
|
|
||||||
if recipe.selector.is_none() {
|
|
||||||
recipe.apply(world, self)
|
|
||||||
} else {
|
|
||||||
Ok(self.styled(Style::Recipe(recipe)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Repeat this content `n` times.
|
|
||||||
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
|
||||||
let count = usize::try_from(n)
|
|
||||||
.map_err(|_| format!("cannot repeat this content {} times", n))?;
|
|
||||||
|
|
||||||
Ok(Self::sequence(vec![self.clone(); count]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Content {
|
|
||||||
/// The id of the contained node.
|
/// The id of the contained node.
|
||||||
pub fn id(&self) -> NodeId {
|
pub fn id(&self) -> NodeId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The node's human-readable name.
|
/// Whether the contained node is of type `T`.
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn is<T>(&self) -> bool
|
||||||
self.id.name()
|
where
|
||||||
|
T: Node + 'static,
|
||||||
|
{
|
||||||
|
self.id == NodeId::of::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast to `T` if the contained node is of type `T`.
|
||||||
|
pub fn to<T>(&self) -> Option<&T>
|
||||||
|
where
|
||||||
|
T: Node + 'static,
|
||||||
|
{
|
||||||
|
self.is::<T>().then(|| unsafe { std::mem::transmute(self) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this content has the given capability.
|
||||||
|
pub fn can<C>(&self) -> bool
|
||||||
|
where
|
||||||
|
C: ?Sized + 'static,
|
||||||
|
{
|
||||||
|
(self.id.0.vtable)(TypeId::of::<C>()).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast to a trait object if this content has the given capability.
|
||||||
|
pub fn with<C>(&self) -> Option<&C>
|
||||||
|
where
|
||||||
|
C: ?Sized + 'static,
|
||||||
|
{
|
||||||
|
let vtable = (self.id.0.vtable)(TypeId::of::<C>())?;
|
||||||
|
let data = self as *const Self as *const ();
|
||||||
|
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The node's span.
|
/// The node's span.
|
||||||
pub fn span(&self) -> Option<Span> {
|
pub fn span(&self) -> Span {
|
||||||
self.span
|
self.span
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The content's label.
|
/// Attach a span to the content.
|
||||||
pub fn label(&self) -> Option<&Label> {
|
pub fn spanned(mut self, span: Span) -> Self {
|
||||||
match self.field("label")? {
|
self.span = span;
|
||||||
Value::Label(label) => Some(label),
|
self
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access a field on the content.
|
||||||
|
pub fn field(&self, name: &str) -> Option<&Value> {
|
||||||
|
self.fields
|
||||||
|
.iter()
|
||||||
|
.find(|(field, _)| field == name)
|
||||||
|
.map(|(_, value)| value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all fields on the content.
|
||||||
|
pub fn fields(&self) -> &[(EcoString, Value)] {
|
||||||
|
&self.fields
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach a field to the content.
|
/// Attach a field to the content.
|
||||||
@ -150,88 +137,88 @@ impl Content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access a field on the content.
|
/// Whether the content has the specified field.
|
||||||
pub fn field(&self, name: &str) -> Option<&Value> {
|
pub fn has(&self, field: &str) -> bool {
|
||||||
static NONE: Value = Value::None;
|
self.field(field).is_some()
|
||||||
self.fields
|
|
||||||
.iter()
|
|
||||||
.find(|(field, _)| field == name)
|
|
||||||
.map(|(_, value)| value)
|
|
||||||
.or_else(|| (name == "label").then(|| &NONE))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
/// Borrow the value of the given field.
|
||||||
pub fn cast_required_field<T: Cast>(&self, name: &str) -> T {
|
pub fn at(&self, field: &str) -> StrResult<&Value> {
|
||||||
match self.field(name) {
|
self.field(field).ok_or_else(|| missing_field(field))
|
||||||
Some(value) => value.clone().cast().unwrap(),
|
}
|
||||||
None => field_is_missing(name),
|
|
||||||
|
/// The content's label.
|
||||||
|
pub fn label(&self) -> Option<&Label> {
|
||||||
|
match self.field("label")? {
|
||||||
|
Value::Label(label) => Some(label),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List all fields on the content.
|
/// Attach a label to the content.
|
||||||
pub fn fields(&self) -> &[(EcoString, Value)] {
|
pub fn labelled(self, label: Label) -> Self {
|
||||||
&self.fields
|
self.with_field("label", label)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the contained node is of type `T`.
|
/// Style this content with a style entry.
|
||||||
pub fn is<T>(&self) -> bool
|
pub fn styled(self, style: impl Into<Style>) -> Self {
|
||||||
where
|
self.styled_with_map(style.into().into())
|
||||||
T: Node + 'static,
|
|
||||||
{
|
|
||||||
self.id == NodeId::of::<T>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast to `T` if the contained node is of type `T`.
|
/// Style this content with a full style map.
|
||||||
pub fn to<T>(&self) -> Option<&T>
|
pub fn styled_with_map(self, styles: StyleMap) -> Self {
|
||||||
where
|
if styles.is_empty() {
|
||||||
T: Node + 'static,
|
self
|
||||||
{
|
} else if let Some(styled) = self.to::<StyledNode>() {
|
||||||
self.is::<T>().then(|| unsafe { std::mem::transmute(self) })
|
let mut map = styled.styles();
|
||||||
|
map.apply(styles);
|
||||||
|
StyledNode::new(map, styled.body()).pack()
|
||||||
|
} else {
|
||||||
|
StyledNode::new(styles, self).pack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this content has the given capability.
|
/// Style this content with a recipe, eagerly applying it if possible.
|
||||||
pub fn has<C>(&self) -> bool
|
pub fn styled_with_recipe(
|
||||||
where
|
self,
|
||||||
C: ?Sized + 'static,
|
world: Tracked<dyn World>,
|
||||||
{
|
recipe: Recipe,
|
||||||
(self.id.0.vtable)(TypeId::of::<C>()).is_some()
|
) -> SourceResult<Self> {
|
||||||
|
if recipe.selector.is_none() {
|
||||||
|
recipe.apply(world, self)
|
||||||
|
} else {
|
||||||
|
Ok(self.styled(Style::Recipe(recipe)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast to a trait object if this content has the given capability.
|
/// Repeat this content `n` times.
|
||||||
pub fn with<C>(&self) -> Option<&C>
|
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||||
where
|
let count = usize::try_from(n)
|
||||||
C: ?Sized + 'static,
|
.map_err(|_| format!("cannot repeat this content {} times", n))?;
|
||||||
{
|
|
||||||
let vtable = (self.id.0.vtable)(TypeId::of::<C>())?;
|
|
||||||
let data = self as *const Self as *const ();
|
|
||||||
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Ok(Self::sequence(vec![self.clone(); count]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl Content {
|
||||||
/// Disable a show rule recipe.
|
/// Disable a show rule recipe.
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn guarded(mut self, id: Guard) -> Self {
|
pub fn guarded(mut self, id: Guard) -> Self {
|
||||||
self.modifiers.push(Modifier::Guard(id));
|
self.modifiers.push(Modifier::Guard(id));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark this content as prepared.
|
/// Mark this content as prepared.
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn prepared(mut self) -> Self {
|
pub fn prepared(mut self) -> Self {
|
||||||
self.modifiers.push(Modifier::Prepared);
|
self.modifiers.push(Modifier::Prepared);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this node was prepared.
|
/// Whether this node was prepared.
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn is_prepared(&self) -> bool {
|
pub fn is_prepared(&self) -> bool {
|
||||||
self.modifiers.contains(&Modifier::Prepared)
|
self.modifiers.contains(&Modifier::Prepared)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a label can be attached to the content.
|
|
||||||
pub(crate) fn labellable(&self) -> bool {
|
|
||||||
!self.has::<dyn Unlabellable>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether no show rule was executed for this node so far.
|
/// Whether no show rule was executed for this node so far.
|
||||||
pub(super) fn is_pristine(&self) -> bool {
|
pub(super) fn is_pristine(&self) -> bool {
|
||||||
!self
|
!self
|
||||||
@ -257,32 +244,24 @@ impl Content {
|
|||||||
|
|
||||||
impl Debug for Content {
|
impl Debug for Content {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
struct Pad<'a>(&'a str);
|
let name = self.id.name;
|
||||||
impl Debug for Pad<'_> {
|
if let Some(text) = item!(text_str)(self) {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
f.write_char('[')?;
|
||||||
f.pad(self.0)
|
f.write_str(&text)?;
|
||||||
}
|
f.write_char(']')?;
|
||||||
|
return Ok(());
|
||||||
|
} else if name == "space" {
|
||||||
|
return f.write_str("[ ]");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(styled) = self.to::<StyledNode>() {
|
let pieces: Vec<_> = self
|
||||||
styled.map().fmt(f)?;
|
.fields
|
||||||
styled.body().fmt(f)
|
.iter()
|
||||||
} else if let Some(seq) = self.to::<SequenceNode>() {
|
.map(|(name, value)| eco_format!("{name}: {value:?}"))
|
||||||
f.debug_list().entries(&seq.children()).finish()
|
.collect();
|
||||||
} else if self.id.name() == "space" {
|
|
||||||
' '.fmt(f)
|
f.write_str(name)?;
|
||||||
} else if self.id.name() == "text" {
|
f.write_str(&pretty_array(&pieces, false))
|
||||||
self.field("text").unwrap().fmt(f)
|
|
||||||
} else {
|
|
||||||
f.write_str(self.name())?;
|
|
||||||
if self.fields.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
f.write_char(' ')?;
|
|
||||||
f.debug_map()
|
|
||||||
.entries(self.fields.iter().map(|(name, value)| (Pad(name), value)))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,6 +271,17 @@ impl Default for Content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Content {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
&& self.fields.len() == other.fields.len()
|
||||||
|
&& self
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.all(|(name, value)| other.field(name) == Some(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Add for Content {
|
impl Add for Content {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
@ -323,48 +313,6 @@ impl Sum for Content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node with applied styles.
|
|
||||||
///
|
|
||||||
/// Display: Styled
|
|
||||||
/// Category: special
|
|
||||||
#[node]
|
|
||||||
pub struct StyledNode {
|
|
||||||
/// The styles.
|
|
||||||
#[required]
|
|
||||||
pub map: StyleMap,
|
|
||||||
|
|
||||||
/// The styled content.
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
cast_from_value! {
|
|
||||||
StyleMap: "style map",
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sequence of nodes.
|
|
||||||
///
|
|
||||||
/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in
|
|
||||||
/// Typst, the two text nodes are combined into a single sequence node.
|
|
||||||
///
|
|
||||||
/// Display: Sequence
|
|
||||||
/// Category: special
|
|
||||||
#[node]
|
|
||||||
pub struct SequenceNode {
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A label for a node.
|
|
||||||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
||||||
pub struct Label(pub EcoString);
|
|
||||||
|
|
||||||
impl Debug for Label {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "<{}>", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A constructable, stylable content node.
|
/// A constructable, stylable content node.
|
||||||
pub trait Node: Construct + Set + Sized + 'static {
|
pub trait Node: Construct + Set + Sized + 'static {
|
||||||
/// The node's ID.
|
/// The node's ID.
|
||||||
@ -372,33 +320,22 @@ pub trait Node: Construct + Set + Sized + 'static {
|
|||||||
|
|
||||||
/// Pack a node into type-erased content.
|
/// Pack a node into type-erased content.
|
||||||
fn pack(self) -> Content;
|
fn pack(self) -> Content;
|
||||||
|
|
||||||
/// The node's function.
|
|
||||||
fn func() -> NodeFunc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique identifier for a node.
|
/// A unique identifier for a node.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct NodeId(&'static NodeMeta);
|
pub struct NodeId(pub &'static NodeMeta);
|
||||||
|
|
||||||
impl NodeId {
|
impl NodeId {
|
||||||
|
/// Get the id of a node.
|
||||||
pub fn of<T: Node>() -> Self {
|
pub fn of<T: Node>() -> Self {
|
||||||
T::id()
|
T::id()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_meta(meta: &'static NodeMeta) -> Self {
|
|
||||||
Self(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The name of the identified node.
|
|
||||||
pub fn name(self) -> &'static str {
|
|
||||||
self.0.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for NodeId {
|
impl Debug for NodeId {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.pad(self.name())
|
f.pad(self.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,10 +353,26 @@ impl PartialEq for NodeId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
impl Deref for NodeId {
|
||||||
|
type Target = NodeMeta;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Static node for a node.
|
||||||
pub struct NodeMeta {
|
pub struct NodeMeta {
|
||||||
|
/// The node's name.
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
/// The node's vtable for caspability dispatch.
|
||||||
pub vtable: fn(of: TypeId) -> Option<*const ()>,
|
pub vtable: fn(of: TypeId) -> Option<*const ()>,
|
||||||
|
/// The node's constructor.
|
||||||
|
pub construct: fn(&Vm, &mut Args) -> SourceResult<Content>,
|
||||||
|
/// The node's set rule.
|
||||||
|
pub set: fn(&mut Args) -> SourceResult<StyleMap>,
|
||||||
|
/// Details about the function.
|
||||||
|
pub info: Lazy<FuncInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node's constructor function.
|
/// A node's constructor function.
|
||||||
@ -440,8 +393,51 @@ pub trait Set {
|
|||||||
/// Indicates that a node cannot be labelled.
|
/// Indicates that a node cannot be labelled.
|
||||||
pub trait Unlabellable {}
|
pub trait Unlabellable {}
|
||||||
|
|
||||||
|
/// A label for a node.
|
||||||
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct Label(pub EcoString);
|
||||||
|
|
||||||
|
impl Debug for Label {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "<{}>", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sequence of nodes.
|
||||||
|
///
|
||||||
|
/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in
|
||||||
|
/// Typst, the two text nodes are combined into a single sequence node.
|
||||||
|
///
|
||||||
|
/// Display: Sequence
|
||||||
|
/// Category: special
|
||||||
|
#[node]
|
||||||
|
pub struct SequenceNode {
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A node with applied styles.
|
||||||
|
///
|
||||||
|
/// Display: Styled
|
||||||
|
/// Category: special
|
||||||
|
#[node]
|
||||||
|
pub struct StyledNode {
|
||||||
|
/// The styles.
|
||||||
|
#[required]
|
||||||
|
pub styles: StyleMap,
|
||||||
|
|
||||||
|
/// The styled content.
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_value! {
|
||||||
|
StyleMap: "style map",
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The missing key access error message.
|
||||||
#[cold]
|
#[cold]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn field_is_missing(name: &str) -> ! {
|
fn missing_field(key: &str) -> EcoString {
|
||||||
panic!("required field `{name}` is missing")
|
eco_format!("content does not contain field {:?}", Str::from(key))
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ use crate::diag::SourceResult;
|
|||||||
|
|
||||||
/// Whether the target is affected by show rules in the given style chain.
|
/// Whether the target is affected by show rules in the given style chain.
|
||||||
pub fn applicable(target: &Content, styles: StyleChain) -> bool {
|
pub fn applicable(target: &Content, styles: StyleChain) -> bool {
|
||||||
if target.has::<dyn Prepare>() && !target.is_prepared() {
|
if target.can::<dyn Prepare>() && !target.is_prepared() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.has::<dyn Show>() && target.is_pristine() {
|
if target.can::<dyn Show>() && target.is_pristine() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ pub fn realize(
|
|||||||
if let Some(showable) = target.with::<dyn Show>() {
|
if let Some(showable) = target.with::<dyn Show>() {
|
||||||
let guard = Guard::Base(target.id());
|
let guard = Guard::Base(target.id());
|
||||||
if realized.is_none() && !target.is_guarded(guard) {
|
if realized.is_none() && !target.is_guarded(guard) {
|
||||||
realized = Some(showable.show(vt, target, styles)?);
|
realized = Some(showable.show(vt, styles)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,23 +135,13 @@ fn try_apply(
|
|||||||
/// Preparations before execution of any show rule.
|
/// Preparations before execution of any show rule.
|
||||||
pub trait Prepare {
|
pub trait Prepare {
|
||||||
/// Prepare the node for show rule application.
|
/// Prepare the node for show rule application.
|
||||||
fn prepare(
|
fn prepare(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content>;
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
this: Content,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The base recipe for a node.
|
/// The base recipe for a node.
|
||||||
pub trait Show {
|
pub trait Show {
|
||||||
/// Execute the base recipe for this node.
|
/// Execute the base recipe for this node.
|
||||||
fn show(
|
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content>;
|
||||||
&self,
|
|
||||||
vt: &mut Vt,
|
|
||||||
this: &Content,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Post-process a node after it was realized.
|
/// Post-process a node after it was realized.
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use ecow::EcoString;
|
use ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
use super::{Content, Label, Node, NodeId};
|
use super::{Content, Label, Node, NodeId};
|
||||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||||
use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value};
|
use crate::eval::{cast_from_value, Args, Cast, Dict, Func, Regex, Value};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
|
use crate::util::pretty_array;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
@ -79,10 +80,13 @@ impl PartialEq for StyleMap {
|
|||||||
|
|
||||||
impl Debug for StyleMap {
|
impl Debug for StyleMap {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
for entry in self.0.iter() {
|
if let [style] = self.0.as_slice() {
|
||||||
writeln!(f, "{:?}", entry)?;
|
return style.fmt(f);
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
|
let pieces: Vec<_> =
|
||||||
|
self.0.iter().map(|value| eco_format!("{value:?}")).collect();
|
||||||
|
f.write_str(&pretty_array(&pieces, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +164,7 @@ impl Property {
|
|||||||
|
|
||||||
impl Debug for Property {
|
impl Debug for Property {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "#set {}({}: {:?})", self.node.name(), self.name, self.value)?;
|
write!(f, "set {}({}: {:?})", self.node.name, self.name, self.value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,12 +207,10 @@ impl Recipe {
|
|||||||
Transform::Func(func) => {
|
Transform::Func(func) => {
|
||||||
let args = Args::new(self.span, [Value::Content(content.clone())]);
|
let args = Args::new(self.span, [Value::Content(content.clone())]);
|
||||||
let mut result = func.call_detached(world, args);
|
let mut result = func.call_detached(world, args);
|
||||||
if let Some(span) = content.span() {
|
|
||||||
// For selector-less show rules, a tracepoint makes no sense.
|
// For selector-less show rules, a tracepoint makes no sense.
|
||||||
if self.selector.is_some() {
|
if self.selector.is_some() {
|
||||||
let point = || Tracepoint::Show(content.name().into());
|
let point = || Tracepoint::Show(content.id().name.into());
|
||||||
result = result.trace(world, point, span);
|
result = result.trace(world, point, content.span());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(result?.display())
|
Ok(result?.display())
|
||||||
}
|
}
|
||||||
@ -219,12 +221,18 @@ impl Recipe {
|
|||||||
|
|
||||||
impl Debug for Recipe {
|
impl Debug for Recipe {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "#show {:?}: {:?}", self.selector, self.transform)
|
f.write_str("show")?;
|
||||||
|
if let Some(selector) = &self.selector {
|
||||||
|
f.write_char(' ')?;
|
||||||
|
selector.fmt(f)?;
|
||||||
|
}
|
||||||
|
f.write_str(": ")?;
|
||||||
|
self.transform.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A selector in a show rule.
|
/// A selector in a show rule.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub enum Selector {
|
pub enum Selector {
|
||||||
/// Matches a specific type of node.
|
/// Matches a specific type of node.
|
||||||
///
|
///
|
||||||
@ -267,6 +275,23 @@ impl Selector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Selector {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Node(node, dict) => {
|
||||||
|
f.write_str(node.name)?;
|
||||||
|
if let Some(dict) = dict {
|
||||||
|
f.write_str(".where")?;
|
||||||
|
dict.fmt(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Self::Label(label) => label.fmt(f),
|
||||||
|
Self::Regex(regex) => regex.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
Selector: "selector",
|
Selector: "selector",
|
||||||
text: EcoString => Self::text(&text),
|
text: EcoString => Self::text(&text),
|
||||||
@ -276,7 +301,7 @@ cast_from_value! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A show rule transformation that can be applied to a match.
|
/// A show rule transformation that can be applied to a match.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub enum Transform {
|
pub enum Transform {
|
||||||
/// Replacement content.
|
/// Replacement content.
|
||||||
Content(Content),
|
Content(Content),
|
||||||
@ -286,6 +311,16 @@ pub enum Transform {
|
|||||||
Style(StyleMap),
|
Style(StyleMap),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Transform {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Content(content) => content.fmt(f),
|
||||||
|
Self::Func(func) => func.fmt(f),
|
||||||
|
Self::Style(styles) => styles.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
Transform,
|
Transform,
|
||||||
content: Content => Self::Content(content),
|
content: Content => Self::Content(content),
|
||||||
@ -434,9 +469,9 @@ impl<'a> StyleChain<'a> {
|
|||||||
.map(|property| property.value.clone()),
|
.map(|property| property.value.clone()),
|
||||||
)
|
)
|
||||||
.map(move |value| {
|
.map(move |value| {
|
||||||
value.cast().unwrap_or_else(|err| {
|
value
|
||||||
panic!("{} (for {}.{})", err, node.name(), name)
|
.cast()
|
||||||
})
|
.unwrap_or_else(|err| panic!("{} (for {}.{})", err, node.name, name))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,12 +6,12 @@ mod buffer;
|
|||||||
|
|
||||||
pub use buffer::Buffer;
|
pub use buffer::Buffer;
|
||||||
|
|
||||||
use std::any::TypeId;
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ecow::EcoString;
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
|
|
||||||
/// Turn a closure into a struct implementing [`Debug`].
|
/// Turn a closure into a struct implementing [`Debug`].
|
||||||
@ -133,35 +133,63 @@ impl PathExt for Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An alternative type id that prints as something readable in debug mode.
|
/// Format something as a a comma-separated list that support horizontal
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
/// formatting but falls back to vertical formatting if the pieces are too long.
|
||||||
pub struct ReadableTypeId {
|
pub fn pretty_array(pieces: &[EcoString], trailing_comma: bool) -> String {
|
||||||
id: TypeId,
|
let list = pretty_comma_list(&pieces, trailing_comma);
|
||||||
#[cfg(debug_assertions)]
|
let mut buf = String::new();
|
||||||
name: &'static str,
|
buf.push('(');
|
||||||
|
if list.contains('\n') {
|
||||||
|
buf.push('\n');
|
||||||
|
buf.push_str(&indent(&list, 2));
|
||||||
|
buf.push('\n');
|
||||||
|
} else {
|
||||||
|
buf.push_str(&list);
|
||||||
|
}
|
||||||
|
buf.push(')');
|
||||||
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReadableTypeId {
|
/// Format something as a a comma-separated list that support horizontal
|
||||||
/// The type id of the given type.
|
/// formatting but falls back to vertical formatting if the pieces are too long.
|
||||||
pub fn of<T: 'static>() -> Self {
|
pub fn pretty_comma_list(pieces: &[EcoString], trailing_comma: bool) -> String {
|
||||||
Self {
|
const MAX_WIDTH: usize = 50;
|
||||||
id: TypeId::of::<T>(),
|
|
||||||
#[cfg(debug_assertions)]
|
let mut buf = String::new();
|
||||||
name: std::any::type_name::<T>(),
|
let len = pieces.iter().map(|s| s.len()).sum::<usize>()
|
||||||
|
+ 2 * pieces.len().saturating_sub(1);
|
||||||
|
|
||||||
|
if len <= MAX_WIDTH {
|
||||||
|
for (i, piece) in pieces.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
buf.push_str(", ");
|
||||||
|
}
|
||||||
|
buf.push_str(piece);
|
||||||
|
}
|
||||||
|
if trailing_comma {
|
||||||
|
buf.push(',');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for piece in pieces {
|
||||||
|
buf.push_str(piece.trim());
|
||||||
|
buf.push_str(",\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for ReadableTypeId {
|
/// Indent a string by two spaces.
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
pub fn indent(text: &str, amount: usize) -> String {
|
||||||
#[cfg(debug_assertions)]
|
let mut buf = String::new();
|
||||||
if let Some(part) = self.name.split("::").last() {
|
for (i, line) in text.lines().enumerate() {
|
||||||
f.pad(part)?;
|
if i > 0 {
|
||||||
|
buf.push('\n');
|
||||||
}
|
}
|
||||||
|
for _ in 0..amount {
|
||||||
#[cfg(not(debug_assertions))]
|
buf.push(' ');
|
||||||
f.pad("ReadableTypeId")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
buf.push_str(line);
|
||||||
|
}
|
||||||
|
buf
|
||||||
}
|
}
|
||||||
|
BIN
tests/ref/compiler/content-field.png
Normal file
BIN
tests/ref/compiler/content-field.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
41
tests/typ/compiler/content-field.typ
Normal file
41
tests/typ/compiler/content-field.typ
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Integrated test for content fields.
|
||||||
|
|
||||||
|
#let compute(formula, ..vars) = {
|
||||||
|
let vars = vars.named()
|
||||||
|
let f(node) = {
|
||||||
|
let func = node.func()
|
||||||
|
if func == text {
|
||||||
|
let text = node.text
|
||||||
|
if regex("^\d+$") in text {
|
||||||
|
int(text)
|
||||||
|
} else if text in vars {
|
||||||
|
int(vars.at(text))
|
||||||
|
} else {
|
||||||
|
panic("unknown math variable: " + text)
|
||||||
|
}
|
||||||
|
} else if func == math.attach {
|
||||||
|
let value = f(node.base)
|
||||||
|
if node.has("top") {
|
||||||
|
value = calc.pow(value, f(node.top))
|
||||||
|
}
|
||||||
|
value
|
||||||
|
} else if node.has("children") {
|
||||||
|
node
|
||||||
|
.children
|
||||||
|
.filter(v => v != [ ])
|
||||||
|
.split[+]
|
||||||
|
.map(xs => xs.fold(1, (prod, v) => prod * f(v)))
|
||||||
|
.fold(0, (sum, v) => sum + v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = f(formula.body)
|
||||||
|
[With ]
|
||||||
|
vars
|
||||||
|
.pairs()
|
||||||
|
.map(p => $#p.first() = #p.last()$)
|
||||||
|
.join(", ", last: " and ")
|
||||||
|
[ we have:]
|
||||||
|
$ formula = result $
|
||||||
|
}
|
||||||
|
|
||||||
|
#compute($x y + y^2$, x: 2, y: 3)
|
@ -31,7 +31,7 @@
|
|||||||
#false.ok
|
#false.ok
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 29-32 unknown field `fun`
|
// Error: 29-32 content does not contain field "fun"
|
||||||
#show heading: node => node.fun
|
#show heading: node => node.fun
|
||||||
= A
|
= A
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ The end.
|
|||||||
---
|
---
|
||||||
// Test labelled text.
|
// Test labelled text.
|
||||||
#show "t": it => {
|
#show "t": it => {
|
||||||
set text(blue) if it.label == <last>
|
set text(blue) if it.has("label") and it.label == <last>
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,11 +152,13 @@
|
|||||||
#test(test == test, true)
|
#test(test == test, true)
|
||||||
#test((() => {}) == (() => {}), false)
|
#test((() => {}) == (() => {}), false)
|
||||||
|
|
||||||
// Content cannot be compared.
|
// Content compares field by field.
|
||||||
#let t = [a]
|
#let t = [a]
|
||||||
#test(t == t, false)
|
#test(t == t, true)
|
||||||
#test([] == [], false)
|
#test([] == [], true)
|
||||||
#test([a] == [a], false)
|
#test([a] == [a], true)
|
||||||
|
#test(grid[a] == grid[a], true)
|
||||||
|
#test(grid[a] == grid[b], false)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test comparison operators.
|
// Test comparison operators.
|
||||||
|
@ -30,16 +30,13 @@
|
|||||||
#rgb("f7a205") \
|
#rgb("f7a205") \
|
||||||
#(2pt + rgb("f7a205"))
|
#(2pt + rgb("f7a205"))
|
||||||
|
|
||||||
---
|
|
||||||
// Strings and escaping.
|
// Strings and escaping.
|
||||||
#raw(repr("hi"), lang: "typc") \
|
#raw(repr("hi"), lang: "typc")
|
||||||
#repr("a\n[]\"\u{1F680}string")
|
#repr("a\n[]\"\u{1F680}string")
|
||||||
|
|
||||||
---
|
|
||||||
// Content.
|
// Content.
|
||||||
#raw(repr[*{"H" + "i"} there*])
|
#raw(lang: "typc", repr[*Hey*])
|
||||||
|
|
||||||
---
|
|
||||||
// Functions are invisible.
|
// Functions are invisible.
|
||||||
Nothing
|
Nothing
|
||||||
#let f(x) = x
|
#let f(x) = x
|
||||||
|
@ -78,7 +78,7 @@ Another text.
|
|||||||
= Heading
|
= Heading
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 25-29 unknown field `page`
|
// Error: 25-29 content does not contain field "page"
|
||||||
#show heading: it => it.page
|
#show heading: it => it.page
|
||||||
= Heading
|
= Heading
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user