diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-17 11:32:15 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-17 11:45:57 +0100 |
| commit | 312197b276748e1a17258ad21837850f582a467c (patch) | |
| tree | 3fd0c078a2673a98b74bc12b4d654a4c143b4e1f /src | |
| parent | e8435df5ec718e8ecc8a2ad48e4eb3ddd1f92a72 (diff) | |
Counters
Diffstat (limited to 'src')
| -rw-r--r-- | src/doc.rs | 66 | ||||
| -rw-r--r-- | src/eval/library.rs | 11 | ||||
| -rw-r--r-- | src/eval/methods.rs | 15 | ||||
| -rw-r--r-- | src/export/pdf/page.rs | 6 | ||||
| -rw-r--r-- | src/geom/align.rs | 20 | ||||
| -rw-r--r-- | src/ide/analyze.rs | 2 | ||||
| -rw-r--r-- | src/ide/jump.rs | 7 | ||||
| -rw-r--r-- | src/model/content.rs | 48 | ||||
| -rw-r--r-- | src/model/realize.rs | 52 | ||||
| -rw-r--r-- | src/model/styles.rs | 16 | ||||
| -rw-r--r-- | src/model/typeset.rs | 168 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 3 | ||||
| -rw-r--r-- | src/util/mod.rs | 14 |
13 files changed, 243 insertions, 185 deletions
@@ -14,7 +14,7 @@ use crate::geom::{ Numeric, Paint, Point, Rel, RgbaColor, Shape, Sides, Size, Stroke, Transform, }; use crate::image::Image; -use crate::model::{node, Content, Fold, Introspector, StableId, StyleChain}; +use crate::model::{Content, Introspector, MetaNode, StableId, StyleChain}; use crate::syntax::Span; /// A finished document with metadata and page frames. @@ -271,16 +271,15 @@ impl Frame { } /// Attach the metadata from this style chain to the frame. - pub fn meta(&mut self, styles: StyleChain) { - if self.is_empty() { - return; - } - for meta in MetaNode::data_in(styles) { - if matches!(meta, Meta::Hide) { - self.clear(); - break; + pub fn meta(&mut self, styles: StyleChain, force: bool) { + if force || !self.is_empty() { + for meta in MetaNode::data_in(styles) { + if matches!(meta, Meta::Hide) { + self.clear(); + break; + } + self.prepend(Point::zero(), Element::Meta(meta, self.size)); } - self.prepend(Point::zero(), Element::Meta(meta, self.size)); } } @@ -607,6 +606,16 @@ pub enum Meta { Node(Content), } +cast_from_value! { + Meta: "meta", +} + +impl PartialEq for Meta { + fn eq(&self, other: &Self) -> bool { + crate::util::hash128(self) == crate::util::hash128(other) + } +} + /// A possibly unresolved link. #[derive(Debug, Clone, Hash)] pub enum Link { @@ -623,45 +632,14 @@ impl Link { pub fn resolve<'a>( &self, introspector: impl FnOnce() -> &'a Introspector, - ) -> Option<Destination> { + ) -> Destination { match self { - Self::Dest(dest) => Some(dest.clone()), - Self::Node(id) => introspector().location(*id).map(Destination::Internal), + Self::Dest(dest) => dest.clone(), + Self::Node(id) => Destination::Internal(introspector().location(*id)), } } } -/// Host for metadata. -/// -/// Display: Meta -/// Category: special -#[node] -pub struct MetaNode { - /// Metadata that should be attached to all elements affected by this style - /// property. - #[fold] - pub data: Vec<Meta>, -} - -impl Fold for Vec<Meta> { - type Output = Self; - - fn fold(mut self, outer: Self::Output) -> Self::Output { - self.extend(outer); - self - } -} - -cast_from_value! { - Meta: "meta", -} - -impl PartialEq for Meta { - fn eq(&self, other: &Self) -> bool { - crate::util::hash128(self) == crate::util::hash128(other) - } -} - /// A link destination. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Destination { diff --git a/src/eval/library.rs b/src/eval/library.rs index 1240d9bb..45c23d17 100644 --- a/src/eval/library.rs +++ b/src/eval/library.rs @@ -6,11 +6,12 @@ use comemo::Tracked; use ecow::EcoString; use once_cell::sync::OnceCell; -use super::Module; +use super::{Args, Dynamic, Module, Value}; use crate::diag::SourceResult; use crate::doc::Document; use crate::geom::{Abs, Dir}; use crate::model::{Content, Introspector, Label, NodeId, StyleChain, StyleMap, Vt}; +use crate::syntax::Span; use crate::util::hash128; use crate::World; @@ -89,6 +90,14 @@ pub struct LangItems { pub math_accent: fn(base: Content, accent: char) -> Content, /// A fraction in a formula: `x/2`. pub math_frac: fn(num: Content, denom: Content) -> Content, + /// Dispatch a method on a counter. This is hacky and should be superseded + /// by more dynamic method dispatch. + pub counter_method: fn( + dynamic: &Dynamic, + method: &str, + args: Args, + span: Span, + ) -> SourceResult<Value>, } impl Debug for LangItems { diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 197a2f65..a449ac16 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -134,6 +134,14 @@ pub fn call( _ => return missing(), }, + Value::Dyn(dynamic) => { + if dynamic.type_name() == "counter" { + return (vm.items.counter_method)(&dynamic, method, args, span); + } + + return missing(); + } + _ => return missing(), }; @@ -281,6 +289,13 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { ], "function" => &[("where", true), ("with", true)], "arguments" => &[("named", false), ("pos", false)], + "counter" => &[ + ("get", true), + ("final", true), + ("both", true), + ("step", true), + ("update", true), + ], _ => &[], } } diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index df7b517f..5347d831 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -114,11 +114,7 @@ fn write_page(ctx: &mut PdfContext, page: Page) { let mut annotation = annotations.push(); annotation.subtype(AnnotationType::Link).rect(rect); annotation.border(0.0, 0.0, 0.0, None); - - let dest = link.resolve(|| &ctx.introspector); - let Some(dest) = dest else { continue }; - - match dest { + match link.resolve(|| &ctx.introspector) { Destination::Url(uri) => { annotation .action() diff --git a/src/geom/align.rs b/src/geom/align.rs index b14e6775..239a6e70 100644 --- a/src/geom/align.rs +++ b/src/geom/align.rs @@ -143,6 +143,26 @@ cast_to_value! { } } +impl From<Axes<GenAlign>> for Axes<Option<GenAlign>> { + fn from(axes: Axes<GenAlign>) -> Self { + axes.map(Some) + } +} + +impl From<Axes<Align>> for Axes<Option<GenAlign>> { + fn from(axes: Axes<Align>) -> Self { + axes.map(GenAlign::Specific).into() + } +} + +impl From<Align> for Axes<Option<GenAlign>> { + fn from(align: Align) -> Self { + let mut axes = Axes::splat(None); + axes.set(align.axis(), Some(align.into())); + axes + } +} + impl Resolve for GenAlign { type Output = Align; diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index ed868e53..ccb89a9c 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -78,7 +78,7 @@ pub fn analyze_labels( let items = &world.library().items; // Labels in the document. - for node in introspector.nodes() { + for node in introspector.all() { let Some(label) = node.label() else { continue }; let details = node .field("caption") diff --git a/src/ide/jump.rs b/src/ide/jump.rs index 0aa97b56..17e318a7 100644 --- a/src/ide/jump.rs +++ b/src/ide/jump.rs @@ -36,12 +36,9 @@ pub fn jump_from_click( for (pos, element) in frame.elements() { if let Element::Meta(Meta::Link(link), size) = element { if is_in_rect(*pos, *size, click) { - let dest = link.resolve(|| { + return Some(Jump::Dest(link.resolve(|| { introspector.get_or_insert_with(|| Introspector::new(frames)) - }); - - let Some(dest) = dest else { continue }; - return Some(Jump::Dest(dest)); + }))); } } } diff --git a/src/model/content.rs b/src/model/content.rs index 58b80487..11ad635f 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -8,8 +8,12 @@ use comemo::Tracked; use ecow::{eco_format, EcoString, EcoVec}; use once_cell::sync::Lazy; -use super::{node, Guard, Locatable, Recipe, StableId, Style, StyleMap, Synthesize}; +use super::{ + node, Behave, Behaviour, Fold, Guard, Locatable, Recipe, StableId, Style, StyleMap, + Synthesize, +}; use crate::diag::{SourceResult, StrResult}; +use crate::doc::Meta; use crate::eval::{ cast_from_value, cast_to_value, Args, Cast, Func, FuncInfo, Str, Value, Vm, }; @@ -35,9 +39,15 @@ enum Modifier { } impl Content { + /// Create a content of the given node kind. pub fn new<T: Node>() -> Self { + Self::new_of(T::id()) + } + + /// Create a content of the given node kind. + pub fn new_of(id: NodeId) -> Self { Self { - id: T::id(), + id, span: Span::detached(), fields: EcoVec::new(), modifiers: EcoVec::new(), @@ -133,11 +143,10 @@ impl Content { .map(|(_, value)| value) } - /// Access a field on the content as a specified type. - #[track_caller] + /// Try to access a field on the content as a specified type. pub fn cast_field<T: Cast>(&self, name: &str) -> Option<T> { match self.field(name) { - Some(value) => Some(value.clone().cast().unwrap()), + Some(value) => value.clone().cast().ok(), None => None, } } @@ -145,7 +154,7 @@ impl Content { /// Expect a field on the content to exist as a specified type. #[track_caller] pub fn expect_field<T: Cast>(&self, name: &str) -> T { - self.cast_field(name).unwrap() + self.field(name).unwrap().clone().cast().unwrap() } /// List all fields on the content. @@ -500,6 +509,33 @@ cast_from_value! { StyleMap: "style map", } +/// Host for metadata. +/// +/// Display: Meta +/// Category: special +#[node(Behave)] +pub struct MetaNode { + /// Metadata that should be attached to all elements affected by this style + /// property. + #[fold] + pub data: Vec<Meta>, +} + +impl Behave for MetaNode { + fn behaviour(&self) -> Behaviour { + Behaviour::Ignorant + } +} + +impl Fold for Vec<Meta> { + type Output = Self; + + fn fold(mut self, outer: Self::Output) -> Self::Output { + self.extend(outer); + self + } +} + /// The missing key access error message. #[cold] #[track_caller] diff --git a/src/model/realize.rs b/src/model/realize.rs index 7a171cfc..70c75644 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -1,6 +1,7 @@ -use super::{Content, NodeId, Recipe, Selector, StyleChain, Vt}; +use super::{Content, MetaNode, Node, NodeId, Recipe, Selector, StyleChain, Vt}; use crate::diag::SourceResult; -use crate::doc::{Meta, MetaNode}; +use crate::doc::Meta; +use crate::util::hash128; /// Whether the target is affected by show rules in the given style chain. pub fn applicable(target: &Content, styles: StyleChain) -> bool { @@ -36,7 +37,7 @@ pub fn realize( if target.needs_preparation() { let mut node = target.clone(); if target.can::<dyn Locatable>() || target.label().is_some() { - let id = vt.identify(target); + let id = vt.provider.identify(hash128(target)); node.set_stable_id(id); } @@ -47,8 +48,12 @@ pub fn realize( node.mark_prepared(); if node.stable_id().is_some() { + let span = node.span(); let meta = Meta::Node(node.clone()); - return Ok(Some(node.styled(MetaNode::set_data(vec![meta])))); + return Ok(Some( + (node + MetaNode::new().pack().spanned(span)) + .styled(MetaNode::set_data(vec![meta])), + )); } return Ok(Some(node)); @@ -103,7 +108,7 @@ fn try_apply( return Ok(None); } - recipe.apply(vt.world(), target.clone().guarded(guard)).map(Some) + recipe.apply(vt.world, target.clone().guarded(guard)).map(Some) } Some(Selector::Label(label)) => { @@ -111,7 +116,7 @@ fn try_apply( return Ok(None); } - recipe.apply(vt.world(), target.clone().guarded(guard)).map(Some) + recipe.apply(vt.world, target.clone().guarded(guard)).map(Some) } Some(Selector::Regex(regex)) => { @@ -135,7 +140,7 @@ fn try_apply( } let piece = make(m.as_str().into()).guarded(guard); - let transformed = recipe.apply(vt.world(), piece)?; + let transformed = recipe.apply(vt.world, piece)?; result.push(transformed); cursor = m.end(); } @@ -151,6 +156,9 @@ fn try_apply( Ok(Some(Content::sequence(result))) } + // Not supported here. + Some(Selector::Any(_)) => Ok(None), + None => Ok(None), } } @@ -178,6 +186,36 @@ pub trait Finalize { fn finalize(&self, realized: Content, styles: StyleChain) -> Content; } +/// How a node interacts with other nodes. +pub trait Behave { + /// The node's interaction behaviour. + fn behaviour(&self) -> Behaviour; + + /// Whether this weak node is larger than a previous one and thus picked as + /// the maximum when the levels are the same. + #[allow(unused_variables)] + fn larger(&self, prev: &Content) -> bool { + false + } +} + +/// How a node interacts with other nodes in a stream. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Behaviour { + /// A weak node which only survives when a supportive node is before and + /// after it. Furthermore, per consecutive run of weak nodes, only one + /// survives: The one with the lowest weakness level (or the larger one if + /// there is a tie). + Weak(usize), + /// A node that enables adjacent weak nodes to exist. The default. + Supportive, + /// A node that destroys adjacent weak nodes. + Destructive, + /// A node that does not interact at all with other nodes, having the + /// same effect as if it didn't exist. + Ignorant, +} + /// Guards content against being affected by the same show rule multiple times. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Guard { diff --git a/src/model/styles.rs b/src/model/styles.rs index 9a562c75..359f1461 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::iter; use comemo::Tracked; -use ecow::{eco_format, EcoString}; +use ecow::{eco_format, EcoString, EcoVec}; use super::{Content, Label, Node, NodeId}; use crate::diag::{SourceResult, Trace, Tracepoint}; @@ -31,8 +31,8 @@ impl StyleMap { /// If the property needs folding and the value is already contained in the /// style map, `self` contributes the outer values and `value` is the inner /// one. - pub fn set(&mut self, property: Property) { - self.0.push(Style::Property(property)); + pub fn set(&mut self, style: impl Into<Style>) { + self.0.push(style.into()); } /// Remove the style that was last set. @@ -243,6 +243,8 @@ pub enum Selector { Label(Label), /// Matches text nodes through a regular expression. Regex(Regex), + /// Matches if any of the subselectors match. + Any(EcoVec<Self>), } impl Selector { @@ -271,6 +273,7 @@ impl Selector { target.id() == item!(text_id) && item!(text_str)(target).map_or(false, |text| regex.is_match(&text)) } + Self::Any(selectors) => selectors.iter().any(|sel| sel.matches(target)), } } } @@ -288,6 +291,12 @@ impl Debug for Selector { } Self::Label(label) => label.fmt(f), Self::Regex(regex) => regex.fmt(f), + Self::Any(selectors) => { + f.write_str("any")?; + let pieces: Vec<_> = + selectors.iter().map(|sel| eco_format!("{sel:?}")).collect(); + f.write_str(&pretty_array_like(&pieces, false)) + } } } } @@ -659,6 +668,7 @@ impl<T: Debug> Debug for StyleVec<T> { } /// Assists in the construction of a [`StyleVec`]. +#[derive(Debug)] pub struct StyleVecBuilder<'a, T> { items: Vec<T>, chains: Vec<(StyleChain<'a>, usize)>, diff --git a/src/model/typeset.rs b/src/model/typeset.rs index f68d337d..4c8be135 100644 --- a/src/model/typeset.rs +++ b/src/model/typeset.rs @@ -1,15 +1,13 @@ -use std::cell::RefCell; -use std::collections::HashMap; use std::hash::Hash; use std::num::NonZeroUsize; -use comemo::{Track, Tracked, TrackedMut}; +use comemo::{Constraint, Track, Tracked, TrackedMut}; -use super::{Content, Node, Selector, StyleChain}; +use super::{Content, Selector, StyleChain}; use crate::diag::SourceResult; use crate::doc::{Document, Element, Frame, Location, Meta}; -use crate::geom::Transform; -use crate::util::hash128; +use crate::geom::{Point, Transform}; +use crate::util::NonZeroExt; use crate::World; /// Typeset content into a fully layouted document. @@ -25,17 +23,21 @@ pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Doc // Relayout until all introspections stabilize. // If that doesn't happen within five attempts, we give up. loop { + let constraint = Constraint::new(); let mut provider = StabilityProvider::new(); let mut vt = Vt { world, provider: provider.track_mut(), - introspector: introspector.track(), + introspector: introspector.track_with(&constraint), }; document = (library.items.layout)(&mut vt, content, styles)?; iter += 1; - if iter >= 5 || introspector.update(&document.pages) { + introspector = Introspector::new(&document.pages); + introspector.init = true; + + if iter >= 5 || introspector.valid(&constraint) { break; } } @@ -56,137 +58,86 @@ pub struct Vt<'a> { pub introspector: Tracked<'a, Introspector>, } -impl<'a> Vt<'a> { - /// Access the underlying world. - pub fn world(&self) -> Tracked<'a, dyn World> { - self.world +/// Provides stable identities to nodes. +#[derive(Clone)] +pub struct StabilityProvider { + hashes: Vec<u128>, + checkpoints: Vec<usize>, +} + +impl StabilityProvider { + /// Create a new stability provider. + pub fn new() -> Self { + Self { hashes: vec![], checkpoints: vec![] } } +} +#[comemo::track] +impl StabilityProvider { /// Produce a stable identifier for this call site. - /// - /// The key should be something that identifies the call site, but is not - /// necessarily unique. The stable marker incorporates the key's hash plus - /// additional disambiguation from other call sites with the same key. - /// - /// The returned id can be attached to content as metadata is the then - /// locatable through [`locate`](Self::locate). - pub fn identify<T: Hash>(&mut self, key: &T) -> StableId { - self.provider.identify(hash128(key)) + pub fn identify(&mut self, hash: u128) -> StableId { + let count = self.hashes.iter().filter(|&&prev| prev == hash).count(); + self.hashes.push(hash); + StableId(hash, count, 0) } - /// Whether things are locatable already. - pub fn locatable(&self) -> bool { - self.introspector.init() + /// Create a checkpoint of the state that can be restored. + pub fn save(&mut self) { + self.checkpoints.push(self.hashes.len()); } - /// Locate all metadata matches for the given node. - pub fn query_node<T: Node>(&self) -> impl Iterator<Item = &T> { - self.introspector - .query(Selector::node::<T>()) - .into_iter() - .map(|content| content.to::<T>().unwrap()) + /// Restore the last checkpoint. + pub fn restore(&mut self) { + if let Some(checkpoint) = self.checkpoints.pop() { + self.hashes.truncate(checkpoint); + } } } /// Stably identifies a call site across multiple layout passes. /// -/// This struct is created by [`Vt::identify`]. +/// This struct is created by [`StabilityProvider::identify`]. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct StableId(u128, u64, u64); +pub struct StableId(u128, usize, usize); impl StableId { /// Produce a variant of this id. - pub fn variant(self, n: u64) -> Self { + pub fn variant(self, n: usize) -> Self { Self(self.0, self.1, n) } } -/// Provides stable identities to nodes. -#[derive(Clone)] -pub struct StabilityProvider(HashMap<u128, u64>); - -impl StabilityProvider { - /// Create a new stability provider. - fn new() -> Self { - Self(HashMap::new()) - } -} - -#[comemo::track] -impl StabilityProvider { - /// Produce a stable identifier for this call site. - fn identify(&mut self, hash: u128) -> StableId { - let slot = self.0.entry(hash).or_default(); - let id = StableId(hash, *slot, 0); - *slot += 1; - id - } -} - /// Provides access to information about the document. pub struct Introspector { init: bool, nodes: Vec<(Content, Location)>, - queries: RefCell<Vec<(Selector, u128)>>, } impl Introspector { /// Create a new introspector. pub fn new(frames: &[Frame]) -> Self { - let mut introspector = Self { - init: false, - nodes: vec![], - queries: RefCell::new(vec![]), - }; - introspector.extract_from_frames(frames); - introspector - } - - /// Update the information given new frames and return whether we can stop - /// layouting. - pub fn update(&mut self, frames: &[Frame]) -> bool { - self.nodes.clear(); - self.extract_from_frames(frames); - - let was_init = std::mem::replace(&mut self.init, true); - let queries = std::mem::take(&mut self.queries).into_inner(); - - for (selector, hash) in &queries { - let nodes = self.query_impl(selector); - if hash128(&nodes) != *hash { - return false; - } - } - - if !was_init && !queries.is_empty() { - return false; + let mut introspector = Self { init: false, nodes: vec![] }; + for (i, frame) in frames.iter().enumerate() { + let page = NonZeroUsize::new(1 + i).unwrap(); + introspector.extract(frame, page, Transform::identity()); } - - true + introspector } /// Iterate over all nodes. - pub fn nodes(&self) -> impl Iterator<Item = &Content> { + pub fn all(&self) -> impl Iterator<Item = &Content> { self.nodes.iter().map(|(node, _)| node) } - /// Extract metadata from frames. - fn extract_from_frames(&mut self, frames: &[Frame]) { - for (i, frame) in frames.iter().enumerate() { - let page = NonZeroUsize::new(1 + i).unwrap(); - self.extract_from_frame(frame, page, Transform::identity()); - } - } - /// Extract metadata from a frame. - fn extract_from_frame(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) { + fn extract(&mut self, frame: &Frame, page: NonZeroUsize, ts: Transform) { for (pos, element) in frame.elements() { match element { Element::Group(group) => { let ts = ts .pre_concat(Transform::translate(pos.x, pos.y)) .pre_concat(group.transform); - self.extract_from_frame(&group.frame, page, ts); + self.extract(&group.frame, page, ts); } Element::Meta(Meta::Node(content), _) if !self @@ -212,27 +163,20 @@ impl Introspector { /// Query for all metadata matches for the given selector. pub fn query(&self, selector: Selector) -> Vec<&Content> { - let nodes = self.query_impl(&selector); - let mut queries = self.queries.borrow_mut(); - if !queries.iter().any(|(prev, _)| prev == &selector) { - queries.push((selector, hash128(&nodes))); - } - nodes + self.all().filter(|node| selector.matches(node)).collect() } /// Find the page number for the given stable id. - pub fn page(&self, id: StableId) -> Option<NonZeroUsize> { - Some(self.location(id)?.page) + pub fn page(&self, id: StableId) -> NonZeroUsize { + self.location(id).page } /// Find the location for the given stable id. - pub fn location(&self, id: StableId) -> Option<Location> { - Some(self.nodes.iter().find(|(node, _)| node.stable_id() == Some(id))?.1) - } -} - -impl Introspector { - fn query_impl(&self, selector: &Selector) -> Vec<&Content> { - self.nodes().filter(|node| selector.matches(node)).collect() + pub fn location(&self, id: StableId) -> Location { + self.nodes + .iter() + .find(|(node, _)| node.stable_id() == Some(id)) + .map(|(_, loc)| *loc) + .unwrap_or(Location { page: NonZeroUsize::ONE, pos: Point::zero() }) } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 8e48358d..760a6499 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -12,6 +12,7 @@ use super::{ is_id_continue, is_id_start, is_newline, split_newlines, Span, SyntaxKind, SyntaxNode, }; use crate::geom::{AbsUnit, AngleUnit}; +use crate::util::NonZeroExt; /// A typed AST node. pub trait AstNode: Sized { @@ -641,7 +642,7 @@ impl Heading { .children() .find(|node| node.kind() == SyntaxKind::HeadingMarker) .and_then(|node| node.len().try_into().ok()) - .unwrap_or(NonZeroUsize::new(1).unwrap()) + .unwrap_or(NonZeroUsize::ONE) } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 1eb19113..596282de 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -8,6 +8,7 @@ pub use buffer::Buffer; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; +use std::num::NonZeroUsize; use std::path::{Component, Path, PathBuf}; use std::sync::Arc; @@ -39,6 +40,19 @@ pub fn hash128<T: Hash + ?Sized>(value: &T) -> u128 { state.finish128().as_u128() } +/// Extra methods for [`NonZeroUsize`]. +pub trait NonZeroExt { + /// The number `1`. + const ONE: Self; +} + +impl NonZeroExt for NonZeroUsize { + const ONE: Self = match Self::new(1) { + Some(v) => v, + None => unreachable!(), + }; +} + /// Extra methods for [`str`]. pub trait StrExt { /// The number of code units this string would use if it was encoded in |
