diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-26 13:39:18 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-26 13:41:27 +0100 |
| commit | 7af46fc025ee08eb78ae7f6898300083c886bf6f (patch) | |
| tree | 5837d972961844650bc9668d8516d7b5239a8d18 /src | |
| parent | 3cdd8bfa40fe5fdf0c676af905c3c2c1f614ef24 (diff) | |
Dynamic labels
Diffstat (limited to 'src')
| -rw-r--r-- | src/model/cast.rs | 3 | ||||
| -rw-r--r-- | src/model/content.rs | 18 | ||||
| -rw-r--r-- | src/model/eval.rs | 19 | ||||
| -rw-r--r-- | src/model/ops.rs | 1 | ||||
| -rw-r--r-- | src/model/realize.rs | 8 | ||||
| -rw-r--r-- | src/model/styles.rs | 14 | ||||
| -rw-r--r-- | src/model/value.rs | 8 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 23 | ||||
| -rw-r--r-- | src/syntax/kind.rs | 2 | ||||
| -rw-r--r-- | src/syntax/parsing.rs | 5 | ||||
| -rw-r--r-- | src/syntax/tokens.rs | 18 |
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); |
