summaryrefslogtreecommitdiff
path: root/src
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
parent3cdd8bfa40fe5fdf0c676af905c3c2c1f614ef24 (diff)
Dynamic labels
Diffstat (limited to 'src')
-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
-rw-r--r--src/syntax/ast.rs23
-rw-r--r--src/syntax/kind.rs2
-rw-r--r--src/syntax/parsing.rs5
-rw-r--r--src/syntax/tokens.rs18
11 files changed, 70 insertions, 49 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(),
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 5b61af77..81ddd596 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -95,8 +95,6 @@ pub enum MarkupNode {
Raw(Raw),
/// A hyperlink: `https://typst.org`.
Link(Link),
- /// A label: `<label>`.
- Label(Label),
/// A reference: `@target`.
Ref(Ref),
/// A section heading: `= Introduction`.
@@ -126,7 +124,6 @@ impl AstNode for MarkupNode {
SyntaxKind::Emph => node.cast().map(Self::Emph),
SyntaxKind::Raw(_) => node.cast().map(Self::Raw),
SyntaxKind::Link(_) => node.cast().map(Self::Link),
- SyntaxKind::Label(_) => node.cast().map(Self::Label),
SyntaxKind::Ref(_) => node.cast().map(Self::Ref),
SyntaxKind::Heading => node.cast().map(Self::Heading),
SyntaxKind::ListItem => node.cast().map(Self::List),
@@ -149,7 +146,6 @@ impl AstNode for MarkupNode {
Self::Emph(v) => v.as_untyped(),
Self::Raw(v) => v.as_untyped(),
Self::Link(v) => v.as_untyped(),
- Self::Label(v) => v.as_untyped(),
Self::Ref(v) => v.as_untyped(),
Self::Heading(v) => v.as_untyped(),
Self::List(v) => v.as_untyped(),
@@ -314,21 +310,6 @@ impl Link {
}
node! {
- /// A label: `<label>`.
- Label
-}
-
-impl Label {
- /// Get the label.
- pub fn get(&self) -> &EcoString {
- match self.0.kind() {
- SyntaxKind::Label(v) => v,
- _ => panic!("label is of wrong kind"),
- }
- }
-}
-
-node! {
/// A reference: `@target`.
Ref
}
@@ -704,6 +685,7 @@ node! {
| SyntaxKind::Float(_)
| SyntaxKind::Numeric(_, _)
| SyntaxKind::Str(_)
+ | SyntaxKind::Label(_)
}
impl Lit {
@@ -717,6 +699,7 @@ impl Lit {
SyntaxKind::Float(v) => LitKind::Float(v),
SyntaxKind::Numeric(v, unit) => LitKind::Numeric(v, unit),
SyntaxKind::Str(ref v) => LitKind::Str(v.clone()),
+ SyntaxKind::Label(ref v) => LitKind::Label(v.clone()),
_ => panic!("literal is of wrong kind"),
}
}
@@ -739,6 +722,8 @@ pub enum LitKind {
Numeric(f64, Unit),
/// A quoted string: `"..."`.
Str(EcoString),
+ /// A label: `<intro>`.
+ Label(EcoString),
}
node! {
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index 58e2b915..b7ee6a79 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -153,7 +153,7 @@ pub enum SyntaxKind {
Raw(Arc<RawFields>),
/// A hyperlink: `https://typst.org`.
Link(EcoString),
- /// A label: `<label>`.
+ /// A label: `<intro>`.
Label(EcoString),
/// A reference: `@target`.
Ref(EcoString),
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 9529d1d1..1678eb01 100644
--- a/src/syntax/parsing.rs
+++ b/src/syntax/parsing.rs
@@ -232,7 +232,6 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
| SyntaxKind::Shorthand(_)
| SyntaxKind::Link(_)
| SyntaxKind::Raw(_)
- | SyntaxKind::Label(_)
| SyntaxKind::Ref(_) => p.eat(),
// Math.
@@ -257,6 +256,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
// Hashtag + keyword / identifier.
SyntaxKind::Ident(_)
+ | SyntaxKind::Label(_)
| SyntaxKind::Let
| SyntaxKind::Set
| SyntaxKind::Show
@@ -617,7 +617,8 @@ fn literal(p: &mut Parser) -> bool {
| SyntaxKind::Float(_)
| SyntaxKind::Bool(_)
| SyntaxKind::Numeric(_, _)
- | SyntaxKind::Str(_),
+ | SyntaxKind::Str(_)
+ | SyntaxKind::Label(_),
) => {
p.eat();
true
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index 4b86c89b..e0ef2fa1 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -207,8 +207,8 @@ impl<'s> Tokens<'s> {
}
'`' => self.raw(),
c if c.is_ascii_digit() => self.numbering(start),
- '<' => self.label(),
- '@' => self.reference(start),
+ '<' if self.s.at(is_id_continue) => self.label(),
+ '@' if self.s.at(is_id_continue) => self.reference(),
// Escape sequences.
'\\' => self.backslash(),
@@ -417,13 +417,8 @@ impl<'s> Tokens<'s> {
}
}
- fn reference(&mut self, start: usize) -> SyntaxKind {
- let label = self.s.eat_while(is_id_continue);
- if !label.is_empty() {
- SyntaxKind::Ref(label.into())
- } else {
- self.text(start)
- }
+ fn reference(&mut self) -> SyntaxKind {
+ SyntaxKind::Ref(self.s.eat_while(is_id_continue).into())
}
fn math(&mut self, start: usize, c: char) -> SyntaxKind {
@@ -475,6 +470,9 @@ impl<'s> Tokens<'s> {
'(' => SyntaxKind::LeftParen,
')' => SyntaxKind::RightParen,
+ // Labels.
+ '<' if self.s.at(is_id_continue) => self.label(),
+
// Two-char operators.
'=' if self.s.eat_if('=') => SyntaxKind::EqEq,
'!' if self.s.eat_if('=') => SyntaxKind::ExclEq,
@@ -954,7 +952,7 @@ mod tests {
t!(Code: "=" => Eq);
t!(Code: "==" => EqEq);
t!(Code: "!=" => ExclEq);
- t!(Code: "<" => Lt);
+ t!(Code[" /"]: "<" => Lt);
t!(Code: "<=" => LtEq);
t!(Code: ">" => Gt);
t!(Code: ">=" => GtEq);