summaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-26 13:39:18 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-26 13:41:27 +0100
commit7af46fc025ee08eb78ae7f6898300083c886bf6f (patch)
tree5837d972961844650bc9668d8516d7b5239a8d18 /src/model
parent3cdd8bfa40fe5fdf0c676af905c3c2c1f614ef24 (diff)
Dynamic labels
Diffstat (limited to 'src/model')
-rw-r--r--src/model/cast.rs3
-rw-r--r--src/model/content.rs18
-rw-r--r--src/model/eval.rs19
-rw-r--r--src/model/ops.rs1
-rw-r--r--src/model/realize.rs8
-rw-r--r--src/model/styles.rs14
-rw-r--r--src/model/value.rs8
7 files changed, 54 insertions, 17 deletions
diff --git a/src/model/cast.rs b/src/model/cast.rs
index d5b4893d..0e7739ac 100644
--- a/src/model/cast.rs
+++ b/src/model/cast.rs
@@ -183,8 +183,9 @@ dynamic! {
dynamic! {
Selector: "selector",
- Value::Func(func) => Self::Node(func.node()?, None),
Value::Str(text) => Self::text(&text),
+ Value::Label(label) => Self::Label(label),
+ Value::Func(func) => Self::Node(func.node()?, None),
@regex: Regex => Self::Regex(regex.clone()),
}
diff --git a/src/model/content.rs b/src/model/content.rs
index 0faf76cb..a7161798 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -21,7 +21,7 @@ pub struct Content {
obj: Arc<dyn Bounds>,
guards: Vec<Guard>,
span: Option<Span>,
- label: Option<EcoString>,
+ label: Option<Label>,
}
impl Content {
@@ -54,7 +54,7 @@ impl Content {
}
/// Attach a label to the content.
- pub fn labelled(mut self, label: EcoString) -> Self {
+ pub fn labelled(mut self, label: Label) -> Self {
self.label = Some(label);
self
}
@@ -131,7 +131,7 @@ impl Content {
}
/// The content's label.
- pub fn label(&self) -> Option<&EcoString> {
+ pub fn label(&self) -> Option<&Label> {
self.label.as_ref()
}
@@ -139,7 +139,7 @@ impl Content {
pub fn field(&self, name: &str) -> Option<Value> {
if name == "label" {
return Some(match &self.label {
- Some(label) => Value::Str(label.clone().into()),
+ Some(label) => Value::Label(label.clone()),
None => Value::None,
});
}
@@ -335,6 +335,16 @@ impl Debug for SequenceNode {
}
}
+/// 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.
pub trait Node: 'static + Capable {
/// Pack a node into type-erased content.
diff --git a/src/model/eval.rs b/src/model/eval.rs
index 9ed6195e..6c21a666 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -8,7 +8,7 @@ use comemo::{Track, Tracked};
use unicode_segmentation::UnicodeSegmentation;
use super::{
- methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func,
+ methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func, Label,
LangItems, Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value,
};
use crate::diag::{
@@ -231,11 +231,16 @@ fn eval_markup(
let tail = eval_markup(vm, nodes)?;
seq.push(tail.styled_with_recipe(vm.world, recipe)?)
}
- ast::MarkupNode::Label(label) => {
- if let Some(node) = seq.iter_mut().rev().find(|node| node.labellable()) {
- *node = mem::take(node).labelled(label.get().clone());
+ ast::MarkupNode::Expr(expr) => match expr.eval(vm)? {
+ Value::Label(label) => {
+ if let Some(node) =
+ seq.iter_mut().rev().find(|node| node.labellable())
+ {
+ *node = mem::take(node).labelled(label);
+ }
}
- }
+ value => seq.push(value.display().spanned(expr.span())),
+ },
_ => seq.push(node.eval(vm)?),
}
@@ -274,9 +279,8 @@ impl Eval for ast::MarkupNode {
Self::List(v) => v.eval(vm)?,
Self::Enum(v) => v.eval(vm)?,
Self::Desc(v) => v.eval(vm)?,
- Self::Label(_) => unimplemented!("handled above"),
Self::Ref(v) => v.eval(vm)?,
- Self::Expr(v) => v.eval(vm)?.display(),
+ Self::Expr(_) => unimplemented!("handled above"),
}
.spanned(self.span()))
}
@@ -527,6 +531,7 @@ impl Eval for ast::Lit {
Unit::Percent => Ratio::new(v / 100.0).into(),
},
ast::LitKind::Str(v) => Value::Str(v.into()),
+ ast::LitKind::Label(v) => Value::Label(Label(v)),
})
}
}
diff --git a/src/model/ops.rs b/src/model/ops.rs
index 0110fb96..9a731d65 100644
--- a/src/model/ops.rs
+++ b/src/model/ops.rs
@@ -307,6 +307,7 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
(Fraction(a), Fraction(b)) => a == b,
(Color(a), Color(b)) => a == b,
(Str(a), Str(b)) => a == b,
+ (Label(a), Label(b)) => a == b,
(Content(a), Content(b)) => a == b,
(Array(a), Array(b)) => a == b,
(Dict(a), Dict(b)) => a == b,
diff --git a/src/model/realize.rs b/src/model/realize.rs
index d63c1aac..9d7c4aec 100644
--- a/src/model/realize.rs
+++ b/src/model/realize.rs
@@ -78,6 +78,14 @@ fn try_apply(
recipe.apply(world, target.clone().guarded(guard)).map(Some)
}
+ Some(Selector::Label(label)) => {
+ if target.label() != Some(label) {
+ return Ok(None);
+ }
+
+ recipe.apply(world, target.clone().guarded(guard)).map(Some)
+ }
+
Some(Selector::Regex(regex)) => {
let Some(text) = item!(text_str)(&target) else {
return Ok(None);
diff --git a/src/model/styles.rs b/src/model/styles.rs
index 966a57ec..f3cfb648 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -7,7 +7,7 @@ use std::sync::Arc;
use comemo::{Prehashed, Tracked};
-use super::{Args, Content, Dict, Func, NodeId, Regex, Smart, Value};
+use super::{Args, Content, Dict, Func, Label, NodeId, Regex, Smart, Value};
use crate::diag::{SourceResult, Trace, Tracepoint};
use crate::geom::{
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
@@ -354,7 +354,9 @@ pub enum Selector {
/// If there is a dictionary, only nodes with the fields from the
/// dictionary match.
Node(NodeId, Option<Dict>),
- /// Matches text through a regular expression.
+ /// Matches nodes with a specific label.
+ Label(Label),
+ /// Matches text nodes through a regular expression.
Regex(Regex),
}
@@ -368,13 +370,17 @@ impl Selector {
pub fn matches(&self, target: &Content) -> bool {
match self {
Self::Node(id, dict) => {
- *id == target.id()
+ target.id() == *id
&& dict
.iter()
.flat_map(|dict| dict.iter())
.all(|(name, value)| target.field(name).as_ref() == Some(value))
}
- Self::Regex(_) => target.id() == item!(text_id),
+ Self::Label(label) => target.label() == Some(label),
+ Self::Regex(regex) => {
+ target.id() == item!(text_id)
+ && item!(text_str)(target).map_or(false, |text| regex.is_match(text))
+ }
}
}
}
diff --git a/src/model/value.rs b/src/model/value.rs
index aec68ca1..043fde34 100644
--- a/src/model/value.rs
+++ b/src/model/value.rs
@@ -6,7 +6,7 @@ use std::sync::Arc;
use siphasher::sip128::{Hasher128, SipHasher};
-use super::{format_str, ops, Args, Array, Cast, Content, Dict, Func, Str};
+use super::{format_str, ops, Args, Array, Cast, Content, Dict, Func, Label, Str};
use crate::diag::StrResult;
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
use crate::util::{format_eco, EcoString};
@@ -38,6 +38,8 @@ pub enum Value {
Color(Color),
/// A string: `"string"`.
Str(Str),
+ /// A label: `<intro>`.
+ Label(Label),
/// A content value: `[*Hi* there]`.
Content(Content),
/// An array of values: `(1, "hi", 12cm)`.
@@ -76,6 +78,7 @@ impl Value {
Self::Fraction(_) => Fr::TYPE_NAME,
Self::Color(_) => Color::TYPE_NAME,
Self::Str(_) => Str::TYPE_NAME,
+ Self::Label(_) => Label::TYPE_NAME,
Self::Content(_) => Content::TYPE_NAME,
Self::Array(_) => Array::TYPE_NAME,
Self::Dict(_) => Dict::TYPE_NAME,
@@ -130,6 +133,7 @@ impl Debug for Value {
Self::Fraction(v) => Debug::fmt(v, f),
Self::Color(v) => Debug::fmt(v, f),
Self::Str(v) => Debug::fmt(v, f),
+ Self::Label(v) => Debug::fmt(v, f),
Self::Content(_) => f.pad("[...]"),
Self::Array(v) => Debug::fmt(v, f),
Self::Dict(v) => Debug::fmt(v, f),
@@ -168,6 +172,7 @@ impl Hash for Value {
Self::Fraction(v) => v.hash(state),
Self::Color(v) => v.hash(state),
Self::Str(v) => v.hash(state),
+ Self::Label(v) => v.hash(state),
Self::Content(v) => v.hash(state),
Self::Array(v) => v.hash(state),
Self::Dict(v) => v.hash(state),
@@ -373,6 +378,7 @@ primitive! { Rel<Length>: "relative length",
primitive! { Fr: "fraction", Fraction }
primitive! { Color: "color", Color }
primitive! { Str: "string", Str }
+primitive! { Label: "label", Label }
primitive! { Content: "content",
Content,
None => Content::empty(),